Back to... GLOBE_3D

Source file : globe_3d_demo.adb


------------------------------------------------------------------------------
--  File:            GLOBE_3D_Demo.adb
--  Description:     A small demo for GLOBE_3D
--  Copyright (c) Gautier de Montmollin 2002 .. 2016
------------------------------------------------------------------------------

with GL,
     GL.IO,
     GL.Materials,
     GL.Math;

with GLOBE_3D,
     GLOBE_3D.IO,
     GLOBE_3D.BSP,
     GLOBE_3D.Options,
     GLOBE_3D.Math,
     GLOBE_3D.Textures,
     GLOBE_3D.Software_Anti_Aliasing,
     GLOBE_3D.Stars_sky,
     GLOBE_3D.Collision_detection,
     GLOBE_3D.Aux;

with GLU, GLUT.Devices, GLUT_2D;

with Actors, Game_control;

---------------
-- 3D models --
---------------

with Vehic001, Vehic002,
     X29,
     Brick, Icosahedron,
     SkotKnot, Lissajous,
     Knot_10_102, Knot_link,
     Planet,
     A319,
     Dreadnought,
     --  VRML_scene,
     --  gmax_scene,
     --  Doom3_Level,
     Extruded_surface,
     Sierpinski
     ;

-- with Ada.Text_IO;

with Ada.Numerics;                      use Ada.Numerics;
with Ada.Command_Line;
with Ada.Strings.Fixed;                 use Ada.Strings, Ada.Strings.Fixed;
with Ada.Strings.Unbounded;             use Ada.Strings.Unbounded;
with Ada.Characters.Handling;           use Ada.Characters.Handling;

