/**
 * Temporary fork of Three.js example.
 * Waiting for fix of https://github.com/mrdoob/three.js/issues/21330
 * Main changes in parseDefs
 */ import { Box2 , BufferGeometry , FileLoader , Float32BufferAttribute , Loader , Matrix3 , Path , Shape , ShapePath , ShapeUtils , Vector2 , Vector3 } from 'three' ; class SVGLoader extends Loader { defaultDPI = 90 ; defaultUnit = 'px' ; defs = [ ] ; constructor ( manager ? ) { super ( manager ) ; this . defaultDPI = 90 ; this . defaultUnit = 'px' ; } load ( url , onLoad , onProgress , onError ) { const scope = this ; const loader = new FileLoader ( scope . manager ) ; loader . setPath ( scope . path ) ; loader . setRequestHeader ( scope . requestHeader ) ; loader . setWithCredentials ( scope . withCredentials ) ; loader . load ( url , function ( text ) { try { onLoad ( scope . parse ( text ) ) ; } catch ( e ) { if ( onError ) { onError ( e ) ; } else { console . error ( e ) ; } scope . manager . itemError ( url ) ; } } , onProgress , onError ) ; } parse ( text ) { const scope = this ; function parseNode ( node , style ) { if ( node . nodeType !== 1 ) return ; const transform = getNodeTransform ( node ) ; let traverseChildNodes = true ; let path = null ; switch ( node . nodeName ) { case 'svg' : break ; case 'style' : parseCSSStylesheet ( node ) ; break ; case 'g' : style = parseStyle ( node , style ) ; break ; case 'path' : style = parseStyle ( node , style ) ; if ( node . hasAttribute ( 'd' ) ) path = parsePathNode ( node ) ; break ; case 'rect' : style = parseStyle ( node , style ) ; path = parseRectNode ( node ) ; break ; case 'polygon' : style = parseStyle ( node , style ) ; path = parsePolygonNode ( node ) ; break ; case 'polyline' : style = parseStyle ( node , style ) ; path = parsePolylineNode ( node ) ; break ; case 'circle' : style = parseStyle ( node , style ) ; path = parseCircleNode ( node ) ; break ; case 'ellipse' : style = parseStyle ( node , style ) ; path = parseEllipseNode ( node ) ; break ; case 'line' : style = parseStyle ( node , style ) ; path = parseLineNode ( node ) ; break ; case 'defs' : traverseChildNodes = false ; parseDefs ( node ) ; break ; case 'use' : style = parseStyle ( node , style ) ; const usedNodeId = node . href . baseVal . substring ( 1 ) ; const usedNode = node . viewportElement . getElementById ( usedNodeId ) ; if ( usedNode ) { parseNode ( usedNode , style ) ; } else { console . warn ( 'SVGLoader: \'use node\' references non-existent node id: ' + usedNodeId ) ; } break ; default : } 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 } ; } if ( traverseChildNodes ) { const nodes = node . childNodes ; for ( let i = 0 ; i < nodes . length ; i ++ ) { parseNode ( nodes [ i ] , style ) ; } } if ( transform ) { transformStack . pop ( ) ; if ( transformStack . length > 0 ) { currentTransform . copy ( transformStack [ transformStack . length - 1 ] ) ; } else { currentTransform . identity ( ) ; } } } function parsePathNode ( node ) { const path = new ShapePath ( ) ; const point = new Vector2 ( ) ; const control = new Vector2 ( ) ; const firstPoint = new Vector2 ( ) ; let isFirstPoint = true ; let doSetFirstPoint = false ; const d = node . getAttribute ( 'd' ) ; const commands = d . match ( /[a-df-z][^a-df-z]*/ig ) ; for ( let i = 0 , l = commands . length ; i < l ; i ++ ) { const command = commands [ i ] ; const type = command . charAt ( 0 ) ; const data = command . substr ( 1 ) . trim ( ) ; if ( isFirstPoint === true ) { doSetFirstPoint = true ; isFirstPoint = false ; } let numbers ; switch ( type ) { case 'M' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 2 ) { point . x = numbers [ j + 0 ] ; point . y = numbers [ j + 1 ] ; control . x = point . x ; control . y = point . y ; if ( j === 0 ) { path . moveTo ( point . x , point . y ) ; } else { path . lineTo ( point . x , point . y ) ; } if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'H' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j ++ ) { point . x = numbers [ j ] ; control . x = point . x ; control . y = point . y ; path . lineTo ( point . x , point . y ) ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'V' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j ++ ) { point . y = numbers [ j ] ; control . x = point . x ; control . y = point . y ; path . lineTo ( point . x , point . y ) ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'L' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 2 ) { point . x = numbers [ j + 0 ] ; point . y = numbers [ j + 1 ] ; control . x = point . x ; control . y = point . y ; path . lineTo ( point . x , point . y ) ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'C' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 6 ) { path . bezierCurveTo ( numbers [ j + 0 ] , numbers [ j + 1 ] , numbers [ j + 2 ] , numbers [ j + 3 ] , numbers [ j + 4 ] , numbers [ j + 5 ] ) ; control . x = numbers [ j + 2 ] ; control . y = numbers [ j + 3 ] ; point . x = numbers [ j + 4 ] ; point . y = numbers [ j + 5 ] ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'S' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 4 ) { path . bezierCurveTo ( getReflection ( point . x , control . x ) , getReflection ( point . y , control . y ) , numbers [ j + 0 ] , numbers [ j + 1 ] , numbers [ j + 2 ] , numbers [ j + 3 ] ) ; control . x = numbers [ j + 0 ] ; control . y = numbers [ j + 1 ] ; point . x = numbers [ j + 2 ] ; point . y = numbers [ j + 3 ] ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'Q' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 4 ) { path . quadraticCurveTo ( numbers [ j + 0 ] , numbers [ j + 1 ] , numbers [ j + 2 ] , numbers [ j + 3 ] ) ; control . x = numbers [ j + 0 ] ; control . y = numbers [ j + 1 ] ; point . x = numbers [ j + 2 ] ; point . y = numbers [ j + 3 ] ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'T' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 2 ) { const rx = getReflection ( point . x , control . x ) ; const ry = getReflection ( point . y , control . y ) ; path . quadraticCurveTo ( rx , ry , numbers [ j + 0 ] , numbers [ j + 1 ] ) ; control . x = rx ; control . y = ry ; point . x = numbers [ j + 0 ] ; point . y = numbers [ j + 1 ] ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'A' : numbers = parseFloats ( data , [ 3 , 4 ] , 7 ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 7 ) { if ( numbers [ j + 5 ] == point . x && numbers [ j + 6 ] == point . y ) continue ; const start = point . clone ( ) ; point . x = numbers [ j + 5 ] ; point . y = numbers [ j + 6 ] ; control . x = point . x ; control . y = point . y ; parseArcCommand ( path , numbers [ j ] , numbers [ j + 1 ] , numbers [ j + 2 ] , numbers [ j + 3 ] , numbers [ j + 4 ] , start , point ) ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'm' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 2 ) { point . x += numbers [ j + 0 ] ; point . y += numbers [ j + 1 ] ; control . x = point . x ; control . y = point . y ; if ( j === 0 ) { path . moveTo ( point . x , point . y ) ; } else { path . lineTo ( point . x , point . y ) ; } if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'h' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j ++ ) { point . x += numbers [ j ] ; control . x = point . x ; control . y = point . y ; path . lineTo ( point . x , point . y ) ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'v' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j ++ ) { point . y += numbers [ j ] ; control . x = point . x ; control . y = point . y ; path . lineTo ( point . x , point . y ) ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'l' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 2 ) { point . x += numbers [ j + 0 ] ; point . y += numbers [ j + 1 ] ; control . x = point . x ; control . y = point . y ; path . lineTo ( point . x , point . y ) ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'c' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 6 ) { path . bezierCurveTo ( point . x + numbers [ j + 0 ] , point . y + numbers [ j + 1 ] , point . x + numbers [ j + 2 ] , point . y + numbers [ j + 3 ] , point . x + numbers [ j + 4 ] , point . y + numbers [ j + 5 ] ) ; control . x = point . x + numbers [ j + 2 ] ; control . y = point . y + numbers [ j + 3 ] ; point . x += numbers [ j + 4 ] ; point . y += numbers [ j + 5 ] ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 's' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 4 ) { path . bezierCurveTo ( getReflection ( point . x , control . x ) , getReflection ( point . y , control . y ) , point . x + numbers [ j + 0 ] , point . y + numbers [ j + 1 ] , point . x + numbers [ j + 2 ] , point . y + numbers [ j + 3 ] ) ; control . x = point . x + numbers [ j + 0 ] ; control . y = point . y + numbers [ j + 1 ] ; point . x += numbers [ j + 2 ] ; point . y += numbers [ j + 3 ] ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'q' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 4 ) { path . quadraticCurveTo ( point . x + numbers [ j + 0 ] , point . y + numbers [ j + 1 ] , point . x + numbers [ j + 2 ] , point . y + numbers [ j + 3 ] ) ; control . x = point . x + numbers [ j + 0 ] ; control . y = point . y + numbers [ j + 1 ] ; point . x += numbers [ j + 2 ] ; point . y += numbers [ j + 3 ] ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 't' : numbers = parseFloats ( data ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 2 ) { const rx = getReflection ( point . x , control . x ) ; const ry = getReflection ( point . y , control . y ) ; path . quadraticCurveTo ( rx , ry , point . x + numbers [ j + 0 ] , point . y + numbers [ j + 1 ] ) ; control . x = rx ; control . y = ry ; point . x = point . x + numbers [ j + 0 ] ; point . y = point . y + numbers [ j + 1 ] ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'a' : numbers = parseFloats ( data , [ 3 , 4 ] , 7 ) ; for ( let j = 0 , jl = numbers . length ; j < jl ; j += 7 ) { if ( numbers [ j + 5 ] == 0 && numbers [ j + 6 ] == 0 ) continue ; const start = point . clone ( ) ; point . x += numbers [ j + 5 ] ; point . y += numbers [ j + 6 ] ; control . x = point . x ; control . y = point . y ; parseArcCommand ( path , numbers [ j ] , numbers [ j + 1 ] , numbers [ j + 2 ] , numbers [ j + 3 ] , numbers [ j + 4 ] , start , point ) ; if ( j === 0 && doSetFirstPoint === true ) firstPoint . copy ( point ) ; } break ; case 'Z' : case 'z' : path . currentPath . autoClose = true ; if ( path . currentPath . curves . length > 0 ) { point . copy ( firstPoint ) ; path . currentPath . currentPoint . copy ( point ) ; isFirstPoint = true ; } break ; default : console . warn ( command ) ; } doSetFirstPoint = false ; } return path ; } function parseCSSStylesheet ( node ) { if ( ! node . sheet || ! node . sheet . cssRules || ! node . sheet . cssRules . length ) return ; for ( let i = 0 ; i < node . sheet . cssRules . length ; i ++ ) { const stylesheet = node . sheet . cssRules [ i ] ; if ( stylesheet . type !== 1 ) continue ; const selectorList = stylesheet . selectorText . split ( /,/gm ) . filter ( Boolean ) . map ( i => i . trim ( ) ) ; for ( let j = 0 ; j < selectorList . length ; j ++ ) { stylesheets [ selectorList [ j ] ] = Object . assign ( stylesheets [ selectorList [ j ] ] || { } , stylesheet . style ) ; } } } /**
         * https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
         * https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ Appendix: Endpoint to center arc conversion
         * From
         * rx ry x-axis-rotation large-arc-flag sweep-flag x y
         * To
         * aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation
         */ function parseArcCommand ( path , rx , ry , x_axis_rotation , large_arc_flag , sweep_flag , start , end ) { if ( rx == 0 || ry == 0 ) { path . lineTo ( end . x , end . y ) ; return ; } x_axis_rotation = x_axis_rotation * Math . PI / 180 ; rx = Math . abs ( rx ) ; ry = Math . abs ( ry ) ; const dx2 = ( start . x - end . x ) / 2.0 ; const dy2 = ( start . y - end . y ) / 2.0 ; const x1p = Math . cos ( x_axis_rotation ) * dx2 + Math . sin ( x_axis_rotation ) * dy2 ; const y1p = - Math . sin ( x_axis_rotation ) * dx2 + Math . cos ( x_axis_rotation ) * dy2 ; let rxs = rx * rx ; let rys = ry * ry ; const x1ps = x1p * x1p ; const y1ps = y1p * y1p ; const cr = x1ps / rxs + y1ps / rys ; if ( cr > 1 ) { const s = Math . sqrt ( cr ) ; rx = s * rx ; ry = s * ry ; rxs = rx * rx ; rys = ry * ry ; } const dq = ( rxs * y1ps + rys * x1ps ) ; const pq = ( rxs * rys - dq ) / dq ; let q = Math . sqrt ( Math . max ( 0 , pq ) ) ; if ( large_arc_flag === sweep_flag ) q = - q ; const cxp = q * rx * y1p / ry ; const cyp = - q * ry * x1p / rx ; const cx = Math . cos ( x_axis_rotation ) * cxp - Math . sin ( x_axis_rotation ) * cyp + ( start . x + end . x ) / 2 ; const cy = Math . sin ( x_axis_rotation ) * cxp + Math . cos ( x_axis_rotation ) * cyp + ( start . y + end . y ) / 2 ; const theta = svgAngle ( 1 , 0 , ( x1p - cxp ) / rx , ( y1p - cyp ) / ry ) ; const delta = svgAngle ( ( x1p - cxp ) / rx , ( y1p - cyp ) / ry , ( - x1p - cxp ) / rx , ( - y1p - cyp ) / ry ) % ( Math . PI * 2 ) ; path . currentPath . absellipse ( cx , cy , rx , ry , theta , theta + delta , sweep_flag === 0 , x_axis_rotation ) ; } function svgAngle ( ux , uy , vx , vy ) { const dot = ux * vx + uy * vy ; const len = Math . sqrt ( ux * ux + uy * uy ) * Math . sqrt ( vx * vx + vy * vy ) ; let ang = Math . acos ( Math . max ( - 1 , Math . min ( 1 , dot / len ) ) ) ; if ( ( ux * vy - uy * vx ) < 0 ) ang = - ang ; return ang ; } function parseRectNode ( node ) { const x = parseFloatWithUnits ( node . getAttribute ( 'x' ) || 0 ) ; const y = parseFloatWithUnits ( node . getAttribute ( 'y' ) || 0 ) ; const rx = parseFloatWithUnits ( node . getAttribute ( 'rx' ) || 0 ) ; const ry = parseFloatWithUnits ( node . getAttribute ( 'ry' ) || 0 ) ; const w = parseFloatWithUnits ( node . getAttribute ( 'width' ) ) ; const h = parseFloatWithUnits ( node . getAttribute ( 'height' ) ) ; const path = new ShapePath ( ) ; 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 ) ; path . lineTo ( x + w , y + h - 2 * ry ) ; if ( rx !== 0 || ry !== 0 ) path . bezierCurveTo ( x + w , y + h , x + w , y + h , x + w - 2 * rx , y + h ) ; path . lineTo ( x + 2 * rx , y + h ) ; if ( rx !== 0 || ry !== 0 ) { path . bezierCurveTo ( x , y + h , x , y + h , x , y + h - 2 * ry ) ; } path . lineTo ( x , y + 2 * ry ) ; if ( rx !== 0 || ry !== 0 ) { path . bezierCurveTo ( x , y , x , y , x + 2 * rx , y ) ; } return path ; } function parsePolygonNode ( node ) { function iterator ( match , a , b ) { const x = parseFloatWithUnits ( a ) ; const y = parseFloatWithUnits ( b ) ; if ( index === 0 ) { path . moveTo ( x , y ) ; } else { path . lineTo ( x , y ) ; } index ++ ; } const regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g ; const path = new ShapePath ( ) ; let index = 0 ; node . getAttribute ( 'points' ) . replace ( regex , iterator ) ; path . currentPath . autoClose = true ; return path ; } function parsePolylineNode ( node ) { function iterator ( match , a , b ) { const x = parseFloatWithUnits ( a ) ; const y = parseFloatWithUnits ( b ) ; if ( index === 0 ) { path . moveTo ( x , y ) ; } else { path . lineTo ( x , y ) ; } index ++ ; } const regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g ; const path = new ShapePath ( ) ; let index = 0 ; node . getAttribute ( 'points' ) . replace ( regex , iterator ) ; path . currentPath . autoClose = false ; return path ; } function parseCircleNode ( node ) { const x = parseFloatWithUnits ( node . getAttribute ( 'cx' ) || 0 ) ; const y = parseFloatWithUnits ( node . getAttribute ( 'cy' ) || 0 ) ; const r = parseFloatWithUnits ( node . getAttribute ( 'r' ) || 0 ) ; const subpath = new Path ( ) ; subpath . absarc ( x , y , r , 0 , Math . PI * 2 , true ) ; const path = new ShapePath ( ) ; path . subPaths . push ( subpath ) ; return path ; } function parseEllipseNode ( node ) { const x = parseFloatWithUnits ( node . getAttribute ( 'cx' ) || 0 ) ; const y = parseFloatWithUnits ( node . getAttribute ( 'cy' ) || 0 ) ; const rx = parseFloatWithUnits ( node . getAttribute ( 'rx' ) || 0 ) ; const ry = parseFloatWithUnits ( node . getAttribute ( 'ry' ) || 0 ) ; const subpath = new Path ( ) ; subpath . absellipse ( x , y , rx , ry , 0 , Math . PI * 2 , true , 0 ) ; const path = new ShapePath ( ) ; path . subPaths . push ( subpath ) ; return path ; } function parseLineNode ( node ) { const x1 = parseFloatWithUnits ( node . getAttribute ( 'x1' ) || 0 ) ; const y1 = parseFloatWithUnits ( node . getAttribute ( 'y1' ) || 0 ) ; const x2 = parseFloatWithUnits ( node . getAttribute ( 'x2' ) || 0 ) ; const y2 = parseFloatWithUnits ( node . getAttribute ( 'y2' ) || 0 ) ; const path = new ShapePath ( ) ; path . moveTo ( x1 , y1 ) ; path . lineTo ( x2 , y2 ) ; path . currentPath . autoClose = false ; return path ; } function parseDefs ( node ) { if ( ! scope . defs ) { scope . defs = [ ] ; } const nodes = node . childNodes ; for ( const node of nodes ) { const stops = node . childNodes ; for ( const stop of stops ) { if ( stop . hasAttribute && stop . hasAttribute ( 'stop-color' ) ) { scope . defs [ node . id ] = stop . getAttribute ( 'stop-color' ) ; break ; } } } } function parseStyle ( node , style ) { style = Object . assign ( { } , style ) ; let stylesheetStyles = { } ; if ( node . hasAttribute ( 'class' ) ) { const classSelectors = node . getAttribute ( 'class' ) . split ( /\s/ ) . filter ( Boolean ) . map ( i => i . trim ( ) ) ; for ( let i = 0 ; i < classSelectors . length ; i ++ ) { stylesheetStyles = Object . assign ( stylesheetStyles , stylesheets [ '.' + classSelectors [ i ] ] ) ; } } if ( node . hasAttribute ( 'id' ) ) { stylesheetStyles = Object . assign ( stylesheetStyles , stylesheets [ '#' + node . getAttribute ( 'id' ) ] ) ; } function addStyle ( svgName , jsName , adjustFunction ? ) { if ( adjustFunction === undefined ) adjustFunction = function copy ( v ) { if ( v . startsWith ( 'url' ) ) { let ref = v . match ( /url\(#(.*)\)/ ) ; if ( ref && ref [ 1 ] ) { return scope . defs [ ref [ 1 ] ] ; } else { return v ; } } else { return v ; } } ; if ( node . hasAttribute ( svgName ) ) style [ jsName ] = adjustFunction ( node . getAttribute ( svgName ) ) ; if ( stylesheetStyles [ svgName ] ) style [ jsName ] = adjustFunction ( stylesheetStyles [ svgName ] ) ; if ( node . style && node . style [ svgName ] !== '' ) style [ jsName ] = adjustFunction ( node . style [ svgName ] ) ; } function clamp ( v ) { return Math . max ( 0 , Math . min ( 1 , parseFloatWithUnits ( v ) ) ) ; } function positive ( v ) { return Math . max ( 0 , parseFloatWithUnits ( v ) ) ; } addStyle ( 'fill' , 'fill' ) ; addStyle ( 'fill-opacity' , 'fillOpacity' , clamp ) ; addStyle ( 'opacity' , 'opacity' , 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 ) ; addStyle ( 'visibility' , 'visibility' ) ; return style ; } function getReflection ( a , b ) { return a - ( b - a ) ; } function parseFloats ( input , flags ? , stride ? ) { if ( typeof input !== 'string' ) { throw new TypeError ( 'Invalid input: ' + typeof input ) ; } const RE = { SEPARATOR : /[ \t\r\n\,.\-+]/ , WHITESPACE : /[ \t\r\n]/ , DIGIT : /[\d]/ , SIGN : /[-+]/ , POINT : /\./ , COMMA : /,/ , EXP : /e/i , FLAGS : /[01]/ } ; const SEP = 0 ; const INT = 1 ; const FLOAT = 2 ; const EXP = 3 ; let state = SEP ; let seenComma = true ; let number = '' , exponent = '' ; const result = [ ] ; function throwSyntaxError ( current , i , partial ) { const error = new SyntaxError ( 'Unexpected character "' + current + '" at index ' + i + '.' ) ; throw error ; } function newNumber ( ) { if ( number !== '' ) { if ( exponent === '' ) result . push ( Number ( number ) ) ; else result . push ( Number ( number ) * Math . pow ( 10 , Number ( exponent ) ) ) ; } number = '' ; exponent = '' ; } let current ; const length = input . length ; for ( let i = 0 ; i < length ; i ++ ) { current = input [ i ] ; if ( Array . isArray ( flags ) && flags . includes ( result . length % stride ) && RE . FLAGS . test ( current ) ) { state = INT ; number = current ; newNumber ( ) ; continue ; } if ( state === SEP ) { if ( RE . WHITESPACE . test ( current ) ) { continue ; } if ( RE . DIGIT . test ( current ) || RE . SIGN . test ( current ) ) { state = INT ; number = current ; continue ; } if ( RE . POINT . test ( current ) ) { state = FLOAT ; number = current ; continue ; } if ( RE . COMMA . test ( current ) ) { if ( seenComma ) { throwSyntaxError ( current , i , result ) ; } seenComma = true ; } } if ( state === INT ) { if ( RE . DIGIT . test ( current ) ) { number += current ; continue ; } if ( RE . POINT . test ( current ) ) { number += current ; state = FLOAT ; continue ; } if ( RE . EXP . test ( current ) ) { state = EXP ; continue ; } if ( RE . SIGN . test ( current ) && number . length === 1 && RE . SIGN . test ( number [ 0 ] ) ) { throwSyntaxError ( current , i , result ) ; } } if ( state === FLOAT ) { if ( RE . DIGIT . test ( current ) ) { number += current ; continue ; } if ( RE . EXP . test ( current ) ) { state = EXP ; continue ; } if ( RE . POINT . test ( current ) && number [ number . length - 1 ] === '.' ) { throwSyntaxError ( current , i , result ) ; } } if ( state === EXP ) { if ( RE . DIGIT . test ( current ) ) { exponent += current ; continue ; } if ( RE . SIGN . test ( current ) ) { if ( exponent === '' ) { exponent += current ; continue ; } if ( exponent . length === 1 && RE . SIGN . test ( exponent ) ) { throwSyntaxError ( current , i , result ) ; } } } if ( RE . WHITESPACE . test ( current ) ) { newNumber ( ) ; state = SEP ; seenComma = false ; } else if ( RE . COMMA . test ( current ) ) { newNumber ( ) ; state = SEP ; seenComma = true ; } else if ( RE . SIGN . test ( current ) ) { newNumber ( ) ; state = INT ; number = current ; } else if ( RE . POINT . test ( current ) ) { newNumber ( ) ; state = FLOAT ; number = current ; } else { throwSyntaxError ( current , i , result ) ; } } newNumber ( ) ; return result ; } const units = [ 'mm' , 'cm' , 'in' , 'pt' , 'pc' , 'px' ] ; const unitConversion = { 'mm' : { 'mm' : 1 , 'cm' : 0.1 , 'in' : 1 / 25.4 , 'pt' : 72 / 25.4 , 'pc' : 6 / 25.4 , 'px' : - 1 } , 'cm' : { 'mm' : 10 , 'cm' : 1 , 'in' : 1 / 2.54 , 'pt' : 72 / 2.54 , 'pc' : 6 / 2.54 , 'px' : - 1 } , 'in' : { 'mm' : 25.4 , 'cm' : 2.54 , 'in' : 1 , 'pt' : 72 , 'pc' : 6 , 'px' : - 1 } , 'pt' : { 'mm' : 25.4 / 72 , 'cm' : 2.54 / 72 , 'in' : 1 / 72 , 'pt' : 1 , 'pc' : 6 / 72 , 'px' : - 1 } , 'pc' : { 'mm' : 25.4 / 6 , 'cm' : 2.54 / 6 , 'in' : 1 / 6 , 'pt' : 72 / 6 , 'pc' : 1 , 'px' : - 1 } , 'px' : { 'px' : 1 } } ; function parseFloatWithUnits ( string ) { let theUnit = 'px' ; if ( typeof string === 'string' || string instanceof String ) { for ( let i = 0 , n = units . length ; i < n ; i ++ ) { const u = units [ i ] ; if ( string . endsWith ( u ) ) { theUnit = u ; string = string . substring ( 0 , string . length - u . length ) ; break ; } } } let scale = undefined ; if ( theUnit === 'px' && scope . defaultUnit !== 'px' ) { scale = unitConversion [ 'in' ] [ scope . defaultUnit ] / scope . defaultDPI ; } else { scale = unitConversion [ theUnit ] [ scope . defaultUnit ] ; if ( scale < 0 ) { scale = unitConversion [ theUnit ] [ 'in' ] * scope . defaultDPI ; } } return scale * parseFloat ( string ) ; } function getNodeTransform ( node ) { if ( ! ( node . hasAttribute ( 'transform' ) || ( node . nodeName === 'use' && ( node . hasAttribute ( 'x' ) || node . hasAttribute ( 'y' ) ) ) ) ) { return null ; } const transform = parseNodeTransform ( node ) ; if ( transformStack . length > 0 ) { transform . premultiply ( transformStack [ transformStack . length - 1 ] ) ; } currentTransform . copy ( transform ) ; transformStack . push ( transform ) ; return transform ; } function parseNodeTransform ( node ) { const transform = new Matrix3 ( ) ; const currentTransform = tempTransform0 ; if ( node . nodeName === 'use' && ( node . hasAttribute ( 'x' ) || node . hasAttribute ( 'y' ) ) ) { const tx = parseFloatWithUnits ( node . getAttribute ( 'x' ) ) ; const ty = parseFloatWithUnits ( node . getAttribute ( 'y' ) ) ; transform . translate ( tx , ty ) ; } if ( node . hasAttribute ( 'transform' ) ) { const transformsTexts = node . getAttribute ( 'transform' ) . split ( ')' ) ; for ( let tIndex = transformsTexts . length - 1 ; tIndex >= 0 ; tIndex -- ) { const transformText = transformsTexts [ tIndex ] . trim ( ) ; if ( transformText === '' ) continue ; const openParPos = transformText . indexOf ( '(' ) ; const closeParPos = transformText . length ; if ( openParPos > 0 && openParPos < closeParPos ) { const transformType = transformText . substr ( 0 , openParPos ) ; const array = parseFloats ( transformText . substr ( openParPos + 1 , closeParPos - openParPos - 1 ) ) ; currentTransform . identity ( ) ; switch ( transformType ) { case 'translate' : if ( array . length >= 1 ) { const tx = array [ 0 ] ; let ty = tx ; if ( array . length >= 2 ) { ty = array [ 1 ] ; } currentTransform . translate ( tx , ty ) ; } break ; case 'rotate' : if ( array . length >= 1 ) { let angle = 0 ; let cx = 0 ; let cy = 0 ; angle = - array [ 0 ] * Math . PI / 180 ; if ( array . length >= 3 ) { cx = array [ 1 ] ; cy = array [ 2 ] ; } tempTransform1 . identity ( ) . translate ( - cx , - cy ) ; tempTransform2 . identity ( ) . rotate ( angle ) ; tempTransform3 . multiplyMatrices ( tempTransform2 , tempTransform1 ) ; tempTransform1 . identity ( ) . translate ( cx , cy ) ; currentTransform . multiplyMatrices ( tempTransform1 , tempTransform3 ) ; } break ; case 'scale' : if ( array . length >= 1 ) { const scaleX = array [ 0 ] ; let scaleY = scaleX ; if ( array . length >= 2 ) { scaleY = array [ 1 ] ; } currentTransform . scale ( scaleX , scaleY ) ; } break ; case 'skewX' : if ( array . length === 1 ) { currentTransform . set ( 1 , Math . tan ( array [ 0 ] * Math . PI / 180 ) , 0 , 0 , 1 , 0 , 0 , 0 , 1 ) ; } break ; case 'skewY' : if ( array . length === 1 ) { currentTransform . set ( 1 , 0 , 0 , Math . tan ( array [ 0 ] * Math . PI / 180 ) , 1 , 0 , 0 , 0 , 1 ) ; } break ; case 'matrix' : if ( array . length === 6 ) { currentTransform . set ( array [ 0 ] , array [ 2 ] , array [ 4 ] , array [ 1 ] , array [ 3 ] , array [ 5 ] , 0 , 0 , 1 ) ; } break ; } } transform . premultiply ( currentTransform ) ; } } return transform ; } function transformPath ( path , m ) { function transfVec2 ( v2 ) { tempV3 . set ( v2 . x , v2 . y , 1 ) . applyMatrix3 ( m ) ; v2 . set ( tempV3 . x , tempV3 . y ) ; } const isRotated = isTransformRotated ( m ) ; const subPaths = path . subPaths ; for ( let i = 0 , n = subPaths . length ; i < n ; i ++ ) { const subPath = subPaths [ i ] ; const curves = subPath . curves ; for ( let j = 0 ; j < curves . length ; j ++ ) { const curve = curves [ j ] ; if ( curve . isLineCurve ) { transfVec2 ( curve . v1 ) ; transfVec2 ( curve . v2 ) ; } else if ( curve . isCubicBezierCurve ) { transfVec2 ( curve . v0 ) ; transfVec2 ( curve . v1 ) ; transfVec2 ( curve . v2 ) ; transfVec2 ( curve . v3 ) ; } else if ( curve . isQuadraticBezierCurve ) { transfVec2 ( curve . v0 ) ; transfVec2 ( curve . v1 ) ; transfVec2 ( curve . v2 ) ; } else if ( curve . isEllipseCurve ) { if ( isRotated ) { console . warn ( 'SVGLoader: Elliptic arc or ellipse rotation or skewing is not implemented.' ) ; } tempV2 . set ( curve . aX , curve . aY ) ; transfVec2 ( tempV2 ) ; curve . aX = tempV2 . x ; curve . aY = tempV2 . y ; curve . xRadius *= getTransformScaleX ( m ) ; curve . yRadius *= getTransformScaleY ( m ) ; } } } } function isTransformRotated ( m ) { return m . elements [ 1 ] !== 0 || m . elements [ 3 ] !== 0 ; } function getTransformScaleX ( m ) { const te = m . elements ; return Math . sqrt ( te [ 0 ] * te [ 0 ] + te [ 1 ] * te [ 1 ] ) ; } function getTransformScaleY ( m ) { const te = m . elements ; return Math . sqrt ( te [ 3 ] * te [ 3 ] + te [ 4 ] * te [ 4 ] ) ; } const paths = [ ] ; const stylesheets = { } ; const transformStack = [ ] ; const tempTransform0 = new Matrix3 ( ) ; const tempTransform1 = new Matrix3 ( ) ; const tempTransform2 = new Matrix3 ( ) ; const tempTransform3 = new Matrix3 ( ) ; const tempV2 = new Vector2 ( ) ; const tempV3 = new Vector3 ( ) ; const currentTransform = new Matrix3 ( ) ; const xml = new DOMParser ( ) . parseFromString ( text , 'image/svg+xml' ) ; parseNode ( xml . documentElement , { fill : '#000' , fillOpacity : 1 , strokeOpacity : 1 , strokeWidth : 1 , strokeLineJoin : 'miter' , strokeLineCap : 'butt' , strokeMiterLimit : 4 } ) ; const data = { paths : paths , xml : xml . documentElement } ; return data ; } static createShapes ( shapePath ) { const BIGNUMBER = 999999999 ; const IntersectionLocationType = { ORIGIN : 0 , DESTINATION : 1 , BETWEEN : 2 , LEFT : 3 , RIGHT : 4 , BEHIND : 5 , BEYOND : 6 } ; const classifyResult = { loc : IntersectionLocationType . ORIGIN , t : 0 } ; function findEdgeIntersection ( a0 , a1 , b0 , b1 ) { const x1 = a0 . x ; const x2 = a1 . x ; const x3 = b0 . x ; const x4 = b1 . x ; const y1 = a0 . y ; const y2 = a1 . y ; const y3 = b0 . y ; const y4 = b1 . y ; const nom1 = ( x4 - x3 ) * ( y1 - y3 ) - ( y4 - y3 ) * ( x1 - x3 ) ; const nom2 = ( x2 - x1 ) * ( y1 - y3 ) - ( y2 - y1 ) * ( x1 - x3 ) ; const denom = ( y4 - y3 ) * ( x2 - x1 ) - ( x4 - x3 ) * ( y2 - y1 ) ; const t1 = nom1 / denom ; const t2 = nom2 / denom ; if ( ( ( denom === 0 ) && ( nom1 !== 0 ) ) || ( t1 <= 0 ) || ( t1 >= 1 ) || ( t2 < 0 ) || ( t2 > 1 ) ) { return null ; } else if ( ( nom1 === 0 ) && ( denom === 0 ) ) { for ( let i = 0 ; i < 2 ; i ++ ) { classifyPoint ( i === 0 ? b0 : b1 , a0 , a1 ) ; if ( classifyResult . loc == IntersectionLocationType . ORIGIN ) { const point = ( i === 0 ? b0 : b1 ) ; return { x : point . x , y : point . y , t : classifyResult . t } ; } else if ( classifyResult . loc == IntersectionLocationType . BETWEEN ) { const x = + ( ( x1 + classifyResult . t * ( x2 - x1 ) ) . toPrecision ( 10 ) ) ; const y = + ( ( y1 + classifyResult . t * ( y2 - y1 ) ) . toPrecision ( 10 ) ) ; return { x : x , y : y , t : classifyResult . t , } ; } } return null ; } else { for ( let i = 0 ; i < 2 ; i ++ ) { classifyPoint ( i === 0 ? b0 : b1 , a0 , a1 ) ; if ( classifyResult . loc == IntersectionLocationType . ORIGIN ) { const point = ( i === 0 ? b0 : b1 ) ; return { x : point . x , y : point . y , t : classifyResult . t } ; } } const x = + ( ( x1 + t1 * ( x2 - x1 ) ) . toPrecision ( 10 ) ) ; const y = + ( ( y1 + t1 * ( y2 - y1 ) ) . toPrecision ( 10 ) ) ; return { x : x , y : y , t : t1 } ; } } function classifyPoint ( p , edgeStart , edgeEnd ) { const ax = edgeEnd . x - edgeStart . x ; const ay = edgeEnd . y - edgeStart . y ; const bx = p . x - edgeStart . x ; const by = p . y - edgeStart . y ; const sa = ax * by - bx * ay ; if ( ( p . x === edgeStart . x ) && ( p . y === edgeStart . y ) ) { classifyResult . loc = IntersectionLocationType . ORIGIN ; classifyResult . t = 0 ; return ; } if ( ( p . x === edgeEnd . x ) && ( p . y === edgeEnd . y ) ) { classifyResult . loc = IntersectionLocationType . DESTINATION ; classifyResult . t = 1 ; return ; } if ( sa < - Number . EPSILON ) { classifyResult . loc = IntersectionLocationType . LEFT ; return ; } if ( sa > Number . EPSILON ) { classifyResult . loc = IntersectionLocationType . RIGHT ; return ; } if ( ( ( ax * bx ) < 0 ) || ( ( ay * by ) < 0 ) ) { classifyResult . loc = IntersectionLocationType . BEHIND ; return ; } if ( ( Math . sqrt ( ax * ax + ay * ay ) ) < ( Math . sqrt ( bx * bx + by * by ) ) ) { classifyResult . loc = IntersectionLocationType . BEYOND ; return ; } let t ; if ( ax !== 0 ) { t = bx / ax ; } else { t = by / ay ; } classifyResult . loc = IntersectionLocationType . BETWEEN ; classifyResult . t = t ; } function getIntersections ( path1 , path2 ) { const intersectionsRaw = [ ] ; const intersections = [ ] ; for ( let index = 1 ; index < path1 . length ; index ++ ) { const path1EdgeStart = path1 [ index - 1 ] ; const path1EdgeEnd = path1 [ index ] ; for ( let index2 = 1 ; index2 < path2 . length ; index2 ++ ) { const path2EdgeStart = path2 [ index2 - 1 ] ; const path2EdgeEnd = path2 [ index2 ] ; const intersection = findEdgeIntersection ( path1EdgeStart , path1EdgeEnd , path2EdgeStart , path2EdgeEnd ) ; if ( intersection !== null && intersectionsRaw . find ( i => i . t <= intersection . t + Number . EPSILON && i . t >= intersection . t - Number . EPSILON ) === undefined ) { intersectionsRaw . push ( intersection ) ; intersections . push ( new Vector2 ( intersection . x , intersection . y ) ) ; } } } return intersections ; } function getScanlineIntersections ( scanline , boundingBox , paths ) { const center = new Vector2 ( ) ; boundingBox . getCenter ( center ) ; const allIntersections = [ ] ; paths . forEach ( path => { if ( path . boundingBox . containsPoint ( center ) ) { const intersections = getIntersections ( scanline , path . points ) ; intersections . forEach ( p => { allIntersections . push ( { identifier : path . identifier , isCW : path . isCW , point : p } ) ; } ) ; } } ) ; allIntersections . sort ( ( i1 , i2 ) => { return i1 . point . x - i2 . point . x ; } ) ; return allIntersections ; } function isHoleTo ( simplePath , allPaths , scanlineMinX , scanlineMaxX , _fillRule ) { if ( _fillRule === null || _fillRule === undefined || _fillRule === '' ) { _fillRule = 'nonzero' ; } const centerBoundingBox = new Vector2 ( ) ; simplePath . boundingBox . getCenter ( centerBoundingBox ) ; const scanline = [ new Vector2 ( scanlineMinX , centerBoundingBox . y ) , new Vector2 ( scanlineMaxX , centerBoundingBox . y ) ] ; const scanlineIntersections = getScanlineIntersections ( scanline , simplePath . boundingBox , allPaths ) ; scanlineIntersections . sort ( ( i1 , i2 ) => { return i1 . point . x - i2 . point . x ; } ) ; const baseIntersections = [ ] ; const otherIntersections = [ ] ; scanlineIntersections . forEach ( i => { if ( i . identifier === simplePath . identifier ) { baseIntersections . push ( i ) ; } else { otherIntersections . push ( i ) ; } } ) ; const firstXOfPath = baseIntersections [ 0 ] . point . x ; const stack = [ ] ; let i = 0 ; while ( i < otherIntersections . length && otherIntersections [ i ] . point . x < firstXOfPath ) { if ( stack . length > 0 && stack [ stack . length - 1 ] === otherIntersections [ i ] . identifier ) { stack . pop ( ) ; } else { stack . push ( otherIntersections [ i ] . identifier ) ; } i ++ ; } stack . push ( simplePath . identifier ) ; if ( _fillRule === 'evenodd' ) { const isHole = stack . length % 2 === 0 ? true : false ; const isHoleFor = stack [ stack . length - 2 ] ; return { identifier : simplePath . identifier , isHole : isHole , for : isHoleFor } ; } else if ( _fillRule === 'nonzero' ) { let isHole = true ; let isHoleFor = null ; let lastCWValue = null ; for ( let i = 0 ; i < stack . length ; i ++ ) { const identifier = stack [ i ] ; if ( isHole ) { lastCWValue = allPaths [ identifier ] . isCW ; isHole = false ; isHoleFor = identifier ; } else if ( lastCWValue !== allPaths [ identifier ] . isCW ) { lastCWValue = allPaths [ identifier ] . isCW ; isHole = true ; } } return { identifier : simplePath . identifier , isHole : isHole , for : isHoleFor } ; } else { console . warn ( 'fill-rule: "' + _fillRule + '" is currently not implemented.' ) ; } } let identifier = 0 ; let scanlineMinX = BIGNUMBER ; let scanlineMaxX = - BIGNUMBER ; let simplePaths = shapePath . subPaths . map ( p => { const points = p . getPoints ( ) ; let maxY = - BIGNUMBER ; let minY = BIGNUMBER ; let maxX = - BIGNUMBER ; let minX = BIGNUMBER ; for ( let i = 0 ; i < points . length ; i ++ ) { const p = points [ i ] ; if ( p . y > maxY ) { maxY = p . y ; } if ( p . y < minY ) { minY = p . y ; } if ( p . x > maxX ) { maxX = p . x ; } if ( p . x < minX ) { minX = p . x ; } } if ( scanlineMaxX <= maxX ) { scanlineMaxX = maxX + 1 ; } if ( scanlineMinX >= minX ) { scanlineMinX = minX - 1 ; } return { points : points , isCW : ShapeUtils . isClockWise ( points ) , identifier : identifier ++ , boundingBox : new Box2 ( new Vector2 ( minX , minY ) , new Vector2 ( maxX , maxY ) ) } ; } ) ; simplePaths = simplePaths . filter ( sp => sp . points . length > 0 ) ; const isAHole = simplePaths . map ( p => isHoleTo ( p , simplePaths , scanlineMinX , scanlineMaxX , shapePath . userData . style . fillRule ) ) ; const shapesToReturn = [ ] ; simplePaths . forEach ( p => { const amIAHole = isAHole [ p . identifier ] ; if ( ! amIAHole . isHole ) { const shape = new Shape ( p . points ) ; const holes = isAHole . filter ( h => h . isHole && h . for === p . identifier ) ; holes . forEach ( h => { const path = simplePaths [ h . identifier ] ; shape . holes . push ( new Path ( path . points ) ) ; } ) ; shapesToReturn . push ( shape ) ; } } ) ; return shapesToReturn ; } static getStrokeStyle ( width , color , lineJoin , lineCap , miterLimit ) { width = width !== undefined ? width : 1 ; color = color !== undefined ? color : '#000' ; 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 } ; } static pointsToStroke ( points , style , arcDivisions , minDistance ) { const vertices = [ ] ; const normals = [ ] ; const uvs = [ ] ; if ( SVGLoader . pointsToStrokeWithBuffers ( points , style , arcDivisions , minDistance , vertices , normals , uvs ) === 0 ) { return null ; } const geometry = new BufferGeometry ( ) ; geometry . setAttribute ( 'position' , new Float32BufferAttribute ( vertices , 3 ) ) ; geometry . setAttribute ( 'normal' , new Float32BufferAttribute ( normals , 3 ) ) ; geometry . setAttribute ( 'uv' , new Float32BufferAttribute ( uvs , 2 ) ) ; return geometry ; } static pointsToStrokeWithBuffers ( points , style , arcDivisions , minDistance , vertices , normals , uvs , vertexOffset ? ) { const tempV2_1 = new Vector2 ( ) ; const tempV2_2 = new Vector2 ( ) ; const tempV2_3 = new Vector2 ( ) ; const tempV2_4 = new Vector2 ( ) ; const tempV2_5 = new Vector2 ( ) ; const tempV2_6 = new Vector2 ( ) ; const tempV2_7 = new Vector2 ( ) ; const lastPointL = new Vector2 ( ) ; const lastPointR = new Vector2 ( ) ; const point0L = new Vector2 ( ) ; const point0R = new Vector2 ( ) ; const currentPointL = new Vector2 ( ) ; const currentPointR = new Vector2 ( ) ; const nextPointL = new Vector2 ( ) ; const nextPointR = new Vector2 ( ) ; const innerPoint = new Vector2 ( ) ; const outerPoint = new Vector2 ( ) ; arcDivisions = arcDivisions !== undefined ? arcDivisions : 12 ; minDistance = minDistance !== undefined ? minDistance : 0.001 ; vertexOffset = vertexOffset !== undefined ? vertexOffset : 0 ; points = removeDuplicatedPoints ( points ) ; const numPoints = points . length ; if ( numPoints < 2 ) return 0 ; const isClosed = points [ 0 ] . equals ( points [ numPoints - 1 ] ) ; let currentPoint ; let previousPoint = points [ 0 ] ; let nextPoint ; const strokeWidth2 = style . strokeWidth / 2 ; const deltaU = 1 / ( numPoints - 1 ) ; let u0 = 0 , u1 ; let innerSideModified ; let joinIsOnLeftSide ; let isMiter ; let initialJoinIsOnLeftSide = false ; let numVertices = 0 ; let currentCoordinate = vertexOffset * 3 ; let currentCoordinateUV = vertexOffset * 2 ; 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 ( let iPoint = 1 ; iPoint < numPoints ; iPoint ++ ) { currentPoint = points [ iPoint ] ; if ( iPoint === numPoints - 1 ) { if ( isClosed ) { nextPoint = points [ 1 ] ; } else nextPoint = undefined ; } else { nextPoint = points [ iPoint + 1 ] ; } const 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 ) ; u1 = u0 + deltaU ; innerSideModified = false ; if ( nextPoint !== undefined ) { 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 ) ; tempV2_3 . normalize ( ) ; const dot = Math . abs ( normal1 . dot ( tempV2_3 ) ) ; if ( dot !== 0 ) { const 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 ( ) ; const miterLength2 = tempV2_5 . length ( ) ; const segmentLengthPrev = tempV2_4 . length ( ) ; tempV2_4 . divideScalar ( segmentLengthPrev ) ; tempV2_6 . subVectors ( nextPoint , currentPoint ) ; const segmentLengthNext = tempV2_6 . length ( ) ; tempV2_6 . divideScalar ( segmentLengthNext ) ; 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 { makeSegmentTriangles ( ) ; } switch ( style . strokeLineJoin ) { case 'bevel' : makeSegmentWithBevelJoin ( joinIsOnLeftSide , innerSideModified , u1 ) ; break ; case 'round' : createSegmentTrianglesWithMiddleSection ( joinIsOnLeftSide , innerSideModified ) ; if ( joinIsOnLeftSide ) { makeCircularSector ( currentPoint , currentPointL , nextPointL , u1 , 0 ) ; } else { makeCircularSector ( currentPoint , nextPointR , currentPointR , u1 , 1 ) ; } break ; case 'miter' : case 'miter-clip' : default : const miterFraction = ( strokeWidth2 * style . strokeMiterLimit ) / miterLength2 ; if ( miterFraction < 1 ) { if ( style . strokeLineJoin !== 'miter-clip' ) { makeSegmentWithBevelJoin ( joinIsOnLeftSide , innerSideModified , u1 ) ; break ; } else { createSegmentTrianglesWithMiddleSection ( joinIsOnLeftSide , innerSideModified ) ; 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 { if ( innerSideModified ) { 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 { 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 { makeSegmentTriangles ( ) ; } } else { makeSegmentTriangles ( ) ; } if ( ! isClosed && iPoint === numPoints - 1 ) { addCapGeometry ( points [ 0 ] , point0L , point0R , joinIsOnLeftSide , true , u0 ) ; } u0 = u1 ; previousPoint = currentPoint ; lastPointL . copy ( nextPointL ) ; lastPointR . copy ( nextPointR ) ; } if ( ! isClosed ) { addCapGeometry ( currentPoint , currentPointL , currentPointR , joinIsOnLeftSide , false , u1 ) ; } else if ( innerSideModified && vertices ) { let lastOuter = outerPoint ; let lastInner = innerPoint ; if ( initialJoinIsOnLeftSide !== joinIsOnLeftSide ) { lastOuter = innerPoint ; lastInner = outerPoint ; } if ( joinIsOnLeftSide ) { if ( isMiter || initialJoinIsOnLeftSide ) { lastInner . toArray ( vertices , 0 * 3 ) ; lastInner . toArray ( vertices , 3 * 3 ) ; if ( isMiter ) { lastOuter . toArray ( vertices , 1 * 3 ) ; } } } else { if ( isMiter || ! initialJoinIsOnLeftSide ) { lastInner . toArray ( vertices , 1 * 3 ) ; lastInner . toArray ( vertices , 3 * 3 ) ; if ( isMiter ) { lastOuter . toArray ( vertices , 0 * 3 ) ; } } } } return numVertices ; 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 ; if ( normals ) { normals [ currentCoordinate ] = 0 ; normals [ currentCoordinate + 1 ] = 0 ; normals [ currentCoordinate + 2 ] = 1 ; } currentCoordinate += 3 ; if ( uvs ) { uvs [ currentCoordinateUV ] = u ; uvs [ currentCoordinateUV + 1 ] = v ; currentCoordinateUV += 2 ; } } numVertices += 3 ; } function makeCircularSector ( center , p1 , p2 , u , v ) { tempV2_1 . copy ( p1 ) . sub ( center ) . normalize ( ) ; tempV2_2 . copy ( p2 ) . sub ( center ) . normalize ( ) ; let angle = Math . PI ; const dot = tempV2_1 . dot ( tempV2_2 ) ; if ( Math . abs ( dot ) < 1 ) angle = Math . abs ( Math . acos ( dot ) ) ; angle /= arcDivisions ; tempV2_3 . copy ( p1 ) ; for ( let i = 0 , il = arcDivisions - 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 ) { 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 , u , 0 ) ; addVertex ( nextPointL , u , 0 ) ; addVertex ( innerPoint , u , 0.5 ) ; } 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 , u , 1 ) ; addVertex ( nextPointR , u , 0 ) ; addVertex ( innerPoint , u , 0.5 ) ; } } else { 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 ) { 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 ) ; 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 ) ; const vl = vertices . length ; 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 : break ; } } function removeDuplicatedPoints ( points ) { let dupPoints = false ; for ( let i = 1 , n = points . length - 1 ; i < n ; i ++ ) { if ( points [ i ] . distanceTo ( points [ i + 1 ] ) < minDistance ) { dupPoints = true ; break ; } } if ( ! dupPoints ) return points ; const newPoints = [ ] ; newPoints . push ( points [ 0 ] ) ; for ( let 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 ; } } } export { SVGLoader } ;