Back to... GLOBE_3D

Source file : sierpinski.adb


with GL.Math;

-- with Ada.Text_IO; use Ada.Text_IO;
-- with Ada.Strings.Fixed;                 use Ada.Strings, Ada.Strings.Fixed;

package body Sierpinski is

  -----------------
  -- Create_Cube --
  -----------------

  procedure Create_Cube(
    object       : in out GLOBE_3D.p_Object_3D;
    scale        :        GLOBE_3D.Real;
    centre       :        GLOBE_3D.Point_3D;
    texture      :        Cubic_Face_texture;
    tiled        :        Boolean;
    fractal_level:        Natural
  )
  is
    use GL, GL.Math, GLOBE_3D;

    side: constant Integer:= 3**fractal_level;
    -- We put a layer of empty cubes all around the main one.
    subtype Extended_side_range is Integer range -1..side;
    subtype Side_range is Extended_side_range range 0..side-1;
    subtype Comparison_side_range is Extended_side_range range 0..side;

    filled: array( Extended_side_range,
                   Extended_side_range,
                   Extended_side_range
                 ) of Boolean:=
      (others => (others => (others => False)));

    procedure Fill(x0,y0,z0: Natural; level: Natural) is
      l1: Natural;
      o: Natural;
    begin
      if level=0 then
        filled(x0,y0,z0):= True;
      else
        l1:= level-1;
        o:= 3 ** l1;
        for x in 0..2 loop
          for y in 0..2 loop
            if not (x=1 and y=1) then
              for z in 0..2 loop
                if not ((x=1 and z=1) or (y=1 and z=1)) then
                  Fill(x0+x*o,y0+y*o,z0+z*o,l1);
                end if;
              end loop;
            end if;
          end loop;
        end loop;
      end if;
    end Fill;

    scale_2: constant Real:= scale / Real(side);
    offset : constant Real:= 0.5 * Real(side);

    type Ipoint_3D is array(1..3) of Extended_side_range;

    procedure Trans(i: Ipoint_3D; p: out Point_3D) is
    begin
      p:= scale_2 * (Real(i(1))-offset,Real(i(2))-offset,Real(i(3))-offset);
    end Trans;

    point  : p_Point_3D_array:=
               new Point_3D_array(1..((1+3**fractal_level)**3));
    face   : p_Face_array:=
               new Face_array(1..6*(3**fractal_level)**3);
    face_proto : Face_type; -- takes defaults values

    po, fa: Natural:= 0;

    type Ipoint_2D is array(1..2) of Natural;
    tex_scale: constant Real:= 1.0 / Real(side);

    -- the following is to optimize the search of existing points:
    type Index_stack is array(1..27*6) of Natural;
    touching: array( Extended_side_range,
                     Extended_side_range,
                     Extended_side_range
                 ) of Index_stack:=
      (others => (others => (others => (others=> 0))));

    procedure Register(i,j,k: Integer; idx: Positive) is
    begin
      if i > Extended_side_range'Last or
         j > Extended_side_range'Last or
         k > Extended_side_range'Last
      then
        return;
      end if;
      for s in Index_stack'Range loop
        if touching(i,j,k)(s) = idx then -- already in stack
          return;
        elsif touching(i,j,k)(s) = 0 then
          touching(i,j,k)(s):= idx;
          return;
        end if;
      end loop;
      raise Program_Error; -- cannot have more points in stack
    end Register;

    procedure Do_Face(
      cube_id,
      iP1,iP2,iP3,iP4: Ipoint_3D;
      it1,it2,it3,it4: Ipoint_2D
    )
   is
      P: array(1..4) of Point_3D;
      vtx: GLOBE_3D.Index_array(1..4);
      idx: Natural;
    begin
      Trans(iP1,P(1));
      Trans(iP2,P(2));
      Trans(iP3,P(3));
      Trans(iP4,P(4));
      for pt in P'Range loop
        vtx(pt):= 0;
        for op in Index_stack'Range loop
          idx:= touching(cube_id(1),cube_id(2),cube_id(3))(op);
          if idx = 0 then
            -- no more point in stack
            exit;
          elsif Almost_zero(Norm2(P(pt)-point(idx))) then
            -- exists already
            vtx(pt):= idx;
            exit;
          end if;
        end loop;
        if idx = 0 then
          -- create new point
          po:= po + 1;
          point(po):= P(pt);
          vtx(pt):= po;
          -- add point index to cube's stack
          for i in -1..1 loop
            for j in -1..1 loop
              for k in -1..1 loop
                Register(cube_id(1)+i, cube_id(2)+j, cube_id(3)+k, po);
              end loop;
            end loop;
          end loop;
        end if;
      end loop;
      face_proto.P:= vtx;
      if not tiled then
        face_proto.texture_edge_map:=
          ((tex_scale * Real(it1(1)), tex_scale * Real(it1(2))),
           (tex_scale * Real(it2(1)), tex_scale * Real(it2(2))),
           (tex_scale * Real(it3(1)), tex_scale * Real(it3(2))),
           (tex_scale * Real(it4(1)), tex_scale * Real(it4(2))));
      end if;
      fa:= fa+1;
      face(fa):= face_proto;
    end Do_Face;

  generic
    with function Pm(p: Ipoint_3D) return Ipoint_3D; -- permutation
    front, back: Cubic_Face_count;
  procedure Pave;
  procedure Pave is
  begin
    for i in Side_range loop
      for j in Side_range loop
        for k in Comparison_side_range loop
          -- filled components don't need to be permuted (symmetric)
          if filled(i,j,k) = not filled(i,j,k-1) then
            -- There is a face to display
            if filled(i,j,k) then
              -- * full-empty
              face_proto.texture:= texture(front);
              Do_Face(
                Pm((i,j,k)),
                Pm((i,j,k)),Pm((i,j+1,k)),Pm((i+1,j+1,k)),Pm((i+1,j,k)),
                (i,j),(i,j+1),(i+1,j+1),(i+1,j)
              );
            else
              -- * empty-full
              face_proto.texture:= texture(back);
              Do_Face(
                Pm((i,j,k)),
                Pm((i+1,j,k)),Pm((i+1,j+1,k)),Pm((i,j+1,k)),Pm((i,j,k)),
                (i+1,j),(i+1,j+1),(i,j+1),(i,j)
              );
            end if;
          end if;
        end loop;
      end loop;
    end loop;
  end Pave;

  function Id(p: Ipoint_3D) return Ipoint_3D is
  begin
    return p;
  end Id;

  function Pm1(p: Ipoint_3D) return Ipoint_3D is
  begin
    return (p(3),p(1),p(2));
  end Pm1;

  function Pm2(p: Ipoint_3D) return Ipoint_3D is
  begin
    return (p(2),p(3),p(1));
  end Pm2;

  procedure Pave_z is new Pave(Id,  5,6);
  procedure Pave_y is new Pave(Pm1, 1,3);
  procedure Pave_x is new Pave(Pm2, 2,4);

  begin
    Fill(0,0,0, fractal_level);
    --  -- Test: display 1st layer...
    --  for x in filled'Range(1) loop
    --    for y in filled'Range(2) loop
    --      if filled(x,y,0) then
    --        Put('x');
    --      else
    --        Put(' ');
    --      end if;
    --    end loop;
    --    New_Line;
    --  end loop;

    face_proto.skin:= material_texture;
    face_proto.whole_texture:= tiled;
    face_proto.repeat_U:= 1;
    face_proto.repeat_V:= 1;

    Pave_x;
    Pave_y;
    Pave_z;

    object:= new Object_3D(po,fa);
    object.point:= point(1..po);
    object.face := face(1..fa);

    object.centre:= centre;
    Set_name(object.all,
        "Sierpinski cube-sponge, fractal level" &
        Integer'Image(fractal_level)
    );
    Dispose(point);
    Dispose(face);
  end Create_Cube;

end Sierpinski;

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