Back to... GLOBE_3D

Source file : gl-geometry.adb


-------------------------------------------------------------------------
--  GL.Geometry
--
--  Copyright (c) Rod Kay 2016
--  AUSTRALIA
--  Permission granted to use this software, without any warranty,
--  for any purpose, provided this copyright note remains attached
--  and unmodified if sources are distributed further.
-------------------------------------------------------------------------

with GL.Math,
     Ada.Numerics.Generic_Elementary_Functions,
     Ada.Strings.Unbounded,
     Ada.Characters.Latin_1,
     Ada.Unchecked_Deallocation;

use GL.Math,
    Ada.Strings.Unbounded;

package body GL.Geometry is

   package REF is new Ada.Numerics.Generic_Elementary_functions (gl.Double);  -- tbd: make this public ?

   -- Plane
   --

   procedure normalise (the_Plane : in out Plane)
   is
      use REF;
      inv_Magnitude : constant GL.Double := 1.0 / Sqrt (  the_Plane (0) * the_Plane (0)
                                                        + the_Plane (1) * the_Plane (1)
                                                        + the_Plane (2) * the_Plane (2));
   begin
      the_Plane (0) := the_Plane (0) * inv_Magnitude;
      the_Plane (1) := the_Plane (1) * inv_Magnitude;
      the_Plane (2) := the_Plane (2) * inv_Magnitude;
      the_Plane (3) := the_Plane (3) * inv_Magnitude;
   end normalise;

   -- Bounds
   --

   function Max (L, R : in Extent) return Extent
   is
      the_Max : Extent;
   begin
      the_Max.Min := GL.Double'Max (L.Min,  R.Min);
      the_Max.Max := GL.Double'Max (L.Max,  R.Max);

      return the_Max;
   end Max;

   function Max (L, R : in axis_aligned_bounding_Box) return axis_aligned_bounding_Box
   is
      the_Max : axis_aligned_bounding_Box;
   begin
      the_Max.X_Extent := Max (L.X_Extent,  R.X_Extent);
      the_Max.Y_Extent := Max (L.Y_Extent,  R.Y_Extent);
      the_Max.Z_Extent := Max (L.Z_Extent,  R.Z_Extent);

      return the_Max;
   end Max;

   function Max (L, R : in Bounds_record) return Bounds_record
   is
      the_Max : Bounds_record := null_Bounds;
   begin
      the_Max.sphere_Radius := GL.Double'Max (L.sphere_Radius, R.sphere_Radius);
      the_Max.Box           :=           Max (L.Box,           R.Box);

      return the_Max;
   end Max;

   -- vertex_Id's
   --

   procedure increment (Self : in out vertex_Id_array)
   is
   begin
      for Each in Self'Range loop
         Self (Each) := Self (Each) + 1;
      end loop;
   end increment;

   procedure decrement (Self : in out vertex_Id_array)
   is
   begin
      for Each in Self'Range loop
         Self (Each) := Self (Each) - 1;
      end loop;
   end decrement;

   -- vertices
   --

   function Image (Self : in     Vertex) return String
   is
   begin
      return "(" & Double'Image (Self (0)) & Double'Image (Self (1)) & Double'Image (Self (2)) & ")";
   end Image;

   function Bounds (Self : in     Vertex_array) return GL.Geometry.Bounds_record
   is
      use REF;
      the_Bounds     : Bounds_record := null_Bounds;
      max_Distance_2 : GL.Double     := 0.0;           -- Current maximum distance squared.
   begin
      for p in Self'Range loop
         max_Distance_2 := GL.Double'Max (  Self (p)(0) * Self (p)(0)
                                          + Self (p)(1) * Self (p)(1)
                                          + Self (p)(2) * Self (p)(2),
                                          max_Distance_2);

         the_Bounds.Box.X_Extent.Min := GL.Double'Min (the_Bounds.Box.X_Extent.Min,  Self (p)(0));
         the_Bounds.Box.X_Extent.Max := GL.Double'Max (the_Bounds.Box.X_Extent.Max,  Self (p)(0));
         the_Bounds.Box.Y_Extent.Min := GL.Double'Min (the_Bounds.Box.Y_Extent.Min,  Self (p)(1));
         the_Bounds.Box.Y_Extent.Max := GL.Double'Max (the_Bounds.Box.Y_Extent.Max,  Self (p)(1));
         the_Bounds.Box.Z_Extent.Min := GL.Double'Min (the_Bounds.Box.Z_Extent.Min,  Self (p)(2));
         the_Bounds.Box.Z_Extent.Max := GL.Double'Max (the_Bounds.Box.Z_Extent.Max,  Self (p)(2));
      end loop;

      the_Bounds.sphere_Radius := Sqrt (max_Distance_2);

      return the_Bounds;
   end Bounds;

   function Bounds (Vertices : in     Vertex_array;   Indices : in   vertex_Id_array) return GL.Geometry.Bounds_record
   is
      use REF;
      the_Bounds     : Bounds_record := null_Bounds;
      max_Distance_2 : GL.Double     := 0.0;           -- Current maximum distance squared.
   begin
      for Each in Indices'Range loop
         declare
            the_Point : Vertex renames Vertices (Indices (Each));
         begin
            max_Distance_2 := GL.Double'Max (  the_Point (0) * the_Point (0)
                                             + the_Point (1) * the_Point (1)
                                             + the_Point (2) * the_Point (2),   max_Distance_2);

            the_Bounds.Box.X_Extent.Min := GL.Double'Min (the_Bounds.Box.X_Extent.Min,  the_Point (0));
            the_Bounds.Box.X_Extent.Max := GL.Double'Max (the_Bounds.Box.X_Extent.Max,  the_Point (0));
            the_Bounds.Box.Y_Extent.Min := GL.Double'Min (the_Bounds.Box.Y_Extent.Min,  the_Point (1));
            the_Bounds.Box.Y_Extent.Max := GL.Double'Max (the_Bounds.Box.Y_Extent.Max,  the_Point (1));
            the_Bounds.Box.Z_Extent.Min := GL.Double'Min (the_Bounds.Box.Z_Extent.Min,  the_Point (2));
            the_Bounds.Box.Z_Extent.Max := GL.Double'Max (the_Bounds.Box.Z_Extent.Max,  the_Point (2));
         end;
      end loop;

      the_Bounds.sphere_Radius := Sqrt (max_Distance_2);

      return the_Bounds;
   end Bounds;

   function  face_Count    (Self : in     Geometry'Class) return Natural
   is
      the_Count : Natural;
   begin
      case primitive_Id (Self) is
         when POINTS         =>   the_Count :=              Natural (indices_Count (Self));
         when LINES          =>   the_Count :=              Natural (indices_Count (Self) / 2);
         when LINE_LOOP      =>   the_Count :=              Natural (indices_Count (Self));
         when LINE_STRIP     =>   the_Count := Natural'Max (Natural (indices_Count (Self) - 1),  0);
         when TRIANGLES      =>   the_Count :=              Natural (indices_Count (Self) / 3);
         when TRIANGLE_STRIP =>   the_Count := Natural'Max (Natural (indices_Count (Self) - 2),  0);
         when TRIANGLE_FAN   =>   the_Count := Natural'Max (Natural (indices_Count (Self) - 2),  0);
         when QUADS          =>   the_Count :=              Natural (indices_Count (Self) / 4);
         when QUAD_STRIP     =>   the_Count :=              Natural (indices_Count (Self) / 2  -  1);
         when POLYGON        =>   the_Count := 1;
      end case;

      return the_Count;
   end face_Count;

   function Image  (Self : in     Vertex_array) return String
   is
      the_Image : Unbounded_String;
      NL        : constant String := (1 => Ada.Characters.Latin_1.LF);   -- NL: New Line
   begin
      Append (the_Image, "(" & NL);

      for Each in Self'Range loop
         Append (the_Image, " " & vertex_Id'Image (Each) & " => " & Image (Self (Each)) & NL);
      end loop;

      Append (the_Image, ")" & NL);

      return To_String (the_Image);
   end Image;

   -- Abstract Base Geometry Class
   --

   procedure free (Self : in out p_Geometry)
   is
      procedure deallocate is new Ada.Unchecked_Deallocation (Geometry'Class, p_Geometry);
   begin
      destroy    (Self.all);
      deallocate (Self);
   end free;

   function vertex_Normals (Self : in     Geometry'Class) return Normal_array
   is
   begin
      case primitive_Id (Self) is
         when TRIANGLES =>
            declare
               the_Vertices : Vertex_array    renames Vertices (Self);
               the_Indices  : vertex_Id_array renames Indices (Self);
               the_Normals  : Normal_array (the_Vertices'Range);

               face_Count   : constant Positive                 := the_Indices'Length / 3;
               face_Normals :          Normals (1 .. face_Count);

               N            : GL.Double_Vector_3D;
               length_N     : GL.Double;

               function vertex_Id_for (Face : in Positive;   point_Id : in Positive) return vertex_Id
               is
               begin
                  return the_Indices (positive_uInt (3 * (Face - 1) + point_Id));
               end vertex_Id_for;

            begin
               -- Geometry (Normal of unrotated face)
               --
               for each_Face in 1 .. face_Count loop
                  N        :=   (the_Vertices (vertex_Id_for (each_Face, 2)) - the_Vertices (vertex_Id_for (each_Face, 1)))
                              * (the_Vertices (vertex_Id_for (each_Face, 3)) - the_Vertices (vertex_Id_for (each_Face, 1))) ;
                  length_N := Norm( N );

                  if Almost_zero (length_N) then
                     face_Normals (each_Face) := N; -- 0 vector !
                  else
                     face_Normals (each_Face) := (1.0 / length_N) * N;
                  end if;
               end loop;

               -- Calculate normal at each vertex.
               --
               declare
                  vertex_adjacent_faces_Count : array (the_Vertices'Range) of Natural := (others => 0);
                  the_Vertex                  : vertex_Id;
                  length                      : Double;
               begin

                  for p in the_Vertices'Range loop
                     the_Normals (p):= (0.0, 0.0, 0.0);
                  end loop;

                  for f in 1 .. face_Count loop
                     for p in 1 .. 3 loop
                        the_Vertex := vertex_Id_for (f, p);

                        vertex_adjacent_faces_Count (the_Vertex) := vertex_adjacent_faces_Count (the_Vertex) + 1;
                        the_Normals (the_Vertex)                 := the_Normals (the_Vertex) + face_Normals (f);
                     end loop;
                  end loop;

                  for p in the_Vertices'Range loop

                     length:= Norm (the_Normals (p));

                     if not Almost_zero(length) then
                        the_Normals (p) := (1.0 / length) * the_Normals (p);
                     else
                        null; --raise Constraint_Error;  -- tbd: proper exception as usual.
                     end if;
                  end loop;
               end;

               return the_Normals;
            end;

         when others =>
            raise Constraint_Error; -- tbd: finish these
      end case;
   end vertex_Normals;

   procedure free (vert_Id_array : in out p_vertex_Id_array)
   is
      procedure deallocate is new Ada.Unchecked_Deallocation (vertex_Id_array, p_vertex_Id_array);
   begin
      deallocate (vert_Id_array);
   end free;

   procedure free (Vert_array : in out p_Vertex_array)
   is
      procedure deallocate is new Ada.Unchecked_Deallocation (Vertex_array, p_Vertex_array);
   begin
      deallocate (Vert_array);
   end free;

end GL.Geometry;

GLOBE_3D: Ada library for real-time 3D rendering. Ada programming.