/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.render; import java.awt.Dimension; import java.awt.Toolkit; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.jagatoo.input.devices.InputDeviceFactory; import org.jagatoo.input.devices.components.MouseButton; import org.jagatoo.input.render.Cursor; import org.openmali.types.twodee.ExtPositioned2i; import org.openmali.types.twodee.ExtSized2i; import org.openmali.types.twodee.Rect2i; import org.openmali.types.twodee.Sized2iRO; import org.openmali.types.twodee.util.RepositionListener2i; import org.openmali.types.twodee.util.ResizeListener2i; import org.openmali.vecmath2.Colorf; import org.openmali.vecmath2.Matrix4f; import org.openmali.vecmath2.Point2i; import org.openmali.vecmath2.Tuple2i; import org.openmali.vecmath2.Tuple3f; import org.openmali.vecmath2.Vector4f; import org.xith3d.picking.AllPickListener; import org.xith3d.picking.NearestPickListener; import org.xith3d.picking.PickEngine; import org.xith3d.picking.PickPool; import org.xith3d.picking.PickRequest; import org.xith3d.render.util.WindowClosingListener; import org.xith3d.scenegraph.GroupNode; import org.xith3d.scenegraph.View; import org.xith3d.utility.screenshots.ScreenshotEngine; /** * A Canvas3D offers a plane area where the scene is projectively rendered on. A * RenderPassConfigProvider is used to define some rendering parameters. This * can be the configuration of a RenderPass or a View. * * @author David Yazel * @author Marvin Froehlich (aka Qudus) * @author Kevin Finley (aka Horati) */ public class Canvas3D implements ExtPositioned2i, ExtSized2i, ScreenshotEngine, PickEngine { protected int oldWidth = -1; protected int oldHeight = -1; private CanvasPeer peer; private View view; private Colorf backgroundColor = new Colorf(); private List< ResizeListener2i > resizeListeners = new ArrayList< ResizeListener2i >(); private Renderer renderer = null; /** * Sets the Renderer, this Canvas3D is registered to. * * @param renderer */ protected final void setRenderer( Renderer renderer ) { this.renderer = renderer; } /** * @return the Renderer, this Canvas3D is registered to. */ public final Renderer getRenderer() { return ( renderer ); } /** * Not used! */ public void addRepositionListener( RepositionListener2i listener ) { } /** * Not used! */ public void removeRepositionListener( RepositionListener2i listener ) { } /** * {@inheritDoc} * Notification occurs in the Xith3D rendering thread and complies with * its threading rules. */ public void addResizeListener( ResizeListener2i listener ) { this.resizeListeners.add( listener ); } /** * {@inheritDoc} */ public void removeResizeListener( ResizeListener2i listener ) { this.resizeListeners.remove( listener ); } /** * Notifies any interested parties that this Canvas3D has been resized. * This method should not be called from the Renderer. */ protected void fireResizeEvent() { if ( this.resizeListeners.size() > 0 ) { final int width = getWidth(); final int height = getHeight(); for ( int i = 0; i < resizeListeners.size(); i++ ) { if ( ( oldWidth == -1 ) && ( oldHeight == -1 ) ) resizeListeners.get( i ).onObjectResized( this, width, height, width, height ); else resizeListeners.get( i ).onObjectResized( this, oldWidth, oldHeight, width, height ); } oldWidth = width; oldHeight = height; } } /** * Adds a new WindowClosingListener to be notified, when a non-fullscreen * Canvas3D's Window is to be closed by clicking the X-button. * * @param l */ public void addWindowClosingListener( WindowClosingListener l ) { getPeer().addWindowClosingListener( l ); } /** * Removes a WindowClosingListener. * * @param l */ public void removeWindowClosingListener( WindowClosingListener l ) { getPeer().removeWindowClosingListener( l ); } /** * Checks the Canvas3D for resizing. */ protected void checkForResized() { if ( ( getWidth() != oldWidth ) || ( getHeight() != oldHeight ) ) { fireResizeEvent(); } } /** * Assotiates a View with this Canvas3D. * * @param view */ public final void setView( View view ) { this.view = view; } /** * @return the assotiated View */ public final View getView() { return ( view ); } protected final void setPeer( CanvasPeer peer ) { this.peer = peer; if ( this.backgroundColor != null ) setBackgroundColor( backgroundColor ); } /** * @return the CanvasPeer, backing this Canvas3D. This is the connection * to the rendering system. */ public final CanvasPeer getPeer() { return ( peer ); } /** * @deprecated Use and see {@link #getPeer()}. */ @Deprecated public final CanvasPeer getCanvasPeer() { return ( getPeer() ); } /** * @return the containing window's title */ public final String getTitle() { return ( peer.getTitle() ); } /** * Sets the containing window's title * * @param title the new title */ public final void setTitle( String title ) { peer.setTitle( title ); } /** * Changes the window icon. * * @param iconResource the resource of the image for the new icon */ public final void setIcon( URL iconResource ) throws IOException { if ( peer == null ) throw new NullPointerException( "CanvasPeer not set" ); peer.setIcon( iconResource ); } /** * Clears the screen to BLACK. */ public final void clear() { if ( peer == null ) throw new NullPointerException( "CanvasPeer not set" ); peer.clear(); } /** * Sets the new Cursor for this Mouse. * Use <code>null</code> for an invisible Cursor. * Use {@link Cursor#DEFAULT_CURSOR} for the system's default Cursor. * * @param cursor */ public final void setCursor( Cursor cursor ) { getPeer().setCursor( cursor ); } public final Cursor getCursor() { return ( getPeer().getCursor() ); } /** * @return the InputDeviceFactory, that provides methods to retrieve * InputDevices for the specified implementation.<br> * * @see CanvasPeer#getInputDeviceFactory() */ public final InputDeviceFactory getInputDeviceFactory() { return ( getPeer().getInputDeviceFactory() ); } /** * @return The location of the Canvas3D */ public final Tuple2i getLocation() { return ( new Point2i( getLeft(), getTop() ) ); } /** * Sets the location of the Canvas3D * * @param left * @param top * * @return true, if the location actually has changed */ public final Canvas3D setLocation( int left, int top ) { peer.setLocation( left, top ); return ( this ); } /** * Adjusts this standalone canvas's location so that it is centered on the screen */ public void setCentered() { Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); int x = ( ( d.width - this.getWidth() ) / 2 ); int y = ( ( d.height - this.getHeight() ) / 2 ); setLocation( x, y ); } /** * Sets the location of the Canvas3D */ public final Canvas3D setLocation( Tuple2i loc ) { return ( setLocation( loc.getX(), loc.getY() ) ); } /** * @return The left position of the Canvas3D */ public final int getLeft() { return ( peer.getLeft() ); } /** * @return The top position of the Canvas3D */ public final int getTop() { return ( peer.getTop() ); } /** * @return The width of the Canvas3D */ public final Tuple2i getSize() { return ( new Point2i( peer.getWidth(), peer.getHeight() ) ); } /** * Sets the size of the Canvas3D * * @param width * @param height * * @return true, if the size actually has changed */ public final Canvas3D setSize( int width, int height ) { peer.setSize( width, height ); return ( this ); } /** * Sets the size of the Canvas3D * * @param size * * @return true, if the size actually has changed */ public final Canvas3D setSize( Sized2iRO size ) { return ( setSize( size.getWidth(), size.getHeight() ) ); } /** * Sets the size of the Canvas3D * * @param size * * @return true, if the size actually has changed */ public final Canvas3D setSize( Tuple2i size ) { return ( setSize( size.getX(), size.getY() ) ); } public final void setWidth( int width ) { setSize( width, getHeight() ); } public final void setHeight( int height ) { setSize( getWidth(), height ); } /** * @return The width of the Canvas3D */ public final int getWidth() { return ( peer.getWidth() ); } /** * @return The height of the Canvas3D */ public final int getHeight() { return ( peer.getHeight() ); } /** * @return The aspect ratio of the Canvas3D */ public final float getAspect() { return ( (float)getWidth() / (float)getHeight() ); } /** * Switches this Canvas3D's fullscreen flag.<br> * Depending on the implementation this flag may be applied * on the next render-frame, but not instantly. * * @param fullscreen * * @return the previous fullscreen flag. */ public final boolean setFullscreen( boolean fullscreen ) { final boolean oldState = isFullscreen(); if ( oldState == fullscreen ) return ( oldState ); peer.setFullscreen( fullscreen ); return ( oldState ); } /** * @return true, if the rendering is done in fullscreen mode */ public final boolean isFullscreen() { return ( peer.isFullscreen() ); } /** * @return true, if this Canvas'es window is undecorated. */ public final boolean isUndecorated() { return ( peer.isUndecorated() ); } /** * Sets this CanvasPeer's Viewport. * * @param viewport */ public final void setViewport( Rect2i viewport ) { peer.setViewport( viewport ); } /** * @return this CanvasPeer's Viewport */ public final Rect2i getViewport() { return ( peer.getViewport() ); } /** * Sets the background color for this Canvas3D (in fact for the CanvasPeer). * * @param red the red component of the new background color * @param green the green component of the new background color * @param blue the blue component of the new background color */ public final void setBackgroundColor( float red, float green, float blue ) { this.backgroundColor.set( red, green, blue ); getPeer().getRenderPeer().setClearColor( backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue(), backgroundColor.getAlpha() ); } /** * Sets the background color for this Canvas3D (in fact for the CanvasPeer). * * @param color the new background color */ public final void setBackgroundColor( Colorf color ) { assert ( color != null ); setBackgroundColor( color.getRed(), color.getGreen(), color.getBlue() ); } /** * @return the background color for this Canvas3D (in fact for the * CanvasPeer). */ public final Colorf getBackgroundColor() { return ( backgroundColor ); } /** * Sets this Canvas3D's RenderOptions */ public void setRenderOptions( RenderOptions ro ) { getPeer().getRenderPeer().setRenderOptions( ro ); } /** * @return this Canvas3D's RenderOptions */ public final RenderOptions getRenderOptions() { return ( getPeer().getRenderPeer().getRenderOptions() ); } /** * Enables or disables wireframe mode. * * @param enabled if true, wireframe mode will be enabled */ public void setWireframeMode( boolean enabled ) { getRenderOptions().setWireframeModeEnabled( enabled ); } /** * @return if wireframe mode is enabled or disabled. */ public boolean isWireframeMode() { return ( getRenderOptions().isWireframeModeEnabled() ); } /** * Switches wireframe mode. * * @return the new state. */ public boolean switchWireframeMode() { return ( getRenderOptions().switchWireframeMode() ); } /** * Enables lighting on this Canvas3D. */ public void enableLighting() { getRenderOptions().setLightingEnabled( true ); } /** * Disables lighting on this Canvas3D. */ public void disableLighting() { getRenderOptions().setLightingEnabled( false ); } /** * Changes the gamma-, brightness- and contrast values for this CanvasPeer. * * @param gamma the gamma value * @param brightness the brightness value [-1.0, +1.0] * @param contrast the contrast value [0, +1.0] */ public void setGamma( float gamma, float brightness, float contrast ) { getPeer().setGamma( gamma, brightness, contrast ); } /** * @return the current gamma value for this CanvasPeer. */ public float getGamma() { return ( getPeer().getGamma() ); } /** * @return the current brightness value for this CanvasPeer. */ public float getBrightness() { return ( getPeer().getBrightness() ); } /** * @return the current contrast value for this CanvasPeer. */ public float getContrast() { return ( getPeer().getContrast() ); } /** * Takes a screenshot of the current rendering * * @param file the file to save the screenshot to * @param alpha with alpha channel? */ public final void takeScreenshot( File file, boolean alpha ) { getPeer().takeScreenshot( file, alpha ); } /** * Takes a screenshot of the current rendering * * @param filenameBase the filenameBase to save the screenshot to (e.g. * "screens/shot") The current date and ".png" are appended. * @param alpha with alpha channel? * @return the file where the screenshot has been saved */ public final File takeScreenshot( String filenameBase, boolean alpha ) { return ( getPeer().takeScreenshot( filenameBase, alpha ) ); } /** * Takes a screenshot of the current rendering of the first added Canvas3D. * * @param alpha with alpha channel? * @return the file where the screenshot has been saved */ public final File takeScreenshot( boolean alpha ) { return ( getPeer().takeScreenshot( alpha ) ); } /** * Renders the scene in GLSelect mode. * * @param x the Canvas3D-relative x-component of the picking * @param y the Canvas3D-relative y-component of the picking * @param width with of the pick-ray * @param height height of the pick-ray * * @return a List of the results of this picking of the nearest result */ private final void pick( List< ? extends GroupNode > rootGroups, MouseButton button, int x, int y, int width, int height, Object pickListener, boolean pickAll, Object userObject ) { final List< RenderPass > renderPasses = getRenderer().getRenderPasses( ( (GroupNode)rootGroups.get( 0 ) ).getRoot() ); /* final PickRequest preq = PickHeap.allocatePickRequest( renderPasses.get( 0 ), rootGroups, this, button, x, y, pickListener, userObject, pickAll ); getRenderer().addPickRequest( preq ); */ for ( int i = 0; i < rootGroups.size(); i++ ) { final PickRequest preq = PickPool.allocatePickRequest( renderPasses.get( 0 ), (GroupNode)rootGroups.get( i ), this, button, x, y, pickListener, userObject, pickAll ); getRenderer().addPickRequest( preq ); } } /** * Renders the scene in GLSelect mode. * * @param x the Canvas3D-relative x-component of the picking * @param y the Canvas3D-relative y-component of the picking * @param width with of the pick-ray * @param height height of the pick-ray * * @return a List of the results of this picking of the nearest result */ private final void pick( GroupNode rootGroup, MouseButton button, int x, int y, int width, int height, Object pickListener, boolean pickAll, Object userObject ) { final List< RenderPass > renderPasses = getRenderer().getRenderPasses( rootGroup.getRoot() ); final PickRequest preq = PickPool.allocatePickRequest( renderPasses.get( 0 ), rootGroup, this, button, x, y, pickListener, userObject, pickAll ); getRenderer().addPickRequest( preq ); } public final void pickAll( List< ? extends GroupNode > groups, MouseButton button, int x, int y, int width, int height, AllPickListener pl, Object userObject ) { pick( groups, button, x, y, width, height, pl, true, userObject ); } public final void pickNearest( List< ? extends GroupNode > groups, MouseButton button, int x, int y, int width, int height, NearestPickListener pl, Object userObject ) { pick( groups, button, x, y, width, height, pl, false, userObject ); } /** * {@inheritDoc} */ public final void pickAll( List< ? extends GroupNode > groups, MouseButton button, int x, int y, AllPickListener pl, Object userObject ) { pick( groups, button, x, y, 1, 1, pl, true, userObject ); } /** * {@inheritDoc} */ public final void pickAll( List< ? extends GroupNode > groups, MouseButton button, int x, int y, AllPickListener pl ) { pick( groups, button, x, y, 1, 1, pl, true, (Object)null ); } /** * {@inheritDoc} */ public final void pickNearest( List< ? extends GroupNode > groups, MouseButton button, int x, int y, NearestPickListener pl, Object userObject ) { pick( groups, button, x, y, 1, 1, pl, false, userObject ); } /** * {@inheritDoc} */ public final void pickNearest( List< ? extends GroupNode > groups, MouseButton button, int x, int y, NearestPickListener pl ) { pick( groups, button, x, y, 1, 1, pl, false, (Object)null ); } /** * {@inheritDoc} */ public final void pickAll( GroupNode group, MouseButton button, int x, int y, AllPickListener pl, Object userObject ) { pick( group, button, x, y, 1, 1, pl, true, userObject ); } /** * {@inheritDoc} */ public final void pickAll( GroupNode group, MouseButton button, int x, int y, AllPickListener pl ) { pick( group, button, x, y, 1, 1, pl, true, (Object)null ); } /** * {@inheritDoc} */ public final void pickNearest( GroupNode group, MouseButton button, int x, int y, NearestPickListener pl, Object userObject ) { pick( group, button, x, y, 1, 1, pl, false, userObject ); } /** * {@inheritDoc} */ public final void pickNearest( GroupNode group, MouseButton button, int x, int y, NearestPickListener pl ) { pick( group, button, x, y, 1, 1, pl, false, (Object)null ); } /** * Calculates screen coordinates for the given world coordinates. * * @param world * @param screen */ public void worldToScreen( Tuple3f world, Tuple2i screen ) { Sized2iRO viewport = getViewport(); if ( viewport == null ) viewport = this; // get the model-view and the projection matrix Matrix4f mP = getView().calculatePerspective( viewport ).getMatrix4f(); Matrix4f mM = getView().getModelViewTransform( true ).getMatrix4f(); // convert the point into a vector of length 4 Vector4f v0 = Vector4f.fromPool(); v0.set( world.getX(), world.getY(), world.getZ(), 1f ); Vector4f v = Vector4f.fromPool(); // v' = P x M x v v.mul( mM, v0 ); v0.set( v ); v.mul( mP, v0 ); v.setW( ( 1f / v.getW() ) * 0.5f ); v.setX( v.getX() * v.getW() + 0.5f ); v.setY( v.getY() * v.getW() + 0.5f ); v.setZ( v.getZ() * v.getW() + 0.5f ); float xs = viewport.getWidth() * v.getX(); float ys = viewport.getHeight() - viewport.getHeight() * v.getY(); if ( getViewport() != null ) { xs += ( (Rect2i)viewport ).getLeft(); ys += ( (Rect2i)viewport ).getTop(); } screen.set( (int)Math.round( xs ), (int)Math.round( ys ) ); Vector4f.toPool( v ); Vector4f.toPool( v0 ); } protected Canvas3D( CanvasPeer peer, float backgroundColorR, float backgroundColorG, float backgroundColorB ) { assert ( peer != null ); this.peer = peer; peer.setCanvas3D( this ); setBackgroundColor( backgroundColorR, backgroundColorG, backgroundColorB ); } protected Canvas3D( CanvasPeer peer, Colorf backgroundColor ) { this( peer, backgroundColor.getRed(), backgroundColor.getGreen(), backgroundColor.getBlue() ); } protected Canvas3D( CanvasPeer peer ) { this( peer, 0.2f, 0.2f, 0.2f ); } }