Files
cocos-engine/platforms/native/engine/jsb-physics.js
2021-07-23 16:29:27 +08:00

651 lines
22 KiB
JavaScript

'use strict';
const jsbPhy = globalThis['jsb.physics'];
jsbPhy['CACHE'] = {
trimesh: {},
convex: {},
height: {},
material: {},
};
jsbPhy['OBJECT'] = {
books: [],
ptrToObj: {},
raycastOptions: {
origin: null, unitDir: null,
distance: 0,
mask: 0,
queryTrigger: true,
},
};
jsbPhy['CONFIG'] = {
heightScale: 1 / 5000,
};
const books = jsbPhy['OBJECT'].books;
const ptrToObj = jsbPhy['OBJECT'].ptrToObj;
const raycastOptions = jsbPhy['OBJECT'].raycastOptions;
const TriggerEventObject = {
type: 'onTriggerEnter',
selfCollider: null,
otherCollider: null,
impl: null,
};
const CollisionEventObject = {
type: 'onCollisionEnter',
selfCollider: null,
otherCollider: null,
contacts: [],
impl: null,
};
function emitTriggerEvent (t, c0, c1, impl) {
TriggerEventObject.type = t;
TriggerEventObject.impl = impl;
if (c0.needTriggerEvent) {
TriggerEventObject.selfCollider = c0;
TriggerEventObject.otherCollider = c1;
c0.emit(t, TriggerEventObject);
}
if (c1.needTriggerEvent) {
TriggerEventObject.selfCollider = c1;
TriggerEventObject.otherCollider = c0;
c1.emit(t, TriggerEventObject);
}
}
const quat = new cc.Quat();
const contactsPool = [];
const contactBufferElementLength = 12;
class ContactPoint {
constructor(e) {
this.event = e;
this.impl = null;
this.colliderA = null;
this.colliderB = null;
this.index = 0;
}
get isBodyA () { return this.colliderA.uuid === this.event.selfCollider.uuid; }
getLocalPointOnA (o) {
this.getWorldPointOnB(o);
cc.Vec3.subtract(o, o, this.colliderA.node.worldPosition);
}
getLocalPointOnB (o) {
this.getWorldPointOnB(o);
cc.Vec3.subtract(o, o, this.colliderB.node.worldPosition);
}
getWorldPointOnA (o) {
this.getWorldPointOnB(o);
}
getWorldPointOnB (o) {
const i = this.index * contactBufferElementLength;
o.x = this.impl[i]; o.y = this.impl[i + 1]; o.z = this.impl[i + 2];
}
getLocalNormalOnA (o) {
this.getWorldNormalOnA(o);
cc.Quat.conjugate(quat, this.colliderA.node.worldRotation);
cc.Vec3.transformQuat(o, o, quat);
}
getLocalNormalOnB (o) {
this.getWorldNormalOnB(o);
cc.Quat.conjugate(quat, this.colliderB.node.worldRotation);
cc.Vec3.transformQuat(out, out, quat);
}
getWorldNormalOnA (o) {
this.getWorldNormalOnB(o);
if (!this.isBodyA) cc.Vec3.negate(o, o);
}
getWorldNormalOnB (o) {
const i = this.index * contactBufferElementLength + 3;
o.x = this.impl[i]; o.y = this.impl[i + 1]; o.z = this.impl[i + 2];
}
}
function emitCollisionEvent (t, c0, c1, impl, b) {
CollisionEventObject.type = t;
CollisionEventObject.impl = impl;
const contacts = CollisionEventObject.contacts;
contactsPool.push.apply(contactsPool, contacts);
contacts.length = 0;
const contactCount = b.length / contactBufferElementLength;
for (let i = 0; i < contactCount; i++) {
let c = contactsPool.length > 0 ? contactsPool.pop() : new ContactPoint(CollisionEventObject);
c.colliderA = c0; c.colliderB = c1;
c.impl = b; c.index = i; contacts.push(c);
}
if (c0.needCollisionEvent) {
CollisionEventObject.selfCollider = c0;
CollisionEventObject.otherCollider = c1;
c0.emit(t, CollisionEventObject);
}
if (c1.needCollisionEvent) {
CollisionEventObject.selfCollider = c1;
CollisionEventObject.otherCollider = c0;
c1.emit(t, CollisionEventObject);
}
}
class PhysicsWorld {
get impl () { return this._impl }
constructor() { this._impl = new jsbPhy.World(); }
setGravity (v) {
this._impl.setGravity(v.x, v.y, v.z);
}
setAllowSleep (v) {
this._impl.setAllowSleep(v);
}
setDefaultMaterial (v) { }
step (f, t, m) {
// books.forEach((v) => { v.syncToNativeTransform(); });
this._impl.step(f);
}
raycast (r, o, p, rs) {
raycastOptions.origin = r.o;
raycastOptions.unitDir = r.d;
raycastOptions.mask = o.mask >>> 0;
raycastOptions.distance = o.maxDistance;
raycastOptions.queryTrigger = !!o.queryTrigger;
const isHit = this._impl.raycast(raycastOptions);
if (isHit) {
const hits = this._impl.raycastResult();
for (let i = 0; i < hits.length; i++) {
const hit = hits[i];
const out = p.add();
out._assign(hit.hitPoint, hit.distance, ptrToObj[hit.shape].collider, hit.hitNormal);
rs.push(out);
}
}
return isHit;
}
raycastClosest (r, o, out) {
raycastOptions.origin = r.o;
raycastOptions.unitDir = r.d;
raycastOptions.mask = o.mask >>> 0;
raycastOptions.distance = o.maxDistance;
raycastOptions.queryTrigger = !!o.queryTrigger;
const isHit = this._impl.raycastClosest(raycastOptions);
if (isHit) {
const hit = this._impl.raycastClosestResult();
out._assign(hit.hitPoint, hit.distance, ptrToObj[hit.shape].collider, hit.hitNormal);
}
return isHit;
}
emitEvents () {
this.emitTriggerEvent();
this.emitCollisionEvent();
this._impl.emitEvents();
}
syncSceneToPhysics () {
this._impl.syncSceneToPhysics();
}
syncAfterEvents () {
// this._impl.syncSceneToPhysics()
}
destroy () { this._impl.destroy() }
emitTriggerEvent () {
const teps = this._impl.getTriggerEventPairs();
const len = teps.length / 3;
for (let i = 0; i < len; i++) {
const t = i * 3;
const sa = ptrToObj[teps[t + 0]], sb = ptrToObj[teps[t + 1]];
if (!sa || !sb) continue;
const c0 = sa.collider, c1 = sb.collider;
if (!(c0 && c0.isValid && c1 && c1.isValid)) continue;
if (!c0.needTriggerEvent && !c1.needTriggerEvent) continue;
const state = teps[t + 2];
if (state === 1) {
emitTriggerEvent('onTriggerStay', c0, c1, teps);
} else if (state === 0) {
emitTriggerEvent('onTriggerEnter', c0, c1, teps);
} else {
emitTriggerEvent('onTriggerExit', c0, c1, teps);
}
}
}
emitCollisionEvent () {
const ceps = this._impl.getContactEventPairs();
const len2 = ceps.length / 4;
for (let i = 0; i < len2; i++) {
const t = i * 4;
const sa = ptrToObj[ceps[t + 0]], sb = ptrToObj[ceps[t + 1]];
if (!sa || !sb) continue;
const c0 = sa.collider, c1 = sb.collider;
if (!(c0 && c0.isValid && c1 && c1.isValid)) continue;
if (!c0.needCollisionEvent && !c1.needCollisionEvent) continue;
const state = ceps[t + 2];
if (state === 1) {
emitCollisionEvent('onCollisionStay', c0, c1, ceps, ceps[t + 3]);
} else if (state === 0) {
emitCollisionEvent('onCollisionEnter', c0, c1, ceps, ceps[t + 3]);
} else {
emitCollisionEvent('onCollisionExit', c0, c1, ceps, ceps[t + 3]);
}
}
}
}
function bookNode (v) {
if (v._physicsBookIndex === undefined) {
v._physicsBookIndex = books.length;
books.push(v);
}
}
function unBookNode (v) {
const index = v._physicsBookIndex;
if (index !== undefined) {
books[index] = books[books.length - 1];
books[index]._physicsBookIndex = index;
books.length -= 1;
v._physicsBookIndex = undefined;
}
}
function updateCollisionMatrix () {
const phy = cc.PhysicsSystem.instance;
const world = phy.physicsWorld.impl;
const cm = phy.collisionMatrix;
if (cm.updateArray && cm.updateArray.length > 0) {
cm.updateArray.forEach((e) => {
const key = `${1 << e}`;
const mask = cm[key];
world.setCollisionMatrix(e, mask);
});
cm.updateArray.length = 0;
}
}
class RigidBody {
get impl () { return this._impl }
get rigidBody () { return this._com }
get isAwake () { return this._impl.isAwake(); }
get isSleepy () { return false }
get isSleeping () { return this._impl.isSleeping(); }
constructor() {
updateCollisionMatrix();
this._impl = new jsbPhy.RigidBody();
this._isUsingCCD = false;
}
initialize (v) {
v.node.updateWorldTransform();
this._com = v;
this._impl.initialize(v.node.native, v.type, v._group);
bookNode(v.node);
}
onEnable () {
this.setType(this._com.type);
this.setMass(this._com.mass);
this.setAllowSleep(this._com.allowSleep);
this.setLinearDamping(this._com.linearDamping);
this.setAngularDamping(this._com.angularDamping);
this.setLinearFactor(this._com.linearFactor);
this.setAngularFactor(this._com.angularFactor);
this.useGravity(this._com.useGravity);
this._impl.onEnable();
}
onDisable () { this._impl.onDisable(); }
onDestroy () {
unBookNode(this._com.node);
this._impl.onDestroy();
}
setGroup (v) { this._impl.setGroup(v); }
getGroup () { return this._impl.getGroup(); }
addGroup (v) { this.setGroup(this.getGroup() | v); }
removeGroup (v) { this.setGroup(this.getGroup() & ~v); }
setMask (v) { this._impl.setMask(v>>>0); }
getMask () { return this._impl.getMask(); }
addMask (v) { this.setMask(this.getMask() | v); }
removeMask (v) { this.setMask(this.getMask() & ~v); }
setType (v) { this._impl.setType(v); }
setMass (v) { this._impl.setMass(v); }
setAllowSleep (v) { this._impl.setAllowSleep(v); }
setLinearDamping (v) {
const dt = cc.PhysicsSystem.instance.fixedTimeStep;
this._impl.setLinearDamping((1 - (1 - v) ** dt) / dt);
}
setAngularDamping (v) {
const dt = cc.PhysicsSystem.instance.fixedTimeStep;
this._impl.setAngularDamping((1 - (1 - v) ** dt) / dt);
}
isUsingCCD () { return this._isUsingCCD; }
useCCD (v) {
this._isUsingCCD = v;
return this._impl.useCCD(v);
}
useGravity (v) { this._impl.useGravity(v); }
setLinearFactor (v) { this._impl.setLinearFactor(v.x, v.y, v.z); }
setAngularFactor (v) { this._impl.setAngularFactor(v.x, v.y, v.z); }
wakeUp () { this._impl.wakeUp(); }
sleep () { this._impl.sleep(); }
clearState () { this._impl.clearState(); }
clearForces () { this._impl.clearForces(); }
clearVelocity () { this._impl.clearVelocity(); }
setSleepThreshold (v) { this._impl.setSleepThreshold(v); }
getSleepThreshold () { return this._impl.getSleepThreshold(); }
getLinearVelocity (o) { o.set(this._impl.getLinearVelocity()); }
setLinearVelocity (v) { this._impl.setLinearVelocity(v.x, v.y, v.z); }
getAngularVelocity (o) { o.set(this._impl.getAngularVelocity()); }
setAngularVelocity (v) { this._impl.setAngularVelocity(v.x, v.y, v.z); }
applyForce (f, p) { if (p == null) { p = cc.Vec3.ZERO; } this._impl.applyForce(f.x, f.y, f.z, p.x, p.y, p.z); }
applyLocalForce (f, p) { if (p == null) { p = cc.Vec3.ZERO; } this._impl.applyLocalForce(f.x, f.y, f.z, p.x, p.y, p.z); }
applyImpulse (f, p) { if (p == null) { p = cc.Vec3.ZERO; } this._impl.applyImpulse(f.x, f.y, f.z, p.x, p.y, p.z); }
applyLocalImpulse (f, p) { if (p == null) { p = cc.Vec3.ZERO; } this._impl.applyLocalImpulse(f.x, f.y, f.z, p.x, p.y, p.z); }
applyTorque (t) { this._impl.applyTorque(t.x, t.y, t.z); }
applyLocalTorque (t) { this._impl.applyLocalTorque(t.x, t.y, t.z); }
}
const ESHAPE_FLAG = {
NONE: 0,
QUERY_FILTER: 1 << 0,
QUERY_SINGLE_HIT: 1 << 2,
DETECT_TRIGGER_EVENT: 1 << 3,
DETECT_CONTACT_EVENT: 1 << 4,
DETECT_CONTACT_POINT: 1 << 5,
DETECT_CONTACT_CCD: 1 << 6,
}
class Shape {
get impl () { return this._impl; }
get collider () { return this._com; }
get attachedRigidBody () { return this._attachedRigidBody; }
constructor() { updateCollisionMatrix(); };
initialize (v) {
v.node.updateWorldTransform();
this._com = v;
this._impl.initialize(v.node.native);
ptrToObj[this._impl.getImpl()] = this;
bookNode(v.node);
}
onLoad () {
this.setMaterial(this._com.sharedMaterial);
this.setCenter(this._com.center);
this.setAsTrigger(this._com.isTrigger);
}
onEnable () { this._impl.onEnable(); }
onDisable () { this._impl.onDisable(); }
onDestroy () {
unBookNode(this._com.node);
ptrToObj[this._impl.getImpl()] = null
delete ptrToObj[this._impl.getImpl()];
this._impl.onDestroy();
}
setMaterial (v) {
const ins = cc.PhysicsSystem.instance;
if (!v) v = ins.defaultMaterial;
if (!jsbPhy['CACHE'].material[v.id]) {
jsbPhy['CACHE'].material[v.id] = ins.physicsWorld.impl.createMaterial(v.id, v.friction, v.friction, v.restitution, 2, 2);
}
this._impl.setMaterial(v.id, v.friction, v.friction, v.restitution, 2, 2);
}
setAsTrigger (v) { this._impl.setAsTrigger(v); }
setCenter (v) { this._impl.setCenter(v.x, v.y, v.z); }
getAABB(v) { v.copy(this._impl.getAABB());}
getBoundingSphere (v) { v.copy(this._impl.getBoundingSphere());}
updateEventListener () {
var flag = 0;
flag |= ESHAPE_FLAG.DETECT_CONTACT_CCD;
if (this._com.isTrigger) flag |= ESHAPE_FLAG.IS_TRIGGER;
if (this._com.needTriggerEvent || this._com.needCollisionEvent) flag |= ESHAPE_FLAG.NEED_EVENT;
this._impl.updateEventListener(flag);
}
setGroup (v) { this._impl.setGroup(v); }
getGroup () { return this._impl.getGroup(); }
addGroup (v) { this.setGroup(this.getGroup() | v); }
removeGroup (v) { this.setGroup(this.getGroup() & ~v); }
setMask (v) { this._impl.setMask(v>>>0); }
getMask () { return this._impl.getMask(); }
addMask (v) { this.setMask(this.getMask() | v); }
removeMask (v) { this.setMask(this.getMask() & ~v); }
}
class SphereShape extends Shape {
constructor() { super(); this._impl = new jsbPhy.SphereShape(); }
updateRadius () { this._impl.setRadius(this.collider.radius); }
onLoad () {
super.onLoad();
this.updateRadius();
}
}
class BoxShape extends Shape {
constructor() { super(); this._impl = new jsbPhy.BoxShape(); }
updateSize () {
const v = this.collider.size;
this._impl.setSize(v.x, v.y, v.z);
}
onLoad () {
super.onLoad();
this.updateSize();
}
}
class CapsuleShape extends Shape {
constructor() { super(); this._impl = new jsbPhy.CapsuleShape(); }
setRadius (v) { this._impl.setRadius(v); }
setDirection (v) { this._impl.setDirection(v); }
setCylinderHeight (v) { this._impl.setCylinderHeight(v); }
onLoad () {
super.onLoad();
this.setRadius(this._com.radius);
this.setDirection(this._com.direction);
this.setCylinderHeight(this._com.cylinderHeight);
}
}
class PlaneShape extends Shape {
constructor() { super(); this._impl = new jsbPhy.PlaneShape(); }
setConstant (v) { this._impl.setConstant(v); }
setNormal (v) { this._impl.setNormal(v.x, v.y, v.z); }
onLoad () {
super.onLoad();
this.setNormal(this._com.normal);
this.setConstant(this._com.constant);
}
}
function getConvexMesh (v) {
if (!jsbPhy.CACHE.convex[v._uuid]) {
const posArr = cc.physics.utils.shrinkPositions(v.readAttribute(0, 'a_position'));
const world = cc.PhysicsSystem.instance.physicsWorld.impl;
const convex = { positions: new Float32Array(posArr), positionLength: posArr.length / 3 }
jsbPhy.CACHE.convex[v._uuid] = world.createConvex(convex);
}
return jsbPhy.CACHE.convex[v._uuid];
}
function getTriangleMesh (v) {
if (!jsbPhy.CACHE.trimesh[v._uuid]) {
const indArr = v.readIndices(0);
// const posArr = cc.physics.utils.shrinkPositions(v.readAttribute(0, 'a_position'));
const posArr = v.readAttribute(0, 'a_position');
const world = cc.PhysicsSystem.instance.physicsWorld.impl;
const trimesh = {
positions: new Float32Array(posArr), positionLength: posArr.length / 3,
triangles: new Uint16Array(indArr), triangleLength: indArr.length / 3,
isU16: true,
}
jsbPhy.CACHE.trimesh[v._uuid] = world.createTrimesh(trimesh);
}
return jsbPhy.CACHE.trimesh[v._uuid];
}
function getHeightField (v) {
if (!jsbPhy.CACHE.height[v._uuid]) {
const rows = v.getVertexCountI();
const columns = v.getVertexCountJ();
const samples = new Int16Array(rows * columns);
const heightScale = jsbPhy['CONFIG'].heightScale;
for (let i = 0; i < rows; i++) {
for (let j = 0; j < columns; j++) {
samples[j + i * columns] = v.getHeight(i, j) / heightScale;
}
}
const height = { rows, columns, samples };
const world = cc.PhysicsSystem.instance.physicsWorld.impl;
jsbPhy.CACHE.height[v._uuid] = world.createHeightField(height);
}
return jsbPhy.CACHE.height[v._uuid];
}
class CylinderShape extends Shape {
constructor() { super(); this._impl = new jsbPhy.CylinderShape(); }
setRadius (v) { this.updateGeometry() }
setDirection (v) { this.updateGeometry() }
setHeight (v) { this.updateGeometry() }
updateGeometry () { this._impl.setCylinder(this._com.radius, this._com.height, this._com.direction); }
initialize (v) {
if (!jsbPhy.CACHE.convex["CYLINDER"]) {
const primitive = cc.physics.utils.cylinder(0.5, 0.5, 2, { radialSegments: 32, heightSegments: 1 });
const posArr = cc.physics.utils.shrinkPositions(primitive.positions);
const convex = { positions: new Float32Array(posArr), positionLength: posArr.length / 3 };
const handle = cc.PhysicsSystem.instance.physicsWorld.impl.createConvex(convex);
jsbPhy.CACHE.convex["CYLINDER"] = handle;
}
this._com = v;
this._impl.setCylinder(v.radius, v.height, v.direction);
this._impl.setConvex(jsbPhy.CACHE.convex["CYLINDER"]);
super.initialize(v);
}
}
class ConeShape extends Shape {
constructor() { super(); this._impl = new jsbPhy.ConeShape(); }
setRadius (v) { this.updateGeometry() }
setDirection (v) { this.updateGeometry() }
setHeight (v) { this.updateGeometry() }
updateGeometry () { this._impl.setCone(this._com.radius, this._com.height, this._com.direction); }
initialize (v) {
if (!jsbPhy.CACHE.convex["CONE"]) {
const primitive = cc.physics.utils.cylinder(0, 0.5, 1, { radialSegments: 32, heightSegments: 1 });
const posArr = cc.physics.utils.shrinkPositions(primitive.positions);
const convex = { positions: new Float32Array(posArr), positionLength: posArr.length / 3 };
const handle = cc.PhysicsSystem.instance.physicsWorld.impl.createConvex(convex);
jsbPhy.CACHE.convex["CONE"] = handle;
}
this._com = v;
this._impl.setCone(v.radius, v.height, v.direction);
this._impl.setConvex(jsbPhy.CACHE.convex["CONE"]);
super.initialize(v);
}
}
class TrimeshShape extends Shape {
constructor() { super(); this._impl = new jsbPhy.TrimeshShape(); }
setConvex (v) { this._impl.useConvex(v); }
setMesh (v) {
if (!v) return;
const isConvex = this._com.convex;
this._impl.useConvex(isConvex);
const handle = isConvex ? getConvexMesh(v) : getTriangleMesh(v);
this._impl.setMesh(handle);
}
initialize (v) {
this._com = v;
this.setConvex(v.convex);
this.setMesh(v.mesh);
super.initialize(v);
}
}
class TerrainShape extends Shape {
constructor() { super(); this._impl = new jsbPhy.TerrainShape(); }
setTerrain (v) {
if (!v) return;
const handle = getHeightField(v);
this._impl.setTerrain(handle, v.tileSize, v.tileSize, jsbPhy['CONFIG'].heightScale);
}
initialize (v) {
this._com = v;
this.setTerrain(v.terrain);
super.initialize(v);
}
}
class Joint {
get impl () { return this._impl; }
get joint () { return this._com; }
setEnableCollision (v) { this._impl.setEnableCollision(v); }
setConnectedBody (v) { this._impl.setConnectedBody(v ? v.body.impl.getNodeHandle() : 0); }
initialize (v) {
this._com = v;
this._impl.initialize(v.node.native);
ptrToObj[this._impl.getImpl()] = this;
this.onLoad();
}
onLoad () {
this.setConnectedBody(this._com.connectedBody);
this.setEnableCollision(this._com.enableCollision);
}
onEnable () { this._impl.onEnable(); }
onDisable () { this._impl.onDisable(); }
onDestroy () {
ptrToObj[this._impl.getImpl()] = null
delete ptrToObj[this._impl.getImpl()];
this._impl.onDestroy();
}
}
class DistanceJoint extends Joint {
constructor() { super(); this._impl = new jsbPhy.DistanceJoint(); }
setPivotA (v) { this._impl.setPivotA(v.x, v.y, v.z); }
setPivotB (v) { this._impl.setPivotB(v.x, v.y, v.z); }
onLoad () {
super.onLoad();
this.setPivotA(this._com.pivotA);
this.setPivotB(this._com.pivotB);
}
}
class RevoluteJoint extends Joint {
constructor() { super(); this._impl = new jsbPhy.RevoluteJoint(); }
setAxis (v) { this._impl.setAxis(v.x, v.y, v.z); }
setPivotA (v) { this._impl.setPivotA(v.x, v.y, v.z); }
setPivotB (v) { this._impl.setPivotB(v.x, v.y, v.z); }
onLoad () {
super.onLoad();
this.setAxis(this._com.axis);
this.setPivotA(this._com.pivotA);
this.setPivotB(this._com.pivotB);
}
}
cc.physics.selector.register("physx", {
PhysicsWorld: PhysicsWorld,
RigidBody: RigidBody,
SphereShape: SphereShape,
BoxShape: BoxShape,
PlaneShape: PlaneShape,
CapsuleShape: CapsuleShape,
ConeShape: ConeShape,
CylinderShape: CylinderShape,
TrimeshShape: TrimeshShape,
TerrainShape: TerrainShape,
PointToPointConstraint: DistanceJoint,
HingeConstraint: RevoluteJoint,
});