Rotation et matrice TRS

Faire tourner un point autour d’un autre dans l’espace 3D n’est pas si simple si l’on veut gérer n’importe quel axe sans gimbal lock.
Le but de cette fonction est d’utiliser des maths pures, sans aucun Transform. Pas seulement pour la beauté du geste, mais surtout parce qu’on veut parfois calculer ce genre de chose sans utiliser, ni générer, de GameObjects vides. J’utilise des matrices, des quaternions et quelques maths de base. Voici les paramètres :
Vector3 pivotPoint(pivot de référence pour la rotation)Vector3 pointToRotate(point à faire tourner selon l’axe « haut » donné)Vector3 upNormalized(axe « haut »)Vector3 rotationAxis(rotation X, Y, Z à appliquer)
La façon habituelle de faire tourner des objets dans l’espace global
Avec un Transform, un simple transform.Rotate(X, Y, Z) suffit souvent pour le faire tourner, en local ou en global. En interne, pour un angle d’Euler (X, Y, Z) donné, Unity utilise ce genre de calcul pour faire tourner un Transform dans l’espace global :
Quaternion rotationToApply = Quaternion.Euler(X, Y, Z);
currentRotation = currentRotation * (Quaternion.Inverse(currentRotation) * rotationToApply * currentRotation);
currentRotation est la rotation de la matrice TRS du Transform.
Pour faire tourner un point autour d’un pivot dans l’espace global (dans un monde où Vector3.up est le vrai haut), les fonctions ci-dessous suffisent :
public static Vector3 RotatePointAroundPivot(Vector3 pivot, Vector3 point, Vector3 angles)
{
return RotatePointAroundPivot(point, pivot, Quaternion.Euler(angles));
}
public static Vector3 RotatePointAroundPivot(Vector3 pivot, Vector3 point, Quaternion rotation)
{
return rotation * (point - pivot) + pivot;
}
Comme je le disais, cette méthode fonctionne UNIQUEMENT dans un monde avec ces références :
- up:
Vector3.up (0, 1, 0) - forward:
Vector3.forward (0, 0, 1) - right:
Vector3.right (1, 0, 0)
Et si je veux tourner selon une autre référence de rotation ?
Eh bien, on utilise notre propre matrice pour tourner correctement depuis un espace local :


Fonctions complètes
Fonctions RotatePointAroundAxis() et RotateVectorAroundAxis()
/// <summary>
/// Rotate a Point around an axis
/// usage:
/// Vector3 position = ExtRotation.RotatePointAroundAxis(pivotPosition, pointToRotate, pivotUp, _rotateAxis * TimeEditor.deltaTime);
/// </summary>
public static Vector3 RotatePointAroundAxis(Vector3 pivotPoint, Vector3 pointToRotate, Vector3 upNormalized, Vector3 rotationAxis)
{
Vector3 vectorDirector = pointToRotate - pivotPoint;
Vector3 finalPoint = RotateWithMatrix(pivotPoint, vectorDirector, upNormalized, rotationAxis);
return (finalPoint);
}
/// <summary>
/// Rotate a vectorDirector around an axis
/// usage:
/// Vector3 vectorDirector = ExtRotation.RotateVectorAroundAxis(pivotPoint, vectorDirector, pivotUp, _rotateAxis * TimeEditor.deltaTime);
/// </summary>
public static Vector3 RotateVectorAroundAxis(Vector3 pivotPoint, Vector3 vectorDirector, Vector3 upNormalized, Vector3 rotationAxis)
{
Vector3 finalPoint = RotateWithMatrix(pivotPoint, vectorDirector, upNormalized, rotationAxis);
return (finalPoint - pivotPoint);
}
private static Vector3 RotateWithMatrix(Vector3 pivotPoint, Vector3 vectorDirector, Vector3 upNormalized, Vector3 rotationAxis)
{
Quaternion constrainRotation = TurretLookRotation(vectorDirector, upNormalized); //constrain rotation from up !!
//create a TRS matrix from point & rotation
Matrix4x4 rotationMatrix = Matrix4x4.TRS(pivotPoint, constrainRotation, Vector3.one);
Vector3 projectedForward = ExtVector3.ProjectAOnB(vectorDirector, rotationMatrix.ForwardFast());
Vector3 projectedUp = ExtVector3.ProjectAOnB(vectorDirector, upNormalized);
float distanceForward = projectedForward.magnitude;
float distanceUp = projectedUp.magnitude;
if (ExtVector3.DotProduct(upNormalized, vectorDirector) < 0)
{
distanceUp *= -1;
}
//rotate matrix in x, y & z
rotationMatrix = Matrix4x4.TRS(pivotPoint, constrainRotation * Quaternion.Euler(rotationAxis), Vector3.one);
Vector3 finalPoint = rotationMatrix.MultiplyPoint3x4(new Vector3(0, distanceUp, distanceForward));
return finalPoint;
}
public static Quaternion TurretLookRotation(Vector3 approximateForward, Vector3 exactUp)
{
Quaternion rotateZToUp = Quaternion.LookRotation(exactUp, -approximateForward);
Quaternion rotateYToZ = Quaternion.Euler(90f, 0f, 0f);
return rotateZToUp * rotateYToZ;
}
public static Vector3 ProjectAOnB(Vector3 A, Vector3 B)
{
float sqrMag = DotProduct(B, B);
if (sqrMag < Mathf.Epsilon)
{
return (Vector3.zero);
}
else
{
var dot = DotProduct(A, B);
return new Vector3(B.x * dot / sqrMag,
B.y * dot / sqrMag,
B.z * dot / sqrMag);
}
}
public static float DotProduct(Vector3 a, Vector3 b)
{
return (a.x * b.x + a.y * b.y + a.z * b.z);
}
Comment ça marche ?

