attachToBoxBehavior.ts 8.1 KB

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