From a4b1b6df12603da17e5cb0d7e5325d6649106c39 Mon Sep 17 00:00:00 2001 From: yomboprime Date: Wed, 27 Mar 2019 11:22:34 +0100 Subject: [PATCH] Add stroke generation to SVGLoader --- examples/files.js | 1 + examples/js/loaders/SVGLoader.js | 899 ++++++++++++++++++++++- examples/models/svg/lineJoinsAndCaps.svg | 428 +++++++++++ examples/models/svg/threejs.svg | 155 ++++ examples/webgl_geometry_text_stroke.html | 168 +++++ examples/webgl_loader_svg.html | 102 ++- 6 files changed, 1699 insertions(+), 54 deletions(-) create mode 100644 examples/models/svg/lineJoinsAndCaps.svg create mode 100644 examples/models/svg/threejs.svg create mode 100644 examples/webgl_geometry_text_stroke.html diff --git a/examples/files.js b/examples/files.js index 5052fecf56..bc5f605647 100644 --- a/examples/files.js +++ b/examples/files.js @@ -44,6 +44,7 @@ var files = { "webgl_geometry_terrain_raycast", "webgl_geometry_text", "webgl_geometry_text_shapes", + "webgl_geometry_text_stroke", "webgl_hdr", "webgl_helpers", "webgl_interactive_buffergeometry", diff --git a/examples/js/loaders/SVGLoader.js b/examples/js/loaders/SVGLoader.js index 51775e23a5..d65e2dca08 100644 --- a/examples/js/loaders/SVGLoader.js +++ b/examples/js/loaders/SVGLoader.js @@ -56,37 +56,37 @@ THREE.SVGLoader.prototype = { case 'path': style = parseStyle( node, style ); - if ( node.hasAttribute( 'd' ) && isVisible( style ) ) path = parsePathNode( node, style ); + if ( node.hasAttribute( 'd' ) ) path = parsePathNode( node, style ); break; case 'rect': style = parseStyle( node, style ); - if ( isVisible( style ) ) path = parseRectNode( node, style ); + path = parseRectNode( node, style ); break; case 'polygon': style = parseStyle( node, style ); - if ( isVisible( style ) ) path = parsePolygonNode( node, style ); + path = parsePolygonNode( node, style ); break; case 'polyline': style = parseStyle( node, style ); - if ( isVisible( style ) ) path = parsePolylineNode( node, style ); + path = parsePolylineNode( node, style ); break; case 'circle': style = parseStyle( node, style ); - if ( isVisible( style ) ) path = parseCircleNode( node, style ); + path = parseCircleNode( node, style ); break; case 'ellipse': style = parseStyle( node, style ); - if ( isVisible( style ) ) path = parseEllipseNode( node, style ); + path = parseEllipseNode( node, style ); break; case 'line': style = parseStyle( node, style ); - if ( isVisible( style ) ) path = parseLineNode( node, style ); + path = parseLineNode( node, style ); break; default: @@ -96,9 +96,16 @@ THREE.SVGLoader.prototype = { if ( path ) { + if ( style.fill !== undefined && style.fill !== 'none' ) { + + path.color.setStyle( style.fill ); + + } + transformPath( path, currentTransform ); paths.push( path ); + path.userData = { node: node, style: style }; } @@ -122,7 +129,6 @@ THREE.SVGLoader.prototype = { function parsePathNode( node, style ) { var path = new THREE.ShapePath(); - path.color.setStyle( style.fill ); var point = new THREE.Vector2(); var control = new THREE.Vector2(); @@ -545,7 +551,6 @@ THREE.SVGLoader.prototype = { var h = parseFloat( node.getAttribute( 'height' ) ); var path = new THREE.ShapePath(); - path.color.setStyle( style.fill ); path.moveTo( x + 2 * rx, y ); path.lineTo( x + w - 2 * rx, y ); if ( rx !== 0 || ry !== 0 ) path.bezierCurveTo( x + w, y, x + w, y, x + w, y + 2 * ry ); @@ -591,7 +596,6 @@ THREE.SVGLoader.prototype = { var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; var path = new THREE.ShapePath(); - path.color.setStyle( style.fill ); var index = 0; @@ -623,7 +627,6 @@ THREE.SVGLoader.prototype = { var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g; var path = new THREE.ShapePath(); - path.color.setStyle( style.fill ); var index = 0; @@ -645,7 +648,6 @@ THREE.SVGLoader.prototype = { subpath.absarc( x, y, r, 0, Math.PI * 2 ); var path = new THREE.ShapePath(); - path.color.setStyle( style.fill ); path.subPaths.push( subpath ); return path; @@ -663,7 +665,6 @@ THREE.SVGLoader.prototype = { subpath.absellipse( x, y, rx, ry, 0, Math.PI * 2 ); var path = new THREE.ShapePath(); - path.color.setStyle( style.fill ); path.subPaths.push( subpath ); return path; @@ -692,19 +693,37 @@ THREE.SVGLoader.prototype = { style = Object.assign( {}, style ); // clone style - if ( node.hasAttribute( 'fill' ) ) style.fill = node.getAttribute( 'fill' ); - if ( node.style.fill !== '' ) style.fill = node.style.fill; + function addStyle( svgName, jsName, adjustFunction ) { - if ( node.hasAttribute( 'fill-opacity' ) ) style.fillOpacity = node.getAttribute( 'fill-opacity' ); - if ( node.style.fillOpacity !== '' ) style.fillOpacity = node.style.fillOpacity; + if ( adjustFunction === undefined ) adjustFunction = function copy( v ) { return v; }; - return style; + if ( node.hasAttribute( svgName ) ) style[ jsName ] = adjustFunction( node.getAttribute( svgName ) ); + if ( node.style[ svgName ] !== '' ) style[ jsName ] = adjustFunction( node.style[ svgName ] ); - } + } + + function clamp( v ) { + + return Math.max( 0, Math.min( 1, v ) ); + + } - function isVisible( style ) { + function positive( v ) { - return style.fill !== 'none' && style.fill !== 'transparent'; + return Math.max( 0, v ); + + } + + addStyle( 'fill', 'fill' ); + addStyle( 'fill-opacity', 'fillOpacity', clamp ); + addStyle( 'stroke', 'stroke' ); + addStyle( 'stroke-opacity', 'strokeOpacity', clamp ); + addStyle( 'stroke-width', 'strokeWidth', positive ); + addStyle( 'stroke-linejoin', 'strokeLineJoin' ); + addStyle( 'stroke-linecap', 'strokeLineCap' ); + addStyle( 'stroke-miterlimit', 'strokeMiterLimit', positive ); + + return style; } @@ -754,7 +773,7 @@ THREE.SVGLoader.prototype = { return null; } - var transform = parseTransformNode( node ); + var transform = parseNodeTransform( node ); if ( transform ) { @@ -771,13 +790,13 @@ THREE.SVGLoader.prototype = { } - function parseTransformNode( node ) { + function parseNodeTransform( node ) { var transform = new THREE.Matrix3(); var currentTransform = tempTransform0; var transformsTexts = node.getAttribute( 'transform' ).split( ' ' ); - for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex-- ) { + for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { var transformText = transformsTexts[ tIndex ]; var openParPos = transformText.indexOf( "(" ); @@ -924,9 +943,6 @@ THREE.SVGLoader.prototype = { var isRotated = isTransformRotated( m ); - var tempV2 = new THREE.Vector2(); - var tempV3 = new THREE.Vector3(); - var subPaths = path.subPaths; for ( var i = 0, n = subPaths.length; i < n; i++ ) { @@ -1004,9 +1020,13 @@ THREE.SVGLoader.prototype = { var tempTransform1 = new THREE.Matrix3(); var tempTransform2 = new THREE.Matrix3(); var tempTransform3 = new THREE.Matrix3(); + var tempV2 = new THREE.Vector2(); + var tempV3 = new THREE.Vector3(); var currentTransform = new THREE.Matrix3(); + var scope = this; + console.time( 'THREE.SVGLoader: DOMParser' ); var xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml @@ -1015,8 +1035,16 @@ THREE.SVGLoader.prototype = { console.time( 'THREE.SVGLoader: Parse' ); - parseNode( xml.documentElement, { fill: '#000' } ); - + parseNode( xml.documentElement, { + fill: '#000', + fillOpacity: 1, + strokeOpacity: 1, + strokeWidth: 1, + strokeLineJoin: 'miter', + strokeLineCap: 'butt', + strokeMiterLimit: 4 + } ); + var data = { paths: paths, xml: xml.documentElement }; // console.log( paths ); @@ -1024,9 +1052,820 @@ THREE.SVGLoader.prototype = { console.timeEnd( 'THREE.SVGLoader: Parse' ); - return data; } }; + +THREE.SVGLoader.getStrokeStyle = function ( width, color, opacity, lineJoin, lineCap, miterLimit ) { + + // Param width: Stroke width + // Param color: As returned by THREE.Color.getStyle() + // Param opacity: 0 (transparent) to 1 (opaque) + // Param lineJoin: One of "round", "bevel", "miter" or "miter-limit" + // Param lineCap: One of "round", "square" or "butt" + // Param miterLimit: Maximum join length, in multiples of the "width" parameter (join is truncated if it exceeds that distance) + // Returns style object + + width = width !== undefined ? width : 1; + color = color !== undefined ? color : '#000'; + opacity = opacity !== undefined ? opacity : 1; + lineJoin = lineJoin !== undefined ? lineJoin : 'miter'; + lineCap = lineCap !== undefined ? lineCap : 'butt'; + miterLimit = miterLimit !== undefined ? miterLimit : 4; + + return { + strokeColor: color, + strokeWidth: width, + strokeLineJoin: lineJoin, + strokeLineCap: lineCap, + strokeMiterLimit: miterLimit + }; + +}; + +THREE.SVGLoader.pointsToStroke = function ( points, style, arcDivisions, minDistance ) { + + // Generates a stroke with some witdh around the given path. + // The path can be open or closed (last point equals to first point) + // Param points: Array of Vector2D (the path). Minimum 2 points. + // Param style: Object with SVG properties as returned by SVGLoader.getStrokeStyle(), or SVGLoader.parse() in the path.userData.style object + // Params arcDivisions: Arc divisions for round joins and endcaps. (Optional) + // Param minDistance: Points closer to this distance will be merged. (Optional) + // Returns BufferGeometry with stroke triangles (In plane z = 0). UV coordinates are generated ('u' along path. 'v' across it, from left to right) + + var vertices = []; + var normals = []; + var uvs = []; + + if ( THREE.SVGLoader.pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs ) === 0 ) { + + return null; + + } + + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) ); + geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) ); + + return geometry; + +}; + +THREE.SVGLoader.pointsToStrokeWithBuffers = function () { + + var tempV2_1 = new THREE.Vector2(); + var tempV2_2 = new THREE.Vector2(); + var tempV2_3 = new THREE.Vector2(); + var tempV2_4 = new THREE.Vector2(); + var tempV2_5 = new THREE.Vector2(); + var tempV2_6 = new THREE.Vector2(); + var tempV2_7 = new THREE.Vector2(); + var tempV3_1 = new THREE.Vector3(); + var lastPointL = new THREE.Vector2(); + var lastPointR = new THREE.Vector2(); + var point0L = new THREE.Vector2(); + var point0R = new THREE.Vector2(); + var currentPointL = new THREE.Vector2(); + var currentPointR = new THREE.Vector2(); + var nextPointL = new THREE.Vector2(); + var nextPointR = new THREE.Vector2(); + var innerPoint = new THREE.Vector2(); + var outerPoint = new THREE.Vector2(); + var tempTransform0 = new THREE.Matrix3(); + var tempTransform1 = new THREE.Matrix3(); + var tempTransform2 = new THREE.Matrix3(); + + return function ( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { + + // This function can be called to update existing arrays or buffers. + // Accepts same parameters as pointsToStroke, plus the buffers and optional offset. + // Param vertexOffset: Offset vertices to start writing in the buffers (3 elements/vertex for vertices and normals, and 2 elements/vertex for uvs) + // Returns number of written vertices / normals / uvs + // if 'vertices' parameter is undefined no triangles will be generated, but the returned vertices count will still be valid (useful to preallocate the buffers) + + arcLengthDivisions = arcDivisions !== undefined ? arcDivisions : 12; + minDistance = minDistance !== undefined ? minDistance : 0.001; + vertexOffset = vertexOffset !== undefined ? vertexOffset : 0; + + // First ensure there are no duplicated points + points = removeDuplicatedPoints( points ); + + var numPoints = points.length; + + if ( numPoints < 2 ) return 0; + + var isClosed = points[ 0 ].equals( points[ numPoints - 1 ] ); + + var currentPoint; + var previousPoint = points[ 0 ]; + var nextPoint; + + var strokeWidth2 = style.strokeWidth / 2; + + var deltaU = 1 / ( numPoints - 1 ); + var u0 = 0; + + var innerSideModified; + var joinIsOnLeftSide; + var isMiter; + var initialJoinIsOnLeftSide = false; + + var numVertices = 0; + var currentCoordinate = vertexOffset * 3; + var currentCoordinateUV = vertexOffset * 2; + + // Get initial left and right stroke points + getNormal( points[ 0 ], points[ 1 ], tempV2_1 ).multiplyScalar( strokeWidth2 ); + lastPointL.copy( points[ 0 ] ).sub( tempV2_1 ); + lastPointR.copy( points[ 0 ] ).add( tempV2_1 ); + point0L.copy( lastPointL ); + point0R.copy( lastPointR ); + + for ( var iPoint = 1; iPoint < numPoints; iPoint ++ ) { + + currentPoint = points[ iPoint ]; + + // Get next point + if ( iPoint === numPoints - 1 ) { + + if ( isClosed ) { + + // Skip duplicated initial point + nextPoint = points[ 1 ]; + + } + else nextPoint = undefined; + + } + else { + + nextPoint = points[ iPoint + 1 ]; + + } + + // Normal of previous segment in tempV2_1 + var normal1 = tempV2_1; + getNormal( previousPoint, currentPoint, normal1 ); + + tempV2_3.copy( normal1 ).multiplyScalar( strokeWidth2 ); + currentPointL.copy( currentPoint ).sub( tempV2_3 ); + currentPointR.copy( currentPoint ).add( tempV2_3 ); + + var u1 = u0 + deltaU; + + innerSideModified = false; + + if ( nextPoint !== undefined ) { + + // Normal of next segment in tempV2_2 + getNormal( currentPoint, nextPoint, tempV2_2 ); + + tempV2_3.copy( tempV2_2 ).multiplyScalar( strokeWidth2 ); + nextPointL.copy( currentPoint ).sub( tempV2_3 ); + nextPointR.copy( currentPoint ).add( tempV2_3 ); + + joinIsOnLeftSide = true; + tempV2_3.subVectors( nextPoint, previousPoint ); + if ( normal1.dot( tempV2_3 ) < 0 ) { + + joinIsOnLeftSide = false; + + } + if ( iPoint === 1 ) initialJoinIsOnLeftSide = joinIsOnLeftSide; + + tempV2_3.subVectors( nextPoint, currentPoint ) + var maxInnerDistance = tempV2_3.normalize(); + var dot = Math.abs( normal1.dot( tempV2_3 ) ); + + // If path is straight, don't create join + if ( dot !== 0 ) { + + // Compute inner and outer segment intersections + var miterSide = strokeWidth2 / dot; + tempV2_3.multiplyScalar( - miterSide ); + tempV2_4.subVectors( currentPoint, previousPoint ); + tempV2_5.copy( tempV2_4 ).setLength( miterSide ).add( tempV2_3 ); + innerPoint.copy( tempV2_5 ).negate(); + var miterLength2 = tempV2_5.length(); + var segmentLengthPrev = tempV2_4.length(); + tempV2_4.divideScalar( segmentLengthPrev ); + tempV2_6.subVectors( nextPoint, currentPoint ); + var segmentLengthNext = tempV2_6.length(); + tempV2_6.divideScalar( segmentLengthNext ); + // Check that previous and next segments doesn't overlap with the innerPoint of intersection + if ( tempV2_4.dot( innerPoint ) < segmentLengthPrev && tempV2_6.dot( innerPoint ) < segmentLengthNext ) { + + innerSideModified = true; + + } + outerPoint.copy( tempV2_5 ).add( currentPoint ); + innerPoint.add( currentPoint ); + + isMiter = false; + + if ( innerSideModified ) { + + if ( joinIsOnLeftSide ) { + + nextPointR.copy( innerPoint ); + currentPointR.copy( innerPoint ); + + } + else { + + nextPointL.copy( innerPoint ); + currentPointL.copy( innerPoint ); + + } + + } + else { + + // The segment triangles are generated here if there was overlapping + + makeSegmentTriangles(); + + } + + switch ( style.strokeLineJoin ) { + + case 'bevel': + + makeSegmentWithBevelJoin( joinIsOnLeftSide, innerSideModified, u1 ); + + break; + + case 'round': + + // Segment triangles + + createSegmentTrianglesWithMiddleSection( joinIsOnLeftSide, innerSideModified ); + + // Join triangles + + if ( joinIsOnLeftSide ) { + + makeCircularSector( currentPoint, currentPointL, nextPointL, u1, 0 ); + + } + else { + + makeCircularSector( currentPoint, nextPointR, currentPointR, u1, 1 ); + + } + + break; + + case 'miter': + case 'miter-clip': + default: + + var miterFraction = ( strokeWidth2 * style.strokeMiterLimit ) / miterLength2; + + if ( miterFraction < 1 ) { + + // The join miter length exceeds the miter limit + + if ( style.strokeLineJoin !== 'miter-clip' ) { + + makeSegmentWithBevelJoin( joinIsOnLeftSide, innerSideModified, u1 ); + break; + + } + else { + + // Segment triangles + + createSegmentTrianglesWithMiddleSection( joinIsOnLeftSide, innerSideModified ); + + // Miter-clip join triangles + + if ( joinIsOnLeftSide ) { + + tempV2_6.subVectors( outerPoint, currentPointL ).multiplyScalar( miterFraction ).add( currentPointL ); + tempV2_7.subVectors( outerPoint, nextPointL ).multiplyScalar( miterFraction ).add( nextPointL ); + + addVertex( currentPointL, u1, 0 ); + addVertex( tempV2_6, u1, 0 ); + addVertex( currentPoint, u1, 0.5 ); + + addVertex( currentPoint, u1, 0.5 ); + addVertex( tempV2_6, u1, 0 ); + addVertex( tempV2_7, u1, 0 ); + + addVertex( currentPoint, u1, 0.5 ); + addVertex( tempV2_7, u1, 0 ); + addVertex( nextPointL, u1, 0 ); + + } + else { + + tempV2_6.subVectors( outerPoint, currentPointR ).multiplyScalar( miterFraction ).add( currentPointR ); + tempV2_7.subVectors( outerPoint, nextPointR ).multiplyScalar( miterFraction ).add( nextPointR ); + + addVertex( currentPointR, u1, 1 ); + addVertex( tempV2_6, u1, 1 ); + addVertex( currentPoint, u1, 0.5 ); + + addVertex( currentPoint, u1, 0.5 ); + addVertex( tempV2_6, u1, 1 ); + addVertex( tempV2_7, u1, 1 ); + + addVertex( currentPoint, u1, 0.5 ); + addVertex( tempV2_7, u1, 1 ); + addVertex( nextPointR, u1, 1 ); + + } + + } + + } + else { + + // Miter join segment triangles + + if ( innerSideModified ) { + + // Optimized segment + join triangles + + if ( joinIsOnLeftSide ) { + + addVertex( lastPointR, u0, 1 ); + addVertex( lastPointL, u0, 0 ); + addVertex( outerPoint, u1, 0 ); + + addVertex( lastPointR, u0, 1 ); + addVertex( outerPoint, u1, 0 ); + addVertex( innerPoint, u1, 1 ); + + } + else { + + addVertex( lastPointR, u0, 1 ); + addVertex( lastPointL, u0, 0 ); + addVertex( outerPoint, u1, 1 ); + + addVertex( lastPointL, u0, 0 ); + addVertex( innerPoint, u1, 0 ); + addVertex( outerPoint, u1, 1 ); + + } + + + if ( joinIsOnLeftSide ) { + + nextPointL.copy( outerPoint ); + + } + else { + + nextPointR.copy( outerPoint ); + + } + + + } + else { + + // Add extra miter join triangles + + if ( joinIsOnLeftSide ) { + + addVertex( currentPointL, u1, 0 ); + addVertex( outerPoint, u1, 0 ); + addVertex( currentPoint, u1, 0.5 ); + + addVertex( currentPoint, u1, 0.5 ); + addVertex( outerPoint, u1, 0 ); + addVertex( nextPointL, u1, 0 ); + + } + else { + + addVertex( currentPointR, u1, 1 ); + addVertex( outerPoint, u1, 1 ); + addVertex( currentPoint, u1, 0.5 ); + + addVertex( currentPoint, u1, 0.5 ); + addVertex( outerPoint, u1, 1 ); + addVertex( nextPointR, u1, 1 ); + + } + + } + + isMiter = true; + + } + + break; + + } + + } + else { + + // The segment triangles are generated here when two consecutive points are collinear + + makeSegmentTriangles(); + + } + + } + else { + + // The segment triangles are generated here if it is the ending segment + + makeSegmentTriangles(); + + } + + if ( ! isClosed && iPoint === numPoints - 1 ) { + + // Start line endcap + addCapGeometry( points[ 0 ], point0L, point0R, joinIsOnLeftSide, true, u0 ); + + } + + // Increment loop variables + + u0 = u1; + + previousPoint = currentPoint; + + lastPointL.copy( nextPointL ); + lastPointR.copy( nextPointR ); + + } + + if ( ! isClosed ) { + + // Ending line endcap + addCapGeometry( currentPoint, currentPointL, currentPointR, joinIsOnLeftSide, false, u1 ); + + } + else if ( innerSideModified && vertices ) { + + // Modify path first segment vertices to adjust to the segments inner and outer intersections + + var lastOuter = outerPoint; + var lastInner = innerPoint; + if ( initialJoinIsOnLeftSide !== joinIsOnLeftSide) { + lastOuter = innerPoint; + lastInner = outerPoint; + } + + if ( joinIsOnLeftSide ) { + + lastInner.toArray( vertices, 0 * 3 ); + lastInner.toArray( vertices, 3 * 3 ); + + if ( isMiter ) { + + lastOuter.toArray( vertices, 1 * 3 ); + } + + } + else { + + lastInner.toArray( vertices, 1 * 3 ); + lastInner.toArray( vertices, 3 * 3 ); + + if ( isMiter ) { + + lastOuter.toArray( vertices, 0 * 3 ); + } + + } + + } + + return numVertices; + + // -- End of algorithm + + // -- Functions + + function getNormal( p1, p2, result ) { + + result.subVectors( p2, p1 ); + return result.set( - result.y, result.x ).normalize(); + + } + + function addVertex( position, u, v ) { + + if ( vertices ) { + + vertices[ currentCoordinate ] = position.x; + vertices[ currentCoordinate + 1 ] = position.y; + vertices[ currentCoordinate + 2 ] = 0; + + normals[ currentCoordinate ] = 0; + normals[ currentCoordinate + 1 ] = 0; + normals[ currentCoordinate + 2 ] = 1; + + uvs[ currentCoordinateUV ] = u; + uvs[ currentCoordinateUV + 1 ] = v; + + currentCoordinate += 3; + currentCoordinateUV += 2; + + } + + numVertices += 3; + + } + + function makeCircularSector( center, p1, p2, u, v ) { + + // param p1, p2: Points in the circle arc. + // p1 and p2 are in clockwise direction. + + tempV2_1.copy( p1 ).sub( center ).normalize(); + tempV2_2.copy( p2 ).sub( center ).normalize(); + + var angle = Math.PI; + var dot = tempV2_1.dot( tempV2_2 ); + if ( Math.abs( dot ) < 1 ) angle = Math.abs( Math.acos( dot ) ); + + angle /= arcLengthDivisions; + + tempV2_3.copy( p1 ); + + for ( var i = 0, il = arcLengthDivisions - 1; i < il; i++ ) { + + tempV2_4.copy( tempV2_3 ).rotateAround( center, angle ); + + addVertex( tempV2_3, u, v ); + addVertex( tempV2_4, u, v ); + addVertex( center, u, 0.5 ); + + tempV2_3.copy( tempV2_4 ); + } + + addVertex( tempV2_4, u, v ); + addVertex( p2, u, v ); + addVertex( center, u, 0.5 ); + + } + + function makeSegmentTriangles() { + + addVertex( lastPointR, u0, 1 ); + addVertex( lastPointL, u0, 0 ); + addVertex( currentPointL, u1, 0 ); + + addVertex( lastPointR, u0, 1 ); + addVertex( currentPointL, u1, 1 ); + addVertex( currentPointR, u1, 0 ); + + } + + function makeSegmentWithBevelJoin( joinIsOnLeftSide, innerSideModified, u ) { + + if ( innerSideModified ) { + + // Optimized segment + bevel triangles + + if ( joinIsOnLeftSide ) { + + // Path segments triangles + + addVertex( lastPointR, u0, 1 ); + addVertex( lastPointL, u0, 0 ); + addVertex( currentPointL, u1, 0 ); + + addVertex( lastPointR, u0, 1 ); + addVertex( currentPointL, u1, 0 ); + addVertex( innerPoint, u1, 1 ); + + // Bevel join triangle + + addVertex( currentPointL, u, 0 ); + addVertex( nextPointL, u, 0 ); + addVertex( innerPoint, u, 0.5 ); + + } + else { + + // Path segments triangles + + addVertex( lastPointR, u0, 1 ); + addVertex( lastPointL, u0, 0 ); + addVertex( currentPointR, u1, 1 ); + + addVertex( lastPointL, u0, 0 ); + addVertex( innerPoint, u1, 0 ); + addVertex( currentPointR, u1, 1 ); + + // Bevel join triangle + + addVertex( currentPointR, u, 1 ); + addVertex( nextPointR, u, 0 ); + addVertex( innerPoint, u, 0.5 ); + + } + + } + else { + + // Bevel join triangle. The segment triangles are done in the main loop + + if ( joinIsOnLeftSide ) { + + addVertex( currentPointL, u, 0 ); + addVertex( nextPointL, u, 0 ); + addVertex( currentPoint, u, 0.5 ); + + } + else { + + addVertex( currentPointR, u, 1 ); + addVertex( nextPointR, u, 0 ); + addVertex( currentPoint, u, 0.5 ); + + } + + } + + } + + function createSegmentTrianglesWithMiddleSection( joinIsOnLeftSide, innerSideModified ) { + + if ( innerSideModified ) { + + if ( joinIsOnLeftSide ) { + + addVertex( lastPointR, u0, 1 ); + addVertex( lastPointL, u0, 0 ); + addVertex( currentPointL, u1, 0 ); + + addVertex( lastPointR, u0, 1 ); + addVertex( currentPointL, u1, 0 ); + addVertex( innerPoint, u1, 1 ); + + addVertex( currentPointL, u0, 0 ); + addVertex( currentPoint, u1, 0.5 ); + addVertex( innerPoint, u1, 1 ); + + addVertex( currentPoint, u1, 0.5 ); + addVertex( nextPointL, u0, 0 ); + addVertex( innerPoint, u1, 1 ); + + } + else { + + addVertex( lastPointR, u0, 1 ); + addVertex( lastPointL, u0, 0 ); + addVertex( currentPointR, u1, 1 ); + + addVertex( lastPointL, u0, 0 ); + addVertex( innerPoint, u1, 0 ); + addVertex( currentPointR, u1, 1 ); + + addVertex( currentPointR, u0, 1 ); + addVertex( innerPoint, u1, 0 ); + addVertex( currentPoint, u1, 0.5 ); + + addVertex( currentPoint, u1, 0.5 ); + addVertex( innerPoint, u1, 0 ); + addVertex( nextPointR, u0, 1 ); + + } + + } + + } + + function addCapGeometry( center, p1, p2, joinIsOnLeftSide, start, u ) { + + // param center: End point of the path + // param p1, p2: Left and right cap points + + switch ( style.strokeLineCap ) { + + case 'round': + + if ( start ) { + + makeCircularSector( center, p2, p1, u, 0.5 ); + + } + else { + + makeCircularSector( center, p1, p2, u, 0.5 ); + + } + + break; + + case 'square': + + if ( start ) { + + tempV2_1.subVectors( p1, center ); + tempV2_2.set( tempV2_1.y, - tempV2_1.x ); + + tempV2_3.addVectors( tempV2_1, tempV2_2 ).add( center ); + tempV2_4.subVectors( tempV2_2, tempV2_1 ).add( center ); + + // Modify already existing vertices + if ( joinIsOnLeftSide ) { + + tempV2_3.toArray( vertices, 1 * 3 ); + tempV2_4.toArray( vertices, 0 * 3 ); + tempV2_4.toArray( vertices, 3 * 3 ); + + } + else { + + tempV2_3.toArray( vertices, 1 * 3 ); + tempV2_3.toArray( vertices, 3 * 3 ); + tempV2_4.toArray( vertices, 0 * 3 ); + + } + + } + else { + + tempV2_1.subVectors( p2, center ); + tempV2_2.set( tempV2_1.y, - tempV2_1.x ); + + tempV2_3.addVectors( tempV2_1, tempV2_2 ).add( center ); + tempV2_4.subVectors( tempV2_2, tempV2_1 ).add( center ); + + var vl = vertices.length; + + // Modify already existing vertices + if ( joinIsOnLeftSide ) { + + tempV2_3.toArray( vertices, vl - 1 * 3 ); + tempV2_4.toArray( vertices, vl - 2 * 3 ); + tempV2_4.toArray( vertices, vl - 4 * 3 ); + + } + else { + + tempV2_3.toArray( vertices, vl - 2 * 3 ); + tempV2_4.toArray( vertices, vl - 1 * 3 ); + tempV2_4.toArray( vertices, vl - 4 * 3 ); + + } + + } + + break; + + case 'butt': + default: + + // Nothing to do here + break; + + } + + } + + function removeDuplicatedPoints( points ) { + + // Creates a new array if necessary with duplicated points removed. + // This does not remove duplicated initial and ending points of a closed path. + + var dupPoints = false; + for ( var i = 1, n = points.length - 1; i < n; i ++ ) { + + if ( points[ i ].distanceTo( points[ i + 1 ] ) < minDistance ) { + + dupPoints = true; + break; + + } + + } + + if ( ! dupPoints ) return points; + + var newPoints = []; + newPoints.push( points[ 0 ] ); + + for ( var i = 1, n = points.length - 1; i < n; i ++ ) { + + if ( points[ i ].distanceTo( points[ i + 1 ] ) >= minDistance ) { + + newPoints.push( points[ i ] ); + + } + } + + newPoints.push( points[ points.length - 1 ] ); + + return newPoints; + + } + }; + +}(); diff --git a/examples/models/svg/lineJoinsAndCaps.svg b/examples/models/svg/lineJoinsAndCaps.svg new file mode 100644 index 0000000000..aa1cda4650 --- /dev/null +++ b/examples/models/svg/lineJoinsAndCaps.svg @@ -0,0 +1,428 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/models/svg/threejs.svg b/examples/models/svg/threejs.svg new file mode 100644 index 0000000000..7998149684 --- /dev/null +++ b/examples/models/svg/threejs.svg @@ -0,0 +1,155 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/webgl_geometry_text_stroke.html b/examples/webgl_geometry_text_stroke.html new file mode 100644 index 0000000000..6aebc22f38 --- /dev/null +++ b/examples/webgl_geometry_text_stroke.html @@ -0,0 +1,168 @@ + + + + three.js webgl - Simple text from json + + + + + +
+ three.js webgl - Simple text from json fonts. +
+ + + + + + + + + diff --git a/examples/webgl_loader_svg.html b/examples/webgl_loader_svg.html index f45b8bf1d8..dff806404b 100644 --- a/examples/webgl_loader_svg.html +++ b/examples/webgl_loader_svg.html @@ -44,7 +44,7 @@