kitchen-simulator
Version:
It is a kitchen simulator (self-contained micro-frontend).
1,326 lines (1,253 loc) • 53.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SVGLoader = void 0;
var _three = require("three");
var SVGLoader = exports.SVGLoader = function SVGLoader(manager) {
_three.Loader.call(this, manager);
// Default dots per inch
this.defaultDPI = 90;
// Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px'
this.defaultUnit = 'px';
};
SVGLoader.prototype = Object.assign(Object.create(_three.Loader.prototype), {
constructor: SVGLoader,
load: function load(url, onLoad, onProgress, onError) {
var scope = this;
var loader = new _three.FileLoader(scope.manager);
loader.setPath(scope.path);
loader.setRequestHeader(scope.requestHeader);
loader.setWithCredentials(scope.withCredentials);
loader.load(url, function (text) {
try {
onLoad(scope.parse(text));
} catch (e) {
if (onError) {
onError(e);
} else {
console.error(e);
}
scope.manager.itemError(url);
}
}, onProgress, onError);
},
parse: function parse(text) {
var scope = this;
function parseNode(node, style) {
if (node.nodeType !== 1) return;
var transform = getNodeTransform(node);
var traverseChildNodes = true;
var path = null;
switch (node.nodeName) {
case 'svg':
break;
case 'style':
parseCSSStylesheet(node);
break;
case 'g':
style = parseStyle(node, style);
break;
case 'path':
style = parseStyle(node, style);
if (node.hasAttribute('d')) path = parsePathNode(node);
break;
case 'rect':
style = parseStyle(node, style);
path = parseRectNode(node);
break;
case 'polygon':
style = parseStyle(node, style);
path = parsePolygonNode(node);
break;
case 'polyline':
style = parseStyle(node, style);
path = parsePolylineNode(node);
break;
case 'circle':
style = parseStyle(node, style);
path = parseCircleNode(node);
break;
case 'ellipse':
style = parseStyle(node, style);
path = parseEllipseNode(node);
break;
case 'line':
style = parseStyle(node, style);
path = parseLineNode(node);
break;
case 'defs':
traverseChildNodes = false;
break;
case 'use':
style = parseStyle(node, style);
var usedNodeId = node.href.baseVal.substring(1);
var usedNode = node.viewportElement.getElementById(usedNodeId);
if (usedNode) {
parseNode(usedNode, style);
} else {
console.warn("SVGLoader: 'use node' references non-existent node id: " + usedNodeId);
}
break;
default:
// console.log( node );
}
if (path) {
if (style.fill !== undefined && style.fill !== 'none') {
path.color.setStyle(style.fill);
}
transformPath(path, currentTransform);
paths.push(path);
path.userData = {
node: node,
style: style
};
}
if (traverseChildNodes) {
var nodes = node.childNodes;
for (var i = 0; i < nodes.length; i++) {
parseNode(nodes[i], style);
}
}
if (transform) {
transformStack.pop();
if (transformStack.length > 0) {
currentTransform.copy(transformStack[transformStack.length - 1]);
} else {
currentTransform.identity();
}
}
}
function parsePathNode(node) {
var path = new _three.ShapePath();
var point = new _three.Vector2();
var control = new _three.Vector2();
var firstPoint = new _three.Vector2();
var isFirstPoint = true;
var doSetFirstPoint = false;
var d = node.getAttribute('d');
// console.log( d );
var commands = d.match(/[a-df-z][^a-df-z]*/gi);
for (var i = 0, l = commands.length; i < l; i++) {
var command = commands[i];
var type = command.charAt(0);
var data = command.substr(1).trim();
if (isFirstPoint === true) {
doSetFirstPoint = true;
isFirstPoint = false;
}
switch (type) {
case 'M':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2) {
point.x = numbers[j + 0];
point.y = numbers[j + 1];
control.x = point.x;
control.y = point.y;
if (j === 0) {
path.moveTo(point.x, point.y);
} else {
path.lineTo(point.x, point.y);
}
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'H':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j++) {
point.x = numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'V':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j++) {
point.y = numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'L':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2) {
point.x = numbers[j + 0];
point.y = numbers[j + 1];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'C':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 6) {
path.bezierCurveTo(numbers[j + 0], numbers[j + 1], numbers[j + 2], numbers[j + 3], numbers[j + 4], numbers[j + 5]);
control.x = numbers[j + 2];
control.y = numbers[j + 3];
point.x = numbers[j + 4];
point.y = numbers[j + 5];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'S':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 4) {
path.bezierCurveTo(getReflection(point.x, control.x), getReflection(point.y, control.y), numbers[j + 0], numbers[j + 1], numbers[j + 2], numbers[j + 3]);
control.x = numbers[j + 0];
control.y = numbers[j + 1];
point.x = numbers[j + 2];
point.y = numbers[j + 3];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'Q':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 4) {
path.quadraticCurveTo(numbers[j + 0], numbers[j + 1], numbers[j + 2], numbers[j + 3]);
control.x = numbers[j + 0];
control.y = numbers[j + 1];
point.x = numbers[j + 2];
point.y = numbers[j + 3];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'T':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2) {
var rx = getReflection(point.x, control.x);
var ry = getReflection(point.y, control.y);
path.quadraticCurveTo(rx, ry, numbers[j + 0], numbers[j + 1]);
control.x = rx;
control.y = ry;
point.x = numbers[j + 0];
point.y = numbers[j + 1];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'A':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 7) {
var start = point.clone();
point.x = numbers[j + 5];
point.y = numbers[j + 6];
control.x = point.x;
control.y = point.y;
parseArcCommand(path, numbers[j], numbers[j + 1], numbers[j + 2], numbers[j + 3], numbers[j + 4], start, point);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'm':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2) {
point.x += numbers[j + 0];
point.y += numbers[j + 1];
control.x = point.x;
control.y = point.y;
if (j === 0) {
path.moveTo(point.x, point.y);
} else {
path.lineTo(point.x, point.y);
}
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'h':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j++) {
point.x += numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'v':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j++) {
point.y += numbers[j];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'l':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2) {
point.x += numbers[j + 0];
point.y += numbers[j + 1];
control.x = point.x;
control.y = point.y;
path.lineTo(point.x, point.y);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'c':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 6) {
path.bezierCurveTo(point.x + numbers[j + 0], point.y + numbers[j + 1], point.x + numbers[j + 2], point.y + numbers[j + 3], point.x + numbers[j + 4], point.y + numbers[j + 5]);
control.x = point.x + numbers[j + 2];
control.y = point.y + numbers[j + 3];
point.x += numbers[j + 4];
point.y += numbers[j + 5];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 's':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 4) {
path.bezierCurveTo(getReflection(point.x, control.x), getReflection(point.y, control.y), point.x + numbers[j + 0], point.y + numbers[j + 1], point.x + numbers[j + 2], point.y + numbers[j + 3]);
control.x = point.x + numbers[j + 0];
control.y = point.y + numbers[j + 1];
point.x += numbers[j + 2];
point.y += numbers[j + 3];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'q':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 4) {
path.quadraticCurveTo(point.x + numbers[j + 0], point.y + numbers[j + 1], point.x + numbers[j + 2], point.y + numbers[j + 3]);
control.x = point.x + numbers[j + 0];
control.y = point.y + numbers[j + 1];
point.x += numbers[j + 2];
point.y += numbers[j + 3];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 't':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 2) {
var rx = getReflection(point.x, control.x);
var ry = getReflection(point.y, control.y);
path.quadraticCurveTo(rx, ry, point.x + numbers[j + 0], point.y + numbers[j + 1]);
control.x = rx;
control.y = ry;
point.x = point.x + numbers[j + 0];
point.y = point.y + numbers[j + 1];
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'a':
var numbers = parseFloats(data);
for (var j = 0, jl = numbers.length; j < jl; j += 7) {
var start = point.clone();
point.x += numbers[j + 5];
point.y += numbers[j + 6];
control.x = point.x;
control.y = point.y;
parseArcCommand(path, numbers[j], numbers[j + 1], numbers[j + 2], numbers[j + 3], numbers[j + 4], start, point);
if (j === 0 && doSetFirstPoint === true) firstPoint.copy(point);
}
break;
case 'Z':
case 'z':
path.currentPath.autoClose = true;
if (path.currentPath.curves.length > 0) {
// Reset point to beginning of Path
point.copy(firstPoint);
path.currentPath.currentPoint.copy(point);
isFirstPoint = true;
}
break;
default:
console.warn(command);
}
// console.log( type, parseFloats( data ), parseFloats( data ).length )
doSetFirstPoint = false;
}
return path;
}
function parseCSSStylesheet(node) {
if (!node.sheet || !node.sheet.cssRules || !node.sheet.cssRules.length) return;
for (var i = 0; i < node.sheet.cssRules.length; i++) {
var stylesheet = node.sheet.cssRules[i];
if (stylesheet.type !== 1) continue;
var selectorList = stylesheet.selectorText.split(/,/gm).filter(Boolean).map(function (i) {
return i.trim();
});
for (var j = 0; j < selectorList.length; j++) {
stylesheets[selectorList[j]] = Object.assign(stylesheets[selectorList[j]] || {}, stylesheet.style);
}
}
}
/**
* https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
* https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ Appendix: Endpoint to center arc conversion
* From
* rx ry x-axis-rotation large-arc-flag sweep-flag x y
* To
* aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation
*/
function parseArcCommand(path, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, start, end) {
x_axis_rotation = x_axis_rotation * Math.PI / 180;
// Ensure radii are positive
rx = Math.abs(rx);
ry = Math.abs(ry);
// Compute (x1', y1')
var dx2 = (start.x - end.x) / 2.0;
var dy2 = (start.y - end.y) / 2.0;
var x1p = Math.cos(x_axis_rotation) * dx2 + Math.sin(x_axis_rotation) * dy2;
var y1p = -Math.sin(x_axis_rotation) * dx2 + Math.cos(x_axis_rotation) * dy2;
// Compute (cx', cy')
var rxs = rx * rx;
var rys = ry * ry;
var x1ps = x1p * x1p;
var y1ps = y1p * y1p;
// Ensure radii are large enough
var cr = x1ps / rxs + y1ps / rys;
if (cr > 1) {
// scale up rx,ry equally so cr == 1
var s = Math.sqrt(cr);
rx = s * rx;
ry = s * ry;
rxs = rx * rx;
rys = ry * ry;
}
var dq = rxs * y1ps + rys * x1ps;
var pq = (rxs * rys - dq) / dq;
var q = Math.sqrt(Math.max(0, pq));
if (large_arc_flag === sweep_flag) q = -q;
var cxp = q * rx * y1p / ry;
var cyp = -q * ry * x1p / rx;
// Step 3: Compute (cx, cy) from (cx', cy')
var cx = Math.cos(x_axis_rotation) * cxp - Math.sin(x_axis_rotation) * cyp + (start.x + end.x) / 2;
var cy = Math.sin(x_axis_rotation) * cxp + Math.cos(x_axis_rotation) * cyp + (start.y + end.y) / 2;
// Step 4: Compute ��1 and ����
var theta = svgAngle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
var delta = svgAngle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry) % (Math.PI * 2);
path.currentPath.absellipse(cx, cy, rx, ry, theta, theta + delta, sweep_flag === 0, x_axis_rotation);
}
function svgAngle(ux, uy, vx, vy) {
var dot = ux * vx + uy * vy;
var len = Math.sqrt(ux * ux + uy * uy) * Math.sqrt(vx * vx + vy * vy);
var ang = Math.acos(Math.max(-1, Math.min(1, dot / len))); // floating point precision, slightly over values appear
if (ux * vy - uy * vx < 0) ang = -ang;
return ang;
}
/*
* According to https://www.w3.org/TR/SVG/shapes.html#RectElementRXAttribute
* rounded corner should be rendered to elliptical arc, but bezier curve does the job well enough
*/
function parseRectNode(node) {
var x = parseFloatWithUnits(node.getAttribute('x') || 0);
var y = parseFloatWithUnits(node.getAttribute('y') || 0);
var rx = parseFloatWithUnits(node.getAttribute('rx') || 0);
var ry = parseFloatWithUnits(node.getAttribute('ry') || 0);
var w = parseFloatWithUnits(node.getAttribute('width'));
var h = parseFloatWithUnits(node.getAttribute('height'));
var path = new _three.ShapePath();
path.moveTo(x + 2 * rx, y);
path.lineTo(x + w - 2 * rx, y);
if (rx !== 0 || ry !== 0) path.bezierCurveTo(x + w, y, x + w, y, x + w, y + 2 * ry);
path.lineTo(x + w, y + h - 2 * ry);
if (rx !== 0 || ry !== 0) path.bezierCurveTo(x + w, y + h, x + w, y + h, x + w - 2 * rx, y + h);
path.lineTo(x + 2 * rx, y + h);
if (rx !== 0 || ry !== 0) {
path.bezierCurveTo(x, y + h, x, y + h, x, y + h - 2 * ry);
}
path.lineTo(x, y + 2 * ry);
if (rx !== 0 || ry !== 0) {
path.bezierCurveTo(x, y, x, y, x + 2 * rx, y);
}
return path;
}
function parsePolygonNode(node) {
function iterator(match, a, b) {
var x = parseFloatWithUnits(a);
var y = parseFloatWithUnits(b);
if (index === 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
index++;
}
var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g;
var path = new _three.ShapePath();
var index = 0;
node.getAttribute('points').replace(regex, iterator);
path.currentPath.autoClose = true;
return path;
}
function parsePolylineNode(node) {
function iterator(match, a, b) {
var x = parseFloatWithUnits(a);
var y = parseFloatWithUnits(b);
if (index === 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
index++;
}
var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g;
var path = new _three.ShapePath();
var index = 0;
node.getAttribute('points').replace(regex, iterator);
path.currentPath.autoClose = false;
return path;
}
function parseCircleNode(node) {
var x = parseFloatWithUnits(node.getAttribute('cx'));
var y = parseFloatWithUnits(node.getAttribute('cy'));
var r = parseFloatWithUnits(node.getAttribute('r'));
var subpath = new _three.Path();
subpath.absarc(x, y, r, 0, Math.PI * 2);
var path = new _three.ShapePath();
path.subPaths.push(subpath);
return path;
}
function parseEllipseNode(node) {
var x = parseFloatWithUnits(node.getAttribute('cx'));
var y = parseFloatWithUnits(node.getAttribute('cy'));
var rx = parseFloatWithUnits(node.getAttribute('rx'));
var ry = parseFloatWithUnits(node.getAttribute('ry'));
var subpath = new _three.Path();
subpath.absellipse(x, y, rx, ry, 0, Math.PI * 2);
var path = new _three.ShapePath();
path.subPaths.push(subpath);
return path;
}
function parseLineNode(node) {
var x1 = parseFloatWithUnits(node.getAttribute('x1'));
var y1 = parseFloatWithUnits(node.getAttribute('y1'));
var x2 = parseFloatWithUnits(node.getAttribute('x2'));
var y2 = parseFloatWithUnits(node.getAttribute('y2'));
var path = new _three.ShapePath();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.currentPath.autoClose = false;
return path;
}
//
function parseStyle(node, style) {
style = Object.assign({}, style); // clone style
var stylesheetStyles = {};
if (node.hasAttribute('class')) {
var classSelectors = node.getAttribute('class').split(/\s/).filter(Boolean).map(function (i) {
return i.trim();
});
for (var i = 0; i < classSelectors.length; i++) {
stylesheetStyles = Object.assign(stylesheetStyles, stylesheets['.' + classSelectors[i]]);
}
}
if (node.hasAttribute('id')) {
stylesheetStyles = Object.assign(stylesheetStyles, stylesheets['#' + node.getAttribute('id')]);
}
function addStyle(svgName, jsName, adjustFunction) {
if (adjustFunction === undefined) adjustFunction = function copy(v) {
if (v.startsWith('url')) console.warn('SVGLoader: url access in attributes is not implemented.');
return v;
};
if (node.hasAttribute(svgName)) style[jsName] = adjustFunction(node.getAttribute(svgName));
if (stylesheetStyles[svgName]) style[jsName] = adjustFunction(stylesheetStyles[svgName]);
if (node.style && node.style[svgName] !== '') style[jsName] = adjustFunction(node.style[svgName]);
}
function clamp(v) {
return Math.max(0, Math.min(1, parseFloatWithUnits(v)));
}
function positive(v) {
return Math.max(0, parseFloatWithUnits(v));
}
addStyle('fill', 'fill');
addStyle('fill-opacity', 'fillOpacity', clamp);
addStyle('opacity', 'opacity', clamp);
addStyle('stroke', 'stroke');
addStyle('stroke-opacity', 'strokeOpacity', clamp);
addStyle('stroke-width', 'strokeWidth', positive);
addStyle('stroke-linejoin', 'strokeLineJoin');
addStyle('stroke-linecap', 'strokeLineCap');
addStyle('stroke-miterlimit', 'strokeMiterLimit', positive);
addStyle('visibility', 'visibility');
return style;
}
// http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
function getReflection(a, b) {
return a - (b - a);
}
function parseFloats(string) {
var array = string.split(/[\s,]+|(?=\s?[+\-])/);
for (var i = 0; i < array.length; i++) {
var number = array[i];
// Handle values like 48.6037.7.8
// TODO Find a regex for this
if (number.indexOf('.') !== number.lastIndexOf('.')) {
var split = number.split('.');
for (var s = 2; s < split.length; s++) {
array.splice(i + s - 1, 0, '0.' + split[s]);
}
}
array[i] = parseFloatWithUnits(number);
}
return array;
}
// Units
var units = ['mm', 'cm', 'in', 'pt', 'pc', 'px'];
// Conversion: [ fromUnit ][ toUnit ] (-1 means dpi dependent)
var unitConversion = {
mm: {
mm: 1,
cm: 0.1,
"in": 1 / 25.4,
pt: 72 / 25.4,
pc: 6 / 25.4,
px: -1
},
cm: {
mm: 10,
cm: 1,
"in": 1 / 2.54,
pt: 72 / 2.54,
pc: 6 / 2.54,
px: -1
},
"in": {
mm: 25.4,
cm: 2.54,
"in": 1,
pt: 72,
pc: 6,
px: -1
},
pt: {
mm: 25.4 / 72,
cm: 2.54 / 72,
"in": 1 / 72,
pt: 1,
pc: 6 / 72,
px: -1
},
pc: {
mm: 25.4 / 6,
cm: 2.54 / 6,
"in": 1 / 6,
pt: 72 / 6,
pc: 1,
px: -1
},
px: {
px: 1
}
};
function parseFloatWithUnits(string) {
var theUnit = 'px';
if (typeof string === 'string' || string instanceof String) {
for (var i = 0, n = units.length; i < n; i++) {
var u = units[i];
if (string.endsWith(u)) {
theUnit = u;
string = string.substring(0, string.length - u.length);
break;
}
}
}
var scale = undefined;
if (theUnit === 'px' && scope.defaultUnit !== 'px') {
// Conversion scale from pixels to inches, then to default units
scale = unitConversion['in'][scope.defaultUnit] / scope.defaultDPI;
} else {
scale = unitConversion[theUnit][scope.defaultUnit];
if (scale < 0) {
// Conversion scale to pixels
scale = unitConversion[theUnit]['in'] * scope.defaultDPI;
}
}
return scale * parseFloat(string);
}
// Transforms
function getNodeTransform(node) {
if (!(node.hasAttribute('transform') || node.nodeName === 'use' && (node.hasAttribute('x') || node.hasAttribute('y')))) {
return null;
}
var transform = parseNodeTransform(node);
if (transformStack.length > 0) {
transform.premultiply(transformStack[transformStack.length - 1]);
}
currentTransform.copy(transform);
transformStack.push(transform);
return transform;
}
function parseNodeTransform(node) {
var transform = new _three.Matrix3();
var currentTransform = tempTransform0;
if (node.nodeName === 'use' && (node.hasAttribute('x') || node.hasAttribute('y'))) {
var tx = parseFloatWithUnits(node.getAttribute('x'));
var ty = parseFloatWithUnits(node.getAttribute('y'));
transform.translate(tx, ty);
}
if (node.hasAttribute('transform')) {
var transformsTexts = node.getAttribute('transform').split(')');
for (var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex--) {
var transformText = transformsTexts[tIndex].trim();
if (transformText === '') continue;
var openParPos = transformText.indexOf('(');
var closeParPos = transformText.length;
if (openParPos > 0 && openParPos < closeParPos) {
var transformType = transformText.substr(0, openParPos);
var array = parseFloats(transformText.substr(openParPos + 1, closeParPos - openParPos - 1));
currentTransform.identity();
switch (transformType) {
case 'translate':
if (array.length >= 1) {
var tx = array[0];
var ty = tx;
if (array.length >= 2) {
ty = array[1];
}
currentTransform.translate(tx, ty);
}
break;
case 'rotate':
if (array.length >= 1) {
var angle = 0;
var cx = 0;
var cy = 0;
// Angle
angle = -array[0] * Math.PI / 180;
if (array.length >= 3) {
// Center x, y
cx = array[1];
cy = array[2];
}
// Rotate around center (cx, cy)
tempTransform1.identity().translate(-cx, -cy);
tempTransform2.identity().rotate(angle);
tempTransform3.multiplyMatrices(tempTransform2, tempTransform1);
tempTransform1.identity().translate(cx, cy);
currentTransform.multiplyMatrices(tempTransform1, tempTransform3);
}
break;
case 'scale':
if (array.length >= 1) {
var scaleX = array[0];
var scaleY = scaleX;
if (array.length >= 2) {
scaleY = array[1];
}
currentTransform.scale(scaleX, scaleY);
}
break;
case 'skewX':
if (array.length === 1) {
currentTransform.set(1, Math.tan(array[0] * Math.PI / 180), 0, 0, 1, 0, 0, 0, 1);
}
break;
case 'skewY':
if (array.length === 1) {
currentTransform.set(1, 0, 0, Math.tan(array[0] * Math.PI / 180), 1, 0, 0, 0, 1);
}
break;
case 'matrix':
if (array.length === 6) {
currentTransform.set(array[0], array[2], array[4], array[1], array[3], array[5], 0, 0, 1);
}
break;
}
}
transform.premultiply(currentTransform);
}
}
return transform;
}
function transformPath(path, m) {
function transfVec2(v2) {
tempV3.set(v2.x, v2.y, 1).applyMatrix3(m);
v2.set(tempV3.x, tempV3.y);
}
var isRotated = isTransformRotated(m);
var subPaths = path.subPaths;
for (var i = 0, n = subPaths.length; i < n; i++) {
var subPath = subPaths[i];
var curves = subPath.curves;
for (var j = 0; j < curves.length; j++) {
var curve = curves[j];
if (curve.isLineCurve) {
transfVec2(curve.v1);
transfVec2(curve.v2);
} else if (curve.isCubicBezierCurve) {
transfVec2(curve.v0);
transfVec2(curve.v1);
transfVec2(curve.v2);
transfVec2(curve.v3);
} else if (curve.isQuadraticBezierCurve) {
transfVec2(curve.v0);
transfVec2(curve.v1);
transfVec2(curve.v2);
} else if (curve.isEllipseCurve) {
if (isRotated) {
console.warn('SVGLoader: Elliptic arc or ellipse rotation or skewing is not implemented.');
}
tempV2.set(curve.aX, curve.aY);
transfVec2(tempV2);
curve.aX = tempV2.x;
curve.aY = tempV2.y;
curve.xRadius *= getTransformScaleX(m);
curve.yRadius *= getTransformScaleY(m);
}
}
}
}
function isTransformRotated(m) {
return m.elements[1] !== 0 || m.elements[3] !== 0;
}
function getTransformScaleX(m) {
var te = m.elements;
return Math.sqrt(te[0] * te[0] + te[1] * te[1]);
}
function getTransformScaleY(m) {
var te = m.elements;
return Math.sqrt(te[3] * te[3] + te[4] * te[4]);
}
//
var paths = [];
var stylesheets = {};
var transformStack = [];
var tempTransform0 = new _three.Matrix3();
var tempTransform1 = new _three.Matrix3();
var tempTransform2 = new _three.Matrix3();
var tempTransform3 = new _three.Matrix3();
var tempV2 = new _three.Vector2();
var tempV3 = new _three.Vector3();
var currentTransform = new _three.Matrix3();
var xml = new DOMParser().parseFromString(text, 'image/svg+xml'); // application/xml
parseNode(xml.documentElement, {
fill: '#000',
fillOpacity: 1,
strokeOpacity: 1,
strokeWidth: 1,
strokeLineJoin: 'miter',
strokeLineCap: 'butt',
strokeMiterLimit: 4
});
var data = {
paths: paths,
xml: xml.documentElement
};
// console.log( paths );
return data;
}
});
SVGLoader.getStrokeStyle = function (width, color, lineJoin, lineCap, miterLimit) {
// Param width: Stroke width
// Param color: As returned by THREE.Color.getStyle()
// Param lineJoin: One of "round", "bevel", "miter" or "miter-limit"
// Param lineCap: One of "round", "square" or "butt"
// Param miterLimit: Maximum join length, in multiples of the "width" parameter (join is truncated if it exceeds that distance)
// Returns style object
width = width !== undefined ? width : 1;
color = color !== undefined ? color : '#000';
lineJoin = lineJoin !== undefined ? lineJoin : 'miter';
lineCap = lineCap !== undefined ? lineCap : 'butt';
miterLimit = miterLimit !== undefined ? miterLimit : 4;
return {
strokeColor: color,
strokeWidth: width,
strokeLineJoin: lineJoin,
strokeLineCap: lineCap,
strokeMiterLimit: miterLimit
};
};
SVGLoader.pointsToStroke = function (points, style, arcDivisions, minDistance) {
// Generates a stroke with some witdh around the given path.
// The path can be open or closed (last point equals to first point)
// Param points: Array of Vector2D (the path). Minimum 2 points.
// Param style: Object with SVG properties as returned by SVGLoader.getStrokeStyle(), or SVGLoader.parse() in the path.userData.style object
// Params arcDivisions: Arc divisions for round joins and endcaps. (Optional)
// Param minDistance: Points closer to this distance will be merged. (Optional)
// Returns BufferGeometry with stroke triangles (In plane z = 0). UV coordinates are generated ('u' along path. 'v' across it, from left to right)
var vertices = [];
var normals = [];
var uvs = [];
if (SVGLoader.pointsToStrokeWithBuffers(points, style, arcDivisions, minDistance, vertices, normals, uvs) === 0) {
return null;
}
var geometry = new _three.BufferGeometry();
geometry.setAttribute('position', new _three.Float32BufferAttribute(vertices, 3));
geometry.setAttribute('normal', new _three.Float32BufferAttribute(normals, 3));
geometry.setAttribute('uv', new _three.Float32BufferAttribute(uvs, 2));
return geometry;
};
SVGLoader.pointsToStrokeWithBuffers = function () {
var tempV2_1 = new _three.Vector2();
var tempV2_2 = new _three.Vector2();
var tempV2_3 = new _three.Vector2();
var tempV2_4 = new _three.Vector2();
var tempV2_5 = new _three.Vector2();
var tempV2_6 = new _three.Vector2();
var tempV2_7 = new _three.Vector2();
var lastPointL = new _three.Vector2();
var lastPointR = new _three.Vector2();
var point0L = new _three.Vector2();
var point0R = new _three.Vector2();
var currentPointL = new _three.Vector2();
var currentPointR = new _three.Vector2();
var nextPointL = new _three.Vector2();
var nextPointR = new _three.Vector2();
var innerPoint = new _three.Vector2();
var outerPoint = new _three.Vector2();
return function (points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset) {
// This function can be called to update existing arrays or buffers.
// Accepts same parameters as pointsToStroke, plus the buffers and optional offset.
// Param vertexOffset: Offset vertices to start writing in the buffers (3 elements/vertex for vertices and normals, and 2 elements/vertex for uvs)
// Returns number of written vertices / normals / uvs pairs
// if 'vertices' parameter is undefined no triangles will be generated, but the returned vertices count will still be valid (useful to preallocate the buffers)
// 'normals' and 'uvs' buffers are optional
arcDivisions = arcDivisions !== undefined ? arcDivisions : 12;
minDistance = minDistance !== undefined ? minDistance : 0.001;
vertexOffset = vertexOffset !== undefined ? vertexOffset : 0;
// First ensure there are no duplicated points
points = removeDuplicatedPoints(points);
var numPoints = points.length;
if (numPoints < 2) return 0;
var isClosed = points[0].equals(points[numPoints - 1]);
var currentPoint;
var previousPoint = points[0];
var nextPoint;
var strokeWidth2 = style.strokeWidth / 2;
var deltaU = 1 / (numPoints - 1);
var u0 = 0;
var innerSideModified;
var joinIsOnLeftSide;
var isMiter;
var initialJoinIsOnLeftSide = false;
var numVertices = 0;
var currentCoordinate = vertexOffset * 3;
var currentCoordinateUV = vertexOffset * 2;
// Get initial left and right stroke points
getNormal(points[0], points[1], tempV2_1).multiplyScalar(strokeWidth2);
lastPointL.copy(points[0]).sub(tempV2_1);
lastPointR.copy(points[0]).add(tempV2_1);
point0L.copy(lastPointL);
point0R.copy(lastPointR);
for (var iPoint = 1; iPoint < numPoints; iPoint++) {
currentPoint = points[iPoint];
// Get next point
if (iPoint === numPoints - 1) {
if (isClosed) {
// Skip duplicated initial point
nextPoint = points[1];
} else nextPoint = undefined;
} else {
nextPoint = points[iPoint + 1];
}
// Normal of previous segment in tempV2_1
var normal1 = tempV2_1;
getNormal(previousPoint, currentPoint, normal1);
tempV2_3.copy(normal1).multiplyScalar(strokeWidth2);
currentPointL.copy(currentPoint).sub(tempV2_3);
currentPointR.copy(currentPoint).add(tempV2_3);
var u1 = u0 + deltaU;
innerSideModified = false;
if (nextPoint !== undefined) {
// Normal of next segment in tempV2_2
getNormal(currentPoint, nextPoint, tempV2_2);
tempV2_3.copy(tempV2_2).multiplyScalar(strokeWidth2);
nextPointL.copy(currentPoint).sub(tempV2_3);
nextPointR.copy(currentPoint).add(tempV2_3);
joinIsOnLeftSide = true;
tempV2_3.subVectors(nextPoint, previousPoint);
if (normal1.dot(tempV2_3) < 0) {
joinIsOnLeftSide = false;
}
if (iPoint === 1) initialJoinIsOnLeftSide = joinIsOnLeftSide;
tempV2_3.subVectors(nextPoint, currentPoint);
tempV2_3.normalize();
var dot = Math.abs(normal1.dot(tempV2_3));
// If path is straight, don't create join
if (dot !== 0) {
// Compute inner and outer segment intersections
var miterSide = strokeWidth2 / dot;
tempV2_3.multiplyScalar(-miterSide);
tempV2_4.subVectors(currentPoint, previousPoint);
tempV2_5.copy(tempV2_4).setLength(miterSide).add(tempV2_3);
innerPoint.copy(tempV2_5).negate();
var miterLength2 = tempV2_5.length();
var segmentLengthPrev = tempV2_4.length();
tempV2_4.divideScalar(segmentLengthPrev);
tempV2_6.subVectors(nextPoint, currentPoint);
var segmentLengthNext = tempV2_6.length();
tempV2_6.divideScalar(segmentLengthNext);
// Check that previous and next segments doesn't overlap with the innerPoint of intersection
if (tempV2_4.dot(innerPoint) < segmentLengthPrev && tempV2_6.dot(innerPoint) < segmentLengthNext) {
innerSideModified = true;
}
outerPoint.copy(tempV2_5).add(currentPoint);
innerPoint.add(currentPoint);
isMiter = false;
if (innerSideModified) {
if (joinIsOnLeftSide) {
nextPointR.copy(innerPoint);
currentPointR.copy(innerPoint);
} else {
nextPointL.copy(innerPoint);
currentPointL.copy(innerPoint);
}
} else {
// The segment triangles are generated here if there was overlapping
makeSegmentTriangles();
}
switch (style.strokeLineJoin) {
case 'bevel':
makeSegmentWithBevelJoin(joinIsOnLeftSide, innerSideModified, u1);
break;
case 'round':
// Segment triangles
createSegmentTrianglesWithMiddleSection(joinIsOnLeftSide, innerSideModified);
// Join triangles
if (joinIsOnLeftSide) {
makeCircularSector(currentPoint, currentPointL, nextPointL, u1, 0);
} else {
makeCircularSector(currentPoint, nextPointR, currentPointR, u1, 1);
}
break;
case 'miter':
case 'miter-clip':
default:
var miterFraction = strokeWidth2 * style.strokeMiterLimit / miterLength2;
if (miterFraction < 1) {
// The join miter length exceeds the miter limit
if (style.strokeLineJoin !== 'miter-clip') {
makeSegmentWithBevelJoin(joinIsOnLeftSide, innerSideModified, u1);
break;
} else {
// Segment triangles
createSegmentTrianglesWithMiddleSection(joinIsOnLeftSide, innerSideModified);
// Miter-clip join triangles
if (joinIsOnLeftSide) {
tempV2_6.subVectors(outerPoint, currentPointL).multiplyScalar(miterFraction).add(currentPointL);
tempV2_7.subVectors(outerPoint, nextPointL).multiplyScalar(miterFraction).add(nextPointL);
addVertex(currentPointL, u1, 0);
addVertex(tempV2_6, u1, 0);
addVertex(currentPoint, u1, 0.5);
addVertex(currentPoint, u1, 0.5);
addVertex(tempV2_6, u1, 0);
addVertex(tempV2_7, u1, 0);
addVertex(currentPoint, u1, 0.5);
addVertex(tempV2_7, u1, 0);
addVertex(nextPointL, u1, 0);
} else {
tempV2_6.subVectors(outerPoint, currentPointR).multiplyScalar(miterFraction).add(currentPointR);
tempV2_7.subVectors(outerPoint, nextPointR).multiplyScalar(miterFraction).add(nextPointR);
addVertex(currentPointR, u1, 1);
addVertex(tempV2_6, u1, 1);
addVertex(currentPoint, u1, 0.5);
addVertex(currentPoint, u1, 0.5);
addVertex(tempV2_6, u1, 1);
addVertex(tempV2_7, u1, 1);
addVertex(currentPoint, u1, 0.5);
addVertex(tempV2_7, u1, 1);
addVertex(nextPointR, u1, 1);
}
}
} else {
// Miter join segment triangles
if (innerSideModified) {
// Optimized segment + join triangles
if (joinIsOnLeftSide) {
addVertex(lastPointR, u0, 1);
addVertex(lastPointL, u0, 0);
addVertex(outerPoint, u1, 0);
addVertex(lastPointR, u0, 1);
addVertex(outerPoint, u1, 0);
addVertex(innerPoint, u1, 1);
} else {
addVertex(lastPointR, u0, 1);
addVertex(lastPointL, u0, 0);
addVertex(outerPoint, u1, 1);
addVertex(lastPointL, u0, 0);
addVertex(innerPoint, u1, 0);
addVertex(outerPoint, u1, 1);
}
if (joinIsOnLeftSide) {
nextPointL.copy(outerPoint);
} else {
nextPointR.copy(outerPoint);
}
} else {
// Add extra miter join triangles
if (joinIsOnLeftSide) {
addVertex(currentPointL, u1, 0);
addVertex(outerPoint, u1, 0);
addVertex(currentPoint, u1, 0.5);
addVertex(currentPoint, u1, 0.5);
addVertex(outerPoint, u1, 0);
addVertex(nextPointL, u1, 0);
} else {
addVertex(currentPointR, u1, 1);
addVertex(outerPoint, u1, 1);
addVertex(currentPoint, u1, 0.5);
addVertex(currentPoint, u1, 0.5);
addVertex(outerPoint, u1, 1);
addVertex(nextPointR, u1, 1);
}
}
isMiter = true;
}
break;
}
} else {
// The segment triangles are generated here when two consecutive points are collinear
makeSegmentTriangles();
}
} else {
// The segment triangles are generated here if it is the ending segment
makeSegmentTriangles();
}
if (!isClosed && iPoint === numPoints - 1) {
// Start line endcap
addCapGeometry(points[0], point0L, point0R, joinIsOnLeftSide, true, u0);
}
// Increment loop variables
u0 = u1;
previousPoint = currentPoint;
lastPointL.copy(nextPointL);
lastPointR.copy(nextPointR);
}
if (!isClosed) {
// Ending line endcap
addCapGeometry(currentPoint, currentPointL, currentPointR, joinIsOnLeftSide, false, u1);
} else if (innerSideModified && vertices) {
// Modify path first segment vertices to adjust to the segments inner and outer intersections
var lastOuter = outerPoint;
var lastInner = innerPoint;
if (initialJoinIsOnLeftSide !== joinIsOnLeftSide) {
lastOuter = innerPoint;
lastInner = outerPoint;
}
if (joinIsOnLeftSide) {
if (isMiter || initialJoinIsOnLeftSide) {
lastInner.toArray(vertices, 0 * 3);
lastInner.toArray(vertices, 3 * 3);
if (isMiter) {
lastOuter.toArray(vertices, 1 * 3);
}
}
} else {
if (isMiter || !initialJoinIsOnLeftSide) {
lastInner.toArray(vertices, 1 * 3);
lastInner.toArray(vertices, 3 * 3);
if (isMiter) {
lastOuter.toArray(vertices, 0 * 3);
}
}
}
}
return numVertices;
// -- End of algorithm
// -- Functions
function getNormal(p1, p2, result) {
result.subVectors(p2, p1);
return result.set(-result.y, result.x).normalize();
}
function addVertex(position, u, v) {
if (vertices) {
vertices[currentCoordinate] = position.x;
vertices[currentCoordinate + 1] = position.y;
vertices[currentCoordinate + 2] = 0;
if (normals) {
normals[currentCoordinate] = 0;
normals[currentCoordinate + 1] = 0;
normals[currentCoordinate + 2] = 1;
}
currentCoordinate += 3;
if (uvs) {
uvs[currentCoordinateUV] = u;
uvs[currentCoordinateUV + 1] = v;
currentCoordinateUV += 2;
}
}
numVertices += 3;
}
function makeCircularSector(center, p1, p2, u, v) {
// param p1, p2: Points in the circle arc.
// p1 and p2 are in clockwise direction.
tempV2_1.copy(p1).sub(center).normalize();
tempV2_2.copy(p2).sub(center).normalize();
var angle = Math.PI;
var dot = tempV2_1.dot(tempV2_2);
if (Math.abs(dot) < 1) angle = Math.abs(Math.acos(dot));
angle /= arcDivisions;
tempV2_3.copy(p1);
for (var i = 0, il = arcDivisions - 1; i < il; i++) {
tempV2_4.copy(tempV2_3).rotateAround(center, angle);
addVertex(tempV2_3, u, v);
addVertex(tempV2_4, u, v);
addVertex(center, u, 0.5);
tempV2_3.copy(tempV2_4);
}
addVertex(tempV2_4, u, v);
addVertex(p2, u, v);
addVertex(center, u, 0.5);
}
function makeSegmentTriangles() {
addVertex(lastPointR, u0, 1);
addVertex(lastPointL, u0, 0);
addVertex(currentPointL, u1, 0);
addVertex(lastPointR, u0, 1);
addVertex(currentPointL, u1, 1);
addVertex(currentPointR, u1, 0);
}
function makeSegmentWithBevelJoin(joinIsOnLeftSide, innerSideModified, u) {
if (innerSideModified) {
// Optimized segment + bevel triangles
if (joinIsOnLeftSide) {
// Path segments triangles
addVertex(lastPointR, u0, 1);
addVertex(lastPointL, u0, 0);
addVertex(currentPointL, u1, 0);
addVertex(lastPointR, u0, 1);
addVertex(currentPointL, u1, 0);
addVertex(innerPoint, u1, 1);
// Bevel join triangle
addVertex(currentPointL, u, 0);
addVertex(nextPointL, u, 0);
addVertex(innerPoint, u, 0.5);
} else {
// Path segments triangles
addVertex(lastPointR, u0, 1);
addVertex(lastPointL, u0, 0);
addVertex(currentPointR, u1, 1);
addVertex(lastPointL, u0, 0);
addVertex(innerPoint, u1, 0);
addVertex(currentPointR, u1, 1);
// Bevel join triangle
addVertex(currentPointR, u, 1);
addVertex(nextPointR, u, 0);
addVertex(innerPoint, u, 0.5);
}
} else {
// Bevel join triangle. The segment triangles are done in the main loop
if (joinIsOnLeftSide) {
addVertex(currentPointL, u, 0);
addVertex(nextPointL, u, 0);
addVertex(currentPoint, u, 0.5);
} else {
addVertex(currentPointR, u, 1);
addVertex(nextPointR, u, 0);
addVertex(currentPoint, u, 0.5);
}
}
}
function createSegmentTrianglesWithMiddleSection(joinIsOnLeftSide, innerSideModified) {
if (innerSideModified) {
if (joinIsOnLeftSide) {
addVertex(lastPointR, u0, 1);
addVertex(lastPointL, u0, 0);
addVertex(currentPointL, u1, 0);
addVertex(lastPointR, u0, 1);
addVertex(currentPointL, u1, 0);
addVertex(innerPoint, u1, 1);
addVertex(currentPointL, u0, 0);
addVertex(currentPoint, u1, 0.5);
addVertex(innerPoint, u1, 1);
addVertex(currentPoint, u1, 0.5);
addVertex(nextPointL, u0, 0);
addVertex(innerPoint, u1, 1);
} else {
addVertex(lastPointR, u0, 1);