Program BezierCurve;

Uses
    Crt, NDC;

Const
    TitleString = 'Bezier Curve Generator';

Var
    N, M, I	: Integer;
    W, H, D	: Real;
    Control	: VertexList;
    KeyMeasure	: KeyboardMeasure;
    LocMeasure	: LocatorMeasure;
    Device	: DeviceType;
    Done	: Boolean;
    TextLoc	: Point;

(*-------------------------------------------------------------------------*)

Function C (N, K : Integer) : Real;

Var
    Product	: Real;

Begin
    If K > N div 2 then
	K := N - K;
    Product := 1.0;
    While K > 0 do Begin
	Product := Product * (N - K + 1) / K;
	K := K - 1;
    End; {While}
    C := Product;
End; {C}

(*-------------------------------------------------------------------------*)

Function B (K, N : Integer; U : Real) : Real;

Var
    Product	: Real;
    I		: Integer;

Begin
    Product := C(N,K);
    For I := 1 to K do
	Product := Product * U;
    For I := 1 to N - K do
	Product := Product * (1.0 - U);
    B := Product; 
End; {B}

(*-------------------------------------------------------------------------*)

Procedure Bezier (Var P : Point; U : Real; N : Integer; V : VertexList);

Var
    K		: Integer;
    X, Y, Z	: Real;

Begin
    P.X := 0.0;
    P.Y := 0.0;
    For K := 1 to N+1 do Begin
	Z := B(K-1,N,U);
	P.X := P.X + V[K].X * Z;
	P.Y := P.Y + V[K].Y * Z;
    End;
End;

(*-------------------------------------------------------------------------*)

Procedure Get_Point_Number (Var N : Integer);

Const
    Prompt	= 'Control Point Index: ';

Var
    W, H, D	: Coordinate;
    TextLoc	: Point;
    KeyMeasure	: KeyboardMeasure;
    ErrorPos	: Integer;

Begin
    NDC_SetInputMode (KEYBOARD, SAMPLE);
    NDC_SetKeyboardProcessingMode (EDIT);
    NDC_InquireTextExtent (Prompt, W, H, D);
    NDC_DefPoint (W / 2, 1 - H / 2, TextLoc);
    NDC_Text (TextLoc, Prompt);
    NDC_GetKeyboard (KeyMeasure);
    Val (KeyMeasure, N, ErrorPos);
    {N := Ord(KeyMeasure[1]) - Ord('0');}
    NDC_SetInputMode (KEYBOARD, EVENT);
    NDC_SetKeyboardProcessingMode (RAW);
End; {Get_Point_Number}

(*-------------------------------------------------------------------------*)

Procedure InsertByX (Position : Point; Var Control : VertexList;
			Var N : Integer);

Const
    IN_ORDER	= 1;
    BY_X	= 2;
    MANUAL	= 3;

    Method	= BY_X;

Var
    I, K	: Integer;

Begin
    N := N + 1;
    K := 1;
    If N > 0 then Begin
	Case Method of
	    IN_ORDER:	Begin
			    K := N + 1;
			End; {IN_ORDER}
	    BY_X:	Begin
			    While (K < N) and (Position.X > Control[K].X) do
				K := K + 1;
			    If Position.X <= Control[K].X then
				For I := N downto K do
				    Control[I+1] := Control[I]
			    Else
				K := K + 1;
			End; {BY_X}
	    MANUAL:	Begin
			    Get_Point_Number (K);
			    For I := N downto K do
				Control[I+1] := Control[I];
			End; {MANUAL}
	End; {Case}
    End; {If}
    Control[K] := Position;
End;

(*-------------------------------------------------------------------------*)

Procedure Process_Left_Button (Position : Point; Var Control : VertexList;
				Var N : Integer);

Var
    M, I	: Integer;
    U		: Real;
    P		: VertexList;
    S		: String;

Begin
    InsertByX (Position, Control, N);
    If N > 0 then Begin
	M := 100;
	For I := 1 to M do Begin
	    U := (I - 1) / (M - 1);
	    Bezier (P[I], U, N, Control);
	End; {For}
	NDC_ClearDisplay;
	NDC_SetColor (WHITE);
	NDC_PolyLine (M, P);
    End; {If}
    NDC_SetColor (LIGHT_RED);
    For I := 1 to N+1 do Begin
	Str (I, S);
	NDC_Text (Control[I], S);
    End; {For}
    {NDC_PolyMarker (N+1, Control);}
