Skip to content

Commit

Permalink
- better *.obj to *.hf conversion.
Browse files Browse the repository at this point in the history
- better *.obj to *.3d face normals conversion.
- remove 'type 1' when writing *.3d file because 'type' parameter is deprecated and not used in Sar2.
- usefull informations are now written at *.3d top of file (and not only at console).
- add model rectangular/circular/spherical bounding box volume information, which can help to decide which contact detection shape is the best (lower is better...).
  • Loading branch information
jmbau committed Aug 11, 2023
1 parent a30236d commit d4203a7
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 94 deletions.
41 changes: 23 additions & 18 deletions tools/v3dconverter/readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,42 @@
+--------------------------+

* FOREWORD:
This tools is under development.
Only *.obj to *.3d conversion is fully operational at now.
Convert *.3d to *.obj or *.ac to *.3d is experimental and will only work if you're lucky, therefore please report bugs only for *.obj to *.3d conversion.
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 command line 3d models converter for Sar2 developers. Its purpose is to convert *.3d (Vertex 3d) file format from/to *.obj (WaveFront) or *.ac (AC3D) formats. It will not work for *.3d terrain files.
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 ./src/v3dconverter.c
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 do that with Blender and / or Meshlab) 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 to *.tga format.
- 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), 4*8 bits rgba color, top left origin, without RLE (Run Length Encoding) compression.
- Remember that in Sar2, a pure black color (0x000000) will be 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".
- When you load a new model in Sar2, start it 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, just don't forget to export "Materials groups" in Blender or "Texcoord / Texture file" in 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 object dimensions: always check if they are coherent for your model. If not, use "-sc" (scale) option. 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.
- 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 found on the Web 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. Of course, the better way is to move this object in your 3d editor before convert it to *.3d .
- If your *.3d model is not visible, check if alpha (transparency) parameter is set to 1.0 : look for "color" in *.3d file, alpha chanel is the fourth value. I experienced that in some *.obj files found on the Web because of their "Tr" parameter was wrongly set. V3dconverter will warn you about that.
- 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 reorient them.
- 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 quick check your model rendering in Sar2 without add it in a scenery, you can 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 will print 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 you try *.3d to *.obj conversion, Blender seems to read converted models better than Meshlab. I didn't try to understand why (as said, *.3d to *.obj conversion is experimental), and I didn't try with other 3d softwares.
- 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".
74 changes: 58 additions & 16 deletions tools/v3dconverter/src/objtohf.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ 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];
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 ***/
Expand Down Expand Up @@ -71,12 +72,11 @@ int objToHf(const char *source, const char *dest, UserModifier *userModifier ) {
double p; // pitch rotation, in degrees
double b; // bank rotation, in degrees
double scale; // scale applied by user during *.3d to *.obj conversion
int bitsWidth; // image width, in bits
int bitsHeight; // image height, in bits
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 ?
*/
Expand Down Expand Up @@ -148,6 +148,10 @@ int objToHf(const char *source, const char *dest, UserModifier *userModifier ) {

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" ) )
Expand Down Expand Up @@ -199,13 +203,13 @@ int objToHf(const char *source, const char *dest, UserModifier *userModifier ) {
else if ( !strcmp( lineBuffer, "terrain_bitsWidth" ) )
{
valueSet++;
sscanf( &lineBuffer[ cnt ], "%d", &terrainSpec.bitsWidth );
sscanf( &lineBuffer[ cnt ], "%hd", &terrainSpec.bitsWidth );
//fprintf( stdout, "terrain_bitsWidth = %d\n", terrainSpec.bitsWidth );
}
else if ( !strcmp( lineBuffer, "terrain_bitsHeight" ) )
{
valueSet++;
sscanf( &lineBuffer[ cnt ], "%d", &terrainSpec.bitsHeight );
sscanf( &lineBuffer[ cnt ], "%hd", &terrainSpec.bitsHeight );
//fprintf( stdout, "terrain_bitsHeight = %d\n", terrainSpec.bitsHeight );
}
else
Expand All @@ -223,10 +227,23 @@ int objToHf(const char *source, const char *dest, UserModifier *userModifier ) {
; // 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.
*/
Expand Down Expand Up @@ -353,7 +370,7 @@ for ( int cnt1 = cnt - 1; cnt1 >= 0; cnt1-- ) \

while ( true )
{
fprintf( stderr, "Please enter terrain scale value: " );
fprintf( stderr, "Please enter *.obj terrain scale value (\"*.obj/*.hf distances ratio\"): " );

READ_AND_CHECK_STDIN

Expand Down Expand Up @@ -399,6 +416,7 @@ for ( int cnt1 = cnt - 1; cnt1 >= 0; cnt1-- ) \
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" );

Expand Down Expand Up @@ -432,18 +450,18 @@ for ( int cnt1 = cnt - 1; cnt1 >= 0; cnt1-- ) \
hfTexHeader.xOriginH = 0;
hfTexHeader.yOriginL = 0;
hfTexHeader.yOriginH = 0;
hfTexHeader.widthL = terrainSpec.bitsWidth << 8;
hfTexHeader.widthH = terrainSpec.bitsWidth / 256;
hfTexHeader.heightL = terrainSpec.bitsHeight << 8;
hfTexHeader.heightH = terrainSpec.bitsHeight / 256;
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).
*/
uint32_t imageDataBytes = ( 256 * hfTexHeader.widthH + hfTexHeader.widthL ) * ( 256 * hfTexHeader.heightH + hfTexHeader.heightL ) * ( hfTexHeader.bitsPerPixel / 8 );
uint64_t imageDataBytes = ( 256 * hfTexHeader.widthH + hfTexHeader.widthL ) * ( 256 * hfTexHeader.heightH + hfTexHeader.heightL ) * ( hfTexHeader.bitsPerPixel / 8 );
uint8_t *tgaImageData = malloc( imageDataBytes );
if ( tgaImageData == NULL )
{
Expand All @@ -462,12 +480,24 @@ for ( int cnt1 = cnt - 1; cnt1 >= 0; cnt1-- ) \
lineNr = 1;
double perPixelXDistance = terrainSpec.sizeX / (double)terrainSpec.bitsWidth;
double perPixelYDistance = terrainSpec.sizeY / (double)terrainSpec.bitsHeight;
double perPixelZDistance = terrainSpec.maxAltitude / 255; // altitude is always an 8 bits value
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 == ""
{
Expand Down Expand Up @@ -529,10 +559,22 @@ for ( int cnt1 = cnt - 1; cnt1 >= 0; cnt1-- ) \
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;
}
}
else
;

lineNr++;
}
Expand Down
Loading

0 comments on commit d4203a7

Please sign in to comment.