From cb621c2532556b18a388cb1b9cffd4e2338e2d28 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Fri, 22 Jan 2021 08:42:21 -0600 Subject: [PATCH 1/4] arc stuff, quad discretize --- src/pixie/paths.nim | 203 ++++++++++----------- tests/images/paths/pathBlackRectangleZ.png | Bin 0 -> 350 bytes tests/images/paths/pathHeart.png | Bin 2062 -> 1990 bytes tests/images/svg/quad01.png | Bin 33969 -> 34195 bytes tests/test_paths.nim | 14 +- 5 files changed, 108 insertions(+), 109 deletions(-) create mode 100644 tests/images/paths/pathBlackRectangleZ.png diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 9407cdf..f63268e 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -191,93 +191,77 @@ proc `$`*(path: Path): string = ## See https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes type ArcParams = object - s: float32 - rx, ry: float32 - rotation: float32 - cx, cy: float32 + radii: Vec2 + rotMat: Mat3 + center: Vec2 theta, delta: float32 -proc svgAngle (ux, uy, vx, vy: float32): float32 = - var u = vec2(ux, uy) - var v = vec2(vx, vy) - # (F.6.5.4) - var dot = dot(u,v) - var len = length(u) * length(v) - var ang = arccos( clamp(dot / len,-1,1) ) # floating point precision, slightly over values appear - if (u.x*v.y - u.y*v.x) < 0: - ang = -ang - return ang - proc endpointToCenterArcParams( - ax, ay, rx, ry, rotation, large, sweep, bx, by: float32 + at, radii: Vec2, rotation: float32, large, sweep: bool, to: Vec2 ): ArcParams = + var + radii = vec2(abs(radii.x), abs(radii.y)) + radiiSq = vec2(radii.x * radii.x, radii.y * radii.y) - var r = vec2(rx, ry) - var p1 = vec2(ax, ay) - var p2 = vec2(bx, by) - var xAngle = rotation/180*PI - var flagA = large == 1.0 - var flagS = sweep == 1.0 - var rX = abs(r.x) - var rY = abs(r.y) + let + radians = rotation / 180 * PI + d = vec2((at.x - to.x) / 2.0, (at.y - to.y) / 2.0) + p = vec2( + cos(radians) * d.x + sin(radians) * d.y, + -sin(radians) * d.x + cos(radians) * d.y + ) + pSq = vec2(p.x * p.x, p.y * p.y) - # (F.6.5.1) - var dx2 = (p1.x - p2.x) / 2.0 - var dy2 = (p1.y - p2.y) / 2.0 - var x1p = cos(xAngle)*dx2 + sin(xAngle)*dy2 - var y1p = -sin(xAngle)*dx2 + cos(xAngle)*dy2 - - # (F.6.5.2) - var rxs = rX * rX - var rys = rY * rY - var x1ps = x1p * x1p - var y1ps = y1p * y1p - # check if the radius is too small `pq < 0`, when `dq > rxs * rys` (see below) - # cr is the ratio (dq : rxs * rys) - var cr = x1ps/rxs + y1ps/rys - var s = 1.0 + let cr = pSq.x / radiiSq.x + pSq.y / radiiSq.y if cr > 1: - # scale up rX,rY equally so cr == 1 - s = sqrt(cr) - rX = s * rX - rY = s * rY - rxs = rX * rX - rys = rY * rY + radii *= sqrt(cr) + radiiSq = vec2(radii.x * radii.x, radii.y * radii.y) - var dq = (rxs * y1ps + rys * x1ps) - var pq = (rxs*rys - dq) / dq - var q = sqrt(max(0,pq)) # use Max to account for float precision - if flagA == flagS: + let + dq = radiiSq.x * pSq.y + radiiSq.y * pSq.x + pq = (radiiSq.x * radiiSq.y - dq) / dq + + var q = sqrt(max(0, pq)) + if large == sweep: q = -q - var cxp = q * rX * y1p / rY - var cyp = - q * rY * x1p / rX - # (F.6.5.3) - var cx = cos(xAngle)*cxp - sin(xAngle)*cyp + (p1.x + p2.x)/2 - var cy = sin(xAngle)*cxp + cos(xAngle)*cyp + (p1.y + p2.y)/2 + proc svgAngle(u, v: Vec2): float32 = + let + dot = dot(u,v) + len = length(u) * length(v) + result = arccos(clamp(dot / len, -1, 1)) + if (u.x * v.y - u.y * v.x) < 0: + result = -result + + let + cp = vec2(q * radii.x * p.y / radii.y, -q * radii.y * p.x / radii.x) + center = vec2( + cos(radians) * cp.x - sin(radians) * cp.y + (at.x + to.x) / 2, + sin(radians) * cp.x + cos(radians) * cp.y + (at.y + to.y) / 2 + ) + theta = svgAngle(vec2(1, 0), vec2((p.x-cp.x) / radii.x, (p.y - cp.y) / radii.y)) - # (F.6.5.5) - var theta = svgAngle( 1,0, (x1p-cxp) / rX, (y1p - cyp)/rY ) - # (F.6.5.6) var delta = svgAngle( - (x1p - cxp)/rX, (y1p - cyp)/rY, - (-x1p - cxp)/rX, (-y1p-cyp)/rY) + vec2((p.x - cp.x) / radii.x, (p.y - cp.y) / radii.y), + vec2((-p.x - cp.x) / radii.x, (-p.y - cp.y) / radii.y) + ) delta = delta mod (PI * 2) - if not flagS: + if not sweep: delta -= 2 * PI - # normalize the delta - while delta > PI*2: - delta -= PI*2 - while delta < -PI*2: - delta += PI*2 + # Normalize the delta + while delta > PI * 2: + delta -= PI * 2 + while delta < -PI * 2: + delta += PI * 2 - r = vec2(rX, rY) - - return ArcParams( - s: s, rx: rX, rY: ry, rotation: xAngle, cx: cx, cy: cy, - theta: theta, delta: delta + ArcParams( + radii: radii, + rotMat: rotationMat3(-radians), + center: center, + theta: theta, + delta: delta ) proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] = @@ -325,25 +309,34 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] = discretize(1, 1) - proc drawQuad(p0, p1, p2: Vec2) = - let devx = p0.x - 2.0 * p1.x + p2.x - let devy = p0.y - 2.0 * p1.y + p2.y - let devsq = devx * devx + devy * devy - if devsq < 0.333: - drawLine(p0, p2) - return - let tol = 3.0 - let n = 1 + (tol * (devsq)).sqrt().sqrt().floor() - var p = p0 - let nrecip = 1 / n - var t = 0.0 - for i in 0 ..< int(n): - t += nrecip - let pn = lerp(lerp(p0, p1, t), lerp(p1, p2, t), t) - drawLine(p, pn) - p = pn + proc drawQuad(at, ctrl, to: Vec2) = - drawLine(p, p2) + proc compute(at, ctrl, to: Vec2, t: float32): Vec2 {.inline.} = + pow(1 - t, 2) * at + + 2 * (1 - t) * t * ctrl + + pow(t, 2) * to + + var prev = at + + proc discretize(i, steps: int) = + # Closure captures at, ctrl, to and prev + let + tPrev = (i - 1).float32 / steps.float32 + t = i.float32 / steps.float32 + next = compute(at, ctrl, to, t) + halfway = compute(at, ctrl, to, tPrev + (t - tPrev) / 2) + midpoint = (prev + next) / 2 + error = (midpoint - halfway).length + + if error >= 0.25: + # Error too large, double precision for this step + discretize(i * 2 - 1, steps * 2) + discretize(i * 2, steps * 2) + else: + drawLine(prev, next) + prev = next + + discretize(1, 1) for command in commands: case command.kind @@ -409,25 +402,23 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] = at = to of Arc: - var arc = endpointToCenterArcParams( - at.x, - at.y, - command.numbers[0], - command.numbers[1], - command.numbers[2], - command.numbers[3], - command.numbers[4], - command.numbers[5], - command.numbers[6], - ) - let steps = int(abs(arc.delta)/PI*180/5) - let step = arc.delta / steps.float32 + let + arc = endpointToCenterArcParams( + at, + vec2(command.numbers[0], command.numbers[1]), + command.numbers[2], + command.numbers[3] == 1, + command.numbers[4] == 1, + vec2(command.numbers[5], command.numbers[6]), + ) + steps = int(abs(arc.delta) / PI * 180 / 5) + step = arc.delta / steps.float32 var a = arc.theta - var rotMat = rotationMat3(-arc.rotation) for i in 0 .. steps: - polygon.add(rotMat * vec2( - cos(a)*arc.rx, - sin(a)*arc.ry) + vec2(arc.cx, arc.cy) + polygon.add( + arc.rotMat * vec2( + cos(a) * arc.radii.x, sin(a) * arc.radii.y + ) + arc.center ) a += step at = polygon[^1] @@ -436,7 +427,7 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] = assert command.numbers.len == 0 if at != start: if prevCommand == Quad or prevCommand == TQuad: - drawQuad(at, ctr, start) + drawQuad(at, ctr, start) else: drawLine(at, start) if polygon.len > 0: diff --git a/tests/images/paths/pathBlackRectangleZ.png b/tests/images/paths/pathBlackRectangleZ.png new file mode 100644 index 0000000000000000000000000000000000000000..fb9d80e71dcff1978d2861d54822ee4de05eaae4 GIT binary patch literal 350 zcmeAS@N?(olHy`uVBq!ia0vp^DIm>ctcUEdQtg}0puiDc7kI>I5O?9roOh{U+VxvAeU?DG%aZ8N`>H_b7u@S7mv zn8ea4sNyyV8TX{)Z>3GsHJ+~by7UvOM>vE&S57EiDA|8M;nduIMSB4Z4_GqH)LnSS U+IXunFx(hCUHx3vIVCg!0FH%fIRF3v literal 0 HcmV?d00001 diff --git a/tests/images/paths/pathHeart.png b/tests/images/paths/pathHeart.png index 6c4c9ad455bfeeeed6e1faf07bc5d9f4ec2a8ddd..27add450e3661cf2521093cb5cc898a0c84e0bae 100644 GIT binary patch delta 1976 zcmZWqc~p{#7XQc%atX*X4Rt<|T$3@g%u1i18=`sKT3pd&Tq2j$22m3%R2nPE#4$9q znle*_+)C4=#tcTytfb8y%G7Bru?)3(c+NZT{qgQ!cRTmq-|yaY?m1-7I<`6etdECV zNG9yVB}AXO`=$a?^YXo7B=GVa0Wl)A02!^_`2;Ey*XJZjt zLonbZD^?QJz^Y(&+sVYsvp;seqJQtIi)+um`Gaq9QL^B-c#t&Uu-7p%a(|P$nHwr! z-Af<6DUcBD0p|V3p#RT$3pw+@v=haKL@}1bCL(B1@tf^9IhQD|YIf`zQkvwKk#MUy zjYP4mlzkhxgAz>u=8Usj?tF4=NY6v3k#KTXqPXof*MCDA^jHuAUOJGSh?CO*6IiM~ z_tIU1xNQHrPHjd_I;z+HE92*$43kKr_;#}+Ne_)* zSn;y*B-9Mu+a&4)q!`@@GYSyh=|Bosq`4k*9}raJF*CwGJS=5XfjjD=!y|dV1-BLt zKn((B4iqlacUf4;vS%Z4@=JjK%!WH=ts26-*0@&Rl_!c>&#_f3bPn)%ZbS3o)J z*hHL^_RSE*-W)ciu(GQ4-R|EPABM8f^9jImySnijQ?-{)@|9*sL0bpj1$wL@%=di) ze)Jt}#2n3iZPdyzn8eD{fZ2^+7(KfDcsO0COr#`JX_Or1A0onqOqMMo`Yl`WOd2sa z-=l=LY5r>xodHjEEz{fOn$+K7H>XYQM&QW1$Bo}<<=+mNPN00%4Q3UygB5~&mMe)u zNlts!=PkAer0EDF!$%#~)}K$o{5Bs9>CUINICgYLvIbC%>lpmCSg)*4H(TG)Z$;s4 z&lDrSSiho804NLIGwDa8AkYPBg$WPFbQAD#cfXW>LOocLRYzU(T-)XyuJE4-WBkrm z0*@!XP$_mp(v|m(90lqDgD8Fms~<-@ZR^?TQx>(OHH6WGyuuuI8Q(Y-5c9~TrYvgG zhB@NAC@Vn4CrhOr)9tMSB>mh7>od#LV19P=_$PmSZ*1E?|ImUG1StkGpXY%j}O~uEZ~?LR^TiBE*jl!#gp@>#P)5fZpsoG zDrLI@PP}5Q%qjcIEgR2;txS!yZ9p_RAWlwyw2># z<>^?zS2WjJ-i-B72{*`91s;O*GW(SY-+Bil7DC z;UDz*H$JD=QYXnewoFSQ2P#QDbm-b!1TzQdPtJrYgjJRIaZ_XN| zhB4bKANw?xuAeLQs)bZV^?-BU5vO~D%jp8Z@5Prc`}to2+mTytFmsGW$3&$`x^a|$ za(&8jL&{izL<7iaxdL4x`+oh%i)XzTY&)x#!*+1s;NQK@p0n$vo8wbW>#DbbuWLyApF>)A}qeYXj9x0u={|{pTW*f1)?3xGyc> z1Q#K%@M&R)wotX*6jwHRS~6Vdd}ALx5K7xIXl#_RW#GavHglv<`NEA4-H9a<^m66q z+{&Om9V9_J_%FQp5MdOqv}Am<;UezpxB$ZVl53;iZfaip74!&qAR&}a4EMbA z5?lAlbRgS2^slnn*M{`pkR{aC?6p6>6|Je^>B7D^UOh8V!0CI!Wao599F&fwF~yN9 z8%3k44bC1((Af_kl;%_%T)X6n-;u~UP>2YtQI+mG0*=PyQ(Y1t;x)#NIre!outwv4v z0Em#wn@2MCSs#s?$e8wie&XpAlnTKI-tXeyM5h@8mG#wr$-a#X9%X?T{gx(=54fyq zJ}n3#+4}2LBqHJxdD;`#U3;cFADPr&L=-OqeSlEEYWCC^fKkQxCys{O+}8$QbJua$ zO)x(#y3nXf5pBZgEU`T?7+Z095U%1%(TSc<2Ex_Kd!{hu?x$6EFfB|*Zue4Qw|ycV zu46`MiW+i~(G9C}Bs)l)?+;pnR}(0_sz0I(`S`fA4P)j;R!qMD!S04nkE^!I3hqEG%C D-+!9w literal 2062 zcmZ`)dpOhkAOFtW=DJ3?r{xkcvQ{o5;uMA= zwbITw{ao5;E{80al`)lw9ce|KufKlJ^Lw7(AD`RndA%>s>;3*b?>rx`6RJwON&o<; zy16?0f@k#BQj`bxxOcQu0HE-fo3qoY6v(H_m|WxI+S%cc{CIW|Xq*quqnZ(!SDQSo zh@X{o1|D{Cw`lCh1F;sSrCOuHAKS~>nZ8SL!zFU zPp)tD?0yUy^|w{s2K;r*;`%@595X#cApS65qh}=C-H+j6BB6 zT5f&6k+HnM^PS^EV=^s$5X9=Z*<2&h0if8O58Qe@2~NAprNH2zq{7_-snSH5RN;(Guw;SBov4+~iZ~P1 z?4f+B<;bk!GGERmM_s$}J~QaxH*}?FN4MN($N;~fq`$xTTM+R($hG7zHy$QtLnl4*OKx4!EOoh$$?cd=Lm8KAe#o@` zNCE3Hf0*X>9;3L-Fh$Z8IP9)OQ3EJ`^X?Ta1uT*TNRnxV&5Y;9ypDUhGu}^h`Zm0o ziYTegTbf&e>l>vibJ#e9*09$c=FFCBZ9t zGrTbPlaV2mq;oW&^7I@?|2G@4imY~6_|brtC{D-DGP>0CXx5;hA(C>$YH|B{y5%NC zs;6+KwTg30`xd;0DWyfRB!+PNl%xw@!Jdiw6|8;o`#@FaO_5cyoI6KuMoj?1E3T8) z*yl>g5kicsAmD36b;cLJwcLsH%-U@zD|HdHR~czMb#m#wV(HL<2yWM?NuHR-qIRaBzyLpdG4Zw%Pj@{z+KPH%$u09IVQ(wp-D#$J z{(i82VyS+R|E_Ttw{!%(SYLzI<~--l%j%dvhY}eAd&FM_fv4f9mAa^b{bxIOVA^e3 z_92f#_p5SB&>yqpfI_f8vJKcFrqjwuLD_mK)w|BuKQc&-E#DeY`ocOPJqACDQ5+iM z;V{!`_!Gd+2fQpT$iW)pAKejQmiMlMx&&2II-wjTV9$eCm3pY{K5V4H6~Zq}3itzd zlLkCcqTX}cJ2bgDR)(AhIE1&j^Us$=r%x1>JIvcmO1cV16KJfTtCAF;vG0sC!jBNl zwluBprIE5a3QTbb$*!;vf;ouoAP|>4!j*&kV+fYfo&SH7t{D36>-S!AS0aFO9&kJE K<=l+H|Mm~Avc9VT diff --git a/tests/images/svg/quad01.png b/tests/images/svg/quad01.png index 452a9e27227db969f8a149bb5cc6d0246c657948..63e9998039c729eafcd76c1b5f3f652a90b10cfd 100644 GIT binary patch literal 34195 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

