Cframe from position, upvector, and lookNear vectors

Given a position, upVector, and lookVector. I want a function that creates a cframe that

  1. Has the same position as the given position
  2. Has the same upVector as the given upVector
  3. Has a lookVector that is as close as possible to the given lookVector

Here is an demonstration done with constraints which is pretty close to what I want

test_snap.rbxl (24.2 KB)

Green is the upVector.
Red is the lookVector.
Grey is the position (grey ball is covered by the cube).
The balls’ position are the target vectors. The lines coming out of the cube represent’s the cube’s vectors.

As you can see, the green upVector of the cube points straight at the green ball at all times.
The red lookVector is doing its best to point in the general direction of the red ball,
but does not change the upVector in the process.

I am trying to achieve the same rough results with a function instead of constraints. Something like

function calculateCframeFromWorldVectors(pos: Vector3 ,up: Vector3 ,targetLook: Vector3): CFrame

For testing, I will be using the balls’ position as parameter’s for the function and applying the resulting cframe to the cube.

calculateCframeFromWorldVectors(greyBall.Position, greenBall.Position, redBall.Position)

and return a cframe similar to the cube’s cframe now.

This has to be calculated without relying on the physics system as the goal is to do this in a plugin.
I believe I should be able to achieve this with CFrame.fromMatrix(), but haven’t conceived a reliable formula… and in the end just became extremely confused.
I originally wanted the function to take unit vectors for up * targetLook, but have reached the point where I don’t trust myself to calculate anything…

Here is the broken demo world. The broken function is at the top of the script in game.Workspace.PublicTest. To test, you can push run (Do not push play or play here).
The very transparent cube is the physics based implimentation, and the smaller, more opaque cube is the one using the function. If the function is fixed then the opaque cube should align itself close to the transparent one (but the transparent physics one isn’t perfect, so it shouldn’t be a perfect fit)

test_snap.rbxl (24.2 KB)


Just take the cross product

local right = up:Cross(look)
    0, 0, 0,
    right.X, up.X, look.X,
    right.Y, up.Y, look.Y,
    right.Z, up.Z, look.Z

This works bc the CFrame is made up of a rotation matrix and also positional values. You can cross 2 of the directions to get a third direction perpendicular to both. If the right vector is in the wrong direction, just swap look and up.
Also, keep in mind that this does not work properly if the directional vectors are not perpendicular to one another (and you will also need to normalize it with .Unit and calculate other values based on it). I don’t see why you would use it when they are not though.
Example if not perpendicular:

local right = up:Cross(look).Unit
look = right:Cross(up)

I had been doing the cross, but still wasn’t getting the results I expected with the data I was feeding it. However, with your function also giving odd results, I realized I must be doing something fundamentally wrong. After sleeping on it, I realized I needed to subtract the cframe position from the up and look world positions to get the actual up vector/look vector.

local function calculateCframeFromWorldVectors(pos, up, look)
	local up = (up - pos).Unit
	local look = (look - pos).Unit
	local right = up:Cross(-look).Unit
	return CFrame.fromMatrix(pos,right,up)
	look = right:Cross(up)
		pos.X, pos.Y, pos.Z,
		right.X, up.X, look.X,
		right.Y, up.Y, look.Y,
		right.Z, up.Z, look.Z

My fundamental problem was failing to calculate the up/look Vectors (I knew I needed to, but for some reason was using Cross on the position instead of subtracting).
Thanks for the help. I had spent hours stabbing fruitlessly at my cframe function thinking it was the issue, wasn’t until I used yours that I realized my vectors were wack.
Looks good now!