Source file : globe_3d-io.adb
with Ada.Exceptions; use Ada.Exceptions;
with Ada.Strings.Fixed; use Ada.Strings, Ada.Strings.Fixed;
-- with Ada.Characters.Handling; use Ada.Characters.Handling;
with Ada.Unchecked_Conversion;
with UnZip.Streams;
with Float_portable_binary_transfer;
pragma Elaborate_All(Float_portable_binary_transfer);
with GLOBE_3D.Textures;
with GL.IO;
package body GLOBE_3D.IO is
------------------------------------------------
-- Common, internal definitions, routines,... --
------------------------------------------------
stop_type: constant Character:=
Character'Val(26); -- Ctrl-Z to stop displaying a binary file as a text
signature_obj_root: constant String:=
"GLOBE_3D 3D Binary Object File (" & object_extension & "). ";
signature_obj_v2008: constant String:= signature_obj_root &
"Format version: 2-Apr-2008." & stop_type;
signature_obj_v2016a: constant String:= signature_obj_root &
"Format version: 09-Jun-2016" & stop_type;
signature_obj: constant String:= signature_obj_root &
"Format version: 15-Jun-2016" & stop_type;
signature_bsp: constant String:=
"GLOBE_3D Binary Space Partition File (" & BSP_extension & "). " &
"Format version: 2-Apr-2008." & stop_type;
subtype U8 is GL.Ubyte;
type U16 is mod 2 ** 16; for U16'Size use 16;
type U32 is mod 2 ** 32; for U32'Size use 32;
type I16 is range -2 ** 15 .. 2 ** 15 - 1; for I16'Size use 16;
type I32 is range -2 ** 31 .. 2 ** 31 - 1; for I32'Size use 32;
f_scaling: constant:= 2.0**24;
package FFBT is
new Float_portable_binary_transfer(GL.Float,I32,I16,True,f_scaling);
use FFBT;
d_scaling: constant:= 2.0**27; -- 53/2=26.5
package DFBT is
new Float_portable_binary_transfer(GL.Double,I32,I16,True,d_scaling);
use DFBT;
function Cvt is new Ada.Unchecked_Conversion( I16, U16 );
function Cvt is new Ada.Unchecked_Conversion( I32, U32 );
function Cvt is new Ada.Unchecked_Conversion( U16, I16 );
function Cvt is new Ada.Unchecked_Conversion( U32, I32 );
generic
type Number is mod <>;
procedure Read_Intel_x86_number(sb: in out GL.IO.Input_buffer; n: out Number);
procedure Read_Intel_x86_number(sb: in out GL.IO.Input_buffer; n: out Number) is
b: U8;
m: Number:= 1;
bytes: constant Integer:= Number'Size/8;
begin
n:= 0;
for i in 1..bytes loop
GL.IO.Get_Byte(sb,b);
n:= n + m * Number(b);
m:= m * 256;
end loop;
end Read_Intel_x86_number;
procedure Read_Double(
sb: in out GL.IO.Input_buffer;
n: out GL.Double
) is
procedure Read_Intel is new Read_Intel_x86_number( U16 );
procedure Read_Intel is new Read_Intel_x86_number( U32 );
m1,m2: U32; e: U16;
begin
Read_Intel(sb, m1);
Read_Intel(sb, m2);
Read_Intel(sb, e);
Merge(Cvt(m1),Cvt(m2),Cvt(e),n);
-- Double is stored in two parts due to the absence of
-- 64-bit integers on certain compilers (e.g. OA 8.2)
end Read_Double;
generic
s: Ada.Streams.Stream_IO.Stream_Access;
type Number is mod <>;
procedure Write_Intel_x86_number(n: in Number);
procedure Write_Intel_x86_number(n: in Number) is
m: Number:= n;
bytes: constant Integer:= Number'Size/8;
begin
for i in 1..bytes loop
U8'Write(s, U8(m mod 256));
m:= m / 256;
end loop;
end Write_Intel_x86_number;
procedure Write_Double(
s: Ada.Streams.Stream_IO.Stream_Access;
n: in GL.Double)
is
procedure Write_Intel is new Write_Intel_x86_number( s, U16 );
procedure Write_Intel is new Write_Intel_x86_number( s, U32 );
m1,m2: I32; e: I16;
begin
Split(n,m1,m2,e);
-- Double is stored in two parts due to the absence of
-- 64-bit integers on certain compilers (e.g. OA 8.2)
Write_Intel(Cvt(m1));
Write_Intel(Cvt(m2));
Write_Intel(Cvt(e));
end Write_Double;
procedure Write_String(
s : in Ada.Streams.Stream_IO.Stream_Access;
str: in String
)
is
tstr: constant String:= Trim(str,Right);
begin
U8'Write(s,tstr'Length);
String'Write(s,tstr);
end Write_String;
procedure Read_String(
sb: in out GL.IO.Input_buffer;
str: out String
)
is
l8: U8;
l: Natural;
begin
GL.IO.Get_Byte(sb,l8);
l:= Natural(l8);
if l > str'Length then
raise Constraint_Error;
end if;
for i in str'First .. str'First+l-1 loop
GL.IO.Get_Byte(sb,l8);
str(i):= Character'Val(l8);
end loop;
str(str'First+l..str'Last):= (others => ' ');
end Read_String;
-------------------
-- Object_3D I/O --
-------------------
procedure Read(
s: in Ada.Streams.Stream_IO.Stream_Access;
o: out p_Object_3D
)
is
buf: GL.IO.Input_buffer;
procedure Read_Intel is new Read_Intel_x86_number( U16 );
procedure Read_Intel is new Read_Intel_x86_number( U32 );
procedure Read_Float(n: out GL.Float) is
m: U32; e: U16;
begin
Read_Intel(buf, m);
Read_Intel(buf, e);
Merge(Cvt(m),Cvt(e),n);
end Read_Float;
procedure Read_Material_Float_vector(mfv: out GL.Material_Float_vector) is
begin
for i in mfv'Range loop
Read_Float(mfv(i));
end loop;
end Read_Material_Float_vector;
procedure Read_Point_3D(p: out Point_3D) is
begin
for i in p'Range loop
Read_Double(buf, p(i));
end loop;
end Read_Point_3D;
procedure Read_Map_idx_pair_array(m: out Map_idx_pair_array) is
begin
for i in m'Range loop
Read_Double(buf, m(i).U);
Read_Double(buf, m(i).V);
end loop;
end Read_Map_idx_pair_array;
v8: U8;
v32, mp32, mf32: U32;
with_specular: Boolean:= True;
with_sub_objects: Boolean:= True;
procedure Read_face(face: out Face_type; face_invar: in out Face_internal_type) is
begin
-- 1/ Points
for i in face.P'Range loop
Read_Intel(buf, v32);
face.P(i):= Integer(v32);
end loop;
-- 2/ Portal connection: object name is stored;
-- access must be found later
Read_String(buf, face_invar.connect_name);
-- 3/ Skin
GL.IO.Get_Byte(buf,v8);
face.skin:= Skin_type'Val(v8);
-- 4/ Mirror
GL.IO.Get_Byte(buf,v8);
face.mirror:= Boolean'Val(v8);
-- 5/ Alpha
Read_Double(buf, face.alpha);
-- 6/ Colour
case face.skin is
when colour_only | coloured_texture =>
Read_Double(buf, face.colour.red);
Read_Double(buf, face.colour.green);
Read_Double(buf, face.colour.blue);
when others =>
null;
end case;
-- 7/ Material
case face.skin is
when material_only | material_texture =>
Read_Material_Float_vector(face.material.ambient);
Read_Material_Float_vector(face.material.diffuse);
Read_Material_Float_vector(face.material.specular);
Read_Material_Float_vector(face.material.emission);
Read_Float(face.material.shininess);
when others =>
null;
end case;
-- 8/ Texture: texture name is stored; id must be found later
Read_String(buf, face_invar.texture_name);
if with_specular then
Read_String(buf, face_invar.specular_name);
end if;
GL.IO.Get_Byte(buf,v8);
face.whole_texture:= Boolean'Val(v8);
GL.IO.Get_Byte(buf,v8);
face.repeat_U:= Positive'Val(v8);
GL.IO.Get_Byte(buf,v8);
face.repeat_V:= Positive'Val(v8);
if not face.whole_texture then
Read_Map_idx_pair_array(face.texture_edge_map);
end if;
end Read_face;
--
test_signature: String(signature_obj'Range);
ID: Ident;
begin
String'Read(s,test_signature);
if test_signature = signature_obj then
null;
elsif test_signature = signature_obj_v2016a then
with_sub_objects := False;
elsif test_signature = signature_obj_v2008 then
with_sub_objects := False;
with_specular := False;
else
raise Bad_data_format with "Signature not found: " & signature_obj_root;
end if;
GL.IO.Attach_Stream(b => buf, stm => s);
Read_String(buf, ID);
-- Read the object's dimensions, create object, read its contents
Read_Intel(buf, mp32);
Read_Intel(buf, mf32);
o:= new Object_3D(Integer(mp32), Integer(mf32));
o.ID:= ID;
for p in o.point'Range loop
Read_Point_3D(o.point(p));
end loop;
for f in o.face'Range loop
Read_face(o.face(f),o.face_internal(f));
end loop;
Read_Point_3D(o.centre);
for i in Matrix_33'Range(1) loop
for j in Matrix_33'Range(2) loop
Read_Double(buf, o.rotation(i,j));
end loop;
end loop;
if with_sub_objects then
loop
GL.IO.Get_Byte(buf,v8);
exit when Boolean'Val(v8);
Read_String(buf, ID);
o.sub_obj_ids.Append(ID);
end loop;
end if;
-- Main operation done!
end Read;
procedure Write(
s: in Ada.Streams.Stream_IO.Stream_Access;
o: in Object_3D
)
is
procedure Write_Intel is new Write_Intel_x86_number( s, U16 );
procedure Write_Intel is new Write_Intel_x86_number( s, U32 );
procedure Write_Float(n: in GL.Float) is
m: I32; e: I16;
begin
Split(n,m,e);
Write_Intel(Cvt(m));
Write_Intel(Cvt(e));
end Write_Float;
procedure Write_Material_Float_vector(mfv: in GL.Material_Float_vector) is
begin
for i in mfv'Range loop
Write_Float(mfv(i));
end loop;
end Write_Material_Float_vector;
procedure Write_Point_3D(p: in Point_3D) is
begin
for i in p'Range loop
Write_Double(s, p(i));
end loop;
end Write_Point_3D;
procedure Write_Map_idx_pair_array(m: in Map_idx_pair_array) is
begin
for i in m'Range loop
Write_Double(s, m(i).U);
Write_Double(s, m(i).V);
end loop;
end Write_Map_idx_pair_array;
procedure Write_face(face: Face_type; face_invar: Face_internal_type) is
begin
-- 1/ Points
for i in face.P'Range loop
Write_Intel(U32(face.P(i)));
end loop;
-- 2/ Portal connection: object name is stored
if face.connecting = null then
Write_String(s, empty);
else
Write_String(s, face.connecting.ID);
end if;
-- 3/ Skin
U8'Write(s,Skin_type'Pos(face.skin));
-- 4/ Mirror
U8'Write(s,Boolean'Pos(face.mirror));
-- 5/ Alpha
Write_Double(s, face.alpha);
-- 6/ Colour
case face.skin is
when colour_only | coloured_texture =>
Write_Double(s, face.colour.red);
Write_Double(s, face.colour.green);
Write_Double(s, face.colour.blue);
when others =>
null;
end case;
-- 7/ Material
case face.skin is
when material_only | material_texture =>
Write_Material_Float_vector(face.material.ambient);
Write_Material_Float_vector(face.material.diffuse);
Write_Material_Float_vector(face.material.specular);
Write_Material_Float_vector(face.material.emission);
Write_Float(face.material.shininess);
when others =>
null;
end case;
-- 8/ Texture: texture name is stored
-- First, the main (diffuse) bitmap.
if face.texture = null_image then
-- Maybe a texture name has been given with Texture_name_hint,
-- but was not yet attached to a GL ID number through Rebuild_Links (e.g., the d3g tool).
-- In doubt, we give the hinted name (better than losing that information).
Write_String(s, face_invar.texture_name);
else
-- Usual way: We can get the texture name associated to the
-- GL ID number; name is stored by GLOBE_3D.Textures.
Write_String(s, Textures.Texture_name(face.texture, trim => False));
end if;
-- Next, the specular map.
if face.specular_map = null_image then
Write_String(s, face_invar.specular_name);
else
Write_String(s, Textures.Texture_name(face.specular_map, trim => False));
end if;
U8'Write(s,Boolean'Pos(face.whole_texture));
U8'Write(s,Positive'Pos(face.repeat_U));
U8'Write(s,Positive'Pos(face.repeat_V));
if not face.whole_texture then
Write_Map_idx_pair_array(face.texture_edge_map);
end if;
end Write_face;
sub_obj_lst: p_Object_3D_list:= o.sub_objects;
last: Boolean;
begin
String'Write(s, signature_obj);
Write_String(s, o.ID);
Write_Intel(U32(o.Max_points));
Write_Intel(U32(o.Max_faces));
for p in o.point'Range loop
Write_Point_3D(o.point(p));
end loop;
for f in o.face'Range loop
Write_face(o.face(f), o.face_internal(f));
end loop;
Write_Point_3D(o.centre);
for i in Matrix_33'Range(1) loop
for j in Matrix_33'Range(2) loop
Write_Double(s, o.rotation(i,j));
end loop;
end loop;
loop
last:= sub_obj_lst = null;
U8'Write(s, Boolean'Pos(last));
exit when last;
Write_String(s, sub_obj_lst.objc.ID);
sub_obj_lst:= sub_obj_lst.next;
end loop;
-- Main operation done!
end Write;
generic
type Anything is private;
extension: String;
animal: String;
with procedure Read(
s: in Ada.Streams.Stream_IO.Stream_Access;
a: out Anything
);
procedure Load_generic(name_in_resource: String; a: out Anything);
procedure Load_generic(name_in_resource: String; a: out Anything) is
name_ext: constant String:= Trim(name_in_resource, Both) & extension;
procedure Try( zif: in out Zip.Zip_info; name: String ) is
use UnZip.Streams;
fobj: Zipped_File_Type;
begin -- Try
Load_if_needed( zif, name );
Open( fobj, zif, name_ext );
Read( Ada.Streams.Stream_IO.Stream_Access(Stream(fobj)), a );
Close( fobj );
exception
when Zip.File_name_not_found =>
raise;
when e: others =>
Raise_Exception(
Exception_Identity(e),
Exception_Message(e) & " on " & animal & ": " & name_ext
);
end Try;
begin
begin
Try( zif_level, S(level_data_name) );
exception
when Zip.File_name_not_found |
Zip.Zip_file_open_Error =>
-- Not found in level-specific pack
Try( zif_global, S(global_data_name) );
end;
exception
when Zip.File_name_not_found |
Zip.Zip_file_open_Error =>
-- Never found - neither in level, nor in global pack
raise Missing_object with
animal & " not found in any data resource pack: " & name_in_resource;
end Load_generic;
procedure Load_Internal is
new Load_generic(
Anything => p_Object_3D,
extension => object_extension,
animal => "object",
Read => Read
);
procedure Load(name_in_resource: String; o: out p_Object_3D)
renames Load_Internal;
procedure Load_file(file_name: String; o: out p_Object_3D) is
use Ada.Streams.Stream_IO;
f: File_Type;
begin
Open(f, In_File, file_name);
Read(Stream(f),o);
Close(f);
end Load_file;
procedure Save_file(file_name: String; o: in Object_3D'Class) is
use Ada.Streams.Stream_IO;
f: File_Type;
begin
Create(f, Out_File, file_name);
Write(Stream(f), Object_3D (o));
-- ^ endian-proof and floating-point hardware neutral;
-- using stream attribute would be machine-specific.
Close(f);
end Save_file;
procedure Save_file(o: in Object_3D'Class) is
begin
Save_file(Trim(o.ID,Right) & object_extension, o);
end Save_file;
-------------
-- BSP I/O --
-------------
-- Write a BSP tree to a stream
procedure Write(
s: in Ada.Streams.Stream_IO.Stream_Access;
tree: in BSP.p_BSP_node
)
is
procedure Write_Intel is new Write_Intel_x86_number( s, U32 );
use BSP;
n: Natural:= 0;
procedure Numbering(node: p_BSP_node) is
begin
if node /= null then
n:= n + 1;
node.node_id:= n;
Numbering(node.front_child);
Numbering(node.back_child);
end if;
end Numbering;
procedure Save_node(node: p_BSP_node) is
begin
if node /= null then
Write_Intel(U32(node.node_id));
if node.front_child = null then
Write_Intel(U32'(0)); -- Leaf nodes have index 0.
if node.front_leaf = null then
Write_String(s, empty);
else
Write_String(s, node.front_leaf.ID);
end if;
else
Write_Intel(U32(node.front_child.node_id));
end if;
if node.back_child = null then
Write_Intel(U32'(0)); -- Leaf nodes have index 0.
if node.back_leaf = null then
Write_String(s, empty);
else
Write_String(s, node.back_leaf.ID);
end if;
else
Write_Intel(U32(node.back_child.node_id));
end if;
for i in node.normal'Range loop
Write_Double(s, node.normal(i));
end loop;
Write_Double(s, node.distance);
--
Save_node(node.front_child);
Save_node(node.back_child);
end if;
end Save_node;
begin
Numbering(tree); -- fill the node_id's
String'Write(s, signature_bsp); -- header
Write_Intel(U32(n)); -- give the number of nodes first
Save_node(tree);
end Write;
-- Write a BSP tree to a file
procedure Save_file(file_name: String; tree: in BSP.p_BSP_node) is
use Ada.Streams.Stream_IO;
f: File_Type;
begin
if Index(file_name, ".")=0 then
Create(f, Out_File, file_name & BSP_extension);
else
Create(f, Out_File, file_name);
end if;
Write(Stream(f),tree);
Close(f);
end Save_file;
procedure Load(
name_in_resource: in String;
referred : in Map_of_Visuals;
tree : out BSP.p_BSP_node
)
is
function Find_object(ID: Ident; tolerant: Boolean) return p_Object_3D is
use Visuals_Mapping;
c: Cursor;
begin
if ID = empty then
return null;
end if;
c:= referred.Find(U(ID));
if c = No_Element then
-- Key not found
if tolerant then
return null;
else
raise Missing_object_in_BSP with "Object not found: [" & Trim(ID,Right) & ']';
end if;
else
return p_Object_3D(Element(c));
end if;
end Find_object;
procedure Read_BSP(
s : in Ada.Streams.Stream_IO.Stream_Access;
tree : out BSP.p_BSP_node
)
is
use BSP;
buf: GL.IO.Input_buffer;
procedure Read_Intel is new Read_Intel_x86_number( U32 );
test_signature: String(signature_bsp'Range);
n, j, k: U32;
ID: Ident;
tol: constant Boolean:= False;
begin
String'Read(s,test_signature);
if test_signature /= signature_bsp then
raise Bad_data_format;
end if;
GL.IO.Attach_Stream(b => buf, stm => s);
Read_Intel(buf, n);
if n < 1 then
tree:= null;
return;
end if;
declare
-- We put all the new-born nodes into a farm with numbered boxes,
-- because only the numbers are stored in the BSP file.
-- Once the nodes are linked together through accesses (pointers),
-- we can forget the farm and let the tree float...
farm: array (0..n) of p_BSP_node;
begin
farm(0):= null; -- Leaf nodes have index 0.
for i in 1..n loop
farm(i):= new BSP_node;
end loop;
for i in 1..n loop
Read_Intel(buf, j); -- node_id
farm(j).node_id:= Integer(j);
-- Front child:
Read_Intel(buf, k);
farm(j).front_child:= farm(k); -- Connect with an access.
if k = 0 then -- it is a front leaf -> associate object
Read_String(buf, ID);
farm(j).front_leaf:= Find_object(ID, tol);
end if;
-- Back child:
Read_Intel(buf, k);
farm(j).back_child := farm(k); -- Connect with an access.
if k = 0 then -- it is a back leaf -> associate object
Read_String(buf, ID);
farm(j).back_leaf := Find_object(ID, tol);
end if;
-- The node's geometric information (a plane):
for ii in farm(j).normal'Range loop
Read_Double(buf, farm(j).normal(ii));
end loop;
Read_Double(buf, farm(j).distance);
end loop;
tree:= farm(1);
end;
end Read_BSP;
procedure Load_Internal is
new Load_generic(
Anything => BSP.p_BSP_node,
extension => BSP_extension,
animal => "BSP tree",
Read => Read_BSP
);
begin
Load_Internal(name_in_resource, tree);
end Load;
end GLOBE_3D.IO;
GLOBE_3D: Ada library for real-time 3D rendering.
Ada programming.