poseEnabledController.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. import { Observable } from "Misc/observable";
  2. import { Nullable } from "types";
  3. import { Quaternion, Matrix, Vector3, Tmp } from "Maths/math";
  4. import { Node } from "node";
  5. import { TransformNode } from "Meshes/transformNode";
  6. import { AbstractMesh } from "Meshes/abstractMesh";
  7. import { Ray } from "Culling/ray";
  8. import { _TimeToken } from "Instrumentation/timeToken";
  9. import { _DepthCullingState, _StencilState, _AlphaState } from "States";
  10. import { Engine } from "Engines/engine";
  11. import { Gamepad } from "Gamepads/gamepad";
  12. import { ExtendedGamepadButton } from "./poseEnabledController";
  13. import { WebVRFreeCamera, PoseControlled, DevicePose } from "Cameras/VR/webVRCamera";
  14. import { TargetCamera } from "Cameras/targetCamera";
  15. /**
  16. * Defines the types of pose enabled controllers that are supported
  17. */
  18. export enum PoseEnabledControllerType {
  19. /**
  20. * HTC Vive
  21. */
  22. VIVE,
  23. /**
  24. * Oculus Rift
  25. */
  26. OCULUS,
  27. /**
  28. * Windows mixed reality
  29. */
  30. WINDOWS,
  31. /**
  32. * Samsung gear VR
  33. */
  34. GEAR_VR,
  35. /**
  36. * Google Daydream
  37. */
  38. DAYDREAM,
  39. /**
  40. * Generic
  41. */
  42. GENERIC
  43. }
  44. /**
  45. * Defines the MutableGamepadButton interface for the state of a gamepad button
  46. */
  47. export interface MutableGamepadButton {
  48. /**
  49. * Value of the button/trigger
  50. */
  51. value: number;
  52. /**
  53. * If the button/trigger is currently touched
  54. */
  55. touched: boolean;
  56. /**
  57. * If the button/trigger is currently pressed
  58. */
  59. pressed: boolean;
  60. }
  61. /**
  62. * Defines the ExtendedGamepadButton interface for a gamepad button which includes state provided by a pose controller
  63. * @hidden
  64. */
  65. export interface ExtendedGamepadButton extends GamepadButton {
  66. /**
  67. * If the button/trigger is currently pressed
  68. */
  69. readonly pressed: boolean;
  70. /**
  71. * If the button/trigger is currently touched
  72. */
  73. readonly touched: boolean;
  74. /**
  75. * Value of the button/trigger
  76. */
  77. readonly value: number;
  78. }
  79. /** @hidden */
  80. export interface _GamePadFactory {
  81. /**
  82. * Returns wether or not the current gamepad can be created for this type of controller.
  83. * @param gamepadInfo Defines the gamepad info as receveid from the controller APIs.
  84. * @returns true if it can be created, otherwise false
  85. */
  86. canCreate(gamepadInfo: any): boolean;
  87. /**
  88. * Creates a new instance of the Gamepad.
  89. * @param gamepadInfo Defines the gamepad info as receveid from the controller APIs.
  90. * @returns the new gamepad instance
  91. */
  92. create(gamepadInfo: any): Gamepad;
  93. }
  94. /**
  95. * Defines the PoseEnabledControllerHelper object that is used initialize a gamepad as the controller type it is specified as (eg. windows mixed reality controller)
  96. */
  97. export class PoseEnabledControllerHelper {
  98. /** @hidden */
  99. public static _ControllerFactories: _GamePadFactory[] = [];
  100. /** @hidden */
  101. public static _DefaultControllerFactory: Nullable<(gamepadInfo: any) => Gamepad> = null;
  102. /**
  103. * Initializes a gamepad as the controller type it is specified as (eg. windows mixed reality controller)
  104. * @param vrGamepad the gamepad to initialized
  105. * @returns a vr controller of the type the gamepad identified as
  106. */
  107. public static InitiateController(vrGamepad: any) {
  108. for (let factory of this._ControllerFactories) {
  109. if (factory.canCreate(vrGamepad)) {
  110. return factory.create(vrGamepad);
  111. }
  112. }
  113. if (this._DefaultControllerFactory) {
  114. return this._DefaultControllerFactory(vrGamepad);
  115. }
  116. throw "The type of gamepad you are trying to load needs to be imported first or is not supported.";
  117. }
  118. }
  119. /**
  120. * Defines the PoseEnabledController object that contains state of a vr capable controller
  121. */
  122. export class PoseEnabledController extends Gamepad implements PoseControlled {
  123. // Represents device position and rotation in room space. Should only be used to help calculate babylon space values
  124. private _deviceRoomPosition = Vector3.Zero();
  125. private _deviceRoomRotationQuaternion = new Quaternion();
  126. /**
  127. * The device position in babylon space
  128. */
  129. public devicePosition = Vector3.Zero();
  130. /**
  131. * The device rotation in babylon space
  132. */
  133. public deviceRotationQuaternion = new Quaternion();
  134. /**
  135. * The scale factor of the device in babylon space
  136. */
  137. public deviceScaleFactor: number = 1;
  138. /**
  139. * (Likely devicePosition should be used instead) The device position in its room space
  140. */
  141. public position: Vector3;
  142. /**
  143. * (Likely deviceRotationQuaternion should be used instead) The device rotation in its room space
  144. */
  145. public rotationQuaternion: Quaternion;
  146. /**
  147. * The type of controller (Eg. Windows mixed reality)
  148. */
  149. public controllerType: PoseEnabledControllerType;
  150. protected _calculatedPosition: Vector3;
  151. private _calculatedRotation: Quaternion;
  152. /**
  153. * The raw pose from the device
  154. */
  155. public rawPose: DevicePose; //GamepadPose;
  156. // Used to convert 6dof controllers to 3dof
  157. private _trackPosition = true;
  158. private _maxRotationDistFromHeadset = Math.PI / 5;
  159. private _draggedRoomRotation = 0;
  160. /**
  161. * @hidden
  162. */
  163. public _disableTrackPosition(fixedPosition: Vector3) {
  164. if (this._trackPosition) {
  165. this._calculatedPosition.copyFrom(fixedPosition);
  166. this._trackPosition = false;
  167. }
  168. }
  169. /**
  170. * Internal, the mesh attached to the controller
  171. * @hidden
  172. */
  173. public _mesh: Nullable<AbstractMesh>; // a node that will be attached to this Gamepad
  174. private _poseControlledCamera: TargetCamera;
  175. private _leftHandSystemQuaternion: Quaternion = new Quaternion();
  176. /**
  177. * Internal, matrix used to convert room space to babylon space
  178. * @hidden
  179. */
  180. public _deviceToWorld = Matrix.Identity();
  181. /**
  182. * Node to be used when casting a ray from the controller
  183. * @hidden
  184. */
  185. public _pointingPoseNode: Nullable<TransformNode> = null;
  186. /**
  187. * Name of the child mesh that can be used to cast a ray from the controller
  188. */
  189. public static readonly POINTING_POSE = "POINTING_POSE";
  190. /**
  191. * Creates a new PoseEnabledController from a gamepad
  192. * @param browserGamepad the gamepad that the PoseEnabledController should be created from
  193. */
  194. constructor(browserGamepad: any) {
  195. super(browserGamepad.id, browserGamepad.index, browserGamepad);
  196. this.type = Gamepad.POSE_ENABLED;
  197. this.controllerType = PoseEnabledControllerType.GENERIC;
  198. this.position = Vector3.Zero();
  199. this.rotationQuaternion = new Quaternion();
  200. this._calculatedPosition = Vector3.Zero();
  201. this._calculatedRotation = new Quaternion();
  202. Quaternion.RotationYawPitchRollToRef(Math.PI, 0, 0, this._leftHandSystemQuaternion);
  203. }
  204. private _workingMatrix = Matrix.Identity();
  205. /**
  206. * Updates the state of the pose enbaled controller and mesh based on the current position and rotation of the controller
  207. */
  208. public update() {
  209. super.update();
  210. this._updatePoseAndMesh();
  211. }
  212. /**
  213. * Updates only the pose device and mesh without doing any button event checking
  214. */
  215. protected _updatePoseAndMesh() {
  216. var pose: GamepadPose = this.browserGamepad.pose;
  217. this.updateFromDevice(pose);
  218. if (!this._trackPosition && Engine.LastCreatedScene && Engine.LastCreatedScene.activeCamera && (<WebVRFreeCamera>Engine.LastCreatedScene.activeCamera).devicePosition) {
  219. var camera = <WebVRFreeCamera>Engine.LastCreatedScene.activeCamera;
  220. camera._computeDevicePosition();
  221. this._deviceToWorld.setTranslation(camera.devicePosition);
  222. if (camera.deviceRotationQuaternion) {
  223. var camera = camera;
  224. camera._deviceRoomRotationQuaternion.toEulerAnglesToRef(Tmp.Vector3[0]);
  225. // Find the radian distance away that the headset is from the controllers rotation
  226. var distanceAway = Math.atan2(Math.sin(Tmp.Vector3[0].y - this._draggedRoomRotation), Math.cos(Tmp.Vector3[0].y - this._draggedRoomRotation));
  227. if (Math.abs(distanceAway) > this._maxRotationDistFromHeadset) {
  228. // Only rotate enouph to be within the _maxRotationDistFromHeadset
  229. var rotationAmount = distanceAway - (distanceAway < 0 ? -this._maxRotationDistFromHeadset : this._maxRotationDistFromHeadset);
  230. this._draggedRoomRotation += rotationAmount;
  231. // Rotate controller around headset
  232. var sin = Math.sin(-rotationAmount);
  233. var cos = Math.cos(-rotationAmount);
  234. this._calculatedPosition.x = this._calculatedPosition.x * cos - this._calculatedPosition.z * sin;
  235. this._calculatedPosition.z = this._calculatedPosition.x * sin + this._calculatedPosition.z * cos;
  236. }
  237. }
  238. }
  239. Vector3.TransformCoordinatesToRef(this._calculatedPosition, this._deviceToWorld, this.devicePosition);
  240. this._deviceToWorld.getRotationMatrixToRef(this._workingMatrix);
  241. Quaternion.FromRotationMatrixToRef(this._workingMatrix, this.deviceRotationQuaternion);
  242. this.deviceRotationQuaternion.multiplyInPlace(this._calculatedRotation);
  243. if (this._mesh) {
  244. this._mesh.position.copyFrom(this.devicePosition);
  245. if (this._mesh.rotationQuaternion) {
  246. this._mesh.rotationQuaternion.copyFrom(this.deviceRotationQuaternion);
  247. }
  248. }
  249. }
  250. /**
  251. * Updates the state of the pose enbaled controller based on the raw pose data from the device
  252. * @param poseData raw pose fromthe device
  253. */
  254. updateFromDevice(poseData: DevicePose) {
  255. if (poseData) {
  256. this.rawPose = poseData;
  257. if (poseData.position) {
  258. this._deviceRoomPosition.copyFromFloats(poseData.position[0], poseData.position[1], -poseData.position[2]);
  259. if (this._mesh && this._mesh.getScene().useRightHandedSystem) {
  260. this._deviceRoomPosition.z *= -1;
  261. }
  262. if (this._trackPosition) {
  263. this._deviceRoomPosition.scaleToRef(this.deviceScaleFactor, this._calculatedPosition);
  264. }
  265. this._calculatedPosition.addInPlace(this.position);
  266. }
  267. let pose = this.rawPose;
  268. if (poseData.orientation && pose.orientation) {
  269. this._deviceRoomRotationQuaternion.copyFromFloats(pose.orientation[0], pose.orientation[1], -pose.orientation[2], -pose.orientation[3]);
  270. if (this._mesh) {
  271. if (this._mesh.getScene().useRightHandedSystem) {
  272. this._deviceRoomRotationQuaternion.z *= -1;
  273. this._deviceRoomRotationQuaternion.w *= -1;
  274. } else {
  275. this._deviceRoomRotationQuaternion.multiplyToRef(this._leftHandSystemQuaternion, this._deviceRoomRotationQuaternion);
  276. }
  277. }
  278. // if the camera is set, rotate to the camera's rotation
  279. this._deviceRoomRotationQuaternion.multiplyToRef(this.rotationQuaternion, this._calculatedRotation);
  280. }
  281. }
  282. }
  283. /**
  284. * @hidden
  285. */
  286. public _meshAttachedObservable = new Observable<AbstractMesh>();
  287. /**
  288. * Attaches a mesh to the controller
  289. * @param mesh the mesh to be attached
  290. */
  291. public attachToMesh(mesh: AbstractMesh) {
  292. if (this._mesh) {
  293. this._mesh.parent = null;
  294. }
  295. this._mesh = mesh;
  296. if (this._poseControlledCamera) {
  297. this._mesh.parent = this._poseControlledCamera;
  298. }
  299. if (!this._mesh.rotationQuaternion) {
  300. this._mesh.rotationQuaternion = new Quaternion();
  301. }
  302. // Sync controller mesh and pointing pose node's state with controller, this is done to avoid a frame where position is 0,0,0 when attaching mesh
  303. this._updatePoseAndMesh();
  304. if (this._pointingPoseNode) {
  305. var parents = [];
  306. var obj: Node = this._pointingPoseNode;
  307. while (obj.parent) {
  308. parents.push(obj.parent);
  309. obj = obj.parent;
  310. }
  311. parents.reverse().forEach((p) => { p.computeWorldMatrix(true); });
  312. }
  313. this._meshAttachedObservable.notifyObservers(mesh);
  314. }
  315. /**
  316. * Attaches the controllers mesh to a camera
  317. * @param camera the camera the mesh should be attached to
  318. */
  319. public attachToPoseControlledCamera(camera: TargetCamera) {
  320. this._poseControlledCamera = camera;
  321. if (this._mesh) {
  322. this._mesh.parent = this._poseControlledCamera;
  323. }
  324. }
  325. /**
  326. * Disposes of the controller
  327. */
  328. public dispose() {
  329. if (this._mesh) {
  330. this._mesh.dispose();
  331. }
  332. this._mesh = null;
  333. super.dispose();
  334. }
  335. /**
  336. * The mesh that is attached to the controller
  337. */
  338. public get mesh(): Nullable<AbstractMesh> {
  339. return this._mesh;
  340. }
  341. /**
  342. * Gets the ray of the controller in the direction the controller is pointing
  343. * @param length the length the resulting ray should be
  344. * @returns a ray in the direction the controller is pointing
  345. */
  346. public getForwardRay(length = 100): Ray {
  347. if (!this.mesh) {
  348. return new Ray(Vector3.Zero(), new Vector3(0, 0, 1), length);
  349. }
  350. var m = this._pointingPoseNode ? this._pointingPoseNode.getWorldMatrix() : this.mesh.getWorldMatrix();
  351. var origin = m.getTranslation();
  352. var forward = new Vector3(0, 0, -1);
  353. var forwardWorld = Vector3.TransformNormal(forward, m);
  354. var direction = Vector3.Normalize(forwardWorld);
  355. return new Ray(origin, direction, length);
  356. }
  357. }