-- $Date: 2004/01/25 13:55:33 $
-- $Revision: 1.14 $
-- $Author: jcrocholl $

with Messages; use Messages;
with Outlines; use Outlines;
with Lines; use Lines;
with Cubics; use Cubics;
with Real_Vectors; use Real_Vectors;

package body Curves is

   -- Create a cubic bezier curve that goes through 4 given points.
   -- According to the PostScript Reference Manual.
   function Make_Cubic_With_Coefficients
     (D, C, B, A : in Vector-- Coefficients for ascending degrees of t.
     return Line_Access       -- The newly created cubic bezier curve.
   is
      Control_A : Vector := D + C / 3.0;
      Control_B : Vector := Control_A + (C + B) / 3.0;
      To        : Vector := D + C + B + A;
   begin
      return Create(To => To,
        Control_A => Control_A,
        Control_B => Control_B);
   end Make_Cubic_With_Coefficients;

   -- Create a cubic bezier curve that goes through 4 given points.
   function Make_Cubic_Through_Points
     (F0 : in Vector;   -- Starting point.
      F1 : in Vector;   -- Go through this inner point at t=1/3.
      F2 : in Vector;   -- Go through this inner point at t=2/3.
      F3 : in Vector)   -- End point.
     return Line_Access -- The newly created cubic bezier curve.
   is
      X0 : Vector := (0.0, 0.0);
      X1 : Vector := (1.0 / 3.0, 1.0 / 3.0);
      X2 : Vector := (2.0 / 3.0, 2.0 / 3.0);
      X3 : Vector := (1.0, 1.0);
      -- Create an interpolating polynomial with Newton's divided differences.
      F01   : Vector := 3.0 * (F1 - F0);
      F12   : Vector := 3.0 * (F2 - F1);
      F23   : Vector := 3.0 * (F3 - F2);
      F012  : Vector := 1.5 * (F12 - F01);
      F123  : Vector := 1.5 * (F23 - F12);
      F0123 : Vector := 1.0 * (F123 - F012);
      -- Coefficients for ascending degrees of t.
      D : Vector := F0;
      C : Vector := F01 - (X1) * F012 + (X1 * X2) * F0123;
      B : Vector := F012 - X1 * F0123 - X2 * F0123;
      A : Vector := F0123;
   begin
      -- Debug(To_String(X0) & ' ' & To_String(F0) & ' ' & To_String(F01)
      -- & ' ' & To_String(F012) & ' ' & To_String(F0123));
      -- Debug(To_String(X1) & ' ' & To_String(F1) & ' ' & To_String(F12)
      -- & ' ' & To_String(F123));
      -- Debug(To_String(X2) & ' ' & To_String(F2) & ' ' & To_String(F23));
      -- Debug(To_String(X3) & ' ' & To_String(F3));
      return Make_Cubic_With_Coefficients(D, C, B, A);
   end Make_Cubic_Through_Points;

   function Make_Cubic_From_Slopes
     (P0, P1, P2 : in Vector)
     return Line_Access is
   begin
      return Create(Control_A => P1, Control_B => P1, To => P2);
      -- return Create(P2, P0 + (P1 - P0) / 2.0, P2 - (P1 - P2) / 2.0);
   end Make_Cubic_From_Slopes;

   -- Create a cubic bezier curve by joining two adjacent lines.
   -- Output value Error is the maximum distance between the original
   -- lines and the newly created curve.
   procedure Make_Cubic
     (Start  : in Vector;              -- Starting vector.
      A, B   : access Line'Class-- Two adjacent lines.
      Result : out Line_Access;        -- The new cubic bezier curve.
      Error  : out Real)               -- The maximum error.
   is
      Length_A    : Real := Length(Start, A);
      Length_B    : Real := Length(A.To, B);
      Length_Both : Real := Length_A + Length_B;
      Part_A      : Real := Length_A / Length_Both;

      function Way_Point
        (Part : in Real)
        return Vector is
      begin
         if Part < Part_A
         then return Way_Point(Start, A, Part * Length_Both / Length_A);
         else return Way_Point(A.To, B, (Part - Part_A) * Length_Both / Length_B);
         end if;
      end Way_Point;

      Interpolate_A : Vector := Way_Point(1.0 / 3.0);
      Interpolate_B : Vector := Way_Point(2.0 / 3.0);
      Part          : Real;
   begin
      -- Result := Make_Cubic_From_Slopes(Start, A.To, B.To);
      Result := Make_Cubic_Through_Points(Start, Interpolate_A, Interpolate_B, B.To);
      Result.Length := Length_Both;

      Error := 0.0;
      for Index in 1 .. 24 loop
         Part := Real(Index * 2 - 1) / 48.0;
         Error := Real'Max(Error, abs(Way_Point(Part) - Way_Point(Start, Result, Part)));
      end loop;
   end Make_Cubic;

   -- Join straight lines into cubic bezier curves, if the distance
   -- between original and joined lines remains below tolerance.
   procedure Make_Cubic
     (This      : access Outline-- Make curves in this outline.
      Tolerance : in Real)        -- Maximum distance to the joined line.
   is
      use Line_Lists;
      Item_1  : Item_Access := Last_Item(This);
      Item_2  : Item_Access := First_Item(This);
      Item_3  : Item_Access := Next_Item(Item_2);
      Item_4  : Item_Access := Next_Item(Item_3);
      Stop    : Item_Access := Item_3;
      Counter : Natural := 2;

      Line_1, Line_2, Line_3, Line_4 : Line_Access;