babylon-mmd
Version:
babylon.js mmd loader and runtime
739 lines (738 loc) • 37.2 kB
JavaScript
import "@babylonjs/core/Physics/joinedPhysicsEngineComponent";
import "@babylonjs/core/Physics/v1/physicsEngineComponent";
import { BoundingInfo } from "@babylonjs/core/Culling/boundingInfo";
import { Matrix, Quaternion, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";
import { PhysicsImpostor } from "@babylonjs/core/Physics/v1/physicsImpostor";
import { PmxObject } from "../../Loader/Parser/pmxObject";
import { Generic6DofSpringJoint } from "./mmdAmmoJSPlugin";
class MmdPhysicsMesh extends AbstractMesh {
linkedBone;
physicsMode;
bodyOffsetMatrix;
bodyOffsetMatrixInverse;
_customBoundingInfo;
constructor(name, scene, linkedBone, physicsMode, customBoundingInfo) {
super(name, scene);
this.linkedBone = linkedBone;
this.physicsMode = physicsMode;
this.bodyOffsetMatrix = Matrix.Identity();
this.bodyOffsetMatrixInverse = Matrix.Identity();
this._customBoundingInfo = customBoundingInfo;
}
static _WorldMatrix = new Matrix();
computeBodyOffsetMatrix(parentWorldMatrixInverse) {
const worldMatrix = Matrix.ComposeToRef(this.scaling, this.rotationQuaternion, this.position, MmdPhysicsMesh._WorldMatrix);
worldMatrix.multiplyToRef(parentWorldMatrixInverse, this.bodyOffsetMatrix);
this.bodyOffsetMatrix.invertToRef(this.bodyOffsetMatrixInverse);
}
getBoundingInfo() {
return this._customBoundingInfo ?? super.getBoundingInfo();
}
get _positions() {
return null;
}
copyVerticesData(_kind, _vertexData) {
// do nothing
}
refreshBoundingInfo(_applySkeletonOrOptions, _applyMorph) {
return this;
}
get geometry() {
return null;
}
getVertexBuffer(_kind, _bypassInstanceData) {
return null;
}
}
class MmdAmmoPhysicsImpostor extends PhysicsImpostor {
_temporalKinematic;
_kinematicToggle;
constructor(mesh, type, options, scene) {
super(mesh, type, options, scene);
this._temporalKinematic = false;
this._kinematicToggle = mesh.physicsMode === PmxObject.RigidBody.PhysicsMode.FollowBone
? true // if the physics mode is FollowBone, the impostor is always kinematic
: false;
}
_makeKinematic() {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const body = this.physicsBody;
body.setCollisionFlags(body.getCollisionFlags() | 2); // CF_KINEMATIC_OBJECT
}
static _ZeroVector = Vector3.Zero();
_restoreDynamic() {
this.setLinearVelocity(MmdAmmoPhysicsImpostor._ZeroVector);
this.setAngularVelocity(MmdAmmoPhysicsImpostor._ZeroVector);
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const body = this.physicsBody;
body.setCollisionFlags(body.getCollisionFlags() & ~2); // CF_KINEMATIC_OBJECT
}
get temporalKinematic() {
return this._temporalKinematic;
}
set temporalKinematic(value) {
// // disableBidirectionalTransformation is true only for non follow bone impostors
// if (!((this as any)._options as IAmmoPhysicsImpostorParameters).disableBidirectionalTransformation) {
// // if impostor is follow bone, it is always kinematic
// return;
// }
if (value === this._temporalKinematic) {
return;
}
this._temporalKinematic = value;
if (this._kinematicToggle) {
return; // if kinematicToggle is true, the impostor is always kinematic
}
if (value) {
this._makeKinematic();
}
else {
this._restoreDynamic();
}
}
get kinematicToggle() {
return this._kinematicToggle;
}
set kinematicToggle(value) {
// // disableBidirectionalTransformation is true only for non follow bone impostors
// if (!((this as any)._options as IAmmoPhysicsImpostorParameters).disableBidirectionalTransformation) {
// // if impostor is follow bone, it is always true
// return;
// }
if (value === this._kinematicToggle) {
return;
}
this._kinematicToggle = value;
if (this._temporalKinematic) {
return;
}
if (value) {
this._makeKinematic();
}
else {
this._restoreDynamic();
}
}
}
class MmdAmmoPhysicsImpostorWithBone extends MmdAmmoPhysicsImpostor {
linkedBone;
constructor(mesh, type, options, linkedBone, scene) {
super(mesh, type, options, scene);
this.linkedBone = linkedBone;
}
}
/**
* MMD ammo physics model is container of the ammo.js physics resources of the MMD model
*/
export class MmdAmmoPhysicsModel {
_mmdPhysics;
_nodes;
_impostors;
_rootMesh;
_syncedRigidBodyStates;
_disabledRigidBodyCount;
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
_ammoInstance;
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
_tmpBtVector3;
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
_tmpBtQuaternion;
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
_tmpBtTransform;
/**
* Create a new MMD ammo.js physics model
* @param mmdPhysics MMD ammo physics
* @param nodes MMD physics transform nodes
* @param impostors Physics impostors
* @param rootMesh Root mesh of the MMD model
* @param ammoInstance Ammo.js instance
*/
constructor(mmdPhysics, nodes, impostors, rootMesh, ammoInstance) {
this._mmdPhysics = mmdPhysics;
this._nodes = nodes;
this._impostors = impostors;
this._rootMesh = rootMesh;
this._syncedRigidBodyStates = new Uint8Array(impostors.length).fill(1);
this._disabledRigidBodyCount = 0;
this._ammoInstance = ammoInstance;
this._tmpBtVector3 = new ammoInstance.btVector3();
this._tmpBtQuaternion = new ammoInstance.btQuaternion();
this._tmpBtTransform = new ammoInstance.btTransform();
}
/**
* Dispose the physics resources
*/
dispose() {
const impostors = this._impostors;
for (let i = 0; i < impostors.length; ++i) {
const impostor = impostors[i];
if (impostor === null)
continue;
impostor.dispose();
impostor.object.physicsImpostor = null;
}
impostors.length = 0;
const nodes = this._nodes;
for (let i = 0; i < nodes.length; ++i) {
nodes[i]?.dispose(false, true);
}
nodes.length = 0;
this._ammoInstance.destroy(this._tmpBtVector3);
this._ammoInstance.destroy(this._tmpBtQuaternion);
this._ammoInstance.destroy(this._tmpBtTransform);
}
static _NodeWorldMatrix = new Matrix();
static _ZeroVector = Vector3.Zero();
static _Position = new Vector3();
static _Rotation = new Quaternion();
/**
* Reset the rigid body positions and velocities
*/
initialize() {
const modelWorldMatrix = this._rootMesh.computeWorldMatrix();
const position = MmdAmmoPhysicsModel._Position;
const rotation = MmdAmmoPhysicsModel._Rotation;
const btVector3 = this._tmpBtVector3;
const btQuaternion = this._tmpBtQuaternion;
const btTransform = this._tmpBtTransform;
const mmdPhysics = this._mmdPhysics;
const nodes = this._nodes;
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
if (node === null)
continue;
if (node.linkedBone === null)
continue;
const nodeWorldMatrix = node.linkedBone.getWorldMatrixToRef(MmdAmmoPhysicsModel._NodeWorldMatrix);
node.bodyOffsetMatrix.multiplyToRef(nodeWorldMatrix, nodeWorldMatrix);
if (node.physicsMode === PmxObject.RigidBody.PhysicsMode.FollowBone) {
nodeWorldMatrix.decompose(node.scaling, node.rotationQuaternion, node.position);
}
else {
nodeWorldMatrix.multiplyToRef(modelWorldMatrix, nodeWorldMatrix);
nodeWorldMatrix.decompose(undefined, rotation, position);
const impostor = node.physicsImpostor;
mmdPhysics._makeKinematicOnce(impostor);
btVector3.setValue(position.x, position.y, position.z);
btQuaternion.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
btTransform.setOrigin(btVector3);
btTransform.setRotation(btQuaternion);
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const body = impostor.physicsBody;
body.getMotionState().setWorldTransform(btTransform);
}
}
}
/**
* Indicate whether all IK must be solved
*/
get needDeoptimize() {
return 0 < this._disabledRigidBodyCount;
}
/**
* commit rigid body states to physics model
*
* if rigidBodyStates[i] is 0, the rigid body motion type is kinematic,
* if rigidBodyStates[i] is 1 and physicsMode is not FollowBone, the rigid body motion type is dynamic.
*
* @param rigidBodyStates state of rigid bodies for physics toggle
*/
commitBodyStates(rigidBodyStates) {
const nodes = this._nodes;
const syncedRigidBodyStates = this._syncedRigidBodyStates;
for (let i = 0; i < rigidBodyStates.length; ++i) {
const node = nodes[i];
if (node === null) {
continue;
}
if (node.physicsMode === PmxObject.RigidBody.PhysicsMode.FollowBone) {
continue;
}
const state = rigidBodyStates[i];
if (state !== syncedRigidBodyStates[i]) {
syncedRigidBodyStates[i] = state;
if (state !== 0) {
this._disabledRigidBodyCount -= 1;
node.physicsImpostor.kinematicToggle = false;
}
else {
this._disabledRigidBodyCount += 1;
}
}
}
}
static _EulerAngles1 = new Vector3();
static _EulerAngles2 = new Vector3();
/**
* 0: unknown, 1: kinematic, 2: target transform
*/
_bodyKinematicToggleMap = null;
/**
* Set the rigid bodies transform to the bones transform
*/
syncBodies() {
if (0 < this._disabledRigidBodyCount) {
if (this._bodyKinematicToggleMap === null) {
this._bodyKinematicToggleMap = new Uint8Array(this._nodes.length);
}
else {
this._bodyKinematicToggleMap.fill(0);
}
const bodyKinematicToggleMap = this._bodyKinematicToggleMap;
const syncedRigidBodyStates = this._syncedRigidBodyStates;
const modelWorldMatrix = this._rootMesh.computeWorldMatrix();
const position = MmdAmmoPhysicsModel._Position;
const rotation = MmdAmmoPhysicsModel._Rotation;
const btVector3 = this._tmpBtVector3;
const btQuaternion = this._tmpBtQuaternion;
const btTransform = this._tmpBtTransform;
const nodes = this._nodes;
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
if (node === null)
continue;
if (node.linkedBone === null)
continue;
switch (node.physicsMode) {
case PmxObject.RigidBody.PhysicsMode.FollowBone:
{
const nodeWorldMatrix = node.linkedBone.getWorldMatrixToRef(MmdAmmoPhysicsModel._NodeWorldMatrix);
node.bodyOffsetMatrix.multiplyToRef(nodeWorldMatrix, nodeWorldMatrix);
nodeWorldMatrix.decompose(node.scaling, node.rotationQuaternion, node.position);
}
break;
case PmxObject.RigidBody.PhysicsMode.Physics:
case PmxObject.RigidBody.PhysicsMode.PhysicsWithBone:
if (syncedRigidBodyStates[i] === 0) { // body need to be disabled
let useTargetTransformMethod = true;
for (let currentNode = node;;) {
const linkedBone = currentNode.linkedBone;
if (linkedBone === null) { // orphan body
useTargetTransformMethod = false;
break;
}
const parentBone = linkedBone.parentBone;
if (parentBone === null) { // root bone
useTargetTransformMethod = false;
break;
}
if (parentBone.rigidBodyIndices.length < 1) { // parent is animated bone
useTargetTransformMethod = false;
break;
}
const parentNodeIndex = parentBone.rigidBodyIndices[0];
const parentNode = nodes[parentNodeIndex];
if (parentNode === null) { // parent body is failed to create. treat as animated body
useTargetTransformMethod = false;
break;
}
if (parentNode.physicsMode === PmxObject.RigidBody.PhysicsMode.FollowBone) { // parent is animated bone
useTargetTransformMethod = false;
break;
}
else { // Physics or PhysicsWithBone
if (syncedRigidBodyStates[parentNodeIndex] === 0) { // parent body physics toggle is enabled
const parentKinematicToggleState = bodyKinematicToggleMap[parentNodeIndex];
if (parentKinematicToggleState === 0) { // unknown state
// we can't determine the method in this iteration stage
}
else {
if (parentKinematicToggleState === 1) { // parent body is kinematic
useTargetTransformMethod = false;
break;
}
else {
useTargetTransformMethod = true;
break;
}
}
}
else { // parent body is driven by physics so we need to use target transform method
useTargetTransformMethod = true;
break;
}
}
currentNode = parentNode;
}
bodyKinematicToggleMap[i] = useTargetTransformMethod ? 2 : 1; // memo the result for fast computation next time
const nodeWorldMatrix = node.linkedBone.getWorldMatrixToRef(MmdAmmoPhysicsModel._NodeWorldMatrix);
node.bodyOffsetMatrix.multiplyToRef(nodeWorldMatrix, nodeWorldMatrix);
nodeWorldMatrix.multiplyToRef(modelWorldMatrix, nodeWorldMatrix);
nodeWorldMatrix.decompose(undefined, rotation, position);
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const body = node.physicsImpostor.physicsBody;
if (useTargetTransformMethod) {
node.physicsImpostor.kinematicToggle = false;
const forceFactor = 30;
const torqueFactor = 30;
const currentTransform = body.getWorldTransform();
const currentOrigin = currentTransform.getOrigin();
const currentRotation = currentTransform.getRotation();
{
// Linear difference
const dx = position.x - currentOrigin.x();
const dy = position.y - currentOrigin.y();
const dz = position.z - currentOrigin.z();
btVector3.setValue(dx * forceFactor, dy * forceFactor, dz * forceFactor);
body.setLinearVelocity(btVector3);
}
{
// Angular difference
const targetAngles = rotation.toEulerAnglesToRef(MmdAmmoPhysicsModel._EulerAngles1);
rotation.set(currentRotation.x(), currentRotation.y(), currentRotation.z(), currentRotation.w());
const currentAngles = rotation.toEulerAnglesToRef(MmdAmmoPhysicsModel._EulerAngles2);
const dx = (targetAngles.x - currentAngles.x + Math.PI) % (2 * Math.PI) - Math.PI;
const dy = (targetAngles.y - currentAngles.y + Math.PI) % (2 * Math.PI) - Math.PI;
const dz = (targetAngles.z - currentAngles.z + Math.PI) % (2 * Math.PI) - Math.PI;
btVector3.setValue(dx * torqueFactor, dy * torqueFactor, dz * torqueFactor);
body.setAngularVelocity(btVector3);
}
}
else {
node.physicsImpostor.kinematicToggle = true;
btVector3.setValue(position.x, position.y, position.z);
btQuaternion.setValue(rotation.x, rotation.y, rotation.z, rotation.w);
btTransform.setOrigin(btVector3);
btTransform.setRotation(btQuaternion);
body.getMotionState().setWorldTransform(btTransform);
}
}
break;
default:
throw new Error(`Unknown physics mode: ${node.physicsMode}`);
}
}
}
else {
const nodes = this._nodes;
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
if (node === null)
continue;
if (node.linkedBone === null)
continue;
switch (node.physicsMode) {
case PmxObject.RigidBody.PhysicsMode.FollowBone:
{
const nodeWorldMatrix = node.linkedBone.getWorldMatrixToRef(MmdAmmoPhysicsModel._NodeWorldMatrix);
node.bodyOffsetMatrix.multiplyToRef(nodeWorldMatrix, nodeWorldMatrix);
nodeWorldMatrix.decompose(node.scaling, node.rotationQuaternion, node.position);
}
break;
case PmxObject.RigidBody.PhysicsMode.Physics:
case PmxObject.RigidBody.PhysicsMode.PhysicsWithBone:
break;
default:
throw new Error(`Unknown physics mode: ${node.physicsMode}`);
}
}
}
}
static _BoneWorldPosition = new Vector3();
/**
* Set the bones transform to the rigid bodies transform
*/
syncBones() {
const nodes = this._nodes;
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
if (node === null)
continue;
if (node.linkedBone === null)
continue;
switch (node.physicsMode) {
case PmxObject.RigidBody.PhysicsMode.FollowBone:
break;
case PmxObject.RigidBody.PhysicsMode.Physics:
{
node.bodyOffsetMatrixInverse.multiplyToArray(Matrix.ComposeToRef(node.scaling, node.rotationQuaternion, node.position, MmdAmmoPhysicsModel._NodeWorldMatrix), node.linkedBone.worldMatrix, 0);
}
break;
case PmxObject.RigidBody.PhysicsMode.PhysicsWithBone:
{
node.linkedBone.getWorldTranslationToRef(MmdAmmoPhysicsModel._BoneWorldPosition);
node.bodyOffsetMatrixInverse.multiplyToArray(Matrix.ComposeToRef(node.scaling, node.rotationQuaternion, MmdAmmoPhysicsModel._ZeroVector, MmdAmmoPhysicsModel._NodeWorldMatrix), node.linkedBone.worldMatrix, 0);
node.linkedBone.setWorldTranslation(MmdAmmoPhysicsModel._BoneWorldPosition);
}
break;
default:
throw new Error(`Unknown physics mode: ${node.physicsMode}`);
}
}
}
}
/**
* Use the v1 physics engine to build the physics model of the MMD model
*
* If you do not want to use a physics engine, you can reduce the bundling size by not import this class
*/
export class MmdAmmoPhysics {
_scene;
_kinematicOnces = [];
/**
* Create a new MMD ammo.js physics
*
* Scene must have a physics engine enabled
* @param scene The scene to build the physics model
*/
constructor(scene) {
this._scene = scene;
}
/**
* Build the physics model of the MMD model
* @param rootMesh Root mesh of the MMD model
* @param bones MMD runtime bones
* @param rigidBodies rigid bodies information
* @param joints joints information
* @param logger Logger
* @param physicsOptions Optional physics options
* @returns MMD physics model
* @throws If the ammo physics engine is not enabled
*/
buildPhysics(rootMesh, bones, rigidBodies, joints, logger, physicsOptions) {
if (physicsOptions?.worldId !== undefined) {
logger.warn("Ammo physics does not support multiple physics world");
}
const disableOffsetForConstraintFrame = physicsOptions?.disableOffsetForConstraintFrame ?? false;
const scene = this._scene;
const physicsPlugin = scene.getPhysicsEngine()?.getPhysicsPlugin();
if (!physicsPlugin) {
throw new Error("Physics engine is not enabled");
}
if (physicsPlugin.name !== "MmdAmmoJSPlugin") {
throw new Error("Physics engine is not MMDAmmoJSPlugin");
}
const originalForceDisableOffsetForConstraintFrame = physicsPlugin.forceDisableOffsetForConstraintFrame;
if (disableOffsetForConstraintFrame) {
// create constraint with forceDisableOffsetForConstraintFrame
physicsPlugin.forceDisableOffsetForConstraintFrame = true;
}
let scalingFactor;
rootMesh.computeWorldMatrix(true);
const worldMatrix = rootMesh.getWorldMatrix();
const worldRotation = new Quaternion();
{
const worldScale = new Vector3();
worldMatrix.decompose(worldScale, worldRotation);
if (Math.abs(worldScale.x - worldScale.y) < 0.0001 && Math.abs(worldScale.y - worldScale.z) < 0.0001) {
if (Math.abs(worldScale.x - 1.0) < 0.0001) {
scalingFactor = 1;
}
else {
scalingFactor = worldScale.x;
logger.warn("Root node scaling is not 1, simulation may differ from the original");
}
}
else {
scalingFactor = Math.max(worldScale.x, worldScale.y, worldScale.z);
logger.warn("Root node scaling is not uniform, physics may not work correctly");
}
}
const nodes = new Array(rigidBodies.length);
const impostors = new Array(rigidBodies.length);
const boneNameMap = new Map();
for (let i = 0; i < bones.length; ++i) {
const bone = bones[i];
boneNameMap.set(bone.name, bone);
}
const resolveRigidBodyBone = (rigidBody) => {
if (rigidBody.boneIndex < 0 || bones.length <= rigidBody.boneIndex) {
return boneNameMap.get(rigidBody.name);
}
return bones[rigidBody.boneIndex];
};
for (let i = 0; i < rigidBodies.length; ++i) {
const rigidBody = rigidBodies[i];
const bone = resolveRigidBodyBone(rigidBody);
if (bone === undefined) {
logger.warn(`Bone index out of range create unmapped rigid body: ${rigidBody.name}`);
}
let impostorType;
const boundMin = new Vector3();
const boundMax = new Vector3();
let isZeroVolume = false;
switch (rigidBody.shapeType) {
case PmxObject.RigidBody.ShapeType.Sphere: {
impostorType = PhysicsImpostor.SphereImpostor;
const radius = rigidBody.shapeSize[0] * scalingFactor;
boundMin.copyFromFloats(-radius, -radius, -radius);
boundMax.copyFromFloats(radius, radius, radius);
if (radius === 0)
isZeroVolume = true;
break;
}
case PmxObject.RigidBody.ShapeType.Box: {
impostorType = PhysicsImpostor.BoxImpostor;
const shapeSize = rigidBody.shapeSize;
boundMin.copyFromFloats(-shapeSize[0] * scalingFactor, -shapeSize[1] * scalingFactor, -shapeSize[2] * scalingFactor);
boundMax.copyFromFloats(shapeSize[0] * scalingFactor, shapeSize[1] * scalingFactor, shapeSize[2] * scalingFactor);
if (shapeSize[0] === 0 || shapeSize[1] === 0 || shapeSize[2] === 0)
isZeroVolume = true;
break;
}
case PmxObject.RigidBody.ShapeType.Capsule: {
impostorType = PhysicsImpostor.CapsuleImpostor;
const shapeSize = rigidBody.shapeSize;
const x = shapeSize[0] * scalingFactor;
const y = shapeSize[1] / 2 * scalingFactor + x;
boundMin.copyFromFloats(-x, -y, -x);
boundMax.copyFromFloats(x, y, x);
if (shapeSize[0] === 0 || shapeSize[1] === 0)
isZeroVolume = true;
break;
}
default:
logger.warn(`Unknown rigid body shape type: ${rigidBody.shapeType}`);
nodes[i] = null;
impostors[i] = null;
continue;
}
const node = new MmdPhysicsMesh(rigidBody.name, scene, bone ?? null, rigidBody.physicsMode, new BoundingInfo(boundMin, boundMax));
const shapePosition = rigidBody.shapePosition;
node.position.copyFromFloats(shapePosition[0], shapePosition[1], shapePosition[2]);
const shapeRotation = rigidBody.shapeRotation;
node.rotationQuaternion = Quaternion.FromEulerAngles(shapeRotation[0], shapeRotation[1], shapeRotation[2]);
// compute the body offset matrix in local space
if (bone !== undefined) {
node.computeBodyOffsetMatrix(bone.linkedBone.getAbsoluteInverseBindMatrix());
}
// then convert the body transform to world space
Vector3.TransformCoordinatesToRef(node.position, worldMatrix, node.position);
worldRotation.multiplyToRef(node.rotationQuaternion, node.rotationQuaternion);
const mass = rigidBody.physicsMode === PmxObject.RigidBody.PhysicsMode.FollowBone
? 0
: rigidBody.mass * scalingFactor;
// if mass is 0, the object will be constructed as a kinematic object by babylon.js physics plugin
const physicsImpostorParameters = {
mass: mass,
friction: rigidBody.friction,
restitution: rigidBody.repulsion,
ignoreParent: true,
disableBidirectionalTransformation: rigidBody.physicsMode !== PmxObject.RigidBody.PhysicsMode.FollowBone,
group: 1 << rigidBody.collisionGroup,
mask: rigidBody.collisionMask
};
const impostor = node.physicsImpostor = bone !== undefined
? new MmdAmmoPhysicsImpostorWithBone(node, impostorType, physicsImpostorParameters, bone, scene)
: new MmdAmmoPhysicsImpostor(node, impostorType, physicsImpostorParameters, scene);
impostor.setDeltaPosition(new Vector3(0, 0, 0));
node.setParent(rootMesh);
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const body = impostor.physicsBody;
if (rigidBody.collisionMask === 0 || isZeroVolume) {
body.setCollisionFlags(body.getCollisionFlags() | 4); // CF_NO_CONTACT_RESPONSE
}
body.setDamping(rigidBody.linearDamping, rigidBody.angularDamping);
body.setSleepingThresholds(0.0, 0.0);
const bodyPtr = physicsPlugin.bjsAMMO.getPointer(body);
const heap32 = physicsPlugin.bjsAMMO.HEAP32;
// ptr + 113 = m_additionalDamping (bool but 4 bytes aligned)
heap32[bodyPtr / 4 + 113] = 0xFFFFFFFF; // enable additional damping
nodes[i] = node;
impostors[i] = impostor;
}
const one = Vector3.One();
const jointRotation = new Quaternion();
const jointPosition = new Vector3();
const jointTransform = new Matrix();
const rigidBodyRotation = new Quaternion();
const rigidBodyPosition = new Vector3();
const rigidBodyAInverse = new Matrix();
const rigidBodyBInverse = new Matrix();
const jointFinalTransformA = new Matrix();
const jointFinalTransformB = new Matrix();
for (let i = 0; i < joints.length; ++i) {
const joint = joints[i];
if (joint.rigidbodyIndexA < 0 || rigidBodies.length <= joint.rigidbodyIndexA) {
logger.warn(`Rigid body index out of range failed to create joint: ${joint.name}`);
continue;
}
if (joint.rigidbodyIndexB < 0 || rigidBodies.length <= joint.rigidbodyIndexB) {
logger.warn(`Rigid body index out of range failed to create joint: ${joint.name}`);
continue;
}
const bodyA = impostors[joint.rigidbodyIndexA];
const bodyB = impostors[joint.rigidbodyIndexB];
if (bodyA === null || bodyB === null) {
logger.warn(`Rigid body not found failed to create joint: ${joint.name}`);
continue;
}
Matrix.ComposeToRef(one, Quaternion.FromEulerAnglesToRef(joint.rotation[0], joint.rotation[1], joint.rotation[2], jointRotation), jointPosition.copyFromFloats(joint.position[0] * scalingFactor, joint.position[1] * scalingFactor, joint.position[2] * scalingFactor), jointTransform);
const bodyInfoA = rigidBodies[joint.rigidbodyIndexA];
const bodyInfoB = rigidBodies[joint.rigidbodyIndexB];
{
const shapeRotation = bodyInfoA.shapeRotation;
const shapePosition = bodyInfoA.shapePosition;
Matrix.ComposeToRef(one, Quaternion.FromEulerAnglesToRef(shapeRotation[0], shapeRotation[1], shapeRotation[2], rigidBodyRotation), rigidBodyPosition.copyFromFloats(shapePosition[0] * scalingFactor, shapePosition[1] * scalingFactor, shapePosition[2] * scalingFactor), rigidBodyAInverse).invert();
}
{
const shapeRotation = bodyInfoB.shapeRotation;
const shapePosition = bodyInfoB.shapePosition;
Matrix.ComposeToRef(one, Quaternion.FromEulerAnglesToRef(shapeRotation[0], shapeRotation[1], shapeRotation[2], rigidBodyRotation), rigidBodyPosition.copyFromFloats(shapePosition[0] * scalingFactor, shapePosition[1] * scalingFactor, shapePosition[2] * scalingFactor), rigidBodyBInverse).invert();
}
jointTransform.multiplyToRef(rigidBodyAInverse, jointFinalTransformA);
jointTransform.multiplyToRef(rigidBodyBInverse, jointFinalTransformB);
const physicsJoint = new Generic6DofSpringJoint({
mainFrame: jointFinalTransformA,
connectedFrame: jointFinalTransformB,
useLinearReferenceFrameA: true,
linearLowerLimit: new Vector3(joint.positionMin[0], joint.positionMin[1], joint.positionMin[2]),
linearUpperLimit: new Vector3(joint.positionMax[0], joint.positionMax[1], joint.positionMax[2]),
angularLowerLimit: new Vector3(joint.rotationMin[0], joint.rotationMin[1], joint.rotationMin[2]),
angularUpperLimit: new Vector3(joint.rotationMax[0], joint.rotationMax[1], joint.rotationMax[2]),
linearStiffness: new Vector3(joint.springPosition[0], joint.springPosition[1], joint.springPosition[2]),
angularStiffness: new Vector3(joint.springRotation[0], joint.springRotation[1], joint.springRotation[2]),
collision: true // do not disable collision between the two rigid bodies
});
bodyA.addJoint(bodyB, physicsJoint);
// adjust the physics mode of the rigid bodies
// ref: https://web.archive.org/web/20140815111315/www20.atpages.jp/katwat/wp/?p=4135
const nodeA = nodes[joint.rigidbodyIndexA];
const nodeB = nodes[joint.rigidbodyIndexB];
if (nodeA.physicsMode !== PmxObject.RigidBody.PhysicsMode.FollowBone &&
nodeB.physicsMode === PmxObject.RigidBody.PhysicsMode.PhysicsWithBone) { // case: A is parent of B
const runtimeBoneB = resolveRigidBodyBone(bodyInfoB);
if (runtimeBoneB !== undefined) {
if (runtimeBoneB.parentBone === resolveRigidBodyBone(bodyInfoA)) {
nodeB.physicsMode = PmxObject.RigidBody.PhysicsMode.Physics;
}
}
}
else if (nodeB.physicsMode !== PmxObject.RigidBody.PhysicsMode.FollowBone &&
nodeA.physicsMode === PmxObject.RigidBody.PhysicsMode.PhysicsWithBone) { // case: B is parent of A
const runtimeBoneA = resolveRigidBodyBone(bodyInfoA);
if (runtimeBoneA !== undefined) {
if (runtimeBoneA.parentBone === resolveRigidBodyBone(bodyInfoB)) {
nodeA.physicsMode = PmxObject.RigidBody.PhysicsMode.Physics;
}
}
}
}
// restore the original forceDisableOffsetForConstraintFrame
if (disableOffsetForConstraintFrame) {
physicsPlugin.forceDisableOffsetForConstraintFrame = originalForceDisableOffsetForConstraintFrame;
}
return new MmdAmmoPhysicsModel(this, nodes, impostors, rootMesh, physicsPlugin.bjsAMMO);
}
_onAfterPhysics = () => {
const kinematicOnces = this._kinematicOnces;
for (let i = 0; i < kinematicOnces.length; ++i) {
kinematicOnces[i].temporalKinematic = false;
}
kinematicOnces.length = 0;
};
/** @internal */
_makeKinematicOnce(impostor) {
// if (!((impostor as any)._options as IAmmoPhysicsImpostorParameters).disableBidirectionalTransformation) {
// return;
// }
if (this._kinematicOnces.length === 0) {
this._scene.onAfterPhysicsObservable.addOnce(this._onAfterPhysics);
}
if (!impostor.temporalKinematic) {
this._kinematicOnces.push(impostor);
impostor.temporalKinematic = true;
}
}
}