Revision: 492 http://skycastle.svn.sourceforge.net/skycastle/?rev=492&view=rev Author: zzorn Date: 2008-04-28 00:19:44 -0700 (Mon, 28 Apr 2008) Log Message: ----------- Fixed projection! Yay. Modified Paths: -------------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java Added Paths: ----------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeRenderer.java Removed Paths: ------------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java 2008-04-27 23:20:35 UTC (rev 491) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java 2008-04-28 07:19:44 UTC (rev 492) @@ -14,6 +14,7 @@ import org.skycastle.sketch.model.group.Group; import org.skycastle.sketch.model.group.GroupElement; import org.skycastle.sketch.model.stroke.Stroke; +import org.skycastle.sketch.rendering.StrokeRenderer; import org.skycastle.util.ParameterChecker; import org.skycastle.util.listenable.collection.CollectionListener; import org.skycastle.util.listenable.collection.ListenableCollection; @@ -47,6 +48,49 @@ private final Node myRoot3DNode; private final PenManager myPenManager; + private final Object myProjectionLock = new Object(); + + private final List<ProjectionChangeListener> myProjectionChangeListeners = new ArrayList<ProjectionChangeListener>(); + + private final ProjectionChangeListener myProjectionChangeListenerDelegate = new ProjectionChangeListener() + { + + public void onProjectionChanged( final float xOffs, + final float yOffs, + final float xScale, + final float yScale ) + { + for ( final ProjectionChangeListener listener : myProjectionChangeListeners ) + { + listener.onProjectionChanged( xOffs, yOffs, xScale, yScale ); + } + } + + }; + + private final ResizeHandler myResizeHandler = new ResizeHandler() + { + + public void onScreenResized( final float newCanvasWidth, + final float newCanvasHeight, + final float canvasAspectRatio, + final Camera camera ) + { + updateCameraSyncronized(); + + SwingUtilities.invokeLater( new Runnable() + { + + public void run() + { + notifyListenerAboutProjectionChange( myProjectionChangeListenerDelegate ); + } + + } ); + } + + }; + private Canvas3D myCanvas3D; private JToolBar myToolBar; private JMenuBar myMenuBar; @@ -97,27 +141,13 @@ myPenManager = new PenManager( myCanvas3D.get3DView() ); - myCanvas3D.setResizeHandler( createResizeHandler() ); + myCanvas3D.setResizeHandler( myResizeHandler ); // Make all popup menus and tooltips heavyweight so that they can overlap the 3D view JPopupMenu.setDefaultLightWeightPopupEnabled( false ); ToolTipManager.sharedInstance().setLightWeightPopupEnabled( false ); } - private ResizeHandler createResizeHandler() - { - return new ResizeHandler() - { - public void onScreenResized( final float newCanvasWidth, - final float newCanvasHeight, - final float canvasAspectRatio, - final Camera camera ) - { - // TODO - } - }; - } - //---------------------------------------------------------------------- // Other Public Methods @@ -190,10 +220,13 @@ ParameterChecker.checkPositiveNonZeroNormalNumber( viewSizeHorizontally, "viewSizeHorizontally" ); ParameterChecker.checkNormalNumber( rotationRadians, "rotationRadians" ); - myCameraX = cameraX; - myCameraY = cameraY; - myViewSizeHorizontally = viewSizeHorizontally; - myRotationRadians = rotationRadians; + synchronized ( myProjectionLock ) + { + myCameraX = cameraX; + myCameraY = cameraY; + myViewSizeHorizontally = viewSizeHorizontally; + myRotationRadians = rotationRadians; + } if ( myCanvas3D.getCamera() != null && myCanvas3D.isInitialized() ) @@ -217,22 +250,7 @@ notifyListenerAboutProjectionChange( myProjectionChangeListenerDelegate ); } - private void notifyListenerAboutProjectionChange( final ProjectionChangeListener listener ) - { - final Component view = myCanvas3D.get3DView(); - if ( view != null ) - { - final float width = view.getWidth(); - final float height = view.getHeight(); - final float scale = myViewSizeHorizontally / width; - listener.onProjectionChanged( myCameraX - 0.5f * width * scale, - myCameraY - 0.5f * height * scale, - scale, - scale ); - } - } - /** * Rotates the camera. * @@ -356,9 +374,62 @@ } ); } + /** + * @param listener a listener that is notified whenever the projection changes. + */ + public void addProjectionChangeListener( final ProjectionChangeListener listener ) + { + ParameterChecker.checkNotNull( listener, "listener" ); + ParameterChecker.checkNotAlreadyContained( listener, + myProjectionChangeListeners, + "myProjectionChangeListeners" ); + + myProjectionChangeListeners.add( listener ); + + notifyListenerAboutProjectionChange( listener ); + } + + /** + * Removes the specified ProjectionChangeListener. + * + * @param listener should not be null, and should be present. + */ + public void removeProjectionChangeListener( final ProjectionChangeListener listener ) + { + ParameterChecker.checkNotNull( listener, "listener" ); + ParameterChecker.checkContained( listener, + myProjectionChangeListeners, + "myProjectionChangeListeners" ); + + myProjectionChangeListeners.remove( listener ); + } + //====================================================================== // Private Methods + private void updateCameraSyncronized() + { + synchronized ( myProjectionLock ) + { + updateCamera( myCanvas3D, myCameraX, myCameraY, myViewSizeHorizontally, myRotationRadians ); + } + } + + private void notifyListenerAboutProjectionChange( final ProjectionChangeListener listener ) + { + final Component view = myCanvas3D.get3DView(); + if ( view != null ) + { + final float width = view.getWidth(); + final float height = view.getHeight(); + final float scale = myViewSizeHorizontally / width; + listener.onProjectionChanged( myCameraX - 0.5f * width * scale, + myCameraY - 0.5f * height * scale, + scale, + scale ); + } + } + private void updateCamera( final Canvas3D canvas3D, final float cameraX, final float cameraY, @@ -445,51 +516,4 @@ return menu; } - private final List<ProjectionChangeListener> myProjectionChangeListeners = new ArrayList<ProjectionChangeListener>(); - - private final ProjectionChangeListener myProjectionChangeListenerDelegate = new ProjectionChangeListener() - { - public void onProjectionChanged( final float xOffs, - final float yOffs, - final float xScale, - final float yScale ) - { - for ( final ProjectionChangeListener listener : myProjectionChangeListeners ) - { - listener.onProjectionChanged( xOffs, yOffs, xScale, yScale ); - } - } - }; - - /** - * @param listener a listener that is notified whenever the projection changes. - */ - public void addProjectionChangeListener( final ProjectionChangeListener listener ) - { - ParameterChecker.checkNotNull( listener, "listener" ); - ParameterChecker.checkNotAlreadyContained( listener, - myProjectionChangeListeners, - "myProjectionChangeListeners" ); - - myProjectionChangeListeners.add( listener ); - - notifyListenerAboutProjectionChange( listener ); - } - - /** - * Removes the specified ProjectionChangeListener. - * - * @param listener should not be null, and should be present. - */ - public void removeProjectionChangeListener( final ProjectionChangeListener listener ) - { - ParameterChecker.checkNotNull( listener, "listener" ); - ParameterChecker.checkContained( listener, - myProjectionChangeListeners, - "myProjectionChangeListeners" ); - - myProjectionChangeListeners.remove( listener ); - } - - } Deleted: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java 2008-04-27 23:20:35 UTC (rev 491) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java 2008-04-28 07:19:44 UTC (rev 492) @@ -1,508 +0,0 @@ -package org.skycastle.sketch; - -import com.jme.bounding.BoundingBox; -import com.jme.image.Image; -import com.jme.image.Texture; -import com.jme.math.Vector2f; -import com.jme.math.Vector3f; -import com.jme.renderer.ColorRGBA; -import com.jme.renderer.Renderer; -import com.jme.scene.TriMesh; -import com.jme.scene.state.AlphaState; -import com.jme.scene.state.TextureState; -import com.jme.system.DisplaySystem; -import com.jme.util.GameTaskQueue; -import com.jme.util.GameTaskQueueManager; -import com.jme.util.TextureManager; -import com.jme.util.geom.BufferUtils; -import com.jmex.awt.swingui.ImageGraphics; -import org.skycastle.sketch.model.stroke.Stroke; -import org.skycastle.sketch.sample.DataSample; -import org.skycastle.util.ImageUtils; -import org.skycastle.util.ParameterChecker; - -import java.awt.image.BufferedImage; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.util.concurrent.Callable; - -/** - * A 3D node that renders a {@link Stroke}. - * - * @author Hans Häggström - */ -@SuppressWarnings( { "serial" } ) -public final class StrokeRenderer - extends TriMesh -{ - - //====================================================================== - // Private Fields - - private static int theStrokeMeshCounter = 0; - - private final Stroke myStroke; - - private final float myZ; - private final FloatBuffer myVertexes; - private final FloatBuffer myColors; - private final FloatBuffer myTextureCoordinates; - private final FloatBuffer myNormals; - private final IntBuffer myIndices; - private final int myNumberOfVertices; - private final int myNumberOfIndices; - - private final Object myTextureStateLock = new Object(); - private final int myNumberOfPoints; - private final int myNumberOfSegments; - private final int myNumberOfTriangles; - - private TextureState myTextureState; - private Texture myTexture; - private ImageGraphics myTextureGraphics; - - private TextureState myPlaceholderTextureState = null; - - private boolean myPlaceholderTextureInUse = false; - - //====================================================================== - // Private Constants - - private static final BufferedImage PLACEHOLDER_PICTURE = ImageUtils.createPlaceholderPicture( 128, 128 ); - private static final float DEFAULT_ANISO_LEVEL = 1.0f; - private static final int DEFAULT_TEXTURE_IMAGE_FORMAT = com.jme.image.Image.GUESS_FORMAT_NO_S3TC; - private static final Texture PLACEHOLDER_TEXTURE = TextureManager.loadTexture( PLACEHOLDER_PICTURE, - Texture.MM_LINEAR_LINEAR, - Texture.FM_LINEAR, - 1, - Image.GUESS_FORMAT_NO_S3TC, - false ); - private static final int NUMBER_OF_TRIANGLES_PER_SEGMENT = 4; - private static final int NUMBER_OF_INDICES_IN_SEGMENT = 3 * NUMBER_OF_TRIANGLES_PER_SEGMENT; - - //====================================================================== - // Public Methods - - //---------------------------------------------------------------------- - // Constructors - - /** - * Creates a new {@link org.skycastle.sketch.StrokeRenderer}. - * - * @param stroke the stroke that should be rendered - */ - public StrokeRenderer( final Stroke stroke ) - { - this( stroke, 0 ); - } - - - /** - * Creates a new {@link org.skycastle.sketch.StrokeRenderer}. - * - * @param stroke the stroke that should be rendered - * @param z the default height of the stroke. - */ - public StrokeRenderer( final Stroke stroke, final float z ) - { - // JME seems to need an unique identifier for each node. NOTE: Not thread safe. - //noinspection AssignmentToStaticFieldFromInstanceMethod - super( "StrokeMesh_" + theStrokeMeshCounter++ ); - - // Check parameters - ParameterChecker.checkNotNull( stroke, "stroke" ); - ParameterChecker.checkNormalNumber( z, "z" ); - - myStroke = stroke; - myZ = z; - - // Calculate sizes - myNumberOfPoints = myStroke.getPoints().size(); - myNumberOfSegments = myNumberOfPoints - 1; - myNumberOfTriangles = myNumberOfSegments * NUMBER_OF_TRIANGLES_PER_SEGMENT; - myNumberOfVertices = myNumberOfPoints * 3; - myNumberOfIndices = myNumberOfSegments * NUMBER_OF_INDICES_IN_SEGMENT; - - // Create databuffers - myVertexes = BufferUtils.createVector3Buffer( myNumberOfVertices ); - myColors = BufferUtils.createColorBuffer( myNumberOfVertices ); - myTextureCoordinates = BufferUtils.createVector2Buffer( myNumberOfVertices ); - myNormals = BufferUtils.createVector3Buffer( myNumberOfVertices ); - myIndices = BufferUtils.createIntBuffer( myNumberOfIndices ); - - // Stich together the vertices into triangles - initializeIndices(); - - - updateBounds(); - - - GameTaskQueueManager.getManager().getQueue( GameTaskQueue.UPDATE ).enqueue( new Callable<Object>() - { - - public Object call() throws Exception - { - final Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer(); - - final StrokeRenderer strokeRenderer = StrokeRenderer.this; - - // strokeRenderer.setRenderQueueMode( Renderer.QUEUE_TRANSPARENT ); - final AlphaState alphaState = renderer.createAlphaState(); - alphaState.setBlendEnabled( true ); - alphaState.setSrcFunction( AlphaState.SB_SRC_ALPHA ); - alphaState.setDstFunction( AlphaState.DB_ONE_MINUS_SRC_ALPHA ); - alphaState.setTestEnabled( false ); - alphaState.setEnabled( true ); - - strokeRenderer.setRenderState( alphaState ); - strokeRenderer.updateRenderState(); - - return null; - } - - } ); - - } - - //---------------------------------------------------------------------- - // Other Public Methods - - /** - * Updates the positon and covered area of the terrain mesh. - * <p/> - * Called from the constructor, as well as when a TerrainMesh is re-used. - */ - public void updateBounds() - { - // Put vertices in correct places - initializeVetices(); - - // Initialize the TriMesh - setVertexBuffer( 0, myVertexes ); - setColorBuffer( 0, myColors ); - setTextureBuffer( 0, myTextureCoordinates ); - setNormalBuffer( 0, myNormals ); - setIndexBuffer( 0, myIndices ); - - // Update bounding box - setModelBound( new BoundingBox() ); - updateModelBound(); - } - - /* * - * Creates a texture from the specified image and applies it to this Terrainmesh. - * - * @param textureImage the image to create a texture from. If null, a placeholder texture is created. - */ - /* public void setTextureImage( final BufferedImage textureImage ) - { - GameTaskQueueManager.getManager().getQueue( GameTaskQueue.UPDATE ).enqueue( new Callable<Object>() - { - - public Object call() throws Exception - { - final Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer(); - initTexture( textureImage, renderer ); - return null; - } - - } ); - } - - */ - - /** - * @return the texture that this TerrainMesh is currently using, or null if it is using a placeholder - * texture. - */ - public Texture getTexture() - { - return myTexture; - } - - //====================================================================== - // Private Methods - -/* - public void setPlaceholderTexture( final Texture texture, final BoundingRectangle textureArea ) - { - synchronized ( myTextureStateLock ) - { - myPlaceholderTextureInUse = true; - - if ( myTextureState != null ) - { - // Update texture indexes - setTextureCoordinates( textureArea ); - - // Update the geometry - updateGeometricState( 0, true ); - - // Use placeholder if no texture specified - Texture textureToUse = texture; - if ( textureToUse == null ) - { - textureToUse = PLACEHOLDER_TEXTURE; - } - - // Set the texture - myTextureState.setTexture( textureToUse ); - - updateRenderState(); - } - } - } -*/ - -/* - public boolean isPlaceholderTextureInUse() - { - return myPlaceholderTextureInUse; - } -*/ - - //====================================================================== - // Private Methods - - private void initializeVetices() - { - final DataSample pointState = new DataSample(); - - int pointIndex = 0; - for ( final DataSample strokePoint : myStroke.getPoints() ) - { - pointState.updateValuesWith( strokePoint ); - - initializePoint( pointState, pointIndex++ ); - } - } - - - @SuppressWarnings( { "PointlessArithmeticExpression" } ) - private void initializePoint( final DataSample strokePoint, final int pointIndex ) - { - final int index = pointIndex * 3; - - final float xPos = strokePoint.getVariable( "x", 0 ); - final float yPos = strokePoint.getVariable( "y", 0 ); - final float zPos = myZ; - final float width = strokePoint.getVariable( "width", 0.5f ); - - final float colorR = strokePoint.getVariable( "colorR", 0 ); - final float colorG = strokePoint.getVariable( "colorG", 0 ); - final float colorB = strokePoint.getVariable( "colorB", 0 ); - final float colorA = strokePoint.getVariable( "colorA", 0.7f ); - - final Vector3f positionLeft = new Vector3f( xPos, yPos + width, zPos ); - final ColorRGBA colorLeft = new ColorRGBA( colorR, colorG, colorB, 0 ); - - final Vector3f positionMid = new Vector3f( xPos, yPos, zPos ); - final ColorRGBA colorMid = new ColorRGBA( colorR, colorG, colorB, colorA ); - - final Vector3f positionRight = new Vector3f( xPos, yPos - width, zPos ); - final ColorRGBA colorRight = new ColorRGBA( colorR, colorG, colorB, 0 ); - - setPointData( index + 0, positionLeft, colorLeft ); - setPointData( index + 1, positionMid, colorMid ); - setPointData( index + 2, positionRight, colorRight ); - } - - - private void setPointData( final int index, final Vector3f position, final ColorRGBA color ) - { - BufferUtils.setInBuffer( position, myVertexes, index ); - BufferUtils.setInBuffer( new Vector3f(), myNormals, index ); - BufferUtils.setInBuffer( color, myColors, index ); - BufferUtils.setInBuffer( new Vector2f(), myTextureCoordinates, index ); - } - - - @SuppressWarnings( { "PointlessArithmeticExpression" } ) - private void initializeIndices() - { - // OPTIMIZE: Use triangle strips or fans to get more efficient results! - - // Create indices indicating the vertexes that make up triangle faces - int index = 0; - for ( int segment = 0; segment < myNumberOfSegments; segment++ ) - { - /* - Segment, with vertex order marked in: - - 0---3 - | \ | - 1---4 --> direction of stroke - | / | - 2---5 - - */ - - final int segmentStartVertex = segment * 3; - - myIndices.put( index++, segmentStartVertex + 3 ); - myIndices.put( index++, segmentStartVertex + 4 ); - myIndices.put( index++, segmentStartVertex + 0 ); - - myIndices.put( index++, segmentStartVertex + 0 ); - myIndices.put( index++, segmentStartVertex + 4 ); - myIndices.put( index++, segmentStartVertex + 1 ); - - myIndices.put( index++, segmentStartVertex + 1 ); - myIndices.put( index++, segmentStartVertex + 4 ); - myIndices.put( index++, segmentStartVertex + 2 ); - - myIndices.put( index++, segmentStartVertex + 2 ); - myIndices.put( index++, segmentStartVertex + 4 ); - myIndices.put( index++, segmentStartVertex + 5 ); - } - } - -/* - private void initTexture( BufferedImage textureImage, final Renderer renderer ) - { - synchronized ( myTextureStateLock ) - { - // The texture can be null e.g. if we ran out of memory - if ( textureImage == null ) - { - textureImage = PLACEHOLDER_PICTURE; - } - - // Remove any placeholder render state - if ( myPlaceholderTextureInUse ) - { -*/ -/* - clearRenderState( RenderState.RS_TEXTURE ); -*/ -/* -*/ -/* - myPlaceholderTextureState.setEnabled( false ); - myPlaceholderTextureState.setTexture( null ); - myPlaceholderTextureState = null; -*/ -/* - setTextureCoordinates( WHOLE_TEXTURE_AREA ); -*/ -/* -*/ -/* - } - - if ( myTextureGraphics == null ) - { - // First time initializations: - - // Create JME Image Renderer - myTextureGraphics = ImageGraphics.createInstance( textureImage.getWidth( null ), - textureImage.getHeight( null ), - 0 ); - myTextureGraphics.drawImage( textureImage, 0, 0, null ); - myTextureGraphics.update(); - - // Create texture - myTexture = TextureManager.loadTexture( null, - createTextureKey( textureImage.hashCode() ), - myTextureGraphics.getImage() ); - - // Make sure this texture is not cached, as we will be updating it when the TerrainMesh is re-used - TextureManager.releaseTexture( myTexture ); - - // Clamp texture at edges (no wrapping) - myTexture.setWrap( Texture.WM_ECLAMP_S_ECLAMP_T ); - myTexture.setMipmapState( Texture.MM_LINEAR_LINEAR ); - - createTextureRenderState( renderer, myTexture ); - - if ( myPlaceholderTextureInUse ) - { - myTextureState.setTexture( myTexture, 0 ); - } - } - else - { - // Release the previously reserved textures, so that they don't take up space on the 3D card - // NOTE: Maybe this also forces JME to re-upload the changed texture? - if ( !myPlaceholderTextureInUse ) - { - myTextureState.deleteAll( true ); - } - else - { - myTextureState.setTexture( myTexture, 0 ); - myTextureState.deleteAll( true ); - } - - // Update the JME Image used by the texture - myTextureGraphics.drawImage( textureImage, 0, 0, null ); - myTextureGraphics.update(); - myTextureGraphics.update( myTexture ); - - // Make sure this texture is not cached, as we will be updating it when the TerrainMesh is re-used - TextureManager.releaseTexture( myTexture ); - - // Smoother look at low viewing angles - myTexture.setMipmapState( Texture.MM_LINEAR_LINEAR ); - } - - myPlaceholderTextureInUse = false; - } - } -*/ - -/* - private void createTextureRenderState( final Renderer renderer, final Texture texture ) - { - myTextureState = renderer.createTextureState(); - myTextureState.setEnabled( true ); - myTextureState.setTexture( texture, 0 ); - setRenderState( myTextureState ); - updateRenderState(); - } - - - private TextureKey createTextureKey( final int imageHashcode ) - { - final TextureKey tkey = new TextureKey( null, Texture.MM_LINEAR, Texture.FM_LINEAR, - DEFAULT_ANISO_LEVEL, false, DEFAULT_TEXTURE_IMAGE_FORMAT ); - tkey.setFileType( "" + imageHashcode ); - return tkey; - } - - - private void setTextureCoordinates( BoundingRectangle boundingRectangle ) - { - if ( boundingRectangle == null ) - { - boundingRectangle = WHOLE_TEXTURE_AREA; - } - - for ( int y = 0; y < mySizeY_vertices; y++ ) - { - for ( int x = 0; x < mySizeX_vertices; x++ ) - { - final int index = calculateMeshIndex( x, y ); - - final float textureXPos = (float) MathUtils.interpolateClamp( x, - 1, - mySizeX_vertices - 2, - boundingRectangle.getX1(), - boundingRectangle.getX2() ); - final float textureYPos = (float) MathUtils.interpolateClamp( y, - 1, - mySizeY_vertices - 2, - boundingRectangle.getY2(), - boundingRectangle.getY1() ); - - final Vector2f textureCoordinate = new Vector2f( textureXPos, textureYPos ); - - BufferUtils.setInBuffer( textureCoordinate, myTextureCoordinates, index ); - } - } - - setTextureBuffer( 0, myTextureCoordinates ); - } -*/ - -} Copied: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeRenderer.java (from rev 491, trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/StrokeRenderer.java) =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeRenderer.java (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeRenderer.java 2008-04-28 07:19:44 UTC (rev 492) @@ -0,0 +1,518 @@ +package org.skycastle.sketch.rendering; + +import com.jme.bounding.BoundingBox; +import com.jme.image.Image; +import com.jme.image.Texture; +import com.jme.math.Vector2f; +import com.jme.math.Vector3f; +import com.jme.renderer.ColorRGBA; +import com.jme.renderer.Renderer; +import com.jme.scene.TriMesh; +import com.jme.scene.state.AlphaState; +import com.jme.scene.state.TextureState; +import com.jme.system.DisplaySystem; +import com.jme.util.GameTaskQueue; +import com.jme.util.GameTaskQueueManager; +import com.jme.util.TextureManager; +import com.jme.util.geom.BufferUtils; +import com.jmex.awt.swingui.ImageGraphics; +import org.skycastle.sketch.model.stroke.Stroke; +import org.skycastle.sketch.sample.DataSample; +import org.skycastle.util.ImageUtils; +import org.skycastle.util.ParameterChecker; + +import java.awt.image.BufferedImage; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.concurrent.Callable; + +/** + * A 3D node that renders a {@link Stroke}. + * + * @author Hans Häggström + */ +@SuppressWarnings( { "serial" } ) +public final class StrokeRenderer + extends TriMesh +{ + + //====================================================================== + // Private Fields + + private static int theStrokeMeshCounter = 0; + + private final Stroke myStroke; + + private final float myZ; + private final FloatBuffer myVertexes; + private final FloatBuffer myColors; + private final FloatBuffer myTextureCoordinates; + private final FloatBuffer myNormals; + private final IntBuffer myIndices; + private final int myNumberOfVertices; + private final int myNumberOfIndices; + + private final Object myTextureStateLock = new Object(); + private final int myNumberOfPoints; + private final int myNumberOfSegments; + private final int myNumberOfTriangles; + + private TextureState myTextureState; + private Texture myTexture; + private ImageGraphics myTextureGraphics; + + private TextureState myPlaceholderTextureState = null; + + private boolean myPlaceholderTextureInUse = false; + + //====================================================================== + // Private Constants + + private static final BufferedImage PLACEHOLDER_PICTURE = ImageUtils.createPlaceholderPicture( 128, 128 ); + private static final float DEFAULT_ANISO_LEVEL = 1.0f; + private static final int DEFAULT_TEXTURE_IMAGE_FORMAT = com.jme.image.Image.GUESS_FORMAT_NO_S3TC; + private static final Texture PLACEHOLDER_TEXTURE = TextureManager.loadTexture( PLACEHOLDER_PICTURE, + Texture.MM_LINEAR_LINEAR, + Texture.FM_LINEAR, + 1, + Image.GUESS_FORMAT_NO_S3TC, + false ); + private static final int NUMBER_OF_TRIANGLES_PER_SEGMENT = 4; + private static final int NUMBER_OF_INDICES_IN_SEGMENT = 3 * NUMBER_OF_TRIANGLES_PER_SEGMENT; + private static final float DEFAULT_WIDTH = 8f; + private static final float DEFAULT_RED = 0; + private static final float DEFAULT_GREEN = 0; + private static final float DEFAULT_BLUE = 0f; + private static final float DEFAULT_ALPHA = 0.7f; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new {@link StrokeRenderer}. + * + * @param stroke the stroke that should be rendered + */ + public StrokeRenderer( final Stroke stroke ) + { + this( stroke, 0 ); + } + + + /** + * Creates a new {@link StrokeRenderer}. + * + * @param stroke the stroke that should be rendered + * @param z the default height of the stroke. + */ + public StrokeRenderer( final Stroke stroke, final float z ) + { + // JME seems to need an unique identifier for each node. NOTE: Not thread safe. + //noinspection AssignmentToStaticFieldFromInstanceMethod + super( "StrokeMesh_" + theStrokeMeshCounter++ ); + + // Check parameters + ParameterChecker.checkNotNull( stroke, "stroke" ); + ParameterChecker.checkNormalNumber( z, "z" ); + + myStroke = stroke; + myZ = z; + + // Calculate sizes + myNumberOfPoints = myStroke.getPoints().size(); + myNumberOfSegments = myNumberOfPoints - 1; + myNumberOfTriangles = myNumberOfSegments * NUMBER_OF_TRIANGLES_PER_SEGMENT; + myNumberOfVertices = myNumberOfPoints * 3; + myNumberOfIndices = myNumberOfSegments * NUMBER_OF_INDICES_IN_SEGMENT; + + // Create databuffers + myVertexes = BufferUtils.createVector3Buffer( myNumberOfVertices ); + myColors = BufferUtils.createColorBuffer( myNumberOfVertices ); + myTextureCoordinates = BufferUtils.createVector2Buffer( myNumberOfVertices ); + myNormals = BufferUtils.createVector3Buffer( myNumberOfVertices ); + myIndices = BufferUtils.createIntBuffer( myNumberOfIndices ); + + // Stich together the vertices into triangles + initializeIndices(); + + + updateBounds(); + + + GameTaskQueueManager.getManager().getQueue( GameTaskQueue.UPDATE ).enqueue( new Callable<Object>() + { + + public Object call() + throws Exception + { + final Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer(); + + final StrokeRenderer strokeRenderer = StrokeRenderer.this; + + // strokeRenderer.setRenderQueueMode( Renderer.QUEUE_TRANSPARENT ); + final AlphaState alphaState = renderer.createAlphaState(); + alphaState.setBlendEnabled( true ); + alphaState.setSrcFunction( AlphaState.SB_SRC_ALPHA ); + alphaState.setDstFunction( AlphaState.DB_ONE_MINUS_SRC_ALPHA ); + alphaState.setTestEnabled( false ); + alphaState.setEnabled( true ); + + strokeRenderer.setRenderState( alphaState ); + strokeRenderer.updateRenderState(); + + // TODO: Make the rendering two sided - how? +/* + setCullMode( CULL_NEVER ); +*/ + + return null; + } + + } ); + } + + //---------------------------------------------------------------------- + // Other Public Methods + + /** + * Updates the positon and covered area of the terrain mesh. + * <p/> + * Called from the constructor, as well as when a TerrainMesh is re-used. + */ + public void updateBounds() + { + // Put vertices in correct places + initializeVetices(); + + // Initialize the TriMesh + setVertexBuffer( 0, myVertexes ); + setColorBuffer( 0, myColors ); + setTextureBuffer( 0, myTextureCoordinates ); + setNormalBuffer( 0, myNormals ); + setIndexBuffer( 0, myIndices ); + + // Update bounding box + setModelBound( new BoundingBox() ); + updateModelBound(); + } + + /* * + * Creates a texture from the specified image and applies it to this Terrainmesh. + * + * @param textureImage the image to create a texture from. If null, a placeholder texture is created. + */ + /* public void setTextureImage( final BufferedImage textureImage ) + { + GameTaskQueueManager.getManager().getQueue( GameTaskQueue.UPDATE ).enqueue( new Callable<Object>() + { + + public Object call() throws Exception + { + final Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer(); + initTexture( textureImage, renderer ); + return null; + } + + } ); + } + + */ + + /** + * @return the texture that this TerrainMesh is currently using, or null if it is using a placeholder + * texture. + */ + public Texture getTexture() + { + return myTexture; + } + + //====================================================================== + // Private Methods + +/* + public void setPlaceholderTexture( final Texture texture, final BoundingRectangle textureArea ) + { + synchronized ( myTextureStateLock ) + { + myPlaceholderTextureInUse = true; + + if ( myTextureState != null ) + { + // Update texture indexes + setTextureCoordinates( textureArea ); + + // Update the geometry + updateGeometricState( 0, true ); + + // Use placeholder if no texture specified + Texture textureToUse = texture; + if ( textureToUse == null ) + { + textureToUse = PLACEHOLDER_TEXTURE; + } + + // Set the texture + myTextureState.setTexture( textureToUse ); + + updateRenderState(); + } + } + } +*/ + +/* + public boolean isPlaceholderTextureInUse() + { + return myPlaceholderTextureInUse; + } +*/ + + //====================================================================== + // Private Methods + + private void initializeVetices() + { + final DataSample pointState = new DataSample(); + + int pointIndex = 0; + for ( final DataSample strokePoint : myStroke.getPoints() ) + { + pointState.updateValuesWith( strokePoint ); + + initializePoint( pointState, pointIndex++ ); + } + } + + + @SuppressWarnings( { "PointlessArithmeticExpression" } ) + private void initializePoint( final DataSample strokePoint, final int pointIndex ) + { + final int index = pointIndex * 3; + + final float xPos = strokePoint.getVariable( "x", 0 ); + final float yPos = strokePoint.getVariable( "y", 0 ); + final float zPos = myZ; + final float width = strokePoint.getVariable( "width", DEFAULT_WIDTH ); + + final float colorR = strokePoint.getVariable( "colorR", DEFAULT_RED ); + final float colorG = strokePoint.getVariable( "colorG", DEFAULT_GREEN ); + final float colorB = strokePoint.getVariable( "colorB", DEFAULT_BLUE ); + final float colorA = strokePoint.getVariable( "colorA", DEFAULT_ALPHA ); + + final Vector3f positionLeft = new Vector3f( xPos, yPos + width, zPos ); + final ColorRGBA colorLeft = new ColorRGBA( colorR, colorG, colorB, 0 ); + + final Vector3f positionMid = new Vector3f( xPos, yPos, zPos ); + final ColorRGBA colorMid = new ColorRGBA( colorR, colorG, colorB, colorA ); + + final Vector3f positionRight = new Vector3f( xPos, yPos - width, zPos ); + final ColorRGBA colorRight = new ColorRGBA( colorR, colorG, colorB, 0 ); + + setPointData( index + 0, positionLeft, colorLeft ); + setPointData( index + 1, positionMid, colorMid ); + setPointData( index + 2, positionRight, colorRight ); + } + + + private void setPointData( final int index, final Vector3f position, final ColorRGBA color ) + { + BufferUtils.setInBuffer( position, myVertexes, index ); + BufferUtils.setInBuffer( new Vector3f(), myNormals, index ); + BufferUtils.setInBuffer( color, myColors, index ); + BufferUtils.setInBuffer( new Vector2f(), myTextureCoordinates, index ); + } + + + @SuppressWarnings( { "PointlessArithmeticExpression" } ) + private void initializeIndices() + { + // OPTIMIZE: Use triangle strips or fans to get more efficient results! + + // Create indices indicating the vertexes that make up triangle faces + int index = 0; + for ( int segment = 0; segment < myNumberOfSegments; segment++ ) + { + /* + Segment, with vertex order marked in: + + 0---3 + | \ | + 1---4 --> direction of stroke + | / | + 2---5 + + */ + + final int segmentStartVertex = segment * 3; + + myIndices.put( index++, segmentStartVertex + 3 ); + myIndices.put( index++, segmentStartVertex + 4 ); + myIndices.put( index++, segmentStartVertex + 0 ); + + myIndices.put( index++, segmentStartVertex + 0 ); + myIndices.put( index++, segmentStartVertex + 4 ); + myIndices.put( index++, segmentStartVertex + 1 ); + + myIndices.put( index++, segmentStartVertex + 1 ); + myIndices.put( index++, segmentStartVertex + 4 ); + myIndices.put( index++, segmentStartVertex + 2 ); + + myIndices.put( index++, segmentStartVertex + 2 ); + myIndices.put( index++, segmentStartVertex + 4 ); + myIndices.put( index++, segmentStartVertex + 5 ); + } + } + +/* + private void initTexture( BufferedImage textureImage, final Renderer renderer ) + { + synchronized ( myTextureStateLock ) + { + // The texture can be null e.g. if we ran out of memory + if ( textureImage == null ) + { + textureImage = PLACEHOLDER_PICTURE; + } + + // Remove any placeholder render state + if ( myPlaceholderTextureInUse ) + { +*/ +/* + clearRenderState( RenderState.RS_TEXTURE ); +*/ +/* +*/ +/* + myPlaceholderTextureState.setEnabled( false ); + myPlaceholderTextureState.setTexture( null ); + myPlaceholderTextureState = null; +*/ +/* + setTextureCoordinates( WHOLE_TEXTURE_AREA ); +*/ +/* +*/ +/* + } + + if ( myTextureGraphics == null ) + { + // First time initializations: + + // Create JME Image Renderer + myTextureGraphics = ImageGraphics.createInstance( textureImage.getWidth( null ), + textureImage.getHeight( null ), + 0 ); + myTextureGraphics.drawImage( textureImage, 0, 0, null ); + myTextureGraphics.update(); + + // Create texture + myTexture = TextureManager.loadTexture( null, + createTextureKey( textureImage.hashCode() ), + myTextureGraphics.getImage() ); + + // Make sure this texture is not cached, as we will be updating it when the TerrainMesh is re-used + TextureManager.releaseTexture( myTexture ); + + // Clamp texture at edges (no wrapping) + myTexture.setWrap( Texture.WM_ECLAMP_S_ECLAMP_T ); + myTexture.setMipmapState( Texture.MM_LINEAR_LINEAR ); + + createTextureRenderState( renderer, myTexture ); + + if ( myPlaceholderTextureInUse ) + { + myTextureState.setTexture( myTexture, 0 ); + } + } + else + { + // Release the previously reserved textures, so that they don't take up space on the 3D card + // NOTE: Maybe this also forces JME to re-upload the changed texture? + if ( !myPlaceholderTextureInUse ) + { + myTextureState.deleteAll( true ); + } + else + { + myTextureState.setTexture( myTexture, 0 ); + myTextureState.deleteAll( true ); + } + + // Update the JME Image used by the texture + myTextureGraphics.drawImage( textureImage, 0, 0, null ); + myTextureGraphics.update(); + myTextureGraphics.update( myTexture ); + + // Make sure this texture is not cached, as we will be updating it when the TerrainMesh is re-used + TextureManager.releaseTexture( myTexture ); + + // Smoother look at low viewing angles + myTexture.setMipmapState( Texture.MM_LINEAR_LINEAR ); + } + + myPlaceholderTextureInUse = false; + } + } +*/ + +/* + private void createTextureRenderState( final Renderer renderer, final Texture texture ) + { + myTextureState = renderer.createTextureState(); + myTextureState.setEnabled( true ); + myTextureState.setTexture( texture, 0 ); + setRenderState( myTextureState ); + updateRenderState(); + } + + + private TextureKey createTextureKey( final int imageHashcode ) + { + final TextureKey tkey = new TextureKey( null, Texture.MM_LINEAR, Texture.FM_LINEAR, + DEFAULT_ANISO_LEVEL, false, DEFAULT_TEXTURE_IMAGE_FORMAT ); + tkey.setFileType( "" + imageHashcode ); + return tkey; + } + + + private void setTextureCoordinates( BoundingRectangle boundingRectangle ) + { + if ( boundingRectangle == null ) + { + boundingRectangle = WHOLE_TEXTURE_AREA; + } + + for ( int y = 0; y < mySizeY_vertices; y++ ) + { + for ( int x = 0; x < mySizeX_vertices; x++ ) + { + final int index = calculateMeshIndex( x, y ); + + final float textureXPos = (float) MathUtils.interpolateClamp( x, + 1, + mySizeX_vertices - 2, + boundingRectangle.getX1(), + boundingRectangle.getX2() ); + final float textureYPos = (float) MathUtils.interpolateClamp( y, + 1, + mySizeY_vertices - 2, + boundingRectangle.getY2(), + boundingRectangle.getY1() ); + + final Vector2f textureCoordinate = new Vector2f( textureXPos, textureYPos ); + + BufferUtils.setInBuffer( textureCoordinate, myTextureCoordinates, index ); + } + } + + setTextureBuffer( 0, myTextureCoordinates ); + } +*/ + +} This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.