Customise Consent Preferences

We use cookies to help you navigate efficiently and perform certain functions. You will find detailed information about all cookies under each consent category below.

The cookies that are categorised as "Necessary" are stored on your browser as they are essential for enabling the basic functionalities of the site. ... 

Always Active

Necessary cookies are required to enable the basic features of this site, such as providing secure log-in or adjusting your consent preferences. These cookies do not store any personally identifiable data.

No cookies to display.

Functional cookies help perform certain functionalities like sharing the content of the website on social media platforms, collecting feedback, and other third-party features.

No cookies to display.

Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.

No cookies to display.

Performance cookies are used to understand and analyse the key performance indexes of the website which helps in delivering a better user experience for the visitors.

No cookies to display.

Advertisement cookies are used to provide visitors with customised advertisements based on the pages you visited previously and to analyse the effectiveness of the ad campaigns.

No cookies to display.

[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]

Leave a Reply

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