Kb2mDGB7EK7=nvWVEWLFA0 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 literal 33969 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 diff --git a/tests/test_paths.nim b/tests/test_paths.nim index 51d86a6..1cb9e78 100644 --- a/tests/test_paths.nim +++ b/tests/test_paths.nim @@ -37,10 +37,10 @@ block: let path = parsePath(pathStr) doAssert $path == "M1 2 L3 4 H5 V6 C0 0 0 0 0 0 Q1 1 1 1 T2 2 A7 7 7 7 7 7 7 Z" - block: - let pathStr = "M 0.1E-10 0.1e10 L2+2 L3-3 L0.1E+10-1" - let path = parsePath(pathStr) + let + pathStr = "M 0.1E-10 0.1e10 L2+2 L3-3 L0.1E+10-1" + path = parsePath(pathStr) block: let image = newImage(100, 100) @@ -72,6 +72,14 @@ block: image.fillPath(pathStr, color) image.writeFile("tests/images/paths/pathBlackRectangle.png") +block: + let + image = newImage(100, 100) + pathStr = "M 10 10 H 90 V 90 H 10 Z" + color = rgba(0, 0, 0, 255) + image.fillPath(parsePath(pathStr), color) + image.writeFile("tests/images/paths/pathBlackRectangleZ.png") + block: let image = newImage(100, 100) image.fillPath( From 7271cdfe80e16cbbdcb09014086386f150c583e8 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Fri, 22 Jan 2021 08:47:54 -0600 Subject: [PATCH 2/4] arc discretize --- src/pixie/paths.nim | 210 ++++++++++--------- tests/images/paths/pathBottomArc.png | Bin 1083 -> 1092 bytes tests/images/paths/pathCornerArc.png | Bin 975 -> 990 bytes tests/images/paths/pathHeart.png | Bin 1990 -> 1997 bytes tests/images/paths/pathInvertedCornerArc.png | Bin 981 -> 990 bytes tests/images/paths/pathRotatedArc.png | Bin 1276 -> 1302 bytes tests/images/paths/pathRoundRect.png | Bin 810 -> 832 bytes 7 files changed, 115 insertions(+), 95 deletions(-) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index f63268e..f4d7320 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -188,82 +188,6 @@ proc `$`*(path: Path): string = if i != path.commands.len - 1 or j != command.numbers.len - 1: result.add " " -## See https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes - -type ArcParams = object - radii: Vec2 - rotMat: Mat3 - center: Vec2 - theta, delta: float32 - -proc endpointToCenterArcParams( - at, radii: Vec2, rotation: float32, large, sweep: bool, to: Vec2 -): ArcParams = - var - radii = vec2(abs(radii.x), abs(radii.y)) - radiiSq = vec2(radii.x * radii.x, radii.y * radii.y) - - let - radians = rotation / 180 * PI - d = vec2((at.x - to.x) / 2.0, (at.y - to.y) / 2.0) - p = vec2( - cos(radians) * d.x + sin(radians) * d.y, - -sin(radians) * d.x + cos(radians) * d.y - ) - pSq = vec2(p.x * p.x, p.y * p.y) - - let cr = pSq.x / radiiSq.x + pSq.y / radiiSq.y - if cr > 1: - radii *= sqrt(cr) - radiiSq = vec2(radii.x * radii.x, radii.y * radii.y) - - let - dq = radiiSq.x * pSq.y + radiiSq.y * pSq.x - pq = (radiiSq.x * radiiSq.y - dq) / dq - - var q = sqrt(max(0, pq)) - if large == sweep: - q = -q - - proc svgAngle(u, v: Vec2): float32 = - let - dot = dot(u,v) - len = length(u) * length(v) - result = arccos(clamp(dot / len, -1, 1)) - if (u.x * v.y - u.y * v.x) < 0: - result = -result - - let - cp = vec2(q * radii.x * p.y / radii.y, -q * radii.y * p.x / radii.x) - center = vec2( - cos(radians) * cp.x - sin(radians) * cp.y + (at.x + to.x) / 2, - sin(radians) * cp.x + cos(radians) * cp.y + (at.y + to.y) / 2 - ) - theta = svgAngle(vec2(1, 0), vec2((p.x-cp.x) / radii.x, (p.y - cp.y) / radii.y)) - - var delta = svgAngle( - vec2((p.x - cp.x) / radii.x, (p.y - cp.y) / radii.y), - vec2((-p.x - cp.x) / radii.x, (-p.y - cp.y) / radii.y) - ) - delta = delta mod (PI * 2) - - if not sweep: - delta -= 2 * PI - - # Normalize the delta - while delta > PI * 2: - delta -= PI * 2 - while delta < -PI * 2: - delta += PI * 2 - - ArcParams( - radii: radii, - rotMat: rotationMat3(-radians), - center: center, - theta: theta, - delta: delta - ) - proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] = ## Converts SVG-like commands to simpler polygon @@ -338,6 +262,114 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] = discretize(1, 1) + proc drawArc( + at, radii: Vec2, + rotation: float32, + large, sweep: bool, + to: Vec2 + ) = + type ArcParams = object + radii: Vec2 + rotMat: Mat3 + center: Vec2 + theta, delta: float32 + + proc endpointToCenterArcParams( + at, radii: Vec2, rotation: float32, large, sweep: bool, to: Vec2 + ): ArcParams = + var + radii = vec2(abs(radii.x), abs(radii.y)) + radiiSq = vec2(radii.x * radii.x, radii.y * radii.y) + + let + radians = rotation / 180 * PI + d = vec2((at.x - to.x) / 2.0, (at.y - to.y) / 2.0) + p = vec2( + cos(radians) * d.x + sin(radians) * d.y, + -sin(radians) * d.x + cos(radians) * d.y + ) + pSq = vec2(p.x * p.x, p.y * p.y) + + let cr = pSq.x / radiiSq.x + pSq.y / radiiSq.y + if cr > 1: + radii *= sqrt(cr) + radiiSq = vec2(radii.x * radii.x, radii.y * radii.y) + + let + dq = radiiSq.x * pSq.y + radiiSq.y * pSq.x + pq = (radiiSq.x * radiiSq.y - dq) / dq + + var q = sqrt(max(0, pq)) + if large == sweep: + q = -q + + proc svgAngle(u, v: Vec2): float32 = + let + dot = dot(u,v) + len = length(u) * length(v) + result = arccos(clamp(dot / len, -1, 1)) + if (u.x * v.y - u.y * v.x) < 0: + result = -result + + let + cp = vec2(q * radii.x * p.y / radii.y, -q * radii.y * p.x / radii.x) + center = vec2( + cos(radians) * cp.x - sin(radians) * cp.y + (at.x + to.x) / 2, + sin(radians) * cp.x + cos(radians) * cp.y + (at.y + to.y) / 2 + ) + theta = svgAngle(vec2(1, 0), vec2((p.x-cp.x) / radii.x, (p.y - cp.y) / radii.y)) + + var delta = svgAngle( + vec2((p.x - cp.x) / radii.x, (p.y - cp.y) / radii.y), + vec2((-p.x - cp.x) / radii.x, (-p.y - cp.y) / radii.y) + ) + delta = delta mod (PI * 2) + + if not sweep: + delta -= 2 * PI + + # Normalize the delta + while delta > PI * 2: + delta -= PI * 2 + while delta < -PI * 2: + delta += PI * 2 + + ArcParams( + radii: radii, + rotMat: rotationMat3(-radians), + center: center, + theta: theta, + delta: delta + ) + + proc compute(arc: ArcParams, a: float32): Vec2 = + result = vec2(cos(a) * arc.radii.x, sin(a) * arc.radii.y) + result = arc.rotMat * result + arc.center + + proc discretize(arc: ArcParams, steps: int) = + let + initialShapeLen = polygon.len + step = arc.delta / steps.float32 + var prev = at + for i in 1 .. steps: + let + aPrev = arc.theta + step * (i - 1).float32 + a = arc.theta + step * i.float32 + next = arc.compute(a) + halfway = arc.compute(aPrev + (a - aPrev) / 2) + midpoint = (prev + next) / 2 + error = (midpoint - halfway).length + if error >= 0.25: + # Error too large, try again with doubled precision + polygon.setLen(initialShapeLen) + discretize(arc, steps * 2) + return + drawLine(prev, next) + prev = next + + let arc = endpointToCenterArcParams(at, radii, rotation, large, sweep, to) + discretize(arc, 4) + for command in commands: case command.kind of Move: @@ -403,25 +435,13 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] = of Arc: let - arc = endpointToCenterArcParams( - at, - vec2(command.numbers[0], command.numbers[1]), - command.numbers[2], - command.numbers[3] == 1, - command.numbers[4] == 1, - vec2(command.numbers[5], command.numbers[6]), - ) - steps = int(abs(arc.delta) / PI * 180 / 5) - step = arc.delta / steps.float32 - var a = arc.theta - for i in 0 .. steps: - polygon.add( - arc.rotMat * vec2( - cos(a) * arc.radii.x, sin(a) * arc.radii.y - ) + arc.center - ) - a += step - at = polygon[^1] + radii = vec2(command.numbers[0], command.numbers[1]) + rotation = command.numbers[2] + large = command.numbers[3] == 1 + sweep = command.numbers[4] == 1 + to = vec2(command.numbers[5], command.numbers[6]) + drawArc(at, radii, rotation, large, sweep, to) + at = to of Close: assert command.numbers.len == 0 diff --git a/tests/images/paths/pathBottomArc.png b/tests/images/paths/pathBottomArc.png index f535a0e74fe3e53de7090c8e3de211644ce168aa..7b96411268976e8c54f6530a9e0c09ef6ae8af84 100644 GIT binary patch literal 1092 zcmeAS@N?(olHy`uVBq!ia0vp^DIm8NO6lgwRF5y7U`8B+zG-af-Bn0VgDta@MdUpa{xJ3oJ)TlxI?xieusT4FAe z+>3fx1CKRnBs)&osBkJmAXTTsXz&Ryi<;2Qzwc9Z!9Lz|`+wP6>=7xh+PkE7tHQyb z%NwP=+GHA^&ba(=Qt1^QBlbH{Mo&0uWQ(nWHucSNlw&NL#$(Ae|JWSADQgy|P59k- zSJk=iz|9$#UxJhu@qY+7IYIeD-IYY~4*pl%1pxqRktr#=eK?zhyPx^8{h1o=ks zM%|^%{AXMf>d(A%dU+?b`NV>QKiM0sz1ZbuZ1%mr&-m<_(B_T@d*0NZIIofXCFD$I z<*e3FekEr1=jDvn%Oq~eN}>&<+=M$U!h`Tj}YqaH|E+A?kGwiYk`m3nLI3J#@a z_UBKFUH0wmKe%yOws=}(N0)+<#eNPOZj+*_11bGk`DZ-$3HbT?3Pd^-{9}Kxab@_z z)BBcXUtXiUl2a+kFP`oBde(Q7FEf{HczMfweM(-aqf5a*o&x=X<#XgF^f%NRp8xYG zVzFL_j)p~j)~9Qwmv2q^Rm=S*b643{FZWP!mm_oj@tiwt?DsEpPvEwzj-AB^7W-cR z7gO}`YfkKYrmaWjoMV69&$zqccJ|qgeZ~joEz7QtdR}z&3Y(zea_)mazc;*Yyt~PF z;^70rKy4c*8n5I7x~l%@96$Ew;SKGLZrj4QaJ|XjRTh_a`-SBd_LoQJh&6v!Z;Wo( z{p;=QHzEetOWqz2e_h8e40K6duca*ixzo&>n0NSY%Wq<_xmWVmapuFUd-oSKeM$1u zHe9ZJ@aOr)xd+bvt(1Pl{b1&@?D%QF?>xBu&%c@HQn%&YZcAOCZ^!mAZ)03%U+*US zLF_}#<*n>b?9%qkoylY6;a@f}*>C>QIrn(a$umuB$ZzmI@cn?;1FK)!G23`_{LW3c z-Pw9_^~1L(bLZ}_*?88pOX0sraa6CR?$J5BW?a5_@?pT4ms{&)Q=;ql#e}U>t_}$8 i)jM$^5_@+0!&~w(@ACQBFM`0*fx*+&&t;ucLK6VOrSiT2 literal 1083 zcmeAS@N?(olHy`uVBq!ia0vp^DIm*k&w#{i!Uiswv?ls9L6v~|T?>{|3xIkg*pIngoC*leE1%A^V-QUP3lxzJgnQqD> zc7&~G;k`n!IXCQA-g*`=T_SCRf?C_Jqz7WVZ4=vrPRf_X-oM~j$5Q6|BRiY1LSyQm z+Zs(b78qVJX9_Qstw_1I`qr}tI>!7}Ei)uuRX&K@ZR=PabTa<@CcRdL`wZob)6W_G z;E-XL^Hx1?^6G(O&I5^8@(sV!X2sk;w<iOdV`&ql)Pc;O-cvSu1+N#_)ySDr8{l393=;Z!m0sGl< z*!3cpPk;H^J0bh3nfkAH88dSPCVS26Us%VwLu|(_IpHFa9ecB82WMMOE&)o|&Go7? zZTsc_Kux0N+5U#w(Azdin{W1Hd)l4Y6!MAP@QQoG>qhNq^7j+u8MlY#e*RUp_gzNQ zjF3+&Lq2sIUb)X`&M^D!N8SYWH?g6&-)xh-_W7%uXiB8k&kCNejuADB*RkkD*X8xf zGS6d>TXE~zXVZJ%>!nhG7RPz0%6qHY%RB#7OL#s*_kioS8h+La$%-9Wv;TUp-*_Rp z-dHks!qlLX>cJ=9FPSty_Seckk{^T(_~-D)Y$*CInlszKvAps5n~W8=((Yf{=eO=g z-eDi56JFnB_kUSwH_NL|bLyWGt)D0V@4QjGtv5H@{r0YT^1EilOxXAtYp(k#zSuJV V+TG_iwZKAw!PC{xWt~$(69Bf7?5O|% diff --git a/tests/images/paths/pathCornerArc.png b/tests/images/paths/pathCornerArc.png index 26f348f82ad4ed5780cbe398017663b55aaa81ec..9e0a56ab2acac106046223af29939021738698e8 100644 GIT binary patch literal 990 zcmeAS@N?(olHy`uVBq!ia0vp^DImEToK^c#raO?f=l=*9!?IW!175_T(Wo09P7vsi)3*xSjfRx zsJrqqcf0oe+Ib5FERRf|^m+5ipOa7i zZ6itRsU<;-bZ-_Sg} z;>8V}6B0!o$0vU-XS^A(-{P3F)n*0VqK@5@Ki_AvXI>ulZda<#2@cC6$_L^Pq$b?% z*qv+1y{S>=(d+~EA9C#HnI$>So8Y{M@r3ybTmL&IqGC%XI?wSuSI>XqUGKfRpV0yn zB&r+j8~LA9&1aK$`L>VgKhyeel8@?}`6fBvVYhja{MhzXLh}=Wax?kh=Wf1!@@ zmi6ov>-#(X=GGOl6jqBnW(jXs zaRTjr{G>=AYxg(L{9xY+Pxh?&yzi#j4WTO{Dcd3^#BZ6^1PpDvHH)r_xQM;pA6&v~ zc`fF2e74H-`>gjSIZj=(OyN_yUbJk1yXf7jKo|dxfk`Y1|DnDNOqjEQQ=esMZ#N> lUZpMj*R^aasU|*C%&47s@oi0CI56Kbc)I$ztaD0e0s#8rwjKZg literal 975 zcmeAS@N?(olHy`uVBq!ia0vp^DIm`YxLjq@79Z(v)p#c&B67D&+jXYA zQT-CJPp2|vj=r?J?B{PE`h4#Kao)d6%)E=Pj7HKE$+{s%N4ESs2r zSD4?($@gdGgY*YH1t~|Or#*9gwnFoMl+zxz8rD0u&zJr_*8FDCvOg*x*gyQy(~soj z|0JJvYTLRWb^F6_Bv(w3%{sMxiokx>d#zt{a;hdc`u@~=aJ@Os)f964(Kj<7Q4K4M;7b`N`F4UefY<$%gGb^ zqhe!bt^-QG{d4=g!}p5gO0le(inr`mhl}w>Mdmx+W0@B)??Lw$u@vDQ|c^7Pnr!7o?%MZ0UI~nZ-aM_Ib09^F&4ZJDN?~%iKRHuPG-dKlfHb zRK3Pui+}u`(ld7MuM{wpbLn9heC&9nf-1}uJ-#~&ng6?)PbbaL&+11dCSG$CpPvoP Os0^O2elF{r5}E*OE3|_E diff --git a/tests/images/paths/pathHeart.png b/tests/images/paths/pathHeart.png index 27add450e3661cf2521093cb5cc898a0c84e0bae..60bcb67ccab47d2b8a2b936e89142e019c57b9df 100644 GIT binary patch delta 1983 zcmY*XeLT~P8viXEdr@YFO6V8U7`4mUVJA{M9S2WA8y@s|2xlr-{*P0-_Q4X8896&ORoUEIieaxj8?3PBD>Fp4l)9T@e$z)q(?$yGkrT)@4S zO8?lb_Ex+0hCyw+M_vfHjRde2$S`F`1C9o-c8Mb&ovs;tnY69437Ys8A=1;m(?{YG zB`R`nf1#dX4s!v-gYRLD=|tNWcN>89(C-bMZfW84M61(%fu%H!O@t#vXt4;8yIv2i z$ZWu<@wi!QMRn_@lT52qVD~k=NU1fPrUF+wQ3tq}U^NZUR+v_ouJZ2vbV1H_8hg@gD}Jk4$fz3Ec=vkUM~dz=48{30Hwi^0M)xS`($-8mY(qJns#JQ5R8uR0i_+NXwCGU7GgEcKVvXU# zzS-r1XNIMIy>j_#grDO9N^U9h-Y~}CYE+$2R4+q4$~5bDEEoP{g_b;F?Mol&_6q8P zlO;1V%r^|bape-|UCO?eZVY(<&uph&kWk6s1RKMndAz@T zT(@&!0AKvtSb<0eeT!KSara=&LyKbgiSB;~#b~4)7R~Kl0NkSiO1OO8#%;*1$;%R_ zn(SF-hEVh`VGJ~`GU5%b(&)<^5&2y-+~XGHwF989km;On>UD{$zKs0Hf;ON?YEO^>~NX?aW9C* zIzkz3wz%0TaphuG7co#7BwF!fQE#DnrBx(CPhIMl8VmMsit0B%Pdx1}Bp$&z_|rTY z*v>ERk#bJ(A9ufM3q@K{Jn_F^)LQDqhcz|6cp*Ek+fuZ)SB3oZO95T}&K|>q7UBq{#X|^jE}`!)OfKkjNoT9N1wP6L5H5H*{ZcY zL)TkRZCQ%*sTj>dTD(aFd`!-~;+PlfJa{M9xG#Be6#GDB!p}#6i)2P^`TVMlb!Kc~ z_q8G2-Tw3z>_G@!s`w4+_W3Wp;r5PVM#?ycS1EdgVs)EQ+DK9XR|8v)Jv#SK!C|?3 zF4*Lei-Q_IU_Wa5zrw|ue*)OwH?^$I^&J8sQeNyoTA>L;z?gvNjn_LIzBDd5fEC<3 zF&A_8NQ|Zd!=wLg{uJz_1x|%C;)m~!MS+THD*<=Kz|&Cd`D`ga4mSKVPuF297h+cm z(X8|l|3HZmaC2Lwgt5Z~mT`pt_~6&g?!+`ue~4ZhWeOE<1;k{>?ywB|JO6P|w#$-H z`~NMslQewp3~X7-ICcN4K&9+{@a4qr`bdgGf4>zTO|hYzUss>2tDr~%)qz5_-~KcL zWFjT-sZGqW;u9!v*ld`vDD;fns55W3a#O%5UiNU+FMA)m7obP~1a!vv)_p-4KXz&X OeFXRg`?gTx3;qYyVz7Du delta 1976 zcmZWqc~p{#7XQc%as#qVLw%o0uE{f_%u1i18=`sKT3pd&Tq2j$22m3%R2nPE#4$9q znle*_TqsS`8Z#I*vywJcx{r&De=ML!i>a#8B=X^cg zL$hEXFC+RbJvJATT2}6tAc+TANGd#dv;FH6B=@T0#TI|XSqEpDgJ76Di_o);buJFE zEffPzvEn4bjjT#$kAqCSGWTQGYx>_^_3`aFw|?-gE=d;smkyBzo%Z3Qq7F2xo4cb5 z)V&SRn}Y~3o?!le4Eq19caRJJYdcYFL=;;xp>)p@z#*BP)IteRxBZ}MJa051`Lr(;u;N^okNmw}zFomTV zFuts0d9)4W0|9-HI_mh!`Vwaud>`E{n9B~R@6=}0W}x~UzcK#Vn`s(F6yIsVll0KI z#Z_+`FGB6`{mtE-fE1$}X-)xRI-N)nigdT*9)p6)d}e0&$46ysDsWf5`^ad%U*YYg zgHWS@nF~e83|tpivmMz;to$+%FuU=txky8pFNzoWtv*%Ed5y1Op>u#I^BY@^h=QQM z`FvVZQ#v%5YQBtqmejhW-hJ&f3Hy7@F08!ojq%(1Pfrz%S1 zo!Ax?p-7;HU70?_yo2N=t2x(Lr|CAVn=nfr&>sD%fJZ__uXUOQOaWnN&;<9@!7~@G z$*JXr*?A6GbVLxuQ;R9|o8V4po&HF&n6|nyJ$*{Rz6X4=Mx4=Y#Wz>5(XlF5^BPT9 zhF#<-Y5y!y?89MGimIwb@Av$=^eBvlUPuI1+SN_gnW?>Uman$p1#KNTSLlg`u)yyr z_{ndq33Dv(jj@PfIAtME2j(_;WAy0q6A^TwGKrExrBQO3zjYHXX0hxMG4I%l=hDde zgu>l9_VCGP`j#>RFEVC!OM_aIbdeMS8 zFj90}MgU(PrHtVs>x=W4LXQVFpQls7alrXNgn~NBjmpxFg%JtS4x@a#0P@~lTf>%D zztvhjj(`Upkz2>&dLOl6jI^}sP3!r}6C-vSi&&4FHl{}Ub|8iv7%!*4-x`)Z`J*^b%Lfd?cGQU{w!ESK|h=wzH9&8kkjDjD5l8-HyKH3w!ygnrNs6Uu_NUj--V+ z;2!n|G`*nLQK!f{_DpL~d6^d8y>Cl>JdB~%ig(iw543?QSm<;ba8HjL=+OpBo)z_p zI}Jcjgal?|1suN6SBxYniUuD3-n?>s2k1{gC`}k1%lmI!sk%h2Q*3--PTZjCH*X74 z!PkfuoHq4iK*FmbHdcb*~$TNK*6?6gM|MF{>k_2 z<@uK9g_g5|h=!2!N+r5f_V;4V;fm`3JpO#eG>J zC!`p8l}`&tw1uhdp}4WhGm?=amz(?HK~Vb6Ars@wt%DawEV4$6lrP=+(A_v9K`&2k z$*l_BOMZ3O#YqyplmF724-v-TN^8bKcD7`-R{g&qSpFz(EClW&G%<#y+ zAhGwD$^ddK!~Q6rdt*fZ1zAcJ<*fhly?9+MPZ#zTf9>pGA*cT-lbzckaZ=*TVoRb{ zH+7GxHaK@QQRe`BNSa%DXc1$(t3h(fZJ_JMt8)oAb)w)BfkeUGQ9FOQcyL&p({>hj z)II|`g@nG1hY-apz@NOq>cd*ANpFcwWhZ=)>K4pI$|h@Yo~|W~(zPntJ0qiMYBgzk z1VV&7-U5=b-}YGiWaf;|i<8f$p)?3S_+dBy7CPMosA{P3Pw{J7^ehj;7_>Hfe#B?RDCX3^ E0lqGqC;$Ke diff --git a/tests/images/paths/pathInvertedCornerArc.png b/tests/images/paths/pathInvertedCornerArc.png index 50691b0322a17733227da848d72b02cf63cdbe1c..bad937245e757059e99aeb5d540ba71d67212cde 100644 GIT binary patch delta 858 zcmV-g1Eu`c2i^yeB!8tzL_t(|0qxsM%N#`*hT$9IDWZ5n5K&2OOweouHHc7)inws2 z!E7W73IWMZ5EU&bur`V!ih`md5=aEaLj)IzAR@K%2e@s-A0W7{g;bbNMia-G>6z~L z>pU0Ca;9qPo>O<%>|p|GhI9w&lL(TWzcY8`4+SwIIe&k5?#k~*rqjz*-XVu_ zSAOla5=KlV$l=_TUyQ72FH;F}BzNVAM zrQDSdjjU-eQwfrDSKjKi5=QL$AT`9hfR}SuUNf?$z3loR$@yo1M-;?}8I1RL)y#JseBS_A_J^#yUo`MXlhPWU2q<@{ay4cVPGO*Y)G|XU$d=U*+qLpH zmK{bxwy1{qAn+A%`Le?($d=R)ZwJ~w-5XXJLP55m{SylK0cd-zR-zyqHoM00IS|%) zXa(7z8h_%gz&W_^Ebg)tWJ79*_W?fv2l_muf^0wy@fh$e&^EmFq98rh5Z?sa>GTV( zZLC8<)>K2hE;;`V&<^}IMnTq4Lu|VY+n4u4YutSy1zAD6^qQQ1cXom4mKE;0GzD2g z+nt`A&v5?uqFvM28UNOD@Rl3;Jks{?+9C zXTY;?{)>9F2=gWvq=tA$a{k`r{J!M;vE+P6&Og~UHYeww1KtOY%(miOIlmYdOfE?7 z$|9e+E5FTdC3ofL+?DThS5D@xeA=F{<(q{U#U||f|EW)nlVAfUlTZUH2@5f}J%f)Z k3n`Ov0~V7|0~i*60j7P4$vLHQbN~PV07*qoM6N<$g4#oz!2kdN delta 854 zcmV-c1F8Jp2h|6VB!8SqL_t(|0qxvPs~klbhT&UdH28s{27(`G=|WtI2udJSOpy>o zj6%eq0d=FeP&ckjVS%*@`2bxAi7OF579uJH1$840{sotfD|c>ODTPQK=fE(@$(b`f z)BT=4&jrIQW~QsAZswz>XJ!{8P(r*ex6QvJxGRhzNOJy`+335KS&ACo1VZ^o%QbK$HIF;MxeIsky$+i!YoPP>9t{_Gv z=U)dNR}dqT^MB`oy$WJL3GpFd?X^Qe3`owu1{|(aQ4j-?^YyLnJ#{JyGO)?p-P6D| zbt(!nu;l!^z=8E?D9C_Hh&KSA0XMEsLqP_VoIeS?G(80c8BlWm6JX!;6cl72CB)}} zw`Ye}kO7ns>t}CY)jjjs;T2>6$@!Oox-)ng1?gD{@qcFEYv9%^4Xq$Oo9vYz*=Se= z=}`&s3E<-TUipTiv{ZELr_o8t?^h zY>`bX$bS-*5bJ@aZ-8eP-K>HvNeS^b;3we8E}K-4B`6_2IEnFS*Uc$NMn;9d)_?d1G{} z{u!A|Zku0n+g!?RbFtoG%cs7=uUh@CtQew`Zv!Hea04Nea03kr$W_t1UA>PeSEZA0 g10s`f0}K}Y2ihKi!X|l8E&u=k07*qoM6N<$f}`S(ApigX diff --git a/tests/images/paths/pathRotatedArc.png b/tests/images/paths/pathRotatedArc.png index a4d3a326b41f058c58f77aa83f7fb4b9b9f8e0c1..f197060a316fa47e4b57d77a75fd082d747a9a16 100644 GIT binary patch delta 1194 zcmeyvIgM+AO8s3=7srqa#<#O$PlURPwB4WLbok67dEFIang25rCW`7ND_^p25p|8K zjdT(W5vbUh7+5TNw(K^5ML`$G0A<0*wvf1p7w3{>zqyKktgYMp z|Ju#9?0j>IEz;Aj*`%MJ7ipZXC=*0UidX4K;l``dAD28m2Pjv3%GekT3K|)M=0XlKLN?W$w$%uH{(S zRm9xD&zQrwVNIg`T)qU`hW6Hq)dK5<9<6uk+Ha~;u9cw!bXFu%zTlQt?i9fPcJSx9yT>;0Y))3d7Ob6T< z&b`?A@R#z1g>S4B?@0&!+4j8tm$hnihk!kELh|ypvt{S<X->EJ9l9l=f{N;9;vh4y4<0o=u{)!@LTHY9lc-7H#Lr!bAC)$JUaiD z?mqjXRvT`gV@26>I3Fk~#;F|H?bJ1OE8DrFdw{N}G-?oM(3!FNL*s?ASndaT^@{&& zmi+kZ@b*N~J*So*F%11_SFh=-zjO-i;BOJJf4luvO;O7W-;(UQse2cNpZ@B!C#d88 z+%`oW_YU_Y+YJ+s)rEzIU;8P1?fpc9G!sTjfWU?fRx zU_0RI^e4^ZqepX5l&X8jvz~3tljCn~DK>1lx^>Ea9Tww!Ma84`tv?(YymeVFFo`o< z`1?;*Y}wJzVv9I7rd5{O8i|E5?f|-0BFbysgwqT!a%wAPLS^M=+;r@LWO>a7h_+DbXvF5z{8Krs#bB>SNK#%O|+q`muEyFg( zh)bo;>~7oFmOlKEb-i-En^Q@Y#*tg$yNcwkcez;#1zkDZIQODQ$0BylS8v{P{;GQN z!t3AL36Jirdz7Q&Qf&Wq?~`Ziq%t|)>QDU?*-f{5 zwwfw_?eYHKRF*!Q*QCd<)%5<-`mn6tYb_dzN7p-d`Ty^iTYD-XYO)_oOwbvws`SM7 jrdFFDIrTgw5q;$6Uw8fAnIeB>1|aZs^>bP0l+XkKpE5F^ literal 1276 zcmeAS@N?(olHy`uVBq!ia0vp^DImdAN77wRAAbujqKhJfGYA&;eKOgRLoV-4s1KBw}}PC~53F z)vC}Wbka%KJ5ZobP>E%J@)T>6$dd-av9`6#|5qm@%ro8otRy`A?yk)ueVQ>HhWJsG zxX~Im>6RZ_igDUURE}i7`n}qz%U?4e`1slBk5+>^!=3N;+u2)w zL@L$=bZqDTn5Foye51FPT1o_O7{^C$hHuO}SPxh+EcY!q5zCNZ+aTRIafy0s#bN<{ z!AFY~k4_fea#6aALo%#6mf;@r2HuQ2&u%i9G47B(ko;AkTxZD-@BF&2h9+V__w+MZ zF!a=&xU^!b+R9d*)dy7>;^tgDqcuG{%djf< zr``66dNZOr1YWoP`KQ-3cM|t&#WyJ)A0KPl-8|U1Xb4NDA&8Aa*WUw+wa4(-qvf8>1a(r-(rFvcCl^K!4m@Fr{)*e@FN zXLsGY;-Z#0><<>6Q`SE$y30`Q2>V(crQ#MD_5~}2eZ#*n+CDDl`uG~?sa@|bTyb*h za#q~O+_3J(??b5!dF#$kxxf3(l`Bpniu?E^dJon*UH-Yd<%h%c!{O@$bbWSg^*75h zN!;bz5&!y?(|qj|#sim2)qfp(RrPJD_^z!g47k`># zx7c_+e_H6HS-LKQNpm-6=Z4Lw4o#l1zkLrp?uY}o4b6;$k zb3udoOfG9C$KA!(7rn1l+&cMlmX3?yBYg%Nt_MD~cZyANx9BbT;U@i8N6DpM>~AwK z!(29fwlDmyQHqsIO47SI0$Y9xeeP$M91p8 X`z|rWtqZ6GmTC;1u6{1-oD!M-2rlOg4${ST6C5mxLML&mg9_Tg!9k}YsI`Nj=pZ;a6p0*LX(c!5IZ1`D z&G$e^ayj?hob%>qOAjW+lf3~Ke{>wA4*LQe4{$!f)c|(_gf6-b2G|>5TYy@G&z&@G zJ4hWi2N(y&fFa-@u%nX~vr45kr?sa&xENp{z|#mn+gVRrLF#Z4m<0B>bI~G`jYdySo(drY#ZT6?j95c z32-0SlRLTndE@Sm^WP|0vV+v&2yi`XR$FRUfaeiDExydR_1$ziJVhPg4_W5awfF~y%9zmj#elMalUZ5 z7E**0HJEKN<{#NXk_evy^a6*{b6eI(4aS!pwSPoWkR-qhU?XrKJ-4RDfNSZSrRj(Y z#X-^qB1{H&1`Gn5D(3BGGr&*{Zl%2{-H75Ke?^!La2xmvd|5cYb>Y6-yapaK|MhgC z2JhP4fVP7q5#}PijPNkdzawI#D{tx3koF+$#a!B(c6AiQR-S{1aS$;MBE~_)IEWYr f5tFY01s2S2npjzF?$%$%00000NkvXXu0mjfgLE70 delta 608 zcmV-m0-ycB2C4>-BWMEgNkl9MXMv}{aGi^;b7@Z|0(_6~ru1Hm zgEZj+a1&^hUaXI#y%`JeE5c04eHR63!YJ^_F|r*FFdgAT?u_gpO0(G|7q$ zMYx+Yt0YMJW;K|zsCC#F;d;l}3ME0VRm`_MgAroR(2^h_XF;pxldl1IlfD5Tf6V^~ zlK!qD`=`lmnLiP(E<4jj_67$y32g4N&N{_f`5fVP&SC>OgOUKFz>f6TicLp&oHMH= z$R=PsXHn~LE5cmI*$O2=-UBCqp^9~^yRX1xgx}qVEXfX%77Q>C99f!f8O}FgCSzPn zc90~(Y=9A9S9)$eO*Pn+<;SQuSz$fI7nNB*8v^?Uw|#ZaQn4BH4EGUCK~Xn?jF<*l0=wKVItg* u5c~W{dyw`beetPnRY7d+IgyYQ7W@VM5?5;AHv=R90000 Date: Fri, 22 Jan 2021 08:56:18 -0600 Subject: [PATCH 3/4] like quad, cubic --- src/pixie/paths.nim | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index f4d7320..c183e8b 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -346,29 +346,28 @@ proc commandsToPolygons*(commands: seq[PathCommand]): seq[seq[Vec2]] = result = vec2(cos(a) * arc.radii.x, sin(a) * arc.radii.y) result = arc.rotMat * result + arc.center - proc discretize(arc: ArcParams, steps: int) = + var prev = at + + proc discretize(arc: ArcParams, i, steps: int) = let - initialShapeLen = polygon.len step = arc.delta / steps.float32 - var prev = at - for i in 1 .. steps: - let - aPrev = arc.theta + step * (i - 1).float32 - a = arc.theta + step * i.float32 - next = arc.compute(a) - halfway = arc.compute(aPrev + (a - aPrev) / 2) - midpoint = (prev + next) / 2 - error = (midpoint - halfway).length - if error >= 0.25: - # Error too large, try again with doubled precision - polygon.setLen(initialShapeLen) - discretize(arc, steps * 2) - return + aPrev = arc.theta + step * (i - 1).float32 + a = arc.theta + step * i.float32 + next = arc.compute(a) + halfway = arc.compute(aPrev + (a - aPrev) / 2) + midpoint = (prev + next) / 2 + error = (midpoint - halfway).length + + if error >= 0.25: + # Error too large, try again with doubled precision + discretize(arc, i * 2 - 1, steps * 2) + discretize(arc, i * 2, steps * 2) + else: drawLine(prev, next) prev = next let arc = endpointToCenterArcParams(at, radii, rotation, large, sweep, to) - discretize(arc, 4) + discretize(arc, 1, 1) for command in commands: case command.kind From e676121ae32bc6564f4a0821a3ecb49423cf6e45 Mon Sep 17 00:00:00 2001 From: Ryan Oldenburg Date: Fri, 22 Jan 2021 10:31:04 -0600 Subject: [PATCH 4/4] arc small stuff --- src/pixie/paths.nim | 46 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index c183e8b..f060909 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -875,56 +875,52 @@ proc arc*(path: Path) = proc arcTo*(path: Path, x1, y1, x2, y2, r: float32) = ## Adds a circular arc to the path with the given control points and radius, connected to the previous point by a straight line. - var - x0 = path.at.x - y0 = path.at.y - x21 = x2 - x1 - y21 = y2 - y1 - x01 = x0 - x1 - y01 = y0 - y1 - l01_2 = x01 * x01 + y01 * y01 + const epsilon = 1e-6.float32 - const epsilon: float32 = 1e-6 + if path.commands.len == 0: + # Is this path empty? Move to (x1,y1). + path.moveTo(x1, y1) var r = r if r < 0: # Is the radius negative? Flip it. r = -r - if path.commands.len == 0: - # Is this path empty? Move to (x1,y1). - path.commands.add(PathCommand(kind: Move, numbers: @[x1,y1])) + var + x21 = x2 - x1 + y21 = y2 - y1 + x01 = path.at.x - x1 + y01 = path.at.y - y1 + l01_2 = x01 * x01 + y01 * y01 - elif not (l01_2 > epsilon): - # Or, is (x1,y1) coincident with (x0,y0)? Do nothing. + if not (l01_2 > epsilon): + # Is (x1,y1) coincident with (x0,y0)? Do nothing. discard elif not (abs(y01 * x21 - y21 * x01) > epsilon) or r == 0: - # // Or, are (x0,y0), (x1,y1) and (x2,y2) collinear? + # // Or, are (x0,y0), (x1,y1) and (x2,y2) colinear? # // Equivalently, is (x1,y1) coincident with (x2,y2)? # // Or, is the radius zero? Line to (x1,y1). - path.commands.add(PathCommand(kind: Line, numbers: @[x1, y1])) - path.at.x = x1 - path.at.y = y1 + path.lineTo(x1, y1) else: - # Otherwise, draw an arc! + # Draw an arc var - x20 = x2 - x0 - y20 = y2 - y0 + x20 = x2 - path.at.x + y20 = y2 - path.at.y l21_2 = x21 * x21 + y21 * y21 l20_2 = x20 * x20 + y20 * y20 l21 = sqrt(l21_2) l01 = sqrt(l01_2) - l:float32 = r * tan((PI - arccos((l21_2 + l01_2 - l20_2) / (2 * l21 * l01))) / 2) + l = r * tan((PI - arccos((l21_2 + l01_2 - l20_2) / (2 * l21 * l01))) / 2).float32 t01 = l / l01 t21 = l / l21 - # If the start tangent is not coincident with (x0,y0), line to. + # If the start tangent is not coincident with path.at, line to. if abs(t01 - 1) > epsilon: - path.commands.add(PathCommand(kind: Line, numbers: @[x1 + t01 * x01, y1 + t01 * y01])) - discard + path.lineTo(x1 + t01 * x01, y1 + t01 * y01) + path.at.x = x1 + t21 * x21 path.at.y = y1 + t21 * y21 path.commands.add(PathCommand(