Linear Velocity Constraint Causes Undesired Behavior During Collisions

Hello! I am using a Linear Velocity Constraint to handle motion for a vehicle of mine, specifically a scooter. The scooter itself is really just more of an aesthetics thing on the physics side as collisions are off and it is massless. What’s really interacting with the world is a transparent rectangular prism part with enabled collisions. I will refer to this part as the physics box.

My goal is to have the physics box not reorient or bounce at all during collisions. This is the expected behavior, except the wall (represented by one of the carts) would not be moving:
Video of part colliding into wall without reorienting or bouncing

I do not know how to go about this. When going into a collision, the physics box orients rapidly in a jittery motion and even slowly slides up the wall (you will notice that the tangent axes continually change but this is expected behavior in my project and in my tests does not impact the results):
Video of scooter (physics box) colliding with wall

I did the same thing with a simple part instead of a model and the results are similar:
Video of part colliding with wall

How can I achieve the desired behavior of no reorientations or bounces during collisions involving a part using a linear velocity constraint to move?

Just to clarify, every single object in the “Scooter-Vulqilin” model (shown in physics box collision video) is non-collidable with the exception of the physics box.

5 Likes

I tested with rectangular prisms of different sizes as well as a ball just out of curiosity and the result is essentially the same, even when going at a lower speed. It seems the issue arises when the linear velocity keeps applying a constant velocity when already in contact with the wall. Any ideas on how to prevent reorientations/bounces after a collision with the wall? I just want a “hard stop.”

1 Like

bump since it’s been two hours and I unfortunately haven’t found a solution yet

1 Like

another bump since its now been three; I was thinking of possibly implementing a system such that once the physics box is some small distance away from the wall, it would not be able to move any closer to the wall, but that would be difficult as it may interfere with certain movements and cause the physics box to be unnaturally “stuck.”

2 Likes

another bump is due since it’s been an hour. I experienced the consequence mentioned in my last response after my implementation ;-;

2 Likes

“and so the worn-out boy pleads once again for a seemingly-distant aid in hopes of resolution.”
– a worn-out boy

2 Likes

“a cry for help is but a means of expressing one’s distress and so the boy screeches sounds of desperation”
– a worn-out boy

2 Likes

I tried creating a function to make the physics box become parallel to the wall that is run at every Heartbeat event and although it does successfully do so when the scooter is tilted enough, the same problem still arises when the scooter is just going straight into the wall:
Video of physics box colliding with wall with adjustment

Here’s the relevant code:

local function handleCollisions()
	local proximityThreshold = 0.25 -- Adjust this value based on testing

	local function checkCollisionAndAdjust(direction: Vector3)
		local distance = direction.Magnitude
		direction = direction.Unit

		local result = workspace:Raycast(scooterPhysicsBox.Position, direction * distance, raycastParams)

		if result ~= nil and result.Instance ~= nil and distance < proximityThreshold then
			-- Calculate the component of velocity along the surface normal
			local velocityComponent = linearVelocity.PlaneVelocity:Dot(Vector2.new(result.Normal.X, result.Normal.Z))

			-- If the component is towards the obstacle, zero it out
			if velocityComponent > 0 then
				linearVelocity.PlaneVelocity = linearVelocity.PlaneVelocity - velocityComponent * Vector2.new(result.Normal.X, result.Normal.Z)
			end

			-- Adjust orientation based on the surface normal
			local newUpVector = result.Normal:Cross(direction):Cross(result.Normal).Unit
			scooterPhysicsBox.CFrame = CFrame.lookAt(scooterPhysicsBox.Position, result.Position, newUpVector)
		end
	end

	-- Check collisions for each part of the scooter (modify as needed)
	local edgeDistance = maxDimension / 2

	checkCollisionAndAdjust(scooterPhysicsBox.CFrame.LookVector * (edgeDistance + proximityThreshold))
	checkCollisionAndAdjust(-scooterPhysicsBox.CFrame.LookVector * (edgeDistance + proximityThreshold))
	checkCollisionAndAdjust(scooterPhysicsBox.CFrame.RightVector * (edgeDistance + proximityThreshold))
	checkCollisionAndAdjust(-scooterPhysicsBox.CFrame.RightVector * (edgeDistance + proximityThreshold))
