babylon.physicsHelper.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. module BABYLON {
  2. export class PhysicsHelper {
  3. private _scene: Scene;
  4. private _physicsEngine: Nullable<PhysicsEngine>;
  5. constructor(scene: Scene) {
  6. this._scene = scene;
  7. this._physicsEngine = this._scene.getPhysicsEngine();
  8. if (!this._physicsEngine) {
  9. Tools.Warn('Physics engine not enabled. Please enable the physics before you can use the methods.');
  10. }
  11. }
  12. /**
  13. * @param {Vector3} origin the origin of the explosion
  14. * @param {number} radius the explosion radius
  15. * @param {number} strength the explosion strength
  16. * @param {PhysicsRadialImpulseFalloff} falloff possible options: Constant & Linear. Defaults to Constant
  17. */
  18. public applyRadialExplosionImpulse(origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFalloff = PhysicsRadialImpulseFalloff.Constant): Nullable<PhysicsRadialExplosionEvent> {
  19. if (!this._physicsEngine) {
  20. Tools.Warn('Physics engine not enabled. Please enable the physics before you call this method.');
  21. return null;
  22. }
  23. var impostors = this._physicsEngine.getImpostors();
  24. if (impostors.length === 0) {
  25. return null;
  26. }
  27. var event = new PhysicsRadialExplosionEvent(this._scene);
  28. impostors.forEach(impostor => {
  29. var impostorForceAndContactPoint = event.getImpostorForceAndContactPoint(impostor, origin, radius, strength, falloff);
  30. if (!impostorForceAndContactPoint) {
  31. return;
  32. }
  33. impostor.applyImpulse(impostorForceAndContactPoint.force, impostorForceAndContactPoint.contactPoint);
  34. });
  35. event.dispose(false);
  36. return event;
  37. }
  38. /**
  39. * @param {Vector3} origin the origin of the explosion
  40. * @param {number} radius the explosion radius
  41. * @param {number} strength the explosion strength
  42. * @param {PhysicsRadialImpulseFalloff} falloff possible options: Constant & Linear. Defaults to Constant
  43. */
  44. public applyRadialExplosionForce(origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFalloff = PhysicsRadialImpulseFalloff.Constant): Nullable<PhysicsRadialExplosionEvent> {
  45. if (!this._physicsEngine) {
  46. Tools.Warn('Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.');
  47. return null;
  48. }
  49. var impostors = this._physicsEngine.getImpostors();
  50. if (impostors.length === 0) {
  51. return null;
  52. }
  53. var event = new PhysicsRadialExplosionEvent(this._scene);
  54. impostors.forEach(impostor => {
  55. var impostorForceAndContactPoint = event.getImpostorForceAndContactPoint(impostor, origin, radius, strength, falloff);
  56. if (!impostorForceAndContactPoint) {
  57. return;
  58. }
  59. impostor.applyForce(impostorForceAndContactPoint.force, impostorForceAndContactPoint.contactPoint);
  60. });
  61. event.dispose(false);
  62. return event;
  63. }
  64. /**
  65. * @param {Vector3} origin the origin of the explosion
  66. * @param {number} radius the explosion radius
  67. * @param {number} strength the explosion strength
  68. * @param {PhysicsRadialImpulseFalloff} falloff possible options: Constant & Linear. Defaults to Constant
  69. */
  70. public gravitationalField(origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFalloff = PhysicsRadialImpulseFalloff.Constant): Nullable<PhysicsGravitationalFieldEvent> {
  71. if (!this._physicsEngine) {
  72. Tools.Warn('Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.');
  73. return null;
  74. }
  75. var impostors = this._physicsEngine.getImpostors();
  76. if (impostors.length === 0) {
  77. return null;
  78. }
  79. var event = new PhysicsGravitationalFieldEvent(this, this._scene, origin, radius, strength, falloff);
  80. event.dispose(false);
  81. return event;
  82. }
  83. /**
  84. * @param {Vector3} origin the origin of the updraft
  85. * @param {number} radius the radius of the updraft
  86. * @param {number} strength the strength of the updraft
  87. * @param {number} height the height of the updraft
  88. * @param {PhysicsUpdraftMode} updraftMode possible options: Center & Perpendicular. Defaults to Center
  89. */
  90. public updraft(origin: Vector3, radius: number, strength: number, height: number, updraftMode: PhysicsUpdraftMode = PhysicsUpdraftMode.Center): Nullable<PhysicsUpdraftEvent> {
  91. if (!this._physicsEngine) {
  92. Tools.Warn('Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.');
  93. return null;
  94. }
  95. if (this._physicsEngine.getImpostors().length === 0) {
  96. return null;
  97. }
  98. var event = new PhysicsUpdraftEvent(this._scene, origin, radius, strength, height, updraftMode);
  99. event.dispose(false);
  100. return event;
  101. }
  102. /**
  103. * @param {Vector3} origin the of the vortex
  104. * @param {number} radius the radius of the vortex
  105. * @param {number} strength the strength of the vortex
  106. * @param {number} height the height of the vortex
  107. */
  108. public vortex(origin: Vector3, radius: number, strength: number, height: number): Nullable<PhysicsVortexEvent> {
  109. if (!this._physicsEngine) {
  110. Tools.Warn('Physics engine not enabled. Please enable the physics before you call the PhysicsHelper.');
  111. return null;
  112. }
  113. if (this._physicsEngine.getImpostors().length === 0) {
  114. return null;
  115. }
  116. var event = new PhysicsVortexEvent(this._scene, origin, radius, strength, height);
  117. event.dispose(false);
  118. return event;
  119. }
  120. }
  121. /***** Radial explosion *****/
  122. export class PhysicsRadialExplosionEvent {
  123. private _scene: Scene;
  124. private _sphere: Mesh; // create a sphere, so we can get the intersecting meshes inside
  125. private _sphereOptions: { segments: number, diameter: number } = { segments: 32, diameter: 1 }; // TODO: make configurable
  126. private _rays: Array<Ray> = [];
  127. private _dataFetched: boolean = false; // check if the data has been fetched. If not, do cleanup
  128. constructor(scene: Scene) {
  129. this._scene = scene;
  130. }
  131. /**
  132. * Returns the data related to the radial explosion event (sphere & rays).
  133. * @returns {PhysicsRadialExplosionEventData}
  134. */
  135. public getData(): PhysicsRadialExplosionEventData {
  136. this._dataFetched = true;
  137. return {
  138. sphere: this._sphere,
  139. rays: this._rays,
  140. };
  141. }
  142. /**
  143. * Returns the force and contact point of the impostor or false, if the impostor is not affected by the force/impulse.
  144. * @param impostor
  145. * @param {Vector3} origin the origin of the explosion
  146. * @param {number} radius the explosion radius
  147. * @param {number} strength the explosion strength
  148. * @param {PhysicsRadialImpulseFalloff} falloff possible options: Constant & Linear
  149. * @returns {Nullable<PhysicsForceAndContactPoint>}
  150. */
  151. public getImpostorForceAndContactPoint(impostor: PhysicsImpostor, origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFalloff): Nullable<PhysicsForceAndContactPoint> {
  152. if (impostor.mass === 0) {
  153. return null;
  154. }
  155. if (!this._intersectsWithSphere(impostor, origin, radius)) {
  156. return null;
  157. }
  158. if (impostor.object.getClassName() !== 'Mesh') {
  159. return null;
  160. }
  161. var impostorObject = <Mesh>impostor.object;
  162. var impostorObjectCenter = impostor.getObjectCenter();
  163. var direction = impostorObjectCenter.subtract(origin);
  164. var ray = new Ray(origin, direction, radius);
  165. this._rays.push(ray);
  166. var hit = ray.intersectsMesh(impostorObject);
  167. var contactPoint = hit.pickedPoint;
  168. if (!contactPoint) {
  169. return null;
  170. }
  171. var distanceFromOrigin = Vector3.Distance(origin, contactPoint);
  172. if (distanceFromOrigin > radius) {
  173. return null;
  174. }
  175. var multiplier = falloff === PhysicsRadialImpulseFalloff.Constant
  176. ? strength
  177. : strength * (1 - (distanceFromOrigin / radius));
  178. var force = direction.multiplyByFloats(multiplier, multiplier, multiplier);
  179. return { force: force, contactPoint: contactPoint };
  180. }
  181. /**
  182. * Disposes the sphere.
  183. * @param {bolean} force
  184. */
  185. public dispose(force: boolean = true) {
  186. if (force) {
  187. this._sphere.dispose();
  188. } else {
  189. setTimeout(() => {
  190. if (!this._dataFetched) {
  191. this._sphere.dispose();
  192. }
  193. }, 0);
  194. }
  195. }
  196. /*** Helpers ***/
  197. private _prepareSphere(): void {
  198. if (!this._sphere) {
  199. this._sphere = MeshBuilder.CreateSphere("radialExplosionEventSphere", this._sphereOptions, this._scene);
  200. this._sphere.isVisible = false;
  201. }
  202. }
  203. private _intersectsWithSphere(impostor: PhysicsImpostor, origin: Vector3, radius: number): boolean {
  204. var impostorObject = <Mesh>impostor.object;
  205. this._prepareSphere();
  206. this._sphere.position = origin;
  207. this._sphere.scaling = new Vector3(radius * 2, radius * 2, radius * 2);
  208. this._sphere._updateBoundingInfo();
  209. this._sphere.computeWorldMatrix(true);
  210. return this._sphere.intersectsMesh(impostorObject, true);
  211. }
  212. }
  213. /***** Gravitational Field *****/
  214. export class PhysicsGravitationalFieldEvent {
  215. private _physicsHelper: PhysicsHelper;
  216. private _scene: Scene;
  217. private _origin: Vector3;
  218. private _radius: number;
  219. private _strength: number;
  220. private _falloff: PhysicsRadialImpulseFalloff;
  221. private _tickCallback: any;
  222. private _sphere: Mesh;
  223. private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup
  224. constructor(physicsHelper: PhysicsHelper, scene: Scene, origin: Vector3, radius: number, strength: number, falloff: PhysicsRadialImpulseFalloff = PhysicsRadialImpulseFalloff.Constant) {
  225. this._physicsHelper = physicsHelper;
  226. this._scene = scene;
  227. this._origin = origin;
  228. this._radius = radius;
  229. this._strength = strength;
  230. this._falloff = falloff;
  231. this._tickCallback = this._tick.bind(this);
  232. }
  233. /**
  234. * Returns the data related to the gravitational field event (sphere).
  235. * @returns {PhysicsGravitationalFieldEventData}
  236. */
  237. public getData(): PhysicsGravitationalFieldEventData {
  238. this._dataFetched = true;
  239. return {
  240. sphere: this._sphere,
  241. };
  242. }
  243. /**
  244. * Enables the gravitational field.
  245. */
  246. public enable() {
  247. this._tickCallback.call(this);
  248. this._scene.registerBeforeRender(this._tickCallback);
  249. }
  250. /**
  251. * Disables the gravitational field.
  252. */
  253. public disable() {
  254. this._scene.unregisterBeforeRender(this._tickCallback);
  255. }
  256. /**
  257. * Disposes the sphere.
  258. * @param {bolean} force
  259. */
  260. public dispose(force: boolean = true) {
  261. if (force) {
  262. this._sphere.dispose();
  263. } else {
  264. setTimeout(() => {
  265. if (!this._dataFetched) {
  266. this._sphere.dispose();
  267. }
  268. }, 0);
  269. }
  270. }
  271. private _tick() {
  272. // Since the params won't change, we fetch the event only once
  273. if (this._sphere) {
  274. this._physicsHelper.applyRadialExplosionForce(this._origin, this._radius, this._strength * -1, this._falloff);
  275. } else {
  276. var radialExplosionEvent = this._physicsHelper.applyRadialExplosionForce(this._origin, this._radius, this._strength * -1, this._falloff);
  277. if (radialExplosionEvent) {
  278. this._sphere = <Mesh>radialExplosionEvent.getData().sphere.clone('radialExplosionEventSphereClone');
  279. }
  280. }
  281. }
  282. }
  283. /***** Updraft *****/
  284. export class PhysicsUpdraftEvent {
  285. private _physicsEngine: PhysicsEngine;
  286. private _originTop: Vector3 = Vector3.Zero(); // the most upper part of the cylinder
  287. private _originDirection: Vector3 = Vector3.Zero(); // used if the updraftMode is perpendicular
  288. private _tickCallback: any;
  289. private _cylinder: Mesh;
  290. private _cylinderPosition: Vector3 = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom
  291. private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup
  292. constructor(private _scene: Scene, private _origin: Vector3, private _radius: number, private _strength: number, private _height: number, private _updraftMode: PhysicsUpdraftMode) {
  293. this._physicsEngine = <PhysicsEngine>this._scene.getPhysicsEngine();
  294. this._origin.addToRef(new Vector3(0, this._height / 2, 0), this._cylinderPosition);
  295. this._origin.addToRef(new Vector3(0, this._height, 0), this._originTop);
  296. if (this._updraftMode === PhysicsUpdraftMode.Perpendicular) {
  297. this._originDirection = this._origin.subtract(this._originTop).normalize();
  298. }
  299. this._tickCallback = this._tick.bind(this);
  300. }
  301. /**
  302. * Returns the data related to the updraft event (cylinder).
  303. * @returns {PhysicsUpdraftEventData}
  304. */
  305. public getData(): PhysicsUpdraftEventData {
  306. this._dataFetched = true;
  307. return {
  308. cylinder: this._cylinder,
  309. };
  310. }
  311. /**
  312. * Enables the updraft.
  313. */
  314. public enable() {
  315. this._tickCallback.call(this);
  316. this._scene.registerBeforeRender(this._tickCallback);
  317. }
  318. /**
  319. * Disables the cortex.
  320. */
  321. public disable() {
  322. this._scene.unregisterBeforeRender(this._tickCallback);
  323. }
  324. /**
  325. * Disposes the sphere.
  326. * @param {bolean} force
  327. */
  328. public dispose(force: boolean = true) {
  329. if (force) {
  330. this._cylinder.dispose();
  331. } else {
  332. setTimeout(() => {
  333. if (!this._dataFetched) {
  334. this._cylinder.dispose();
  335. }
  336. }, 0);
  337. }
  338. }
  339. private getImpostorForceAndContactPoint(impostor: PhysicsImpostor): Nullable<PhysicsForceAndContactPoint> {
  340. if (impostor.mass === 0) {
  341. return null;
  342. }
  343. if (!this._intersectsWithCylinder(impostor)) {
  344. return null;
  345. }
  346. var impostorObjectCenter = impostor.getObjectCenter();
  347. if (this._updraftMode === PhysicsUpdraftMode.Perpendicular) {
  348. var direction = this._originDirection;
  349. } else {
  350. var direction = impostorObjectCenter.subtract(this._originTop);
  351. }
  352. var multiplier = this._strength * -1;
  353. var force = direction.multiplyByFloats(multiplier, multiplier, multiplier);
  354. return { force: force, contactPoint: impostorObjectCenter };
  355. }
  356. private _tick() {
  357. this._physicsEngine.getImpostors().forEach(impostor => {
  358. var impostorForceAndContactPoint = this.getImpostorForceAndContactPoint(impostor);
  359. if (!impostorForceAndContactPoint) {
  360. return;
  361. }
  362. impostor.applyForce(impostorForceAndContactPoint.force, impostorForceAndContactPoint.contactPoint);
  363. });
  364. }
  365. /*** Helpers ***/
  366. private _prepareCylinder(): void {
  367. if (!this._cylinder) {
  368. this._cylinder = MeshBuilder.CreateCylinder("updraftEventCylinder", {
  369. height: this._height,
  370. diameter: this._radius * 2,
  371. }, this._scene);
  372. this._cylinder.isVisible = false;
  373. }
  374. }
  375. private _intersectsWithCylinder(impostor: PhysicsImpostor): boolean {
  376. var impostorObject = <Mesh>impostor.object;
  377. this._prepareCylinder();
  378. this._cylinder.position = this._cylinderPosition;
  379. return this._cylinder.intersectsMesh(impostorObject, true);
  380. }
  381. }
  382. /***** Vortex *****/
  383. export class PhysicsVortexEvent {
  384. private _physicsEngine: PhysicsEngine;
  385. private _originTop: Vector3 = Vector3.Zero(); // the most upper part of the cylinder
  386. private _centripetalForceThreshold: number = 0.7; // at which distance, relative to the radius the centripetal forces should kick in
  387. private _updraftMultiplier: number = 0.02;
  388. private _tickCallback: any;
  389. private _cylinder: Mesh;
  390. private _cylinderPosition: Vector3 = Vector3.Zero(); // to keep the cylinders position, because normally the origin is in the center and not on the bottom
  391. private _dataFetched: boolean = false; // check if the has been fetched the data. If not, do cleanup
  392. constructor(private _scene: Scene, private _origin: Vector3, private _radius: number, private _strength: number, private _height: number) {
  393. this._physicsEngine = <PhysicsEngine>this._scene.getPhysicsEngine();
  394. this._origin.addToRef(new Vector3(0, this._height / 2, 0), this._cylinderPosition);
  395. this._origin.addToRef(new Vector3(0, this._height, 0), this._originTop);
  396. this._tickCallback = this._tick.bind(this);
  397. }
  398. /**
  399. * Returns the data related to the vortex event (cylinder).
  400. * @returns {PhysicsVortexEventData}
  401. */
  402. public getData(): PhysicsVortexEventData {
  403. this._dataFetched = true;
  404. return {
  405. cylinder: this._cylinder,
  406. };
  407. }
  408. /**
  409. * Enables the vortex.
  410. */
  411. public enable() {
  412. this._tickCallback.call(this);
  413. this._scene.registerBeforeRender(this._tickCallback);
  414. }
  415. /**
  416. * Disables the cortex.
  417. */
  418. public disable() {
  419. this._scene.unregisterBeforeRender(this._tickCallback);
  420. }
  421. /**
  422. * Disposes the sphere.
  423. * @param {bolean} force
  424. */
  425. public dispose(force: boolean = true) {
  426. if (force) {
  427. this._cylinder.dispose();
  428. } else {
  429. setTimeout(() => {
  430. if (!this._dataFetched) {
  431. this._cylinder.dispose();
  432. }
  433. }, 0);
  434. }
  435. }
  436. private getImpostorForceAndContactPoint(impostor: PhysicsImpostor): Nullable<PhysicsForceAndContactPoint> {
  437. if (impostor.mass === 0) {
  438. return null;
  439. }
  440. if (!this._intersectsWithCylinder(impostor)) {
  441. return null;
  442. }
  443. if (impostor.object.getClassName() !== 'Mesh') {
  444. return null;
  445. }
  446. var impostorObject = <Mesh>impostor.object;
  447. var impostorObjectCenter = impostor.getObjectCenter();
  448. var originOnPlane = new Vector3(this._origin.x, impostorObjectCenter.y, this._origin.z); // the distance to the origin as if both objects were on a plane (Y-axis)
  449. var originToImpostorDirection = impostorObjectCenter.subtract(originOnPlane);
  450. var ray = new Ray(originOnPlane, originToImpostorDirection, this._radius);
  451. var hit = ray.intersectsMesh(impostorObject);
  452. var contactPoint = hit.pickedPoint;
  453. if (!contactPoint) {
  454. return null;
  455. }
  456. var absoluteDistanceFromOrigin = hit.distance / this._radius;
  457. var perpendicularDirection = Vector3.Cross(originOnPlane, impostorObjectCenter).normalize();
  458. var directionToOrigin = contactPoint.normalize();
  459. if (absoluteDistanceFromOrigin > this._centripetalForceThreshold) {
  460. directionToOrigin = directionToOrigin.negate();
  461. }
  462. // TODO: find a more physically based solution
  463. if (absoluteDistanceFromOrigin > this._centripetalForceThreshold) {
  464. var forceX = directionToOrigin.x * this._strength / 8;
  465. var forceY = directionToOrigin.y * this._updraftMultiplier;
  466. var forceZ = directionToOrigin.z * this._strength / 8;
  467. } else {
  468. var forceX = (perpendicularDirection.x + directionToOrigin.x) / 2;
  469. var forceY = this._originTop.y * this._updraftMultiplier;
  470. var forceZ = (perpendicularDirection.z + directionToOrigin.z) / 2;
  471. }
  472. var force = new Vector3(forceX, forceY, forceZ);
  473. force = force.multiplyByFloats(this._strength, this._strength, this._strength);
  474. return { force: force, contactPoint: impostorObjectCenter };
  475. }
  476. private _tick() {
  477. this._physicsEngine.getImpostors().forEach(impostor => {
  478. var impostorForceAndContactPoint = this.getImpostorForceAndContactPoint(impostor);
  479. if (!impostorForceAndContactPoint) {
  480. return;
  481. }
  482. impostor.applyForce(impostorForceAndContactPoint.force, impostorForceAndContactPoint.contactPoint);
  483. });
  484. }
  485. /*** Helpers ***/
  486. private _prepareCylinder(): void {
  487. if (!this._cylinder) {
  488. this._cylinder = MeshBuilder.CreateCylinder("vortexEventCylinder", {
  489. height: this._height,
  490. diameter: this._radius * 2,
  491. }, this._scene);
  492. this._cylinder.isVisible = false;
  493. }
  494. }
  495. private _intersectsWithCylinder(impostor: PhysicsImpostor): boolean {
  496. var impostorObject = <Mesh>impostor.object;
  497. this._prepareCylinder();
  498. this._cylinder.position = this._cylinderPosition;
  499. return this._cylinder.intersectsMesh(impostorObject, true);
  500. }
  501. }
  502. /***** Enums *****/
  503. /**
  504. * The strenght of the force in correspondence to the distance of the affected object
  505. */
  506. export enum PhysicsRadialImpulseFalloff {
  507. Constant, // impulse is constant in strength across it's whole radius
  508. Linear // impulse gets weaker if it's further from the origin
  509. }
  510. /**
  511. * The strenght of the force in correspondence to the distance of the affected object
  512. */
  513. export enum PhysicsUpdraftMode {
  514. Center, // the upstream forces will pull towards the top center of the cylinder
  515. Perpendicular // once a impostor is inside the cylinder, it will shoot out perpendicular from the ground of the cylinder
  516. }
  517. /***** Data interfaces *****/
  518. export interface PhysicsForceAndContactPoint {
  519. force: Vector3;
  520. contactPoint: Vector3;
  521. }
  522. export interface PhysicsRadialExplosionEventData {
  523. sphere: Mesh;
  524. rays: Array<Ray>;
  525. }
  526. export interface PhysicsGravitationalFieldEventData {
  527. sphere: Mesh;
  528. }
  529. export interface PhysicsUpdraftEventData {
  530. cylinder: Mesh;
  531. }
  532. export interface PhysicsVortexEventData {
  533. cylinder: Mesh;
  534. }
  535. }