Skip to main content
Cleanup
Source Link
DMGregory
  • 136.3k
  • 22
  • 247
  • 373

EDIT: this is for a texture on a quad, so 2D in 3D

In unityUnity, 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 unityUnity does:

scale first,

(translation offset)

rotate Z

(translation offset)

rotate X (translation offset)

rotate Y

(translation offset)

and final transformation,

  • 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 unityUnity? Apologies if my methodology, terminology isn't the best. I welcome any advice and filling of knowledge gaps I might be displaying.

EDIT:

Examples:

Examples: In unityIn 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.

enter image description here [Unrotated sprite1

enter image description hereRotated sprite

THISThis raylib (wrapping OpenGL calls) replicates the behavior perfectly

enter image description hereRendered result

enter image description hereUnwanted Result

EDIT: this is for a texture on a quad, so 2D in 3D

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 transformation,

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.

EDIT:

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.

enter image description here

enter image description here

THIS raylib (wrapping OpenGL calls) replicates the behavior perfectly

enter image description here

enter image description here

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.

[Unrotated sprite1

Rotated sprite

This raylib (wrapping OpenGL calls) replicates the behavior perfectly

Rendered result

Unwanted Result

added 4 characters in body
Source Link

(Notice the Y axisrotation before and after applying a y 290 degree Euler on it)

(Notice the Y axis before and after applying a y 290 degree Euler on it)

(Notice the Y rotation before and after applying a y 290 degree Euler on it)

added 8385 characters in body
Source Link

EDIT:

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 axis before and after applying a y 290 degree Euler on it)

enter image description here

enter image description here

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:

enter image description here

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?

enter image description here

EDIT:

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 axis before and after applying a y 290 degree Euler on it)

enter image description here

enter image description here

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:

enter image description here

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?

enter image description here

added 54 characters in body
Source Link
Loading
added 25 characters in body
Source Link
Loading
Source Link
Loading