In Unity, whenever I rotate an object, it also performs a translation. If I simply try to draw a quad with the quaternion used in Unity, it appears off because of that missing translation unity applies. I can 'replicate' it by performing each step in openGL in the same order that Unity does:
scale first,
(translation offset)
rotate Z
(translation offset)
rotate X
(translation offset)
rotate Y
(translation offset)
and final translation
My question is, given a quaternion that will be applied to a quad to rotate it, how can I also calculate this final translation that needs to be applied to the quad to mimic the behavior exhibited in Unity? Apologies if my methodology, terminology isn't the best. I welcome any advice and filling of knowledge gaps I might be displaying.
Examples:
In Unity, I simply change the position / scale / rotation in editor. Values for rotation displayed are simply Euler angles like how unity represents them in the editor.
(Notice the Y rotation before and after applying a y 290 degree Euler on it)
[
This raylib (wrapping OpenGL calls) replicates the behavior perfectly
void Draw3DBillboardRecUnity(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Vector3 eulerAngles, Vector3 scale, Color tint) {
rlPushMatrix();
Vector3 camPos = camera.position;
Vector3 camTar = camera.target;
Vector3 camUp = camera.up;
// get the camera view matrix
Matrix mat = MatrixInvert(MatrixLookAt(camPos, camTar, camUp));
// peel off just the rotation
Quaternion quat = QuaternionFromMatrix(mat);
mat = QuaternionToMatrix(quat);
// apply just the rotation
rlMultMatrixf(MatrixToFloat(mat));
// draw the billboard
float width = size.x / 2;
float height = size.y / 2;
// initial spot
rlScalef(1, 1, 1);
rlTranslatef(width, height, 0);
// null out position experiment
// position = Vector3Zero();
float positionOffsetX = position.x - width * 0.5;
float positionOffsetY = position.y - height * 0.5;
Vector3 positionOffsetVector;
positionOffsetVector.x = positionOffsetX;
positionOffsetVector.y = positionOffsetY;
positionOffsetVector.z = position.z;
Vector3 positionOffsetVectorInverse = Vector3Negate(positionOffsetVector);
// apply scaling first
if (vectorAvailable(scale)) {
rlScalef(scale.x, scale.y, scale.z);
if (vectorAvailable(position))
rlTranslatef(positionOffsetVector.x, positionOffsetVector.y, positionOffsetVector.z);
else
rlTranslatef(-width, -height, 0);
}
// apply rotation
if (rotVectorAvailable(eulerAngles)) {
// prepare yourself
rlTranslatef(positionOffsetVector.x, positionOffsetVector.y, positionOffsetVector.z);
if (eulerAngles.z > 180) {
// eh questionable
rlTranslatef(positionOffsetVector.x, positionOffsetVector.y, positionOffsetVector.z);
rlRotatef(180 - (eulerAngles.z - 180), 0, 0, 1);
rlRotatef(180 - (eulerAngles.z - 180), 0, 0, 1);
rlTranslatef(positionOffsetVectorInverse.x, positionOffsetVectorInverse.y, positionOffsetVectorInverse.z);
} else {
rlTranslatef(positionOffsetVector.x, positionOffsetVector.y, positionOffsetVector.z);
rlRotatef(eulerAngles.z, 0, 0, 1);
rlTranslatef(positionOffsetVectorInverse.x, positionOffsetVectorInverse.y, positionOffsetVectorInverse.z);
}
rlTranslatef(positionOffsetVector.x, positionOffsetVector.y, positionOffsetVector.z);
rlRotatef(eulerAngles.x, -1, 0, 0);
rlTranslatef(positionOffsetVectorInverse.x, positionOffsetVectorInverse.y, positionOffsetVectorInverse.z);
if (eulerAngles.y > 180) {
// greater than 180
rlTranslatef(positionOffsetVector.x, positionOffsetVector.y, positionOffsetVector.z);
rlRotatef(eulerAngles.y, 0, -1, 0);
rlTranslatef(positionOffsetVectorInverse.x, positionOffsetVectorInverse.y, positionOffsetVectorInverse.z);
} else {
// less than 180
rlTranslatef(positionOffsetVectorInverse.x, positionOffsetVectorInverse.y, positionOffsetVectorInverse.z);
rlRotatef(eulerAngles.y, 0, -1, 0);
rlTranslatef(positionOffsetVector.x, positionOffsetVector.y, positionOffsetVector.z);
}
// inverse
rlTranslatef(positionOffsetVectorInverse.x, positionOffsetVectorInverse.y, positionOffsetVectorInverse.z);
}
// apply transformation last
if (vectorAvailable(position)) {
rlTranslatef(positionOffsetVector.x, positionOffsetVector.y, positionOffsetVector.z);
}
Color color = tint;
rlCheckRenderBatchLimit(6);
rlSetTexture(texture.id);
// draw quad
rlBegin(RL_QUADS);
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
rlTexCoord2f((float)source.x / texture.width, (float)(source.y + source.height) / texture.height);
rlVertex3f(-width, -height, 0); // Bottom Left Of The Texture and Quad
rlTexCoord2f((float)(source.x + source.width) / texture.width, (float)(source.y + source.height) / texture.height);
rlVertex3f(+width, -height, 0); // Bottom Right Of The Texture and Quad
rlTexCoord2f((float)(source.x + source.width) / texture.width, (float)source.y / texture.height);
rlVertex3f(+width, +height, 0); // Top Right Of The Texture and Quad
rlTexCoord2f((float)source.x / texture.width, (float)source.y / texture.height);
rlVertex3f(-width, +height, 0); // Top Left Of The Texture and Quad
rlEnd();
rlSetTexture(0);
rlPopMatrix();
}
With this result:
However this code that simply applies the quaternion, does not.
void Draw3DBillboardRecAlmost(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Vector3 eulerAngles, Vector3 scale, Color tint) {
Vector2 sizeRatio = { size.x, size.y };
Quaternion tQuat = QuaternionFromEuler(eulerAngles.x*DEG2RAD, eulerAngles.y*-DEG2RAD, eulerAngles.z*-DEG2RAD);
Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up);
Vector3 right = { matView.m0, matView.m4, matView.m8 };
Vector3 up = { matView.m1, matView.m5, matView.m9 };
// rotate both up + right vectors to achieve same effect like unity
up = Vector3RotateByQuaternion(up, tQuat);
right = Vector3RotateByQuaternion(right, tQuat);
Vector3 rightScaled = Vector3Scale(right, sizeRatio.x/2);
Vector3 upScaled = Vector3Scale(up, sizeRatio.y/2);
Vector3 p1 = Vector3Add(rightScaled, upScaled);
Vector3 p2 = Vector3Subtract(rightScaled, upScaled);
Vector3 topLeft = Vector3Scale(p2, -1);
Vector3 topRight = p1;
Vector3 bottomRight = p2;
Vector3 bottomLeft = Vector3Scale(p1, -1);
// Translate points to the draw center (position)
topLeft = Vector3Add(topLeft, position);
topRight = Vector3Add(topRight, position);
bottomRight = Vector3Add(bottomRight, position);
bottomLeft = Vector3Add(bottomLeft, position);
rlCheckRenderBatchLimit(4);
rlSetTexture(texture.id);
rlBegin(RL_QUADS);
rlColor4ub(tint.r, tint.g, tint.b, tint.a);
// Bottom-left corner for texture and quad
rlTexCoord2f((float)source.x/texture.width, (float)source.y/texture.height);
rlVertex3f(topLeft.x, topLeft.y, topLeft.z);
// Top-left corner for texture and quad
rlTexCoord2f((float)source.x/texture.width, (float)(source.y + source.height)/texture.height);
rlVertex3f(bottomLeft.x, bottomLeft.y, bottomLeft.z);
// Top-right corner for texture and quad
rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)(source.y + source.height)/texture.height);
rlVertex3f(bottomRight.x, bottomRight.y, bottomRight.z);
// Bottom-right corner for texture and quad
rlTexCoord2f((float)(source.x + source.width)/texture.width, (float)source.y/texture.height);
rlVertex3f(topRight.x, topRight.y, topRight.z);
rlEnd();
rlSetTexture(0);
}
With this result (notice it missing the translation)
How can I get the below code to work like the above test case?
transform.position
gives the same value before and after the rotation). You can get a translation if you're rotating the parent of an object, where that child object is placed somewhere other than the parent's origin in the parent coordinate system. \$\endgroup\$