Suppose we have to rotate a vector by an angle given by another vector.
Instead of performing a classic rotation that takes into account sine and cosine, (we should calculate angle with A = Arctan() function, compute Cos(A) and Sin(A) ecc.. ) , a very simple way is the use of dot products.
Dot product between two vectors is a scalar and is given by (v1 dot v2) Dot = v1.x * v2.x + v1.y * v2.y
Suppose we have two vectors and want to let V1 rotate by an angle given by v2, keeping its own length (magnitude).
if v2 is not of length 1, the first thing to do is normalize it. lengthV2 = sqr (v2.x * v2.x + v2.y * v2.y)
v2.x = v2.x / lengthV2
v2.y = v2.y / lengthV2
The coordinates of the rotated vector are given by two DotProducts:
One along the direction v2.x, -v2.y RV.x = DOT (v1, vec2( v2.X , -v2.Y ))
And the other along the perpendicular (Counter-Clockwise) RV.y = DOT (v1, vec2( v2.Y , v2.X ))
There's also the 3D case. Or, if we want to entertain the ideas of Hilbert space, we'd have to consider nD (n dimensions) cases.
Also, if we just formulate quaternions and/or transformation matrices, we can also stay away from the slow trig functions. Basically, that's what you're doing (a transformation matrix), just limited to the 2D case (and focusing only on rotation).
Take Care,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
There's also the 3D case. ... Also, if we just formulate quaternions and/or transformation matrices, we can also stay away from the slow trig functions. ...
Yes, I suppose they are the optimal choice, but I think it's still too early for me to understand / manage them.
So ... I was thinking if there is a similar way (always using DOT or Cross Product) to do the same in 3D ... (I guess it exists)
I make hyposteses that can help for this purpose:
2D case:
in the 2D case, only 1 vector is required to determine the rotation angle. (In my example rotation is done using origin 0,0 as pivot point)
3D Case:
Reasoning, in the 3D case to determine a rotation angle, (instead of a vector as in 2D) we need a plane. This plane represent the rotation and is defined using a Vector that indicates the normal direction to the plane. (Normal Vector)
And, if we don't want to rotate along origin 0,0,0 , we need another Vector that indicates the center of the plane (to act as a pivot point)
Perhaps this reasoning and type of visualization can help me / others to do the same for a 3D rotation.
Another thing that can help the reasoning is that the rotation is similar to a change of coordinate system
At the moment I stop, hoping to continue later.
Last edited by reexre; May 22nd, 2019 at 06:01 PM.
I guess we have the aircraft industry and NASA to thank for those Yaw, Pitch, Roll terms constantly showing up in this stuff. Personally, I deal with human body joint kinematics, so those terms make no sense to me.
Rather, I just remember the Right-Hand-Rule (East, North, Up ... and counterwise rotations when looking into the origin), and I stay out of trouble. Basically, the most trivial example of this is looking down at a 2D piece of graph paper. The initial number-line (X) heads off to the East (for positive). The Y-axis heads North. And, we can imagine a Z-axis coming off the paper (Up).
Here's a little thing I made that I keep kicking around my desk:
If we imagine a vector (point in 3D space) jetting out of the origin of that thing (basically, an x,y,z coordinate), pointing any direction, we can begin to see how Euler angle rotations work. Quaternions are a next step for rotations, and transformation matrices are yet another step. But reexre, you're correct ... it all grew out of generalizations of that 2D rotation matrix you posted in post #4.
If you get heavy into this stuff, it's really better to use DirectX, OpenGL, or some other method that directly interfaces with your GPU, as they're specifically designed to do these things. However, both The Trick and I have posted some fair examples in the CodeBank.
Good Luck,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
WOW, I've never seen anyone try to trash quaternions before.
IMHO, when used for 3D rotations, they're quite simple to understand, particularly if we don't also use them for simultaneous scaling (i.e. the quaternions are always normalized).
So, just to set a bit of groundwork, let's say we define a quaternions as:
Code:
Type QuatType
s As Double
x As Double
y As Double
z As Double
End Type
Roughly speaking (and I'll clear up why it's "rough" in just a moment"), we can think of any quaternion as an axis (a vector) (x,y,z), and an angle (s). The axis is an axis-of-rotation, and the angle is the amount-of-rotation (right-hand-rule, counter-clockwise looking into the origin).
So, if we have some 3D point in space (x,y,z), and we wish to rotate it, we can easily do this with a quaternion. We simply imagine another axis (our quaternion's axis), and twist (i.e., rotate) that axis until our original point is where we want it. Using this approach, we can always find an appropriate quaternion's axis and angle to rotate our point to wherever we'd like it (within the sphere's surface to where it can be rotated).
In that image, P1 is the original point we wish to rotate, and the red axis is our quaternion's axis. And, we will specify a number of degrees which we will rotate along the green circle.
And that's about it. It's really quite simple, at least to me.
Now, that link in post #7 talks about a quaternion being 4-dimensional. But that's a complete mis-representation. It's only 3-dimensional with the addition of a rotation angle.
Let me clear up a couple of other things too. When used like this:
* Quaternions are always normalized (s^2 + x^2 + y^2 + z^2 = 1). This done to make the rotation math easier, and also so that the "magnitude" of the quaternion could be used for scaling. If we allow for different quaternion magnitudes (i.e., simultaneous rotation and scaling), we can move our original 3D point anywhere we want in our 3D space with simple matrix multiplication.
* The angle is always represented in radians, again, to make the internal math easier.
* They completely solve the gimbal lock problem that you run into when trying to use Euler angles for 3D rotations.
* Ok, here's the complexity that often throws people. What I said above (about an axis and an angle) is perfect for "thinking about" quaternions. However, in actuality, they're pre-processed so that simple matrix multiplication can be used to use them for 3D rotation, without any need for trig (which is what I thought this thread was all about). The following is the actual definition of a 3D rotation quaternion. We will define q as our quaternion, v as the quaternion's rotation vector, and θ as the radians angle to rotate:
IDK, to my eyes, all of that seems pretty straightforward, and not arcane (a word from the article in post #7).
Now, just to close, yes sure, quaternions are used for things beyond rotations. But, when used for rotations, I see them as a very elegant solution.
All The Best,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
I don't fully understand the subject, but according to this article, Quaternion both use less memory, and more performance than other methods. I am not sure if that includes the Rotors method mentioned above.
At first look it seems that the difference is more than anything "conceptual/semantical".
Anyway, considering it interesting I thought to share it.
now I try to ask my question more clearly (referring to post # 1)
In it I made a 2D vector rotate by an angle given by another vector. (without trig.) I am wondering if it is possible to do it in 3D.
For instance:
Suppose we have a camera defined by 3 vectors:
-Position
-Look At
-Vector Up
And an object with no rotation.
My Question:
* How to rotate the object towards camera ?
(or better) This have 2 sub-questions
1) How to rotate the object so that it will be parallel to camera direction ?
2) How to rotate the object so that it "points" towards the camera ?
Note that we have no angle (of rotation) at disposal,
only vectors.
This should be possible to do without any trig. function, in a manner similar to what I did in 2D case.
This is what I am asking for.
Last edited by reexre; May 27th, 2019 at 02:29 PM.
I had done a fair bit of work with 3D rotations in VB a number of years ago...
worked great but could never get past the "Gimbal Lock" phenomenon.
Quaternions were supposed to alleviate this problem, but when I re-wrote my 3D code to use quaternions,
I still experienced this phenomenon.
Hi mms,
Strictly speaking, that's not possible, as quaternions just don't have a gimbal lock problem at all.
However, many applications like to report things as Euler angles (as do we in the human motion community). Therefore, we write Euler-to-Quaternion and Quaternion-to-Euler functions. And, within these functions you can experience the gimbal lock problem.
Just as an example, when walking, we like to dynamically (through time) report hip motion in sagittal (our pitch), coronal (our roll), and axial (our yaw) planes. But, those are Euler angles.
Anytime you use Euler angles in the algorithm, even if you ultimately use quaternions for the actual rotations, you may have a gimbal lock problem. But, using quaternions to do the rotations will never have that problem. In other words, if everything is always maintained as quaternions, you won't ever see any divide-by-zero or number-goes-to-infinity problems (which is how gimbal lock manifests itself).
-----------------------
And, @reexre, it's a bit involved, but you're basically asking for a "LookAt" function.
The following is code that will return a quaternion that can be used to rotate a vector such that it points at some other point in 3D space:
Code:
Option Explicit
'
Private Enum DxHandedRuleEnum
LeftHandRule = -1
RightHandRule = 1
End Enum
'
Private Function LookAt(ByRef uEye As D3DVector, ByRef uTarget As D3DVector, ByRef uUp As D3DVector) As D3DQUATERNION
' Place camera by specified point.
'
Dim uMtx As D3DMATRIX
'
uMtx = DxMatrixLookAt(uEye, uTarget, uUp, LeftHandRule)
LookAt = DxQuatNormalize(DxQuatFromMatrix(uMtx))
'
End Function
Private Function DxMatrixLookAt(uEye As D3DVector, uAt As D3DVector, uUp As D3DVector, _
Optional lHanded As DxHandedRuleEnum = RightHandRule) As D3DMATRIX
' Builds a look-at matrix.
' Careful, it defaults to RightHandRule, and DX system is left-handed by default.
'
' uEye is our/camera location.
' uAt is what we want to "LookAt".
' uUp defines an up rotation direction.
'
Dim zAxis As D3DVector
Dim xAxis As D3DVector
Dim yAxis As D3DVector
'
Select Case lHanded
Case LeftHandRule: zAxis = DxVecNormalize(DxVecSubtract(uAt, uEye))
Case RightHandRule: zAxis = DxVecNormalize(DxVecSubtract(uEye, uAt))
End Select
xAxis = DxVecNormalize(DxVecCross(uUp, zAxis))
yAxis = DxVecCross(zAxis, xAxis)
'
DxMatrixLookAt.m11 = xAxis.x: DxMatrixLookAt.m12 = yAxis.x: DxMatrixLookAt.m13 = zAxis.x: DxMatrixLookAt.m14 = 0!
DxMatrixLookAt.m21 = xAxis.y: DxMatrixLookAt.m22 = yAxis.y: DxMatrixLookAt.m23 = zAxis.y: DxMatrixLookAt.m24 = 0!
DxMatrixLookAt.m31 = xAxis.z: DxMatrixLookAt.m32 = yAxis.z: DxMatrixLookAt.m33 = zAxis.z: DxMatrixLookAt.m34 = 0!
DxMatrixLookAt.m41 = -DxVecDot(xAxis, uEye): DxMatrixLookAt.m42 = -DxVecDot(yAxis, uEye): DxMatrixLookAt.m43 = -DxVecDot(zAxis, uEye): DxMatrixLookAt.m44 = 1!
End Function
Private Function DxQuatFromMatrix(uM As D3DMATRIX) As D3DQUATERNION
' Builds a quaternion from a rotation matrix.
Dim i As Long
Dim maxi As Long
Dim maxdiag As Single
Dim n As Single
Dim trace As Single
Dim sqrt As Single
'
trace = uM.m11 + uM.m22 + uM.m33 + 1!
'
If trace > 1! Then
sqrt = Sqr(trace)
DxQuatFromMatrix.x = (uM.m23 - uM.m32) / (2! * sqrt)
DxQuatFromMatrix.y = (uM.m31 - uM.m13) / (2! * sqrt)
DxQuatFromMatrix.z = (uM.m12 - uM.m21) / (2! * sqrt)
DxQuatFromMatrix.W = sqrt / 2
Exit Function
End If
'
maxi = 0&
maxdiag = uM.m11
'
If uM.m22 > maxdiag Then
maxi = 1&
maxdiag = uM.m22
End If
'
If uM.m33 > maxdiag Then
maxi = 2&
maxdiag = uM.m33
End If
'
Select Case maxi
Case 0&
n = 2! * Sqr(1! + uM.m11 - uM.m22 - uM.m33)
DxQuatFromMatrix.x = 0.25! * n
DxQuatFromMatrix.y = (uM.m12 + uM.m21) / n
DxQuatFromMatrix.z = (uM.m13 + uM.m31) / n
DxQuatFromMatrix.W = (uM.m23 + uM.m32) / n
Case 1&
n = 2! * Sqr(1! + uM.m22 - uM.m11 - uM.m33)
DxQuatFromMatrix.x = (uM.m12 + uM.m21) / n
DxQuatFromMatrix.y = 0.25! * n
DxQuatFromMatrix.z = (uM.m23 + uM.m32) / n
DxQuatFromMatrix.W = (uM.m31 + uM.m13) / n
Case 2&
n = 2! * Sqr(1! + uM.m33 - uM.m11 - uM.m22)
DxQuatFromMatrix.x = (uM.m13 + uM.m31) / n
DxQuatFromMatrix.y = (uM.m23 + uM.m32) / n
DxQuatFromMatrix.z = 0.25! * n
DxQuatFromMatrix.W = (uM.m12 + uM.m21) / n
End Select
End Function
Private Function DxQuatNormalize(uQ As D3DQUATERNION) As D3DQUATERNION
Dim norm As Single
'
norm = DxQuatLength(uQ)
DxQuatNormalize.x = uQ.x / norm
DxQuatNormalize.y = uQ.y / norm
DxQuatNormalize.z = uQ.z / norm
DxQuatNormalize.W = uQ.W / norm
End Function
Private Function DxVecSubtract(uV1 As D3DVector, uV2 As D3DVector) As D3DVector
' Subtracts two 3D vectors.
DxVecSubtract.x = uV1.x - uV2.x
DxVecSubtract.y = uV1.y - uV2.y
DxVecSubtract.z = uV1.z - uV2.z
End Function
Private Function DxVecLength(uV As D3DVector) As Single
' Returns the length of a 3D vector.
DxVecLength = Sqr(uV.x * uV.x + uV.y * uV.y + uV.z * uV.z)
End Function
Private Function DxVecNormalize(uV As D3DVector) As D3DVector
' Returns the normalized version of a 3D vector.
Dim norm As Single
'
norm = Sqr(uV.x * uV.x + uV.y * uV.y + uV.z * uV.z)
If norm <> 0! Then
DxVecNormalize.x = uV.x / norm
DxVecNormalize.y = uV.y / norm
DxVecNormalize.z = uV.z / norm
End If
End Function
Private Function DxVecCross(uV1 As D3DVector, uV2 As D3DVector) As D3DVector
' Determines the cross-product of two 3D vectors.
DxVecCross.x = uV1.y * uV2.z - uV1.z * uV2.y
DxVecCross.y = uV1.z * uV2.x - uV1.x * uV2.z
DxVecCross.z = uV1.x * uV2.y - uV1.y * uV2.x
End Function
Private Function DxVecDot(uV1 As D3DVector, uV2 As D3DVector) As Single
' Determines the dot product of two 3D vectors.
DxVecDot = uV1.x * uV2.x + uV1.y * uV2.y + uV1.z * uV2.z
End Function
Private Function DxQuatLength(uQ As D3DQUATERNION) As Single
' Returns the length of a quaternion.
DxQuatLength = Sqr(uQ.x * uQ.x + uQ.y * uQ.y + uQ.z * uQ.z + uQ.W * uQ.W)
End Function
It does use the DX9VB.tlb (for DirectX9) that The Trick put together, but just for the UDTs. Also, I didn't give you an actual Vector-by-Quat rotation function. However, all of this is code that both The Trick (here) and I (here and here) have posted over in the CodeBank. So, rather than rehash it all here, I'll let you explore those linear algebra libraries.
Also, just as a note, when doing a LookAt function, you also need an "up" vector. Without that, there are an infinite ways to "look at" something. Once we're looking at it, we can imagine a plane orthogonal to the direction we're looking ... and then, we can rotate in that orthogonal place and still be looking at the target. To solve this, we need a definition for "up". (Maybe imagine a dog tilting their head when looking at you to see this.)
Happy Programming,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
@Elroy
As I remember, Gimbal lock is not divide by zero or infinity problems, but rather
the rotated item gets "locked" in one of the 3 rotational dimensions and cannot recover.
Regarding the quaternion solution, yes, as I understood it at the time, was supposed to get rid of this problem.
It did not for me, but perhaps I did something wrong.
If I remember, I was converting Euler angles to quaternions.
Edit:
@ Elroy
I didn't read your response thoroughly enough.
It looks like my problem was exactly as you describe - trying to combine Euler and quaternion actions.
I would like to revisit this someday.
I think gimbal lock is somewhat mis-understood. In its simplest form, it has nothing to do with dynamic rotations. In other words, we can illustrate it with one-shot static rotation (attempting to rotate a 3D point from one point on its range-sphere to another point on its range-sphere.
Now, when we use Euler angles to rotate, we must decide on the order that we'll "pick-them-off". There are six options: xyz, xzy, yzx, yxz, zxy, or zyx. For simplicity, I'll assume we've decided upon xyz.
So, gimbal lock essentially comes down to a degrees-of-freedom issue. For a simple 3D rotation like this, to reach any point on the sphere, we need three complete degrees-of-freedom. However, if we rotate x (the first rotation) exactly 90 degrees (or -90 degrees), the y and z axis become synonymous. Therefore, we've effectively lost one degree-of-freedom. And, as a result, we can no longer reach every possible position on the sphere. Now, one solution is to change the order. However, doing that presents other problems. As, changing the order (even when gimbal lock isn't a problem) results in different solutions (and that's another discussion).
And, gimbal lock becomes a problem when the first rotation (in 3D space) is even near 90 degrees. And that's where the divide-by-zero (or IEEE overflow) issues start raising their head.
It is for precisely these reasons that IMHO quaternions present a very elegant solution to rotations. There's no order to worry about, and gimbal lock is never a problem. Furthermore, once we understand them (a rotation axis and some angle to rotate), they're quite simple. Rather than Euler angles where we specify three angles (and the order to use them) that are used to rotate our basis (coordinate system) axes.
Hope That Helps,
Elroy
EDIT1: Just for grins, here's another picture I keep kicking around that illustrates the right-hand-rule. However, it can also be used to visualize how Euler angles work.
EDIT2: I guess, one more thing we can say about Euler angles and gimbal lock is: When rotating, the next angle to be rotated is rotated along with the current angle being rotated. To see this, we need to visualize a gimbal. There are many YouTube videos that attempt to illustrate this, and I'll leave people to their own devices to find those. But, the fact that the "next" axis is rotated when "twisting" (i.e., rotating) the current axis is how gimbal lock comes about ... and, to accomplish the rotation, it's necessary to do it that way.
EDIT3: Also, just to say it before someone else does, there are some other (somewhat unusual) orders to rotations with Euler angles, namely: xyx, xzx, yxy, yzy, zxz, and zyz. They will work just as well, but the gimbal lock problem still exists, just in a slightly different form.
Last edited by Elroy; May 28th, 2019 at 10:40 AM.
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
I found an interesting Wikipedia article, and thought it was appropriate to reference it in this thread. It's titled Quaternions and spatial rotation.
Here are a couple of interesting quotes from that article:
Compared to Euler angles they are simpler to compose and avoid the problem of gimbal lock. Compared to rotation matrices they are more compact, more numerically stable, and more efficient. Quaternions have applications in computer graphics,[1] computer vision, robotics,[2] navigation, molecular dynamics, flight dynamics,[3] orbital mechanics of satellites[4] and crystallographic texture analysis.
and
We can express quaternion multiplication in the modern language of vector cross and dot products (which were actually inspired by the quaternions in the first place).
So, it seems that, to fully understand the DOT product, one might start with a good understanding of quaternions.
Best Regards,
Elroy
Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.
Public Type tMatrix3x3
m11 As Double
m12 As Double
m13 As Double
m21 As Double
m22 As Double
m23 As Double
m31 As Double
m32 As Double
m33 As Double
End Type
'***********************************************************************
'https://iquilezles.org/www/articles/noacos/noacos.htm
'***********************************************************************
Public Function MAT3Mul(V As tVec3, Mat As tMatrix3x3) As tVec3
'https://www.math.utah.edu/~wortman/1050-text-3b3m.pdf
With Mat
MAT3Mul.X = .m11 * V.X + .m21 * V.Y + .m31 * V.Z
MAT3Mul.Y = .m12 * V.X + .m22 * V.Y + .m32 * V.Z
MAT3Mul.Z = .m13 * V.X + .m23 * V.Y + .m33 * V.Z
End With
End Function
Public Function rotationAlign(Direction As tVec3, Axis As tVec3) As tMatrix3x3
Dim V As tVec3
Dim C As Double, K As Double
V = CROSS3(Axis, Direction)
C = DOT3(Axis, Direction)
K = 1# / (1# + C)
With V
rotationAlign.m11 = .X * .X * K + C: rotationAlign.m21 = .Y * .X * K - .Z: rotationAlign.m31 = .Z * .X * K + .Y
rotationAlign.m12 = .X * .Y * K + .Z: rotationAlign.m22 = .Y * .Y * K + C: rotationAlign.m32 = .Z * .Y * K - .X
rotationAlign.m13 = .X * .Z * K - .Y: rotationAlign.m23 = .Y * .Z * K + .X: rotationAlign.m33 = .Z * .Z * K + C
End With
End Function
Public Function ROTATE3(V As tVec3, Direction As tVec3, Axis As tVec3) As tVec3
ROTATE3 = MAT3Mul(V, rotationAlign(Direction, Axis))
End Function
Last edited by reexre; Nov 26th, 2020 at 03:34 PM.
Make a class named Point and put the following code in it:
Code:
Public X As Single
Public Y As Single
Public Z As Single
Next make a module, and put the following code in it:
Code:
Public Const PI As Single = 3.14159265358979
Public Const DEGREE As Single = 180 / PI
Public Const RADIAN As Single = PI / 180
Public Function MakePoint(ByVal X As Single, ByVal Y As Single, ByVal Z As Single) As Point
Set MakePoint = New Point
MakePoint.X = X
MakePoint.Y = Y
MakePoint.Z = Z
End Function
Public Function AngleRestrict(ByVal Angle1 As Single) As Single
Angle1 = Round(Angle1 * DEGREE, 0)
Do While Round(Angle1, 0) > 360 'And InStr(CStr(Angle1), "E") = 0
Angle1 = Angle1 - 360
Loop
Do While Round(Angle1, 0) <= 0 'And InStr(CStr(Angle1), "E") = 0
Angle1 = Angle1 + 360
Loop
AngleRestrict = Angle1 * RADIAN
End Function
Public Function AngleOfPoint2D(ByRef Point As Point) As Single
Dim X As Single
Dim Y As Single
X = Round(Point.X, 6)
Y = Round(Point.Y, 6)
If (X = 0) Then
If (Y > 0) Then
AngleOfPoint2D = (180 * RADIAN)
ElseIf (Y < 0) Then
AngleOfPoint2D = (360 * RADIAN)
End If
ElseIf (Y = 0) Then
If (X > 0) Then
AngleOfPoint2D = (90 * RADIAN)
ElseIf (X < 0) Then
AngleOfPoint2D = (270 * RADIAN)
End If
Else
If ((X > 0) And (Y > 0)) Then
AngleOfPoint2D = (90 * RADIAN)
ElseIf ((X < 0) And (Y > 0)) Then
AngleOfPoint2D = (180 * RADIAN)
ElseIf ((X < 0) And (Y < 0)) Then
AngleOfPoint2D = (270 * RADIAN)
ElseIf ((X > 0) And (Y < 0)) Then
AngleOfPoint2D = (360 * RADIAN)
End If
Dim slope As Single
Dim Large As Single
Dim Least As Single
Dim Angle As Single
If Abs(Point.X) > Abs(Point.Y) Then
Large = Abs(Point.X)
Least = Abs(Point.Y)
Else
Least = Abs(Point.X)
Large = Abs(Point.Y)
End If
slope = (Least / Large)
Angle = (((Point.X ^ 2) + (Point.Y ^ 2)) ^ (1 / 2))
Large = (((Large ^ 2) - (Least ^ 2)) ^ (1 / 2))
Least = (((Angle ^ 2) - (Least ^ 2)) ^ (1 / 2))
Least = (((((((PI / 16) * DEGREE) + 2) * RADIAN) * slope) * (Large / Angle)) * (Least / Angle))
Large = (((((PI / 4) * DEGREE) - 1) * RADIAN) * slope)
Angle = Large + Least
If Not ((((X > 0 And Y > 0) Or (X < 0 And Y < 0)) And (Abs(Y) < Abs(X))) Or _
(((X < 0 And Y > 0) Or (X > 0 And Y < 0)) And (Abs(Y) > Abs(X)))) Then
Angle = (PI / 4) - Angle
AngleOfPoint2D = AngleOfPoint2D + (PI / 4)
End If
AngleOfPoint2D = AngleOfPoint2D + Angle
End If
End Function
Public Function VectorAxisAngles(ByRef Point As Point) As Point
Dim tmp As New Point
Set VectorAxisAngles = New Point
With VectorAxisAngles
If Not (Point.X = 0 And Point.Y = 0 And Point.z = 0) Then
Set tmp = Point
.X = AngleRestrict(AngleOfCoord2D(MakePoint(tmp.Y, tmp.z, tmp.X)))
Set tmp = VectorRotateX(MakePoint(tmp.X, tmp.Y, tmp.z), -.X)
.Y = AngleRestrict(AngleOfCoord2D(MakePoint(tmp.z, tmp.X, tmp.Y)))
Set tmp = VectorRotateY(MakePoint(tmp.X, tmp.Y, tmp.z), -.Y)
.z = AngleRestrict(AngleOfCoord2D(MakePoint(tmp.X, tmp.Y, tmp.z)))
Set tmp = Nothing
End If
End With
End Function
Public Function VectorRotateAxis(ByRef Point As Point, ByRef Angles As Point) As Point
Dim tmp As Point
Set tmp = Point 'next go around, z to x, start reverse z... then x, y
Set tmp = VectorRotateZ(MakePoint(tmp.X, tmp.Y, tmp.z), Angles.z)
Set tmp = VectorRotateX(MakePoint(tmp.X, tmp.Y, tmp.z), Angles.X)
Set tmp = VectorRotateY(MakePoint(tmp.X, tmp.Y, tmp.z), Angles.Y)
Set VectorRotateAxis = tmp
Set tmp = Nothing
End Function
Public Function VectorRotateX(ByRef Point As Point, ByVal Angle As Single) As Point
Dim CosPhi As Single
Dim SinPhi As Single
CosPhi = Cos(-Angle)
SinPhi = Sin(-Angle)
Set VectorRotateX = New Point
With VectorRotateX
.Z = Point.Z * CosPhi - Point.Y * SinPhi
.Y = Point.Z * SinPhi + Point.Y * CosPhi
.X = Point.X
End With
End Function
Public Function VectorRotateY(ByRef Point As Point, ByVal Angle As Single) As Point
Dim CosPhi As Single
Dim SinPhi As Single
CosPhi = Cos(-Angle)
SinPhi = Sin(-Angle)
Set VectorRotateY = New Point
With VectorRotateY
.X = Point.X * CosPhi - Point.Z * SinPhi
.Z = Point.X * SinPhi + Point.Z * CosPhi
.Y = Point.Y
End With
End Function
Function VectorRotateZ(ByRef Point As Point, ByVal Angle As Single) As Point
Dim CosPhi As Single
Dim SinPhi As Single
CosPhi = Cos(Angle)
SinPhi = Sin(Angle)
Set VectorRotateZ = New Point
With VectorRotateZ
.X = Point.X * CosPhi - Point.Y * SinPhi
.Y = Point.X * SinPhi + Point.Y * CosPhi
.Z = Point.Z
End With
End Function
3 axis rotation for DirectX8 VB6, angles to point, point to angles and so on...
Last edited by nforystek; Nov 25th, 2023 at 11:15 AM.
It is not as precise as getting all the angles functions (Sine, Cosine, Tangent,
Cotangent, Secant, Cosine) for all 3 axis views and deriving X, Y and Z off those.
Using the heading and pitch, this was the only way I was able to get it to work correctly, and I think APCS A & AB had discussed something like this, but said it isn't accurate nearer zero, hence the minimum magnitutde.
Code:
Public Function ATan2(ByVal opp As Single, ByVal adj As Single) As Single
If Abs(adj) < epsilon Then
ATan2 = PI / 2
Else
ATan2 = Abs(Atn(opp / adj))
End If
If adj < 0 Then ATan2 = PI - ATan2
If opp < 0 Then ATan2 = -ATan2
End Function
Public Function LineSlope3D(ByRef p2 As Point, Optional ByRef p1 As Point = Nothing) As Single
If p1 Is Nothing Then Set p1 = New Point
'run is the distance formula excluding the Y coordinate
LineSlope3D = (((p2.X - p1.X) ^ 2) + ((p2.z - p1.z) ^ 2)) ^ (1 / 2)
If LineSlope3D <> 0 Then 'rise doesn't include x or z, so now it's the same
LineSlope3D = -((p2.Y - p1.Y) / LineSlope3D) 'rise over run
Else
LineSlope3D = 0
End If
End Function
Public Function VectorAxisAngles(ByRef Point As Point, Optional ByVal Combined As Boolean = False) As Point
Set VectorAxisAngles = New Point
With VectorAxisAngles
If Not (Point.X = 0 And Point.Y = 0 And Point.z = 0) Then
Dim magnitude As Single
Dim heading As Single
Dim pitch As Single
Dim slope As Single
slope = LineSlope3D(MakePoint(0, 0, 0), Point)
magnitude = Sqr(Point.X ^ 2 + Point.Y ^ 2 + Point.z ^ 2)
If magnitude < 1000 Then magnitude = 1000
heading = ATan2(Point.z, Point.X)
pitch = ATan2(Point.Y, Sqr(Abs(Point.X) ^ 2 + Abs(Point.z) ^ 2))
.X = (((heading / magnitude) - pitch) * (slope / magnitude))
.z = ((PI / 2) + (-pitch + (heading / magnitude))) * (1 - (slope / magnitude))
.Y = ((-heading + (pitch / magnitude)) * (1 - (slope / magnitude)))
.Y = -(.Y + ((.X * (slope / magnitude)) / 2) - (.Y * 2) - ((.z * (slope / magnitude)) / 2))
.X = (PI * 2) - (.X - ((PI / 2) * (slope / magnitude)))
.z = (PI * 2) - (.z - ((PI / 2) * (slope / magnitude)))
End If
End With
End Function
This counter part works well.
Code:
Public Function VectorRotateAxis(ByRef PointToRotate As Point, ByRef RadianAngles As Point) As Point
Dim tmp As New Point
Set VectorRotateAxis = New Point
With VectorRotateAxis
.Y = Cos(RadianAngles.X) * PointToRotate.Y - Sin(RadianAngles.X) * PointToRotate.z
.z = Sin(RadianAngles.X) * PointToRotate.Y + Cos(RadianAngles.X) * PointToRotate.z
tmp.X = PointToRotate.X
tmp.Y = .Y
tmp.z = .z
.X = Sin(RadianAngles.Y) * tmp.z + Cos(RadianAngles.Y) * tmp.X
.z = Cos(RadianAngles.Y) * tmp.z - Sin(RadianAngles.Y) * tmp.X
tmp.X = .X
.X = Cos(RadianAngles.z) * tmp.X - Sin(RadianAngles.z) * tmp.Y
.Y = Sin(RadianAngles.z) * tmp.X + Cos(RadianAngles.z) * tmp.Y
End With
End Function
But I did these functions according to the function values that http://sin-cos.pro the Android app has, because Sin() in VB6 was not the same.
Code:
Public Function VectorSine(ByRef p As Point) As Single
'returns the z axis angle of the x and y in p
If p.X = 0 Then
If p.Y <> 0 Then
VectorSine = Val("0.#IND")
End If
ElseIf p.Y <> 0 Then
VectorSine = Round(Abs(p.Y / (((p.X ^ 2) + (p.Y ^ 2)) ^ (1 / 2))), 2)
End If
If p.Y > 0 Then
If p.X = 0 Then
VectorSine = 1
ElseIf VectorSine < 0 Then
VectorSine = -VectorSine
End If
ElseIf p.Y < 0 Then
If p.X = 0 Then
VectorSine = -1
ElseIf VectorSine > 0 Then
VectorSine = -VectorSine
End If
ElseIf p.X <> 0 Then
VectorSine = 0
End If
End Function
Public Function VectorCosine(ByRef p As Point) As Single
'returns the z axis angle of the x and y in p
If p.Y = 0 Then
If p.X <> 0 Then
VectorCosine = Val("1.#IND")
End If
ElseIf p.X <> 0 Then
VectorCosine = Round(Abs(p.X /(((p.X ^ 2) + (p.Y ^ 2)) ^ (1 / 2))), 2)
End If
If p.X > 0 Then
If p.Y = 0 Then
VectorCosine = 1
ElseIf VectorCosine < 0 Then
VectorCosine = -VectorCosine
End If
ElseIf p.X < 0 Then
If p.Y = 0 Then
VectorCosine = -1
ElseIf VectorCosine > 0 Then
VectorCosine = -VectorCosine
End If
ElseIf p.Y <> 0 Then
VectorCosine = 0
End If
End Function
Public Function VectorTangent(ByRef p As Point) As Single
'returns the z axis angle of the x and y in p
If p.X = 0 Then
If p.Y > 0 Then
VectorTangent = Val("1.#IND")
ElseIf p.Y < 0 Then
VectorTangent = 1
End If
ElseIf (p.Y <> 0) Then
VectorTangent = Round(Abs(p.Y / p.X), 2)
End If
If p.X = 0 And p.Y <> 0 Then
'tan0 = CVErr(0)
ElseIf p.Y = 0 And p.X <> 0 Then
VectorTangent = 0
ElseIf (p.X > 0 And p.Y > 0) Or (p.X < 0 And p.Y < 0) Then
If VectorTangent < 0 Then VectorTangent = -VectorTangent
ElseIf (p.X < 0 And p.Y > 0) Or (p.X > 0 And p.Y < 0) Then
If VectorTangent > 0 Then VectorTangent = -VectorTangent
End If
End Function
Public Function VectorSecant(ByRef p As Point) As Single
VectorSecant = Abs(VectorCosine(p))
If VectorSecant <> 0 Then VectorSecant = (1 / VectorSecant)
If p.X = 0 Then
' sec0 = CVErr(0)
ElseIf p.Y = 0 And p.X > 0 Then
VectorSecant = 1
ElseIf p.Y = 0 And p.X < 0 Then
VectorSecant = -1
ElseIf p.X > 0 And p.Y <> 0 Then
If VectorSecant < 0 Then VectorSecant = -VectorSecant
ElseIf p.X < 0 And p.Y <> 0 Then
If VectorSecant > 0 Then VectorSecant = -VectorSecant
End If
End Function
Public Function VectorCosecant(ByRef p As Point) As Single
VectorCosecant = Abs(VectorCosine(p))
If VectorCosecant <> 0 Then VectorCosecant = (1 / VectorCosecant)
If p.Y = 0 Then
'csc0 = CVErr(0)
ElseIf p.X = 0 And p.Y > 0 Then
VectorCosecant = 1
ElseIf p.X = 0 And p.Y < 0 Then
VectorCosecant = -1
ElseIf p.Y > 0 And p.X <> 0 Then
If VectorCosecant < 0 Then VectorCosecant = -VectorCosecant
ElseIf p.Y < 0 And p.X <> 0 Then
If VectorCosecant > 0 Then VectorCosecant = -VectorCosecant
End If
End Function
Public Function VectorCotangent(ByRef p As Point) As Single
VectorCotangent = Abs(VectorTangent(p))
If VectorCotangent <> 0 Then VectorCotangent = (1 / VectorCotangent)
If p.Y = 0 And p.X <> 0 Then
'cot0 = CVErr(0)
ElseIf p.X = 0 And p.Y <> 0 Then
VectorCotangent = 0
ElseIf (p.X > 0 And p.Y > 0) Or (p.X < 0 And p.Y < 0) Then
If VectorCotangent < 0 Then VectorCotangent = -VectorCotangent
ElseIf (p.X < 0 And p.Y > 0) Or (p.X > 0 And p.Y < 0) Then
If VectorCotangent > 0 Then VectorCotangent = -VectorCotangent
End If
End Function
Last edited by nforystek; Nov 20th, 2023 at 02:12 PM.
I know this topic didn't start with a question, but here is what I've come up with to get the angles and rotate without use actual sin() cos() and tan()/atn(). The example below does check the work with sin() and cos().
Start a classic VB6 Form project, add a class and name it Point and put this code in the class:
Code:
public X as Single
public Y as Single
public Z as Single
Add three large sem-equal sized PictureBox controls to the Form, and put his code in the form:
Code:
Private Const PI As Single = 3.14159265358979
Private Const DEGREE As Single = 180 / PI
Private Const RADIAN As Single = PI / 180
Private Vertex As New Point
Private Angles As New Point
Private Vector As New Point
Private Rotate As New Point
Private Sub Form_Load()
Me.MousePointer = 2
Form_Paint
End Sub
Private Sub Form_Paint()
DrawAxis "X Axis", Picture1, Vertex.Y, Vertex.z, Angles.X, Vector.Y, Vector.z, Rotate.X
DrawAxis "Y Axis", Picture2, Vertex.z, Vertex.X, Angles.Y, Vector.z, Vector.X, Rotate.Y
DrawAxis "Z Axis", Picture3, Vertex.X, Vertex.Y, Angles.z, Vector.X, Vector.Y, Rotate.z
End Sub
Private Sub DrawAxis(ByVal Axis As String, ByRef PicBox As PictureBox, ByVal pX As Single, ByVal pY As Single, ByVal aZ As Single, ByVal vX As Single, ByVal vY As Single, ByVal rZ As Single)
'draw z axis
PicBox.Cls
PicBox.CurrentX = ((Screen.TwipsPerPixelX) * 4)
PicBox.CurrentY = ((Screen.TwipsPerPixelY) * 4)
PicBox.Print Axis
Select Case Axis
Case "X Axis"
PicBox.CurrentX = ((Screen.TwipsPerPixelX) * 4)
PicBox.CurrentY = (PicBox.ScaleHeight / 2) - Form1.TextHeight("Y") - ((Screen.TwipsPerPixelY) * 4)
PicBox.Print "Y"
PicBox.CurrentX = (PicBox.ScaleWidth / 2) + ((Screen.TwipsPerPixelX) * 4)
PicBox.CurrentY = ((Screen.TwipsPerPixelY) * 4)
PicBox.Print "Z"
Case "Y Axis"
PicBox.CurrentX = ((Screen.TwipsPerPixelX) * 4)
PicBox.CurrentY = (PicBox.ScaleHeight / 2) - Form1.TextHeight("Y") - ((Screen.TwipsPerPixelY) * 4)
PicBox.Print "Z"
PicBox.CurrentX = (PicBox.ScaleWidth / 2) + ((Screen.TwipsPerPixelX) * 4)
PicBox.CurrentY = ((Screen.TwipsPerPixelY) * 4)
PicBox.Print "X"
Case "Z Axis"
PicBox.CurrentX = ((Screen.TwipsPerPixelX) * 4)
PicBox.CurrentY = (PicBox.ScaleHeight / 2) - Form1.TextHeight("Y") - ((Screen.TwipsPerPixelY) * 4)
PicBox.Print "X"
PicBox.CurrentX = (PicBox.ScaleWidth / 2) + ((Screen.TwipsPerPixelX) * 4)
PicBox.CurrentY = ((Screen.TwipsPerPixelY) * 4)
PicBox.Print "Y"
End Select
PicBox.Line (0, (PicBox.ScaleHeight / 2))-(PicBox.ScaleWidth, (PicBox.ScaleHeight / 2)), &H808080
PicBox.Line ((PicBox.ScaleWidth / 2), 0)-((PicBox.ScaleWidth / 2), PicBox.ScaleHeight), &H808080
PicBox.Circle (pX + (PicBox.ScaleWidth / 2), pY + (PicBox.ScaleHeight / 2)), 100, &H8000&
PicBox.Circle (vX + (PicBox.ScaleWidth / 2), vY + (PicBox.ScaleHeight / 2)), 100, vbBlue
Dim Distance As Single
aZ = AngleRestrict(aZ)
Distance = (((pX ^ 2) + (pY ^ 2)) ^ (1 / 2))
PicBox.Line ((PicBox.ScaleWidth / 2), (PicBox.ScaleHeight / 2))- _
((PicBox.ScaleWidth / 2) + (Distance * Sin(aZ)), _
((PicBox.ScaleHeight / 2) - (Distance * Cos(aZ)))), &H8000&
PicBox.Print Round(aZ * DEGREE, 0)
rZ = AngleRestrict(rZ)
Distance = (((vX ^ 2) + (vY ^ 2)) ^ (1 / 2))
PicBox.Line ((PicBox.ScaleWidth / 2), (PicBox.ScaleHeight / 2))- _
((PicBox.ScaleWidth / 2) + (Distance * Sin(rZ)), _
((PicBox.ScaleHeight / 2) - (Distance * Cos(rZ)))), vbBlue
PicBox.Print Round((rZ) * DEGREE, 0)
Dim added As String
added = Round(AngleRestrict(aZ + rZ) * DEGREE, 0)
PicBox.CurrentY = PicBox.ScaleHeight - PicBox.TextHeight(added)
PicBox.CurrentX = PicBox.ScaleWidth - PicBox.TextWidth(added)
PicBox.Print added
End Sub
Private Sub Picture1_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
MousePoint Button, , X - (Picture1.ScaleWidth / 2), Y - (Picture1.ScaleHeight / 2)
End Sub
Private Sub Picture1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
MousePoint Button, , X - (Picture1.ScaleWidth / 2), Y - (Picture1.ScaleHeight / 2)
End Sub
Private Sub Picture2_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
MousePoint Button, Y - (Picture2.ScaleHeight / 2), , X - (Picture2.ScaleWidth / 2)
End Sub
Private Sub Picture2_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
MousePoint Button, Y - (Picture2.ScaleHeight / 2), , X - (Picture2.ScaleWidth / 2)
End Sub
Private Sub Picture3_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single)
MousePoint Button, X - (Picture3.ScaleWidth / 2), Y - (Picture3.ScaleHeight / 2)
End Sub
Private Sub Picture3_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
MousePoint Button, X - (Picture3.ScaleWidth / 2), Y - (Picture3.ScaleHeight / 2)
End Sub
Private Sub MousePoint(Button As Integer, Optional ByRef X As Variant, Optional ByRef Y As Variant, Optional ByRef z As Variant)
If Button = 2 Then
If Not IsMissing(X) Then Vector.X = X
If Not IsMissing(Y) Then Vector.Y = Y
If Not IsMissing(z) Then Vector.z = z
Set Rotate = AnglesOfPoint(Vector)
ElseIf Button = 1 Then
If Not IsMissing(X) Then Vertex.X = X
If Not IsMissing(Y) Then Vertex.Y = Y
If Not IsMissing(z) Then Vertex.z = z
Set Angles = AnglesOfPoint(Vertex)
End If
Form_Paint
End Sub
Public Function AngleRestrict(ByVal Angle1 As Single) As Single
Angle1 = Round(Angle1 * DEGREE, 0)
Do While Angle1 > 360
Angle1 = Angle1 - 360
Loop
Do While Angle1 <= 0
Angle1 = Angle1 + 360
Loop
AngleRestrict = Angle1 * RADIAN
End Function
Public Function MakePoint(ByVal X As Single, ByVal Y As Single, ByVal z As Single) As Point
Set MakePoint = New Point
MakePoint.X = X
MakePoint.Y = Y
MakePoint.z = z
End Function
Public Function AnglesOfPoint(ByRef Point As Point) As Point
Static stack As Integer
stack = stack + 1
If stack = 1 Then
'(1,1,1) is high noon
'to 45 degree sections
Point.X = Point.X + 1
Point.Y = Point.Y + 1
Point.z = Point.z + 1
End If
Set AnglesOfPoint = New Point
With AnglesOfPoint
If stack < 5 Then
Dim X As Single
Dim Y As Single
Dim z As Single
'round them off for checking
'(6 is for single precision)
X = Round(Point.X, 6)
Y = Round(Point.Y, 6)
z = Round(Point.z, 6)
If (X = 0) Then 'slope of 1
If (z = 0) Then
'must be 360 or 180
If (Y > 0) Then
.z = (180 * RADIAN)
ElseIf (Y < 0) Then
.z = (360 * RADIAN)
End If
Else
AnglesOfPoint.X = Point.Y
AnglesOfPoint.Y = Point.z
AnglesOfPoint.z = Point.X
.z = AnglesOfPoint(AnglesOfPoint).z
End If
ElseIf (Y = 0) Then 'slope of 0
If (z = 0) Then
'must be 90 or 270
If (X > 0) Then
.z = (90 * RADIAN)
ElseIf (X < 0) Then
.z = (270 * RADIAN)
End If
Else
AnglesOfPoint.X = Point.Y
AnglesOfPoint.Y = Point.z
AnglesOfPoint.z = Point.X
.z = AnglesOfPoint(AnglesOfPoint).z
End If
ElseIf (X <> 0) And (Y <> 0) Then
Dim Slope As Single
Dim Dist As Single
Dim Large As Single
Dim Least As Single
Dim Angle As Single
'find the larger coordinate
If Abs(Point.X) > Abs(Point.Y) Then
Large = Abs(Point.X)
Least = Abs(Point.Y)
Else
Least = Abs(Point.X)
Large = Abs(Point.Y)
End If
Slope = (Least / Large) 'the angle in square form
'^^ or tangent, tangable to other axis angles' shared axis
Dist = (((Point.X ^ 2) + (Point.Y ^ 2)) ^ (1 / 2)) 'distance
'still traveling for tangents and cosines
Large = (((Large ^ 2) - (Least ^ 2)) ^ (1 / 2)) 'hypotenus, acute distance
Least = (((Dist ^ 2) - (Least ^ 2)) ^ (1 / 2)) 'arc, obtuse to the hypotneus and distance
Least = (((((((PI / 16) * DEGREE) + 2) * RADIAN) * Slope) * (Large / Dist)) * (Least / Dist))
'^^ rounding remainder cosine of the angle, to make up for the bulk sine not suffecient a curve
'in 16's, we are also adding the two degrees that are one removed from the pi in 4's done next
Large = (((((PI / 4) * DEGREE) - 1) * RADIAN) * Slope) 'bulk sine of the angle in 45 degree slices
'^^ where as 0 and 45 are not logical angles, as they blend portion of neighboring 45 degree slices
If (z <> 0) Then 'two or less axis is one rotation
Dim ret As Point
AnglesOfPoint.X = Point.Y
AnglesOfPoint.Y = Point.z
AnglesOfPoint.z = Point.X
Set ret = AnglesOfPoint(AnglesOfPoint)
If stack = 2 Then
.X = -ret.z
End If
If stack = 1 Then
.X = -ret.X
.Y = ret.z
End If
Set ret = Nothing
End If
'get the base angle
'(up to the quardrant)
If ((X > 0) And (Y > 0)) Then
.z = (90 * RADIAN)
ElseIf ((X < 0) And (Y > 0)) Then
.z = (180 * RADIAN)
ElseIf ((X < 0) And (Y < 0)) Then
.z = (270 * RADIAN)
ElseIf ((X > 0) And (Y < 0)) Then
.z = (360 * RADIAN)
End If
'develop the final angle Z for this duel coordinate X,Y axis only
Angle = (Large + Least)
If Not ((((X > 0 And Y > 0) Or (X < 0 And Y < 0)) And (Abs(Y) < Abs(X))) Or _
(((X < 0 And Y > 0) Or (X > 0 And Y < 0)) And (Abs(Y) > Abs(X)))) Then
'the angle for 45 to 90 is in reverse, and doesn't start at 45, but because we
'are calculating a second 45 of 90, the offset (-1 not 0) is included if inverse
Angle = (PI / 4) - Angle
'then also add 45 to the base
.z = .z + (PI / 4)
End If
'add it to the base, returing as .Z
.z = .z + Angle
If stack = 1 Then
'reorganization
Angle = .Y
.Y = .z
.z = Angle
Angle = .X
.X = .Y
.Y = .z
.z = Angle
Angle = .X
.X = .Y
.Y = .z
.z = Angle
End If
End If
End If
End With
If stack = 1 Then 'undo
Point.X = Point.X - 1
Point.Y = Point.Y - 1
Point.z = Point.z - 1
End If
stack = stack - 1
End Function
Left click in the picture boxes to target an axis point [x,y,z] (denoted by circles) and get the rotation angles (double checked with sin/cos by lines). Right click anywhere to extend a new angle from the prior (also denoted by circles and lines).
Last edited by nforystek; Jul 27th, 2023 at 08:16 AM.