arithmetic – Is that this gimbal lock I am hitting even with rotation matrices / quaternions?

[ad_1]

I am spawning a set off field relative to a different object utilizing native offset. The engine I am utilizing doesn’t present a local operate to make that conversion (no less than in a manner accessible to me), so I’m performing the conversion from native offset to world coordinates manually. The engine offers world rotation of objects in Euler angles, which is topic to gimbal lock, so I am utilizing the 3×3 rotation matrix from the article’s remodel as a substitute.

Nevertheless, I nonetheless appear to be experiencing what I imagine is gimbal lock even when utilizing the 3×3 rotation matrix relatively than Euler angles. Usually when I’ve an object solely rotated on one or two axes, the set off is spawned accurately (within the “push” place of the lever):


Nevertheless, there are specific rotations the place native to world area conversion will get confused and the set off doesn’t spawn in the best location.

The set off spawns close to the middle of the lever relatively than within the desired location. These levers’ world rotations (from nearest to furthest from the digital camera) are:

  1. X=0, Y=-36, Z=32 levels
  2. X=0, Y=-36, Z=-32 levels
  3. X=0, Y=36, Z=32 levels

Although I’m not utilizing Euler angles to compute the local->world transformation, it appears to be like like that is nonetheless gimbal lock? Is that correct, or is there one other error that would trigger this? The code I’m utilizing to make the transformation:

struct Quaternion {
    double w, x, y, z;
};

Quaternion matrixToQuaternion(const RE::NiMatrix3& m) {
    Quaternion q;
    double hint = m.entry[0][0] + m.entry[1][1] + m.entry[2][2];

    if (hint > 0) {
        double s = 0.5 / sqrt(hint + 1.0);
        q.w = 0.25 / s;
        q.x = (m.entry[2][1] - m.entry[1][2]) * s;
        q.y = (m.entry[0][2] - m.entry[2][0]) * s;
        q.z = (m.entry[1][0] - m.entry[0][1]) * s;
    } else {
        if (m.entry[0][0] > m.entry[1][1] && m.entry[0][0] > m.entry[2][2]) {
            double s = 2.0 * sqrt(1.0 + m.entry[0][0] - m.entry[1][1] - m.entry[2][2]);
            q.w = (m.entry[2][1] - m.entry[1][2]) / s;
            q.x = 0.25 * s;
            q.y = (m.entry[0][1] + m.entry[1][0]) / s;
            q.z = (m.entry[0][2] + m.entry[2][0]) / s;
        } else if (m.entry[1][1] > m.entry[2][2]) {
            double s = 2.0 * sqrt(1.0 + m.entry[1][1] - m.entry[0][0] - m.entry[2][2]);
            q.w = (m.entry[0][2] - m.entry[2][0]) / s;
            q.x = (m.entry[0][1] + m.entry[1][0]) / s;
            q.y = 0.25 * s;
            q.z = (m.entry[1][2] + m.entry[2][1]) / s;
        } else {
            double s = 2.0 * sqrt(1.0 + m.entry[2][2] - m.entry[0][0] - m.entry[1][1]);
            q.w = (m.entry[1][0] - m.entry[0][1]) / s;
            q.x = (m.entry[0][2] + m.entry[2][0]) / s;
            q.y = (m.entry[1][2] + m.entry[2][1]) / s;
            q.z = 0.25 * s;
        }
    }

    return q;
}

RE::NiPoint3 rotateQ(const Quaternion& q, const RE::NiPoint3& v) {
    RE::NiPoint3 u = {(float)q.x, (float)q.y, (float)q.z};
    RE::NiPoint3 uv = {u.y * v.z - u.z * v.y, u.z * v.x - u.x * v.z, u.x * v.y - u.y * v.x};
    RE::NiPoint3 uuv = {u.y * uv.z - u.z * uv.y, u.z * uv.x - u.x * uv.z, u.x * uv.y - u.y * uv.x};

    return v + ((uv * (float)q.w) + uuv) * 2.0f;
}

