WebXRControllerPhysics.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import { WebXRAbstractFeature } from "./WebXRAbstractFeature.js";
  2. import { Vector3, Quaternion } from "../../Maths/math.vector.js";
  3. import { PhysicsImpostor } from "../../Physics/v1/physicsImpostor.js";
  4. import { CreateSphere } from "../../Meshes/Builders/sphereBuilder.js";
  5. import { WebXRFeatureName, WebXRFeaturesManager } from "../webXRFeaturesManager.js";
  6. import { Logger } from "../../Misc/logger.js";
  7. /**
  8. * Options for the controller physics feature
  9. */
  10. export class IWebXRControllerPhysicsOptions {
  11. }
  12. /**
  13. * Add physics impostor to your webxr controllers,
  14. * including naive calculation of their linear and angular velocity
  15. */
  16. export class WebXRControllerPhysics extends WebXRAbstractFeature {
  17. _createPhysicsImpostor(xrController) {
  18. const impostorType = this._options.physicsProperties.impostorType || PhysicsImpostor.SphereImpostor;
  19. const impostorSize = this._options.physicsProperties.impostorSize || 0.1;
  20. const impostorMesh = CreateSphere("impostor-mesh-" + xrController.uniqueId, {
  21. diameterX: typeof impostorSize === "number" ? impostorSize : impostorSize.width,
  22. diameterY: typeof impostorSize === "number" ? impostorSize : impostorSize.height,
  23. diameterZ: typeof impostorSize === "number" ? impostorSize : impostorSize.depth,
  24. });
  25. impostorMesh.isVisible = this._debugMode;
  26. impostorMesh.isPickable = false;
  27. impostorMesh.rotationQuaternion = new Quaternion();
  28. const controllerMesh = xrController.grip || xrController.pointer;
  29. impostorMesh.position.copyFrom(controllerMesh.position);
  30. impostorMesh.rotationQuaternion.copyFrom(controllerMesh.rotationQuaternion);
  31. const impostor = new PhysicsImpostor(impostorMesh, impostorType, {
  32. mass: 0,
  33. ...this._options.physicsProperties,
  34. });
  35. this._controllers[xrController.uniqueId] = {
  36. xrController,
  37. impostor,
  38. impostorMesh,
  39. };
  40. }
  41. /**
  42. * Construct a new Controller Physics Feature
  43. * @param _xrSessionManager the corresponding xr session manager
  44. * @param _options options to create this feature with
  45. */
  46. constructor(_xrSessionManager, _options) {
  47. super(_xrSessionManager);
  48. this._options = _options;
  49. this._attachController = (xrController) => {
  50. if (this._controllers[xrController.uniqueId]) {
  51. // already attached
  52. return;
  53. }
  54. if (!this._xrSessionManager.scene.isPhysicsEnabled()) {
  55. Logger.Warn("physics engine not enabled, skipped. Please add this controller manually.");
  56. }
  57. // if no motion controller available, create impostors!
  58. if (this._options.physicsProperties.useControllerMesh && xrController.inputSource.gamepad) {
  59. xrController.onMotionControllerInitObservable.addOnce((motionController) => {
  60. if (!motionController._doNotLoadControllerMesh) {
  61. motionController.onModelLoadedObservable.addOnce(() => {
  62. const impostor = new PhysicsImpostor(motionController.rootMesh, PhysicsImpostor.MeshImpostor, {
  63. mass: 0,
  64. ...this._options.physicsProperties,
  65. });
  66. const controllerMesh = xrController.grip || xrController.pointer;
  67. this._controllers[xrController.uniqueId] = {
  68. xrController,
  69. impostor,
  70. oldPos: controllerMesh.position.clone(),
  71. oldRotation: controllerMesh.rotationQuaternion.clone(),
  72. };
  73. });
  74. }
  75. else {
  76. // This controller isn't using a model, create impostors instead
  77. this._createPhysicsImpostor(xrController);
  78. }
  79. });
  80. }
  81. else {
  82. this._createPhysicsImpostor(xrController);
  83. }
  84. };
  85. this._controllers = {};
  86. this._debugMode = false;
  87. this._delta = 0;
  88. this._lastTimestamp = 0;
  89. this._tmpQuaternion = new Quaternion();
  90. this._tmpVector = new Vector3();
  91. if (!this._options.physicsProperties) {
  92. this._options.physicsProperties = {};
  93. }
  94. }
  95. /**
  96. * @internal
  97. * enable debugging - will show console outputs and the impostor mesh
  98. */
  99. _enablePhysicsDebug() {
  100. this._debugMode = true;
  101. Object.keys(this._controllers).forEach((controllerId) => {
  102. const controllerData = this._controllers[controllerId];
  103. if (controllerData.impostorMesh) {
  104. controllerData.impostorMesh.isVisible = true;
  105. }
  106. });
  107. }
  108. /**
  109. * Manually add a controller (if no xrInput was provided or physics engine was not enabled)
  110. * @param xrController the controller to add
  111. */
  112. addController(xrController) {
  113. this._attachController(xrController);
  114. }
  115. /**
  116. * attach this feature
  117. * Will usually be called by the features manager
  118. *
  119. * @returns true if successful.
  120. */
  121. attach() {
  122. if (!super.attach()) {
  123. return false;
  124. }
  125. if (!this._options.xrInput) {
  126. return true;
  127. }
  128. this._options.xrInput.controllers.forEach(this._attachController);
  129. this._addNewAttachObserver(this._options.xrInput.onControllerAddedObservable, this._attachController);
  130. this._addNewAttachObserver(this._options.xrInput.onControllerRemovedObservable, (controller) => {
  131. // REMOVE the controller
  132. this._detachController(controller.uniqueId);
  133. });
  134. if (this._options.enableHeadsetImpostor) {
  135. const params = this._options.headsetImpostorParams || {
  136. impostorType: PhysicsImpostor.SphereImpostor,
  137. restitution: 0.8,
  138. impostorSize: 0.3,
  139. };
  140. const impostorSize = params.impostorSize || 0.3;
  141. this._headsetMesh = CreateSphere("headset-mesh", {
  142. diameterX: typeof impostorSize === "number" ? impostorSize : impostorSize.width,
  143. diameterY: typeof impostorSize === "number" ? impostorSize : impostorSize.height,
  144. diameterZ: typeof impostorSize === "number" ? impostorSize : impostorSize.depth,
  145. });
  146. this._headsetMesh.rotationQuaternion = new Quaternion();
  147. this._headsetMesh.isVisible = false;
  148. this._headsetImpostor = new PhysicsImpostor(this._headsetMesh, params.impostorType, { mass: 0, ...params });
  149. }
  150. return true;
  151. }
  152. /**
  153. * detach this feature.
  154. * Will usually be called by the features manager
  155. *
  156. * @returns true if successful.
  157. */
  158. detach() {
  159. if (!super.detach()) {
  160. return false;
  161. }
  162. Object.keys(this._controllers).forEach((controllerId) => {
  163. this._detachController(controllerId);
  164. });
  165. if (this._headsetMesh) {
  166. this._headsetMesh.dispose();
  167. }
  168. return true;
  169. }
  170. /**
  171. * Get the headset impostor, if enabled
  172. * @returns the impostor
  173. */
  174. getHeadsetImpostor() {
  175. return this._headsetImpostor;
  176. }
  177. /**
  178. * Get the physics impostor of a specific controller.
  179. * The impostor is not attached to a mesh because a mesh for each controller is not obligatory
  180. * @param controller the controller or the controller id of which to get the impostor
  181. * @returns the impostor or null
  182. */
  183. getImpostorForController(controller) {
  184. const id = typeof controller === "string" ? controller : controller.uniqueId;
  185. if (this._controllers[id]) {
  186. return this._controllers[id].impostor;
  187. }
  188. else {
  189. return null;
  190. }
  191. }
  192. /**
  193. * Update the physics properties provided in the constructor
  194. * @param newProperties the new properties object
  195. * @param newProperties.impostorType
  196. * @param newProperties.impostorSize
  197. * @param newProperties.friction
  198. * @param newProperties.restitution
  199. */
  200. setPhysicsProperties(newProperties) {
  201. this._options.physicsProperties = {
  202. ...this._options.physicsProperties,
  203. ...newProperties,
  204. };
  205. }
  206. _onXRFrame(_xrFrame) {
  207. this._delta = this._xrSessionManager.currentTimestamp - this._lastTimestamp;
  208. this._lastTimestamp = this._xrSessionManager.currentTimestamp;
  209. if (this._headsetMesh && this._headsetImpostor) {
  210. this._headsetMesh.position.copyFrom(this._options.xrInput.xrCamera.globalPosition);
  211. this._headsetMesh.rotationQuaternion.copyFrom(this._options.xrInput.xrCamera.absoluteRotation);
  212. if (this._options.xrInput.xrCamera._lastXRViewerPose?.linearVelocity) {
  213. const lv = this._options.xrInput.xrCamera._lastXRViewerPose.linearVelocity;
  214. this._tmpVector.set(lv.x, lv.y, lv.z);
  215. this._headsetImpostor.setLinearVelocity(this._tmpVector);
  216. }
  217. if (this._options.xrInput.xrCamera._lastXRViewerPose?.angularVelocity) {
  218. const av = this._options.xrInput.xrCamera._lastXRViewerPose.angularVelocity;
  219. this._tmpVector.set(av.x, av.y, av.z);
  220. this._headsetImpostor.setAngularVelocity(this._tmpVector);
  221. }
  222. }
  223. Object.keys(this._controllers).forEach((controllerId) => {
  224. const controllerData = this._controllers[controllerId];
  225. const controllerMesh = controllerData.xrController.grip || controllerData.xrController.pointer;
  226. const comparedPosition = controllerData.oldPos || controllerData.impostorMesh.position;
  227. if (controllerData.xrController._lastXRPose?.linearVelocity) {
  228. const lv = controllerData.xrController._lastXRPose.linearVelocity;
  229. this._tmpVector.set(lv.x, lv.y, lv.z);
  230. controllerData.impostor.setLinearVelocity(this._tmpVector);
  231. }
  232. else {
  233. controllerMesh.position.subtractToRef(comparedPosition, this._tmpVector);
  234. this._tmpVector.scaleInPlace(1000 / this._delta);
  235. controllerData.impostor.setLinearVelocity(this._tmpVector);
  236. }
  237. comparedPosition.copyFrom(controllerMesh.position);
  238. if (this._debugMode) {
  239. Logger.Log([this._tmpVector, "linear"]);
  240. }
  241. const comparedQuaternion = controllerData.oldRotation || controllerData.impostorMesh.rotationQuaternion;
  242. if (controllerData.xrController._lastXRPose?.angularVelocity) {
  243. const av = controllerData.xrController._lastXRPose.angularVelocity;
  244. this._tmpVector.set(av.x, av.y, av.z);
  245. controllerData.impostor.setAngularVelocity(this._tmpVector);
  246. }
  247. else {
  248. if (!comparedQuaternion.equalsWithEpsilon(controllerMesh.rotationQuaternion)) {
  249. // roughly based on this - https://www.gamedev.net/forums/topic/347752-quaternion-and-angular-velocity/
  250. comparedQuaternion.conjugateInPlace().multiplyToRef(controllerMesh.rotationQuaternion, this._tmpQuaternion);
  251. const len = Math.sqrt(this._tmpQuaternion.x * this._tmpQuaternion.x + this._tmpQuaternion.y * this._tmpQuaternion.y + this._tmpQuaternion.z * this._tmpQuaternion.z);
  252. this._tmpVector.set(this._tmpQuaternion.x, this._tmpQuaternion.y, this._tmpQuaternion.z);
  253. // define a better epsilon
  254. if (len < 0.001) {
  255. this._tmpVector.scaleInPlace(2);
  256. }
  257. else {
  258. const angle = 2 * Math.atan2(len, this._tmpQuaternion.w);
  259. this._tmpVector.scaleInPlace(angle / (len * (this._delta / 1000)));
  260. }
  261. controllerData.impostor.setAngularVelocity(this._tmpVector);
  262. }
  263. }
  264. comparedQuaternion.copyFrom(controllerMesh.rotationQuaternion);
  265. if (this._debugMode) {
  266. Logger.Log([this._tmpVector, this._tmpQuaternion, "angular"]);
  267. }
  268. });
  269. }
  270. _detachController(xrControllerUniqueId) {
  271. const controllerData = this._controllers[xrControllerUniqueId];
  272. if (!controllerData) {
  273. return;
  274. }
  275. if (controllerData.impostorMesh) {
  276. controllerData.impostorMesh.dispose();
  277. }
  278. // remove from the map
  279. delete this._controllers[xrControllerUniqueId];
  280. }
  281. }
  282. /**
  283. * The module's name
  284. */
  285. WebXRControllerPhysics.Name = WebXRFeatureName.PHYSICS_CONTROLLERS;
  286. /**
  287. * The (Babylon) version of this module.
  288. * This is an integer representing the implementation version.
  289. * This number does not correspond to the webxr specs version
  290. */
  291. WebXRControllerPhysics.Version = 1;
  292. //register the plugin
  293. WebXRFeaturesManager.AddWebXRFeature(WebXRControllerPhysics.Name, (xrSessionManager, options) => {
  294. return () => new WebXRControllerPhysics(xrSessionManager, options);
  295. }, WebXRControllerPhysics.Version, true);
  296. //# sourceMappingURL=WebXRControllerPhysics.js.map