How would I use trigonometry?

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!

I want to learn how trig can be used in Roblox studio

  1. What is the issue? Include screenshots / videos if possible!

I can’t find a good tutorial.

  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?

I looked over the developer forum but the tutorial I used was the basics and didn’t really apply to Roblox.

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

I know sin,cos,tan and the inverses but I want to learn how to apply them in a game.

2 Likes

All the math functions are shown there which include the trig functions

1 Like

But how does trigonometry work in a 3d environment? I only know how to use it in a 2d environment.

A 3d environment is just a 2d environment with an extra d, but you can always set that dimension to 0. By doing that you can just use trig as if it’s 2d, and this could have many uses. Here I using a loop to get the 4 ortographic directions.

local function convertIndexToDirection(index)
	local angle = (index - 1) * math.pi / 2 -- Convert index to an angle (0, 90, 180, 270 degrees)
	local direction = Vector3.new(math.sin(angle), 0, math.cos(angle))
	return direction
end

for i = 1, 4 do
	local direction = convertIndexToDirection(i)
	print(direction)
end

1 Like

So is the direction is getting the sine and cosine of a triangle angle?

In that example I am converting an angle to a Vector3 direction, using the unit circle you can easily understand why. I input the 4 divisions a full circle has and calculate the corresponding sin and cos values. at 0 pi, that means sin is 0 and cos is 1, so the direction is Vector3.new(0,0,1)

1 Like

In order to use trigonometry to solve a problem in 3 dimensions, you’ll need to somehow turn the problem into a 2-dimensional problem.

If the original problem is in XY-plane, XZ-plane (@ExercitusMortem’s example) or YZ-plane, then turning it into a 2-dimensional problem can be done in the way @ExercitusMortem mentioned, by ignoring the irrelevant dimension.

However, not all situations are like this. Sometimes turning the problem into a 2-dimensional one is more complicated.

Let’s say you have a turret that can rotate around the y-axis of its base and its own x-axis (which changes based on rotation around the base). You may want to limit its rotation around it’s x-axis to be between some angles relative to it’s default rotation relative to the base.

You probably have a target position you want the turret to point towards. First we can simplify the problem by calculating where this position is relative to the CFrame whose position is the rotation point (assuming the rotation axes intersect) and rotation is the default orientation of the turret. If you are using a motor6D for rotating the turret, the aforementioned CFrame (which should be in world space) should be equal to Motor6D.Part0.CFrame * Motor6D.C0. This CFrame is called rotationPointCFrameInWorldSpace in the code below.

local rotationPointCFrameInWorldSpace = -- the aforementioned CFrame
local targetPositionInWorldSpace = -- the target position
local relativeTargetPosition = rotationPointCFrameInWorldSpace:ToObjectSpace(targetPositionInWorldSpace)

If we had no rotation limits, we could just simply use CFrame.lookAt. But when we do have limits, it’s not that easy.

No trigonometry has been used yet, but next it’ll be time for that. You can calculate the angle between the vector from rotationPointCFrameInWorldSpace.Position to targetPositionInWorldSpace and the XZ-plane of rotationPointCFrameInWorldSpace (this plane is defined by the right vector and look vector of rotationPointCFrameInWorldSpace) by using the XZ-plane length of the vector describing the relative target position (relativeTargetPosition) and the y-component of relativeTargetPosition.

local distanceInLocalXZPlane = Vector2.new(relativeTargetPositon.X, relativeTargetPosition.Z).Magnitude
local angleBetweenTargetOffsetVectorAndLocalXZPlane = math.atan2(relativeTargetPosition.Y, distanceInLocalXZPlane)

We just turned a three dimensional problem into a two-dimensional one by kind of treating the XZ-direction of relativeTargetPositon as one axis and the y-direction in the same coordinate system (which is defined by rotationPointCFrameInWorldSpace) as the other axis of a two-dimensional coordinate system.

Next, we can calculate an angle that respects the limits.

local validRotationAngleAroundTurretXAxis = math.clamp(angleBetweenTargetOffsetVectorAndLocalXZPlane, minAngle, maxAngle)

