diff options
Diffstat (limited to 'engine/src/core/com/jme3/scene/control/BillboardControl.java')
-rw-r--r-- | engine/src/core/com/jme3/scene/control/BillboardControl.java | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/scene/control/BillboardControl.java b/engine/src/core/com/jme3/scene/control/BillboardControl.java new file mode 100644 index 0000000..40c2fea --- /dev/null +++ b/engine/src/core/com/jme3/scene/control/BillboardControl.java @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2009-2010 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.scene.control; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.io.IOException; + +public class BillboardControl extends AbstractControl { + + private Matrix3f orient; + private Vector3f look; + private Vector3f left; + private Alignment alignment; + + /** + * Determines how the billboard is aligned to the screen/camera. + */ + public enum Alignment { + /** + * Aligns this Billboard to the screen. + */ + Screen, + + /** + * Aligns this Billboard to the camera position. + */ + Camera, + + /** + * Aligns this Billboard to the screen, but keeps the Y axis fixed. + */ + AxialY, + + /** + * Aligns this Billboard to the screen, but keeps the Z axis fixed. + */ + AxialZ; + } + + public BillboardControl() { + super(); + orient = new Matrix3f(); + look = new Vector3f(); + left = new Vector3f(); + alignment = Alignment.Screen; + } + + public Control cloneForSpatial(Spatial spatial) { + BillboardControl control = new BillboardControl(); + control.alignment = this.alignment; + control.setSpatial(spatial); + return control; + } + + @Override + protected void controlUpdate(float tpf) { + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + Camera cam = vp.getCamera(); + rotateBillboard(cam); + } + + private void fixRefreshFlags(){ + // force transforms to update below this node + spatial.updateGeometricState(); + + // force world bound to update + Spatial rootNode = spatial; + while (rootNode.getParent() != null){ + rootNode = rootNode.getParent(); + } + rootNode.getWorldBound(); + } + + /** + * rotate the billboard based on the type set + * + * @param cam + * Camera + */ + private void rotateBillboard(Camera cam) { + switch (alignment) { + case AxialY: + rotateAxial(cam, Vector3f.UNIT_Y); + break; + case AxialZ: + rotateAxial(cam, Vector3f.UNIT_Z); + break; + case Screen: + rotateScreenAligned(cam); + break; + case Camera: + rotateCameraAligned(cam); + break; + } + } + + /** + * Aligns this Billboard so that it points to the camera position. + * + * @param camera + * Camera + */ + private void rotateCameraAligned(Camera camera) { + look.set(camera.getLocation()).subtractLocal( + spatial.getWorldTranslation()); + // coopt left for our own purposes. + Vector3f xzp = left; + // The xzp vector is the projection of the look vector on the xz plane + xzp.set(look.x, 0, look.z); + + // check for undefined rotation... + if (xzp.equals(Vector3f.ZERO)) { + return; + } + + look.normalizeLocal(); + xzp.normalizeLocal(); + float cosp = look.dot(xzp); + + // compute the local orientation matrix for the billboard + orient.set(0, 0, xzp.z); + orient.set(0, 1, xzp.x * -look.y); + orient.set(0, 2, xzp.x * cosp); + orient.set(1, 0, 0); + orient.set(1, 1, cosp); + orient.set(1, 2, look.y); + orient.set(2, 0, -xzp.x); + orient.set(2, 1, xzp.z * -look.y); + orient.set(2, 2, xzp.z * cosp); + + // The billboard must be oriented to face the camera before it is + // transformed into the world. + spatial.setLocalRotation(orient); + fixRefreshFlags(); + } + + /** + * Rotate the billboard so it points directly opposite the direction the + * camera's facing + * + * @param camera + * Camera + */ + private void rotateScreenAligned(Camera camera) { + // coopt diff for our in direction: + look.set(camera.getDirection()).negateLocal(); + // coopt loc for our left direction: + left.set(camera.getLeft()).negateLocal(); + orient.fromAxes(left, camera.getUp(), look); + Node parent = spatial.getParent(); + Quaternion rot=new Quaternion().fromRotationMatrix(orient); + if ( parent != null ) { + rot = parent.getWorldRotation().inverse().multLocal(rot); + rot.normalizeLocal(); + } + spatial.setLocalRotation(rot); + fixRefreshFlags(); + } + + /** + * Rotate the billboard towards the camera, but keeping a given axis fixed. + * + * @param camera + * Camera + */ + private void rotateAxial(Camera camera, Vector3f axis) { + // Compute the additional rotation required for the billboard to face + // the camera. To do this, the camera must be inverse-transformed into + // the model space of the billboard. + look.set(camera.getLocation()).subtractLocal( + spatial.getWorldTranslation()); + spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own + // purposes. + left.x *= 1.0f / spatial.getWorldScale().x; + left.y *= 1.0f / spatial.getWorldScale().y; + left.z *= 1.0f / spatial.getWorldScale().z; + + // squared length of the camera projection in the xz-plane + float lengthSquared = left.x * left.x + left.z * left.z; + if (lengthSquared < FastMath.FLT_EPSILON) { + // camera on the billboard axis, rotation not defined + return; + } + + // unitize the projection + float invLength = FastMath.invSqrt(lengthSquared); + if (axis.y == 1) { + left.x *= invLength; + left.y = 0.0f; + left.z *= invLength; + + // compute the local orientation matrix for the billboard + orient.set(0, 0, left.z); + orient.set(0, 1, 0); + orient.set(0, 2, left.x); + orient.set(1, 0, 0); + orient.set(1, 1, 1); + orient.set(1, 2, 0); + orient.set(2, 0, -left.x); + orient.set(2, 1, 0); + orient.set(2, 2, left.z); + } else if (axis.z == 1) { + left.x *= invLength; + left.y *= invLength; + left.z = 0.0f; + + // compute the local orientation matrix for the billboard + orient.set(0, 0, left.y); + orient.set(0, 1, left.x); + orient.set(0, 2, 0); + orient.set(1, 0, -left.y); + orient.set(1, 1, left.x); + orient.set(1, 2, 0); + orient.set(2, 0, 0); + orient.set(2, 1, 0); + orient.set(2, 2, 1); + } + + // The billboard must be oriented to face the camera before it is + // transformed into the world. + spatial.setLocalRotation(orient); + fixRefreshFlags(); + } + + /** + * Returns the alignment this Billboard is set too. + * + * @return The alignment of rotation, AxialY, AxialZ, Camera or Screen. + */ + public Alignment getAlignment() { + return alignment; + } + + /** + * Sets the type of rotation this Billboard will have. The alignment can + * be Camera, Screen, AxialY, or AxialZ. Invalid alignments will + * assume no billboard rotation. + */ + public void setAlignment(Alignment alignment) { + this.alignment = alignment; + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(orient, "orient", null); + capsule.write(look, "look", null); + capsule.write(left, "left", null); + capsule.write(alignment, "alignment", Alignment.Screen); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule capsule = e.getCapsule(this); + orient = (Matrix3f) capsule.readSavable("orient", null); + look = (Vector3f) capsule.readSavable("look", null); + left = (Vector3f) capsule.readSavable("left", null); + alignment = capsule.readEnum("alignment", Alignment.class, Alignment.Screen); + } +} |