babylon.physicsHelper.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. module BABYLON {
  2. /**
  3. * The strenght of the force in correspondence to the distance of the affected object
  4. */
  5. export enum PhysicsRadialImpulseFallof {
  6. Constant, // impulse is constant in strength across it's whole radius
  7. Linear // impulse gets weaker if it's further from the origin
  8. }
  9. /**
  10. * The strenght of the force in correspondence to the distance of the affected object
  11. */
  12. export enum PhysicsUpdraftMode {
  13. Center, // the upstream forces will pull towards the top center of the cylinder
  14. Perpendicular // once a impostor is inside the cylinder, it will shoot out perpendicular towards the ground of the cylinder
  15. }
  16. export class PhysicsHelper {
  17. private _scene: Scene;
  18. private _physicsEngine: Nullable<PhysicsEngine>;
  19. constructor(scene: Scene) {
  20. this._scene = scene;
  21. this._physicsEngine = this._scene.getPhysicsEngine();
  22. if (!this._physicsEngine) {
  23. Tools.Warn('Physics engine not enabled. Please enable the physics before you can use the methods.');
  24. }
  25. }
  26. /**
  27. * @param {Vector3} origin the origin of the explosion
  28. * @param {number} radius the explosion radius
  29. * @param {number} strength the explosion strength
  30. * @param {PhysicsRadialImpulseFallof} falloff possible options: Constant & Linear. Defaults to Constant
  31. */
  32. public applyRadialExplosionImpulse(origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFallof = PhysicsRadialImpulseFallof.Constant): Nullable<PhysicsRadialExplosionEvent> {
  33. if (!this._physicsEngine) {
  34. Tools.Warn('Physics engine not enabled. Please enable the physics before you call this method.');
  35. return null;
  36. }
  37. var impostors = this._physicsEngine.getImpostors();
  38. if (impostors.length === 0) {
  39. return null;
  40. }
  41. var event = new PhysicsRadialExplosionEvent(this._scene);
  42. impostors.forEach(impostor => {
  43. var impostorForceAndContactPoint = event.getImpostorForceAndContactPoint(impostor, origin, radius, strength, falloff);
  44. if (!impostorForceAndContactPoint) {
  45. return;
  46. }
  47. impostor.applyImpulse(impostorForceAndContactPoint.force, impostorForceAndContactPoint.contactPoint);
  48. });
  49. event.dispose(false);
  50. return event;
  51. }
  52. /**
  53. * @param {Vector3} origin the origin of the explosion
  54. * @param {number} radius the explosion radius
  55. * @param {number} strength the explosion strength
  56. * @param {PhysicsRadialImpulseFallof} falloff possible options: Constant & Linear. Defaults to Constant
  57. */
  58. public applyRadialExplosionForce(origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFallof = PhysicsRadialImpulseFallof.Constant): Nullable<PhysicsRadialExplosionEvent> {
  59. if (!this._physicsEngine) {
  60. Tools.Warn('Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.');
  61. return null;
  62. }
  63. var impostors = this._physicsEngine.getImpostors();
  64. if (impostors.length === 0) {
  65. return null;
  66. }
  67. var event = new PhysicsRadialExplosionEvent(this._scene);
  68. impostors.forEach(impostor => {
  69. var impostorForceAndContactPoint = event.getImpostorForceAndContactPoint(impostor, origin, radius, strength, falloff);
  70. if (!impostorForceAndContactPoint) {
  71. return;
  72. }
  73. impostor.applyForce(impostorForceAndContactPoint.force, impostorForceAndContactPoint.contactPoint);
  74. });
  75. event.dispose(false);
  76. return event;
  77. }
  78. /**
  79. * @param {Vector3} origin the origin of the explosion
  80. * @param {number} radius the explosion radius
  81. * @param {number} strength the explosion strength
  82. * @param {PhysicsRadialImpulseFallof} falloff possible options: Constant & Linear. Defaults to Constant
  83. */
  84. public gravitationalField(origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFallof = PhysicsRadialImpulseFallof.Constant): Nullable<PhysicsGravitationalFieldEvent> {
  85. if (!this._physicsEngine) {
  86. Tools.Warn('Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.');
  87. return null;
  88. }
  89. var impostors = this._physicsEngine.getImpostors();
  90. if (impostors.length === 0) {
  91. return null;
  92. }
  93. var event = new PhysicsGravitationalFieldEvent(this, this._scene, origin, radius, strength, falloff);
  94. event.dispose(false);
  95. return event;
  96. }
  97. /**
  98. * @param {Vector3} origin the origin of the updraft
  99. * @param {number} radius the radius of the updraft
  100. * @param {number} strength the strength of the updraft
  101. * @param {number} height the height of the updraft
  102. */
  103. public updraft(origin: Vector3, radius: number, strength: number, height: number, updraftMode: PhysicsUpdraftMode): Nullable<PhysicsUpdraftEvent> {
  104. if (!this._physicsEngine) {
  105. Tools.Warn('Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.');
  106. return null;
  107. }
  108. if (this._physicsEngine.getImpostors().length === 0) {
  109. return null;
  110. }
  111. var event = new PhysicsUpdraftEvent(this._physicsEngine, this._scene, origin, radius, strength, height, updraftMode);
  112. event.dispose(false);
  113. return event;
  114. }
  115. }
  116. /***** Radial explosion *****/
  117. export class PhysicsRadialExplosionEvent {
  118. private _scene: Scene;
  119. private _sphere: Mesh; // create a sphere, so we can get the intersecting meshes inside
  120. private _sphereOptions: { segments: number, diameter: number } = { segments: 32, diameter: 1 }; // TODO: make configurable
  121. private _rays: Array<Ray> = [];
  122. private _dataFetched: boolean = false; // check if the data has been fetched. If not, do cleanup
  123. constructor(scene: Scene) {
  124. this._scene = scene;
  125. }
  126. /**
  127. * Returns the data related to the radial explosion event (sphere & rays).
  128. * @returns {PhysicsRadialExplosionEventData}
  129. */
  130. public getData(): PhysicsRadialExplosionEventData {
  131. this._dataFetched = true;
  132. return {
  133. sphere: this._sphere,
  134. rays: this._rays,
  135. };
  136. }
  137. /**
  138. * Returns the force and contact point of the impostor or false, if the impostor is not affected by the force/impulse.
  139. * @param impostor
  140. * @param {Vector3} origin the origin of the explosion
  141. * @param {number} radius the explosion radius
  142. * @param {number} strength the explosion strength
  143. * @param {PhysicsRadialImpulseFallof} falloff possible options: Constant & Linear
  144. * @returns {Nullable<PhysicsForceAndContactPoint>}
  145. */
  146. public getImpostorForceAndContactPoint(impostor: PhysicsImpostor, origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFallof): Nullable<PhysicsForceAndContactPoint> {
  147. if (impostor.mass === 0) {
  148. return null;
  149. }
  150. if (!this._intersectsWithSphere(impostor, origin, radius)) {
  151. return null;
  152. }
  153. var impostorObject = (<Mesh>impostor.object);
  154. var impostorObjectCenter = impostor.getObjectCenter();
  155. var direction = impostorObjectCenter.subtract(origin);
  156. var ray = new Ray(origin, direction, radius);
  157. this._rays.push(ray);
  158. var hit = ray.intersectsMesh(impostorObject);
  159. var contactPoint = hit.pickedPoint;
  160. if (!contactPoint) {
  161. return null;
  162. }
  163. var distanceFromOrigin = Vector3.Distance(origin, contactPoint);
  164. if (distanceFromOrigin > radius) {
  165. return null;
  166. }
  167. var multiplier = falloff === PhysicsRadialImpulseFallof.Constant
  168. ? strength
  169. : strength * (1 - (distanceFromOrigin / radius));
  170. var force = direction.multiplyByFloats(multiplier, multiplier, multiplier);
  171. return { force: force, contactPoint: contactPoint };
  172. }
  173. /**
  174. * Disposes the sphere.
  175. * @param {bolean} force
  176. */
  177. public dispose(force: boolean = true) {
  178. if (force) {
  179. this._sphere.dispose();
  180. } else {
  181. setTimeout(() => {
  182. if (!this._dataFetched) {
  183. this._sphere.dispose();
  184. }
  185. }, 0);
  186. }
  187. }
  188. /*** Helpers ***/
  189. private _prepareSphere(): void {
  190. if (!this._sphere) {
  191. this._sphere = MeshBuilder.CreateSphere("radialExplosionEventSphere", this._sphereOptions, this._scene);
  192. this._sphere.isVisible = false;
  193. }
  194. }
  195. private _intersectsWithSphere(impostor: PhysicsImpostor, origin: Vector3, radius: number): boolean {
  196. var impostorObject = <Mesh>impostor.object;
  197. this._prepareSphere();
  198. this._sphere.position = origin;
  199. this._sphere.scaling = new Vector3(radius * 2, radius * 2, radius * 2);
  200. this._sphere._updateBoundingInfo();
  201. this._sphere.computeWorldMatrix(true);
  202. return this._sphere.intersectsMesh(impostorObject, true);
  203. }
  204. }
  205. /***** Gravitational Field *****/
  206. export class PhysicsGravitationalFieldEvent {
  207. private _physicsHelper: PhysicsHelper;
  208. private _scene: Scene;
  209. private _origin: Vector3;
  210. private _radius: number;
  211. private _strength: number;
  212. private _falloff: PhysicsRadialImpulseFallof;
  213. private _tickCallback: any;
  214. private _sphere: Mesh;
  215. private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup
  216. constructor(physicsHelper: PhysicsHelper, scene: Scene, origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFallof = PhysicsRadialImpulseFallof.Constant) {
  217. this._physicsHelper = physicsHelper;
  218. this._scene = scene;
  219. this._origin = origin;
  220. this._radius = radius;
  221. this._strength = strength;
  222. this._falloff = falloff;
  223. this._tickCallback = this._tick.bind(this);
  224. }
  225. /**
  226. * Returns the data related to the gravitational field event (sphere).
  227. * @returns {PhysicsGravitationalFieldEventData}
  228. */
  229. public getData(): PhysicsGravitationalFieldEventData {
  230. this._dataFetched = true;
  231. return {
  232. sphere: this._sphere,
  233. };
  234. }
  235. /**
  236. * Enables the gravitational field.
  237. */
  238. public enable() {
  239. this._tickCallback.call(this);
  240. this._scene.registerBeforeRender(this._tickCallback);
  241. }
  242. /**
  243. * Disables the gravitational field.
  244. */
  245. public disable() {
  246. this._scene.unregisterBeforeRender(this._tickCallback);
  247. }
  248. /**
  249. * Disposes the sphere.
  250. * @param {bolean} force
  251. */
  252. public dispose(force: boolean = true) {
  253. if (force) {
  254. this._sphere.dispose();
  255. } else {
  256. setTimeout(() => {
  257. if (!this._dataFetched) {
  258. this._sphere.dispose();
  259. }
  260. }, 0);
  261. }
  262. }
  263. private _tick() {
  264. // Since the params won't change, we fetch the event only once
  265. if (this._sphere) {
  266. this._physicsHelper.applyRadialExplosionForce(this._origin, this._radius, this._strength * -1, this._falloff);
  267. } else {
  268. var radialExplosionEvent = this._physicsHelper.applyRadialExplosionForce(this._origin, this._radius, this._strength * -1, this._falloff);
  269. if (radialExplosionEvent) {
  270. this._sphere = <Mesh>radialExplosionEvent.getData().sphere.clone('radialExplosionEventSphereClone');
  271. }
  272. }
  273. }
  274. }
  275. export interface PhysicsGravitationalFieldEventData {
  276. sphere: Mesh;
  277. }
  278. /***** Updraft *****/
  279. export class PhysicsUpdraftEvent {
  280. private _physicsEngine: PhysicsEngine;
  281. private _scene: Scene;
  282. private _origin: Vector3;
  283. private _originTop: Vector3 = Vector3.Zero(); // the most upper part of the cylinder
  284. private _originDirection: Vector3 = Vector3.Zero(); // used if the updraftMode is perpendicular
  285. private _radius: number;
  286. private _strength: number;
  287. private _height: number;
  288. private _updraftMode: PhysicsUpdraftMode;
  289. private _tickCallback: any;
  290. private _cylinder: Mesh;
  291. private _cylinderPosition: Vector3 = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom
  292. private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup
  293. constructor(physicsEngine: PhysicsEngine, scene: Scene, origin: Vector3, radius: number, strength: number, height: number, updraftMode: PhysicsUpdraftMode) {
  294. this._physicsEngine = physicsEngine;
  295. this._scene = scene;
  296. this._origin = origin;
  297. this._radius = radius;
  298. this._strength = strength;
  299. this._height = height;
  300. this._updraftMode = updraftMode;
  301. // TODO: for this._cylinderPosition & this._originTop, take rotation into account
  302. this._origin.addToRef(new Vector3(0, this._height / 2, 0), this._cylinderPosition);
  303. this._origin.addToRef(new Vector3(0, this._height, 0), this._originTop);
  304. if (this._updraftMode === PhysicsUpdraftMode.Perpendicular) {
  305. this._originDirection = this._origin.subtract(this._originTop).normalize();
  306. }
  307. this._tickCallback = this._tick.bind(this);
  308. }
  309. /**
  310. * Returns the data related to the updraft event (cylinder).
  311. * @returns {PhysicsGravitationalFieldEventData}
  312. */
  313. public getData(): PhysicsUpdraftEventData {
  314. this._dataFetched = true;
  315. return {
  316. cylinder: this._cylinder,
  317. };
  318. }
  319. /**
  320. * Enables the updraft.
  321. */
  322. public enable() {
  323. this._tickCallback.call(this);
  324. this._scene.registerBeforeRender(this._tickCallback);
  325. }
  326. /**
  327. * Disables the cortex.
  328. */
  329. public disable() {
  330. this._scene.unregisterBeforeRender(this._tickCallback);
  331. }
  332. /**
  333. * Disposes the sphere.
  334. * @param {bolean} force
  335. */
  336. public dispose(force: boolean = true) {
  337. if (force) {
  338. this._cylinder.dispose();
  339. } else {
  340. setTimeout(() => {
  341. if (!this._dataFetched) {
  342. this._cylinder.dispose();
  343. }
  344. }, 0);
  345. }
  346. }
  347. private getImpostorForceAndContactPoint(impostor: PhysicsImpostor): Nullable<PhysicsForceAndContactPoint> {
  348. if (impostor.mass === 0) {
  349. return null;
  350. }
  351. if (!this._intersectsWithCylinder(impostor)) {
  352. return null;
  353. }
  354. var impostorObjectCenter = impostor.getObjectCenter();
  355. if (this._updraftMode === PhysicsUpdraftMode.Perpendicular) {
  356. var direction = this._originDirection;
  357. } else {
  358. var direction = impostorObjectCenter.subtract(this._originTop);
  359. }
  360. var multiplier = this._strength * -1;
  361. var force = direction.multiplyByFloats(multiplier, multiplier, multiplier);
  362. return { force: force, contactPoint: impostorObjectCenter };
  363. }
  364. private _tick() {
  365. this._physicsEngine.getImpostors().forEach(impostor => {
  366. var impostorForceAndContactPoint = this.getImpostorForceAndContactPoint(impostor);
  367. if (!impostorForceAndContactPoint) {
  368. return;
  369. }
  370. impostor.applyForce(impostorForceAndContactPoint.force, impostorForceAndContactPoint.contactPoint);
  371. });
  372. }
  373. /*** Helpers ***/
  374. private _prepareCylinder(): void {
  375. if (!this._cylinder) {
  376. this._cylinder = MeshBuilder.CreateCylinder("updraftEventCylinder", {
  377. height: this._height,
  378. diameter: this._radius * 2,
  379. }, this._scene);
  380. this._cylinder.isVisible = false;
  381. }
  382. }
  383. private _intersectsWithCylinder(impostor: PhysicsImpostor): boolean {
  384. var impostorObject = <Mesh>impostor.object;
  385. this._prepareCylinder();
  386. this._cylinder.position = this._cylinderPosition;
  387. return this._cylinder.intersectsMesh(impostorObject, true);
  388. }
  389. }
  390. /***** Data interfaces *****/
  391. export interface PhysicsRadialExplosionEventData {
  392. sphere: Mesh;
  393. rays: Array<Ray>;
  394. }
  395. export interface PhysicsForceAndContactPoint {
  396. force: Vector3;
  397. contactPoint: Vector3;
  398. }
  399. export interface PhysicsUpdraftEventData {
  400. cylinder: Mesh;
  401. }
  402. }