I somehow managed to write a script for WASD camera movement along the terrain surface.
Here is the blend if anyone interested: align.blend (2.74 MB)
The setup uses 4 objects:
-sonic_hitbox
-the camera
-dir, which takes the direction of the camera.
-and cam, which aligns to the normal of the surface.
It works like this:
depending of the keys you press, sonic creates 8 possible direction vectors, which is called vec.
Then, vec is multiplied by the matrix of dir, which allows for camera relative movement.
and finally, it is multiplied by the matrix of cam, which allows to move along the surface.
It works pretty well, but the problem comes when sonic is standing on an edge of the surface, then he starts flickering (like sonic 06 :spin:), and, for some reason, cam rotates weirdly to the world x axis, which screws up the controls very badly.
Also, it only happens in an outer curve, in the inner ones, (like the loop-de-loop), I haven’t gotten any problems so far.
To be able to see cam rotating, you should play on wireframe mode.
If the ray cast to align to the surface does not hit anything,
you dont get a surface normal, and will need to resort to a stored
normal or world axis etc.
As noted, alignAxisToVect has some artifacts. All that you need do is build an orientation matrix from the appropriate vectors - maintaining the current direction, and then use quaternion slerp to smoothly move between the current orientation and target.
from mathutils import Matrix, Quaternion, Vector
def _align_to_surface(obj, distance=2, slerp_factor=0.5):
orientation = obj.worldOrientation
z_direction = orientation * Vector((0, 0, 1))
source = obj.worldPosition
target = source - z_direction
hit_obj, hit_pos, hit_nor = obj.rayCast(target, source, distance)
if hit_obj is None:
return
x_direction = orientation * Vector((1, 0, 0))
y_direction = x_direction.cross(hit_nor)
new_orientation = Matrix.Identity(3)
new_orientation.col[0] = x_direction
new_orientation.col[1] = y_direction
new_orientation.col[2] = hit_nor
new_quaternion = new_orientation.to_quaternion()
current_quaternion = orientation.to_quaternion()
slerped_orientation = current_quaternion.slerp(new_quaternion, slerp_factor)
obj.worldOrientation = slerped_orientation
def any_positive(cont):
for sens in cont.sensors:
if sens.positive:
return True
return False
def align_to_surface(cont):
if not any_positive(cont):
return
own = cont.owner
_align_to_surface(own)
to orientate the object i just had to calculate the angle between ray.hitNormal and the world Z axis, and then rotate to that angle by the cross product of those two.
ang = Vector(ray.hitNormal).angle(Vector([0,0,1]), 0)
mat = Matrix.Rotation(ang, 3, Vector([0,0,1]).cross(ray.hitNormal))
And for the flickering I had to snap the object back to the ray.hitPosition.
if ray.positive:
own.alignAxisToVect(Vector,2,1)
ali.worldOrientation = mat
own.worldPosition = Vector(ray.hitPosition) + own.worldOrientation*Vector([0,0,(distance from origin to bottom)])