diff --git a/tools/v3dconverter/readme.txt b/tools/v3dconverter/readme.txt
new file mode 100644
index 0000000..25de064
--- /dev/null
+++ b/tools/v3dconverter/readme.txt
@@ -0,0 +1,45 @@
+
+ +--------------------------+
+ | V3dconverter quick start |
+ +--------------------------+
+
+* FOREWORD:
+ This tools is under development. Only *.obj to *.3d conversion is fully operational at now. Convert *.3d terrain to *.obj in order to edit an height field seems to gives good results too. Convert other *.3d objects to *.obj or convert *.ac to *.3d will only work if you're lucky, therefore for now please report bugs only for *.obj to *.3d conversion. I do 99% of my testing by exporting files from Blender in *.obj format, then converting them to *.3d format with v3dconverter.
+
+* DESCRIPTION:
+ V3dconverter is a file converter for Sar2 developers. Its purpose is to convert *.3d (Vertex 3d) model files from/to *.obj (WaveFront) or *.ac (AC3D) files.
+
+* HOW TO COMPILE:
+ cd /path/to/v3dconverter
+ gcc -lm -Wall -o v3dconverter v3dconverter.c
+
+* HOW TO USE:
+ ./v3dconverter -h
+
+* IMPORTANT NOTES:
+ - Don't stole 3D models. Create a model can take a lot of hours/days/months of hard work. Ensure that original model is free before add it in Sar2. If you're not sure of that, please try to contact model author and kindly ask him for permission to reuse it in Sar2.
+ - Even if v3dconverter can convert *.ac to *.3d and even if FlightGear models are generally free, it is not a very good idea to become a "FlightGear models serial converter". FlightGear models -especially flying ones- can be very detailed, generally too much detailed for Sar2. A better way is to open and simplificate them as much as possible (I use mainly Blender and / or sometimes Meshlab to do that) BEFORE convert them to *.3d format.
+ - V3dconverter will only do for you the longest and less interesting job, i.e. convert 3D primitives. To get clean Sar2 models, you will certainly have to manually add/remove/modify some stuffs in your *.3d file, and you always will have to convert texture files.
+
+* CONVERSION TIPS:
+ - Sar2 texture (*.tex) is a rebadged *.tga (Truevision Targa), 3*8 bits rgb or 4*8 bits rgba color, top left origin, without RLE (Run Length Encoding) compression image.
+ - Remember that in Sar2, a pure black color (0x000000) will be fully transparent. V3dconverter will check and automatically overwrite "pure black" by "almost black" (0x010101) in color statements. If you see some holes in your *.3d textured model, maybe your *.tex file contains pure black color. To replace pure black color in a texture, you can of course do it with an image editor or, in command line, you can do it with Imagemagick (https://imagemagick.org/) as this: "convert myoriginaltexture.tga -fill 'rgb(1, 1, 1)' -opaque black mynewtexture.tga". Then, rename your *.tga file to *.tex, and check your model in Sar2 again. Of course, due to Imagemagick magic, "myoriginaltexture.tga" can be "myoriginaltexture.jpg" or "myoriginaltexture.png".
+ - Because of a pure black (0x000000) colored pixel will be fully transparent, if you don't need semi-transparency, preferably use 3*8 bits rgb image instead of 4*8 bits rgba: file size of your image will be 25% lowered!
+ - When you load a new model in Sar2, start Sar2 from command line. If Sar2 can't properly load your model, it will explain why to you.
+ - V3dconverter will only work with *.obj and *.ac files. Blender and Meshlab can export to *.obj: do not forget to export "Materials groups" (Blender) or "Texcoord / Texture file" (Meshlab). You can also try assimp (https://www.assimp.org/), which one works as backend of a lot of web 3D converters. Assimp quick start example: "assimp export inputFileName.3ds outputFileName.obj".
+ - When you convert a model to Vertex 3d, v3dconverter will print objects dimensions: always check if they are coherent for your model. If not, use "-sc" option to scale. For example, if object X dimension is 230 meters but should be 2.3 meters, just add "-sc 2.3/230" or "-sc 1/100" or "-sc 0.01" to v3dconverter command line.
+ - Generally, we want object "Zmin" (minimal vertical) value to be zero. As v3dconverter prints object rectangular bounds, you can easy check it and if necessary modify Zmin value using "-tr" (translate) option. For example, if object Zmin value is 12.3 meters but should be 0, just add "-tr 0 0 -12.3" to v3dconverter command line.
+ - Beware, some models have their origin far away from main object center. If you don't see you model in Sar2, first zoom out and check if you see it.
+ - If your *.3d model is not visible, check if alpha (transparency) parameter is rightly set to 1.0 : look for "color" in *.3d file, alpha chanel is the fourth value.
+ - If your *.3d model seems to have all faces inverted (for example, if when you look at your model from front of it, you see its back texture), you can try "-if" option to invert faces visibility (flip winding).
+ - If you have removed all pure black colors and it already seems that some parts of you model are missing, maybe that missing faces have to be flipped. This can be checked with Blender by showing face orientation. Select bad meshes then use Mesh -> Normals -> Flip to flip them.
+ - Especially if you wants to create new aircrafts, don't hesitate to save your *.obj or *.ac file into small parts, then convert these parts to *.3d, then manually reassemble your multiple *.3d files in the final one. If not, it can be very difficult to define aircraft moving parts (begin_model ... / end_model ...) as rotors, gears, doors, flaps, an so on...
+ - To check your model rendering in Sar2 without add it in a scenery, copy or link your *.3d file in data/aircrafts/ directory, then start Sar2 (aircrafts list is loaded at startup), go to Free Flight, click on Aircraft button, select your model in list, then click on Details button. If your model is not a flying one, don't forget to remove your file / link from aircrafts directory once checked.
+ - Sometimes, v3dconverter prints a lot of (too much ?) warnings. These warnings can be redirected in a text file as this: v3dconverter -i "my input file.obj" -o my_output_file.3d 2> warnings.txt .
+ - If in Blender, your imported *.obj model has no texture and appears pink, it's because Blender can't find the path of textures. This can be fixed by creating a link to the textures folder (for example: "cd my_working_directory && ln -s /usr/share/sar2/textures/"), or by copying the whole textures folder in your working directory, or by editing your model *.mtl file (look for "map_Kd ..........." in *.mtl file).
+ - A Sar2 height field (*.hf) is a rebadged *.tga (Truevision Targa), 8 bits grey color, top left origin, without RLE (Run Length Encoding) compression image.
+ - Keep in mind that *.3d terrain to *.obj conversion -> manual terrain editing -> *.obj to *.hf conversion, is only available for altitude ("Z axis") modification. X and/or Y edition will cause *.obj to *.hf conversion to stop.
+ - Keep in mind that v3dconverter uses *.3d file data to convert terrain to *.obj, thus it will place your *.obj terrain "as in Sar2". It is very usefull if your terrain is made from multiple tiles, because once each terrain tile converted to *.obj, you can import all of them in Blender and they will automatically be put at the right place, but, if you import only one tile and if translation in *.3d file is not null, terrain can be far away from view center (0,0,0). If you want that it be centered in Blender view, DO NOT move it from Blender, add a -tr (translate) option to your conversion command line. If you move it from Blender, *.obj to *.hf conversion will fail.
+ - If you want to edit a terrain file in Blender, it's a good idea to add a -sc 1/100 (i.e. scale 1/100) or -sc 1/1000 option to your v3dconverter command line. If not and if -like me- you are not a Blender professional, it will be difficult to see your terrain because it will be too big for camera clipping and focal length. Don't worry about scale value: it will be stored during *.3d to *.obj conversion process, then automatically canceled during *.obj to *.hf conversion.
+ - Warning: in *.obj terrain files, material has a strange name: this name don't be modified because it will be used as a file name to retrieve important data during *.obj to *.hf conversion process. If you're curious, have a look in $HOME/.local/share/v3dconverter.
+ - You can easily compare two *.hf files with ImageMagick like this: "magick compare image_1.hf.tga image_2.hf.tga -compose src difference.png".
diff --git a/tools/v3dconverter/src/ac3dtov3d.c b/tools/v3dconverter/src/ac3dtov3d.c
new file mode 100644
index 0000000..d0832e2
--- /dev/null
+++ b/tools/v3dconverter/src/ac3dtov3d.c
@@ -0,0 +1,481 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+
+
+
+
+/**************************************************
+* WARNING: EXPERIMENTAL (BUT FUNCTIONAL) CODE *
+* *
+* This whole code must be rewritten ! *
+***************************************************/
+
+#define AC3DMAXLEVELS 20
+#define AC3DMAXTEXTURES 10
+#define AC3DMAXMATERIALS 50
+#define AC3DMAXLIGHTS 20
+
+// per object verticies
+#define AC3DMAXVERTICIES 10000
+
+int ac3dToV3d(const char *source, const char *dest, UserModifier *userModifier) {
+ FILE *fpIn, *fpOut, *fopen();
+ char lineBuffer[MAX_LENGTH + 1], buffer[MAXPATHLENGTH + MAX_LENGTH + 7], token[MAX_LENGTH + 1];
+ char textureName[MAX_LENGTH + 1];
+ char *path = NULL, *baseName = NULL, *extension = NULL; // for output files names
+ unsigned int lines = 0, triangle = 0, quads = 0, polygon = 0, textures = 0, lights = 0;
+ unsigned int ac3dVersion = 0;
+ int level = -1, activeMaterial = -1;
+ unsigned int polys = 0, groups = 0, verticies = 0, surfaces = 0, materials = 0;
+ char * surfType;
+ float Xmin = 99999.99, Ymin = 99999.99, Zmin = 99999.99, Xmax = -99999.99, Ymax = -99999.99, Zmax = -99999.99;
+
+ struct LevelDatas {
+ unsigned int kids; // remaining kids for this object
+ char type[5+1]; // world, poly, group, light
+ struct Modifier modifier; // translate x, y, z, rotate h, p, b.
+ };
+/*
+ struct Vertex {
+ double x;
+ double y;
+ double z;
+ };
+*/
+ struct MatColor {
+ float red;
+ float green;
+ float blue;
+ };
+
+ struct Material { // ac3d material (color)
+ struct MatColor rgb; // diffuse, from 0.0 to 1.0
+ struct MatColor amb; // ambient, from 0.0 to 1.0
+ struct MatColor emis; // emissive, from 0.0 to 1.0
+ struct MatColor spec; // specular, from 0.0 to 1.0
+ struct MatColor shi; // shininess, from 0 to 255 ?
+ float transp; // transparency, from 0.0 to 1.0. A fully transparent object has a transparency value of 100% (100% means 0% in v3d format).
+ };
+
+ struct Vcoord vertex[AC3DMAXVERTICIES];
+
+ struct LevelDatas levelDatas[AC3DMAXLEVELS];
+
+ struct Material material[AC3DMAXMATERIALS];
+
+ modifier.tx = 0;
+ modifier.ty = 0;
+ modifier.tz = 0;
+ modifier.rh = 0;
+ modifier.rp = 0;
+ modifier.rb = 0;
+
+ if ((fpIn = fopen(source, "r")) == NULL) {
+ perror(source);
+ return -1;
+ }
+ fgets(lineBuffer, MAX_LENGTH, fpIn);
+ sscanf( lineBuffer, "%4c%x", buffer, &ac3dVersion );
+ if ( !strcmp(buffer, "AC3D") ) {
+ printf(" *.ac file version: %d ('%s%x')\n", ac3dVersion, buffer, ac3dVersion );
+ }
+ else {
+ fprintf( stderr, "%s is not an ac3d file.\n", source );
+ fclose(fpIn);
+ return -1;
+ }
+
+ // Generate output file name
+ scanFileName( dest, &path, &baseName, &extension );
+ sprintf( buffer, "%s%s.3d", path, baseName );
+ if ((fpOut = fopen(buffer, "w")) == NULL) {
+ perror(buffer);
+ return(-1);
+ }
+
+ fprintf(fpOut, "begin_header\n");
+ // look for textures in whole AC3D file because vertex3d needs them in file header
+ char * texturesList[AC3DMAXTEXTURES] = { NULL };
+ float texPriority = 0.7; // default texture priority
+ lineNr = 1; // input file line counter
+ while ( fgets(lineBuffer, MAX_LENGTH, fpIn) ) {
+ sscanf( lineBuffer, "%s", token );
+ if ( !strcmp(token, "texture") ) {
+ sscanf( lineBuffer, "%*s \"%[^\"]s", textureName );
+ scanFileName( textureName, &path, &baseName, &extension );
+ short registered = 0;
+ for ( int counter = 0; counter < textures; counter++) {
+ if ( !strcmp( baseName, texturesList[counter] ) ) { // if texture found in textures list
+ registered = 1; // mark it at registered
+ break;
+ }
+ }
+ if ( !registered ) { // it's a new texture, write it in *.3d file
+ fprintf(fpOut, "texture_load %s textures/%s.tga %f\n", baseName, baseName, texPriority); // WARNING : extension is hard coded
+ texturesList[textures] = strdup(baseName);
+ textures++;
+ }
+ }
+ lineNr++;
+ }
+ rewind(fpIn);
+ lineNr = 1; // input file line counter
+
+ fprintf(fpOut, "end_header\n\n");
+ fprintf(fpOut, "type 1\n");
+ fprintf(fpOut, "range 2000\n");
+ fprintf(fpOut, "crash_flags 0 1 0 3\n\n");
+
+ while ( fgets(lineBuffer, MAX_LENGTH, fpIn) ) {
+ sscanf( lineBuffer, "%s", token );
+
+ if ( !strcmp(token, "OBJECT") ) {
+ sscanf( lineBuffer, "%*s %s", token );
+ if ( ! ( !strcmp(token, "world") || !strcmp(token, "poly") || !strcmp(token, "group") || !strcmp(token, "light") ) ) { //
+ fprintf( stderr, "Input error: unknown OBJECT type (\"%s\") at line #%ld.\n", token, lineNr );
+ fclose(fpIn);
+ fclose(fpOut);
+ return -1;
+ }
+
+ if ( level < AC3DMAXLEVELS - 1 ) {
+ level++;
+ strcpy ( levelDatas[level].type, token );
+ }
+ else {
+ fprintf( stderr, "ac3dtov3d error: too much levels in %s file. See #define AC3DMAXLEVELS %d\n", source, AC3DMAXLEVELS);
+ return -1;
+ }
+
+ levelDatas[level].modifier.tx = 0;
+ levelDatas[level].modifier.ty = 0;
+ levelDatas[level].modifier.tz = 0;
+
+ if ( !strcmp(token, "world") ) {
+ // we don't care of world datas, read file until world "kids" line
+ while ( strcmp(token, "kids") ) { // current token value is "OBJECT"...
+ if ( fgets(lineBuffer, MAX_LENGTH, fpIn) ) {
+ lineNr++; // input file line counter
+ sscanf( lineBuffer, "%s", token );
+ }
+ }
+ sscanf( lineBuffer, "%*s %u", &levelDatas[level].kids );
+ }
+ else if ( !strcmp(token, "poly") ) {
+ if ( polys == 0) fprintf(fpOut, "begin_model standard\n");
+ polys++;
+ if ( levelDatas[level-1].kids > 0 ) {
+ levelDatas[level-1].kids--;
+ }
+ }
+ else if ( !strcmp(token, "group") ) {
+ groups++;
+ if ( levelDatas[level-1].kids > 0 ) {
+ levelDatas[level-1].kids--;
+ }
+ }
+ else if ( !strcmp(token, "light") ) {
+
+
+
+
+
+ double xx = 0.0, yy = 0.0, zz = 0.0;
+ while ( strcmp(token, "loc") ) { // current token value is "OBJECT"...
+ if ( fgets(lineBuffer, MAX_LENGTH, fpIn) ) {
+ lineNr++; // input file line counter
+ sscanf( lineBuffer, "%s", token );
+ }
+ }
+ sscanf( lineBuffer, "%*s %lf %lf %lf", &xx, &yy, &zz );
+
+ // apply scale
+ xx *= userModifier->scale;
+ yy *= userModifier->scale;
+ zz *= userModifier->scale;
+
+ // add current level offsets
+ //xx += levelDatas[level].modifier.tx;
+ //yy += levelDatas[level].modifier.ty;
+ //zz += levelDatas[level].modifier.tz;
+
+ // add lowers levels offsets
+ for ( int counter1 = level; counter1 > 0; counter1--) {
+//fprintf( stderr, "levelDatas[%d].modifier.translate = %lf %lf %lf\n", counter1 - 1, levelDatas[counter1 - 1].modifier.tx, levelDatas[counter1 - 1].modifier.ty, levelDatas[counter1 - 1].modifier.tz );
+ if ( !strcmp( levelDatas[counter1 - 1].type, "group" ) ) {
+ xx += levelDatas[counter1 - 1].modifier.tx;
+ yy += levelDatas[counter1 - 1].modifier.ty;
+ zz += levelDatas[counter1 - 1].modifier.tz;
+ }
+ }
+
+
+
+
+
+ fprintf( stdout, "Light #%d detected at line %ld. Position: %lf %lf %lf\n", lights, lineNr, xx, yy, zz );
+
+ lights++;
+ if ( levelDatas[level-1].kids > 0 ) {
+ levelDatas[level-1].kids--;
+ }
+ }
+ else {
+ ;
+ }
+ }
+ else if ( !strcmp(token, "kids") ) {
+ sscanf( lineBuffer, "%*s %d", &levelDatas[level].kids );
+
+ while ( levelDatas[level].kids == 0 ) { // clear level offsets values for this level and lower ones
+ levelDatas[level].modifier.tx = 0;
+ levelDatas[level].modifier.ty = 0;
+ levelDatas[level].modifier.tz = 0;
+ if ( level >= 0 ) {
+ level--;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ else if ( !strcmp(token, "numvert") ) {
+ sscanf( lineBuffer, "%*s %d", &verticies );
+ if ( verticies > AC3DMAXVERTICIES - 1 ) {
+ fprintf( stderr, "ac3dtov3d error: too much verticies in %s file. See #define AC3DMAXVERTICIES %d\n", source, AC3DMAXVERTICIES);
+ return -1;
+ }
+
+ for ( unsigned int counter = 0; counter < verticies; counter++ ) {
+ fgets(lineBuffer, MAX_LENGTH, fpIn);
+ lineNr++; // input file line counter
+ sscanf( lineBuffer, "%lf %lf %lf", &vertex[counter].x, &vertex[counter].y, &vertex[counter].z );
+
+ // apply scale
+ vertex[counter].x *= userModifier->scale;
+ vertex[counter].y *= userModifier->scale;
+ vertex[counter].z *= userModifier->scale;
+
+ // add current level offsets
+ vertex[counter].x += levelDatas[level].modifier.tx;
+ vertex[counter].y += levelDatas[level].modifier.ty;
+ vertex[counter].z += levelDatas[level].modifier.tz;
+
+ // add lowers levels offsets
+ for ( int counter1 = level; counter1 >= 0; counter1--) {
+ if ( !strcmp( levelDatas[counter1 - 1].type, "group" ) ) {
+ vertex[counter].x += levelDatas[counter1 - 1].modifier.tx;
+ vertex[counter].y += levelDatas[counter1 - 1].modifier.ty;
+ vertex[counter].z += levelDatas[counter1 - 1].modifier.tz;
+ }
+ }
+
+ // apply rotations (ac3d to v3d orientation conversion)
+ double pw = 0;
+ RotateX ( &vertex[counter].x, &vertex[counter].y, &vertex[counter].z, &pw, -90 );
+ RotateZ ( &vertex[counter].x, &vertex[counter].y, &vertex[counter].z, &pw, 90 );
+ }
+ }
+ else if ( !strcmp(token, "numsurf") ) {
+ unsigned int refs = 0; // number of refs
+ sscanf( lineBuffer, "%*s %d", &surfaces );
+
+
+ while ( surfaces ) {
+ while ( 1 ) { // read until 'refs'
+ fgets(lineBuffer, MAX_LENGTH, fpIn);
+ lineNr++; // input file line counter
+ sscanf( lineBuffer, "%s", token );
+ if ( !strcmp(token, "refs") ) {
+ sscanf( lineBuffer, "%*s %d", &refs ); // number of refs
+ break;
+ }
+ else if ( !strcmp(token, "SURF") ) {
+ ;
+ }
+ else if ( !strcmp(token, "mat") ) {
+ sscanf( lineBuffer, "%*s %d", &activeMaterial ); // material number
+ }
+ }
+
+ struct Ref {
+ unsigned int index;
+ float u;
+ float v;
+ };
+ struct Ref ref[refs];
+
+ switch (refs) { // refs is the number of surface verticies
+ case 0:
+ case 1:
+ surfType = "";
+ break;
+ case 2:
+ surfType = "line";
+ lines++;
+ break;
+ case 3:
+ surfType = "triangles";
+ triangle++;
+ break;
+ case 4:
+ surfType = "quads";
+ quads++;
+ break;
+ default:
+ surfType = "polygon";
+ polygon++;
+ break;
+ }
+
+ if ( activeMaterial >= 0 ) {
+ fprintf(fpOut, "color %f %f %f %f\n", material[activeMaterial].rgb.red, material[activeMaterial].rgb.green, material[activeMaterial].rgb.blue, 1.0 - material[activeMaterial].transp );
+ }
+
+ if ( strcmp( surfType, "" ) ) {
+ fprintf(fpOut, "begin_%s\n", surfType);
+
+ long counter = 0;
+ for ( counter = 0; counter < refs; counter++ ) {
+ fgets(lineBuffer, MAX_LENGTH, fpIn);
+ lineNr++; // input file line counter
+ sscanf( lineBuffer, "%d %f %f", &ref[counter].index, &ref[counter].u, &ref[counter].v );
+ }
+
+ // it it's a surface, then calculate surface normal and write it
+ if ( refs >= 3 )
+ {
+ Vnormal vnTemp;
+ calculateSurfaceNormal( &vertex[ref[0].index], (int) refs, &vnTemp );
+ fprintf( fpOut, " normal %f %f %f\n", vnTemp.i, vnTemp.j, vnTemp.k );
+ }
+
+ if ( userModifier->invertFaces == 1 ) { // invert faces
+ for ( counter = 0; counter < refs; counter++ ) {
+ fprintf(fpOut, " texcoord %f %f\n", ref[counter].u, ref[counter].v );
+ fprintf(fpOut, " %f %f %f\n", vertex[ref[counter].index].x, vertex[ref[counter].index].y, vertex[ref[counter].index].z );
+
+ // for object size calculation
+ if ( vertex[ref[counter].index].x < Xmin ) Xmin = vertex[ref[counter].index].x;
+ else if ( vertex[ref[counter].index].x > Xmax ) Xmax = vertex[ref[counter].index].x;
+ if ( vertex[ref[counter].index].y < Ymin ) Ymin = vertex[ref[counter].index].y;
+ else if ( vertex[ref[counter].index].y > Ymax ) Ymax = vertex[ref[counter].index].y;
+ if ( vertex[ref[counter].index].z < Zmin ) Zmin = vertex[ref[counter].index].z;
+ else if ( vertex[ref[counter].index].z > Zmax ) Zmax = vertex[ref[counter].index].z;
+ }
+ }
+ else { // don't invert faces
+ for ( counter = refs - 1; counter >= 0; counter-- ) {
+ fprintf(fpOut, " texcoord %f %f\n", ref[counter].u, ref[counter].v );
+ fprintf(fpOut, " %f %f %f\n", vertex[ref[counter].index].x, vertex[ref[counter].index].y, vertex[ref[counter].index].z );
+
+ // for object size calculation
+ if ( vertex[ref[counter].index].x < Xmin ) Xmin = vertex[ref[counter].index].x;
+ else if ( vertex[ref[counter].index].x > Xmax ) Xmax = vertex[ref[counter].index].x;
+ if ( vertex[ref[counter].index].y < Ymin ) Ymin = vertex[ref[counter].index].y;
+ else if ( vertex[ref[counter].index].y > Ymax ) Ymax = vertex[ref[counter].index].y;
+ if ( vertex[ref[counter].index].z < Zmin ) Zmin = vertex[ref[counter].index].z;
+ else if ( vertex[ref[counter].index].z > Zmax ) Zmax = vertex[ref[counter].index].z;
+ }
+ }
+ fprintf(fpOut, "end_%s\n", surfType);
+ }
+ surfaces--;
+
+ }
+ }
+ else if ( !strcmp(token, "texture") ) {
+ sscanf( lineBuffer, "%*s \"%s\"\"", textureName );
+ scanFileName( textureName, &path, &baseName, &extension );
+ fprintf(fpOut, "texture_select %s\n", baseName);
+ }
+ else if ( !strcmp(token, "MATERIAL") ) {
+ sscanf( lineBuffer, "MATERIAL %*s rgb %f %f %f amb %*f %*f %*f emis %*f %*f %*f spec %*f %*f %*f shi %*f %*f %*f trans %f", &material[materials].rgb.red, &material[materials].rgb.green, &material[materials].rgb.blue, &material[materials].transp );
+
+ if ( material[materials].rgb.red == 0 && material[materials].rgb.green == 0 && material[materials].rgb.blue == 0 ) {
+ material[materials].rgb.red = 0.004;
+ material[materials].rgb.green = 0.004;
+ material[materials].rgb.blue = 0.004;
+ }
+
+ if ( materials < AC3DMAXMATERIALS - 1 ) {
+ materials++;
+ }
+ else {
+ fprintf( stderr, "ac3dtov3d.c error: too much materials in %s file. See #define AC3DMAXMATERIALS %d\n", source, AC3DMAXMATERIALS);
+ return -1;
+ }
+ }
+ else if ( !strcmp(token, "loc") ) {
+ sscanf( lineBuffer, "%*s %lf %lf %lf", &levelDatas[level].modifier.tx, &levelDatas[level].modifier.ty, &levelDatas[level].modifier.tz );
+
+ // apply scale
+ levelDatas[level].modifier.tx *= userModifier->scale;
+ levelDatas[level].modifier.ty *= userModifier->scale;
+ levelDatas[level].modifier.tz *= userModifier->scale;
+
+ // sar2 issue ??? "translate" command seems to have no effet, thus we add "loc" values to verticies (see "numvert")
+ //fprintf(fpOut, "translate %f %f %f\n", levelDatas[level].modifier.tx, levelDatas[level].modifier.ty, levelDatas[level].modifier.tz );
+ }
+ else if ( !strcmp(token, "name") ) {
+ sscanf( lineBuffer, "%*s \"%s", token );
+ token[strlen(token) - 1] = '\0'; // discard last double quote
+ fprintf(fpOut, "# name: %s\n", token);
+ }
+ else {
+ if ( lineBuffer[0] != '\n' ) {
+ fprintf(fpOut, "#%ld: %s", lineNr, lineBuffer); // if tag is not recognized, set it as comment and start it with input file line number
+ }
+ else {
+ fprintf(fpOut, "\n");
+ }
+ }
+
+ lineNr++; // input file line counter
+ }
+ fprintf(fpOut, "end_model standard\n");
+
+ // print some usefull information on console
+ fprintf( stdout, "Object X, Y and Z dimensions [m]: %.3lf %.3lf %.3lf\n", Xmax - Xmin, Ymax - Ymin, Zmax - Zmin );
+ fprintf( stdout, "Rectangular Xmin Xmax Ymin Ymax Zmin Zmax bounds [m]: %.3lf %.3lf %.3lf %.3lf %.3lf %.3lf\n", Xmin, Xmax, Ymin, Ymax, Zmin, Zmax );
+
+ for ( int cnt = 0; cnt < AC3DMAXTEXTURES; cnt++ )
+ {
+ free( texturesList[cnt] );
+ }
+
+
+ if ( path != NULL )
+ {
+ free( path );
+ path = NULL;
+ }
+ if ( baseName != NULL )
+ {
+ free( baseName );
+ baseName = NULL;
+ }
+ if ( extension != NULL )
+ {
+ free( extension );
+ extension = NULL;
+ }
+
+ fclose(fpOut);
+ fclose(fpIn);
+ return 0;
+}
diff --git a/tools/v3dconverter/src/objtohf.c b/tools/v3dconverter/src/objtohf.c
new file mode 100644
index 0000000..fd391b7
--- /dev/null
+++ b/tools/v3dconverter/src/objtohf.c
@@ -0,0 +1,603 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+
+#include
+#include
+#include
+
+
+#define FREE_AND_CLOSE_ALL_OBJTOHF \
+if ( fpObjIn && (fpObjIn != NULL) ) fclose( fpObjIn ); \
+if ( fpHfOut && (fpHfOut != NULL) ) fclose( fpHfOut ); \
+if ( fpConversionDataFileIn != NULL ) fclose( fpConversionDataFileIn );
+
+
+int objToHf(const char *source, const char *dest, UserModifier *userModifier ) {
+ FILE *fpObjIn = NULL, *fpHfOut = NULL, *fopen(), *fpConversionDataFileIn = NULL;
+ char lineBuffer[MAX_LENGTH + 1], objTag[MAX_LENGTH + 1], terrainFileName[MAX_LENGTH + 1];
+ long lineNr = 1;
+ unsigned long verticesNr = 0;
+ bool specFileFound = false;
+ double newObjMaxAltitude = 0; // max altitude in modified *.obj file
+
+ struct TGAImageHeader {
+ /*** Fixed length data ***/
+ uint8_t idFieldLength; // image ID field length
+ uint8_t colorMapType;
+ uint8_t imageType;
+ /* Color Map Specification */
+ uint8_t colorMapIndexL;
+ uint8_t colorMapIndexH;
+ uint8_t colorMapLengthL;
+ uint8_t colorMapLengthH;
+ uint8_t colorMapSize;
+ /* Image specification */
+ uint8_t xOriginL;
+ uint8_t xOriginH;
+ uint8_t yOriginL;
+ uint8_t yOriginH;
+ uint8_t widthL;
+ uint8_t widthH;
+ uint8_t heightL;
+ uint8_t heightH;
+ uint8_t bitsPerPixel;
+ uint8_t imageDescriptor;
+
+ /*** Variable length data ***/
+ // ...
+ };
+
+ struct TerrainSpec {
+ double sizeX; // "width", in meters
+ double sizeY; // "height", in meters
+ double maxAltitude; // max altitude, in meters.
+ double xPos; // x translation, in meters
+ double yPos; // y translation, in meters
+ double zPos; // z translation, in meters
+ double h; // heading rotation, in degrees
+ double p; // pitch rotation, in degrees
+ double b; // bank rotation, in degrees
+ double scale; // scale applied by user during *.3d to *.obj conversion
+ uint16_t bitsWidth; // image width, in bits (0 to 65535)
+ uint16_t bitsHeight; // image height, in bits (0 to 65535)
+ };
+ struct TerrainSpec terrainSpec;
+
+ /*
+ * Input and output files ready ?
+ */
+ if ( ( fpObjIn = fopen(source, "r" ) ) == NULL ) {
+ perror(source);
+
+ FREE_AND_CLOSE_ALL_OBJTOHF
+ return -1;
+ }
+ if ( ( fpHfOut = fopen( dest, "w" ) ) == NULL ) {
+ perror( dest );
+
+ FREE_AND_CLOSE_ALL_OBJTOHF
+ return -1;
+ }
+
+
+ /*
+ * Look for material name in *.obj file in order to retrieve terrain specification file name.
+ * This specification file has been automatically generated during *.3d terrain to *.obj conversion
+ * and contains some specification data. Then, extract data from this file.
+ */
+ int valueSet = 0;
+ while ( fgets(lineBuffer, MAX_LENGTH, fpObjIn ) )
+ {
+ if ( lineBuffer[ 0 ] == '\n' || lineBuffer[ 0 ] == '\r' || ( sscanf( lineBuffer, " %s[\n]", objTag ) ) == 0 ) // blank line or objTag == ""
+ {
+ lineNr++;
+ continue;
+ }
+
+ if ( !strcmp( objTag, "usemtl" ) ) // *.obj material name
+ {
+ if ( sscanf( lineBuffer, "usemtl %s", objTag ) == 1 )
+ {
+ int homeLength = strlen( getenv("HOME") );
+ char v3dConverterPath[] = "/.local/share/v3dconverter/";
+ unsigned int dirLength = strlen( v3dConverterPath );
+ int materialNameLength = strlen( objTag );
+
+ char *name = malloc( homeLength + dirLength + materialNameLength + 1 );
+ if ( name == NULL )
+ {
+ fprintf( stderr, "objtohf.c: can't allocate memory for name.\n" );
+ return -1;
+ }
+ objTag[ 14 ] = '\0'; // limit objTag string length because sometimes Blender adds its own extension to material name.
+ sprintf( name, "%s%s%s", getenv("HOME"), v3dConverterPath, objTag );
+
+ if ( ( fpConversionDataFileIn = fopen(name, "r" ) ) == NULL )
+ {
+ specFileFound = false;
+ }
+ else
+ {
+ specFileFound = true;
+
+ while ( fgets( lineBuffer, MAX_LENGTH, fpConversionDataFileIn ) )
+ {
+ if ( lineBuffer[ 0 ] == '\n' || lineBuffer[ 0 ] == '\r' || ( sscanf( lineBuffer, " %s[\n]", objTag ) ) == 0 ) // blank line or objTag == ""
+ continue;
+
+ if ( lineBuffer[ 0 ] == '#' )
+ continue;
+
+ int cnt = 0;
+ while ( lineBuffer[ cnt++ ] != '=' );
+ lineBuffer[ cnt - 1 ] = '\0';
+
+ if ( !strcmp( lineBuffer, "file" ) )
+ {
+ strncpy( terrainFileName, &lineBuffer[ cnt ], MAX_LENGTH + 1 );
+ char * lastDotPos = strrchr( terrainFileName, '.' );
+ strcpy( lastDotPos, ".3d" );
+ terrainFileName[ strlen( terrainFileName ) ] = '\0';
+ //fprintf( stdout, "file = %s", &lineBuffer[ cnt ] );
+ }
+ else if ( !strcmp( lineBuffer, "material_name" ) )
+ {
+ //fprintf( stdout, "material_name = %s", &lineBuffer[ cnt ] );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_width" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%lf", &terrainSpec.sizeX );
+ //fprintf( stdout, "terrain_width = %f\n", terrainSpec.sizeX );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_height" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%lf", &terrainSpec.sizeY );
+ //fprintf( stdout, "terrain_height = %f\n", terrainSpec.sizeY );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_altitude" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%lf", &terrainSpec.maxAltitude );
+ //fprintf( stdout, "terrain_altitude = %f\n", terrainSpec.maxAltitude );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_transX" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%lf", &terrainSpec.xPos );
+ //fprintf( stdout, "terrain_transX = %f\n", terrainSpec.xPos );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_transY" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%lf", &terrainSpec.yPos );
+ //fprintf( stdout, "terrain_transY = %f\n", terrainSpec.yPos );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_transZ" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%lf", &terrainSpec.zPos );
+ //fprintf( stdout, "terrain_transZ = %f\n", terrainSpec.zPos );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_scale" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%lf", &terrainSpec.scale );
+ //fprintf( stdout, "terrain_scale = %f\n", terrainSpec.scale );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_bitsWidth" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%hd", &terrainSpec.bitsWidth );
+ //fprintf( stdout, "terrain_bitsWidth = %d\n", terrainSpec.bitsWidth );
+ }
+ else if ( !strcmp( lineBuffer, "terrain_bitsHeight" ) )
+ {
+ valueSet++;
+ sscanf( &lineBuffer[ cnt ], "%hd", &terrainSpec.bitsHeight );
+ //fprintf( stdout, "terrain_bitsHeight = %d\n", terrainSpec.bitsHeight );
+ }
+ else
+ {
+ fprintf( stdout, "objtohf.c: unexpected data found in file '%s'.\n", name );
+ }
+ }
+ free( fpConversionDataFileIn );
+ fpConversionDataFileIn = NULL;
+ }
+ free( name );
+ }
+ else
+ {
+ ; // sscanf error
+ }
+ }
+ else if ( !strcmp( objTag, "v" ) ) // *.obj vertex
+ {
+ if ( sscanf( lineBuffer, "v %*s %s %*s", objTag ) == 1 )
+ {
+ double foo;
+ sscanf( objTag, "%lf", &foo );
+ if ( foo > newObjMaxAltitude )
+ newObjMaxAltitude = foo;
+ }
+ else
+ {
+ ; // sscanf error
+ }
+ }
+ lineNr++;
+ }
+
+ /*
+ * Check if all terrain specification values are set, and ask them to user if not.
+ */
+#define TERRAIN_SPEC_VALUES_TO_SET 9
+
+#define READ_AND_CHECK_STDIN \
+cnt = 0; \
+while( ( cnt < MAX_LENGTH ) && ( c = getchar() ) != '\n' && c != EOF ) \
+ lineBuffer[ cnt++ ] = c; \
+lineBuffer[ cnt ] = '\0'; \
+for ( int cnt1 = cnt - 1; cnt1 >= 0; cnt1-- ) \
+{ \
+ if ( !( isdigit( lineBuffer[ cnt1 ] ) || ( lineBuffer[ cnt1 ] == '-' ) || ( lineBuffer[ cnt1 ] == '.' ) ) ) \
+ { \
+ lineBuffer[ 0 ] = '\0'; \
+ break; \
+ }; \
+}
+
+ if ( !specFileFound )
+ {
+ double userAnswer;
+ char c, lineBuffer[MAX_LENGTH + 1] = { '\0' };
+ int cnt = 0;
+
+ fprintf( stderr, "Terrain specification file not found. Please enter data manually:\n" );
+ fprintf( stderr, "----------------------------------------------------------------\n" );
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter terrain X value (*.hf width) in meters: " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.sizeX = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter terrain Y (*.hf height) value in meters: " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.sizeY = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter terrain Z value (*.hf max altitude) in meters: " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.maxAltitude = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter terrain translation X value in meters: " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.xPos = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter terrain translation Y value in meters: " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.yPos = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter terrain translation Z value in meters: " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.zPos = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter *.obj terrain scale value (\"*.obj/*.hf distances ratio\"): " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.scale = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter *.hf texture width number of bits: " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.bitsWidth = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+ while ( true )
+ {
+ fprintf( stderr, "Please enter *.hf texture height number of bits: " );
+
+ READ_AND_CHECK_STDIN
+
+ if ( ( sscanf( lineBuffer, "%lf", &userAnswer ) ) == 1 )
+ {
+ terrainSpec.bitsHeight = userAnswer;
+ valueSet++;
+ break;
+ }
+ else
+ fprintf( stderr, "INPUT ERROR\n" );
+ }
+
+
+ if ( valueSet != TERRAIN_SPEC_VALUES_TO_SET )
+ fprintf( stderr, "objtohf.c : something bad has happen. Wrong TERRAIN_SPEC_VALUES_TO_SET ?\n" );
+
+ /*
+ fprintf( stderr, " terrain width = %lf\n", terrainSpec.sizeX);
+ fprintf( stderr, "terrain height = %lf\n", terrainSpec.sizeY);
+ fprintf( stderr, " max altitude = %lf\n", terrainSpec.maxAltitude);
+ fprintf( stderr, "terrain transX = %lf\n", terrainSpec.xPos);
+ fprintf( stderr, "terrain transY = %lf\n", terrainSpec.yPos);
+ fprintf( stderr, "terrain transZ = %lf\n", terrainSpec.zPos);
+ fprintf( stderr, " terrain scale = %lf\n", terrainSpec.scale);
+ fprintf( stderr, " terrain bitsW = %d\n", terrainSpec.bitsWidth);
+ fprintf( stderr, " terrain bitsH = %d\n", terrainSpec.bitsHeight);
+ */
+ }
+
+
+ /*
+ * Prepare tga '.hf' image header
+ */
+ struct TGAImageHeader hfTexHeader;
+ hfTexHeader.idFieldLength = 0;
+ hfTexHeader.colorMapType = 0;
+ hfTexHeader.imageType = 3; // Uncompressed, "black and white" (grey level) image
+ hfTexHeader.colorMapIndexL = 0;
+ hfTexHeader.colorMapIndexH = 0;
+ hfTexHeader.colorMapLengthL = 0;
+ hfTexHeader.colorMapLengthH = 0;
+ hfTexHeader.colorMapSize = 0;
+ hfTexHeader.xOriginL = 0;
+ hfTexHeader.xOriginH = 0;
+ hfTexHeader.yOriginL = 0;
+ hfTexHeader.yOriginH = 0;
+ hfTexHeader.widthL = terrainSpec.bitsWidth & 0xFF;
+ hfTexHeader.widthH = terrainSpec.bitsWidth >> 8;
+ hfTexHeader.heightL = terrainSpec.bitsHeight & 0xFF;
+ hfTexHeader.heightH = terrainSpec.bitsHeight >> 8;
+ hfTexHeader.bitsPerPixel = 8;
+ hfTexHeader.imageDescriptor = 32; // Top left origin
+
+
+ /*
+ * Reserve memory for tga '.hf' image data (i.e. color bytes).
+ */
+ uint64_t imageDataBytes = ( 256 * hfTexHeader.widthH + hfTexHeader.widthL ) * ( 256 * hfTexHeader.heightH + hfTexHeader.heightL ) * ( hfTexHeader.bitsPerPixel / 8 );
+ uint8_t *tgaImageData = malloc( imageDataBytes );
+ if ( tgaImageData == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for *.hf TGA image data.\n" );
+
+ FREE_AND_CLOSE_ALL_OBJTOHF
+ return -1;
+ }
+
+
+ /*
+ * Read and process each *.obj file line in order to extract vertices,
+ * then convert vertices coordinates to tga ".hf" image column bit number, row bit number, and grey value (i.e. altitude, from 0 to 255).
+ */
+ rewind( fpObjIn );
+ lineNr = 1;
+ double perPixelXDistance = terrainSpec.sizeX / (double)terrainSpec.bitsWidth;
+ double perPixelYDistance = terrainSpec.sizeY / (double)terrainSpec.bitsHeight;
+ double newTerrainMaxAltitude = ( newObjMaxAltitude - terrainSpec.zPos ) / terrainSpec.scale;
+
+ double perPixelZDistance = 0;
+ if ( round( newTerrainMaxAltitude ) <= round( terrainSpec.maxAltitude ) )
+ perPixelZDistance = terrainSpec.maxAltitude / 255; // altitude is always an 8 bits value
+ else
+ { // New max altitude is higher than original one
+ perPixelZDistance = newTerrainMaxAltitude / 255;
+ fprintf( stdout, "\e[1;33mYou should modify the heightfield_load 4th argument (altitude) value to %.3f in the *.3d terrain file\e[0m.\n", newTerrainMaxAltitude );
+ //fprintf( stdout, "\e[1;33mYou should modify the heightfield_load 4th argument (altitude) value to %.3f instead of %.3f in the %s file\e[0m.\n", newTerrainMaxAltitude, terrainSpec.maxAltitude, terrainFileName );
+ }
+
+ double halfXSize = terrainSpec.sizeX / 2 - perPixelXDistance / 2;
+ double halfYSize = terrainSpec.sizeY / 2 - perPixelYDistance / 2;
+ double x = 0;
+ double y = 0;
+ double z = 0;
+ bool objectFound = false;
+ while ( fgets(lineBuffer, MAX_LENGTH, fpObjIn) ) {
+ if ( lineBuffer[ 0 ] == '\n' || lineBuffer[ 0 ] == '\r' || ( sscanf( lineBuffer, " %s[\n]", objTag ) ) == 0 ) // blank line or objTag == ""
+ {
+ lineNr++;
+ continue;
+ }
+
+ if ( !strcmp( objTag, "v" ) ) // vertex
+ {
+ verticesNr++;
+
+ sscanf( lineBuffer, "%*s %lf %lf %lf", &x, &z, &y );
+ y = -y;
+
+ /* Revert *.3d to *.obj scale (set by user) */
+ x /= terrainSpec.scale ;
+ y /= terrainSpec.scale ;
+ z /= terrainSpec.scale ;
+
+ /* Revert translations. These translations includes *.3d terrain translations and translations requested by user. */
+ x -= terrainSpec.xPos;
+ y -= terrainSpec.yPos;
+ z -= terrainSpec.zPos;
+
+ double col = ( x + halfXSize ) / perPixelXDistance;
+ double row = (double)(terrainSpec.bitsWidth) - 1 - ( y + halfYSize ) / perPixelYDistance;
+ double altitude = z / perPixelZDistance;
+
+ col = round(col);
+ row = round(row);
+ altitude = round(altitude);
+
+ if ( altitude > 255 )
+ altitude = 255;
+ else if ( altitude < 0 )
+ altitude = 0;
+
+ if ( col < 0 || col > terrainSpec.bitsWidth - 1 )
+ {
+ fprintf( stderr, "Error : Bad column number detected in *.hf image (col = %lf, row = %lf). Exiting.\n", col, row );
+
+ FREE_AND_CLOSE_ALL_OBJTOHF
+ return -1;
+ }
+
+ if ( row < 0 || row > terrainSpec.bitsHeight - 1 )
+ {
+ fprintf( stderr, "Error : Bad row number detected in *.hf image (col = %lf, row = %lf). Exiting.\n", col, row );
+
+ FREE_AND_CLOSE_ALL_OBJTOHF
+ return -1;
+ }
+
+ unsigned long offset = (unsigned long)col + (unsigned long)row * terrainSpec.bitsWidth;
+ if ( ( offset >= 0 ) && ( offset < imageDataBytes ) )
+ {
+ tgaImageData[ offset ] = (uint8_t)altitude;
+ }
+ else
+ {
+ fprintf( stderr, "objtohf.c : tgaImageData offset error. Bad terrain specification data ?\n" );
+ FREE_AND_CLOSE_ALL_OBJTOHF
+
+ return EXIT_FAILURE;
+ }
+ }
+ else if ( !strcmp( objTag, "o" ) ) // object
+ {
+ if ( !objectFound ) objectFound = true;
+ else
+ {
+ fprintf( stderr, "File %s, line #%ld: can't convert *.obj to *.hf because more than one object found in *.obj file. Exiting.\n", source, lineNr );
+ FREE_AND_CLOSE_ALL_OBJTOHF
+
+ return EXIT_FAILURE;
+ }
+ }
+
+ lineNr++;
+ }
+
+
+ /*
+ * Write tga ".hf" image header
+ */
+ fwrite( &hfTexHeader, sizeof( hfTexHeader ), 1, fpHfOut );
+
+ /*
+ * No color map to write
+ */
+
+ /*
+ * Write image data
+ */
+ for ( unsigned long cnt0 = 0; cnt0 < imageDataBytes / terrainSpec.bitsWidth; cnt0++ )
+ {
+ fwrite( &tgaImageData[ cnt0 * terrainSpec.bitsWidth ], terrainSpec.bitsWidth, 1, fpHfOut );
+ }
+
+ FREE_AND_CLOSE_ALL_OBJTOHF
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/v3dconverter/src/objtov3d.c b/tools/v3dconverter/src/objtov3d.c
new file mode 100644
index 0000000..f1937f9
--- /dev/null
+++ b/tools/v3dconverter/src/objtov3d.c
@@ -0,0 +1,1322 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+
+#include
+
+/* *.mtl material */
+typedef struct Material Material;
+struct Material {
+ char *name;
+ double KaR; double KaG; double KaB;
+ double KdR; double KdG; double KdB;
+ double KeR; double KeG; double KeB;
+ double KsR; double KsG; double KsB;
+ double Ns;
+ double Ni;
+ double d;
+ int illum;
+ char *texName;
+};
+
+#define MAX_MTLLIB_FILENAMES 10 // a mtllib statement can be followed by multiple file names. Here is defined maximum number of file names.
+
+#define FREE_AND_CLOSE_ALL_OBJTOV3D \
+if ( v != NULL ) free( v ); \
+if ( vn != NULL ) free( vn ); \
+if ( vt != NULL ) free( vt ); \
+if ( mat != NULL ) { \
+ for ( int cnt = 0; cnt < materials; cnt++ ) { \
+ if ( mat[ cnt ].name != NULL ) free( mat[ cnt ].name ); \
+ if ( mat[ cnt ].texName != NULL ) free( mat[ cnt ].texName ); \
+ } \
+ free( mat ); \
+} \
+if ( fpOut != NULL ) fclose( fpOut ); \
+if ( fpIn != NULL ) fclose( fpIn );
+
+
+int readMtlFile( char *mtllibSource, Material **mat, int *mtl ); // read .mtl (material) file
+
+int objToV3d(const char *source, const char *dest, UserModifier *userModifier ) { // special bit nr0 = 1: invert faces
+ FILE *fpIn = NULL, *fpOut = NULL, *fopen();
+ long lineNr = 1;
+ long vertices = 0, textCoords = 0, normals = 0, faces = 0; // counters.
+ int materials = 0; // counters.
+ double vx = 0, vy = 0, vz = 0, vni = 0, vnj = 0, vnk = 0;
+ bool textureOn = false, firstObject = true;
+ double Xmin = 999999999, Xmax = -999999999, Ymin = 999999999, Ymax = -999999999, Zmin = 999999999, Zmax = -999999999; // object rectangular bounds
+ double XYradius, maxXYradius = 0; // object cylendrical bounds (maxXYradius, Zmin, Zmax)
+ double XYZradius, maxXYZradius = 0; // object spherical bound (maxXYZradius)
+ char lineBuffer[MAX_LENGTH + 1], objTag[MAX_LENGTH + 1];
+ char previousV3dParam[ 40 + 1 ] = "";
+
+ #define V3DMODELTYPES 18+1
+ #define V3DMODELTYPEMAXLENGTH 14
+ char v3dModelType[ V3DMODELTYPES ][ V3DMODELTYPEMAXLENGTH + 1 ] = {
+ { "standard" }, // "by tradition", first model is always named 'standard'. V3dconverter will automatically create it after "texture_load" statements.
+ { "standard_dawn" },
+ { "standard_dusk" },
+ { "standard_far" },
+ { "standard_night" },
+ { "rotor" },
+ { "aileron_left" },
+ { "aileron_right" },
+ { "rudder_top" },
+ { "rudder_bottom" },
+ { "elevator" },
+ { "flap" },
+ { "air_brake" },
+ { "landing_gear" },
+ { "door" },
+ { "fueltank" },
+ { "cockpit" },
+ { "shadow" },
+ { "" }
+ };
+ char currentV3dModelType[ V3DMODELTYPEMAXLENGTH + 1 ] = "";
+
+ /* Input and output files ok ? */
+ if ( ( fpIn = fopen(source, "r" ) ) == NULL ) {
+ perror(source);
+ return -1;
+ }
+ if ( ( fpOut = fopen( dest, "w+" ) ) == NULL ) {
+ fclose( fpIn );
+ perror( dest );
+ return -1;
+ }
+
+ /* Prepare structures */
+ Vcoord *v = malloc ( sizeof ( Vcoord ) );
+ if ( v == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for Vcoord.\n");
+ return EXIT_FAILURE;
+ }
+
+ Vnormal *vn = malloc ( sizeof ( Vnormal ) );
+ if ( vn == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for Vnormal.\n");
+ free( v );
+ return EXIT_FAILURE;
+ }
+
+ Tcoord *vt = malloc ( sizeof ( Tcoord ) );
+ if ( vt == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for Tcoord.\n");
+ free( v );
+ free( vn );
+ return EXIT_FAILURE;
+ }
+
+ Material *mat = malloc ( sizeof ( Material ) );
+ if ( mat == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for Material.\n");
+ free( v );
+ free( vn );
+ free( vt );
+ return EXIT_FAILURE;
+ }
+
+ struct Color currentColor = { -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0 };
+
+ /* Read and process each *.obj file line */
+ while ( fgets(lineBuffer, MAX_LENGTH, fpIn) ) {
+ if ( lineBuffer[ 0 ] == '\n' || lineBuffer[ 0 ] == '\r' || ( sscanf( lineBuffer, " %s[\n]", objTag ) ) == 0 ) // blank line or objTag == ""
+ {
+ lineNr++;
+ continue;
+ }
+ else if ( !strcmp( objTag, "v" ) ) // vertex
+ {
+ if ( previousV3dParam[0] != '\0' )
+ {
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ strcpy( previousV3dParam, "" );
+ }
+
+ vertices++; // add a vertex coord. v[0] is never used (as in *.obj file format description).
+
+ Vcoord *tmp = realloc( v, ( vertices + 1 ) * sizeof( Vcoord ) );
+ if (tmp == NULL)
+ {
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+ fprintf( stderr, "Line #%ld: Can't allocate memory for Vcoord[%ld].\n", lineNr, vertices );
+ return EXIT_FAILURE;
+ }
+ v = tmp;
+
+ /* read vertex coordinates and re-orient them because obj coords system is different from v3d coords system */
+ sscanf( lineBuffer, " v %lf %lf %lf", &vx, &vy, &vz );
+ double foo = 0; // rotateZ waits for a 'w' value
+ RotateX ( &vx, &vy, &vz, &foo, -90 ); // rotate vertex arround X axis. Angle given in degrees.
+ RotateZ ( &vx, &vy, &vz, &foo, 90 ); // rotate vertex arround Z axis. Angle given in degrees.
+
+ /* apply user modifiers. Apply order is: X rotation, then Y rotation, then Z rotation, then scale, then translations. */
+ if ( userModifier->rx != 0.0 )
+ {
+ RotateX ( &vx, &vy, &vz, &foo, userModifier->rx );
+ }
+ if ( userModifier->ry != 0.0 )
+ {
+ RotateY ( &vx, &vy, &vz, &foo, userModifier->ry );
+ }
+ if ( userModifier->rz != 0.0 )
+ {
+ RotateZ ( &vx, &vy, &vz, &foo, userModifier->rz );
+ }
+
+ if ( userModifier->scale != 1.0 )
+ {
+ vx *= userModifier->scale;
+ vy *= userModifier->scale;
+ vz *= userModifier->scale;
+ }
+
+ if ( userModifier->tx != 0.0 )
+ {
+ vx += userModifier->tx;
+ }
+ if ( userModifier->ty != 0.0 )
+ {
+ vy += userModifier->ty;
+ }
+ if ( userModifier->tz != 0.0 )
+ {
+ vz += userModifier->tz;
+ }
+
+ /* store coords */
+ v[vertices].x = vx;
+ v[vertices].y = vy;
+ v[vertices].z = vz;
+
+ /* easy way to compute object rectangular bounds */
+ if ( vx < Xmin )
+ Xmin = vx;
+ if ( vx > Xmax )
+ Xmax = vx;
+ if ( vy < Ymin )
+ Ymin = vy;
+ if ( vy > Ymax )
+ Ymax = vy;
+ if ( vz < Zmin )
+ Zmin = vz;
+ if ( vz > Zmax )
+ Zmax = vz;
+
+ /* cylindrical bounds */
+ XYradius = sqrt( pow( vx, 2 ) + pow( vy, 2 ) );
+ if ( XYradius > maxXYradius )
+ maxXYradius = XYradius;
+
+ /* spherical bound */
+ XYZradius = sqrt( pow( XYradius, 2 ) + pow( vz, 2 ) );
+ if ( XYZradius > maxXYZradius )
+ maxXYZradius = XYZradius;
+ }
+ else if ( !strcmp( objTag, "vn" ) ) // vertex normal
+ {
+ if ( previousV3dParam[0] != '\0' )
+ {
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ strcpy( previousV3dParam, "" );
+ }
+
+ normals++; // add a vertex normal. vn[0] is not used.
+
+ Vnormal *tmp = realloc( vn, ( normals + 1 ) * sizeof( Vnormal ) );
+ if (tmp == NULL)
+ {
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+ fprintf( stderr, "Line #%ld: Can't allocate memory for Vnormal[%ld].\n", lineNr, normals );
+ return EXIT_FAILURE;
+ }
+ vn = tmp;
+
+ /* read vertex normal coordinates and re-orient them because obj coords system is different from v3d coords system */
+ sscanf( lineBuffer, " vn %lf %lf %lf", &vni, &vnj, &vnk );
+ double foo = 0; // rotateZ waits for a 'w' value
+ RotateX ( &vni, &vnj, &vnk, &foo, -90 ); // rotate vertex arround X axis. Angle given in degrees.
+ RotateZ ( &vni, &vnj, &vnk, &foo, 90 ); // rotate vertex arround Z axis. Angle given in degrees.
+
+ /* it's a normal, no need to apply user modifiers. */
+
+ /* store coords */
+ vn[normals].i = vni;
+ vn[normals].j = vnj;
+ vn[normals].k = vnk;
+ }
+ else if ( !strcmp( objTag, "vt" ) ) // texture vertex
+ {
+ if ( previousV3dParam[0] != '\0' )
+ {
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ strcpy( previousV3dParam, "" );
+ }
+
+ textCoords++; // add a vertex texture coord. vt[0] is not used.
+
+ Tcoord *tmp = realloc( vt, ( textCoords + 1 ) * sizeof( Tcoord ) );
+ if (tmp == NULL)
+ {
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+ fprintf( stderr, "Line #%ld: Can't allocate memory for Tcoord[%ld].\n", lineNr, textCoords );
+ return EXIT_FAILURE;
+ }
+ vt = tmp;
+
+ sscanf( lineBuffer, " vt %lf %lf %lf", &vt[textCoords].u, &vt[textCoords].v, &vt[textCoords].w );
+ }
+ else if ( !strcmp( objTag, "p" ) ) // point
+ {
+ if ( strcmp( previousV3dParam, "points" ) ) // previous type was not points ?
+ {
+ if ( previousV3dParam[0] != '\0' )
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ else
+ fprintf( fpOut, "begin_points\n" );
+
+ strcpy( previousV3dParam, "points" );
+ }
+
+ const char delim[2] = " ";
+ char *token;
+ int vNum = 0;
+ token = strtok(lineBuffer, delim); // first token contains "p" (point)
+ token = strtok(NULL, delim); // second token contains first point vertex
+ while( token != NULL ) {
+ sscanf( token, " %d", &vNum ); // read vertex number
+
+ /* write point coord
+ * Info: an obj point don't have a normal, but a v3d point seems to have one (which is always 0.0 0.0 1.0 ?).
+ * FIXME: is it necessary to add a normal, and if yes, how to compute this normal? This point is not on a surface!
+ */
+ if ( vNum > 0 )
+ fprintf( fpOut, " %f %f %f\n", v[vNum].x, v[vNum].y, v[vNum].z );
+ else
+ fprintf( fpOut, " %f %f %f\n", v[vertices + vNum].x, v[vertices + vNum].y, v[vertices + vNum].z );
+ }
+ }
+ else if ( !strcmp( objTag, "l" ) ) // line
+ {
+ // count line vertices
+ int lineVerticies = 0, lastStatementPos = 0, firstVertexNr = 0, lastVertexNr = 0;
+ int vNum = 0, vtNum = 0;
+ const char delim[2] = " ";
+ char *token;
+
+ for ( int cnt = 0; cnt < strlen( lineBuffer ); cnt++ ) {
+ if ( lineBuffer[ cnt ] == ' ' || lineBuffer[ cnt ] == '\t' )
+ {
+ lineVerticies++;
+ while ( lineBuffer[ cnt ] == ' ' || lineBuffer[ cnt ] == '\t' ) cnt++; // if multiple spaces / tabs
+ lastStatementPos = cnt - 1;
+ }
+ }
+
+ char lastToken[MAX_LENGTH + 1];
+ for ( int cnt = lastStatementPos; lineBuffer[ cnt ] != '\0'; cnt++ )
+ lastToken[ cnt - lastStatementPos ] = lineBuffer[ cnt + 1 ];
+ sscanf( lastToken, " %d", &lastVertexNr );
+ sscanf( lineBuffer, " l %d", &firstVertexNr );
+
+ if ( lineVerticies == 2 ) // it's a single line
+ {
+ if ( strcmp( previousV3dParam, "lines" ) ) // if previous type was not "lines"
+ {
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ fprintf( fpOut, "begin_lines\n" );
+
+ strcpy( previousV3dParam, "lines" );
+ }
+ }
+ else if ( firstVertexNr == lastVertexNr ) // there are more than 2 vertices and it's a line loop
+ {
+ // always only one loop per v3d "begin_line_loop" statement: no need to check previous type, just "end_" it.
+ if ( previousV3dParam[0] != '\0' )
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+
+ fprintf( fpOut, "begin_line_loop\n" );
+ strcpy( previousV3dParam, "line_loop" );
+
+ lineVerticies--; // will be used when writing datas to avoid last vertex print in case of line loop
+
+ }
+ else // there are more than 2 vertices, and it's a line strip
+ {
+ // always only one strip per v3d "begin_line_strip" statement: no need to check previous type, just "end_" it.
+ if ( previousV3dParam[0] != '\0' )
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+
+ fprintf( fpOut, "begin_line_strip\n" );
+ strcpy( previousV3dParam, "line_strip" );
+ }
+
+ // write datas
+ token = strtok( lineBuffer, delim ); // first token contains "l" (line)
+ token = strtok( NULL, delim ); // second token contains first v/vt reference numbers
+ while( token != NULL ) {
+ vNum = 0; vtNum = 0;
+ sscanf( token, " %d/%d", &vNum, &vtNum );
+
+ if ( lineVerticies > 0 ) // check lineVerticies value to avoid last vertex print in case of line loop
+ {
+ if ( textureOn == true )
+ {
+ // write texture coord only if vtNum is not equal to 0
+ if ( vtNum > 0 )
+ fprintf( fpOut, " texcoord %f %f\n", vt[vtNum].u, vt[vtNum].v );
+ else if ( vtNum < 0 )
+ fprintf( fpOut, " texcoord %f %f\n", vt[textCoords + vtNum].u, vt[textCoords + vtNum].v );
+ }
+
+ // write vertex coord
+ if ( vNum > 0 )
+ fprintf( fpOut, " %f %f %f\n", v[vNum].x, v[vNum].y, v[vNum].z );
+ else
+ fprintf( fpOut, " %f %f %f\n", v[vertices + vNum].x, v[vertices + vNum].y, v[vertices + vNum].z );
+ }
+
+ lineVerticies--;
+ token = strtok( NULL, delim ); // next token
+ }
+ }
+ else if ( !strcmp( objTag, "f" ) ) // face
+ {
+ char *token;
+ long vNum = 0, vtNum = 0, vnNum = 0;
+ int faceVerticies = 0;
+
+ faces++;
+
+ /* count face vertices in order to determine face type (triangle, quad, polygon) */
+ int cnt = 0;
+ int bufLength = strlen( lineBuffer );
+ while ( ( cnt < bufLength ) && ( lineBuffer[ cnt ] != 'f' ) ) // look for 'f'
+ cnt++;
+ cnt++; // go to next character
+ while ( cnt < bufLength )
+ {
+ while ( ( cnt < bufLength ) && ( isspace( lineBuffer[ cnt ] ) ) ) // look for next non-blank character
+ cnt++;
+ while ( ( cnt < bufLength ) && ( !isspace( lineBuffer[ cnt ] ) ) ) // look for next blank character
+ cnt++;
+ if ( cnt < bufLength )
+ faceVerticies++;
+ }
+
+ if ( faceVerticies == 3 ) // triangle
+ {
+ if ( strcmp( previousV3dParam, "triangles" ) ) // previous surface type was not triangles ?
+ {
+ if ( previousV3dParam[0] != '\0' )
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+
+ fprintf( fpOut, "begin_triangles\n" );
+ strcpy( previousV3dParam, "triangles" );
+ }
+ }
+ else if ( faceVerticies == 4 ) // quad
+ {
+ if ( strcmp( previousV3dParam, "quads" ) ) // previous surface type was not quads ?
+ {
+ if ( previousV3dParam[0] != '\0' )
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+
+ fprintf( fpOut, "begin_quads\n" );
+ strcpy( previousV3dParam, "quads" );
+ }
+ }
+ else if ( faceVerticies > 4 ) // if faceVerticies is > 4, it is a polygon.
+ {
+ // always only one polygon per "begin_polygon" statement: no need to check previous type, just "end_" it.
+ if ( previousV3dParam[0] != '\0' )
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ fprintf( fpOut, "begin_polygon\n" );
+
+ strcpy( previousV3dParam, "polygon" );
+ }
+ else // faceVerticies < 3 : should never happen !!!
+ {
+ fprintf( stderr, " Warning in *.obj file, line #%ld: face with only %d vertices detected and removed.\n", lineNr, faceVerticies );
+ }
+
+ /* alloc memory */
+ Vertex *tmpVertex = calloc ( faceVerticies, sizeof ( Vertex ) );
+ if ( tmpVertex == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for tmpVertex.\n");
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+ return EXIT_FAILURE;
+ }
+
+ Vcoord *tmpVertex2 = calloc ( faceVerticies, sizeof ( Vcoord ) ); // for surface normal calculation (when necessary)
+ if ( tmpVertex2 == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for tmpVertex2.\n");
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+ return EXIT_FAILURE;
+ }
+
+ /* face type (triangle, quad, polygon) has been determined. Now, read input line until end and write face vertices. */
+ token = strtok( lineBuffer, " " ); // first token contains "f" (face)
+ token = strtok( NULL, " " ); // second token contains first v/vt/vn reference numbers
+ long tabCounter;
+ double first_i, first_j, first_k;
+ bool faceNormal = true;
+
+ if ( userModifier->invertFaces == 0 ) // default case: faces MUST be re-oriented
+ {
+ tabCounter = faceVerticies;
+
+ while( token != NULL ) {
+ tabCounter--;
+
+ if ( !( sscanf( token, " */%ld/*", &tmpVertex[tabCounter].vt ) ) ) // 'v' / no 'vt' / 'vn' case : sscanf will stop reading after 'v'
+ sscanf( token, " %ld//%ld", &tmpVertex[tabCounter].v, &tmpVertex[tabCounter].vn );
+ sscanf( token, " %ld/%ld/%ld", &tmpVertex[tabCounter].v, &tmpVertex[tabCounter].vt, &tmpVertex[tabCounter].vn );
+
+ token = strtok( NULL, " " ); // next token
+ }
+
+ for ( int cnt0 = 0; cnt0 < faceVerticies; cnt0++ )
+ {
+ tmpVertex2[ cnt0 ].x = v[ cnt0 + 1 ].x; // + 1 because first *.obj vertex number is always equal to 1
+ tmpVertex2[ cnt0 ].y = v[ cnt0 + 1 ].y;
+ tmpVertex2[ cnt0 ].z = v[ cnt0 + 1 ].z;
+ }
+ }
+ else // user asked to re-orient faces, i.e. they MUST NOT be re-oriented!
+ {
+ tabCounter = 0;
+ while( token != NULL ) {
+ if ( !( sscanf( token, " */%ld/*", &tmpVertex[tabCounter].vt ) ) ) // 'v' / no 'vt' / 'vn' case : sscanf will stop reading after 'v'
+ sscanf( token, " %ld//%ld", &tmpVertex[tabCounter].v, &tmpVertex[tabCounter].vn ); // read v and vn
+ sscanf( token, " %ld/%ld/%ld", &tmpVertex[tabCounter].v, &tmpVertex[tabCounter].vt, &tmpVertex[tabCounter].vn ); // read v, vt, and vn if any
+
+ token = strtok( NULL, " " ); // next token
+
+ tabCounter++;
+ }
+ for ( int cnt0 = 0; cnt0 < faceVerticies; cnt0++ )
+ {
+ tmpVertex2[ cnt0 ].x = v[ faceVerticies - cnt0 ].x; // last [ faceVerticies - cnt0 ] will be equal to 1, which is first *.obj vertex number
+ tmpVertex2[ cnt0 ].y = v[ faceVerticies - cnt0 ].y;
+ tmpVertex2[ cnt0 ].z = v[ faceVerticies - cnt0 ].z;
+ }
+ }
+
+ /* check faces has identical vertices */
+ bool faceHasIdenticalVertexIndicies = false;
+ for ( int cnt0 = 0; cnt0 < faceVerticies - 1; cnt0++ )
+ {
+ for ( int cnt1 = cnt0 + 1; cnt1 < faceVerticies; cnt1++ )
+ {
+ if ( tmpVertex[cnt1].v == tmpVertex[cnt0].v )
+ faceHasIdenticalVertexIndicies = true;
+ if ( faceHasIdenticalVertexIndicies == true )
+ break;
+ }
+ if ( faceHasIdenticalVertexIndicies == true )
+ break;
+ }
+
+ if ( faceHasIdenticalVertexIndicies == true )
+ {
+ fprintf( stderr, " Warning in *.obj file, line #%ld: identical vertex indicies found in the same face. Face ignored.\n", lineNr );
+ lineNr++;
+ continue; // go to *.obj file next line
+ }
+
+ // if vertices don't have normals, then calculate face normal and write it
+ if ( tmpVertex[1].vn == 0 ) // if first vertex normal is not defined
+ {
+ faceNormal = true;
+ Vnormal vnTemp;
+ if ( !calculateSurfaceNormal( tmpVertex2, (int) faceVerticies, &vnTemp ) )
+ fprintf( fpOut, " normal %lf %lf %lf\n", vnTemp.i, vnTemp.j, vnTemp.k );
+ else
+ ; // DIV BY ZERO ERROR when calculating normal
+ }
+ else
+ { // check if normal is a face normal (i.e. not a vertex one). If all vertices on a face have the same normal, then normal is a face normal.
+ vnNum = tmpVertex[0].vn;
+ if ( vnNum > 0 )
+ {
+ first_i = vn[vnNum].i;
+ first_j = vn[vnNum].j;
+ first_k = vn[vnNum].k;
+ }
+ else // vnNum < 0
+ {
+ first_i = vn[normals + vnNum].i;
+ first_j = vn[normals + vnNum].j;
+ first_k = vn[normals + vnNum].k;
+ }
+
+ for ( tabCounter = 1; tabCounter < faceVerticies; tabCounter++ )
+ {
+ vnNum = tmpVertex[tabCounter].vn;
+ if ( vnNum > 0 )
+ {
+ if ( ( vn[vnNum].i != first_i ) || ( vn[vnNum].j != first_j ) || ( vn[vnNum].k != first_k ) )
+ {
+ faceNormal = false;
+ break;
+ }
+ }
+ else // vnNum is negative (a *.obj vertex number can't be null)
+ {
+ if ( ( vn[normals + vnNum].i != first_i ) || ( vn[normals + vnNum].j != first_j ) || ( vn[normals + vnNum].k != first_k ) )
+ {
+ faceNormal = false;
+ break;
+ }
+ }
+ }
+ if ( faceNormal == true )
+ {
+ fprintf( fpOut, " normal %f %f %f\n", first_i, first_j, first_k );
+ }
+ }
+ free( tmpVertex2 );
+
+ for ( tabCounter = 0; tabCounter < faceVerticies; tabCounter++ )
+ {
+ vnNum = tmpVertex[tabCounter].vn;
+ if ( faceNormal == false )
+ {
+ // write vertex normal only if vnNum is not equal to 0
+ if ( vnNum == 0 )
+ ;
+ else if ( vnNum > 0 )
+ fprintf( fpOut, " normal %f %f %f\n", vn[vnNum].i, vn[vnNum].j, vn[vnNum].k );
+ else // vnNum < 0
+ fprintf( fpOut, " normal %f %f %f\n", vn[normals + vnNum].i, vn[normals + vnNum].j, vn[normals + vnNum].k );
+ }
+
+ vtNum = tmpVertex[tabCounter].vt;
+ // write texture coord only if vtNum is not equal to 0 and texture is on.
+ // "textureOn" test is not mandatory because texcoord statement is automatically ignored by v3d if texture is off,
+ // but v3d file will be smaller if we test it...
+ if ( vtNum == 0 || textureOn == false )
+ ;
+ else if ( vtNum > 0 )
+ fprintf( fpOut, " texcoord %f %f\n", vt[vtNum].u, vt[vtNum].v );
+ else // vtNum < 0
+ fprintf( fpOut, " texcoord %f %f\n", vt[textCoords + vtNum].u, vt[textCoords + vtNum].v );
+
+ vNum = tmpVertex[tabCounter].v;
+ // write vertex coord
+ if ( vNum > 0 )
+ fprintf( fpOut, " %f %f %f\n", v[vNum].x, v[vNum].y, v[vNum].z );
+ else // vNum < 0
+ fprintf( fpOut, " %f %f %f\n", v[vertices + vNum].x, v[vertices + vNum].y, v[vertices + vNum].z );
+ }
+
+ free( tmpVertex );
+ }
+ else if ( !strcmp( objTag, "mtllib" ) ) // material file
+ {
+ /*
+ * According to *.obj specifications, mtllib can be followed by multiple *.mtl file names
+ */
+ char mtlSourceTmp[ MAX_LENGTH + 1 ];
+ char mtlSource[ 2 * MAX_LENGTH + 2 ];
+
+ char *path = NULL;
+ char *textureBaseName = NULL;
+ char *modelBaseName = NULL;
+ char *extension = NULL;
+ scanFileName( source, &path, &modelBaseName, &extension ); // scan source model name to extract path
+
+ /* remove 'mtllib' then copy (all) file(s) name(s) to tempBuffer */
+ char *ret;
+ int start = 0, end = 0, cnt = 0, originPos = 0, copyPos = 0;
+ char tempBuffer[MAX_LENGTH + 1];
+ strcpy( tempBuffer, lineBuffer );
+ ret = strstr( tempBuffer, "mtllib" );
+ start = ret + strlen( "mtllib" ) - tempBuffer;
+ while ( lineBuffer[ start ] == ' ' || lineBuffer[ start ] == '\t' )
+ start++;
+ copyPos = 0;
+ while ( lineBuffer[ end ] == ' ' || lineBuffer[ end ] == '\t' )
+ end++;
+ for ( originPos = start; originPos < strlen ( lineBuffer ); originPos++ )
+ tempBuffer[ copyPos++ ] = lineBuffer[ originPos ];
+ tempBuffer[ copyPos ] = '\0';
+
+ int mtllibFileStart[MAX_MTLLIB_FILENAMES], mtllibFileNr = 0;
+ mtllibFileStart[mtllibFileNr++] = 0;
+
+ for ( cnt = 0; cnt < strlen( tempBuffer ); cnt++ )
+ {
+ if ( ( ret = strstr( ( tempBuffer + cnt), ".mtl" ) ) )
+ {
+ mtllibFileStart[ mtllibFileNr ] = ret - tempBuffer + strlen( ".mtl" );
+ cnt = mtllibFileStart[ mtllibFileNr ];
+ mtllibFileNr++;
+ }
+ if ( mtllibFileNr > MAX_MTLLIB_FILENAMES - 1 )
+ {
+ fprintf(stderr, "Line #%ld: more than %d file names found in mtllib statement. Exiting.\n", lineNr, MAX_MTLLIB_FILENAMES );
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+ return EXIT_FAILURE;
+ }
+ }
+
+ for ( cnt = 0; cnt < mtllibFileNr - 1; cnt ++ )
+ {
+ while ( tempBuffer[ mtllibFileStart[ cnt ] ] == ' ' || tempBuffer[ mtllibFileStart[ cnt ] ] == '\t' )
+ mtllibFileStart[ cnt ] += 1;
+ copyPos = 0;
+ for ( int cnt1 = mtllibFileStart[ cnt ]; cnt1 < mtllibFileStart[ cnt + 1 ]; cnt1++ )
+ mtlSourceTmp[ copyPos++ ] = tempBuffer[ cnt1 ];
+ mtlSourceTmp[ copyPos ] = '\0';
+
+ sprintf( mtlSource, "%s%s", path, mtlSourceTmp ); // put *.obj file path before mtl file name
+
+ int result = readMtlFile( mtlSource, &mat, &materials );
+ if ( result == -1 )
+ {
+ fprintf(stderr, "Line #%ld: file '%s' not found. Exiting.\n", lineNr, mtlSource );
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+ return EXIT_FAILURE;
+ }
+ else if ( result == -2 )
+ {
+ fprintf(stderr, "Line #%ld: something wrong has happen in %s file. Exiting.\n", lineNr, mtlSource );
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+ return EXIT_FAILURE;
+ }
+ }
+
+ fprintf( fpOut, "begin_header\n" );
+ fprintf( fpOut, "creator v3dconverter\n" );
+
+ if ( materials > 0 )
+ {
+ if ( path != NULL )
+ {
+ free( path );
+ path = NULL;
+ }
+ if ( textureBaseName != NULL )
+ {
+ free( textureBaseName );
+ textureBaseName = NULL;
+ }
+ if ( modelBaseName != NULL )
+ {
+ free( modelBaseName );
+ modelBaseName = NULL;
+ }
+ if (extension != NULL)
+ {
+ free( extension );
+ extension = NULL;
+ }
+ for ( int cnt = 0; cnt < materials; cnt++ )
+ {
+ if ( mat[ cnt ].texName != NULL ) // if material is a texture
+ {
+ scanFileName( dest, &path, &modelBaseName, &extension ); // scan destination model name to extract model base name
+ for ( int cnt = 0; modelBaseName[ cnt ] != '\0'; cnt++ ) // replace all spaces by underscores before because v3d format don't like spaces in pathes / names
+ {
+ if ( modelBaseName[ cnt ] == ' ')
+ modelBaseName[ cnt ] = '_';
+ }
+
+ // Use model base name as texture sub-directory name
+ fprintf( fpOut, "texture_load %s textures/%s/", mat[ cnt ].name, modelBaseName );
+
+ char *fullFileName = strdup( mat[ cnt ].texName );
+
+ scanFileName( fullFileName, &path, &textureBaseName, &extension ); // scan material texName to extract texture name
+ if ( fullFileName != NULL )
+ {
+ free( fullFileName );
+ fullFileName = NULL;
+ }
+
+ // Use texture base name as *.tex texture file name
+ fprintf( fpOut, "%s.tex 0.8\n", textureBaseName );
+ }
+ }
+ if ( path != NULL )
+ {
+ free( path );
+ path = NULL;
+ }
+ if ( textureBaseName != NULL )
+ {
+ free( textureBaseName );
+ textureBaseName = NULL;
+ }
+ if ( modelBaseName != NULL )
+ {
+ free( modelBaseName );
+ modelBaseName = NULL;
+ }
+ if (extension != NULL)
+ {
+ free( extension );
+ extension = NULL;
+ }
+ }
+
+ fprintf( fpOut, "end_header\n\n" );
+
+ /* Considering that 'mtllib' is the first statement of an *.obj file, once all texture_load lines are written,
+ * we can write some generic statements.
+ */
+ fprintf( fpOut, "# Generic statements for making this object visible:\n" );
+ //fprintf( fpOut, "type 1\n" ); // deprecated
+ fprintf( fpOut, "range 2000\n\n" );
+
+ fprintf( fpOut, "# Generic statement to add object smoothing. Generally, buildings do not need to be smoothed:\n" );
+ fprintf( fpOut, "shade_model_smooth\n\n" );
+ }
+ else if ( !strcmp( objTag, "usemtl" ) )
+ {
+ if ( previousV3dParam[0] != '\0' )
+ {
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ strcpy( previousV3dParam, "" );
+ }
+
+ char mtlName[MAX_LENGTH + 1];
+ int cnt0 = 0, cnt1 = 0;
+ while ( lineBuffer[cnt0++] != ' ' ); // discard "usemtl "
+ while ( cnt0 < strlen( lineBuffer ) ) // copy material name
+ {
+ if ( lineBuffer[cnt0] == ' ' )
+ lineBuffer[cnt0] = '_'; // replace ' ' per '_'
+ if ( lineBuffer[ cnt0 ] == '\n' || lineBuffer[ cnt0 ] == '\r' )
+ break;
+ mtlName[cnt1++] = lineBuffer[cnt0++];
+ }
+ mtlName[cnt1] = '\0'; // close string
+
+ int mtlNr = 0;
+ while ( mtlNr < materials ) // look for material number
+ {
+ if ( !strcmp( mat[ mtlNr ].name, mtlName ) ) // material found
+ break;
+ mtlNr++;
+ }
+
+ if ( mtlNr == materials ) // material not found
+ {
+ fprintf(stderr, " Warning in *.obj file, line #%ld: material %s called by *.obj file not found in *.mtl file.\n", lineNr, mtlName );
+ }
+ else
+ {
+ double new_r = 0.2, new_g = 0.2, new_b = 0.2, new_a = 1.0, new_amb = 1.0, new_dif = 1.0, new_spe = 0.1, new_shi = 0.0, new_emi = 0.1; // init and default values
+ int counter = 0;
+ double KTotal = 0;
+
+ // *** Remember that readMtlFile() initialized unread values to -1.0 ***
+
+ /* Color */
+ if ( mat[ mtlNr ].KdR >= 0 )
+ new_r = mat[ mtlNr ].KdR; // use mtl diffuse red color as v3d base color
+ else
+ ; // use default new_r value
+ if ( mat[ mtlNr ].KdG >= 0 )
+ new_g = mat[ mtlNr ].KdG; // use mtl diffuse green color as v3d base color
+ else
+ ; // use default new_g value
+ if ( mat[ mtlNr ].KdB >= 0 )
+ new_b = mat[ mtlNr ].KdB; // use mtl diffuse blue color as v3d base color
+ else
+ ; // use default new_b value
+
+ /* Transparency */
+ if ( mat[ mtlNr ].d >= 0 )
+ new_a = mat[ mtlNr ].d; // transparency ( mtl "dissolve" coefficient )
+ else
+ ; // use default new_a value
+
+ /* Ambient coefficient */
+ new_amb = 1.0; // FIXME force ambient coefficient to 1.0 (as Blender seems to do).
+
+ /* Diffuse coefficient */
+ new_dif = 1.0; // mtl Kd diffuse color was used as v3d base color, then Kd(r,g,b) / new_(r,g,b) = 1.0
+
+ /* Specularity coefficent. FIXME Is this calculation right ??? */
+ counter = 0;
+ KTotal = 0.0;
+ if ( mat[ mtlNr ].KsR >= 0 )
+ {
+ KTotal = mat[ mtlNr ].KsR;
+ counter++;
+ }
+ if ( mat[ mtlNr ].KsG >= 0 )
+ {
+ KTotal += mat[ mtlNr ].KsG;
+ counter++;
+ }
+ if ( mat[ mtlNr ].KsB >= 0 )
+ {
+ KTotal += mat[ mtlNr ].KsB;
+ counter++;
+ }
+ if ( counter != 0 )
+ new_spe = KTotal / counter;
+ else
+ ; // use default new_spe value
+
+ /* Shininess coefficient */
+ if ( mat[ mtlNr ].Ns >= 0 )
+ new_shi = mat[ mtlNr ].Ns / 1000; // Ns range is 0 to 1000
+ else
+ ; // use default value
+
+ /* Emission coefficient. FIXME Is this calculation right ??? */
+ counter = 0;
+ KTotal = 0.0;
+ if ( mat[ mtlNr ].KeR >= 0 )
+ {
+ KTotal = mat[ mtlNr ].KeR;
+ counter++;
+ }
+ if ( mat[ mtlNr ].KeG >= 0 )
+ {
+ KTotal += mat[ mtlNr ].KeG;
+ counter++;
+ }
+ if ( mat[ mtlNr ].KeB >= 0 )
+ {
+ KTotal += mat[ mtlNr ].KeB;
+ counter++;
+ }
+ if ( counter != 0 )
+ new_emi = KTotal / counter;
+ else
+ ; // use default new_emi value
+
+ if ( mat[ mtlNr ].texName != NULL ) // if material is a texture, force color to white
+ {
+ new_r = 1.0;
+ new_g = 1.0;
+ new_b = 1.0;
+ }
+
+ /* check if current color is same as new one */
+ if ( currentColor.r == new_r
+ && currentColor.g == new_g
+ && currentColor.b == new_b
+ && currentColor.a == new_a
+ && currentColor.amb == new_amb
+ && currentColor.dif == new_dif
+ && currentColor.spe == new_spe
+ && currentColor.shi == new_shi
+ && currentColor.emi == new_emi
+ )
+ {
+ ; // current color is same as the new one, no need to repeat it
+ }
+ else
+ {
+ // set current color to new value, then print it
+ currentColor.r = new_r;
+ currentColor.g = new_g;
+ currentColor.b = new_b;
+ currentColor.a = new_a;
+ currentColor.amb = new_amb;
+ currentColor.dif = new_dif;
+ currentColor.spe = new_spe;
+ currentColor.shi = new_shi;
+ currentColor.emi = new_emi;
+
+ if ( mat[ mtlNr ].texName == NULL ) // if material has no texture ...
+ {
+ if ( textureOn == true ) // ... and if previous material was a textured one
+ {
+ fprintf( fpOut, "texture_off\n" );
+ textureOn = false;
+ }
+ }
+
+ fprintf( fpOut, "\n#OBJFILE material: %s\n", mtlName );
+ fprintf( fpOut, "color %.6f %.6f %.6f", currentColor.r, currentColor.g, currentColor.b ); // v3d base color
+ fprintf( fpOut, " %.6f", currentColor.a ); // transparency
+ fprintf( fpOut, " %.6f %.6f %.6f %.6f %.6f\n", currentColor.amb, currentColor.dif, currentColor.spe, currentColor.shi, currentColor.emi ); // coefficients
+ }
+
+ if ( mat[ mtlNr ].texName != NULL ) // if material is a texture, print it
+ {
+ fprintf( fpOut, "texture_select %s\n", mat[ mtlNr ].name );
+ textureOn = true;
+ }
+
+ fprintf( fpOut, "\n" );
+ }
+ }
+ /* ### See "o" (object) ###
+ else if ( !strcmp( objTag, "g" ) ) // group
+ {
+ // remove carriage return
+ int cnt = 0;
+ while ( lineBuffer[ cnt ] != '\n' && lineBuffer[ cnt++ ] != '\r' );
+ lineBuffer[ cnt ] = '\0';
+
+ fprintf( fpOut, "#OBJFILE group: %s\n", lineBuffer );
+ }
+ */
+ else if ( !strcmp( objTag, "s" ) ) // smoothing group
+ {
+ if ( previousV3dParam[0] != '\0' )
+ {
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ strcpy( previousV3dParam, "" );
+ }
+
+ // Don't convert obj 's' statement into v3d 'shade_model_*' because only one 'shade_model_*' per V3d object.
+ /*
+ int foo = -1;
+ if ( sscanf( lineBuffer, " s %d", &foo ) ) // if foo is an int
+ {
+ if ( foo > 0 ) fprintf( fpOut, "shade_model_smooth\n" ); // smooth on
+ }
+ else
+ {
+ fprintf( fpOut, "shade_model_flat\n" ); // smooth off
+ }
+ */
+ }
+ else if ( !strcmp( objTag, "o" ) || !strcmp( objTag, "g" ) ) // new object or group
+ {
+ if ( previousV3dParam[0] != '\0' )
+ {
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ strcpy( previousV3dParam, "" );
+ }
+
+ if ( textureOn )
+ {
+ fprintf( fpOut, "texture_off\n" );
+ textureOn = false;
+ }
+
+ /* check if object name is known as a v3d model type */
+ char objectName[MAX_LENGTH + 1];
+ int cnt = 0;
+ sscanf( lineBuffer, " %*s %[^\n]", objectName ); // read object name
+
+ while ( cnt < V3DMODELTYPES )
+ {
+ if ( !strcmp( objectName, v3dModelType[ cnt ] ) ) // if object name found in v3dModelType[] array
+ break;
+ cnt++;
+ }
+
+ if ( cnt != V3DMODELTYPES ) // if object name is a v3d model type
+ {
+ if ( currentV3dModelType[0] != '\0' )
+ {
+ fprintf( fpOut, "end_model %s\n", currentV3dModelType );
+ }
+
+ fprintf( fpOut, "begin_model %s\n", objectName );
+ strcpy( currentV3dModelType, objectName );
+ }
+ else
+ {
+ for ( int cnt = 0; objectName[ cnt ] != '\0'; cnt++ ) // replace all spaces by underscores
+ {
+ if ( objectName[ cnt ] == ' ')
+ objectName[ cnt ] = '_';
+ }
+ if ( !strcmp( objTag, "o" ) )
+ fprintf( fpOut, "#OBJFILE object: %s\n", objectName );
+ else
+ fprintf( fpOut, "#OBJFILE group: %s\n", objectName );
+
+ if ( firstObject )
+ {
+ strcpy( currentV3dModelType, "standard" );
+ fprintf( fpOut, "begin_model standard\n" );
+ firstObject = false;
+ }
+ }
+ }
+ else if ( objTag[0] == '#' ) // comment
+ {
+ // remove carriage return
+ int cnt = 0;
+ while ( lineBuffer[ cnt ] != '\n' && lineBuffer[ cnt++ ] != '\r' );
+ lineBuffer[ cnt ] = '\0';
+
+ fprintf( fpOut, "#OBJFILE comment: %s\n", lineBuffer );
+ }
+ else if ( !strcmp( objTag, "curv" ) || !strcmp( objTag, "curv2" ) || !strcmp( objTag, "surf" ) )
+ {
+ fprintf( stderr, " Warning in *.obj file, line #%ld: free-form geometry conversion not supported.\n", lineNr );
+ }
+ else
+ {
+ if ( previousV3dParam[0] != '\0' )
+ {
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (if any)
+ strcpy( previousV3dParam, "" );
+ }
+
+ fprintf( stderr, " Warning in *.obj file, line #%ld: unknown tag \"%s\".\n", lineNr, objTag );
+ }
+
+ lineNr++;
+
+ } // end of main file read/write loop
+
+ if ( previousV3dParam[0] != '\0' )
+ fprintf( fpOut, "end_%s\n", previousV3dParam ); // write end of previous parameter type (triangle, quad, line, ...), if any.
+
+ if ( currentV3dModelType[0] != '\0' )
+ fprintf( fpOut, "end_model %s\n", currentV3dModelType ); // write end of previous model type (if any)
+
+ // print some usefull information at start of v3d output file and on console
+ FILE* tmp = tmpfile();
+ int c;
+ rewind ( fpOut );
+ while ( ( c = fgetc( fpOut ) ) != EOF )
+ fputc( c, tmp );
+ rewind (fpOut);
+ fprintf( fpOut, "# Object X, Y and Z dimensions [m]: %.3lf %.3lf %.3lf\n", Xmax - Xmin, Ymax - Ymin, Zmax - Zmin );
+ fprintf( stdout, "Object X, Y and Z dimensions [m]: %.3lf %.3lf %.3lf\n", Xmax - Xmin, Ymax - Ymin, Zmax - Zmin );
+ double volume = (Xmax - Xmin) * (Ymax - Ymin) * (Zmax - Zmin);
+ fprintf( fpOut, "# Rectangular Xmin Xmax Ymin Ymax Zmin Zmax bounds [m]: %.3lf %.3lf %.3lf %.3lf %.3lf %.3lf (volume: %.1lf m3)\n", Xmin, Xmax, Ymin, Ymax, Zmin, Zmax, volume );
+ fprintf( stdout, "Rectangular Xmin Xmax Ymin Ymax Zmin Zmax bounds [m]: %.3lf %.3lf %.3lf %.3lf %.3lf %.3lf (volume: %.1lf m3)\n", Xmin, Xmax, Ymin, Ymax, Zmin, Zmax, volume );
+ volume = M_PI * maxXYradius * maxXYradius * (Zmax - Zmin);
+ fprintf( fpOut, "# Cylendrical radius Zmin Zmax bounds [m]: %.3lf %.3lf %.3lf (volume: %.1lf m3)\n", maxXYradius, Zmin, Zmax, volume );
+ fprintf( stdout, "Cylendrical radius Zmin Zmax bounds [m]: %.3lf %.3lf %.3lf (volume: %.1lf m3)\n", maxXYradius, Zmin, Zmax, volume );
+ volume = 4/3 * M_PI * pow(maxXYZradius, 3.0);
+ fprintf( fpOut, "# Spherical bound radius [m]: %.3lf (volume: %.1lf m3)\n\n", maxXYZradius, volume );
+ fprintf( stdout, "Spherical bound radius [m]: %.3lf (volume: %.1lf m3)\n", maxXYZradius, volume );
+ rewind ( tmp );
+ while ( ( c = fgetc( tmp ) ) != EOF )
+ fputc( c, fpOut );
+ fclose( tmp );
+
+ //fprintf( stderr, "Number of\n vertices: %ld\n normals: %ld\n textCoords: %ld\n faces: %ld\n", vertices, normals, textCoords, faces );
+
+ FREE_AND_CLOSE_ALL_OBJTOV3D
+
+ return EXIT_SUCCESS;
+}
+
+
+int readMtlFile( char *mtllibSource, Material **mat, int *mtl ) { // read a *.mtl file and store materials in a mat[] array
+ FILE *fpIn = NULL, *fopen();
+ char lineBuffer[MAX_LENGTH + 1], fileName[MAX_LENGTH + 1] = "", mtlLibTag[MAX_LENGTH + 1];
+ long mtlLibLineNr = 1;
+ Material *tmpMat = NULL, *localMat = NULL;
+
+ if ( ( fpIn = fopen( mtllibSource, "r" ) ) == NULL)
+ {
+ return -1;
+ }
+
+ tmpMat = realloc( *mat, (*mtl + 1) * sizeof( Material ) );
+ if (tmpMat == NULL)
+ {
+ fprintf( stderr, "File '%s' , line #%ld: Can't allocate memory for tmpMat.\n", mtllibSource, mtlLibLineNr );
+ return -2;
+ }
+ localMat = tmpMat;
+
+ (*mtl)--; // because mtl will be incremented before first use
+
+ // Read each line of .mtl file
+ while ( fgets( lineBuffer, MAX_LENGTH, fpIn ) ) {
+ if ( lineBuffer[ 0 ] == '\n' || lineBuffer[ 0 ] == '\r' || ( sscanf( lineBuffer, " %s", mtlLibTag ) ) == 0 ) // blank line or objTag == ""
+ {
+ mtlLibLineNr++;
+ continue;
+ }
+
+ if ( !strcmp( mtlLibTag, "newmtl" ) ) // new material
+ {
+ (*mtl)++;
+
+ tmpMat = realloc( localMat, ( *mtl + 1 ) * sizeof( Material ) );
+ if (tmpMat == NULL)
+ {
+ *mat = localMat;
+ fprintf( stderr, "File '%s' , line #%ld: Can't allocate memory for more Material.\n", mtllibSource, mtlLibLineNr );
+ return -1;
+ }
+ localMat = tmpMat;
+
+ int cnt0 = 0, cnt1 = 0;
+ while ( lineBuffer[cnt0++] != ' ' ); // discard up to "newmtl "
+ while ( cnt0 < strlen( lineBuffer ) ) // copy material name
+ {
+ if ( lineBuffer[cnt0] == ' ' )
+ lineBuffer[cnt0] = '_'; // replace ' ' per '_'
+ if ( lineBuffer[ cnt0 ] == '\n' || lineBuffer[ cnt0 ] == '\r' )
+ break;
+ fileName[cnt1++] = lineBuffer[cnt0++];
+ }
+ fileName[cnt1] = '\0'; // close string
+ localMat[ *mtl ].name = strdup( fileName );
+
+ // init
+ localMat[ *mtl ].KaR = -1.0; localMat[ *mtl ].KaG = -1.0; localMat[ *mtl ].KaB = -1.0;
+ localMat[ *mtl ].KdR = -1.0; localMat[ *mtl ].KdG = -1.0; localMat[ *mtl ].KdB = -1.0;
+ localMat[ *mtl ].KeR = -1.0; localMat[ *mtl ].KeG = -1.0; localMat[ *mtl ].KeB = -1.0;
+ localMat[ *mtl ].KsR = -1.0; localMat[ *mtl ].KsG = -1.0; localMat[ *mtl ].KsB = -1.0;
+ localMat[ *mtl ].Ns = -1.0;
+ localMat[ *mtl ].Ni = -1.0;
+ localMat[ *mtl ].d = -1.0;
+ localMat[ *mtl ].illum = -1.0;
+ localMat[ *mtl ].texName = NULL;
+ }
+ else if ( !strcmp( mtlLibTag, "Ka" ) ) // ambient color (ambient reflectivity of material, from 0.0 to 1.0)
+ {
+ sscanf( lineBuffer, " Ka %lf %lf %lf", &localMat[ *mtl ].KaR, &localMat[ *mtl ].KaG, &localMat[ *mtl ].KaB );
+ }
+ else if ( !strcmp( mtlLibTag, "Kd" ) ) // diffuse color (diffuse reflectivity of material, from 0.0 to 1.0). This is the main object color.
+ {
+ sscanf( lineBuffer, " Kd %lf %lf %lf", &localMat[ *mtl ].KdR, &localMat[ *mtl ].KdG, &localMat[ *mtl ].KdB );
+ if ( localMat[ *mtl ].KdR == 0.0 && localMat[ *mtl ].KdG == 0.0 && localMat[ *mtl ].KdB == 0.0 )
+ {
+ fprintf( stderr, " Warning in file '%s' line #%ld: pure black (0,0,0) color found and overwritten by (1,1,1).\n", mtllibSource, mtlLibLineNr );
+ localMat[ *mtl ].KdR = 1.0 / 255;
+ localMat[ *mtl ].KdG = 1.0 / 255;
+ localMat[ *mtl ].KdB = 1.0 / 255;
+ }
+ }
+ else if ( !strcmp( mtlLibTag, "Ke" ) ) // emissive color (from 0.0 to 1.0).
+ {
+ sscanf( lineBuffer, " Ke %lf %lf %lf", &localMat[ *mtl ].KeR, &localMat[ *mtl ].KeG, &localMat[ *mtl ].KeB );
+ }
+ else if ( !strcmp( mtlLibTag, "Ks" ) ) // specular color (specular reflectivity of material, from 0.0 to 1.0)
+ {
+ sscanf( lineBuffer, " Ks %lf %lf %lf", &localMat[ *mtl ].KsR, &localMat[ *mtl ].KsG, &localMat[ *mtl ].KsB );
+ }
+ else if ( !strcmp( mtlLibTag, "Ns" ) ) // specular exponent (from 0 to 1000)
+ {
+ sscanf( lineBuffer, " Ns %lf", &localMat[ *mtl ].Ns );
+ }
+ else if ( !strcmp( mtlLibTag, "Ni" ) ) // optical density (index of refraction, from 0.001 to 10)
+ {
+ sscanf( lineBuffer, " Ni %lf", &localMat[ *mtl ].Ni );
+ }
+ else if ( !strcmp( mtlLibTag, "Tr" ) ) // transparency (from 0.0 to 1.0). Transparency = 1.0 - d
+ {
+ // Tr = 0.0 = fully opaque, Tr = 1.0 = fully transparent (opposite as v3d format)
+ double tempTr = 0.0;
+ sscanf( lineBuffer, " Tr %lf", &tempTr );
+ localMat[ *mtl ].d = 1.0 - tempTr; // 'Tr' to 'd' conversion
+ if ( localMat[ *mtl ].d == 0.0 )
+ fprintf( stderr, " Warning in file '%s' line #%ld: 'Tr 1.0' makes '%s' invisible.\n", mtllibSource, mtlLibLineNr, localMat[ *mtl ].name );
+ }
+ else if ( !strcmp( mtlLibTag, "d" ) ) // dissolve (from 0.0 to 1.0). Dissolve = 1.0 - Tr
+ {
+ // d = 0.0 = fully dissolved (fully transparent), d = 1.0 = fully opaque (as in v3d format)
+ sscanf( lineBuffer, " d %lf", &localMat[ *mtl ].d );
+ if ( localMat[ *mtl ].d == 0.0 )
+ fprintf( stderr, " Warning in file '%s' line #%ld: 'd 0.0' makes '%s' invisible.\n", mtllibSource, mtlLibLineNr, localMat[ *mtl ].name );
+ }
+ else if ( !strcmp( mtlLibTag, "illum" ) )
+ {
+ sscanf( lineBuffer, " illum %d", &localMat[ *mtl ].illum );
+ }
+ else if ( !strcmp( mtlLibTag, "map_Kd" ) ) // texture
+ {
+ int cnt = 0;
+ int bufLength = strlen( lineBuffer );
+
+ /* Extract file name from buffer string, then replace file name spaces (if any) by underscores */
+ while ( ( cnt < bufLength ) && ( isspace( lineBuffer[ cnt ] ) ) ) // look for next non-blank character
+ cnt++;
+ cnt += strlen( "map_Kd" );
+ for ( ; lineBuffer[ cnt ] != '\0'; cnt++ ) // replace all spaces by underscores because v3d format don't like spaces in pathes / names
+ {
+ if ( isblank( lineBuffer[ cnt ] ) )
+ lineBuffer[ cnt ] = '_';
+ }
+ fileName[0] = '\0';
+ sscanf( lineBuffer, " map_Kd_%s", fileName );
+
+ if ( fileName[0] == '\0' )
+ {
+ fprintf( stderr, " Warning in file '%s' line #%ld: map_Kd has no file name.\n", mtllibSource, mtlLibLineNr );
+ }
+ else if ( fileName[0] != '-' )
+ {
+ localMat[ *mtl ].texName = strdup( fileName );
+ }
+ else
+ {
+ ; // If first token after map_Kd begins whith a '-', it's an option. See http://www.paulbourke.net/dataformats/mtl/
+ fprintf( stderr, " Warning in file '%s' line #%ld: option '%s' not supported.\n", mtllibSource, mtlLibLineNr, mtlLibTag );
+ }
+ }
+ else if ( mtlLibTag[0] == '#' ) { // it's a comment
+ ;
+ }
+ else if ( !strcmp( mtlLibTag, "Tf" ) ) // transmission filter (for transparent materials, from 0.0 to 1.0)
+ {
+ ; // See http://www.paulbourke.net/dataformats/mtl/
+ fprintf( stderr, " Warning in file '%s' line #%ld: 'Tf' (Transmission Filter color) not supported.\n", mtllibSource, mtlLibLineNr );
+ }
+ else if ( !strcmp( mtlLibTag, "map_Ka" ) ) // texture ambient reflectivity
+ {
+ ; // See http://www.paulbourke.net/dataformats/mtl/
+ fprintf( stderr, " Warning in file '%s' line #%ld: 'map_Ka' (texture Ambient reflectivity coefficient) not supported.\n", mtllibSource, mtlLibLineNr );
+ }
+ else if ( !strcmp( mtlLibTag, "map_Ks" ) ) // texture specular reflectivity
+ {
+ ; // See http://www.paulbourke.net/dataformats/mtl/
+ fprintf( stderr, " Warning in file '%s' line #%ld: 'map_Ks' (texture Specular reflectivity coefficient) not supported.\n", mtllibSource, mtlLibLineNr );
+ }
+ else if ( !strcmp( mtlLibTag, "bump" ) ) // bump texture
+ {
+ ; // See http://www.paulbourke.net/dataformats/mtl/
+ fprintf( stderr, " Warning in file '%s' line #%ld: 'bump' (surface normal modification texture) not supported.\n", mtllibSource, mtlLibLineNr );
+ }
+ else if ( !strcmp( mtlLibTag, "map_d" ) ) // scalar texture file or scalar procedural texture file
+ {
+ ; // See http://www.paulbourke.net/dataformats/mtl/
+ fprintf( stderr, " Warning in file '%s' line #%ld: 'map_d' (scalar [procedural] texture file) not supported.\n", mtllibSource, mtlLibLineNr );
+ }
+ else if ( !strcmp( mtlLibTag, "refl" ) ) // reflection map
+ {
+ ; // See http://www.paulbourke.net/dataformats/mtl/
+ fprintf( stderr, " Warning in file '%s' line #%ld: 'refl' (reflection map) not supported.\n", mtllibSource, mtlLibLineNr );
+ }
+ else {
+ fprintf( stderr, " Warning in file '%s' line #%ld: unknown tag '%s'.\n", mtllibSource, mtlLibLineNr, mtlLibTag );
+ }
+
+ mtlLibLineNr++; // line counter
+
+ *mat = localMat;
+ }
+ fclose( fpIn );
+
+ (*mtl)++; // because mtl was decremented before entering main reading loop
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/v3dconverter/src/rotate.c b/tools/v3dconverter/src/rotate.c
new file mode 100644
index 0000000..8b155c5
--- /dev/null
+++ b/tools/v3dconverter/src/rotate.c
@@ -0,0 +1,108 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+
+#include
+#include
+/*
+void RotateX ( double *px, double *py, double *pz, double *pw, double angle ) // Right Handed
+{
+ double x = 1*(*px) + 0*(*py) + 0*(*pz) + 0*(*pw);
+ double y = 0*(*px) + cos(angle*M_PI/180)*(*py) - sin(angle*M_PI/180)*(*pz) + 0*(*pw);
+ double z = 0*(*px) + sin(angle*M_PI/180)*(*py) + cos(angle*M_PI/180)*(*pz) + 0*(*pw);
+ double w = 0*(*px) + 0*(*py) + 0*(*pz) + 1*(*pw);
+ *px = x;
+ *py = y;
+ *pz = z;
+ *pw = w;
+}
+
+void RotateY ( double *px, double *py, double *pz, double *pw, double angle ) // Right Handed
+{
+ double x = cos(angle*M_PI/180)*(*px) + 0*(*py) + sin(angle*M_PI/180)*(*pz) + 0*(*pw);
+ double y = 0*(*px) + 1*(*py) + 0*(*pz) + 0*(*pw);
+ double z = -sin(angle*M_PI/180)*(*px) + 0*(*py) + cos(angle*M_PI/180)*(*pz) + 0*(*pw);
+ double w = 0*(*px) + 0*(*py) + 0*(*pz) + 1*(*pw);
+ *px = x;
+ *py = y;
+ *pz = z;
+ *pw = w;
+}
+
+void RotateZ ( double *px, double *py, double *pz, double *pw, double angle ) // Right Handed
+{
+ double x = cos(angle*M_PI/180)*(*px) - sin(angle*M_PI/180)*(*py) + 0*(*pz) + 0*(*pw);
+ double y = sin(angle*M_PI/180)*(*px) + cos(angle*M_PI/180)*(*py) - 0*(*pz) + 0*(*pw);
+ double z = 0*(*px) + 0*(*py) + 1*(*pz) + 0*(*pw);
+ double w = 0*(*px) + 0*(*py) + 0*(*pz) + 1*(*pw);
+ *px = x;
+ *py = y;
+ *pz = z;
+ *pw = w;
+}
+*/
+
+void RotateX ( double *px, double *py, double *pz, double *pw, double angle ) // Left Handed
+{
+ double x = 1*(*px) + 0*(*py) + 0*(*pz) + 0*(*pw);
+ double y = 0*(*px) + cos(angle*M_PI/180)*(*py) + sin(angle*M_PI/180)*(*pz) + 0*(*pw);
+ double z = 0*(*px) - sin(angle*M_PI/180)*(*py) + cos(angle*M_PI/180)*(*pz) + 0*(*pw);
+ double w = 0*(*px) + 0*(*py) + 0*(*pz) + 1*(*pw);
+ *px = x;
+ *py = y;
+ *pz = z;
+ *pw = w;
+}
+
+void RotateY ( double *px, double *py, double *pz, double *pw, double angle ) // Left Handed
+{
+ double x = cos(angle*M_PI/180)*(*px) + 0*(*py) - sin(angle*M_PI/180)*(*pz) + 0*(*pw);
+ double y = 0*(*px) + 1*(*py) + 0*(*pz) + 0*(*pw);
+ double z = sin(angle*M_PI/180)*(*px) + 0*(*py) + cos(angle*M_PI/180)*(*pz) + 0*(*pw);
+ double w = 0*(*px) + 0*(*py) + 0*(*pz) + 1*(*pw);
+ *px = x;
+ *py = y;
+ *pz = z;
+ *pw = w;
+}
+
+void RotateZ ( double *px, double *py, double *pz, double *pw, double angle ) // Left Handed
+{
+ double x = cos(angle*M_PI/180)*(*px) + sin(angle*M_PI/180)*(*py) + 0*(*pz) + 0*(*pw);
+ double y = -sin(angle*M_PI/180)*(*px) + cos(angle*M_PI/180)*(*py) - 0*(*pz) + 0*(*pw);
+ double z = 0*(*px) + 0*(*py) + 1*(*pz) + 0*(*pw);
+ double w = 0*(*px) + 0*(*py) + 0*(*pz) + 1*(*pw);
+ *px = x;
+ *py = y;
+ *pz = z;
+ *pw = w;
+}
+
+
+// gcc -lm -o rotate rotate.c
+/*
+void main() {
+ double x, y, z, w, angle = 90;
+ x = 10;
+ y = 20;
+ z = 30;
+ w = 1;
+
+ printf("Before: x=%lf, y=%lf, z=%lf, w=%lf, angle=%lf\n", x, y, z, w, angle );
+ RotateX ( &x, &y, &z, &w, 90);
+ printf("After: x=%lf, y=%lf, z=%lf, w=%lf, angle=%lf\n", x, y, z, w, angle );
+}
+*/
diff --git a/tools/v3dconverter/src/utils.c b/tools/v3dconverter/src/utils.c
new file mode 100644
index 0000000..f40d247
--- /dev/null
+++ b/tools/v3dconverter/src/utils.c
@@ -0,0 +1,317 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+char* extOf( char *bname) { // returns extension of base name
+ char *ext;
+ ext = strrchr (bname, '.');
+ if (ext == NULL) ext = ""; // fast method, could also use &(path[strlen(path)]).
+ return ext;
+}
+
+char * strtoupper( char * dest, const char * src ) {
+ char * result = dest;
+ while( (*dest++ = toupper( *src++ )) );
+ return result;
+}
+
+char * strtolower( char * dest, const char * src ) {
+ char * result = dest;
+ while( (*dest++ = tolower( *src++ )) );
+ return result;
+}
+
+long file_size(const char *filename)
+{
+ struct stat s;
+ if (stat(filename, &s) != 0) return 0;
+ else return s.st_size;
+}
+
+
+/* Parse a full file and fill path, baseName, and extention strings.
+ *
+ * Examples:
+ *
+ * fullFileName = "./a/such/full/name.txt" returns:
+ * path = "./a/such/full/"
+ * baseName = "name"
+ * extension = ".txt"
+ *
+ * fullFileName = "a/such/path/" returns:
+ * path = "a/such/path/"
+ * baseName = ""
+ * extension = ""
+ *
+ */
+void scanFileName( const char *fullFileName, char **path, char **baseName, char **extension )
+{
+ int cnt0 = 0, fullFileNameLength = 0, lastSlashPos = 0, fullPathLength = 0, lastDotPos = 0;
+
+ if ( *path != NULL )
+ {
+ free( *path );
+ *path = NULL;
+ }
+ if ( *baseName != NULL )
+ {
+ free( *baseName );
+ *baseName = NULL;
+ }
+ if ( *extension != NULL )
+ {
+ free( *extension );
+ *extension = NULL;
+ }
+
+ /* full file name length */
+ fullFileNameLength = strlen( fullFileName );
+
+ /* last (back)slash position */
+ for ( lastSlashPos = fullFileNameLength; lastSlashPos >= 0; lastSlashPos-- )
+ {
+ if ( ( *( fullFileName + lastSlashPos ) == '/' ) || ( *( fullFileName + lastSlashPos ) == '\\' ) )
+ break;
+ }
+
+ /* path length */
+ fullPathLength = lastSlashPos + 1;
+
+ /* last dot position */
+ for ( lastDotPos = fullFileNameLength; lastDotPos > fullPathLength; lastDotPos-- )
+ {
+ if ( *( fullFileName + lastDotPos ) == '.' )
+ break;
+ }
+
+ /* extract base name, path, and extension */
+ if ( lastSlashPos < 0 && lastDotPos == 0) // full file name has NO path and NO extension
+ {
+ /* extract baseName */
+ *baseName = malloc ( ( fullFileNameLength + 1 ) * sizeof (char) );
+ if ( *baseName == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+ for ( cnt0 = 0; cnt0 < fullFileNameLength; cnt0++ )
+ *( *baseName + cnt0 ) = *( fullFileName + cnt0 );
+ *( *baseName + cnt0 ) = '\0'; // close string
+
+ /* no path */
+ *path = malloc ( sizeof (char) );
+ if ( *path == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+ **path = '\0'; // close string
+
+ /* no extension */
+ *extension = malloc ( sizeof (char) );
+ if ( *extension == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+ **extension = '\0'; // close string
+
+ }
+ else if ( lastDotPos == fullPathLength ) // full file name has NO extension
+ {
+ /* extract baseName */
+ *baseName = malloc ( ( fullFileNameLength - fullPathLength + 1 ) * sizeof (char) );
+ if ( *baseName == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+
+ for ( cnt0 = fullPathLength; cnt0 < fullFileNameLength; cnt0++ )
+ {
+ *( *baseName - fullPathLength + cnt0 ) = *( fullFileName + cnt0 );
+ }
+ *( *baseName - fullPathLength + cnt0 ) = '\0'; // close string
+
+ /* extract path */
+ *path = malloc ( ( lastSlashPos + 2 ) * sizeof (char) );
+ if ( *path == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+ for ( cnt0 = 0; cnt0 <= lastSlashPos; cnt0++ )
+ {
+ *( *path + cnt0 ) = *( fullFileName + cnt0 );
+ }
+ *( *path + cnt0 ) = '\0'; // close string
+
+ /* no extension */
+ *extension = malloc ( sizeof (char) );
+ if ( *extension == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+ **extension = '\0'; // close string
+ }
+ else // full file name has an extension
+ {
+ /* extract baseName */
+ *baseName = malloc ( ( lastDotPos - fullPathLength + 1 ) * sizeof (char) );
+ if ( *baseName == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+ for ( cnt0 = fullPathLength; cnt0 < lastDotPos; cnt0++ )
+ *( *baseName - fullPathLength + cnt0 ) = *( fullFileName + cnt0 );
+ *( *baseName - fullPathLength + cnt0 ) = '\0'; // close string
+
+ /* extract path */
+ *path = malloc ( ( lastSlashPos + 2 ) * sizeof (char) );
+ if ( *path == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+ for ( cnt0 = 0; cnt0 <= lastSlashPos; cnt0++ )
+ {
+ *( *path + cnt0 ) = *( fullFileName + cnt0 );
+ }
+ *( *path + cnt0 ) = '\0'; // close string
+
+ /* extract extension */
+ *extension = malloc ( ( fullFileNameLength - lastDotPos + 1 ) * sizeof (char) );
+ if ( *extension == NULL )
+ {
+ fprintf(stderr, "Memory allocation error.\n");
+ exit (EXIT_FAILURE);
+ }
+ for ( cnt0 = lastDotPos; cnt0 < fullFileNameLength; cnt0++ )
+ {
+ *( *extension - lastDotPos + cnt0 ) = *( fullFileName + cnt0 );
+ }
+ *( *extension - lastDotPos + cnt0 ) = '\0'; // close string
+ }
+}
+
+
+int calculateSurfaceNormal ( Vcoord *v, int verticies, Vnormal *vn )
+{
+ // Newell's method, from: https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal pseudo-code
+ // Returns EXIT_FAILURE (0) in case of div by zero error
+
+ Vcoord vCurrent, vNext;
+ int cnt1 = 0;
+
+ // init normal
+ vn->i = 0;
+ vn->j = 0;
+ vn->k = 0;
+
+ for ( int cnt0 = 0; cnt0 < verticies; cnt0++ )
+ {
+ vCurrent.x = v[ cnt0 ].x;
+ vCurrent.y = v[ cnt0 ].y;
+ vCurrent.z = v[ cnt0 ].z;
+
+ if ( ( cnt0 + 1 ) >= verticies )
+ cnt1 = 0;
+ else
+ cnt1 = cnt0 + 1;
+
+ vNext.x = v[ cnt1 ].x;
+ vNext.y = v[ cnt1 ].y;
+ vNext.z = v[ cnt1 ].z;
+
+ vn->i += ( vCurrent.y + vNext.y ) * ( vCurrent.z - vNext.z );
+ vn->j += ( vCurrent.z + vNext.z ) * ( vCurrent.x - vNext.x );
+ vn->k += ( vCurrent.x + vNext.x ) * ( vCurrent.y - vNext.y );
+ }
+
+ // normalize
+ double vecLength = sqrt( vn->i * vn->i + vn->j * vn->j + vn->k * vn->k );
+ if ( vecLength == 0 )
+ return EXIT_FAILURE; // div by zero error
+ else
+ {
+ vn->i = vn->i / vecLength;
+ vn->j = vn->j / vecLength;
+ vn->k = vn->k / vecLength;
+ return EXIT_SUCCESS;
+ }
+}
+
+#include
+char *timeStamp() // returns a YYYYMMDDHHMMSS timestamp
+{
+ int h, min, s, day, month, year;
+ time_t now;
+ char *timeStampString;
+
+ // Current time
+ time(&now);
+ // Convert to local time
+ struct tm *local = localtime(&now);
+ h = local->tm_hour;
+ min = local->tm_min;
+ s = local->tm_sec;
+ day = local->tm_mday;
+ month = local->tm_mon + 1;
+ year = local->tm_year + 1900;
+
+ timeStampString = strdup("YYYYMMDDHHMMSS");
+ sprintf(timeStampString, "%02d%02d%d%02d%02d%02d", year, month, day, h, min, s);
+
+ return(timeStampString);
+}
+
+#include
+bool isTex ( const char *fullFileName ) // returns 'true' if file baseName contains '.tex'
+{
+ char *path = NULL, *baseName = NULL, *extension = NULL;
+
+ scanFileName( fullFileName, &path, &baseName, &extension );
+
+ if ( strstr( baseName, ".tex" ) )
+ return true;
+ else
+ return false;
+}
+
+#include
+bool isHf ( const char *fullFileName ) // returns 'true' if file baseName contains '.hf'
+{
+ char *path = NULL, *baseName = NULL, *extension = NULL;
+
+ scanFileName( fullFileName, &path, &baseName, &extension );
+
+ if ( strstr( baseName, ".hf" ) )
+ return true;
+ else
+ return false;
+}
diff --git a/tools/v3dconverter/src/v3dconverter.c b/tools/v3dconverter/src/v3dconverter.c
new file mode 100644
index 0000000..08603b3
--- /dev/null
+++ b/tools/v3dconverter/src/v3dconverter.c
@@ -0,0 +1,314 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+//
+// Compile with:
+// gcc -lm -Wall -o v3dconverter v3dconverter.c
+//
+
+
+#include
+#include
+#include
+#include
+#include
+#include // for getcwd
+
+#include "v3dconverter.h"
+#include "utils.c"
+#include "rotate.c"
+#include "v3dtoobj.c"
+#include "ac3dtov3d.c"
+#include "objtov3d.c"
+#include "v3dhftoobj.c"
+#include "objtohf.c"
+
+int main( int argc, char *argv[] )
+{
+ char * source;
+ char * destination;
+ char *scaleString;
+ char lineBuffer[MAX_LENGTH + 1], objTag[MAX_LENGTH + 1];
+ FILE *fp;
+
+ struct UserModifier userModifier;
+ userModifier.rx = 0;
+ userModifier.ry = 0;
+ userModifier.rz = 0;
+ userModifier.tx = 0;
+ userModifier.ty = 0;
+ userModifier.tz = 0;
+ userModifier.scale = 1.0;
+ userModifier.invertFaces = false;
+
+ if ( argc == 1 )
+ {
+ fprintf( stdout, "Try \"%s -h\" for help.\n", argv[0] );
+ return 0;
+ }
+ else if ( ( argc == 2 && !strcmp( argv[1], "-h" ) ) || !strcmp( argv[1], "--help" ) )
+ {
+ fprintf( stdout, "v3dconverter %s\n", VERSION );
+ fprintf( stdout, "Convert a Search And Rescue V3D *.3d model or terrain file to/from Wavefront *.obj or AC3D *.ac format.\n\n" );
+ fprintf( stdout, "Usage : %s -i input_file [options] -o output_file | [-h] | [--help]\n", argv[0] );
+ fprintf( stdout, " OPTIONS:\n");
+ fprintf( stdout, " -if\n");
+ fprintf( stdout, " Invert faces visibility (flip winding).\n");
+ fprintf( stdout, " -ro x y z\n");
+ fprintf( stdout, " Rotate object of x, y, and z degrees around respectives axes.\n" );
+ fprintf( stdout, " -sc scale\n");
+ fprintf( stdout, " Scale object. Scale value can be specified as decimal (0.1) or fractional (1/10).\n" );
+ fprintf( stdout, " -tr x y z\n");
+ fprintf( stdout, " Translate object of x, y, and z meters in respectives axes directions.\n" );
+ fprintf( stdout, " -v | --version\n");
+ fprintf( stdout, " Prints v3dconverter version.\n" );
+ fprintf( stdout, "\n");
+ fprintf( stdout, " Available conversions:\n");
+ fprintf( stdout, " +--------------------------+\n" );
+ fprintf( stdout, " | Destination |\n" );
+ fprintf( stdout, " +------+------+------+------+-----+\n" );
+ fprintf( stdout, " +--. |format| .3d | .hf | .obj | .ac |\n" );
+ fprintf( stdout, " | S `+------+------+------+------+-----+\n" );
+ fprintf( stdout, " | o | .3d | . | . |works1|todo?|\n" );
+ fprintf( stdout, " | u +------+------+------+------+-----+\n" );
+ fprintf( stdout, " | r | .obj | DONE | DONE | . | . |\n" );
+ fprintf( stdout, " | c +------+------+------+------+-----+\n" );
+ fprintf( stdout, " | e | .ac |works2| todo?| . | . |\n" );
+ fprintf( stdout, " +----+------+------+------+------+-----+\n" );
+ fprintf( stdout, " DONE : *.obj to *.3d conversion functional for *.obj objects with polygonal (non-\"freeform\") faces. *.obj to *.hf conversion functional for *.obj \"terrain\" file converted from a *.3d terrain file.\n" );
+ fprintf( stdout, " works1 : Works fine for *.3d terrain files. For object files, works with a lot of (all of ?) \"original\" Sar2 objects, but code is a proof of concept (not finished, to rewrite, some color issues when opening *.obj files in Blender).\n" );
+ fprintf( stdout, " works2 : Proof of concept: code to rewrite, not finished (no -tr option, ...)\n" );
+ fprintf( stdout, " todo? : Maybe later...\n" );
+ fprintf( stdout, " . : Not planned.\n" );
+ fprintf( stdout, " For other formats, try assimp: https://www.assimp.org/\n" );
+ fprintf( stdout, " \n" );
+
+ return 0;
+ }
+
+ /* Read command line arguments */
+ for ( unsigned short counter = 1; counter < argc; counter++ )
+ {
+ if ( !strcmp(argv[counter], "-i") ) // input model file name
+ {
+ if ( counter < argc )
+ source = argv[++counter];
+ if ( source == NULL || source[0] == '-' || source[0] == '\0' )
+ {
+ fprintf( stderr, "Error while reading source filename.\n" );
+ return 0;
+ }
+ }
+ else if ( !strcmp(argv[counter], "-o") ) // outpout model file name
+ {
+ if ( counter < argc )
+ destination = argv[++counter];
+ if ( destination == NULL || destination[0] == '-' || destination[0] == '\0' )
+ {
+ fprintf( stderr, "Error while reading destination filename.\n" );
+ return 0;
+ }
+ }
+ else if ( !strcmp(argv[counter], "-if") ) // invert model faces
+ {
+ userModifier.invertFaces = true;
+ }
+ else if ( !strcmp(argv[counter], "-sc") ) // scale model
+ {
+ if ( counter < argc )
+ scaleString = argv[++counter];
+ if ( scaleString == NULL )
+ {
+ fprintf( stderr, "Error while reading scale.\n" );
+ return 0;
+ }
+
+ if ( memchr( scaleString, '/', strlen(scaleString) ) != NULL )
+ {
+ long dividend = 1, divider = 1;
+ sscanf( scaleString, "%ld/%ld", ÷nd, ÷r );
+ userModifier.scale = (float) dividend / (float) divider;
+ }
+ else
+ sscanf( scaleString, "%lf", &userModifier.scale );
+
+ if ( userModifier.scale < 0 )
+ {
+ fprintf( stdout, "\033[7mNegative scale will produce strange results. You've been warned!\033[0m\n" );
+ }
+ else if ( userModifier.scale < 0.001 || userModifier.scale > 1000.0 )
+ {
+ fprintf( stdout, "\033[1m%s\033[0m??? What a scale!!! Okay, you're the boss...\n", scaleString );
+ }
+ }
+ else if ( !strcmp(argv[counter], "-tr") ) // translate model
+ {
+ if ( counter >= argc - 1 || !sscanf( argv[++counter], "%lf", &userModifier.tx ) )
+ {
+ fprintf( stderr, "Error while reading -tr X value.\n" );
+ return 0;
+ }
+ if ( counter >= argc - 1 || !sscanf( argv[++counter], "%lf", &userModifier.ty ) )
+ {
+ fprintf( stderr, "Error while reading -tr Y value.\n" );
+ return 0;
+ }
+ if ( counter >= argc - 1 || !sscanf( argv[++counter], "%lf", &userModifier.tz ) )
+ {
+ fprintf( stderr, "Error while reading -tr Z value.\n" );
+ return 0;
+ }
+ }
+ else if ( !strcmp(argv[counter], "-ro") ) // rotate model
+ {
+ if ( counter >= argc - 1 || !sscanf( argv[++counter], "%lf", &userModifier.rx ) )
+ {
+ fprintf( stderr, "Error while reading -ro X value.\n" );
+ return 0;
+ }
+ if ( counter >= argc - 1 || !sscanf( argv[++counter], "%lf", &userModifier.ry ) )
+ {
+ fprintf( stderr, "Error while reading -ro Y value.\n" );
+ return 0;
+ }
+ if ( counter >= argc - 1 || !sscanf( argv[++counter], "%lf", &userModifier.rz ) )
+ {
+ fprintf( stderr, "Error while reading -ro Z value.\n" );
+ return 0;
+ }
+ }
+ else if ( !strcmp(argv[counter], "-v" ) || !strcmp( argv[counter], "--version" ) ) // print version
+ {
+ fprintf( stdout, "%s\n", VERSION );
+ return 0;
+ }
+ else if ( argv[counter][0] == '-' ) // unknown option
+ {
+ fprintf( stderr, "\"%s\": unknown option.\n", argv[counter] );
+ return 0;
+ }
+ else
+ {
+ fprintf( stderr, "\"%s\": unknown statement.\n", argv[counter] );
+ return 0;
+ }
+ }
+
+ /* Convert */
+ if ( !strcmp( extOf( source ), ".3d") && !strcmp( extOf( destination ), ".obj" ) )
+ {
+ /* Check if *.3d file is a terrain file, i.e. if it has "heightfield_load" and "texture_load" statements */
+ bool hasHeightfield = false, hasTexture = false;
+ if ( ( fp = fopen(source, "r" ) ) == NULL ) {
+ perror(source);
+ return -1;
+ }
+ while ( fgets(lineBuffer, MAX_LENGTH, fp) )
+ {
+ if ( lineBuffer[ 0 ] == '\n' || lineBuffer[ 0 ] == '\r' || ( sscanf( lineBuffer, " %s[\n]", objTag ) ) == 0 ) // blank line or objTag == ""
+ {
+ lineNr++;
+ continue;
+ }
+ else if ( !strcmp( objTag, "heightfield_load" ) )
+ {
+ hasHeightfield = true;
+ if ( hasTexture )
+ break;
+ }
+ else if ( !strcmp( objTag, "texture_load" ) )
+ {
+ hasTexture = true;
+ if ( hasHeightfield )
+ break;
+ }
+ else
+ ;
+ }
+ fclose( fp );
+
+ /* Select the right function, depending of *.3d file type ("object" or "terrain") */
+ if ( !hasHeightfield )
+ { // it is *.3d object file
+ fprintf( stdout, "[ INFO ] *** .3d object to .obj conversion is EXPERIMENTAL. Please don't report bugs and use it at your own risk ;-) ***\n" );
+ if ( userModifier.scale != 1.0 )
+ {
+ fprintf( stdout, "[ INFO ] -sc (scale) option currently not available in *.3d to *.obj conversion.\n" );
+ userModifier.scale = 1.0;
+ }
+ v3dToObj( source, destination, &userModifier );
+ }
+ else
+ { // it is *.3d terrain file
+ if ( userModifier.rx != 0 || userModifier.ry != 0 || userModifier.rz != 0 )
+ {
+ fprintf( stdout, "[ INFO ] -ro (rotate) option not available in *.3d terrain to *.obj conversion.\n" );
+ userModifier.rx = 0;
+ userModifier.ry = 0;
+ userModifier.rz = 0;
+ }
+ if ( userModifier.invertFaces == true )
+ {
+ fprintf( stdout, "[ INFO ] -if (invert faces visibility) option not available in *.3d terrain to *.obj conversion.\n" );
+ userModifier.invertFaces = false;
+ }
+ v3dHfToObj( source, destination, &userModifier );
+ }
+ }
+ else if ( !strcmp(extOf( source ), ".obj" ) && !strcmp( extOf( destination ), ".3d" ) )
+ {
+ objToV3d( source, destination, &userModifier );
+ }
+ else if ( !strcmp(extOf( source ), ".obj" ) && ( !strcmp( extOf( destination ), ".hf" ) || isHf( destination ) ) )
+ {
+ if ( userModifier.tx != 0 || userModifier.ty != 0 || userModifier.tz != 0 || userModifier.rx != 0 || userModifier.ry != 0 || userModifier.rz != 0 || userModifier.invertFaces == 1 )
+ {
+ fprintf( stdout, "[ INFO ] -tr (translate), -ro (rotate), and -if (invert faces) options not available in *.obj terrain to *.hf conversion:\n translations have been saved during *.3d to *.obj conversion and will be automatically set.\n" );
+ userModifier.tx = 0;
+ userModifier.ty = 0;
+ userModifier.tz = 0;
+ userModifier.rx = 0;
+ userModifier.ry = 0;
+ userModifier.rz = 0;
+ userModifier.invertFaces = false;
+ }
+ if ( userModifier.scale != 1.0 )
+ {
+ fprintf( stdout, "[ INFO ] -sc (scale) option not available in *.obj terrain to *.hf conversion:\n scale has been saved during *.3d to *.obj conversion and will be automatically set.\n" );
+ userModifier.scale = 1.0;
+ }
+ objToHf( source, destination, &userModifier );
+ }
+ else if ( !strcmp(extOf( source ), ".ac" ) && !strcmp( extOf( destination ), ".3d" ) )
+ {
+ fprintf( stdout, "[ INFO ] *** .ac to .3d conversion is EXPERIMENTAL. Please don't report bugs and use it at your own risk ;-) ***\n" );
+ if ( userModifier.tx != 0 || userModifier.ty != 0 || userModifier.tz != 0 )
+ {
+ fprintf( stdout, "[ INFO ] -tr (translate) option currently not available in *.ac to *.3d conversion.\n" );
+ userModifier.tx = 0;
+ userModifier.ty = 0;
+ userModifier.tz = 0;
+ }
+ ac3dToV3d( source, destination, &userModifier );
+ }
+ else
+ {
+ fprintf( stderr, "%s can't convert \"%s\" format to \"%s\" format. ", argv[0], extOf( source ), extOf( destination ) );
+ fprintf( stderr, "Try to convert your \"%s\" file to \".obj\" or \".ac\" with 'assimp': https://www.assimp.org/\n", extOf( source ) );
+ }
+
+ return 0;
+}
diff --git a/tools/v3dconverter/src/v3dconverter.h b/tools/v3dconverter/src/v3dconverter.h
new file mode 100644
index 0000000..50579c3
--- /dev/null
+++ b/tools/v3dconverter/src/v3dconverter.h
@@ -0,0 +1,85 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+
+#define VERSION "0.3.2"
+
+#include // for PATH_MAX
+#define MAXPATHLENGTH PATH_MAX
+
+#define MAX_LENGTH 2048
+#define MAX_PARAMETER_LINES 6
+
+#define OBJ_OBJECT_NAME_TERRAIN "Terrain"
+#define OBJ_OBJECT_NAME_V3DCONVERTER_DATA "v3dConverter_data_DO_NOT_REMOVE"
+
+typedef struct UserModifier UserModifier;
+struct UserModifier {
+ double rx;
+ double ry;
+ double rz;
+ double tx;
+ double ty;
+ double tz;
+ double scale;
+ unsigned short invertFaces;
+};
+
+// vertex geometric coords
+typedef struct Vcoord Vcoord;
+struct Vcoord {
+ double x;
+ double y;
+ double z;
+};
+
+// normal direction
+typedef struct Vnormal Vnormal;
+struct Vnormal {
+ double i;
+ double j;
+ double k;
+};
+
+// texture coords
+typedef struct Tcoord Tcoord;
+struct Tcoord {
+ double u;
+ double v;
+ double w; // not mandatory
+};
+
+// whole vertex datas (vertex coords, normal, texture coords)
+typedef struct Vertex Vertex;
+struct Vertex {
+ long v;
+ long vn;
+ long vt;
+};
+
+// color
+typedef struct Color Color;
+struct Color {
+ double r; // red
+ double g; // green
+ double b; // blue
+ double a; // alpha (transparency)
+ double amb; // ambiant
+ double dif; // diffuse
+ double spe; // specular
+ double shi; // shininess
+ double emi; // emission
+};
diff --git a/tools/v3dconverter/src/v3dhftoobj.c b/tools/v3dconverter/src/v3dhftoobj.c
new file mode 100644
index 0000000..a80c4dd
--- /dev/null
+++ b/tools/v3dconverter/src/v3dhftoobj.c
@@ -0,0 +1,564 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+#include
+
+#define MAX_LENGTH 2048
+
+#define FREE_AND_CLOSE_ALL_V3DHFTOOBJ \
+if ( header != NULL ) free( header ); \
+if ( path != NULL ) free( path ); \
+if ( baseName != NULL ) free( baseName ); \
+if ( extension != NULL ) free( extension ); \
+if ( fp3dFileIn != NULL ) fclose( fp3dFileIn ); \
+if ( fpHfIn != NULL ) fclose( fpHfIn ); \
+if ( fpTexIn != NULL ) fclose( fpTexIn ); \
+if ( fpObjOut && (fpObjOut != NULL) ) fclose( fpObjOut ); \
+if ( fpMtlOut != NULL ) fclose( fpMtlOut ); \
+if ( timeStampString != NULL ) free( timeStampString ); \
+if ( fpTerrainDataOut != NULL ) fclose( fpTerrainDataOut );
+
+
+int v3dHfToObj( const char *source, const char *dest, UserModifier *userModifier ) {
+ FILE *fp3dFileIn = NULL, *fpHfIn = NULL, *fpTexIn = NULL, *fpObjOut = NULL, *fpMtlOut = NULL, *fpTerrainDataOut = NULL, *fopen();
+ char buffer[MAXPATHLENGTH + MAX_LENGTH + 7], lineBuffer[MAX_LENGTH + 1], objTag[MAX_LENGTH + 1];
+ char *path = NULL, *baseName = NULL, *extension = NULL; // for output files names
+ long lineNr = 1;
+ unsigned int triangles = 0;
+ bool hasHeightfield = false, hasTexture = false;
+
+ struct Terrain {
+ char path[ MAX_LENGTH + 1 ]; // texture path
+ double sizeX; // "width", in meters
+ double sizeY; // "height", in meters
+ double maxAltitude; // max altitude, in meters.
+ double xPos; // x translation, in meters
+ double yPos; // y translation, in meters
+ double zPos; // z translation, in meters
+ double h; // heading rotation, in degrees
+ double p; // pitch rotation, in degrees
+ double b; // bank rotation, in degrees
+ };
+
+ struct TexLoad {
+ char name[MAX_LENGTH + 1]; // texture name
+ char path[MAX_LENGTH + 1]; // texture path
+ double priority;
+ };
+
+ struct TgaMinimalHeader {
+ uint8_t idFieldLength; // Byte 0: image ID field length
+ uint8_t colorMapType;
+ uint8_t imageType;
+
+ /* Color Map Specification */
+ uint16_t colorMapIndex; // Byte 3 (LSB), byte 4 (MSB)
+ uint16_t colorMapLength;
+ uint8_t colorMapSize;
+
+ /* Image specification */
+ uint16_t xOrigin; // Byte 8 (LSB), byte 9 (MSB)
+ uint16_t yOrigin;
+ uint16_t width;
+ uint16_t height;
+ uint8_t pixelDepth; // Byte 16
+ uint8_t imageDescriptor;
+ };
+
+ struct Terrain terrain;
+
+ struct TexLoad texLoad;
+ strcpy( texLoad.name, "" );
+ strcpy( texLoad.path, "" );
+
+ /*
+ * Generate a time stamp string. This string will be used to name material and to name
+ * the file which will be used to store terrain data for later *.obj to *.hf conversion.
+ */
+ char *timeStampString = timeStamp();
+
+
+ /*
+ * Create and open "timeStampString" file to store terrain data
+ * for later *.obj to *.hf conversion.
+ */
+ int homeLength = strlen( getenv("HOME") );
+ char v3dConverterPath[] = "/.local/share/v3dconverter/";
+ int dirLength = strlen( v3dConverterPath );
+ int fileNameLength = strlen( timeStampString );
+
+ char *name = malloc( homeLength + dirLength + fileNameLength + 1 );
+ if ( name == NULL )
+ {
+ fprintf( stderr, "v3dhftoobj.c: can't allocate memory for name.\n" );
+ return -1;
+ }
+
+ strcat( name, getenv("HOME") );
+ strcat( name, v3dConverterPath );
+
+ errno = 0;
+ if ( ( mkdir(name, S_IRWXU) ) == -1 ) {
+ switch ( errno ) {
+ case EEXIST:
+ //fprintf( stderr, "v3dhftoobj.c: pathname already exists.\n");
+ break;
+ case EACCES :
+ fprintf( stderr, "v3dhftoobj.c: the parent directory does not allow write.\n");
+ //exit(EXIT_FAILURE);
+ break;
+ case ENAMETOOLONG:
+ fprintf( stderr, "v3dhftoobj.c: pathname is too long.\n");
+ //exit(EXIT_FAILURE);
+ break;
+ default:
+ fprintf( stderr, "v3dhftoobj.c: mkdir error.\n");
+ //exit(EXIT_FAILURE);
+ break;
+ }
+ }
+
+ strcat( name, timeStampString );
+
+ if ( ( fpTerrainDataOut = fopen( name, "w" ) ) == NULL ) {
+ perror( name );
+ return -1;
+ }
+ else
+ {
+ free( name );
+ }
+
+
+ /*
+ * Open *.3d file, then look for heightfield and texture names.
+ */
+ if ( ( fp3dFileIn = fopen( source, "r" ) ) == NULL ) {
+ perror( source );
+ return -1;
+ }
+ while ( fgets( lineBuffer, MAX_LENGTH, fp3dFileIn ) )
+ {
+ if ( lineBuffer[ 0 ] == '\n' || lineBuffer[ 0 ] == '\r' || ( sscanf( lineBuffer, " %s[\n]", objTag ) ) == 0 ) // blank line or objTag == ""
+ {
+ lineNr++;
+ continue;
+ }
+ else if ( !strcmp( objTag, "heightfield_load" ) )
+ {
+ hasHeightfield = true;
+ int sscanfResult = 0;
+ sscanfResult = sscanf( lineBuffer, "%*s %s %lf %lf %lf %lf %lf %lf %lf %lf %lf",
+ terrain.path,
+ &terrain.sizeX, &terrain.sizeY, &terrain.maxAltitude,
+ &terrain.xPos, &terrain.yPos, &terrain.zPos,
+ &terrain.h, &terrain.p, &terrain.b
+ );
+ if ( sscanfResult != 10 )
+ {
+ int cnt = 0;
+ while ( lineBuffer[ cnt++ ] != '\n' );
+ lineBuffer[ --cnt ] = '\0';
+ fprintf( stderr, "v3dhftoobj.c: Error while scanning line #%ld (\"%s\").\n", lineNr, lineBuffer );
+ }
+ }
+ else if ( !strcmp( objTag, "texture_load" ) )
+ {
+ hasTexture = true;
+ int sscanfResult = 0;
+ sscanfResult = sscanf( lineBuffer, "%*s %s %s %lf",
+ texLoad.name,
+ texLoad.path,
+ &texLoad.priority
+ );
+ if ( sscanfResult != 3 )
+ {
+ int cnt = 0;
+ while ( lineBuffer[ cnt++ ] != '\n' );
+ lineBuffer[ --cnt ] = '\0';
+ fprintf( stderr, "v3dhftoobj.c: Error while scanning line #%ld (\"%s\").\n", lineNr, lineBuffer );
+ }
+ }
+ else
+ ;
+
+ if ( hasHeightfield && hasTexture )
+ break;
+
+ lineNr++;
+ }
+ fclose( fp3dFileIn );
+ fp3dFileIn = NULL;
+
+
+ /*
+ * Read *.hf image header.
+ */
+ uint8_t imageOrigin;
+ uint32_t colorMapBytes;
+ struct TgaMinimalHeader hfHeader;
+ uint8_t *header = malloc( sizeof( hfHeader ) );
+ if ( header == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for TGA image header.\n" );
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+ if ( (fpHfIn = fopen( terrain.path, "rb") ) == NULL ) {
+ perror( terrain.path );
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+
+ /* Warning: sizeof( hfHeader ) don't work for image reading because of memory alignment. */
+ int tgaImageHeaderLength = 18;
+ fread( header, 1, tgaImageHeaderLength, fpHfIn );
+ hfHeader.idFieldLength = header[0];
+ hfHeader.colorMapType = header[1];
+ hfHeader.imageType = header[2];
+ hfHeader.colorMapIndex = header[3] + header[4] * 256; // LSB, MSB
+ hfHeader.colorMapLength = header[5] + header[6] * 256;
+ hfHeader.colorMapSize = header[7];
+ hfHeader.xOrigin = header[8] + header[9] * 256;
+ hfHeader.yOrigin = header[10] + header[11] * 256;
+ hfHeader.width = header[12] + header[13] * 256;
+ hfHeader.height = header[14] + header[15] * 256;
+ hfHeader.pixelDepth = header[16];
+ hfHeader.imageDescriptor= header[17];
+ /*
+ long fileSize = 0;
+ fileSize = file_size( terrain.path );
+ fprintf( stderr, "------ *.hf image: %s, %ld bytes ------\n", terrain.path, fileSize );
+ fprintf( stderr, " idFieldLength = %d\n", hfHeader.idFieldLength );
+ fprintf( stderr, " colorMapType = %d\n", hfHeader.colorMapType );
+ fprintf( stderr, " imageType = %d\n", hfHeader.imageType );
+ fprintf( stderr, " colorMapIndex = %d ( %d + %d * 256 )\n", hfHeader.colorMapIndex, header[3], header[4] );
+ fprintf( stderr, " colorMapLength = %d ( %d + %d * 256 )\n", hfHeader.colorMapLength, header[5], header[6] );
+ fprintf( stderr, " colorMapSize = %d\n", hfHeader.colorMapSize );
+ fprintf( stderr, " xOrigin = %d ( %d + %d * 256 )\n", hfHeader.xOrigin, header[8], header[9] );
+ fprintf( stderr, " yOrigin = %d ( %d + %d * 256 )\n", hfHeader.yOrigin, header[10], header[11] );
+ fprintf( stderr, " width = %d ( %d + %d * 256 )\n", hfHeader.width, header[12], header[13] );
+ fprintf( stderr, " height = %d ( %d + %d * 256 )\n", hfHeader.height, header[14], header[15] );
+ fprintf( stderr, " pixelDepth = %d\n", hfHeader.pixelDepth );
+ fprintf( stderr, "imageDescriptor = %d\n\n", hfHeader.imageDescriptor );
+ */
+ if ( hfHeader.imageType != 3 )
+ {
+ fprintf( stderr, "Warning: *.hf TGA image should preferably be of type 3 (ie an uncompressed grayscale image).\n" );
+ }
+ if ( hfHeader.pixelDepth != 8 )
+ {
+ fprintf( stderr, "Warning: *.hf TGA image should preferably have 8 bits per pixel (%d bits found).\n", hfHeader.pixelDepth );
+ }
+
+ imageOrigin = hfHeader.imageDescriptor & 0b00110000;
+ imageOrigin = imageOrigin >> 4;
+ if ( imageOrigin == 2 )
+ ; // top left origin, okay.
+ else if ( imageOrigin == 0 )
+ {
+ fprintf( stderr, "Warning: *.hf TGA image should preferably be top left origin (bottom left found and accepted).\n" );
+ }
+ else if ( imageOrigin == 1 )
+ {
+ fprintf( stderr, "Error: *.hf TGA image should preferably be top left origin (bottom right found).\n" );
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+ else // imageOrigin == 3
+ {
+ fprintf( stderr, "Error: *.hf TGA image should preferably be top left origin (top right found).\n" );
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+
+
+ /*
+ * Read ID field (if any). We don't need it, but this will increment file data pointer.
+ */
+ if ( hfHeader.idFieldLength != 0 )
+ {
+ uint8_t *imageID = malloc( hfHeader.idFieldLength );
+ fread( imageID, 1, hfHeader.idFieldLength, fpHfIn );
+ free( imageID );
+ }
+
+
+ /*
+ * Read color map field (if any). We don't need it, but this will increment file data pointer.
+ */
+ colorMapBytes = hfHeader.colorMapLength * hfHeader.colorMapSize;
+ if ( colorMapBytes != 0 )
+ {
+ uint8_t *colorMap = malloc( colorMapBytes );
+ fread( colorMap, 1, colorMapBytes, fpHfIn );
+ free( colorMap );
+ fprintf( stderr, "Error: *.hf TGA image shouldn't have any color map (%d length color map found).\n", hfHeader.colorMapLength);
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+ free( header );
+ header = NULL;
+
+
+ /*
+ * Generate output (*.obj and *.mtl) file names.
+ */
+ scanFileName( dest, &path, &baseName, &extension );
+ if ( path[0] == '\0' ) // if no path
+ sprintf( buffer, "%s.obj", baseName );
+ else
+ sprintf( buffer, "%s/%s.obj", path, baseName );
+
+ if ( ( fpObjOut = fopen(buffer, "w" ) ) == NULL) {
+ perror(buffer);
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+
+ if ( path[0] == '\0' ) // if no path
+ sprintf( buffer, "%s.mtl", baseName );
+ else
+ sprintf( buffer, "%s/%s.mtl", path, baseName );
+
+ if ( ( fpMtlOut = fopen( buffer, "w") ) == NULL ) {
+ perror( buffer );
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+
+
+ /*
+ * Write *.mtl file name in *.obj file and write material in *.mtl file.
+ */
+ if ( fpMtlOut != NULL ) {
+ /* Write terrain object name in *obj file */
+ //fprintf( fpObjOut, "o Terrain\n\n" );
+
+ /* Write material file name in *obj file, and add texture file name (if any) */
+ fprintf( fpObjOut, "mtllib ./%s.mtl\n\n", baseName );
+
+ /* Create material in *.mtl file */
+ fprintf( fpMtlOut, "newmtl %s\n", timeStampString );
+ fprintf( fpMtlOut, "Ns 127.999985\n" );
+ fprintf( fpMtlOut, "Ka 1.000000 1.000000 1.000000\n" );
+ fprintf( fpMtlOut, "Kd 0.800000 0.800000 0.800000\n" );
+ fprintf( fpMtlOut, "Ks 0.000000 0.000000 0.000000\n" );
+ fprintf( fpMtlOut, "Ke 0.000000 0.000000 0.000000\n" );
+ fprintf( fpMtlOut, "Ni 1.450000\n" );
+ fprintf( fpMtlOut, "d 1.000000\n" );
+ fprintf( fpMtlOut, "illum 2\n" );
+ if ( texLoad.path != NULL )
+ fprintf( fpMtlOut, "map_Kd %s\n", texLoad.path );
+
+ fclose( fpMtlOut );
+ fpMtlOut = NULL;
+ }
+
+
+ /*
+ * Read *.hf altitudes.
+ */
+ uint8_t bytesPerPixel = hfHeader.pixelDepth / 8;
+ uint8_t *altitudeData = malloc( hfHeader.width * hfHeader.height * bytesPerPixel);
+ if ( altitudeData == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for altitudeData.\n" );
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+
+ if ( imageOrigin == 0 )
+ {
+ /* Image is bottom left origin: rows order must be inverted in order to obtain a top left origin image. */
+ unsigned long bytesPerRow = hfHeader.width * bytesPerPixel;
+ uint8_t *tempRow = malloc( bytesPerRow );
+ if ( tempRow == NULL )
+ {
+ fprintf( stderr, "Can't allocate memory for tempRow.\n" );
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return -1;
+ }
+ for ( int cnt = hfHeader.height - 1; cnt >= 0; cnt-- )
+ {
+ fread( tempRow, 1, bytesPerRow, fpHfIn );
+ memcpy( altitudeData + cnt * bytesPerRow, tempRow, bytesPerRow );
+ }
+ free( tempRow );
+ }
+ else if ( imageOrigin == 2)
+ {
+ /* Image is top left origin: we just have to read data. */
+ fread( altitudeData, 1, hfHeader.width * hfHeader.height * bytesPerPixel, fpHfIn );
+ }
+ else
+ {
+ ; /* Other cases (imageOrigin == 1 or imageOrigin == 2) have been detected and have generated an error earlier */
+ }
+
+
+ /*
+ * Write terrain vertices.
+ */
+ long offset = 0;
+ double perPixelXDistance = terrain.sizeX / (double)hfHeader.width;
+ double perPixelYDistance = terrain.sizeY / (double)hfHeader.height;
+ double perPixelZDistance = terrain.maxAltitude / 255; // altitude is always an 8 bits value
+ double halfXSize = terrain.sizeX / 2 - perPixelXDistance / 2;
+ double halfYSize = terrain.sizeY / 2 - perPixelYDistance / 2;
+ for ( int line = hfHeader.height - 1; line >= 0; line-- )
+ {
+ for ( int col = 0; col < hfHeader.width; col++ )
+ {
+ double x = (double)col * perPixelXDistance - halfXSize;
+ double y = (double)line * perPixelYDistance - halfYSize;
+ double z = (double)altitudeData[ offset ] * perPixelZDistance;
+ offset += bytesPerPixel;
+
+ /* Apply *.3d terrain translations */
+ x += terrain.xPos;
+ y += terrain.yPos;
+ z += terrain.zPos;
+
+ /* Apply user translations */
+ x += userModifier->tx;
+ y += userModifier->ty;
+ z += userModifier->tz;
+
+ /* Apply user scale */
+ x *= userModifier->scale;
+ y *= userModifier->scale;
+ z *= userModifier->scale;
+
+ /* Write vertex to *.obj file */
+ fprintf( fpObjOut, "v %.3f %.3f %.3f\n", x, z, -y );
+ }
+ }
+ free( altitudeData );
+
+
+ /*
+ * Write texture vertices, if any.
+ */
+ bool isTextured = true;
+ if ( texLoad.path == NULL )
+ {
+ isTextured = false;
+ }
+ else /* There is a texture to apply */
+ {
+ /* Write texture vertices in *.obj file */
+ for ( int cnt0 = hfHeader.height; cnt0 >= 0; cnt0-- )
+ {
+ for ( int cnt1 = 0; cnt1 < hfHeader.width; cnt1++ )
+ {
+ fprintf( fpObjOut, "vt %.6f %.6f\n", (double)cnt1 / (double)hfHeader.width, (double)cnt0 / (double)hfHeader.height );
+ }
+ }
+ }
+
+ /*
+ * Write material name.
+ */
+ fprintf( fpObjOut, "usemtl %s\n", timeStampString );
+
+ /*
+ * Set smooth rendering.
+ */
+ fprintf( fpObjOut, "s 1\n" );
+
+
+ /*
+ * Write terrain faces.
+ */
+ int hOffset = 0;
+ int vOffset = 0;
+ for ( int y = 0; y < hfHeader.height - 1; y++ )
+ {
+ vOffset = y * hfHeader.width;
+ for ( int x = 1; x < hfHeader.width; x++ )
+ {
+ hOffset = vOffset + x;
+ if ( isTextured ) // print v/vt
+ {
+ /* first triangle */
+ fprintf(fpObjOut, "f %d/%d %d/%d %d/%d\n",
+ hOffset + 1 + hfHeader.width, hOffset + 1 + hfHeader.width,
+ hOffset + 1, hOffset + 1,
+ hOffset, hOffset
+ );
+
+ /* second triangle */
+ fprintf(fpObjOut, "f %d/%d %d/%d %d/%d\n", hOffset, hOffset,
+ hOffset + hfHeader.width, hOffset + hfHeader.width,
+ hOffset + 1 + hfHeader.width, hOffset + 1 + hfHeader.width
+ );
+ }
+ else // no texture, don't print vt, print v only
+ {
+ /* first triangle */
+ fprintf(fpObjOut, "f %d %d %d\n", hOffset + 1 + hfHeader.width, hOffset + 1, hOffset );
+
+ /* second triangle */
+ fprintf(fpObjOut, "f %d %d %d\n", hOffset, hOffset + hfHeader.width, hOffset + 1 + hfHeader.width );
+ }
+ triangles += 2;
+ }
+ }
+
+
+ /*
+ * Store *.3d terrain data for later *.obj to *.hf conversion.
+ */
+ fprintf( fpTerrainDataOut, "# File automatically generated. Do not edit manually.\n" );
+ fprintf( fpTerrainDataOut, "file=%s.obj\n", baseName );
+ fprintf( fpTerrainDataOut, "material_name=%s\n", timeStampString );
+ fprintf( fpTerrainDataOut, "terrain_width=%.6f\n", terrain.sizeX );
+ fprintf( fpTerrainDataOut, "terrain_height=%.6f\n", terrain.sizeY );
+ fprintf( fpTerrainDataOut,"terrain_altitude=%.6f\n", terrain.maxAltitude );
+ fprintf( fpTerrainDataOut, "terrain_transX=%.6f\n", terrain.xPos + userModifier->tx );
+ fprintf( fpTerrainDataOut, "terrain_transY=%.6f\n", terrain.yPos + userModifier->ty );
+ fprintf( fpTerrainDataOut, "terrain_transZ=%.6f\n", terrain.zPos + userModifier->tz );
+ fprintf( fpTerrainDataOut, "terrain_scale=%.6f\n", userModifier->scale );
+ fprintf( fpTerrainDataOut, "terrain_bitsWidth=%d\n", hfHeader.width );
+ fprintf( fpTerrainDataOut,"terrain_bitsHeight=%d\n", hfHeader.height );
+ fclose( fpTerrainDataOut );
+ fpTerrainDataOut = NULL;
+
+
+ /*
+ * Print some info to console.
+ */
+ //printf("%s contains:\n %d triangles.\n", terrain.path, triangles);
+ printf("Files %s.obj and %s.mtl written to: ", baseName, baseName);
+ if ( path[0] == '\0' ) // if no path
+ printf( "./ directory.\n" );
+ else
+ printf( "%s directory.\n", path );
+
+
+ FREE_AND_CLOSE_ALL_V3DHFTOOBJ
+ return 0;
+}
diff --git a/tools/v3dconverter/src/v3dtoobj.c b/tools/v3dconverter/src/v3dtoobj.c
new file mode 100644
index 0000000..add3725
--- /dev/null
+++ b/tools/v3dconverter/src/v3dtoobj.c
@@ -0,0 +1,852 @@
+/**********************************************************************
+* This file is part of Search and Rescue II (SaR2). *
+* *
+* SaR2 is free software: you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License v.2 as *
+* published by the Free Software Foundation. *
+* *
+* SaR2 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 SaR2. If not, see . *
+***********************************************************************/
+
+
+
+
+
+/**************************************************
+* WARNING: EXPERIMENTAL (BUT FUNCTIONAL) CODE *
+* *
+* This whole code must be rewritten ! *
+***************************************************/
+
+#define MAX_LENGTH 2048
+#define MAX_PARAMETER_LINES 6
+
+struct Modifier {
+ double tx;
+ double ty;
+ double tz;
+ double rh; // heading
+ double rp; // pitch
+ double rb; // bank
+};
+struct Modifier modifier;
+
+
+long int lineNr = 1; // input file line counter
+
+long readV3dVerticies ( FILE *fpIn, const char * endBoundString, FILE *fpOut, char* textureOrient, UserModifier *userModifier, struct Modifier modifier ) {
+ char lineBuffer[MAX_LENGTH], buffer[MAX_LENGTH], result[MAX_LENGTH];
+ int v = 0, vt = 0, vn = 0, primitives = 0;
+ double ta = 0, tb = 0, tda = 0, tdb = 0; // texture_orient_ values
+ double tu = 0, tv = 0; // texture vertex
+ double vx = 0, vy = 0, vz = 0, pw = 0, nx = 0, ny = 0, nz = 0;
+
+ //long position, startPosition, endPosition;
+ //startPosition = ftell(fpIn);
+
+ // read and count verticies in *.3d file, then write them to *.obj file
+ while ( fgets(lineBuffer, MAX_LENGTH, fpIn) ) {
+ sscanf( lineBuffer, "%s", buffer );
+ if ( !strcmp(strtoupper( result, buffer), endBoundString) ) { // end of bound
+ lineNr++; // input file line counter
+ break;
+ }
+
+ if ( !strcmp(strtoupper( result, buffer), "") ) { // it's an empty line
+ break;
+ }
+ else if ( !strcmp(strtoupper( result, buffer), "NORMAL") ) { // it's a vertex or a face normal
+ //
+ // If it is a face normal, 'vn x y z' must NOT be written because a *.obj surface normal only depends of winding.
+ // Hereunder a very ugly way to detect if it is a surface normal.
+ //
+ fpos_t position;
+ fgetpos(fpIn, &position); // save in-file position
+ int isAVertexNormal = 0;
+ /* Read next three v3d file lines. If "NORMAL" is found in one of these three lines, first "NORMAL" statement was a vertex normal. */
+ for ( int cnt0 = 0; cnt0 < 3; cnt0++ )
+ {
+ fgets(lineBuffer, MAX_LENGTH, fpIn);
+ sscanf( lineBuffer, "%s", buffer );
+ if ( !strcmp(strtoupper( result, buffer), "NORMAL") )
+ isAVertexNormal = 1; // new "NORMAL" statement found, first "NORMAL" statement was a vertex normal
+ }
+ fsetpos(fpIn, &position); // return to original in-file position
+
+ if ( isAVertexNormal == 1 )
+ {
+ vn++;
+ sscanf( lineBuffer, "%*s %lf %lf %lf", &nx, &ny, &nz );
+ fprintf( fpOut, "vn ");
+
+ // apply v3d rotation and position modifiers
+ if ( modifier.rh ) RotateZ ( &nx, &ny, &nz, &pw, modifier.rh );
+ if ( modifier.rp ) RotateX ( &nx, &ny, &nz, &pw, modifier.rp );
+ if ( modifier.rb ) RotateY ( &nx, &ny, &nz, &pw, modifier.rb );
+ nx += modifier.tx;
+ ny += modifier.ty;
+ nz += modifier.tz;
+
+ /* TODO apply user modifiers. Apply order is: X rotation, then Y rotation, then Z rotation, then scale, then translations. */
+ /*
+ if ( userModifier->rx != 0.0 )
+ {
+ RotateX ( &nx, &ny, &nz, &pw, userModifier->rx );
+ }
+ if ( userModifier->ry != 0.0 )
+ {
+ RotateY ( &nx, &ny, &nz, &pw, userModifier->ry );
+ }
+ if ( userModifier->rz != 0.0 )
+ {
+ RotateZ ( &nx, &ny, &nz, &pw, userModifier->rz );
+ }
+ */
+
+ /* re-orient normal them because v3d coords system is different from obj coords system */
+ RotateZ ( &nx, &ny, &nz, &pw, -90 ); // rotate normal arround Z axis. Angle given in degrees.
+ RotateX ( &nx, &ny, &nz, &pw, 90 ); // rotate normal arround X axis. Angle given in degrees.
+
+ // write vertex normal
+ fprintf( fpOut, "%lf %lf %lf\n", nx, ny, nz );
+ }
+ }
+ else if ( !strcmp(strtoupper( result, buffer), "TEXTURE") || !strcmp(strtoupper( result, buffer), "TEXCOORD") ) { // it's a texture coordinate
+ vt++;
+ fprintf( fpOut, "vt ");
+ sscanf( lineBuffer, "%*s %lf %lf", &tu, &tv ); // u, v
+
+ // write texture coords
+ fprintf( fpOut, "%lf %lf\n", tu, tv ); // u, v
+ }
+/*
+ else if ( (!strcmp(strtoupper( result, buffer), "TEXTURE") && strlen( result ) == strlen( "TEXTURE" )) || (!strcmp(strtoupper( result, buffer), "TEXCOORD") && strlen( result ) == strlen( "TEXCOORD" )) || (!strcmp(strtoupper( result, buffer), "TEX_COORD") && strlen( result ) == strlen( "TEX_COORD" )) ) { // it's a texture vertex
+printf("TEXTURE VERTEX FOUND at line #%ld\n", lineNr);
+ vt++;
+ fprintf( fpOut, "vt ");
+ sscanf( lineBuffer, "%*s %lf %lf %lf", &x, &y, &z ); // u, v, w
+
+ //if ( modifier.rh ) RotateZ ( &x, &y, &z, &pw, modifier.rh );
+ //if ( modifier.rp ) RotateX ( &x, &y, &z, &pw, modifier.rp );
+ //if ( modifier.rb ) RotateY ( &x, &y, &z, &pw, modifier.rb );
+
+ //x += modifier.tx;
+ //y += modifier.ty;
+ //z += modifier.tz;
+ fprintf( fpOut, "%lf %lf %lf\n", x, y, z ); // u, v, w
+ }
+*/
+ else { // it's a geometric vertex
+ v++;
+ fprintf( fpOut, "v ");
+ sscanf( lineBuffer, "%lf %lf %lf", &vx, &vy, &vz );
+
+ if ( textureOrient[0] != '\0' ) { // if there is a running "texture_orient_" command, then create a texture vertex
+ vt++;
+ sscanf( textureOrient, "%*s %lf %lf %lf %lf", &ta, &tb, &tda, &tdb ); // "texture_orient_" command parameters
+ if ( textureOrient[15] == 'x' || textureOrient[15] == 'X' ) {
+ if ( textureOrient[16] == 'y' || textureOrient[15] == 'Y' ) { // TEXTURE_ORIENT_XY (example: cuda top/bottom)
+ if ( vx > ta ) tu = ( vx - ta ) / tda;
+ else tu = ( ta - vx ) / tda;
+ if ( vy > tb ) tv = ( vy - tb ) / tdb;
+ else tv = ( tb - vy ) / tdb;
+ }
+ else { // TEXTURE_ORIENT_XZ (example: cuda front/rear)
+ if ( vx > ta ) tu = ( vx - ta ) / tda;
+ else tu = ( ta - vx ) / tda;
+ if ( vz > tb ) tv = ( vz - tb ) / tdb;
+ else tv = ( tb - vz ) / tdb;
+ }
+ }
+ else { // TEXTURE_ORIENT_YZ (example: cuda left/right)
+ if ( vy > ta ) tu = ( vy - ta ) / tda;
+ else tu = ( ta - vy ) / tda;
+ if ( vz > tb ) tv = ( vz - tb ) / tdb;
+ else tv = ( tb - vz ) / tdb;
+ }
+
+ }
+
+ // apply v3d rotation and position modifiers
+ if ( modifier.rh ) RotateZ ( &vx, &vy, &vz, &pw, modifier.rh );
+ if ( modifier.rp ) RotateX ( &vx, &vy, &vz, &pw, modifier.rp );
+ if ( modifier.rb ) RotateY ( &vx, &vy, &vz, &pw, modifier.rb );
+ vx += modifier.tx;
+ vy += modifier.ty;
+ vz += modifier.tz;
+
+ /* TODO apply user modifiers. Apply order is: X rotation, then Y rotation, then Z rotation, then scale, then translations. */
+ /*
+ if ( userModifier->rx != 0.0 )
+ {
+ RotateX ( &vx, &vy, &vz, &pw, userModifier->rx );
+ }
+ if ( userModifier->ry != 0.0 )
+ {
+ RotateY ( &vx, &vy, &vz, &pw, userModifier->ry );
+ }
+ if ( userModifier->rz != 0.0 )
+ {
+ RotateZ ( &vx, &vy, &vz, &pw, userModifier->rz );
+ }
+ */
+
+ /* re-orient vertex because v3d coords system is different from obj coords system */
+ RotateZ ( &vx, &vy, &vz, &pw, -90 ); // rotate vertex arround Z axis. Angle given in degrees.
+ RotateX ( &vx, &vy, &vz, &pw, 90 ); // rotate vertex arround X axis. Angle given in degrees.
+
+ fprintf( fpOut, "%lf %lf %lf\n", vx, vy, vz ); // write geometric vertex
+ if ( textureOrient[0] != '\0' ) { // write texture vertex
+ fprintf( fpOut, "vt %lf %lf\n", tu, tv );
+ }
+ }
+ lineNr++; // input file line counter
+ }
+ //endPosition = ftell(fpIn);
+ endBoundString += 4; // remove firsts 4 characters (i.e. "END_")
+
+ // generate and write primitives (points, lines, faces, ...) to *.obj file
+ if ( !strcmp(endBoundString, "POINTS" ) ) {
+ primitives = v;
+ fprintf(fpOut, "p");
+ for ( int cnt0 = -primitives; cnt0 < 0; cnt0++ ) {
+ fprintf(fpOut, " %d", cnt0 );
+ }
+ fprintf(fpOut, "\n" );
+ }
+ else if ( !strcmp(endBoundString, "LINES" ) ) {
+ primitives = v / 2; // number of lines
+ int v1 = 0, v2 = 0;
+ for ( int cnt0 = -primitives; cnt0 < 0; cnt0++ ) {
+ v1 = cnt0 * 2;
+ v2 = v1 + 1;
+ fprintf(fpOut, "l");
+ if ( v && !vt ) {
+ fprintf(fpOut, " %d", v1 );
+ fprintf(fpOut, " %d", v2 );
+ }
+ else { // ( v && vt )
+ fprintf(fpOut, " %d/%d", v1, v1 );
+ fprintf(fpOut, " %d/%d", v2, v2 );
+ }
+ fprintf(fpOut, "\n" );
+ }
+ }
+ else if ( !strcmp(endBoundString, "LINE_STRIP" ) ) {
+ primitives = v - 1; // number of lines
+ fprintf(fpOut, "l");
+ for ( int cnt0 = -primitives - 1; cnt0 < 0; cnt0++ ) {
+ if ( v && !vt ) {
+ fprintf(fpOut, " %d", cnt0 );
+ }
+ else { // ( v && vt )
+ fprintf(fpOut, " %d/%d", cnt0, cnt0 );
+ }
+ }
+ fprintf(fpOut, "\n");
+ }
+ else if ( !strcmp(endBoundString, "LINE_LOOP" ) ) {
+ primitives = v; // number of lines
+ fprintf(fpOut, "l");
+ for ( int cnt0 = -primitives; cnt0 < 0; cnt0++ ) {
+ if ( v && !vt ) {
+ fprintf(fpOut, " %d", cnt0 );
+ }
+ else { // ( v && vt )
+ fprintf(fpOut, " %d/%d", cnt0, cnt0 );
+ }
+ }
+ // close loop
+ if ( v && !vt ) {
+ fprintf(fpOut, " %d", -primitives );
+ }
+ else { // ( v && vt )
+ fprintf(fpOut, " %d/%d", -primitives, -primitives );
+ }
+ fprintf(fpOut, "\n");
+ }
+ else if ( !strcmp(endBoundString, "TRIANGLES" ) ) {
+ primitives = v / 3; // number of triangles
+ for ( int cnt0 = -primitives; cnt0 < 0; cnt0++ ) {
+ int offset = cnt0 * 3;
+ fprintf(fpOut, "f");
+ if ( v && !vt && !vn ) {
+ fprintf(fpOut, " %d", offset + 2 );
+ fprintf(fpOut, " %d", offset + 1 );
+ fprintf(fpOut, " %d", offset );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", offset + 2, offset + 2 );
+ fprintf(fpOut, " %d//%d", offset + 1, offset + 1 );
+ fprintf(fpOut, " %d//%d", offset, offset );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", offset + 2, offset + 2 );
+ fprintf(fpOut, " %d/%d", offset + 1, offset + 1 );
+ fprintf(fpOut, " %d/%d", offset, offset );
+ }
+ else { // ( v && vt && vn )
+ fprintf(fpOut, " %d/%d/%d", offset + 2, offset + 2, offset + 2 );
+ fprintf(fpOut, " %d/%d/%d", offset + 1, offset + 1, offset + 1 );
+ fprintf(fpOut, " %d/%d/%d", offset, offset, offset );
+ }
+ fprintf(fpOut, "\n" );
+ }
+ }
+ else if ( !strcmp(endBoundString, "TRIANGLE_STRIP" ) ) {
+ /* Example of triangle strip:
+ v -11.1 -35.1 5.3
+ v -4.9 -21.5 5.3
+ v 0.7 -40.4 5.3
+ v 26.1 19 5.3
+ v 2.6 -36.1 5.3
+ v 17.8 -13.6 5.3
+ v 15.1 -41.7 5.3
+ v 26 -17.3 5.3
+ f -6 -7 -8
+ f -5 -7 -6
+ f -4 -5 -6
+ f -3 -5 -4
+ f -2 -3 -4
+ f -1 -3 -2
+ */
+ primitives = v - 2; // number of triangles
+ // first triangle
+ int v1 = - primitives, v2 = v1 - 1, v3 = v2 - 1;
+ fprintf(fpOut, "f");
+ if ( v && !vt && !vn ) {
+ fprintf(fpOut, " %d", v1 );
+ fprintf(fpOut, " %d", v2 );
+ fprintf(fpOut, " %d", v3 );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", v1, v1 );
+ fprintf(fpOut, " %d//%d", v2, v2 );
+ fprintf(fpOut, " %d//%d", v3, v3 );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", v1, v1 );
+ fprintf(fpOut, " %d/%d", v2, v2 );
+ fprintf(fpOut, " %d/%d", v3, v3 );
+ }
+ else { // ( v && vt && vn )
+ fprintf(fpOut, " %d/%d/%d", v1, v1, v1 );
+ fprintf(fpOut, " %d/%d/%d", v2, v2, v2 );
+ fprintf(fpOut, " %d/%d/%d", v3, v3, v3 );
+ }
+ fprintf(fpOut, "\n");
+
+ // next triangles
+ for ( int cnt0 = primitives - 1; cnt0 > 0; cnt0 = cnt0 - 2 ) {
+ v1 = v1 + 1;
+ v3 = v3 + 2;
+ fprintf(fpOut, "f");
+ if ( v && !vt && !vn ) {
+ fprintf(fpOut, " %d", v1 );
+ fprintf(fpOut, " %d", v2 );
+ fprintf(fpOut, " %d", v3 );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", v1, v1 );
+ fprintf(fpOut, " %d//%d", v2, v2 );
+ fprintf(fpOut, " %d//%d", v3, v3 );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", v1, v1 );
+ fprintf(fpOut, " %d/%d", v2, v2 );
+ fprintf(fpOut, " %d/%d", v3, v3 );
+ }
+ else { // ( v && vt && vn )
+ fprintf(fpOut, " %d/%d/%d", v1, v1, v1 );
+ fprintf(fpOut, " %d/%d/%d", v2, v2, v2 );
+ fprintf(fpOut, " %d/%d/%d", v3, v3, v3 );
+ }
+ fprintf(fpOut, "\n");
+
+ v1 = v1 + 1;
+ if ( v1 < 0 ) {
+ v2 = v2 + 2;
+ fprintf(fpOut, "f");
+ if ( v && !vt && !vn ) {
+ fprintf(fpOut, " %d", v1 );
+ fprintf(fpOut, " %d", v2 );
+ fprintf(fpOut, " %d", v3 );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", v1, v1 );
+ fprintf(fpOut, " %d//%d", v2, v2 );
+ fprintf(fpOut, " %d//%d", v3, v3 );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", v1, v1 );
+ fprintf(fpOut, " %d/%d", v2, v2 );
+ fprintf(fpOut, " %d/%d", v3, v3 );
+ }
+ else { // v && vt && vn )
+ fprintf(fpOut, " %d/%d/%d", v1, v1, v1 );
+ fprintf(fpOut, " %d/%d/%d", v2, v2, v2 );
+ fprintf(fpOut, " %d/%d/%d", v3, v3, v3 );
+ }
+ fprintf(fpOut, "\n");
+ }
+ }
+ }
+ else if ( !strcmp(endBoundString, "TRIANGLE_FAN" ) ) {
+ primitives = v - 2; // number of triangles
+ for ( int cnt0 = -primitives; cnt0 < 0; cnt0++ ) {
+ fprintf(fpOut, "f");
+ if ( v && !vt && !vn ) {
+ fprintf(fpOut, " %d", -primitives - 2 );
+ fprintf(fpOut, " %d", cnt0 );
+ fprintf(fpOut, " %d", cnt0 - 1 );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", -primitives - 2, -primitives - 2 );
+ fprintf(fpOut, " %d//%d", cnt0, cnt0 );
+ fprintf(fpOut, " %d//%d", cnt0 - 1, cnt0 - 1 );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", -primitives - 2, -primitives - 2 );
+ fprintf(fpOut, " %d/%d", cnt0, cnt0 );
+ fprintf(fpOut, " %d/%d", cnt0 - 1, cnt0 - 1 );
+ }
+ else { // ( v && vt && vn )
+ fprintf(fpOut, " %d/%d/%d", -primitives - 2, -primitives - 2, -primitives - 2 );
+ fprintf(fpOut, " %d/%d/%d", cnt0, cnt0, cnt0 );
+ fprintf(fpOut, " %d/%d/%d", cnt0 - 1, cnt0 - 1, cnt0 - 1 );
+ }
+ fprintf(fpOut, "\n");
+ }
+ }
+ else if ( !strcmp(endBoundString, "QUADS" ) ) {
+ primitives = v / 4; // number of quads
+ for ( int cnt0 = -primitives; cnt0 < 0; cnt0++ ) {
+ int offset = cnt0 * 4;
+ fprintf(fpOut, "f");
+ if ( v && !vt && !vn ) {
+ fprintf(fpOut, " %d", offset + 3 );
+ fprintf(fpOut, " %d", offset + 2 );
+ fprintf(fpOut, " %d", offset + 1 );
+ fprintf(fpOut, " %d", offset );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", offset + 3, offset + 3 );
+ fprintf(fpOut, " %d//%d", offset + 2, offset + 2 );
+ fprintf(fpOut, " %d//%d", offset + 1, offset + 1 );
+ fprintf(fpOut, " %d//%d", offset, offset );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", offset + 3, offset + 3 );
+ fprintf(fpOut, " %d/%d", offset + 2, offset + 2 );
+ fprintf(fpOut, " %d/%d", offset + 1, offset + 1 );
+ fprintf(fpOut, " %d/%d", offset, offset );
+ }
+ else { // ( v && vt && vn )
+ fprintf(fpOut, " %d/%d/%d", offset + 3, offset + 3, offset + 3 );
+ fprintf(fpOut, " %d/%d/%d", offset + 2, offset + 2, offset + 2 );
+ fprintf(fpOut, " %d/%d/%d", offset + 1, offset + 1, offset + 1 );
+ fprintf(fpOut, " %d/%d/%d", offset, offset, offset );
+ }
+ fprintf(fpOut, "\n");
+ }
+ }
+ else if ( !strcmp(endBoundString, "QUAD_STRIP" ) ) {
+ primitives = ( v / 2 ) - 1; // number of quads
+ // first quad
+ fprintf(fpOut, "f");
+ if ( v && !vt && !vn ) {
+ fprintf(fpOut, " %d", -primitives * 2 - 2 );
+ fprintf(fpOut, " %d", -primitives * 2 - 1 );
+ fprintf(fpOut, " %d", -primitives * 2 + 1 );
+ fprintf(fpOut, " %d", -primitives * 2 + 0 );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", -primitives * 2 - 2, -primitives * 2 - 2 );
+ fprintf(fpOut, " %d//%d", -primitives * 2 - 1, -primitives * 2 - 1 );
+ fprintf(fpOut, " %d//%d", -primitives * 2 + 0, -primitives * 2 + 0 );
+ fprintf(fpOut, " %d//%d", -primitives * 2 + 1, -primitives * 2 + 1 );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", -primitives * 2 - 2, -primitives * 2 - 2 );
+ fprintf(fpOut, " %d/%d", -primitives * 2 - 1, -primitives * 2 - 1 );
+ fprintf(fpOut, " %d/%d", -primitives * 2 + 0, -primitives * 2 + 0 );
+ fprintf(fpOut, " %d/%d", -primitives * 2 + 1, -primitives * 2 + 1 );
+ }
+ else { // ( v && vt && vn )
+ fprintf(fpOut, " %d/%d/%d", -primitives * 2 - 2, -primitives * 2 - 2, -primitives * 2 - 2 );
+ fprintf(fpOut, " %d/%d/%d", -primitives * 2 - 1, -primitives * 2 - 1, -primitives * 2 - 1 );
+ fprintf(fpOut, " %d/%d/%d", -primitives * 2 + 0, -primitives * 2 + 0, -primitives * 2 + 0 );
+ fprintf(fpOut, " %d/%d/%d", -primitives * 2 + 1, -primitives * 2 + 1, -primitives * 2 + 1 );
+ }
+ fprintf(fpOut, "\n");
+
+ // next quads
+ for ( int cnt0 = primitives - 1; cnt0 > 0; cnt0-- ) {
+ int offset = -cnt0 * 2;
+ fprintf(fpOut, "f");
+ if ( v && !vt && !vn ) {
+ fprintf(fpOut, " %d", offset - 2 );
+ fprintf(fpOut, " %d", offset - 1 );
+ fprintf(fpOut, " %d", offset + 1 );
+ fprintf(fpOut, " %d", offset + 0 );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", offset - 2, offset - 2 );
+ fprintf(fpOut, " %d//%d", offset - 1, offset - 1 );
+ fprintf(fpOut, " %d//%d", offset + 0, offset + 0 );
+ fprintf(fpOut, " %d//%d", offset + 1, offset + 1 );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", offset - 2, offset - 2 );
+ fprintf(fpOut, " %d/%d", offset - 1, offset - 1 );
+ fprintf(fpOut, " %d/%d", offset + 0, offset + 0 );
+ fprintf(fpOut, " %d/%d", offset + 1, offset + 1 );
+ }
+ else { // ( v && vt && vn )
+ fprintf(fpOut, " %d/%d/%d", offset - 2, offset - 2, offset - 2 );
+ fprintf(fpOut, " %d/%d/%d", offset - 1, offset - 1, offset - 1 );
+ fprintf(fpOut, " %d/%d/%d", offset + 0, offset + 0, offset + 0 );
+ fprintf(fpOut, " %d/%d/%d", offset + 1, offset + 1, offset + 1 );
+ }
+ fprintf(fpOut, "\n");
+ }
+ }
+ else if ( !strcmp(endBoundString, "POLYGON" ) ) {
+ primitives = v;
+ fprintf(fpOut, "f");
+ for ( int cnt0 = 1; cnt0 <= primitives; cnt0++ ) {
+ int offset = -cnt0;
+ if ( v && vt && vn ) {
+ fprintf(fpOut, " %d/%d/%d", offset, offset, offset );
+ }
+ else if ( v && !vt && vn ) {
+ fprintf(fpOut, " %d//%d", offset, offset );
+ }
+ else if ( v && vt && !vn ) {
+ fprintf(fpOut, " %d/%d", offset, offset );
+ }
+ else { // ( v && !vt && !vn )
+ fprintf(fpOut, " %d", offset );
+ }
+ }
+ fprintf(fpOut, "\n");
+ primitives = 1; // for printf output
+ }
+ ////////////////////////////////////////////////////printf("%d %s with %d v, %d vt, %d vn\n", primitives, endBoundString, v, vt, vn);
+
+
+
+
+ /*
+ fprintf( fpOut, "****************\n");
+ fseek(fpIn, startPosition, SEEK_SET);
+ while ( ftell(fpIn) <= endPosition ) {
+ fgets ( buffer, MAX_LENGTH, fpIn );
+ fprintf( fpOut, "%s", buffer );
+ }
+ fprintf( fpOut, "****************\n");
+ */
+
+ return primitives;
+}
+
+int v3dToObj( const char *source, const char *dest, UserModifier *userModifier ) {
+ FILE *fpIn, *fpOut, *fpMtl, *fopen();
+ char lineBuffer[MAX_LENGTH + 1], buffer[MAXPATHLENGTH + MAX_LENGTH + 7], tagName[MAX_LENGTH + 1], parameter1[MAX_LENGTH + 1], textureName[MAX_LENGTH + 1], texturePath[MAX_LENGTH + 1];
+ char *path = NULL, *baseName = NULL, *extension = NULL; // for output files names
+ char textureOrient[MAX_LENGTH + 1];
+ char modelType[22 + 1]; // longest model type: 'aileron_elevator_right'
+ long fileSize = 0;
+ unsigned int points = 0, lines = 0, line_strip = 0, line_loop = 0, triangle = 0, triangle_strip = 0, triangle_fan = 0, quads = 0, quad_strip = 0, polygon = 0, color = 0, texture = 0, objectNr = 0;
+
+ modifier.tx = 0;
+ modifier.ty = 0;
+ modifier.tz = 0;
+ modifier.rh = 0;
+ modifier.rp = 0;
+ modifier.rb = 0;
+
+ textureName[0] = '\0'; // no texture at startup
+ textureOrient[0] = '\0'; // no texture at startup
+
+ fileSize = file_size(source);
+ printf("*.3d file size: %ld bytes.\n", fileSize);
+
+ if ((fpIn = fopen(source, "r")) == NULL) {
+ perror(source);
+ return -1;
+ }
+
+ // Generate output file name
+ scanFileName( dest, &path, &baseName, &extension );
+
+ if ( path[0] == '\0' ) // if no path
+ sprintf( buffer, "%s.obj", baseName );
+ else
+ sprintf( buffer, "%s/%s.obj", path, baseName );
+
+ if ((fpOut = fopen(buffer, "w")) == NULL) {
+ perror(buffer);
+ return -1;
+ }
+
+ if ( path[0] == '\0' ) // if no path
+ sprintf( buffer, "%s.mtl", baseName );
+ else
+ sprintf( buffer, "%s/%s.mtl", path, baseName );
+
+ if ((fpMtl = fopen(buffer, "w")) == NULL) {
+ perror(buffer);
+ return -1;
+ }
+ else {
+ fprintf(fpOut, "mtllib ./%s.mtl\n", baseName); // write material file name in fpOut
+ }
+
+ /////////////////////////////fprintf(fpOut, "g %s\n", baseName); // create a group and give it 3d model name FIXME: can be an issue if model name contains one ore more spaces!
+
+ /*
+ // count file lines, then create as many verticies as lines number (not efficient but easier way to do the job...)
+ long counter = 0;
+ while ( fgets(lineBuffer, MAX_LENGTH, fpIn) ) counter++;
+ printf("Nombre de lignes du fichier: %ld.\n", counter);
+ struct Vertex {
+ long x;
+ long y;
+ long z;
+ };
+ struct Vertex vertex[counter];
+ rewind(fpIn);
+ */
+
+ while ( fgets(lineBuffer, MAX_LENGTH, fpIn) ) {
+ strcpy( buffer, ""); // clear buffer
+ sscanf( lineBuffer, "%s %s", buffer, parameter1 );
+ strtoupper( tagName, buffer );
+ strtoupper( parameter1, parameter1 );
+ if ( tagName[0] == '#' ) { // it's a comment: just copy it
+ fprintf(fpOut, "%s", lineBuffer);
+ }
+ else if ( !strcmp(tagName, "BEGIN_POINTS") ) {
+ fprintf(fpOut, "#*.3d begin_points\n");
+ points += readV3dVerticies ( fpIn, "END_POINTS", fpOut, "", userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_points\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_LINES") ) {
+ fprintf(fpOut, "#*.3d begin_lines\n");
+ lines += readV3dVerticies ( fpIn, "END_LINES", fpOut, "", userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_lines\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_LINE_STRIP") ) {
+ fprintf(fpOut, "#*.3d begin_line_strip\n");
+ line_strip += readV3dVerticies ( fpIn, "END_LINE_STRIP", fpOut, "", userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_line_strip\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_LINE_LOOP") ) {
+ fprintf(fpOut, "#*.3d begin_line_loop\n");
+ line_loop += readV3dVerticies ( fpIn, "END_LINE_LOOP", fpOut, "", userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_line_loop\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_TRIANGLES") ) {
+ fprintf(fpOut, "#*.3d begin_triangles\n");
+ triangle += readV3dVerticies ( fpIn, "END_TRIANGLES", fpOut, textureOrient, userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_triangles\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_TRIANGLE_STRIP") ) {
+ fprintf(fpOut, "#*.3d begin_triangle_strip\n");
+ triangle_strip += readV3dVerticies ( fpIn, "END_TRIANGLE_STRIP", fpOut, textureOrient, userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_triangle_strip\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_TRIANGLE_FAN") ) {
+ fprintf(fpOut, "#*.3d begin_triangle_fan\n");
+ triangle_fan += readV3dVerticies ( fpIn, "END_TRIANGLE_FAN", fpOut, textureOrient, userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_triangle_fan\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_QUADS") ) {
+ fprintf(fpOut, "#*.3d begin_quads\n");
+ quads += readV3dVerticies ( fpIn, "END_QUADS", fpOut, textureOrient, userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_quads\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_QUAD_STRIP") ) {
+ fprintf(fpOut, "#*.3d begin_quad_strip\n");
+ quad_strip += readV3dVerticies ( fpIn, "END_QUAD_STRIP", fpOut, textureOrient, userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_quad_strip\n");
+ }
+ else if ( !strcmp(tagName, "BEGIN_POLYGON") ) {
+ fprintf(fpOut, "#*.3d begin_polygon\n");
+ polygon += readV3dVerticies ( fpIn, "END_POLYGON", fpOut, textureOrient, userModifier, modifier );
+ fprintf(fpOut, "#*.3d end_polygon\n");
+ }
+ else if ( !strcmp(tagName, "COLOR") ) {
+ // create material in *.mtl file
+ //char red[20], green[20], blue[20], transparency[20], ambient[20], diffuse[20], specular[20], shininess[20], emission[20];
+ double red = 0, green = 0, blue = 0, transparency = 0, ambient = 1, diffuse = 1, specular = 1, shininess = 1, emission = 1;
+
+ // TODO FIXME check if material already exists before creating a new one !!! If not, a VERY big quantity of materials can be generated and Blender don't like it!!!
+ fprintf(fpMtl, "newmtl color_%d\n", color);
+
+ if ( sscanf( lineBuffer, " %*s %lf %lf %lf %lf %lf %lf %lf %lf %lf", &red, &green, &blue, &transparency, &ambient, &diffuse, &specular, &shininess, &emission ) ) {
+ /*
+ fprintf(fpMtl, "Ka %lf %lf %lf\n", red * ambient, green * ambient, blue * ambient);
+ fprintf(fpMtl, "Ks %lf %lf %lf\n", red * specular, green * specular, blue * specular);
+ */
+ fprintf(fpMtl, "Ka 0.200000 0.200000 0.200000\n");
+ fprintf(fpMtl, "Ks 1.000000 1.000000 1.000000\n");
+ fprintf(fpMtl, "Kd %lf %lf %lf\n", red * diffuse, green * diffuse, blue * diffuse);
+
+ // Ka r g b : The Ka statement specifies the ambient reflectivity using RGB values.
+ // Kd r g b : The Kd statement specifies the diffuse reflectivity using RGB values.
+ // Ks r g b : The Ks statement specifies the specular reflectivity using RGB values.
+ // Illumination Properties that are turned on in the
+ // model Property Editor
+ // 0 Color on and Ambient off
+ // 1 Color on and Ambient on
+ // 2 Highlight on
+ // 3 Reflection on and Ray trace on
+ // 4 Transparency: Glass on
+ // Reflection: Ray trace on
+ // 5 Reflection: Fresnel on and Ray trace on
+ // 6 Transparency: Refraction on
+ // Reflection: Fresnel off and Ray trace on
+ // 7 Transparency: Refraction on
+ // Reflection: Fresnel on and Ray trace on
+ // 8 Reflection on and Ray trace off
+ // 9 Transparency: Glass on
+ // Reflection: Ray trace off
+ // 10 Casts shadows onto invisible surfaces
+
+ // TODO ? : implement transparency, ambient, diffuse, specular, shininess and emission in *.mtl file
+ }
+ else { // create a default color
+ fprintf(fpMtl, "Kd 0.5 0.5 0.5\n"); // grey
+ }
+ fprintf(fpMtl, "illum 1\n");
+
+ // specify material in *.obj file
+ fprintf(fpOut, "usemtl color_%d\n", color);
+ textureName[0] = '\0';
+ textureOrient[0] = '\0';
+ color++;
+ }
+ else if ( !strcmp(tagName, "BEGIN_MODEL") && !strcmp(parameter1, "SHADOW") ) {
+ // create material in *.mtl file
+ fprintf(fpMtl, "newmtl color_%d\n", color);
+ // create a default color
+ fprintf(fpMtl, "Kd 0.000000 0.000000 0.000000\n");
+ fprintf(fpMtl, "illum 0\n");
+ // specify material in *.obj file
+ fprintf(fpOut, "#*.3d %s", lineBuffer); // print "begin_model shadow" as a comment
+ sscanf( lineBuffer, "%*s %s", modelType );
+ // We can't use only the model type as object name because a *.3d file can contain more than one "begin_model" statement with the same model type (example: begin_model rotor)
+ fprintf( fpOut, "o obj%d_%s\n", objectNr++, modelType );
+ fprintf(fpOut, "usemtl color_%d\n", color);
+ textureName[0] = '\0';
+ textureOrient[0] = '\0';
+ color++;
+ }
+ else if ( !strcmp(tagName, "BEGIN_MODEL") ) { // create an object and give it model type as name
+ sscanf( lineBuffer, "%*s %s", modelType );
+ // We can't use only the model type as object name because a *.3d file can contain more than one "begin_model" statement with the same model type (example: begin_model rotor)
+ fprintf( fpOut, "o obj%d_%s\n", objectNr++, modelType );
+ }
+ else if ( !strcmp(tagName, "END_MODEL") ) {
+ modifier.tx = 0;
+ modifier.ty = 0;
+ modifier.tz = 0;
+ modifier.rh = 0;
+ modifier.rp = 0;
+ modifier.rb = 0;
+ textureName[0] = '\0';
+ textureOrient[0] = '\0';
+ }
+ else if ( !strcmp(tagName, "TRANSLATE") ) {
+ sscanf( lineBuffer, "%*s %lf %lf %lf", &modifier.tx, &modifier.ty, &modifier.tz );
+ }
+ else if ( !strcmp(tagName, "UNTRANSLATE") || !strcmp(tagName, "END_MODEL") ) {
+ modifier.tx = 0;
+ modifier.ty = 0;
+ modifier.tz = 0;
+ }
+ else if ( !strcmp(tagName, "ROTATE") ) {
+ sscanf( lineBuffer, "%*s %lf %lf %lf", &modifier.rh, &modifier.rp, &modifier.rb );
+ }
+ else if ( !strcmp(tagName, "UNROTATE") || !strcmp(tagName, "END_MODEL") ) {
+ modifier.rh = 0;
+ modifier.rp = 0;
+ modifier.rb = 0;
+ }
+ else if ( !strcmp(tagName, "TEXTURE_LOAD") ) {
+ char priority[20];
+ sscanf( lineBuffer, "%*s %s %s %s", textureName, texturePath, priority );
+ fprintf(fpMtl, "newmtl %s\n", textureName);
+ fprintf(fpMtl, "Kd 1.000000 1.000000 1.000000\n");
+
+ /* fast but dirty way to modify extension */
+ int extensionPosition = strlen( texturePath ) - 3;
+ texturePath[ extensionPosition++ ] = 't';
+ texturePath[ extensionPosition++ ] = 'g';
+ texturePath[ extensionPosition ] = 'a';
+
+ fprintf(fpMtl, "map_Kd %s\n", texturePath);
+ textureName[0] = '\0';
+ textureOrient[0] = '\0';
+ texture++;
+ }
+ else if ( !strcmp(tagName, "TEXTURE_SELECT") ) {
+ sscanf( lineBuffer, "%*s %s", textureName );
+ fprintf( fpOut, "usemtl %s\n", textureName );
+
+ textureOrient[0] = '\0';
+ }
+ else if ( !strcmp(tagName, "TEXTURE_ORIENT_XY") || !strcmp(tagName, "TEXTURE_ORIENT_XZ") || !strcmp(tagName, "TEXTURE_ORIENT_YZ")) {
+ strcpy( textureOrient, lineBuffer ); // save texture_orient_ type
+ }
+ else if ( !strcmp(tagName, "TEXTURE_OFF") ) {
+ textureName[0] = '\0';
+ textureOrient[0] = '\0';
+ }
+ else {
+//printf("lineBuffer='%s'\n", lineBuffer);
+ if ( lineBuffer[0] != '\n' ) {
+ fprintf(fpOut, "#%ld: %s", lineNr, lineBuffer); // if tag is not recognized, set it as comment and start it with input file line number
+ }
+ else {
+ fprintf(fpOut, "\n");
+ }
+ //while ( (c = fgetc(fpIn)) != '\n') fprintf(fpOut, "%c", c);
+
+ }
+
+ lineNr++; // input file line counter
+ }
+ printf("%s contains:\n %d points, %d lines, %d line_strip, %d line_loop, %d triangle, %d triangle_strip, %d triangle_fan, %d quads, %d quad_strip, %d polygon, and %d color statements.\n", source, points, lines, line_strip, line_loop, triangle, triangle_strip, triangle_fan, quads, quad_strip, polygon, color);
+
+ printf("Files %s.obj and %s.mtl written to: %s directory.\n", baseName, baseName, path);
+
+ if ( path != NULL )
+ {
+ free( path );
+ path = NULL;
+ }
+ if ( baseName != NULL )
+ {
+ free( baseName );
+ baseName = NULL;
+ }
+ if ( extension != NULL )
+ {
+ free( extension );
+ extension = NULL;
+ }
+
+ fclose(fpMtl);
+ fclose(fpOut);
+ fclose(fpIn);
+ return 0;
+}