attachToBoxBehavior.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { Vector3, Matrix, Quaternion } from "Math";
  2. import { Mesh } from "Mesh";
  3. import { Scene } from "scene";
  4. import { Nullable } from "types";
  5. import { Observer } from "Tools";
  6. import { WebVRFreeCamera } from "Cameras";
  7. /**
  8. * @hidden
  9. */
  10. class FaceDirectionInfo {
  11. constructor(public direction: Vector3, public rotatedDirection = new Vector3(), public diff = 0, public ignore = false) {}
  12. }
  13. /**
  14. * A behavior that when attached to a mesh will will place a specified node on the meshes face pointing towards the camera
  15. */
  16. export class AttachToBoxBehavior implements BABYLON.Behavior<Mesh> {
  17. /**
  18. * The name of the behavior
  19. */
  20. public name = "AttachToBoxBehavior";
  21. /**
  22. * The distance away from the face of the mesh that the UI should be attached to (default: 0.15)
  23. */
  24. public distanceAwayFromFace = 0.15;
  25. /**
  26. * The distance from the bottom of the face that the UI should be attached to (default: 0.15)
  27. */
  28. public distanceAwayFromBottomOfFace = 0.15;
  29. private _faceVectors = [new FaceDirectionInfo(Vector3.Up()), new FaceDirectionInfo(Vector3.Down()), new FaceDirectionInfo(Vector3.Left()), new FaceDirectionInfo(Vector3.Right()), new FaceDirectionInfo(Vector3.Forward()), new FaceDirectionInfo(Vector3.Forward().scaleInPlace(-1))];
  30. private _target: Mesh;
  31. private _scene: Scene;
  32. private _onRenderObserver: Nullable<Observer<Scene>>;
  33. private _tmpMatrix = new Matrix();
  34. private _tmpVector = new Vector3();
  35. /**
  36. * Creates the AttachToBoxBehavior, used to attach UI to the closest face of the box to a camera
  37. * @param ui The transform node that should be attched to the mesh
  38. */
  39. constructor(private ui: BABYLON.TransformNode) {
  40. /* Does nothing */
  41. }
  42. /**
  43. * Initializes the behavior
  44. */
  45. public init() {
  46. /* Does nothing */
  47. }
  48. private _closestFace(targetDirection: Vector3) {
  49. // Go over each face and calculate the angle between the face's normal and targetDirection
  50. this._faceVectors.forEach((v) => {
  51. if (!this._target.rotationQuaternion) {
  52. this._target.rotationQuaternion = Quaternion.RotationYawPitchRoll(this._target.rotation.y, this._target.rotation.x, this._target.rotation.z);
  53. }
  54. this._target.rotationQuaternion.toRotationMatrix(this._tmpMatrix);
  55. Vector3.TransformCoordinatesToRef(v.direction, this._tmpMatrix, v.rotatedDirection);
  56. v.diff = Vector3.GetAngleBetweenVectors(v.rotatedDirection, targetDirection, Vector3.Cross(v.rotatedDirection, targetDirection));
  57. });
  58. // Return the face information of the one with the normal closeset to target direction
  59. return this._faceVectors.reduce((min, p) => {
  60. if (min.ignore) {
  61. return p;
  62. }else if (p.ignore) {
  63. return min;
  64. }else {
  65. return min.diff < p.diff ? min : p;
  66. }
  67. }, this._faceVectors[0]);
  68. }
  69. private _zeroVector = Vector3.Zero();
  70. private _lookAtTmpMatrix = new Matrix();
  71. private _lookAtToRef(pos: Vector3, up = new Vector3(0, 1, 0), ref: Quaternion) {
  72. BABYLON.Matrix.LookAtLHToRef(this._zeroVector, pos, up, this._lookAtTmpMatrix);
  73. this._lookAtTmpMatrix.invert();
  74. BABYLON.Quaternion.FromRotationMatrixToRef(this._lookAtTmpMatrix, ref);
  75. }
  76. /**
  77. * Attaches the AttachToBoxBehavior to the passed in mesh
  78. * @param target The mesh that the specified node will be attached to
  79. */
  80. attach(target: Mesh) {
  81. this._target = target;
  82. this._scene = this._target.getScene();
  83. // Every frame, update the app bars position
  84. this._onRenderObserver = this._scene.onBeforeRenderObservable.add(() => {
  85. if (!this._scene.activeCamera) {
  86. return;
  87. }
  88. // Find the face closest to the cameras position
  89. var cameraPos = this._scene.activeCamera.position;
  90. if ((<WebVRFreeCamera>this._scene.activeCamera).devicePosition) {
  91. cameraPos = (<WebVRFreeCamera>this._scene.activeCamera).devicePosition;
  92. }
  93. var facing = this._closestFace(cameraPos.subtract(target.position));
  94. if (this._scene.activeCamera.leftCamera) {
  95. this._scene.activeCamera.leftCamera.computeWorldMatrix().getRotationMatrixToRef(this._tmpMatrix);
  96. }else {
  97. this._scene.activeCamera.computeWorldMatrix().getRotationMatrixToRef(this._tmpMatrix);
  98. }
  99. // Get camera up direction
  100. Vector3.TransformCoordinatesToRef(Vector3.Up(), this._tmpMatrix, this._tmpVector);
  101. // Ignore faces to not select a parrelel face for the up vector of the UI
  102. this._faceVectors.forEach((v) => {
  103. if (facing.direction.x && v.direction.x) {
  104. v.ignore = true;
  105. }
  106. if (facing.direction.y && v.direction.y) {
  107. v.ignore = true;
  108. }
  109. if (facing.direction.z && v.direction.z) {
  110. v.ignore = true;
  111. }
  112. });
  113. var facingUp = this._closestFace(this._tmpVector);
  114. // Unignore faces
  115. this._faceVectors.forEach((v) => {
  116. v.ignore = false;
  117. });
  118. // Position the app bar on that face
  119. this.ui.position.copyFrom(target.position);
  120. if (facing.direction.x) {
  121. facing.rotatedDirection.scaleToRef((target.scaling.x / 2) + this.distanceAwayFromFace, this._tmpVector);
  122. this.ui.position.addInPlace(this._tmpVector);
  123. }
  124. if (facing.direction.y) {
  125. facing.rotatedDirection.scaleToRef((target.scaling.y / 2) + this.distanceAwayFromFace, this._tmpVector);
  126. this.ui.position.addInPlace(this._tmpVector);
  127. }
  128. if (facing.direction.z) {
  129. facing.rotatedDirection.scaleToRef((target.scaling.z / 2) + this.distanceAwayFromFace, this._tmpVector);
  130. this.ui.position.addInPlace(this._tmpVector);
  131. }
  132. // Rotate to be oriented properly to the camera
  133. if (!this.ui.rotationQuaternion) {
  134. this.ui.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.ui.rotation.y, this.ui.rotation.x, this.ui.rotation.z);
  135. }
  136. facing.rotatedDirection.scaleToRef(-1, this._tmpVector);
  137. this._lookAtToRef(this._tmpVector, facingUp.rotatedDirection, this.ui.rotationQuaternion);
  138. // Place ui the correct distance from the bottom of the mesh
  139. if (facingUp.direction.x) {
  140. this.ui.up.scaleToRef(this.distanceAwayFromBottomOfFace - target.scaling.x / 2, this._tmpVector);
  141. }
  142. if (facingUp.direction.y) {
  143. this.ui.up.scaleToRef(this.distanceAwayFromBottomOfFace - target.scaling.y / 2, this._tmpVector);
  144. }
  145. if (facingUp.direction.z) {
  146. this.ui.up.scaleToRef(this.distanceAwayFromBottomOfFace - target.scaling.z / 2, this._tmpVector);
  147. }
  148. this.ui.position.addInPlace(this._tmpVector);
  149. });
  150. }
  151. /**
  152. * Detaches the behavior from the mesh
  153. */
  154. detach() {
  155. this._scene.onBeforeRenderObservable.remove(this._onRenderObserver);
  156. }
  157. }