with
     ada.Text_IO,
     ada.Strings.unbounded,
     ada.Strings.Maps;


package body any_Math.any_Geometry.any_d3.any_Modeller.any_Forge
is

   function to_Box_Model (half_Extents : in Vector_3 := [0.5, 0.5, 0.5]) return a_Model
   is
      pragma Unreferenced (half_Extents);
      Modeller : any_Modeller.item;
   begin
      Modeller.add_Triangle ([0.0, 0.0, 0.0],
                             [1.0, 0.0, 0.0],
                             [1.0, 1.0, 0.0]);

      Modeller.add_Triangle ([1.0, 1.0, 0.0],
                             [0.0, 1.0, 0.0],
                             [0.0, 0.0, 0.0]);

      --  TODO: Add the rest.

      return Modeller.Model;
   end to_Box_Model;



   function to_Capsule_Model (Length : in Real := 1.0;
                              Radius : in Real := 0.5) return a_Model
   is
      use Functions;

      quality_Level : constant Positive := 4;
      sides_Count   : constant Positive := Positive (quality_Level * 4);     -- Number of sides to the cylinder (divisible by 4).

      type Edge is   -- 'Barrel' edge.
         record
            Fore : Site;
            Aft  : Site;
         end record;

      type      Edges is array (Positive range 1 .. sides_Count)   of Edge;
      type arch_Edges is array (Positive range 1 .. quality_Level) of Sites (1 .. sides_Count);

      tmp,
      ny, nz,
      start_nx,
      start_ny  : Real;
      a         : constant Real := Pi * 2.0 / Real (sides_Count);
      ca        : constant Real := Cos (a);
      sa        : constant Real := Sin (a);
      L         : constant Real := Length * 0.5;

      the_Edges : Edges;
      Modeller  : any_Modeller.item;

   begin
      --  Define cylinder body.
      --
      ny := 1.0;
      nz := 0.0;              -- Normal vector = (0, ny, nz)

      for Each in Edges'Range
      loop
         the_Edges (Each).Fore (1) :=  ny * Radius;
         the_Edges (Each).Fore (2) :=  nz * Radius;
         the_Edges (Each).Fore (3) :=  L;

         the_Edges (Each).Aft  (1) :=  ny * Radius;
         the_Edges (Each).Aft  (2) :=  nz * Radius;
         the_Edges (Each).Aft  (3) := -L;

         --  Rotate ny, nz.
         --
         tmp := ca * ny  -  sa * nz;
         nz  := sa * ny  +  ca * nz;
         ny  := tmp;
      end loop;


      for Each in Edges'Range
      loop
         if Each /= Edges'Last
         then
            Modeller.add_Triangle (the_Edges (Each)    .Fore,
                                   the_Edges (Each)    .Aft,
                                   the_Edges (Each + 1).Aft);
            Modeller.add_Triangle (the_Edges (Each + 1).Aft,
                                   the_Edges (Each + 1).Fore,
                                   the_Edges (Each)    .Fore);
         else
            Modeller.add_Triangle (the_Edges (Each)       .Fore,
                                   the_Edges (Each)       .Aft,
                                   the_Edges (edges'First).Aft);
            Modeller.add_Triangle (the_Edges (edges'First).Aft,
                                   the_Edges (edges'First).Fore,
                                   the_Edges (Each)       .Fore);
         end if;
      end loop;


      --  Define fore cylinder cap.
      --
      declare
         the_arch_Edges : arch_Edges;
      begin
         start_nx := 0.0;
         start_ny := 1.0;

         for each_Hoop in 1 .. quality_Level
         loop
            --  Get start_n2 = rotated start_n.
            --
            declare
               start_nx2 : constant Real :=  ca * start_nx  +  sa * start_ny;
               start_ny2 : constant Real := -sa * start_nx  +  ca * start_ny;
            begin
               --  Get n = start_n and n2 = start_n2.
               --
               ny := start_ny;
               nz := 0.0;

               declare
                  nx2 : constant Real := start_nx2;
                  ny2 :          Real := start_ny2;
                  nz2 :          Real := 0.0;
               begin
                  for Each in 1 .. sides_Count
                  loop
                     the_arch_Edges (each_Hoop)(Each) (1) := ny2 * Radius;
                     the_arch_Edges (each_Hoop)(Each) (2) := nz2 * Radius;
                     the_arch_Edges (each_Hoop)(Each) (3) := nx2 * Radius + L;

                     --  Rotate n, n2.
                     --
                     tmp := ca * ny  -  sa * nz;
                     nz  := sa * ny  +  ca * nz;
                     ny  := tmp;

                     tmp := ca * ny2  -  sa * nz2;
                     nz2 := sa * ny2  +  ca * nz2;
                     ny2 := tmp;
                  end loop;
               end;

               start_nx := start_nx2;
               start_ny := start_ny2;
            end;
         end loop;


         for Each in 1 .. sides_Count
         loop
            if Each /= sides_Count
            then
               Modeller.add_Triangle (the_Edges (Each)    .Fore,
                                      the_Edges (Each + 1).Fore,
                                      the_arch_Edges (1) (Each));
            else
               Modeller.add_Triangle (the_Edges (Each).Fore,
                                      the_Edges (1)   .Fore,
                                      the_arch_Edges (1) (Each));
            end if;

            if Each /= sides_Count
            then
               Modeller.add_Triangle (the_Edges (Each + 1).Fore,
                                      the_arch_Edges (1) (Each + 1),
                                      the_arch_Edges (1) (Each));
            else
               Modeller.add_Triangle (the_Edges (1).Fore,
                                      the_arch_Edges (1) (1),
                                      the_arch_Edges (1) (Each));
            end if;
         end loop;


         for each_Hoop in 1 .. quality_Level - 1
         loop
            for Each in 1 .. sides_Count
            loop
               declare
                  function next_Hoop_Vertex return Positive
                  is
                  begin
                     if Each = sides_Count then return 1;
                     else                       return Each + 1;
                     end if;
                  end next_Hoop_Vertex;
               begin
                  Modeller.add_Triangle (the_arch_Edges (each_Hoop)     (Each),
                                         the_arch_Edges (each_Hoop)     (next_Hoop_Vertex),
                                         the_arch_Edges (each_Hoop + 1) (Each));

                  if each_Hoop /= quality_Level - 1
                  then
                     Modeller.add_Triangle (the_arch_Edges (each_Hoop)     (next_Hoop_Vertex),
                                            the_arch_Edges (each_Hoop + 1) (next_Hoop_Vertex),
                                            the_arch_Edges (each_Hoop + 1) (Each));
                  end if;
               end;
            end loop;
         end loop;
      end;


      --  Define aft cylinder cap.
      --
      declare
         the_arch_Edges : arch_Edges;
      begin
         start_nx := 0.0;
         start_ny := 1.0;

         for each_Hoop in 1 .. quality_Level
         loop
            declare
               --  Get start_n2 = rotated start_n.
               --
               start_nx2 : constant Real := ca * start_nx  -  sa * start_ny;
               start_ny2 : constant Real := sa * start_nx  +  ca * start_ny;
            begin
               --  Get n = start_n and n2 = start_n2.
               --
               ny := start_ny;
               nz := 0.0;

               declare
                  nx2 : constant Real := start_nx2;
                  ny2 :          Real := start_ny2;
                  nz2 :          Real := 0.0;
               begin
                  for Each in 1 .. sides_Count
                  loop
                     the_arch_Edges (each_Hoop) (Each) (1) := ny2 * Radius;
                     the_arch_Edges (each_Hoop) (Each) (2) := nz2 * Radius;
                     the_arch_Edges (each_Hoop) (Each) (3) := nx2 * Radius - L;

                     --  Rotate n, n2
                     --
                     tmp := ca * ny  -  sa * nz;
                     nz  := sa * ny  +  ca * nz;
                     ny  := tmp;

                     tmp := ca * ny2  -  sa * nz2;
                     nz2 := sa * ny2  +  ca * nz2;
                     ny2 := tmp;
                  end loop;
               end;

               start_nx := start_nx2;
               start_ny := start_ny2;
            end;
         end loop;


         for Each in 1 .. sides_Count
         loop
            if Each /= sides_Count
            then
               Modeller.add_Triangle (the_Edges (Each).Aft,
                                      the_arch_Edges (1) (Each),
                                      the_Edges (Each + 1).Aft);
            else
               Modeller.add_Triangle (the_Edges (Each).Aft,
                                      the_arch_Edges (1) (Each),
                                      the_Edges (1).Aft);
            end if;

            if Each /= sides_Count
            then
               Modeller.add_Triangle (The_Edges (Each + 1).Aft,
                                      the_arch_Edges (1) (Each),
                                      the_arch_Edges (1) (Each + 1));
            else
               Modeller.add_Triangle (the_Edges (1).Aft,
                                      the_arch_Edges (1) (Each),
                                      the_arch_Edges (1) (1));
            end if;
         end loop;


         for each_Hoop in 1 .. quality_Level - 1
         loop
            for Each in 1 .. sides_Count
            loop
               declare
                  function next_Hoop_Vertex return Positive
                  is
                  begin
                     if Each = sides_Count then return 1;
                     else                       return Each + 1;
                     end if;
                  end next_hoop_Vertex;
               begin
                  Modeller.add_Triangle (the_arch_Edges (each_Hoop)     (Each),
                                         the_arch_Edges (each_Hoop + 1) (Each),
                                         the_arch_Edges (each_Hoop)     (next_Hoop_Vertex));

                  if each_Hoop /= quality_Level - 1
                  then
                     Modeller.add_Triangle (the_arch_Edges (each_Hoop)     (next_hoop_Vertex),
                                            the_arch_Edges (each_Hoop + 1) (Each),
                                            the_arch_Edges (each_Hoop + 1) (next_Hoop_Vertex));
                  end if;
               end;
            end loop;
         end loop;
      end;


      return Modeller.Model;
   end to_capsule_Model;




   -- Polar to euclidian shape models.
   --

   function to_Radians (From : in Latitude) return Radians
   is
   begin
      return Radians (From) * Pi / 180.0;
   end to_Radians;


   function to_Radians (From : in Longitude) return Radians
   is
   begin
      return Radians (From) * Pi / 180.0;
   end to_Radians;



   function polar_Model_from (model_Filename : in String) return polar_Model     -- TODO: Handle different file formats.
   is
      use Functions,
          ada.Text_IO,
          ada.Strings.unbounded;

      the_File : File_type;
      the_Text : unbounded_String;

   begin
      open (the_File, in_File, model_Filename);

      while not end_of_File (the_File)
      loop
         append (the_Text, get_Line (the_File) & " ");
      end loop;

      declare
         text_Length : constant Natural  := Length (the_Text);
         First       :          Positive := 1;

         function get_Real return Real
         is
            use ada.Strings,
                ada.Strings.Maps;

            real_Set : constant Character_Set :=    to_Set (Span => (Low  => '0',
                                                                     High => '9'))
                                                 or to_Set ('-' & '.');
            Last   : Positive;
            Result : Real;
         begin
            find_Token (the_Text, Set   => real_Set,
                                  From  => First,
                                  Test  => Inside,
                                  First => First,
                                  Last  => Last);

            Result := Real'Value (Slice (the_Text,
                                         Low  => First,
                                         High => Last));
            First  := Last + 1;

            return Result;
         end get_Real;


         Lat       : Latitude;
         Long      : Longitude;
         Value     : Integer;
         Distance  : Real;
         Scale     : constant Real := 10.0;     -- TODO: Add a 'Scale' parameter.

         the_Model : polar_Model;

      begin
         while First < text_Length
         loop
            Value := Integer (get_Real);
            exit when Value = 360;

            Long     := Longitude (Value);
            Lat      := Latitude  (get_Real);
            Distance := get_Real;

            the_Model (Long) (Lat).Site (1) := Scale * Distance * Cos (to_Radians (Lat)) * Sin (to_Radians (Long));
            the_Model (Long) (Lat).Site (2) := Scale * Distance * Sin (to_Radians (Lat));
            the_Model (Long) (Lat).Site (3) := Scale * Distance * Cos (to_Radians (Lat)) * Cos (to_Radians (Long));
         end loop;

         return the_Model;
      end;
   end polar_Model_from;



   function mesh_Model_from (Model : in polar_Model) return a_Model
   is
      the_raw_Model  : polar_Model := Model;
      the_mesh_Model : a_Model (site_Count => 2522,
                                tri_Count  => 73 * (16 * 4 + 6));

      the_longitude  : Longitude := 0;
      the_latitude   : Latitude ;

      the_Vertex     : Positive := 1;
      the_Triangle   : Positive := 1;

      the_North_Pole : Positive;
      the_South_Pole : Positive;

      function Sum (the_Longitude : in Longitude;   Increment : in Integer) return Longitude
      is
         Result : Integer := Integer (the_Longitude) + Increment;
      begin
         if Result >= 360
         then
            Result := Result - 360;
         end if;

         return longitude (Result);
      end Sum;

   begin
      the_mesh_Model.Sites (the_Vertex) := (the_raw_model (0) (-90).Site);
      the_North_Pole                    := the_Vertex;
      the_raw_Model (0) (-90).Id        := the_Vertex;
      the_Vertex                        := the_Vertex + 1;

      the_mesh_Model.Sites (the_Vertex) := (the_raw_model (0) (90).Site);
      the_south_Pole                    := the_Vertex;
      the_raw_Model (0) (90).Id         := the_Vertex;
      the_Vertex                        := the_Vertex + 1;

      loop
         the_latitude := -90;
         loop
            if the_Latitude = -90
            then
               the_raw_Model (the_Longitude) (the_Latitude).Id := the_North_Pole;

            elsif the_Latitude = 90
            then
               the_raw_Model (the_Longitude) (the_Latitude).Id := the_South_Pole;

            else
               the_mesh_Model.Sites (the_Vertex)               := the_raw_model (the_Longitude) (the_Latitude).Site;
               the_raw_Model (the_Longitude) (the_Latitude).Id := the_Vertex;
               the_Vertex                                      := the_Vertex + 1;
            end if;

            exit when the_Latitude = 90;

            the_Latitude := the_Latitude + 5;
         end loop;

         exit when the_Longitude = 355;

         the_Longitude := the_Longitude + 5;
      end loop;


      the_Longitude := 0;
      loop
         the_mesh_Model.Triangles (the_Triangle) := [1 => the_North_Pole,
                                                     2 => the_raw_Model (Sum (the_Longitude, 5)) (-85).Id,
                                                     3 => the_raw_Model (     the_Longitude    ) (-85).Id];
         the_Triangle := the_Triangle + 1;

         the_mesh_Model.Triangles (the_Triangle) := [1 => the_South_Pole,
                                                     2 => the_raw_Model      (the_Longitude)     (85).Id,
                                                     3 => the_raw_Model (Sum (the_Longitude, 5)) (85).Id];
         the_Triangle := the_Triangle + 1;

         the_Latitude := -85;
         loop
            the_mesh_Model.Triangles (the_Triangle) := [1 => the_raw_Model (     the_Longitude)     (the_Latitude    ).Id,
                                                        2 => the_raw_Model (Sum (the_Longitude, 5)) (the_Latitude    ).Id,
                                                        3 => the_raw_Model (     the_Longitude)     (the_Latitude + 5).Id];
            the_Triangle := the_Triangle + 1;


            the_mesh_Model.Triangles (the_Triangle) := [1 => the_raw_Model (the_Longitude)          (the_Latitude + 5).Id,
                                                        2 => the_raw_Model (Sum (the_Longitude, 5)) (the_Latitude    ).Id,
                                                        3 => the_raw_Model (Sum (the_Longitude, 5)) (the_Latitude + 5).Id];
            the_Triangle := the_Triangle + 1;


            the_Latitude := the_Latitude + 5;
            exit when       the_Latitude = 85;
         end loop;

         exit when        the_Longitude = 355;
         the_Longitude := the_Longitude + 5;
      end loop;

      the_mesh_Model.Triangles (the_Triangle) := [1 => the_North_Pole,
                                                  2 => the_raw_Model (5) (-85).Id,
                                                  3 => the_raw_Model (0) (-85).Id];
      the_Triangle := the_Triangle + 1;

      the_mesh_Model.Triangles (the_Triangle) := [1 => the_South_Pole,
                                                  2 => the_raw_Model (0) (85).Id,
                                                  3 => the_raw_Model (5) (85).Id];
      the_Triangle := the_Triangle + 1;


      the_latitude := -85;
      loop
         the_mesh_Model.Triangles (the_Triangle) := [1 => the_raw_Model (0) (the_Latitude    ).Id,
                                                     2 => the_raw_Model (5) (the_Latitude    ).Id,
                                                     3 => the_raw_Model (0) (the_Latitude + 5).Id];
         the_Triangle := the_Triangle + 1;

         the_mesh_Model.Triangles (the_Triangle) := [1 => the_raw_Model (0) (the_Latitude + 5).Id,
                                                     2 => the_raw_Model (5) (the_Latitude    ).Id,
                                                     3 => the_raw_Model (5) (the_Latitude + 5).Id];
         the_Triangle := the_Triangle + 1;


         the_Latitude := the_Latitude + 5;
         exit when       the_Latitude = 85;
      end loop;

      return the_mesh_Model;
   end mesh_Model_from;


end any_Math.any_Geometry.any_d3.any_Modeller.any_Forge;
