Last active
March 26, 2019 12:43
-
-
Save malipetek/36ad449a7a7da57720b1be136d2f00cc to your computer and use it in GitHub Desktop.
Fix By Ikaros
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function transformSVGPath(pathStr) { | |
const DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46, | |
MINUS = 45; | |
const DEGS_TO_RADS = Math.PI/180.0; | |
var path = new THREE.Shape(); | |
var idx = 1, len = pathStr.length, activeCmd, | |
x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null, | |
x1 = 0, x2 = 0, y1 = 0, y2 = 0, | |
rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy; | |
function eatNum() { | |
var sidx, c, isFloat = false, s; | |
// eat delims | |
while (idx < len) { | |
c = pathStr.charCodeAt(idx); | |
if (c !== COMMA && c !== SPACE) | |
break; | |
idx++; | |
} | |
if (c === MINUS) | |
sidx = idx++; | |
else | |
sidx = idx; | |
// eat number | |
while (idx < len) { | |
c = pathStr.charCodeAt(idx); | |
if (DIGIT_0 <= c && c <= DIGIT_9) { | |
idx++; | |
continue; | |
} | |
else if (c === PERIOD) { | |
idx++; | |
isFloat = true; | |
continue; | |
} | |
s = pathStr.substring(sidx, idx); | |
return isFloat ? parseFloat(s) : parseInt(s); | |
} | |
s = pathStr.substring(sidx); | |
return isFloat ? parseFloat(s) : parseInt(s); | |
} | |
function nextIsNum() { | |
var c; | |
// do permanently eat any delims... | |
while (idx < len) { | |
c = pathStr.charCodeAt(idx); | |
if (c !== COMMA && c !== SPACE) | |
break; | |
idx++; | |
} | |
c = pathStr.charCodeAt(idx); | |
return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9)); | |
} | |
function eatAbsoluteArc() { | |
rx = eatNum(); | |
ry = eatNum(); | |
xar = eatNum() * DEGS_TO_RADS; | |
laf = eatNum(); | |
sf = eatNum(); | |
nx = eatNum(); | |
ny = eatNum(); | |
if( activeCmd == 'a' ) { // relative | |
nx += x; | |
ny += y; | |
} | |
console.debug( "[SVGPath2ThreeShape.eatAbsoluteArc] Read arc params: rx=" + rx + ", ry=" + ry + ", xar=" + xar + ", laf=" + laf + ", sf=" + sf + ", nx=" + nx + ", ny=" + ny ); | |
if (rx !== ry) { | |
console.warn("Forcing elliptical arc to be a circular one :(", | |
rx, ry); | |
} | |
// SVG implementation notes does all the math for us! woo! | |
// http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes | |
// step1, using x1 as x1' | |
x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2; | |
y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2; | |
// step 2, using x2 as cx' | |
console.debug( "[SVGPath2ThreeShape.eatAbsoluteArc] TMP x1=" + x1 + ", y1=" + y1 + ", (rx*rx * y1*y1 + ry*ry * x1*x1)=" + (rx*rx * y1*y1 + ry*ry * x1*x1) + ", (rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1)=" + (rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) ); | |
var norm = Math.sqrt( Math.abs( | |
(rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) / | |
(rx*rx * y1*y1 + ry*ry * x1*x1) ) ); | |
if (laf === sf) | |
norm = -norm; | |
x2 = norm * rx * y1 / ry; | |
y2 = norm * -ry * x1 / rx; | |
console.debug( "[SVGPath2ThreeShape.eatAbsoluteArc] TMP norm=" + norm + ", x2=" + x2 + ", y2=" + y2 ); | |
// step 3 | |
cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2; | |
cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2; | |
console.debug( "[SVGPath2ThreeShape.eatAbsoluteArc] TMP cx=" + cx + ", cy=" + cy ); | |
var u = new THREE.Vector2(1, 0), | |
v = new THREE.Vector2((x1 - x2) / rx, | |
(y1 - y2) / ry); | |
var startAng = Math.acos(u.dot(v) / u.length() / v.length()); | |
if (u.x * v.y - u.y * v.x < 0) | |
startAng = -startAng; | |
// we can reuse 'v' from start angle as our 'u' for delta angle | |
u.x = (-x1 - x2) / rx; | |
u.y = (-y1 - y2) / ry; | |
var deltaAng = Math.acos(v.dot(u) / v.length() / u.length()); | |
// This normalization ends up making our curves fail to triangulate... | |
if (v.x * u.y - v.y * u.x < 0) | |
deltaAng = -deltaAng; | |
if (!sf && deltaAng > 0) | |
deltaAng -= Math.PI * 2; | |
if (sf && deltaAng < 0) | |
deltaAng += Math.PI * 2; | |
console.debug( "[SVGPath2ThreeShape.eatAbsoluteArc] Building arc from values: cx=" + cx + ", cy=" + cy + ", startAng=" + startAng + ", deltaAng=" + deltaAng + ", endAng=" + (startAng+deltaAng) + ", sweepFlag=" + sf ); | |
path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf); | |
x = nx; | |
y = ny; | |
} | |
var canRepeat; | |
activeCmd = pathStr[0]; | |
while (idx <= len) { | |
canRepeat = true; | |
switch (activeCmd) { | |
// moveto commands, become lineto's if repeated | |
case 'M': | |
x = eatNum(); | |
y = eatNum(); | |
path.moveTo(x, y); | |
activeCmd = 'L'; | |
break; | |
case 'm': | |
x += eatNum(); | |
y += eatNum(); | |
path.moveTo(x, y); | |
activeCmd = 'l'; | |
break; | |
case 'Z': | |
case 'z': | |
canRepeat = false; | |
if (x !== firstX || y !== firstY) | |
path.lineTo(firstX, firstY); | |
break; | |
// - lines! | |
case 'L': | |
case 'H': | |
case 'V': | |
nx = (activeCmd === 'V') ? x : eatNum(); | |
ny = (activeCmd === 'H') ? y : eatNum(); | |
path.lineTo(nx, ny); | |
x = nx; | |
y = ny; | |
break; | |
case 'l': | |
case 'h': | |
case 'v': | |
nx = (activeCmd === 'v') ? x : (x + eatNum()); | |
ny = (activeCmd === 'h') ? y : (y + eatNum()); | |
path.lineTo(nx, ny); | |
x = nx; | |
y = ny; | |
break; | |
// - cubic bezier | |
case 'C': | |
x1 = eatNum(); y1 = eatNum(); | |
case 'S': | |
if (activeCmd === 'S') { | |
x1 = 2 * x - x2; y1 = 2 * y - y2; | |
} | |
x2 = eatNum(); | |
y2 = eatNum(); | |
nx = eatNum(); | |
ny = eatNum(); | |
path.bezierCurveTo(x1, y1, x2, y2, nx, ny); | |
x = nx; y = ny; | |
break; | |
case 'c': | |
x1 = x + eatNum(); | |
y1 = y + eatNum(); | |
case 's': | |
if (activeCmd === 's') { | |
x1 = 2 * x - x2; | |
y1 = 2 * y - y2; | |
} | |
x2 = x + eatNum(); | |
y2 = y + eatNum(); | |
nx = x + eatNum(); | |
ny = y + eatNum(); | |
path.bezierCurveTo(x1, y1, x2, y2, nx, ny); | |
x = nx; y = ny; | |
break; | |
// - quadratic bezier | |
case 'Q': | |
x1 = eatNum(); y1 = eatNum(); | |
case 'T': | |
if (activeCmd === 'T') { | |
x1 = 2 * x - x1; | |
y1 = 2 * y - y1; | |
} | |
nx = eatNum(); | |
ny = eatNum(); | |
path.quadraticCurveTo(x1, y1, nx, ny); | |
x = nx; | |
y = ny; | |
break; | |
case 'q': | |
x1 = x + eatNum(); | |
y1 = y + eatNum(); | |
case 't': | |
if (activeCmd === 't') { | |
x1 = 2 * x - x1; | |
y1 = 2 * y - y1; | |
} | |
nx = x + eatNum(); | |
ny = y + eatNum(); | |
path.quadraticCurveTo(x1, y1, nx, ny); | |
x = nx; y = ny; | |
break; | |
// - elliptical arc | |
case 'A': | |
// eatAbsoluteArc(); | |
case 'a': | |
eatAbsoluteArc(); | |
break; | |
default: | |
throw new Error("weird path command: " + activeCmd); | |
} | |
if (firstX === null) { | |
firstX = x; | |
firstY = y; | |
} | |
// just reissue the command | |
if (canRepeat && nextIsNum()) | |
continue; | |
activeCmd = pathStr[idx++]; | |
} | |
return path; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment