Browse Source

SVGLoader: Implement ellipse transformations. (#24750)

* SVGLoader: improve support for ellipse transforms

* SVGLoader: add ellipse transform test file

* Update SVGLoader.js

Code style clean up.

* SVGLoader: float-error tolerance when chosing ellipse transform branch

* SVGLoader: lower tolerance of skew transform detection

Co-authored-by: Michael Herzog <[email protected]>
Victor Nakoryakov 2 years ago
parent
commit
885c783da4

+ 140 - 10
examples/jsm/loaders/SVGLoader.js

@@ -1588,7 +1588,100 @@ class SVGLoader extends Loader {
 
 			}
 
-			const isRotated = isTransformRotated( m );
+			function ellipseMatrixForm( curve, out ) {
+
+				// Build matrix form of the original ellipse curve
+				// See: https://en.wikipedia.org/wiki/Ellipse → General ellipse
+
+				const a = curve.xRadius;
+				const b = curve.yRadius;
+
+				const x0 = curve.aX;
+				const y0 = curve.aY;
+				const theta = curve.aRotation;
+				const sth = Math.sin( theta );
+				const cth = Math.cos( theta );
+
+				const A = a * a * sth * sth + b * b * cth * cth;
+				const B = 2 * ( b * b - a * a ) * sth * cth;
+				const C = a * a * cth * cth + b * b * sth * sth;
+				const D = - 2 * A * x0 - B * y0;
+				const E = - B * x0 - 2 * C * y0;
+				const F = A * x0 * x0 + B * x0 * y0 + C * y0 * y0 - a * a * b * b;
+
+				const result = out || new Matrix3();
+
+				return result.set(
+					A, B / 2, D / 2,
+					B / 2, C, E / 2,
+					D / 2, E / 2, F,
+				);
+
+			}
+
+			function transfEllipse( curve ) {
+
+				// See:
+				// - https://math.stackexchange.com/questions/1498799/
+				// - https://math.stackexchange.com/questions/3076317/
+
+				const mQ = ellipseMatrixForm( curve, tempTransform0 );
+				const mInvT = tempTransform1.copy( m ).invert();
+				const mQNew = tempTransform2.copy( mInvT ).transpose().multiply( mQ ).multiply( mInvT );
+
+				// Perform de-composition
+
+				const elem = mQNew.elements;
+				const A = elem[ 0 ];
+				const B = elem[ 1 ] * 2;
+				const C = elem[ 4 ];
+				const D = elem[ 2 ] * 2;
+				const E = elem[ 5 ] * 2;
+				const F = elem[ 8 ];
+
+				// Compute canonical ellipse params
+
+				const discriminant = B * B - 4 * A * C;
+
+				const abP1 = 2 * ( A * E * E + C * D * D - B * D * E + discriminant * F );
+				const abP2 = Math.sqrt( ( A - C ) * ( A - C ) + B * B );
+
+				const a = Math.sqrt( abP1 * ( A + C + abP2 ) ) / discriminant;
+				const b = Math.sqrt( abP1 * ( A + C - abP2 ) ) / discriminant;
+
+				const x0 = ( 2 * C * D - B * E ) / discriminant;
+				const y0 = ( 2 * A * E - B * D ) / discriminant;
+
+				let theta = 0;
+
+				if ( B !== 0 ) {
+
+					theta = Math.atan( ( C - A - Math.sqrt( ( A - C ) * ( A - C ) + B * B ) ) / B );
+
+				} else if ( A > C ) {
+
+					theta = Math.PI / 2;
+
+				}
+
+				// Update data in-place
+
+				curve.aX = x0;
+				curve.aY = y0;
+				curve.xRadius = a;
+				curve.yRadius = b;
+				curve.aRotation = theta;
+
+				// TODO: Linear algebra voodoo required!
+				// We should also re-map start/end angles to the domain of the new ellipse.
+				// Can’t find a correct analytical way to compute it. Help is much appreciated.
+				// As a starting point see: https://math.stackexchange.com/questions/4544164/
+				//curve.aStartAngle = ???;
+				//curve.aEndAngle = ???;
+
+				return curve;
+
+			}
 
 			const subPaths = path.subPaths;
 
@@ -1621,19 +1714,41 @@ class SVGLoader extends Loader {
 
 					} else if ( curve.isEllipseCurve ) {
 
-						if ( isRotated ) {
+						if ( isTransformSkewed( m ) ) {
 
-							console.warn( 'SVGLoader: Elliptic arc or ellipse rotation or skewing is not implemented.' );
+							if ( ( curve.aEndAngle - curve.aStartAngle ) % ( 2 * Math.PI ) !== 0 ) {
 
-						}
+								console.warn( 'SVGLoader: Elliptic arc skewing is not implemented.' );
+
+							}
+
+							transfEllipse( curve );
+
+						} else {
+
+							// Faster shortcut if no skew is applied
+							// (e.g, a euclidean transform of a group containing the ellipse)
+
+							tempV2.set( curve.aX, curve.aY );
+							transfVec2( tempV2 );
+							curve.aX = tempV2.x;
+							curve.aY = tempV2.y;
+
+							const sx = getTransformScaleX( m );
+							const sy = getTransformScaleY( m );
 
-						tempV2.set( curve.aX, curve.aY );
-						transfVec2( tempV2 );
-						curve.aX = tempV2.x;
-						curve.aY = tempV2.y;
+							curve.xRadius *= sx;
+							curve.yRadius *= sy;
 
-						curve.xRadius *= getTransformScaleX( m );
-						curve.yRadius *= getTransformScaleY( m );
+							if ( isTransformRotated( m ) ) {
+
+								const sin = m.elements[ 1 ] / sx;
+								const cos = m.elements[ 0 ] / sx;
+								curve.aRotation += Math.atan2( sin, cos );
+
+							}
+
+						}
 
 					}
 
@@ -1649,6 +1764,21 @@ class SVGLoader extends Loader {
 
 		}
 
+		function isTransformSkewed( m ) {
+
+			const te = m.elements;
+			const basisDot = te[ 0 ] * te[ 3 ] + te[ 1 ] * te[ 4 ];
+
+			// Shortcut for trivial rotations and transformations
+			if ( basisDot === 0 ) return false;
+
+			const sx = getTransformScaleX( m );
+			const sy = getTransformScaleY( m );
+
+			return Math.abs( basisDot / ( sx * sy ) ) > Number.EPSILON;
+
+		}
+
 		function getTransformScaleX( m ) {
 
 			const te = m.elements;

+ 511 - 0
examples/models/svg/tests/ellipseTransform.svg

@@ -0,0 +1,511 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="210mm"
+   height="297mm"
+   viewBox="0 0 210 297"
+   version="1.1"
+   id="svg1231"
+   inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
+   sodipodi:docname="pacman.svg"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <sodipodi:namedview
+     id="namedview1233"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="0"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:document-units="mm"
+     showgrid="false"
+     inkscape:zoom="0.42048261"
+     inkscape:cx="-365.05672"
+     inkscape:cy="413.81022"
+     inkscape:window-width="1912"
+     inkscape:window-height="1039"
+     inkscape:window-x="2"
+     inkscape:window-y="35"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="layer1" />
+  <defs
+     id="defs1228" />
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <g
+       id="g1683"
+       transform="translate(-32.245799,132.66262)">
+      <g
+         id="g576">
+        <path
+           style="fill:#ffd700;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path445"
+           sodipodi:type="arc"
+           sodipodi:cx="54.246853"
+           sodipodi:cy="-111.15029"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="1.0471976"
+           sodipodi:end="6.2831853"
+           sodipodi:arc-type="slice"
+           d="m 60.246852,-100.75799 a 12,12 0 0 1 -14.485281,-1.90702 12,12 0 0 1 -1.907023,-14.48528 12,12 0 0 1 13.498133,-5.59111 12,12 0 0 1 8.894172,11.59111 h -12 z" />
+        <path
+           style="fill:#ffd700;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path447"
+           sodipodi:type="arc"
+           sodipodi:cx="87.55822"
+           sodipodi:cy="-110.98909"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0.52359878"
+           sodipodi:end="5.7595865"
+           sodipodi:arc-type="slice"
+           d="m 97.950525,-104.98909 a 12,12 0 0 1 -13.498134,5.59111 12,12 0 0 1 -8.894171,-11.59111 12,12 0 0 1 8.894171,-11.59111 12,12 0 0 1 13.498134,5.59111 l -10.392305,6 z" />
+        <path
+           style="fill:#ffd700;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path501"
+           sodipodi:type="arc"
+           sodipodi:cx="62.420654"
+           sodipodi:cy="-81.835556"
+           sodipodi:rx="19.613878"
+           sodipodi:ry="9.3405294"
+           sodipodi:start="0"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 82.034533,-81.835556 a 19.613878,9.3405294 0 0 1 -14.537434,9.022259 19.613878,9.3405294 0 0 1 -22.062562,-4.351994 19.613878,9.3405294 0 0 1 3.117011,-11.275017 19.613878,9.3405294 0 0 1 23.676046,-1.484384 l -9.80694,8.089136 z" />
+        <path
+           style="fill:#ffd700;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path503"
+           sodipodi:type="arc"
+           sodipodi:cx="60.804901"
+           sodipodi:cy="-107.88588"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0"
+           sodipodi:end="1.0471976"
+           sodipodi:arc-type="slice"
+           d="m 72.804901,-107.88588 a 12,12 0 0 1 -6,10.392306 l -6,-10.392306 z" />
+        <path
+           style="fill:#ffd700;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path505"
+           sodipodi:type="arc"
+           sodipodi:cx="94.866829"
+           sodipodi:cy="-110.62257"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="5.7595865"
+           sodipodi:end="0.52359878"
+           sodipodi:arc-type="slice"
+           d="m 105.25913,-116.62257 a 12,12 0 0 1 0,12 l -10.392301,-6 z" />
+        <circle
+           style="fill:#ffd700;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="path818"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#ffd700;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path509"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#ffd700;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse511"
+           sodipodi:type="arc"
+           sodipodi:cx="167.56453"
+           sodipodi:cy="-95.790558"
+           sodipodi:rx="15.161935"
+           sodipodi:ry="25.93924"
+           sodipodi:start="1.0471976"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 175.1455,-73.326517 a 15.161935,25.93924 0 0 1 -19.19569,-5.79062 15.161935,25.93924 0 0 1 0,-33.346843 15.161935,25.93924 0 0 1 19.19569,-5.79062 l -7.58097,22.464042 z" />
+      </g>
+    </g>
+    <g
+       id="g1703"
+       transform="rotate(90,56.119805,23.399341)"
+       style="fill:#87cefa">
+      <g
+         id="g1701"
+         style="fill:#87cefa">
+        <path
+           style="fill:#87cefa;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1685"
+           sodipodi:type="arc"
+           sodipodi:cx="54.246853"
+           sodipodi:cy="-111.15029"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="1.0471976"
+           sodipodi:end="6.2831853"
+           sodipodi:arc-type="slice"
+           d="m 60.246852,-100.75799 a 12,12 0 0 1 -14.485281,-1.90702 12,12 0 0 1 -1.907023,-14.48528 12,12 0 0 1 13.498133,-5.59111 12,12 0 0 1 8.894172,11.59111 h -12 z" />
+        <path
+           style="fill:#87cefa;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1687"
+           sodipodi:type="arc"
+           sodipodi:cx="87.55822"
+           sodipodi:cy="-110.98909"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0.52359878"
+           sodipodi:end="5.7595865"
+           sodipodi:arc-type="slice"
+           d="m 97.950525,-104.98909 a 12,12 0 0 1 -13.498134,5.59111 12,12 0 0 1 -8.894171,-11.59111 12,12 0 0 1 8.894171,-11.59111 12,12 0 0 1 13.498134,5.59111 l -10.392305,6 z" />
+        <path
+           style="fill:#87cefa;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path1689"
+           sodipodi:type="arc"
+           sodipodi:cx="62.420654"
+           sodipodi:cy="-81.835556"
+           sodipodi:rx="19.613878"
+           sodipodi:ry="9.3405294"
+           sodipodi:start="0"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 82.034533,-81.835556 a 19.613878,9.3405294 0 0 1 -14.537434,9.022259 19.613878,9.3405294 0 0 1 -22.062562,-4.351994 19.613878,9.3405294 0 0 1 3.117011,-11.275017 19.613878,9.3405294 0 0 1 23.676046,-1.484384 l -9.80694,8.089136 z" />
+        <path
+           style="fill:#87cefa;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1691"
+           sodipodi:type="arc"
+           sodipodi:cx="60.804901"
+           sodipodi:cy="-107.88588"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0"
+           sodipodi:end="1.0471976"
+           sodipodi:arc-type="slice"
+           d="m 72.804901,-107.88588 a 12,12 0 0 1 -6,10.392306 l -6,-10.392306 z" />
+        <path
+           style="fill:#87cefa;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1693"
+           sodipodi:type="arc"
+           sodipodi:cx="94.866829"
+           sodipodi:cy="-110.62257"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="5.7595865"
+           sodipodi:end="0.52359878"
+           sodipodi:arc-type="slice"
+           d="m 105.25913,-116.62257 a 12,12 0 0 1 0,12 l -10.392301,-6 z" />
+        <circle
+           style="fill:#87cefa;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="circle1695"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#87cefa;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse1697"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#87cefa;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1699"
+           sodipodi:type="arc"
+           sodipodi:cx="167.56453"
+           sodipodi:cy="-95.790558"
+           sodipodi:rx="15.161935"
+           sodipodi:ry="25.93924"
+           sodipodi:start="1.0471976"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 175.1455,-73.326517 a 15.161935,25.93924 0 0 1 -19.19569,-5.79062 15.161935,25.93924 0 0 1 0,-33.346843 15.161935,25.93924 0 0 1 19.19569,-5.79062 l -7.58097,22.464042 z" />
+      </g>
+    </g>
+    <g
+       id="g1723"
+       transform="matrix(-0.32285127,0.32285127,-0.32285127,-0.32285127,118.3435,39.613059)"
+       style="fill:#d3d3d3">
+      <g
+         id="g1721"
+         style="fill:#d3d3d3">
+        <path
+           style="fill:#d3d3d3;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1705"
+           sodipodi:type="arc"
+           sodipodi:cx="54.246853"
+           sodipodi:cy="-111.15029"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="1.0471976"
+           sodipodi:end="6.2831853"
+           sodipodi:arc-type="slice"
+           d="m 60.246852,-100.75799 a 12,12 0 0 1 -14.485281,-1.90702 12,12 0 0 1 -1.907023,-14.48528 12,12 0 0 1 13.498133,-5.59111 12,12 0 0 1 8.894172,11.59111 h -12 z" />
+        <path
+           style="fill:#d3d3d3;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1707"
+           sodipodi:type="arc"
+           sodipodi:cx="87.55822"
+           sodipodi:cy="-110.98909"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0.52359878"
+           sodipodi:end="5.7595865"
+           sodipodi:arc-type="slice"
+           d="m 97.950525,-104.98909 a 12,12 0 0 1 -13.498134,5.59111 12,12 0 0 1 -8.894171,-11.59111 12,12 0 0 1 8.894171,-11.59111 12,12 0 0 1 13.498134,5.59111 l -10.392305,6 z" />
+        <path
+           style="fill:#d3d3d3;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path1709"
+           sodipodi:type="arc"
+           sodipodi:cx="62.420654"
+           sodipodi:cy="-81.835556"
+           sodipodi:rx="19.613878"
+           sodipodi:ry="9.3405294"
+           sodipodi:start="0"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 82.034533,-81.835556 a 19.613878,9.3405294 0 0 1 -14.537434,9.022259 19.613878,9.3405294 0 0 1 -22.062562,-4.351994 19.613878,9.3405294 0 0 1 3.117011,-11.275017 19.613878,9.3405294 0 0 1 23.676046,-1.484384 l -9.80694,8.089136 z" />
+        <path
+           style="fill:#d3d3d3;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1711"
+           sodipodi:type="arc"
+           sodipodi:cx="60.804901"
+           sodipodi:cy="-107.88588"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0"
+           sodipodi:end="1.0471976"
+           sodipodi:arc-type="slice"
+           d="m 72.804901,-107.88588 a 12,12 0 0 1 -6,10.392306 l -6,-10.392306 z" />
+        <path
+           style="fill:#d3d3d3;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1713"
+           sodipodi:type="arc"
+           sodipodi:cx="94.866829"
+           sodipodi:cy="-110.62257"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="5.7595865"
+           sodipodi:end="0.52359878"
+           sodipodi:arc-type="slice"
+           d="m 105.25913,-116.62257 a 12,12 0 0 1 0,12 l -10.392301,-6 z" />
+        <circle
+           style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="circle1715"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#d3d3d3;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse1717"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#d3d3d3;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1719"
+           sodipodi:type="arc"
+           sodipodi:cx="167.56453"
+           sodipodi:cy="-95.790558"
+           sodipodi:rx="15.161935"
+           sodipodi:ry="25.93924"
+           sodipodi:start="1.0471976"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 175.1455,-73.326517 a 15.161935,25.93924 0 0 1 -19.19569,-5.79062 15.161935,25.93924 0 0 1 0,-33.346843 15.161935,25.93924 0 0 1 19.19569,-5.79062 l -7.58097,22.464042 z" />
+      </g>
+    </g>
+    <g
+       id="g1743"
+       transform="matrix(1.4672636,0,0,0.99244251,-54.088826,270.36317)"
+       style="fill:#90ee90">
+      <g
+         id="g1741"
+         style="fill:#90ee90">
+        <path
+           style="fill:#90ee90;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1725"
+           sodipodi:type="arc"
+           sodipodi:cx="54.246853"
+           sodipodi:cy="-111.15029"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="1.0471976"
+           sodipodi:end="6.2831853"
+           sodipodi:arc-type="slice"
+           d="m 60.246852,-100.75799 a 12,12 0 0 1 -14.485281,-1.90702 12,12 0 0 1 -1.907023,-14.48528 12,12 0 0 1 13.498133,-5.59111 12,12 0 0 1 8.894172,11.59111 h -12 z" />
+        <path
+           style="fill:#90ee90;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1727"
+           sodipodi:type="arc"
+           sodipodi:cx="87.55822"
+           sodipodi:cy="-110.98909"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0.52359878"
+           sodipodi:end="5.7595865"
+           sodipodi:arc-type="slice"
+           d="m 97.950525,-104.98909 a 12,12 0 0 1 -13.498134,5.59111 12,12 0 0 1 -8.894171,-11.59111 12,12 0 0 1 8.894171,-11.59111 12,12 0 0 1 13.498134,5.59111 l -10.392305,6 z" />
+        <path
+           style="fill:#90ee90;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path1729"
+           sodipodi:type="arc"
+           sodipodi:cx="62.420654"
+           sodipodi:cy="-81.835556"
+           sodipodi:rx="19.613878"
+           sodipodi:ry="9.3405294"
+           sodipodi:start="0"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 82.034533,-81.835556 a 19.613878,9.3405294 0 0 1 -14.537434,9.022259 19.613878,9.3405294 0 0 1 -22.062562,-4.351994 19.613878,9.3405294 0 0 1 3.117011,-11.275017 19.613878,9.3405294 0 0 1 23.676046,-1.484384 l -9.80694,8.089136 z" />
+        <path
+           style="fill:#90ee90;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1731"
+           sodipodi:type="arc"
+           sodipodi:cx="60.804901"
+           sodipodi:cy="-107.88588"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0"
+           sodipodi:end="1.0471976"
+           sodipodi:arc-type="slice"
+           d="m 72.804901,-107.88588 a 12,12 0 0 1 -6,10.392306 l -6,-10.392306 z" />
+        <path
+           style="fill:#90ee90;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1733"
+           sodipodi:type="arc"
+           sodipodi:cx="94.866829"
+           sodipodi:cy="-110.62257"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="5.7595865"
+           sodipodi:end="0.52359878"
+           sodipodi:arc-type="slice"
+           d="m 105.25913,-116.62257 a 12,12 0 0 1 0,12 l -10.392301,-6 z" />
+        <circle
+           style="fill:#90ee90;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="circle1735"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#90ee90;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse1737"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#90ee90;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1739"
+           sodipodi:type="arc"
+           sodipodi:cx="167.56453"
+           sodipodi:cy="-95.790558"
+           sodipodi:rx="15.161935"
+           sodipodi:ry="25.93924"
+           sodipodi:start="1.0471976"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 175.1455,-73.326517 a 15.161935,25.93924 0 0 1 -19.19569,-5.79062 15.161935,25.93924 0 0 1 0,-33.346843 15.161935,25.93924 0 0 1 19.19569,-5.79062 l -7.58097,22.464042 z" />
+      </g>
+    </g>
+    <g
+       id="g1763"
+       transform="matrix(1,0,0.57735027,1,51.196895,338.95991)"
+       style="fill:#f4a460">
+      <g
+         id="g1761"
+         style="fill:#f4a460">
+        <path
+           style="fill:#f4a460;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1745"
+           sodipodi:type="arc"
+           sodipodi:cx="54.246853"
+           sodipodi:cy="-111.15029"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="1.0471976"
+           sodipodi:end="6.2831853"
+           sodipodi:arc-type="slice"
+           d="m 60.246852,-100.75799 a 12,12 0 0 1 -14.485281,-1.90702 12,12 0 0 1 -1.907023,-14.48528 12,12 0 0 1 13.498133,-5.59111 12,12 0 0 1 8.894172,11.59111 h -12 z" />
+        <path
+           style="fill:#f4a460;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1747"
+           sodipodi:type="arc"
+           sodipodi:cx="87.55822"
+           sodipodi:cy="-110.98909"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0.52359878"
+           sodipodi:end="5.7595865"
+           sodipodi:arc-type="slice"
+           d="m 97.950525,-104.98909 a 12,12 0 0 1 -13.498134,5.59111 12,12 0 0 1 -8.894171,-11.59111 12,12 0 0 1 8.894171,-11.59111 12,12 0 0 1 13.498134,5.59111 l -10.392305,6 z" />
+        <path
+           style="fill:#f4a460;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path1749"
+           sodipodi:type="arc"
+           sodipodi:cx="62.420654"
+           sodipodi:cy="-81.835556"
+           sodipodi:rx="19.613878"
+           sodipodi:ry="9.3405294"
+           sodipodi:start="0"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 82.034533,-81.835556 a 19.613878,9.3405294 0 0 1 -14.537434,9.022259 19.613878,9.3405294 0 0 1 -22.062562,-4.351994 19.613878,9.3405294 0 0 1 3.117011,-11.275017 19.613878,9.3405294 0 0 1 23.676046,-1.484384 l -9.80694,8.089136 z" />
+        <path
+           style="fill:#f4a460;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1751"
+           sodipodi:type="arc"
+           sodipodi:cx="60.804901"
+           sodipodi:cy="-107.88588"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="0"
+           sodipodi:end="1.0471976"
+           sodipodi:arc-type="slice"
+           d="m 72.804901,-107.88588 a 12,12 0 0 1 -6,10.392306 l -6,-10.392306 z" />
+        <path
+           style="fill:#f4a460;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1753"
+           sodipodi:type="arc"
+           sodipodi:cx="94.866829"
+           sodipodi:cy="-110.62257"
+           sodipodi:rx="12"
+           sodipodi:ry="12"
+           sodipodi:start="5.7595865"
+           sodipodi:end="0.52359878"
+           sodipodi:arc-type="slice"
+           d="m 105.25913,-116.62257 a 12,12 0 0 1 0,12 l -10.392301,-6 z" />
+        <circle
+           style="fill:#f4a460;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="circle1755"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#f4a460;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse1757"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#f4a460;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path1759"
+           sodipodi:type="arc"
+           sodipodi:cx="167.56453"
+           sodipodi:cy="-95.790558"
+           sodipodi:rx="15.161935"
+           sodipodi:ry="25.93924"
+           sodipodi:start="1.0471976"
+           sodipodi:end="5.2359878"
+           sodipodi:arc-type="slice"
+           d="m 175.1455,-73.326517 a 15.161935,25.93924 0 0 1 -19.19569,-5.79062 15.161935,25.93924 0 0 1 0,-33.346843 15.161935,25.93924 0 0 1 19.19569,-5.79062 l -7.58097,22.464042 z" />
+      </g>
+    </g>
+  </g>
+</svg>

+ 2 - 2
examples/webgl_loader_svg.html

@@ -119,8 +119,8 @@
 					'Multiple CSS classes': 'models/svg/multiple-css-classes.svg',
 					'Zero Radius': 'models/svg/zero-radius.svg',
 					'Styles in svg tag': 'models/svg/tests/styles.svg',
-					'Round join': 'models/svg/tests/roundJoinPrecisionIssue.svg'
-
+					'Round join': 'models/svg/tests/roundJoinPrecisionIssue.svg',
+					'Ellipse Transformations': 'models/svg/tests/ellipseTransform.svg',
 
 				} ).name( 'SVG File' ).onChange( update );