[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:
- X=0, Y=-36, Z=32 levels
- X=0, Y=-36, Z=-32 levels
- 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:
- Extrinsic, left-handed angles utilized within the order of Z, Y, after which X
- 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]