/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package xenon3d;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.GLCapabilities;
import xenon3d.scene.RestrictedAccessException;
/**
* The Canvas3D class provides a drawing canvas for 3D rendering. It is used
* either for on-screen rendering or off-screen rendering.<p>
* NOTE: the rendering process automatically starts as soon as a the Canvas3D
* is attached to a Container. By default, the rendering is only done whenever a
* paint event for the Canvas3D 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 Canvas3D implements RenderListener {
// <editor-fold defaultstate="collapsed" desc=" Private Fields ">
/** The internal GraphicsConfiguration object. */
private GraphicsConfiguration gc;
/** The offScreen flag. */
private boolean offScreen;
/** The internal GLCapabilities object. */
private GLCapabilities caps;
/** The internal GLCanvas object. */
private GLCanvas canvas;
/** The internal View3D object. */
private View3D view;
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Initialization ">
/**
* Creates a new Canvas3D suitable for the current display settings. The
* following Canvas3D attributes are initialized to default values as shown:
* <ul>
* <li>Off-Screen: No (not supported)</li>
* <li>Stereo: No (not supported)</li>
* <li>Double Buffer: Yes (if available)</li>
* <li>Hardware Acceleration: Yes (if available)</li>
* <li>Scene Antialiasing: No</li>
* <li>Depth Buffering: Yes</li>
* </ul>
*/
public Canvas3D() {
this(null, false);
}
/**
* Creates a new Canvas3D that is optimized for the specified graphics
* configuration.<p>
* NOTE: the GraphicsConfiguration object should be created using a
* GraphicsConfigTemplate3D
* @param gc a valid GraphicsConfiguration object that will be used to
* create the canvas
*/
public Canvas3D(GraphicsConfiguration gc) {
this(gc, false);
}
/**
* Creates a new Canvas3D that is optimized for the specified graphics
* configuration. The GraphicsConfiguration object should be created using a
* GraphicsConfigTemplate3D.<p>
* Note that if offScreen is set to true, this Canvas3D object cannot be
* used for normal rendering; it should not be aattached to any Container
* object.
* @param gc a valid GraphicsConfiguration object that will be used to
* create the canvas
* @param offScreen a flag that indicates whether this canvas is an
* off-screen 3D rendering canvas
*/
public Canvas3D(GraphicsConfiguration gc, boolean offScreen) {
// TODO: Off-screen rendering
if (offScreen) throw new UnsupportedOperationException(Xenon3D.ERR_OFFSCREEN_CANVAS);
if (gc == GraphicsConfigTemplate3D.bestGC) caps = GraphicsConfigTemplate3D.bestCaps;
else {
if (gc == null) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
gc = gd.getDefaultConfiguration();
}
caps = new GLCapabilities(Xenon3D.getProfile());
caps.setDoubleBuffered(isDoubleBufferAvailable());
caps.setHardwareAccelerated(isHardwareAccelerationAvailable());
}
this.gc = gc;
this.offScreen = offScreen;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Public Properties (General) ">
/**
* Returns the attached View3D object, or null, if no view is attached.
* @return the attached View3D
*/
public View3D getView() {
return view;
}
/**
* Returns the 2D graphics object associated with this Canvas3D.
* @return a Graphics2D object that can be used for Java 2D rendering into
* this Canvas3D
*/
public Graphics2D getGraphics2D() {
if (canvas == null) return null;
return (Graphics2D) canvas.getGraphics();
}
/**
* Returns the parent container of this Canvas3D.
* @return the parent container, or null, if this Canvas3D is not attached
* to a container
*/
public final Container getParent() {
if (canvas == null) return null;
return canvas.getParent();
}
/**
* Gets the bounds rectangel of thic Canvas3D.
* @return the bounds as a Rectangle object, or null, if this Canvas3D is
* not attached to a container
*/
public final Rectangle getBounds() {
if (canvas == null) return null;
return canvas.getBounds();
}
/**
* Gets the size of this Canvas3D.
* @return the canvas size, or null, if this Canvas3D is not attached to
* a container
*/
public final Dimension getSize() {
if (canvas == null) return null;
return canvas.getSize();
}
/**
* Gets the location of this Canvas3D within its Container.
* @return the location, or null, if this Canvas3D is not attached to
* a container
*/
public final Point getLocation() {
if (canvas == null) return null;
return canvas.getLocation();
}
/**
* Gets the width of this Canvas3D.
* @return the width, or -1 if this Canvas3D is not attached to a container
*/
public final int getWidth() {
if (canvas == null) return -1;
return canvas.getWidth();
}
/**
* Gets the height of this Canvas3D.
* @return the height, or -1, if this Canvas3D is not attached to a container
*/
public final int getHeight() {
if (canvas == null) return -1;
return canvas.getHeight();
}
/**
* Gets the x coordinate of this Canvas3D.
* @return the x coordinate, or -1, if this Canvas3D is not attached to a
* container
*/
public final int getX() {
if (canvas == null) return -1;
return canvas.getX();
}
/**
* Gets the y coordinate of this Canvas3D.
* @return the y coordinate, or -1, if this Canvas3D is not attached to a
* container
*/
public final int getY() {
if (canvas == null) return -1;
return canvas.getY();
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Public Properties (3D) ">
/**
* Returns a status flag indicating whether or not double buffering is
* available.
* @return true, if double buffering is available
*/
public final boolean isDoubleBufferAvailable() {
return gc.getBufferCapabilities().isPageFlipping();
}
/**
* Returns a status flag indicating whether or not double buffering is
* enabled. Default: true.
* @return true, if double buffering is enabled
*/
public final boolean isDoubleBufferEnabled() {
return caps.getDoubleBuffered();
}
/**
* Sets whether or not double buffering is enabled. Default: true. If double
* buffering is off, all drawing is to the front buffer and no buffer swap
* is done between frames. Running Xenon3D with double buffering disabled is
* not recommended. Enabling double buffering on a Canvas3D that does not
* support double buffering has no effect.
* @param enable if true, double buffering will be enabled
* @throws IllegalStateException if the Canvas3D is already attached to a Container
*/
public final void setDoubleBufferEnabled(boolean enable) {
if (canvas != null) throw new RestrictedAccessException();
if (isDoubleBufferAvailable() || !enable) caps.setDoubleBuffered(enable);
}
/**
* Returns a status flag indicating whether or not scene antialiasing is
* enabled. Default: false.
* @return true, if scene antialiasing is enabled
*/
public final boolean isSceneAntialiasingEnabled() {
return caps.getSampleBuffers();
}
/**
* Sets whether or not scene antialiasing is enabled. Default: false.
* @param enable if true, scene antialiasing will be enabled
* @throws IllegalStateException if the Canvas3D is already attached to a Container
*/
public final void setSceneAntialiasingEnabled(boolean enable) {
if (canvas != null) throw new IllegalStateException(Xenon3D.ERR_CANVAS_ALREADY_ATTACHED);
caps.setSampleBuffers(enable);
}
/**
* Returns a status flag indicating whether or not stereo is available.
* @return if true, stereo rendering is available
*/
public final boolean isStereoAvailable() {
return gc.getBufferCapabilities().isMultiBufferAvailable();
}
/**
* Returns a status flag indicating whether or not stereo is enabled.
* Default: false.
* @return true, if stereo is enabled
*/
public final boolean isStereoEnabled() {
return caps.getStereo();
}
/**
* Sets whether or not stereo is enabled. Default: false. Note that this
* attribute is used only when stereo is available. Enabling stereo on a
* Canvas3D that does not support stereo has no effect.
* @param enable if true, stereo will be enabled
* @throws IllegalStateException if the Canvas3D is already attached to a Container
*/
public final void setStereoEnabled(boolean enable) {
if (canvas != null) throw new IllegalStateException(Xenon3D.ERR_CANVAS_ALREADY_ATTACHED);
if (isStereoAvailable() || !enable) caps.setStereo(enable);
}
/**
* Returns a status flag indicating whether or not hardware acceleration is
* available.
* @return if true, hardware acceleration is available
*/
public final boolean isHardwareAccelerationAvailable() {
return gc.getImageCapabilities().isAccelerated();
}
/**
* Returns a status flag indicating whether or not hardware acceleration is
* enabled. Default: true.
* @return true, if hardware acceleration is enabled
*/
public final boolean isHardwareAccelerationEnabled() {
return caps.getHardwareAccelerated();
}
/**
* Sets whether or not hardware acceleration is enabled. Default: true.
* Note that this attribute is used only when hardware acceleration is
* available. Enabling hardware acceleration on a Canvas3D that does not
* support it has no effect.
* @param enable if true, hardware acceleration will be enabled
* @throws IllegalStateException if the Canvas3D is already attached to a Container
*/
public final void setHardwareAccelerationEnabled(boolean enable) {
if (canvas != null) throw new IllegalStateException(Xenon3D.ERR_CANVAS_ALREADY_ATTACHED);
if (isHardwareAccelerationAvailable() || !enable) caps.setHardwareAccelerated(enable);
}
/**
* Returns a status flag indicating whether or not the depth buffer is
* enabled. Default: true.
* @return true, if the depth buffer is initially enabled
*/
public final boolean isDepthBufferEnabled() {
return caps.getDepthBits() > 0;
}
/**
* Sets whether or not the depth buffer is enabled. Default: true.<p>
* NOTE: the actual number of depth buffer bits that is used depends on
* the OpenGL implementation.
* @param enable if true, the depth buffer will be enabled
* @throws IllegalStateException if the Canvas3D is already attached to a Container
*/
public final void setDepthBufferEnabled(boolean enable) {
if (canvas != null) throw new IllegalStateException(Xenon3D.ERR_CANVAS_ALREADY_ATTACHED);
caps.setDepthBits(16);
}
/**
* Returns a status flag indicating whether or not the stencil buffer is
* enabled for this Canvas3D. Default: false.
* @return true, if the stencil buffer is enabled
*/
public final boolean isStencilBufferEnabled() {
return caps.getStencilBits() > 0;
}
/**
* Sets whether or not the stencil buffer will be enabled. Default: false.<p>
* NOTE: the actual number of stencil buffer bits that is used depends on
* the OpenGL implementation.
* @param enable if true, the stencil buffer will be enabled
* @throws IllegalStateException if the Canvas3D is already attached to a Container
*/
public final void setStencilBufferEnabled(boolean enable) {
if (canvas != null) throw new IllegalStateException(Xenon3D.ERR_CANVAS_ALREADY_ATTACHED);
int bits = enable ? 8 : 0;
caps.setStencilBits(bits);
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Public Methods (General) ">
/**
* Attaches the specified View3D object to this canvas.
* @param view the View3D to attach
*/
public void attachView(View3D view) {
if (view == null) throw new NullPointerException();
if (this.view != null) removeView();
view.addCanvas(this);
if (canvas != null) canvas.addGLEventListener(view.getInternalListener());
this.view = view;
}
/**
* Removes any attached View3D object from this Canvas3D.
*/
public void removeView() {
if (view == null) return;
if (canvas != null) canvas.removeGLEventListener(view.getInternalListener());
view.removeCanvas();
view = null;
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Public Methods (Render Listener) ">
/**
* Called immediately after the OpenGL context is initialized. Applications
* that wish to perform operations before the start of the rendering loop,
* may override this method.<p>
* NOTE: Applications should not call this method directly. It is called by
* the Xenon3D rendering system.
*/
public void init() {}
/**
* Called at the begin of the rendering loop, after clearing the canvas and
* before any rendering has been done for this frame. Applications that wish
* to perform operations in the rendering loop, prior to any actual
* rendering, may override this method.<p>
* NOTE: Applications should not call this method directly. It is called by
* the Xenon3D rendering system.
*/
public void preRender() {}
/**
* Called during the execution of the rendering loop, right after the solid
* rendering pass and before the transparent rendering pass. Furthermore, it
* is called once for each field (i.e., once per frame on a mono system or
* once each for the right eye and left eye on a two-pass stereo system.
* Applications that wish to perform operations during the rendering loop,
* may override this method.<p>
* NOTE: Applications should not call this method directly. It is called by
* the Xenon3D rendering system.
*/
public void render() {}
/**
* Called at the end of each rendering loop after completing all rendering
* to the canvas for this frame and before the buffer swap. Applications
* that wish to perform operations in the rendering loop, following any
* actual rendering may override this method.<p>
* NOTE: Applications should not call this method directly. It is called by
* the Xenon3D rendering system.
*/
public void postRender() {}
/**
* Called during the first repaint after the Canvas3D has been resized. The
* client can update certain settings appropriately. Note that for
* convenience the component has already called glViewport(x, y, width,
* height) when this method is called, so the client may not have to do
* anything in this method. Applications that wish to perform operations in
* case of the resizing of the Canvas3D may override this method.<p>
* NOTE: Applications should not call this method directly. It is called by
* the Xenon3D rendering system.
* @param width the new viewport width
* @param height the new viewport height
*/
public void reshape(int width, int height) {}
/**
* Called when the display mode or the display device associated with this
* Canvas3D has changed, or when the library is to shut down. Applications
* that wish to perform operations in one of these cases, may override this
* method.<p>
* NOTE: Applications should not call this method directly. It is called by
* the Xenon3D rendering system.
*/
public void dispose() {}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" Package Private Methods ">
/**
* Utility method to track when this Canvas3D is added to a container.
* @param c the container to which to add the canvas
* @throws IllegalStateException if the Canvas3D is already attached to a Container
*/
void addNotify(Container c) {
if (canvas != null) throw new IllegalStateException(Xenon3D.ERR_CANVAS_ALREADY_ATTACHED);
canvas = new GLCanvas(caps);
if (view != null) canvas.addGLEventListener(view.getInternalListener());
c.add(canvas);
}
/**
* Utility method to track when this Canvas3D is removed from its container.
* @param c the container from which to remove the canvas
*/
void removeNotify(Container c) {
if (canvas == null) return;
if (view != null) canvas.removeGLEventListener(view.getInternalListener());
c.remove(canvas);
canvas = null;
}
// </editor-fold>
} // end class Canvas3D