End; {Process_Left_Button}

(*-------------------------------------------------------------------------*)

Procedure Process_Right_Button (Position : Point; Var Control : VertexList;
				Var N : Integer);

Var
    MinD, D	: Real;
    MinP	: Integer;
    I		: Integer;
    U		: Real;
    P		: VertexList;
    S		: String;

Begin
    MinD := Sqr(Position.X - Control[1].X) + Sqr(Position.Y - Control[1].Y);
    MinP := 1;
    For I := 2 to N+1 do Begin
	D := Sqr(Position.X - Control[I].X) + Sqr(Position.Y - Control[I].Y);
	If D < MinD then Begin
	    MinD := D;
	    MinP := I;
	End; {If}
    End; {For}

    If MinP <= N then Begin
	For I := MinP to N do
	    Control[I] := Control[I+1];
    End; {If}
    N := N - 1;

    If N > 0 then Begin
	M := 100;
	For I := 1 to M do Begin
	    U := (I - 1) / (M - 1);
	    Bezier (P[I], U, N, Control);
	End; {For}
	NDC_ClearDisplay;
	NDC_SetColor (WHITE);
	NDC_PolyLine (M, P);
    End; {If}
    NDC_SetColor (LIGHT_GREEN);
    For I := 1 to N+1 do Begin
	Str (I, S);
	NDC_Text (Control[I], S);
    End; {For}
    {NDC_PolyMarker (N+1, Control);}
End; {Process_Right_Button}

(*-------------------------------------------------------------------------*)
(*-------------------------------------------------------------------------*)
(*-------------------------------------------------------------------------*)

Begin
    ClrScr;
    Writeln (TitleString,'.');
    Writeln;
    Writeln ('Left Button:   Add a point at cursor location.');
    Writeln;
    Writeln ('Right Button:  Remove point closest to cursor location.');
    Writeln;
    Writeln ('q or Q:        Quit.');
    Writeln;
    Writeln;
    Writeln ('NOTE: Edit the function InsertByX() in the source to change');
    Writeln ('      the order in which the new points are added.');
    Writeln ('      The constant "Method" is used to control this.');
    Writeln;
    Writeln;
    Write ('Press RETURN to continue...');
    Readln;
    
    NDC_Begin (640, 480);
    NDC_SetInputMode (KEYBOARD, EVENT);
    NDC_SetKeyboardProcessingMode (RAW);
    NDC_SetInputMode (LOCATOR, EVENT);
    NDC_SetLocatorButtonMask ([LEFT_BUTTON, RIGHT_BUTTON]);
    NDC_SetMarker ('*');
    NDC_SetFont (0);
    NDC_InquireTextExtent (TitleString, W, H, D);
    TextLoc.X := 0.5;
    TextLoc.Y := 1.0 - H / 2.0;
    NDC_Text (TextLoc, TitleString);

    Done := False;
    N := -1;
    Repeat
	NDC_WaitEvent (INDEFINITE, Device);
	Case Device of
	    KEYBOARD:
		Begin
		    NDC_GetKeyboard (KeyMeasure);
		    Case KeyMeasure[1] of
			'q','Q'	: Done := True;
			'n','N'	: N := -1;
			'r','R'	: NDC_ClearDisplay;
		    End; {Case}
		End; {KEYBOARD}
	    LOCATOR:
		Begin
		    NDC_GetLocator (LocMeasure);
		    With LocMeasure do
			Case ButtonOfMostRecentTransition of
			    LEFT_BUTTON:
				If (ButtonChord[LEFT_BUTTON] = DOWN) then
				    Process_Left_Button (Position, Control, N);
			    RIGHT_BUTTON:
				If (ButtonChord[RIGHT_BUTTON] = DOWN) then
				    Process_Right_Button (Position, Control, N);
			End; {Case}
		End; {LOCATOR}
	End; {Case}
    Until Done;
    NDC_End;
End.
