123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947 |
- import { Logger } from "../Misc/logger.js";
- import { TmpVectors, Vector3 } from "../Maths/math.vector.js";
- import { CreateSphere } from "../Meshes/Builders/sphereBuilder.js";
- import { CreateCylinder } from "../Meshes/Builders/cylinderBuilder.js";
- import { Ray } from "../Culling/ray.js";
- import { PhysicsMotionType } from "./v2/IPhysicsEnginePlugin.js";
- class HelperTools {
- /*
- * Gets the hit contact point between a mesh and a ray. The method varies between
- * the different plugin versions; V1 uses a mesh intersection, V2 uses the physics body instance/object center (to avoid a raycast and improve perf).
- */
- static GetContactPointToRef(mesh, origin, direction, result, instanceIndex) {
- const engine = mesh.getScene().getPhysicsEngine();
- const pluginVersion = engine?.getPluginVersion();
- if (pluginVersion === 1) {
- const ray = new Ray(origin, direction);
- const hit = ray.intersectsMesh(mesh);
- if (hit.hit && hit.pickedPoint) {
- result.copyFrom(hit.pickedPoint);
- return true;
- }
- }
- else if (pluginVersion === 2) {
- mesh.physicsBody.getObjectCenterWorldToRef(result, instanceIndex);
- return true;
- }
- return false;
- }
- /**
- * Checks if a body will be affected by forces
- * @param body the body to check
- * @param instanceIndex for instanced bodies, the index of the instance to check
- * @returns
- */
- static HasAppliedForces(body, instanceIndex) {
- return (body.getMotionType(instanceIndex) === PhysicsMotionType.STATIC ||
- (body.getMassProperties(instanceIndex)?.mass ?? 0) === 0 ||
- body.transformNode?.getTotalVertices() === 0);
- }
- /**
- * Checks if a point is inside a cylinder
- * @param point point to check
- * @param origin cylinder origin on the bottom
- * @param radius cylinder radius
- * @param height cylinder height
- * @returns
- */
- static IsInsideCylinder(point, origin, radius, height) {
- const distance = TmpVectors.Vector3[0];
- point.subtractToRef(origin, distance);
- return Math.abs(distance.x) <= radius && Math.abs(distance.z) <= radius && distance.y >= 0 && distance.y <= height;
- }
- }
- /**
- * A helper for physics simulations
- * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
- */
- export class PhysicsHelper {
- /**
- * Initializes the Physics helper
- * @param scene Babylon.js scene
- */
- constructor(scene) {
- this._hitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 };
- 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
- */
- applyRadialExplosionImpulse(origin, radiusOrEventOptions, strength, falloff) {
- 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) => {
- 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 {
- this._applicationForBodies(event, origin, hitData, useCallback, (body, hitData) => {
- body.applyImpulse(hitData.force, hitData.contactPoint, hitData.instanceIndex);
- });
- }
- 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
- */
- applyRadialExplosionForce(origin, radiusOrEventOptions, strength, falloff) {
- 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) => {
- 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 {
- this._applicationForBodies(event, origin, hitData, useCallback, (body, hitData) => {
- body.applyForce(hitData.force, hitData.contactPoint, hitData.instanceIndex);
- });
- }
- event.dispose(false);
- return event;
- }
- _applicationForBodies(event, origin, hitData, useCallback, fnApplication) {
- const affectedBodiesWithData = Array();
- const bodies = this._physicsEngine.getBodies();
- for (const body of bodies) {
- body.iterateOverAllInstances((body, instanceIndex) => {
- if (!event.getBodyHitData(body, origin, hitData, instanceIndex)) {
- return;
- }
- fnApplication(body, hitData);
- if (useCallback) {
- affectedBodiesWithData.push({
- body: body,
- hitData: this._copyPhysicsHitData(hitData),
- });
- }
- });
- }
- event.triggerAffectedBodiesCallback(affectedBodiesWithData);
- }
- /**
- * 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
- */
- gravitationalField(origin, radiusOrEventOptions, strength, falloff) {
- 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
- */
- updraft(origin, radiusOrEventOptions, strength, height, updraftMode) {
- 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
- */
- vortex(origin, radiusOrEventOptions, strength, height) {
- 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;
- }
- _copyPhysicsHitData(data) {
- return { force: data.force.clone(), contactPoint: data.contactPoint.clone(), distanceFromOrigin: data.distanceFromOrigin, instanceIndex: data.instanceIndex };
- }
- }
- /**
- * Represents a physics radial explosion event
- */
- class PhysicsRadialExplosionEvent {
- /**
- * Initializes a radial explosion event
- * @param _scene BabylonJS scene
- * @param _options The options for the vortex event
- */
- constructor(_scene, _options) {
- this._scene = _scene;
- this._options = _options;
- this._dataFetched = false; // check if the data has been fetched. If not, do cleanup
- this._options = { ...new PhysicsRadialExplosionEventOptions(), ...this._options };
- }
- /**
- * Returns the data related to the radial explosion event (sphere).
- * @returns The radial explosion event data
- */
- getData() {
- this._dataFetched = true;
- return {
- sphere: this._sphere,
- };
- }
- _getHitData(mesh, center, origin, data) {
- const direction = TmpVectors.Vector3[0];
- direction.copyFrom(center).subtractInPlace(origin);
- const contactPoint = TmpVectors.Vector3[1];
- const hasContactPoint = HelperTools.GetContactPointToRef(mesh, origin, direction, contactPoint, data.instanceIndex);
- if (!hasContactPoint) {
- 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);
- // Direction x multiplier equals force
- direction.scaleInPlace(multiplier);
- data.force.copyFrom(direction);
- data.contactPoint.copyFrom(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 where the transform node is an AbstractMesh
- * @param origin the origin of the explosion
- * @param data the data of the hit
- * @param instanceIndex the instance index of the body
- * @returns if there was a hit
- */
- getBodyHitData(body, origin, data, instanceIndex) {
- // No force will be applied in these cases, so we skip calculation
- if (HelperTools.HasAppliedForces(body, instanceIndex)) {
- return false;
- }
- const mesh = body.transformNode;
- const bodyObjectCenter = body.getObjectCenterWorld(instanceIndex);
- data.instanceIndex = instanceIndex;
- return this._getHitData(mesh, bodyObjectCenter, origin, data);
- }
- /**
- * 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
- * @param data the data of the hit
- * @returns A physics force and contact point, or null
- */
- getImpostorHitData(impostor, origin, data) {
- if (impostor.mass === 0) {
- return false;
- }
- if (impostor.object.getClassName() !== "Mesh" && impostor.object.getClassName() !== "InstancedMesh") {
- return false;
- }
- const mesh = impostor.object;
- 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)
- */
- triggerAffectedImpostorsCallback(affectedImpostorsWithData) {
- if (this._options.affectedImpostorsCallback) {
- this._options.affectedImpostorsCallback(affectedImpostorsWithData);
- }
- }
- /**
- * Triggers affected bodies callbacks
- * @param affectedBodiesWithData defines the list of affected bodies (including associated data)
- */
- triggerAffectedBodiesCallback(affectedBodiesWithData) {
- if (this._options.affectedBodiesCallback) {
- this._options.affectedBodiesCallback(affectedBodiesWithData);
- }
- }
- /**
- * Disposes the sphere.
- * @param force Specifies if the sphere should be disposed by force
- */
- dispose(force = true) {
- if (this._sphere) {
- if (force) {
- this._sphere.dispose();
- }
- else {
- setTimeout(() => {
- if (!this._dataFetched) {
- this._sphere.dispose();
- }
- }, 0);
- }
- }
- }
- /*** Helpers ***/
- _prepareSphere() {
- if (!this._sphere) {
- this._sphere = CreateSphere("radialExplosionEventSphere", this._options.sphere, this._scene);
- this._sphere.isVisible = false;
- }
- }
- _intersectsWithSphere(mesh, origin, radius) {
- 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 {
- /**
- * 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(_physicsHelper, _scene, _origin, _options) {
- this._physicsHelper = _physicsHelper;
- this._scene = _scene;
- this._origin = _origin;
- this._options = _options;
- this._dataFetched = false; // check if the has been fetched the data. If not, do cleanup
- this._options = { ...new PhysicsRadialExplosionEventOptions(), ...this._options };
- this._tickCallback = () => this._tick();
- this._options.strength = this._options.strength * -1;
- }
- /**
- * Returns the data related to the gravitational field event (sphere).
- * @returns A gravitational field event
- */
- getData() {
- this._dataFetched = true;
- return {
- sphere: this._sphere,
- };
- }
- /**
- * Enables the gravitational field.
- */
- enable() {
- this._tickCallback.call(this);
- this._scene.registerBeforeRender(this._tickCallback);
- }
- /**
- * Disables the gravitational field.
- */
- disable() {
- this._scene.unregisterBeforeRender(this._tickCallback);
- }
- /**
- * Disposes the sphere.
- * @param force The force to dispose from the gravitational field event
- */
- dispose(force = true) {
- if (!this._sphere) {
- return;
- }
- if (force) {
- this._sphere.dispose();
- }
- else {
- setTimeout(() => {
- if (!this._dataFetched) {
- this._sphere.dispose();
- }
- }, 0);
- }
- }
- _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 {
- /**
- * 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(_scene, _origin, _options) {
- this._scene = _scene;
- this._origin = _origin;
- this._options = _options;
- this._originTop = Vector3.Zero(); // the most upper part of the cylinder
- this._originDirection = Vector3.Zero(); // used if the updraftMode is perpendicular
- this._cylinderPosition = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom
- this._dataFetched = false; // check if the has been fetched the data. If not, do cleanup
- 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();
- if (this._physicsEngine.getPluginVersion() === 1) {
- this._prepareCylinder();
- }
- }
- /**
- * Returns the data related to the updraft event (cylinder).
- * @returns A physics updraft event
- */
- getData() {
- this._dataFetched = true;
- return {
- cylinder: this._cylinder,
- };
- }
- /**
- * Enables the updraft.
- */
- enable() {
- this._tickCallback.call(this);
- this._scene.registerBeforeRender(this._tickCallback);
- }
- /**
- * Disables the updraft.
- */
- disable() {
- this._scene.unregisterBeforeRender(this._tickCallback);
- }
- /**
- * Disposes the cylinder.
- * @param force Specifies if the updraft should be disposed by force
- */
- dispose(force = true) {
- if (!this._cylinder) {
- return;
- }
- if (force) {
- this._cylinder.dispose();
- this._cylinder = undefined;
- }
- else {
- setTimeout(() => {
- if (!this._dataFetched && this._cylinder) {
- this._cylinder.dispose();
- this._cylinder = undefined;
- }
- }, 0);
- }
- }
- _getHitData(center, data) {
- let direction;
- 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.copyFrom(force);
- data.contactPoint.copyFrom(center);
- data.distanceFromOrigin = distanceFromOrigin;
- }
- _getBodyHitData(body, data, instanceIndex) {
- if (HelperTools.HasAppliedForces(body)) {
- return false;
- }
- const center = body.getObjectCenterWorld(instanceIndex);
- if (!HelperTools.IsInsideCylinder(center, this._origin, this._options.radius, this._options.height)) {
- return false;
- }
- data.instanceIndex = instanceIndex;
- this._getHitData(center, data);
- return true;
- }
- _getImpostorHitData(impostor, data) {
- 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;
- }
- _tick() {
- const hitData = PhysicsUpdraftEvent._HitData;
- if (this._physicsEngine.getPluginVersion() === 1) {
- this._physicsEngine.getImpostors().forEach((impostor) => {
- if (!this._getImpostorHitData(impostor, hitData)) {
- return;
- }
- impostor.applyForce(hitData.force, hitData.contactPoint);
- });
- }
- else {
- // V2
- this._physicsEngine.getBodies().forEach((body) => {
- body.iterateOverAllInstances((body, instanceIndex) => {
- if (!this._getBodyHitData(body, hitData, instanceIndex)) {
- return;
- }
- body.applyForce(hitData.force, hitData.contactPoint, hitData.instanceIndex);
- });
- });
- }
- }
- /*** Helpers ***/
- _prepareCylinder() {
- if (!this._cylinder) {
- this._cylinder = CreateCylinder("updraftEventCylinder", {
- height: this._options.height,
- diameter: this._options.radius * 2,
- }, this._scene);
- this._cylinder.isVisible = false;
- }
- }
- _intersectsWithCylinder(mesh) {
- if (!this._cylinder) {
- return false;
- }
- this._cylinder.position = this._cylinderPosition;
- return this._cylinder.intersectsMesh(mesh, true);
- }
- }
- PhysicsUpdraftEvent._HitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 };
- /**
- * Represents a physics vortex event
- */
- class PhysicsVortexEvent {
- /**
- * 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(_scene, _origin, _options) {
- this._scene = _scene;
- this._origin = _origin;
- this._options = _options;
- this._originTop = Vector3.Zero(); // the most upper part of the cylinder
- this._cylinderPosition = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom
- this._dataFetched = false; // check if the has been fetched the data. If not, do cleanup
- 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();
- if (this._physicsEngine.getPluginVersion() === 1) {
- this._prepareCylinder();
- }
- }
- /**
- * Returns the data related to the vortex event (cylinder).
- * @returns The physics vortex event data
- */
- getData() {
- this._dataFetched = true;
- return {
- cylinder: this._cylinder,
- };
- }
- /**
- * Enables the vortex.
- */
- enable() {
- this._tickCallback.call(this);
- this._scene.registerBeforeRender(this._tickCallback);
- }
- /**
- * Disables the cortex.
- */
- disable() {
- this._scene.unregisterBeforeRender(this._tickCallback);
- }
- /**
- * Disposes the sphere.
- * @param force
- */
- dispose(force = true) {
- if (!this._cylinder) {
- return;
- }
- if (force) {
- this._cylinder.dispose();
- }
- else {
- setTimeout(() => {
- if (!this._dataFetched) {
- this._cylinder.dispose();
- }
- }, 0);
- }
- }
- _getHitData(mesh, center, data) {
- 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 = TmpVectors.Vector3[0];
- center.subtractToRef(originOnPlane, originToImpostorDirection);
- const contactPoint = TmpVectors.Vector3[1];
- const hasContactPoint = HelperTools.GetContactPointToRef(mesh, originOnPlane, originToImpostorDirection, contactPoint, data.instanceIndex);
- if (!hasContactPoint) {
- return false;
- }
- const distance = Vector3.Distance(contactPoint, originOnPlane);
- const absoluteDistanceFromOrigin = distance / this._options.radius;
- const directionToOrigin = TmpVectors.Vector3[2];
- contactPoint.normalizeToRef(directionToOrigin);
- if (absoluteDistanceFromOrigin > this._options.centripetalForceThreshold) {
- directionToOrigin.negateInPlace();
- }
- let forceX;
- let forceY;
- let forceZ;
- 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;
- }
- const force = TmpVectors.Vector3[3];
- force.set(forceX, forceY, forceZ);
- force.scaleInPlace(this._options.strength);
- data.force.copyFrom(force);
- data.contactPoint.copyFrom(center);
- data.distanceFromOrigin = absoluteDistanceFromOrigin;
- return true;
- }
- _getBodyHitData(body, data, instanceIndex) {
- if (HelperTools.HasAppliedForces(body, instanceIndex)) {
- return false;
- }
- const bodyObject = body.transformNode;
- const bodyCenter = body.getObjectCenterWorld(instanceIndex);
- if (!HelperTools.IsInsideCylinder(bodyCenter, this._origin, this._options.radius, this._options.height)) {
- return false;
- }
- data.instanceIndex = instanceIndex;
- return this._getHitData(bodyObject, bodyCenter, data);
- }
- _getImpostorHitData(impostor, data) {
- if (impostor.mass === 0) {
- return false;
- }
- if (impostor.object.getClassName() !== "Mesh" && impostor.object.getClassName() !== "InstancedMesh") {
- return false;
- }
- const impostorObject = impostor.object;
- if (!this._intersectsWithCylinder(impostorObject)) {
- return false;
- }
- const impostorObjectCenter = impostor.getObjectCenter();
- this._getHitData(impostorObject, impostorObjectCenter, data);
- return true;
- }
- _tick() {
- const hitData = PhysicsVortexEvent._HitData;
- if (this._physicsEngine.getPluginVersion() === 1) {
- this._physicsEngine.getImpostors().forEach((impostor) => {
- if (!this._getImpostorHitData(impostor, hitData)) {
- return;
- }
- impostor.applyForce(hitData.force, hitData.contactPoint);
- });
- }
- else {
- this._physicsEngine.getBodies().forEach((body) => {
- body.iterateOverAllInstances((body, instanceIndex) => {
- if (!this._getBodyHitData(body, hitData, instanceIndex)) {
- return;
- }
- body.applyForce(hitData.force, hitData.contactPoint, hitData.instanceIndex);
- });
- });
- }
- }
- /*** Helpers ***/
- _prepareCylinder() {
- if (!this._cylinder) {
- this._cylinder = CreateCylinder("vortexEventCylinder", {
- height: this._options.height,
- diameter: this._options.radius * 2,
- }, this._scene);
- this._cylinder.isVisible = false;
- }
- }
- _intersectsWithCylinder(mesh) {
- this._cylinder.position = this._cylinderPosition;
- return this._cylinder.intersectsMesh(mesh, true);
- }
- }
- PhysicsVortexEvent._OriginOnPlane = Vector3.Zero();
- PhysicsVortexEvent._HitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 };
- /**
- * Options fot the radial explosion event
- * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
- */
- export class PhysicsRadialExplosionEventOptions {
- constructor() {
- /**
- * The radius of the sphere for the radial explosion.
- */
- this.radius = 5;
- /**
- * The strength of the explosion.
- */
- this.strength = 10;
- /**
- * The strength of the force in correspondence to the distance of the affected object
- */
- this.falloff = PhysicsRadialImpulseFalloff.Constant;
- /**
- * Sphere options for the radial explosion.
- */
- this.sphere = { segments: 32, diameter: 1 };
- }
- }
- /**
- * Options fot the updraft event
- * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
- */
- export class PhysicsUpdraftEventOptions {
- constructor() {
- /**
- * The radius of the cylinder for the vortex
- */
- this.radius = 5;
- /**
- * The strength of the updraft.
- */
- this.strength = 10;
- /**
- * The height of the cylinder for the updraft.
- */
- this.height = 10;
- /**
- * The mode for the updraft.
- */
- this.updraftMode = 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 {
- constructor() {
- /**
- * The radius of the cylinder for the vortex
- */
- this.radius = 5;
- /**
- * The strength of the vortex.
- */
- this.strength = 10;
- /**
- * The height of the cylinder for the vortex.
- */
- this.height = 10;
- /**
- * At which distance, relative to the radius the centripetal forces should kick in? Range: 0-1
- */
- this.centripetalForceThreshold = 0.7;
- /**
- * This multiplier determines with how much force the objects will be pushed sideways/around the vortex, when below the threshold.
- */
- this.centripetalForceMultiplier = 5;
- /**
- * This multiplier determines with how much force the objects will be pushed sideways/around the vortex, when above the threshold.
- */
- this.centrifugalForceMultiplier = 0.5;
- /**
- * This multiplier determines with how much force the objects will be pushed upwards, when in the vortex.
- */
- this.updraftForceMultiplier = 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 var PhysicsRadialImpulseFalloff;
- (function (PhysicsRadialImpulseFalloff) {
- /** Defines that impulse is constant in strength across it's whole radius */
- PhysicsRadialImpulseFalloff[PhysicsRadialImpulseFalloff["Constant"] = 0] = "Constant";
- /** Defines that impulse gets weaker if it's further from the origin */
- PhysicsRadialImpulseFalloff[PhysicsRadialImpulseFalloff["Linear"] = 1] = "Linear";
- })(PhysicsRadialImpulseFalloff || (PhysicsRadialImpulseFalloff = {}));
- /**
- * 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 var PhysicsUpdraftMode;
- (function (PhysicsUpdraftMode) {
- /** Defines that the upstream forces will pull towards the top center of the cylinder */
- PhysicsUpdraftMode[PhysicsUpdraftMode["Center"] = 0] = "Center";
- /** Defines that once a impostor is inside the cylinder, it will shoot out perpendicular from the ground of the cylinder */
- PhysicsUpdraftMode[PhysicsUpdraftMode["Perpendicular"] = 1] = "Perpendicular";
- })(PhysicsUpdraftMode || (PhysicsUpdraftMode = {}));
- //# sourceMappingURL=physicsHelper.js.map
|