-module(svg_path@bezier).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/svg_path/bezier.gleam").
-export([linear_bezier_data/2, quadratic_bezier_data/3, cubic_bezier_data/4, bezier_start/1, bezier_end/1, bezier_point/2, bezier_derivative/2, bezier_bounding_box/1, map_points/2, split_bezier/2, split_bezier_inside/2, split_bezier_many/2, split_bezier_inside_many/2, cubic_inflection_parameters/1]).
-export_type([point/0, bounding_box/0, bezier_data/0, error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Lower-level helpers for Bezier curves.\n"
"\n"
" Most users should work with `svg_path.Line`, `svg_path.QuadraticBezier`,\n"
" and `svg_path.CubicBezier` values through the root module. This module is\n"
" the more technical layer for users who want the curve math behind line and\n"
" Bezier segments.\n"
"\n"
" A line segment is a degree-1 Bezier curve, a quadratic Bezier has one\n"
" control point, and a cubic Bezier has two control points. All evaluation\n"
" and splitting helpers use the standard Bezier parameter `t`:\n"
"\n"
" - `bezier_point(curve, at: 0.0)` is the curve start point.\n"
" - `bezier_point(curve, at: 1.0)` is the curve end point.\n"
" - `bezier_derivative(curve, at: t)` is the derivative with respect to `t`.\n"
" - `split_bezier(curve, at: t)` preserves the curve degree and divides it\n"
" with de Casteljau's algorithm.\n"
" - `map_points(curve, with: f)` maps the curve's defining points.\n"
"\n"
" The `at` value is not clamped. Values outside `0.0..1.0` extrapolate along\n"
" the same polynomial curve. `split_bezier` follows the same unclamped policy;\n"
" use `split_bezier_inside` when outside values should return an error.\n"
" `split_bezier_many` and `split_bezier_inside_many` sort their split points,\n"
" remove exact duplicates, and trim boundary `0.0` or `1.0` split points that\n"
" would only create zero-length boundary curves.\n"
"\n"
" `map_points` maps the control points that define the curve. For nonlinear\n"
" functions, this is not the exact image of every point on the rendered curve;\n"
" it is the Bezier curve obtained by applying the function to the defining\n"
" points.\n"
).
-type point() :: {point, float(), float()}.
-type bounding_box() :: {bounding_box, point(), point()}.
-type bezier_data() :: {linear_bezier_data, point(), point()} |
{quadratic_bezier_data, point(), point(), point()} |
{cubic_bezier_data, point(), point(), point(), point()}.
-type error() :: split_outside_bezier.
-file("src/svg_path/bezier.gleam", 65).
?DOC(" Create linear Bezier data.\n").
-spec linear_bezier_data(point(), point()) -> bezier_data().
linear_bezier_data(Start, End) ->
{linear_bezier_data, Start, End}.
-file("src/svg_path/bezier.gleam", 70).
?DOC(" Create quadratic Bezier data.\n").
-spec quadratic_bezier_data(point(), point(), point()) -> bezier_data().
quadratic_bezier_data(Start, Control, End) ->
{quadratic_bezier_data, Start, Control, End}.
-file("src/svg_path/bezier.gleam", 79).
?DOC(" Create cubic Bezier data.\n").
-spec cubic_bezier_data(point(), point(), point(), point()) -> bezier_data().
cubic_bezier_data(Start, Control1, Control2, End) ->
{cubic_bezier_data, Start, Control1, Control2, End}.
-file("src/svg_path/bezier.gleam", 89).
?DOC(" Return the curve's start point.\n").
-spec bezier_start(bezier_data()) -> point().
bezier_start(Curve) ->
case Curve of
{linear_bezier_data, Start, _} ->
Start;
{quadratic_bezier_data, Start, _, _} ->
Start;
{cubic_bezier_data, Start, _, _, _} ->
Start
end.
-file("src/svg_path/bezier.gleam", 98).
?DOC(" Return the curve's end point.\n").
-spec bezier_end(bezier_data()) -> point().
bezier_end(Curve) ->
case Curve of
{linear_bezier_data, _, End} ->
End;
{quadratic_bezier_data, _, _, End} ->
End;
{cubic_bezier_data, _, _, _, End} ->
End
end.
-file("src/svg_path/bezier.gleam", 589).
-spec interpolate(point(), point(), float()) -> point().
interpolate(Start, End, T) ->
{point,
erlang:element(2, Start) + ((erlang:element(2, End) - erlang:element(
2,
Start
))
* T),
erlang:element(3, Start) + ((erlang:element(3, End) - erlang:element(
3,
Start
))
* T)}.
-file("src/svg_path/bezier.gleam", 111).
?DOC(
" Evaluate a Bezier curve at parameter `t`.\n"
"\n"
" `t` is not clamped. `0.0` evaluates the start of the curve, `1.0` evaluates\n"
" the end of the curve, and values outside that range extrapolate along the\n"
" same polynomial curve.\n"
).
-spec bezier_point(bezier_data(), float()) -> point().
bezier_point(Curve, T) ->
case Curve of
{linear_bezier_data, Start, End} ->
interpolate(Start, End, T);
{quadratic_bezier_data, Start@1, Control, End@1} ->
interpolate(
interpolate(Start@1, Control, T),
interpolate(Control, End@1, T),
T
);
{cubic_bezier_data, Start@2, Control1, Control2, End@2} ->
Left = interpolate(Start@2, Control1, T),
Middle = interpolate(Control1, Control2, T),
Right = interpolate(Control2, End@2, T),
interpolate(
interpolate(Left, Middle, T),
interpolate(Middle, Right, T),
T
)
end.
-file("src/svg_path/bezier.gleam", 604).
-spec scale(point(), float()) -> point().
scale(Point, Factor) ->
{point,
erlang:element(2, Point) * Factor,
erlang:element(3, Point) * Factor}.
-file("src/svg_path/bezier.gleam", 596).
-spec difference(point(), point()) -> point().
difference(Left, Right) ->
{point,
erlang:element(2, Left) - erlang:element(2, Right),
erlang:element(3, Left) - erlang:element(3, Right)}.
-file("src/svg_path/bezier.gleam", 136).
?DOC(" Return the derivative with respect to Bezier parameter `t`.\n").
-spec bezier_derivative(bezier_data(), float()) -> point().
bezier_derivative(Curve, T) ->
case Curve of
{linear_bezier_data, Start, End} ->
difference(End, Start);
{quadratic_bezier_data, Start@1, Control, End@1} ->
scale(
interpolate(
difference(Control, Start@1),
difference(End@1, Control),
T
),
2.0
);
{cubic_bezier_data, Start@2, Control1, Control2, End@2} ->
Left = difference(Control1, Start@2),
Middle = difference(Control2, Control1),
Right = difference(End@2, Control2),
scale(
interpolate(
interpolate(Left, Middle, T),
interpolate(Middle, Right, T),
T
),
3.0
)
end.
-file("src/svg_path/bezier.gleam", 510).
-spec include_point(bounding_box(), point()) -> bounding_box().
include_point(Box, Point) ->
{bounding_box,
{point,
gleam@float:min(
erlang:element(2, erlang:element(2, Box)),
erlang:element(2, Point)
),
gleam@float:min(
erlang:element(3, erlang:element(2, Box)),
erlang:element(3, Point)
)},
{point,
gleam@float:max(
erlang:element(2, erlang:element(3, Box)),
erlang:element(2, Point)
),
gleam@float:max(
erlang:element(3, erlang:element(3, Box)),
erlang:element(3, Point)
)}}.
-file("src/svg_path/bezier.gleam", 506).
-spec is_inside_unit_interval(float()) -> boolean().
is_inside_unit_interval(T) ->
(T >= +0.0) andalso (T =< 1.0).
-file("src/svg_path/bezier.gleam", 499).
-spec linear_roots(float(), float()) -> list(float()).
linear_roots(A, B) ->
case A =:= +0.0 of
true ->
[];
false ->
[case A of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> (+0.0 - B) / Gleam@denominator
end]
end.
-file("src/svg_path/bezier.gleam", 447).
-spec quadratic_roots(float(), float(), float()) -> list(float()).
quadratic_roots(A, B, C) ->
case A =:= +0.0 of
true ->
linear_roots(B, C);
false ->
Discriminant = (B * B) - ((4.0 * A) * C),
case Discriminant < +0.0 of
true ->
[];
false ->
Root_discriminant@1 = case gleam@float:square_root(
Discriminant
) of
{ok, Root_discriminant} -> Root_discriminant;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"svg_path/bezier"/utf8>>,
function => <<"quadratic_roots"/utf8>>,
line => 456,
value => _assert_fail,
start => 13837,
'end' => 13903,
pattern_start => 13848,
pattern_end => 13869})
end,
Denominator = 2.0 * A,
case Root_discriminant@1 =:= +0.0 of
true ->
[case Denominator of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> (+0.0 - B) / Gleam@denominator
end];
false ->
[case Denominator of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> ((+0.0 - B) - Root_discriminant@1)
/ Gleam@denominator@1
end, case Denominator of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> ((+0.0 - B) + Root_discriminant@1)
/ Gleam@denominator@2
end]
end
end
end.
-file("src/svg_path/bezier.gleam", 409).
-spec cubic_extrema(float(), float(), float(), float()) -> list(float()).
cubic_extrema(Start, Control1, Control2, End) ->
A = (((+0.0 - Start) + (3.0 * Control1)) - (3.0 * Control2)) + End,
B = ((3.0 * Start) - (6.0 * Control1)) + (3.0 * Control2),
C = (3.0 * Control1) - (3.0 * Start),
quadratic_roots(3.0 * A, 2.0 * B, C).
-file("src/svg_path/bezier.gleam", 400).
-spec quadratic_extrema(float(), float(), float()) -> list(float()).
quadratic_extrema(Start, Control, End) ->
Denominator = (Start - (2.0 * Control)) + End,
case Denominator =:= +0.0 of
true ->
[];
false ->
[case Denominator of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> (Start - Control) / Gleam@denominator
end]
end.
-file("src/svg_path/bezier.gleam", 383).
-spec bezier_extrema(bezier_data()) -> list(float()).
bezier_extrema(Curve) ->
_pipe = case Curve of
{linear_bezier_data, _, _} ->
[];
{quadratic_bezier_data, Start, Control, End} ->
lists:append(
quadratic_extrema(
erlang:element(2, Start),
erlang:element(2, Control),
erlang:element(2, End)
),
quadratic_extrema(
erlang:element(3, Start),
erlang:element(3, Control),
erlang:element(3, End)
)
);
{cubic_bezier_data, Start@1, Control1, Control2, End@1} ->
lists:append(
cubic_extrema(
erlang:element(2, Start@1),
erlang:element(2, Control1),
erlang:element(2, Control2),
erlang:element(2, End@1)
),
cubic_extrema(
erlang:element(3, Start@1),
erlang:element(3, Control1),
erlang:element(3, Control2),
erlang:element(3, End@1)
)
)
end,
gleam@list:filter(_pipe, fun is_inside_unit_interval/1).
-file("src/svg_path/bezier.gleam", 163).
?DOC(" Return the curve's exact axis-aligned bounding box over `0.0..1.0`.\n").
-spec bezier_bounding_box(bezier_data()) -> bounding_box().
bezier_bounding_box(Curve) ->
Points = begin
_pipe = [+0.0, 1.0 | bezier_extrema(Curve)],
gleam@list:map(_pipe, fun(T) -> bezier_point(Curve, T) end)
end,
{First@1, Rest@1} = case Points of
[First | Rest] -> {First, Rest};
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"svg_path/bezier"/utf8>>,
function => <<"bezier_bounding_box"/utf8>>,
line => 168,
value => _assert_fail,
start => 5378,
'end' => 5413,
pattern_start => 5389,
pattern_end => 5404})
end,
_pipe@1 = Rest@1,
gleam@list:fold(
_pipe@1,
{bounding_box, First@1, First@1},
fun include_point/2
).
-file("src/svg_path/bezier.gleam", 178).
?DOC(
" Map a Bezier curve's defining points.\n"
"\n"
" For nonlinear functions, this is not the exact image of every point on the\n"
" rendered curve. It maps the control polygon and preserves the curve degree.\n"
).
-spec map_points(bezier_data(), fun((point()) -> point())) -> bezier_data().
map_points(Curve, F) ->
case Curve of
{linear_bezier_data, Start, End} ->
{linear_bezier_data, F(Start), F(End)};
{quadratic_bezier_data, Start@1, Control, End@1} ->
{quadratic_bezier_data, F(Start@1), F(Control), F(End@1)};
{cubic_bezier_data, Start@2, Control1, Control2, End@2} ->
{cubic_bezier_data, F(Start@2), F(Control1), F(Control2), F(End@2)}
end.
-file("src/svg_path/bezier.gleam", 201).
?DOC(
" Split a Bezier curve at parameter `t`.\n"
"\n"
" `t` is not clamped. Values outside `0.0..1.0` extrapolate along the same\n"
" polynomial curve, matching `bezier_point`.\n"
).
-spec split_bezier(bezier_data(), float()) -> {bezier_data(), bezier_data()}.
split_bezier(Curve, T) ->
case Curve of
{linear_bezier_data, Start, End} ->
Split = interpolate(Start, End, T),
{{linear_bezier_data, Start, Split},
{linear_bezier_data, Split, End}};
{quadratic_bezier_data, Start@1, Control, End@1} ->
Start_control = interpolate(Start@1, Control, T),
Control_end = interpolate(Control, End@1, T),
Split@1 = interpolate(Start_control, Control_end, T),
{{quadratic_bezier_data, Start@1, Start_control, Split@1},
{quadratic_bezier_data, Split@1, Control_end, End@1}};
{cubic_bezier_data, Start@2, Control1, Control2, End@2} ->
Start_control@1 = interpolate(Start@2, Control1, T),
Controls = interpolate(Control1, Control2, T),
Control_end@1 = interpolate(Control2, End@2, T),
Left_control = interpolate(Start_control@1, Controls, T),
Right_control = interpolate(Controls, Control_end@1, T),
Split@2 = interpolate(Left_control, Right_control, T),
{{cubic_bezier_data,
Start@2,
Start_control@1,
Left_control,
Split@2},
{cubic_bezier_data,
Split@2,
Right_control,
Control_end@1,
End@2}}
end.
-file("src/svg_path/bezier.gleam", 254).
?DOC(
" Split a Bezier curve at parameter `t`, returning an error outside `0.0..1.0`.\n"
"\n"
" Values exactly at `0.0` or `1.0` are accepted and produce one zero-length\n"
" curve.\n"
).
-spec split_bezier_inside(bezier_data(), float()) -> {ok,
{bezier_data(), bezier_data()}} |
{error, error()}.
split_bezier_inside(Curve, T) ->
case (T < +0.0) orelse (T > 1.0) of
true ->
{error, split_outside_bezier};
false ->
{ok, split_bezier(Curve, T)}
end.
-file("src/svg_path/bezier.gleam", 565).
-spec trim_reversed_end_progress(list(float())) -> list(float()).
trim_reversed_end_progress(Points) ->
case Points of
[1.0 | Rest] ->
trim_reversed_end_progress(Rest);
_ ->
Points
end.
-file("src/svg_path/bezier.gleam", 558).
-spec trim_end_progress(list(float())) -> list(float()).
trim_end_progress(Points) ->
_pipe = Points,
_pipe@1 = lists:reverse(_pipe),
_pipe@2 = trim_reversed_end_progress(_pipe@1),
lists:reverse(_pipe@2).
-file("src/svg_path/bezier.gleam", 551).
-spec trim_start_progress(list(float())) -> list(float()).
trim_start_progress(Points) ->
case Points of
[+0.0 | Rest] ->
trim_start_progress(Rest);
_ ->
Points
end.
-file("src/svg_path/bezier.gleam", 572).
-spec insert_unique_progress(list(float()), float()) -> list(float()).
insert_unique_progress(Sorted, Point) ->
case Sorted of
[] ->
[Point];
[First | Rest] ->
case Point =:= First of
true ->
Sorted;
false ->
case Point =< First of
true ->
[Point | Sorted];
false ->
[First | insert_unique_progress(Rest, Point)]
end
end
end.
-file("src/svg_path/bezier.gleam", 517).
-spec sort_unique_progresses(list(float())) -> list(float()).
sort_unique_progresses(Points) ->
case Points of
[] ->
[];
[First | Rest] ->
_pipe = sort_unique_progresses(Rest),
insert_unique_progress(_pipe, First)
end.
-file("src/svg_path/bezier.gleam", 376).
-spec normalized_progresses(list(float())) -> list(float()).
normalized_progresses(Points) ->
_pipe = Points,
_pipe@1 = sort_unique_progresses(_pipe),
_pipe@2 = trim_start_progress(_pipe@1),
trim_end_progress(_pipe@2).
-file("src/svg_path/bezier.gleam", 608).
-spec offset(point(), point(), float()) -> point().
offset(Point, Direction, Distance) ->
{point,
erlang:element(2, Point) + (erlang:element(2, Direction) * Distance),
erlang:element(3, Point) + (erlang:element(3, Direction) * Distance)}.
-file("src/svg_path/bezier.gleam", 339).
-spec bezier_between(bezier_data(), float(), float()) -> bezier_data().
bezier_between(Curve, From, To) ->
Start = bezier_point(Curve, From),
End = bezier_point(Curve, To),
Delta = To - From,
case Curve of
{linear_bezier_data, _, _} ->
{linear_bezier_data, Start, End};
{quadratic_bezier_data, _, _, _} ->
{quadratic_bezier_data,
Start,
offset(Start, bezier_derivative(Curve, From), Delta / 2.0),
End};
{cubic_bezier_data, _, _, _, _} ->
{cubic_bezier_data,
Start,
offset(Start, bezier_derivative(Curve, From), Delta / 3.0),
offset(End, bezier_derivative(Curve, To), +0.0 - (Delta / 3.0)),
End}
end.
-file("src/svg_path/bezier.gleam", 319).
-spec split_bezier_between_progresses(
bezier_data(),
float(),
list(float()),
list(bezier_data())
) -> list(bezier_data()).
split_bezier_between_progresses(Curve, Previous, Points, Pieces) ->
case Points of
[] ->
lists:reverse([bezier_between(Curve, Previous, 1.0) | Pieces]);
[Next | Rest] ->
split_bezier_between_progresses(
Curve,
Next,
Rest,
[bezier_between(Curve, Previous, Next) | Pieces]
)
end.
-file("src/svg_path/bezier.gleam", 312).
-spec split_bezier_at_progresses(bezier_data(), list(float())) -> list(bezier_data()).
split_bezier_at_progresses(Curve, Points) ->
split_bezier_between_progresses(Curve, +0.0, Points, []).
-file("src/svg_path/bezier.gleam", 270).
?DOC(
" Split a Bezier curve at multiple parameter values.\n"
"\n"
" Split points are sorted, exact duplicates are removed, and boundary `0.0`\n"
" or `1.0` split points are trimmed when they would only create zero-length\n"
" boundary curves. Values outside `0.0..1.0` are allowed and extrapolate along\n"
" the same polynomial curve, matching `split_bezier`.\n"
).
-spec split_bezier_many(bezier_data(), list(float())) -> list(bezier_data()).
split_bezier_many(Curve, Points) ->
split_bezier_at_progresses(Curve, normalized_progresses(Points)).
-file("src/svg_path/bezier.gleam", 282).
?DOC(
" Split a Bezier curve at multiple parameter values, erroring outside `0.0..1.0`.\n"
"\n"
" Split points are sorted, exact duplicates are removed, and boundary `0.0`\n"
" or `1.0` split points are trimmed when they would only create zero-length\n"
" boundary curves. Values exactly at `0.0` or `1.0` are accepted.\n"
).
-spec split_bezier_inside_many(bezier_data(), list(float())) -> {ok,
list(bezier_data())} |
{error, error()}.
split_bezier_inside_many(Curve, Points) ->
Points@1 = normalized_progresses(Points),
case gleam@list:any(Points@1, fun(T) -> (T < +0.0) orelse (T > 1.0) end) of
true ->
{error, split_outside_bezier};
false ->
{ok, split_bezier_at_progresses(Curve, Points@1)}
end.
-file("src/svg_path/bezier.gleam", 534).
-spec unique_close_progresses(list(float()), float(), list(float())) -> list(float()).
unique_close_progresses(Points, Previous, Kept) ->
case Points of
[] ->
Kept;
[Point | Rest] ->
case gleam@float:absolute_value(Point - Previous) =< 0.000000001 of
true ->
unique_close_progresses(Rest, Previous, Kept);
false ->
unique_close_progresses(Rest, Point, [Point | Kept])
end
end.
-file("src/svg_path/bezier.gleam", 525).
-spec sort_unique_close_progresses(list(float())) -> list(float()).
sort_unique_close_progresses(Points) ->
case gleam@list:sort(Points, fun gleam@float:compare/2) of
[] ->
[];
[First | Rest] ->
_pipe = unique_close_progresses(Rest, First, [First]),
lists:reverse(_pipe)
end.
-file("src/svg_path/bezier.gleam", 612).
-spec cross(point(), point()) -> float().
cross(Left, Right) ->
(erlang:element(2, Left) * erlang:element(3, Right)) - (erlang:element(
3,
Left
)
* erlang:element(2, Right)).
-file("src/svg_path/bezier.gleam", 472).
-spec tolerant_quadratic_roots(float(), float(), float()) -> list(float()).
tolerant_quadratic_roots(A, B, C) ->
case gleam@float:absolute_value(A) < 0.000000000001 of
true ->
case gleam@float:absolute_value(B) < 0.000000000001 of
true ->
[];
false ->
[case B of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> (+0.0 - C) / Gleam@denominator
end]
end;
false ->
Discriminant = (B * B) - ((4.0 * A) * C),
case Discriminant < +0.0 of
true ->
[];
false ->
Root_discriminant@1 = case gleam@float:square_root(
Discriminant
) of
{ok, Root_discriminant} -> Root_discriminant;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"svg_path/bezier"/utf8>>,
function => <<"tolerant_quadratic_roots"/utf8>>,
line => 486,
value => _assert_fail,
start => 14657,
'end' => 14723,
pattern_start => 14668,
pattern_end => 14689})
end,
Denominator = 2.0 * A,
[case Denominator of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@1 -> ((+0.0 - B) - Root_discriminant@1)
/ Gleam@denominator@1
end, case Denominator of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator@2 -> ((+0.0 - B) + Root_discriminant@1)
/ Gleam@denominator@2
end]
end
end.
-file("src/svg_path/bezier.gleam", 600).
-spec add(point(), point()) -> point().
add(Left, Right) ->
{point,
erlang:element(2, Left) + erlang:element(2, Right),
erlang:element(3, Left) + erlang:element(3, Right)}.
-file("src/svg_path/bezier.gleam", 422).
-spec inflection_roots(point(), point(), point(), point()) -> list(float()).
inflection_roots(Start, Control1, Control2, End) ->
A = add(
difference(scale(Control1, 3.0), Start),
difference(End, scale(Control2, 3.0))
),
B = add(
difference(scale(Start, 3.0), scale(Control1, 6.0)),
scale(Control2, 3.0)
),
C = difference(scale(Control1, 3.0), scale(Start, 3.0)),
tolerant_quadratic_roots(
-6.0 * cross(A, B),
6.0 * cross(C, A),
2.0 * cross(C, B)
).
-file("src/svg_path/bezier.gleam", 302).
?DOC(
" Return the Bezier parameters of a cubic curve's inflection points.\n"
"\n"
" A cubic Bezier can have up to two inflection points. Values outside\n"
" `0.0..1.0`, values too close to the endpoints, and numerically duplicate\n"
" roots are not returned. Splitting at these parameters gives pieces with no\n"
" interior inflection, which is often the useful first step before treating\n"
" each piece as a convex curve plus its chord. Linear and quadratic curves\n"
" return an empty list.\n"
).
-spec cubic_inflection_parameters(bezier_data()) -> list(float()).
cubic_inflection_parameters(Curve) ->
case Curve of
{cubic_bezier_data, Start, Control1, Control2, End} ->
_pipe = inflection_roots(Start, Control1, Control2, End),
_pipe@1 = gleam@list:filter(
_pipe,
fun(T) ->
(T > 0.000000001) andalso (T < (1.0 - 0.000000001))
end
),
sort_unique_close_progresses(_pipe@1);
{linear_bezier_data, _, _} ->
[];
{quadratic_bezier_data, _, _, _} ->
[]
end.