x_ite 8.6.4 → 8.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/assets/components/Annotation.js +13 -13
  2. package/dist/assets/components/Annotation.min.js +1 -1
  3. package/dist/assets/components/CADGeometry.js +13 -13
  4. package/dist/assets/components/CADGeometry.min.js +1 -1
  5. package/dist/assets/components/CubeMapTexturing.js +25 -25
  6. package/dist/assets/components/CubeMapTexturing.min.js +1 -1
  7. package/dist/assets/components/DIS.js +13 -13
  8. package/dist/assets/components/DIS.min.js +1 -1
  9. package/dist/assets/components/EventUtilities.js +9 -9
  10. package/dist/assets/components/EventUtilities.min.js +1 -1
  11. package/dist/assets/components/Geometry2D.js +19 -19
  12. package/dist/assets/components/Geometry2D.min.js +1 -1
  13. package/dist/assets/components/Geospatial.js +33 -33
  14. package/dist/assets/components/Geospatial.min.js +1 -1
  15. package/dist/assets/components/HAnim.js +18 -18
  16. package/dist/assets/components/HAnim.min.js +1 -1
  17. package/dist/assets/components/KeyDeviceSensor.js +8 -8
  18. package/dist/assets/components/KeyDeviceSensor.min.js +1 -1
  19. package/dist/assets/components/Layout.js +27 -27
  20. package/dist/assets/components/Layout.min.js +1 -1
  21. package/dist/assets/components/NURBS.js +24 -24
  22. package/dist/assets/components/NURBS.min.js +1 -1
  23. package/dist/assets/components/ParticleSystems.js +23 -23
  24. package/dist/assets/components/ParticleSystems.min.js +1 -1
  25. package/dist/assets/components/Picking.js +19 -19
  26. package/dist/assets/components/Picking.min.js +1 -1
  27. package/dist/assets/components/RigidBodyPhysics.js +18 -18
  28. package/dist/assets/components/RigidBodyPhysics.min.js +1 -1
  29. package/dist/assets/components/Scripting.js +28 -28
  30. package/dist/assets/components/Scripting.min.js +1 -1
  31. package/dist/assets/components/Text.js +24 -24
  32. package/dist/assets/components/Text.min.js +1 -1
  33. package/dist/assets/components/TextureProjector.js +14 -14
  34. package/dist/assets/components/TextureProjector.min.js +1 -1
  35. package/dist/assets/components/Texturing3D.js +30 -30
  36. package/dist/assets/components/Texturing3D.min.js +1 -1
  37. package/dist/assets/components/VolumeRendering.js +19 -19
  38. package/dist/assets/components/VolumeRendering.min.js +1 -1
  39. package/dist/assets/components/X_ITE.js +9 -9
  40. package/dist/assets/components/X_ITE.min.js +1 -1
  41. package/dist/x_ite.css +1 -1
  42. package/dist/x_ite.js +1235 -1000
  43. package/dist/x_ite.min.js +1 -1
  44. package/dist/x_ite.zip +0 -0
  45. package/docs/_config.yml +2 -2
  46. package/docs/_posts/components/Navigation/NavigationInfo.md +8 -3
  47. package/docs/_posts/laboratory/x3d-file-converter.md +2 -0
  48. package/package.json +1 -1
  49. package/src/standard/Math/Numbers/Matrix4.js +7 -0
  50. package/src/x_ite/Base/Events.js +10 -23
  51. package/src/x_ite/Base/X3DObject.js +29 -16
  52. package/src/x_ite/Browser/VERSION.js +1 -1
  53. package/src/x_ite/Components/Shape/Material.js +2 -2
  54. package/src/x_ite/Components/Shape/PhysicalMaterial.js +2 -2
  55. package/src/x_ite/Components/Shape/UnlitMaterial.js +2 -2
  56. package/src/x_ite/Components/Texturing/ImageTexture.js +3 -0
  57. package/src/x_ite/Parser/SVGParser.js +652 -427
  58. package/src/x_ite/Parser/X3DParser.js +2 -2
@@ -57,8 +57,10 @@ import Vector4 from "../../standard/Math/Numbers/Vector4.js";
57
57
  import Rotation4 from "../../standard/Math/Numbers/Rotation4.js";
58
58
  import Matrix3 from "../../standard/Math/Numbers/Matrix3.js";
59
59
  import Matrix4 from "../../standard/Math/Numbers/Matrix4.js";
60
+ import Complex from "../../standard/Math/Numbers/Complex.js";
60
61
  import Box2 from "../../standard/Math/Geometry/Box2.js"
61
62
  import Bezier from "../../standard/Math/Algorithms/Bezier.js";
63
+ import MatrixStack from "../../standard/Math/Utility/MatrixStack.js";
62
64
 
63
65
  /*
64
66
  * Grammar
@@ -73,7 +75,7 @@ const Grammar = Expressions ({
73
75
  closeParenthesis: /\)/gy,
74
76
 
75
77
  // Units
76
- length: /(em|ex|px|in|cm|mm|pt|pc)/gy,
78
+ length: /(em|ex|px|in|cm|mm|pt|pc|%)/gy,
77
79
  percent: /%/gy,
78
80
 
79
81
  // Values
@@ -96,16 +98,14 @@ const Grammar = Expressions ({
96
98
  */
97
99
 
98
100
  const
99
- MM = 0.001, // One mm in meters.
100
- CM = 0.01, // One cm in meters.
101
- INCH = 0.0254, // One inch in meters.
102
- POINT = INCH / 72, // One point in meters.
103
- PICA = INCH / 6, // One pica in meters.
104
- PIXEL = INCH / 90, // One pixel in meters.
105
- EM = 16, // One em in pixels,
106
- BEZIER_STEPS = 10, // Subdivisions of a span.
107
- CIRCLE_STEPS = 64, // Subdivisions of a circle, used for arc.
108
- GRADIENT_SIZE = 256; // In pixels.
101
+ MM = 0.001, // One mm in meters.
102
+ CM = 0.01, // One cm in meters.
103
+ INCH = 0.0254, // One inch in meters.
104
+ POINT = INCH / 72, // One point in meters.
105
+ PICA = INCH / 6, // One pica in meters.
106
+ PIXEL = INCH / 90, // One pixel in meters.
107
+ EM = 16, // One em in pixels.
108
+ SPREAD = 16; // Spread factor, Integer.
109
109
 
