413 lines
14 KiB
TypeScript
413 lines
14 KiB
TypeScript
/**
|
||
* WASM 物理引擎实现
|
||
* 提供与 pinball-physics WASM 模块的接口
|
||
*/
|
||
|
||
import { wasmLoader } from '../../../Utils/WasmLoader';
|
||
import { BodyId, PhysicsBodyData, PhysicsSettings, Vector2, WorldId } from '../Core/GameData';
|
||
import { IPhysicsEngine } from '../Core/IPhysicsEngine';
|
||
import { WasmExports, WasmModuleState } from './PhysicsTypes';
|
||
|
||
export class WasmPhysicsEngine implements IPhysicsEngine {
|
||
private wasmModule: WebAssembly.Module | null = null;
|
||
private wasmInstance: WebAssembly.Instance | null = null;
|
||
private wasmExports: WasmExports | null = null;
|
||
private state: WasmModuleState = WasmModuleState.UNLOADED;
|
||
private worlds: Map<WorldId, boolean> = new Map();
|
||
private bodies: Map<WorldId, Map<BodyId, boolean>> = new Map();
|
||
|
||
/**
|
||
* 初始化 WASM 物理引擎
|
||
* @param settings 物理设置
|
||
* @param wasmFactory WASM工厂函数(可选,推荐使用)
|
||
*/
|
||
async initialize(settings: PhysicsSettings ): Promise<void> {
|
||
if (this.state === WasmModuleState.LOADED) {
|
||
return;
|
||
}
|
||
|
||
this.state = WasmModuleState.LOADING;
|
||
|
||
try {
|
||
await this.initializeLegacy();
|
||
this.state = WasmModuleState.LOADED;
|
||
console.log('WASM Physics Engine initialized successfully');
|
||
|
||
} catch (error) {
|
||
this.state = WasmModuleState.ERROR;
|
||
console.error('Failed to initialize WASM Physics Engine:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建 WASM 导入对象
|
||
*/
|
||
private createImportObject(): WebAssembly.Imports {
|
||
return {
|
||
env: {
|
||
// 内存管理
|
||
memory: new WebAssembly.Memory({
|
||
initial: 10,
|
||
maximum: 100
|
||
}),
|
||
|
||
// 日志函数
|
||
console_log: (ptr: number, len: number) => {
|
||
// 可以实现 WASM 的日志输出
|
||
},
|
||
|
||
// 错误处理
|
||
abort: (msg: number, file: number, line: number, col: number) => {
|
||
console.error('WASM abort:', { msg, file, line, col });
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 使用 WasmLoader 初始化(推荐方式)
|
||
*/
|
||
private async initializeWithWasmLoader(wasmFactory: any): Promise<void> {
|
||
// 初始化 WASM 加载器
|
||
wasmLoader.initialize();
|
||
|
||
// 检查平台支持
|
||
if (!wasmLoader.isWasmSupported()) {
|
||
throw new Error('当前平台不支持 WASM,需要提供 ASM 回退选项');
|
||
}
|
||
|
||
// 加载 WASM 模块
|
||
// 注意:在实际使用时需要提供正确的 editorUuid
|
||
const instance = await wasmLoader.loadSimpleWasm(
|
||
wasmFactory,
|
||
'pinball_physics.wasm',
|
||
undefined, // editorUuid,需要在实际使用时提供
|
||
'wasm' // bundleName
|
||
);
|
||
|
||
this.wasmExports = instance as WasmExports;
|
||
}
|
||
|
||
/**
|
||
* 旧的初始化方法(保持向后兼容)
|
||
*/
|
||
private async initializeLegacy(): Promise<void> {
|
||
// 加载 WASM 文件
|
||
const result = await wasmLoader.loadWasmWithAsmFallback(
|
||
wasmFactory, // WASM 工厂函数
|
||
asmFactory, // ASM 工厂函数
|
||
'pinball_physics.wasm', // WASM 文件名
|
||
'pinball_physics.asm.mem', // ASM 内存文件名
|
||
'44cacb3c-e901-455d-b3e1-1c38a69718e1', // WASM UUID
|
||
'3400003e-dc3c-43c1-8757-3e082429125a', // ASM UUID
|
||
'wasmFiles' // Bundle 名称
|
||
);
|
||
|
||
console.log('模块加载成功,使用的是:', result.isWasm ? 'WASM' : 'ASM');
|
||
console.log('模块实例:', result.instance);
|
||
|
||
if (!wasmResponse.ok) {
|
||
throw new Error(`Failed to fetch WASM file: ${wasmResponse.statusText}`);
|
||
}
|
||
|
||
const wasmBytes = await wasmResponse.arrayBuffer();
|
||
|
||
// 编译 WASM 模块
|
||
this.wasmModule = await WebAssembly.compile(wasmBytes);
|
||
|
||
// 创建实例
|
||
const importObject = this.createImportObject();
|
||
this.wasmInstance = await WebAssembly.instantiate(this.wasmModule, importObject);
|
||
this.wasmExports = this.wasmInstance.exports as unknown as WasmExports;
|
||
}
|
||
|
||
/**
|
||
* 检查 WASM 是否已加载
|
||
*/
|
||
private ensureLoaded(): void {
|
||
if (this.state !== WasmModuleState.LOADED || !this.wasmExports) {
|
||
throw new Error('WASM Physics Engine not loaded. Call initialize() first.');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建物理世界
|
||
*/
|
||
async createWorld(gravity: Vector2): Promise<WorldId> {
|
||
this.ensureLoaded();
|
||
|
||
const worldId = this.wasmExports!.pinball_create_world(gravity.x, gravity.y);
|
||
this.worlds.set(worldId, true);
|
||
this.bodies.set(worldId, new Map());
|
||
|
||
console.log(`Created physics world ${worldId} with gravity (${gravity.x}, ${gravity.y})`);
|
||
return worldId;
|
||
}
|
||
|
||
/**
|
||
* 销毁物理世界
|
||
*/
|
||
async destroyWorld(worldId: WorldId): Promise<void> {
|
||
if (this.worlds.has(worldId)) {
|
||
this.worlds.delete(worldId);
|
||
this.bodies.delete(worldId);
|
||
console.log(`Destroyed physics world ${worldId}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 执行物理步进
|
||
*/
|
||
async step(worldId: WorldId): Promise<void> {
|
||
this.ensureLoaded();
|
||
|
||
if (!this.worlds.has(worldId)) {
|
||
throw new Error(`World ${worldId} does not exist`);
|
||
}
|
||
|
||
this.wasmExports!.pinball_step_world(worldId);
|
||
}
|
||
|
||
/**
|
||
* 创建动态刚体
|
||
*/
|
||
async createDynamicBody(worldId: WorldId, position: Vector2, radius: number): Promise<BodyId> {
|
||
this.ensureLoaded();
|
||
|
||
if (!this.worlds.has(worldId)) {
|
||
throw new Error(`World ${worldId} does not exist`);
|
||
}
|
||
|
||
const bodyId = this.wasmExports!.pinball_create_dynamic_body(worldId, position.x, position.y);
|
||
|
||
// 记录刚体
|
||
const worldBodies = this.bodies.get(worldId)!;
|
||
worldBodies.set(bodyId, true);
|
||
|
||
console.log(`Created dynamic body ${bodyId} at (${position.x}, ${position.y}) with radius ${radius}`);
|
||
return bodyId;
|
||
}
|
||
|
||
/**
|
||
* 创建静态刚体(暂时使用动态刚体实现)
|
||
*/
|
||
async createStaticBody(worldId: WorldId, position: Vector2, radius: number): Promise<BodyId> {
|
||
// 目前 WASM 中只有 pinball_create_dynamic_body,后续可以扩展
|
||
return this.createDynamicBody(worldId, position, radius);
|
||
}
|
||
|
||
/**
|
||
* 销毁刚体(目前 WASM 中没有此函数,仅从记录中移除)
|
||
*/
|
||
async destroyBody(worldId: WorldId, bodyId: BodyId): Promise<void> {
|
||
const worldBodies = this.bodies.get(worldId);
|
||
if (worldBodies && worldBodies.has(bodyId)) {
|
||
worldBodies.delete(bodyId);
|
||
console.log(`Removed body ${bodyId} from tracking (WASM destroy not implemented yet)`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取刚体位置
|
||
*/
|
||
async getBodyPosition(worldId: WorldId, bodyId: BodyId): Promise<Vector2 | null> {
|
||
this.ensureLoaded();
|
||
|
||
const worldBodies = this.bodies.get(worldId);
|
||
if (!worldBodies || !worldBodies.has(bodyId)) {
|
||
return null;
|
||
}
|
||
|
||
const x = this.wasmExports!.pinball_get_body_x(worldId, bodyId);
|
||
const y = this.wasmExports!.pinball_get_body_y(worldId, bodyId);
|
||
|
||
return { x, y };
|
||
}
|
||
|
||
/**
|
||
* 设置刚体位置(目前 WASM 中没有此函数)
|
||
*/
|
||
async setBodyPosition(worldId: WorldId, bodyId: BodyId, position: Vector2): Promise<void> {
|
||
// 目前 WASM 中没有 pinball_set_body_position 函数
|
||
console.warn('setBodyPosition not implemented in WASM yet');
|
||
}
|
||
|
||
/**
|
||
* 获取刚体速度(目前 WASM 中没有此函数)
|
||
*/
|
||
async getBodyVelocity(worldId: WorldId, bodyId: BodyId): Promise<Vector2 | null> {
|
||
// 目前 WASM 中没有速度获取函数
|
||
console.warn('getBodyVelocity not implemented in WASM yet');
|
||
return { x: 0, y: 0 };
|
||
}
|
||
|
||
/**
|
||
* 设置刚体速度(目前 WASM 中没有此函数)
|
||
*/
|
||
async setBodyVelocity(worldId: WorldId, bodyId: BodyId, velocity: Vector2): Promise<void> {
|
||
// 目前 WASM 中没有 pinball_set_body_velocity 函数
|
||
console.warn('setBodyVelocity not implemented in WASM yet');
|
||
}
|
||
|
||
/**
|
||
* 获取所有物理体数据
|
||
*/
|
||
async getAllBodies(worldId: WorldId): Promise<PhysicsBodyData[]> {
|
||
const worldBodies = this.bodies.get(worldId);
|
||
if (!worldBodies) {
|
||
return [];
|
||
}
|
||
|
||
const bodiesData: PhysicsBodyData[] = [];
|
||
|
||
for (const bodyId of worldBodies.keys()) {
|
||
const position = await this.getBodyPosition(worldId, bodyId);
|
||
const velocity = await this.getBodyVelocity(worldId, bodyId);
|
||
|
||
if (position && velocity) {
|
||
bodiesData.push({
|
||
id: bodyId,
|
||
position,
|
||
velocity,
|
||
rotation: 0,
|
||
angularVelocity: 0,
|
||
bodyType: 'circle',
|
||
radius: 0.5, // 默认半径,可以后续改进
|
||
isStatic: false // 可以根据需要区分静态和动态
|
||
});
|
||
}
|
||
}
|
||
|
||
return bodiesData;
|
||
}
|
||
|
||
/**
|
||
* 创建圆形刚体 (新接口方法)
|
||
*/
|
||
createCircle(options: import('../Core/IPhysicsEngine').CreateCircleOptions): BodyId {
|
||
this.ensureLoaded();
|
||
|
||
// 由于目前使用第一个世界,获取第一个世界ID
|
||
const firstWorldId = this.worlds.keys().next().value;
|
||
if (firstWorldId === undefined) {
|
||
throw new Error('No physics world created');
|
||
}
|
||
|
||
// 目前WASM只支持动态刚体创建
|
||
const bodyId = this.wasmExports!.pinball_create_dynamic_body(firstWorldId, options.position.x, options.position.y);
|
||
|
||
this.bodies.get(firstWorldId)!.set(bodyId, true);
|
||
return bodyId;
|
||
}
|
||
|
||
/**
|
||
* 创建矩形刚体 (新接口方法)
|
||
*/
|
||
createBox(options: import('../Core/IPhysicsEngine').CreateBoxOptions): BodyId {
|
||
this.ensureLoaded();
|
||
|
||
const firstWorldId = this.worlds.keys().next().value;
|
||
if (firstWorldId === undefined) {
|
||
throw new Error('No physics world created');
|
||
}
|
||
|
||
// 目前WASM只支持动态刚体创建,矩形用动态刚体模拟
|
||
const bodyId = this.wasmExports!.pinball_create_dynamic_body(firstWorldId, options.position.x, options.position.y);
|
||
|
||
this.bodies.get(firstWorldId)!.set(bodyId, true);
|
||
return bodyId;
|
||
}
|
||
|
||
/**
|
||
* 移除刚体 (新接口方法)
|
||
*/
|
||
removeBody(bodyId: BodyId): void {
|
||
// 查找包含此bodyId的世界
|
||
for (const [worldId, worldBodies] of this.bodies) {
|
||
if (worldBodies.has(bodyId)) {
|
||
worldBodies.delete(bodyId);
|
||
// 注意:这里需要WASM支持删除函数
|
||
// this.wasmExports!.pinball_remove_body(worldId, bodyId);
|
||
console.log(`Body ${bodyId} removed from world ${worldId}`);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取刚体数据 (新接口方法)
|
||
*/
|
||
getBodyData(bodyId: BodyId): PhysicsBodyData | null {
|
||
// 查找包含此bodyId的世界
|
||
for (const [worldId, worldBodies] of this.bodies) {
|
||
if (worldBodies.has(bodyId)) {
|
||
// 这里需要同步调用WASM函数获取数据
|
||
try {
|
||
const x = this.wasmExports!.pinball_get_body_x(worldId, bodyId);
|
||
const y = this.wasmExports!.pinball_get_body_y(worldId, bodyId);
|
||
// WASM暂时不支持速度获取,使用默认值
|
||
const vx = 0;
|
||
const vy = 0;
|
||
|
||
return {
|
||
id: bodyId,
|
||
position: { x, y },
|
||
velocity: { x: vx, y: vy },
|
||
rotation: 0, // 暂时固定值
|
||
angularVelocity: 0, // 暂时固定值
|
||
bodyType: 'circle',
|
||
radius: 0.5, // 暂时固定值
|
||
isStatic: false // 暂时固定值
|
||
};
|
||
} catch (error) {
|
||
console.error(`Failed to get body data for body ${bodyId}:`, error);
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 清理资源 (新接口方法)
|
||
*/
|
||
cleanup(): void {
|
||
this.dispose(); // 异步转同步
|
||
}
|
||
|
||
/**
|
||
* 清理资源
|
||
*/
|
||
async dispose(): Promise<void> {
|
||
this.worlds.clear();
|
||
this.bodies.clear();
|
||
this.wasmInstance = null;
|
||
this.wasmExports = null;
|
||
this.wasmModule = null;
|
||
this.state = WasmModuleState.UNLOADED;
|
||
|
||
console.log('WASM Physics Engine disposed');
|
||
}
|
||
|
||
/**
|
||
* 获取当前状态
|
||
*/
|
||
getState(): WasmModuleState {
|
||
return this.state;
|
||
}
|
||
|
||
/**
|
||
* 检查平台是否支持WASM(使用WasmLoader)
|
||
*/
|
||
isWasmSupported(): boolean {
|
||
return wasmLoader.isWasmSupported();
|
||
}
|
||
|
||
/**
|
||
* 获取推荐的加载策略
|
||
*/
|
||
getRecommendedStrategy(): 'wasm' | 'asm' | 'unsupported' {
|
||
return wasmLoader.getRecommendedStrategy();
|
||
}
|
||
} |