Revision: 498 http://skycastle.svn.sourceforge.net/skycastle/?rev=498&view=rev Author: zzorn Date: 2008-04-29 03:36:15 -0700 (Tue, 29 Apr 2008) Log Message: ----------- Extracted stroke appearance calculation into its own class. Implemented merging with previous point edges if current point edges overlap them. Modified Paths: -------------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeRenderer.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataSample.java Added Paths: ----------- 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 trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/GeometryUtils.java Added: 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 (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeAppearanceCalculator.java 2008-04-29 10:36:15 UTC (rev 498) @@ -0,0 +1,194 @@ +package org.skycastle.sketch.rendering; + +import com.jme.math.FastMath; +import com.jme.math.Vector2f; +import com.jme.math.Vector3f; +import com.jme.renderer.ColorRGBA; +import org.skycastle.sketch.model.stroke.Stroke; +import org.skycastle.sketch.sample.DataSample; +import org.skycastle.util.GeometryUtils; +import org.skycastle.util.ParameterChecker; + +/** + * Calculates rendering parameters for a stroke. + * + * @author Hans Haggstrom + */ +public final class StrokeAppearanceCalculator +{ + + //====================================================================== + // Private Fields + + private final Stroke myStroke; + + + private final DataSample myPointStatePrevious = new DataSample(); + private final DataSample myPointStateCurrent = new DataSample(); + private final DataSample myPointStateNext = new DataSample(); + + private final Vector2f myPreviousLeftEdge = new Vector2f(); + private final Vector2f myPreviousRightEdge = new Vector2f(); + private final Vector2f myPreviousMid = new Vector2f(); + + private int myCurrentIndex = 0; + + //====================================================================== + // Private Constants + + private static final float DEFAULT_Z_LEVEL = 0; + private static final float DEFAULT_WIDTH = 50; + private static final float DEFAULT_RED = 0; + private static final float DEFAULT_GREEN = 0; + private static final float DEFAULT_BLUE = 0; + private static final float DEFAULT_ALPHA = 0.33f; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + public StrokeAppearanceCalculator( final Stroke stroke ) + { + ParameterChecker.checkNotNull( stroke, "stroke" ); + + myStroke = stroke; + + resetToStart(); + } + + //---------------------------------------------------------------------- + // Other Public Methods + + /** + * Moves to the start of the stroke, and does various initialization. + */ + public void resetToStart() + { + myPointStatePrevious.clear(); + myPointStateCurrent.clear(); + myPointStateNext.clear(); + + myCurrentIndex = 0; + } + + /** + * 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. + */ + public boolean calculateNextPoint( Vector3f posLeft, Vector3f posMid, Vector3f posRight, + ColorRGBA colorLeft, ColorRGBA colorMid, ColorRGBA colorRight ) + { + final int lastPointIndex = getLastPointIndex(); + if ( myCurrentIndex > lastPointIndex ) + { + throw new IndexOutOfBoundsException( "There are no more points" ); + } + + final DataSample strokePoint = myStroke.getPoints().get( myCurrentIndex ); + final DataSample nextPoint = myStroke.getPoints().get( Math.min( myCurrentIndex + 1, lastPointIndex ) ); + + myPointStateCurrent.updateValuesWith( strokePoint ); + + myPointStateNext.updateValuesWith( strokePoint ); + myPointStateNext.updateValuesWith( nextPoint ); + + initializePoint( myPointStatePrevious, myPointStateCurrent, myPointStateNext, + posLeft, posMid, posRight, + colorLeft, colorMid, colorRight ); + + myPointStatePrevious.updateValuesWith( strokePoint ); + + myCurrentIndex++; + + return myCurrentIndex <= lastPointIndex; + } + + private int getLastPointIndex() + { + return myStroke.getPoints().size() - 1; + } + + //====================================================================== + // Private Methods + + @SuppressWarnings( { "MagicNumber" } ) + private Vector2f calculateLeftEdgeOffset( final DataSample previousPoint, + final DataSample nextPoint, + final float width ) + { + final Vector2f v = new Vector2f( nextPoint.getVariable( "x", 0 ), + nextPoint.getVariable( "y", 0 ) ); + v.subtractLocal( new Vector2f( previousPoint.getVariable( "x", 0 ), + previousPoint.getVariable( "y", 0 ) ) ); + v.normalizeLocal(); + + v.rotateAroundOrigin( FastMath.HALF_PI, true ); + + v.multLocal( 0.5f * width ); + + return v; + } + + + 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 ); + final float zPos = DEFAULT_Z_LEVEL; + 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 Vector2f mid = new Vector2f( xPos, yPos ); + + + final Vector2f leftEdge = calculateLeftEdgeOffset( previousPoint, nextPoint, width ); + final Vector2f rightEdge = new Vector2f( -leftEdge.x, -leftEdge.y ); + + leftEdge.addLocal( mid ); + rightEdge.addLocal( mid ); + + // Check if either edge crosses the previous one. In that case, use the previous endpoint + if ( linesIntersect( mid, leftEdge, myPreviousMid, myPreviousLeftEdge ) ) + { + leftEdge.set( myPreviousLeftEdge ); + } + if ( linesIntersect( mid, rightEdge, myPreviousMid, myPreviousRightEdge ) ) + { + rightEdge.set( myPreviousRightEdge ); + } + + myPreviousMid.set( mid ); + myPreviousLeftEdge.set( leftEdge ); + myPreviousRightEdge.set( rightEdge ); + + + posMid.set( mid.x, mid.y, zPos ); + posLeft.set( leftEdge.x, leftEdge.y, zPos ); + posRight.set( rightEdge.x, rightEdge.y, zPos ); + + colorMid.set( colorR, colorG, colorB, colorA ); + colorLeft.set( colorR, colorG, colorB, 0 ); + colorRight.set( colorR, colorG, colorB, 0 ); + } + + private boolean linesIntersect( final Vector2f startA, final Vector2f endA, + final Vector2f startB, final Vector2f endB ) + { + return GeometryUtils.isLineIntersectingLine( startA.x, startA.y, endA.x, endA.y, + startB.x, startB.y, endB.x, endB.y ); + } + + public boolean hasNextPoint() + { + return myCurrentIndex <= getLastPointIndex(); + } +} Added: 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 (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokePartRenderer.java 2008-04-29 10:36:15 UTC (rev 498) @@ -0,0 +1,8 @@ +package org.skycastle.sketch.rendering; + +/** + * @author Hans Haggstrom + */ +public class StrokePartRenderer +{ +} Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeRenderer.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeRenderer.java 2008-04-28 20:34:20 UTC (rev 497) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/rendering/StrokeRenderer.java 2008-04-29 10:36:15 UTC (rev 498) @@ -3,7 +3,6 @@ import com.jme.bounding.BoundingBox; import com.jme.image.Image; import com.jme.image.Texture; -import com.jme.math.FastMath; import com.jme.math.Vector2f; import com.jme.math.Vector3f; import com.jme.renderer.ColorRGBA; @@ -45,8 +44,6 @@ private final Stroke myStroke; - private final float myZ; - private final Object myTextureStateLock = new Object(); private FloatBuffer myVertexes; @@ -54,11 +51,7 @@ private FloatBuffer myTextureCoordinates; private FloatBuffer myNormals; private IntBuffer myIndices; - private int myNumberOfVertices; - private int myNumberOfIndices; - private int myNumberOfPoints; private int myNumberOfSegments; - private int myNumberOfTriangles; private TextureState myTextureState; private Texture myTexture; @@ -82,11 +75,7 @@ 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 = 15f; - 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.33f; + private float myZ1; //====================================================================== // Public Methods @@ -104,6 +93,7 @@ this( stroke, 0 ); } + private final StrokeAppearanceCalculator myAppearanceCalculator; /** * Creates a new {@link StrokeRenderer}. @@ -122,8 +112,11 @@ ParameterChecker.checkNormalNumber( z, "z" ); myStroke = stroke; - myZ = z; + myZ1 = z; + myAppearanceCalculator = new StrokeAppearanceCalculator( myStroke ); + + myStroke.addStrokeListener( new StrokeListener() { @@ -233,18 +226,18 @@ private void recalculateStroke() { // 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; + 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; // Create databuffers - myVertexes = BufferUtils.createVector3Buffer( myNumberOfVertices ); - myColors = BufferUtils.createColorBuffer( myNumberOfVertices ); - myTextureCoordinates = BufferUtils.createVector2Buffer( myNumberOfVertices ); - myNormals = BufferUtils.createVector3Buffer( myNumberOfVertices ); - myIndices = BufferUtils.createIntBuffer( myNumberOfIndices ); + myVertexes = BufferUtils.createVector3Buffer( numberOfVertices ); + myColors = BufferUtils.createColorBuffer( numberOfVertices ); + myTextureCoordinates = BufferUtils.createVector2Buffer( numberOfVertices ); + myNormals = BufferUtils.createVector3Buffer( numberOfVertices ); + myIndices = BufferUtils.createIntBuffer( numberOfIndices ); // Stich together the vertices into triangles initializeIndices(); @@ -296,88 +289,29 @@ private void initializeVetices() { - final DataSample pointStatePrevious = new DataSample(); - final DataSample pointState = new DataSample(); - final DataSample pointStateNext = new DataSample(); + myAppearanceCalculator.resetToStart(); - int pointIndex = 0; - final int numberOfPoints = myStroke.getPoints().size(); - for ( int i = 0; i < numberOfPoints; i++ ) + int index = 0; + while ( myAppearanceCalculator.hasNextPoint() ) { - final int nextIndex = Math.min( i + 1, numberOfPoints - 1 ); + final Vector3f positionLeft = new Vector3f(); + final Vector3f positionMid = new Vector3f(); + final Vector3f positionRight = new Vector3f(); - final DataSample strokePoint = myStroke.getPoints().get( i ); - final DataSample nextPoint = myStroke.getPoints().get( nextIndex ); + final ColorRGBA colorLeft = new ColorRGBA(); + final ColorRGBA colorMid = new ColorRGBA(); + final ColorRGBA colorRight = new ColorRGBA(); - pointStateNext.updateValuesWith( strokePoint ); - pointStateNext.updateValuesWith( nextPoint ); + myAppearanceCalculator.calculateNextPoint( positionLeft, positionMid, positionRight, + colorLeft, colorMid, colorRight ); - pointState.updateValuesWith( strokePoint ); - - initializePoint( pointState, pointIndex++, pointStatePrevious, pointStateNext ); - - pointStatePrevious.updateValuesWith( strokePoint ); + setPointData( index++, positionLeft, colorLeft ); + setPointData( index++, positionMid, colorMid ); + setPointData( index++, positionRight, colorRight ); } } - @SuppressWarnings( { "PointlessArithmeticExpression" } ) - private void initializePoint( final DataSample strokePoint, - final int pointIndex, - final DataSample previousPoint, - final DataSample nextPoint ) - { - 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 positionMid = new Vector3f( xPos, yPos, zPos ); - final ColorRGBA colorMid = new ColorRGBA( colorR, colorG, colorB, colorA ); - - - final Vector2f leftEdgeOffset = calculateLeftEdgeOffset( previousPoint, nextPoint, width ); - - final ColorRGBA colorLeft = new ColorRGBA( colorR, colorG, colorB, 0 ); - final Vector3f positionLeft = new Vector3f( positionMid ); - positionLeft.addLocal( leftEdgeOffset.x, leftEdgeOffset.y, 0 ); - - final ColorRGBA colorRight = new ColorRGBA( colorR, colorG, colorB, 0 ); - final Vector3f positionRight = new Vector3f( positionMid ); - positionRight.addLocal( -leftEdgeOffset.x, -leftEdgeOffset.y, 0 ); - - setPointData( index + 0, positionLeft, colorLeft ); - setPointData( index + 1, positionMid, colorMid ); - setPointData( index + 2, positionRight, colorRight ); - } - - - private Vector2f calculateLeftEdgeOffset( final DataSample previousPoint, - final DataSample nextPoint, - final float width ) - { - final Vector2f v = new Vector2f( nextPoint.getVariable( "x", 0 ), - nextPoint.getVariable( "y", 0 ) ); - v.subtractLocal( new Vector2f( previousPoint.getVariable( "x", 0 ), - previousPoint.getVariable( "y", 0 ) ) ); - v.normalizeLocal(); - - v.rotateAroundOrigin( FastMath.HALF_PI, true ); - - v.multLocal( 0.5f * width ); - - return v; - } - - private void setPointData( final int index, final Vector3f position, final ColorRGBA color ) { BufferUtils.setInBuffer( position, myVertexes, index ); Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataSample.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataSample.java 2008-04-28 20:34:20 UTC (rev 497) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/sample/DataSample.java 2008-04-29 10:36:15 UTC (rev 498) @@ -204,4 +204,11 @@ }; } + /** + * Removes all variable values from this {@link DataSample}. + */ + public void clear() + { + myVariables.clear(); + } } Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/GeometryUtils.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/GeometryUtils.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/GeometryUtils.java 2008-04-29 10:36:15 UTC (rev 498) @@ -0,0 +1,108 @@ +package org.skycastle.util; + + +/** + * Collection of geometry utility methods. + * <p/> + * Some of the source is adapted from http://geosoft.no/software/index.html (LGPL licensed). + * + * @author Hans Haggstrom + */ +public final class GeometryUtils +{ + + //---------------------------------------------------------------------- + // Static Methods + + /** + * @return true if c is between a and b. + */ + @SuppressWarnings( { "JavaDoc" } ) + public static boolean isBetween( float a, float b, float c ) + { + return b > a ? c >= a && c <= b : c >= b && c <= a; + } + + + /** + * Check if two line segments intersects. + * + * @param ax0 First line start x. + * @param ay0 First line start y. + * @param ax1 First line end x. + * @param ay1 First line end y. + * @param bx0 Second line start x. + * @param by0 Second line start y. + * @param bx1 Second line end x. + * @param by1 Second line end y. + * + * @return True if the two lines intersects. + */ + public static boolean isLineIntersectingLine( float ax0, float ay0, float ax1, float ay1, + float bx0, float by0, float bx1, float by1 ) + { + int s1 = sameSide( ax0, ay0, ax1, ay1, bx0, by0, bx1, by1 ); + int s2 = sameSide( bx0, by0, bx1, by1, ax0, ay0, ax1, ay1 ); + + return s1 <= 0 && s2 <= 0; + } + + + /** + * Check if two points are on the same side of a given line. + * Algorithm from Sedgewick page 350. + * + * @param x0 Line start x. + * @param y0 Line start y. + * @param x1 Line end x. + * @param y1 Line end y. + * @param px0 First point x. + * @param py0 First point y. + * @param px1 Second point x. + * @param py1 Second point y. + * + * @return < 0 if points on opposite sides, <br> + * = 0 if one of the points is exactly on the line, or <br> + * > 0 if points on same side. <br> + */ + public static int sameSide( float x0, float y0, float x1, float y1, + float px0, float py0, float px1, float py1 ) + { + int sameSide = 0; + + float dx = x1 - x0; + float dy = y1 - y0; + float dx1 = px0 - x0; + float dy1 = py0 - y0; + float dx2 = px1 - x1; + float dy2 = py1 - y1; + + // Cross product of the vector from the endpoint of the line to the point + float c1 = dx * dy1 - dy * dx1; + float c2 = dx * dy2 - dy * dx2; + + if ( c1 != 0 && c2 != 0 ) + { + sameSide = c1 < 0 == c2 < 0 ? 1 : -1; + } + else if ( dx == 0 && dx1 == 0 && dx2 == 0 ) + { + sameSide = !isBetween( y0, y1, py0 ) && !isBetween( y0, y1, py1 ) ? 1 : 0; + } + else if ( dy == 0 && dy1 == 0 && dy2 == 0 ) + { + sameSide = !isBetween( x0, x1, px0 ) && !isBetween( x0, x1, px1 ) ? 1 : 0; + } + + return sameSide; + } + + //====================================================================== + // Private Methods + + private GeometryUtils() + { + } + +} + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.