Jump to content
Sign in to follow this  
Gabor64738

Rotate a cube to align with a 3D line

Recommended Posts

I am trying to rotate a 3D object (StrokeCube) to align its Y axis with a line segment defined by two points (two Spheres). Is there an easy way to do this using say CreateLookAtRH, or similar? The Help and https://docwiki.embarcadero.com/ does not describe this and similar methods and I cannot find any example code. Having searched and read for a few days/nights, I've gone down the scary path of rotation matrices etc.

 

This is the process I have so far:

  • Create Vector1 as the AbsoluteUp (TVector3D) of the StrokeCube.
  • Create Vector2 that runs from Sphere1 to Sphere2 (subtracting them), also TVector3D.
  • Normalise the two vectors.
  • Create RotationAxis by calculating their cross product (RotationAxis perpendicular to both).
  • Normalise RotationAxis.
  • Calculate Angle between Vector1 and Vector2 as arccos of their dot product divided by (Vector1Length*Vector2Length).
  • R := mat.CreateRotation() with the normalised RotationAxis and the Angle.
  • Getting the X Y Z rotations for the StrokeCube out of R does not give correct results. I've tried many variants, e.g. https://learnopencv.com/rotation-matrix-to-euler-angles/ and https://uk.mathworks.com/matlabcentral/answers/493771-euler-3d-rotation-between-two-vectors.

 

My questions:

  • Is there an easy way to align a 3D object (StrokeCube) to a line defined by two points?
  • If not, then is my processing correct so far?
  • What matches the X Y Z rotations of an FMX 3D object? One of the Euler sequences, Quaternions, or something else? How do I extract the necessary rotations from the rotation matrix?

 

Thanks for your help.

 

Update: I've tried these but they don't rotate the Segment (StrokeCube):

Segment.AbsoluteMatrix.CreateRotation(Vector3DP1P2n, Angle);
Segment.AbsoluteMatrix.CreateLookAtRH(TPoint3D.Create(M1.Position.X,M1.Position.Y,M1.Position.Z), TPoint3D.Create(M2.Position.X,M2.Position.Y,M2.Position.Z), TPoint3D.Create(0,0,0));
Segment.LocalMatrix.CreateRotation(Vector3DP1P2n, Angle);
Segment.LocalMatrix.CreateLookAtRH(TPoint3D.Create(M1.Position.X, M1.Position.Y, M1.Position.Z), TPoint3D.Create(M2.Position.X,M2.Position.Y,M2.Position.Z), TPoint3D.Create(0,0,0));

 

Edited by Gabor64738

Share this post


Link to post

The CreateXX are class functions. Therefore they don't change LocalMatrix itself, but return a new matrix instead.

 

I'm not sure if I understand you right, but it might be sufficient to set the RotationAngle of the StrokeCube according to the vector V between those two spheres. The Y value should get the angle (in degrees) in the X/Z plane, while the Z value should get the inclination angle to that plane (not tested).

Share this post


Link to post

Thanks for your reply, Uwe.

 

Setting StrokeCube.RotationAngle.X and StrokeCube.RotationAngle.Z would only work if these rotations were applied about the global X and Z axes. The first rotation about X works fine because the local X axis is the same as the global X axis (after the obligatory first step of StrokeCube.ResetRotationAngles), but the second rotation about Z happens about the rotated Z axis of the StrokeCube. The result is not pretty. Also, the order of the two rotations (X Z or Z X) gives two different results, as we know is the case with Euler angles.

 

The CreateRotation function will lead to the solution, but the (rotated) matrix it returns cannot be applied to a 3D object (StrokeCube) because a 3D object's matrix is read-only. By default, the only way the StrokeCube can be rotated is with its StrokeCube.RotationAngle, about the local X Y Z axes of the object. BUT, I found a promising lead last night (dawn), code that opens up the 4x4 matrix of a 3D object making it writeable! I will try this out and report back here.

 

Still, there may be an easy way to rotate a 3D object to align its Y axis with a line segment, using the local X and Z rotations of the object, correcting for the fact that the second axis is rotated due to the first rotation. If you know how to do it, please let us know.

Edited by Gabor64738

Share this post


Link to post

I actually looked at some JS code and Physics for Game Developers by Bourg for stuff on Quaternions.  The Model-3D sample may be a start. 

 

Years ago at a talk about making the Matrix movie the speaker said the stuff in background was all 2D "decals". And it takes three years of schooling for 3D. (Also even a graphic artist needs know linux to get hired.)  

 

 

procedure TModel3DTest.Form3DMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
begin
  if (ssLeft in Shift) and Mouse then
  begin
    DummyX.RotationAngle.X := DummyX.RotationAngle.X - (Y - Down.Y)* 0.3 ;
    DummyY.RotationAngle.Y := DummyY.RotationAngle.Y + (X - Down.X)* 0.3 ;
    Down.X := X;
    Down.Y := Y;
  end;
end;

 

