|
@@ -0,0 +1,166 @@
|
|
|
+module BABYLON {
|
|
|
+ /**
|
|
|
+ * @hidden
|
|
|
+ */
|
|
|
+ class FaceDirectionInfo {
|
|
|
+ constructor(public direction:Vector3, public rotatedDirection = new BABYLON.Vector3(), public diff = 0, public ignore = false){}
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * A behavior that when attached to a mesh will will place a specified node on the meshes face pointing towards the camera
|
|
|
+ */
|
|
|
+ export class AttachToBoxBehavior implements BABYLON.Behavior<BABYLON.Mesh> {
|
|
|
+ /**
|
|
|
+ * The name of the behavior
|
|
|
+ */
|
|
|
+ public name = "AttachToBoxBehavior";
|
|
|
+ /**
|
|
|
+ * The distance away from the face of the mesh that the UI should be attached to (default: 0.15)
|
|
|
+ */
|
|
|
+ public distanceAwayFromFace = 0.15;
|
|
|
+ /**
|
|
|
+ * The distance from the bottom of the face that the UI should be attached to (default: 0.15)
|
|
|
+ */
|
|
|
+ public distanceAwayFromBottomOfFace = 0.15;
|
|
|
+ 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))];
|
|
|
+ private _target:Mesh;
|
|
|
+ private _scene:Scene;
|
|
|
+ private _onRenderObserver:Nullable<Observer<Scene>>;
|
|
|
+ private _tmpMatrix = new Matrix();
|
|
|
+ private _tmpVector = new Vector3();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates the AttachToBoxBehavior, used to attach UI to the closest face of the box to a camera
|
|
|
+ * @param ui The transform node that should be attched to the mesh
|
|
|
+ */
|
|
|
+ constructor(private ui: BABYLON.TransformNode){
|
|
|
+ /* Does nothing */
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Initializes the behavior
|
|
|
+ */
|
|
|
+ public init(){
|
|
|
+ /* Does nothing */
|
|
|
+ }
|
|
|
+
|
|
|
+ private _closestFace(targetDirection:Vector3){
|
|
|
+ // Go over each face and calculate the angle between the face's normal and targetDirection
|
|
|
+ this._faceVectors.forEach((v)=>{
|
|
|
+ if(!this._target.rotationQuaternion){
|
|
|
+ this._target.rotationQuaternion = Quaternion.RotationYawPitchRoll(this._target.rotation.y, this._target.rotation.x, this._target.rotation.z);
|
|
|
+ }
|
|
|
+ this._target.rotationQuaternion.toRotationMatrix(this._tmpMatrix)
|
|
|
+ BABYLON.Vector3.TransformCoordinatesToRef(v.direction, this._tmpMatrix, v.rotatedDirection);
|
|
|
+ v.diff = BABYLON.Vector3.GetAngleBetweenVectors(v.rotatedDirection, targetDirection, BABYLON.Vector3.Cross(v.rotatedDirection, targetDirection));
|
|
|
+ });
|
|
|
+ // Return the face information of the one with the normal closeset to target direction
|
|
|
+ return this._faceVectors.reduce((min,p)=>{
|
|
|
+ if(min.ignore){
|
|
|
+ return p;
|
|
|
+ }else if(p.ignore){
|
|
|
+ return min;
|
|
|
+ }else{
|
|
|
+ return min.diff < p.diff ? min : p
|
|
|
+ }
|
|
|
+ }, this._faceVectors[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _zeroVector = Vector3.Zero();
|
|
|
+ private _lookAtTmpMatrix = new Matrix();
|
|
|
+ private _lookAtToRef(pos:Vector3, up = new BABYLON.Vector3(0,1,0), ref:Quaternion){
|
|
|
+ BABYLON.Matrix.LookAtLHToRef(this._zeroVector, pos, up, this._lookAtTmpMatrix);
|
|
|
+ this._lookAtTmpMatrix.invert();
|
|
|
+ BABYLON.Quaternion.FromRotationMatrixToRef(this._lookAtTmpMatrix, ref);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Attaches the AttachToBoxBehavior to the passed in mesh
|
|
|
+ * @param target The mesh that the specified node will be attached to
|
|
|
+ */
|
|
|
+ attach(target: BABYLON.Mesh){
|
|
|
+ this._target = target;
|
|
|
+ this._scene = this._target.getScene();
|
|
|
+
|
|
|
+ // Every frame, update the app bars position
|
|
|
+ this._onRenderObserver = this._scene.onBeforeRenderObservable.add(()=>{
|
|
|
+ if(!this._scene.activeCamera){
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Find the face closest to the cameras position
|
|
|
+ var cameraPos = this._scene.activeCamera.position;
|
|
|
+ if((<WebVRFreeCamera>this._scene.activeCamera).devicePosition){
|
|
|
+ cameraPos = (<WebVRFreeCamera>this._scene.activeCamera).devicePosition;
|
|
|
+ }
|
|
|
+ var facing = this._closestFace(cameraPos.subtract(target.position));
|
|
|
+ if(this._scene.activeCamera.leftCamera){
|
|
|
+ this._scene.activeCamera.leftCamera.computeWorldMatrix().getRotationMatrixToRef(this._tmpMatrix);
|
|
|
+ }else{
|
|
|
+ this._scene.activeCamera.computeWorldMatrix().getRotationMatrixToRef(this._tmpMatrix);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get camera up direction
|
|
|
+ BABYLON.Vector3.TransformCoordinatesToRef(BABYLON.Vector3.Up(), this._tmpMatrix, this._tmpVector);
|
|
|
+ // Ignore faces to not select a parrelel face for the up vector of the UI
|
|
|
+ this._faceVectors.forEach((v)=>{
|
|
|
+ if(facing.direction.x && v.direction.x){
|
|
|
+ v.ignore = true;
|
|
|
+ }
|
|
|
+ if(facing.direction.y && v.direction.y){
|
|
|
+ v.ignore = true;
|
|
|
+ }
|
|
|
+ if(facing.direction.z && v.direction.z){
|
|
|
+ v.ignore = true;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ var facingUp = this._closestFace(this._tmpVector);
|
|
|
+ // Unignore faces
|
|
|
+ this._faceVectors.forEach((v)=>{
|
|
|
+ v.ignore = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Position the app bar on that face
|
|
|
+ this.ui.position.copyFrom(target.position);
|
|
|
+ if(facing.direction.x){
|
|
|
+ facing.rotatedDirection.scaleToRef((target.scaling.x/2)+this.distanceAwayFromFace, this._tmpVector);
|
|
|
+ this.ui.position.addInPlace(this._tmpVector);
|
|
|
+ }
|
|
|
+ if(facing.direction.y){
|
|
|
+ facing.rotatedDirection.scaleToRef((target.scaling.y/2)+this.distanceAwayFromFace, this._tmpVector);
|
|
|
+ this.ui.position.addInPlace(this._tmpVector);
|
|
|
+ }
|
|
|
+ if(facing.direction.z){
|
|
|
+ facing.rotatedDirection.scaleToRef((target.scaling.z/2)+this.distanceAwayFromFace, this._tmpVector);
|
|
|
+ this.ui.position.addInPlace(this._tmpVector);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Rotate to be oriented properly to the camera
|
|
|
+ if(!this.ui.rotationQuaternion){
|
|
|
+ this.ui.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.ui.rotation.y, this.ui.rotation.x, this.ui.rotation.z);
|
|
|
+ }
|
|
|
+ facing.rotatedDirection.scaleToRef(-1, this._tmpVector);
|
|
|
+ this._lookAtToRef(this._tmpVector, facingUp.rotatedDirection, this.ui.rotationQuaternion);
|
|
|
+
|
|
|
+ // Place ui the correct distance from the bottom of the mesh
|
|
|
+ if(facingUp.direction.x){
|
|
|
+ this.ui.up.scaleToRef(this.distanceAwayFromBottomOfFace-target.scaling.x/2, this._tmpVector);
|
|
|
+ }
|
|
|
+ if(facingUp.direction.y){
|
|
|
+ this.ui.up.scaleToRef(this.distanceAwayFromBottomOfFace-target.scaling.y/2, this._tmpVector);
|
|
|
+ }
|
|
|
+ if(facingUp.direction.z){
|
|
|
+ this.ui.up.scaleToRef(this.distanceAwayFromBottomOfFace-target.scaling.z/2, this._tmpVector);
|
|
|
+ }
|
|
|
+ this.ui.position.addInPlace(this._tmpVector);
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Detaches the behavior from the mesh
|
|
|
+ */
|
|
|
+ detach(){
|
|
|
+ this._scene.onBeforeRenderObservable.remove(this._onRenderObserver);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|