@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
1,029 lines • 112 kB
JavaScript
import { Matrix, Quaternion, TmpVectors, Vector3 } from "../../../Maths/math.vector.js";
import { PhysicsPrestepType, } from "../IPhysicsEnginePlugin.js";
import { Logger } from "../../../Misc/logger.js";
import { PhysicsShape } from "../physicsShape.js";
import { BoundingBox } from "../../../Culling/boundingBox.js";
import { Mesh } from "../../../Meshes/mesh.js";
import { InstancedMesh } from "../../../Meshes/instancedMesh.js";
import { VertexBuffer } from "../../../Buffers/buffer.js";
import { BuildArray } from "../../../Misc/arrayTools.js";
import { Observable } from "../../../Misc/observable.js";
class MeshAccumulator {
/**
* Constructor of the mesh accumulator
* @param mesh - The mesh used to compute the world matrix.
* @param collectIndices - use mesh indices
* @param scene - The scene used to determine the right handed system.
*
* Merge mesh and its children so whole hierarchy can be used as a mesh shape or convex hull
*/
constructor(mesh, collectIndices, scene) {
this._vertices = []; /// Vertices in body space
this._indices = [];
this._isRightHanded = scene.useRightHandedSystem;
this._collectIndices = collectIndices;
}
/**
* Adds a mesh to the physics engine.
* @param mesh The mesh to add.
* @param includeChildren Whether to include the children of the mesh.
*
* This method adds a mesh to the physics engine by computing the world matrix,
* multiplying it with the body from world matrix, and then transforming the
* coordinates of the mesh's vertices. It also adds the indices of the mesh
* to the physics engine. If includeChildren is true, it will also add the
* children of the mesh to the physics engine, ignoring any children which
* have a physics impostor. This is useful for creating a physics engine
* that accurately reflects the mesh and its children.
*/
addNodeMeshes(mesh, includeChildren) {
// Force absoluteScaling to be computed; we're going to use that to bake
// the scale of any parent nodes into this shape, as physics engines
// usually use rigid transforms, so can't handle arbitrary scale.
mesh.computeWorldMatrix(true);
const rootScaled = TmpVectors.Matrix[0];
Matrix.ScalingToRef(mesh.absoluteScaling.x, mesh.absoluteScaling.y, mesh.absoluteScaling.z, rootScaled);
if (mesh instanceof Mesh) {
this._addMesh(mesh, rootScaled);
}
else if (mesh instanceof InstancedMesh) {
this._addMesh(mesh.sourceMesh, rootScaled);
}
if (includeChildren) {
const worldToRoot = TmpVectors.Matrix[1];
mesh.computeWorldMatrix().invertToRef(worldToRoot);
const worldToRootScaled = TmpVectors.Matrix[2];
worldToRoot.multiplyToRef(rootScaled, worldToRootScaled);
const children = mesh.getChildMeshes(false);
// Ignore any children which have a physics body.
// Other plugin implementations do not have this check, which appears to be
// a bug, as otherwise, the mesh will have a duplicate collider
const transformNodes = children.filter((m) => !m.physicsBody);
for (const m of transformNodes) {
const childToWorld = m.computeWorldMatrix();
const childToRootScaled = TmpVectors.Matrix[3];
childToWorld.multiplyToRef(worldToRootScaled, childToRootScaled);
if (m instanceof Mesh) {
this._addMesh(m, childToRootScaled);
}
else if (m instanceof InstancedMesh) {
this._addMesh(m.sourceMesh, childToRootScaled);
}
}
}
}
_addMesh(mesh, meshToRoot) {
const vertexData = mesh.getVerticesData(VertexBuffer.PositionKind) || [];
const numVerts = vertexData.length / 3;
const indexOffset = this._vertices.length;
for (let v = 0; v < numVerts; v++) {
const pos = new Vector3(vertexData[v * 3 + 0], vertexData[v * 3 + 1], vertexData[v * 3 + 2]);
this._vertices.push(Vector3.TransformCoordinates(pos, meshToRoot));
}
if (this._collectIndices) {
const meshIndices = mesh.getIndices();
if (meshIndices) {
for (let i = 0; i < meshIndices.length; i += 3) {
// Havok wants the correct triangle winding to enable the interior triangle optimization
if (this._isRightHanded) {
this._indices.push(meshIndices[i + 0] + indexOffset);
this._indices.push(meshIndices[i + 1] + indexOffset);
this._indices.push(meshIndices[i + 2] + indexOffset);
}
else {
this._indices.push(meshIndices[i + 2] + indexOffset);
this._indices.push(meshIndices[i + 1] + indexOffset);
this._indices.push(meshIndices[i + 0] + indexOffset);
}
}
}
}
}
/**
* Allocate and populate the vertex positions inside the physics plugin.
*
* @param plugin - The plugin to allocate the memory in.
* @returns An array of floats, whose backing memory is inside the plugin. The array contains the
* positions of the mesh vertices, where a position is defined by three floats. You must call
* freeBuffer() on the returned array once you have finished with it, in order to free the
* memory inside the plugin..
*/
getVertices(plugin) {
const nFloats = this._vertices.length * 3;
const bytesPerFloat = 4;
const nBytes = nFloats * bytesPerFloat;
const bufferBegin = plugin._malloc(nBytes);
const ret = new Float32Array(plugin.HEAPU8.buffer, bufferBegin, nFloats);
for (let i = 0; i < this._vertices.length; i++) {
ret[i * 3 + 0] = this._vertices[i].x;
ret[i * 3 + 1] = this._vertices[i].y;
ret[i * 3 + 2] = this._vertices[i].z;
}
return { offset: bufferBegin, numObjects: nFloats };
}
freeBuffer(plugin, arr) {
plugin._free(arr.offset);
}
/**
* Allocate and populate the triangle indices inside the physics plugin
*
* @param plugin - The plugin to allocate the memory in.
* @returns A new Int32Array, whose backing memory is inside the plugin. The array contains the indices
* of the triangle positions, where a single triangle is defined by three indices. You must call
* freeBuffer() on this array once you have finished with it, to free the memory inside the plugin..
*/
getTriangles(plugin) {
const bytesPerInt = 4;
const nBytes = this._indices.length * bytesPerInt;
const bufferBegin = plugin._malloc(nBytes);
const ret = new Int32Array(plugin.HEAPU8.buffer, bufferBegin, this._indices.length);
for (let i = 0; i < this._indices.length; i++) {
ret[i] = this._indices[i];
}
return { offset: bufferBegin, numObjects: this._indices.length };
}
}
class BodyPluginData {
constructor(bodyId) {
this.hpBodyId = bodyId;
this.userMassProps = { centerOfMass: undefined, mass: undefined, inertia: undefined, inertiaOrientation: undefined };
}
}
/*
class ShapePath
{
public colliderId: number;
public pathData: number;
}
*/
class CollisionContactPoint {
constructor() {
this.bodyId = BigInt(0); //0,2
//public colliderId: number = 0; //2,4
//public shapePath: ShapePath = new ShapePath(); //4,8
this.position = new Vector3(); //8,11
this.normal = new Vector3(); //11,14
//public triIdx: number = 0; //14,15
}
}
class CollisionEvent {
constructor() {
this.contactOnA = new CollisionContactPoint(); //1
this.contactOnB = new CollisionContactPoint();
this.impulseApplied = 0;
this.type = 0;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
static readToRef(buffer, offset, eventOut) {
const intBuf = new Int32Array(buffer, offset);
const floatBuf = new Float32Array(buffer, offset);
const offA = 2;
eventOut.contactOnA.bodyId = BigInt(intBuf[offA]); //<todo Need to get the high+low words!
eventOut.contactOnA.position.set(floatBuf[offA + 8], floatBuf[offA + 9], floatBuf[offA + 10]);
eventOut.contactOnA.normal.set(floatBuf[offA + 11], floatBuf[offA + 12], floatBuf[offA + 13]);
const offB = 18;
eventOut.contactOnB.bodyId = BigInt(intBuf[offB]);
eventOut.contactOnB.position.set(floatBuf[offB + 8], floatBuf[offB + 9], floatBuf[offB + 10]);
eventOut.contactOnB.normal.set(floatBuf[offB + 11], floatBuf[offB + 12], floatBuf[offB + 13]);
eventOut.impulseApplied = floatBuf[offB + 13 + 3];
eventOut.type = intBuf[0];
}
}
class TriggerEvent {
constructor() {
this.bodyIdA = BigInt(0);
this.bodyIdB = BigInt(0);
this.type = 0;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
static readToRef(buffer, offset, eventOut) {
const intBuf = new Int32Array(buffer, offset);
eventOut.type = intBuf[0];
eventOut.bodyIdA = BigInt(intBuf[2]);
eventOut.bodyIdB = BigInt(intBuf[6]);
}
}
/**
* The Havok Physics plugin
*/
export class HavokPlugin {
constructor(_useDeltaForWorldStep = true, hpInjection = HK) {
this._useDeltaForWorldStep = _useDeltaForWorldStep;
/**
* Reference to the WASM library
*/
this._hknp = {};
/**
* Name of the plugin
*/
this.name = "HavokPlugin";
this._fixedTimeStep = 1 / 60;
this._tmpVec3 = BuildArray(3, Vector3.Zero);
this._bodies = new Map();
this._shapes = new Map();
this._bodyCollisionObservable = new Map();
// Map from constraint id to the pair of bodies, where the first is the parent and the second is the child
this._constraintToBodyIdPair = new Map();
this._bodyCollisionEndedObservable = new Map();
/**
* Observable for collision started and collision continued events
*/
this.onCollisionObservable = new Observable();
/**
* Observable for collision ended events
*/
this.onCollisionEndedObservable = new Observable();
/**
* Observable for trigger entered and trigger exited events
*/
this.onTriggerCollisionObservable = new Observable();
if (typeof hpInjection === "function") {
Logger.Error("Havok is not ready. Please make sure you await HK() before using the plugin.");
return;
}
else {
this._hknp = hpInjection;
}
if (!this.isSupported()) {
Logger.Error("Havok is not available. Please make sure you included the js file.");
return;
}
this.world = this._hknp.HP_World_Create()[1];
this._queryCollector = this._hknp.HP_QueryCollector_Create(1)[1];
}
/**
* If this plugin is supported
* @returns true if its supported
*/
isSupported() {
return this._hknp !== undefined;
}
/**
* Sets the gravity of the physics world.
*
* @param gravity - The gravity vector to set.
*
*/
setGravity(gravity) {
this._hknp.HP_World_SetGravity(this.world, this._bVecToV3(gravity));
}
/**
* Sets the fixed time step for the physics engine.
*
* @param timeStep - The fixed time step to use for the physics engine.
*
*/
setTimeStep(timeStep) {
this._fixedTimeStep = timeStep;
}
/**
* Gets the fixed time step used by the physics engine.
*
* @returns The fixed time step used by the physics engine.
*
*/
getTimeStep() {
return this._fixedTimeStep;
}
/**
* Executes a single step of the physics engine.
*
* @param delta The time delta in seconds since the last step.
* @param physicsBodies An array of physics bodies to be simulated.
*
* This method is useful for simulating the physics engine. It sets the physics body transformation,
* steps the world, syncs the physics body, and notifies collisions. This allows for the physics engine
* to accurately simulate the physics bodies in the world.
*/
executeStep(delta, physicsBodies) {
for (const physicsBody of physicsBodies) {
if (physicsBody.disablePreStep) {
continue;
}
this.setPhysicsBodyTransformation(physicsBody, physicsBody.transformNode);
}
const deltaTime = this._useDeltaForWorldStep ? delta : this._fixedTimeStep;
this._hknp.HP_World_SetIdealStepTime(this.world, deltaTime);
this._hknp.HP_World_Step(this.world, deltaTime);
this._bodyBuffer = this._hknp.HP_World_GetBodyBuffer(this.world)[1];
for (const physicsBody of physicsBodies) {
if (!physicsBody.disableSync) {
this.sync(physicsBody);
}
}
this._notifyCollisions();
this._notifyTriggers();
}
/**
* Returns the version of the physics engine plugin.
*
* @returns The version of the physics engine plugin.
*
* This method is useful for determining the version of the physics engine plugin that is currently running.
*/
getPluginVersion() {
return 2;
}
/**
* Set the maximum allowed linear and angular velocities
* @param maxLinearVelocity maximum allowed linear velocity
* @param maxAngularVelocity maximum allowed angular velocity
*/
setVelocityLimits(maxLinearVelocity, maxAngularVelocity) {
this._hknp.HP_World_SetSpeedLimit(this.world, maxLinearVelocity, maxAngularVelocity);
}
/**
* @returns maximum allowed linear velocity
*/
getMaxLinearVelocity() {
const limits = this._hknp.HP_World_GetSpeedLimit(this.world);
return limits[1];
}
/**
* @returns maximum allowed angular velocity
*/
getMaxAngularVelocity() {
const limits = this._hknp.HP_World_GetSpeedLimit(this.world);
return limits[2];
}
/**
* Initializes a physics body with the given position and orientation.
*
* @param body - The physics body to initialize.
* @param motionType - The motion type of the body.
* @param position - The position of the body.
* @param orientation - The orientation of the body.
* This code is useful for initializing a physics body with the given position and orientation.
* It creates a plugin data for the body and adds it to the world. It then converts the position
* and orientation to a transform and sets the body's transform to the given values.
*/
initBody(body, motionType, position, orientation) {
body._pluginData = new BodyPluginData(this._hknp.HP_Body_Create()[1]);
this._internalSetMotionType(body._pluginData, motionType);
const transform = [this._bVecToV3(position), this._bQuatToV4(orientation)]; //<todo temp transform?
this._hknp.HP_Body_SetQTransform(body._pluginData.hpBodyId, transform);
this._hknp.HP_World_AddBody(this.world, body._pluginData.hpBodyId, body.startAsleep);
this._bodies.set(body._pluginData.hpBodyId[0], { body: body, index: 0 });
}
/**
* Removes a body from the world. To dispose of a body, it is necessary to remove it from the world first.
*
* @param body - The body to remove.
*/
removeBody(body) {
if (body._pluginDataInstances && body._pluginDataInstances.length > 0) {
for (const instance of body._pluginDataInstances) {
this._bodyCollisionObservable.delete(instance.hpBodyId[0]);
this._hknp.HP_World_RemoveBody(this.world, instance.hpBodyId);
this._bodies.delete(instance.hpBodyId[0]);
}
}
if (body._pluginData) {
this._bodyCollisionObservable.delete(body._pluginData.hpBodyId[0]);
this._hknp.HP_World_RemoveBody(this.world, body._pluginData.hpBodyId);
this._bodies.delete(body._pluginData.hpBodyId[0]);
}
}
/**
* Initializes the body instances for a given physics body and mesh.
*
* @param body - The physics body to initialize.
* @param motionType - How the body will be handled by the engine
* @param mesh - The mesh to initialize.
*
* This code is useful for creating a physics body from a mesh. It creates a
* body instance for each instance of the mesh and adds it to the world. It also
* sets the position of the body instance to the position of the mesh instance.
* This allows for the physics engine to accurately simulate the mesh in the
* world.
*/
initBodyInstances(body, motionType, mesh) {
const instancesCount = mesh._thinInstanceDataStorage?.instancesCount ?? 0;
const matrixData = mesh._thinInstanceDataStorage.matrixData;
if (!matrixData) {
return; // TODO: error handling
}
this._createOrUpdateBodyInstances(body, motionType, matrixData, 0, instancesCount, false);
for (let index = 0; index < body._pluginDataInstances.length; index++) {
const bodyId = body._pluginDataInstances[index];
this._bodies.set(bodyId.hpBodyId[0], { body: body, index: index });
}
}
_createOrUpdateBodyInstances(body, motionType, matrixData, startIndex, endIndex, update) {
const rotation = TmpVectors.Quaternion[0];
const rotationMatrix = Matrix.Identity();
for (let i = startIndex; i < endIndex; i++) {
const position = [matrixData[i * 16 + 12], matrixData[i * 16 + 13], matrixData[i * 16 + 14]];
let hkbody;
if (!update) {
hkbody = this._hknp.HP_Body_Create()[1];
}
else {
hkbody = body._pluginDataInstances[i].hpBodyId;
}
rotationMatrix.setRowFromFloats(0, matrixData[i * 16 + 0], matrixData[i * 16 + 1], matrixData[i * 16 + 2], 0);
rotationMatrix.setRowFromFloats(1, matrixData[i * 16 + 4], matrixData[i * 16 + 5], matrixData[i * 16 + 6], 0);
rotationMatrix.setRowFromFloats(2, matrixData[i * 16 + 8], matrixData[i * 16 + 9], matrixData[i * 16 + 10], 0);
Quaternion.FromRotationMatrixToRef(rotationMatrix, rotation);
const transform = [position, [rotation.x, rotation.y, rotation.z, rotation.w]];
this._hknp.HP_Body_SetQTransform(hkbody, transform);
if (!update) {
const pluginData = new BodyPluginData(hkbody);
if (body._pluginDataInstances.length) {
// If an instance already exists, copy any user-provided mass properties
pluginData.userMassProps = body._pluginDataInstances[0].userMassProps;
}
this._internalSetMotionType(pluginData, motionType);
this._internalUpdateMassProperties(pluginData);
body._pluginDataInstances.push(pluginData);
this._hknp.HP_World_AddBody(this.world, hkbody, body.startAsleep);
pluginData.worldTransformOffset = this._hknp.HP_Body_GetWorldTransformOffset(hkbody)[1];
}
}
}
/**
* Update the internal body instances for a given physics body to match the instances in a mesh.
* @param body the body that will be updated
* @param mesh the mesh with reference instances
*/
updateBodyInstances(body, mesh) {
const instancesCount = mesh._thinInstanceDataStorage?.instancesCount ?? 0;
const matrixData = mesh._thinInstanceDataStorage.matrixData;
if (!matrixData) {
return; // TODO: error handling
}
const pluginInstancesCount = body._pluginDataInstances.length;
const motionType = this.getMotionType(body);
if (instancesCount > pluginInstancesCount) {
this._createOrUpdateBodyInstances(body, motionType, matrixData, pluginInstancesCount, instancesCount, false);
const firstBodyShape = this._hknp.HP_Body_GetShape(body._pluginDataInstances[0].hpBodyId)[1];
// firstBodyShape[0] might be 0 in the case where thin instances data is set (with thinInstancesSetBuffer call) after body creation
// in that case, use the shape provided at body creation.
if (!firstBodyShape[0]) {
firstBodyShape[0] = body.shape?._pluginData[0];
}
for (let i = pluginInstancesCount; i < instancesCount; i++) {
this._hknp.HP_Body_SetShape(body._pluginDataInstances[i].hpBodyId, firstBodyShape);
this._internalUpdateMassProperties(body._pluginDataInstances[i]);
this._bodies.set(body._pluginDataInstances[i].hpBodyId[0], { body: body, index: i });
}
}
else if (instancesCount < pluginInstancesCount) {
const instancesToRemove = pluginInstancesCount - instancesCount;
for (let i = 0; i < instancesToRemove; i++) {
const hkbody = body._pluginDataInstances.pop();
this._bodies.delete(hkbody.hpBodyId[0]);
this._hknp.HP_World_RemoveBody(this.world, hkbody.hpBodyId);
this._hknp.HP_Body_Release(hkbody.hpBodyId);
}
this._createOrUpdateBodyInstances(body, motionType, matrixData, 0, instancesCount, true);
}
}
/**
* Synchronizes the transform of a physics body with its transform node.
* @param body - The physics body to synchronize.
*
* This function is useful for keeping the physics body's transform in sync with its transform node.
* This is important for ensuring that the physics body is accurately represented in the physics engine.
*/
sync(body) {
this.syncTransform(body, body.transformNode);
}
/**
* Synchronizes the transform of a physics body with the transform of its
* corresponding transform node.
*
* @param body - The physics body to synchronize.
* @param transformNode - The destination Transform Node.
*
* This code is useful for synchronizing the position and orientation of a
* physics body with the position and orientation of its corresponding
* transform node. This is important for ensuring that the physics body and
* the transform node are in the same position and orientation in the scene.
* This is necessary for the physics engine to accurately simulate the
* physical behavior of the body.
*/
syncTransform(body, transformNode) {
if (body._pluginDataInstances.length) {
// instances
const m = transformNode;
const matrixData = m._thinInstanceDataStorage.matrixData;
if (!matrixData) {
return; // TODO: error handling
}
const instancesCount = body._pluginDataInstances.length;
for (let i = 0; i < instancesCount; i++) {
const bufOffset = body._pluginDataInstances[i].worldTransformOffset;
const transformBuffer = new Float32Array(this._hknp.HEAPU8.buffer, this._bodyBuffer + bufOffset, 16);
const index = i * 16;
for (let mi = 0; mi < 15; mi++) {
if ((mi & 3) != 3) {
matrixData[index + mi] = transformBuffer[mi];
}
}
matrixData[index + 15] = 1;
}
m.thinInstanceBufferUpdated("matrix");
}
else {
try {
// regular
const bodyTransform = this._hknp.HP_Body_GetQTransform(body._pluginData.hpBodyId)[1];
const bodyTranslation = bodyTransform[0];
const bodyOrientation = bodyTransform[1];
const quat = TmpVectors.Quaternion[0];
quat.set(bodyOrientation[0], bodyOrientation[1], bodyOrientation[2], bodyOrientation[3]);
const parent = transformNode.parent;
// transform position/orientation in parent space
if (parent && !parent.getWorldMatrix().isIdentity()) {
parent.computeWorldMatrix(true);
// Save scaling for future use
TmpVectors.Vector3[1].copyFrom(transformNode.scaling);
quat.normalize();
const finalTransform = TmpVectors.Matrix[0];
const finalTranslation = TmpVectors.Vector3[0];
finalTranslation.copyFromFloats(bodyTranslation[0], bodyTranslation[1], bodyTranslation[2]);
Matrix.ComposeToRef(transformNode.absoluteScaling, quat, finalTranslation, finalTransform);
const parentInverseTransform = TmpVectors.Matrix[1];
parent.getWorldMatrix().invertToRef(parentInverseTransform);
const localTransform = TmpVectors.Matrix[2];
finalTransform.multiplyToRef(parentInverseTransform, localTransform);
localTransform.decomposeToTransformNode(transformNode);
transformNode.rotationQuaternion?.normalize();
// Keep original scaling. Re-injecting scaling can introduce discontinuity between frames. Basically, it grows or shrinks.
transformNode.scaling.copyFrom(TmpVectors.Vector3[1]);
}
else {
transformNode.position.set(bodyTranslation[0], bodyTranslation[1], bodyTranslation[2]);
if (transformNode.rotationQuaternion) {
transformNode.rotationQuaternion.copyFrom(quat);
}
else {
quat.toEulerAnglesToRef(transformNode.rotation);
}
}
}
catch (e) {
Logger.Error(`Syncing transform failed for node ${transformNode.name}: ${e.message}...`);
}
}
}
/**
* Sets the shape of a physics body.
* @param body - The physics body to set the shape for.
* @param shape - The physics shape to set.
*
* This function is used to set the shape of a physics body. It is useful for
* creating a physics body with a specific shape, such as a box or a sphere,
* which can then be used to simulate physical interactions in a physics engine.
* This function is especially useful for meshes with multiple instances, as it
* will set the shape for each instance of the mesh.
*/
setShape(body, shape) {
const shapeHandle = shape && shape._pluginData ? shape._pluginData : BigInt(0);
if (!(body.transformNode instanceof Mesh) || !body.transformNode._thinInstanceDataStorage?.matrixData) {
this._hknp.HP_Body_SetShape(body._pluginData.hpBodyId, shapeHandle);
this._internalUpdateMassProperties(body._pluginData);
return;
}
const m = body.transformNode;
const instancesCount = m._thinInstanceDataStorage?.instancesCount ?? 0;
for (let i = 0; i < instancesCount; i++) {
this._hknp.HP_Body_SetShape(body._pluginDataInstances[i].hpBodyId, shapeHandle);
this._internalUpdateMassProperties(body._pluginDataInstances[i]);
}
}
/**
* Returns a reference to the first instance of the plugin data for a physics body.
* @param body
* @param instanceIndex
* @returns a reference to the first instance
*/
_getPluginReference(body, instanceIndex) {
return body._pluginDataInstances?.length ? body._pluginDataInstances[instanceIndex ?? 0] : body._pluginData;
}
/**
* Gets the shape of a physics body. This will create a new shape object
*
* @param body - The physics body.
* @returns The shape of the physics body.
*
*/
getShape(body) {
const pluginRef = this._getPluginReference(body);
const shapePluginData = this._hknp.HP_Body_GetShape(pluginRef.hpBodyId)[1];
if (shapePluginData != 0) {
const scene = body.transformNode.getScene();
return new PhysicsShape({ pluginData: shapePluginData }, scene);
}
return null;
}
/**
* Gets the type of a physics shape.
* @param shape - The physics shape to get the type for.
* @returns The type of the physics shape.
*
*/
getShapeType(shape) {
if (shape.type) {
return shape.type;
}
else {
//<todo This returns a native type!
return this._hknp.HP_Shape_GetType(shape._pluginData);
}
}
/**
* Sets the event mask of a physics body.
* @param body - The physics body to set the event mask for.
* @param eventMask - The event mask to set.
* @param instanceIndex - The index of the instance to set the event mask for
*
* This function is useful for setting the event mask of a physics body, which is used to determine which events the body will respond to. This is important for ensuring that the physics engine is able to accurately simulate the behavior of the body in the game world.
*/
setEventMask(body, eventMask, instanceIndex) {
this._applyToBodyOrInstances(body, (bodyPluginData) => {
this._hknp.HP_Body_SetEventMask(bodyPluginData.hpBodyId, eventMask);
}, instanceIndex);
}
/**
* Retrieves the event mask of a physics body.
*
* @param body - The physics body to retrieve the event mask from.
* @param instanceIndex - The index of the instance to retrieve the event mask from.
* @returns The event mask of the physics body.
*
*/
getEventMask(body, instanceIndex) {
const pluginRef = this._getPluginReference(body, instanceIndex);
return this._hknp.HP_Body_GetEventMask(pluginRef.hpBodyId)[1];
}
_fromMassPropertiesTuple(massPropsTuple) {
return {
centerOfMass: Vector3.FromArray(massPropsTuple[0]),
mass: massPropsTuple[1],
inertia: Vector3.FromArray(massPropsTuple[2]),
inertiaOrientation: Quaternion.FromArray(massPropsTuple[3]),
};
}
_internalUpdateMassProperties(pluginData) {
// Recompute the mass based on the shape
const newProps = this._internalComputeMassProperties(pluginData);
const massProps = pluginData.userMassProps;
// Override the computed values with any the user has set
if (massProps.centerOfMass) {
newProps[0] = massProps.centerOfMass.asArray();
}
if (massProps.mass != undefined) {
newProps[1] = massProps.mass;
}
if (massProps.inertia) {
newProps[2] = massProps.inertia.asArray();
}
if (massProps.inertiaOrientation) {
newProps[3] = massProps.inertiaOrientation.asArray();
}
this._hknp.HP_Body_SetMassProperties(pluginData.hpBodyId, newProps);
}
_internalSetMotionType(pluginData, motionType) {
switch (motionType) {
case 0 /* PhysicsMotionType.STATIC */:
this._hknp.HP_Body_SetMotionType(pluginData.hpBodyId, this._hknp.MotionType.STATIC);
break;
case 1 /* PhysicsMotionType.ANIMATED */:
this._hknp.HP_Body_SetMotionType(pluginData.hpBodyId, this._hknp.MotionType.KINEMATIC);
break;
case 2 /* PhysicsMotionType.DYNAMIC */:
this._hknp.HP_Body_SetMotionType(pluginData.hpBodyId, this._hknp.MotionType.DYNAMIC);
break;
}
}
/**
* sets the motion type of a physics body.
* @param body - The physics body to set the motion type for.
* @param motionType - The motion type to set.
* @param instanceIndex - The index of the instance to set the motion type for. If undefined, the motion type of all the bodies will be set.
*/
setMotionType(body, motionType, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginData) => {
this._internalSetMotionType(pluginData, motionType);
}, instanceIndex);
}
/**
* Gets the motion type of a physics body.
* @param body - The physics body to get the motion type from.
* @param instanceIndex - The index of the instance to get the motion type from. If not specified, the motion type of the first instance will be returned.
* @returns The motion type of the physics body.
*/
getMotionType(body, instanceIndex) {
const pluginRef = this._getPluginReference(body, instanceIndex);
const type = this._hknp.HP_Body_GetMotionType(pluginRef.hpBodyId)[1];
switch (type) {
case this._hknp.MotionType.STATIC:
return 0 /* PhysicsMotionType.STATIC */;
case this._hknp.MotionType.KINEMATIC:
return 1 /* PhysicsMotionType.ANIMATED */;
case this._hknp.MotionType.DYNAMIC:
return 2 /* PhysicsMotionType.DYNAMIC */;
}
throw new Error("Unknown motion type: " + type);
}
/**
* sets the activation control mode of a physics body, for instance if you need the body to never sleep.
* @param body - The physics body to set the activation control mode.
* @param controlMode - The activation control mode.
*/
setActivationControl(body, controlMode) {
switch (controlMode) {
case 1 /* PhysicsActivationControl.ALWAYS_ACTIVE */:
this._hknp.HP_Body_SetActivationControl(body._pluginData.hpBodyId, this._hknp.ActivationControl.ALWAYS_ACTIVE);
break;
case 2 /* PhysicsActivationControl.ALWAYS_INACTIVE */:
this._hknp.HP_Body_SetActivationControl(body._pluginData.hpBodyId, this._hknp.ActivationControl.ALWAYS_INACTIVE);
break;
case 0 /* PhysicsActivationControl.SIMULATION_CONTROLLED */:
this._hknp.HP_Body_SetActivationControl(body._pluginData.hpBodyId, this._hknp.ActivationControl.SIMULATION_CONTROLLED);
break;
}
}
_internalComputeMassProperties(pluginData) {
const shapeRes = this._hknp.HP_Body_GetShape(pluginData.hpBodyId);
if (shapeRes[0] == this._hknp.Result.RESULT_OK) {
const shapeMass = this._hknp.HP_Shape_BuildMassProperties(shapeRes[1]);
if (shapeMass[0] == this._hknp.Result.RESULT_OK) {
return shapeMass[1];
}
}
// Failed; return a unit inertia
return [[0, 0, 0], 1, [1, 1, 1], [0, 0, 0, 1]];
}
/**
* Computes the mass properties of a physics body, from it's shape
*
* @param body - The physics body to copmute the mass properties of
* @param instanceIndex - The index of the instance to compute the mass properties of.
* @returns The mass properties of the physics body.
*/
computeMassProperties(body, instanceIndex) {
const pluginRef = this._getPluginReference(body, instanceIndex);
const computed = this._internalComputeMassProperties(pluginRef);
return this._fromMassPropertiesTuple(computed);
}
/**
* Sets the mass properties of a physics body.
*
* @param body - The physics body to set the mass properties of.
* @param massProps - The mass properties to set.
* @param instanceIndex - The index of the instance to set the mass properties of. If undefined, the mass properties of all the bodies will be set.
* This function is useful for setting the mass properties of a physics body,
* such as its mass, inertia, and center of mass. This is important for
* accurately simulating the physics of the body in the physics engine.
*
*/
setMassProperties(body, massProps, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginData) => {
pluginData.userMassProps = massProps;
this._internalUpdateMassProperties(pluginData);
}, instanceIndex);
}
/**
* Gets the mass properties of a physics body.
* @param body - The physics body to get the mass properties from.
* @param instanceIndex - The index of the instance to get the mass properties from. If not specified, the mass properties of the first instance will be returned.
* @returns The mass properties of the physics body.
*/
getMassProperties(body, instanceIndex) {
const pluginRef = this._getPluginReference(body, instanceIndex);
const massPropsTuple = this._hknp.HP_Body_GetMassProperties(pluginRef.hpBodyId)[1];
return this._fromMassPropertiesTuple(massPropsTuple);
}
/**
* Sets the linear damping of the given body.
* @param body - The body to set the linear damping for.
* @param damping - The linear damping to set.
* @param instanceIndex - The index of the instance to set the linear damping for. If not specified, the linear damping of the first instance will be set.
*
* This method is useful for controlling the linear damping of a body in a physics engine.
* Linear damping is a force that opposes the motion of the body, and is proportional to the velocity of the body.
* This method allows the user to set the linear damping of a body, which can be used to control the motion of the body.
*/
setLinearDamping(body, damping, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginData) => {
this._hknp.HP_Body_SetLinearDamping(pluginData.hpBodyId, damping);
}, instanceIndex);
}
/**
* Gets the linear damping of the given body.
* @param body - The body to get the linear damping from.
* @param instanceIndex - The index of the instance to get the linear damping from. If not specified, the linear damping of the first instance will be returned.
* @returns The linear damping of the given body.
*
* This method is useful for getting the linear damping of a body in a physics engine.
* Linear damping is a force that opposes the motion of the body and is proportional to the velocity of the body.
* It is used to simulate the effects of air resistance and other forms of friction.
*/
getLinearDamping(body, instanceIndex) {
const pluginRef = this._getPluginReference(body, instanceIndex);
return this._hknp.HP_Body_GetLinearDamping(pluginRef.hpBodyId)[1];
}
/**
* Sets the angular damping of a physics body.
* @param body - The physics body to set the angular damping for.
* @param damping - The angular damping value to set.
* @param instanceIndex - The index of the instance to set the angular damping for. If not specified, the angular damping of the first instance will be set.
*
* This function is useful for controlling the angular velocity of a physics body.
* By setting the angular damping, the body's angular velocity will be reduced over time, allowing for more realistic physics simulations.
*/
setAngularDamping(body, damping, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginData) => {
this._hknp.HP_Body_SetAngularDamping(pluginData.hpBodyId, damping);
}, instanceIndex);
}
/**
* Gets the angular damping of a physics body.
* @param body - The physics body to get the angular damping from.
* @param instanceIndex - The index of the instance to get the angular damping from. If not specified, the angular damping of the first instance will be returned.
* @returns The angular damping of the body.
*
* This function is useful for retrieving the angular damping of a physics body,
* which is used to control the rotational motion of the body. The angular damping is a value between 0 and 1, where 0 is no damping and 1 is full damping.
*/
getAngularDamping(body, instanceIndex) {
const pluginRef = this._getPluginReference(body, instanceIndex);
return this._hknp.HP_Body_GetAngularDamping(pluginRef.hpBodyId)[1];
}
/**
* Sets the linear velocity of a physics body.
* @param body - The physics body to set the linear velocity of.
* @param linVel - The linear velocity to set.
* @param instanceIndex - The index of the instance to set the linear velocity of. If not specified, the linear velocity of the first instance will be set.
*
* This function is useful for setting the linear velocity of a physics body, which is necessary for simulating
* motion in a physics engine. The linear velocity is the speed and direction of the body's movement.
*/
setLinearVelocity(body, linVel, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginData) => {
this._hknp.HP_Body_SetLinearVelocity(pluginData.hpBodyId, this._bVecToV3(linVel));
}, instanceIndex);
}
/**
* Gets the linear velocity of a physics body and stores it in a given vector.
* @param body - The physics body to get the linear velocity from.
* @param linVel - The vector to store the linear velocity in.
* @param instanceIndex - The index of the instance to get the linear velocity from. If not specified, the linear velocity of the first instance will be returned.
*
* This function is useful for retrieving the linear velocity of a physics body,
* which can be used to determine the speed and direction of the body. This
* information can be used to simulate realistic physics behavior in a game.
*/
getLinearVelocityToRef(body, linVel, instanceIndex) {
const pluginRef = this._getPluginReference(body, instanceIndex);
const lv = this._hknp.HP_Body_GetLinearVelocity(pluginRef.hpBodyId)[1];
this._v3ToBvecRef(lv, linVel);
}
/*
* Apply an operation either to all instances of a body, if instanceIndex is not specified, or to a specific instance.
*/
_applyToBodyOrInstances(body, fnToApply, instanceIndex) {
if (body._pluginDataInstances?.length > 0 && instanceIndex === undefined) {
for (let i = 0; i < body._pluginDataInstances.length; i++) {
fnToApply(body._pluginDataInstances[i]);
}
}
else {
fnToApply(this._getPluginReference(body, instanceIndex));
}
}
/**
* Applies an impulse to a physics body at a given location.
* @param body - The physics body to apply the impulse to.
* @param impulse - The impulse vector to apply.
* @param location - The location in world space to apply the impulse.
* @param instanceIndex - The index of the instance to apply the impulse to. If not specified, the impulse will be applied to all instances.
*
* This method is useful for applying an impulse to a physics body at a given location.
* This can be used to simulate physical forces such as explosions, collisions, and gravity.
*/
applyImpulse(body, impulse, location, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginRef) => {
this._hknp.HP_Body_ApplyImpulse(pluginRef.hpBodyId, this._bVecToV3(location), this._bVecToV3(impulse));
}, instanceIndex);
}
/**
* Applies an angular impulse(torque) to a physics body
* @param body - The physics body to apply the impulse to.
* @param angularImpulse - The torque value
* @param instanceIndex - The index of the instance to apply the impulse to. If not specified, the impulse will be applied to all instances.
*/
applyAngularImpulse(body, angularImpulse, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginRef) => {
this._hknp.HP_Body_ApplyAngularImpulse(pluginRef.hpBodyId, this._bVecToV3(angularImpulse));
}, instanceIndex);
}
/**
* Applies a force to a physics body at a given location.
* @param body - The physics body to apply the impulse to.
* @param force - The force vector to apply.
* @param location - The location in world space to apply the impulse.
* @param instanceIndex - The index of the instance to apply the force to. If not specified, the force will be applied to all instances.
*
* This method is useful for applying a force to a physics body at a given location.
* This can be used to simulate physical forces such as explosions, collisions, and gravity.
*/
applyForce(body, force, location, instanceIndex) {
force.scaleToRef(this.getTimeStep(), this._tmpVec3[0]);
this.applyImpulse(body, this._tmpVec3[0], location, instanceIndex);
}
/**
* Sets the angular velocity of a physics body.
*
* @param body - The physics body to set the angular velocity of.
* @param angVel - The angular velocity to set.
* @param instanceIndex - The index of the instance to set the angular velocity of. If not specified, the angular velocity of the first instance will be set.
*
* This function is useful for setting the angular velocity of a physics body in a physics engine.
* This allows for more realistic simulations of physical objects, as they can be given a rotational velocity.
*/
setAngularVelocity(body, angVel, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginRef) => {
this._hknp.HP_Body_SetAngularVelocity(pluginRef.hpBodyId, this._bVecToV3(angVel));
}, instanceIndex);
}
/**
* Gets the angular velocity of a body.
* @param body - The body to get the angular velocity from.
* @param angVel - The vector3 to store the angular velocity.
* @param instanceIndex - The index of the instance to get the angular velocity from. If not specified, the angular velocity of the first instance will be returned.
*
* This method is useful for getting the angular velocity of a body in a physics engine. It
* takes the body and a vector3 as parameters and stores the angular velocity of the body
* in the vector3. This is useful for getting the angular velocity of a body in order to
* calculate the motion of the body in the physics engine.
*/
getAngularVelocityToRef(body, angVel, instanceIndex) {
const pluginRef = this._getPluginReference(body, instanceIndex);
const av = this._hknp.HP_Body_GetAngularVelocity(pluginRef.hpBodyId)[1];
this._v3ToBvecRef(av, angVel);
}
/**
* Sets the transformation of the given physics body to the given transform node.
* @param body The physics body to set the transformation for.
* @param node The transform node to set the transformation from.
* Sets the transformation of the given physics body to the given transform node.
*
* This function is useful for setting the transformation of a physics body to a
* transform node, which is necessary for the physics engine to accurately simulate
* the motion of the body. It also takes into account instances of the transform
* node, which is necessary for accurate simulation of multiple bodies with the
* same transformation.
*/
setPhysicsBodyTransformation(body, node) {
if (body.getPrestepType() == PhysicsPrestepType.TELEPORT) {
const transformNode = body.transformNode;
if (body.numInstances > 0) {
// instances
const m = transformNode;
const matrixData = m._thinInstanceDataStorage.matrixData;
if (!matrixData) {
return; // TODO: error handling
}
const instancesCount = body.numInstances;
this._createOrUpdateBodyInstances(body, body.getMotionType(), matrixData, 0, instancesCount, true);
}
else {
// regular
this._hknp.HP_Body_SetQTransform(body._pluginData.hpBodyId, this._getTransformInfos(node));
}
}
else if (body.getPrestepType() == PhysicsPrestepType.ACTION) {
this.setTargetTransform(body, node.absolutePosition, node.absoluteRotationQuaternion);
}
else if (body.getPrestepType() == PhysicsPrestepType.DISABLED) {
Logger.Warn("Prestep type is set to DISABLED. Unable to set physics body transformation.");
}
else {
Logger.Warn("Invalid prestep type set to physics body.");
}
}
/**
* Set the target transformation (position and rotation) of the body, such that the body will set its velocity to reach that target
* @param body The physics body to set the target transformation for.
* @param position The target position
* @param rotation The target rotation
* @param instanceIndex The index of the instance in an instanced body
*/
setTargetTransform(body, position, rotation, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginRef) => {
this._hknp.HP_Body_SetTargetQTransform(pluginRef.hpBodyId, [this._bVecToV3(position), this._bQuatToV4(rotation)]);
}, instanceIndex);
}
/**
* Sets the gravity factor of a body
* @param body the physics body to set the gravity factor for
* @param factor the gravity factor
* @param instanceIndex the index of the instance in an instanced body
*/
setGravityFactor(body, factor, instanceIndex) {
this._applyToBodyOrInstances(body, (pluginRef) => {
this._hknp.HP_Body_S