RE::NiPoint3 localToWorld(RE::NiPoint3 localOffset, RE::TESObjectREFR* relativeTo) {
    // Get object's rotation matrix and convert it to a quaternion for simpler use
    RE::NiTransform remodel;
    relativeTo->GetTransform(remodel);
    Quaternion q = matrixToQuaternion(remodel.rotate);

    RE::NiPoint3 rotatedOffset = rotateQ(q, localOffset);
    return relativeTo->GetPosition() + rotatedOffset;
}

For reference, I am utilizing the Creation Engine whose axial implementation (in accordance with these docs) is:

  1. Extrinsic, left-handed angles utilized within the order of Z, Y, after which X
  2. X+ is true, Y+ is forwards, Z+ is up

and I’ve seen that even utilizing an Euler angles transformation that applies the angles in the best order nonetheless hits this downside. Papyrus operate tailored from right here:

float[] operate GetPosXYZRotateAroundRef(ObjectReference akOrigin, ObjectReference akObject, float fOffsetX, float fOffsetY, float fOffsetZ)
        ; flip angles because it's left-handed
        float fAngleX = -akOrigin.GetAngleX()
        float fAngleY = -akOrigin.GetAngleY()
        float fAngleZ = -akOrigin.GetAngleZ()

        float myOriginPosX = akOrigin.GetPositionX()
        float myOriginPosY = akOrigin.GetPositionY()
        float myOriginPosZ = akOrigin.GetPositionZ()

        float fInitialX = fOffsetX
        float fInitialY = fOffsetY
        float fInitialZ = fOffsetZ

        float fNewX
        float fNewY
        float fNewZ

        ;Objects in Skyrim are rotated so as of Z, Y, X, so we'll do this right here as effectively.

        ;Z-axis rotation matrix
        float fVectorX = fInitialX
        float fVectorY = fInitialY
        float fVectorZ = fInitialZ
        fNewX = (fVectorX * Math.cos(fAngleZ)) + (fVectorY * Math.sin(-fAngleZ)) + (fVectorZ * 0)
        fNewY = (fVectorX * Math.sin(fAngleZ)) + (fVectorY * Math.cos(fAngleZ)) + (fVectorZ * 0)
        fNewZ = (fVectorX * 0) + (fVectorY * 0) + (fVectorZ * 1)        

        ;Y-axis rotation matrix
        fVectorX = fNewX
        fVectorY = fNewY
        fVectorZ = fNewZ
        fNewX = (fVectorX * Math.cos(fAngleY)) + (fVectorY * 0) + (fVectorZ * Math.sin(fAngleY))
        fNewY = (fVectorX * 0) + (fVectorY * 1) + (fVectorZ * 0)
        fNewZ = (fVectorX * Math.sin(-fAngleY)) + (fVectorY * 0) + (fVectorZ * Math.cos(fAngleY))

        ;X-axis rotation matrix
        fVectorX = fNewX
        fVectorY = fNewY
        fVectorZ = fNewZ        
        fNewX = (fVectorX * 1) + (fVectorY * 0) + (fVectorZ * 0)
        fNewY = (fVectorX * 0) + (fVectorY * Math.cos(fAngleX)) + (fVectorZ * Math.sin(-fAngleX))
        fNewZ = (fVectorX * 0) + (fVectorY * Math.sin(fAngleX)) + (fVectorZ * Math.cos(fAngleX))

        ;Return consequence
        float[] fNewPos = new float[3]
        fNewPos[0] = fNewX + myOriginPosX
        fNewPos[1] = fNewY + myOriginPosY
        fNewPos[2] = fNewZ + myOriginPosZ
        return fNewPos
endFunction

I am barely skeptical that that is gimbal lock because it occurs each with a rotation matrix transformation & Euler angles utilized within the right order, however I am not an knowledgeable in linear algebra. Is that this gimbal lock, or another subject? Is there a strategy to work round it?

[ad_2]

Categories:

Leave a Reply

Your email address will not be published. Required fields are marked *