Files
cocos-engine/cocos/physics/framework/physics-selector.ts
2022-05-17 11:56:06 +08:00

444 lines
15 KiB
TypeScript

/*
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/* eslint-disable import/no-mutable-exports */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { EDITOR, TEST } from 'internal:constants';
import { legacyCC } from '../../core/global-exports';
import { IBaseConstraint, IPointToPointConstraint, IHingeConstraint, IConeTwistConstraint } from '../spec/i-physics-constraint';
import {
IBoxShape, ISphereShape, ICapsuleShape, ITrimeshShape, ICylinderShape,
IConeShape, ITerrainShape, ISimplexShape, IPlaneShape, IBaseShape,
} from '../spec/i-physics-shape';
import { IPhysicsWorld } from '../spec/i-physics-world';
import { IRigidBody } from '../spec/i-rigid-body';
import { errorID, IVec3Like, warn } from '../../core';
import { EColliderType, EConstraintType } from './physics-enum';
import { PhysicsMaterial } from '.';
export type IPhysicsEngineId = 'builtin' | 'cannon.js' | 'ammo.js' | 'physx' | string;
interface IPhysicsWrapperObject {
PhysicsWorld?: Constructor<IPhysicsWorld>,
RigidBody?: Constructor<IRigidBody>,
BoxShape?: Constructor<IBoxShape>,
SphereShape?: Constructor<ISphereShape>,
CapsuleShape?: Constructor<ICapsuleShape>,
TrimeshShape?: Constructor<ITrimeshShape>,
CylinderShape?: Constructor<ICylinderShape>,
ConeShape?: Constructor<IConeShape>,
TerrainShape?: Constructor<ITerrainShape>,
SimplexShape?: Constructor<ISimplexShape>,
PlaneShape?: Constructor<IPlaneShape>,
PointToPointConstraint?: Constructor<IPointToPointConstraint>,
HingeConstraint?: Constructor<IHingeConstraint>,
ConeTwistConstraint?: Constructor<IConeTwistConstraint>,
}
type IPhysicsBackend = { [key: string]: IPhysicsWrapperObject; }
interface IPhysicsSelector {
/**
* @en
* The id of the physics engine being used by the physics system.
* @zh
* 物理系统正在使用的物理引擎的唯一标志。
*/
readonly id: IPhysicsEngineId,
/**
* @en
* The wrapper of the physics engine being used by the physics system.
* @zh
* 物理系统使用的物理引擎的封装层。
*/
readonly wrapper: IPhysicsWrapperObject,
/**
* @en
* All physics engine backends that the physics module has registered.
* @zh
* 物理模块已注册的所有物理引擎后端。
*/
readonly backend: IPhysicsBackend,
/**
* @en
* An instance of the physical world through which you can access the lowlevel objects.
* @zh
* 物理世界实例,通过它可以访问到底层对象。
*/
readonly physicsWorld: IPhysicsWorld | null;
/**
* @en
* To register the backend, the system will use the last backend registered before initialization,
* and the registration after that needs to be switched manually.
* @zh
* 注册后端,系统将使用在初始化前注册的最后一个后端,此后注册的需要手动切换。
*/
register: (id: IPhysicsEngineId, wrapper: IPhysicsWrapperObject) => void,
/**
* @en
* Switch to the physics backend corresponding to the id in the registry.
* @zh
* 切换为注册表里对应 id 的物理后端。
*/
switchTo: (id: IPhysicsEngineId) => void,
// polyfill
[x: string]: any,
}
function updateLegacyMacro (id: string) {
legacyCC._global.CC_PHYSICS_BUILTIN = id === 'builtin';
legacyCC._global.CC_PHYSICS_CANNON = id === 'cannon.js';
legacyCC._global.CC_PHYSICS_AMMO = id === 'ammo.js';
}
function register (id: IPhysicsEngineId, wrapper: IPhysicsWrapperObject): void {
if (!EDITOR && !TEST) console.info(`[PHYSICS]: register ${id}.`);
selector.backend[id] = wrapper;
if (!selector.physicsWorld || selector.id === id) {
updateLegacyMacro(id);
const mutableSelector = selector as Mutable<IPhysicsSelector>;
mutableSelector.id = id;
mutableSelector.wrapper = wrapper;
}
}
export interface IWorldInitData {
gravity: IVec3Like;
allowSleep: boolean;
defaultMaterial: PhysicsMaterial;
}
let worldInitData: IWorldInitData | null;
function switchTo (id: IPhysicsEngineId) {
if (!selector.runInEditor) return;
const mutableSelector = selector as Mutable<IPhysicsSelector>;
if (selector.physicsWorld && id !== selector.id && selector.backend[id] != null) {
selector.physicsWorld.destroy();
if (!TEST) console.info(`[PHYSICS]: switch from ${selector.id} to ${id}.`);
updateLegacyMacro(id);
mutableSelector.id = id;
mutableSelector.wrapper = selector.backend[id];
mutableSelector.physicsWorld = createPhysicsWorld();
} else {
if (!EDITOR && !TEST) console.info(`[PHYSICS]: using ${id}.`);
mutableSelector.physicsWorld = createPhysicsWorld();
}
if (worldInitData) {
const world = mutableSelector.physicsWorld;
world.setGravity(worldInitData.gravity);
world.setAllowSleep(worldInitData.allowSleep);
world.setDefaultMaterial(worldInitData.defaultMaterial);
}
}
/**
* @en
* The physics selector is used to register and switch the physics engine backend.
* @zh
* 物理选择器用于注册和切换物理引擎后端。
*/
export const selector: IPhysicsSelector = {
id: '',
switchTo,
register,
wrapper: {} as any,
backend: {} as any,
physicsWorld: null as any,
/// hide for now ///
runInEditor: !EDITOR,
};
export function constructDefaultWorld (data: IWorldInitData) {
if (!worldInitData) worldInitData = data;
if (!selector.runInEditor) return;
if (!selector.physicsWorld) {
if (!TEST) console.info(`[PHYSICS]: using ${selector.id}.`);
const mutableSelector = selector as Mutable<IPhysicsSelector>;
const world = mutableSelector.physicsWorld = createPhysicsWorld();
world.setGravity(worldInitData.gravity);
world.setAllowSleep(worldInitData.allowSleep);
world.setDefaultMaterial(worldInitData.defaultMaterial);
}
}
/// Utility Function For Create Wrapper Entity ///
const FUNC = (...v: any) => 0 as any;
const ENTIRE_WORLD: IPhysicsWorld = {
impl: null,
setGravity: FUNC,
setAllowSleep: FUNC,
setDefaultMaterial: FUNC,
step: FUNC,
syncAfterEvents: FUNC,
syncSceneToPhysics: FUNC,
raycast: FUNC,
raycastClosest: FUNC,
emitEvents: FUNC,
destroy: FUNC,
};
enum ECheckType {
World,
RigidBody,
// COLLIDER //
BoxCollider,
SphereCollider,
CapsuleCollider,
MeshCollider,
CylinderCollider,
ConeCollider,
TerrainCollider,
SimplexCollider,
PlaneCollider,
// JOINT //
PointToPointConstraint,
HingeConstraint,
ConeTwistConstraint,
}
function check (obj: any, type: ECheckType) {
if (obj == null) {
if (selector.id) {
warn(`${selector.id} physics does not support ${ECheckType[type]}`);
} else {
errorID(9600);
}
return true;
}
return false;
}
export function createPhysicsWorld (): IPhysicsWorld {
if (check(selector.wrapper.PhysicsWorld, ECheckType.World)) {
return ENTIRE_WORLD;
}
return new selector.wrapper.PhysicsWorld!();
}
const ENTIRE_RIGID_BODY: IRigidBody = {
impl: null,
rigidBody: null as unknown as any,
isAwake: false,
isSleepy: false,
isSleeping: false,
initialize: FUNC,
onEnable: FUNC,
onDisable: FUNC,
onDestroy: FUNC,
setType: FUNC,
setMass: FUNC,
setLinearDamping: FUNC,
setAngularDamping: FUNC,
useGravity: FUNC,
setLinearFactor: FUNC,
setAngularFactor: FUNC,
setAllowSleep: FUNC,
wakeUp: FUNC,
sleep: FUNC,
clearState: FUNC,
clearForces: FUNC,
clearVelocity: FUNC,
setSleepThreshold: FUNC,
getSleepThreshold: FUNC,
getLinearVelocity: FUNC,
setLinearVelocity: FUNC,
getAngularVelocity: FUNC,
setAngularVelocity: FUNC,
applyForce: FUNC,
applyLocalForce: FUNC,
applyImpulse: FUNC,
applyLocalImpulse: FUNC,
applyTorque: FUNC,
applyLocalTorque: FUNC,
setGroup: FUNC,
getGroup: FUNC,
addGroup: FUNC,
removeGroup: FUNC,
setMask: FUNC,
getMask: FUNC,
addMask: FUNC,
removeMask: FUNC,
isUsingCCD: FUNC,
useCCD: FUNC,
};
export function createRigidBody (): IRigidBody {
if (check(selector.wrapper.RigidBody, ECheckType.RigidBody)) {
return ENTIRE_RIGID_BODY;
}
return new selector.wrapper.RigidBody!();
}
/// CREATE COLLIDER ///
const CREATE_COLLIDER_PROXY = { INITED: false };
interface IEntireShape extends IBoxShape, ISphereShape, ICapsuleShape,
ITrimeshShape, ICylinderShape, IConeShape, ITerrainShape, ISimplexShape, IPlaneShape { }
const ENTIRE_SHAPE: IEntireShape = {
impl: null,
collider: null as unknown as any,
attachedRigidBody: null,
initialize: FUNC,
onLoad: FUNC,
onEnable: FUNC,
onDisable: FUNC,
onDestroy: FUNC,
setGroup: FUNC,
getGroup: FUNC,
addGroup: FUNC,
removeGroup: FUNC,
setMask: FUNC,
getMask: FUNC,
addMask: FUNC,
removeMask: FUNC,
setMaterial: FUNC,
setAsTrigger: FUNC,
setCenter: FUNC,
getAABB: FUNC,
getBoundingSphere: FUNC,
updateSize: FUNC,
updateRadius: FUNC,
setRadius: FUNC,
setCylinderHeight: FUNC,
setDirection: FUNC,
setHeight: FUNC,
setShapeType: FUNC,
setVertices: FUNC,
setMesh: FUNC,
setTerrain: FUNC,
setNormal: FUNC,
setConstant: FUNC,
updateEventListener: FUNC,
};
export function createShape (type: EColliderType): IBaseShape {
initColliderProxy();
return CREATE_COLLIDER_PROXY[type]();
}
function initColliderProxy () {
if (CREATE_COLLIDER_PROXY.INITED) return;
CREATE_COLLIDER_PROXY.INITED = true;
CREATE_COLLIDER_PROXY[EColliderType.BOX] = function createBoxShape (): IBoxShape {
if (check(selector.wrapper.BoxShape, ECheckType.BoxCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.BoxShape!();
};
CREATE_COLLIDER_PROXY[EColliderType.SPHERE] = function createSphereShape (): ISphereShape {
if (check(selector.wrapper.SphereShape, ECheckType.SphereCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.SphereShape!();
};
CREATE_COLLIDER_PROXY[EColliderType.CAPSULE] = function createCapsuleShape (): ICapsuleShape {
if (check(selector.wrapper.CapsuleShape, ECheckType.CapsuleCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.CapsuleShape!();
};
CREATE_COLLIDER_PROXY[EColliderType.CYLINDER] = function createCylinderShape (): ICylinderShape {
if (check(selector.wrapper.CylinderShape, ECheckType.CylinderCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.CylinderShape!();
};
CREATE_COLLIDER_PROXY[EColliderType.CONE] = function createConeShape (): IConeShape {
if (check(selector.wrapper.ConeShape, ECheckType.ConeCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.ConeShape!();
};
CREATE_COLLIDER_PROXY[EColliderType.MESH] = function createTrimeshShape (): ITrimeshShape {
if (check(selector.wrapper.TrimeshShape, ECheckType.MeshCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.TrimeshShape!();
};
CREATE_COLLIDER_PROXY[EColliderType.TERRAIN] = function createTerrainShape (): ITerrainShape {
if (check(selector.wrapper.TerrainShape, ECheckType.TerrainCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.TerrainShape!();
};
CREATE_COLLIDER_PROXY[EColliderType.SIMPLEX] = function createSimplexShape (): ISimplexShape {
if (check(selector.wrapper.SimplexShape, ECheckType.SimplexCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.SimplexShape!();
};
CREATE_COLLIDER_PROXY[EColliderType.PLANE] = function createPlaneShape (): IPlaneShape {
if (check(selector.wrapper.PlaneShape, ECheckType.PlaneCollider)) { return ENTIRE_SHAPE; }
return new selector.wrapper.PlaneShape!();
};
}
/// CREATE CONSTRAINT ///
const CREATE_CONSTRAINT_PROXY = { INITED: false };
interface IEntireConstraint extends IPointToPointConstraint, IHingeConstraint, IConeTwistConstraint { }
const ENTIRE_CONSTRAINT: IEntireConstraint = {
impl: null,
initialize: FUNC,
onLoad: FUNC,
onEnable: FUNC,
onDisable: FUNC,
onDestroy: FUNC,
setEnableCollision: FUNC,
setConnectedBody: FUNC,
setPivotA: FUNC,
setPivotB: FUNC,
setAxis: FUNC,
};
export function createConstraint (type: EConstraintType): IBaseConstraint {
initConstraintProxy();
return CREATE_CONSTRAINT_PROXY[type]();
}
function initConstraintProxy () {
if (CREATE_CONSTRAINT_PROXY.INITED) return;
CREATE_CONSTRAINT_PROXY.INITED = true;
CREATE_CONSTRAINT_PROXY[EConstraintType.POINT_TO_POINT] = function createPointToPointConstraint (): IPointToPointConstraint {
if (check(selector.wrapper.PointToPointConstraint, ECheckType.PointToPointConstraint)) { return ENTIRE_CONSTRAINT; }
return new selector.wrapper.PointToPointConstraint!();
};
CREATE_CONSTRAINT_PROXY[EConstraintType.HINGE] = function createHingeConstraint (): IHingeConstraint {
if (check(selector.wrapper.HingeConstraint, ECheckType.HingeConstraint)) { return ENTIRE_CONSTRAINT; }
return new selector.wrapper.HingeConstraint!();
};
CREATE_CONSTRAINT_PROXY[EConstraintType.CONE_TWIST] = function createConeTwistConstraint (): IConeTwistConstraint {
if (check(selector.wrapper.ConeTwistConstraint, ECheckType.ConeTwistConstraint)) { return ENTIRE_CONSTRAINT; }
return new selector.wrapper.ConeTwistConstraint!();
};
}