看见反常 · 动一处 · 抓证据
看到哪种故障,就从哪段代码开始查
6 个代码锚点 · 只读 · 与实验共用模型
这页解决什么
这里不是要你背实现。每段代码只回答一个问题:这条约束究竟在哪里生效?
角色转身时被拉长?
检查四元数是否保持单位长度。
// SECTION:quaternion
function quaternionFromAxisAngle(axis, angle) {
const half = angle * 0.5;
const s = Math.sin(half);
return axis === 'y'
? { x: 0, y: s, z: 0, w: Math.cos(half) }
: { x: s, y: 0, z: 0, w: Math.cos(half) };
}
物体搬动后位置不对?
检查平移、缩放和旋转的组合顺序。
// SECTION:matrix
function composeAffine2D(translation, scale, angle) {
const c = Math.cos(angle), s = Math.sin(angle);
return [scale.x * c, -scale.y * s, translation.x,
scale.x * s, scale.y * c, translation.y,
0, 0, 1];
}
方向也被一起搬走?
检查点与方向的 w 身份。
// SECTION:homogeneous
function transformHomogeneous2D(matrix, value) {
return {
x: matrix[0] * value.x + matrix[1] * value.y + matrix[2] * value.w,
y: matrix[3] * value.x + matrix[4] * value.y + matrix[5] * value.w,
w: matrix[6] * value.x + matrix[7] * value.y + matrix[8] * value.w,
};
}
function perspectiveDivide2D(clip) {
if (Math.abs(clip.w) < 1e-6) return undefined;
// Keep the sign. A negative divisor must not be hidden by a positive clamp.
return { x: clip.x / clip.w, y: clip.y / clip.w };
}
关节塌陷或飞点?
沿 inverse bind、当前全局骨骼、palette、权重、模型到世界与法线切线逐站检查。
// SECTION:skinning
function buildSkinPalette(localBones, parentIndices, inverseBind) {
const currentGlobal = new Array(localBones.length);
for (let index = 0; index < localBones.length; index += 1) {
const local = localBones[index];
const parent = parentIndices[index];
currentGlobal[index] = parent < 0 ? local : currentGlobal[parent] * local;
}
return currentGlobal.map((globalBone, index) => {
return globalBone * inverseBind[index];
});
}
function skinVertexToWorld(vertex, influences, palette, objectToWorld) {
let positionModel = vec4(0, 0, 0, 0);
let blendedLinear = mat3(0);
for (const { boneIndex, weight } of influences) {
positionModel += weight * (palette[boneIndex] * vertex.positionBind);
blendedLinear += weight * mat3(palette[boneIndex]);
}
const positionWorld = objectToWorld * positionModel;
// Common per-vertex approximation. Varying weights and non-rigid scale need
// project-specific normal validation instead of copying the position path.
const worldLinear = mat3(objectToWorld) * blendedLinear;
const normalWorld = normalize(transpose(inverse(worldLinear)) * vertex.normalBind);
const tangentCandidate = worldLinear * vertex.tangentBind.xyz;
const tangentWorld = normalize(tangentCandidate
- normalWorld * dot(normalWorld, tangentCandidate));
const bitangentWorld = cross(normalWorld, tangentWorld) * vertex.tangentBind.w;
return { positionWorld, normalWorld, tangentWorld, bitangentWorld };
}
轨迹没有经过预期位置?
检查控制点与曲线进度 t。
// SECTION:bezier
function cubicBezier(p0, p1, p2, p3, t) {
const u = 1 - t;
return u ** 3 * p0 + 3 * u ** 2 * t * p1
+ 3 * u * t ** 2 * p2 + t ** 3 * p3;
}
手臂碰不到目标?
比较可达范围、迭代次数和剩余误差。
// SECTION:ik
function fabrik(root, target, lengths, iterations) {
let joints = seedChain(root, target, lengths);
for (let pass = 0; pass < iterations; pass += 1) {
joints = backwardReach(joints, target, lengths);
joints = forwardReach(joints, root, lengths);
}
return { joints, error: distance(joints.at(-1), target) };
}