2 * Copyright (c) 2003-2009 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
33 package com.jme.scene;
35 import java.io.IOException;
37 import com.jme.math.FastMath;
38 import com.jme.math.Matrix3f;
39 import com.jme.math.Vector3f;
40 import com.jme.renderer.Camera;
41 import com.jme.renderer.Renderer;
42 import com.jme.util.export.InputCapsule;
43 import com.jme.util.export.JMEExporter;
44 import com.jme.util.export.JMEImporter;
45 import com.jme.util.export.OutputCapsule;
48 * <code>BillboardNode</code> defines a node that always orients towards the
49 * camera. However, it does not tilt up/down as the camera rises. This keep
50 * geometry from appearing to fall over if the camera rises or lowers.
51 * <code>BillboardNode</code> is useful to contain a single quad that has a
52 * image applied to it for lowest detail models. This quad, with the texture,
53 * will appear to be a full model at great distances, and save on rendering and
54 * memory. It is important to note that for AXIAL mode, the billboards
55 * orientation will always be up (0,1,0). This means that a "standard" jME
56 * camera with up (0,1,0) is the only camera setting compatible with AXIAL mode.
59 * @author Joshua Slack
60 * @version $Id: BillboardNode.java,v 1.31 2007/09/21 15:45:29 nca Exp $
62 public class BillboardNode extends Node {
63 private static final long serialVersionUID = 1L;
65 private float lastTime;
67 private Matrix3f orient;
69 private Vector3f look;
71 private Vector3f left;
73 private int alignment;
75 /** Alligns this Billboard Node to the screen. */
76 public static final int SCREEN_ALIGNED = 0;
78 /** Alligns this Billboard Node to the screen, but keeps the Y axis fixed. */
79 public static final int AXIAL = 1;
80 public static final int AXIAL_Y = 1;
82 /** Alligns this Billboard Node to the camera position. */
83 public static final int CAMERA_ALIGNED = 2;
85 /** Alligns this Billboard Node to the screen, but keeps the Z axis fixed. */
86 public static final int AXIAL_Z = 3;
89 public BillboardNode() {}
91 * Constructor instantiates a new <code>BillboardNode</code>. The name of
92 * the node is supplied during construction.
95 * the name of the node.
97 public BillboardNode(String name) {
99 orient = new Matrix3f();
100 look = new Vector3f();
101 left = new Vector3f();
102 alignment = SCREEN_ALIGNED;
106 * <code>updateWorldData</code> defers the updating of the billboards
107 * orientation until rendering. This keeps the billboard from being
108 * needlessly oriented if the player can not actually see it.
111 * the time between frames.
112 * @see com.jme.scene.Spatial#updateWorldData(float)
114 public void updateWorldData(float time) {
115 // removed due to bounding problems (incorrect bound -> culled -> not drawn -> not updated)
117 // TODO: optimze this again?
118 lastTime = 0; // time
119 super.updateWorldData( time );
123 * <code>draw</code> updates the billboards orientation then renders the
124 * billboard's children.
127 * the renderer used to draw.
128 * @see com.jme.scene.Spatial#draw(com.jme.renderer.Renderer)
130 public void draw(Renderer r) {
131 Camera cam = r.getCamera();
132 rotateBillboard(cam);
138 * rotate the billboard based on the type set
143 public void rotateBillboard(Camera cam) {
144 // get the scale, translation and rotation of the node in world space
145 updateWorldVectors();
149 rotateAxial(cam, Vector3f.UNIT_Y);
152 rotateAxial(cam, Vector3f.UNIT_Z);
155 rotateScreenAligned(cam);
158 rotateCameraAligned(cam);
162 if (children == null) return;
163 for (int i = 0, cSize = getChildren().size(); i < cSize; i++) {
164 Spatial child = getChildren().get(i);
166 child.updateGeometricState(lastTime, false);
172 * Alligns this Billboard Node so that it points to the camera position.
177 private void rotateCameraAligned(Camera camera) {
178 look.set(camera.getLocation()).subtractLocal(worldTranslation);
179 // coopt left for our own purposes.
181 // The xzp vector is the projection of the look vector on the xz plane
182 xzp.set(look.x, 0, look.z);
184 // check for undefined rotation...
185 if (xzp.equals(Vector3f.ZERO)) return;
187 look.normalizeLocal();
188 xzp.normalizeLocal();
189 float cosp = look.dot(xzp);
191 // compute the local orientation matrix for the billboard
193 orient.m01 = xzp.x * -look.y;
194 orient.m02 = xzp.x * cosp;
199 orient.m21 = xzp.z * -look.y;
200 orient.m22 = xzp.z * cosp;
202 // The billboard must be oriented to face the camera before it is
203 // transformed into the world.
204 worldRotation.apply(orient);
208 * Rotate the billboard so it points directly opposite the direction the
214 private void rotateScreenAligned(Camera camera) {
215 // coopt diff for our in direction:
216 look.set(camera.getDirection()).negateLocal();
217 // coopt loc for our left direction:
218 left.set(camera.getLeft()).negateLocal();
219 orient.fromAxes(left, camera.getUp(), look);
220 worldRotation.fromRotationMatrix(orient);
224 * Rotate the billboard towards the camera, but keeping a given axis fixed.
229 private void rotateAxial(Camera camera, Vector3f axis) {
230 // Compute the additional rotation required for the billboard to face
231 // the camera. To do this, the camera must be inverse-transformed into
232 // the model space of the billboard.
233 look.set(camera.getLocation()).subtractLocal(worldTranslation);
234 worldRotation.mult(look, left); // coopt left for our own purposes.
235 left.x *= 1.0f / worldScale.x;
236 left.y *= 1.0f / worldScale.y;
237 left.z *= 1.0f / worldScale.z;
239 // squared length of the camera projection in the xz-plane
240 float lengthSquared = left.x * left.x + left.z * left.z;
241 if (lengthSquared < FastMath.FLT_EPSILON) {
242 // camera on the billboard axis, rotation not defined
246 // unitize the projection
247 float invLength = FastMath.invSqrt(lengthSquared);
253 // compute the local orientation matrix for the billboard
260 orient.m20 = -left.x;
263 } else if (axis.z == 1) {
268 // compute the local orientation matrix for the billboard
272 orient.m10 = -left.y;
280 // The billboard must be oriented to face the camera before it is
281 // transformed into the world.
282 worldRotation.apply(orient);
286 * Returns the alignment this BillboardNode is set too.
288 * @return The alignment of rotation, AXIAL, CAMERA or SCREEN.
290 public int getAlignment() {
295 * Sets the type of rotation this BillboardNode will have. The alignment can
296 * be CAMERA_ALIGNED, SCREEN_ALIGNED or AXIAL. Invalid alignments will
297 * assume no billboard rotation.
299 public void setAlignment(int alignment) {
300 this.alignment = alignment;
304 public void write(JMEExporter e) throws IOException {
306 OutputCapsule capsule = e.getCapsule(this);
307 capsule.write(orient, "orient", new Matrix3f());
308 capsule.write(look, "look", Vector3f.ZERO);
309 capsule.write(left, "left", Vector3f.ZERO);
310 capsule.write(alignment, "alignment", SCREEN_ALIGNED);
314 public void read(JMEImporter e) throws IOException {
316 InputCapsule capsule = e.getCapsule(this);
317 orient = (Matrix3f)capsule.readSavable("orient", new Matrix3f());
318 look = (Vector3f)capsule.readSavable("look", Vector3f.ZERO.clone());
319 left = (Vector3f)capsule.readSavable("left", Vector3f.ZERO.clone());
320 alignment = capsule.readInt("alignment", SCREEN_ALIGNED);