procedure GLOBE_3D_Demo is

  --pragma Linker_options("-mwindows"); -- Suppress console window

  package G3D  renames GLOBE_3D;
  package G3DM renames G3D.Math;

  fairly_far: constant:= 10_000.0;

  package Stars is new GLOBE_3D.Stars_sky(
    num_stars => 5_000,
    far_side  => fairly_far
  );

  GLUT_Problem: exception;

  main_size_x, main_size_y: GL.Sizei;

  foggy: constant Boolean:= True;

  frontal_light: G3D.Light_definition;

  procedure Prepare_demo_lighting(fact: GL.Float) is
    use GL, G3D;
    proto_light: Light_definition:=
      (position => (3.0, 4.0, 10.0, 1.0),
       ambient  => (0.2, 0.2, 0.2, fact),
       diffuse  => (0.9, 0.9, 0.9, fact),  -- +/- a bulb
       specular => (0.8, 0.8, 0.8, fact)); -- +/- a flashlight
  begin
    Enable( LIGHTING );
    G3D.Define( 1, proto_light);
    frontal_light:= proto_light;
    proto_light.diffuse:= (0.5, 0.9, 0.5, fact);
    G3D.Define( 2, proto_light);
    proto_light.position:= (10.0, 0.0, 0.0, 1.0);
    proto_light.diffuse:= (0.2, 0.0, 0.9, fact);
    proto_light.specular:= (1.0, 1.0, 1.0, fact);
    G3D.Define( 3, proto_light);
    proto_light.position:= (-3.0, 4.0, 10.0, 1.0);
    G3D.Define( 4, proto_light);
    proto_light.position:= (3.0, -4.0, 10.0, 1.0);
    proto_light.ambient := (0.6, 0.6, 0.6, 0.1);
    G3D.Define( 5, proto_light);
    proto_light.ambient := (0.6, 0.0, 0.0, 0.1);
    G3D.Define( 6, proto_light);
    proto_light.ambient := (0.0, 0.6, 0.0, 0.1);
    G3D.Define( 7, proto_light);
    proto_light.ambient := (0.0, 0.0, 0.6, 0.1);
    G3D.Define( 8, proto_light);
    G3D.Switch_lights(True);
    G3D.Switch_light(4,False);
    G3D.Switch_light(5,False);
    G3D.Switch_light(6,False);
    G3D.Switch_light(7,False);
    G3D.Switch_light(8,False);
  end Prepare_demo_lighting;

  procedure Clear_modes is
    use GL;
  begin
    Disable( BLEND );
    Disable( LIGHTING );
    Disable( AUTO_NORMAL );
    Disable( NORMALIZE );
    Disable( DEPTH_TEST );
  end Clear_modes;

  deg2rad   : constant:= Pi / 180.0;
  ego: G3D.Camera;

  procedure Set_Background_Color is
    use GL;
    fact: constant:= 0.4;
  begin
    ClearColor( fact * 0.2275, fact * 0.0745, fact * 0.4431, 0.0 );  --  Dark violet
  end Set_Background_Color;

  procedure Reset_for_3D( width, height: Integer ) is
    use GL, G3D, G3D.REF;
    aspect, half_fov_max_rads, fovy: Real;
  begin
    Viewport(0, 0, Sizei(width), Sizei(height));
    MatrixMode( PROJECTION );
    LoadIdentity;
    aspect:= GL.Double(width) / GL.Double(height);
    fovy:= ego.FoVy;
    half_fov_max_rads:= 0.5 * fovy * deg2rad;
    if aspect > 1.0 then -- x side angle broader than y side angle
      half_fov_max_rads:= Arctan(aspect * Tan(half_fov_max_rads));
    end if;
    ego.clipper.max_dot_product:= Sin(half_fov_max_rads);
    ego.clipper.main_clipping:= (0,0, width-1, height-1);
    GLU.Perspective(
      fovy   => fovy,
      -- field of view angle (deg) in the y direction
      aspect => aspect,
      -- x/y aspect ratio
      zNear  => 1.0,
      -- distance from the viewer to the near clipping plane
      zFar   => fairly_far
      -- distance from the viewer to the far clipping plane
    );
    -- The matrix generated by GLU.Perspective is
    -- multipled by the current matrix
    MatrixMode( MODELVIEW );
    ShadeModel( SMOOTH ); -- GL's default is SMOOTH, vs FLAT
    Set_Background_Color;
    -- Specify clear values for the accumulation buffer
    ClearAccum(0.0, 0.0, 0.0, 0.0);
  end Reset_for_3D;

  forget_mouse: Natural:= 0;
  capturing_video: Boolean:= False;

  procedure Window_Resize( width, height: Integer ) is
  begin
    if capturing_video then
      GL.IO.Stop_capture;
    end if;
    main_size_x:= GL.Sizei(width);
    main_size_y:= GL.Sizei(height);
    forget_mouse:= 5;
    Reset_for_3D( Integer(main_size_x), Integer(main_size_y ));
  end Window_Resize;

  full_screen: Boolean:= False;

  procedure Say_bye_to_GLUT is
  begin
    if capturing_video then
      GL.IO.Stop_capture;
    end if;
    GLUT.LeaveMainLoop;
  end Say_bye_to_GLUT;

  procedure Menu( value: Integer ) is
    --  GameModeString("800x600:16@60") ;
    --  if GameModeGet(GLUT_GAME_MODE_WIDTH) /= -1 then -- if the width is different to -1
    --     EnterGameMode;                               -- enter full screen mode
    --  else                                            -- print out that this mode is not available
    --     printf("The current mode is not supported in this system\n") ;

    -- res: Integer;
    --  Full_Screen_Mode   : constant String:= "640x480:16@60";
    --  Full_Screen_Mode_2 : constant String:= "400x300:16@60";
  begin
    case value is
      when 1 => -- GLUT.GameModeString (Full_Screen_Mode);
                GLUT.FullScreen;
                -- res := GLUT.EnterGameMode;
                GLUT.SetCursor(GLUT.CURSOR_NONE);
                forget_mouse:= 10;
                full_screen:= True;
      when 2 => Say_bye_to_GLUT;
      when others => null;
    end case;
  end Menu;

  alpha: GL.Double:= 1.0;

  bri1, bri2,
  ico, icos,
  -- vrml,
  -- gmax,
  knot, liss,
  knot_10_102_obj, knot_link_obj,
  globe,
  a319_plane,
  x29_plane,
  vhc_001, vhc_002,
  dreadnought_ship,
  extrude_test_1, borg_star,
  sierp,
  cube, cube_glossy, cube_tri, cube_tri_quad, cube_bico : G3D.p_Object_3D;

  bestiaire, level_stuff: G3D.p_Object_3D_array:= null;
  level_idx, bri_idx, beast_idx: Integer;
  level_BSP: G3D.BSP.p_BSP_node:= null;

  procedure Create_objects(load: Boolean; doom3_custom: String) is

    level_map: G3D.Map_of_Visuals:= G3D.empty_map; -- dictionary

    procedure Load_Doom(name: String) is
      area_max: Natural:= 0;
      ls: G3D.Object_3D_array(1..1_000);
      so: G3D.p_Object_3D;
      empty_level: exception;
      use G3D.Ident_Vectors;
      cv: Cursor;
      id: G3D.Ident;
    begin
      Area_loop: for i in ls'Range loop
        begin
          G3D.IO.Load(
            name & "_$_area" & Trim(Integer'Image(i-1),Left),
            ls(i)
          );
        exception
          when G3D.Missing_object =>
            exit Area_loop;  --  Previous area was the last one, exit.
        end;
        area_max:= i;
        G3D.Add(level_map, G3D.p_Visual(ls(i))); -- add area to dictionary
        --
        --  Load sub-objects for area #i.
        --
        --  for id of ls(i).sub_obj_ids loop  --  Ada 2012 shortcut notation
        cv:= ls(i).sub_obj_ids.First;
        while Has_Element(cv) loop
          id:= Element(cv);
          G3D.IO.Load(id, so);
          ls(i).sub_objects:= new G3D.Object_3D_list'(
            objc => so,
            next => ls(i).sub_objects
          );
          G3D.Add(level_map, G3D.p_Visual(so)); -- add sub-object to dictionary
          Next(cv);
        end loop;
      end loop Area_loop;
      begin
        G3D.IO.Load(name, level_map, level_BSP); -- load BSP tree
      exception
        when G3D.Missing_object =>
          null; -- Some custom levels like the Reims Cathedral have no BSP
      end;
      if area_max = 0 then
        -- Perhaps just one object to display
        -- In this case we have only the name, no area counter
        -- E.g. a319.g3d inside of a319.zip
        begin
          G3D.IO.Load(name, ls(1));
          area_max:= 1;
          G3D.Add(level_map, G3D.p_Visual(ls(1))); -- add to dictionary
        exception
          when G3D.Missing_object =>
            raise empty_level with "Object name " & name & " not found";
        end;
      end if;
      level_stuff:= new G3D.Object_3D_array'(ls(1..area_max));
    end Load_Doom;

    t: constant:= 20.0;
    f2: Natural;
    use GL, GL.Materials, G3D, G3D.Textures;

    function Basic_cube_face(
      P       : G3D.Index_array;
      tex_name: String;
      colour  : GL.RGB_Color;
      repeat  : Positive;
      material: Material_type:= neutral_material)
    return Face_type
    is
      f: Face_type; -- takes defaults values
    begin
      f.P       := P;
      f.texture := Texture_ID(tex_name);
      f.repeat_U:= repeat;
      f.repeat_V:= repeat;
      if material = neutral_material then
        f.colour  := colour;
        f.skin    := coloured_texture;
      else
        f.material := material;
        f.skin     := material_texture;
      end if;
      f.alpha   := alpha;
      return f;
    end Basic_cube_face;

    portal1, portal2: Brick.Cubic_Face_index;

    Shiny : constant Material_type:= (
      ambient =>        (0.24725, 0.2245, 0.0645, 1.0),
      diffuse =>        (0.34615, 0.3143, 0.0903, 1.0),
      specular =>       (1.0, 1.0, 1.0, 1.0),
      emission =>       (0.0, 0.0, 0.0, 0.0),
      shininess =>      64.0);

  begin
    -- Basic cube
    cube:= new G3D.Object_3D( Max_points=> 8, Max_faces=> 6 );
    cube.centre:= (0.0,0.0, -4.0 - 3.0*t);
    cube.point:=
      ( (-t,-t,-t), (-t, t,-t), ( t, t,-t), ( t,-t,-t),
        (-t,-t, t), (-t, t, t), ( t, t, t), ( t,-t, t));
    cube.face:=
      ( Basic_cube_face((3,2,6,7),"face1",(1.0,0.0,0.0),1, Polished_Gold),
        Basic_cube_face((4,3,7,8),"face2",(0.0,1.0,0.0),2, Shiny),
        Basic_cube_face((8,7,6,5),"face3",(0.0,0.0,1.0),3),
        Basic_cube_face((1,4,8,5),"face4",(1.0,1.0,0.0),4),
        Basic_cube_face((2,1,5,6),"face5",(0.0,1.0,1.0),5),
        Basic_cube_face((3,4,1,2),"face6",(1.0,0.0,1.0),6));
    Set_name(cube.all,"Trust the Cube !");
    --  Basic cube, but with a specular map. A glossy 'S' should appear...
    cube_glossy:= new G3D.Object_3D'Class'(cube.all);  --  cloning
    for f in cube.face'Range loop
      cube_glossy.face(f).specular_map:= Texture_ID("face_specular");
    end loop;
    Set_name(cube_glossy.all,"Shiny cube (check the 'S' !)");
    --
    -- Basic cube, but with half-faces as triangles
    -- must look identical as the first one
    cube_tri:= new G3D.Object_3D( Max_points=> 8, Max_faces=> 12 );
    cube_tri.centre:= cube.centre;
    cube_tri.point:= cube.point;
    for f in cube.face'Range loop
      f2:= (f-cube.face'First)*2+cube_tri.face'First;
      cube_tri.face(f2)       := cube.face(f);
      cube_tri.face(f2).P(1)  := 0;
      cube_tri.face(f2+1)     := cube.face(f);
      cube_tri.face(f2+1).P(3):= 0;
      -- Now we have killed opposite edges, one on each triangle :-)
    end loop;
    Set_name(cube_tri.all,"Triangular Cube !");

    cube_tri_quad:= new G3D.Object_3D'(GLOBE_3D.Aux.Merge_triangles(Object_3D(cube_tri.all)));
    Set_name(cube_tri_quad.all,"Cube, triangles merged to squares");

    -- Also a cube with half-faces, but playing with colour/texture
    cube_bico:= new G3D.Object_3D'Class'(cube_tri.all); -- cloning
    Set_name(cube_bico.all,"Technicolor");
    for f in cube_bico.face'Range loop
      if f mod 2 = 0 then
        if cube_bico.face(f).skin = coloured_texture then
          cube_bico.face(f).skin:= colour_only;
        else
          cube_bico.face(f).skin:= material_only;
        end if;
      end if;
    end loop;

    -- Plane: Airbus A319
    --
    A319.Create(
      object => a319_plane,
      scale  => 20.0,
      centre => (0.0,0.0,-796.0)
    );
    -- G3D.IO.Load("A319", a319_plane);

    -- Plane: X29 prototype
    X29.Create(
      object => x29_plane,
      scale  => 10.0,
      centre => (0.0,0.0,-170.0)
    );

    -- Space vehicle 1
    Vehic001.Create(
      object => vhc_001,
      scale  => 4.0,
      centre => (0.0,0.0,-180.0)
    );
    -- Space vehicle 2
    Vehic002.Create(
      object => vhc_002,
      scale  => 100.0,
      centre => (80.0,0.0,-70.0),
      metal_door    => Texture_ID("portmet1"),
      metal_surface => Texture_ID("fdmetal1"),
      bumped_blue   => Texture_ID("bleubosl")
    );
    Pre_calculate(vhc_002.all);

    Icosahedron.Create(
      object => ico,
      scale  => 12.0,
      centre => (0.0,0.0,-60.0),
      alpha  => 0.8,
      polyball => False
    );

    Icosahedron.Create(
      object => icos,
      scale  => 12.0,
      centre => ico.centre,
      alpha  => 0.5,
      polyball => True
    );
    for i in icos.face'Range loop
      icos.face(i).skin:= material_only;
      case (i-1) / 20 is
        -- Non-transparent things
        when  1 => icos.face(i).material:= GL.Materials.Brass;
        when  2 => icos.face(i).material:= GL.Materials.Bronze;
        when  3 => icos.face(i).material:= GL.Materials.Copper;
        when  4 => icos.face(i).material:= GL.Materials.Polished_Copper;
        when  5 => icos.face(i).material:= GL.Materials.Gold;
        when  6 => icos.face(i).material:= GL.Materials.Polished_Bronze;
        -- Transparent things (Nabokov!)
        when  7 => icos.face(i).material:= GL.Materials.Pewter;
        when  8 => icos.face(i).material:= GL.Materials.Pearl;
        when  9 => icos.face(i).material:= GL.Materials.Obsidian;
        when 10 => icos.face(i).material:= GL.Materials.Jade;
        when 11 => icos.face(i).material:= GL.Materials.Emerald;
        when  0 => icos.face(i).material:= GL.Materials.Ruby;
        when others => null;
      end case;
    end loop;

    -- Dreadnought space ship modeled with GMax
    Dreadnought.Create(
      object => dreadnought_ship,
      scale  => 0.065,
      centre => (0.0, -250.0, -700.0),
      alum_001 => Texture_ID("alum_001"),
      alum_002 => Texture_ID("alum_002"),
      grumnoir => Texture_ID("grumnoir"),
      tole_001 => Texture_ID("tole_001")
    );
    Set_name(dreadnought_ship.all, "Dreadnought");
    -- G3D.IO.Load("Dreadnought", dreadnought_ship);
    Pre_calculate(dreadnought_ship.all);

    Extruded_surface.Create(
      object     => extrude_test_1,
      scale      => 400.0,
      centre     => (-160.0,-160.0,-300.0),
      grid       => 57,
      surface    => Extruded_surface.square,
      max_u3     => 0.15,
      iterations => 100,
      hor_tex    => Texture_ID("spacity1"),
      ver_tex    => Texture_ID("spacity1"),
      tiling_hu  => 1,
      tiling_hv  => 1,
      tiling_vu  => 2,
      tiling_vv  => 2
    );
    Set_name(extrude_test_1.all,"Space City");

    Extruded_surface.Create(
      object     => borg_star,
      scale      => 500.0,
      centre     => (0.0,0.0,-1000.0),
      grid       => 65,
      surface    => Extruded_surface.sphere,
      max_u3     => 0.03,
      iterations => 2000,
      hor_tex    => Texture_ID("alum_001"),
      ver_tex    => Texture_ID("spacity1"),
      tiling_hu  => 30, -- ~ 2 * v-tiling
      tiling_hv  => 15,
      tiling_vu  => 31, -- should be ~ 2*pi* v-tiling
      tiling_vv  => 5
    );
    Set_name(borg_star.all,"Borg Star");

    Sierpinski.Create_Cube(
      object  => sierp,
      scale   => 200.0,
      centre  => (0.0,0.0,-300.0),
      texture => (Texture_ID("face1"),
                  Texture_ID("face2"),
                  Texture_ID("face3"),
                  Texture_ID("face4"),
                  Texture_ID("face5"),
                  Texture_ID("face6")),
      tiled   => False,
      fractal_level => 2
    );

    Planet.Create(
      object   => globe,
      scale    => 200.0,
      centre   => (0.0,0.0,-800.0),
      mercator => Texture_ID("earth_map"),
      parts    => 47
    );
    Set_name(globe.all,"The Earth !");
    Pre_calculate(globe.all);

    SkotKnot.Create(
      object => knot,
      scale  => 1.0,
      centre => (0.0,0.0,-40.0)
    );
    Pre_calculate(knot.all);

    Lissajous.Create(
      object => liss,
      scale  => 1.0,
      centre => (0.0,0.0,-25.0)
    );
    Pre_calculate(liss.all);

    Knot_10_102.Create(
      object => knot_10_102_obj,
      scale  => 1.0,
      centre => (0.0,0.0,-50.0)
    );
    Pre_calculate(knot_10_102_obj.all);

    Knot_link.Create(
      object => knot_link_obj,
      scale  => 1.0,
      centre => (0.0,0.0,-50.0)
    );
    Pre_calculate(knot_link_obj.all);

    --
    -- Load a Doom 3 level from .g3d/.bsp files
    --
    if doom3_custom = "" then
      Load_Doom("Delta4g1");
    else
      G3D.Set_level_data_name(doom3_custom & ".zip");
      G3D.Textures.Reset_textures;
      G3D.Textures.Register_textures_from_resources;
      Load_Doom(doom3_custom);
    end if;

    if load and doom3_custom = "" then
      -- We test here the loading and mutual linking
      -- of some objects dumped by the -dump option.
      --
      -- Load the space station scene:
      --
      G3D.IO.Load("Space station brick ONE",bri1);
      G3D.IO.Load("Space station brick TWO",bri2);
      -- Relink both bricks:
      declare
        bricks_map: constant Map_of_Visuals:= Map_of((p_Visual(bri1),p_Visual(bri2)));
      begin
        G3D.Rebuild_links(bri1.all,bricks_map,False,False,True);
        G3D.Rebuild_links(bri2.all,bricks_map,False,False,True);
      end;
      Set_name(bri1.all,"Space station brick ONE (loaded)");
      Set_name(bri2.all,"Space station brick TWO (loaded)");
    else
      -- Create objects, don't load them (default).
      --
      -- Create the space station scene:
      --
      Brick.Create(
        object  => bri1,
        scale   => 100.0,
        centre  => (0.0,0.0,0.0),
        kind    => Brick.cube,
        opening => (5 => True, others=> False),
        portal  => portal1,
        texture => (Texture_ID("face1"),
                    Texture_ID("face2"),
                    Texture_ID("face3"),
                    Texture_ID("face4"),
                    Texture_ID("face5"),
                    Texture_ID("face6"))
      );
      Set_name(bri1.all,"Space station brick ONE");

      Brick.Create(
        object  => bri2,
        scale   => 100.0,
        centre  => (0.0,0.0,-100.0),
        kind    => Brick.cube,
        opening => (5|6 => True, others=> False),
        portal  => portal2,
        texture => (others => Texture_ID("alum_002"))
      );
      Set_name(bri2.all,"Space station brick TWO");

      -- Connecting portals:
      bri1.face(portal1(5)).connecting:= bri2;
      bri2.face(portal2(6)).connecting:= bri1;
    end if;

    -- Relink Doom 3 level (either loaded or created):
    if level_stuff /= null then
      for i in level_stuff'Range loop
        -- NB:
        -- - portals may have been already linked (if created, not loaded);
        -- - textures need to be linked
        G3D.Rebuild_links(level_stuff(i).all, level_map,False,False,True);
        G3D.Pre_calculate(level_stuff(i).all);
      end loop;
    end if;

    -- Whole 3D zoo:
    bestiaire:= new Object_3D_array'(
      level_stuff(level_stuff'First), -- starting area in the DOOM 3 level is the first area
      cube, cube_glossy, cube_tri, cube_tri_quad, cube_bico,
      globe,
      sierp,
      extrude_test_1,
      borg_star,
      dreadnought_ship,
      a319_plane,
      x29_plane,
      vhc_001, vhc_002,
      ico, icos,
      knot, liss,
      knot_10_102_obj, knot_link_obj,
      bri1
    );

    -- Indices in the 3D zoo area where object accesses may change depending where the camera is:
    level_idx:= bestiaire'First; -- this is the index of the DOOM 3 level
    bri_idx:= bestiaire'Last;    -- this is the index of the pair of cubes

    -- We start with the first object:
    beast_idx:= bestiaire'First;

    --  -- Not necessary, just for testing new objects
    --  for b in bestiaire'Range loop
    --    Check_object(bestiaire(b).all);
    --  end loop;

  end Create_objects;

  procedure Dump_objects is
  begin
    for i in bestiaire'Range loop
      if i = level_idx then
        for j in level_stuff'Range loop
          G3D.IO.Save_file(level_stuff(j).all);
        end loop;
      else
        G3D.IO.Save_file(bestiaire(i).all);
      end if;
    end loop;
    G3D.IO.Save_file(bri2.all);
    G3D.IO.Save_file("Delta4g1", level_BSP);
  end Dump_objects;

  detect_collisions: Boolean:= True;

  procedure Display_scene(
    o: in out G3D.Object_3D'Class;
    gc: Game_control.Command_set;
    sec: G3D.Real;
    technical_infos: Boolean
  )
  is
    procedure Msg(line: GL.Int; s: String) is
    begin
      GLUT_2D.Text_output(
        0,line,main_size_x, main_size_y, s, GLUT_2D.Helvetica_10
      );
    end Msg;
    use GL, G3D, G3D.Aux, G3D.REF, G3DM, GL.Math;
    light_info: String(1..8);
  begin
    Disable( LIGHTING );
    --  Depth comparison function is set to LEQUAL is needed for multitexturing:
    --  LESS (the default) prevents showing another texture onto the first one.
    Clear( DEPTH_BUFFER_BIT );
    Enable( DEPTH_TEST );
    DepthFunc( LEQUAL );
    --  ALPHA_TEST: prevent very transparent pixels to be displayed at all and to influence
    --  the depth buffer. E.g. for cross-shaped (non-convex) grass, the faces displayed behind
    --  are hidden by texture pixels that are transparent, but in front and displayed first.
    Enable    (ALPHA_TEST);
    AlphaFunc (GREATER, 0.05);
    --
    MatrixMode( MODELVIEW );
    Set_GL_Matrix(ego.world_rotation);
    Stars.Display(ego.world_rotation);
    Enable( LIGHTING );
    Enable( CULL_FACE );
    CullFace( BACK );

    GL.Translate ( - ego.clipper.eye_position );

    ------------------------
    -- Display the object --
    ------------------------
    PushMatrix;
    G3D.Display( o, ego.clipper );
    PopMatrix;

    if technical_infos then
      PushMatrix;

      Disable( LIGHTING );
      Disable( TEXTURE_2D );

      Color( red   => 0.7,
             green => 0.7,
             blue  => 0.6);

      GLUT_2D.Text_output( (0.0,0.0,0.0),"O", GLUT_2D.Times_Roman_24 );
      GLUT_2D.Text_output( (1.0,0.0,0.0),"x", GLUT_2D.Times_Roman_24 );
      GLUT_2D.Text_output( (0.0,1.0,0.0),"y", GLUT_2D.Times_Roman_24 );
      GLUT_2D.Text_output( (0.0,0.0,1.0),"z", GLUT_2D.Times_Roman_24 );

      Msg(10, "Press Space for next object/scene.    Object name: " & Get_name(o) &
        ". Points:" & Integer'Image(o.Max_points) &
        ". Faces:"  & Integer'Image(o.Max_faces) &
        ". GL Lists: " & List_Cases'Image(o.List_Status));
      Msg(20, "Run mode (Shift): " &
        Boolean'Image(gc( Game_control.run_mode )));
      Msg(30, "Slide mode (Alt): " &
        Boolean'Image(gc( Game_control.slide_mode )));
      Msg(40, "Ctrl mode: "  &
        Boolean'Image(gc( Game_control.ctrl_mode )));

      Msg(50, "Eye: " & Coords(ego.clipper.eye_position) & " reset: 0" );
      Msg(60, "View direction: " & Coords(ego.clipper.view_direction));

      for i in light_info'Range loop
        if Is_light_switched(i) then
          light_info(i):= Character'Val(Character'Pos('0')+i);
        else
          light_info(i):= 'x';
        end if;
      end loop;
      Msg(70, "Lights: [" & light_info & ']');
      if Options.portal_tracking then
        Msg(80,
          "Connected objects seen:" & Natural'Image(info_b_ntl2) &
          "; max portal depth:" & Natural'Image(info_b_ntl3));
      end if;
      if Options.BSP_tracking and then beast_idx = level_idx then
        Msg(90, "BSP depth: " & Natural'Image(info_b_ntl1) &
          ". Area found: " & Boolean'Image(info_b_bool1) &
          ". BSP path: " & To_String(info_b_str1));
      end if;
      Msg(100, "Collision detection (F10): " & Boolean'Image(detect_collisions));

      if sec > 0.0 then
        Msg(140, "FPS: " & Integer'Image(Integer(1.0/sec)));
      end if;

      PopMatrix;
    end if; -- technical_infos
  end Display_scene;

  -- Timer management
  last_time: Integer;
  sample: array(1..123) of Natural:= (others =>0);
  average: G3D.Real; -- avg milliseconds
  new_scene: Boolean:= True;

  gc: Game_control.Command_set:= Game_control.no_command;

  technical_infos_enabled: Boolean:= True;

  procedure Graphic_display is
    use GL;
  begin
    Display_scene(
      bestiaire(beast_idx).all, gc,
      average*0.001,
      technical_infos_enabled
    );
  end Graphic_display;

  package SAA is new GLOBE_3D.Software_Anti_Aliasing(Graphic_display);

  type Smoothing_method is ( none, software, hardware );
  -- hardware doesn't work (some code must be missing) and produces
  -- a dotted display on Vista.

  smoothing: constant Smoothing_method:= none;

  procedure Fill_screen is
    use GL;
  begin
    Set_Background_Color;
    case smoothing is
      when software =>
        SAA.Set_quality(SAA.Q3);
        for SAA_Phase in 1..SAA.Anti_Alias_phases loop
          SAA.Display_with_Anti_Aliasing(SAA_Phase);
        end loop;
      when hardware =>
        Enable( MULTISAMPLE_ARB ); -- (if not done yet)
        Clear( COLOR_BUFFER_BIT );
        Graphic_display;
        Flush;
      when none =>
        Clear( COLOR_BUFFER_BIT );
        Graphic_display;
        Flush;
    end case;
    GLUT.SwapBuffers;
  end Fill_screen;

  mem_rot: G3D.Matrix_33:= G3D.Id_33;

  procedure Reset_eye is
  begin
    ego.clipper.eye_position:= ( 0.0, 0.0, 4.0 );
    ego.rotation:= ( 0.0, 0.0, 0.0 );
    ego.world_rotation:= G3D.Id_33;
  end Reset_eye;

  screenshot_count: Natural:= 0;

  video_count: Natural:= 0;

  video_rate: constant:= 24;                   --  was 20 (for an old machine...)
  video_declared_rate: constant:= video_rate;  --  was 30 (for an old machine...)
  seconds_video: Long_Float; -- seconds since last captured image
  trigger_video: constant Long_Float:= 1.0 / Long_Float(video_rate);

  object_rotation_speed: G3D.Vector_3D:= ( 0.0, 0.0, 0.0 );

  procedure My_Limiting(step: in out GLOBE_3D.Vector_3D) is
    use G3D.Collision_detection;
    radius: constant:= 4.0;
    reacted: G3D.Real; -- unused further
  begin
    if detect_collisions then
      Reaction(
        bestiaire(beast_idx).all,
        (ego.clipper.eye_position, radius),
        slide,
        step,
        reacted
      );
    end if;
  end My_Limiting;

  procedure My_Limited_Translation is
  new Actors.Limited_Translation(My_Limiting);

  procedure Main_operations is

    use GL, G3D, G3DM, G3D.REF, G3D.BSP, Game_control;

    function Can_be_rotated return Boolean is
    begin
      -- Block object rotation if we have several objects
      return not (
        beast_idx = bri_idx or
        (beast_idx = level_idx and level_stuff'Length > 1)
      );
    end Can_be_rotated;

    elaps, time_now: Integer;
    gx,gy: GL.Double;   -- mouse movement since last call
    seconds: GL.Double; -- seconds since last image
    alpha_correct: Boolean;
    attenu_t, attenu_r: Real;
    cycle_scene: Boolean;
  begin
    -- Number of milliseconds since GLUT.Init
    time_now := GLUT.Get( GLUT.ELAPSED_TIME );

    if new_scene then
      new_scene:= False;
      elaps:= 0;
    else
      elaps:= time_now - last_time;
    end if;
    last_time := time_now;
    average:= 0.0;
    for i in reverse sample'First+1..sample'Last loop
      sample(i):= sample(i-1);
      average:= average + Real(sample(i));
    end loop;
    sample(sample'First):= elaps;
    average:= average + Real(elaps);
    average:= average / Real(sample'Length);

    seconds:= Real(elaps) * 0.001;
    attenu_t:= Real'Min(0.975, Real'Max( 0.40, 1.0 - seconds*3.0) );
    attenu_r:= attenu_t ** 0.75;

    gc:= no_command;

    Game_control.Append_commands(
      size_x     => Integer(main_size_x),
      size_y     => Integer(main_size_y),
      warp_mouse => full_screen,
      c          => gc,
      gx         => gx,
      gy         => gy
    );

    if forget_mouse > 0 then -- mouse coords disturbed by resize
      gx:= 0.0;
      gy:= 0.0;
      forget_mouse:= forget_mouse - 1;
    end if;

    if gc( interrupt_game ) then
      Say_bye_to_GLUT;
    end if;

    technical_infos_enabled:=
      not (gc( photo ) or capturing_video);

    cycle_scene:= gc( jump );
    if cycle_scene then
      if Can_be_rotated then
        mem_rot:= bestiaire(beast_idx).rotation;
      end if;
      beast_idx:= beast_idx + 1; -- Next object, please !
      if beast_idx > bestiaire'Last then
        beast_idx:= bestiaire'First;
      end if;
      if Can_be_rotated then
        bestiaire(beast_idx).rotation:= mem_rot;
      end if;
      Reset_eye;
    end if;

    alpha_correct:= False;
    if gc( special_plus )  then alpha:= alpha + seconds; alpha_correct:= True; end if;
    if gc( special_minus ) then alpha:= alpha - seconds; alpha_correct:= True; end if;
    if alpha_correct then
      if alpha < 0.0 then alpha:= 0.0;
      elsif alpha > 1.0 then alpha:= 1.0; end if;
      for f in bestiaire(beast_idx).face'Range loop
        bestiaire(beast_idx).face(f).alpha:= alpha;
      end loop;
    end if;

    if gc(toggle_10) then
      detect_collisions:= not detect_collisions;
    end if;

    ego.compose_rotations:= Can_be_rotated;
    -- ^ otherwise you get sea-sick when walking!...

    -------------------------------------
    -- Rotating they eye or the object --
    -------------------------------------
    if gc( ctrl_mode ) then
      if Can_be_rotated then
        Actors.Abstract_rotation(
          gc => gc,
          gx => gx,
          gy => gy,
          unitary_change => seconds,
          deceleration   => attenu_r,
          matrix         => bestiaire(beast_idx).rotation,
          time_step      => seconds,
          rotation_speed => object_rotation_speed
        );
      end if;
    else
      Actors.Rotation(
        actor => ego,
        gc => gc,
        gx => gx,
        gy => gy,
        unitary_change => seconds,
        deceleration   => attenu_r,
        time_step      => seconds
      );
    end if;

    --------------------
    -- Moving the eye --
    --------------------
    if cycle_scene then
      --  When scene just has been changed, a call to collision detection after
      --  or during a movement may produce:
      --    raised PROGRAM_ERROR : EXCEPTION_ACCESS_VIOLATION
      --  and this, in "Fast" or "Small" modes, not in "Debug" mode. Strange, isn't it ?
      null;
    else
      My_Limited_Translation(
        actor => ego,
        gc => gc,
        gx => gx,
        gy => gy,
        unitary_change => seconds,
        deceleration   => attenu_t,
        time_step      => seconds
      );
    end if;

    if beast_idx = bri_idx then
      -- The cheapest Binary Space Partition ever !...
      if ego.clipper.eye_position(2) < -50.0 then
        bestiaire(bri_idx):= bri2;
      else
        bestiaire(bri_idx):= bri1;
      end if;
    elsif beast_idx = level_idx and level_BSP /= null then
      declare
        area: p_Object_3D;
      begin
        G3D.BSP.Locate(ego.clipper.eye_position, level_BSP, area);
        if area = null then
          null; -- not found, we keep the previous one
        else
          bestiaire(level_idx):= area;
        end if;
      end;
    end if;

    ego.clipper.view_direction:= Transpose(ego.world_rotation) * (0.0,0.0,-1.0);

    frontal_light.position:= (
      GL.Float(ego.clipper.eye_position(0)),
      GL.Float(ego.clipper.eye_position(1)),
      GL.Float(ego.clipper.eye_position(2)),1.0);
    G3D.Define( 1, frontal_light);

    if gc( n0 ) then
      Reset_eye;
    end if;

    -------------------------
    -- Control of lighting --
    -------------------------

    for c in n1..n8 loop
      if gc( c ) then
        Reverse_light_switch(1 + Command'Pos(c) - Command'Pos(n1));
      end if;
    end loop;

    ------------------------
    -- Display everything --
    ------------------------

    Fill_screen;

    --------------------------------
    -- Screenshot / Video capture --
    --------------------------------

    if gc( photo ) then
      screenshot_count:= screenshot_count + 1;
      declare
        n: constant String:= Integer'Image(1_0000 + screenshot_count);
      begin
        GL.IO.Screenshot("shot" & n(n'Last-3..n'Last) & ".bmp");
      end;
    end if;

    if gc( video ) then -- start / stop capture
      if capturing_video then
        GL.IO.Stop_capture;
      else
        video_count:= video_count + 1;
        declare
          n: constant String:= Integer'Image(1_0000 + video_count);
        begin
          GL.IO.Start_capture(
            "capture" & n(n'Last-3..n'Last) & ".avi",
            video_declared_rate
          );
        end;
        seconds_video:= 0.0;
      end if;
      capturing_video:= not capturing_video;
    end if;

    if capturing_video then
      if seconds_video > trigger_video then
        seconds_video := seconds_video - trigger_video;
        GL.IO.Capture_frame;
      end if;
      seconds_video:= seconds_video + Long_Float(seconds);
    end if;

  end Main_operations;

  -- Procedures passed to GLUT here: Window_Resize, Menu, Main_Operations
  -- GLUT.Devices handles: Keyboard, Motion, Mouse

  procedure Start_GLUTs is
    use GL,GLUT;
    GLUT_options: GLUT.Unsigned:= GLUT.DOUBLE or GLUT.RGB or GLUT.DEPTH;
  begin
    Init;
    case smoothing is
      when hardware =>
        GLUT_options:= GLUT_options or GLUT.MULTISAMPLE;
      when others =>
        null;
    end case;
    InitDisplayMode( GLUT_options );
    main_size_x:= 1280; -- 854;  --  YouTube recommended sizes, for 16/9
    main_size_y:= 720;  -- 480;  --  YouTube recommended sizes, for 16/9
    InitWindowSize(Integer(main_size_x), Integer(main_size_y));
    InitWindowPosition(120, 120);
    if CreateWindow(
      "GLOBE_3D / Demo_1 / Any Debug = " &
      Boolean'Image(G3D.Options.Is_debug_mode) & " / Press Space key for next scene"
    ) = 0
    then
      raise GLUT_Problem;
    end if;
    ReshapeFunc(      Window_Resize'Address        );
    DisplayFunc(      Main_operations'Address      );
    IdleFunc(         Main_operations'Address      );
    GLUT.Devices.Initialize;

    if CreateMenu( Menu'Address ) = 0 then
      raise GLUT_Problem;
    end if;
    AttachMenu( MIDDLE_BUTTON );
    AddMenuEntry(" * Full Screen", 1);
    AddMenuEntry("--> Exit (Esc)", 2);
  end Start_GLUTs;

  procedure Start_GLs is
    use GL;
    fog_colour: GL.Light_Float_Vector:= (0.05,0.15,0.15, 1.0);  --  looks like a toxic smoke...
  begin
    Clear_modes;
    Prepare_demo_lighting(0.9);
    if foggy then
      Enable (FOG);
      Fog (FOG_MODE, LINEAR);
      Fog (FOG_COLOR, fog_colour(0)'Unchecked_Access);
      Hint (FOG_HINT, FASTEST);
      Fog (FOG_START, 1.0);
      Fog (FOG_END, 0.125 * fairly_far);
    end if;
    Reset_for_3D( Integer(main_size_x), Integer(main_size_y ));
    case smoothing is
      when hardware =>
        Enable( MULTISAMPLE_ARB );
        Enable( SAMPLE_COVERAGE_ARB ); -- Hope it helps switching on the AA...
      when others =>
        null;
    end case;
  end Start_GLs;

  -- Get eventual command line arguments.

  type Switch_Type is (
    load, -- load some scenes from .g3d files stored in the GLOBE_3D
          --        resource files, instead of rebuilding them (default).
          --  Additionally,
          --   "-load=mylevel" sets "mylevel.zip" as level resource;
          --   from that resource, the demo loads the mylevel_$_area#.g3d
          --   objects with #=1,2,3..., and loads mylevel.bsp.
    dump  -- dump all objects of the demo to .g3d files
  );

  switch: array(Switch_Type) of Boolean:= (others => False);
  custom: Unbounded_String;

  procedure Get_arguments is
    use Ada.Command_Line;
  begin
    for s in Switch_Type loop
      for a in 1..Argument_Count loop
        declare
          arg_long: constant String:= Argument(a);
          swi     : constant String:= Switch_Type'Image(s);
          arg     : constant String:= To_Upper(arg_long(arg_long'First..swi'Last+1));
        begin
          if arg = '-' & swi or arg = '/' & swi then
            switch(s):= True;
            if s = load then
              custom:= To_Unbounded_String(arg_long(swi'Last+3..arg_long'Last));
            end if;
          end if;
        end;
      end loop;
    end loop;
  end Get_arguments;

begin
  Get_arguments;
  G3D.Set_global_data_name("g3demo_global_resources.zip");
  G3D.Set_level_data_name("g3demo_level_resources.zip");
  --
  G3D.Textures.Register_textures_from_resources;

  Create_objects(switch(load), To_String(custom));
  if switch(dump) then
    Dump_objects; -- even those that were loaded (entropy check)
  end if;

  Start_GLUTs;    -- Initialize the GLUT things
  Start_GLs;      -- Initialize the (Open)GL things
  Reset_eye;

  G3D.Textures.Check_all_textures; -- Preload the textures

  --  So far, there is an issue with ObjectAda Win32, GLUT.MainLoop callback,
  --  freeglut, under Windows 7 x64. Display @ Main_Operations is fine.
  --
  GLUT.MainLoop;  -- Let's rock !

end GLOBE_3D_Demo;

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