Accounting for ground surface normal in a hoverbike

Hi, I’ve been writing a hoverbike system for the past couple of days and it’s been going well, but I’ve hit a problem: I can’t figure out how to make it account for the ground’s surface normal. What I have so far just makes it suddenly turn 180 seemingly at random:

local cameraLevelledVector = Vector3.new(camera.CFrame.LookVector.X,0,camera.CFrame.LookVector.Z)
local normal = sharedFunctions:GetSurfaceNormal()
local normalisedCFrame = normal and CFrame.new(Vector3.new(),normal)*CFrame.Angles(0,math.rad(-90),math.rad(90)) or CFrame.new()
bike.Chassis.BodyGyro.CFrame = normalisedCFrame*CFrame.new(Vector3.new(),cameraLevelledVector)*CFrame.Angles(0,math.rad(90),0)

Yes, I know it’s ugly. Don’t worry about GetSurfaceNormal() - it basically returns a normal you’d get from a raycast, but it’s the mean average of 4 to smooth out the result. I already tried it with a single raycast to rule it out as a problem and the test yielded the same unpredictable results.

The extra set of angles on the end (0,-90,90) is to account for the MeshPart’s weird orientation.

For reference, here’s the code without my attempt at accounting for ground normal, which works fine (albeit doesn’t adapt to the terrain):

local cameraLevelledVector = Vector3.new(camera.CFrame.LookVector.X,0,camera.CFrame.LookVector.Z)
bike.Chassis.BodyGyro.CFrame = CFrame.new(Vector3.new(),cameraLevelledVector)*CFrame.Angles(0,math.rad(90),0)

What am I doing wrong? Why does my bike suddenly turn about the Y axis 180 degrees? I’m for all intents and purposes new to CFrames, as historically they’ve given me nothing but grief, so anything you all could tell me would help a lot. If I’ve missed anything out, please ask.

Thanks!

This looks like the problem line, specifically:

CFrame.new(Vector3.new(),normal)

This two-argument CFrame constructor can cause very different orientations based on small changes in input, which is most likely what is causing your random 180 turns. This is because the constructor only supplies information to create the LookVector of the CFrame, so internally a RightVector and UpVector must be chosen and this can be fairly unpredictable/unstable. There’s an explanation which may help you here.

The best way to solve your problem, assuming I’ve diagnosed it correctly, is to use the full 12-argument CFrame constructor - you can find information on it here.

Leaving aside the extra orientation of your MeshPart you mentioned, you need to calculate the LookVector, RightVector, and UpVector of the CFrame you want to supply to the BodyGyro (Position doesn’t matter for the BodyGyro).

  1. Your LookVector relates to your camera LookVector (judging from your code)
  2. Your UpVector is the computed surface normal
  3. Your RightVector is the vector which is orthogonal (at a right angle) to both the LookVector and the UpVector - you can use the Cross product to calculate this.

Your code might look something like this:

local lookVector = camera.CFrame.LookVector
local upVector = sharedFunctions:GetSurfaceNormal()
local rightVector = lookVector:Cross(upVector)

local gyroCFrame = CFrame.new(
    0, 0, 0
    rightVector.x, upVector.x, -lookVector.x, -- constructor actually takes backVector (-lookVector)
    rightVector.y, upVector.y, -lookVector.y,
    rightVector.z, upVector.z, -lookVector.z
)

bike.Chassis.BodyGyro.CFrame = gyroCFrame

You will need to account for “the MeshPart’s weird orientation” by adding the * CFrame.Angles(…) after the longer constructor code.

6 Likes

I was dreading having to do that, but you made the 12-argument constructor much easier to understand. It works like a charm, thank you!

2 Likes