Back to... GLOBE_3D

Source file : globe_3d-aux.adb


with GL.Math, GLOBE_3D.Math;

package body GLOBE_3D.Aux is

  use GL, GL.Math;

  subtype Tri_count is Integer range 1..3;
  subtype Zero_or_tri_count is Integer range 0..3;

  function Is_right_angled_triangle(
    o        : Object_3D;
    face_num : Positive
  )
  return Zero_or_tri_count
  is
    P1, P2, P3: Point_3D;
  begin
    if o.face_internal(face_num).last_edge = 4 then
      return 0;
    end if;
    P1:= o.point(o.face_internal(face_num).P_compact(1));
    P2:= o.point(o.face_internal(face_num).P_compact(2));
    P3:= o.point(o.face_internal(face_num).P_compact(3));
    --
    if    Almost_zero((P2-P1) * (P3-P1)) then
      return 1;
    elsif Almost_zero((P1-P2) * (P3-P2)) then
      return 2;
    elsif Almost_zero((P2-P3) * (P1-P3)) then
      return 3;
    else
      return 0;
    end if;
  end Is_right_angled_triangle;

  --  VR----- V2
  --    |  /|
  --    | / |
  --    |/__|
  --  V1     Point returned
  --
  function Rectangle_completion(
    o           : Object_3D;
    face_num    : Positive;
    right_angle : Tri_count
  )
  return Point_3D
  is
    use GLOBE_3D.Math;
    first: Boolean:= True;
    C, P, V1, V2, VR: Point_3D;
    u: constant Vector_3D:= o.face_internal(face_num).normal;
    --  R: Rotate half-turn around axis = face's normal
    R: constant Matrix_33:=
      ( (2.0*u(0)*u(0) - 1.0, 2.0*u(1)*u(0),       2.0*u(2)*u(0)      ),
        (2.0*u(0)*u(1)      , 2.0*u(1)*u(1) - 1.0, 2.0*u(2)*u(1)      ),
        (2.0*u(0)*u(2)      , 2.0*u(1)*u(2)      , 2.0*u(2)*u(2) - 1.0) );
  begin
    for i in Tri_count loop
      P:= o.point(o.face_internal(face_num).P_compact(i));
      if i = right_angle then
        VR:= P;
      elsif first then
        V1:= P;
        first:= False;
      else
        V2:= P;
      end if;
    end loop;
    C:= 0.5 * (V1 + V2);
    return C + R * (VR - C);
  end Rectangle_completion;

  --  Same for texture coordinates
  --  Assumption: textured skin, o.face.whole_texture(face_num) = False

  --  VR----- V2
  --    |  /|
  --    | / |
  --    |/__|
  --  V1     Point returned
  --
  function Rectangle_completion(
    o           : Object_3D;
    face_num    : Positive;
    right_angle : Tri_count
  )
  return Map_idx_pair
  is
    use GLOBE_3D.Math;
    first: Boolean:= True;
    C, P, V1, V2, VR: Map_idx_pair;
  begin
    for i in Tri_count loop
      P:= o.face_internal(face_num).UV_extrema(i);
      if i = right_angle then
        VR:= P;
      elsif first then
        V1:= P;
        first:= False;
      else
        V2:= P;
      end if;
    end loop;
    C:= 0.5 * (V1 + V2);
    return C + (-1.0) * (VR - C);
  end Rectangle_completion;

  --  -----
  --  |A /|
  --  | /B|
  --  |/__|
  --
  function Matching_right_angled_triangles(
    o        : Object_3D;
    fa, fb   : Positive;   --  Face index of both right-angled triangles
    raa, rab : Tri_count   --  Right angle vertex index of both triangles in P_compact arrays.
  )
  return Boolean
  is
    --  Conditions with numerical identity
    function Match_point(P1, P2: Point_3D) return Boolean renames GL.Math.Identical;
    function Match_vector(P1, P2: Vector_3D) return Boolean renames GL.Math.Identical;
    function Match_UV(P1, P2: Map_idx_pair) return Boolean renames GLOBE_3D.Identical;
    function Match_color(P1, P2: RGB_Color) return Boolean renames GL.Math.Identical;
    function Match_alpha(a1, a2: GL.Double) return Boolean is
    begin
      return GL.Math.Almost_zero(a1-a2);
    end Match_alpha;
    function Match_material(M1, M2: GL.Materials.Material_type) return Boolean
      renames GL.Materials.Identical;
    match_count: Natural:= 0;
    use GL.Materials;
  begin
    if not Match_vector(o.face_internal(fa).normal, o.face_internal(fb).normal) then
      return False;
    end if;
    if o.face(fa).skin /= o.face(fb).skin then
      return False;
    end if;
    --  We look if two vertices, not with the right angle, of rectangle A match
    --  two vertices of rectangle B (again, not with the right angle)
    for ia in Tri_count loop
      if ia /= raa then
        for ib in Tri_count loop
          if ib /= rab then
            if Match_point(
              o.point(o.face_internal(fa).P_compact(ia)),
              o.point(o.face_internal(fb).P_compact(ib))
            )
            then
              match_count:= match_count + 1;
            end if;
          end if;
        end loop;
      end if;
    end loop;
    if match_count /= 2 then  --  if match_count = 3 or 4, we have 1 or 2 degenerated triangles.
      return False;
    end if;
    if not Match_point(
      Rectangle_completion(o, fa, raa),
      o.point(o.face_internal(fb).P_compact(rab))
    )
    then
      return False;
    end if;
    --  At this point, triangles can be merged geometrically and have the same skin type.
    --  We check if they are compatible.
    if o.face(fa).skin = invisible then
      return True;
    end if;
    if is_textured(o.face(fa).skin) then
      if o.face(fa).texture = null_image then
        --  Names not yet resolved
        if o.face_internal(fa).texture_name  /= o.face_internal(fb).texture_name or else
           o.face_internal(fa).specular_name /= o.face_internal(fb).specular_name
        then
          return False;
        end if;
      else
        if o.face(fa).texture      /= o.face(fb).texture or else
           o.face(fa).specular_map /= o.face(fb).specular_map
        then
          return False;
        end if;
      end if;
      --  Now we check if the points in texture coordinates would match
      if not Match_UV(Rectangle_completion(o, fa, raa), o.face_internal(fb).UV_extrema(rab)) then
        return False;  --  Picture would not match the one of both triangles.
      end if;
    end if;
    if is_coloured(o.face(fa).skin) then
      if (not Match_color(o.face(fa).colour, o.face(fb).colour)) or else
         (not Match_alpha(o.face(fa).alpha,  o.face(fb).alpha))
      then
        return False;
      end if;
    end if;
    if is_material(o.face(fa).skin) then
      if not Match_material(o.face(fa).material, o.face(fb).material) then
        return False;
      end if;
    end if;
    return True;
  end Matching_right_angled_triangles;

  function Merge_triangles(obj: Object_3D) return Object_3D is
    o: Object_3D:= obj;  --  Clone, for pre-calculating if needed.
    right_angled_triangle_idx_0123: array(1..o.Max_faces) of Zero_or_tri_count;
    matching_index: array(1..o.Max_faces) of Natural:= (others => 0);
    matched: array(1..o.Max_faces) of Boolean:= (others => False);
    raa, rab: Zero_or_tri_count;
    face_reduction: Natural:= 0;
  begin
    if not o.pre_calculated then
      o.Pre_calculate;
    end if;
    --  First, flag all right-angled triangles.
    for f in 1..o.Max_faces loop
      right_angled_triangle_idx_0123(f):= Is_right_angled_triangle(o, f);
    end loop;
    --  Find matching pairs of right-angled triangles.
    for fa in 1..o.Max_faces loop
      raa:= right_angled_triangle_idx_0123(fa);
      if raa > 0 then
        for fb in fa+1..o.Max_faces loop
          rab:= right_angled_triangle_idx_0123(fb);
          if rab > 0 then
            if Matching_right_angled_triangles(o, fa, fb, raa, rab) then
              --  fa will become a rectangle in new object.
              --  fb will be ignored in new object.
              matching_index(fa):= fb;
              matched(fb):= True;
              face_reduction:= face_reduction + 1;
            end if;
          end if;
        end loop;
      end if;
    end loop;
    --  Build compacted object
    declare
      res: Object_3D( Max_points=> o.Max_points, Max_faces=> o.Max_faces - face_reduction );
      nf: Natural:= 0;
      fb: Natural;
      ra: Tri_count;
      new_vertex_id: Positive;
      new_UV: Map_idx_pair;
      aux: Positive;
    begin
      --  Clone basic features
      res.ID          := o.ID;
      res.centre      := o.centre;
      res.point       := o.point;
      res.sub_objects := o.sub_objects;
      for f in 1..o.Face_Count loop
        if matched(f) then
          null;  --  skip this face
        else
          nf:= nf + 1;
          --  Clone face features
          res.face(nf):= o.face(f);
          res.face_internal(nf):= o.face_internal(f);
          fb:= matching_index(f);
          if fb > 0 then
            --  We transform the triangle into a rectangle, taking the extra vertex from fb.
            ra := right_angled_triangle_idx_0123(f);
            rab:= right_angled_triangle_idx_0123(fb);
            new_vertex_id:= o.face_internal(fb).P_compact(rab);
            new_UV:= o.face_internal(fb).UV_extrema(rab);
            --  Now, we need to have a correct orientation for the new rectangle.
            --  It depends on which vertex has the right angle.
            --  The new vertex need to be on the other side, and you need to take everybody
            --  without making a 'Z'.
            --  Ouch! But with a drawing it's easy...
            case ra is
              when 1 =>  --  New vertex to be inserted between 2 and 3. 1 and 2 are ok.
                res.face(nf).P(1):= o.face_internal(f).P_compact(1);
                res.face(nf).P(2):= o.face_internal(f).P_compact(2);
                res.face(nf).P(3):= new_vertex_id;
                res.face(nf).P(4):= o.face_internal(f).P_compact(3);
              when 2 =>  --  New vertex to be inserted after 3.
                res.face(nf).P(1):= o.face_internal(f).P_compact(1);
                res.face(nf).P(2):= o.face_internal(f).P_compact(2);
                res.face(nf).P(3):= o.face_internal(f).P_compact(3);
                res.face(nf).P(4):= new_vertex_id;
              when 3 =>  --  New vertex to be inserted between 1 and 2.
                res.face(nf).P(1):= o.face_internal(f).P_compact(1);
                res.face(nf).P(2):= new_vertex_id;
                res.face(nf).P(3):= o.face_internal(f).P_compact(2);
                res.face(nf).P(4):= o.face_internal(f).P_compact(3);
            end case;
            if is_textured(res.face(nf).skin) then
              if res.face(nf).whole_texture then
                --  Edges are calculated in Calculate_face_internals (Pre_calculate).
                --    - either the (0,0) point in texture was with the original triangle,
                --      the P(1) was already = P_compact(1) and is preserved by the above ordering
                --    - or the new edge has the (0,0): then we need to reorder...
                if Identical(new_UV, (0.0, 0.0)) then
                  for count in 1..4 loop
                    --  Rotate the edge indices
                    aux:= res.face(nf).P(1);
                    res.face(nf).P(1):= res.face(nf).P(2);
                    res.face(nf).P(2):= res.face(nf).P(3);
                    res.face(nf).P(3):= res.face(nf).P(4);
                    res.face(nf).P(4):= aux;
                    exit when res.face(nf).P(1) = new_vertex_id;  --  Now (0,0) is on P(1).
                  end loop;
                end if;
              else
                --  Re-ouch!
                case ra is
                  when 1 =>  --  New vertex to be inserted between 2 and 3. 1 and 2 are ok.
                    res.face(nf).texture_edge_map(1):= o.face_internal(f).UV_extrema(1);
                    res.face(nf).texture_edge_map(2):= o.face_internal(f).UV_extrema(2);
                    res.face(nf).texture_edge_map(3):= new_UV;
                    res.face(nf).texture_edge_map(4):= o.face_internal(f).UV_extrema(3);
                  when 2 =>  --  New vertex to be inserted after 3.
                    res.face(nf).texture_edge_map(1):= o.face_internal(f).UV_extrema(1);
                    res.face(nf).texture_edge_map(2):= o.face_internal(f).UV_extrema(2);
                    res.face(nf).texture_edge_map(3):= o.face_internal(f).UV_extrema(3);
                    res.face(nf).texture_edge_map(4):= new_UV;
                  when 3 =>  --  New vertex to be inserted between 1 and 2.
                    res.face(nf).texture_edge_map(1):= o.face_internal(f).UV_extrema(1);
                    res.face(nf).texture_edge_map(2):= new_UV;
                    res.face(nf).texture_edge_map(3):= o.face_internal(f).UV_extrema(2);
                    res.face(nf).texture_edge_map(4):= o.face_internal(f).UV_extrema(3);
                end case;
              end if;
            end if;
          end if;
        end if;
      end loop;
      res.Pre_calculate;
      return res;
    end;
  end Merge_triangles;

  procedure Set_ident(i: out Ident; name: String)
  is
  begin
    if name'Length > Ident'Length then
      raise Constraint_Error with "Name too long for fixed-length type Ident";
    end if;
    i:= empty;  --  Stuff with blanks.
    i(1..name'Length):= name;
  end Set_ident;

  procedure Texture_name_hint(
    o   : in out Object_3D'Class;
    face:        Positive;
    name:        String  --  give name as hint for texture
  )
  is
  begin
    Set_ident(o.face_internal(face).texture_name, name);
  end Texture_name_hint;

  procedure Specular_name_hint(
    o   : in out Object_3D'Class;
    face:        Positive;
    name:        String  --  give name as hint for texture
  )
  is
  begin
    Set_ident(o.face_internal(face).specular_name, name);
  end Specular_name_hint;

  procedure Portal_name_hint(
    o   : in out Object_3D'Class;
    face:        Positive;
    name:        String  --  give name as hint for connected object
  )
  is
  begin
    Set_ident(o.face_internal(face).connect_name(1..name'Length), name);
  end Portal_name_hint;

  function Image( r: Real ) return String is
    s: String(1..10);
  begin
    RIO.Put(s,r,4,0);
    return s;
  exception
    when Ada.Text_IO.Layout_Error =>
      return Real'Image(r);
  end Image;

  function Coords( p: Point_3D ) return String is
  begin
    return '(' & Image(p(0)) &
           ',' & Image(p(1)) &
           ',' & Image(p(2)) &
           ')';
  end Coords;

  prec_a360    : constant:= 10000;
  r_prec_a360  : constant:= 10000.0;
  i_r_prec_a360: constant:= 1.0 / r_prec_a360;

  procedure Angles_modulo_360( v: in out Vector_3D )is
  begin
    for i in v'Range loop
      v(i):=
        GL.Double(Integer(r_prec_a360 * v(i)) mod (360*prec_a360))
        * i_r_prec_a360;
    end loop;
  end Angles_modulo_360;

  --  Blending support
  --

  function Is_to_blend(m: GL.Double) return Boolean is
  begin
    return not Almost_zero(m-1.0);
  end Is_to_blend;

  function Is_to_blend(m: GL.Float) return Boolean is
  begin
    return not Almost_zero(m-1.0);
  end Is_to_blend;

  function Is_to_blend(m: GL.Material_Float_vector) return Boolean is
  begin
    return Is_to_blend(m(3));
  end Is_to_blend;

  function Is_to_blend(m: GL.Materials.Material_type) return Boolean  is
  begin
    return
      Is_to_blend(m.ambient) or
      Is_to_blend(m.diffuse) or
      Is_to_blend(m.specular);
      -- m.emission, m.shininess not relevant
  end Is_to_blend;

end GLOBE_3D.Aux;

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