Skip to content

Commit 1d13d3b

Browse files
committed
[[ CanvasPathArcTo ]] Add best-fit version of path "arc to ..." command.
1 parent 82a4207 commit 1d13d3b

5 files changed

Lines changed: 236 additions & 2 deletions

File tree

engine/src/canvas.mlc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,6 +2675,7 @@ public foreign handler MCCanvasPathLineTo(in pPoint as Point, inout xPath as Pat
26752675
public foreign handler MCCanvasPathCurveThroughPoint(in pThrough as Point, in pTo as Point, inout xPath as Path) returns nothing binds to "<builtin>"
26762676
public foreign handler MCCanvasPathCurveThroughPoints(in pThroughA as Point, in pThroughB as Point, in pTo as Point, inout xPath as Path) returns nothing binds to "<builtin>"
26772677

2678+
public foreign handler MCCanvasPathEllipticArcToWithRadiiAsList(in pTo as Point, in pRadii as List, in pRotation as CanvasFloat, inout xPath as Path) returns nothing binds to "<builtin>"
26782679
public foreign handler MCCanvasPathEllipticArcToWithFlagsWithRadiiAsList(in pTo as Point, in pRadii as List, in pRotation as CanvasFloat, in pLargest as bool, in pClockwise as bool, inout xPath as Path) returns nothing binds to "<builtin>"
26792680

26802681
public foreign handler MCCanvasPathClosePath(inout xPath as Path) returns nothing binds to "<builtin>"
@@ -2787,6 +2788,52 @@ begin
27872788
end syntax
27882789

27892790

2791+
/*
2792+
Summary: Adds an arc to a path.
2793+
2794+
mEnd: An expression which evaluates to a point.
2795+
mRadii: An expression which evaluates to a list of numbers.
2796+
mAngle: An expression which evaluates to a number.
2797+
mPath: An expression which evaluates to a path.
2798+
2799+
Description: Adds an arc from the previous point to <mEnd> on <mPath>, following a section of an ellipse with the given radii & angle.
2800+
As there can be two different ellipses that match the parameters, and two potential arcs for each ellipse, this variation of "arc to ..." will select the arc that most closely matches the direction from the last point to the current position on <mPath>.
2801+
2802+
Example:
2803+
// Construct a path tracing out a rectangle with rounded bottom corners.
2804+
variable tPath
2805+
put the empty path into tPath
2806+
2807+
// Begin a new subpath
2808+
move to point [0, 0] on tPath
2809+
2810+
// Trace the left edge
2811+
line to point [0, my height - 25] on tPath
2812+
2813+
// Continue path with an arc to the bottom edge
2814+
arc to point [my height, 25] with radii [25, 25] rotated by 0 on tPath
2815+
2816+
// Trace the bottom edge
2817+
line to point [my width - 25, my height] on tPath
2818+
2819+
// Continue path with an arc to the right edge
2820+
arc to point [my width, my height - 25] with radii [25, 25] rotated by 0 on tPath
2821+
2822+
// Trace the right edge
2823+
line to point [my width, 0] on tPath
2824+
2825+
// Close the path with a line back to the starting point
2826+
close path on tPath
2827+
2828+
Tags: Canvas
2829+
*/
2830+
syntax PathOperationEllipticArcTo is statement
2831+
"arc" "to" <mEnd: Expression> "with" "radii" <mRadii: Expression> "rotated" "by" <mAngle: Expression> "on" <mPath: Expression>
2832+
begin
2833+
MCCanvasPathEllipticArcToWithRadiiAsList(mEnd, mRadii, mAngle, mPath)
2834+
end syntax
2835+
2836+
27902837
/*
27912838
Summary: Closes the current subpath of a path.
27922839

engine/src/module-canvas.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3720,6 +3720,38 @@ void MCCanvasPathEllipticArcToWithFlagsWithRadiiAsList(MCCanvasPointRef p_to, MC
37203720
MCGPathRelease(t_path);
37213721
}
37223722

3723+
void MCCanvasPathEllipticArcToWithRadiiAsList(MCCanvasPointRef p_to, MCProperListRef p_radii, MCCanvasFloat p_rotation, MCCanvasPathRef &x_path)
3724+
{
3725+
bool t_success;
3726+
t_success = true;
3727+
3728+
MCGPathRef t_path;
3729+
t_path = nil;
3730+
3731+
if (t_success)
3732+
{
3733+
MCGPathMutableCopy(*MCCanvasPathGet(x_path), t_path);
3734+
t_success = MCGPathIsValid(t_path);
3735+
}
3736+
3737+
MCGPoint t_radii;
3738+
if (t_success)
3739+
t_success = MCProperListToRadii(p_radii, t_radii);
3740+
3741+
if (t_success)
3742+
{
3743+
MCGPathArcToBestFit(t_path, MCGSizeMake(t_radii.x, t_radii.y), p_rotation, *MCCanvasPointGet(p_to));
3744+
t_success = MCGPathIsValid(t_path);
3745+
}
3746+
3747+
if (t_success)
3748+
MCCanvasPathSetMCGPath(t_path, x_path);
3749+
3750+
MCGPathRelease(t_path);
3751+
}
3752+
3753+
3754+
37233755
////////////////////////////////////////////////////////////////////////////////
37243756

37253757
// Effect

engine/src/module-canvas.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,9 @@ extern "C" MC_DLLEXPORT void MCCanvasPathCurveThroughPoint(MCCanvasPointRef p_th
441441
extern "C" MC_DLLEXPORT void MCCanvasPathCurveThroughPoints(MCCanvasPointRef p_through_a, MCCanvasPointRef p_through_b, MCCanvasPointRef p_to, MCCanvasPathRef &x_path);
442442
extern "C" MC_DLLEXPORT void MCCanvasPathClosePath(MCCanvasPathRef &x_path);
443443

444+
extern "C" MC_DLLEXPORT void MCCanvasPathEllipticArcToWithFlagsWithRadiiAsList(MCCanvasPointRef p_to, MCProperListRef p_radii, MCCanvasFloat p_rotation, bool p_largest, bool p_clockwise, MCCanvasPathRef &x_path);
445+
extern "C" MC_DLLEXPORT void MCCanvasPathEllipticArcToWithRadiiAsList(MCCanvasPointRef p_to, MCProperListRef p_radii, MCCanvasFloat p_rotation, MCCanvasPathRef &x_path);
446+
444447
//////////
445448

446449
// Effect

libgraphics/include/graphics.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,7 @@ void MCGPathLineTo(MCGPathRef path, MCGPoint end_point);
782782
void MCGPathQuadraticTo(MCGPathRef path, MCGPoint control_point, MCGPoint end_point);
783783
void MCGPathCubicTo(MCGPathRef path, MCGPoint first_control_point, MCGPoint second_control_point, MCGPoint end_point);
784784
void MCGPathArcTo(MCGPathRef path, MCGSize radii, MCGFloat rotation, bool large_arc, bool sweep, MCGPoint end_point);
785+
void MCGPathArcToBestFit(MCGPathRef path, MCGSize p_radii, MCGFloat p_rotation, MCGPoint p_end_point);
785786
void MCGPathCloseSubpath(MCGPathRef path);
786787

787788
void MCGPathThicken(MCGPathRef path, const MCGStrokeAttr& attr, MCGPathRef& r_thick_path);

libgraphics/src/path.cpp

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,16 @@ static inline MCGFloat MCGAngleBetweenVectors(const MCGPoint &u, const MCGPoint
752752
return t_s * acosf(MCClamp(MCGVectorDotProduct(u, v) / (MCGVectorMagnitude(u) * MCGVectorMagnitude(v)), -1, 1));
753753
}
754754

755+
static inline MCGPoint MCGVectorMake(const MCGPoint p_start, MCGPoint p_end)
756+
{
757+
return MCGPointMake(p_end.x - p_start.x, p_end.y - p_start.y);
758+
}
759+
760+
static inline MCGPoint MCGVectorRotate90(const MCGPoint &p_vector)
761+
{
762+
return MCGPointMake(-p_vector.y, p_vector.x);
763+
}
764+
755765
//////////
756766

757767
static inline MCGPoint MCGPointGetMidPoint(const MCGPoint &p_a, const MCGPoint &p_b)
@@ -774,8 +784,6 @@ static void _MCGPathEllipticArc(MCGPathRef self, const MCGSize &p_radii, MCGFloa
774784
if (MCGPointIsEqual(t_current, p_end_point))
775785
return;
776786

777-
p_angle = fmodf(p_angle + 2 * M_PI, 2 * M_PI);
778-
779787
MCGPoint t_mid;
780788
t_mid = MCGPointGetMidPoint(t_current, p_end_point);
781789

@@ -834,6 +842,132 @@ static void _MCGPathEllipticArc(MCGPathRef self, const MCGSize &p_radii, MCGFloa
834842
_MCGPathArcTo(self, kMCGPathArcContinue, t_center, t_radii, t_start_angle, t_sweep, p_angle);
835843
}
836844

845+
static MCGPoint _MCGEllipseTangentLineVector(const MCGPoint &p_center, const MCGSize &p_radii, const MCGPoint &p_tangent_point)
846+
{
847+
MCGPoint t_tangent_a;
848+
t_tangent_a = MCGPointScale(MCGPointTranslate(p_tangent_point, -p_center.x, -p_center.y), 1 / p_radii.width, 1 / p_radii.height);
849+
850+
MCGPoint t_tangent_vector;
851+
t_tangent_vector = MCGVectorRotate90(MCGVectorMake(t_tangent_a, MCGPointMake(0,0)));
852+
853+
MCGPoint t_tangent_b;
854+
t_tangent_b = MCGPointTranslate(t_tangent_a, t_tangent_vector.x, t_tangent_vector.y);
855+
t_tangent_b = MCGPointTranslate(MCGPointScale(t_tangent_b, p_radii.width, p_radii.height), p_center.x, p_center.y);
856+
857+
return MCGVectorMake(p_tangent_point, t_tangent_b);
858+
}
859+
860+
static void _MCGPathEllipticArc(MCGPathRef self, const MCGSize &p_radii, MCGFloat p_angle, const MCGPoint &p_end_point)
861+
{
862+
if (p_radii.width == 0 || p_radii.height == 0)
863+
{
864+
MCGPathLineTo(self, p_end_point);
865+
return;
866+
}
867+
868+
MCGPoint t_current;
869+
if (!MCGPathGetCurrentPoint(self, t_current))
870+
t_current = MCGPointMake(0, 0);
871+
872+
if (MCGPointIsEqual(t_current, p_end_point))
873+
return;
874+
875+
MCGPoint t_mid;
876+
t_mid = MCGPointGetMidPoint(t_current, p_end_point);
877+
878+
MCGPoint t_p;
879+
t_p = MCGPointRotate(MCGPointTranslate(t_current, -t_mid.x, -t_mid.y), -p_angle);
880+
881+
MCGSize t_radii;
882+
t_radii = MCGSizeMake(MCAbs(p_radii.width), MCAbs(p_radii.height));
883+
884+
MCGFloat t_rx2, t_ry2, t_x2, t_y2;
885+
t_rx2 = _sqr(t_radii.width);
886+
t_ry2 = _sqr(t_radii.height);
887+
t_x2 = _sqr(t_p.x);
888+
t_y2 = _sqr(t_p.y);
889+
890+
MCGFloat t_a;
891+
t_a = (t_x2 / t_rx2) + (t_y2 / t_ry2);
892+
893+
MCGPoint t_c;
894+
895+
// Adjust if radii are too small
896+
if (t_a > 1)
897+
{
898+
t_radii = MCGSizeScale(t_radii, sqrtf(t_a));
899+
900+
t_c = MCGPointMake(0, 0);
901+
}
902+
else
903+
{
904+
MCGFloat t_scale;
905+
t_scale = sqrtf((t_rx2 * t_ry2 - t_rx2 * t_y2 - t_ry2 * t_x2) / (t_rx2 * t_y2 + t_ry2 * t_x2));
906+
907+
t_c = MCGPointMake(t_scale * t_radii.width * t_p.y / t_radii.height, t_scale * -t_radii.height * t_p.x / t_radii.width);
908+
}
909+
910+
MCGPoint t_previous;
911+
if (!MCGPathGetPreviousPoint(self, t_previous))
912+
t_previous = MCGPointMake(0, 0);
913+
914+
bool t_large, t_sweep;
915+
if (MCGPointIsEqual(t_current, t_previous))
916+
{
917+
t_large = false;
918+
t_sweep = true;
919+
}
920+
else
921+
{
922+
MCGPoint t_current_vector;
923+
t_current_vector = MCGVectorMake(MCGPointRotate(MCGPointTranslate(t_previous, -t_mid.x, -t_mid.y), -p_angle), t_p);
924+
925+
MCGPoint t_tangent_vector_a, t_tangent_vector_b;
926+
t_tangent_vector_a = _MCGEllipseTangentLineVector(t_c, t_radii, t_p);
927+
t_tangent_vector_b = _MCGEllipseTangentLineVector(MCGPointScale(t_c, -1), t_radii, t_p);
928+
929+
MCGFloat t_a_positive, t_a_negative, t_b_positive, t_b_negative;
930+
t_a_positive = MCAbs(MCGAngleBetweenVectors(t_current_vector, t_tangent_vector_a));
931+
t_a_negative = M_PI - t_a_positive;
932+
t_b_positive = MCAbs(MCGAngleBetweenVectors(t_current_vector, t_tangent_vector_b));
933+
t_b_negative = M_PI - t_b_positive;
934+
935+
if (MCMin(t_a_positive, t_a_negative) <= MCMin(t_b_positive, t_b_negative))
936+
{
937+
t_sweep = t_a_positive > t_a_negative;
938+
t_large = !t_sweep;
939+
}
940+
else
941+
{
942+
t_sweep = t_b_positive > t_b_negative;
943+
t_large = t_sweep;
944+
}
945+
}
946+
947+
if (t_large == t_sweep)
948+
t_c = MCGPointScale(t_c, -1);
949+
950+
MCGPoint t_center;
951+
t_center = MCGPointTranslate(MCGPointRotate(t_c, p_angle), t_mid.x, t_mid.y);
952+
953+
MCGPoint t_v0, t_v1, t_v2;
954+
t_v0 = MCGPointMake(1, 0);
955+
t_v1 = MCGPointMake((t_p.x - t_c.x) / t_radii.width, (t_p.y - t_c.y) / t_radii.height);
956+
t_v2 = MCGPointMake((-t_p.x - t_c.x) / t_radii.width, (-t_p.y - t_c.y) / t_radii.height);
957+
958+
MCGFloat t_start_angle, t_sweep_angle;
959+
t_start_angle = MCGAngleBetweenVectors(t_v0, t_v1);
960+
t_sweep_angle = MCGAngleBetweenVectors(t_v1, t_v2);
961+
t_sweep_angle = fmodf(t_sweep_angle, 2 * M_PI);
962+
963+
if (!t_sweep && t_sweep_angle > 0)
964+
t_sweep_angle -= 2 * M_PI;
965+
else if (t_sweep && t_sweep_angle < 0)
966+
t_sweep_angle += 2 * M_PI;
967+
968+
_MCGPathArcTo(self, kMCGPathArcContinue, t_center, t_radii, t_start_angle, t_sweep_angle, p_angle);
969+
}
970+
837971
////////////////////////////////////////////////////////////////////////////////
838972

839973
void MCGPathArcTo(MCGPathRef self, MCGSize p_radii, MCGFloat p_rotation, bool p_large_arc, bool p_sweep, MCGPoint p_end_point)
@@ -853,6 +987,23 @@ void MCGPathArcTo(MCGPathRef self, MCGSize p_radii, MCGFloat p_rotation, bool p_
853987
self -> is_valid = t_success;
854988
}
855989

990+
void MCGPathArcToBestFit(MCGPathRef self, MCGSize p_radii, MCGFloat p_rotation, MCGPoint p_end_point)
991+
{
992+
if (!MCGPathIsValid(self))
993+
return;
994+
995+
bool t_success;
996+
t_success = true;
997+
998+
if (t_success)
999+
t_success = self -> is_mutable;
1000+
1001+
if (t_success)
1002+
_MCGPathEllipticArc(self, p_radii, p_rotation * M_PI / 180, p_end_point);
1003+
1004+
self -> is_valid = t_success;
1005+
}
1006+
8561007
void MCGPathCloseSubpath(MCGPathRef self)
8571008
{
8581009
if (!MCGPathIsValid(self))

0 commit comments

Comments
 (0)