end

RunService.Heartbeat:Connect(handleCollisions)
2 Likes

bumping bc I updated original prompt as maybe the first video was not clear enough; there were several clicks on the initial first video but close to none on the following ones

2 Likes

How about checking if there is a wall in front of the scooter (perhaps with a Raycast), and if there is, nullify the velocity?

3 Likes

Thanks for the reply! I’m thinking of a modified version: shooting multiple raycasts top to bottom along the direction of the velocity so that it not only works when the physics box is moving forward but also backward and also in edge cases where the obstacle is only blocking the bottom and/or top of the physics box. And if so, i will have to determine what component of the velocity will result in closing the distance further between the physics box and the obstacle and nullify that component.

I’ll post my testing results once I am able. In the meantime, I would appreciate if anyone posted an alternative solution because this is quite the hairy workaround.

1 Like

I think this Method is going into the right direction, this is how I would have done it:

local physicsBox = --[[ your physics box ]]
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
raycastParams.FilterDescendantsInstances = {physicsBox}

local function checkCollision()
    local raycastResult = workspace:Raycast(physicsBox.Position, physicsBox.Velocity.Unit * 10, raycastParams)

    if raycastResult then
        local normal = raycastResult.Normal
        local velocity = physicsBox.Velocity
        local reflectedVelocity = velocity - 2 * (velocity:Dot(normal)) * normal
        physicsBox.Velocity = reflectedVelocity
    end
end

game:GetService("RunService").Heartbeat:Connect(checkCollision)

It should prevent the jittering and glitching since we reflect the velocity off the wall. This is only an idea though and I don’t know it will work for sure.

2 Likes

Thanks for the reply! I modified the code and it works beautifully when colliding straight into the wall, but issues arise again when colliding at certain angles:

Video of scooter physics box colliding with wall at different angles

A little bit of context: the linearVelocity I keep referencing is a LinearVelocity constraint whose constraint mode is Plane. Assume that the constraint is constantly being updated such that the X component of the linearVelocity is always collinear with the look vector of the physicsBox, while the Y component is always collinear with the right vector of the physicsBox. There is no Z component since the constraint mode is Plane.

Here’s the code:

local function handleCollisions()
  local velocity = scooterPhysicsBox.CFrame.LookVector * linearVelocity.PlaneVelocity.X

  local origin = scooterPhysicsBox.Position

  -- multiply by 0.55 to make ray go a little past the scooter
  local direction = velocity.Unit * maxDimension * 0.55
  local result = workspace:Raycast(origin, direction, raycastParams)

	if result ~= nil and result.Instance ~= nil then
		local incidentVector = velocity.Unit
		local normalVector = result.Normal
		local speed = linearVelocity.PlaneVelocity.X
                
        local dampingFactor = 0.5

		local reflectedVector = incidentVector - 2 * incidentVector:Dot(normalVector) * normalVector
		local reflectedVelocity = reflectedVector * math.abs(speed) * dampingFactor

		linearVelocity.PlaneVelocity = Vector2.new(reflectedVelocity.X, reflectedVelocity.Z)
	end
end

RunService.Heartbeat:Connect(handleCollisions)

bumping since I haven’t found a solution yet ;-;

I don’t have very much experience with making vehicles, but have you tried limiting the max velocity?

Thank you for your response! Yes, I have limited the max velocity. The problem in this case, though is with how Roblox treats the collision between the obstacle (e.g., a wall) and the physics box (an invisible rectangular prism that represents the hitbox of the scooter). No matter what the speed is (even if it’s as small as 1), the same issue occurs with the physics box jittering and sliding up the wall. The linear velocity constraint keeps pushing the physics box in the direction the player is trying to move (even when touching the obstacle) and so I guess this causes issues, but I would think that the rectangular prism would simply not move if it’s already pressed against the wall

After some brief testing, it seems that a VectorForce is much more stable than a LinearVelocity. Have you tried using those?

No because it applies a constant force and therefore constant acceleration but I want constant velocity (no acceleration)

Bumping as I have not yet found a working solution

two days later and still no solution… ;-;