I have tried for DAYS please help (Cross P)

I am trying to get A to smoothly look at B using CROSS PRODUCT (or some other way that is not cf:Lerp).

This is my problem:

As you can see it flips around for some reason and that can’t happen because it needs to look at that part.

This is my code:

local RunService = game:GetService("RunService")

local Part0 = workspace:WaitForChild("Part0")
local Part1 = workspace:WaitForChild("Part1")

local function Heartbeat(DeltaTime)
	local Cross = Part0.CFrame.LookVector:Cross((Part1.Position - Part0.Position).Unit)

	Part0.CFrame = Part0.CFrame * CFrame.Angles(math.asin(Cross.X * DeltaTime), math.asin(Cross.Y * DeltaTime), 0)
	return
end

RunService.Heartbeat:Connect(Heartbeat)

This is the place:

PLEASE.rbxl (31.4 KB)

Can you please help, I am actually really struggling and have been trying to get it to work all week

I would actually use a TweenService system for smooth part orientation.

local A = script.Parent
local B = game.Workspace.B

local kP = 0.5
local dt = 1/30

function unitOrZero(v3)
	return v3 == Vector3.new() and v3 or v3.Unit
end

while wait() do
	if A.Position == B.Position then continue end
	
	local aToB = B.Position - A.Position
	local aLookVector = A.CFrame.LookVector
	
	local rotationVector = aLookVector:Cross(aToB.Unit)
	local rotationAmount = math.asin(rotationVector.Magnitude)
	local rotationAxis = unitOrZero(rotationVector)
	
	if rotationAmount ~= 0 then
		local objRotationAxis = A.CFrame:VectorToObjectSpace(rotationAxis)
                local stepRotationAmount = math.clamp(rotationAmount * kP * dt, 0, rotationAmount)
		A.CFrame *= CFrame.fromAxisAngle(objRotationAxis, stepRotationAmount )
	end
end

you should probably use quaternions, but I don’t understand enough about them to explain them to you. I think this might help.

Can you explain what going on here, for example… example. Why are you using the Magnitude?? Why are you using VectorToObjectSpace? Multiplying A by another CFrame? and why are you using fromAxisAngle?

1 Like

The CFrame.lookAt constructor does a good job of keeping the box oriented nicely. What about calculating a trailing point for the lookAt position. Maybe something like this:

local RunService = game:GetService("RunService")

local Part0 = workspace:WaitForChild("Part0")
local Part1 = workspace:WaitForChild("Part1")

local trailingPoint = Part1.Position
local function Heartbeat(DeltaTime)
	trailingPoint = trailingPoint + (Part1.Position - trailingPoint) * DeltaTime

	Part0:PivotTo(CFrame.lookAt(Part0.Position, trailingPoint))
end

RunService.Heartbeat:Connect(Heartbeat)

or something like this if you don’t want to store trailingPoint outside the Heartbeat fn:

local RunService = game:GetService("RunService")

local Part0 = workspace:WaitForChild("Part0")
local Part1 = workspace:WaitForChild("Part1")

local function Heartbeat(DeltaTime)
	local trailingPoint = Part0.Position + (Part1.Position - Part0.Position).magnitude * Part0.CFrame.LookVector
	trailingPoint = trailingPoint + (Part1.Position - trailingPoint) * DeltaTime

	Part0:PivotTo(CFrame.lookAt(Part0.Position, trailingPoint))
end

RunService.Heartbeat:Connect(Heartbeat)

Any rotation happens about an axis vector, and by some amount of radians. Using the fromAxisAngle CFrame constructor we can create a CFrame that represents a specific rotation if we know the axis and the angle. It is handy that we can work with the angle separately from the axis, because we don’t actually want to rotate A to face B, we want to rotate a small amount towards B so it eventually gets there, but in a “straight” rotation i.e. always around the same axis. This way we can easily modify the rotation amount independently of the axis.

A is pointing one way, and we want to point it a different way, towards B. So the rotation we want should move the current lookvector (aLookVector) onto the desired one (aToB). We want the rotation to be “straight”, so the axis of rotation should be perpendicular to both the current lookvector and the goal lookvector. Another way to see that the axis of rotation must be perpendicular to both is to look at it from the side. It’s clear that the current and desired lookvectors are in a 2D that is rotated somehow in 3D, and when we rotate something in 2D the axis of rotation is perpendicular to the 2D plane. One property of the 3D cross product is that the resulting vector is always perpendicular to the two input vectors, which is why we use it to get the axis of rotation. Since this axis doesn’t encode any information about the angle to rotate, I just take it to be the unit vector of the cross product. The fromAxisAngle method also only works with unit vectors as the axis, according to the wiki.

As for the rotation amount, it is just a number of radians to rotate. We could use the dot product to compute the angle between the two vectors, but since we already have the cross product for a different purpose we can use that as well. A property of the cross product is that

image

where the left hand side is the magnitude of the cross product. Reorder to solve for the angle and we get

image

We don’t need the lengths of the vectors to affect the output. After all we’re only talking about directions which should be unit vectors, so let’s work with the unit vectors of A.CFrame.LookVector (already a unitvector) and aToB to construct the cross product. This makes the product under the fraction equal 1 so it simplifies to

image

which is what you see in the code. This angle will always be positive, but that’s fine. Rotations in opposite directions is handled with opposite axis vectors.

The reason the axis vector is converted to the object space of the current CFrame of A is that we’re using the * operator to apply the rotation. This operator applies a transform to the left hand side, and when transforms are applied they’re always relative to the existing transform (i.e. the current CFrame). Since the current and goal lookvectors are in world space, the axis of rotation is also in world space. We need to convert it to a vector that is relative to the current orientation of A, whatever it happens to be. VectorToObjectSpace converts a vector from world space to a specific object space, which is just what we need.

2 Likes

To explain a bit about transformations, there are two ways to think of it. CFrame is the Roblox data type that stores a transformation. One way to think of a transformation is as a position and orientation. E.g. pos (5, 0, 10) and orientation (45, 0, 0) (in degrees for this comment). Another way is as a translation and rotation, which are the words we use for changes in position and orientation (although rotation and orientation are often used interchangeably). Parts in Roblox use one transformation (CFrame) to store their Position and Orientation. We can “apply” another transformation, meaning another CFrame, to that CFrame. That is what C1 * C2 does, it applies C2 to C1 and the result is another CFrame that represents C2 applied to C1. An example is a CFrame “C2” at (0, 0, 0) with orientation (0, 15, 0). Thought of as a change in orientation, it represents turning around your Y axis by 15 degrees. So if we do A.CFrame *= C2, we change the transform of A to be whatever it was, but rotated 15 degrees on the Y axis. We could also do A.CFrame *= C2 * C2, which would rotate it another 15 degrees for a total of 30 degrees. If C2 was instead (1, 0, 0) with 0 orientation, A.CFrame *= C2 would cause A to move 1 stud on the X axis, which is to the right. These transforms are always relative to the one on the left hand side of C1 * C2, so no matter how A is oriented it would rotate 15 degrees relative to itself, or move 1 to the right relative to itself. This explains why we needed to convert from a rotation axis that was relative to the world, to one that was relative to whatever transform A happened to have at the moment when we were applying the rotation.

1 Like