Home | History | Annotate | Download | only in control
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.scene.control;
     34 
     35 import com.jme3.export.InputCapsule;
     36 import com.jme3.export.JmeExporter;
     37 import com.jme3.export.JmeImporter;
     38 import com.jme3.export.OutputCapsule;
     39 import com.jme3.math.FastMath;
     40 import com.jme3.math.Matrix3f;
     41 import com.jme3.math.Quaternion;
     42 import com.jme3.math.Vector3f;
     43 import com.jme3.renderer.Camera;
     44 import com.jme3.renderer.RenderManager;
     45 import com.jme3.renderer.ViewPort;
     46 import com.jme3.scene.Node;
     47 import com.jme3.scene.Spatial;
     48 import java.io.IOException;
     49 
     50 public class BillboardControl extends AbstractControl {
     51 
     52     private Matrix3f orient;
     53     private Vector3f look;
     54     private Vector3f left;
     55     private Alignment alignment;
     56 
     57     /**
     58      * Determines how the billboard is aligned to the screen/camera.
     59      */
     60     public enum Alignment {
     61         /**
     62          * Aligns this Billboard to the screen.
     63          */
     64         Screen,
     65 
     66         /**
     67          * Aligns this Billboard to the camera position.
     68          */
     69         Camera,
     70 
     71         /**
     72           * Aligns this Billboard to the screen, but keeps the Y axis fixed.
     73           */
     74         AxialY,
     75 
     76         /**
     77          * Aligns this Billboard to the screen, but keeps the Z axis fixed.
     78          */
     79         AxialZ;
     80     }
     81 
     82     public BillboardControl() {
     83         super();
     84         orient = new Matrix3f();
     85         look = new Vector3f();
     86         left = new Vector3f();
     87         alignment = Alignment.Screen;
     88     }
     89 
     90     public Control cloneForSpatial(Spatial spatial) {
     91         BillboardControl control = new BillboardControl();
     92         control.alignment = this.alignment;
     93         control.setSpatial(spatial);
     94         return control;
     95     }
     96 
     97     @Override
     98     protected void controlUpdate(float tpf) {
     99     }
    100 
    101     @Override
    102     protected void controlRender(RenderManager rm, ViewPort vp) {
    103         Camera cam = vp.getCamera();
    104         rotateBillboard(cam);
    105     }
    106 
    107     private void fixRefreshFlags(){
    108         // force transforms to update below this node
    109         spatial.updateGeometricState();
    110 
    111         // force world bound to update
    112         Spatial rootNode = spatial;
    113         while (rootNode.getParent() != null){
    114             rootNode = rootNode.getParent();
    115         }
    116         rootNode.getWorldBound();
    117     }
    118 
    119     /**
    120      * rotate the billboard based on the type set
    121      *
    122      * @param cam
    123      *            Camera
    124      */
    125     private void rotateBillboard(Camera cam) {
    126         switch (alignment) {
    127             case AxialY:
    128                 rotateAxial(cam, Vector3f.UNIT_Y);
    129                 break;
    130             case AxialZ:
    131                 rotateAxial(cam, Vector3f.UNIT_Z);
    132                 break;
    133             case Screen:
    134                 rotateScreenAligned(cam);
    135                 break;
    136             case Camera:
    137                 rotateCameraAligned(cam);
    138                 break;
    139         }
    140     }
    141 
    142     /**
    143      * Aligns this Billboard so that it points to the camera position.
    144      *
    145      * @param camera
    146      *            Camera
    147      */
    148     private void rotateCameraAligned(Camera camera) {
    149         look.set(camera.getLocation()).subtractLocal(
    150                 spatial.getWorldTranslation());
    151         // coopt left for our own purposes.
    152         Vector3f xzp = left;
    153         // The xzp vector is the projection of the look vector on the xz plane
    154         xzp.set(look.x, 0, look.z);
    155 
    156         // check for undefined rotation...
    157         if (xzp.equals(Vector3f.ZERO)) {
    158             return;
    159         }
    160 
    161         look.normalizeLocal();
    162         xzp.normalizeLocal();
    163         float cosp = look.dot(xzp);
    164 
    165         // compute the local orientation matrix for the billboard
    166         orient.set(0, 0, xzp.z);
    167         orient.set(0, 1, xzp.x * -look.y);
    168         orient.set(0, 2, xzp.x * cosp);
    169         orient.set(1, 0, 0);
    170         orient.set(1, 1, cosp);
    171         orient.set(1, 2, look.y);
    172         orient.set(2, 0, -xzp.x);
    173         orient.set(2, 1, xzp.z * -look.y);
    174         orient.set(2, 2, xzp.z * cosp);
    175 
    176         // The billboard must be oriented to face the camera before it is
    177         // transformed into the world.
    178         spatial.setLocalRotation(orient);
    179         fixRefreshFlags();
    180     }
    181 
    182     /**
    183      * Rotate the billboard so it points directly opposite the direction the
    184      * camera's facing
    185      *
    186      * @param camera
    187      *            Camera
    188      */
    189     private void rotateScreenAligned(Camera camera) {
    190         // coopt diff for our in direction:
    191         look.set(camera.getDirection()).negateLocal();
    192         // coopt loc for our left direction:
    193         left.set(camera.getLeft()).negateLocal();
    194         orient.fromAxes(left, camera.getUp(), look);
    195         Node parent = spatial.getParent();
    196         Quaternion rot=new Quaternion().fromRotationMatrix(orient);
    197         if ( parent != null ) {
    198             rot =  parent.getWorldRotation().inverse().multLocal(rot);
    199             rot.normalizeLocal();
    200         }
    201         spatial.setLocalRotation(rot);
    202         fixRefreshFlags();
    203     }
    204 
    205     /**
    206      * Rotate the billboard towards the camera, but keeping a given axis fixed.
    207      *
    208      * @param camera
    209      *            Camera
    210      */
    211     private void rotateAxial(Camera camera, Vector3f axis) {
    212         // Compute the additional rotation required for the billboard to face
    213         // the camera. To do this, the camera must be inverse-transformed into
    214         // the model space of the billboard.
    215         look.set(camera.getLocation()).subtractLocal(
    216                 spatial.getWorldTranslation());
    217         spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own
    218         // purposes.
    219         left.x *= 1.0f / spatial.getWorldScale().x;
    220         left.y *= 1.0f / spatial.getWorldScale().y;
    221         left.z *= 1.0f / spatial.getWorldScale().z;
    222 
    223         // squared length of the camera projection in the xz-plane
    224         float lengthSquared = left.x * left.x + left.z * left.z;
    225         if (lengthSquared < FastMath.FLT_EPSILON) {
    226             // camera on the billboard axis, rotation not defined
    227             return;
    228         }
    229 
    230         // unitize the projection
    231         float invLength = FastMath.invSqrt(lengthSquared);
    232         if (axis.y == 1) {
    233             left.x *= invLength;
    234             left.y = 0.0f;
    235             left.z *= invLength;
    236 
    237             // compute the local orientation matrix for the billboard
    238             orient.set(0, 0, left.z);
    239             orient.set(0, 1, 0);
    240             orient.set(0, 2, left.x);
    241             orient.set(1, 0, 0);
    242             orient.set(1, 1, 1);
    243             orient.set(1, 2, 0);
    244             orient.set(2, 0, -left.x);
    245             orient.set(2, 1, 0);
    246             orient.set(2, 2, left.z);
    247         } else if (axis.z == 1) {
    248             left.x *= invLength;
    249             left.y *= invLength;
    250             left.z = 0.0f;
    251 
    252             // compute the local orientation matrix for the billboard
    253             orient.set(0, 0, left.y);
    254             orient.set(0, 1, left.x);
    255             orient.set(0, 2, 0);
    256             orient.set(1, 0, -left.y);
    257             orient.set(1, 1, left.x);
    258             orient.set(1, 2, 0);
    259             orient.set(2, 0, 0);
    260             orient.set(2, 1, 0);
    261             orient.set(2, 2, 1);
    262         }
    263 
    264         // The billboard must be oriented to face the camera before it is
    265         // transformed into the world.
    266         spatial.setLocalRotation(orient);
    267         fixRefreshFlags();
    268     }
    269 
    270     /**
    271      * Returns the alignment this Billboard is set too.
    272      *
    273      * @return The alignment of rotation, AxialY, AxialZ, Camera or Screen.
    274      */
    275     public Alignment getAlignment() {
    276         return alignment;
    277     }
    278 
    279     /**
    280      * Sets the type of rotation this Billboard will have. The alignment can
    281      * be Camera, Screen, AxialY, or AxialZ. Invalid alignments will
    282      * assume no billboard rotation.
    283      */
    284     public void setAlignment(Alignment alignment) {
    285         this.alignment = alignment;
    286     }
    287 
    288     @Override
    289     public void write(JmeExporter e) throws IOException {
    290         super.write(e);
    291         OutputCapsule capsule = e.getCapsule(this);
    292         capsule.write(orient, "orient", null);
    293         capsule.write(look, "look", null);
    294         capsule.write(left, "left", null);
    295         capsule.write(alignment, "alignment", Alignment.Screen);
    296     }
    297 
    298     @Override
    299     public void read(JmeImporter e) throws IOException {
    300         super.read(e);
    301         InputCapsule capsule = e.getCapsule(this);
    302         orient = (Matrix3f) capsule.readSavable("orient", null);
    303         look = (Vector3f) capsule.readSavable("look", null);
    304         left = (Vector3f) capsule.readSavable("left", null);
    305         alignment = capsule.readEnum("alignment", Alignment.class, Alignment.Screen);
    306     }
    307 }
    308