소스 검색

SVGLoader: support arbitrary arcs arbitrary transforms (#24778)

* SVGLoader: support ellipse transform when flipped and rotated 180°

* SVGLoader: rewrite skewed ellipse transform to support arbitrary arcs
Victor Nakoryakov 2 년 전
부모
커밋
d75f4d120c
2개의 변경된 파일562개의 추가작업 그리고 102개의 파일을 삭제
  1. 175 92
      examples/jsm/loaders/SVGLoader.js
  2. 387 10
      examples/models/svg/tests/ellipseTransform.svg

+ 175 - 92
examples/jsm/loaders/SVGLoader.js

@@ -1588,98 +1588,118 @@ class SVGLoader extends Loader {
 
 			}
 
-			function ellipseMatrixForm( curve, out ) {
+			function transfEllipseGeneric( curve ) {
 
-				// Build matrix form of the original ellipse curve
-				// See: https://en.wikipedia.org/wiki/Ellipse → General ellipse
+				// For math description see:
+				// https://math.stackexchange.com/questions/4544164
 
 				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,
+				const cosTheta = Math.cos( curve.aRotation );
+				const sinTheta = Math.sin( curve.aRotation );
+
+				const v1 = new Vector3( a * cosTheta, a * sinTheta, 0 );
+				const v2 = new Vector3( -b * sinTheta, b * cosTheta, 0 );
+
+				const f1 = v1.applyMatrix3( m );
+				const f2 = v2.applyMatrix3( m );
+
+				const mF = tempTransform0.set(
+					f1.x, f2.x, 0,
+					f1.y, f2.y, 0,
+					0,    0,    1,
 				);
 
-			}
+				const mFInv = tempTransform1.copy( mF ).invert();
+				const mFInvT = tempTransform2.copy( mFInv ).transpose();
+				const mQ = mFInvT.multiply( mFInv );
+				const mQe = mQ.elements;
 
-			function transfEllipse( curve ) {
+				const ed = eigenDecomposition( mQe[0], mQe[1], mQe[4] );
+				const rt1sqrt = Math.sqrt( ed.rt1 );
+				const rt2sqrt = Math.sqrt( ed.rt2 );
 
-				// See:
-				// - https://math.stackexchange.com/questions/1498799/
-				// - https://math.stackexchange.com/questions/3076317/
+				curve.xRadius = 1 / rt1sqrt;
+				curve.yRadius = 1 / rt2sqrt;
+				curve.aRotation = Math.atan2( ed.sn, ed.cs );
 
-				const mQ = ellipseMatrixForm( curve, tempTransform0 );
-				const mInvT = tempTransform1.copy( m ).invert();
-				const mQNew = tempTransform2.copy( mInvT ).transpose().multiply( mQ ).multiply( mInvT );
+				const isFullEllipse =
+					( curve.aEndAngle - curve.aStartAngle ) % ( 2 * Math.PI ) < Number.EPSILON;
 
-				// Perform de-composition
+				// Do not touch angles of a full ellipse because after transformation they
+				// would converge to a sinle value effectively removing the whole curve
 
-				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 ];
+				if ( !isFullEllipse ) {
 
-				// Compute canonical ellipse params
+					const mDsqrt = tempTransform1.set(
+						rt1sqrt, 0, 0,
+						0, rt2sqrt, 0,
+						0, 0,       1,
+					);
 
-				const discriminant = B * B - 4 * A * C;
+					const mRT = tempTransform2.set(
+						ed.cs,  ed.sn, 0,
+						-ed.sn, ed.cs, 0,
+						0,      0,     1,
+					);
+
+					const mDRF = mDsqrt.multiply( mRT ).multiply( mF );
 
-				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 transformAngle = phi => {
 
-				const a = Math.sqrt( abP1 * ( A + C + abP2 ) ) / discriminant;
-				const b = Math.sqrt( abP1 * ( A + C - abP2 ) ) / discriminant;
+						const { x: cosR, y: sinR } =
+							new Vector3( Math.cos( phi ), Math.sin( phi ), 0 ).applyMatrix3( mDRF );
 
-				const x0 = ( 2 * C * D - B * E ) / discriminant;
-				const y0 = ( 2 * A * E - B * D ) / discriminant;
+						return Math.atan2( sinR, cosR );
 
-				let theta = 0;
+					}
 
-				if ( B !== 0 ) {
+					curve.aStartAngle = transformAngle( curve.aStartAngle );
+					curve.aEndAngle = transformAngle( curve.aEndAngle );
 
-					theta = Math.atan( ( C - A - Math.sqrt( ( A - C ) * ( A - C ) + B * B ) ) / B );
+					if ( isTransformFlipped( m ) ) {
 
-				} else if ( A > C ) {
+						curve.aClockwise = !curve.aClockwise;
 
-					theta = Math.PI / 2;
+					}
 
 				}
 
-				// Update data in-place
+			}
+
+			function transfEllipseNoSkew( curve ) {
+
+				// Faster shortcut if no skew is applied
+				// (e.g, a euclidean transform of a group containing the ellipse)
+
+				const sx = getTransformScaleX( m );
+				const sy = getTransformScaleY( m );
 
-				curve.aX = x0;
-				curve.aY = y0;
-				curve.xRadius = a;
-				curve.yRadius = b;
-				curve.aRotation = theta;
+				curve.xRadius *= sx;
+				curve.yRadius *= sy;
 
-				// 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 = ???;
+				// Extract rotation angle from the matrix of form:
+				//
+				//  | cosθ sx   -sinθ sy |
+				//  | sinθ sx    cosθ sy |
+				//
+				// Remembering that tanθ = sinθ / cosθ; and that
+				// `sx`, `sy`, or both might be zero.
+				const theta =
+					sx > Number.EPSILON
+					? Math.atan2( m.elements[ 1 ], m.elements[ 0 ] )
+					: Math.atan2( - m.elements[ 3 ], m.elements[ 4 ] );
 
-				return curve;
+				curve.aRotation += theta;
+
+				if ( isTransformFlipped( m ) ) {
+
+					curve.aStartAngle *= -1;
+					curve.aEndAngle *= -1;
+					curve.aClockwise = !curve.aClockwise;
+
+				}
 
 			}
 
@@ -1714,39 +1734,22 @@ class SVGLoader extends Loader {
 
 					} else if ( curve.isEllipseCurve ) {
 
-						if ( isTransformSkewed( m ) ) {
+						// Transform ellipse center point
 
-							if ( ( curve.aEndAngle - curve.aStartAngle ) % ( 2 * Math.PI ) !== 0 ) {
+						tempV2.set( curve.aX, curve.aY );
+						transfVec2( tempV2 );
+						curve.aX = tempV2.x;
+						curve.aY = tempV2.y;
 
-								console.warn( 'SVGLoader: Elliptic arc skewing is not implemented.' );
+						// Transform ellipse shape parameters
 
-							}
+						if ( isTransformSkewed( m ) ) {
 
-							transfEllipse( curve );
+							transfEllipseGeneric( 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 );
-
-							curve.xRadius *= sx;
-							curve.yRadius *= sy;
-
-							if ( isTransformRotated( m ) ) {
-
-								const sin = m.elements[ 1 ] / sx;
-								const cos = m.elements[ 0 ] / sx;
-								curve.aRotation += Math.atan2( sin, cos );
-
-							}
+							transfEllipseNoSkew( curve );
 
 						}
 
@@ -1758,9 +1761,10 @@ class SVGLoader extends Loader {
 
 		}
 
-		function isTransformRotated( m ) {
+		function isTransformFlipped( m ) {
 
-			return m.elements[ 1 ] !== 0 || m.elements[ 3 ] !== 0;
+			const te = m.elements;
+			return te[ 0 ] * te[ 4 ] - te[ 1 ] * te[ 3 ] < 0;
 
 		}
 
@@ -1793,6 +1797,85 @@ class SVGLoader extends Loader {
 
 		}
 
+		// Calculates the eigensystem of a real symmetric 2x2 matrix
+		//    [ A  B ]
+		//    [ B  C ]
+		// in the form
+		//    [ A  B ]  =  [ cs  -sn ] [ rt1   0  ] [  cs  sn ]
+		//    [ B  C ]     [ sn   cs ] [  0   rt2 ] [ -sn  cs ]
+		// where rt1 >= rt2.
+		//
+		// Adapted from: https://www.mpi-hd.mpg.de/personalhomes/globes/3x3/index.html
+		// -> Algorithms for real symmetric matrices -> Analytical (2x2 symmetric)
+		function eigenDecomposition( A, B, C ) {
+
+			let rt1, rt2, cs, sn, t;
+			const sm = A + C;
+			const df = A - C;
+			const rt = Math.sqrt( df * df + 4 * B * B );
+
+			if ( sm > 0 ) {
+
+				rt1 = 0.5 * ( sm + rt );
+				t = 1 / rt1;
+				rt2 = A * t * C - B * t * B;
+
+			} else if ( sm < 0 ) {
+
+				rt2 = 0.5 * ( sm - rt );
+
+			} else {
+
+				// This case needs to be treated separately to avoid div by 0
+
+				rt1 = 0.5 * rt;
+				rt2 = -0.5 * rt;
+
+			}
+
+			// Calculate eigenvectors
+
+			if ( df > 0 ) {
+
+				cs = df + rt;
+
+			} else {
+
+				cs = df - rt;
+
+			}
+
+			if ( Math.abs( cs ) > 2 * Math.abs( B ) ) {
+
+				t = -2 * B / cs;
+				sn = 1 / Math.sqrt( 1 + t * t );
+				cs = t * sn;
+
+			} else if ( Math.abs( B ) === 0 ) {
+
+				cs = 1;
+				sn = 0;
+
+			} else {
+
+				t = -0.5 * cs / B;
+				cs = 1 / Math.sqrt( 1 + t * t );
+				sn = t * cs;
+
+			}
+
+			if ( df > 0 ) {
+
+				t = cs;
+				cs = -sn;
+				sn = t;
+
+			}
+
+			return { rt1, rt2, cs, sn };
+
+		}
+
 		//
 
 		const paths = [];

+ 387 - 10
examples/models/svg/tests/ellipseTransform.svg

@@ -2,13 +2,13 @@
 <!-- Created with Inkscape (http://www.inkscape.org/) -->
 
 <svg
-   width="210mm"
-   height="297mm"
-   viewBox="0 0 210 297"
+   width="267.87769mm"
+   height="321.00046mm"
+   viewBox="0 0 267.87769 321.00046"
    version="1.1"
    id="svg1231"
    inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
-   sodipodi:docname="pacman.svg"
+   sodipodi:docname="pacman-v3.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"
@@ -24,9 +24,9 @@
      inkscape:deskcolor="#d1d1d1"
      inkscape:document-units="mm"
      showgrid="false"
-     inkscape:zoom="0.42048261"
-     inkscape:cx="-365.05672"
-     inkscape:cy="413.81022"
+     inkscape:zoom="0.59465221"
+     inkscape:cx="707.13603"
+     inkscape:cy="566.71781"
      inkscape:window-width="1912"
      inkscape:window-height="1039"
      inkscape:window-x="2"
@@ -38,7 +38,8 @@
   <g
      inkscape:label="Layer 1"
      inkscape:groupmode="layer"
-     id="layer1">
+     id="layer1"
+     transform="translate(-4.3378114,-8.5715742)">
     <g
        id="g1683"
        transform="translate(-32.245799,132.66262)">
@@ -227,7 +228,7 @@
     </g>
     <g
        id="g1723"
-       transform="matrix(-0.32285127,0.32285127,-0.32285127,-0.32285127,118.3435,39.613059)"
+       transform="matrix(-0.32285127,0.32285127,-0.32285127,-0.32285127,191.3114,168.32464)"
        style="fill:#d3d3d3">
       <g
          id="g1721"
@@ -415,7 +416,7 @@
     </g>
     <g
        id="g1763"
-       transform="matrix(1,0,0.57735027,1,51.196895,338.95991)"
+       transform="matrix(1,0,0.57735027,1,30.45554,334.5724)"
        style="fill:#f4a460">
       <g
          id="g1761"
@@ -507,5 +508,381 @@
            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="g487"
+       transform="matrix(0,1,1,0,340.33732,-32.720464)"
+       style="fill:#f4679d">
+      <g
+         id="g485"
+         style="fill:#f4679d">
+        <path
+           style="fill:#f4679d;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path469"
+           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:#f4679d;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path471"
+           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:#f4679d;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path473"
+           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:#f4679d;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path475"
+           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:#f4679d;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path477"
+           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:#f4679d;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="circle479"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#f4679d;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse481"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#f4679d;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path483"
+           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="g508"
+       transform="matrix(0,-1,-1,0,148.12444,323.29383)"
+       style="fill:#cd9ef7">
+      <g
+         id="g506"
+         style="fill:#cd9ef7">
+        <path
+           style="fill:#cd9ef7;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path489"
+           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:#cd9ef7;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path491"
+           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:#cd9ef7;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path493"
+           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:#cd9ef7;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path495"
+           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:#cd9ef7;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path497"
+           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:#cd9ef7;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="circle499"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#cd9ef7;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse501"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#cd9ef7;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path504"
+           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="g528"
+       transform="matrix(-1,0,-0.57735027,1,140.85866,398.32563)"
+       style="fill:#d1ff82">
+      <g
+         id="g526"
+         style="fill:#d1ff82">
+        <path
+           style="fill:#d1ff82;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path510"
+           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:#d1ff82;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path512"
+           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:#d1ff82;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path514"
+           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:#d1ff82;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path516"
+           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:#d1ff82;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path518"
+           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:#d1ff82;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="circle520"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#d1ff82;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse522"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#d1ff82;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path524"
+           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="g548"
+       transform="rotate(180,90.28112,5.0646332)"
+       style="fill:#f37329">
+      <g
+         id="g546"
+         style="fill:#f37329">
+        <path
+           style="fill:#f37329;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path530"
+           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:#f37329;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path532"
+           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:#f37329;stroke:#000000;stroke-width:2.12143;stroke-linecap:round"
+           id="path534"
+           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:#f37329;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path536"
+           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:#f37329;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path538"
+           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:#f37329;fill-opacity:1;stroke:#000000;stroke-width:1.881;stroke-linecap:round;stroke-dasharray:none"
+           id="circle540"
+           cx="99.741562"
+           cy="-83.12532"
+           r="12.126567" />
+        <ellipse
+           style="fill:#f37329;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="ellipse542"
+           cx="131.77115"
+           cy="-95.633247"
+           rx="15.161935"
+           ry="25.93924" />
+        <path
+           style="fill:#f37329;stroke:#000000;stroke-width:1.8808;stroke-linecap:round"
+           id="path544"
+           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>