Hello Everyone! I am making a skateboard game and I am struggling to align the skateboard to a a surface. I made a decision that i will use 4 raycasts in each corner of the skateboard (i will call it a part just so its easier to understand. Also i use “alignOrientation” object for rotation). Now here comes the tough part - After getting the normal vector of all 4 rays, idk how to rotate the part acording to them. I’ve tried to get the avg normal vector by doing this (n1 + n2 + n3 + n4) / 4. It works but it for some reason gives a default rotation for a flat surface of (90,90,0). So i suppose this does not work. Here’s an image of my part. Also if anyone reads this, please make it so the calculations EXCLUDE the Y axis, since i don’t want it overlapping with other scripts.
Probably a ton of complicated math, try looking into Cross Product, that might help with the math.
My idea is, use the 4 rays as hit points that contact the surface, then make 2 vectors, one for the width and the length, basically,
front right wheel - front left wheel.
and
front right wheel - back right wheel
Then get the normal by getting the Cross Product of these 2 vectors. And that would be your normal, it would be more accurate than averaging.
Sorry my math is not that good. Can you maybe make a tiny code sample showcasing what you just said - mostly the “then make 2 vectors, one for the width and the length” part. Does this method involve normal vectors in the calculations or is the final result the normal vector? Thanks.
Here something like this:
-- Assuming your raycasts gave you these hit positions for each wheel
local frontRightPos = Vector3.new(5, 0, 5) -- Example position
local frontLeftPos = Vector3.new(-5, 0, 5) -- Example position
local backRightPos = Vector3.new(5, 0, -5) -- Example position
--Make the width vector (front right to front left)
local widthVector = frontLeftPos - frontRightPos
--Make the length vector (front right to back right)
local lengthVector = backRightPos - frontRightPos
--Get the surface normal using cross product
local surfaceNormal = widthVector:Cross(lengthVector).Unit
The surfaceNormal
variable is the final result/normal vector
Thanks a lot. Will def try it!!
I tried it and constructed a code that looks like this . Is it correct?
local function alignPartToSurface()
local ray1 = workspace:Raycast(raycastOrigins["LeftFront"], Vector3.new(0, -5, 0), raycastParams)
local ray2 = workspace:Raycast(raycastOrigins["RightFront"], Vector3.new(0, -5, 0), raycastParams)
local ray3 = workspace:Raycast(raycastOrigins["RightBack"], Vector3.new(0, -5, 0), raycastParams)
if ray1 and ray2 and ray3 then
local widthVector = ray1.Position - ray2.Position
--Make the length vector (front right to back right)
local lengthVector = ray3.Position - ray2.Position
--Get the surface normal using cross product
local surfaceNormal = widthVector:Cross(lengthVector).Unit
--[[local alignedCFrame = CFrame.fromMatrix(
part.Position,
part.CFrame.RightVector,
surfaceNormal -- Ensure the normal aligns properly
)]]
bodyGyro.CFrame = CFrame.lookAt(part.Position, part.Position + surfaceNormal)
end
end
Edit : I tried using the commented part - the CFrame.fromMatrix part and it seems to always keep a straight up 90 degree angle on any surface its on
It’s on the right track, the width vector should be right - left, not the other way around. And I would recommend you use your commented code, I think its a little better in my opinion.
Tried it and it is still not working. Here’s a video ( the two visible attachments are the two front ones)
Here’s what the gyro shows on flat ground
Try this version:
local function alignPartToSurface()
local ray1 = workspace:Raycast(raycastOrigins["LeftFront"], Vector3.new(0, -5, 0), raycastParams)
local ray2 = workspace:Raycast(raycastOrigins["RightFront"], Vector3.new(0, -5, 0), raycastParams)
local ray3 = workspace:Raycast(raycastOrigins["RightBack"], Vector3.new(0, -5, 0), raycastParams)
if ray1 and ray2 and ray3 then
local widthVector = ray2.Position - ray1.Position
local lengthVector = ray3.Position - ray2.Position
local surfaceNormal = widthVector:Cross(lengthVector).Unit
if surfaceNormal.Y < 0 then
surfaceNormal = -surfaceNormal
end
local currentUp = part.CFrame.UpVector
local rotationAxis = currentUp:Cross(surfaceNormal).Unit
local rotationAngle = math.acos(math.clamp(currentUp:Dot(surfaceNormal), -1, 1))
local currentYRotation = math.atan2(part.CFrame.LookVector.X, part.CFrame.LookVector.Z)
bodyGyro.CFrame = CFrame.new(part.Position) *
CFrame.fromAxisAngle(rotationAxis, rotationAngle) *
CFrame.Angles(0, currentYRotation, 0)
end
end
Same thing. The only real rotation is if ray3 is detecting a not 90 degree surface and the two front rays are detecting a straight surface
Interesting, I guess add more checks? Test this, if this doesn’t work, I will look into the calculations more:
local function alignPartToSurface()
local ray1 = workspace:Raycast(raycastOrigins["LeftFront"], Vector3.new(0, -5, 0), raycastParams)
local ray2 = workspace:Raycast(raycastOrigins["RightFront"], Vector3.new(0, -5, 0), raycastParams)
local ray3 = workspace:Raycast(raycastOrigins["RightBack"], Vector3.new(0, -5, 0), raycastParams)
if ray1 and ray2 and ray3 then
local widthVector = ray2.Position - ray1.Position
local lengthVector = ray3.Position - ray2.Position
if widthVector.Magnitude == 0 or lengthVector.Magnitude == 0 then
return
end
local surfaceNormal = lengthVector:Cross(widthVector).Unit
if surfaceNormal.Y < 0 then
surfaceNormal = -surfaceNormal
end
local currentUp = part.CFrame.UpVector
local dot = math.clamp(currentUp:Dot(surfaceNormal), -1, 1)
local rotationAngle = math.acos(dot)
if math.abs(rotationAngle) < 1e-4 then
return
end
local rotationAxis = currentUp:Cross(surfaceNormal).Unit
if rotationAxis.Magnitude == 0 then
return
end
local currentRotation = part.CFrame - part.CFrame.Position
bodyGyro.CFrame = CFrame.new(part.Position) *
CFrame.fromAxisAngle(rotationAxis, rotationAngle) *
currentRotation
end
end
Uhh it kinda worked. When i launch the game, the part gets aligned with the other part that its on but then it just stops aligning when i move it over to other parts, The good thing is that this time it aligns perfectly with the first detected part
Try putting it in a Run Service loop, if it isnt already.
Try visualising the origins, printing what the raycasts detected, and the CFrame of the BodyGyro, see if you find any anomalies.
Interesting! When i move the part to another slope, it stops printing the instances, and it only does it if i manually move the part with the roblox studio select tool ( or with any other thing that moves the part)
try this:
local RunService = game:GetService("RunService")
local lastPosition = part.Position
local function alignPartToSurface()
local raycastOrigins = {
["LeftFront"] = part.Position + part.CFrame:VectorToWorldSpace(Vector3.new(-1, 0, -1)),
["RightFront"] = part.Position + part.CFrame:VectorToWorldSpace(Vector3.new(1, 0, -1)),
["RightBack"] = part.Position + part.CFrame:VectorToWorldSpace(Vector3.new(1, 0, 1))
}
local ray1 = workspace:Raycast(raycastOrigins["LeftFront"], Vector3.new(0, -5, 0), raycastParams)
local ray2 = workspace:Raycast(raycastOrigins["RightFront"], Vector3.new(0, -5, 0), raycastParams)
local ray3 = workspace:Raycast(raycastOrigins["RightBack"], Vector3.new(0, -5, 0), raycastParams)
if ray1 and ray2 and ray3 then
local widthVector = ray2.Position - ray1.Position
local lengthVector = ray3.Position - ray2.Position
if widthVector.Magnitude == 0 or lengthVector.Magnitude == 0 then
return
end
local surfaceNormal = lengthVector:Cross(widthVector).Unit
if surfaceNormal.Y < 0 then
surfaceNormal = -surfaceNormal
end
local currentUp = part.CFrame.UpVector
local dot = math.clamp(currentUp:Dot(surfaceNormal), -1, 1)
local rotationAngle = math.acos(dot)
if math.abs(rotationAngle) < 1e-4 then
return
end
local rotationAxis = currentUp:Cross(surfaceNormal).Unit
if rotationAxis.Magnitude == 0 then
return
end
local currentRotation = part.CFrame - part.CFrame.Position
bodyGyro.CFrame = CFrame.new(part.Position) *
CFrame.fromAxisAngle(rotationAxis, rotationAngle) *
currentRotation
end
end
RunService.Heartbeat:Connect(function()
if (part.Position - lastPosition).Magnitude > 0.01 then
alignPartToSurface()
lastPosition = part.Position
end
end)