Files
esengine/scripts/test-animation-t0.mjs
YHH 828ff969e1 feat(3d): FBX/GLTF/OBJ 加载器与骨骼动画支持 (#315)
* feat(3d): FBX/GLTF/OBJ 加载器与骨骼动画支持

* chore: 更新 pnpm-lock.yaml

* fix: 移除未使用的变量和方法

* fix: 修复 mesh-3d-editor tsconfig 引用路径

* fix: 修复正则表达式 ReDoS 漏洞
2025-12-23 15:34:01 +08:00

144 lines
4.4 KiB
JavaScript

/**
* Test Animation at t=0
* 测试 t=0 时的动画值
*
* Compare animation values at t=0 with node.transform
*/
import { readFileSync } from 'fs';
const filePath = process.argv[2] || 'F:\\MyProject4\\assets\\octopus.fbx';
console.log(`Testing animation at t=0: ${filePath}\n`);
async function main() {
const { FBXLoader } = await import('../packages/asset-system/dist/index.js');
const binaryData = readFileSync(filePath);
const loader = new FBXLoader();
const context = {
metadata: {
path: filePath,
name: filePath.split(/[\\/]/).pop(),
type: 'model/fbx',
guid: '',
size: binaryData.length,
hash: '',
dependencies: [],
lastModified: Date.now(),
importerVersion: '1.0.0',
labels: [],
tags: [],
version: 1
},
loadDependency: async () => null
};
const content = {
type: 'binary',
binary: binaryData.buffer
};
const asset = await loader.parse(content, context);
if (!asset.animations || asset.animations.length === 0) {
console.log('No animation data!');
return;
}
const clip = asset.animations[0];
const nodes = asset.nodes;
const skeleton = asset.skeleton;
console.log(`Animation: "${clip.name}", duration: ${clip.duration}s`);
console.log(`Channels: ${clip.channels.length}, Samplers: ${clip.samplers.length}`);
// Sample animation at t=0
function sampleAtT0(sampler, componentCount) {
if (!sampler.output || sampler.output.length === 0) return null;
const result = [];
for (let i = 0; i < componentCount; i++) {
result.push(sampler.output[i]);
}
return result;
}
// Get animated transforms at t=0
const animTransforms = new Map();
for (const channel of clip.channels) {
const sampler = clip.samplers[channel.samplerIndex];
if (!sampler) continue;
const nodeIndex = channel.target.nodeIndex;
const path = channel.target.path;
let value;
if (path === 'rotation') {
value = sampleAtT0(sampler, 4);
} else {
value = sampleAtT0(sampler, 3);
}
if (!value) continue;
if (!animTransforms.has(nodeIndex)) {
animTransforms.set(nodeIndex, {});
}
const t = animTransforms.get(nodeIndex);
if (path === 'translation') t.position = value;
else if (path === 'rotation') t.rotation = value;
else if (path === 'scale') t.scale = value;
}
console.log(`\nAnimated node count at t=0: ${animTransforms.size}`);
// Compare with node.transform for first few skeleton joints
if (skeleton) {
console.log(`\n=== COMPARING ANIMATION vs NODE.TRANSFORM ===\n`);
let matchCount = 0;
let mismatchCount = 0;
const mismatches = [];
for (let i = 0; i < skeleton.joints.length; i++) {
const joint = skeleton.joints[i];
const node = nodes[joint.nodeIndex];
const animT = animTransforms.get(joint.nodeIndex);
if (!node || !animT) continue;
// Compare rotation (most important)
const nodeRot = node.transform.rotation;
const animRot = animT.rotation;
if (animRot) {
const rotMatch = nodeRot.every((v, idx) => Math.abs(v - animRot[idx]) < 0.001);
if (rotMatch) {
matchCount++;
} else {
mismatchCount++;
mismatches.push({ jointIndex: i, name: joint.name, nodeRot, animRot });
}
}
}
console.log(`Rotation matches: ${matchCount}/${matchCount + mismatchCount}`);
if (mismatches.length > 0) {
console.log(`\n❌ MISMATCHES found!`);
console.log(`First 5 mismatches:`);
for (let i = 0; i < 5 && i < mismatches.length; i++) {
const m = mismatches[i];
console.log(`\n Joint[${m.jointIndex}] "${m.name}":`);
console.log(` node.rotation: [${m.nodeRot.map(v => v.toFixed(4)).join(', ')}]`);
console.log(` anim.rotation: [${m.animRot.map(v => v.toFixed(4)).join(', ')}]`);
}
} else {
console.log(`\n✅ All rotations match at t=0!`);
}
}
console.log('\nDone!');
}
main().catch(console.error);