Bézier curves/Intersections: Difference between revisions

Content added Content deleted
(→‎{{header|D}}: Added a note about nasty things one can run into.)
(Added Modula-2)
Line 586: Line 586:
</syntaxhighlight>
</syntaxhighlight>


{{out}}
<pre>(%i2) b02(t):=1-2*t+t^2
<pre>(%i2) b02(t):=1-2*t+t^2
(%i3) b12(t):=2*t-2*t^2
(%i3) b12(t):=2*t-2*t^2
Line 619: Line 620:


</pre>
</pre>

=={{header|Modula-2}}==
{{works with|GCC|13.1.1}}

Compile with the "-fiso" flag.

This program is similar to the [[#D|D]] but, instead of using control points to form rectangles, uses values of the curves to form the rectangles. Instead of subdivision, there is function evaluation.

<syntaxhighlight lang="modula2">
(* This program does not do any subdivision, but instead takes
advantage of monotonicity.

It is possible for points accidentally to be counted twice, for
instance if they lie right on an interval boundary. We will avoid
that by the VERY CRUDE (but perhaps often satisfactory) mechanism
of requiring a minimum max norm between intersections. *)

MODULE bezierIntersectionsInModula2;

(* ISO Modula-2 libraries. *)
FROM Storage IMPORT ALLOCATE, DEALLOCATE;
FROM SYSTEM IMPORT TSIZE;
IMPORT SLongIO;
IMPORT STextIO;

(* GNU Modula-2 gm2-libs *)
FROM Assertion IMPORT Assert;

(* Schumaker's and Volk's algorithm for evaluating a Bézier spline in
Bernstein basis. This is faster than de Casteljau, though not quite
as numerical stable. *)
PROCEDURE SchumakerVolk (c0, c1, c2, t : LONGREAL) : LONGREAL;
VAR s, u, v : LONGREAL;
BEGIN
s := 1.0 - t;
IF t <= 0.5 THEN
(* Horner form in the variable u = t/s, taking into account the
binomial coefficients = 1,2,1. *)
u := t / s;
v := c0 + (u * (c1 + c1 + (u * c2)));
(* Multiply by s raised to the degree of the spline. *)
v := v * s * s;
ELSE
(* Horner form in the variable u = s/t, taking into account the
binomial coefficients = 1,2,1. *)
u := s / t;
v := c2 + (u * (c1 + c1 + (u * c0)));
(* Multiply by t raised to the degree of the spline. *)
v := v * t * t;
END;
RETURN v;
END SchumakerVolk;

PROCEDURE FindExtremePoint (c0, c1, c2 : LONGREAL;
VAR LiesInside01 : BOOLEAN;
VAR ExtremePoint : LONGREAL);
VAR numer, denom : LONGREAL;
BEGIN
(* If the spline has c0=c2 but not c0=c1=c2, then treat it as having
an extreme point at 0.5. *)
IF (c0 = c2) AND (c0 <> c1) THEN
LiesInside01 := TRUE;
ExtremePoint := 0.5
ELSE
(* Find the root of the derivative of the spline. *)
LiesInside01 := FALSE;
numer := c0 - c1;
denom := c2 - c1 - c1 + c0;
IF (denom <> 0.0) AND (numer * denom >= 0.0)
AND (numer <= denom) THEN
LiesInside01 := TRUE;
ExtremePoint := numer / denom
END
END
END FindExtremePoint;

TYPE StartIntervCount = [2 .. 4];
StartIntervArray = ARRAY [1 .. 4] OF LONGREAL;

PROCEDURE PossiblyInsertExtremePoint
(c0, c1, c2 : LONGREAL;
VAR numStartInterv : StartIntervCount;
VAR startInterv : StartIntervArray);
VAR liesInside01 : BOOLEAN;
extremePt : LONGREAL;
BEGIN
FindExtremePoint (c0, c1, c2, liesInside01, extremePt);
IF liesInside01 AND (0.0 < extremePt) AND (extremePt < 1.0) THEN
IF numStartInterv = 2 THEN
startInterv[3] := 1.0;
startInterv[2] := extremePt;
numStartInterv := 3
ELSIF extremePt < startInterv[2] THEN
startInterv[4] := 1.0;
startInterv[3] := startInterv[2];
startInterv[2] := extremePt;
numStartInterv := 4
ELSIF extremePt > startInterv[2] THEN
startInterv[4] := 1.0;
startInterv[3] := extremePt;
numStartInterv := 4
END
END
END PossiblyInsertExtremePoint;

PROCEDURE minimum2 (x, y : LONGREAL) : LONGREAL;
VAR w : LONGREAL;
BEGIN
IF x <= y THEN
w := x
ELSE
w := y
END;
RETURN w;
END minimum2;

PROCEDURE maximum2 (x, y : LONGREAL) : LONGREAL;
VAR w : LONGREAL;
BEGIN
IF x >= y THEN
w := x
ELSE
w := y
END;
RETURN w;
END maximum2;

PROCEDURE RectanglesOverlap (xa0, ya0, xa1, ya1 : LONGREAL;
xb0, yb0, xb1, yb1 : LONGREAL) : BOOLEAN;
BEGIN
(* It is assumed that xa0<=xa1, ya0<=ya1, xb0<=xb1, and yb0<=yb1. *)
RETURN ((xb0 <= xa1) AND (xa0 <= xb1)
AND (yb0 <= ya1) AND (ya0 <= yb1))
END RectanglesOverlap;

PROCEDURE TestIntersection (xp0, xp1 : LONGREAL;
yp0, yp1 : LONGREAL;
xq0, xq1 : LONGREAL;
yq0, yq1 : LONGREAL;
tol : LONGREAL;
VAR exclude, accept : BOOLEAN;
VAR x, y : LONGREAL);
VAR xpmin, ypmin, xpmax, ypmax : LONGREAL;
xqmin, yqmin, xqmax, yqmax : LONGREAL;
xmin, xmax, ymin, ymax : LONGREAL;
BEGIN
xpmin := minimum2 (xp0, xp1);
ypmin := minimum2 (yp0, yp1);
xpmax := maximum2 (xp0, xp1);
ypmax := maximum2 (yp0, yp1);

xqmin := minimum2 (xq0, xq1);
yqmin := minimum2 (yq0, yq1);
xqmax := maximum2 (xq0, xq1);
yqmax := maximum2 (yq0, yq1);

exclude := TRUE;
accept := FALSE;
IF RectanglesOverlap (xpmin, ypmin, xpmax, ypmax,
xqmin, yqmin, xqmax, yqmax) THEN
exclude := FALSE;
xmin := maximum2 (xpmin, xqmin);
xmax := minimum2 (xpmax, xqmax);
Assert (xmax >= xmin);
IF xmax - xmin <= tol THEN
ymin := maximum2 (ypmin, yqmin);
ymax := minimum2 (ypmax, yqmax);
Assert (ymax >= ymin);
IF ymax - ymin <= tol THEN
accept := TRUE;
x := (0.5 * xmin) + (0.5 * xmax);
y := (0.5 * ymin) + (0.5 * ymax);
END;
END;
END;
END TestIntersection;

TYPE WorkPile = POINTER TO WorkTask;
WorkTask =
RECORD
tp0, tp1 : LONGREAL;
tq0, tq1 : LONGREAL;
next : WorkPile
END;

PROCEDURE WorkIsDone (workload : WorkPile) : BOOLEAN;
BEGIN
RETURN workload = NIL
END WorkIsDone;

PROCEDURE DeferWork (VAR workload : WorkPile;
tp0, tp1 : LONGREAL;
tq0, tq1 : LONGREAL);
VAR work : WorkPile;
BEGIN
ALLOCATE (work, TSIZE (WorkTask));
work^.tp0 := tp0;
work^.tp1 := tp1;
work^.tq0 := tq0;
work^.tq1 := tq1;
work^.next := workload;
workload := work
END DeferWork;

PROCEDURE DoSomeWork (VAR workload : WorkPile;
VAR tp0, tp1 : LONGREAL;
VAR tq0, tq1 : LONGREAL);
VAR work : WorkPile;
BEGIN
Assert (NOT WorkIsDone (workload));
work := workload;
tp0 := work^.tp0;
tp1 := work^.tp1;
tq0 := work^.tq0;
tq1 := work^.tq1;
workload := work^.next;
DEALLOCATE (work, TSIZE (WorkTask));
END DoSomeWork;

CONST px0 = -1.0; px1 = 0.0; px2 = 1.0;
py0 = 0.0; py1 = 10.0; py2 = 0.0;
qx0 = 2.0; qx1 = -8.0; qx2 = 2.0;
qy0 = 1.0; qy1 = 2.0; qy2 = 3.0;
tol = 0.0000001;
spacing = 100.0 * tol;

TYPE IntersectionCount = [0 .. 4];
IntersectionRange = [1 .. 4];

VAR pxHasExtremePt, pyHasExtremePt : BOOLEAN;
qxHasExtremePt, qyHasExtremePt : BOOLEAN;
pxExtremePt, pyExtremePt : LONGREAL;
qxExtremePt, qyExtremePt : LONGREAL;
pNumStartInterv, qNumStartInterv : StartIntervCount;
pStartInterv, qStartInterv : StartIntervArray;
workload : WorkPile;
i, j : StartIntervCount;
numIntersections, k : IntersectionCount;
intersectionsX : ARRAY IntersectionRange OF LONGREAL;
intersectionsY : ARRAY IntersectionRange OF LONGREAL;
tp0, tp1, tq0, tq1 : LONGREAL;
xp0, xp1, xq0, xq1 : LONGREAL;
yp0, yp1, yq0, yq1 : LONGREAL;
exclude, accept : BOOLEAN;
x, y : LONGREAL;
tpMiddle, tqMiddle : LONGREAL;

PROCEDURE MaybeAddIntersection (x, y : LONGREAL;
spacing : LONGREAL);
VAR i : IntersectionRange;
VAR TooClose : BOOLEAN;
BEGIN
IF numIntersections = 0 THEN
intersectionsX[1] := x;
intersectionsY[1] := y;
numIntersections := 1;
ELSE
TooClose := FALSE;
FOR i := 1 TO numIntersections DO
IF (ABS (x - intersectionsX[i]) < spacing)
AND (ABS (y - intersectionsY[i]) < spacing) THEN
TooClose := TRUE
END
END;
IF NOT TooClose THEN
numIntersections := numIntersections + 1;
intersectionsX[numIntersections] := x;
intersectionsY[numIntersections] := y
END
END
END MaybeAddIntersection;

BEGIN
(* Find monotonic sections of the curves, and use those as the
starting jobs. *)
pNumStartInterv := 2;
pStartInterv[1] := 0.0; pStartInterv[2] := 1.0;
PossiblyInsertExtremePoint (px0, px1, px2,
pNumStartInterv, pStartInterv);
PossiblyInsertExtremePoint (py0, py1, py2,
pNumStartInterv, pStartInterv);
qNumStartInterv := 2;
qStartInterv[1] := 0.0; qStartInterv[2] := 1.0;
PossiblyInsertExtremePoint (qx0, qx1, qx2,
qNumStartInterv, qStartInterv);
PossiblyInsertExtremePoint (qy0, qy1, qy2,
qNumStartInterv, qStartInterv);
workload := NIL;
FOR i := 2 TO pNumStartInterv DO
FOR j := 2 TO qNumStartInterv DO
DeferWork (workload, pStartInterv[i - 1], pStartInterv[i],
qStartInterv[j - 1], qStartInterv[j])
END;
END;

(* Go through the workload, deferring work as necessary. *)
numIntersections := 0;
WHILE (numIntersections <> 4) AND (NOT WorkIsDone (workload)) DO
(* The following code recomputes values of the splines
sometimes. You may wish to store such values in the work pile,
to avoid recomputing them. *)
DoSomeWork (workload, tp0, tp1, tq0, tq1);
xp0 := SchumakerVolk (px0, px1, px2, tp0);
yp0 := SchumakerVolk (py0, py1, py2, tp0);
xp1 := SchumakerVolk (px0, px1, px2, tp1);
yp1 := SchumakerVolk (py0, py1, py2, tp1);
xq0 := SchumakerVolk (qx0, qx1, qx2, tq0);
yq0 := SchumakerVolk (qy0, qy1, qy2, tq0);
xq1 := SchumakerVolk (qx0, qx1, qx2, tq1);
yq1 := SchumakerVolk (qy0, qy1, qy2, tq1);
TestIntersection (xp0, xp1, yp0, yp1,
xq0, xq1, yq0, yq1, tol,
exclude, accept, x, y);
IF accept THEN
MaybeAddIntersection (x, y, spacing)
ELSIF NOT exclude THEN
tpMiddle := (0.5 * tp0) + (0.5 * tp1);
tqMiddle := (0.5 * tq0) + (0.5 * tq1);
DeferWork (workload, tp0, tpMiddle, tq0, tqMiddle);
DeferWork (workload, tp0, tpMiddle, tqMiddle, tq1);
DeferWork (workload, tpMiddle, tp1, tq0, tqMiddle);
DeferWork (workload, tpMiddle, tp1, tqMiddle, tq1);
END
END;

IF numIntersections = 0 THEN
STextIO.WriteString ("no intersections");
STextIO.WriteLn;
ELSE
FOR k := 1 TO numIntersections DO
STextIO.WriteString ("(");
SLongIO.WriteReal (intersectionsX[k], 10);
STextIO.WriteString (", ");
SLongIO.WriteReal (intersectionsY[k], 10);
STextIO.WriteString (")");
STextIO.WriteLn;
END
END
END bezierIntersectionsInModula2.
</syntaxhighlight>

{{out}}
<pre>(0.65498343, 2.85498342)
(0.88102499, 1.11897501)
(-0.6810249, 2.68102500)
(-0.8549834, 1.34501657)</pre>