D’abord, récapitulons ce dont on a besoin
Vector3 pivotPoint(pivot de référence pour la rotation)Vector3 pointToRotate(point à faire tourner selon l’axe « haut » donné)Vector3 upNormalized(axe « haut »)Vector3 rotationAxis(rotation X, Y, Z à appliquer)
À partir de là, on peut visualiser le vecteur « haut » et calculer le vecteur directeur : Vector3 vectorDirector = pointToRotate - pivotPoint;
Créer une matrice TRS
Une matrice TRS représente une position, une rotation et une échelle dans le monde. On a la position Vector3 pivotPoint, l’échelle vaut Vector3.one, mais il manque la rotation. Il faut transformer notre vectorDirector en quaternion.
Ce quaternion doit être aligné sur le vecteur « haut ». Il est représenté par la flèche bleue sur l’image ci-dessous. La fonction suivante fait exactement cela : Quaternion constrainRotation = TurretLookRotation(vectorDirector, upNormalized);

Créons maintenant notre matrice : Matrix4x4.TRS(pivotPoint, constrainRotation, Vector3.one);
Pour récupérer les vecteurs avant, droite et haut d’une matrice, j’ai créé des extensions pratiques :
Matrix4x4.UpFast()
Matrix4x4.ForwardFast()
Matrix4x4.RightFast()
Elles appliquent simplement un Matrix4x4.GetColumn(i) pour obtenir l’axe voulu.

Récupérons ces vecteurs jaunes pour plus tard
La projection d’un vecteur se fait avec cette fonction : Vector3 projectedVector = ProjectAOnB(Vector3 A, Vector3 B)
Il nous faut les normes avant et haut. On calcule donc les deux vecteurs, en tenant compte de l’éventuelle valeur négative pour le haut :
Vector3 projectedForward = ProjectAOnB(vectorDirector, rotationMatrix.ForwardFast());
Vector3 projectedUp = ProjectAOnB(vectorDirector, upNormalized);
float distanceForward = projectedForward.magnitude;
float distanceUp = projectedUp.magnitude;
if (DotProduct(upNormalized, vectorDirector) < 0)
{
distanceUp *= -1;
}

Appliquer la rotation à la matrice
Plus que deux étapes. On peut enfin faire tourner la matrice. Par simplicité, j’ai choisi de créer une autre matrice, avec un nouveau décalage de rotation :
rotationMatrix = Matrix4x4.TRS(pivotPoint, constrainRotation * Quaternion.Euler(rotationAxis), Vector3.one);

Obtenir le point final
Et après tout ce travail, on peut utiliser la fonction MultiplyPoint3x4, qui transforme nos distances locales haut et avant en un point final global :
Vector3 finalPoint = rotationMatrix.MultiplyPoint3x4(new Vector3(0, distanceUp, distanceForward));



