Source file : globe_3d.adb
with GLOBE_3D.Options,
GLOBE_3D.Textures,
GLOBE_3D.Math,
GLOBE_3D.Portals,
GLOBE_3D.Aux;
with GL.Math,
GL.Simple_text;
with Ada.Characters.Handling; use Ada.Characters.Handling;
with Ada.Exceptions; use Ada.Exceptions;
with Ada.Strings.Fixed; use Ada.Strings, Ada.Strings.Fixed;
with Ada.Text_IO; use Ada.Text_IO;
package body GLOBE_3D is
use GLOBE_3D.Options;
package G3DT renames GLOBE_3D.Textures;
package G3DM renames GLOBE_3D.Math;
-- normal support
--
procedure Add_Normal_of_3p (o : in Object_3D'Class;
Pn0, Pn1, Pn2 : in Integer;
N : in out Vector_3D) is
use GL, G3DM, GL.Math, GLOBE_3D.Aux;
function Params return String is
begin
return
" Object: " & Trim(o.ID,Right) &
" Pn0=" & Integer'Image(Pn0) &
" Pn1=" & Integer'Image(Pn1) &
" Pn2=" & Integer'Image(Pn2);
end Params;
N_contrib: Vector_3D;
begin
if Pn0=0 or Pn1=0 or Pn2=0 then return; end if;
N_contrib:= (o.point(Pn1)-o.point(Pn0))*(o.point(Pn2)-o.point(Pn0)) ;
if strict_geometry and then Almost_zero(Norm2(N_contrib)) then
raise zero_normal with
Params &
" P0=" & Coords(o.point(Pn0)) &
" P1=" & Coords(o.point(Pn1)) &
" P2=" & Coords(o.point(Pn2)) &
" Nc=" & Coords(N_contrib);
end if;
N:= N + N_contrib;
exception
when e: others =>
Raise_Exception(
Exception_Identity(e),
Exception_Message(e) & Params
);
end Add_Normal_of_3p;
-- 'Visual'
--
procedure Free (o : in out p_Visual)
is
procedure deallocate is new Ada.Unchecked_Deallocation (Visual'Class, p_Visual);
begin
Destroy (o.all);
deallocate (o);
end Free;
function Width (o: in Visual'class) return Real
is
begin
return Bounds (o).Box.X_Extent.Max - Bounds (o).Box.X_Extent.Min;
end Width;
function Height (o: in Visual'class) return Real
is
begin
return Bounds (o).Box.Y_Extent.Max - Bounds (o).Box.Y_Extent.Min;
end Height;
function Depth (o: in Visual'class) return Real
is
begin
return Bounds (o).Box.Z_Extent.Max - Bounds (o).Box.Z_Extent.Min;
end Depth;
-- 'Object_3D'
--
-- object validation
--
procedure Check_object(o: Object_3D) is
use GL.Math, G3DM;
procedure Check_faces is
procedure Check(f,v: Integer) is
pragma Inline(Check);
begin
if v < 0 or else v > o.Max_points then
raise bad_vertex_number with
o.ID & " face=" & Integer'Image(f) &
" vertex=" & Integer'Image(v);
end if;
end Check;
procedure Check_duplicate(f,Pn1,Pn2: Integer) is
pragma Inline(Check_duplicate);
begin
-- Skip "dead" edge (triangle), 30-Dec-2001
if Pn1=0 or else Pn2=0 then return; end if;
-- Detect same point number
if Pn1=Pn2 then
raise duplicated_vertex with o.ID & " in face" & Integer'Image(f);
end if;
-- Detect same point coordinates (tolerated in an object,
-- although inefficient, but harms as vertex of the same face!)
if Almost_zero(Norm2(o.point(Pn1) - o.point(Pn2))) then
raise duplicated_vertex_location with o.ID & " in face" & Integer'Image(f);
end if;
end Check_duplicate;
begin
for fa in o.face'Range loop
for edge_num in 1..4 loop
Check( fa, o.face(fa).P(edge_num) );
for other_edge in edge_num+1 .. 4 loop
Check_duplicate( fa, o.face(fa).P(edge_num),
o.face(fa).P(other_edge) );
end loop;
end loop;
end loop; -- fa
end Check_faces;
begin
Check_faces;
end Check_object;
procedure Check_textures(o: Object_3D) is
begin
for f in o.face'Range loop
if is_textured(o.face(f).skin) and then not Textures.Valid_texture_ID(o.face(f).texture) then
raise Textures.Undefined_texture_ID with
Trim(o.ID, Right) &
" face=" & Integer'Image(f) &
" skin=" & Skin_type'Image(o.face(f).skin) &
" texture_id=" & Image_ID'Image(o.face(f).texture);
end if;
end loop;
end Check_textures;
--------------------------------------------
-- Object initialization (1x in its life) --
--------------------------------------------
overriding procedure Pre_calculate(o: in out Object_3D) is
use GL, GL.Math, G3DM, GLOBE_3D.Aux;
N: Vector_3D;
length_N : Real;
procedure Calculate_face_internals(
fa: in Face_type;
fi: out Face_internal_type
)
is
l: Natural:= 0;
quadri_edge: array(fa.P'Range) of Natural;
ex_U, ex_V: Real;
begin
l:= 0;
for qe in fa.P'Range loop
if fa.P(qe) /= 0 then
l:= l + 1;
quadri_edge(l):= qe; -- if triangle, "map" edge on a quadri
fi.P_compact(l):= fa.P(qe);
end if;
end loop;
if l in Edge_count then
fi.last_edge:= l;
else
raise bad_edge_number with o.ID & " edge=" & Integer'Image(l);
end if;
-- * Face invariant : Textured face: extremities
for e in 1..l loop
if fa.whole_texture then
ex_U:= Real(fa.repeat_U);
ex_V:= Real(fa.repeat_V);
case quadri_edge(e) is
when 1 => fi.UV_extrema(e):= (0.0, 0.0 ); -- bottom, left 4--<--3
when 2 => fi.UV_extrema(e):= (ex_U,0.0 ); -- bottom, right | |
when 3 => fi.UV_extrema(e):= (ex_U,ex_V); -- top, right 1-->--2
when 4 => fi.UV_extrema(e):= (0.0, ex_V); -- top, left
when others => null;
end case;
else
-- Just copy the mapping, but in compact form for triangles:
fi.UV_extrema(e):= fa.texture_edge_map(quadri_edge(e));
end if;
end loop;
-- * Face invariant : Normal of unrotated face
N:= (0.0, 0.0, 0.0);
case fi.last_edge is
when 3 =>
Add_Normal_of_3p(o,
fi.P_compact(1),
fi.P_compact(2),
fi.P_compact(3),
N
);
when 4 =>
Add_Normal_of_3p(o, fa.P(1), fa.P(2), fa.P(4), N);
-- We sum other normals for not perfectly flat faces,
-- in order to have a convenient average...
Add_Normal_of_3p(o, fa.P(2), fa.P(3), fa.P(1), N);
Add_Normal_of_3p(o, fa.P(3), fa.P(4), fa.P(2), N);
Add_Normal_of_3p(o, fa.P(4), fa.P(1), fa.P(3), N);
end case;
length_N:= Norm( N );
if Almost_zero(length_N) then
if strict_geometry then
raise zero_summed_normal;
else
fi.normal:= N; -- 0 vector !
end if;
else
fi.normal:= (1.0 / length_N) * N;
end if;
end Calculate_face_internals;
adjacent_faces: array(o.point'Range) of Natural:= (others => 0);
pf: Natural;
length: Real;
begin --Pre_calculate
if full_check_objects then
Check_object(o);
end if;
for i in o.face'Range loop
begin
-- Geometry
Calculate_face_internals( o.face(i), o.face_internal(i) );
-- Disable blending when alphas are = 1
case o.face(i).skin is
when material_only | material_texture =>
o.face_internal(i).blending:= Is_to_blend(o.face(i).material);
when colour_only | coloured_texture | texture_only=>
o.face_internal(i).blending:= Is_to_blend(o.face(i).alpha);
when invisible =>
o.face_internal(i).blending:= False;
end case;
o.transparent:= o.transparent or o.face_internal(i).blending;
exception
when zero_summed_normal =>
raise zero_summed_normal with o.ID & " face=" & Integer'Image(i);
end;
end loop;
declare
use GLOBE_3D.REF;
max_Norm2 : Real := 0.0;
begin
o.bounds.Box.X_Extent.Min := Real'Last; o.bounds.Box.X_Extent.Max := Real'First;
o.bounds.Box.Y_Extent.Min := Real'Last; o.bounds.Box.Y_Extent.Max := Real'First;
o.bounds.Box.Z_Extent.Min := Real'Last; o.bounds.Box.Z_Extent.Max := Real'First;
for p in o.point'Range loop
o.edge_vector(p) := (0.0,0.0,0.0);
max_Norm2 := Real'Max (Norm2 (o.point (p)), max_Norm2);
o.bounds.Box.X_Extent.Min := Real'Min (o.bounds.Box.X_Extent.Min, o.point (p)(0)); -- tbd: set extents and bounding sphere radius in
o.bounds.Box.X_Extent.Max := Real'Max (o.bounds.Box.X_Extent.Max, o.point (p)(0)); -- common procedure for 'object_base' class.
o.bounds.Box.Y_Extent.Min := Real'Min (o.bounds.Box.Y_Extent.Min, o.point (p)(1));
o.bounds.Box.Y_Extent.Max := Real'Max (o.bounds.Box.Y_Extent.Max, o.point (p)(1));
o.bounds.Box.Z_Extent.Min := Real'Min (o.bounds.Box.Z_Extent.Min, o.point (p)(2));
o.bounds.Box.Z_Extent.Max := Real'Max (o.bounds.Box.Z_Extent.Max, o.point (p)(2));
end loop;
o.bounds.sphere_Radius := Sqrt (max_Norm2);
end;
-- Calculate edge vectors.
-- Naive algorithm: for each point, scan all faces to see
-- if they are adjacent. It took #points * #faces steps.
-- -> better algorithm here: 2 * #points + 4 * #faces. (22-Jan-2006)
for f in o.face'Range loop
for p in o.face(f).P'Range loop
pf:= o.face(f).P(p);
if pf /= 0 then
adjacent_faces(pf):= adjacent_faces(pf) + 1;
o.edge_vector(pf):= o.edge_vector(pf) + o.face_internal(f).normal;
end if;
end loop;
end loop;
for p in o.point'Range loop
if adjacent_faces(p) = 0 then
if strict_geometry then
-- Strict approach: detect any unmatched point:
raise point_unmatched with
Trim(o.ID, Right) &
" point " & Integer'Image(p) &
" belongs to none of the object's face";
end if;
else
length:= Norm( o.edge_vector(p) );
if not Almost_zero(length) then
o.edge_vector(p):= (1.0/length) * o.edge_vector(p);
end if;
end if;
end loop;
-- Ooof. Now we can certify:
o.pre_calculated:= True;
end Pre_calculate;
procedure Arrow(P: Point_3D; D: Vector_3D) is
use GL, GL.Math, G3DM;
V,V1,V2: Vector_3D;
begin
if Almost_zero(Norm2(D)) then
return;
end if;
V:= (D(1),-D(0),0.0); -- an orthogonal, or zero
if Almost_zero(Norm2(V)) then -- bad luck, it is zero
V:= (0.0,-D(2),D(1)); -- 2nd try
end if;
V:= (0.2/Norm(V)) * V;
V1:= 0.7*D + V;
V2:= 0.7*D - V;
GL_Begin(GL.LINES);
Vertex(P+D); Vertex(P);
Vertex(P+D); Vertex(P+V1);
Vertex(P+D); Vertex(P+V2);
GL_End;
end Arrow;
shiny_material :
constant GL.Materials.Material_type:=
(ambient => (0.1, 0.1, 0.1, 1.0),
diffuse => (0.1, 0.1, 0.1, 1.0),
specular => (0.8, 0.8, 0.8, 1.0),
emission => (0.0, 0.0, 0.0, 1.0),
shininess => 50.0); -- 77: Chrome, 96: Glass
-------------
-- Display --
-------------
procedure Display_one(o: in out Object_3D) is
-- Display only this object and not connected objects
-- out: object will be initialized if not yet
--
--
-- Display face routine which is optimized to produce a shorter list
-- of GL commands. Runs slower then the original Display face routine
-- yet needs to be executed only once.
--
-- Uwe R. Zimmer, July 2011
--
package Display_face_optimized is
procedure Display_face (First_Face : Boolean; fa: Face_type; fi: in out Face_internal_type);
procedure Display_specular (fa: Face_type; fi: Face_internal_type);
private
Previous_face : Face_type;
Previous_face_internal : Face_internal_type;
Previous_specular_face : Face_type;
end Display_face_optimized;
package body Display_face_optimized is
use GL, GL.Materials;
procedure Draw_polygon (fa: Face_type; fi: Face_internal_type) is
begin
case fi.last_edge is
when 3 => GL_Begin( TRIANGLES );
when 4 => GL_Begin( QUADS );
end case;
for i in 1..fi.last_edge loop
if is_textured(fa.skin) then
TexCoord(fi.UV_extrema(i).U, fi.UV_extrema(i).V);
end if;
Normal(o.edge_vector(fi.P_compact(i)));
Vertex(o.point(fi.P_compact(i)));
end loop;
GL_End;
end Draw_polygon;
procedure Display_face (First_Face : Boolean; fa: Face_type; fi: in out Face_internal_type) is
blending_hint: Boolean;
procedure Display_texture_label(name: Ident; p: Point_3D) is
use GL.Simple_text;
begin
Disable( TEXTURE_2D );
Text_output(p, name, (0.7, 0.7, 0.9, 1.0), 5.0, Sans_Serif);
Enable( TEXTURE_2D );
end Display_texture_label;
begin -- Display_face
if fa.skin = invisible then
Previous_face := fa;
Previous_face_internal := fi;
return;
end if;
------------------------------
-- 1) Set Face's Material --
------------------------------
if First_Face
or else Previous_face.skin = invisible
or else fa.skin /= Previous_face.skin
or else (fa.skin = Previous_face.skin
and then is_material(fa.skin)
and then not Identical(fa.material, Previous_face.material))
then
case fa.skin is
when material_only | material_texture =>
Disable(COLOR_MATERIAL);
Set_Material(fa.material);
when invisible =>
null; -- NB: this case doesn't happen since procedure was quitted before
when others =>
Set_Material(neutral_material);
end case;
end if;
----------------------------
-- 2) Set Face's Colour --
----------------------------
if First_Face
or else Previous_face.skin = invisible
or else fa.skin /= Previous_face.skin
then
case fa.skin is
when material_only | material_texture =>
null; -- done above
when colour_only | coloured_texture =>
Enable(COLOR_MATERIAL);
ColorMaterial(FRONT_AND_BACK, AMBIENT_AND_DIFFUSE);
when texture_only =>
Disable(COLOR_MATERIAL);
when invisible =>
null;
end case;
end if;
if is_coloured(fa.skin) and then
(First_Face
or else Previous_face.skin = invisible
or else not (GL.Math.Identical(fa.colour, Previous_face.colour) and then
GL.Math.Almost_zero(fa.alpha - Previous_face.alpha))
)
then
Color(
red => fa.colour.red,
green => fa.colour.green,
blue => fa.colour.blue,
alpha => fa.alpha
);
end if;
-----------------------------
-- 3) Set Face's Texture --
-----------------------------
if is_textured(fa.skin) then
G3DT.Check_2D_texture(fa.texture, blending_hint);
if blending_hint then
fi.blending:= True;
-- 13-Oct-2006: override the decision made at Pre_calculate.
-- If texture data contains an alpha layer, we switch on transparency.
end if;
end if;
if First_Face
or else Previous_face.skin = invisible
or else is_textured(fa.skin) /= is_textured(Previous_face.skin)
then
case fa.skin is
when texture_only | coloured_texture | material_texture =>
Enable( TEXTURE_2D );
when colour_only | material_only =>
Disable( TEXTURE_2D );
when invisible =>
null;
end case;
end if;
if is_textured(fa.skin) and then
(First_Face
or else not
( -- In this case we don't need to bind again the same image ID
is_textured(Previous_face.skin) and then
fa.texture = Previous_face.texture
)
)
then
BindTexture( TEXTURE_2D, GL.Uint(Image_ID'Pos(fa.texture)+1) );
end if;
------------------------------------------
-- Set Face's Blending / Transparency --
------------------------------------------
if First_Face
or else Previous_face.skin = invisible
or else fi.blending /= Previous_face_internal.blending
then
if fi.blending then
Enable( BLEND ); -- See 4.1.7 Blending
BlendFunc( sfactor => SRC_ALPHA,
dfactor => ONE_MINUS_SRC_ALPHA );
-- Disable( DEPTH_TEST );
-- Disable( CULL_FACE );
else
Disable( BLEND );
-- Enable( DEPTH_TEST );
-- Enable( CULL_FACE );
-- CullFace( BACK );
end if;
end if;
--------------------------
-- Now, draw the face --
--------------------------
-- Texture (diffuse) is drawn here:
Draw_polygon(fa, fi);
if show_texture_labels then
Display_texture_label(fi.texture_name, o.point(fi.P_compact(1)));
end if;
Previous_face := fa;
Previous_face_internal := fi;
end Display_face;
procedure Display_specular (fa: Face_type; fi: Face_internal_type) is
blending_hint: Boolean;
begin
-- Specular map (the optional "glossy" or "shiny" image) is drawn here:
if is_textured(fa.skin) and then fa.specular_map /= null_image then
G3DT.Check_2D_texture(fa.specular_map, blending_hint);
if fa.specular_map /= Previous_specular_face.specular_map then
BindTexture( TEXTURE_2D, GL.Uint(Image_ID'Pos(fa.specular_map)+1) );
end if;
-- NB: display only works when setting GL.DepthFunc(GL.LEQUAL)
-- Default is GL.LESS, and thus only first texture per face will be drawn.
Draw_polygon(fa, fi);
Previous_specular_face := fa;
end if;
end Display_specular;
end Display_face_optimized;
procedure Display_normals is
use GL, GL.Math, G3DM;
C: Vector_3D;
begin
GL.Color( 0.5, 0.5, 1.0, 1.0);
-- show pseudo (average) normals at edges:
for e in o.point'Range loop
Arrow(o.point(e), arrow_inflator * o.edge_vector(e));
end loop;
GL.Color( 1.0, 1.0, 0.5, 1.0);
-- show normals of faces:
for f in o.face'Range loop
C:= (0.0,0.0,0.0);
for i in 1..o.face_internal(f).last_edge loop
C:= C+o.point(o.face_internal(f).P_compact(i));
end loop;
C:= (1.0/Real(o.face_internal(f).last_edge)) * C;
Arrow(C, arrow_inflator * o.face_internal(f).normal);
end loop;
end Display_normals;
procedure Set_for_specular is
use GL, GL.Materials;
begin
Disable(COLOR_MATERIAL);
Set_Material(shiny_material);
Enable( BLEND );
BlendFunc( sfactor => ONE, dfactor => ONE );
end Set_for_specular;
use GL, G3DM;
begin -- Display_one
if not o.pre_calculated then
Pre_calculate(o);
end if;
-- GL.Extended.BindBuffer (GL.ARRAY_BUFFER, 0); -- disable 'vertex buffer objects'
-- GL.Extended.BindBuffer (GL.ELEMENT_ARRAY_BUFFER, 0); -- disable 'vertex buffer objects' indices
-- gl.disableClientState (gl.TEXTURE_COORD_ARRAY);
-- gl.disable (ALPHA_TEST);
GL.Enable (LIGHTING);
GL.PushMatrix; -- 26-May-2006: instead of rotating/translating back
GL.Translate( o.centre );
Multiply_GL_Matrix(o.rotation);
-- List preparation phase.
case o.List_Status is
when No_List | No_List_Optimized | Is_List =>
null;
when Generate_List =>
o.List_Id := Integer(GL.GenLists(1));
GL.NewList (GL.Uint (o.List_Id), COMPILE_AND_EXECUTE);
end case;
-- List generation phase or execution.
case o.List_Status is
when No_List =>
for f in o.face'Range loop
Display_face_optimized.Display_face(True, o.face(f), o.face_internal(f));
-- We mimic the old, direct, Display_face with redundant color, material, etc.
-- instructions by passing True for First_Face.
end loop;
Set_for_specular;
for f in o.face'Range loop
Display_face_optimized.Display_specular(o.face(f), o.face_internal(f));
end loop;
when No_List_Optimized | Generate_List =>
for f in o.face'Range loop
Display_face_optimized.Display_face(f = o.face'First, o.face(f), o.face_internal(f));
end loop;
Set_for_specular;
for f in o.face'Range loop
Display_face_optimized.Display_specular(o.face(f), o.face_internal(f));
end loop;
when Is_List =>
GL.CallList (GL.Uint (o.List_Id));
end case;
-- Close list - if any.
case o.List_Status is
when No_List | No_List_Optimized | Is_List =>
null;
when Generate_List =>
GL.EndList;
if GL.GetError = OUT_OF_MEMORY then
o.List_Status := No_List;
else
o.List_Status := Is_List;
GL.CallList (GL.Uint (o.List_Id)); -- First display of the freshly generated list.
end if;
end case;
if show_normals then
GL.Disable( GL.LIGHTING );
GL.Disable( GL.TEXTURE_2D );
Display_normals;
GL.Enable( GL.LIGHTING ); -- mmmh...
end if;
GL.PopMatrix; -- 26-May-2006: instead of rotating/translating back
-- GL.Rotate( o.auto_rotation(2), 0.0, 0.0, -1.0 );
-- GL.Rotate( o.auto_rotation(1), 0.0, -1.0, 0.0 );
-- GL.Rotate( o.auto_rotation(0), -1.0, 0.0, 0.0 );
-- GL.Translate( -o.centre );
end Display_one;
overriding procedure Display(
o : in out Object_3D;
clip : in Clipping_data
)
is
use GLOBE_3D.Portals;
procedure Display_clipped(
o : in out Object_3D'Class;
clip_area : in Clipping_area;
portal_depth : in Natural
)
is
procedure Try_portal(f: Positive) is
use G3DM, GL, GL.Math;
dot_product: Real;
plane_to_eye: Vector_3D; -- vector from any point in plane to the eye
bounding_of_face, intersection_clip_and_face: Clipping_area;
success, non_empty_intersection: Boolean;
begin
dot_product:= o.face_internal(f).normal * clip.view_direction;
-- Culling #1: check if portal is in field of view's "dead angle"
if dot_product < clip.max_dot_product then
plane_to_eye:=
clip.eye_position -
-- We just choose any point of the face.
(o.point(o.face_internal(f).P_compact(1)) + o.centre)
;
dot_product:= plane_to_eye * o.face_internal(f).normal;
-- Culling #2: check if we are on the right side of the portal
-- dot_product = signed distance to the plane
-- NB: this ignores o.auto_rotation !
if dot_product > 0.0 then
Find_bounding_box( o, f, bounding_of_face, success );
if success then
Intersect( clip_area, bounding_of_face,
intersection_clip_and_face, non_empty_intersection );
else
-- in doubt, draw with the present clipping
intersection_clip_and_face:= clip_area;
non_empty_intersection:= True;
end if;
-- Culling #3: clipping rectangle
if non_empty_intersection then
-- Recursion happens here:
Display_clipped(
o => o.face(f).connecting.all,
clip_area => intersection_clip_and_face,
portal_depth => portal_depth + 1
);
end if;
end if;
end if;
end Try_portal;
so: p_Object_3D_list;
begin -- Display_clipped
if not o.pre_calculated then
Pre_calculate(o);
end if;
--
-- a/ Display connected objects which are visible through o's faces
-- This is where recursion happens
if (not filter_portal_depth) or else -- filter_portal_depth: test/debug
portal_depth <= 6
then
for f in o.face'Range loop
if o.face(f).connecting /= null and then
-- Prevent infinite recursion on rare cases where
-- object A or B is not convex, and A and B see each other
-- and the culling by clipping cannot stop the recursion:
-- (e.g. origin2.proc, tomb.proc)
not o.face_internal(f).portal_seen
-- NB: drawing [different parts of] the same object several times
-- is right, since portions can be seen through different portals,
-- but walking more than once through the same *portal* with
-- this algorithm is wrong, causing infinite recursion.
then
o.face_internal(f).portal_seen := True;
-- Recursively calls Display_clipped for objects visible through face f.
Try_portal(f);
end if;
end loop;
end if;
-- b/ Display the object itself
if (not filter_portal_depth) or else -- filter_portal_depth: test/debug
(portal_depth = 1 or portal_depth = 5)
then
-- The graphical clipping (Scissor) gives various effects
-- - almost no speedup on the ATI Radeon 9600 Pro (hardware)
-- - factor: ~ Sqrt(clipped surface ratio) with software GL
if portal_depth > 0 then
GL.Enable(GL.SCISSOR_TEST);
GL.Scissor(
x => GL.Int(clip_area.X1),
y => GL.Int(clip_area.Y1),
width => GL.Sizei(clip_area.X2 - clip_area.X1+1),
height => GL.Sizei(clip_area.Y2 - clip_area.Y1+1)
);
else
GL.Disable(GL.SCISSOR_TEST);
end if;
if portal_tracking then
info_b_ntl2:= info_b_ntl2 + 1;
info_b_ntl3:= Natural'Max(portal_depth, info_b_ntl3);
end if;
Display_one(o);
so:= o.sub_objects;
while so /= null loop
Display_one(so.objc.all); -- No portals, sub-obj recursion in this call - may want it.
so:= so.next;
end loop;
end if;
if show_portals and then portal_depth > 0 then
Draw_boundary(clip.main_clipping, clip_area, portal_depth);
end if;
end Display_clipped;
procedure Reset_portal_seen(o: in out Object_3D'Class) is
begin
for f in o.face'Range loop
if o.face_internal(f).portal_seen then
o.face_internal(f).portal_seen := False;
Reset_portal_seen(o.face(f).connecting.all);
end if;
end loop;
end Reset_portal_seen;
begin
if portal_tracking then
info_b_ntl2:= 0; -- count amount of objects displayed, not distinct
info_b_ntl3:= 0; -- records max depth
end if;
Display_clipped( o, clip_area => clip.main_clipping, portal_depth => 0 );
Reset_portal_seen(o);
end Display;
function "+" (a, b: Map_idx_pair) return Map_idx_pair is
begin
return (a.U + b.U, a.V + b.V);
end "+";
function "-" (a, b: Map_idx_pair) return Map_idx_pair is
begin
return (a.U - b.U, a.V - b.V);
end "-";
function "*" (l: GL.Double; p: Map_idx_pair) return Map_idx_pair is
begin
return (l * p.U, l * p.V);
end "*";
function Identical(a, b: Map_idx_pair) return Boolean is
use GL.Math;
begin
return
Almost_zero(a.U-b.U) and then Almost_zero(a.V-b.V);
end Identical;
function Is_textured_specular(fa: Face_type) return Boolean is
begin
return is_textured(fa.skin) and then fa.specular_map /= null_image;
end Is_textured_specular;
overriding procedure Destroy (o : in out Object_3D) is
ol, ol_prev: p_Object_3D_list:= o.sub_objects;
procedure Dispose is new Ada.Unchecked_Deallocation (Object_3D_list, p_Object_3D_list);
begin
while ol /= null loop
Free(p_Visual(ol.objc)); -- Sub-object will be destroyed first - then, sub-sub-objects etc.
ol_prev:= ol;
ol:= ol.next;
Dispose(ol_prev);
end loop;
if o.List_Status = Is_List then
GL.DeleteLists (GL.Uint (o.List_Id), 1);
end if;
end Destroy;
overriding procedure Set_Alpha(o: in out Object_3D; Alpha : in GL.Double) is
begin
for f in o.face'Range loop
o.face(f).alpha := Alpha;
end loop;
end Set_Alpha;
overriding function Is_Transparent(o: in Object_3D) return Boolean is
begin
return o.transparent;
end Is_Transparent;
overriding function Face_Count(o: in Object_3D) return Natural is
begin
return o.Max_faces;
end Face_Count;
overriding function Bounds(o: in Object_3D) return GL.Geometry.Bounds_record is
begin
return o.bounds;
end Bounds;
overriding function Skinned_Geometries (o : in Object_3D) return GL.Skinned_Geometry.Skinned_Geometries
is
pragma Unreferenced (o);
begin
return GL.Skinned_Geometry.null_skinned_geometries;
end Skinned_Geometries;
-- Lighting support.
--
-- lights: array( Light_ident ) of Light_definition;
light_defined: array( Light_ident ) of Boolean:= (others => False);
procedure Define(which: Light_ident; as: Light_definition) is
id: constant GL.LightIDEnm:= GL.LightIDEnm'Val(which-1);
use GL;
begin
-- lights(which):= as;
Light( id, POSITION, as.position );
Light( id, AMBIENT, as.ambient );
Light( id, DIFFUSE, as.diffuse );
Light( id, SPECULAR, as.specular );
light_defined(which):= True;
end Define;
procedure Switch_lights(on: Boolean) is
begin
for l in Light_ident loop
Switch_light(l,on);
end loop;
end Switch_lights;
function Server_id(which: Light_ident) return GL.ServerCapabilityEnm is
begin
return GL.ServerCapabilityEnm'Val(GL.ServerCapabilityEnm'Pos(GL.LIGHT0) + which - 1);
end Server_id;
procedure Switch_light(which: Light_ident; on: Boolean) is
begin
if light_defined(which) then
if on then
GL.Enable( Server_id(which) );
else
GL.Disable( Server_id(which) );
end if;
end if;
end Switch_light;
function Is_light_switched(which: Light_ident) return Boolean is
begin
return Boolean'Val(GL.IsEnabled(Server_id(which)));
end Is_light_switched;
procedure Reverse_light_switch(which: Light_ident) is
begin
Switch_light(which, not Is_light_switched(which));
end Reverse_light_switch;
------------------
-- Resource I/O --
------------------
procedure Load_if_needed( zif: in out Zip.Zip_info; name: String) is
begin
if not Zip.Is_loaded(zif) then
begin
Zip.Load( zif, name );
exception
when Zip.Zip_file_open_Error => -- Try with lower case:
Zip.Load( zif, To_Lower(name) );
end;
end if;
end Load_if_needed;
procedure Set_local_data_name(s: String) is
begin
if Zip.Is_loaded( zif_level ) then
Zip.Delete( zif_level );
end if;
-- ^ Possible resource name change -> need this, will be reloaded on next use
level_data_name:= U(s);
if not Zip.Exists(s) then
raise data_file_not_found with s;
end if;
end Set_local_data_name;
procedure Set_global_data_name(s: String) is
begin
if Zip.Is_loaded( zif_global ) then
Zip.Delete( zif_global );
end if;
-- ^ Possible resource name change -> need this, will be reloaded on next use
global_data_name:= U(s);
if not Zip.Exists(s) then
raise data_file_not_found with s;
end if;
end Set_global_data_name;
procedure Set_name(o: in out Visual'class; new_name: String) is
begin
if new_name'Length > Ident'Length then
raise Constraint_Error with "Visual identifier is too long, maximum is" & Integer'Image(Ident'Length);
end if;
o.ID:= empty;
o.ID(1..new_name'Length):= new_name;
end Set_name;
function Get_name(o: Visual'class) return String is
begin
return Trim(o.ID,Right);
end Get_name;
procedure Rebuild_links(
o : in out Object_3D'Class; -- object to be relinked
neighbouring: in Map_of_Visuals; -- neighbourhood
tolerant_obj: in Boolean; -- tolerant on missing objects
tolerant_tex: in Boolean; -- tolerant on missing textures
tolerant_spc: in Boolean -- tolerant on missing specular maps
)
is
use Visuals_Mapping, Ident_Vectors;
c: Visuals_Mapping.Cursor;
cv: Ident_Vectors.Cursor;
id: Ident;
--
procedure Relink_specular(fa: in out Face_type; fi: Face_internal_type) is
begin
fa.specular_map:= Textures.Texture_ID( fi.specular_name );
exception
when Textures.Texture_name_not_found =>
if tolerant_spc then
fa.specular_map:= null_image;
else
raise;
end if;
end Relink_specular;
--
begin
for f in o.face'Range loop
-- 1/ Find texture IDs:
if is_textured(o.face(f).skin) and then o.face_internal(f).texture_name /= empty then
begin
o.face(f).texture:= Textures.Texture_ID( o.face_internal(f).texture_name );
if o.face_internal(f).specular_name /= empty then
Relink_specular(o.face(f), o.face_internal(f));
end if;
exception
when Textures.Texture_name_not_found =>
if tolerant_tex then
o.face(f).texture:= null_image;
o.face(f).skin:= material_only;
else
raise;
end if;
end;
end if;
-- 2/ Connections through portals:
if o.face_internal(f).connect_name /= empty then
c:= neighbouring.Find(U(o.face_internal(f).connect_name));
if c = Visuals_Mapping.No_Element then
-- Key not found
if tolerant_obj then
o.face(f).connecting:= null;
else
raise
Portal_connection_failed with
"For object name [" & Trim(o.ID,Right) & "], looking for object [" &
Trim(o.face_internal(f).connect_name,Right) & ']';
end if;
else
o.face(f).connecting:= p_Object_3D(Element(c));
end if;
end if;
end loop;
-- for id of o.sub_obj_ids loop -- Ada 2012 shortcut notation
cv:= o.sub_obj_ids.First;
while Has_Element(cv) loop
id:= Element(cv);
c:= neighbouring.Find(U(id));
if c = Visuals_Mapping.No_Element then
-- Key not found
if tolerant_obj then
null;
else
raise
Sub_object_connection_failed with
"For object name [" & Trim(o.ID, Right) & "], looking for object [" &
Trim(id, Right) & ']';
end if;
else
o.sub_objects:= new Object_3D_list'(
objc => p_Object_3D(Element(c)),
next => o.sub_objects
);
Rebuild_links(
p_Object_3D(Element(c)).all, neighbouring, tolerant_obj, tolerant_tex, tolerant_spc
);
end if;
Next(cv);
end loop;
end Rebuild_links;
function empty_map return Map_of_Visuals is
thing: Map_of_Visuals;
begin
Visuals_Mapping.Map(thing):= Visuals_Mapping.Empty_Map;
return thing;
end empty_map;
procedure Add( to_map: in out Map_of_Visuals; what: p_Visual ) is
pos: Visuals_Mapping.Cursor;
success: Boolean;
begin
Visuals_Mapping.Insert(
Visuals_Mapping.Map(to_map),
U(what.ID),
what,
pos,
success
);
if not success then -- A.18.4. 45/2
raise Duplicate_name with what.ID;
end if;
end Add;
function Map_of( va: Visual_array ) return Map_of_Visuals is
res: Map_of_Visuals:= empty_map;
begin
-- Perhaps Reserve_Capacity would be good here ??
for i in va'Range loop
Add(res, va(i));
end loop;
return res;
end Map_of;
end GLOBE_3D;
GLOBE_3D: Ada library for real-time 3D rendering.
Ada programming.