From b1cbbaef5f2942cd5839e12e1e6901b0010c09a9 Mon Sep 17 00:00:00 2001 From: klsruan Date: Sun, 7 Apr 2024 12:15:15 -0300 Subject: [PATCH] Update Shapery --- macros/ILL.Shapery.moon | 352 +--------------------------------------- 1 file changed, 8 insertions(+), 344 deletions(-) diff --git a/macros/ILL.Shapery.moon b/macros/ILL.Shapery.moon index 3535747..4f1380c 100644 --- a/macros/ILL.Shapery.moon +++ b/macros/ILL.Shapery.moon @@ -1,6 +1,6 @@ export script_name = "Shapery" export script_description = "Does several types of shape manipulations from the simplest to the most complex" -export script_version = "2.5.3" +export script_version = "2.5.4" export script_author = "ILLTeam" export script_namespace = "ILL.Shapery" @@ -19,7 +19,7 @@ if haveDepCtrl } { "ILL.ILL" - version: "1.4.4" + version: "1.4.5" url: "https://github.com/TypesettingTools/ILL-Aegisub-Scripts/" feed: "https://raw.githubusercontent.com/TypesettingTools/ILL-Aegisub-Scripts/main/DependencyControl.json" } @@ -32,316 +32,12 @@ else ILL = require "ILL.ILL" {:Aegi, :Ass, :Config, :Line, :Curve, :Path, :Point, :Util, :Math, :Table, :Util} = ILL --- https://github.com/colinmeinke/svg-arc-to-cubic-bezier -arc2CubicBezier = (info) -> - TAU = math.pi * 2 - - mapToEllipse = (p, rx, ry, cosphi, sinphi, centerx, centery) -> - p.x *= rx - p.y *= ry - xp = cosphi * p.x - sinphi * p.y - yp = sinphi * p.x + cosphi * p.y - return Point xp + centerx, yp + centery - - approxUnitArc = (ang1, ang2) -> - -- If 90 degree circular arc, use a constant - -- as derived from http://spencermortensen.com/articles/bezier-circle - a = ang2 == 1.5707963267948966 and 0.551915024494 or (ang2 == -1.5707963267948966 and -0.551915024494 or 4 / 3 * math.tan(ang2 / 4)) - - x1 = math.cos ang1 - y1 = math.sin ang1 - x2 = math.cos ang1 + ang2 - y2 = math.sin ang1 + ang2 - - return { - Point x1 - y1 * a, y1 + x1 * a - Point x2 + y2 * a, y2 - x2 * a - Point x2, y2 - } - - vectorAngle = (ux, uy, vx, vy) -> - sign = (ux * vy - uy * vx < 0) and -1 or 1 - dot = ux * vx + uy * vy - if dot > 1 - dot = 1 - if dot < -1 - dot = -1 - return sign * math.acos dot - - getArcCenter = (px, py, cx, cy, rx, ry, largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp) -> - rxsq = rx ^ 2 - rysq = ry ^ 2 - pxpsq = pxp ^ 2 - pypsq = pyp ^ 2 - - radicant = rxsq * rysq - rxsq * pypsq - rysq * pxpsq - if radicant < 0 - radicant = 0 - - radicant = radicant / (rxsq * pypsq + rysq * pxpsq) - radicant = math.sqrt(radicant) * (largeArcFlag == sweepFlag and -1 or 1) - - centerxp = radicant * rx / ry * pyp - centeryp = radicant * -ry / rx * pxp - - centerx = cosphi * centerxp - sinphi * centeryp + (px + cx) / 2 - centery = sinphi * centerxp + cosphi * centeryp + (py + cy) / 2 - - vx1 = (pxp - centerxp) / rx - vy1 = (pyp - centeryp) / ry - vx2 = (-pxp - centerxp) / rx - vy2 = (-pyp - centeryp) / ry - - ang1 = vectorAngle 1, 0, vx1, vy1 - ang2 = vectorAngle vx1, vy1, vx2, vy2 - - if sweepFlag == 0 and ang2 > 0 - ang2 -= TAU - - if sweepFlag == 1 and ang2 < 0 - ang2 += TAU - - return {centerx, centery, ang1, ang2} - - {:px, :py, :cx, :cy, :rx, :ry, :xAxisRotation, :largeArcFlag, :sweepFlag} = info - xAxisRotation or= 0 - largeArcFlag or= 0 - sweepFlag or= 0 - - if rx == 0 or ry == 0 - return {} - - sinphi = math.sin xAxisRotation * TAU / 360 - cosphi = math.cos xAxisRotation * TAU / 360 - - pxp = cosphi * (px - cx) / 2 + sinphi * (py - cy) / 2 - pyp = -sinphi * (px - cx) / 2 + cosphi * (py - cy) / 2 - - if pxp == 0 and pyp == 0 - return {} - - rx = math.abs rx - ry = math.abs ry - - lambda = (pxp ^ 2) / (rx ^ 2) + (pyp ^ 2) / (ry ^ 2) - if lambda > 1 - rx *= math.sqrt lambda - ry *= math.sqrt lambda - - {centerx, centery, ang1, ang2} = getArcCenter px, py, cx, cy, rx, ry, largeArcFlag, sweepFlag, sinphi, cosphi, pxp, pyp - - ratio = math.abs(ang2) / (TAU / 4) - if math.abs(1 - ratio) < 1e-7 - ratio = 1 - - segments = math.max math.ceil(ratio), 1 - - ang2 /= segments - - curves = {} - for i = 1, segments - table.insert curves, approxUnitArc ang1, ang2 - ang1 += ang2 - - result = {} - for i = 1, #curves - curve = curves[i] - {x: x1, y: y1} = mapToEllipse curve[1], rx, ry, cosphi, sinphi, centerx, centery - {x: x2, y: y2} = mapToEllipse curve[2], rx, ry, cosphi, sinphi, centerx, centery - {:x, :y} = mapToEllipse curve[3], rx, ry, cosphi, sinphi, centerx, centery - table.insert result, {x1, y1, x2, y2, x, y} - - return result - -sweepAngularPoint = (a, b, c) -> - center = a\lerp c, 0.5 - - cos_pi = math.cos math.pi - sin_pi = math.sin math.pi - - p = Point! - p.x = cos_pi * (b.x - center.x) - sin_pi * (b.y - center.y) + center.x - p.y = sin_pi * (b.x - center.x) + cos_pi * (b.y - center.y) + center.y - return p - --- https://stackoverflow.com/a/40444735 -getSweepFlag = (S, V, E) -> - getAngle = (a, b, c) -> - angle_1 = math.atan2 a.y - b.y, a.x - b.x - angle_2 = math.atan2 c.y - b.y, c.x - b.x - angle_3 = angle_2 - angle_1 - return (angle_3 + 3 * math.pi) % (2 * math.pi) - math.pi - return getAngle(E, S, V) > 0 and 0 or 1 - --- https://stackoverflow.com/a/24780108 -getProportionPoint = (point, segment, length, d) -> - factor = segment / length - return Point point.x - d.x * factor, point.y - d.y * factor - -modeRoundingAbsolute = (radius, p1, angularPoint, p2) -> - -- Vector 1 - v1 = Point angularPoint.x - p1.x, angularPoint.y - p1.y - - -- Vector 2 - v2 = Point angularPoint.x - p2.x, angularPoint.y - p2.y - - -- Angle between vector 1 and vector 2 - angle = math.atan2(v1.y, v1.x) - math.atan2(v2.y, v2.x) - - -- The length of segment between angular point and the - -- points of intersection with the circle of a given radius - abs_tan = math.abs math.tan angle / 2 - segment = radius / abs_tan - - -- Check the segment - length1 = v1\vecMagnitude! - length2 = v2\vecMagnitude! - - length = math.min(length1, length2) / 2 - if segment > length - segment = length - radius = length * abs_tan - - -- Points of intersection are calculated by the proportion between - -- the coordinates of the vector, length of vector and the length of the segment. - p1Cross = getProportionPoint angularPoint, segment, length1, v1 - p2Cross = getProportionPoint angularPoint, segment, length2, v2 - - -- Calculation of the coordinates of the circle - -- center by the addition of angular vectors. - c = Point! - c.x = angularPoint.x * 2 - p1Cross.x - p2Cross.x - c.y = angularPoint.y * 2 - p1Cross.y - p2Cross.y - - L = c\vecMagnitude! - d = Point(segment, radius)\vecMagnitude! - - circlePoint = getProportionPoint angularPoint, d, L, c - - -- StartAngle and EndAngle of arc - startAngle = math.atan2 p1Cross.y - circlePoint.y, p1Cross.x - circlePoint.x - endAngle = math.atan2 p2Cross.y - circlePoint.y, p2Cross.x - circlePoint.x - - -- Sweep angle - sweepAngle = endAngle - startAngle - - -- Some additional checks - if sweepAngle < 0 - startAngle = endAngle - sweepAngle = -sweepAngle - - if sweepAngle > math.pi - sweepAngle = -(2 * math.pi - sweepAngle) - - degreeFactor = 180 / math.pi - sweepFlag = getSweepFlag p1Cross, angularPoint, p2Cross - - return { - line1: {p1, p1Cross} - line2: {p2, p2Cross} - arc: { - :sweepFlag - rx: radius - ry: radius - start_angle: startAngle * degreeFactor - end_angle: sweepAngle * degreeFactor - } - } - -modeRoundingRelative = (radius, inverted, a, b, c) -> - {:line1, :line2} = modeRoundingAbsolute radius, a, b, c - p1 = line1[2] - p2 = b\clone! - p3 = line2[2] - if inverted - p2 = sweepAngularPoint p1, p2, p3 - c1 = p1\lerp p2, 0.5 - c2 = p2\lerp p3, 0.5 - return p1, c1, c2, p3 - -modeSpike = (radius, a, b, c, path) -> - {:line1, :line2} = modeRoundingAbsolute radius, a, b, c - p1 = line1[2] - p3 = line2[2] - p2 = sweepAngularPoint p1, b, p3 - table.insert path, p1 - table.insert path, p2 - table.insert path, p3 - -modeChamfer = (radius, a, b, c, path) -> - {:line1, :line2} = modeRoundingAbsolute radius, a, b, c - p1 = line1[2] - p3 = line2[2] - table.insert path, p1 - table.insert path, p3 - -makeRoundingAbsolute = (r, inverted, a, b, c, path) -> - {:line1, :line2, :arc} = modeRoundingAbsolute r, a, b, c - curves = arc2CubicBezier { - px: line1[2].x - py: line1[2].y - cx: line2[2].x - cy: line2[2].y - rx: arc.rx - ry: arc.ry - sweepFlag: inverted and 1 - arc.sweepFlag or arc.sweepFlag - } - table.insert path, Point line1[2].x, line1[2].y - for curve in *curves - table.insert path, Point curve[1], curve[2], "b" - table.insert path, Point curve[3], curve[4], "b" - table.insert path, Point curve[5], curve[6], "b" - -makeRoundingRelative = (r, inverted, a, b, c, path) -> - p1, c1, c2, p4 = modeRoundingRelative r, inverted, a, b, c - table.insert path, Point p1.x, p1.y - table.insert path, Point c1.x, c1.y, "b" - table.insert path, Point c2.x, c2.y, "b" - table.insert path, Point p4.x, p4.y, "b" - -shadow3D = (shape, xshad, yshad) -> - -- sorts the points to clockwise - toClockWise = (points) -> - cx, cy, n = 0, 0, #points - for {:x, :y} in *points - cx += x - cy += y - cx /= n - cy /= n - table.sort points, (a, b) -> - a1 = (math.deg(math.atan2(a.x - cx, a.y - cy)) + 360) % 360 - a2 = (math.deg(math.atan2(b.x - cx, b.y - cy)) + 360) % 360 - return a1 < a2 - return unpack points - pathA = Path shape - pathA\closeContours! - pathA\flatten! - pathB = pathA\clone! - pathB\move xshad, yshad - pathsClipperA = pathA\convertToClipper! - newPathsClipper = Clipper.paths.new! - for i = 1, #pathA.path - pa = pathA.path[i] - pb = pathB.path[i] - for j = 1, #pa - 1 - newPathClipper = Clipper.path.new! - newPathClipper\push toClockWise {pa[j], pa[j + 1], pb[j + 1], pb[j]} - newPathsClipper\add newPathClipper - return Path.convertFromClipper(pathsClipperA\union newPathsClipper)\export! - -shadowInner = (shape, xshad, yshad) -> - pathA = Path shape - pathB = pathA\clone! - pathB\move xshad, yshad - pathA\difference pathB - return pathA\export! - checkPathClockWise = (path) -> sum = 0 for i = 1, #path currPoint = path[i] nextPoint = path[(i % #path) + 1] - sum = sum + (nextPoint.x - currPoint.x) * (nextPoint.y + currPoint.y) + sum += (nextPoint.x - currPoint.x) * (nextPoint.y + currPoint.y) return sum < 0 interfaces = { @@ -612,7 +308,7 @@ UtilitiesDialog = (sub, sel, activeLine) -> when "3D from shadow", "Inner shadow" xshad, yshad = Line.solveShadow line if shadow == "3D from shadow" - line.shape = shadow3D line.shape, xshad, yshad + line.shape = Path(line.shape)\shadow(xshad, yshad, "3D")\export! else -- adds the current line, removing unnecessary tags line.tags\remove "shadow", "4c" @@ -621,7 +317,8 @@ UtilitiesDialog = (sub, sel, activeLine) -> -- adds the shadow color to the first color and sets -- the new value for the shape line.tags\insert {{"c", data.color4}} - line.shape = shadowInner line.shape, xshad, yshad + + line.shape = Path(line.shape)\shadow(xshad, yshad, "inner")\export! when "3D from shape" if n < 2 or n > 2 Aegi.progressCancel "You must select 2 lines." @@ -629,7 +326,7 @@ UtilitiesDialog = (sub, sel, activeLine) -> {xshad, yshad} = data.pos return else - line.shape = shadow3D line.shape, xshad - data.pos[1], yshad - data.pos[2] + line.shape = Path(line.shape)\shadow(xshad - data.pos[1], yshad - data.pos[2], "3D")\export! line.tags\remove "shad", "xshad", "yshad", "4c" line.tags\insert "\\shad0" ass\insertLine line, s @@ -640,40 +337,7 @@ UtilitiesDialog = (sub, sel, activeLine) -> ass\removeLine l, s Line.extend ass, l Line.callBackExpand ass, l, nil, (line) -> - path = Path line.shape - path\openContours! - newPath = Path! - for contour in *path.path - newContour, len = {}, #contour - for i = 1, len - j = i % len + 1 - k = (i + 1) % len + 1 - -- points that form a possible corner - a = contour[i] - b = contour[j] - c = contour[k] - -- checks if the start point is equal to the end point of the last segment, if it is a bezier segment - -- for example, this happens for the letter S with the Arial font - if i == len and a.id == "b" and b.id == "l" and a\equals b - table.insert newContour, 1, a - -- if the id value of point b(angle point) is "l", this means it is a corner - elseif b.id == "l" and c.id == "l" - if cornerStyle == "Rounded" or inverted - if rounding == "Absolute" - makeRoundingAbsolute radius, inverted, a, b, c, newContour - elseif rounding == "Relative" - makeRoundingRelative radius, inverted, a, b, c, newContour - elseif cornerStyle == "Spike" - modeSpike radius, a, b, c, newContour - elseif cornerStyle == "Chamfer" - modeChamfer radius, a, b, c, newContour - -- this is not a corner, add the angle point and continue - else - if i == 1 and not b.id == "l" - table.insert newContour, a - table.insert newContour, b - table.insert newPath.path, newContour - line.shape = newPath\export! + line.shape = Path.RoundingPath(line.shape, radius, inverted, cornerStyle, rounding)\export! ass\insertLine line, s return ass\getNewSelection!