OSDN Git Service

Set optimal mime types and executable settings.
[mikumikustudio/MikuMikuStudio.git] / src / com / jme / scene / BillboardNode.java
1 /*
2  * Copyright (c) 2003-2009 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.jme.scene;
34
35 import java.io.IOException;
36
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;
46
47 /**
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.
57  * 
58  * @author Mark Powell
59  * @author Joshua Slack
60  * @version $Id: BillboardNode.java,v 1.31 2007/09/21 15:45:29 nca Exp $
61  */
62 public class BillboardNode extends Node {
63     private static final long serialVersionUID = 1L;
64
65     private float lastTime;
66
67     private Matrix3f orient;
68
69     private Vector3f look;
70
71     private Vector3f left;
72
73     private int alignment;
74
75     /** Alligns this Billboard Node to the screen. */
76     public static final int SCREEN_ALIGNED = 0;
77
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;
81
82     /** Alligns this Billboard Node to the camera position. */
83     public static final int CAMERA_ALIGNED = 2;
84
85     /** Alligns this Billboard Node to the screen, but keeps the Z axis fixed. */
86     public static final int AXIAL_Z = 3;
87
88     
89     public BillboardNode() {}
90     /**
91      * Constructor instantiates a new <code>BillboardNode</code>. The name of
92      * the node is supplied during construction.
93      * 
94      * @param name
95      *            the name of the node.
96      */
97     public BillboardNode(String name) {
98         super(name);
99         orient = new Matrix3f();
100         look = new Vector3f();
101         left = new Vector3f();
102         alignment = SCREEN_ALIGNED;
103     }
104
105     /**
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.
109      * 
110      * @param time
111      *            the time between frames.
112      * @see com.jme.scene.Spatial#updateWorldData(float)
113      */
114     public void updateWorldData(float time) {
115         // removed due to bounding problems (incorrect bound -> culled -> not drawn -> not updated)
116         // see topic 5684
117         // TODO: optimze this again?
118         lastTime = 0; // time
119         super.updateWorldData( time );
120     }
121
122     /**
123      * <code>draw</code> updates the billboards orientation then renders the
124      * billboard's children.
125      * 
126      * @param r
127      *            the renderer used to draw.
128      * @see com.jme.scene.Spatial#draw(com.jme.renderer.Renderer)
129      */
130     public void draw(Renderer r) {
131         Camera cam = r.getCamera();
132         rotateBillboard(cam);
133
134         super.draw(r);
135     }
136
137     /**
138      * rotate the billboard based on the type set
139      * 
140      * @param cam
141      *            Camera
142      */
143     public void rotateBillboard(Camera cam) {
144         // get the scale, translation and rotation of the node in world space
145         updateWorldVectors();
146
147         switch (alignment) {
148             case AXIAL_Y:
149                 rotateAxial(cam, Vector3f.UNIT_Y);
150                 break;
151             case AXIAL_Z:
152                 rotateAxial(cam, Vector3f.UNIT_Z);
153                 break;
154             case SCREEN_ALIGNED:
155                 rotateScreenAligned(cam);
156                 break;
157             case CAMERA_ALIGNED:
158                 rotateCameraAligned(cam);
159                 break;
160         }
161
162         if (children == null) return;
163         for (int i = 0, cSize = getChildren().size(); i < cSize; i++) {
164             Spatial child = getChildren().get(i);
165             if (child != null) {
166                 child.updateGeometricState(lastTime, false);
167             }
168         }
169     }
170
171     /**
172      * Alligns this Billboard Node so that it points to the camera position.
173      * 
174      * @param camera
175      *            Camera
176      */
177     private void rotateCameraAligned(Camera camera) {
178         look.set(camera.getLocation()).subtractLocal(worldTranslation);
179         // coopt left for our own purposes.
180         Vector3f xzp = left;
181         // The xzp vector is the projection of the look vector on the xz plane
182         xzp.set(look.x, 0, look.z);
183         
184         // check for undefined rotation...
185         if (xzp.equals(Vector3f.ZERO)) return;
186
187         look.normalizeLocal();
188         xzp.normalizeLocal();
189         float cosp = look.dot(xzp);
190
191         // compute the local orientation matrix for the billboard
192         orient.m00 = xzp.z;
193         orient.m01 = xzp.x * -look.y;
194         orient.m02 = xzp.x * cosp;
195         orient.m10 = 0;
196         orient.m11 = cosp;
197         orient.m12 = look.y;
198         orient.m20 = -xzp.x;
199         orient.m21 = xzp.z * -look.y;
200         orient.m22 = xzp.z * cosp;
201
202         // The billboard must be oriented to face the camera before it is
203         // transformed into the world.
204         worldRotation.apply(orient);
205     }
206
207     /**
208      * Rotate the billboard so it points directly opposite the direction the
209      * camera's facing
210      * 
211      * @param camera
212      *            Camera
213      */
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);
221     }
222
223     /**
224      * Rotate the billboard towards the camera, but keeping a given axis fixed.
225      * 
226      * @param camera
227      *            Camera
228      */
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;
238
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
243             return;
244         }
245
246         // unitize the projection
247         float invLength = FastMath.invSqrt(lengthSquared);
248         if (axis.y == 1) {
249             left.x *= invLength;
250             left.y = 0.0f;
251             left.z *= invLength;
252
253             // compute the local orientation matrix for the billboard
254             orient.m00 = left.z;
255             orient.m01 = 0;
256             orient.m02 = left.x;
257             orient.m10 = 0;
258             orient.m11 = 1;
259             orient.m12 = 0;
260             orient.m20 = -left.x;
261             orient.m21 = 0;
262             orient.m22 = left.z;
263         } else if (axis.z == 1) {
264             left.x *= invLength;
265             left.y *= invLength;            
266             left.z = 0.0f;
267
268             // compute the local orientation matrix for the billboard
269             orient.m00 = left.y;
270             orient.m01 = left.x;
271             orient.m02 = 0;
272             orient.m10 = -left.y;
273             orient.m11 = left.x;
274             orient.m12 = 0;
275             orient.m20 = 0;
276             orient.m21 = 0;
277             orient.m22 = 1;
278         }
279
280         // The billboard must be oriented to face the camera before it is
281         // transformed into the world.
282         worldRotation.apply(orient);
283     }
284
285     /**
286      * Returns the alignment this BillboardNode is set too.
287      * 
288      * @return The alignment of rotation, AXIAL, CAMERA or SCREEN.
289      */
290     public int getAlignment() {
291         return alignment;
292     }
293
294     /**
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.
298      */
299     public void setAlignment(int alignment) {
300         this.alignment = alignment;
301     }
302     
303     @Override
304     public void write(JMEExporter e) throws IOException {
305         super.write(e);
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);
311     }
312
313     @Override
314     public void read(JMEImporter e) throws IOException {
315         super.read(e);
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);
321     }
322 }