physicsHelper.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. import { Logger } from "../Misc/logger.js";
  2. import { TmpVectors, Vector3 } from "../Maths/math.vector.js";
  3. import { CreateSphere } from "../Meshes/Builders/sphereBuilder.js";
  4. import { CreateCylinder } from "../Meshes/Builders/cylinderBuilder.js";
  5. import { Ray } from "../Culling/ray.js";
  6. import { PhysicsMotionType } from "./v2/IPhysicsEnginePlugin.js";
  7. class HelperTools {
  8. /*
  9. * Gets the hit contact point between a mesh and a ray. The method varies between
  10. * the different plugin versions; V1 uses a mesh intersection, V2 uses the physics body instance/object center (to avoid a raycast and improve perf).
  11. */
  12. static GetContactPointToRef(mesh, origin, direction, result, instanceIndex) {
  13. const engine = mesh.getScene().getPhysicsEngine();
  14. const pluginVersion = engine?.getPluginVersion();
  15. if (pluginVersion === 1) {
  16. const ray = new Ray(origin, direction);
  17. const hit = ray.intersectsMesh(mesh);
  18. if (hit.hit && hit.pickedPoint) {
  19. result.copyFrom(hit.pickedPoint);
  20. return true;
  21. }
  22. }
  23. else if (pluginVersion === 2) {
  24. mesh.physicsBody.getObjectCenterWorldToRef(result, instanceIndex);
  25. return true;
  26. }
  27. return false;
  28. }
  29. /**
  30. * Checks if a body will be affected by forces
  31. * @param body the body to check
  32. * @param instanceIndex for instanced bodies, the index of the instance to check
  33. * @returns
  34. */
  35. static HasAppliedForces(body, instanceIndex) {
  36. return (body.getMotionType(instanceIndex) === PhysicsMotionType.STATIC ||
  37. (body.getMassProperties(instanceIndex)?.mass ?? 0) === 0 ||
  38. body.transformNode?.getTotalVertices() === 0);
  39. }
  40. /**
  41. * Checks if a point is inside a cylinder
  42. * @param point point to check
  43. * @param origin cylinder origin on the bottom
  44. * @param radius cylinder radius
  45. * @param height cylinder height
  46. * @returns
  47. */
  48. static IsInsideCylinder(point, origin, radius, height) {
  49. const distance = TmpVectors.Vector3[0];
  50. point.subtractToRef(origin, distance);
  51. return Math.abs(distance.x) <= radius && Math.abs(distance.z) <= radius && distance.y >= 0 && distance.y <= height;
  52. }
  53. }
  54. /**
  55. * A helper for physics simulations
  56. * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
  57. */
  58. export class PhysicsHelper {
  59. /**
  60. * Initializes the Physics helper
  61. * @param scene Babylon.js scene
  62. */
  63. constructor(scene) {
  64. this._hitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 };
  65. this._scene = scene;
  66. this._physicsEngine = this._scene.getPhysicsEngine();
  67. if (!this._physicsEngine) {
  68. Logger.Warn("Physics engine not enabled. Please enable the physics before you can use the methods.");
  69. return;
  70. }
  71. }
  72. /**
  73. * Applies a radial explosion impulse
  74. * @param origin the origin of the explosion
  75. * @param radiusOrEventOptions the radius or the options of radial explosion
  76. * @param strength the explosion strength
  77. * @param falloff possible options: Constant & Linear. Defaults to Constant
  78. * @returns A physics radial explosion event, or null
  79. */
  80. applyRadialExplosionImpulse(origin, radiusOrEventOptions, strength, falloff) {
  81. if (!this._physicsEngine) {
  82. Logger.Warn("Physics engine not enabled. Please enable the physics before you call this method.");
  83. return null;
  84. }
  85. if (this._physicsEngine.getPluginVersion() === 1 && this._physicsEngine.getImpostors().length === 0) {
  86. return null;
  87. }
  88. if (this._physicsEngine.getPluginVersion() === 2 && this._physicsEngine.getBodies().length === 0) {
  89. return null;
  90. }
  91. let useCallback = false;
  92. if (typeof radiusOrEventOptions === "number") {
  93. const r = radiusOrEventOptions;
  94. radiusOrEventOptions = new PhysicsRadialExplosionEventOptions();
  95. radiusOrEventOptions.radius = r;
  96. radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength;
  97. radiusOrEventOptions.falloff = falloff ?? radiusOrEventOptions.falloff;
  98. }
  99. else {
  100. useCallback = !!(radiusOrEventOptions.affectedImpostorsCallback || radiusOrEventOptions.affectedBodiesCallback);
  101. }
  102. const event = new PhysicsRadialExplosionEvent(this._scene, radiusOrEventOptions);
  103. const hitData = this._hitData;
  104. if (this._physicsEngine.getPluginVersion() === 1) {
  105. const affectedImpostorsWithData = Array();
  106. const impostors = this._physicsEngine.getImpostors();
  107. impostors.forEach((impostor) => {
  108. if (!event.getImpostorHitData(impostor, origin, hitData)) {
  109. return;
  110. }
  111. impostor.applyImpulse(hitData.force, hitData.contactPoint);
  112. if (useCallback) {
  113. affectedImpostorsWithData.push({
  114. impostor: impostor,
  115. hitData: this._copyPhysicsHitData(hitData),
  116. });
  117. }
  118. });
  119. event.triggerAffectedImpostorsCallback(affectedImpostorsWithData);
  120. }
  121. else {
  122. this._applicationForBodies(event, origin, hitData, useCallback, (body, hitData) => {
  123. body.applyImpulse(hitData.force, hitData.contactPoint, hitData.instanceIndex);
  124. });
  125. }
  126. event.dispose(false);
  127. return event;
  128. }
  129. /**
  130. * Applies a radial explosion force
  131. * @param origin the origin of the explosion
  132. * @param radiusOrEventOptions the radius or the options of radial explosion
  133. * @param strength the explosion strength
  134. * @param falloff possible options: Constant & Linear. Defaults to Constant
  135. * @returns A physics radial explosion event, or null
  136. */
  137. applyRadialExplosionForce(origin, radiusOrEventOptions, strength, falloff) {
  138. if (!this._physicsEngine) {
  139. Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.");
  140. return null;
  141. }
  142. if (this._physicsEngine.getPluginVersion() === 1 && this._physicsEngine.getImpostors().length === 0) {
  143. return null;
  144. }
  145. if (this._physicsEngine.getPluginVersion() === 2 && this._physicsEngine.getBodies().length === 0) {
  146. return null;
  147. }
  148. let useCallback = false;
  149. if (typeof radiusOrEventOptions === "number") {
  150. const r = radiusOrEventOptions;
  151. radiusOrEventOptions = new PhysicsRadialExplosionEventOptions();
  152. radiusOrEventOptions.radius = r;
  153. radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength;
  154. radiusOrEventOptions.falloff = falloff ?? radiusOrEventOptions.falloff;
  155. }
  156. else {
  157. useCallback = !!(radiusOrEventOptions.affectedImpostorsCallback || radiusOrEventOptions.affectedBodiesCallback);
  158. }
  159. const event = new PhysicsRadialExplosionEvent(this._scene, radiusOrEventOptions);
  160. const hitData = this._hitData;
  161. if (this._physicsEngine.getPluginVersion() === 1) {
  162. const affectedImpostorsWithData = Array();
  163. const impostors = this._physicsEngine.getImpostors();
  164. impostors.forEach((impostor) => {
  165. if (!event.getImpostorHitData(impostor, origin, hitData)) {
  166. return;
  167. }
  168. impostor.applyForce(hitData.force, hitData.contactPoint);
  169. if (useCallback) {
  170. affectedImpostorsWithData.push({
  171. impostor: impostor,
  172. hitData: this._copyPhysicsHitData(hitData),
  173. });
  174. }
  175. });
  176. event.triggerAffectedImpostorsCallback(affectedImpostorsWithData);
  177. }
  178. else {
  179. this._applicationForBodies(event, origin, hitData, useCallback, (body, hitData) => {
  180. body.applyForce(hitData.force, hitData.contactPoint, hitData.instanceIndex);
  181. });
  182. }
  183. event.dispose(false);
  184. return event;
  185. }
  186. _applicationForBodies(event, origin, hitData, useCallback, fnApplication) {
  187. const affectedBodiesWithData = Array();
  188. const bodies = this._physicsEngine.getBodies();
  189. for (const body of bodies) {
  190. body.iterateOverAllInstances((body, instanceIndex) => {
  191. if (!event.getBodyHitData(body, origin, hitData, instanceIndex)) {
  192. return;
  193. }
  194. fnApplication(body, hitData);
  195. if (useCallback) {
  196. affectedBodiesWithData.push({
  197. body: body,
  198. hitData: this._copyPhysicsHitData(hitData),
  199. });
  200. }
  201. });
  202. }
  203. event.triggerAffectedBodiesCallback(affectedBodiesWithData);
  204. }
  205. /**
  206. * Creates a gravitational field
  207. * @param origin the origin of the gravitational field
  208. * @param radiusOrEventOptions the radius or the options of radial gravitational field
  209. * @param strength the gravitational field strength
  210. * @param falloff possible options: Constant & Linear. Defaults to Constant
  211. * @returns A physics gravitational field event, or null
  212. */
  213. gravitationalField(origin, radiusOrEventOptions, strength, falloff) {
  214. if (!this._physicsEngine) {
  215. Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.");
  216. return null;
  217. }
  218. if (this._physicsEngine.getPluginVersion() === 1 && this._physicsEngine.getImpostors().length === 0) {
  219. return null;
  220. }
  221. if (this._physicsEngine.getPluginVersion() === 2 && this._physicsEngine.getBodies().length === 0) {
  222. return null;
  223. }
  224. if (typeof radiusOrEventOptions === "number") {
  225. const r = radiusOrEventOptions;
  226. radiusOrEventOptions = new PhysicsRadialExplosionEventOptions();
  227. radiusOrEventOptions.radius = r;
  228. radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength;
  229. radiusOrEventOptions.falloff = falloff ?? radiusOrEventOptions.falloff;
  230. }
  231. const event = new PhysicsGravitationalFieldEvent(this, this._scene, origin, radiusOrEventOptions);
  232. event.dispose(false);
  233. return event;
  234. }
  235. /**
  236. * Creates a physics updraft event
  237. * @param origin the origin of the updraft
  238. * @param radiusOrEventOptions the radius or the options of the updraft
  239. * @param strength the strength of the updraft
  240. * @param height the height of the updraft
  241. * @param updraftMode possible options: Center & Perpendicular. Defaults to Center
  242. * @returns A physics updraft event, or null
  243. */
  244. updraft(origin, radiusOrEventOptions, strength, height, updraftMode) {
  245. if (!this._physicsEngine) {
  246. Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.");
  247. return null;
  248. }
  249. if (this._physicsEngine.getPluginVersion() === 1 && this._physicsEngine.getImpostors().length === 0) {
  250. return null;
  251. }
  252. if (this._physicsEngine.getPluginVersion() === 2 && this._physicsEngine.getBodies().length === 0) {
  253. return null;
  254. }
  255. if (typeof radiusOrEventOptions === "number") {
  256. const r = radiusOrEventOptions;
  257. radiusOrEventOptions = new PhysicsUpdraftEventOptions();
  258. radiusOrEventOptions.radius = r;
  259. radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength;
  260. radiusOrEventOptions.height = height ?? radiusOrEventOptions.height;
  261. radiusOrEventOptions.updraftMode = updraftMode ?? radiusOrEventOptions.updraftMode;
  262. }
  263. const event = new PhysicsUpdraftEvent(this._scene, origin, radiusOrEventOptions);
  264. event.dispose(false);
  265. return event;
  266. }
  267. /**
  268. * Creates a physics vortex event
  269. * @param origin the of the vortex
  270. * @param radiusOrEventOptions the radius or the options of the vortex
  271. * @param strength the strength of the vortex
  272. * @param height the height of the vortex
  273. * @returns a Physics vortex event, or null
  274. * A physics vortex event or null
  275. */
  276. vortex(origin, radiusOrEventOptions, strength, height) {
  277. if (!this._physicsEngine) {
  278. Logger.Warn("Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.");
  279. return null;
  280. }
  281. if (this._physicsEngine.getPluginVersion() === 1 && this._physicsEngine.getImpostors().length === 0) {
  282. return null;
  283. }
  284. if (this._physicsEngine.getPluginVersion() === 2 && this._physicsEngine.getBodies().length === 0) {
  285. return null;
  286. }
  287. if (typeof radiusOrEventOptions === "number") {
  288. const r = radiusOrEventOptions;
  289. radiusOrEventOptions = new PhysicsVortexEventOptions();
  290. radiusOrEventOptions.radius = r;
  291. radiusOrEventOptions.strength = strength ?? radiusOrEventOptions.strength;
  292. radiusOrEventOptions.height = height ?? radiusOrEventOptions.height;
  293. }
  294. const event = new PhysicsVortexEvent(this._scene, origin, radiusOrEventOptions);
  295. event.dispose(false);
  296. return event;
  297. }
  298. _copyPhysicsHitData(data) {
  299. return { force: data.force.clone(), contactPoint: data.contactPoint.clone(), distanceFromOrigin: data.distanceFromOrigin, instanceIndex: data.instanceIndex };
  300. }
  301. }
  302. /**
  303. * Represents a physics radial explosion event
  304. */
  305. class PhysicsRadialExplosionEvent {
  306. /**
  307. * Initializes a radial explosion event
  308. * @param _scene BabylonJS scene
  309. * @param _options The options for the vortex event
  310. */
  311. constructor(_scene, _options) {
  312. this._scene = _scene;
  313. this._options = _options;
  314. this._dataFetched = false; // check if the data has been fetched. If not, do cleanup
  315. this._options = { ...new PhysicsRadialExplosionEventOptions(), ...this._options };
  316. }
  317. /**
  318. * Returns the data related to the radial explosion event (sphere).
  319. * @returns The radial explosion event data
  320. */
  321. getData() {
  322. this._dataFetched = true;
  323. return {
  324. sphere: this._sphere,
  325. };
  326. }
  327. _getHitData(mesh, center, origin, data) {
  328. const direction = TmpVectors.Vector3[0];
  329. direction.copyFrom(center).subtractInPlace(origin);
  330. const contactPoint = TmpVectors.Vector3[1];
  331. const hasContactPoint = HelperTools.GetContactPointToRef(mesh, origin, direction, contactPoint, data.instanceIndex);
  332. if (!hasContactPoint) {
  333. return false;
  334. }
  335. const distanceFromOrigin = Vector3.Distance(origin, contactPoint);
  336. if (distanceFromOrigin > this._options.radius) {
  337. return false;
  338. }
  339. const multiplier = this._options.falloff === PhysicsRadialImpulseFalloff.Constant ? this._options.strength : this._options.strength * (1 - distanceFromOrigin / this._options.radius);
  340. // Direction x multiplier equals force
  341. direction.scaleInPlace(multiplier);
  342. data.force.copyFrom(direction);
  343. data.contactPoint.copyFrom(contactPoint);
  344. data.distanceFromOrigin = distanceFromOrigin;
  345. return true;
  346. }
  347. /**
  348. * Returns the force and contact point of the body or false, if the body is not affected by the force/impulse.
  349. * @param body A physics body where the transform node is an AbstractMesh
  350. * @param origin the origin of the explosion
  351. * @param data the data of the hit
  352. * @param instanceIndex the instance index of the body
  353. * @returns if there was a hit
  354. */
  355. getBodyHitData(body, origin, data, instanceIndex) {
  356. // No force will be applied in these cases, so we skip calculation
  357. if (HelperTools.HasAppliedForces(body, instanceIndex)) {
  358. return false;
  359. }
  360. const mesh = body.transformNode;
  361. const bodyObjectCenter = body.getObjectCenterWorld(instanceIndex);
  362. data.instanceIndex = instanceIndex;
  363. return this._getHitData(mesh, bodyObjectCenter, origin, data);
  364. }
  365. /**
  366. * Returns the force and contact point of the impostor or false, if the impostor is not affected by the force/impulse.
  367. * @param impostor A physics imposter
  368. * @param origin the origin of the explosion
  369. * @param data the data of the hit
  370. * @returns A physics force and contact point, or null
  371. */
  372. getImpostorHitData(impostor, origin, data) {
  373. if (impostor.mass === 0) {
  374. return false;
  375. }
  376. if (impostor.object.getClassName() !== "Mesh" && impostor.object.getClassName() !== "InstancedMesh") {
  377. return false;
  378. }
  379. const mesh = impostor.object;
  380. if (!this._intersectsWithSphere(mesh, origin, this._options.radius)) {
  381. return false;
  382. }
  383. const impostorObjectCenter = impostor.getObjectCenter();
  384. this._getHitData(mesh, impostorObjectCenter, origin, data);
  385. return true;
  386. }
  387. /**
  388. * Triggers affected impostors callbacks
  389. * @param affectedImpostorsWithData defines the list of affected impostors (including associated data)
  390. */
  391. triggerAffectedImpostorsCallback(affectedImpostorsWithData) {
  392. if (this._options.affectedImpostorsCallback) {
  393. this._options.affectedImpostorsCallback(affectedImpostorsWithData);
  394. }
  395. }
  396. /**
  397. * Triggers affected bodies callbacks
  398. * @param affectedBodiesWithData defines the list of affected bodies (including associated data)
  399. */
  400. triggerAffectedBodiesCallback(affectedBodiesWithData) {
  401. if (this._options.affectedBodiesCallback) {
  402. this._options.affectedBodiesCallback(affectedBodiesWithData);
  403. }
  404. }
  405. /**
  406. * Disposes the sphere.
  407. * @param force Specifies if the sphere should be disposed by force
  408. */
  409. dispose(force = true) {
  410. if (this._sphere) {
  411. if (force) {
  412. this._sphere.dispose();
  413. }
  414. else {
  415. setTimeout(() => {
  416. if (!this._dataFetched) {
  417. this._sphere.dispose();
  418. }
  419. }, 0);
  420. }
  421. }
  422. }
  423. /*** Helpers ***/
  424. _prepareSphere() {
  425. if (!this._sphere) {
  426. this._sphere = CreateSphere("radialExplosionEventSphere", this._options.sphere, this._scene);
  427. this._sphere.isVisible = false;
  428. }
  429. }
  430. _intersectsWithSphere(mesh, origin, radius) {
  431. this._prepareSphere();
  432. this._sphere.position = origin;
  433. this._sphere.scaling.setAll(radius * 2);
  434. this._sphere._updateBoundingInfo();
  435. this._sphere.computeWorldMatrix(true);
  436. return this._sphere.intersectsMesh(mesh, true);
  437. }
  438. }
  439. /**
  440. * Represents a gravitational field event
  441. */
  442. class PhysicsGravitationalFieldEvent {
  443. /**
  444. * Initializes the physics gravitational field event
  445. * @param _physicsHelper A physics helper
  446. * @param _scene BabylonJS scene
  447. * @param _origin The origin position of the gravitational field event
  448. * @param _options The options for the vortex event
  449. */
  450. constructor(_physicsHelper, _scene, _origin, _options) {
  451. this._physicsHelper = _physicsHelper;
  452. this._scene = _scene;
  453. this._origin = _origin;
  454. this._options = _options;
  455. this._dataFetched = false; // check if the has been fetched the data. If not, do cleanup
  456. this._options = { ...new PhysicsRadialExplosionEventOptions(), ...this._options };
  457. this._tickCallback = () => this._tick();
  458. this._options.strength = this._options.strength * -1;
  459. }
  460. /**
  461. * Returns the data related to the gravitational field event (sphere).
  462. * @returns A gravitational field event
  463. */
  464. getData() {
  465. this._dataFetched = true;
  466. return {
  467. sphere: this._sphere,
  468. };
  469. }
  470. /**
  471. * Enables the gravitational field.
  472. */
  473. enable() {
  474. this._tickCallback.call(this);
  475. this._scene.registerBeforeRender(this._tickCallback);
  476. }
  477. /**
  478. * Disables the gravitational field.
  479. */
  480. disable() {
  481. this._scene.unregisterBeforeRender(this._tickCallback);
  482. }
  483. /**
  484. * Disposes the sphere.
  485. * @param force The force to dispose from the gravitational field event
  486. */
  487. dispose(force = true) {
  488. if (!this._sphere) {
  489. return;
  490. }
  491. if (force) {
  492. this._sphere.dispose();
  493. }
  494. else {
  495. setTimeout(() => {
  496. if (!this._dataFetched) {
  497. this._sphere.dispose();
  498. }
  499. }, 0);
  500. }
  501. }
  502. _tick() {
  503. // Since the params won't change, we fetch the event only once
  504. if (this._sphere) {
  505. this._physicsHelper.applyRadialExplosionForce(this._origin, this._options);
  506. }
  507. else {
  508. const radialExplosionEvent = this._physicsHelper.applyRadialExplosionForce(this._origin, this._options);
  509. if (radialExplosionEvent) {
  510. this._sphere = radialExplosionEvent.getData().sphere?.clone("radialExplosionEventSphereClone");
  511. }
  512. }
  513. }
  514. }
  515. /**
  516. * Represents a physics updraft event
  517. */
  518. class PhysicsUpdraftEvent {
  519. /**
  520. * Initializes the physics updraft event
  521. * @param _scene BabylonJS scene
  522. * @param _origin The origin position of the updraft
  523. * @param _options The options for the updraft event
  524. */
  525. constructor(_scene, _origin, _options) {
  526. this._scene = _scene;
  527. this._origin = _origin;
  528. this._options = _options;
  529. this._originTop = Vector3.Zero(); // the most upper part of the cylinder
  530. this._originDirection = Vector3.Zero(); // used if the updraftMode is perpendicular
  531. this._cylinderPosition = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom
  532. this._dataFetched = false; // check if the has been fetched the data. If not, do cleanup
  533. this._physicsEngine = this._scene.getPhysicsEngine();
  534. this._options = { ...new PhysicsUpdraftEventOptions(), ...this._options };
  535. this._origin.addToRef(new Vector3(0, this._options.height / 2, 0), this._cylinderPosition);
  536. this._origin.addToRef(new Vector3(0, this._options.height, 0), this._originTop);
  537. if (this._options.updraftMode === PhysicsUpdraftMode.Perpendicular) {
  538. this._originDirection = this._origin.subtract(this._originTop).normalize();
  539. }
  540. this._tickCallback = () => this._tick();
  541. if (this._physicsEngine.getPluginVersion() === 1) {
  542. this._prepareCylinder();
  543. }
  544. }
  545. /**
  546. * Returns the data related to the updraft event (cylinder).
  547. * @returns A physics updraft event
  548. */
  549. getData() {
  550. this._dataFetched = true;
  551. return {
  552. cylinder: this._cylinder,
  553. };
  554. }
  555. /**
  556. * Enables the updraft.
  557. */
  558. enable() {
  559. this._tickCallback.call(this);
  560. this._scene.registerBeforeRender(this._tickCallback);
  561. }
  562. /**
  563. * Disables the updraft.
  564. */
  565. disable() {
  566. this._scene.unregisterBeforeRender(this._tickCallback);
  567. }
  568. /**
  569. * Disposes the cylinder.
  570. * @param force Specifies if the updraft should be disposed by force
  571. */
  572. dispose(force = true) {
  573. if (!this._cylinder) {
  574. return;
  575. }
  576. if (force) {
  577. this._cylinder.dispose();
  578. this._cylinder = undefined;
  579. }
  580. else {
  581. setTimeout(() => {
  582. if (!this._dataFetched && this._cylinder) {
  583. this._cylinder.dispose();
  584. this._cylinder = undefined;
  585. }
  586. }, 0);
  587. }
  588. }
  589. _getHitData(center, data) {
  590. let direction;
  591. if (this._options.updraftMode === PhysicsUpdraftMode.Perpendicular) {
  592. direction = this._originDirection;
  593. }
  594. else {
  595. direction = center.subtract(this._originTop);
  596. }
  597. const distanceFromOrigin = Vector3.Distance(this._origin, center);
  598. const multiplier = this._options.strength * -1;
  599. const force = direction.multiplyByFloats(multiplier, multiplier, multiplier);
  600. data.force.copyFrom(force);
  601. data.contactPoint.copyFrom(center);
  602. data.distanceFromOrigin = distanceFromOrigin;
  603. }
  604. _getBodyHitData(body, data, instanceIndex) {
  605. if (HelperTools.HasAppliedForces(body)) {
  606. return false;
  607. }
  608. const center = body.getObjectCenterWorld(instanceIndex);
  609. if (!HelperTools.IsInsideCylinder(center, this._origin, this._options.radius, this._options.height)) {
  610. return false;
  611. }
  612. data.instanceIndex = instanceIndex;
  613. this._getHitData(center, data);
  614. return true;
  615. }
  616. _getImpostorHitData(impostor, data) {
  617. if (impostor.mass === 0) {
  618. return false;
  619. }
  620. const impostorObject = impostor.object;
  621. if (!this._intersectsWithCylinder(impostorObject)) {
  622. return false;
  623. }
  624. const center = impostor.getObjectCenter();
  625. this._getHitData(center, data);
  626. return true;
  627. }
  628. _tick() {
  629. const hitData = PhysicsUpdraftEvent._HitData;
  630. if (this._physicsEngine.getPluginVersion() === 1) {
  631. this._physicsEngine.getImpostors().forEach((impostor) => {
  632. if (!this._getImpostorHitData(impostor, hitData)) {
  633. return;
  634. }
  635. impostor.applyForce(hitData.force, hitData.contactPoint);
  636. });
  637. }
  638. else {
  639. // V2
  640. this._physicsEngine.getBodies().forEach((body) => {
  641. body.iterateOverAllInstances((body, instanceIndex) => {
  642. if (!this._getBodyHitData(body, hitData, instanceIndex)) {
  643. return;
  644. }
  645. body.applyForce(hitData.force, hitData.contactPoint, hitData.instanceIndex);
  646. });
  647. });
  648. }
  649. }
  650. /*** Helpers ***/
  651. _prepareCylinder() {
  652. if (!this._cylinder) {
  653. this._cylinder = CreateCylinder("updraftEventCylinder", {
  654. height: this._options.height,
  655. diameter: this._options.radius * 2,
  656. }, this._scene);
  657. this._cylinder.isVisible = false;
  658. }
  659. }
  660. _intersectsWithCylinder(mesh) {
  661. if (!this._cylinder) {
  662. return false;
  663. }
  664. this._cylinder.position = this._cylinderPosition;
  665. return this._cylinder.intersectsMesh(mesh, true);
  666. }
  667. }
  668. PhysicsUpdraftEvent._HitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 };
  669. /**
  670. * Represents a physics vortex event
  671. */
  672. class PhysicsVortexEvent {
  673. /**
  674. * Initializes the physics vortex event
  675. * @param _scene The BabylonJS scene
  676. * @param _origin The origin position of the vortex
  677. * @param _options The options for the vortex event
  678. */
  679. constructor(_scene, _origin, _options) {
  680. this._scene = _scene;
  681. this._origin = _origin;
  682. this._options = _options;
  683. this._originTop = Vector3.Zero(); // the most upper part of the cylinder
  684. this._cylinderPosition = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom
  685. this._dataFetched = false; // check if the has been fetched the data. If not, do cleanup
  686. this._physicsEngine = this._scene.getPhysicsEngine();
  687. this._options = { ...new PhysicsVortexEventOptions(), ...this._options };
  688. this._origin.addToRef(new Vector3(0, this._options.height / 2, 0), this._cylinderPosition);
  689. this._origin.addToRef(new Vector3(0, this._options.height, 0), this._originTop);
  690. this._tickCallback = () => this._tick();
  691. if (this._physicsEngine.getPluginVersion() === 1) {
  692. this._prepareCylinder();
  693. }
  694. }
  695. /**
  696. * Returns the data related to the vortex event (cylinder).
  697. * @returns The physics vortex event data
  698. */
  699. getData() {
  700. this._dataFetched = true;
  701. return {
  702. cylinder: this._cylinder,
  703. };
  704. }
  705. /**
  706. * Enables the vortex.
  707. */
  708. enable() {
  709. this._tickCallback.call(this);
  710. this._scene.registerBeforeRender(this._tickCallback);
  711. }
  712. /**
  713. * Disables the cortex.
  714. */
  715. disable() {
  716. this._scene.unregisterBeforeRender(this._tickCallback);
  717. }
  718. /**
  719. * Disposes the sphere.
  720. * @param force
  721. */
  722. dispose(force = true) {
  723. if (!this._cylinder) {
  724. return;
  725. }
  726. if (force) {
  727. this._cylinder.dispose();
  728. }
  729. else {
  730. setTimeout(() => {
  731. if (!this._dataFetched) {
  732. this._cylinder.dispose();
  733. }
  734. }, 0);
  735. }
  736. }
  737. _getHitData(mesh, center, data) {
  738. const originOnPlane = PhysicsVortexEvent._OriginOnPlane;
  739. 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)
  740. const originToImpostorDirection = TmpVectors.Vector3[0];
  741. center.subtractToRef(originOnPlane, originToImpostorDirection);
  742. const contactPoint = TmpVectors.Vector3[1];
  743. const hasContactPoint = HelperTools.GetContactPointToRef(mesh, originOnPlane, originToImpostorDirection, contactPoint, data.instanceIndex);
  744. if (!hasContactPoint) {
  745. return false;
  746. }
  747. const distance = Vector3.Distance(contactPoint, originOnPlane);
  748. const absoluteDistanceFromOrigin = distance / this._options.radius;
  749. const directionToOrigin = TmpVectors.Vector3[2];
  750. contactPoint.normalizeToRef(directionToOrigin);
  751. if (absoluteDistanceFromOrigin > this._options.centripetalForceThreshold) {
  752. directionToOrigin.negateInPlace();
  753. }
  754. let forceX;
  755. let forceY;
  756. let forceZ;
  757. if (absoluteDistanceFromOrigin > this._options.centripetalForceThreshold) {
  758. forceX = directionToOrigin.x * this._options.centripetalForceMultiplier;
  759. forceY = directionToOrigin.y * this._options.updraftForceMultiplier;
  760. forceZ = directionToOrigin.z * this._options.centripetalForceMultiplier;
  761. }
  762. else {
  763. const perpendicularDirection = Vector3.Cross(originOnPlane, center).normalize();
  764. forceX = (perpendicularDirection.x + directionToOrigin.x) * this._options.centrifugalForceMultiplier;
  765. forceY = this._originTop.y * this._options.updraftForceMultiplier;
  766. forceZ = (perpendicularDirection.z + directionToOrigin.z) * this._options.centrifugalForceMultiplier;
  767. }
  768. const force = TmpVectors.Vector3[3];
  769. force.set(forceX, forceY, forceZ);
  770. force.scaleInPlace(this._options.strength);
  771. data.force.copyFrom(force);
  772. data.contactPoint.copyFrom(center);
  773. data.distanceFromOrigin = absoluteDistanceFromOrigin;
  774. return true;
  775. }
  776. _getBodyHitData(body, data, instanceIndex) {
  777. if (HelperTools.HasAppliedForces(body, instanceIndex)) {
  778. return false;
  779. }
  780. const bodyObject = body.transformNode;
  781. const bodyCenter = body.getObjectCenterWorld(instanceIndex);
  782. if (!HelperTools.IsInsideCylinder(bodyCenter, this._origin, this._options.radius, this._options.height)) {
  783. return false;
  784. }
  785. data.instanceIndex = instanceIndex;
  786. return this._getHitData(bodyObject, bodyCenter, data);
  787. }
  788. _getImpostorHitData(impostor, data) {
  789. if (impostor.mass === 0) {
  790. return false;
  791. }
  792. if (impostor.object.getClassName() !== "Mesh" && impostor.object.getClassName() !== "InstancedMesh") {
  793. return false;
  794. }
  795. const impostorObject = impostor.object;
  796. if (!this._intersectsWithCylinder(impostorObject)) {
  797. return false;
  798. }
  799. const impostorObjectCenter = impostor.getObjectCenter();
  800. this._getHitData(impostorObject, impostorObjectCenter, data);
  801. return true;
  802. }
  803. _tick() {
  804. const hitData = PhysicsVortexEvent._HitData;
  805. if (this._physicsEngine.getPluginVersion() === 1) {
  806. this._physicsEngine.getImpostors().forEach((impostor) => {
  807. if (!this._getImpostorHitData(impostor, hitData)) {
  808. return;
  809. }
  810. impostor.applyForce(hitData.force, hitData.contactPoint);
  811. });
  812. }
  813. else {
  814. this._physicsEngine.getBodies().forEach((body) => {
  815. body.iterateOverAllInstances((body, instanceIndex) => {
  816. if (!this._getBodyHitData(body, hitData, instanceIndex)) {
  817. return;
  818. }
  819. body.applyForce(hitData.force, hitData.contactPoint, hitData.instanceIndex);
  820. });
  821. });
  822. }
  823. }
  824. /*** Helpers ***/
  825. _prepareCylinder() {
  826. if (!this._cylinder) {
  827. this._cylinder = CreateCylinder("vortexEventCylinder", {
  828. height: this._options.height,
  829. diameter: this._options.radius * 2,
  830. }, this._scene);
  831. this._cylinder.isVisible = false;
  832. }
  833. }
  834. _intersectsWithCylinder(mesh) {
  835. this._cylinder.position = this._cylinderPosition;
  836. return this._cylinder.intersectsMesh(mesh, true);
  837. }
  838. }
  839. PhysicsVortexEvent._OriginOnPlane = Vector3.Zero();
  840. PhysicsVortexEvent._HitData = { force: new Vector3(), contactPoint: new Vector3(), distanceFromOrigin: 0 };
  841. /**
  842. * Options fot the radial explosion event
  843. * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
  844. */
  845. export class PhysicsRadialExplosionEventOptions {
  846. constructor() {
  847. /**
  848. * The radius of the sphere for the radial explosion.
  849. */
  850. this.radius = 5;
  851. /**
  852. * The strength of the explosion.
  853. */
  854. this.strength = 10;
  855. /**
  856. * The strength of the force in correspondence to the distance of the affected object
  857. */
  858. this.falloff = PhysicsRadialImpulseFalloff.Constant;
  859. /**
  860. * Sphere options for the radial explosion.
  861. */
  862. this.sphere = { segments: 32, diameter: 1 };
  863. }
  864. }
  865. /**
  866. * Options fot the updraft event
  867. * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
  868. */
  869. export class PhysicsUpdraftEventOptions {
  870. constructor() {
  871. /**
  872. * The radius of the cylinder for the vortex
  873. */
  874. this.radius = 5;
  875. /**
  876. * The strength of the updraft.
  877. */
  878. this.strength = 10;
  879. /**
  880. * The height of the cylinder for the updraft.
  881. */
  882. this.height = 10;
  883. /**
  884. * The mode for the updraft.
  885. */
  886. this.updraftMode = PhysicsUpdraftMode.Center;
  887. }
  888. }
  889. /**
  890. * Options fot the vortex event
  891. * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
  892. */
  893. export class PhysicsVortexEventOptions {
  894. constructor() {
  895. /**
  896. * The radius of the cylinder for the vortex
  897. */
  898. this.radius = 5;
  899. /**
  900. * The strength of the vortex.
  901. */
  902. this.strength = 10;
  903. /**
  904. * The height of the cylinder for the vortex.
  905. */
  906. this.height = 10;
  907. /**
  908. * At which distance, relative to the radius the centripetal forces should kick in? Range: 0-1
  909. */
  910. this.centripetalForceThreshold = 0.7;
  911. /**
  912. * This multiplier determines with how much force the objects will be pushed sideways/around the vortex, when below the threshold.
  913. */
  914. this.centripetalForceMultiplier = 5;
  915. /**
  916. * This multiplier determines with how much force the objects will be pushed sideways/around the vortex, when above the threshold.
  917. */
  918. this.centrifugalForceMultiplier = 0.5;
  919. /**
  920. * This multiplier determines with how much force the objects will be pushed upwards, when in the vortex.
  921. */
  922. this.updraftForceMultiplier = 0.02;
  923. }
  924. }
  925. /**
  926. * The strength of the force in correspondence to the distance of the affected object
  927. * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
  928. */
  929. export var PhysicsRadialImpulseFalloff;
  930. (function (PhysicsRadialImpulseFalloff) {
  931. /** Defines that impulse is constant in strength across it's whole radius */
  932. PhysicsRadialImpulseFalloff[PhysicsRadialImpulseFalloff["Constant"] = 0] = "Constant";
  933. /** Defines that impulse gets weaker if it's further from the origin */
  934. PhysicsRadialImpulseFalloff[PhysicsRadialImpulseFalloff["Linear"] = 1] = "Linear";
  935. })(PhysicsRadialImpulseFalloff || (PhysicsRadialImpulseFalloff = {}));
  936. /**
  937. * The strength of the force in correspondence to the distance of the affected object
  938. * @see https://doc.babylonjs.com/features/featuresDeepDive/physics/usingPhysicsEngine#further-functionality-of-the-impostor-class
  939. */
  940. export var PhysicsUpdraftMode;
  941. (function (PhysicsUpdraftMode) {
  942. /** Defines that the upstream forces will pull towards the top center of the cylinder */
  943. PhysicsUpdraftMode[PhysicsUpdraftMode["Center"] = 0] = "Center";
  944. /** Defines that once a impostor is inside the cylinder, it will shoot out perpendicular from the ground of the cylinder */
  945. PhysicsUpdraftMode[PhysicsUpdraftMode["Perpendicular"] = 1] = "Perpendicular";
  946. })(PhysicsUpdraftMode || (PhysicsUpdraftMode = {}));
  947. //# sourceMappingURL=physicsHelper.js.map