Share this post


Link to post
3 hours ago, Gabor64738 said:

Setting StrokeCube.RotationAngle.X and StrokeCube.RotationAngle.Z

I didn't say anything about X:

10 hours ago, Uwe Raabe said:

The Y value should get the angle (in degrees) in the X/Z plane, while the Z value should get the inclination angle to that plane

 

Share this post


Link to post
1 hour ago, Uwe Raabe said:

I didn't say anything about X:

 

Yes, sorry, as I sunk back into the code, I moved away from what you said and ended up rotating about X and Z (not Y ). Now that I read your instruction more carefully, it does make perfect sense: rotate the StrokeCube about its vertical Y axis in the XZ plane. This rotation will keep the rotated Z axis in the XZ plane, so the next rotation about the Z axis will align the Y axis of the StrokeCube with the Vector. In fact, 90 degrees less rotation about Y can be followed by the next rotation about the rotated X axis, and will give the same aligned StrokeCube, but with 90 degrees rotated about its Y axis. I'll go back and try these, thanks.

 

In the meantime, I've explored directly writing the 3D matrix of the StrokeCube and it works wonderfully! The idea comes from Paul Toth and the trick is to use a class helper for TControl3D which makes the matrix writeable.

See https://github.com/tothpaul/Delphi/blob/master/Google Cardboard/4 FullDemo/Main.pas

 

I'm using FMX/FireMonkey in Delphi 10.3.3 where an object's 3D matrix is read-only. Has it been changed in a more recent version to writeable?

Share this post


Link to post
2 hours ago, Pat Foley said:

 


procedure TModel3DTest.Form3DMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
begin
  if (ssLeft in Shift) and Mouse then
  begin
    DummyX.RotationAngle.X := DummyX.RotationAngle.X - (Y - Down.Y)* 0.3 ;
    DummyY.RotationAngle.Y := DummyY.RotationAngle.Y + (X - Down.X)* 0.3 ;
    Down.X := X;
    Down.Y := Y;
  end;
end;

 

To be able to see the effects of my rotations, I do have the StrokeCube in a Dummy and I rotate it with this code, but only horizontally with the mouse. The vertical rotations I sort out by rotating the camera on a circle up and down. I made this a few years ago and it gives the exact behaviour one expects in a 3D world. Others suggested having two nested Dummies with the objects inside, one Dummy rotating in the X direction, other in the Y direction.

 

Using this logic to align my StrokeCube with a Vector is interesting...

Edited by Gabor64738

Share this post


Link to post
14 hours ago, Gabor64738 said:

...Now that I read your instruction more carefully, it does make perfect sense... I'll go back and try these, thanks.

Uwe's perfect solution deserves a medal! This is most probably the shortest, quickest and therefore most efficient solution. No need for changing the 3D matrix of the object with the class helper, which has some side effects, e.g. the RotationAngle returns zeros. Writing the matrix does have some advantages though, so it goes into my toolbox.

 

Here is the code and screenshot:

  // See thread: https://en.delphipraxis.net/topic/13848-rotate-a-cube-to-align-with-a-3d-line/

  // Create a Viewport3D.
  // As its child, create a Dummy.
  // As its children, create a StrokeCube (name: Segment) and two Spheres (names: M1 M2).
  // Make Segment Height 6, Width 2, Depth 1
  // Create a Cylinder as Segment's child, Height 6, Width 0.01, Depth 0.01
  // Make the M1 and M2 H/W/D 0.2
  // Position M1 and M2 randomly.
  // To test, change the position of M1 and/or M2 with a TrackBar and call this procedure in its OnChange event.

procedure TForm1.Rotate(Sender: TObject);
var
  M2M1distanceX, M2M1distanceZ, M2M1distanceXZ, M2M1distanceY: single;
begin
  // Translate Segment to M1.
  Segment.Position.X:= M1.Position.X;
  Segment.Position.Y:= M1.Position.Y;
  Segment.Position.Z:= M1.Position.Z;

  // Reset Segment's rotations to zero.
  Segment.ResetRotationAngle;

  // X distance between M2 and M1.
  M2M1distanceX:= M2.Position.X-M1.Position.X;
  // Z distance between M1 and M2.
  M2M1distanceZ:= M2.Position.Z-M1.Position.Z;
  // Rotate Segment about Y.
  Segment.RotationAngle.Y:= RadToDeg(arctan2(M2M1distanceX,M2M1distanceZ));

  // Distance between the projections of M1 and M2 on the XZ plane.
  M2M1distanceXZ:= sqrt(sqr(M2M1distanceX)+sqr(M2M1distanceZ));
  // Vertical distance between M1 and M2.
  M2M1distanceY:= M2.Position.Y-M1.Position.Y;
  // Rotate Segment about X.
  Segment.RotationAngle.X:= RadToDeg(arctan2(M2M1distanceXZ,M2M1distanceY));
end;

 

Align.jpg

Edited by Gabor64738

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×