Now, we can calculate the relative rotation CFrame (relative to rotationPointCFrameInWorldSpace) using the CFrame.Angles constructor.

We need a rotation CFrame that first rotates around the y-axis of rotationPointCFrameInWorldSpace and then rotates around the x-axis of the CFrame resulting from applying this rotation.

If we have a matrix A, we get a matrix rotated around its own axes by a rotation defined by matrix B by calculating A * B. Calculating a CFrame rotated around the axes of A * B by a rotation defined by matrix C is done by calculating (A * B) * C. Although matrix operations have stricter equality rules than operations on numbers, (A * B) * C is still equal to A * (B * C). Thus, we can combine both of the aforementioned rotations into one rotation CFrame and set this CFrame as the value of Motor6D.Transform. By aforementioned rotations I mean rotation around the y-axis of `rotationPointCFrameInWorldSpace and rotation around the x-axis of the CFrame resulting from applying this rotation.

FCFrame for the rotation around the y-axis of rotationPointCFrameInWorldSpace is CFrame.Angles(0, math.atan2(relativeTargetPosition.X, relativeTargetPosition.Z), 0). This is the rotation B in the equation.

The x-axis rotation CFrame (C) is CFrame.Angles(validRotationAngleAroundTurretXAxis, 0, 0).

turretRotationMotor6D.Transform = CFrame.Angles(0, math.atan2(relativeTargetPosition.X, relativeTargetPosition.Z), 0) * CFrame.Angles(validRotationAngleAroundTurretXAxis, 0, 0)

Now, we didn’t use much trigonometry (only atan2 a couple of times). However, we don’t need to use CFrame.Angles. Alternatively, with some more trigonometry, we can calculate the axes of Motor6D.Transform that results in the correct rotation and use CFrame.fromMatrix. The axes should be unit vectors (length 1).

Let’s start with the look vector. It should have the same XZ-direction as relativeTargetPosition. The angle between its look vector and the XZ plane should be validRotationAngleAroundTurretXAxis. Cosine and sine are the x and y position of a point on the unit circle on xy-plane (the center of this unit circle is origo and the radius is 1). Thus, Vector2.new(cos(angle), sin(angle)) is a unit vector. We need a three-dimensional vector, though, so we need a vector whose horizontal component (projection to XZ-plane) has the absolute value of cos(angle) as its length and the horizontal direction of relativeTargetPosition as its direction. The vertical component (projection to Y-axis) should be sin(angle).

We get a XZ-plane unit vector with the same XZ-direction as relativeTargetPosition by dividing Vector3.new(relativeTargetPosition.X, 0, relativeTargetPosition’Z) by its length which we already calculated before (distanceInLocalXZPlane). By multiplying this vector by cos(angle), we get the desired XZ-component of the look vector.

local lookVector = Vector3.new(relativeTargetPosition.X, 0, relativeTargetPosition.Z) / distanceInLocalXZPlane * cos(validRotationAngleAroundTurretXAxis) + Vector3.new(0, sin(validRotationAngleAroundTurretXAxis), 0)

Next it’s time to calculate the up vector. The angle between the upvector and the y-axis should be the same as the angle between the look vector and the XZ-plane, and the up vector should be perpendicular to the look vector.

local upVector = Vector3.new(relativeTargetPosition.X, 0, relativeTargetPosition.Z) / distanceInLocalXZPlane * (-sin(validRotationAngleAroundTurretXAxis)) + Vector3.new(0, cos(validRotationAngleAroundTurretXAxis), 0)

A picture may help in understanding the formula for the up vector. Thinking about two-dimensional vectors instead of 3-dimensional ones helped in thinking of how to edit the look vector formula to get the up vector formula.
image

Here's a way to think of how the look vector and up vector are calculated

We first form the unit vector of the horizontal component of the look vector (let’s call this h (horizontal)) and the unit vector of the positive y-axis (let’s call this v (vertical)). Let’s also define a = Vector2.new(cos(angle), sin(angle)) and b = Vector2.new(-sin(angle), cos(angle)). These two vectors are from the picture.

The look vector is h * a.X + v * a.Y. The up vector is h * b.X + v * b.Y.

The right vector should be perpendicular to both the look vector and the up vector. It should also point to the right, not to the left (both right and left are perpendicular to front and up directions). We can calculate it as the unit of the cross product of look vector and up vector. The order of these in the cross product is important because with the incorrect order, the resulting vector would point to the left.

local rightVector = lookVector:Cross(upVector).Unit

Now, it’s time to construct the CFrame. As it’s a CFrame defining only a (relative) rotation, its position should be the zero vector.

turretRotationMotor6D.Transform = CFrame.fromMatrix(Vector3.zero, rightVector, upVector, lookVector)
3 Likes

Could you explain what CFrame.fromMatrix() and math.atan2() is?

CFrame.fromMatrix
A CFrame (coordinate frame) is a matrix that contains data about four vectors. These include a position vector and three direction vectors that are perpendicular to each other (and they are unit vectors). Let’s say we have a CFrame cf and a CFrame base that cf is defined relative to.

The direction vectors of cf are LookVector (front direction, negative z-axis defined by the CFrame in terms of the axes (X, Y and Z) of base, RightVector (positive x-axis defined by the CFrame in terms of the axes of base) and UpVector (positive y-axis defined in terms of the axes of base).

base is usually the identity CFrame in which case the axes of cf are defined in terms of the global X, Y and Z axes)

CFrame.fromMatrix constructs a CFrame given these four vectors.

math.atan2
math.atan takes one number (the tangent of an angle) and returns the signed angle between Vector2.new(cos(angle), sin(angle)) and the x-axis. The returned angle is in the range [-pi/2, pi/2] (which is [-90, 90] in degrees). By x-axis, I mean the line, not just a vector with the direction of the positive x-axis. The angle is positive when the sine and cosine define a point in the upper right of lower left corner of the unit circle and negative otherwise (when starting rotation from the x-axis and rotating counterclockwise either above or below x-axis, the angle is non-negative for the first < 90 degrees rotation and non-positive for the rest > 90 and <= 180 degrees).

The tangent of an angle a is sin(a) / cos(a). If we have angles a = -150 degrees and b = 30 degrees, then abs(cos(a)) = abs(cos(b)) and abs(sin(a)) = abs(cos(a)). cos(a) is negative and cos(b) is positive. sin(a) is negative and sin(b) is positive. In division the negative signs of cos(a) and sin(a) cancel each other out so tan(a) = tan(b) although the angles are not the same.

Another example: a = -30 degrees and b = 150 degrees. Again, abs(cos(a)) = abs(cos(b)) and abs(sin(a)) = abs(cos(a)). This time, cos(a) is positive and sin(a) is negative. cos(b) is negative and sin(b) is positive. Again, the tangents are the same but the angles are not.

From this, we can conclude that the tangent of an angle is not enough for finding the angle unless we know that the angle is the range [-pi/2, pi/2] which is only half of the unit circle.

Additionally, if the cosine is 0 and sine is 1 or -1 (90 degrees or -90 degrees rotation, for example) atan will not work because the tangent is undefined in this situation because of division by zero.

math.atan2 takes two numbers y and x (x is the cosine of the angle and y is the sine of the angle) and returns the signed angle between Vector2.new(x, y) and positive x-axis direction (whose unit vector is Vector2.new(1, 0)). The returned angle is in the range [-pi, pi] so the angle can be anywhere on the unit circle. It is positive when sine (y-coordinate) is positive and negative when sine is negative. math.atan2 also works in the case where cosine is zero. Thus, math.atan2 gives a valid angle for any sine and cosine combination. The values given to atan2 can also be multiples of the sine and cosine (both sine and cosine multiplied by the same positive number).

The reason why atan2 is able to give a valid angle for any sine and cosine is that it can determine the correct unit circle quadrant by checking the signs of sine and cosine. The sign of just the tangent is not enough to determine the quadrant because the tangent has the same sign in two quadrants (opposite ones).

3 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.