diff --git a/packages/dev/core/src/Physics/index.ts b/packages/dev/core/src/Physics/index.ts index e5958535f36..54d4e0c54ff 100644 --- a/packages/dev/core/src/Physics/index.ts +++ b/packages/dev/core/src/Physics/index.ts @@ -3,3 +3,4 @@ export * from "./v1/index"; export * from "./v2/index"; export * from "./physicsEngineComponent"; export * from "./v1/physicsEngineComponent"; +export * from "./physicsHelper"; diff --git a/packages/dev/core/src/Physics/physicsHelper.ts b/packages/dev/core/src/Physics/physicsHelper.ts index f930608428d..9ad53e69c93 100644 --- a/packages/dev/core/src/Physics/physicsHelper.ts +++ b/packages/dev/core/src/Physics/physicsHelper.ts @@ -1,2 +1,1164 @@ -// ES 6 Compatibility -export * from "./v1/physicsHelper"; +import type { Nullable } from "../types"; +import { Logger } from "../Misc/logger"; +import { Vector3 } from "../Maths/math.vector"; +import type { AbstractMesh } from "../Meshes/abstractMesh"; +import type { Mesh } from "../Meshes/mesh"; +import { CreateSphere } from "../Meshes/Builders/sphereBuilder"; +import { CreateCylinder } from "../Meshes/Builders/cylinderBuilder"; +import { Ray } from "../Culling/ray"; +import type { Scene } from "../scene"; +import type { PhysicsEngine as PhysicsEngineV1 } from "./physicsEngine"; +import type { PhysicsEngine as PhysicsEngineV2 } from "./v2/physicsEngine"; +import type { IPhysicsEngine } from "./IPhysicsEngine"; +import type { PhysicsImpostor } from "./v1/physicsImpostor"; +import type { PhysicsBody } from "./v2/physicsBody"; + +/** + * A helper for physics simulations + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export class PhysicsHelper { + private _scene: Scene; + private _physicsEngine: Nullable; + private _hitData: PhysicsHitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 }; + + /** + * Initializes the Physics helper + * @param scene Babylon.js scene + */ + constructor(scene: Scene) { + this._scene = scene; + this._physicsEngine = this._scene.getPhysicsEngine(); + + if (!this._physicsEngine) { + Logger.Warn("Physics engine not enabled. Please enable the physics before you can use the methods."); + return; + } + } + + /** + * Applies a radial explosion impulse + * @param origin the origin of the explosion + * @param radiusOrEventOptions the radius or the options of radial explosion + * @param strength the explosion strength + * @param falloff possible options: Constant & Linear. Defaults to Constant + * @returns A physics radial explosion event, or null + */ + public applyRadialExplosionImpulse( + origin: Vector3, + radiusOrEventOptions: number | PhysicsRadialExplosionEventOptions, + strength?: number, + falloff?: PhysicsRadialImpulseFalloff + ): Nullable { + if (!this._physicsEngine) { + Logger.Warn("Physics engine not enabled. Please enable the physics before you call this method."); + return null; + } + + if (this._physicsEngine.getPluginVersion() === 1 && (this._physicsEngine).getImpostors().length === 0) { + return null; + } + + if (this._physicsEngine.getPluginVersion() === 2 && (this._physicsEngine).getBodies().length === 0) { + return null; + } + + let useCallback = false; + if (typeof radiusOrEventOptions === "number") { + const r = radiusOrEventOptions; + radiusOrEventOptions = new PhysicsRadialExplosionEventOptions(); + radiusOrEventOptions.radius = r; + radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength; + radiusOrEventOptions.falloff = falloff ?? radiusOrEventOptions.falloff; + } else { + useCallback = !!(radiusOrEventOptions.affectedImpostorsCallback || radiusOrEventOptions.affectedBodiesCallback); + } + + const event = new PhysicsRadialExplosionEvent(this._scene, radiusOrEventOptions); + + const hitData = this._hitData; + if (this._physicsEngine.getPluginVersion() === 1) { + const affectedImpostorsWithData = Array(); + const impostors = (this._physicsEngine).getImpostors(); + impostors.forEach((impostor: PhysicsImpostor) => { + if (!event.getImpostorHitData(impostor, origin, hitData)) { + return; + } + + impostor.applyImpulse(hitData.force, hitData.contactPoint); + + if (useCallback) { + affectedImpostorsWithData.push({ + impostor: impostor, + hitData: this._copyPhysicsHitData(hitData), + }); + } + }); + + event.triggerAffectedImpostorsCallback(affectedImpostorsWithData); + } else { + const affectedBodiesWithData = Array(); + const bodies = (this._physicsEngine).getBodies(); + bodies.forEach((body: PhysicsBody) => { + if (!event.getBodyHitData(body, origin, hitData)) { + return; + } + + body.applyImpulse(hitData.force, hitData.contactPoint); + + if (useCallback) { + affectedBodiesWithData.push({ + body: body, + hitData: this._copyPhysicsHitData(hitData), + }); + } + }); + + event.triggerAffectedBodiesCallback(affectedBodiesWithData); + } + + event.dispose(false); + + return event; + } + + /** + * Applies a radial explosion force + * @param origin the origin of the explosion + * @param radiusOrEventOptions the radius or the options of radial explosion + * @param strength the explosion strength + * @param falloff possible options: Constant & Linear. Defaults to Constant + * @returns A physics radial explosion event, or null + */ + public applyRadialExplosionForce( + origin: Vector3, + radiusOrEventOptions: number | PhysicsRadialExplosionEventOptions, + strength?: number, + falloff?: PhysicsRadialImpulseFalloff + ): Nullable { + if (!this._physicsEngine) { + Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper."); + return null; + } + + if (this._physicsEngine.getPluginVersion() === 1 && (this._physicsEngine).getImpostors().length === 0) { + return null; + } + + if (this._physicsEngine.getPluginVersion() === 2 && (this._physicsEngine).getBodies().length === 0) { + return null; + } + + let useCallback = false; + if (typeof radiusOrEventOptions === "number") { + const r = radiusOrEventOptions; + radiusOrEventOptions = new PhysicsRadialExplosionEventOptions(); + radiusOrEventOptions.radius = r; + radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength; + radiusOrEventOptions.falloff = falloff ?? radiusOrEventOptions.falloff; + } else { + useCallback = !!(radiusOrEventOptions.affectedImpostorsCallback || radiusOrEventOptions.affectedBodiesCallback); + } + + const event = new PhysicsRadialExplosionEvent(this._scene, radiusOrEventOptions); + + const hitData = this._hitData; + if (this._physicsEngine.getPluginVersion() === 1) { + const affectedImpostorsWithData = Array(); + const impostors = (this._physicsEngine).getImpostors(); + impostors.forEach((impostor: PhysicsImpostor) => { + if (!event.getImpostorHitData(impostor, origin, hitData)) { + return; + } + + impostor.applyForce(hitData.force, hitData.contactPoint); + + if (useCallback) { + affectedImpostorsWithData.push({ + impostor: impostor, + hitData: this._copyPhysicsHitData(hitData), + }); + } + }); + + event.triggerAffectedImpostorsCallback(affectedImpostorsWithData); + } else { + const affectedBodiesWithData = Array(); + const bodies = (this._physicsEngine).getBodies(); + bodies.forEach((body: PhysicsBody) => { + if (!event.getBodyHitData(body, origin, hitData)) { + return; + } + + body.applyForce(hitData.force, hitData.contactPoint); + + if (useCallback) { + affectedBodiesWithData.push({ + body: body, + hitData: this._copyPhysicsHitData(hitData), + }); + } + }); + + event.triggerAffectedBodiesCallback(affectedBodiesWithData); + } + + event.dispose(false); + + return event; + } + + /** + * Creates a gravitational field + * @param origin the origin of the gravitational field + * @param radiusOrEventOptions the radius or the options of radial gravitational field + * @param strength the gravitational field strength + * @param falloff possible options: Constant & Linear. Defaults to Constant + * @returns A physics gravitational field event, or null + */ + public gravitationalField( + origin: Vector3, + radiusOrEventOptions: number | PhysicsRadialExplosionEventOptions, + strength?: number, + falloff?: PhysicsRadialImpulseFalloff + ): Nullable { + if (!this._physicsEngine) { + Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper."); + return null; + } + + if (this._physicsEngine.getPluginVersion() === 1 && (this._physicsEngine).getImpostors().length === 0) { + return null; + } + + if (this._physicsEngine.getPluginVersion() === 2 && (this._physicsEngine).getBodies().length === 0) { + return null; + } + + if (typeof radiusOrEventOptions === "number") { + const r = radiusOrEventOptions; + radiusOrEventOptions = new PhysicsRadialExplosionEventOptions(); + radiusOrEventOptions.radius = r; + radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength; + radiusOrEventOptions.falloff = falloff ?? radiusOrEventOptions.falloff; + } + + const event = new PhysicsGravitationalFieldEvent(this, this._scene, origin, radiusOrEventOptions); + + event.dispose(false); + + return event; + } + + /** + * Creates a physics updraft event + * @param origin the origin of the updraft + * @param radiusOrEventOptions the radius or the options of the updraft + * @param strength the strength of the updraft + * @param height the height of the updraft + * @param updraftMode possible options: Center & Perpendicular. Defaults to Center + * @returns A physics updraft event, or null + */ + public updraft( + origin: Vector3, + radiusOrEventOptions: number | PhysicsUpdraftEventOptions, + strength?: number, + height?: number, + updraftMode?: PhysicsUpdraftMode + ): Nullable { + if (!this._physicsEngine) { + Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper."); + return null; + } + + if (this._physicsEngine.getPluginVersion() === 1 && (this._physicsEngine).getImpostors().length === 0) { + return null; + } + + if (this._physicsEngine.getPluginVersion() === 2 && (this._physicsEngine).getBodies().length === 0) { + return null; + } + + if (typeof radiusOrEventOptions === "number") { + const r = radiusOrEventOptions; + radiusOrEventOptions = new PhysicsUpdraftEventOptions(); + radiusOrEventOptions.radius = r; + radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength; + radiusOrEventOptions.height = height ?? radiusOrEventOptions.height; + radiusOrEventOptions.updraftMode = updraftMode ?? radiusOrEventOptions.updraftMode; + } + + const event = new PhysicsUpdraftEvent(this._scene, origin, radiusOrEventOptions); + + event.dispose(false); + + return event; + } + + /** + * Creates a physics vortex event + * @param origin the of the vortex + * @param radiusOrEventOptions the radius or the options of the vortex + * @param strength the strength of the vortex + * @param height the height of the vortex + * @returns a Physics vortex event, or null + * A physics vortex event or null + */ + public vortex(origin: Vector3, radiusOrEventOptions: number | PhysicsVortexEventOptions, strength?: number, height?: number): Nullable { + if (!this._physicsEngine) { + Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper."); + return null; + } + + if (this._physicsEngine.getPluginVersion() === 1 && (this._physicsEngine).getImpostors().length === 0) { + return null; + } + + if (this._physicsEngine.getPluginVersion() === 2 && (this._physicsEngine).getBodies().length === 0) { + return null; + } + + if (typeof radiusOrEventOptions === "number") { + const r = radiusOrEventOptions; + radiusOrEventOptions = new PhysicsVortexEventOptions(); + radiusOrEventOptions.radius = r; + radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength; + radiusOrEventOptions.height = height ?? radiusOrEventOptions.height; + } + + const event = new PhysicsVortexEvent(this._scene, origin, radiusOrEventOptions); + + event.dispose(false); + + return event; + } + + private _copyPhysicsHitData(data: PhysicsHitData): PhysicsHitData { + return { force: data.force.clone(), contactPoint: data.contactPoint.clone(), distanceFromOrigin: data.distanceFromOrigin }; + } +} + +/** + * Represents a physics radial explosion event + */ +class PhysicsRadialExplosionEvent { + private _sphere: Mesh; // create a sphere, so we can get the intersecting meshes inside + private _dataFetched: boolean = false; // check if the data has been fetched. If not, do cleanup + + /** + * Initializes a radial explosion event + * @param _scene BabylonJS scene + * @param _options The options for the vortex event + */ + constructor(private _scene: Scene, private _options: PhysicsRadialExplosionEventOptions) { + this._options = { ...new PhysicsRadialExplosionEventOptions(), ...this._options }; + } + + /** + * Returns the data related to the radial explosion event (sphere). + * @returns The radial explosion event data + */ + public getData(): PhysicsRadialExplosionEventData { + this._dataFetched = true; + + return { + sphere: this._sphere, + }; + } + + private _getHitData(mesh: AbstractMesh, center: Vector3, origin: Vector3, data: PhysicsHitData): boolean { + const direction = center.subtract(origin); + + const ray = new Ray(origin, direction, this._options.radius); + const hit = ray.intersectsMesh(mesh); + + const contactPoint = hit.pickedPoint; + if (!contactPoint) { + return false; + } + + const distanceFromOrigin = Vector3.Distance(origin, contactPoint); + + if (distanceFromOrigin > this._options.radius) { + return false; + } + + const multiplier = + this._options.falloff === PhysicsRadialImpulseFalloff.Constant ? this._options.strength : this._options.strength * (1 - distanceFromOrigin / this._options.radius); + + const force = direction.multiplyByFloats(multiplier, multiplier, multiplier); + + data.force = force; + data.contactPoint = contactPoint; + data.distanceFromOrigin = distanceFromOrigin; + return true; + } + + /** + * Returns the force and contact point of the body or false, if the body is not affected by the force/impulse. + * @param body A physics body + * @param origin the origin of the explosion + * @returns A physics force and contact point, or null + */ + public getBodyHitData(body: PhysicsBody, origin: Vector3, data: PhysicsHitData): boolean { + if (body.transformNode.getClassName() !== "Mesh" && body.transformNode.getClassName() !== "InstancedMesh") { + return false; + } + + const mesh = body.transformNode as AbstractMesh; + if (!this._intersectsWithSphere(mesh, origin, this._options.radius)) { + return false; + } + + const bodyObjectCenter = body.getObjectCenter(); + this._getHitData(mesh, bodyObjectCenter, origin, data); + return true; + } + /** + * Returns the force and contact point of the impostor or false, if the impostor is not affected by the force/impulse. + * @param impostor A physics imposter + * @param origin the origin of the explosion + * @returns A physics force and contact point, or null + */ + public getImpostorHitData(impostor: PhysicsImpostor, origin: Vector3, data: PhysicsHitData): boolean { + if (impostor.mass === 0) { + return false; + } + + if (impostor.object.getClassName() !== "Mesh" && impostor.object.getClassName() !== "InstancedMesh") { + return false; + } + + const mesh = impostor.object as AbstractMesh; + if (!this._intersectsWithSphere(mesh, origin, this._options.radius)) { + return false; + } + + const impostorObjectCenter = impostor.getObjectCenter(); + + this._getHitData(mesh, impostorObjectCenter, origin, data); + return true; + } + + /** + * Triggers affected impostors callbacks + * @param affectedImpostorsWithData defines the list of affected impostors (including associated data) + */ + public triggerAffectedImpostorsCallback(affectedImpostorsWithData: Array) { + if (this._options.affectedImpostorsCallback) { + this._options.affectedImpostorsCallback(affectedImpostorsWithData); + } + } + + /** + * Triggers affected bodies callbacks + * @param affectedBodiesWithData defines the list of affected bodies (including associated data) + */ + public triggerAffectedBodiesCallback(affectedBodiesWithData: Array) { + if (this._options.affectedBodiesCallback) { + this._options.affectedBodiesCallback(affectedBodiesWithData); + } + } + /** + * Disposes the sphere. + * @param force Specifies if the sphere should be disposed by force + */ + public dispose(force: boolean = true) { + if (force) { + this._sphere.dispose(); + } else { + setTimeout(() => { + if (!this._dataFetched) { + this._sphere.dispose(); + } + }, 0); + } + } + + /*** Helpers ***/ + + private _prepareSphere(): void { + if (!this._sphere) { + this._sphere = CreateSphere("radialExplosionEventSphere", this._options.sphere, this._scene); + this._sphere.isVisible = false; + } + } + + private _intersectsWithSphere(mesh: AbstractMesh, origin: Vector3, radius: number): boolean { + this._prepareSphere(); + + this._sphere.position = origin; + this._sphere.scaling.setAll(radius * 2); + this._sphere._updateBoundingInfo(); + this._sphere.computeWorldMatrix(true); + + return this._sphere.intersectsMesh(mesh, true); + } +} + +/** + * Represents a gravitational field event + */ +class PhysicsGravitationalFieldEvent { + private _tickCallback: any; + private _sphere: Mesh; + private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup + + /** + * Initializes the physics gravitational field event + * @param _physicsHelper A physics helper + * @param _scene BabylonJS scene + * @param _origin The origin position of the gravitational field event + * @param _options The options for the vortex event + */ + constructor(private _physicsHelper: PhysicsHelper, private _scene: Scene, private _origin: Vector3, private _options: PhysicsRadialExplosionEventOptions) { + this._options = { ...new PhysicsRadialExplosionEventOptions(), ...this._options }; + + this._tickCallback = this._tick.bind(this); + + this._options.strength = this._options.strength * -1; + } + + /** + * Returns the data related to the gravitational field event (sphere). + * @returns A gravitational field event + */ + public getData(): PhysicsGravitationalFieldEventData { + this._dataFetched = true; + + return { + sphere: this._sphere, + }; + } + + /** + * Enables the gravitational field. + */ + public enable() { + this._tickCallback.call(this); + this._scene.registerBeforeRender(this._tickCallback); + } + + /** + * Disables the gravitational field. + */ + public disable() { + this._scene.unregisterBeforeRender(this._tickCallback); + } + + /** + * Disposes the sphere. + * @param force The force to dispose from the gravitational field event + */ + public dispose(force: boolean = true) { + if (force) { + this._sphere.dispose(); + } else { + setTimeout(() => { + if (!this._dataFetched) { + this._sphere.dispose(); + } + }, 0); + } + } + + private _tick() { + // Since the params won't change, we fetch the event only once + if (this._sphere) { + this._physicsHelper.applyRadialExplosionForce(this._origin, this._options); + } else { + const radialExplosionEvent = this._physicsHelper.applyRadialExplosionForce(this._origin, this._options); + if (radialExplosionEvent) { + this._sphere = radialExplosionEvent.getData().sphere.clone("radialExplosionEventSphereClone"); + } + } + } +} + +/** + * Represents a physics updraft event + */ +class PhysicsUpdraftEvent { + private _physicsEngine: PhysicsEngineV1 | PhysicsEngineV2; + private _originTop: Vector3 = Vector3.Zero(); // the most upper part of the cylinder + private _originDirection: Vector3 = Vector3.Zero(); // used if the updraftMode is perpendicular + private _tickCallback: any; + private _cylinder: Mesh; + private _cylinderPosition: Vector3 = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom + private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup + private static hitData: PhysicsHitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 }; + /** + * Initializes the physics updraft event + * @param _scene BabylonJS scene + * @param _origin The origin position of the updraft + * @param _options The options for the updraft event + */ + constructor(private _scene: Scene, private _origin: Vector3, private _options: PhysicsUpdraftEventOptions) { + this._physicsEngine = this._scene.getPhysicsEngine() as PhysicsEngineV1 | PhysicsEngineV2; + this._options = { ...new PhysicsUpdraftEventOptions(), ...this._options }; + + this._origin.addToRef(new Vector3(0, this._options.height / 2, 0), this._cylinderPosition); + this._origin.addToRef(new Vector3(0, this._options.height, 0), this._originTop); + + if (this._options.updraftMode === PhysicsUpdraftMode.Perpendicular) { + this._originDirection = this._origin.subtract(this._originTop).normalize(); + } + + this._tickCallback = this._tick.bind(this); + + this._prepareCylinder(); + } + + /** + * Returns the data related to the updraft event (cylinder). + * @returns A physics updraft event + */ + public getData(): PhysicsUpdraftEventData { + this._dataFetched = true; + + return { + cylinder: this._cylinder, + }; + } + + /** + * Enables the updraft. + */ + public enable() { + this._tickCallback.call(this); + this._scene.registerBeforeRender(this._tickCallback); + } + + /** + * Disables the updraft. + */ + public disable() { + this._scene.unregisterBeforeRender(this._tickCallback); + } + + /** + * Disposes the cylinder. + * @param force Specifies if the updraft should be disposed by force + */ + public dispose(force: boolean = true) { + if (!this._cylinder) { + return; + } + if (force) { + this._cylinder.dispose(); + } else { + setTimeout(() => { + if (!this._dataFetched) { + this._cylinder.dispose(); + } + }, 0); + } + } + + private _getHitData(center: Vector3, data: PhysicsHitData): void { + let direction: Vector3; + if (this._options.updraftMode === PhysicsUpdraftMode.Perpendicular) { + direction = this._originDirection; + } else { + direction = center.subtract(this._originTop); + } + + const distanceFromOrigin = Vector3.Distance(this._origin, center); + + const multiplier = this._options.strength * -1; + + const force = direction.multiplyByFloats(multiplier, multiplier, multiplier); + + data.force = force; + data.contactPoint = center; + data.distanceFromOrigin = distanceFromOrigin; + } + + private _getBodyHitData(body: PhysicsBody, data: PhysicsHitData): boolean { + if (body.transformNode.getClassName() !== "Mesh" && body.transformNode.getClassName() !== "InstancedMesh") { + return false; + } + const bodyObject = body.transformNode as AbstractMesh; + if (!this._intersectsWithCylinder(bodyObject)) { + return false; + } + + const center = body.getObjectCenter(); + this._getHitData(center, data); + return true; + } + + private _getImpostorHitData(impostor: PhysicsImpostor, data: PhysicsHitData): boolean { + if (impostor.mass === 0) { + return false; + } + + const impostorObject = impostor.object; + if (!this._intersectsWithCylinder(impostorObject)) { + return false; + } + + const center = impostor.getObjectCenter(); + this._getHitData(center, data); + return true; + } + + private _tick() { + const hitData = PhysicsUpdraftEvent.hitData; + if (this._physicsEngine.getPluginVersion() === 1) { + (this._physicsEngine).getImpostors().forEach((impostor: PhysicsImpostor) => { + if (!this._getImpostorHitData(impostor, hitData)) { + return; + } + + impostor.applyForce(hitData.force, hitData.contactPoint); + }); + } else { + // V2 + (this._physicsEngine).getBodies().forEach((body: PhysicsBody) => { + if (!this._getBodyHitData(body, hitData)) { + return; + } + + body.applyForce(hitData.force, hitData.contactPoint); + }); + } + } + + /*** Helpers ***/ + + private _prepareCylinder(): void { + if (!this._cylinder) { + this._cylinder = CreateCylinder( + "updraftEventCylinder", + { + height: this._options.height, + diameter: this._options.radius * 2, + }, + this._scene + ); + this._cylinder.isVisible = false; + } + } + + private _intersectsWithCylinder(mesh: AbstractMesh): boolean { + this._cylinder.position = this._cylinderPosition; + return this._cylinder.intersectsMesh(mesh, true); + } +} + +/** + * Represents a physics vortex event + */ +class PhysicsVortexEvent { + private _physicsEngine: PhysicsEngineV1 | PhysicsEngineV2; + private _originTop: Vector3 = Vector3.Zero(); // the most upper part of the cylinder + private _tickCallback: any; + private _cylinder: Mesh; + private _cylinderPosition: Vector3 = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom + private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup + private static originOnPlane: Vector3 = Vector3.Zero(); + private static hitData: PhysicsHitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 }; + + /** + * Initializes the physics vortex event + * @param _scene The BabylonJS scene + * @param _origin The origin position of the vortex + * @param _options The options for the vortex event + */ + constructor(private _scene: Scene, private _origin: Vector3, private _options: PhysicsVortexEventOptions) { + this._physicsEngine = this._scene.getPhysicsEngine() as PhysicsEngineV1 | PhysicsEngineV2; + this._options = { ...new PhysicsVortexEventOptions(), ...this._options }; + + this._origin.addToRef(new Vector3(0, this._options.height / 2, 0), this._cylinderPosition); + this._origin.addToRef(new Vector3(0, this._options.height, 0), this._originTop); + + this._tickCallback = this._tick.bind(this); + + this._prepareCylinder(); + } + + /** + * Returns the data related to the vortex event (cylinder). + * @returns The physics vortex event data + */ + public getData(): PhysicsVortexEventData { + this._dataFetched = true; + + return { + cylinder: this._cylinder, + }; + } + + /** + * Enables the vortex. + */ + public enable() { + this._tickCallback.call(this); + this._scene.registerBeforeRender(this._tickCallback); + } + + /** + * Disables the cortex. + */ + public disable() { + this._scene.unregisterBeforeRender(this._tickCallback); + } + + /** + * Disposes the sphere. + * @param force + */ + public dispose(force: boolean = true) { + if (force) { + this._cylinder.dispose(); + } else { + setTimeout(() => { + if (!this._dataFetched) { + this._cylinder.dispose(); + } + }, 0); + } + } + + private _getHitData(mesh: AbstractMesh, center: Vector3, data: PhysicsHitData): boolean { + const originOnPlane = PhysicsVortexEvent.originOnPlane; + originOnPlane.set(this._origin.x, center.y, this._origin.z); // the distance to the origin as if both objects were on a plane (Y-axis) + const originToImpostorDirection = center.subtract(originOnPlane); + + const ray = new Ray(originOnPlane, originToImpostorDirection, this._options.radius); + const hit = ray.intersectsMesh(mesh); + const contactPoint = hit.pickedPoint; + if (!contactPoint) { + return false; + } + const absoluteDistanceFromOrigin = hit.distance / this._options.radius; + + let directionToOrigin = contactPoint.normalize(); + if (absoluteDistanceFromOrigin > this._options.centripetalForceThreshold) { + directionToOrigin = directionToOrigin.negate(); + } + + let forceX: number; + let forceY: number; + let forceZ: number; + + if (absoluteDistanceFromOrigin > this._options.centripetalForceThreshold) { + forceX = directionToOrigin.x * this._options.centripetalForceMultiplier; + forceY = directionToOrigin.y * this._options.updraftForceMultiplier; + forceZ = directionToOrigin.z * this._options.centripetalForceMultiplier; + } else { + const perpendicularDirection = Vector3.Cross(originOnPlane, center).normalize(); + + forceX = (perpendicularDirection.x + directionToOrigin.x) * this._options.centrifugalForceMultiplier; + forceY = this._originTop.y * this._options.updraftForceMultiplier; + forceZ = (perpendicularDirection.z + directionToOrigin.z) * this._options.centrifugalForceMultiplier; + } + + let force = new Vector3(forceX, forceY, forceZ); + force = force.multiplyByFloats(this._options.strength, this._options.strength, this._options.strength); + + data.force = force; + data.contactPoint = center; + data.distanceFromOrigin = absoluteDistanceFromOrigin; + return true; + } + + private _getBodyHitData(body: PhysicsBody, data: PhysicsHitData): boolean { + if (body.transformNode.getClassName() !== "Mesh" && body.transformNode.getClassName() !== "InstancedMesh") { + return false; + } + + const bodyObject = body.transformNode as AbstractMesh; + + if (!this._intersectsWithCylinder(bodyObject)) { + return false; + } + + const bodyCenter = body.getObjectCenter(); + this._getHitData(bodyObject, bodyCenter, data); + return true; + } + + private _getImpostorHitData(impostor: PhysicsImpostor, data: PhysicsHitData): boolean { + if (impostor.mass === 0) { + return false; + } + + if (impostor.object.getClassName() !== "Mesh" && impostor.object.getClassName() !== "InstancedMesh") { + return false; + } + + const impostorObject = impostor.object as AbstractMesh; + if (!this._intersectsWithCylinder(impostorObject)) { + return false; + } + + const impostorObjectCenter = impostor.getObjectCenter(); + this._getHitData(impostorObject, impostorObjectCenter, data); + return true; + } + + private _tick() { + const hitData = PhysicsVortexEvent.hitData; + if (this._physicsEngine.getPluginVersion() === 1) { + (this._physicsEngine).getImpostors().forEach((impostor: PhysicsImpostor) => { + if (!this._getImpostorHitData(impostor, hitData)) { + return; + } + + impostor.applyForce(hitData.force, hitData.contactPoint); + }); + } else { + (this._physicsEngine).getBodies().forEach((body: PhysicsBody) => { + if (!this._getBodyHitData(body, hitData)) { + return; + } + + body.applyForce(hitData.force, hitData.contactPoint); + }); + } + } + + /*** Helpers ***/ + + private _prepareCylinder(): void { + if (!this._cylinder) { + this._cylinder = CreateCylinder( + "vortexEventCylinder", + { + height: this._options.height, + diameter: this._options.radius * 2, + }, + this._scene + ); + this._cylinder.isVisible = false; + } + } + + private _intersectsWithCylinder(mesh: AbstractMesh): boolean { + this._cylinder.position = this._cylinderPosition; + + return this._cylinder.intersectsMesh(mesh, true); + } +} + +/** + * Options fot the radial explosion event + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export class PhysicsRadialExplosionEventOptions { + /** + * The radius of the sphere for the radial explosion. + */ + radius: number = 5; + + /** + * The strength of the explosion. + */ + strength: number = 10; + + /** + * The strength of the force in correspondence to the distance of the affected object + */ + falloff: PhysicsRadialImpulseFalloff = PhysicsRadialImpulseFalloff.Constant; + + /** + * Sphere options for the radial explosion. + */ + sphere: { segments: number; diameter: number } = { segments: 32, diameter: 1 }; + + /** + * Sphere options for the radial explosion. + */ + affectedImpostorsCallback: (affectedImpostorsWithData: Array) => void; + + /** + * Sphere options for the radial explosion. + */ + affectedBodiesCallback: (affectedBodiesWithData: Array) => void; +} + +/** + * Options fot the updraft event + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export class PhysicsUpdraftEventOptions { + /** + * The radius of the cylinder for the vortex + */ + radius: number = 5; + + /** + * The strength of the updraft. + */ + strength: number = 10; + + /** + * The height of the cylinder for the updraft. + */ + height: number = 10; + + /** + * The mode for the the updraft. + */ + updraftMode: PhysicsUpdraftMode = PhysicsUpdraftMode.Center; +} + +/** + * Options fot the vortex event + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export class PhysicsVortexEventOptions { + /** + * The radius of the cylinder for the vortex + */ + radius: number = 5; + + /** + * The strength of the vortex. + */ + strength: number = 10; + + /** + * The height of the cylinder for the vortex. + */ + height: number = 10; + + /** + * At which distance, relative to the radius the centripetal forces should kick in? Range: 0-1 + */ + centripetalForceThreshold: number = 0.7; + + /** + * This multiplier determines with how much force the objects will be pushed sideways/around the vortex, when below the threshold. + */ + centripetalForceMultiplier: number = 5; + + /** + * This multiplier determines with how much force the objects will be pushed sideways/around the vortex, when above the threshold. + */ + centrifugalForceMultiplier: number = 0.5; + + /** + * This multiplier determines with how much force the objects will be pushed upwards, when in the vortex. + */ + updraftForceMultiplier: number = 0.02; +} + +/** + * The strength of the force in correspondence to the distance of the affected object + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export enum PhysicsRadialImpulseFalloff { + /** Defines that impulse is constant in strength across it's whole radius */ + Constant, + /** Defines that impulse gets weaker if it's further from the origin */ + Linear, +} + +/** + * The strength of the force in correspondence to the distance of the affected object + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export enum PhysicsUpdraftMode { + /** Defines that the upstream forces will pull towards the top center of the cylinder */ + Center, + /** Defines that once a impostor is inside the cylinder, it will shoot out perpendicular from the ground of the cylinder */ + Perpendicular, +} + +/** + * Interface for a physics hit data + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export interface PhysicsHitData { + /** + * The force applied at the contact point + */ + force: Vector3; + /** + * The contact point + */ + contactPoint: Vector3; + /** + * The distance from the origin to the contact point + */ + distanceFromOrigin: number; +} + +/** + * Interface for radial explosion event data + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export interface PhysicsRadialExplosionEventData { + /** + * A sphere used for the radial explosion event + */ + sphere: Mesh; +} + +/** + * Interface for gravitational field event data + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export interface PhysicsGravitationalFieldEventData { + /** + * A sphere mesh used for the gravitational field event + */ + sphere: Mesh; +} + +/** + * Interface for updraft event data + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export interface PhysicsUpdraftEventData { + /** + * A cylinder used for the updraft event + */ + cylinder: Mesh; +} + +/** + * Interface for vortex event data + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export interface PhysicsVortexEventData { + /** + * A cylinder used for the vortex event + */ + cylinder: Mesh; +} + +/** + * Interface for an affected physics impostor + * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class + */ +export interface PhysicsAffectedImpostorWithData { + /** + * The impostor affected by the effect + */ + impostor: PhysicsImpostor; + + /** + * The data about the hit/force from the explosion + */ + hitData: PhysicsHitData; +} + +/** + * Interface for an affected physics body + * @see + */ +export interface PhysicsAffectedBodyWithData { + /** + * The impostor affected by the effect + */ + body: PhysicsBody; + + /** + * The data about the hit/force from the explosion + */ + hitData: PhysicsHitData; +} diff --git a/packages/dev/core/src/Physics/v1/index.ts b/packages/dev/core/src/Physics/v1/index.ts index 3fc1fc56741..8be22d70cef 100644 --- a/packages/dev/core/src/Physics/v1/index.ts +++ b/packages/dev/core/src/Physics/v1/index.ts @@ -5,4 +5,3 @@ export * from "./physicsEngineComponent"; export * from "./physicsImpostor"; export * from "./physicsJoint"; export * from "./Plugins/index"; -export * from "./physicsHelper"; diff --git a/packages/dev/core/src/Physics/v1/physicsHelper.ts b/packages/dev/core/src/Physics/v1/physicsHelper.ts deleted file mode 100644 index c88819c0a6d..00000000000 --- a/packages/dev/core/src/Physics/v1/physicsHelper.ts +++ /dev/null @@ -1,957 +0,0 @@ -import type { Nullable } from "../../types"; -import { Logger } from "../../Misc/logger"; -import { Vector3 } from "../../Maths/math.vector"; -import type { AbstractMesh } from "../../Meshes/abstractMesh"; -import type { Mesh } from "../../Meshes/mesh"; -import { CreateSphere } from "../../Meshes/Builders/sphereBuilder"; -import { CreateCylinder } from "../../Meshes/Builders/cylinderBuilder"; -import { Ray } from "../../Culling/ray"; -import type { Scene } from "../../scene"; -import type { PhysicsEngine } from "./physicsEngine"; -import type { PhysicsImpostor } from "./physicsImpostor"; - -/** - * A helper for physics simulations - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export class PhysicsHelper { - private _scene: Scene; - private _physicsEngine: Nullable; - - /** - * Initializes the Physics helper - * @param scene Babylon.js scene - */ - constructor(scene: Scene) { - this._scene = scene; - this._physicsEngine = this._scene.getPhysicsEngine() as any; - - if (!this._physicsEngine) { - Logger.Warn("Physics engine not enabled. Please enable the physics before you can use the methods."); - return; - } - } - - /** - * Applies a radial explosion impulse - * @param origin the origin of the explosion - * @param radiusOrEventOptions the radius or the options of radial explosion - * @param strength the explosion strength - * @param falloff possible options: Constant & Linear. Defaults to Constant - * @returns A physics radial explosion event, or null - */ - public applyRadialExplosionImpulse( - origin: Vector3, - radiusOrEventOptions: number | PhysicsRadialExplosionEventOptions, - strength?: number, - falloff?: PhysicsRadialImpulseFalloff - ): Nullable { - if (!this._physicsEngine) { - Logger.Warn("Physics engine not enabled. Please enable the physics before you call this method."); - return null; - } - - const impostors = this._physicsEngine.getImpostors(); - if (impostors.length === 0) { - return null; - } - - if (typeof radiusOrEventOptions === "number") { - radiusOrEventOptions = new PhysicsRadialExplosionEventOptions(); - radiusOrEventOptions.radius = (radiusOrEventOptions); - radiusOrEventOptions.strength = strength || radiusOrEventOptions.strength; - radiusOrEventOptions.falloff = falloff || radiusOrEventOptions.falloff; - } - - const event = new PhysicsRadialExplosionEvent(this._scene, radiusOrEventOptions); - const affectedImpostorsWithData = Array(); - - impostors.forEach((impostor) => { - const impostorHitData = event.getImpostorHitData(impostor, origin); - if (!impostorHitData) { - return; - } - - impostor.applyImpulse(impostorHitData.force, impostorHitData.contactPoint); - - affectedImpostorsWithData.push({ - impostor: impostor, - hitData: impostorHitData, - }); - }); - - event.triggerAffectedImpostorsCallback(affectedImpostorsWithData); - - event.dispose(false); - - return event; - } - - /** - * Applies a radial explosion force - * @param origin the origin of the explosion - * @param radiusOrEventOptions the radius or the options of radial explosion - * @param strength the explosion strength - * @param falloff possible options: Constant & Linear. Defaults to Constant - * @returns A physics radial explosion event, or null - */ - public applyRadialExplosionForce( - origin: Vector3, - radiusOrEventOptions: number | PhysicsRadialExplosionEventOptions, - strength?: number, - falloff?: PhysicsRadialImpulseFalloff - ): Nullable { - if (!this._physicsEngine) { - Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper."); - return null; - } - - const impostors = this._physicsEngine.getImpostors(); - if (impostors.length === 0) { - return null; - } - - if (typeof radiusOrEventOptions === "number") { - radiusOrEventOptions = new PhysicsRadialExplosionEventOptions(); - radiusOrEventOptions.radius = (radiusOrEventOptions); - radiusOrEventOptions.strength = strength || radiusOrEventOptions.strength; - radiusOrEventOptions.falloff = falloff || radiusOrEventOptions.falloff; - } - - const event = new PhysicsRadialExplosionEvent(this._scene, radiusOrEventOptions); - const affectedImpostorsWithData = Array(); - - impostors.forEach((impostor) => { - const impostorHitData = event.getImpostorHitData(impostor, origin); - if (!impostorHitData) { - return; - } - - impostor.applyForce(impostorHitData.force, impostorHitData.contactPoint); - - affectedImpostorsWithData.push({ - impostor: impostor, - hitData: impostorHitData, - }); - }); - - event.triggerAffectedImpostorsCallback(affectedImpostorsWithData); - - event.dispose(false); - - return event; - } - - /** - * Creates a gravitational field - * @param origin the origin of the explosion - * @param radiusOrEventOptions the radius or the options of radial explosion - * @param strength the explosion strength - * @param falloff possible options: Constant & Linear. Defaults to Constant - * @returns A physics gravitational field event, or null - */ - public gravitationalField( - origin: Vector3, - radiusOrEventOptions: number | PhysicsRadialExplosionEventOptions, - strength?: number, - falloff?: PhysicsRadialImpulseFalloff - ): Nullable { - if (!this._physicsEngine) { - Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper."); - return null; - } - - const impostors = this._physicsEngine.getImpostors(); - if (impostors.length === 0) { - return null; - } - - if (typeof radiusOrEventOptions === "number") { - radiusOrEventOptions = new PhysicsRadialExplosionEventOptions(); - radiusOrEventOptions.radius = (radiusOrEventOptions); - radiusOrEventOptions.strength = strength || radiusOrEventOptions.strength; - radiusOrEventOptions.falloff = falloff || radiusOrEventOptions.falloff; - } - - const event = new PhysicsGravitationalFieldEvent(this, this._scene, origin, radiusOrEventOptions); - - event.dispose(false); - - return event; - } - - /** - * Creates a physics updraft event - * @param origin the origin of the updraft - * @param radiusOrEventOptions the radius or the options of the updraft - * @param strength the strength of the updraft - * @param height the height of the updraft - * @param updraftMode possible options: Center & Perpendicular. Defaults to Center - * @returns A physics updraft event, or null - */ - public updraft( - origin: Vector3, - radiusOrEventOptions: number | PhysicsUpdraftEventOptions, - strength?: number, - height?: number, - updraftMode?: PhysicsUpdraftMode - ): Nullable { - if (!this._physicsEngine) { - Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper."); - return null; - } - - if (this._physicsEngine.getImpostors().length === 0) { - return null; - } - - if (typeof radiusOrEventOptions === "number") { - radiusOrEventOptions = new PhysicsUpdraftEventOptions(); - radiusOrEventOptions.radius = (radiusOrEventOptions); - radiusOrEventOptions.strength = strength || radiusOrEventOptions.strength; - radiusOrEventOptions.height = height || radiusOrEventOptions.height; - radiusOrEventOptions.updraftMode = updraftMode || radiusOrEventOptions.updraftMode; - } - - const event = new PhysicsUpdraftEvent(this._scene, origin, radiusOrEventOptions); - - event.dispose(false); - - return event; - } - - /** - * Creates a physics vortex event - * @param origin the of the vortex - * @param radiusOrEventOptions the radius or the options of the vortex - * @param strength the strength of the vortex - * @param height the height of the vortex - * @returns a Physics vortex event, or null - * A physics vortex event or null - */ - public vortex(origin: Vector3, radiusOrEventOptions: number | PhysicsVortexEventOptions, strength?: number, height?: number): Nullable { - if (!this._physicsEngine) { - Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper."); - return null; - } - - if (this._physicsEngine.getImpostors().length === 0) { - return null; - } - - if (typeof radiusOrEventOptions === "number") { - radiusOrEventOptions = new PhysicsVortexEventOptions(); - radiusOrEventOptions.radius = (radiusOrEventOptions); - radiusOrEventOptions.strength = strength || radiusOrEventOptions.strength; - radiusOrEventOptions.height = height || radiusOrEventOptions.height; - } - - const event = new PhysicsVortexEvent(this._scene, origin, radiusOrEventOptions); - - event.dispose(false); - - return event; - } -} - -/** - * Represents a physics radial explosion event - */ -class PhysicsRadialExplosionEvent { - private _sphere: Mesh; // create a sphere, so we can get the intersecting meshes inside - private _dataFetched: boolean = false; // check if the data has been fetched. If not, do cleanup - - /** - * Initializes a radial explosion event - * @param _scene BabylonJS scene - * @param _options The options for the vortex event - */ - constructor(private _scene: Scene, private _options: PhysicsRadialExplosionEventOptions) { - this._options = { ...new PhysicsRadialExplosionEventOptions(), ...this._options }; - } - - /** - * Returns the data related to the radial explosion event (sphere). - * @returns The radial explosion event data - */ - public getData(): PhysicsRadialExplosionEventData { - this._dataFetched = true; - - return { - sphere: this._sphere, - }; - } - - /** - * Returns the force and contact point of the impostor or false, if the impostor is not affected by the force/impulse. - * @param impostor A physics imposter - * @param origin the origin of the explosion - * @returns {Nullable} A physics force and contact point, or null - */ - public getImpostorHitData(impostor: PhysicsImpostor, origin: Vector3): Nullable { - if (impostor.mass === 0) { - return null; - } - - if (!this._intersectsWithSphere(impostor, origin, this._options.radius)) { - return null; - } - - if (impostor.object.getClassName() !== "Mesh" && impostor.object.getClassName() !== "InstancedMesh") { - return null; - } - - const impostorObjectCenter = impostor.getObjectCenter(); - const direction = impostorObjectCenter.subtract(origin); - - const ray = new Ray(origin, direction, this._options.radius); - const hit = ray.intersectsMesh(impostor.object); - - const contactPoint = hit.pickedPoint; - if (!contactPoint) { - return null; - } - - const distanceFromOrigin = Vector3.Distance(origin, contactPoint); - - if (distanceFromOrigin > this._options.radius) { - return null; - } - - const multiplier = - this._options.falloff === PhysicsRadialImpulseFalloff.Constant ? this._options.strength : this._options.strength * (1 - distanceFromOrigin / this._options.radius); - - const force = direction.multiplyByFloats(multiplier, multiplier, multiplier); - - return { force: force, contactPoint: contactPoint, distanceFromOrigin: distanceFromOrigin }; - } - - /** - * Triggers affected impostors callbacks - * @param affectedImpostorsWithData defines the list of affected impostors (including associated data) - */ - public triggerAffectedImpostorsCallback(affectedImpostorsWithData: Array) { - if (this._options.affectedImpostorsCallback) { - this._options.affectedImpostorsCallback(affectedImpostorsWithData); - } - } - - /** - * Disposes the sphere. - * @param force Specifies if the sphere should be disposed by force - */ - public dispose(force: boolean = true) { - if (force) { - this._sphere.dispose(); - } else { - setTimeout(() => { - if (!this._dataFetched) { - this._sphere.dispose(); - } - }, 0); - } - } - - /*** Helpers ***/ - - private _prepareSphere(): void { - if (!this._sphere) { - this._sphere = CreateSphere("radialExplosionEventSphere", this._options.sphere, this._scene); - this._sphere.isVisible = false; - } - } - - private _intersectsWithSphere(impostor: PhysicsImpostor, origin: Vector3, radius: number): boolean { - const impostorObject = impostor.object; - - this._prepareSphere(); - - this._sphere.position = origin; - this._sphere.scaling = new Vector3(radius * 2, radius * 2, radius * 2); - this._sphere._updateBoundingInfo(); - this._sphere.computeWorldMatrix(true); - - return this._sphere.intersectsMesh(impostorObject, true); - } -} - -/** - * Represents a gravitational field event - */ -class PhysicsGravitationalFieldEvent { - private _tickCallback: any; - private _sphere: Mesh; - private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup - - /** - * Initializes the physics gravitational field event - * @param _physicsHelper A physics helper - * @param _scene BabylonJS scene - * @param _origin The origin position of the gravitational field event - * @param _options The options for the vortex event - */ - constructor(private _physicsHelper: PhysicsHelper, private _scene: Scene, private _origin: Vector3, private _options: PhysicsRadialExplosionEventOptions) { - this._options = { ...new PhysicsRadialExplosionEventOptions(), ...this._options }; - - this._tickCallback = this._tick.bind(this); - - this._options.strength = this._options.strength * -1; - } - - /** - * Returns the data related to the gravitational field event (sphere). - * @returns A gravitational field event - */ - public getData(): PhysicsGravitationalFieldEventData { - this._dataFetched = true; - - return { - sphere: this._sphere, - }; - } - - /** - * Enables the gravitational field. - */ - public enable() { - this._tickCallback.call(this); - this._scene.registerBeforeRender(this._tickCallback); - } - - /** - * Disables the gravitational field. - */ - public disable() { - this._scene.unregisterBeforeRender(this._tickCallback); - } - - /** - * Disposes the sphere. - * @param force The force to dispose from the gravitational field event - */ - public dispose(force: boolean = true) { - if (force) { - this._sphere.dispose(); - } else { - setTimeout(() => { - if (!this._dataFetched) { - this._sphere.dispose(); - } - }, 0); - } - } - - private _tick() { - // Since the params won't change, we fetch the event only once - if (this._sphere) { - this._physicsHelper.applyRadialExplosionForce(this._origin, this._options); - } else { - const radialExplosionEvent = this._physicsHelper.applyRadialExplosionForce(this._origin, this._options); - if (radialExplosionEvent) { - this._sphere = radialExplosionEvent.getData().sphere.clone("radialExplosionEventSphereClone"); - } - } - } -} - -/** - * Represents a physics updraft event - */ -class PhysicsUpdraftEvent { - private _physicsEngine: PhysicsEngine; - private _originTop: Vector3 = Vector3.Zero(); // the most upper part of the cylinder - private _originDirection: Vector3 = Vector3.Zero(); // used if the updraftMode is perpendicular - private _tickCallback: any; - private _cylinder: Mesh; - private _cylinderPosition: Vector3 = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom - private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup - - /** - * Initializes the physics updraft event - * @param _scene BabylonJS scene - * @param _origin The origin position of the updraft - * @param _options The options for the updraft event - */ - constructor(private _scene: Scene, private _origin: Vector3, private _options: PhysicsUpdraftEventOptions) { - this._physicsEngine = this._scene.getPhysicsEngine(); - this._options = { ...new PhysicsUpdraftEventOptions(), ...this._options }; - - this._origin.addToRef(new Vector3(0, this._options.height / 2, 0), this._cylinderPosition); - this._origin.addToRef(new Vector3(0, this._options.height, 0), this._originTop); - - if (this._options.updraftMode === PhysicsUpdraftMode.Perpendicular) { - this._originDirection = this._origin.subtract(this._originTop).normalize(); - } - - this._tickCallback = this._tick.bind(this); - - this._prepareCylinder(); - } - - /** - * Returns the data related to the updraft event (cylinder). - * @returns A physics updraft event - */ - public getData(): PhysicsUpdraftEventData { - this._dataFetched = true; - - return { - cylinder: this._cylinder, - }; - } - - /** - * Enables the updraft. - */ - public enable() { - this._tickCallback.call(this); - this._scene.registerBeforeRender(this._tickCallback); - } - - /** - * Disables the updraft. - */ - public disable() { - this._scene.unregisterBeforeRender(this._tickCallback); - } - - /** - * Disposes the cylinder. - * @param force Specifies if the updraft should be disposed by force - */ - public dispose(force: boolean = true) { - if (!this._cylinder) { - return; - } - if (force) { - this._cylinder.dispose(); - } else { - setTimeout(() => { - if (!this._dataFetched) { - this._cylinder.dispose(); - } - }, 0); - } - } - - private _getImpostorHitData(impostor: PhysicsImpostor): Nullable { - if (impostor.mass === 0) { - return null; - } - - if (!this._intersectsWithCylinder(impostor)) { - return null; - } - - const impostorObjectCenter = impostor.getObjectCenter(); - - let direction: Vector3; - if (this._options.updraftMode === PhysicsUpdraftMode.Perpendicular) { - direction = this._originDirection; - } else { - direction = impostorObjectCenter.subtract(this._originTop); - } - - const distanceFromOrigin = Vector3.Distance(this._origin, impostorObjectCenter); - - const multiplier = this._options.strength * -1; - - const force = direction.multiplyByFloats(multiplier, multiplier, multiplier); - - return { force: force, contactPoint: impostorObjectCenter, distanceFromOrigin: distanceFromOrigin }; - } - - private _tick() { - this._physicsEngine.getImpostors().forEach((impostor) => { - const impostorHitData = this._getImpostorHitData(impostor); - if (!impostorHitData) { - return; - } - - impostor.applyForce(impostorHitData.force, impostorHitData.contactPoint); - }); - } - - /*** Helpers ***/ - - private _prepareCylinder(): void { - if (!this._cylinder) { - this._cylinder = CreateCylinder( - "updraftEventCylinder", - { - height: this._options.height, - diameter: this._options.radius * 2, - }, - this._scene - ); - this._cylinder.isVisible = false; - } - } - - private _intersectsWithCylinder(impostor: PhysicsImpostor): boolean { - const impostorObject = impostor.object; - - this._cylinder.position = this._cylinderPosition; - - return this._cylinder.intersectsMesh(impostorObject, true); - } -} - -/** - * Represents a physics vortex event - */ -class PhysicsVortexEvent { - private _physicsEngine: PhysicsEngine; - private _originTop: Vector3 = Vector3.Zero(); // the most upper part of the cylinder - private _tickCallback: any; - private _cylinder: Mesh; - private _cylinderPosition: Vector3 = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom - private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup - - /** - * Initializes the physics vortex event - * @param _scene The BabylonJS scene - * @param _origin The origin position of the vortex - * @param _options The options for the vortex event - */ - constructor(private _scene: Scene, private _origin: Vector3, private _options: PhysicsVortexEventOptions) { - this._physicsEngine = this._scene.getPhysicsEngine(); - this._options = { ...new PhysicsVortexEventOptions(), ...this._options }; - - this._origin.addToRef(new Vector3(0, this._options.height / 2, 0), this._cylinderPosition); - this._origin.addToRef(new Vector3(0, this._options.height, 0), this._originTop); - - this._tickCallback = this._tick.bind(this); - - this._prepareCylinder(); - } - - /** - * Returns the data related to the vortex event (cylinder). - * @returns The physics vortex event data - */ - public getData(): PhysicsVortexEventData { - this._dataFetched = true; - - return { - cylinder: this._cylinder, - }; - } - - /** - * Enables the vortex. - */ - public enable() { - this._tickCallback.call(this); - this._scene.registerBeforeRender(this._tickCallback); - } - - /** - * Disables the cortex. - */ - public disable() { - this._scene.unregisterBeforeRender(this._tickCallback); - } - - /** - * Disposes the sphere. - * @param force - */ - public dispose(force: boolean = true) { - if (force) { - this._cylinder.dispose(); - } else { - setTimeout(() => { - if (!this._dataFetched) { - this._cylinder.dispose(); - } - }, 0); - } - } - - private _getImpostorHitData(impostor: PhysicsImpostor): Nullable { - if (impostor.mass === 0) { - return null; - } - - if (!this._intersectsWithCylinder(impostor)) { - return null; - } - - if (impostor.object.getClassName() !== "Mesh" && impostor.object.getClassName() !== "InstancedMesh") { - return null; - } - - const impostorObjectCenter = impostor.getObjectCenter(); - const originOnPlane = new Vector3(this._origin.x, impostorObjectCenter.y, this._origin.z); // the distance to the origin as if both objects were on a plane (Y-axis) - const originToImpostorDirection = impostorObjectCenter.subtract(originOnPlane); - - const ray = new Ray(originOnPlane, originToImpostorDirection, this._options.radius); - const hit = ray.intersectsMesh(impostor.object); - const contactPoint = hit.pickedPoint; - if (!contactPoint) { - return null; - } - const absoluteDistanceFromOrigin = hit.distance / this._options.radius; - - let directionToOrigin = contactPoint.normalize(); - if (absoluteDistanceFromOrigin > this._options.centripetalForceThreshold) { - directionToOrigin = directionToOrigin.negate(); - } - - let forceX: number; - let forceY: number; - let forceZ: number; - - if (absoluteDistanceFromOrigin > this._options.centripetalForceThreshold) { - forceX = directionToOrigin.x * this._options.centripetalForceMultiplier; - forceY = directionToOrigin.y * this._options.updraftForceMultiplier; - forceZ = directionToOrigin.z * this._options.centripetalForceMultiplier; - } else { - const perpendicularDirection = Vector3.Cross(originOnPlane, impostorObjectCenter).normalize(); - - forceX = (perpendicularDirection.x + directionToOrigin.x) * this._options.centrifugalForceMultiplier; - forceY = this._originTop.y * this._options.updraftForceMultiplier; - forceZ = (perpendicularDirection.z + directionToOrigin.z) * this._options.centrifugalForceMultiplier; - } - - let force = new Vector3(forceX, forceY, forceZ); - force = force.multiplyByFloats(this._options.strength, this._options.strength, this._options.strength); - - return { force: force, contactPoint: impostorObjectCenter, distanceFromOrigin: absoluteDistanceFromOrigin }; - } - - private _tick() { - this._physicsEngine.getImpostors().forEach((impostor) => { - const impostorHitData = this._getImpostorHitData(impostor); - if (!impostorHitData) { - return; - } - - impostor.applyForce(impostorHitData.force, impostorHitData.contactPoint); - }); - } - - /*** Helpers ***/ - - private _prepareCylinder(): void { - if (!this._cylinder) { - this._cylinder = CreateCylinder( - "vortexEventCylinder", - { - height: this._options.height, - diameter: this._options.radius * 2, - }, - this._scene - ); - this._cylinder.isVisible = false; - } - } - - private _intersectsWithCylinder(impostor: PhysicsImpostor): boolean { - const impostorObject = impostor.object; - - this._cylinder.position = this._cylinderPosition; - - return this._cylinder.intersectsMesh(impostorObject, true); - } -} - -/** - * Options fot the radial explosion event - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export class PhysicsRadialExplosionEventOptions { - /** - * The radius of the sphere for the radial explosion. - */ - radius: number = 5; - - /** - * The strength of the explosion. - */ - strength: number = 10; - - /** - * The strength of the force in correspondence to the distance of the affected object - */ - falloff: PhysicsRadialImpulseFalloff = PhysicsRadialImpulseFalloff.Constant; - - /** - * Sphere options for the radial explosion. - */ - sphere: { segments: number; diameter: number } = { segments: 32, diameter: 1 }; - - /** - * Sphere options for the radial explosion. - */ - affectedImpostorsCallback: (affectedImpostorsWithData: Array) => void; -} - -/** - * Options fot the updraft event - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export class PhysicsUpdraftEventOptions { - /** - * The radius of the cylinder for the vortex - */ - radius: number = 5; - - /** - * The strength of the updraft. - */ - strength: number = 10; - - /** - * The height of the cylinder for the updraft. - */ - height: number = 10; - - /** - * The mode for the the updraft. - */ - updraftMode: PhysicsUpdraftMode = PhysicsUpdraftMode.Center; -} - -/** - * Options fot the vortex event - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export class PhysicsVortexEventOptions { - /** - * The radius of the cylinder for the vortex - */ - radius: number = 5; - - /** - * The strength of the vortex. - */ - strength: number = 10; - - /** - * The height of the cylinder for the vortex. - */ - height: number = 10; - - /** - * At which distance, relative to the radius the centripetal forces should kick in? Range: 0-1 - */ - centripetalForceThreshold: number = 0.7; - - /** - * This multiplier determines with how much force the objects will be pushed sideways/around the vortex, when below the threshold. - */ - centripetalForceMultiplier: number = 5; - - /** - * This multiplier determines with how much force the objects will be pushed sideways/around the vortex, when above the threshold. - */ - centrifugalForceMultiplier: number = 0.5; - - /** - * This multiplier determines with how much force the objects will be pushed upwards, when in the vortex. - */ - updraftForceMultiplier: number = 0.02; -} - -/** - * The strength of the force in correspondence to the distance of the affected object - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export enum PhysicsRadialImpulseFalloff { - /** Defines that impulse is constant in strength across it's whole radius */ - Constant, - /** Defines that impulse gets weaker if it's further from the origin */ - Linear, -} - -/** - * The strength of the force in correspondence to the distance of the affected object - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export enum PhysicsUpdraftMode { - /** Defines that the upstream forces will pull towards the top center of the cylinder */ - Center, - /** Defines that once a impostor is inside the cylinder, it will shoot out perpendicular from the ground of the cylinder */ - Perpendicular, -} - -/** - * Interface for a physics hit data - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export interface PhysicsHitData { - /** - * The force applied at the contact point - */ - force: Vector3; - /** - * The contact point - */ - contactPoint: Vector3; - /** - * The distance from the origin to the contact point - */ - distanceFromOrigin: number; -} - -/** - * Interface for radial explosion event data - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export interface PhysicsRadialExplosionEventData { - /** - * A sphere used for the radial explosion event - */ - sphere: Mesh; -} - -/** - * Interface for gravitational field event data - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export interface PhysicsGravitationalFieldEventData { - /** - * A sphere mesh used for the gravitational field event - */ - sphere: Mesh; -} - -/** - * Interface for updraft event data - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export interface PhysicsUpdraftEventData { - /** - * A cylinder used for the updraft event - */ - cylinder: Mesh; -} - -/** - * Interface for vortex event data - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export interface PhysicsVortexEventData { - /** - * A cylinder used for the vortex event - */ - cylinder: Mesh; -} - -/** - * Interface for an affected physics impostor - * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class - */ -export interface PhysicsAffectedImpostorWithData { - /** - * The impostor affected by the effect - */ - impostor: PhysicsImpostor; - - /** - * The data about the hit/force from the explosion - */ - hitData: PhysicsHitData; -} diff --git a/packages/dev/core/src/Physics/v2/IPhysicsEnginePlugin.ts b/packages/dev/core/src/Physics/v2/IPhysicsEnginePlugin.ts index 3c3c30f6e69..87e1d9e0986 100644 --- a/packages/dev/core/src/Physics/v2/IPhysicsEnginePlugin.ts +++ b/packages/dev/core/src/Physics/v2/IPhysicsEnginePlugin.ts @@ -92,7 +92,7 @@ export interface MassProperties { /** * */ - intertia: Vector3; + inertia: Vector3; /** * */ @@ -138,6 +138,7 @@ export interface IPhysicsEnginePluginV2 { setLinearVelocity(body: PhysicsBody, linVel: Vector3): void; getLinearVelocityToRef(body: PhysicsBody, linVel: Vector3): void; applyImpulse(body: PhysicsBody, location: Vector3, impulse: Vector3): void; + applyForce(body: PhysicsBody, location: Vector3, force: Vector3): void; setAngularVelocity(body: PhysicsBody, angVel: Vector3): void; getAngularVelocityToRef(body: PhysicsBody, angVel: Vector3): void; getBodyGeometry(body: PhysicsBody): {}; diff --git a/packages/dev/core/src/Physics/v2/index.ts b/packages/dev/core/src/Physics/v2/index.ts index 14c3f37c93a..c427e695f7f 100644 --- a/packages/dev/core/src/Physics/v2/index.ts +++ b/packages/dev/core/src/Physics/v2/index.ts @@ -5,3 +5,4 @@ export * from "./physicsShape"; export * from "./physicsConstraint"; export * from "./physicsMaterial"; export * from "./physicsAggregate"; +export * from "./IPhysicsEnginePlugin"; diff --git a/packages/dev/core/src/Physics/v2/physicsAggregate.ts b/packages/dev/core/src/Physics/v2/physicsAggregate.ts index 7d2eeed6e68..1e7ed6b0430 100644 --- a/packages/dev/core/src/Physics/v2/physicsAggregate.ts +++ b/packages/dev/core/src/Physics/v2/physicsAggregate.ts @@ -4,6 +4,9 @@ import { PhysicsShape } from "./physicsShape"; import { Logger } from "../../Misc/logger"; import type { Scene } from "../../scene"; import type { TransformNode } from "../../Meshes/transformNode"; +import { Quaternion, Vector3 } from "../../Maths/math.vector"; +import { Scalar } from "../../Maths/math.scalar"; +import { ShapeType } from "./IPhysicsEnginePlugin"; /** * The interface for the physics aggregate parameters @@ -71,6 +74,26 @@ export interface PhysicsAggregateParameters { * The shape of an extrusion used for a rope based on an extrusion */ shape?: any; + + /** + * Radius for sphere, cylinder and capsule + */ + radius?: number; + + /** + * Starting point for cylinder/capsule + */ + pointA?: Vector3; + + /** + * Ending point for cylinder/capsule + */ + pointB?: Vector3; + + /** + * Extents for box + */ + extents?: Vector3; } /** * Helper class to create and interact with a PhysicsAggregate. @@ -129,11 +152,52 @@ export class PhysicsAggregate { this._options.mass = _options.mass === void 0 ? 0 : _options.mass; this._options.friction = _options.friction === void 0 ? 0.2 : _options.friction; this._options.restitution = _options.restitution === void 0 ? 0.2 : _options.restitution; - this.shape = new PhysicsShape(type, this._options as any, this._scene); + this.body = new PhysicsBody(transformNode, this._scene); - this.material = new PhysicsMaterial(this._options.friction ? this._options.friction : 0, this._options.restitution ? this._options.restitution : 0, this._scene); + this._addSizeOptions(); + this.shape = new PhysicsShape(type, this._options as any, this._scene); + + this.material = new PhysicsMaterial(this._options.friction, this._options.restitution, this._scene); this.body.setShape(this.shape); this.shape.setMaterial(this.material); + this.body.setMassProperties({ centerOfMass: new Vector3(0, 0, 0), mass: this._options.mass, inertia: new Vector3(1, 1, 1), inertiaOrientation: Quaternion.Identity() }); + } + + private _addSizeOptions(): void { + const impostorExtents = this.body.getObjectExtents(); + + switch (this.type) { + case ShapeType.SPHERE: + if (Scalar.WithinEpsilon(impostorExtents.x, impostorExtents.y, 0.0001) && Scalar.WithinEpsilon(impostorExtents.x, impostorExtents.z, 0.0001)) { + this._options.radius = this._options.radius ? this._options.radius : impostorExtents.x / 2; + } else { + Logger.Warn("Non uniform scaling is unsupported for sphere shapes."); + } + break; + case ShapeType.CAPSULE: + { + const capRadius = impostorExtents.x / 2; + this._options.radius = this._options.radius ?? capRadius; + this._options.pointA = this._options.pointA ?? new Vector3(0, -impostorExtents.y * 0.5 + capRadius, 0); + this._options.pointB = this._options.pointB ?? new Vector3(0, impostorExtents.y * 0.5 - capRadius, 0); + } + break; + case ShapeType.CYLINDER: + { + const capRadius = impostorExtents.x / 2; + this._options.radius = this._options.radius ? this._options.radius : capRadius; + this._options.pointA = this._options.pointA ? this._options.pointA : new Vector3(0, -impostorExtents.y * 0.5, 0); + this._options.pointB = this._options.pointB ? this._options.pointB : new Vector3(0, impostorExtents.y * 0.5, 0); + } + break; + case ShapeType.BOX: + this._options.extents = this._options.extents ? this._options.extents : new Vector3(impostorExtents.x, impostorExtents.y, impostorExtents.z); + break; + case ShapeType.MESH: + case ShapeType.CONVEX_HULL: { + break; + } + } } /** diff --git a/packages/dev/core/src/Physics/v2/physicsBody.ts b/packages/dev/core/src/Physics/v2/physicsBody.ts index 95067e9c6d1..8357b905d42 100644 --- a/packages/dev/core/src/Physics/v2/physicsBody.ts +++ b/packages/dev/core/src/Physics/v2/physicsBody.ts @@ -1,10 +1,9 @@ import type { IPhysicsEnginePluginV2, MassProperties } from "./IPhysicsEnginePlugin"; import type { PhysicsShape } from "./physicsShape"; -import type { Vector3 } from "../../Maths/math.vector"; -import { Quaternion } from "../../Maths/math.vector"; +import { Vector3, Quaternion } from "../../Maths/math.vector"; import type { Scene } from "../../scene"; import type { PhysicsEngine } from "./physicsEngine"; -import type { Mesh, TransformNode } from "../../Meshes"; +import type { Mesh, TransformNode, AbstractMesh } from "../../Meshes"; import type { Nullable } from "core/types"; /** @@ -39,6 +38,9 @@ export class PhysicsBody { */ disablePreStep: boolean = true; + private static _DEFAULT_OBJECT_SIZE: Vector3 = new Vector3(1, 1, 1); + private static _IDENTITY_QUATERNION = Quaternion.Identity(); + /** * Constructs a new physics body for the given node. * @param transformNode - The Transform Node to construct the physics body for. @@ -293,6 +295,19 @@ export class PhysicsBody { this._physicsPlugin.applyImpulse(this, location, impulse); } + /** + * Applies a force to the physics object. + * + * @param location The location of the force. + * @param force The force vector. + * + * This method is useful for applying a force to a physics object, which can be used to simulate physical forces such as gravity, + * collisions, and explosions. This can be used to create realistic physics simulations in a game or other application. + */ + public applyForce(location: Vector3, force: Vector3): void { + this._physicsPlugin.applyForce(this, location, force); + } + /** * Retrieves the geometry of the body from the physics plugin. * @@ -320,6 +335,46 @@ export class PhysicsBody { return this._physicsPlugin.unregisterOnBodyCollide(this, func); } + /** + * Gets the object extents + * @returns the object extents + */ + public getObjectExtents(): Vector3 { + const tmAbstractMesh = this.transformNode as AbstractMesh; + if (tmAbstractMesh.getBoundingInfo) { + const q = this.transformNode.rotationQuaternion; + const scaling = this.transformNode.scaling.clone(); + //reset rotation + this.transformNode.rotationQuaternion = PhysicsBody._IDENTITY_QUATERNION; + //calculate the world matrix with no rotation + const worldMatrix = this.transformNode.computeWorldMatrix && this.transformNode.computeWorldMatrix(true); + if (worldMatrix) { + worldMatrix.decompose(scaling, undefined, undefined); + } + const boundingInfo = tmAbstractMesh.getBoundingInfo(); + // get the global scaling of the object + const size = boundingInfo.boundingBox.extendSize.scale(2).multiplyInPlace(scaling); + size.x = Math.abs(size.x); + size.y = Math.abs(size.y); + size.z = Math.abs(size.z); + //bring back the rotation + this.transformNode.rotationQuaternion = q; + //calculate the world matrix with the new rotation + this.transformNode.computeWorldMatrix && this.transformNode.computeWorldMatrix(true); + return size; + } else { + return PhysicsBody._DEFAULT_OBJECT_SIZE; + } + } + + /** + * return geometric center of the associated mesh + */ + public getObjectCenter(): Vector3 { + // TODO + return new Vector3(0, 0, 0); + } + /** * Disposes the body from the physics engine. * diff --git a/packages/dev/core/src/Physics/v2/physicsEngine.ts b/packages/dev/core/src/Physics/v2/physicsEngine.ts index 5d2531b2e10..171aa18bbfa 100644 --- a/packages/dev/core/src/Physics/v2/physicsEngine.ts +++ b/packages/dev/core/src/Physics/v2/physicsEngine.ts @@ -130,14 +130,14 @@ export class PhysicsEngine implements IPhysicsEngine { } /** - * + * Add a body as an active component of this engine * @param body */ public addBody(physicsBody: PhysicsBody): void { this._physicsBodies.push(physicsBody); } /** - * + * Removes a particular body from this engine */ public removeBody(physicsBody: PhysicsBody): void { const index = this._physicsBodies.indexOf(physicsBody); @@ -145,6 +145,13 @@ export class PhysicsEngine implements IPhysicsEngine { /*const removed =*/ this._physicsBodies.splice(index, 1); } } + /** + * Returns an array of bodies added to this engine + + */ + public getBodies(): Array { + return this._physicsBodies; + } /** * Gets the current plugin used to run the simulation