From: curt Date: Thu, 4 Jan 2001 20:16:11 +0000 (+0000) Subject: Move binary file reading and writing to simgear. X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=70f495b4e156dfa076b61f0afadbd288afbbba54;p=simgear.git Move binary file reading and writing to simgear. --- diff --git a/simgear/constants.h b/simgear/constants.h index 4347bd70..ad6af417 100644 --- a/simgear/constants.h +++ b/simgear/constants.h @@ -142,4 +142,11 @@ #define FG_EPSILON 0.0000001 +// Highest binobj format version we know how to read/write. This starts at +// 0 and can go up to 65535 +#define SG_BINOBJ_VERSION 5 +// for backwards compatibility +#define SG_SCENERY_FILE_FORMAT "0.4" + + #endif // _SG_CONSTANTS_H diff --git a/simgear/io/Makefile.am b/simgear/io/Makefile.am index 8752d3d7..5b439944 100644 --- a/simgear/io/Makefile.am +++ b/simgear/io/Makefile.am @@ -4,12 +4,16 @@ lib_LIBRARIES = libsgio.a include_HEADERS = \ iochannel.hxx \ + lowlevel.hxx \ + sg_binobj.hxx \ sg_file.hxx \ sg_serial.hxx \ sg_socket.hxx libsgio_a_SOURCES = \ iochannel.cxx \ + lowlevel.cxx \ + sg_binobj.cxx \ sg_file.cxx \ sg_serial.cxx \ sg_socket.cxx diff --git a/simgear/io/lowlevel.cxx b/simgear/io/lowlevel.cxx new file mode 100644 index 00000000..1439c070 --- /dev/null +++ b/simgear/io/lowlevel.cxx @@ -0,0 +1,306 @@ +// lowlevel.hxx -- routines to handle lowlevel compressed binary IO of +// various datatypes +// +// Shamelessly adapted from plib (plib.sourceforge.net) January 2001 +// +// Original version Copyright (C) 2000 the plib team +// Local changes Copyright (C) 2000 Curtis L. Olson - curt@flightgear.org +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ +// + +#include "lowlevel.hxx" + +static int read_error = false ; +static int write_error = false ; + +int sgReadError (void) { return read_error ; } +int sgWriteError (void) { return write_error ; } + + +void sgReadChar ( gzFile fd, char *var ) +{ + if ( gzread ( fd, var, sizeof(char) ) == 1 ) return ; + read_error = true ; +} + + +void sgWriteChar ( gzFile fd, const char var ) +{ + if ( gzwrite ( fd, (void *)(&var), sizeof(char) ) == 1 ) return ; + write_error = true ; +} + + +void sgReadFloat ( gzFile fd, float *var ) +{ + if ( gzread ( fd, var, sizeof(float) ) == 1 ) return ; + read_error = true ; +} + + +void sgWriteFloat ( gzFile fd, const float var ) +{ + if ( gzwrite ( fd, (void *)(&var), sizeof(float) ) == 1 ) return ; + write_error = true ; +} + + +void sgReadDouble ( gzFile fd, double *var ) +{ + if ( gzread ( fd, var, sizeof(double) ) == 1 ) return ; + read_error = true ; +} + + +void sgWriteDouble ( gzFile fd, const double var ) +{ + if ( gzwrite ( fd, (void *)(&var), sizeof(double) ) == 1 ) return ; + write_error = true ; +} + + +void sgReadUInt ( gzFile fd, unsigned int *var ) +{ + if ( gzread ( fd, var, sizeof(unsigned int) ) == 1 ) return ; + read_error = true ; +} + + +void sgWriteUInt ( gzFile fd, const unsigned int var ) +{ + if ( gzwrite ( fd, (void *)(&var), sizeof(unsigned int) ) == 1 ) return ; + write_error = true ; +} + + +void sgReadInt ( gzFile fd, int *var ) +{ + if ( gzread ( fd, var, sizeof(int) ) == 1 ) return ; + read_error = true ; +} + + +void sgWriteInt ( gzFile fd, const int var ) +{ + if ( gzwrite ( fd, (void *)(&var), sizeof(int) ) == 1 ) return ; + write_error = true ; +} + + +void sgReadLong ( gzFile fd, long int *var ) +{ + if ( gzread ( fd, var, sizeof(long int) ) == 1 ) return ; + read_error = true ; +} + + +void sgWriteLong ( gzFile fd, const long int var ) +{ + if ( gzwrite ( fd, (void *)(&var), sizeof(long int) ) == 1 ) return ; + write_error = true ; +} + + +void sgReadUShort ( gzFile fd, unsigned short *var ) +{ + if ( gzread ( fd, var, sizeof(unsigned short) ) == 1 ) return ; + read_error = true ; +} + + +void sgWriteUShort ( gzFile fd, const unsigned short var ) +{ + if ( gzwrite ( fd, (void *)(&var), sizeof(unsigned short) ) == 1 ) return ; + write_error = true ; +} + + +void sgReadShort ( gzFile fd, short *var ) +{ + if ( gzread ( fd, var, sizeof(short) ) == 1 ) return ; + read_error = true ; +} + + +void sgWriteShort ( gzFile fd, const short var ) +{ + if ( gzwrite ( fd, (void *)(&var), sizeof(short) ) == 1 ) return ; + write_error = true ; +} + + +void sgReadFloat ( gzFile fd, const unsigned int n, float *var ) +{ + if ( gzread ( fd, var, sizeof(float) * n ) == (int)n ) return ; + read_error = true ; +} + + +void sgWriteFloat ( gzFile fd, const unsigned int n, const float *var ) +{ + if ( gzwrite ( fd, (void *)var, sizeof(float) * n ) == (int)n ) return ; + write_error = true ; +} + +void sgReadDouble ( gzFile fd, const unsigned int n, double *var ) +{ + if ( gzread ( fd, var, sizeof(double) * n ) == (int)n ) return ; + read_error = true ; +} + + +void sgWriteDouble ( gzFile fd, const unsigned int n, const double *var ) +{ + if ( gzwrite ( fd, (void *)var, sizeof(double) * n ) == (int)n ) return ; + write_error = true ; +} + +void sgReadBytes ( gzFile fd, const unsigned int n, void *var ) +{ + if ( n == 0) + return; + if ( gzread ( fd, var, n ) == 1 ) + return ; + read_error = true ; +} + +void sgWriteBytes ( gzFile fd, const unsigned int n, const void *var ) +{ + if ( n == 0) + return; + if ( gzwrite ( fd, (void *)var, n ) == 1 ) + return ; + write_error = true ; +} + + +void sgReadUShort ( gzFile fd, const unsigned int n, unsigned short *var ) +{ + if ( gzread ( fd, var, sizeof(unsigned short) * n ) == (int)n ) return ; + read_error = true ; +} + + +void sgWriteUShort ( gzFile fd, const unsigned int n, const unsigned short *var ) +{ + if ( gzwrite ( fd, (void *)var, sizeof(unsigned short) * n ) == (int)n ) return ; + write_error = true ; +} + + + +void sgReadShort ( gzFile fd, const unsigned int n, short *var ) +{ + if ( gzread ( fd, var, sizeof(short) * n ) == (int)n ) return ; + read_error = true ; +} + + +void sgWriteShort ( gzFile fd, const unsigned int n, const short *var ) +{ + if ( gzwrite ( fd, (void *)var, sizeof(short) * n ) == (int)n ) return ; + write_error = true ; +} + + +void sgReadUInt ( gzFile fd, const unsigned int n, unsigned int *var ) +{ + if ( gzread ( fd, var, sizeof(unsigned int)* n ) == (int)n ) return ; + read_error = true ; +} + + +void sgWriteUInt ( gzFile fd, const unsigned int n, const unsigned int *var ) +{ + if ( gzwrite ( fd, (void *)var, sizeof(unsigned int) * n ) == (int)n ) return ; + write_error = true ; +} + + + +void sgReadInt ( gzFile fd, const unsigned int n, int *var ) +{ + if ( gzread ( fd, var, sizeof(int) * n ) == (int)n ) return ; + read_error = true ; +} + + +void sgWriteInt ( gzFile fd, const unsigned int n, const int *var ) +{ + if ( gzwrite ( fd, (void *)var, sizeof(int) * n ) == (int)n ) return ; + write_error = true ; +} + + + +#define MAX_ENTITY_NAME_LENGTH 1024 + +void sgReadString ( gzFile fd, char **var ) +{ + int i ; + char s [ MAX_ENTITY_NAME_LENGTH ] ; + + for ( i = 0 ; i < MAX_ENTITY_NAME_LENGTH ; i++ ) + { + int c = gzgetc ( fd ) ; + s [ i ] = c ; + + if ( c == '\0' ) + break ; + } + + if ( i >= MAX_ENTITY_NAME_LENGTH-1 ) + s [ MAX_ENTITY_NAME_LENGTH-1 ] = '\0' ; + + + if ( s[0] == '\0' ) + *var = NULL ; + else + { + *var = new char [ strlen(s)+1 ] ; + strcpy ( *var, s ) ; + } +} + + +void sgWriteString ( gzFile fd, const char *var ) +{ + if ( var != NULL ) { + if ( gzwrite ( fd, (void *)var, strlen(var) + 1 ) == + (int)(strlen(var) + 1) ) + return ; + } else { + gzputc( fd, 0 ); + } +} + + +void sgReadVec2 ( gzFile fd, sgVec2 var ) { sgReadFloat ( fd, 2, var ) ; } +void sgWriteVec2 ( gzFile fd, const sgVec2 var ) { sgWriteFloat ( fd, 2, var ) ; } + +void sgReadVec3 ( gzFile fd, sgVec3 var ) { sgReadFloat ( fd, 3, var ) ; } +void sgWriteVec3 ( gzFile fd, const sgVec3 var ) { sgWriteFloat ( fd, 3, var ) ; } + +void sgReaddVec3 ( gzFile fd, sgdVec3 var ) { sgReadDouble ( fd, 3, var ) ; } +void sgWritedVec3 ( gzFile fd, const sgdVec3 var ) { sgWriteDouble ( fd, 3, var ) ; } + +void sgReadVec4 ( gzFile fd, sgVec4 var ) { sgReadFloat ( fd, 4, var ) ; } +void sgWriteVec4 ( gzFile fd, const sgVec4 var ) { sgWriteFloat ( fd, 4, var ) ; } + +void sgReadMat4 ( gzFile fd, sgMat4 var ) { sgReadFloat ( fd, 16, (float *)var ) ; } +void sgWriteMat4 ( gzFile fd, const sgMat4 var ) { sgWriteFloat ( fd, 16, (float *)var ) ; } diff --git a/simgear/io/lowlevel.hxx b/simgear/io/lowlevel.hxx new file mode 100644 index 00000000..460d6e26 --- /dev/null +++ b/simgear/io/lowlevel.hxx @@ -0,0 +1,88 @@ +// lowlevel.hxx -- routines to handle lowlevel compressed binary IO of +// various datatypes +// +// Shamelessly adapted from plib January 2001 +// +// Original version Copyright (C) 2000 the plib team +// Local changes Copyright (C) 2000 Curtis L. Olson - curt@flightgear.org +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ +// + + +#ifndef _SG_LOWLEVEL_HXX +#define _SG_LOWLEVEL_HXX + + +#include +#include + +#include + + +void sgReadChar ( gzFile fd, char *var ) ; +void sgWriteChar ( gzFile fd, const char var ) ; +void sgReadFloat ( gzFile fd, float *var ) ; +void sgWriteFloat ( gzFile fd, const float var ) ; +void sgReadDouble ( gzFile fd, double *var ) ; +void sgWriteDouble ( gzFile fd, const double var ) ; +void sgReadUInt ( gzFile fd, unsigned int *var ) ; +void sgWriteUInt ( gzFile fd, const unsigned int var ) ; +void sgReadInt ( gzFile fd, int *var ) ; +void sgWriteInt ( gzFile fd, const int var ) ; +void sgReadLong ( gzFile fd, long int *var ) ; +void sgWriteLong ( gzFile fd, const long int var ) ; +void sgReadUShort ( gzFile fd, unsigned short *var ) ; +void sgWriteUShort ( gzFile fd, const unsigned short var ) ; +void sgReadShort ( gzFile fd, short *var ) ; +void sgWriteShort ( gzFile fd, const short var ) ; + +void sgReadFloat ( gzFile fd, const unsigned int n, float *var ) ; +void sgWriteFloat ( gzFile fd, const unsigned int n, const float *var ) ; +void sgReadDouble ( gzFile fd, const unsigned int n, double *var ) ; +void sgWriteDouble ( gzFile fd, const unsigned int n, const double *var ) ; +void sgReadUInt ( gzFile fd, const unsigned int n, unsigned int *var ) ; +void sgWriteUInt ( gzFile fd, const unsigned int n, const unsigned int *var ) ; +void sgReadInt ( gzFile fd, const unsigned int n, int *var ) ; +void sgWriteInt ( gzFile fd, const unsigned int n, const int *var ) ; +void sgReadUShort ( gzFile fd, const unsigned int n, unsigned short *var ) ; +void sgWriteUShort ( gzFile fd, const unsigned int n, const unsigned short *var ) ; +void sgReadShort ( gzFile fd, const unsigned int n, short *var ) ; +void sgWriteShort ( gzFile fd, const unsigned int n, const short *var ) ; +void sgReadBytes ( gzFile fd, const unsigned int n, void *var ) ; +void sgWriteBytes ( gzFile fd, const unsigned int n, const void *var ) ; + +void sgReadString ( gzFile fd, char **var ) ; +void sgWriteString ( gzFile fd, const char *var ) ; + +void sgReadVec2 ( gzFile fd, sgVec2 var ) ; +void sgWriteVec2 ( gzFile fd, const sgVec2 var ) ; +void sgReadVec3 ( gzFile fd, sgVec3 var ) ; +void sgWriteVec3 ( gzFile fd, const sgVec3 var ) ; +void sgReaddVec3 ( gzFile fd, sgdVec3 var ) ; +void sgWritedVec3 ( gzFile fd, const sgdVec3 var ) ; +void sgReadVec4 ( gzFile fd, sgVec4 var ) ; +void sgWriteVec4 ( gzFile fd, const sgVec4 var ) ; + +void sgReadMat4 ( gzFile fd, sgMat4 var ) ; +void sgWriteMat4 ( gzFile fd, const sgMat4 var ) ; + +int sgReadError ( void ) ; +int sgWriteError ( void ) ; + + +#endif // _SG_LOWLEVEL_HXX diff --git a/simgear/io/sg_binobj.cxx b/simgear/io/sg_binobj.cxx new file mode 100644 index 00000000..0a779970 --- /dev/null +++ b/simgear/io/sg_binobj.cxx @@ -0,0 +1,950 @@ +// sg_binobj.cxx -- routines to read and write low level flightgear 3d objects +// +// Written by Curtis Olson, started January 2000. +// +// Copyright (C) 2000 Curtis L. Olson - curt@flightgear.org +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ +// + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include +#include + +#include +#include STL_STRING + +#include + +#include "lowlevel.hxx" +#include "sg_binobj.hxx" + +#ifdef _MSC_VER +# include +#endif + +FG_USING_STD( cout ); +FG_USING_STD( endl ); + +enum { + SG_BOUNDING_SPHERE = 0, + + SG_VERTEX_LIST = 1, + SG_NORMAL_LIST = 2, + SG_TEXCOORD_LIST = 3, + + SG_TRIANGLE_FACES = 10, + SG_TRIANGLE_STRIPS = 11, + SG_TRIANGLE_FANS = 12 +} tgObjectTypes; + +enum { + SG_MATERIAL = 0 +} tgPropertyTypes; + + +// calculate the center of a list of points, by taking the halfway +// point between the min and max points. +static Point3D calc_center( point_list& wgs84_nodes ) { + Point3D p, min, max; + + if ( wgs84_nodes.size() ) { + min = max = wgs84_nodes[0]; + } else { + min = max = Point3D( 0 ); + } + + for ( int i = 0; i < (int)wgs84_nodes.size(); ++i ) { + p = wgs84_nodes[i]; + + if ( p.x() < min.x() ) { min.setx( p.x() ); } + if ( p.y() < min.y() ) { min.sety( p.y() ); } + if ( p.z() < min.z() ) { min.setz( p.z() ); } + + if ( p.x() > max.x() ) { max.setx( p.x() ); } + if ( p.y() > max.y() ) { max.sety( p.y() ); } + if ( p.z() > max.z() ) { max.setz( p.z() ); } + } + + return ( min + max ) / 2.0; +} + +// calculate the bounding sphere. Center is the center of the +// tile and zero elevation +double sgCalcBoundingRadius( Point3D center, point_list& wgs84_nodes ) { + double dist_squared; + double radius_squared = 0; + + for ( int i = 0; i < (int)wgs84_nodes.size(); ++i ) { + dist_squared = center.distance3Dsquared( wgs84_nodes[i] ); + if ( dist_squared > radius_squared ) { + radius_squared = dist_squared; + } + } + + return sqrt(radius_squared); +} + + +// write out the structures to an ASCII file. We assume that the +// groups come to us sorted by material property. If not, things +// don't break, but the result won't be as optimal. +void sgWriteAsciiObj( const string& base, const string& name, const SGBucket& b, + Point3D gbs_center, float gbs_radius, + const point_list& wgs84_nodes, const point_list& normals, + const point_list& texcoords, + const group_list& tris_v, const group_list& tris_tc, + const string_list& tri_materials, + const group_list& strips_v, const group_list& strips_tc, + const string_list& strip_materials, + const group_list& fans_v, const group_list& fans_tc, + const string_list& fan_materials ) +{ + Point3D p; + int i, j; + + string dir = base + "/" + b.gen_base_path(); + string command = "mkdir -p " + dir; +#ifdef _MSC_VER + fg_mkdir( dir.c_str() ); +#else + system(command.c_str()); +#endif + + // string file = dir + "/" + b.gen_index_str(); + string file = dir + "/" + name; + cout << "Output file = " << file << endl; + + FILE *fp; + if ( (fp = fopen( file.c_str(), "w" )) == NULL ) { + cout << "ERROR: opening " << file << " for writing!" << endl; + exit(-1); + } + + cout << "triangles size = " << tris_v.size() << " tri_materials = " + << tri_materials.size() << endl; + cout << "strips size = " << strips_v.size() << " strip_materials = " + << strip_materials.size() << endl; + cout << "fans size = " << fans_v.size() << " fan_materials = " + << fan_materials.size() << endl; + + cout << "points = " << wgs84_nodes.size() << endl; + cout << "tex coords = " << texcoords.size() << endl; + // write headers + fprintf(fp, "# FGFS Scenery\n"); + fprintf(fp, "# Version %s\n", SG_SCENERY_FILE_FORMAT); + + time_t calendar_time = time(NULL); + struct tm *local_tm; + local_tm = localtime( &calendar_time ); + char time_str[256]; + strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm); + fprintf(fp, "# Created %s\n", time_str ); + fprintf(fp, "\n"); + + // write bounding sphere + fprintf(fp, "# gbs %.5f %.5f %.5f %.2f\n", + gbs_center.x(), gbs_center.y(), gbs_center.z(), gbs_radius); + fprintf(fp, "\n"); + + // dump vertex list + fprintf(fp, "# vertex list\n"); + for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) { + p = wgs84_nodes[i] - gbs_center; + + fprintf(fp, "v %.5f %.5f %.5f\n", p.x(), p.y(), p.z() ); + } + fprintf(fp, "\n"); + + fprintf(fp, "# vertex normal list\n"); + for ( i = 0; i < (int)normals.size(); ++i ) { + p = normals[i]; + fprintf(fp, "vn %.5f %.5f %.5f\n", p.x(), p.y(), p.z() ); + } + fprintf(fp, "\n"); + + // dump texture coordinates + fprintf(fp, "# texture coordinate list\n"); + for ( i = 0; i < (int)texcoords.size(); ++i ) { + p = texcoords[i]; + fprintf(fp, "vt %.5f %.5f\n", p.x(), p.y() ); + } + fprintf(fp, "\n"); + + // dump individual triangles if they exist + if ( tris_v.size() > 0 ) { + fprintf(fp, "# triangle groups\n"); + + int start = 0; + int end = 1; + string material; + while ( start < (int)tri_materials.size() ) { + // find next group + material = tri_materials[start]; + while ( (end < (int)tri_materials.size()) && + (material == tri_materials[end]) ) + { + // cout << "end = " << end << endl; + end++; + } + // cout << "group = " << start << " to " << end - 1 << endl; + + // make a list of points for the group + point_list group_nodes; + group_nodes.clear(); + Point3D bs_center; + double bs_radius = 0; + for ( i = start; i < end; ++i ) { + for ( j = 0; j < (int)tris_v[i].size(); ++j ) { + group_nodes.push_back( wgs84_nodes[ tris_v[i][j] ] ); + bs_center = calc_center( group_nodes ); + bs_radius = sgCalcBoundingRadius( bs_center, group_nodes ); + } + } + + // write group headers + fprintf(fp, "\n"); + fprintf(fp, "# usemtl %s\n", material.c_str()); + fprintf(fp, "# bs %.4f %.4f %.4f %.2f\n", + bs_center.x(), bs_center.y(), bs_center.z(), bs_radius); + + // write groups + for ( i = start; i < end; ++i ) { + fprintf(fp, "f"); + for ( j = 0; j < (int)tris_v[i].size(); ++j ) { + fprintf(fp, " %d/%d", tris_v[i][j], tris_tc[i][j] ); + } + fprintf(fp, "\n"); + } + + start = end; + end = start + 1; + } + } + + // dump triangle groups + if ( strips_v.size() > 0 ) { + fprintf(fp, "# triangle strips\n"); + + int start = 0; + int end = 1; + string material; + while ( start < (int)strip_materials.size() ) { + // find next group + material = strip_materials[start]; + while ( (end < (int)strip_materials.size()) && + (material == strip_materials[end]) ) + { + // cout << "end = " << end << endl; + end++; + } + // cout << "group = " << start << " to " << end - 1 << endl; + + // make a list of points for the group + point_list group_nodes; + group_nodes.clear(); + Point3D bs_center; + double bs_radius = 0; + for ( i = start; i < end; ++i ) { + for ( j = 0; j < (int)strips_v[i].size(); ++j ) { + group_nodes.push_back( wgs84_nodes[ strips_v[i][j] ] ); + bs_center = calc_center( group_nodes ); + bs_radius = sgCalcBoundingRadius( bs_center, group_nodes ); + } + } + + // write group headers + fprintf(fp, "\n"); + fprintf(fp, "# usemtl %s\n", material.c_str()); + fprintf(fp, "# bs %.4f %.4f %.4f %.2f\n", + bs_center.x(), bs_center.y(), bs_center.z(), bs_radius); + + // write groups + for ( i = start; i < end; ++i ) { + fprintf(fp, "ts"); + for ( j = 0; j < (int)strips_v[i].size(); ++j ) { + fprintf(fp, " %d/%d", strips_v[i][j], strips_tc[i][j] ); + } + fprintf(fp, "\n"); + } + + start = end; + end = start + 1; + } + } + + // close the file + fclose(fp); + + command = "gzip --force --best " + file; + system(command.c_str()); +} + + +// read a binary file and populate the provided structures. +void sgReadBinObj( const string& file, + Point3D &gbs_center, float *gbs_radius, + point_list& wgs84_nodes, point_list& normals, + point_list& texcoords, + group_list& tris_v, group_list& tris_tc, + string_list& tri_materials, + group_list& strips_v, group_list& strips_tc, + string_list& strip_materials, + group_list& fans_v, group_list& fans_tc, + string_list& fan_materials ) +{ + Point3D p; + int i, j, k, m; + + // zero out structures + wgs84_nodes.clear(); + normals.clear(); + texcoords.clear(); + + tris_v.clear(); + tris_tc.clear(); + tri_materials.clear(); + + strips_v.clear(); + strips_tc.clear(); + strip_materials.clear(); + + fans_v.clear(); + fans_tc.clear(); + fan_materials.clear(); + + cout << "Loading binary input file = " << file << endl; + + gzFile fp; + if ( (fp = gzopen( file.c_str(), "rb" )) == NULL ) { + string filegz = file + ".gz"; + if ( (fp = gzopen( filegz.c_str(), "rb" )) == NULL ) { + cout << "ERROR: opening " << file << " or " << filegz + << "for reading!" << endl; + exit(-1); + } + } + + // read headers + int header, version; + sgReadInt( fp, &header ); + if ( ((header & 0xFF000000) >> 24) == 'T' && + ((header & 0x00FF0000) >> 16) == 'G' ) { + cout << "Good header" << endl; + // read file version + version = (header & 0x0000FFFF); + cout << "File version = " << version << endl; + } + + // read creation time + time_t calendar_time; + sgReadLong( fp, &calendar_time ); + struct tm *local_tm; + local_tm = localtime( &calendar_time ); + char time_str[256]; + strftime( time_str, 256, "%a %b %d %H:%M:%S %Z %Y", local_tm); + cout << "File created on " << time_str << endl; + + // read number of top level objects + short nobjects; + sgReadShort( fp, &nobjects ); + cout << "Total objects to read = " << nobjects << endl; + + // read in objects + for ( i = 0; i < nobjects; ++i ) { + // read object header + char obj_type; + short nproperties, nelements; + sgReadChar( fp, &obj_type ); + sgReadShort( fp, &nproperties ); + sgReadShort( fp, &nelements ); + + cout << "object " << i << " = " << (int)obj_type << " props = " + << nproperties << " elements = " << nelements << endl; + + if ( obj_type == SG_BOUNDING_SPHERE ) { + // read bounding sphere properties + for ( j = 0; j < nproperties; ++j ) { + char prop_type; + sgReadChar( fp, &prop_type ); + + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "property size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + } + + // read bounding sphere elements + for ( j = 0; j < nelements; ++j ) { + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "element size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + + double *dptr = (double *)ptr; + gbs_center = Point3D( dptr[0], dptr[1], dptr[2] ); + cout << "Center = " << gbs_center << endl; + ptr += sizeof(double) * 3; + + float *fptr = (float *)ptr; + *gbs_radius = fptr[0]; + cout << "Bounding radius = " << *gbs_radius << endl; + } + } else if ( obj_type == SG_VERTEX_LIST ) { + // read vertex list properties + for ( j = 0; j < nproperties; ++j ) { + char prop_type; + sgReadChar( fp, &prop_type ); + + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "property size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + } + + // read vertex list elements + for ( j = 0; j < nelements; ++j ) { + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "element size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + int count = nbytes / (sizeof(float) * 3); + float *fptr = (float *)ptr; + for ( k = 0; k < count; ++k ) { + p = Point3D( fptr[0], fptr[1], fptr[2] ); + cout << "node = " << p << endl; + wgs84_nodes.push_back( p ); + fptr += 3; + } + } + } else if ( obj_type == SG_NORMAL_LIST ) { + // read normal list properties + for ( j = 0; j < nproperties; ++j ) { + char prop_type; + sgReadChar( fp, &prop_type ); + + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "property size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + } + + // read normal list elements + for ( j = 0; j < nelements; ++j ) { + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "element size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + int count = nbytes / 3; + for ( k = 0; k < count; ++k ) { + p = Point3D( ptr[0] / 256.0, ptr[1] / 256.0, + ptr[2] / 256.0 ); + cout << "normal = " << p << endl; + normals.push_back( p ); + ptr += 3; + } + } + } else if ( obj_type == SG_TEXCOORD_LIST ) { + // read texcoord list properties + for ( j = 0; j < nproperties; ++j ) { + char prop_type; + sgReadChar( fp, &prop_type ); + + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "property size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + } + + // read texcoord list elements + for ( j = 0; j < nelements; ++j ) { + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "element size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + int count = nbytes / (sizeof(float) * 2); + float *fptr = (float *)ptr; + for ( k = 0; k < count; ++k ) { + p = Point3D( fptr[0], fptr[1], 0 ); + cout << "texcoord = " << p << endl; + texcoords.push_back( p ); + fptr += 2; + } + } + } else if ( obj_type == SG_TRIANGLE_FACES ) { + // read triangle face properties + for ( j = 0; j < nproperties; ++j ) { + char prop_type; + sgReadChar( fp, &prop_type ); + + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "property size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + if ( prop_type == SG_MATERIAL ) { + char material[256]; + strncpy( material, ptr, nbytes ); + material[nbytes] = '\0'; + cout << "material type = " << material << endl; + tri_materials.push_back( material ); + } + } + + // read triangle face elements + for ( j = 0; j < nelements; ++j ) { + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "element size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + int count = nbytes / (sizeof(short) * 2 * 3); + short *sptr = (short *)ptr; + int_list vs, tcs; + for ( k = 0; k < count; ++k ) { + vs.clear(); tcs.clear(); + for ( m = 0; m < 3; ++m ) { + vs.push_back( sptr[0] ); + tcs.push_back( sptr[1] ); + cout << sptr[0] << "/" << sptr[1] << " "; + sptr += 2; + } + cout << endl; + tris_v.push_back( vs ); + tris_tc.push_back( tcs ); + } + } + } else if ( obj_type == SG_TRIANGLE_STRIPS ) { + // read triangle strip properties + for ( j = 0; j < nproperties; ++j ) { + char prop_type; + sgReadChar( fp, &prop_type ); + + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "property size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + if ( prop_type == SG_MATERIAL ) { + char material[256]; + strncpy( material, ptr, nbytes ); + material[nbytes] = '\0'; + cout << "material type = " << material << endl; + strip_materials.push_back( material ); + } + } + + // read triangle strip elements + for ( j = 0; j < nelements; ++j ) { + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "element size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + int count = nbytes / (sizeof(short) * 2 * 3); + short *sptr = (short *)ptr; + int_list vs, tcs; + vs.clear(); tcs.clear(); + for ( k = 0; k < count; ++k ) { + vs.push_back( sptr[0] ); + tcs.push_back( sptr[1] ); + cout << sptr[0] << "/" << sptr[1] << " "; + sptr += 2; + } + cout << endl; + strips_v.push_back( vs ); + strips_tc.push_back( tcs ); + } + } else if ( obj_type == SG_TRIANGLE_FANS ) { + // read triangle fan properties + for ( j = 0; j < nproperties; ++j ) { + char prop_type; + sgReadChar( fp, &prop_type ); + + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "property size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + if ( prop_type == SG_MATERIAL ) { + char material[256]; + strncpy( material, ptr, nbytes ); + material[nbytes] = '\0'; + cout << "material type = " << material << endl; + fan_materials.push_back( material ); + } + } + + // read triangle fan elements + for ( j = 0; j < nelements; ++j ) { + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "element size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + int count = nbytes / (sizeof(short) * 2 * 3); + short *sptr = (short *)ptr; + int_list vs, tcs; + vs.clear(); tcs.clear(); + for ( k = 0; k < count; ++k ) { + vs.push_back( sptr[0] ); + tcs.push_back( sptr[1] ); + cout << sptr[0] << "/" << sptr[1] << " "; + sptr += 2; + } + cout << endl; + fans_v.push_back( vs ); + fans_tc.push_back( tcs ); + } + } else { + // unknown object type, just skip + + // read properties + for ( j = 0; j < nproperties; ++j ) { + char prop_type; + sgReadChar( fp, &prop_type ); + + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "property size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + } + + // read elements + for ( j = 0; j < nelements; ++j ) { + int nbytes; + sgReadInt( fp, &nbytes ); + cout << "element size = " << nbytes << endl; + char buf[nbytes]; + char *ptr = buf; + sgReadBytes( fp, nbytes, ptr ); + } + } + } + + // close the file + gzclose(fp); +} + + +// write out the structures to a binary file. We assume that the +// groups come to us sorted by material property. If not, things +// don't break, but the result won't be as optimal. +void sgWriteBinObj( const string& base, const string& name, const SGBucket& b, + Point3D gbs_center, float gbs_radius, + const point_list& wgs84_nodes, const point_list& normals, + const point_list& texcoords, + const group_list& tris_v, const group_list& tris_tc, + const string_list& tri_materials, + const group_list& strips_v, const group_list& strips_tc, + const string_list& strip_materials, + const group_list& fans_v, const group_list& fans_tc, + const string_list& fan_materials ) +{ + Point3D p; + sgVec2 t; + sgVec3 pt; + int i, j; + + string dir = base + "/" + b.gen_base_path(); + string command = "mkdir -p " + dir; +#ifdef _MSC_VER + fg_mkdir( dir.c_str() ); +#else + system(command.c_str()); +#endif + + string file = dir + "/" + name + ".gz"; + cout << "Output file = " << file << endl; + + gzFile fp; + if ( (fp = gzopen( file.c_str(), "wb9" )) == NULL ) { + cout << "ERROR: opening " << file << " for writing!" << endl; + exit(-1); + } + + cout << "triangles size = " << tris_v.size() << " tri_materials = " + << tri_materials.size() << endl; + cout << "strips size = " << strips_v.size() << " strip_materials = " + << strip_materials.size() << endl; + cout << "fans size = " << fans_v.size() << " fan_materials = " + << fan_materials.size() << endl; + + cout << "points = " << wgs84_nodes.size() << endl; + cout << "tex coords = " << texcoords.size() << endl; + + // write header magic + sgWriteInt( fp, SG_FILE_MAGIC_NUMBER ); + time_t calendar_time = time(NULL); + sgWriteLong( fp, (long int)calendar_time ); + + // calculate and write number of top level objects + string material; + int start; + int end; + short nobjects = 0; + nobjects++; // for gbs + nobjects++; // for vertices + nobjects++; // for normals + nobjects++; // for texcoords + + // tris + short ntris = 0; + start = 0; end = 1; + while ( start < (int)tri_materials.size() ) { + material = tri_materials[start]; + while ( (end < (int)tri_materials.size()) && + (material == tri_materials[end]) ) { + end++; + } + ntris++; + start = end; end = start + 1; + } + nobjects += ntris; + + // strips + short nstrips = 0; + start = 0; end = 1; + while ( start < (int)strip_materials.size() ) { + material = strip_materials[start]; + while ( (end < (int)strip_materials.size()) && + (material == strip_materials[end]) ) { + end++; + } + nstrips++; + start = end; end = start + 1; + } + nobjects += nstrips; + + // fans + short nfans = 0; + start = 0; end = 1; + while ( start < (int)fan_materials.size() ) { + material = fan_materials[start]; + while ( (end < (int)fan_materials.size()) && + (material == fan_materials[end]) ) { + end++; + } + nfans++; + start = end; end = start + 1; + } + nobjects += nfans; + + cout << "total top level objects = " << nobjects << endl; + sgWriteShort( fp, nobjects ); + + // write bounding sphere + sgWriteChar( fp, (char)SG_BOUNDING_SPHERE ); // type + sgWriteShort( fp, 0 ); // nproperties + sgWriteShort( fp, 1 ); // nelements + + sgWriteInt( fp, sizeof(double) * 3 + sizeof(float) ); // nbytes + sgdVec3 center; + sgdSetVec3( center, gbs_center.x(), gbs_center.y(), gbs_center.z() ); + sgWritedVec3( fp, center ); + sgWriteFloat( fp, gbs_radius ); + + // dump vertex list + sgWriteChar( fp, (char)SG_VERTEX_LIST ); // type + sgWriteShort( fp, 0 ); // nproperties + sgWriteShort( fp, 1 ); // nelements + sgWriteInt( fp, wgs84_nodes.size() * sizeof(float) * 3 ); // nbytes + for ( i = 0; i < (int)wgs84_nodes.size(); ++i ) { + p = wgs84_nodes[i] - gbs_center; + sgSetVec3( pt, p.x(), p.y(), p.z() ); + sgWriteVec3( fp, pt ); + } + + // dump vertex normal list + sgWriteChar( fp, (char)SG_NORMAL_LIST ); // type + sgWriteShort( fp, 0 ); // nproperties + sgWriteShort( fp, 1 ); // nelements + sgWriteInt( fp, normals.size() * 3 ); // nbytes + char normal[3]; + for ( i = 0; i < (int)normals.size(); ++i ) { + p = normals[i]; + normal[0] = (char)(p.x() * 256); + normal[1] = (char)(p.y() * 256); + normal[2] = (char)(p.z() * 256); + sgWriteBytes( fp, 3, normal ); + } + + // dump texture coordinates + sgWriteChar( fp, (char)SG_TEXCOORD_LIST ); // type + sgWriteShort( fp, 0 ); // nproperties + sgWriteShort( fp, 1 ); // nelements + sgWriteInt( fp, texcoords.size() * sizeof(float) * 2 ); // nbytes + for ( i = 0; i < (int)texcoords.size(); ++i ) { + p = texcoords[i]; + sgSetVec2( t, p.x(), p.y() ); + sgWriteVec2( fp, t ); + } + + // dump individual triangles if they exist + if ( tris_v.size() > 0 ) { + int start = 0; + int end = 1; + string material; + while ( start < (int)tri_materials.size() ) { + // find next group + material = tri_materials[start]; + while ( (end < (int)tri_materials.size()) && + (material == tri_materials[end]) ) + { + // cout << "end = " << end << endl; + end++; + } + // cout << "group = " << start << " to " << end - 1 << endl; + + // write group headers + sgWriteChar( fp, (char)SG_TRIANGLE_FACES ); // type + sgWriteShort( fp, 1 ); // nproperties + sgWriteShort( fp, 1 ); // nelements + + sgWriteChar( fp, (char)SG_MATERIAL ); // property + sgWriteInt( fp, material.length() ); // nbytes + sgWriteBytes( fp, material.length(), material.c_str() ); + + sgWriteInt( fp, (end - start) * 3 * 2 * sizeof(short) ); // nbytes + + // write group + for ( i = start; i < end; ++i ) { + for ( j = 0; j < 3; ++j ) { + sgWriteShort( fp, (short)tris_v[i][j] ); + sgWriteShort( fp, (short)tris_tc[i][j] ); + } + } + + start = end; + end = start + 1; + } + } + + // dump triangle strips + if ( strips_v.size() > 0 ) { + int start = 0; + int end = 1; + string material; + while ( start < (int)strip_materials.size() ) { + // find next group + material = strip_materials[start]; + while ( (end < (int)strip_materials.size()) && + (material == strip_materials[end]) ) + { + // cout << "end = " << end << endl; + end++; + } + // cout << "group = " << start << " to " << end - 1 << endl; + + // write group headers + sgWriteChar( fp, (char)SG_TRIANGLE_STRIPS ); // type + sgWriteShort( fp, 1 ); // nproperties + sgWriteShort( fp, end - start ); // nelements + + sgWriteChar( fp, (char)SG_MATERIAL ); // property + sgWriteInt( fp, material.length() ); // nbytes + sgWriteBytes( fp, material.length(), material.c_str() ); + + // write strips + for ( i = start; i < end; ++i ) { + // nbytes + sgWriteInt( fp, strips_v[i].size() * 2 * sizeof(short) ); + for ( j = 0; j < (int)strips_v[i].size(); ++j ) { + sgWriteShort( fp, (short)strips_v[i][j] ); + sgWriteShort( fp, (short)strips_tc[i][j] ); + } + } + + start = end; + end = start + 1; + } + } + + // dump triangle fans + if ( fans_v.size() > 0 ) { + int start = 0; + int end = 1; + string material; + while ( start < (int)fan_materials.size() ) { + // find next group + material = fan_materials[start]; + while ( (end < (int)fan_materials.size()) && + (material == fan_materials[end]) ) + { + // cout << "end = " << end << endl; + end++; + } + // cout << "group = " << start << " to " << end - 1 << endl; + + // write group headers + sgWriteChar( fp, (char)SG_TRIANGLE_FANS ); // type + sgWriteShort( fp, 1 ); // nproperties + sgWriteShort( fp, end - start ); // nelements + + sgWriteChar( fp, (char)SG_MATERIAL ); // property + sgWriteInt( fp, material.length() ); // nbytes + sgWriteBytes( fp, material.length(), material.c_str() ); + + // write fans + for ( i = start; i < end; ++i ) { + // nbytes + sgWriteInt( fp, fans_v[i].size() * 2 * sizeof(short) ); + for ( j = 0; j < (int)fans_v[i].size(); ++j ) { + sgWriteShort( fp, (short)fans_v[i][j] ); + sgWriteShort( fp, (short)fans_tc[i][j] ); + } + } + + start = end; + end = start + 1; + } + } + + // close the file + gzclose(fp); +} diff --git a/simgear/io/sg_binobj.hxx b/simgear/io/sg_binobj.hxx new file mode 100644 index 00000000..fc9a3de9 --- /dev/null +++ b/simgear/io/sg_binobj.hxx @@ -0,0 +1,121 @@ +// sg_binobj.hxx -- routines to read and write low level flightgear 3d objects +// +// Written by Curtis Olson, started January 2000. +// +// Copyright (C) 2000 Curtis L. Olson - curt@flightgear.org +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +// +// $Id$ +// + + +#ifndef _SG_BINOBJ_HXX +#define _SG_BINOBJ_HXX + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#include +#include + +#include +#include STL_STRING + +#include + + +typedef vector < int_list > group_list; +typedef group_list::iterator group_list_iterator; +typedef group_list::const_iterator const_group_list_iterator; + + +#define SG_FILE_MAGIC_NUMBER ( ('S'<<24) + ('G'<<16) + SG_BINOBJ_VERSION ) + + +/* + scenery-file: magic, nobjects, object+ + magic: "TG" + version + object: obj_typecode, nproperties, nelements, property+, element+ + element: nbytes, BYTE+ + property: prop_typecode, nbytes, BYTE+ + obj_typecode: bounding sphere | vertices | normals | texcoords | triangles | + fans | strips + prop_typecode: material_name | ??? + nelements: SHORT (Gives us 65536 which ought to be enough, right?) + nproperties: SHORT + *_typecode: CHAR + nbytes: INTEGER (If we used short here that would mean 65536 bytes = 16384 + floats = 5461 vertices which is not enough for future + growth) + vertex: FLOAT, FLOAT, FLOAT +*/ + + +// calculate the bounding sphere. Center is the center of the +// tile and zero elevation +double sgCalcBoundingRadius( Point3D center, point_list& wgs84_nodes ); + + +// write out the structures to an ASCII file. We assume that the +// groups come to us sorted by material property. If not, things +// don't break, but the result won't be as optimal. +void sgWriteAsciiObj( const string& base, const string& name, const SGBucket& b, + Point3D gbs_center, float gbs_radius, + const point_list& wgs84_nodes, const point_list& normals, + const point_list& texcoords, + const group_list& tris_v, const group_list& tris_tc, + const string_list& tri_materials, + const group_list& strips_v, const group_list& strips_tc, + const string_list& strip_materials, + const group_list& fans_v, const group_list& fans_tc, + const string_list& fan_materials ); + + +// read a binary file object and populate the provided structures. +void sgReadBinObj( const string& file, + Point3D &gbs_center, float *gbs_radius, + point_list& wgs84_nodes, point_list& normals, + point_list& texcoords, + group_list& tris_v, group_list& tris_tc, + string_list& tri_materials, + group_list& strips_v, group_list& strips_tc, + string_list& strip_materials, + group_list& fans_v, group_list& fans_tc, + string_list& fan_materials ); + +// write out the structures to a binary file. We assume that the +// groups come to us sorted by material property. If not, things +// don't break, but the result won't be as optimal. +void sgWriteBinObj( const string& base, const string& name, const SGBucket& b, + Point3D gbs_center, float gbs_radius, + const point_list& wgs84_nodes, const point_list& normals, + const point_list& texcoords, + const group_list& tris_v, const group_list& tris_tc, + const string_list& tri_materials, + const group_list& strips_v, const group_list& strips_tc, + const string_list& strip_materials, + const group_list& fans_v, const group_list& fans_tc, + const string_list& fan_materials ); + + +#endif // _SG_BINOBJ_HXX diff --git a/simgear/misc/Makefile.am b/simgear/misc/Makefile.am index 8e91feb7..9623cc64 100644 --- a/simgear/misc/Makefile.am +++ b/simgear/misc/Makefile.am @@ -28,7 +28,7 @@ libsgmisc_a_SOURCES = \ noinst_PROGRAMS = props_test -props_test_SOURCES = props_test.cxx props_test.hxx +props_test_SOURCES = props_test.cxx props_test_LDADD = libsgmisc.a ../xml/libsgxml.a ../debug/libsgdebug.a INCLUDES += -I$(top_srcdir) $(ZLIB_INCL)