Gabor64738 0 Posted Friday at 07:29 PM (edited) 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 Friday at 08:27 PM by Gabor64738 Share this post Link to post
Uwe Raabe 2165 Posted yesterday at 08:59 AM 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
Gabor64738 0 Posted 20 hours ago (edited) 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 20 hours ago by Gabor64738 Share this post Link to post
Pat Foley 54 Posted 18 hours ago 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
Uwe Raabe 2165 Posted 17 hours ago 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
Gabor64738 0 Posted 15 hours ago 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
Gabor64738 0 Posted 15 hours ago (edited) 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 15 hours ago by Gabor64738 Share this post Link to post
Gabor64738 0 Posted 13 hours ago (edited) 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; Edited 43 minutes ago by Gabor64738 Share this post Link to post