CFrame math assistance needed!

I am trying to make little roomba devices that can move on all surfaces.

The problem I am facing:

When a roomba meets a new surface, I want it to transition to this surface, while maintaining the rough direction it was previously traveling in. I provided an example of what I mean below.

Screen Shot 2020-05-29 at 10.09.34 PM Screen Shot 2020-05-29 at 10.09.40 PM

This seems a bit complicated to solve. Can anyone shed some light?

Well, Ray’s provides a third argument returning the surface of hit, so if you do the pos + normal it’llgive you surface for you can have the CFrame where it should face

You can use raycasting for this

It’s whats after this point that I am uncertain about. I don’t know how much the brick should be rotated to somewhat match its previous trajectory.

Screen Shot 2020-05-29 at 10.52.05 PM

@Fluffmiceter Did you ever encounter this problem in your kart game before you switched to your custom physics? I would assume so, because you used CFraming for the chassis, and you needed to be able to drive on changing surfaces like this.

As a guess you could maybe try using the normal of the surface to rotate the roomba along the x and z axes while keeping its original y rotation along its local y axis.

It’s been so long since I implemented surface normal snapping that to be honest, I don’t remember how I did it. My current system relies on adding and subtracting rotational velocity in the necessary axes in order to correct the current kart normal to the surface normal (feedback control system), and then doing some CFrame magik to calculate the new CFrame of the kart in the current frame from the last CFrame, the velocity, rotvelocity and delta time.

For your application, here’s a solution I’ve just come up with:

To calculate the new roomba CFrame, you will need the following information:
*The lookvector of the last CFrame
*The normal (upvector) of the current surface.

Now, the first thing we want to do is take the last lookvector, and then apply some transformation to make that lookvector be 90 degrees to the new normal.

Before I explain that, I’m going to introduce a function that I use religiously when handling vectors:
*Flatten(vector, axis)
To flatten means to take a vector, and make that vector completely orthogonal to the input axis. In other words, imagine taking a vector, and subtracting all components that produce any movement in the provided axis. So, if we had vec(0.5, 3, 7) and an axis of vec(0, 1, 0), the output would be vec(0.5, 0, 7). This method works for more than just the cardinal axes, which is why it is so useful.

Implementing Flatten:
local new = axis
if axis.Magnitude ~= 0 then
new = axis.Unit
end
return new:Cross(v1):Cross(new)

If you just visualize an axis and a vector in 3D space that aren’t orthogonal to each other initially, and then imagine the output of each cross product one step at a time, it should be obvious why this works. I haven’t heard of anyone else using this, and maybe there is a “proper” way to do this, but cross products should be super fast to compute and it is super simple to write.

Note for the function, I put in an extra check to handle cases where the axis is not a unit vector and may have a magnitude of 0.

Anyways, here’s how I would use Flatten to accomplish your goal:
Take the last lookvector, and flatten it along the normal of the current surface. You may have to normalize the output of flatten? not sure. Anyways, the output is your new lookvector. Use this new lookvector, the current normal (upvector) and take a cross product to find the new rightvector, and then use all 3 to calculate the new CFrame using fromMatrix or the CFrame.new() with 12 arguments. Warning, the devhub page on the two cframe constructors are wrong and the ordering of vectors is flipped; you may have to mess around a bit to get the correct cframe from the 3 vectors.

This way of doing it is super simple; one limitation is that if the new normal is collinear to the last lookvector, the output of flatten will be a zero vector. You can check for this condition if the dot product of the new normal and last lookvector is zero.

One ugly but effective way to counter this problem would be to check if you are hitting the edge case; if so, calculate the new lookvector twice: First, do the calculation with the target normal, plus some minor shifting on a different axis. This is to counter the problem of the zero vector. Then, using the new lookvector you just calculated with the shifted normal, calculate the proper lookvector using the actual normal. It’s a two-step solution, can be done super quickly in the same frame; I would call it “heuristic” in the nature of the solution, but it should in theory produce the precise result always.

1 Like

Try this: https://youtu.be/VxgNleUdmmg

Worked like a charm! Don’t tell anyone, but you are my favorite…

Though I think I am experiencing the zero vector problem when climbing small 90 degree surfaces. It occasionally sets the position of the roomba to something like negative 3 gazillion, which I can only assume is the result of the zero vector.

OR some other part of my code is contributing to it.

I’ll have to figure that part out.