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 */
32package com.jme3.renderer;
33
34import com.jme3.bounding.BoundingBox;
35import com.jme3.bounding.BoundingVolume;
36import com.jme3.export.*;
37import com.jme3.math.*;
38import com.jme3.util.TempVars;
39import java.io.IOException;
40import java.util.logging.Level;
41import java.util.logging.Logger;
42
43/**
44 * <code>Camera</code> is a standalone, purely mathematical class for doing
45 * camera-related computations.
46 *
47 * <p>
48 * Given input data such as location, orientation (direction, left, up),
49 * and viewport settings, it can compute data necessary to render objects
50 * with the graphics library. Two matrices are generated, the view matrix
51 * transforms objects from world space into eye space, while the projection
52 * matrix transforms objects from eye space into clip space.
53 * </p>
54 * <p>Another purpose of the camera class is to do frustum culling operations,
55 * defined by six planes which define a 3D frustum shape, it is possible to
56 * test if an object bounded by a mathematically defined volume is inside
57 * the camera frustum, and thus to avoid rendering objects that are outside
58 * the frustum
59 * </p>
60 *
61 * @author Mark Powell
62 * @author Joshua Slack
63 */
64public class Camera implements Savable, Cloneable {
65
66    private static final Logger logger = Logger.getLogger(Camera.class.getName());
67
68    /**
69     * The <code>FrustumIntersect</code> enum is returned as a result
70     * of a culling check operation,
71     * see {@link #contains(com.jme3.bounding.BoundingVolume) }
72     */
73    public enum FrustumIntersect {
74
75        /**
76         * defines a constant assigned to spatials that are completely outside
77         * of this camera's view frustum.
78         */
79        Outside,
80        /**
81         * defines a constant assigned to spatials that are completely inside
82         * the camera's view frustum.
83         */
84        Inside,
85        /**
86         * defines a constant assigned to spatials that are intersecting one of
87         * the six planes that define the view frustum.
88         */
89        Intersects;
90    }
91    /**
92     * LEFT_PLANE represents the left plane of the camera frustum.
93     */
94    private static final int LEFT_PLANE = 0;
95    /**
96     * RIGHT_PLANE represents the right plane of the camera frustum.
97     */
98    private static final int RIGHT_PLANE = 1;
99    /**
100     * BOTTOM_PLANE represents the bottom plane of the camera frustum.
101     */
102    private static final int BOTTOM_PLANE = 2;
103    /**
104     * TOP_PLANE represents the top plane of the camera frustum.
105     */
106    private static final int TOP_PLANE = 3;
107    /**
108     * FAR_PLANE represents the far plane of the camera frustum.
109     */
110    private static final int FAR_PLANE = 4;
111    /**
112     * NEAR_PLANE represents the near plane of the camera frustum.
113     */
114    private static final int NEAR_PLANE = 5;
115    /**
116     * FRUSTUM_PLANES represents the number of planes of the camera frustum.
117     */
118    private static final int FRUSTUM_PLANES = 6;
119    /**
120     * MAX_WORLD_PLANES holds the maximum planes allowed by the system.
121     */
122    private static final int MAX_WORLD_PLANES = 6;
123    /**
124     * Camera's location
125     */
126    protected Vector3f location;
127    /**
128     * The orientation of the camera.
129     */
130    protected Quaternion rotation;
131    /**
132     * Distance from camera to near frustum plane.
133     */
134    protected float frustumNear;
135    /**
136     * Distance from camera to far frustum plane.
137     */
138    protected float frustumFar;
139    /**
140     * Distance from camera to left frustum plane.
141     */
142    protected float frustumLeft;
143    /**
144     * Distance from camera to right frustum plane.
145     */
146    protected float frustumRight;
147    /**
148     * Distance from camera to top frustum plane.
149     */
150    protected float frustumTop;
151    /**
152     * Distance from camera to bottom frustum plane.
153     */
154    protected float frustumBottom;
155    //Temporary values computed in onFrustumChange that are needed if a
156    //call is made to onFrameChange.
157    protected float[] coeffLeft;
158    protected float[] coeffRight;
159    protected float[] coeffBottom;
160    protected float[] coeffTop;
161    //view port coordinates
162    /**
163     * Percent value on display where horizontal viewing starts for this camera.
164     * Default is 0.
165     */
166    protected float viewPortLeft;
167    /**
168     * Percent value on display where horizontal viewing ends for this camera.
169     * Default is 1.
170     */
171    protected float viewPortRight;
172    /**
173     * Percent value on display where vertical viewing ends for this camera.
174     * Default is 1.
175     */
176    protected float viewPortTop;
177    /**
178     * Percent value on display where vertical viewing begins for this camera.
179     * Default is 0.
180     */
181    protected float viewPortBottom;
182    /**
183     * Array holding the planes that this camera will check for culling.
184     */
185    protected Plane[] worldPlane;
186    /**
187     * A mask value set during contains() that allows fast culling of a Node's
188     * children.
189     */
190    private int planeState;
191    protected int width;
192    protected int height;
193    protected boolean viewportChanged = true;
194    /**
195     * store the value for field parallelProjection
196     */
197    private boolean parallelProjection;
198    protected Matrix4f projectionMatrixOverride;
199    protected Matrix4f viewMatrix = new Matrix4f();
200    protected Matrix4f projectionMatrix = new Matrix4f();
201    protected Matrix4f viewProjectionMatrix = new Matrix4f();
202    private BoundingBox guiBounding = new BoundingBox();
203    /** The camera's name. */
204    protected String name;
205
206    /**
207     * Serialization only. Do not use.
208     */
209    public Camera() {
210        worldPlane = new Plane[MAX_WORLD_PLANES];
211        for (int i = 0; i < MAX_WORLD_PLANES; i++) {
212            worldPlane[i] = new Plane();
213        }
214    }
215
216    /**
217     * Constructor instantiates a new <code>Camera</code> object. All
218     * values of the camera are set to default.
219     */
220    public Camera(int width, int height) {
221        this();
222        location = new Vector3f();
223        rotation = new Quaternion();
224
225        frustumNear = 1.0f;
226        frustumFar = 2.0f;
227        frustumLeft = -0.5f;
228        frustumRight = 0.5f;
229        frustumTop = 0.5f;
230        frustumBottom = -0.5f;
231
232        coeffLeft = new float[2];
233        coeffRight = new float[2];
234        coeffBottom = new float[2];
235        coeffTop = new float[2];
236
237        viewPortLeft = 0.0f;
238        viewPortRight = 1.0f;
239        viewPortTop = 1.0f;
240        viewPortBottom = 0.0f;
241
242        this.width = width;
243        this.height = height;
244
245        onFrustumChange();
246        onViewPortChange();
247        onFrameChange();
248
249        logger.log(Level.INFO, "Camera created (W: {0}, H: {1})", new Object[]{width, height});
250    }
251
252    @Override
253    public Camera clone() {
254        try {
255            Camera cam = (Camera) super.clone();
256            cam.viewportChanged = true;
257            cam.planeState = 0;
258
259            cam.worldPlane = new Plane[MAX_WORLD_PLANES];
260            for (int i = 0; i < worldPlane.length; i++) {
261                cam.worldPlane[i] = worldPlane[i].clone();
262            }
263
264            cam.coeffLeft = new float[2];
265            cam.coeffRight = new float[2];
266            cam.coeffBottom = new float[2];
267            cam.coeffTop = new float[2];
268
269            cam.location = location.clone();
270            cam.rotation = rotation.clone();
271
272            if (projectionMatrixOverride != null) {
273                cam.projectionMatrixOverride = projectionMatrixOverride.clone();
274            }
275
276            cam.viewMatrix = viewMatrix.clone();
277            cam.projectionMatrix = projectionMatrix.clone();
278            cam.viewProjectionMatrix = viewProjectionMatrix.clone();
279            cam.guiBounding = (BoundingBox) guiBounding.clone();
280
281            cam.update();
282
283            return cam;
284        } catch (CloneNotSupportedException ex) {
285            throw new AssertionError();
286        }
287    }
288
289	/**
290	 * This method copise the settings of the given camera.
291	 *
292	 * @param cam
293	 *            the camera we copy the settings from
294	 */
295    public void copyFrom(Camera cam) {
296    	location.set(cam.location);
297        rotation.set(cam.rotation);
298
299        frustumNear = cam.frustumNear;
300        frustumFar = cam.frustumFar;
301        frustumLeft = cam.frustumLeft;
302        frustumRight = cam.frustumRight;
303        frustumTop = cam.frustumTop;
304        frustumBottom = cam.frustumBottom;
305
306        coeffLeft[0] = cam.coeffLeft[0];
307        coeffLeft[1] = cam.coeffLeft[1];
308        coeffRight[0] = cam.coeffRight[0];
309        coeffRight[1] = cam.coeffRight[1];
310        coeffBottom[0] = cam.coeffBottom[0];
311        coeffBottom[1] = cam.coeffBottom[1];
312        coeffTop[0] = cam.coeffTop[0];
313        coeffTop[1] = cam.coeffTop[1];
314
315        viewPortLeft = cam.viewPortLeft;
316        viewPortRight = cam.viewPortRight;
317        viewPortTop = cam.viewPortTop;
318        viewPortBottom = cam.viewPortBottom;
319
320        this.width = cam.width;
321        this.height = cam.height;
322
323        this.planeState = cam.planeState;
324        this.viewportChanged = cam.viewportChanged;
325        for (int i = 0; i < MAX_WORLD_PLANES; ++i) {
326            worldPlane[i].setNormal(cam.worldPlane[i].getNormal());
327            worldPlane[i].setConstant(cam.worldPlane[i].getConstant());
328        }
329
330        this.parallelProjection = cam.parallelProjection;
331        if(cam.projectionMatrixOverride != null) {
332        	if(this.projectionMatrixOverride == null) {
333        		this.projectionMatrixOverride = cam.projectionMatrixOverride.clone();
334        	} else {
335        		this.projectionMatrixOverride.set(cam.projectionMatrixOverride);
336        	}
337        } else {
338        	this.projectionMatrixOverride = null;
339        }
340        this.viewMatrix.set(cam.viewMatrix);
341        this.projectionMatrix.set(cam.projectionMatrix);
342        this.viewProjectionMatrix.set(cam.viewProjectionMatrix);
343
344        this.guiBounding.setXExtent(cam.guiBounding.getXExtent());
345        this.guiBounding.setYExtent(cam.guiBounding.getYExtent());
346        this.guiBounding.setZExtent(cam.guiBounding.getZExtent());
347        this.guiBounding.setCenter(cam.guiBounding.getCenter());
348        this.guiBounding.setCheckPlane(cam.guiBounding.getCheckPlane());
349
350        this.name = cam.name;
351    }
352
353    /**
354     * This method sets the cameras name.
355     * @param name the cameras name
356     */
357    public void setName(String name) {
358        this.name = name;
359    }
360
361    /**
362     * This method returns the cameras name.
363     * @return the cameras name
364     */
365    public String getName() {
366        return name;
367    }
368
369    /**
370     * Sets a clipPlane for this camera.
371     * The cliPlane is used to recompute the projectionMatrix using the plane as the near plane
372     * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel
373     * more info here
374     * <ul>
375     * <li><a href="http://www.terathon.com/code/oblique.html">http://www.terathon.com/code/oblique.html</a>
376     * <li><a href="http://aras-p.info/texts/obliqueortho.html">http://aras-p.info/texts/obliqueortho.html</a>
377     * <li><a href="http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html">http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html</a>
378     * </ul>
379     *
380     * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket.
381     * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java
382     * @param clipPlane the plane
383     * @param side the side the camera stands from the plane
384     */
385    public void setClipPlane(Plane clipPlane, Plane.Side side) {
386        float sideFactor = 1;
387        if (side == Plane.Side.Negative) {
388            sideFactor = -1;
389        }
390        //we are on the other side of the plane no need to clip anymore.
391        if (clipPlane.whichSide(location) == side) {
392            return;
393        }
394        Matrix4f p = projectionMatrix.clone();
395
396        Matrix4f ivm = viewMatrix.clone();
397
398        Vector3f point = clipPlane.getNormal().mult(clipPlane.getConstant());
399        Vector3f pp = ivm.mult(point);
400        Vector3f pn = ivm.multNormal(clipPlane.getNormal(), null);
401        Vector4f clipPlaneV = new Vector4f(pn.x * sideFactor, pn.y * sideFactor, pn.z * sideFactor, -(pp.dot(pn)) * sideFactor);
402
403        Vector4f v = new Vector4f(0, 0, 0, 0);
404
405        v.x = (Math.signum(clipPlaneV.x) + p.m02) / p.m00;
406        v.y = (Math.signum(clipPlaneV.y) + p.m12) / p.m11;
407        v.z = -1.0f;
408        v.w = (1.0f + p.m22) / p.m23;
409
410        float dot = clipPlaneV.dot(v);//clipPlaneV.x * v.x + clipPlaneV.y * v.y + clipPlaneV.z * v.z + clipPlaneV.w * v.w;
411        Vector4f c = clipPlaneV.mult(2.0f / dot);
412
413        p.m20 = c.x - p.m30;
414        p.m21 = c.y - p.m31;
415        p.m22 = c.z - p.m32;
416        p.m23 = c.w - p.m33;
417        setProjectionMatrix(p);
418    }
419
420    /**
421     * Sets a clipPlane for this camera.
422     * The cliPlane is used to recompute the projectionMatrix using the plane as the near plane
423     * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel
424     * more info here
425     * <ul>
426     * <li><a href="http://www.terathon.com/code/oblique.html">http://www.terathon.com/code/oblique.html</a></li>
427     * <li><a href="http://aras-p.info/texts/obliqueortho.html">http://aras-p.info/texts/obliqueortho.html</a></li>
428     * <li><a href="http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html">
429     * http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html</a></li>
430     * </ul>
431     *
432     * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket.
433     * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java
434     * @param clipPlane the plane
435     */
436    public void setClipPlane(Plane clipPlane) {
437        setClipPlane(clipPlane, clipPlane.whichSide(location));
438    }
439
440    /**
441     * Resizes this camera's view with the given width and height. This is
442     * similar to constructing a new camera, but reusing the same Object. This
443     * method is called by an associated {@link RenderManager} to notify the camera of
444     * changes in the display dimensions.
445     *
446     * @param width the view width
447     * @param height the view height
448     * @param fixAspect If true, the camera's aspect ratio will be recomputed.
449     * Recomputing the aspect ratio requires changing the frustum values.
450     */
451    public void resize(int width, int height, boolean fixAspect) {
452        this.width = width;
453        this.height = height;
454        onViewPortChange();
455
456        if (fixAspect /*&& !parallelProjection*/) {
457            frustumRight = frustumTop * ((float) width / height);
458            frustumLeft = -frustumRight;
459            onFrustumChange();
460        }
461    }
462
463    /**
464     * <code>getFrustumBottom</code> returns the value of the bottom frustum
465     * plane.
466     *
467     * @return the value of the bottom frustum plane.
468     */
469    public float getFrustumBottom() {
470        return frustumBottom;
471    }
472
473    /**
474     * <code>setFrustumBottom</code> sets the value of the bottom frustum
475     * plane.
476     *
477     * @param frustumBottom the value of the bottom frustum plane.
478     */
479    public void setFrustumBottom(float frustumBottom) {
480        this.frustumBottom = frustumBottom;
481        onFrustumChange();
482    }
483
484    /**
485     * <code>getFrustumFar</code> gets the value of the far frustum plane.
486     *
487     * @return the value of the far frustum plane.
488     */
489    public float getFrustumFar() {
490        return frustumFar;
491    }
492
493    /**
494     * <code>setFrustumFar</code> sets the value of the far frustum plane.
495     *
496     * @param frustumFar the value of the far frustum plane.
497     */
498    public void setFrustumFar(float frustumFar) {
499        this.frustumFar = frustumFar;
500        onFrustumChange();
501    }
502
503    /**
504     * <code>getFrustumLeft</code> gets the value of the left frustum plane.
505     *
506     * @return the value of the left frustum plane.
507     */
508    public float getFrustumLeft() {
509        return frustumLeft;
510    }
511
512    /**
513     * <code>setFrustumLeft</code> sets the value of the left frustum plane.
514     *
515     * @param frustumLeft the value of the left frustum plane.
516     */
517    public void setFrustumLeft(float frustumLeft) {
518        this.frustumLeft = frustumLeft;
519        onFrustumChange();
520    }
521
522    /**
523     * <code>getFrustumNear</code> gets the value of the near frustum plane.
524     *
525     * @return the value of the near frustum plane.
526     */
527    public float getFrustumNear() {
528        return frustumNear;
529    }
530
531    /**
532     * <code>setFrustumNear</code> sets the value of the near frustum plane.
533     *
534     * @param frustumNear the value of the near frustum plane.
535     */
536    public void setFrustumNear(float frustumNear) {
537        this.frustumNear = frustumNear;
538        onFrustumChange();
539    }
540
541    /**
542     * <code>getFrustumRight</code> gets the value of the right frustum plane.
543     *
544     * @return frustumRight the value of the right frustum plane.
545     */
546    public float getFrustumRight() {
547        return frustumRight;
548    }
549
550    /**
551     * <code>setFrustumRight</code> sets the value of the right frustum plane.
552     *
553     * @param frustumRight the value of the right frustum plane.
554     */
555    public void setFrustumRight(float frustumRight) {
556        this.frustumRight = frustumRight;
557        onFrustumChange();
558    }
559
560    /**
561     * <code>getFrustumTop</code> gets the value of the top frustum plane.
562     *
563     * @return the value of the top frustum plane.
564     */
565    public float getFrustumTop() {
566        return frustumTop;
567    }
568
569    /**
570     * <code>setFrustumTop</code> sets the value of the top frustum plane.
571     *
572     * @param frustumTop the value of the top frustum plane.
573     */
574    public void setFrustumTop(float frustumTop) {
575        this.frustumTop = frustumTop;
576        onFrustumChange();
577    }
578
579    /**
580     * <code>getLocation</code> retrieves the location vector of the camera.
581     *
582     * @return the position of the camera.
583     * @see Camera#getLocation()
584     */
585    public Vector3f getLocation() {
586        return location;
587    }
588
589    /**
590     * <code>getRotation</code> retrieves the rotation quaternion of the camera.
591     *
592     * @return the rotation of the camera.
593     */
594    public Quaternion getRotation() {
595        return rotation;
596    }
597
598    /**
599     * <code>getDirection</code> retrieves the direction vector the camera is
600     * facing.
601     *
602     * @return the direction the camera is facing.
603     * @see Camera#getDirection()
604     */
605    public Vector3f getDirection() {
606        return rotation.getRotationColumn(2);
607    }
608
609    /**
610     * <code>getLeft</code> retrieves the left axis of the camera.
611     *
612     * @return the left axis of the camera.
613     * @see Camera#getLeft()
614     */
615    public Vector3f getLeft() {
616        return rotation.getRotationColumn(0);
617    }
618
619    /**
620     * <code>getUp</code> retrieves the up axis of the camera.
621     *
622     * @return the up axis of the camera.
623     * @see Camera#getUp()
624     */
625    public Vector3f getUp() {
626        return rotation.getRotationColumn(1);
627    }
628
629    /**
630     * <code>getDirection</code> retrieves the direction vector the camera is
631     * facing.
632     *
633     * @return the direction the camera is facing.
634     * @see Camera#getDirection()
635     */
636    public Vector3f getDirection(Vector3f store) {
637        return rotation.getRotationColumn(2, store);
638    }
639
640    /**
641     * <code>getLeft</code> retrieves the left axis of the camera.
642     *
643     * @return the left axis of the camera.
644     * @see Camera#getLeft()
645     */
646    public Vector3f getLeft(Vector3f store) {
647        return rotation.getRotationColumn(0, store);
648    }
649
650    /**
651     * <code>getUp</code> retrieves the up axis of the camera.
652     *
653     * @return the up axis of the camera.
654     * @see Camera#getUp()
655     */
656    public Vector3f getUp(Vector3f store) {
657        return rotation.getRotationColumn(1, store);
658    }
659
660    /**
661     * <code>setLocation</code> sets the position of the camera.
662     *
663     * @param location the position of the camera.
664     */
665    public void setLocation(Vector3f location) {
666        this.location.set(location);
667        onFrameChange();
668    }
669
670    /**
671     * <code>setRotation</code> sets the orientation of this camera.
672     * This will be equivelant to setting each of the axes:
673     * <code><br>
674     * cam.setLeft(rotation.getRotationColumn(0));<br>
675     * cam.setUp(rotation.getRotationColumn(1));<br>
676     * cam.setDirection(rotation.getRotationColumn(2));<br>
677     * </code>
678     *
679     * @param rotation the rotation of this camera
680     */
681    public void setRotation(Quaternion rotation) {
682        this.rotation.set(rotation);
683        onFrameChange();
684    }
685
686    /**
687     * <code>lookAtDirection</code> sets the direction the camera is facing
688     * given a direction and an up vector.
689     *
690     * @param direction the direction this camera is facing.
691     */
692    public void lookAtDirection(Vector3f direction, Vector3f up) {
693        this.rotation.lookAt(direction, up);
694        onFrameChange();
695    }
696
697    /**
698     * <code>setAxes</code> sets the axes (left, up and direction) for this
699     * camera.
700     *
701     * @param left      the left axis of the camera.
702     * @param up        the up axis of the camera.
703     * @param direction the direction the camera is facing.
704     *
705     * @see Camera#setAxes(com.jme3.math.Quaternion)
706     */
707    public void setAxes(Vector3f left, Vector3f up, Vector3f direction) {
708        this.rotation.fromAxes(left, up, direction);
709        onFrameChange();
710    }
711
712    /**
713     * <code>setAxes</code> uses a rotational matrix to set the axes of the
714     * camera.
715     *
716     * @param axes the matrix that defines the orientation of the camera.
717     */
718    public void setAxes(Quaternion axes) {
719        this.rotation.set(axes);
720        onFrameChange();
721    }
722
723    /**
724     * normalize normalizes the camera vectors.
725     */
726    public void normalize() {
727        this.rotation.normalizeLocal();
728        onFrameChange();
729    }
730
731    /**
732     * <code>setFrustum</code> sets the frustum of this camera object.
733     *
734     * @param near   the near plane.
735     * @param far    the far plane.
736     * @param left   the left plane.
737     * @param right  the right plane.
738     * @param top    the top plane.
739     * @param bottom the bottom plane.
740     * @see Camera#setFrustum(float, float, float, float,
741     *      float, float)
742     */
743    public void setFrustum(float near, float far, float left, float right,
744            float top, float bottom) {
745
746        frustumNear = near;
747        frustumFar = far;
748        frustumLeft = left;
749        frustumRight = right;
750        frustumTop = top;
751        frustumBottom = bottom;
752        onFrustumChange();
753    }
754
755    /**
756     * <code>setFrustumPerspective</code> defines the frustum for the camera.  This
757     * frustum is defined by a viewing angle, aspect ratio, and near/far planes
758     *
759     * @param fovY   Frame of view angle along the Y in degrees.
760     * @param aspect Width:Height ratio
761     * @param near   Near view plane distance
762     * @param far    Far view plane distance
763     */
764    public void setFrustumPerspective(float fovY, float aspect, float near,
765            float far) {
766        if (Float.isNaN(aspect) || Float.isInfinite(aspect)) {
767            // ignore.
768            logger.log(Level.WARNING, "Invalid aspect given to setFrustumPerspective: {0}", aspect);
769            return;
770        }
771
772        float h = FastMath.tan(fovY * FastMath.DEG_TO_RAD * .5f) * near;
773        float w = h * aspect;
774        frustumLeft = -w;
775        frustumRight = w;
776        frustumBottom = -h;
777        frustumTop = h;
778        frustumNear = near;
779        frustumFar = far;
780
781        onFrustumChange();
782    }
783
784    /**
785     * <code>setFrame</code> sets the orientation and location of the camera.
786     *
787     * @param location  the point position of the camera.
788     * @param left      the left axis of the camera.
789     * @param up        the up axis of the camera.
790     * @param direction the facing of the camera.
791     * @see Camera#setFrame(com.jme3.math.Vector3f,
792     *      com.jme3.math.Vector3f, com.jme3.math.Vector3f, com.jme3.math.Vector3f)
793     */
794    public void setFrame(Vector3f location, Vector3f left, Vector3f up,
795            Vector3f direction) {
796
797        this.location = location;
798        this.rotation.fromAxes(left, up, direction);
799        onFrameChange();
800    }
801
802    /**
803     * <code>lookAt</code> is a convienence method for auto-setting the frame
804     * based on a world position the user desires the camera to look at. It
805     * repoints the camera towards the given position using the difference
806     * between the position and the current camera location as a direction
807     * vector and the worldUpVector to compute up and left camera vectors.
808     *
809     * @param pos           where to look at in terms of world coordinates
810     * @param worldUpVector a normalized vector indicating the up direction of the world.
811     *                      (typically {0, 1, 0} in jME.)
812     */
813    public void lookAt(Vector3f pos, Vector3f worldUpVector) {
814        TempVars vars = TempVars.get();
815        Vector3f newDirection = vars.vect1;
816        Vector3f newUp = vars.vect2;
817        Vector3f newLeft = vars.vect3;
818
819        newDirection.set(pos).subtractLocal(location).normalizeLocal();
820
821        newUp.set(worldUpVector).normalizeLocal();
822        if (newUp.equals(Vector3f.ZERO)) {
823            newUp.set(Vector3f.UNIT_Y);
824        }
825
826        newLeft.set(newUp).crossLocal(newDirection).normalizeLocal();
827        if (newLeft.equals(Vector3f.ZERO)) {
828            if (newDirection.x != 0) {
829                newLeft.set(newDirection.y, -newDirection.x, 0f);
830            } else {
831                newLeft.set(0f, newDirection.z, -newDirection.y);
832            }
833        }
834
835        newUp.set(newDirection).crossLocal(newLeft).normalizeLocal();
836
837        this.rotation.fromAxes(newLeft, newUp, newDirection);
838        this.rotation.normalizeLocal();
839        vars.release();
840
841        onFrameChange();
842    }
843
844    /**
845     * <code>setFrame</code> sets the orientation and location of the camera.
846     *
847     * @param location
848     *            the point position of the camera.
849     * @param axes
850     *            the orientation of the camera.
851     */
852    public void setFrame(Vector3f location, Quaternion axes) {
853        this.location = location;
854        this.rotation.set(axes);
855        onFrameChange();
856    }
857
858    /**
859     * <code>update</code> updates the camera parameters by calling
860     * <code>onFrustumChange</code>,<code>onViewPortChange</code> and
861     * <code>onFrameChange</code>.
862     *
863     * @see Camera#update()
864     */
865    public void update() {
866        onFrustumChange();
867        onViewPortChange();
868        onFrameChange();
869    }
870
871    /**
872     * <code>getPlaneState</code> returns the state of the frustum planes. So
873     * checks can be made as to which frustum plane has been examined for
874     * culling thus far.
875     *
876     * @return the current plane state int.
877     */
878    public int getPlaneState() {
879        return planeState;
880    }
881
882    /**
883     * <code>setPlaneState</code> sets the state to keep track of tested
884     * planes for culling.
885     *
886     * @param planeState the updated state.
887     */
888    public void setPlaneState(int planeState) {
889        this.planeState = planeState;
890    }
891
892    /**
893     * <code>getViewPortLeft</code> gets the left boundary of the viewport
894     *
895     * @return the left boundary of the viewport
896     */
897    public float getViewPortLeft() {
898        return viewPortLeft;
899    }
900
901    /**
902     * <code>setViewPortLeft</code> sets the left boundary of the viewport
903     *
904     * @param left the left boundary of the viewport
905     */
906    public void setViewPortLeft(float left) {
907        viewPortLeft = left;
908        onViewPortChange();
909    }
910
911    /**
912     * <code>getViewPortRight</code> gets the right boundary of the viewport
913     *
914     * @return the right boundary of the viewport
915     */
916    public float getViewPortRight() {
917        return viewPortRight;
918    }
919
920    /**
921     * <code>setViewPortRight</code> sets the right boundary of the viewport
922     *
923     * @param right the right boundary of the viewport
924     */
925    public void setViewPortRight(float right) {
926        viewPortRight = right;
927        onViewPortChange();
928    }
929
930    /**
931     * <code>getViewPortTop</code> gets the top boundary of the viewport
932     *
933     * @return the top boundary of the viewport
934     */
935    public float getViewPortTop() {
936        return viewPortTop;
937    }
938
939    /**
940     * <code>setViewPortTop</code> sets the top boundary of the viewport
941     *
942     * @param top the top boundary of the viewport
943     */
944    public void setViewPortTop(float top) {
945        viewPortTop = top;
946        onViewPortChange();
947    }
948
949    /**
950     * <code>getViewPortBottom</code> gets the bottom boundary of the viewport
951     *
952     * @return the bottom boundary of the viewport
953     */
954    public float getViewPortBottom() {
955        return viewPortBottom;
956    }
957
958    /**
959     * <code>setViewPortBottom</code> sets the bottom boundary of the viewport
960     *
961     * @param bottom the bottom boundary of the viewport
962     */
963    public void setViewPortBottom(float bottom) {
964        viewPortBottom = bottom;
965        onViewPortChange();
966    }
967
968    /**
969     * <code>setViewPort</code> sets the boundaries of the viewport
970     *
971     * @param left   the left boundary of the viewport (default: 0)
972     * @param right  the right boundary of the viewport (default: 1)
973     * @param bottom the bottom boundary of the viewport (default: 0)
974     * @param top    the top boundary of the viewport (default: 1)
975     */
976    public void setViewPort(float left, float right, float bottom, float top) {
977        this.viewPortLeft = left;
978        this.viewPortRight = right;
979        this.viewPortBottom = bottom;
980        this.viewPortTop = top;
981        onViewPortChange();
982    }
983
984    /**
985     * Returns the pseudo distance from the given position to the near
986     * plane of the camera. This is used for render queue sorting.
987     * @param pos The position to compute a distance to.
988     * @return Distance from the far plane to the point.
989     */
990    public float distanceToNearPlane(Vector3f pos) {
991        return worldPlane[NEAR_PLANE].pseudoDistance(pos);
992    }
993
994    /**
995     * <code>contains</code> tests a bounding volume against the planes of the
996     * camera's frustum. The frustums planes are set such that the normals all
997     * face in towards the viewable scene. Therefore, if the bounding volume is
998     * on the negative side of the plane is can be culled out.
999     *
1000     * NOTE: This method is used internally for culling, for public usage,
1001     * the plane state of the bounding volume must be saved and restored, e.g:
1002     * <code>BoundingVolume bv;<br/>
1003     * Camera c;<br/>
1004     * int planeState = bv.getPlaneState();<br/>
1005     * bv.setPlaneState(0);<br/>
1006     * c.contains(bv);<br/>
1007     * bv.setPlaneState(plateState);<br/>
1008     * </code>
1009     *
1010     * @param bound the bound to check for culling
1011     * @return See enums in <code>FrustumIntersect</code>
1012     */
1013    public FrustumIntersect contains(BoundingVolume bound) {
1014        if (bound == null) {
1015            return FrustumIntersect.Inside;
1016        }
1017
1018        int mask;
1019        FrustumIntersect rVal = FrustumIntersect.Inside;
1020
1021        for (int planeCounter = FRUSTUM_PLANES; planeCounter >= 0; planeCounter--) {
1022            if (planeCounter == bound.getCheckPlane()) {
1023                continue; // we have already checked this plane at first iteration
1024            }
1025            int planeId = (planeCounter == FRUSTUM_PLANES) ? bound.getCheckPlane() : planeCounter;
1026//            int planeId = planeCounter;
1027
1028            mask = 1 << (planeId);
1029            if ((planeState & mask) == 0) {
1030                Plane.Side side = bound.whichSide(worldPlane[planeId]);
1031
1032                if (side == Plane.Side.Negative) {
1033                    //object is outside of frustum
1034                    bound.setCheckPlane(planeId);
1035                    return FrustumIntersect.Outside;
1036                } else if (side == Plane.Side.Positive) {
1037                    //object is visible on *this* plane, so mark this plane
1038                    //so that we don't check it for sub nodes.
1039                    planeState |= mask;
1040                } else {
1041                    rVal = FrustumIntersect.Intersects;
1042                }
1043            }
1044        }
1045
1046        return rVal;
1047    }
1048
1049    /**
1050     * <code>containsGui</code> tests a bounding volume against the ortho
1051     * bounding box of the camera. A bounding box spanning from
1052     * 0, 0 to Width, Height. Constrained by the viewport settings on the
1053     * camera.
1054     *
1055     * @param bound the bound to check for culling
1056     * @return True if the camera contains the gui element bounding volume.
1057     */
1058    public boolean containsGui(BoundingVolume bound) {
1059        return guiBounding.intersects(bound);
1060    }
1061
1062    /**
1063     * @return the view matrix of the camera.
1064     * The view matrix transforms world space into eye space.
1065     * This matrix is usually defined by the position and
1066     * orientation of the camera.
1067     */
1068    public Matrix4f getViewMatrix() {
1069        return viewMatrix;
1070    }
1071
1072    /**
1073     * Overrides the projection matrix used by the camera. Will
1074     * use the matrix for computing the view projection matrix as well.
1075     * Use null argument to return to normal functionality.
1076     *
1077     * @param projMatrix
1078     */
1079    public void setProjectionMatrix(Matrix4f projMatrix) {
1080        projectionMatrixOverride = projMatrix;
1081        updateViewProjection();
1082    }
1083
1084    /**
1085     * @return the projection matrix of the camera.
1086     * The view projection matrix  transforms eye space into clip space.
1087     * This matrix is usually defined by the viewport and perspective settings
1088     * of the camera.
1089     */
1090    public Matrix4f getProjectionMatrix() {
1091        if (projectionMatrixOverride != null) {
1092            return projectionMatrixOverride;
1093        }
1094
1095        return projectionMatrix;
1096    }
1097
1098    /**
1099     * Updates the view projection matrix.
1100     */
1101    public void updateViewProjection() {
1102        if (projectionMatrixOverride != null) {
1103            viewProjectionMatrix.set(projectionMatrixOverride).multLocal(viewMatrix);
1104        } else {
1105            //viewProjectionMatrix.set(viewMatrix).multLocal(projectionMatrix);
1106            viewProjectionMatrix.set(projectionMatrix).multLocal(viewMatrix);
1107        }
1108    }
1109
1110    /**
1111     * @return The result of multiplying the projection matrix by the view
1112     * matrix. This matrix is required for rendering an object. It is
1113     * precomputed so as to not compute it every time an object is rendered.
1114     */
1115    public Matrix4f getViewProjectionMatrix() {
1116        return viewProjectionMatrix;
1117    }
1118
1119    /**
1120     * @return True if the viewport (width, height, left, right, bottom, up)
1121     * has been changed. This is needed in the renderer so that the proper
1122     * viewport can be set-up.
1123     */
1124    public boolean isViewportChanged() {
1125        return viewportChanged;
1126    }
1127
1128    /**
1129     * Clears the viewport changed flag once it has been updated inside
1130     * the renderer.
1131     */
1132    public void clearViewportChanged() {
1133        viewportChanged = false;
1134    }
1135
1136    /**
1137     * Called when the viewport has been changed.
1138     */
1139    public void onViewPortChange() {
1140        viewportChanged = true;
1141        setGuiBounding();
1142    }
1143
1144    private void setGuiBounding() {
1145        float sx = width * viewPortLeft;
1146        float ex = width * viewPortRight;
1147        float sy = height * viewPortBottom;
1148        float ey = height * viewPortTop;
1149        float xExtent = Math.max(0f, (ex - sx) / 2f);
1150        float yExtent = Math.max(0f, (ey - sy) / 2f);
1151        guiBounding.setCenter(new Vector3f(sx + xExtent, sy + yExtent, 0));
1152        guiBounding.setXExtent(xExtent);
1153        guiBounding.setYExtent(yExtent);
1154        guiBounding.setZExtent(Float.MAX_VALUE);
1155    }
1156
1157    /**
1158     * <code>onFrustumChange</code> updates the frustum to reflect any changes
1159     * made to the planes. The new frustum values are kept in a temporary
1160     * location for use when calculating the new frame. The projection
1161     * matrix is updated to reflect the current values of the frustum.
1162     */
1163    public void onFrustumChange() {
1164        if (!isParallelProjection()) {
1165            float nearSquared = frustumNear * frustumNear;
1166            float leftSquared = frustumLeft * frustumLeft;
1167            float rightSquared = frustumRight * frustumRight;
1168            float bottomSquared = frustumBottom * frustumBottom;
1169            float topSquared = frustumTop * frustumTop;
1170
1171            float inverseLength = FastMath.invSqrt(nearSquared + leftSquared);
1172            coeffLeft[0] = frustumNear * inverseLength;
1173            coeffLeft[1] = -frustumLeft * inverseLength;
1174
1175            inverseLength = FastMath.invSqrt(nearSquared + rightSquared);
1176            coeffRight[0] = -frustumNear * inverseLength;
1177            coeffRight[1] = frustumRight * inverseLength;
1178
1179            inverseLength = FastMath.invSqrt(nearSquared + bottomSquared);
1180            coeffBottom[0] = frustumNear * inverseLength;
1181            coeffBottom[1] = -frustumBottom * inverseLength;
1182
1183            inverseLength = FastMath.invSqrt(nearSquared + topSquared);
1184            coeffTop[0] = -frustumNear * inverseLength;
1185            coeffTop[1] = frustumTop * inverseLength;
1186        } else {
1187            coeffLeft[0] = 1;
1188            coeffLeft[1] = 0;
1189
1190            coeffRight[0] = -1;
1191            coeffRight[1] = 0;
1192
1193            coeffBottom[0] = 1;
1194            coeffBottom[1] = 0;
1195
1196            coeffTop[0] = -1;
1197            coeffTop[1] = 0;
1198        }
1199
1200        projectionMatrix.fromFrustum(frustumNear, frustumFar, frustumLeft, frustumRight, frustumTop, frustumBottom, parallelProjection);
1201//        projectionMatrix.transposeLocal();
1202
1203        // The frame is effected by the frustum values
1204        // update it as well
1205        onFrameChange();
1206    }
1207
1208    /**
1209     * <code>onFrameChange</code> updates the view frame of the camera.
1210     */
1211    public void onFrameChange() {
1212        TempVars vars = TempVars.get();
1213
1214        Vector3f left = getLeft(vars.vect1);
1215        Vector3f direction = getDirection(vars.vect2);
1216        Vector3f up = getUp(vars.vect3);
1217
1218        float dirDotLocation = direction.dot(location);
1219
1220        // left plane
1221        Vector3f leftPlaneNormal = worldPlane[LEFT_PLANE].getNormal();
1222        leftPlaneNormal.x = left.x * coeffLeft[0];
1223        leftPlaneNormal.y = left.y * coeffLeft[0];
1224        leftPlaneNormal.z = left.z * coeffLeft[0];
1225        leftPlaneNormal.addLocal(direction.x * coeffLeft[1], direction.y
1226                * coeffLeft[1], direction.z * coeffLeft[1]);
1227        worldPlane[LEFT_PLANE].setConstant(location.dot(leftPlaneNormal));
1228
1229        // right plane
1230        Vector3f rightPlaneNormal = worldPlane[RIGHT_PLANE].getNormal();
1231        rightPlaneNormal.x = left.x * coeffRight[0];
1232        rightPlaneNormal.y = left.y * coeffRight[0];
1233        rightPlaneNormal.z = left.z * coeffRight[0];
1234        rightPlaneNormal.addLocal(direction.x * coeffRight[1], direction.y
1235                * coeffRight[1], direction.z * coeffRight[1]);
1236        worldPlane[RIGHT_PLANE].setConstant(location.dot(rightPlaneNormal));
1237
1238        // bottom plane
1239        Vector3f bottomPlaneNormal = worldPlane[BOTTOM_PLANE].getNormal();
1240        bottomPlaneNormal.x = up.x * coeffBottom[0];
1241        bottomPlaneNormal.y = up.y * coeffBottom[0];
1242        bottomPlaneNormal.z = up.z * coeffBottom[0];
1243        bottomPlaneNormal.addLocal(direction.x * coeffBottom[1], direction.y
1244                * coeffBottom[1], direction.z * coeffBottom[1]);
1245        worldPlane[BOTTOM_PLANE].setConstant(location.dot(bottomPlaneNormal));
1246
1247        // top plane
1248        Vector3f topPlaneNormal = worldPlane[TOP_PLANE].getNormal();
1249        topPlaneNormal.x = up.x * coeffTop[0];
1250        topPlaneNormal.y = up.y * coeffTop[0];
1251        topPlaneNormal.z = up.z * coeffTop[0];
1252        topPlaneNormal.addLocal(direction.x * coeffTop[1], direction.y
1253                * coeffTop[1], direction.z * coeffTop[1]);
1254        worldPlane[TOP_PLANE].setConstant(location.dot(topPlaneNormal));
1255
1256        if (isParallelProjection()) {
1257            worldPlane[LEFT_PLANE].setConstant(worldPlane[LEFT_PLANE].getConstant() + frustumLeft);
1258            worldPlane[RIGHT_PLANE].setConstant(worldPlane[RIGHT_PLANE].getConstant() - frustumRight);
1259            worldPlane[TOP_PLANE].setConstant(worldPlane[TOP_PLANE].getConstant() - frustumTop);
1260            worldPlane[BOTTOM_PLANE].setConstant(worldPlane[BOTTOM_PLANE].getConstant() + frustumBottom);
1261        }
1262
1263        // far plane
1264        worldPlane[FAR_PLANE].setNormal(left);
1265        worldPlane[FAR_PLANE].setNormal(-direction.x, -direction.y, -direction.z);
1266        worldPlane[FAR_PLANE].setConstant(-(dirDotLocation + frustumFar));
1267
1268        // near plane
1269        worldPlane[NEAR_PLANE].setNormal(direction.x, direction.y, direction.z);
1270        worldPlane[NEAR_PLANE].setConstant(dirDotLocation + frustumNear);
1271
1272        viewMatrix.fromFrame(location, direction, up, left);
1273
1274        vars.release();
1275
1276//        viewMatrix.transposeLocal();
1277        updateViewProjection();
1278    }
1279
1280    /**
1281     * @return true if parallel projection is enable, false if in normal perspective mode
1282     * @see #setParallelProjection(boolean)
1283     */
1284    public boolean isParallelProjection() {
1285        return this.parallelProjection;
1286    }
1287
1288    /**
1289     * Enable/disable parallel projection.
1290     *
1291     * @param value true to set up this camera for parallel projection is enable, false to enter normal perspective mode
1292     */
1293    public void setParallelProjection(final boolean value) {
1294        this.parallelProjection = value;
1295        onFrustumChange();
1296    }
1297
1298    /**
1299     * @see Camera#getWorldCoordinates
1300     */
1301    public Vector3f getWorldCoordinates(Vector2f screenPos, float zPos) {
1302        return getWorldCoordinates(screenPos, zPos, null);
1303    }
1304
1305    /**
1306     * @see Camera#getWorldCoordinates
1307     */
1308    public Vector3f getWorldCoordinates(Vector2f screenPosition,
1309            float zPos, Vector3f store) {
1310        if (store == null) {
1311            store = new Vector3f();
1312        }
1313
1314        Matrix4f inverseMat = new Matrix4f(viewProjectionMatrix);
1315        inverseMat.invertLocal();
1316
1317        store.set(
1318                (screenPosition.x / getWidth() - viewPortLeft) / (viewPortRight - viewPortLeft) * 2 - 1,
1319                (screenPosition.y / getHeight() - viewPortBottom) / (viewPortTop - viewPortBottom) * 2 - 1,
1320                zPos * 2 - 1);
1321
1322        float w = inverseMat.multProj(store, store);
1323        store.multLocal(1f / w);
1324
1325        return store;
1326    }
1327
1328    /**
1329     * Converts the given position from world space to screen space.
1330     *
1331     * @see Camera#getScreenCoordinates
1332     */
1333    public Vector3f getScreenCoordinates(Vector3f worldPos) {
1334        return getScreenCoordinates(worldPos, null);
1335    }
1336
1337    /**
1338     * Converts the given position from world space to screen space.
1339     *
1340     * @see Camera#getScreenCoordinates(Vector3f, Vector3f)
1341     */
1342    public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) {
1343        if (store == null) {
1344            store = new Vector3f();
1345        }
1346
1347//        TempVars vars = vars.lock();
1348//        Quaternion tmp_quat = vars.quat1;
1349//        tmp_quat.set( worldPosition.x, worldPosition.y, worldPosition.z, 1 );
1350//        viewProjectionMatrix.mult(tmp_quat, tmp_quat);
1351//        tmp_quat.multLocal( 1.0f / tmp_quat.getW() );
1352//        store.x = ( ( tmp_quat.getX() + 1 ) * ( viewPortRight - viewPortLeft ) / 2 + viewPortLeft ) * getWidth();
1353//        store.y = ( ( tmp_quat.getY() + 1 ) * ( viewPortTop - viewPortBottom ) / 2 + viewPortBottom ) * getHeight();
1354//        store.z = ( tmp_quat.getZ() + 1 ) / 2;
1355//        vars.release();
1356
1357        float w = viewProjectionMatrix.multProj(worldPosition, store);
1358        store.divideLocal(w);
1359
1360        store.x = ((store.x + 1f) * (viewPortRight - viewPortLeft) / 2f + viewPortLeft) * getWidth();
1361        store.y = ((store.y + 1f) * (viewPortTop - viewPortBottom) / 2f + viewPortBottom) * getHeight();
1362        store.z = (store.z + 1f) / 2f;
1363
1364        return store;
1365    }
1366
1367    /**
1368     * @return the width/resolution of the display.
1369     */
1370    public int getWidth() {
1371        return width;
1372    }
1373
1374    /**
1375     * @return the height/resolution of the display.
1376     */
1377    public int getHeight() {
1378        return height;
1379    }
1380
1381    @Override
1382    public String toString() {
1383        return "Camera[location=" + location + "\n, direction=" + getDirection() + "\n"
1384                + "res=" + width + "x" + height + ", parallel=" + parallelProjection + "\n"
1385                + "near=" + frustumNear + ", far=" + frustumFar + "]";
1386    }
1387
1388    public void write(JmeExporter e) throws IOException {
1389        OutputCapsule capsule = e.getCapsule(this);
1390        capsule.write(location, "location", Vector3f.ZERO);
1391        capsule.write(rotation, "rotation", Quaternion.DIRECTION_Z);
1392        capsule.write(frustumNear, "frustumNear", 1);
1393        capsule.write(frustumFar, "frustumFar", 2);
1394        capsule.write(frustumLeft, "frustumLeft", -0.5f);
1395        capsule.write(frustumRight, "frustumRight", 0.5f);
1396        capsule.write(frustumTop, "frustumTop", 0.5f);
1397        capsule.write(frustumBottom, "frustumBottom", -0.5f);
1398        capsule.write(coeffLeft, "coeffLeft", new float[2]);
1399        capsule.write(coeffRight, "coeffRight", new float[2]);
1400        capsule.write(coeffBottom, "coeffBottom", new float[2]);
1401        capsule.write(coeffTop, "coeffTop", new float[2]);
1402        capsule.write(viewPortLeft, "viewPortLeft", 0);
1403        capsule.write(viewPortRight, "viewPortRight", 1);
1404        capsule.write(viewPortTop, "viewPortTop", 1);
1405        capsule.write(viewPortBottom, "viewPortBottom", 0);
1406        capsule.write(width, "width", 0);
1407        capsule.write(height, "height", 0);
1408        capsule.write(name, "name", null);
1409    }
1410
1411    public void read(JmeImporter e) throws IOException {
1412        InputCapsule capsule = e.getCapsule(this);
1413        location = (Vector3f) capsule.readSavable("location", Vector3f.ZERO.clone());
1414        rotation = (Quaternion) capsule.readSavable("rotation", Quaternion.DIRECTION_Z.clone());
1415        frustumNear = capsule.readFloat("frustumNear", 1);
1416        frustumFar = capsule.readFloat("frustumFar", 2);
1417        frustumLeft = capsule.readFloat("frustumLeft", -0.5f);
1418        frustumRight = capsule.readFloat("frustumRight", 0.5f);
1419        frustumTop = capsule.readFloat("frustumTop", 0.5f);
1420        frustumBottom = capsule.readFloat("frustumBottom", -0.5f);
1421        coeffLeft = capsule.readFloatArray("coeffLeft", new float[2]);
1422        coeffRight = capsule.readFloatArray("coeffRight", new float[2]);
1423        coeffBottom = capsule.readFloatArray("coeffBottom", new float[2]);
1424        coeffTop = capsule.readFloatArray("coeffTop", new float[2]);
1425        viewPortLeft = capsule.readFloat("viewPortLeft", 0);
1426        viewPortRight = capsule.readFloat("viewPortRight", 1);
1427        viewPortTop = capsule.readFloat("viewPortTop", 1);
1428        viewPortBottom = capsule.readFloat("viewPortBottom", 0);
1429        width = capsule.readInt("width", 1);
1430        height = capsule.readInt("height", 1);
1431        name = capsule.readString("name", null);
1432        onFrustumChange();
1433        onViewPortChange();
1434        onFrameChange();
1435    }
1436}
1437