attachToBoxBehavior.ts 7.9 KB

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