Revision: 500 http://skycastle.svn.sourceforge.net/skycastle/?rev=500&view=rev Author: zzorn Date: 2008-04-29 08:51:11 -0700 (Tue, 29 Apr 2008) Log Message: ----------- More work on the StrokePartRenderer Modified Paths: -------------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeAppearanceCalculator.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokePartRenderer.java Added Paths: ----------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/EmptyStroke.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-29 14:00:35 UTC (rev 499) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java 2008-04-29 15:51:11 UTC (rev 500) @@ -489,6 +489,11 @@ { final Stroke stroke = (Stroke) groupElement; +/* + final StrokePartRenderer strokePartRenderer = new StrokePartRenderer( 1000 ); + strokePartRenderer.setStroke( stroke, 0 ); + myRoot3DNode.attachChild( strokePartRenderer ); +*/ myRoot3DNode.attachChild( new StrokeRenderer( stroke ) ); } else if ( groupElement instanceof Group ) Added: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/EmptyStroke.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/EmptyStroke.java (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/model/stroke/EmptyStroke.java 2008-04-29 15:51:11 UTC (rev 500) @@ -0,0 +1,74 @@ +package org.skycastle.sketch.model.stroke; + +import org.skycastle.sketch.sample.DataSample; + +import java.util.Collections; +import java.util.List; + +/** + * An empty stroke. + * <p/> + * Immutable, throws error if attempted to modify. + * + * @author Hans Haggstrom + */ +public final class EmptyStroke + implements Stroke +{ + + //====================================================================== + // Public Constants + + /** + * A singleton empty stroke instance. + */ + public static final EmptyStroke EMPTY_STROKE = new EmptyStroke(); + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + /** + * Creates a new {@link org.skycastle.sketch.model.stroke.EmptyStroke}. + */ + public EmptyStroke() + { + } + + //---------------------------------------------------------------------- + // GroupElement Implementation + + + public String getName() + { + return "EmptyStroke"; + } + + //---------------------------------------------------------------------- + // Stroke Implementation + + + public void addPoint( final DataSample addedPoint ) + { + throw new UnsupportedOperationException( "The EmptyStroke is immutable." ); + } + + + public List<DataSample> getPoints() + { + return Collections.emptyList(); + } + + + public void addStrokeListener( final StrokeListener addedStrokeListener ) + { + } + + + public void removeStrokeListener( final StrokeListener removedStrokeListener ) + { + } + +} Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeAppearanceCalculator.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeAppearanceCalculator.java 2008-04-29 14:00:35 UTC (rev 499) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeAppearanceCalculator.java 2008-04-29 15:51:11 UTC (rev 500) @@ -4,6 +4,7 @@ import com.jme.math.Vector2f; import com.jme.math.Vector3f; import com.jme.renderer.ColorRGBA; +import org.skycastle.sketch.model.stroke.EmptyStroke; import org.skycastle.sketch.model.stroke.Stroke; import org.skycastle.sketch.sample.DataSample; import org.skycastle.util.GeometryUtils; @@ -20,9 +21,6 @@ //====================================================================== // Private Fields - private final Stroke myStroke; - - private final DataSample myPointStatePrevious = new DataSample(); private final DataSample myPointStateCurrent = new DataSample(); private final DataSample myPointStateNext = new DataSample(); @@ -31,6 +29,8 @@ private final Vector2f myPreviousRightEdge = new Vector2f(); private final Vector2f myPreviousMid = new Vector2f(); + private Stroke myStroke; + private int myCurrentIndex = 0; //====================================================================== @@ -49,8 +49,27 @@ //---------------------------------------------------------------------- // Constructors + public StrokeAppearanceCalculator() + { + this( EmptyStroke.EMPTY_STROKE ); + } + public StrokeAppearanceCalculator( final Stroke stroke ) { + setStroke( stroke ); + } + + //---------------------------------------------------------------------- + // Other Public Methods + + public Stroke getStroke() + { + return myStroke; + } + + + public void setStroke( final Stroke stroke ) + { ParameterChecker.checkNotNull( stroke, "stroke" ); myStroke = stroke; @@ -58,22 +77,41 @@ resetToStart(); } - //---------------------------------------------------------------------- - // Other Public Methods /** * Moves to the start of the stroke, and does various initialization. */ public void resetToStart() { + myCurrentIndex = 0; + myPointStatePrevious.clear(); myPointStateCurrent.clear(); myPointStateNext.clear(); - myCurrentIndex = 0; + myPreviousLeftEdge.set( 0, 0 ); + myPreviousRightEdge.set( 0, 0 ); + myPreviousMid.set( 0, 0 ); } /** + * Moves to the start of the stroke, and does various initialization. + */ + public void resetToIndex( int startIndex ) + { + resetToStart(); + + final Vector3f v = new Vector3f(); + final ColorRGBA c = new ColorRGBA(); + + for ( int i = 0; i < startIndex && hasNextPoint(); i++ ) + { + calculateNextPoint( v, v, v, c, c, c ); + } + } + + + /** * Fills in the given parameters with the values for the next point on the stroke. * * @return true if there are more points, false if not. @@ -88,7 +126,8 @@ } final DataSample strokePoint = myStroke.getPoints().get( myCurrentIndex ); - final DataSample nextPoint = myStroke.getPoints().get( Math.min( myCurrentIndex + 1, lastPointIndex ) ); + final DataSample nextPoint = myStroke.getPoints().get( Math.min( myCurrentIndex + 1, + lastPointIndex ) ); myPointStateCurrent.updateValuesWith( strokePoint ); @@ -106,14 +145,21 @@ return myCurrentIndex <= lastPointIndex; } - private int getLastPointIndex() + + public boolean hasNextPoint() { - return myStroke.getPoints().size() - 1; + return myCurrentIndex <= getLastPointIndex(); } //====================================================================== // Private Methods + private int getLastPointIndex() + { + return myStroke.getPoints().size() - 1; + } + + @SuppressWarnings( { "MagicNumber" } ) private Vector2f calculateLeftEdgeOffset( final DataSample previousPoint, final DataSample nextPoint, @@ -133,9 +179,15 @@ } - private void initializePoint( final DataSample previousPoint, final DataSample strokePoint, final DataSample nextPoint, - final Vector3f posLeft, final Vector3f posMid, final Vector3f posRight, - final ColorRGBA colorLeft, final ColorRGBA colorMid, final ColorRGBA colorRight ) + private void initializePoint( final DataSample previousPoint, + final DataSample strokePoint, + final DataSample nextPoint, + final Vector3f posLeft, + final Vector3f posMid, + final Vector3f posRight, + final ColorRGBA colorLeft, + final ColorRGBA colorMid, + final ColorRGBA colorRight ) { final float xPos = strokePoint.getVariable( "x", 0 ); final float yPos = strokePoint.getVariable( "y", 0 ); @@ -180,6 +232,7 @@ colorRight.set( colorR, colorG, colorB, 0 ); } + private boolean linesIntersect( final Vector2f startA, final Vector2f endA, final Vector2f startB, final Vector2f endB ) { @@ -187,8 +240,4 @@ startB.x, startB.y, endB.x, endB.y ); } - public boolean hasNextPoint() - { - return myCurrentIndex <= getLastPointIndex(); - } } Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokePartRenderer.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokePartRenderer.java 2008-04-29 14:00:35 UTC (rev 499) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokePartRenderer.java 2008-04-29 15:51:11 UTC (rev 500) @@ -1,28 +1,23 @@ 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.Spatial; 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.EmptyStroke; import org.skycastle.sketch.model.stroke.Stroke; import org.skycastle.sketch.model.stroke.StrokeListener; 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; @@ -43,43 +38,36 @@ private static int theStrokeMeshCounter = 0; - private final int myStartIndex; private final int myPartLength; - private final Stroke myStroke; + private final StrokeAppearanceCalculator myAppearanceCalculator; + private final StrokeListener myStrokeListener = new StrokeListener() + { - private final Object myTextureStateLock = new Object(); + public void onPointAdded( final Stroke updatedStroke, final DataSample point ) + { + onStrokePointAdded(); + } + }; + + private int myStartIndex; + + private Stroke myStroke = EmptyStroke.EMPTY_STROKE; + private FloatBuffer myVertexes; private FloatBuffer myColors; private FloatBuffer myTextureCoordinates; private FloatBuffer myNormals; private IntBuffer myIndices; - private int myNumberOfSegments; - 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 float myZ1; + private static final Vector3f ORIGO = new Vector3f( 0, 0, 0 ); + private static final ColorRGBA TRANSPARENT_BLACK = new ColorRGBA( 0, 0, 0, 0 ); //====================================================================== // Public Methods @@ -87,124 +75,69 @@ //---------------------------------------------------------------------- // Constructors - private final StrokeAppearanceCalculator myAppearanceCalculator; - /** - * Creates a new {@link StrokeRenderer}. + * Creates a new {@link StrokePartRenderer}. * - * @param stroke the stroke that should be rendered - * @param startIndex + * @param partLength maximum number of points to render. */ - public StrokeRenderer( final Stroke stroke, final int startIndex, final int partLength ) + public StrokePartRenderer( final int partLength ) { // JME seems to need an unique identifier for each node. NOTE: Not thread safe. //noinspection AssignmentToStaticFieldFromInstanceMethod super( "StrokeMesh_" + theStrokeMeshCounter++ ); - ParameterChecker.checkNotNull( stroke, "stroke" ); - ParameterChecker.checkPositiveOrZeroInteger( startIndex, "startIndex" ); ParameterChecker.checkPositiveOrZeroInteger( partLength, "partLength" ); - myStartIndex = startIndex; myPartLength = partLength; - myStroke = stroke; - myAppearanceCalculator = new StrokeAppearanceCalculator( myStroke ); + myAppearanceCalculator = new StrokeAppearanceCalculator(); + initializeGeometry( partLength ); - myStroke.addStrokeListener( new StrokeListener() - { - - public void onPointAdded( final Stroke updatedStroke, final DataSample point ) - { - recalculateStroke(); - } - - } ); - - if ( stroke.getPoints().size() >= 2 ) - { - recalculateStroke(); - } - - 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; - } - - } ); + initializeTranslucency(); } //---------------------------------------------------------------------- // 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. + * @param stroke the stroke that should be rendered. Use EmptyStroke if you want to clear the + * renderer. + * @param startIndex point index to start rendering from. */ - public void updateBounds() + public void setStroke( final Stroke stroke, final int startIndex ) { - // Put vertices in correct places - initializeVetices(); + ParameterChecker.checkNotNull( stroke, "stroke" ); + ParameterChecker.checkPositiveOrZeroInteger( startIndex, "startIndex" ); - // Initialize the TriMesh - setVertexBuffer( 0, myVertexes ); - setColorBuffer( 0, myColors ); - setTextureBuffer( 0, myTextureCoordinates ); - setNormalBuffer( 0, myNormals ); - setIndexBuffer( 0, myIndices ); + myStroke.removeStrokeListener( myStrokeListener ); - // Update bounding box - setModelBound( new BoundingBox() ); - updateModelBound(); + myStartIndex = startIndex; + myStroke = stroke; + + myStroke.addStrokeListener( myStrokeListener ); + + recalculateStrokeAppearance(); } - - /** + /* + *//** * @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 - - private void recalculateStroke() + private void initializeGeometry( final int numberOfStrokePoints ) { // Calculate sizes - final int numberOfPoints = myStroke.getPoints().size(); - myNumberOfSegments = numberOfPoints - 1; - final int numberOfTriangles = myNumberOfSegments * NUMBER_OF_TRIANGLES_PER_SEGMENT; - final int numberOfVertices = numberOfPoints * 3; - final int numberOfIndices = myNumberOfSegments * NUMBER_OF_INDICES_IN_SEGMENT; + final int numberOfSegments = numberOfStrokePoints - 1; + final int numberOfVertices = numberOfStrokePoints * 3; + final int numberOfIndices = numberOfSegments * NUMBER_OF_INDICES_IN_SEGMENT; // Create databuffers myVertexes = BufferUtils.createVector3Buffer( numberOfVertices ); @@ -214,40 +147,126 @@ myIndices = BufferUtils.createIntBuffer( numberOfIndices ); // Stich together the vertices into triangles - initializeIndices(); + initializeIndices( myIndices, numberOfSegments ); + // Initialize the TriMesh + setVertexBuffer( 0, myVertexes ); + setColorBuffer( 0, myColors ); + setTextureBuffer( 0, myTextureCoordinates ); + setNormalBuffer( 0, myNormals ); + setIndexBuffer( 0, myIndices ); - updateBounds(); + // Initialize bounding box + setModelBound( new BoundingBox() ); + updateModelBound(); } - //====================================================================== - // Private Methods - private void initializeVetices() + private void onStrokePointAdded() { - myAppearanceCalculator.resetToStart(); + recalculateStrokeAppearance(); +/* + throw new UnsupportedOperationException( "This method has not yet been implemented." ); // IMPLEMENT +*/ + } - int index = 0; - while ( myAppearanceCalculator.hasNextPoint() ) + + private void initializeTranslucency() + { + GameTaskQueueManager.getManager().getQueue( GameTaskQueue.UPDATE ).enqueue( new Callable<Object>() { - final Vector3f positionLeft = new Vector3f(); - final Vector3f positionMid = new Vector3f(); - final Vector3f positionRight = new Vector3f(); - final ColorRGBA colorLeft = new ColorRGBA(); - final ColorRGBA colorMid = new ColorRGBA(); - final ColorRGBA colorRight = new ColorRGBA(); + public Object call() + throws Exception + { + final Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer(); + enableTranslucency( renderer, StrokePartRenderer.this ); + + return null; + } + + } ); + } + + + private void enableTranslucency( final Renderer renderer, final Spatial spatial ) + { + // 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 ); + + spatial.setRenderState( alphaState ); + spatial.updateRenderState(); + + // TODO: Make the rendering two sided - how? +/* + setCullMode( CULL_NEVER ); +*/ + } + + + @SuppressWarnings( { "PointlessArithmeticExpression" } ) + private void recalculateStrokeAppearance() + { + final Vector3f positionLeft = new Vector3f(); + final Vector3f positionMid = new Vector3f(); + final Vector3f positionRight = new Vector3f(); + + final ColorRGBA colorLeft = new ColorRGBA(); + final ColorRGBA colorMid = new ColorRGBA(); + final ColorRGBA colorRight = new ColorRGBA(); + + int pointIndex = myStartIndex; + + myAppearanceCalculator.resetToIndex( pointIndex ); + + while ( myAppearanceCalculator.hasNextPoint() && + pointIndex < getPartEndIndex() ) + { myAppearanceCalculator.calculateNextPoint( positionLeft, positionMid, positionRight, colorLeft, colorMid, colorRight ); - setPointData( index++, positionLeft, colorLeft ); - setPointData( index++, positionMid, colorMid ); - setPointData( index++, positionRight, colorRight ); + final int vertexIndex = pointIndex * 3; + + setPointData( vertexIndex + 0, positionLeft, colorLeft ); + setPointData( vertexIndex + 1, positionMid, colorMid ); + setPointData( vertexIndex + 2, positionRight, colorRight ); + + pointIndex++; } + + // Fill in rest of the stroke part with invisible data + while ( pointIndex < getPartEndIndex() ) + { + final int vertexIndex = pointIndex * 3; + + setPointData( vertexIndex + 0, ORIGO, TRANSPARENT_BLACK ); + setPointData( vertexIndex + 1, ORIGO, TRANSPARENT_BLACK ); + setPointData( vertexIndex + 2, ORIGO, TRANSPARENT_BLACK ); + + pointIndex++; + } + + // Notify the 3D object that we changed it's appearance + // TODO + updateModelBound(); + updateGeometricState( 0, true ); + updateRenderState(); + + updateModelBound(); } + private int getPartEndIndex() + { + return myStartIndex + myPartLength; + } + private void setPointData( final int index, final Vector3f position, final ColorRGBA color ) { BufferUtils.setInBuffer( position, myVertexes, index ); @@ -258,13 +277,13 @@ @SuppressWarnings( { "PointlessArithmeticExpression" } ) - private void initializeIndices() + private void initializeIndices( final IntBuffer indices, final int numberOfSegments ) { // 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++ ) + for ( int segment = 0; segment < numberOfSegments; segment++ ) { /* Segment, with vertex order marked in: @@ -279,23 +298,22 @@ final int segmentStartVertex = segment * 3; - myIndices.put( index++, segmentStartVertex + 3 ); - myIndices.put( index++, segmentStartVertex + 4 ); - myIndices.put( index++, segmentStartVertex + 0 ); + indices.put( index++, segmentStartVertex + 3 ); + indices.put( index++, segmentStartVertex + 4 ); + indices.put( index++, segmentStartVertex + 0 ); - myIndices.put( index++, segmentStartVertex + 0 ); - myIndices.put( index++, segmentStartVertex + 4 ); - myIndices.put( index++, segmentStartVertex + 1 ); + indices.put( index++, segmentStartVertex + 0 ); + indices.put( index++, segmentStartVertex + 4 ); + indices.put( index++, segmentStartVertex + 1 ); - myIndices.put( index++, segmentStartVertex + 1 ); - myIndices.put( index++, segmentStartVertex + 4 ); - myIndices.put( index++, segmentStartVertex + 2 ); + indices.put( index++, segmentStartVertex + 1 ); + indices.put( index++, segmentStartVertex + 4 ); + indices.put( index++, segmentStartVertex + 2 ); - myIndices.put( index++, segmentStartVertex + 2 ); - myIndices.put( index++, segmentStartVertex + 4 ); - myIndices.put( index++, segmentStartVertex + 5 ); + indices.put( index++, segmentStartVertex + 2 ); + indices.put( index++, segmentStartVertex + 4 ); + indices.put( index++, segmentStartVertex + 5 ); } } - } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.