123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- Program Bezier2;
- { This program draws Bezier curves in the slow, simple, recursive
- way. When it first runs, you enter points in the window by
- clicking the left mouse button. After you double click on the
- last point, the program begins drawing the curve.
- Since this is a highly recursive program, it's speed decreases
- dramatically as you enter more points. It can handle six or
- seven points with reasonable speed, but if you enter ten you
- might want to go see a movie while it draws. It also uses
- more stack space as you enter more points, but I hasn't blown
- a 4k stack yet.
- }
- {
- Translated to fpc pascal from pcq pascal.
- Updated the source a bit.
- 04 Apr 2001.
- Changed to use systemvartags, OpenScreenTags
- and OpenWindowTags. Also Text to Gtext.
- 09 Nov 2002.
- [email protected]
- }
- uses exec, intuition, agraphics, utility;
- type
- PointRec = Record
- X, Y : integer;
- end;
- Const
- w : pWindow = Nil;
- s : pScreen = Nil;
- {
- This will make the new look for screen.
- SA_Pens, Integer(pens)
- }
- pens : array [0..0] of integer = (not 0);
- Var
- m : pMessage;
- rp : pRastPort;
- PointCount : integer;
- Points : Array [1..15] of PointRec;
- t, tprime : Real;
- LastX, LastY : integer;
- Procedure CleanUpAndDie;
- begin
- if w <> Nil then begin
- Forbid;
- repeat until GetMsg(w^.UserPort) = Nil;
- CloseWindow(w);
- Permit;
- end;
- if s <> Nil then
- CloseScreen(s);
- halt(0);
- end;
- Procedure DrawLine;
- begin
- GfxMove(rp, Points[PointCount].X, Points[PointCount].Y);
- Draw(rp, LastX, LastY);
- end;
- Procedure GetPoints;
- var
- LastSeconds,
- LastMicros : longint;
- IM : pIntuiMessage;
- StoreMsg : tIntuiMessage;
- Leave : Boolean;
- OutOfBounds : Boolean;
- BorderLeft, BorderRight,
- BorderTop, BorderBottom : integer;
- Procedure AddPoint;
- begin
- Inc(PointCount);
- with Points[PointCount] do begin
- X := StoreMsg.MouseX;
- Y := StoreMsg.MouseY;
- end;
- with StoreMsg do begin
- LastX := MouseX;
- LastY := MouseY;
- LastSeconds := Seconds;
- LastMicros := Micros;
- end;
- SetAPen(rp, 2);
- SetDrMd(rp, JAM1);
- DrawEllipse(rp, LastX, LastY, 5, 3);
- SetAPen(rp, 3);
- SetDrMd(rp, COMPLEMENT);
- DrawLine;
- end;
- Function CheckForExit : Boolean;
- { This function determines whether the user wanted to stop
- entering points. I added the position tests because my
- doubleclick time is too long, and I was too lazy to dig
- out Preferences to change it. }
- begin
- with StoreMsg do
- CheckForExit := DoubleClick(LastSeconds, LastMicros,
- Seconds, Micros) and
- (Abs(MouseX - Points[PointCount].X) < 5) and
- (Abs(MouseY - Points[PointCount].Y) < 3);
- end;
- Procedure ClearIt;
- { This just clears the screen when you enter your first point }
- begin
- SetDrMd(rp, JAM1);
- SetAPen(rp, 0);
- RectFill(rp, BorderLeft, BorderTop,
- BorderRight, BorderBottom);
- SetDrMd(rp, COMPLEMENT);
- SetAPen(rp, 3);
- end;
- begin
- GfxMove(rp, 252, 30);
- GfxText(rp, 'Enter points by pressing the left mouse button', 46);
- GfxMove(rp, 252, 40);
- GfxText(rp, 'Double click on the last point to begin drawing', 47);
- ModifyIDCMP(w, IDCMP_CLOSEWINDOW or IDCMP_MOUSEBUTTONS or IDCMP_MOUSEMOVE);
- SetDrMd(rp, COMPLEMENT);
- PointCount := 0;
- Leave := False;
- OutOfBounds := False;
- BorderLeft := w^.BorderLeft;
- BorderRight := 639 - w^.BorderRight;
- BorderTop := w^.BorderTop;
- BorderBottom := 189 - w^.BorderBottom;
- repeat
- IM := pIntuiMessage(WaitPort(w^.UserPort));
- IM := pIntuiMessage(GetMsg(w^.UserPort));
- StoreMsg := IM^;
- ReplyMsg(pMessage(IM));
- case StoreMsg.IClass of
- IDCMP_MOUSEMOVE : if PointCount > 0 then begin
- if not OutOfBounds then
- DrawLine;
- LastX := StoreMsg.MouseX;
- LastY := StoreMsg.MouseY;
- if (LastX > BorderLeft) and
- (LastX < BorderRight) and
- (LastY > BorderTop) and
- (LastY < BorderBottom) then begin
- DrawLine;
- OutOfBounds := False;
- end else
- OutOfBounds := True;
- end;
- IDCMP_MOUSEBUTTONS : if StoreMsg.Code = SELECTUP then begin
- if PointCount > 0 then
- Leave := CheckForExit
- else
- ClearIt;
- if (not Leave) and (not OutOfBounds) then
- AddPoint;
- end;
- IDCMP_CLOSEWINDOW : CleanUpAndDie;
- end;
- until Leave or (PointCount > 14);
- if not Leave then
- DrawLine;
- ModifyIDCMP(w, IDCMP_CLOSEWINDOW);
- SetDrMd(rp, JAM1);
- SetAPen(rp, 1);
- end;
- {
- These two function just implement the de Casteljau
- algorithm, which looks like:
- r r-1 r-1
- B = (1-t) * B + t * B
- i i i+1
- Where r and i are meant to be subscripts and superscripts. R is
- a level number, where zero represents the data points and
- (the number of points - 1) represents the curve points. I is
- the point numbers, starting from zero normally but in this
- program starting from 1. t is the familiar 'parameter' running
- from 0 to 1 in arbitrary increments.
- }
- Function BezierX(r, i : integer) : Real;
- begin
- if r = 0 then
- BezierX := real(Points[i].X)
- else
- BezierX := tprime * BezierX(Pred(r), i) + t * BezierX(Pred(r), Succ(i));
- end;
- Function BezierY(r, i : integer) : Real;
- begin
- if r = 0 then
- BezierY := real(Points[i].Y)
- else
- BezierY := tprime * BezierY(Pred(r), i) + t * BezierY(Pred(r), Succ(i));
- end;
- Procedure DrawBezier;
- var
- increment : Real;
- begin
- increment := 0.01; { This could be a function of PointCount }
- t := 0.0;
- tprime := 1.0;
- GfxMove(rp, Trunc(BezierX(Pred(PointCount), 1)),
- Trunc(BezierY(Pred(PointCount), 1)));
- t := t + increment;
- tprime := 1.0 - t;
- while t <= 1.0 do begin
- Draw(rp, Trunc(BezierX(Pred(PointCount), 1)),
- Trunc(BezierY(Pred(PointCount), 1)));
- t := t + increment;
- tprime := 1.0 - t;
- if GetMsg(w^.UserPort) <> Nil then
- CleanUpAndDie;
- end;
- t := 1.0;
- tprime := 0.0;
- Draw(rp, Trunc(BezierX(Pred(PointCount), 1)),
- Trunc(BezierY(Pred(PointCount), 1)));
- end;
- begin
- s := OpenScreenTags(nil,[
- SA_Pens, AsTag(@pens),
- SA_Depth, 2,
- SA_DisplayID, HIRES_KEY,
- SA_Title, AsTag('Simple Bezier Curves'),
- TAG_END]);
- if s = NIL then CleanUpAndDie;
- w := OpenWindowTags(nil,[
- WA_IDCMP, IDCMP_CLOSEWINDOW,
- WA_Left, 0,
- WA_Top, s^.BarHeight +1,
- WA_Width, s^.Width,
- WA_Height, s^.Height - (s^.BarHeight + 1),
- WA_DepthGadget, ltrue,
- WA_DragBar, ltrue,
- WA_CloseGadget, ltrue,
- WA_ReportMouse, ltrue,
- WA_SmartRefresh, ltrue,
- WA_Activate, ltrue,
- WA_Title, AsTag('Close the Window to Quit'),
- WA_CustomScreen, AsTag(s),
- TAG_END]);
- IF w=NIL THEN CleanUpAndDie;
- rp := w^.RPort;
- GetPoints;
- DrawBezier;
- m := WaitPort(w^.UserPort);
- Forbid;
- repeat
- m := GetMsg(w^.UserPort);
- until m = nil;
- Permit;
- CleanUpAndDie;
- end.
|