Files
Mujoco_WASM/wasm/web_platform/src/simulation/PhysicsAdapter.ts
T

79 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import loadMujoco, {type MainModule} from '@mujoco/mujoco';
import type {ProjectManifest} from '../project/types';
import {prepareProjectForMujoco} from '../project/importer';
import {groundConvertedMjcf,type UrdfBaseMode} from '../project/urdfToMjcf';
import {MemfsWorkspace} from '../project/workspace';
import {SimulationSession, type FrameResult, type SimulationSnapshot} from './SimulationSession';
export type UrdfLoadMode='mjcf'|'native';
export type {UrdfBaseMode};
export interface PhysicsAdapter {
load(manifest:ProjectManifest,entryPath:string,urdfMode?:UrdfLoadMode,baseMode?:UrdfBaseMode):Promise<SimulationSnapshot>;
advance(now: number): FrameResult;
snapshot(): SimulationSnapshot | null;
setPaused(paused: boolean): void;
setSpeed(speed: number): void;
reset(): void;
singleStep(): void;
setActuator(id: number, value: number): void;
setJointPosition(id:number,value:number):boolean;
resetJoints():void;
setIgnoreJointLimits(ignore:boolean):void;
setExternalForce(bodyId: number, force: [number, number, number]): void;
clearExternalForce(): void;
dispose(): void;
}
let modulePromise: Promise<MainModule> | undefined;
export function getMujocoModule(): Promise<MainModule> {
if (!modulePromise) {
console.info('[MuJoCo] 开始初始化单线程 WASM');
modulePromise = loadMujoco().then((module) => {
console.info('[MuJoCo] WASM 初始化完成');
return module;
});
}
return modulePromise;
}
export class MainThreadPhysicsAdapter implements PhysicsAdapter {
session: SimulationSession | null = null;
workspace: MemfsWorkspace | null = null;
async load(manifest:ProjectManifest,entryPath:string,urdfMode:UrdfLoadMode='mjcf',baseMode:UrdfBaseMode='floating'):Promise<SimulationSnapshot> {
this.releaseCurrent();const module=await getMujocoModule();const workspace=new MemfsWorkspace(module,manifest.id);const prepared=prepareProjectForMujoco(manifest,entryPath);
try{
console.info('[MuJoCo] 写入 MEMFS',prepared.manifest.files.length);workspace.mount(prepared.manifest);
const entry=prepared.manifest.entries.find(candidate=>candidate.path===entryPath);let modelPath=workspace.path(entryPath);const warnings=[...prepared.warnings];
if(entry?.format==='urdf'&&urdfMode==='mjcf'){
console.info('[MuJoCo] 编译 URDF 中间模型',entryPath);const intermediate=new SimulationSession(module,modelPath);
try{
const minimumZ=intermediate.minimumGeometryZ();const slash=entryPath.lastIndexOf('/');const directory=slash>=0?entryPath.slice(0,slash+1):'';const convertedPath=`${directory}.__mujoco_converted_${manifest.id.replace(/[^a-zA-Z0-9_-]/g,'_')}.xml`;
if(module.mj_saveLastXML(workspace.path(convertedPath),intermediate.model)===0)throw new Error('MuJoCo 无法导出中间 MJCF');
workspace.writeGenerated(convertedPath,groundConvertedMjcf(new TextEncoder().encode(workspace.readText(convertedPath)),minimumZ,baseMode));modelPath=workspace.path(convertedPath);
warnings.push(`URDF 已转换为 MJCF${baseMode==='floating'?'浮动基座':'固定基座'}),并整体平移 ${(-minimumZ).toFixed(4)} m,使最低点接触 z=0 地面`);
}finally{intermediate.dispose();}
}
console.info('[MuJoCo] 编译模型',modelPath);const session=new SimulationSession(module,modelPath,warnings);
if(entry?.format==='urdf'&&urdfMode==='native'){const offset=session.alignLowestPointToGround();warnings.push(`原生 URDF 已整体平移 ${offset.toFixed(4)} m,使最低点位于 z=0`);}
if(warnings.length)console.warn('[MuJoCo] URDF 兼容处理',warnings);
console.info('[MuJoCo] 模型编译完成');this.workspace=workspace;this.session=session;const snapshot=session.snapshot();console.info('[MuJoCo] 状态快照完成');return snapshot;
}catch(error){workspace.dispose();throw new Error(`模型编译失败(${entryPath}):${error instanceof Error?error.message:String(error)}`,{cause:error});}
}
advance(now:number):FrameResult{return this.session?.advance(now)??{steps:0,stepMs:0,overBudget:false};}
snapshot():SimulationSnapshot|null{return this.session?.snapshot()??null;}
setPaused(value:boolean):void{this.session?.setPaused(value);}
setSpeed(value:number):void{this.session?.setSpeed(value);}
reset():void{this.session?.reset();}
singleStep():void{this.session?.singleStep();}
setActuator(id:number,value:number):void{this.session?.setActuator(id,value);}
setJointPosition(id:number,value:number):boolean{return this.session?.setJointPosition(id,value)??false;}
resetJoints():void {this.session?.resetJoints();}
setIgnoreJointLimits(ignore:boolean):void {this.session?.setIgnoreJointLimits(ignore);}
setExternalForce(bodyId:number,force:[number,number,number]):void{this.session?.setExternalForce(bodyId,force);}
clearExternalForce():void{this.session?.clearExternalForce();}
private releaseCurrent():void{this.session?.dispose(); this.session=null; this.workspace?.dispose(); this.workspace=null;}
dispose():void{this.releaseCurrent();}
}