Rotation trouble

In my procedural trees, each node has a rotation (around the parent branch) and angle (from the parent branch) I use CalcRotationVector below:

function RotateVectorY(const Vector: TVector3;
                            Angle : Single) : TVector3;
{ rotate around Y axis, used for determining rotation axis on rotates }
var CosTheta, SinTheta: Single;
 // Calculate the cosine and sine of the angle
 SinCos(Angle, CosTheta, SinTheta);
 // Apply the rotation matrix
 Result := Vector;
 Result.X := Vector.X * CosTheta + Vector.Z * SinTheta;
 Result.Z := -Vector.X * SinTheta + Vector.Z * CosTheta;

function CalcRotationVector( angle, rotate : single ) : TVector4;
                                              var RotationAxis : TVector3;
                                              AngleAxis : TVector3;
   RotationAxis := RotateVectorY(Vector3(0, 0, 1), rotate);
   Result := Vector4(RotationAxis.X, RotationAxis.Y, RotationAxis.Z, angle);

to calculate the rotation vector to set the branch’s transform.rotation to. Works great.

Now I have added animals. They also have a rotation (heading) and angle (forward/backward, as they go up down terrain). I use the same function. But with the animals, the heading value is ignored. I only see the angle forward/back on the terrain. They are always facing toward direction 0. Can you see anything wrong with what I am doing here?

I figured it out with a little help from chatgpt. This works:

function CombineRotations(const Quaternion1, Quaternion2: TQuaternion): TQuaternion;
  Result := Quaternion1 * Quaternion2;

function CalcRotationVector( angle, rotate : single ) : TVector4;
 var r1, r2 : TVector4;
     q1, q2, qc : TQuaternion;
     axis : TVector3;
   q1 := QuatFromAxisAngle(Vector3(0, 1, 0), rotate );
   q2 := QuatFromAxisAngle(Vector3(1, 0, 0), angle );
   qc := q1 * q2;
   qc.ToAxisAngle(Axis, angle);
   Result := Vector4( Axis.x, axis.y, axis.z, angle );

ChatGPT knows castle game engine!


The 2nd version is also a bit clearer to me – that is indeed how you combine rotations, by multiplying quaternions.

Another solution could also be a deeper hierarchy: you can place TCastleTransform with 1 rotation under another TCastleTransform with 2nd rotation etc. The transformation hierarchy can have any depth, anything to be comfortable to use.

This can be shortened to

( using dedicated overload function Vector4(const V: TVector3; const W: Single): TVector4; ).

:slight_smile: Yeah. I’m already accustomed to Copilot completing my code :slight_smile:

Thanks for the tips. It is working with the quaternions, I don’t want to add a transform because want to keep these animals as light as possible so there can be many. Is there a way to ask a TCastleScene how much memory it requires? I have already found some animals that give me out of heap memory just loading them. Is the killer the textures? Or the polycount?

( To anyone that finds this thread: the memory usage talk moved to this thread: Memory use of models ).

I can’t help reminding of my .RotationAngle property

CalcRotationVector( angle, rotate : single )

You see, on the conceptual level the vector (direction) and the angle are still different things and have to be reasoned about as different entities. And there are cases (like simple joints) where you would not ever change the once calculated vector, only angles, having explicit access top angle-and-only-angle would make a less error-pronce and easier to use API :smiley:

To deconstruct / construct between axis+angle rotation represented as 3D + float and 4D we already have functionality at TVector4 API level. Here are various things you can do:

  Rotation: TVector4;
  RotationAxis: TVector3;
  RotationAngle: Single;
  Rotation := MyTransform.Rotation;
  RotationAxis := Rotation.XYZ;
  RotationAngle := Rotation.W;
  RotationAngle += RotationSpeed * SecondsPassed;
  Rotation := Vector4(RotationAxis, RotationAngle);
  MyTransform.Rotation := Vector4(
    MyTransform.Rotation.W + 
    RotationSpeed * SecondsPassed);

As for .RotationAngle property (from what I recall, you proposed TCastleTransform:RotationAngle:Single as a shortcut for TCastleTransform:Rotation.W):

After thinking about it in the past I rejected it. It would be a very small convenience in some cases but it may also cause confusion. It would not be obvious does setting RotationAngle keep the axis unchanged? Or does it set it to 0,0,1 (natural for 2D)?

And if it keeps it unchanged, then users will also be surprised that by default, it does nothing, as axis is zero.

Experience shows that such “convenience shortcuts” can effectively have negative effect on API. Better to keep things explicit.

Note that you can implement your own .RotationAngle (using class helper to look just like a property on TCastleTransform) on top of CGE if it makes sense in your game.