1eb05132f1
build / setup (compute matrix) (pull_request) Has been cancelled
build / ${{ matrix.label }} (pull_request) Has been cancelled
build / macos-15-arm64-studio (pull_request) Has been cancelled
build / ubuntu-24.04-clang-18-studio (pull_request) Has been cancelled
build / ubuntu-24.04-gcc-14-studio (pull_request) Has been cancelled
build / windows-2025-ninja-studio (pull_request) Has been cancelled
build / ubuntu-24.04-clang-18-wasm (pull_request) Has been cancelled
build / ubuntu-24.04-clang-18-mjx (pull_request) Has been cancelled
lint / pre-commit (pull_request) Has been cancelled
55 lines
2.6 KiB
TypeScript
55 lines
2.6 KiB
TypeScript
import {LoadingManager,type Material,type Mesh,type Texture} from 'three';
|
|
import {OBJExporter} from 'three/addons/exporters/OBJExporter.js';
|
|
import {ColladaLoader} from 'three/addons/loaders/ColladaLoader.js';
|
|
|
|
const TRANSPARENT_PIXEL='data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
|
|
|
/**
|
|
* 将 Collada 几何转换为 MuJoCo WASM 可读取的 OBJ。
|
|
* ColladaLoader 会先统一为 Y-up;额外旋转到 MuJoCo 使用的 Z-up,并烘焙节点变换与单位缩放。
|
|
*/
|
|
export function convertDaeToObj(data:Uint8Array,path:string):Uint8Array {
|
|
const manager=new LoadingManager();
|
|
// 转换只需要几何。拦截贴图 URL,避免为浏览器内存文件发起无效网络请求。
|
|
manager.setURLModifier(()=>TRANSPARENT_PIXEL);
|
|
const loader=new ColladaLoader(manager);
|
|
const text=new TextDecoder('utf-8').decode(data);
|
|
const xml=new DOMParser().parseFromString(text,'application/xml');
|
|
if(xml.querySelector('parsererror'))throw new Error('Collada XML 格式无效');
|
|
const upAxis=xml.getElementsByTagName('up_axis')[0]?.textContent?.trim().toUpperCase()??'Y_UP';
|
|
// 禁用 ColladaLoader 自带的 Z-up → Y-up 旋转,改为直接统一到 MuJoCo 的 Z-up。
|
|
if(upAxis==='Z_UP')xml.getElementsByTagName('up_axis')[0]!.textContent='Y_UP';
|
|
const normalized=new XMLSerializer().serializeToString(xml);
|
|
const result=loader.parse(normalized,path.slice(0,path.lastIndexOf('/')+1));
|
|
if(!result?.scene)throw new Error('Collada 文件无法解析');
|
|
const scene=result.scene;
|
|
if(upAxis==='Y_UP')scene.rotation.x+=Math.PI/2;
|
|
else if(upAxis==='X_UP')scene.rotation.y-=Math.PI/2;
|
|
scene.updateMatrixWorld(true);
|
|
let meshCount=0;
|
|
scene.traverse(object=>{
|
|
const mesh=object as Mesh;
|
|
if(!mesh.isMesh)return;
|
|
meshCount+=1;
|
|
const materials=Array.isArray(mesh.material)?mesh.material:[mesh.material];
|
|
for(const material of materials)if(material)material.name='';
|
|
});
|
|
if(!meshCount)throw new Error('Collada 文件不包含可转换的三角网格');
|
|
try {
|
|
const output=new OBJExporter().parse(scene);
|
|
if(!/^v\s/m.test(output)||!/^f\s/m.test(output))throw new Error('Collada 文件未生成有效三角面');
|
|
return new TextEncoder().encode(output);
|
|
} finally {
|
|
scene.traverse(object=>{
|
|
const mesh=object as Mesh;
|
|
if(!mesh.isMesh)return;
|
|
mesh.geometry?.dispose();
|
|
const materials:Material[]=Array.isArray(mesh.material)?mesh.material:[mesh.material];
|
|
for(const material of materials){
|
|
for(const value of Object.values(material))if(value&&typeof value==='object'&&(value as Texture).isTexture)(value as Texture).dispose();
|
|
material.dispose();
|
|
}
|
|
});
|
|
}
|
|
}
|