110
110
  /*
111
111
  * Parser
@@ -124,17 +124,18 @@ function SVGParser (scene)
124
124
 
125
125
  // Options
126
126
 
127
- this .solid = false; // Are 2D primitives solid.
127
+ this .solid = false; // Are 2D primitives solid?
128
128
 
129
129
  // Globals
130
130
 
131
- this .nodes = new Map ();
132
- this .tessy = this .createTesselator ();
133
- this .canvas = document .createElement ("canvas");
134
- this .context = this .canvas .getContext ("2d");
135
-
136
- this .canvas .width = GRADIENT_SIZE;
137
- this .canvas .height = GRADIENT_SIZE;
131
+ this .viewBox = new Vector4 (0, 0, 100, 100);
132
+ this .modelMatrix = new MatrixStack (Matrix4);
133
+ this .fillGeometries = new Map ();
134
+ this .strokeGeometries = new Map ();
135
+ this .lineProperties = new Map ();
136
+ this .tessy = this .createTesselator ();
137
+ this .canvas = document .createElement ("canvas");
138
+ this .context = this .canvas .getContext ("2d");
138
139
 
139
140
  this .styles = [{
140
141
  display: "inline",
@@ -153,6 +154,42 @@ function SVGParser (scene)
153
154
  stopOpacity: 1,
154
155
  vectorEffect: "none",
155
156
  }];
157
+
158
+ // Constants
159
+
160
+ const browser = scene .getBrowser ()
161
+
162
+ switch (browser .getBrowserOption ("PrimitiveQuality"))
163
+ {
164
+ case "LOW":
165
+ this .BEZIER_STEPS = 6; // Subdivisions of a span.
166
+ this .CIRCLE_STEPS = 20; // Subdivisions of a circle, used for arc and rounded rect.
167
+ break;
168
+ case "HIGH":
169
+ this .BEZIER_STEPS = 10; // Subdivisions of a span.
170
+ this .CIRCLE_STEPS = 64; // Subdivisions of a circle, used for arc and rounded rect.
171
+ break;
172
+ default:
173
+ this .BEZIER_STEPS = 8; // Subdivisions of a span.
174
+ this .CIRCLE_STEPS = 32; // Subdivisions of a circle, used for arc and rounded rect.
175
+ break;
176
+ }
177
+
178
+ switch (browser .getBrowserOption ("TextureQuality"))
179
+ {
180
+ case "LOW":
181
+ this .GRADIENT_SIZE = 128; // In pixels.
182
+ break;
183
+ case "HIGH":
184
+ this .GRADIENT_SIZE = 512; // In pixels.
185
+ break;
186
+ default:
187
+ this .GRADIENT_SIZE = 256; // In pixels.
188
+ break;
189
+ }
190
+
191
+ this .canvas .width = this .GRADIENT_SIZE;
192
+ this .canvas .height = this .GRADIENT_SIZE;
156
193
  }
157
194
 
158
195
  SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
@@ -261,16 +298,16 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
261
298
  // Get attributes of svg element.
262
299
 
263
300
  const
264
- defaultWidth = this .lengthAttribute (xmlElement .getAttribute ("width"), 300),
265
- defaultHeight = this .lengthAttribute (xmlElement .getAttribute ("height"), 150),
266
- defaultViewBox = new Vector4 (0, 0, defaultWidth, defaultHeight),
301
+ defaultWidth = this .lengthAttribute (xmlElement .getAttribute ("width"), 300, "width"),
302
+ defaultHeight = this .lengthAttribute (xmlElement .getAttribute ("height"), 150, "height"),
303
+ defaultViewBox = this .viewBox .set (0, 0, defaultWidth, defaultHeight),
267
304
  viewBox = this .viewBoxAttribute (xmlElement .getAttribute ("viewBox"), defaultViewBox),
268
- width = this .lengthAttribute (xmlElement .getAttribute ("width"), viewBox [2]),
269
- height = this .lengthAttribute (xmlElement .getAttribute ("height"), viewBox [3]);
305
+ width = this .lengthAttribute (xmlElement .getAttribute ("width"), viewBox [2], "width"),
306
+ height = this .lengthAttribute (xmlElement .getAttribute ("height"), viewBox [3], "height");
270
307
 
271
308
  if (true) // default
272
309
  {
273
- // preserveAspectRatio="xMidYMid meet"
310
+ // preserveAspectRatio = "xMidYMid meet"
274
311
 
275
312
  const
276
313
  r = width / height,
@@ -330,9 +367,6 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
330
367
  },
331
368
  element: function (xmlElement)
332
369
  {
333
- if (this .used (xmlElement))
334
- return;
335
-
336
370
  switch (xmlElement .nodeName)
337
371
  {
338
372
  case "use":
@@ -361,22 +395,11 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
361
395
  return this .pathElement (xmlElement);
362
396
  }
363
397
  },
364
- used: function (xmlElement)
365
- {
366
- const node = this .nodes .get (xmlElement);
367
-
368
- if (!node)
369
- return false;
370
-
371
- this .groupNodes .at (-1) .children .push (node);
372
-
373
- return true;
374
- },
375
398
  useElement: function (xmlElement)
376
399
  {
377
400
  // Get href.
378
401
 
379
- const usedElement = this .hrefAttribute (xmlElement .getAttribute ("xlink:href"));
402
+ const usedElement = this .hrefAttribute (xmlElement .getAttribute ("href") || xmlElement .getAttribute ("xlink:href"));
380
403
 
381
404
  if (!usedElement)
382
405
  return;
@@ -389,10 +412,10 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
389
412
  // Create Transform node.
390
413
 
391
414
  const
392
- x = this .lengthAttribute (xmlElement .getAttribute ("x"), 0),
393
- y = this .lengthAttribute (xmlElement .getAttribute ("y"), 0),
394
- width = this .lengthAttribute (xmlElement .getAttribute ("width"), 0),
395
- height = this .lengthAttribute (xmlElement .getAttribute ("height"), 0);
415
+ x = this .lengthAttribute (xmlElement .getAttribute ("x"), 0, "width"),
416
+ y = this .lengthAttribute (xmlElement .getAttribute ("y"), 0, "height"),
417
+ width = this .lengthAttribute (xmlElement .getAttribute ("width"), 0, "width"),
418
+ height = this .lengthAttribute (xmlElement .getAttribute ("height"), 0, "height");
396
419
 
397
420
  const transformNode = this .createTransform (xmlElement, new Vector2 (x, y));
398
421
 
@@ -400,12 +423,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
400
423
 
401
424
  this .element (usedElement);
402
425
 
403
- this .styles .pop ();
404
- this .groupNodes .pop ();
405
-
406
- // Add node.
407
-
408
- this .groupNodes .at (-1) .children .push (transformNode);
426
+ this .popAll ();
409
427
  },
410
428
  gElement: function (xmlElement)
411
429
  {
@@ -424,12 +442,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
424
442
 
425
443
  this .elements (xmlElement);
426
444
 
427
- this .styles .pop ();
428
- this .groupNodes .pop ();
429
-
430
- // Add node.
431
-
432
- this .groupNodes .at (-1) .children .push (transformNode);
445
+ this .popAll ();
433
446
  },
434
447
  switchElement: function (xmlElement)
435
448
  {
@@ -454,12 +467,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
454
467
 
455
468
  this .elements (xmlElement);
456
469
 
457
- this .styles .pop ();
458
- this .groupNodes .pop ();
459
-
460
- // Add node.
461
-
462
- this .groupNodes .at (-1) .children .push (transformNode);
470
+ this .popAll ();
463
471
  },
464
472
  aElement: function (xmlElement)
465
473
  {
@@ -471,8 +479,8 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
471
479
  // Get attributes.
472
480
 
473
481
  const
474
- href = xmlElement .getAttribute ("xlink:href"),
475
- title = xmlElement .getAttribute ("xlink:title"),
482
+ href = xmlElement .getAttribute ("href") || xmlElement .getAttribute ("xlink:href"),
483
+ title = xmlElement .getAttribute ("title") || xmlElement .getAttribute ("xlink:title"),
476
484
  target = xmlElement .getAttribute ("target");
477
485
 
478
486
  // Create Transform node.
@@ -496,75 +504,134 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
496
504
 
497
505
  this .elements (xmlElement);
498
506
 
499
- this .groupNodes .pop ();
500
- this .styles .pop ();
501
-
502
- this .groupNodes .at (-1) .children .push (transformNode);
507
+ this .popAll ();
503
508
  },
504
509
  rectElement: function (xmlElement)
505
510
  {
506
- // Determine style.
507
-
508
- if (!this .styleAttributes (xmlElement))
509
- return;
510
-
511
511
  // Create Transform node.
512
512
 
513
513
  const
514
- x = this .lengthAttribute (xmlElement .getAttribute ("x"), 0),
515
- y = this .lengthAttribute (xmlElement .getAttribute ("y"), 0),
516
- width = this .lengthAttribute (xmlElement .getAttribute ("width"), 0),
517
- height = this .lengthAttribute (xmlElement .getAttribute ("height"), 0);
514
+ x = this .lengthAttribute (xmlElement .getAttribute ("x"), 0, "width"),
515
+ y = this .lengthAttribute (xmlElement .getAttribute ("y"), 0, "height"),
516
+ width = this .lengthAttribute (xmlElement .getAttribute ("width"), 0, "width"),
517
+ height = this .lengthAttribute (xmlElement .getAttribute ("height"), 0, "height");
518
518
 
519
- const
520
- scene = this .getExecutionContext (),
521
- size = new Vector2 (width, height),
522
- center = new Vector2 (x + width / 2, y + height / 2),
523
- bbox = new Box2 (size, center),
524
- transformNode = this .createTransform (xmlElement, center);
519
+ let
520
+ rx = Math .max (0, this .lengthAttribute (xmlElement .getAttribute ("rx"), 0, "width")),
521
+ ry = Math .max (0, this .lengthAttribute (xmlElement .getAttribute ("ry"), 0, "height"));
525
522
 
526
- this .groupNodes .push (transformNode);
523
+ if (rx === 0 && ry === 0)
524
+ {
525
+ // Determine style.
527
526
 
528
- // Create nodes.
527
+ if (!this .styleAttributes (xmlElement))
528
+ return;
529
+
530
+ // Create Transform node.
529
531
 
530
- if (this .style .fillType !== "none")
531
- {
532
532
  const
533
- shapeNode = scene .createNode ("Shape"),
534
- rectangleNode = scene .createNode ("Rectangle2D");
533
+ scene = this .getExecutionContext (),
534
+ size = new Vector2 (width, height),
535
+ center = new Vector2 (x + width / 2, y + height / 2),
536
+ bbox = new Box2 (size, center),
537
+ transformNode = this .createTransform (xmlElement, center);
535
538
 
536
- shapeNode .appearance = this .createFillAppearance (bbox);
537
- shapeNode .geometry = rectangleNode;
538
- rectangleNode .solid = this .solid;
539
- rectangleNode .size = size;
539
+ this .groupNodes .push (transformNode);
540
540
 
541
- transformNode .children .push (shapeNode);
542
- }
541
+ // Create nodes.
543
542
 
544
- if (this .style .strokeType !== "none")
543
+ if (this .style .fillType !== "none")
544
+ {
545
+ const
546
+ shapeNode = scene .createNode ("Shape"),
547
+ rectangleNode = this .fillGeometries .get (xmlElement);
548
+
549
+ transformNode .children .push (shapeNode);
550
+ shapeNode .appearance = this .createFillAppearance (bbox);
551
+
552
+ if (rectangleNode)
553
+ {
554
+ shapeNode .geometry = rectangleNode;
555
+ }
556
+ else
557
+ {
558
+ const rectangleNode = scene .createNode ("Rectangle2D");
559
+
560
+ this .fillGeometries .set (xmlElement, rectangleNode);
561
+
562
+ shapeNode .geometry = rectangleNode;
563
+ rectangleNode .solid = this .solid;
564
+ rectangleNode .size = size;
565
+ }
566
+ }
567
+
568
+ if (this .style .strokeType !== "none")
569
+ {
570
+ const
571
+ shapeNode = scene .createNode ("Shape"),
572
+ polylineNode = this .strokeGeometries .get (xmlElement);
573
+
574
+ transformNode .children .push (shapeNode);
575
+ shapeNode .appearance = this .createStrokeAppearance ();
576
+
577
+ if (polylineNode)
578
+ {
579
+ shapeNode .geometry = polylineNode;
580
+ }
581
+ else
582
+ {
583
+ const
584
+ polylineNode = scene .createNode ("Polyline2D"),
585
+ width1_2 = width / 2,
586
+ height1_2 = height / 2;
587
+
588
+ this .strokeGeometries .set (xmlElement, polylineNode);
589
+
590
+ shapeNode .geometry = polylineNode;
591
+
592
+ polylineNode .lineSegments = [ width1_2, height1_2,
593
+ -width1_2, height1_2,
594
+ -width1_2, -height1_2,
595
+ width1_2, -height1_2,
596
+ width1_2, height1_2];
597
+ }
598
+ }
599
+
600
+ this .popAll ();
601
+ }
602
+ else
545
603
  {
604
+ // Create points.
605
+
606
+ if (rx && !ry) ry = rx;
607
+ if (ry && !rx) rx = ry;
608
+
609
+ rx = Math .min (rx, width / 2);
610
+ ry = Math .min (ry, height / 2);
611
+
546
612
  const
547
- shapeNode = scene .createNode ("Shape"),
548
- polylineNode = scene .createNode ("Polyline2D"),
549
- width1_2 = width / 2,
550
- height1_2 = height / 2;
613
+ xOffsets = [x + width - rx, x + rx , x + rx, x + width - rx],
614
+ yOffsets = [y + height - ry, y + height - ry, y + ry, y + ry],
615
+ points = Object .assign ([ ], { closed: true });
551
616
 
552
- shapeNode .appearance = this .createStrokeAppearance ();
553
- shapeNode .geometry = polylineNode;
617
+ for (let c = 0; c < 4; ++ c)
618
+ {
619
+ const s = c * Math .PI / 2;
554
620
 
555
- polylineNode .lineSegments = [ width1_2, height1_2,
556
- -width1_2, height1_2,
557
- -width1_2, -height1_2,
558
- width1_2, -height1_2,
559
- width1_2, height1_2];
621
+ for (let i = 0, N = this .CIRCLE_STEPS / 4; i < N; ++ i)
622
+ {
623
+ const p = Complex .Polar (1, s + Math .PI / 2 * i / (N - 1));
560
624
 
561
- transformNode .children .push (shapeNode);
562
- }
625
+ points .push (new Vector3 (xOffsets [c] + p .real * rx, yOffsets [c] + p .imag * ry, 0));
626
+ }
627
+ }
563
628
 
564
- this .groupNodes .pop ();
565
- this .styles .pop ();
629
+ points .pop ();
566
630
 
567
- this .groupNodes .at (-1) .children .push (transformNode);
631
+ // Create nodes.
632
+
633
+ this .pathLikeElement (xmlElement, [points]);
634
+ }
568
635
  },
569
636
  circleElement: function (xmlElement)
570
637
  {
@@ -576,8 +643,8 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
576
643
  // Create Transform node.
577
644
 
578
645
  const
579
- cx = this .lengthAttribute (xmlElement .getAttribute ("cx"), 0),
580
- cy = this .lengthAttribute (xmlElement .getAttribute ("cy"), 0),
646
+ cx = this .lengthAttribute (xmlElement .getAttribute ("cx"), 0, "width"),
647
+ cy = this .lengthAttribute (xmlElement .getAttribute ("cy"), 0, "height"),
581
648
  r = this .lengthAttribute (xmlElement .getAttribute ("r"), 0);
582
649
 
583
650
  const
@@ -593,33 +660,52 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
593
660
  {
594
661
  const
595
662
  shapeNode = scene .createNode ("Shape"),
596
- diskNode = scene .createNode ("Disk2D");
663
+ diskNode = this .fillGeometries .get (xmlElement);
597
664
 
665
+ transformNode .children .push (shapeNode);
598
666
  shapeNode .appearance = this .createFillAppearance (bbox);
599
- shapeNode .geometry = diskNode;
600
- diskNode .solid = this .solid;
601
- diskNode .outerRadius = r;
602
667
 
603
- transformNode .children .push (shapeNode);
668
+ if (diskNode)
669
+ {
670
+ shapeNode .geometry = diskNode;
671
+ }
672
+ else
673
+ {
674
+ const diskNode = scene .createNode ("Disk2D");
675
+
676
+ this .fillGeometries .set (xmlElement, diskNode);
677
+
678
+ shapeNode .geometry = diskNode;
679
+ diskNode .solid = this .solid;
680
+ diskNode .outerRadius = r;
681
+ }
604
682
  }
605
683
 
606
684
  if (this .style .strokeType !== "none")
607
685
  {
608
686
  const
609
687
  shapeNode = scene .createNode ("Shape"),
610
- circleNode = scene .createNode ("Circle2D");
688
+ circleNode = this .strokeGeometries .get (xmlElement);
611
689
 
690
+ transformNode .children .push (shapeNode);
612
691
  shapeNode .appearance = this .createStrokeAppearance ();
613
- shapeNode .geometry = circleNode;
614
- circleNode .radius = r;
615
692
 
616
- transformNode .children .push (shapeNode);
617
- }
693
+ if (circleNode)
694
+ {
695
+ shapeNode .geometry = circleNode;
696
+ }
697
+ else
698
+ {
699
+ const circleNode = scene .createNode ("Circle2D");
618
700
 
619
- this .groupNodes .pop ();
620
- this .styles .pop ();
701
+ this .strokeGeometries .set (xmlElement, circleNode);
621
702
 
622
- this .groupNodes .at (-1) .children .push (transformNode);
703
+ shapeNode .geometry = circleNode;
704
+ circleNode .radius = r;
705
+ }
706
+ }
707
+
708
+ this .popAll ();
623
709
  },
624
710
  ellipseElement: function (xmlElement)
625
711
  {
@@ -631,10 +717,10 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
631
717
  // Create Transform node.
632
718
 
633
719
  const
634
- cx = this .lengthAttribute (xmlElement .getAttribute ("cx"), 0),
635
- cy = this .lengthAttribute (xmlElement .getAttribute ("cy"), 0),
636
- rx = this .lengthAttribute (xmlElement .getAttribute ("rx"), 0),
637
- ry = this .lengthAttribute (xmlElement .getAttribute ("ry"), 0);
720
+ cx = this .lengthAttribute (xmlElement .getAttribute ("cx"), 0, "width"),
721
+ cy = this .lengthAttribute (xmlElement .getAttribute ("cy"), 0, "height"),
722
+ rx = this .lengthAttribute (xmlElement .getAttribute ("rx"), 0, "width"),
723
+ ry = this .lengthAttribute (xmlElement .getAttribute ("ry"), 0, "height");
638
724
 
639
725
  const
640
726
  scene = this .getExecutionContext (),
@@ -650,33 +736,52 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
650
736
  {
651
737
  const
652
738
  shapeNode = scene .createNode ("Shape"),
653
- diskNode = scene .createNode ("Disk2D");
739
+ diskNode = this .fillGeometries .get (xmlElement);
654
740
 
741
+ transformNode .children .push (shapeNode);
655
742
  shapeNode .appearance = this .createFillAppearance (bbox);
656
- shapeNode .geometry = diskNode;
657
- diskNode .solid = this .solid;
658
- diskNode .outerRadius = rMin;
659
743
 
660
- transformNode .children .push (shapeNode);
744
+ if (diskNode)
745
+ {
746
+ shapeNode .geometry = diskNode;
747
+ }
748
+ else
749
+ {
750
+ const diskNode = scene .createNode ("Disk2D");
751
+
752
+ this .fillGeometries .set (xmlElement, diskNode);
753
+
754
+ shapeNode .geometry = diskNode;
755
+ diskNode .solid = this .solid;
756
+ diskNode .outerRadius = rMin;
757
+ }
661
758
  }
662
759
 
663
760
  if (this .style .strokeType !== "none")
664
761
  {
665
762
  const
666
763
  shapeNode = scene .createNode ("Shape"),
667
- circleNode = scene .createNode ("Circle2D");
764
+ circleNode = this .strokeGeometries .get (xmlElement);
668
765
 
766
+ transformNode .children .push (shapeNode);
669
767
  shapeNode .appearance = this .createStrokeAppearance ();
670
- shapeNode .geometry = circleNode;
671
- circleNode .radius = rMin;
672
768
 
673
- transformNode .children .push (shapeNode);
674
- }
769
+ if (circleNode)
770
+ {
771
+ shapeNode .geometry = circleNode;
772
+ }
773
+ else
774
+ {
775
+ const circleNode = scene .createNode ("Circle2D");
675
776
 
676
- this .groupNodes .pop ();
677
- this .styles .pop ();
777
+ this .strokeGeometries .set (xmlElement, circleNode);
678
778
 
679
- this .groupNodes .at (-1) .children .push (transformNode);
779
+ shapeNode .geometry = circleNode;
780
+ circleNode .radius = rMin;
781
+ }
782
+ }
783
+
784
+ this .popAll ();
680
785
  },
681
786
  textElement: function (xmlElement)
682
787
  {
@@ -684,107 +789,81 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
684
789
  },
685
790
  imageElement: function (xmlElement)
686
791
  {
687
- // Create Transform node.
792
+ const transformNode = this .fillGeometries .get (xmlElement);
688
793
 
689
- const
690
- x = this .lengthAttribute (xmlElement .getAttribute ("x"), 0),
691
- y = this .lengthAttribute (xmlElement .getAttribute ("y"), 0),
692
- width = this .lengthAttribute (xmlElement .getAttribute ("width"), 0),
693
- height = this .lengthAttribute (xmlElement .getAttribute ("height"), 0),
694
- href = xmlElement .getAttribute ("xlink:href");
794
+ if (transformNode)
795
+ {
796
+ this .groupNodes .at (-1) .children .push (transformNode);
797
+ }
798
+ else
799
+ {
800
+ // Determine style.
695
801
 
696
- const
697
- scene = this .getExecutionContext (),
698
- transformNode = this .createTransform (xmlElement, new Vector2 (x + width / 2, y + height / 2), new Vector2 (1, -1));
802
+ if (!this .styleAttributes (xmlElement))
803
+ return;
699
804
 
700
- this .groupNodes .push (transformNode);
805
+ // Create Transform node.
701
806
 
702
- // Create nodes.
807
+ const
808
+ x = this .lengthAttribute (xmlElement .getAttribute ("x"), 0, "width"),
809
+ y = this .lengthAttribute (xmlElement .getAttribute ("y"), 0, "height"),
810
+ width = this .lengthAttribute (xmlElement .getAttribute ("width"), 0, "width"),
811
+ height = this .lengthAttribute (xmlElement .getAttribute ("height"), 0, "height"),
812
+ href = xmlElement .getAttribute ("href") || xmlElement .getAttribute ("xlink:href");
703
813
 
704
- const
705
- shapeNode = scene .createNode ("Shape"),
706
- appearanceNode = scene .createNode ("Appearance"),
707
- textureNode = scene .createNode ("ImageTexture"),
708
- rectangleNode = scene .createNode ("Rectangle2D");
814
+ const
815
+ scene = this .getExecutionContext (),
816
+ transformNode = this .createTransform (xmlElement, new Vector2 (x + width / 2, y + height / 2), new Vector2 (1, -1));
709
817
 
710
- shapeNode .appearance = appearanceNode;
711
- shapeNode .geometry = rectangleNode;
712
- appearanceNode .texture = textureNode;
713
- textureNode .url = [href];
714
- textureNode .textureProperties = this .texturePropertiesNode;
715
- rectangleNode .solid = this .solid;
716
- rectangleNode .size = new Vector2 (width, height);
818
+ this .fillGeometries .set (xmlElement, transformNode);
819
+ this .groupNodes .push (transformNode);
717
820
 
718
- transformNode .children .push (shapeNode);
821
+ // Create nodes.
719
822
 
720
- this .groupNodes .pop ();
721
- this .groupNodes .at (-1) .children .push (transformNode);
823
+ const
824
+ shapeNode = scene .createNode ("Shape"),
825
+ appearanceNode = scene .createNode ("Appearance"),
826
+ textureNode = scene .createNode ("ImageTexture"),
827
+ rectangleNode = scene .createNode ("Rectangle2D");
828
+
829
+ shapeNode .appearance = appearanceNode;
830
+ shapeNode .geometry = rectangleNode;
831
+ appearanceNode .texture = textureNode;
832
+ textureNode .url = [href];
833
+ textureNode .textureProperties = this .texturePropertiesNode;
834
+ rectangleNode .solid = this .solid;
835
+ rectangleNode .size = new Vector2 (width, height);
836
+
837
+ transformNode .children .push (shapeNode);
838
+
839
+ this .popAll ();
840
+ }
722
841
  },
723
- polylineElement: function (xmlElement, closed = false)
842
+ polylineElement: function (xmlElement)
724
843
  {
725
- const points = Object .assign ([ ], { index: 0 });
844
+ // Get points.
726
845
 
727
- if (!this .pointsAttribute (xmlElement .getAttribute ("points"), points))
728
- return;
846
+ const points = [ ];
729
847
 
730
- // Determine style.
731
-
732
- if (!this .styleAttributes (xmlElement))
848
+ if (!this .pointsAttribute (xmlElement .getAttribute ("points"), points))
733
849
  return;
734
850
 
735
- // Create Transform node.
736
-
737
- const
738
- scene = this .getExecutionContext (),
739
- transformNode = this .createTransform (xmlElement),
740
- bbox = new Box2 (Vector2 .min (... points), Vector2 .max (... points), true);
741
-
742
- this .groupNodes .push (transformNode);
743
-
744
851
  // Create nodes.
745
852
 
746
- const coordinateNode = scene .createNode ("Coordinate");
747
-
748
- coordinateNode .point .push (... points);
749
-
750
- if (this .style .fillType !== "none")
751
- {
752
- const
753
- shapeNode = scene .createNode ("Shape"),
754
- geometryNode = scene .createNode ("IndexedTriangleSet");
755
-
756
- shapeNode .appearance = this .createFillAppearance (bbox);
757
- shapeNode .geometry = geometryNode;
758
- geometryNode .solid = this .solid;
759
- geometryNode .index = this .triangulatePolygon ([points], coordinateNode);
760
- geometryNode .texCoord = this .createTextureCoordinate (coordinateNode, bbox);
761
- geometryNode .coord = coordinateNode;
762
-
763
- transformNode .children .push (shapeNode);
764
- }
765
-
766
- if (this .style .strokeType !== "none")
767
- {
768
- const
769
- shapeNode = scene .createNode ("Shape"),
770
- geometryNode = scene .createNode ("IndexedLineSet");
853
+ this .pathLikeElement (xmlElement, [points]);
854
+ },
855
+ polygonElement: function (xmlElement)
856
+ {
857
+ // Get points.
771
858
 
772
- shapeNode .appearance = this .createStrokeAppearance ();
773
- shapeNode .geometry = geometryNode;
774
- geometryNode .coordIndex = [... points .keys (), ... (closed ? [points [0]] : [ ]), -1];
775
- geometryNode .coord = coordinateNode;
859
+ const points = Object .assign ([ ], { closed: true });
776
860
 
777
- transformNode .children .push (shapeNode);
778
- }
861
+ if (!this .pointsAttribute (xmlElement .getAttribute ("points"), points))
862
+ return;
779
863
 
780
- this .groupNodes .pop ();
781
- this .styles .pop ();
864
+ // Create nodes.
782
865
 
783
- this .groupNodes .at (-1) .children .push (transformNode);
784
- },
785
- polygonElement: function (xmlElement)
786
- {
787
- this .polylineElement (xmlElement, true);
866
+ this .pathLikeElement (xmlElement, [points]);
788
867
  },
789
868
  pathElement: function (xmlElement)
790
869
  {
@@ -795,11 +874,41 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
795
874
  if (!this .dAttribute (xmlElement .getAttribute ("d"), contours))
796
875
  return;
797
876
 
877
+ // Create nodes.
878
+
879
+ this .pathLikeElement (xmlElement, contours);
880
+ },
881
+ pathLikeElement: function (xmlElement, contours)
882
+ {
798
883
  // Determine style.
799
884
 
800
885
  if (!this .styleAttributes (xmlElement))
801
886
  return;
802
887
 
888
+ // Filter consecutive equal points.
889
+
890
+ const EPSILON = 1e-9; // Min point distance.
891
+
892
+ contours = contours .map (function (points)
893
+ {
894
+ if (points .closed)
895
+ {
896
+ return Object .assign (points .filter ((p, i, a) => p .distance (a [(i + 1) % a .length]) > EPSILON),
897
+ {
898
+ closed: true,
899
+ });
900
+ }
901
+ else
902
+ {
903
+ return points .filter ((p, i, a) => !i || p .distance (a [i - 1]) > EPSILON);
904
+ }
905
+ })
906
+ .filter (points => points .length > 2);
907
+
908
+ // Add index property to points.
909
+
910
+ contours .forEach ((points, i, a) => points .index = i ? a [i - 1] .index + a [i - 1] .length : 0);
911
+
803
912
  // Create Transform node.
804
913
 
805
914
  const
@@ -823,79 +932,111 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
823
932
  {
824
933
  const
825
934
  shapeNode = scene .createNode ("Shape"),
826
- geometryNode = scene .createNode ("IndexedTriangleSet");
827
-
828
- shapeNode .appearance = this .createFillAppearance (bbox);
829
- shapeNode .geometry = geometryNode;
830
- geometryNode .solid = this .solid;
831
- geometryNode .index = this .triangulatePolygon (contours, coordinateNode);
832
- geometryNode .texCoord = this .createTextureCoordinate (coordinateNode, bbox);
833
- geometryNode .coord = coordinateNode;
935
+ geometryNode = this .fillGeometries .get (xmlElement);
834
936
 
835
937
  transformNode .children .push (shapeNode);
938
+ shapeNode .appearance = this .createFillAppearance (bbox);
939
+
940
+ if (geometryNode)
941
+ {
942
+ shapeNode .geometry = geometryNode;
943
+ }
944
+ else
945
+ {
946
+ const geometryNode = scene .createNode ("IndexedTriangleSet");
947
+
948
+ this .fillGeometries .set (xmlElement, geometryNode);
949
+
950
+ shapeNode .geometry = geometryNode;
951
+ geometryNode .solid = this .solid;
952
+ geometryNode .index = this .triangulatePolygon (contours, coordinateNode);
953
+ geometryNode .texCoord = this .createTextureCoordinate (coordinateNode, bbox);
954
+ geometryNode .coord = coordinateNode;
955
+ }
836
956
  }
837
957
 
838
958
  if (this .style .strokeType !== "none")
839
959
  {
840
960
  const
841
961
  shapeNode = scene .createNode ("Shape"),
842
- geometryNode = scene .createNode ("IndexedLineSet");
962
+ geometryNode = this .strokeGeometries .get (xmlElement);
843
963
 
964
+ transformNode .children .push (shapeNode);
844
965
  shapeNode .appearance = this .createStrokeAppearance ();
845
- shapeNode .geometry = geometryNode;
846
- geometryNode .coord = coordinateNode;
847
966
 
848
- for (const points of contours)
967
+ if (geometryNode)
968
+ {
969
+ shapeNode .geometry = geometryNode;
970
+ }
971
+ else
849
972
  {
850
- for (const i of points .keys ())
851
- geometryNode .coordIndex .push (points .index + i);
973
+ const geometryNode = scene .createNode ("IndexedLineSet");
852
974
 
853
- if (points .closed)
854
- geometryNode .coordIndex .push (points .index);
975
+ this .strokeGeometries .set (xmlElement, geometryNode);
855
976
 
856
- geometryNode .coordIndex .push (-1);
857
- }
977
+ shapeNode .geometry = geometryNode;
978
+ geometryNode .coord = coordinateNode;
858
979
 
859
- transformNode .children .push (shapeNode);
860
- }
980
+ // Create contour indices.
861
981
 
862
- this .groupNodes .pop ();
863
- this .styles .pop ();
982
+ const indices = geometryNode .coordIndex;
864
983
 
865
- this .groupNodes .at (-1) .children .push (transformNode);
984
+ for (const points of contours)
985
+ {
986
+ for (const i of points .keys ())
987
+ indices .push (points .index + i);
988
+
989
+ if (points .closed)
990
+ indices .push (points .index);
991
+
992
+ indices .push (-1);
993
+ }
994
+ }
995
+ }
996
+
997
+ this .popAll ();
866
998
  },
867
- linearGradientElementURL: function (xmlElement, bbox)
999
+ linearGradientElementUrl: function (xmlElement, bbox)
868
1000
  {
869
1001
  const
870
- g = this .linearGradientElement (xmlElement, { stops: [ ] }),
1002
+ g = this .linearGradientElement (xmlElement, bbox, { stops: [ ] }),
871
1003
  gradient = this .context .createLinearGradient (g .x1, g .y1, g .x2, g .y2);
872
1004
 
873
- for (const [o, c, a] of g .stops)
874
- gradient .addColorStop (o, `rgba(${c .r * 255},${c .g * 255},${c .b * 255},${a})`);
875
-
876
1005
  return this .drawGradient (gradient, g, bbox);
877
1006
  },
878
- linearGradientElement: function (xmlElement, gradient)
1007
+ linearGradientElement: function (xmlElement, bbox, gradient)
879
1008
  {
880
1009
  if (xmlElement .nodeName !== "linearGradient")
881
1010
  return;
882
1011
 
883
1012
  // Attribute xlink:href
884
1013
 
885
- const refElement = this .hrefAttribute (xmlElement .getAttribute ("xlink:href"));
1014
+ const refElement = this .hrefAttribute (xmlElement .getAttribute ("href") || xmlElement .getAttribute ("xlink:href"));
886
1015
 
887
1016
  if (refElement)
888
- this .gradientElement (refElement, gradient);
1017
+ this .gradientElement (refElement, bbox, gradient);
889
1018
 
890
1019
  // Attributes
891
1020
 
892
- gradient .x1 = this .lengthAttribute (xmlElement .getAttribute ("x1"), gradient .x1 || 0);
893
- gradient .y1 = this .lengthAttribute (xmlElement .getAttribute ("y1"), gradient .y1 || 0);
894
- gradient .x2 = this .lengthAttribute (xmlElement .getAttribute ("x2"), gradient .x2 || 1);
895
- gradient .y2 = this .lengthAttribute (xmlElement .getAttribute ("y2"), gradient .y2 || 0);
1021
+ gradient .x1 = this .lengthAttribute (xmlElement .getAttribute ("x1"), gradient .x1 || 0, "width");
1022
+ gradient .y1 = this .lengthAttribute (xmlElement .getAttribute ("y1"), gradient .y1 || 0, "height");
1023
+ gradient .x2 = this .lengthAttribute (xmlElement .getAttribute ("x2"), gradient .x2 || 1, "width");
1024
+ gradient .y2 = this .lengthAttribute (xmlElement .getAttribute ("y2"), gradient .y2 || 0, "height");
896
1025
  gradient .units = xmlElement .getAttribute ("gradientUnits") || "objectBoundingBox";
897
1026
  gradient .transform = this .transformAttribute (xmlElement .getAttribute ("gradientTransform"));
898
1027
 
1028
+ // Spread matrix
1029
+
1030
+ const
1031
+ s = new Matrix3 (),
1032
+ c = new Vector2 (gradient .x1, gradient .y1);
1033
+
1034
+ s .translate (c);
1035
+ s .scale (new Vector2 (SPREAD, SPREAD));
1036
+ s .translate (c .negate ());
1037
+
1038
+ gradient .spreadMatrix = s;
1039
+
899
1040
  // Stops
900
1041
 
901
1042
  for (const childNode of xmlElement .childNodes)
@@ -903,38 +1044,47 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
903
1044
 
904
1045
  return gradient;
905
1046
  },
906
- radialGradientElementURL: function (xmlElement, bbox)
1047
+ radialGradientElementUrl: function (xmlElement, bbox)
907
1048
  {
908
1049
  const
909
- g = this .radialGradientElement (xmlElement, { stops: [ ] }),
1050
+ g = this .radialGradientElement (xmlElement, bbox, { stops: [ ] }),
910
1051
  gradient = this .context .createRadialGradient (g .fx, g .fy, g. fr, g .cx, g .cy, g .r);
911
1052
 
912
- for (const [o, c, a] of g .stops)
913
- gradient .addColorStop (o, `rgba(${c .r * 255},${c .g * 255},${c .b * 255},${a})`);
914
-
915
1053
  return this .drawGradient (gradient, g, bbox);
916
1054
  },
917
- radialGradientElement: function (xmlElement, gradient)
1055
+ radialGradientElement: function (xmlElement, bbox, gradient)
918
1056
  {
919
1057
  // Attribute xlink:href
920
1058
 
921
- const refElement = this .hrefAttribute (xmlElement .getAttribute ("xlink:href"));
1059
+ const refElement = this .hrefAttribute (xmlElement .getAttribute ("href") || xmlElement .getAttribute ("xlink:href"));
922
1060
 
923
1061
  if (refElement)
924
- this .gradientElement (refElement, gradient);
1062
+ this .gradientElement (refElement, bbox, gradient);
925
1063
 
926
1064
  // Attributes
927
1065
 
928
- gradient .cx = this .lengthAttribute (xmlElement .getAttribute ("cx"), gradient .cx || 0.5),
929
- gradient .cy = this .lengthAttribute (xmlElement .getAttribute ("cy"), gradient .cy || 0.5),
1066
+ gradient .cx = this .lengthAttribute (xmlElement .getAttribute ("cx"), gradient .cx || 0.5, "width"),
1067
+ gradient .cy = this .lengthAttribute (xmlElement .getAttribute ("cy"), gradient .cy || 0.5, "height"),
930
1068
  gradient .r = this .lengthAttribute (xmlElement .getAttribute ("r"), gradient .r || 0.5),
931
- gradient .fx = this .lengthAttribute (xmlElement .getAttribute ("fx"), gradient .fx || gradient .cx),
932
- gradient .fy = this .lengthAttribute (xmlElement .getAttribute ("fy"), gradient .fy || gradient .cy),
1069
+ gradient .fx = this .lengthAttribute (xmlElement .getAttribute ("fx"), gradient .fx || gradient .cx, "width"),
1070
+ gradient .fy = this .lengthAttribute (xmlElement .getAttribute ("fy"), gradient .fy || gradient .cy, "height"),
933
1071
  gradient .fr = this .lengthAttribute (xmlElement .getAttribute ("fr"), gradient .fr || 0),
934
1072
  gradient .units = xmlElement .getAttribute ("gradientUnits") || "objectBoundingBox";
935
1073
  gradient .spreadMethod = xmlElement .getAttribute ("spreadMethod");
936
1074
  gradient .transform = this .transformAttribute (xmlElement .getAttribute ("gradientTransform"));
937
1075
 
1076
+ // Spread matrix
1077
+
1078
+ const
1079
+ s = new Matrix3 (),
1080
+ c = new Vector2 (gradient .fx, gradient .fy);
1081
+
1082
+ s .translate (c);
1083
+ s .scale (new Vector2 (SPREAD, SPREAD));
1084
+ s .translate (c .negate ());
1085
+
1086
+ gradient .spreadMatrix = s;
1087
+
938
1088
  // Stops
939
1089
 
940
1090
  for (const childNode of xmlElement .childNodes)
@@ -942,7 +1092,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
942
1092
 
943
1093
  return gradient;
944
1094
  },
945
- gradientElement: function (xmlElement, gradient)
1095
+ gradientElement: function (xmlElement, bbox, gradient)
946
1096
  {
947
1097
  if (!xmlElement)
948
1098
  return;
@@ -950,9 +1100,9 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
950
1100
  switch (xmlElement .nodeName)
951
1101
  {
952
1102
  case "linearGradient":
953
- return this .linearGradientElement (xmlElement, gradient);
1103
+ return this .linearGradientElement (xmlElement, bbox, gradient);
954
1104
  case "radialGradient":
955
- return this .radialGradientElement (xmlElement, gradient);
1105
+ return this .radialGradientElement (xmlElement, bbox, gradient);
956
1106
  }
957
1107
  },
958
1108
  gradientChild: function (xmlElement, gradient)
@@ -967,6 +1117,9 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
967
1117
 
968
1118
  const offset = this .percentAttribute (xmlElement .getAttribute ("offset"), 0);
969
1119
 
1120
+ if (offset < 0 || offset > 1)
1121
+ return;
1122
+
970
1123
  const { stopColor, stopOpacity } = this .style;
971
1124
 
972
1125
  gradient .stops .push ([offset, stopColor, stopOpacity]);
@@ -975,9 +1128,50 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
975
1128
  },
976
1129
  drawGradient: function (gradient, g, bbox)
977
1130
  {
1131
+ // Add color stops.
1132
+
1133
+ switch (g .spreadMethod)
1134
+ {
1135
+ default: // pad
1136
+ {
1137
+ g .spreadMatrix .identity ();
1138
+
1139
+ for (const [o, c, a] of g .stops)
1140
+ gradient .addColorStop (o, this .cssColor (c, a));
1141
+
1142
+ break;
1143
+ }
1144
+ case "repeat":
1145
+ {
1146
+ for (let i = 0; i < SPREAD; ++ i)
1147
+ {
1148
+ const s = i / SPREAD;
1149
+
1150
+ for (const [o, c, a] of g .stops)
1151
+ gradient .addColorStop (s + o / SPREAD, this .cssColor (c, a));
1152
+ }
1153
+
1154
+ break;
1155
+ }
1156
+ case "reflect":
1157
+ {
1158
+ for (let i = 0; i < SPREAD; ++ i)
1159
+ {
1160
+ const s = i / SPREAD;
1161
+
1162
+ for (const [o, c, a] of g .stops)
1163
+ gradient .addColorStop (s + (i % 2 ? 1 - o : o) / SPREAD, this .cssColor (c, a));
1164
+ }
1165
+
1166
+ break;
1167
+ }
1168
+ }
1169
+
1170
+ // Create Matrix.
1171
+
978
1172
  const m = new Matrix3 ();
979
1173
 
980
- m .scale (new Vector2 (GRADIENT_SIZE / 2, GRADIENT_SIZE / 2));
1174
+ m .scale (new Vector2 (this .GRADIENT_SIZE / 2, this .GRADIENT_SIZE / 2));
981
1175
  m .translate (Vector2 .One);
982
1176
  m .scale (new Vector2 (1, -1));
983
1177
 
@@ -987,6 +1181,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
987
1181
  m .multLeft (new Matrix3 (2, 0, 0, 0, 2, 0, -1, -1, 1));
988
1182
 
989
1183
  m .multLeft (g .transform);
1184
+ m .multLeft (g .spreadMatrix);
990
1185
 
991
1186
  // Paint.
992
1187
 
@@ -994,8 +1189,8 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
994
1189
 
995
1190
  cx .fillStyle = gradient;
996
1191
  cx .save ();
997
- cx .clearRect (0, 0, GRADIENT_SIZE, GRADIENT_SIZE);
998
- cx .rect (0, 0, GRADIENT_SIZE, GRADIENT_SIZE);
1192
+ cx .clearRect (0, 0, this .GRADIENT_SIZE, this .GRADIENT_SIZE);
1193
+ cx .rect (0, 0, this .GRADIENT_SIZE, this .GRADIENT_SIZE);
999
1194
  cx .transform (m [0], m [1], m [3], m [4], m [6], m [7]);
1000
1195
  cx .fill ();
1001
1196
  cx .restore ();
@@ -1003,6 +1198,10 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1003
1198
  // Use PNG because image can have alpha channel.
1004
1199
  return this .canvas .toDataURL ("image/png");
1005
1200
  },
1201
+ patternUrl: function (xmlElement)
1202
+ {
1203
+ //console .debug ("pattern");
1204
+ },
1006
1205
  idAttribute: function (attribute, node)
1007
1206
  {
1008
1207
  if (attribute === null)
@@ -1057,7 +1256,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1057
1256
 
1058
1257
  return this .document .getElementById (hash);
1059
1258
  },
1060
- lengthAttribute: function (attribute, defaultValue)
1259
+ lengthAttribute: function (attribute, defaultValue, percent)
1061
1260
  {
1062
1261
  // Returns length in pixel.
1063
1262
 
@@ -1100,6 +1299,23 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1100
1299
  case "pc":
1101
1300
  value *= PICA / PIXEL;
1102
1301
  break;
1302
+ case "%":
1303
+ {
1304
+ switch (percent)
1305
+ {
1306
+ case "width":
1307
+ value *= this .viewBox [2] / 100;
1308
+ break;
1309
+ case "height":
1310
+ value *= this .viewBox [3] / 100;
1311
+ break;
1312
+ default:
1313
+ value *= Math .hypot (this .viewBox [2], this .viewBox [3]) / 100;
1314
+ break;
1315
+ }
1316
+
1317
+ break;
1318
+ }
1103
1319
  }
1104
1320
  }
1105
1321
 
@@ -1167,7 +1383,6 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1167
1383
 
1168
1384
  let
1169
1385
  points = [ ],
1170
- index = 0,
1171
1386
  previous = "",
1172
1387
  command = "",
1173
1388
  relative = false,
@@ -1195,11 +1410,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1195
1410
  // moveto
1196
1411
 
1197
1412
  if (points .length > 2)
1198
- {
1199
- contours .push (Object .assign (points, { index: index, closed: false }));
1200
-
1201
- index += points .length;
1202
- }
1413
+ contours .push (Object .assign (points, { closed: false }));
1203
1414
 
1204
1415
  points = [ ];
1205
1416
 
@@ -1365,7 +1576,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1365
1576
  y += ay;
1366
1577
  }
1367
1578
 
1368
- Bezier .quadric (ax, ay, 0, x1, y1, 0, x, y, 0, BEZIER_STEPS, points);
1579
+ Bezier .quadric (ax, ay, 0, x1, y1, 0, x, y, 0, this .BEZIER_STEPS, points);
1369
1580
 
1370
1581
  ax = x;
1371
1582
  ay = y;
@@ -1426,7 +1637,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1426
1637
  }
1427
1638
  }
1428
1639
 
1429
- Bezier .quadric (ax, ay, 0, x1, y1, 0, x, y, 0, BEZIER_STEPS, points);
1640
+ Bezier .quadric (ax, ay, 0, x1, y1, 0, x, y, 0, this .BEZIER_STEPS, points);
1430
1641
 
1431
1642
  ax = x;
1432
1643
  ay = y;
@@ -1492,7 +1703,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1492
1703
  y += ay;
1493
1704
  }
1494
1705
 
1495
- Bezier .cubic (ax, ay, 0, x1, y1, 0, x2, y2, 0, x, y, 0, BEZIER_STEPS, points);
1706
+ Bezier .cubic (ax, ay, 0, x1, y1, 0, x2, y2, 0, x, y, 0, this .BEZIER_STEPS, points);
1496
1707
 
1497
1708
  ax = x;
1498
1709
  ay = y;
@@ -1569,7 +1780,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1569
1780
  }
1570
1781
  }
1571
1782
 
1572
- Bezier .cubic (ax, ay, 0, x1, y1, 0, x2, y2, 0, x, y, 0, BEZIER_STEPS, points);
1783
+ Bezier .cubic (ax, ay, 0, x1, y1, 0, x2, y2, 0, x, y, 0, this .BEZIER_STEPS, points);
1573
1784
 
1574
1785
  ax = x;
1575
1786
  ay = y;
@@ -1641,7 +1852,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1641
1852
  y += ay;
1642
1853
  }
1643
1854
 
1644
- Bezier .arc (ax, ay, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y, CIRCLE_STEPS, points);
1855
+ Bezier .arc (ax, ay, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y, this .CIRCLE_STEPS, points);
1645
1856
 
1646
1857
  ax = x;
1647
1858
  ay = y;
@@ -1671,9 +1882,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1671
1882
  ax = points [0] .x;
1672
1883
  ay = points [0] .y;
1673
1884
 
1674
- contours .push (Object .assign (points, { index: index, closed: true }));
1675
-
1676
- index += points .length;
1885
+ contours .push (Object .assign (points, { closed: true }));
1677
1886
  }
1678
1887
 
1679
1888
  points = [ ];
@@ -1687,7 +1896,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1687
1896
  }
1688
1897
 
1689
1898
  if (points .length > 2)
1690
- contours .push (Object .assign (points, { index: index, closed: false }));
1899
+ contours .push (Object .assign (points, { closed: false }));
1691
1900
 
1692
1901
  return !! contours .length;
1693
1902
  },
@@ -1923,45 +2132,24 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1923
2132
 
1924
2133
  return matrix;
1925
2134
  },
1926
- styleAttributes: (function ()
1927
- {
1928
- const Styles = [
1929
- "display",
1930
- "fill",
1931
- "fill-opacity",
1932
- "fill-rule",
1933
- "stroke",
1934
- "stroke-opacity",
1935
- "stroke-width",
1936
- "opacity",
1937
- "stop-color",
1938
- "stop-opacity",
1939
- "vector-effect",
1940
- ];
1941
-
1942
- return function (xmlElement)
1943
- {
1944
- const style = Object .assign ({ }, this .styles [0]);
1945
-
1946
- if (this .style .display === "none")
1947
- return false;
2135
+ styleAttributes: function (xmlElement)
2136
+ {
2137
+ const style = Object .assign ({ }, this .styles .at (-1));
1948
2138
 
1949
- this .styles .push (style);
2139
+ if (this .style .display === "none")
2140
+ return false;
1950
2141
 
1951
- for (const style of Styles)
1952
- {
1953
- const attribute = xmlElement .getAttribute (style);
2142
+ this .styles .push (style);
1954
2143
 
1955
- this .parseStyle (style, attribute ?? "default");
1956
- }
2144
+ for (const attribute of xmlElement .attributes)
2145
+ this .parseStyle (attribute .name, attribute .value)
1957
2146
 
1958
- // Style attribute has higher precedence.
2147
+ // Style attribute has higher precedence.
1959
2148
 
1960
- this .styleAttribute (xmlElement .getAttribute ("style"));
2149
+ this .styleAttribute (xmlElement .getAttribute ("style"));
1961
2150
 
1962
- return true;
1963
- };
1964
- })(),
2151
+ return true;
2152
+ },
1965
2153
  styleAttribute: function (attribute)
1966
2154
  {
1967
2155
  if (attribute === null)
@@ -1981,6 +2169,9 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
1981
2169
  },
1982
2170
  parseStyle: function (style, value)
1983
2171
  {
2172
+ if (value === "inherit" || value == "unset")
2173
+ return;
2174
+
1984
2175
  this .parseValue (value);
1985
2176
 
1986
2177
  switch (style)
@@ -2022,15 +2213,9 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2022
2213
  },
2023
2214
  displayStyle: function (value)
2024
2215
  {
2025
- if (value === null)
2026
- return;
2027
-
2028
2216
  if (value === "default")
2029
- return;
2030
-
2031
- if (value === "inherit")
2032
2217
  {
2033
- this .style .display = styles .at (-1) .display;
2218
+ this .style .display = "inline";
2034
2219
  return;
2035
2220
  }
2036
2221
 
@@ -2038,10 +2223,11 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2038
2223
  },
2039
2224
  fillStyle: function (value)
2040
2225
  {
2041
- if (this .urlValue ())
2226
+ if (value === "default")
2042
2227
  {
2043
- this .style .fillType = "URL";
2044
- this .style .fillURL = this .result [1] .trim ();
2228
+ this .style .fillType = this .styles [0] .fillType;
2229
+ this .style .fillColor = this .styles [0] .fillColor;
2230
+ this .style .fillURL = this .styles [0] .fillURL;
2045
2231
  return;
2046
2232
  }
2047
2233
 
@@ -2053,31 +2239,29 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2053
2239
 
2054
2240
  if (value === "none")
2055
2241
  {
2056
- this .style .fillType ="none";
2242
+ this .style .fillType = "none";
2057
2243
  return;
2058
2244
  }
2059
2245
 
2060
- if (!value .match (/^(?:inherit|unset|default)$/))
2246
+ if (this .urlValue ())
2061
2247
  {
2062
- if (this .colorValue ())
2063
- {
2064
- this .style .fillType = "COLOR";
2065
- this .style .fillColor = this .value .copy ();
2066
- return;
2067
- }
2248
+ this .style .fillType = "URL";
2249
+ this .style .fillURL = this .result [1] .trim ();
2250
+ return;
2068
2251
  }
2069
2252
 
2070
- // inherit
2071
-
2072
- this .style .fillType = this .styles .at (-1) .fillType;
2073
- this .style .fillColor = this .styles .at (-1) .fillColor;
2074
- this .style .fillURL = this .styles .at (-1) .fillURL;
2253
+ if (this .colorValue (this .styles .at (-1) .fillColor))
2254
+ {
2255
+ this .style .fillType = "COLOR";
2256
+ this .style .fillColor = this .value .copy ();
2257
+ return;
2258
+ }
2075
2259
  },
2076
2260
  fillOpacityStyle: function (value)
2077
2261
  {
2078
- if (this .double ())
2262
+ if (value === "default")
2079
2263
  {
2080
- this .style .fillOpacity = Algorithm .clamp (this .value, 0, 1);
2264
+ this .style .fillOpacity = this .styles [0] .fillOpacity;
2081
2265
  return;
2082
2266
  }
2083
2267
 
@@ -2087,20 +2271,29 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2087
2271
  return;
2088
2272
  }
2089
2273
 
2090
- // inherit
2091
-
2092
- this .style .fillOpacity = this .styles .at (-1) .fillOpacity;
2274
+ if (this .double ())
2275
+ {
2276
+ this .style .fillOpacity = Algorithm .clamp (this .value, 0, 1);
2277
+ return;
2278
+ }
2093
2279
  },
2094
2280
  fillRuleStyle: function (value)
2095
2281
  {
2282
+ if (value === "default")
2283
+ {
2284
+ this .style .fillRule = this .styles [0] .fillRule;
2285
+ return;
2286
+ }
2287
+
2096
2288
  this .style .fillRule = value;
2097
2289
  },
2098
2290
  strokeStyle: function (value)
2099
2291
  {
2100
- if (this .urlValue ())
2292
+ if (value === "default")
2101
2293
  {
2102
- this .style .strokeType = "URL";
2103
- this .style .strokeURL = this .result [1] .trim ();
2294
+ this .style .strokeType = this .styles [0] .strokeType;
2295
+ this .style .strokeColor = this .styles [0] .strokeColor;
2296
+ this .style .strokeURL = this .styles [0] .strokeURL;
2104
2297
  return;
2105
2298
  }
2106
2299
 
@@ -2112,31 +2305,29 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2112
2305
 
2113
2306
  if (value === "none")
2114
2307
  {
2115
- this .style .strokeType ="none";
2308
+ this .style .strokeType = "none";
2116
2309
  return;
2117
2310
  }
2118
2311
 
2119
- if (!value .match (/^(?:inherit|unset|default)$/))
2312
+ if (this .urlValue ())
2120
2313
  {
2121
- if (this .colorValue ())
2122
- {
2123
- this .style .strokeType = "COLOR";
2124
- this .style .strokeColor = this .value .copy ();
2125
- return;
2126
- }
2314
+ this .style .strokeType = "URL";
2315
+ this .style .strokeURL = this .result [1] .trim ();
2316
+ return;
2127
2317
  }
2128
2318
 
2129
- // inherit
2130
-
2131
- this .style .strokeType = this .styles .at (-1) .strokeType;
2132
- this .style .strokeColor = this .styles .at (-1) .strokeColor;
2133
- this .style .strokeURL = this .styles .at (-1) .strokeURL;
2319
+ if (this .colorValue (this .styles .at (-1) .strokeColor))
2320
+ {
2321
+ this .style .strokeType = "COLOR";
2322
+ this .style .strokeColor = this .value .copy ();
2323
+ return;
2324
+ }
2134
2325
  },
2135
2326
  strokeOpacityStyle: function (value)
2136
2327
  {
2137
- if (this .double ())
2328
+ if (value === "default")
2138
2329
  {
2139
- this .style .strokeOpacity = Algorithm .clamp (this .value, 0, 1);
2330
+ this .style .strokeOpacity = this .styles [0] .strokeOpacity;
2140
2331
  return;
2141
2332
  }
2142
2333
 
@@ -2146,15 +2337,17 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2146
2337
  return;
2147
2338
  }
2148
2339
 
2149
- // inherit
2150
-
2151
- this .style .strokeOpacity = this .styles .at (-1) .strokeOpacity;
2340
+ if (this .double ())
2341
+ {
2342
+ this .style .strokeOpacity = Algorithm .clamp (this .value, 0, 1);
2343
+ return;
2344
+ }
2152
2345
  },
2153
2346
  strokeWidthStyle: function (value)
2154
2347
  {
2155
- if (this .double ())
2348
+ if (value === "default")
2156
2349
  {
2157
- this .style .strokeWidth = this .lengthAttribute (this .value);
2350
+ this .style .strokeWidth = this .styles [0] .strokeWidth;
2158
2351
  return;
2159
2352
  }
2160
2353
 
@@ -2164,15 +2357,17 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2164
2357
  return;
2165
2358
  }
2166
2359
 
2167
- // inherit
2168
-
2169
- this .style .strokeWidth = this .styles .at (-1) .strokeWidth;
2360
+ if (this .double ())
2361
+ {
2362
+ this .style .strokeWidth = this .lengthAttribute (this .value, 1);
2363
+ return;
2364
+ }
2170
2365
  },
2171
2366
  opacityStyle: function (value)
2172
2367
  {
2173
- if (this .double ())
2368
+ if (value === "default")
2174
2369
  {
2175
- this .style .opacity = Algorithm .clamp (this .value, 0, 1) * this .styles .at (-1) .opacity;
2370
+ this .style .opacity = this .styles [0] .opacity;
2176
2371
  return;
2177
2372
  }
2178
2373
 
@@ -2181,10 +2376,22 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2181
2376
  this .style .opacity = 0;
2182
2377
  return;
2183
2378
  }
2379
+
2380
+ if (this .double ())
2381
+ {
2382
+ this .style .opacity = Algorithm .clamp (this .value, 0, 1) * this .styles .at (-1) .opacity;
2383
+ return;
2384
+ }
2184
2385
  },
2185
2386
  stopColorStyle: function (value)
2186
2387
  {
2187
- if (this .colorValue ())
2388
+ if (value === "default")
2389
+ {
2390
+ this .style .stopColor = this .styles [0] .stopColor;
2391
+ return;
2392
+ }
2393
+
2394
+ if (this .colorValue (Color4 .Black))
2188
2395
  {
2189
2396
  this .style .stopColor = this .value .copy ();
2190
2397
  return;
@@ -2192,9 +2399,9 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2192
2399
  },
2193
2400
  stopOpacityStyle: function (value)
2194
2401
  {
2195
- if (this .double ())
2402
+ if (value === "default")
2196
2403
  {
2197
- this .style .stopOpacity = Algorithm .clamp (this .value, 0, 1);
2404
+ this .style .stopOpacity = this .styles [0] .stopOpacity;
2198
2405
  return;
2199
2406
  }
2200
2407
 
@@ -2203,18 +2410,22 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2203
2410
  this .style .stopOpacity = 0;
2204
2411
  return;
2205
2412
  }
2413
+
2414
+ if (this .double ())
2415
+ {
2416
+ this .style .stopOpacity = Algorithm .clamp (this .value, 0, 1);
2417
+ return;
2418
+ }
2206
2419
  },
2207
2420
  vectorEffectStyle: function (value)
2208
2421
  {
2209
- if (value !== "inherit")
2422
+ if (value === "default")
2210
2423
  {
2211
- this .style .vectorEffect = value;
2424
+ this .style .vectorEffect = this .styles [0] .vectorEffect;
2212
2425
  return;
2213
2426
  }
2214
2427
 
2215
- // inherit
2216
-
2217
- this .style .vectorEffect = this .styles .at (-1) .vectorEffect;
2428
+ this .style .vectorEffect = value;
2218
2429
  },
2219
2430
  parseValue: function (value)
2220
2431
  {
@@ -2270,12 +2481,14 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2270
2481
  {
2271
2482
  const color = new Color4 (0, 0, 0, 0);
2272
2483
 
2273
- return function ()
2484
+ return function (c)
2274
2485
  {
2275
2486
  if (!Grammar .color .parse (this))
2276
2487
  return false;
2277
2488
 
2278
- this .value = color .set (... this .convertColor (this .result [1]));
2489
+ const defaultColor = this .cssColor (c);
2490
+
2491
+ this .value = color .set (... this .convertColor (this .result [1], defaultColor));
2279
2492
 
2280
2493
  return true;
2281
2494
  };
@@ -2284,13 +2497,20 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2284
2497
  {
2285
2498
  return Grammar .url .parse (this);
2286
2499
  },
2500
+ cssColor: function (c, a = c .a)
2501
+ {
2502
+ return `rgba(${c .r * 255},${c .g * 255},${c .b * 255},${a})`;
2503
+ },
2287
2504
  createTransform: function (xmlElement, t = Vector2 .Zero, s = Vector2 .One)
2288
2505
  {
2289
2506
  // Determine matrix.
2290
2507
 
2291
2508
  const
2292
- scene = this .getExecutionContext (),
2293
- m = this .transformAttribute (xmlElement .getAttribute ("transform"));
2509
+ scene = this .getExecutionContext (),
2510
+ m = this .transformAttribute (xmlElement .getAttribute ("transform"));
2511
+
2512
+ this .modelMatrix .push ();
2513
+ this .modelMatrix .multLeft (Matrix4 .Matrix3 (m));
2294
2514
 
2295
2515
  m .translate (t);
2296
2516
  m .scale (s);
@@ -2299,7 +2519,7 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2299
2519
 
2300
2520
  const
2301
2521
  transformNode = scene .createNode ("Transform"),
2302
- matrix = new Matrix4 (m [0], m [1], 0, 0, m [3], m [4], 0, 0, 0, 0, 1, 0, m [6], m [7], 0, 1),
2522
+ matrix = Matrix4 .Matrix3 (m),
2303
2523
  translation = new Vector3 (0, 0, 0),
2304
2524
  rotation = new Rotation4 (),
2305
2525
  scale = new Vector3 (1, 1, 1),
@@ -2316,12 +2536,18 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2316
2536
 
2317
2537
  this .idAttribute (xmlElement .getAttribute ("id"), transformNode);
2318
2538
 
2319
- // Nodes
2539
+ // Add node to parent.
2320
2540
 
2321
- this .nodes .set (xmlElement, transformNode);
2541
+ this .groupNodes .at (-1) .children .push (transformNode);
2322
2542
 
2323
2543
  return transformNode;
2324
2544
  },
2545
+ popAll: function ()
2546
+ {
2547
+ this .groupNodes .pop ();
2548
+ this .modelMatrix .pop ();
2549
+ this .styles .pop ();
2550
+ },
2325
2551
  createFillAppearance: function (bbox)
2326
2552
  {
2327
2553
  const
@@ -2378,10 +2604,13 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2378
2604
  switch (xmlElement .nodeName)
2379
2605
  {
2380
2606
  case "linearGradient":
2381
- return this .linearGradientElementURL (xmlElement, bbox);
2607
+ return this .linearGradientElementUrl (xmlElement, bbox);
2382
2608
 
2383
2609
  case "radialGradient":
2384
- return this .radialGradientElementURL (xmlElement, bbox);
2610
+ return this .radialGradientElementUrl (xmlElement, bbox);
2611
+
2612
+ case "pattern":
2613
+ return this .patternUrl (xmlElement);
2385
2614
  }
2386
2615
  },
2387
2616
  createStrokeAppearance: function ()
@@ -2400,23 +2629,39 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2400
2629
  : this .getStokeWidth ();
2401
2630
 
2402
2631
  if (strokeWidth > 1)
2403
- {
2404
- const lineProperties = scene .createNode ("LineProperties");
2405
-
2406
- appearanceNode .lineProperties = lineProperties;
2407
- lineProperties .linewidthScaleFactor = strokeWidth;
2408
- }
2632
+ appearanceNode .lineProperties = this .getLineProperties (strokeWidth);
2409
2633
 
2410
2634
  return appearanceNode;
2411
2635
  },
2412
2636
  getStokeWidth: function ()
2413
2637
  {
2414
2638
  const
2415
- modelMatrix = this .getModelMatrix (),
2639
+ modelMatrix = this .modelMatrix .get (),
2416
2640
  strokeWidth = modelMatrix .multDirMatrix (new Vector3 (this .style .strokeWidth, this .style .strokeWidth, 0));
2417
2641
 
2418
2642
  return (strokeWidth .x + strokeWidth .y) / 2;
2419
2643
  },
2644
+ getLineProperties: function (strokeWidth)
2645
+ {
2646
+ const lineProperties = this .lineProperties .get (strokeWidth);
2647
+
2648
+ if (lineProperties)
2649
+ {
2650
+ return lineProperties;
2651
+ }
2652
+ else
2653
+ {
2654
+ const
2655
+ scene = this .getExecutionContext (),
2656
+ lineProperties = scene .createNode ("LineProperties");
2657
+
2658
+ lineProperties .linewidthScaleFactor = strokeWidth;
2659
+
2660
+ this .lineProperties .set (strokeWidth, lineProperties);
2661
+
2662
+ return lineProperties;
2663
+ }
2664
+ },
2420
2665
  createTextureProperties: function ()
2421
2666
  {
2422
2667
  const
@@ -2445,26 +2690,6 @@ SVGParser .prototype = Object .assign (Object .create (X3DParser .prototype),
2445
2690
 
2446
2691
  return texCoordNode;
2447
2692
  },
2448
- getModelMatrix: function ()
2449
- {
2450
- const modelMatrix = new Matrix4 ();
2451
-
2452
- for (let i = 1; i < this .groupNodes .length; ++ i)
2453
- {
2454
- const
2455
- node = this .groupNodes [i],
2456
- matrix = new Matrix4 ();
2457
-
2458
- matrix .set (node .translation .getValue (),
2459
- node .rotation .getValue (),
2460
- node .scale .getValue (),
2461
- node .scaleOrientation .getValue ());
2462
-
2463
- modelMatrix .multLeft (matrix);
2464
- }
2465
-
2466
- return modelMatrix;
2467
- },
2468
2693
  createTesselator: function ()
2469
2694
  {
2470
2695
  // Function called for each vertex of tessellator output.