/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package xenon3d;
import javax.media.opengl.DebugGL2;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.TraceGL2;
import javax.media.opengl.glu.GLU;
import xenon3d.vector.Color3f;
/**
* The View3D object manages all parameters and settings needed for rendering a
* three dimensional scene from one viewpoint. A view maintains a link to a
* Canvas3D that the view is rendered into. It exists outside of the scene graph,
* but attaches to a camera node object in the scene graph.<p>
* The View3D object is the main object for controlling the Xenon3D viewing model.
* All of the components that specify the view transform used to render to the
* Canvas3D are either contained in the View3D object or in objects that are
* referenced by it.<p>
* The View3D object has several propertes and methods, but most are calibration
* variables or user-helper functions. Most properties are affected by the Jogl
* rendering thread. Therefore, the setting of most properties can only be done
* from the Jogl rendering thread, in other words: from one of the callback
* methods of the RenderListener interface. Settings may be retrieved from any
* thread, though.<p>
* Note that the rendering process automatically starts as soon as a the View3D is
* attached to the Xenon3D object. By default, the rendering is only done
* whenever a paint event for the canvas occours. For most 3D applications, it
* will be more appropriate for the rendering to occour as fast as possible or
* at fixed time intervalls. Use the start() and stop() methods to turn fast
* rendering on or off.
*
* @author Volker Everts
* @version 0.1 - 13.08.2011: Created
*/
public class View3D {
// <editor-fold defaultstate="collapsed" desc=" Private Fields ">
// ----- Rendering -----
/** The Canvas3D to which this view is attached. */
private Canvas3D canvas;
/** The internal GLEventListener object. */
private GLListener internalListener;
/** The internal frame count. */
private int frameCount;
/** The OpenGL clear flags. */
private int clearFlags = GL2.GL_COLOR_BUFFER_BIT;
/** The OpenGL background clear color. */
private Color3f color;
/** The OpenGL depth buffering flag. */
private boolean depthBuffer;
// ----- Viewport and Projection -----
/** The viewport width. */
private int vpWidth;
/** The viewport height. */
private int vpHeight;
/** The aspect ratio. */
private float aspect = 1.0f;
/** The field of view. */
private float fovy = 45.0f;
/** The near clipping distance. */
private float near = 0.1f;
/** The far clipping distance. */
private float far = 100.0f;
/** The left clipping coordinate. */
private float left = -1.0f;
/** The right clipping coordinate. */
private float right = 1.0f;
/** The top clipping coordinate. */
private float top = 1.0f;
/** The bottom clipping coordinate. */
private float bottom = -1.0f;
/** The scale factor. */
private float scale = 1.0f;
// ----- Change Flags -----
/** The view mode change flag. */
private static final int CF_VIEW_MODE = 1;
/** The clear color change flag. */
private static final int CF_CLEAR_COLOR = 2;
/** The depth buffer change flag. */
private static final int CF_DEPTH_BUFFER = 4;
/** The projection change flag. */
private static final int CF_PROJECTION = 8;
/** The change flags variable. */
private int changeFlags = 0;
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" View3D Policies ">
// ----- The View3D Mode Policy -----
/**
* The view mode policy enumeration.
*/
public enum ViewModePolicy {
/** The release view mode policy, uses the default GL object. */
Release,
/** The debug view mode policy, uses the DebugGL object. */
Debug,
/** The trace view mode policy, uses the TraceGL object. */
Trace;
} // end enum ViewModePolicy
/** The current ViewModePolicy, one of Release, Debug, or Trace. */
private ViewModePolicy mode = ViewModePolicy.Release;
/**
* Returns the ViewModePolicy, one of Release, Debug or Trace.
* Default: Release.
* @return the current ViewModePolicy
*/
public ViewModePolicy getViewModePolicy() {
return mode;
}
/**
* Sets a new ViewModePolicy for this view. Default: Release<p>
* NOTE: this method call will enforce the new view mode for the next
* rendered frame.
* @param policy the new ViewModePolicy
*/
public void setViewModePolicy(ViewModePolicy policy) {
if (policy == null) throw new NullPointerException();
if (policy != mode) {
mode = policy;
changeFlags |= CF_VIEW_MODE;
}
}
// ----- The Projection Policy -----
/**
* The projection policy enumeration.
*/
public enum ProjectionPolicy {
/** The perspective projection policy. */
Perspective,
/** The parallel projection policy. */
Parallel,
/** The frustum based perspective projection policy. */
Frustum;
} // end enum ProjectionPolicy
/** The current projection policy, one of Perspective, Parallel, or Frustum. */
private ProjectionPolicy projection = ProjectionPolicy.Perspective;
/**
* Returns the ProjectionPolicy, one of Perspective, Parallel, or
* Frustum.
* @return the current ProjectionPolicy
*/
public ProjectionPolicy getProjectionPolicy() {
return projection;
}
/**
* Sets a new ProjectionPolicy for this view.<p>
* NOTE: this method call will force the recalculation of the current
* projection matrix at the start of the next rendered frame.
* @param policy the new ProjectionPolicy
*/
public void setProjectionPolicy(ProjectionPolicy policy) {
if (policy == null) throw new IllegalArgumentException();
if (policy != projection) {
projection = policy;
changeFlags |= CF_PROJECTION;
}
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Initialization ">
/**
* Creates a new View3D with default render capabilities.
*/
public View3D() {}
/**
* Package private method that returns the internal GLEventListener object
* of this View3D.
* @return the internal GLEventListener, or null, if the view is not attached
* to a Canvas3D object
*/
GLEventListener getInternalListener() {
return internalListener;
}
/**
* Package private method to set the Canvas3D object for this view.
* @param the Canvas3D to which this view will be attached
*/
void addCanvas(Canvas3D canvas) {
if (canvas == null) throw new NullPointerException();
if (this.canvas != null) throw new IllegalStateException(Xenon3D.ERR_VIEW_ALREADY_ATTACHED);
internalListener = new GLListener(canvas);
this.canvas = canvas;
}
/**
* Package private method to remove the view's Canvas3D object.
*/
void removeCanvas() {
if (canvas == null) return;
internalListener = null;
canvas = null;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Public Properties ">
// ----- Rendering -----
/**
* Returns the frame number.
* @return the number of the current frame
*/
public int getFrameNumber() {
return frameCount;
}
/**
* Returns the background clear color.
* @return the current background clear color
*/
public Color3f getClearColor() {
return color;
}
/**
* Sets a new background clear color. If the new color is null, the color
* buffer will not be cleared at the start of each frame.<p>
* NOTE: this method call will enforce the new clear color for the next
* rendered frame.
* @param color the new clear color
*/
public void setClearColor(Color3f color) {
if (color == this.color) return;
this.color = color;
changeFlags |= CF_CLEAR_COLOR;
}
/**
* Gets a flag indicating whether or not depth buffering is enabled for this
* view.
* @return true, if depth buffering is enabled
*/
public boolean getDepthBufferEnable() {
return depthBuffer;
}
/**
* Sets a flag indicating whether or not depth buffering will be enabled for
* this view.<p>
* NOTE: this method call will enforce the new depth buffering setting for
* the next rendered frame.
* @param enable if true, depth buffering will be enabled
*/
public void setDepthBufferEnable(boolean enable) {
if (enable == depthBuffer) return;
depthBuffer = enable;
changeFlags |= CF_DEPTH_BUFFER;
}
// ----- Viewport and Projection -----
/**
* Returns the viewport width.
* @return the current viewport width in pixels
*/
public int getViewportWidth() {
return vpWidth;
}
/**
* Returns the viewport height.
* @return the current viewport height in pixels
*/
public int getViewportHeight() {
return vpHeight;
}
/**
* Returns the aspect ratio of the viewport.
* @return the current aspect ratio
*/
public float getViewportAspectRatio() {
return aspect;
}
/**
* Returns the vertical field of view.
* @return the current vertical field of view in degrees
*/
public float getFieldOfViewY() {
return fovy;
}
/**
* Sets a new vertical field of view.<p>
* NOTE: this method call will force the recalculation of the current
* projection matrix at the start of the next rendered frame if and only
* if the current ProjectionPolicy is ProjectionPolicy.Perspective.
* @param fovy the new vertical field of view
*/
public void setFieldOfViewY(float fovy) {
if (fovy == this.fovy) return;
if (fovy <= 0.0f) throw new IllegalArgumentException();
this.fovy = fovy;
if (projection == ProjectionPolicy.Perspective) changeFlags |= CF_PROJECTION;
}
/**
* Returns the near clipping distance.
* @return the current near clipping distance
*/
public float getNearClippingDistance() {
return near;
}
/**
* Sets a new near clipping distance.<p>
* NOTE: this method call will force the recalculation of the current
* projection matrix at the start of the next rendered frame.
* @param near the new near clipping distance
*/
public void setNearClippingDistance(float near) {
if (near == this.near) return;
this.near = near;
changeFlags |= CF_PROJECTION;
}
/**
* Returns the far clipping distance.
* @return the current far clipping distance
*/
public float getFarClippingDistance() {
return far;
}
/**
* Sets a new far clipping distance.<p>
* NOTE: this method call will force the recalculation of the current
* projection matrix at the start of the next rendered frame.
* @param far the new far clipping distance
*/
public void setFarClippingDistance(float far) {
if (far == this.far) return;
this.far = far;
changeFlags |= CF_PROJECTION;
}
/**
* Returns the left clipping coordinate.
* @return the current left clipping coordinate
*/
public float getLeftClipCoord() {
return left;
}
/**
* Returns the right clipping coordinate.
* @return the current right clipping coordinate
*/
public float getRightClipCoord() {
return right;
}
/**
* Returns the top clipping coordinate.
* @return the current top clipping coordinate
*/
public float getTopClipCoord() {
return top;
}
/**
* Returns the bottom clipping coordinate.
* @return the current bottom clipping coordinate
*/
public float getBottomClipCoord() {
return bottom;
}
/**
* Returns the scale factor.
* @return the current scale factor
*/
public float getScale() {
return scale;
}
/**
* Sets a new scale factor.<p>
* NOTE: this method call will force the recalculation of the current
* projection matrix at the start of the next rendered frame.
* @param scale the new scale factor
*/
public void setScale(float scale) {
if (scale == this.scale) return;
if (scale <= 0.0f) throw new IllegalArgumentException();
this.scale = scale;
changeFlags |= CF_PROJECTION;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Public Methods ">
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Local Classes ">
/**
* Base class for the GraphicsContext3D singleton; provides access to the
* current GL context.<p>
* NOTE: This class should not used directly by the API user.
*/
public static class GLContext3D {
/** The internal GL object. */
protected static GL2 gl;
/** The internal GLU object. */
protected static GLU glu;
/** The internal delta time. */
protected static float time;
/**
* Package private contructor for helper class.
*/
protected GLContext3D() {}
/**
* Returns the internal GL object.
* @return the current GL object
*/
public GL2 getGL() {
return gl;
}
/**
* Returns the internal GLU object.
* @return the current GLU object
*/
public GLU getGLU() {
return glu;
}
/**
* Returns the delta time for the current frame.
* @return the current delta time
*/
public float getDeltaTime() {
return time;
}
} // end class Context3D
/**
* An implementation of JOGL's GLEventListener interface.
*/
private class GLListener extends GLContext3D implements GLEventListener {
// ----- Private Fields -----
/** The Canvas3D to which this view is attached. */
private Canvas3D canvas;
/** The default GL object, used for resetting the view to Release mode. */
private GL2 releaseGL;
/** The current GLAutoDrawable object. */
private GLAutoDrawable drawable;
/** The last nano timer value. */
private long lastTime;
// ----- Initialization -----
GLListener(Canvas3D canvas) {
this.canvas = canvas;
}
// ----- Implementation GLEventListener -----
/**
* Called by the drawable immediately after the OpenGL context is initialized.
* @param drawable the GLCanvas object
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
@Override
public void init(GLAutoDrawable drawable) {
// Initial code
this.drawable = drawable;
gl = (GL2) drawable.getGL();
glu = new GLU();
releaseGL = gl;
if (mode != ViewModePolicy.Release) System.out.println("** init() **");
// Check property flags
if (changeFlags != 0) check();
// Reset delta time
lastTime = System.nanoTime();
time = 0.0f;
// Initialize render settings
gl.glEnable(GL2.GL_TEXTURE_2D);
frameCount = 1;
// Notify Canvas3D
canvas.init();
// Reset gl object
gl = null;
}
/**
* Called by the drawable to initiate OpenGL rendering by the client.
* @param drawable the GLCanvas object
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
@Override
public void display(GLAutoDrawable drawable) {
// Initial code
gl = (GL2) drawable.getGL();
if (mode != ViewModePolicy.Release) {
if (mode == ViewModePolicy.Trace || mode == ViewModePolicy.Debug && frameCount == 1) System.out.println("** display() **");
}
// Check change flags
if (changeFlags != 0) check();
// Get delta time
long t = System.nanoTime();
double delta = ((t - lastTime) / 1000000000.0);
lastTime = t;
time = (float) delta;
// Initialize a new frame
gl.glClear(clearFlags);
gl.glLoadIdentity();
frameCount++;
// Notify Canvas3D
canvas.preRender();
canvas.render();
canvas.postRender();
// Reset gl object
gl = null;
}
/**
* Called by the drawable during the first repaint after the component
* has been resized.
* @param drawable the GLCanvas object
* @param x the x coordinate of the new viewport position, usually zero
* @param y the y coordinate of the new viewport position, usually zero
* @param width the new viewport width
* @param height the new viewport height
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
@Override
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
// Initial code
gl = (GL2) drawable.getGL();
if (mode != ViewModePolicy.Release) System.out.println("** reshape(" + width + ", " + height + ") **");
// Check change flags
if (changeFlags != 0) check();
// Calculate new projection settings based on the new viewport size
vpWidth = width;
vpHeight = height == 0 ? 1 : height;
aspect = (float) width / (float) height;
float factor = (float) vpHeight * scale;
right = aspect;
top = 1;
left = -right;
bottom = -top;
// Change projection
changeProjection();
// Do more tracing, if not in release mode
if (mode != ViewModePolicy.Release) {
System.out.println(" Fovy: " + fovy);
System.out.println(" Aspect: " + aspect);
System.out.println(" Scale: " + scale);
System.out.println(" Left: " + left);
System.out.println(" Right: " + right);
System.out.println(" Bottom: " + bottom);
System.out.println(" Top: " + top);
System.out.println(" Near: " + near);
System.out.println(" Far: " + far);
}
// Notify Canvas3D
canvas.reshape(width, height);
// Reset gl object
gl = null;
}
/**
* Called by the drawable when the display mode or the display device
* associated with the GLAutoDrawable has changed.
* @param drawable the GLCanvas object
* @param modeChanged indicates whether the display mode has changed
* @param deviceChanged indicates whether the device has changed
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
@Override
public void dispose(GLAutoDrawable drawable) {
// Initial code
gl = (GL2) drawable.getGL();
if (mode != ViewModePolicy.Release) System.out.println("** dispose() **");
// Check cange flags
if (changeFlags != 0) check();
// Notify Canvas3D
canvas.dispose();
// Reset gl object
gl = null;
}
// ----- Private Helper Methods -----
/**
* Check for changed view properties, if so, apply the changes.
*/
private void check() {
int cf = changeFlags;
changeFlags = 0;
if ((cf & CF_VIEW_MODE) != 0) changeMode();
if ((cf & CF_CLEAR_COLOR) != 0) changeClearColor();
if ((cf & CF_DEPTH_BUFFER) != 0) changeDepthBuffering();
if ((cf & CF_PROJECTION) != 0) changeProjection();
}
/**
* Changes the view mode policy.
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
private void changeMode() {
switch (mode) {
case Release:
gl = releaseGL;
break;
case Debug:
gl = new DebugGL2(releaseGL);
break;
case Trace:
gl = new TraceGL2(releaseGL, System.out);
break;
}
drawable.setGL(gl);
}
/**
* Changes the OpenGL background clear color.
*/
private void changeClearColor() {
if (color == null) clearFlags &= ~GL2.GL_COLOR_BUFFER_BIT;
else {
clearFlags |= GL2.GL_COLOR_BUFFER_BIT;
gl.glClearColor(color.r, color.g, color.b, 0.0f);
}
}
/**
* Changes the OpenGL depth buffering flag.
*/
private void changeDepthBuffering() {
if (depthBuffer) {
gl.glEnable(GL2.GL_DEPTH_TEST);
clearFlags |= GL2.GL_DEPTH_BUFFER_BIT;
}
else {
gl.glDisable(GL2.GL_DEPTH_TEST);
clearFlags &= ~GL2.GL_DEPTH_BUFFER_BIT;
}
}
/**
* Changes the projection policy.
*/
private void changeProjection() {
gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
switch (projection) {
case Perspective:
glu.gluPerspective(fovy / scale, aspect, near, far);
break;
case Parallel:
gl.glOrtho(left / scale, right / scale, bottom / scale, top / scale, near, far);
break;
case Frustum:
gl.glFrustum(left / scale, right / scale, bottom / scale, top / scale, near, far);
break;
}
gl.glMatrixMode(GL2.GL_MODELVIEW);
}
} // end class GLListener
// </editor-fold>
} // end class View3D