diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cc9feb4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/rlk"] + path = src/rlk + url = https://github.com/rlk/obj.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fa2850..1830253 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,71 +1,85 @@ # CMakeLists.txt -# Written by JackCarterSmith, 2021 +#################################################### +# Written by JackCarterSmith, 2022 # This code is released under the RSE license. +#################################################### -cmake_minimum_required(VERSION 3.1) -cmake_policy(VERSION 3.1) + +# CMake requirement and general configuration +cmake_minimum_required(VERSION 3.12) +cmake_policy(VERSION 3.12) set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) - -# define project -add_definitions(-DCONF_NO_GL) -if(DEFINED ENV{CI}) - project(rse-map VERSION $ENV{CI_VERSION}.$ENV{CI_BUILD_NUMBER} DESCRIPTION "RogueSquadron Extractor - Map" LANGUAGES C) - set(RSE_MAP_NAME $ENV{CI_OUTPUT_NAME}-${PROJECT_VERSION}) -else() - project(rse-map VERSION 1.0.0 DESCRIPTION "RogueSquadron Extractor - Map" LANGUAGES C) - set(RSE_MAP_NAME RSE_Map-${PROJECT_VERSION}) +if(DEFINED ENV{MS_COMPATIBLE}) + set(CMAKE_GNUtoMS ON) # Enable compatibility level to exported libraries endif() -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/config.h @ONLY) include(CheckIncludeFile) include(CheckCSourceCompiles) -# needed packages +add_definitions(-DCONF_NO_GL) # Used for obj-lib to not compile GL part +#add_definitions(-DNO_PNG_SUPPORT) # Can be used to disable code support for PNG exporting + +# Project definition +if(DEFINED ENV{CI}) # Jenkins CI integration mode + project(rse-terrain VERSION $ENV{CI_VERSION}.$ENV{CI_BUILD_NUMBER} DESCRIPTION "RogueSquadron Extractor - Terrain" LANGUAGES C) + set(RSE_TER_NAME $ENV{CI_OUTPUT_NAME}-${PROJECT_VERSION}) +else() # Standalone project mode, should not be used for release. + project(rse-terrain VERSION 1.0.0 DESCRIPTION "RogueSquadron Extractor - Terrain" LANGUAGES C) + set(RSE_TER_NAME RSE_Terrain-${PROJECT_VERSION}) +endif() +# Push compile infos to source +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/config.h @ONLY) + + +# Import needed packages and references their include path find_package(ZLIB 1.2.11 EXACT REQUIRED) include_directories(${ZLIB_INCLUDE_DIR}) find_package(PNG 1.6.37 EXACT REQUIRED) include_directories(${PNG_INCLUDE_DIR}) -#find_package(GLEW REQUIRED) +#find_package(GLEW REQUIRED) # Enable when GL rendering is ready #include_directories(${GLEW_INCLUDE_DIR}) -# define src/headers files -FILE(GLOB_RECURSE RSE_MAP_SRCS src/*.c) -FILE(GLOB_RECURSE RSE_MAP_HRDS src/*.h) -SOURCE_GROUP("Source Files" FILES ${RSE_MAP_SRCS}) -SOURCE_GROUP("Header Files" FILES ${RSE_MAP_HRDS}) +# Define src/headers files +FILE(GLOB_RECURSE RSE_TER_SOURCES src/*.c) +FILE(GLOB_RECURSE RSE_TER_HEADERS src/*.h) +SOURCE_GROUP("Source Files" FILES ${RSE_TER_SOURCES}) +SOURCE_GROUP("Header Files" FILES ${RSE_TER_HEADERS}) -# begin building RSE-Map -#set(CMAKE_BUILD_TYPE Debug) +# Building instructions for RSE-Terrain +if(DEFINED ENV{RSE-WS}) + set(CMAKE_BUILD_TYPE DEBUG) +endif() #include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -add_executable(rse-map ${RSE_MAP_SRCS} ${RSE_MAP_HRDS}) -#set_property(TARGET rse-map PROPERTY C_STANDARD 99) -set_target_properties(rse-map PROPERTIES OUTPUT_NAME ${RSE_MAP_NAME}) +add_executable(rse-terrain ${RSE_TER_SOURCES} ${RSE_TER_HEADERS}) # Set the inputs for the compiler (srcs&hrds) +set_property(TARGET rse-terrain PROPERTY C_STANDARD 90) +set_target_properties(rse-terrain PROPERTIES OUTPUT_NAME ${RSE_TER_NAME}) # Define the executable file name +# Link externals libraries to the linker if(MSVC) # msvc does not append 'lib' - do it here to have consistent name - #set_target_properties(rse-map PROPERTIES PREFIX "lib") - set_target_properties(rse-map PROPERTIES IMPORT_PREFIX "lib") -endif() -if(MSVC) - target_link_libraries(rse-map ${ZLIB_LIBRARIES} ${PNG_LIBRARIES}) + #set_target_properties(rse-terrain PROPERTIES PREFIX "lib") + set_target_properties(rse-terrain PROPERTIES IMPORT_PREFIX "lib") + target_link_libraries(rse-terrain ${ZLIB_LIBRARIES} ${PNG_LIBRARIES} ${GLEW_LIBRARIES}) else() - target_link_libraries(rse-map ${ZLIB_LIBRARIES} ${PNG_LIBRARIES} m) + target_link_libraries(rse-terrain ${ZLIB_LIBRARIES} ${PNG_LIBRARIES} ${GLEW_LIBRARIES} m) endif() -# add GPG signature command + +# GPG signature custom command #add_custom_command( # OUTPUT "" # COMMAND gpg --batch --detach-sign -# -o ${RSE_MAP_NAME}_${CI_SYS_TARGET}.gpg -# ${RSE_MAP_NAME} -# DEPENDS ${RSE_MAP_NAME} +# -o ${RSE_TER_NAME}_${CI_SYS_TARGET}.gpg +# ${RSE_TER_NAME} +# DEPENDS ${RSE_TER_NAME} # VERBATIM #) -# install executable -install(TARGETS rse-map + +# Install project executable +install(TARGETS rse-terrain RUNTIME DESTINATION bin ) \ No newline at end of file diff --git a/README.md b/README.md index c660ed8..ec751c9 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,71 @@ -# RogueSquadron Extractor - MAP module +# RogueSquadron Extractor - TERRAIN module Inspired by the work of **dpethes** (https://github.com/dpethes/rerogue) :clap: This set of git repos are a compilation of tools coded in C to make RS modding far more than a dream! The collection consist of few independants modules, each of them deals with specific data like sound, textures, heightmaps, etc... -All modules are independants. This is the **'MAP'** module. +All modules are independants. This is the **'TERRAIN'** module. -:exclamation: **Master branch is ugly for now and should not be used, please take only released versions.** :exclamation: +[![Build Status](https://ci.jcsmith.fr/job/JCS-Prod/job/RSE-Terrain/job/master/badge/icon)](https://ci.jcsmith.fr/job/JCS-Prod/job/RSE-Terrain/job/master/) -## MAP MODULE +## TERRAIN MODULE -It's extract texture datas from Rogue Squadron 3D (PC) game map files. \ No newline at end of file +It's extract terrain datas from Rogue Squadron 3D (PC) game map files (hmp). + +This module can do: +- Interpolate 3D model mesh of the terrain, +- Export it as OBJ model file (pretty heavy (~15MB), not to be use direcly for display), +- Export an heightmap-like PNG, +- Multiple inputs files. + +## TODO + +- Add textures to terrain. +- Adding LOD method to optimize datas manipulation and rendering. +- Rewrite OBJ lib... +- Discover all last unknowns fields, etc. + +### Using + +`RSE-Terrain_"version" [options] ` or you can simply drag&drop HOB files on it. + +A futur main program can extract all HOB files directly from DAT file. +Due to issue with copyrights, I can't provide samples... You need to extract HOB files yourself. + + + +### Options + +- -h Print this message +- -v,-vv Activate verbose/debug output mode respectively +- -subdir Export outputs to a sub-directory +- -neg Negative heightmap output + +### Dependencies + +- obj-lib: as obj file exporter. (https://github.com/rlk/obj) + +### Compiling + +:mega: **MSVC compatibility is in progress. Not working for now but you can try to fix error by yourself.** + +You can compile on both Windows (MinGW) or native Linux system thanks to CMake. + +To compile, just clone (**don't forget git submodules**) and launch cmake: + +```shell +cmake . +make +make install +``` + +We can also use cross-compilation (after installing `mingw64` and `cmake` packages on your distrib): + +```shell +mkdir build && cd build +cmake -DGNU_HOST=x86_64-w64-mingw32 \ + -DCMAKE_TOOLCHAIN_FILE=../mingw_cross_toolchain.cmake \ + .. +cmake --build . +``` diff --git a/src/Image_Exporter.c b/src/Image_Exporter.c deleted file mode 100644 index dd43284..0000000 --- a/src/Image_Exporter.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "Image_Exporter.h" - - -int saveToPNG(RS_IMAGE *img, char *tex_path, char *hmt_fileName) { - if (tex_path == NULL || img == NULL) return EXIT_FAILURE; - char export_path[128]; - FILE *_png_f = NULL; - png_structp png_ptr = NULL; - png_infop info_ptr = NULL; - size_t x,y; - png_byte **row_ptrs = NULL; - PIXEL_A *pixel = NULL; - //int pixel_size = 3; - //int depth = 8; //bit par color channel (RGB) - - if (_options & OUTPUT_DIR) { - strcpy(export_path, hmt_fileName); - #ifdef _WIN32 - strcat(export_path, "-out\\"); - #else - strcat(export_path, "-out/"); - #endif - strcat(export_path, tex_path); - } else { - strcpy(export_path, tex_path); - } - strcat(export_path, ".png"); - _png_f = fopen(export_path, "wb"); - if (_png_f == NULL) return EXIT_FAILURE; - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png_ptr == NULL) { - fclose(_png_f); - return EXIT_FAILURE; - } - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) { - fclose(_png_f); - return EXIT_FAILURE; - } - - // Set image attributes - png_set_IHDR(png_ptr, info_ptr, img->width, img->height, 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - // Init PNG datas - row_ptrs = png_malloc(png_ptr, img->height * sizeof(png_byte *)); - for (y=0; yheight; y++) { - png_byte *row = png_malloc(png_ptr, img->width*sizeof(PIXEL_A)); - row_ptrs[y] = row; - for (x=0; xwidth; x++) { - pixel = pixelAt(img, x , y); - if(pixel == NULL) return EXIT_FAILURE; - - *row++ = pixel->_red; - *row++ = pixel->_green; - *row++ = pixel->_blue; - *row++ = pixel->_alpha; - } - } - - png_init_io(png_ptr, _png_f); - png_set_rows(png_ptr, info_ptr, row_ptrs); - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); - - for (y=0; yheight; y++) { - png_free(png_ptr, row_ptrs[y]); - } - png_free(png_ptr, row_ptrs); - png_destroy_write_struct(&png_ptr, &info_ptr); - fclose(_png_f); - - return EXIT_SUCCESS; -} diff --git a/src/Image_Exporter.h b/src/Image_Exporter.h deleted file mode 100644 index d0ff59f..0000000 --- a/src/Image_Exporter.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef IMAGE_EXPORTER_H_ -#define IMAGE_EXPORTER_H_ - -#include "options.h" -#include "RS_images.h" -#include -#include - - -int saveToPNG(RS_IMAGE *img, char *tex_name, char *hmt_fileName); - -#endif diff --git a/src/Map-Extractor.c b/src/Map-Extractor.c deleted file mode 100644 index 796a06d..0000000 --- a/src/Map-Extractor.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - ================================================================================ - Name : Map-Extractor.c - Author : JackCarterSmith - License : GPL-v3.0 - Description : DAT textures extractor to PNG format with enhanced function in C - ================================================================================ - */ - -#include "Map-Extractor.h" - - -int _options; // Global options settings variable - -int main(int argc, char *argv[]) { - // Init buffer vars - HMT_FILE *hmt_fdatas = NULL; - int file_index; - - printf("\n*** RogueSquadron Extractor (RSE) - MAP module - v%s ***\n", VERSION); - - // Check if filenames arguments exist - if (argc < 2) { - printf("\n[ERR] No input file/commands specified!\n"); - dispHelp(); - return EXIT_FAILURE; //TODO: implement own error codes system - } - _options = checkArgs(argv, argc); // Analyse program arguments - if (_options == -1) return EXIT_SUCCESS; - - // Do the work - for (file_index=(_options >> 8) & 0xFF; file_index 1) { - for (i=1; itexture_count > 0) { - if (_options & OUTPUT_DIR) createSubDir(filename); - for (i=0; itexture_count; i++) { - switch (hmt_f->textures_list[i].image.type_) { - case 0: - case 1: - case 3: - case 4: - case 5: - if (saveToPNG(&(hmt_f->textures_list[i].image), hmt_f->textures_list[i].name, filename)) { - printf("[ERR] Failed saving image file: %s\n", hmt_f->textures_list[i].name); - return EXIT_FAILURE; - } else printf("[INFO] Saved image file: %s\n", hmt_f->textures_list[i].name); - break; - default: - printf("[WARN] Can't export %s ! Image type %d not currently supported! (WIP)\n", hmt_f->textures_list[i].name, hmt_f->textures_list[i].image.type_); - } - - } - } - - return EXIT_SUCCESS; -} - -void dispHelp() { - printf("\n"); - printf("Options:\n -h Print this message\n -v Activate verbose console output\n -no-subdir Extract textures inside current folder\n"); - printf("\n"); - printf("Usage: RSE-Texture_%s [options] \n", VERSION); - printf("\n"); -} diff --git a/src/Map-Extractor.h b/src/Map-Extractor.h deleted file mode 100644 index 1311701..0000000 --- a/src/Map-Extractor.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef MAP_EXTRACTOR_H_ -#define MAP_EXTRACTOR_H_ - -#include -#include -#include -#if defined(_WIN32) - #include -#else - #include - #include -#endif -#include "config.h" -#include "options.h" -#include "HMT_Parser.h" -#include "RS_images.h" -#include "Image_Exporter.h" - - -void createSubDir(char *dirName); -int checkArgs(char *args[], int arg_nbr); -HMT_FILE *extractDatasFromHMT(char* hmt_filename); -int exportTextures(HMT_FILE *hmt_f, char *filename); -void dispHelp(); - -#endif diff --git a/src/Terrain-Extractor.c b/src/Terrain-Extractor.c new file mode 100644 index 0000000..01bd59f --- /dev/null +++ b/src/Terrain-Extractor.c @@ -0,0 +1,168 @@ +/** + * \file Terrain-Extractor.c + * \date 31/07/2022 + * \author JackCarterSmith + * \copyright GPL-v3.0 + * \brief Terrain file (hmp) parser with option to export to both Waveform OBJ format and grey-scale PNG heightmap. + */ + +#include +#include +#include +#if defined(_WIN32) + #include +#else + #include + #include +#endif +#include "errors_types.h" +#include "config.h" +#include "options.h" +#include "hmp_struct.h" +#include "hmp_parser.h" +#include "hmp_export.h" + + +/* + * Internal functions declarations + */ + +static unsigned int mainProcess(int args_cnt, char* args_value[], T_PROG_OPTIONS* opt_ptr); +static void createSubDir(char *dirName); +static unsigned char checkInputArgs(T_PROG_OPTIONS* opt_ptr, int p_arg_nbr, char* p_args[]); +static void dispHelp(); + + +/* + * - MAIN - + */ +int main(int argc, char *argv[]) { + T_PROG_OPTIONS _opts; + unsigned char p; + + // Hello world! + printf("\n*** RogueSquadron Extractor (RSE) - TERRAIN module - v%s ***\n", VERSION); + + // Check for arguments + if (argc < 2) { + printf("\n[ERR] No input file/commands specified!\n"); + dispHelp(); + return ERROR_ARGS_NULL; + } + + // Create options for programs according to user's arguments. + p = checkInputArgs(&_opts, argc, argv); + if ( p == ERROR_GENERIC ) return NO_ERROR; + else if ( p != NO_ERROR ) return p; + + return mainProcess(argc, argv, &_opts); +} + + +/* + * Private functions definition + */ + +static unsigned int mainProcess(int args_cnt, char* args_value[], T_PROG_OPTIONS* p_opts) { + unsigned short file_index; + T_TERRAIN* terrainStruct = NULL; + + // Manage multiple inputs files + for ( file_index = p_opts->input_files_cnt; file_index < args_cnt; file_index++) + { + printf("\n=============================================\n[INFO] - Parsing file: %s ...\n", args_value[file_index]); + terrainStruct = calloc(1, sizeof(T_TERRAIN)); + // Parse data from HOB file and put in T_HOB structure. + if (parseHMPFile(args_value[file_index], terrainStruct, p_opts) != NO_ERROR) { + printf("[ERR] Failed to parse datas from %s\n", args_value[file_index]); + free(terrainStruct); + return ERROR_PROCESS; + } + + if (p_opts->output_dir) createSubDir(args_value[file_index]); + +#ifndef NO_PNG_SUPPORT + if (exportHeightmapPNG(terrainStruct, args_value[file_index], p_opts) != NO_ERROR) + printf("[ERR] Failed to export heightmap to PNG format!\n"); + else + printf("[INFO] Successfully exported heightmap to PNG format.\n"); +#endif + + if (exportHeightmapOBJ(terrainStruct, args_value[file_index], p_opts) != NO_ERROR) + printf("[ERR] Failed to export terrain in OBJ format!\n"); + else + printf("[INFO] Successfully exported terrain in OBJ format.\n"); + + cleanUpResources(terrainStruct); + } + + return NO_ERROR; +} + +static unsigned char checkInputArgs(T_PROG_OPTIONS* opt_ptr, int p_arg_nbr, char* p_args[]) { + char test[256]; + int i; + + // Set default options + opt_ptr->raw = 0; + + if (p_arg_nbr > 1) { + for ( i = 1; i < p_arg_nbr; i++) { + strcpy(test, p_args[i]); + if (p_args[i][0] != '-') break; + if (strcmp(p_args[i], "-h") == 0) { + dispHelp(); + return ERROR_GENERIC; + } else if (strcmp(p_args[i], "-v") == 0) { + opt_ptr->verbose_mode = 1; + printf("[OPTN] Verbose enabled.\n"); + } else if (strcmp(p_args[i], "-vv") == 0) { + opt_ptr->verbose_mode = 1; + opt_ptr->debug_mode = 1; + printf("[OPTN] Debug enabled.\n"); + } else if (strcmp(p_args[i], "-vvv") == 0) { + opt_ptr->verbose_mode = 1; + opt_ptr->debug_mode = 1; + opt_ptr->god_mode = 1; + printf("[OPTN] God damn it!\n"); + } else if (strcmp(p_args[i], "-subdir") == 0) { + opt_ptr->output_dir = 0; + printf("[OPTN] Export to sub-directory.\n"); + } else if (strcmp(p_args[i], "-neg") == 0) { + opt_ptr->inverted_HM = 1; + printf("[OPTN] Negative heightmap output mode.\n"); + } else { + printf("[ERR] Unknown option: %s\n", p_args[i]); + } + } + + opt_ptr->input_files_cnt = i; + return NO_ERROR; + } + + return ERROR_ARGS_NULL; +} + +static void createSubDir(char *dirName) { + if (dirName == NULL) return; + char _dir[260]; //TODO: Change directory management + strcpy(_dir, dirName); + strcat(_dir, "-out"); + + #ifdef _WIN32 + CreateDirectory(_dir, NULL); + #else + mkdir(_dir, 0755); + #endif +} + +static void dispHelp() { + printf("\n"); + printf("Options:\n -h Print this message\n"); + printf(" -v -vv Activate verbose console output\n"); + printf(" -subdir Export output to a sub-directory\n"); + printf(" -neg Negative heightmap output\n"); + printf("\n"); + printf("Usage: RSE-Terrain_%s [options] \n", VERSION); + printf("\n"); +} diff --git a/src/errors_types.h b/src/errors_types.h new file mode 100644 index 0000000..6d101ab --- /dev/null +++ b/src/errors_types.h @@ -0,0 +1,28 @@ +/* + * errors_types.h + * + * Created on: 26 juil. 2022 + * Author: JackCarterSmith + */ + +//#include "error.h" //TODO: use it as base for error ID + + +#ifndef SRC_ERRORS_TYPES_H_ +#define SRC_ERRORS_TYPES_H_ + +#ifdef NO_ERROR +#undef NO_ERROR +#endif +#define NO_ERROR 0 +#define ERROR_GENERIC 1 +#define ERROR_MEMORY 2 +#define ERROR_IO 3 +#define ERROR_PROCESS 4 + +#define ERROR_ARGS_NULL 10 +#define ERROR_ARGS_RANGE 11 + +#define ERROR_REALITY_BROKED -1 + +#endif /* SRC_ERRORS_TYPES_H_ */ diff --git a/src/hmp_export.c b/src/hmp_export.c new file mode 100644 index 0000000..1f211e7 --- /dev/null +++ b/src/hmp_export.c @@ -0,0 +1,156 @@ +/** + * \file hmp_export.c + * \date 02/08/2022 + * \author JackCarterSmith + * \copyright GPL-v3.0 + * \brief Export datas to heightmap PNG and Waveform OBJ format. + */ + +#include +#include +#include +#ifndef NO_PNG_SUPPORT +#include +#include +#endif +#include "rlk/obj.h" +#include "errors_types.h" +#include "options.h" +#include "hmp_struct.h" +#include "hmp_export.h" + + +#ifndef NO_PNG_SUPPORT +unsigned char exportHeightmapPNG(const T_TERRAIN* terrain, const char *out_path, T_PROG_OPTIONS* p_opts) { + if (out_path == NULL || terrain == NULL) return ERROR_ARGS_NULL; + char export_path[128]; + FILE *_png_f = NULL; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + size_t x,z; + png_byte **row_ptrs = NULL; + //int pixel_size = 3; + //int depth = 8; //bit par color channel (RGB) + + strcpy(export_path, out_path); + if (p_opts->output_dir) { + #ifdef _WIN32 + strcat(export_path, "-out\\"); + #else + strcat(export_path, "-out/"); + #endif + strcat(export_path, "heightmap.png"); + } else { + strcat(export_path, "-heightmap.png"); + } + + _png_f = fopen(export_path, "wb"); + if (_png_f == NULL) return ERROR_MEMORY; + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + fclose(_png_f); + return ERROR_MEMORY; + } + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + fclose(_png_f); + return ERROR_MEMORY; + } + + // Set image attributes + png_set_IHDR(png_ptr, info_ptr, terrain->width * TERRAIN_TILE_SAMPLING, terrain->height * TERRAIN_TILE_SAMPLING, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + // Store PNG datas + row_ptrs = png_malloc(png_ptr, terrain->height * TERRAIN_TILE_SAMPLING * sizeof(png_byte *)); + for ( z = 0; z < terrain->height * TERRAIN_TILE_SAMPLING; z++ ) { + png_byte *row = png_malloc(png_ptr, terrain->width * TERRAIN_TILE_SAMPLING * sizeof(unsigned char) * 3); + row_ptrs[z] = row; + for ( x = 0; x < terrain->width * TERRAIN_TILE_SAMPLING; x++ ) { + *row++ = terrain->heightmap[x][z]; + *row++ = terrain->heightmap[x][z]; + *row++ = terrain->heightmap[x][z]; + } + } + + png_init_io(png_ptr, _png_f); + png_set_rows(png_ptr, info_ptr, row_ptrs); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + for ( z = 0; z < terrain->height * TERRAIN_TILE_SAMPLING; z++ ) { + png_free(png_ptr, row_ptrs[z]); + } + png_free(png_ptr, row_ptrs); + png_destroy_write_struct(&png_ptr, &info_ptr); + + fclose(_png_f); + + return NO_ERROR; +} +#endif + +unsigned char exportHeightmapOBJ(const T_TERRAIN* terrain_struct, const char *out_path, T_PROG_OPTIONS* p_opts) { + char objExport_path[128]; + char mtlExport_path[128]; + obj* objConstruct = NULL; + unsigned int i,j,stride; + int surfID = 0; + float vertexBuff[3] = {0}; + int indicesBuff[3] = {0}; + + if (terrain_struct == NULL || out_path == NULL) + return ERROR_ARGS_NULL; + + strcpy(objExport_path, out_path); + if (p_opts->output_dir) { + #ifdef _WIN32 + strcat(objExport_path, "-out\\"); + #else + strcat(objExport_path, "-out/"); + #endif + strcat(objExport_path, "heightmap"); + } else { + strcat(objExport_path, "-heightmap"); + } + strcpy(mtlExport_path, objExport_path); + strcat(objExport_path, ".obj\0"); + strcat(mtlExport_path, ".mtl\0"); + + objConstruct = obj_create(NULL); + + // Build face/surface material group + surfID = obj_add_surf(objConstruct); + obj_add_mtrl(objConstruct); + + // Build vertex container + for ( i = 0; i < terrain_struct->verticesmap_size; i++ ) { + vertexBuff[0] = terrain_struct->verticesmap[i].x; + vertexBuff[1] = terrain_struct->verticesmap[i].y; + vertexBuff[2] = terrain_struct->verticesmap[i].z; + obj_set_vert_v(objConstruct, obj_add_vert(objConstruct), vertexBuff); + } + + // Build indices container + // Each tile contains 2 triangles, build both of them. + // 1-2 2 + // |/ /| + // 3 3-4 + stride = terrain_struct->width * TERRAIN_TILE_SAMPLING; + for ( j = 0; j < terrain_struct->width * TERRAIN_TILE_SAMPLING - 1; j++ ) { + for ( i = 0; i < terrain_struct->width * TERRAIN_TILE_SAMPLING - 1; i++ ) { + indicesBuff[0] = j * stride + i + 1 - 1; //TODO: -1 needed to compensate the obj constructor. Really need to rewrite my own... + indicesBuff[1] = indicesBuff[0] + 1; + indicesBuff[2] = indicesBuff[0] + stride; + obj_set_poly(objConstruct, surfID, obj_add_poly(objConstruct, surfID), indicesBuff); + indicesBuff[0] = indicesBuff[1]; + indicesBuff[1] = indicesBuff[2] + 1; + //indicesBuff[2] = indicesBuff[0] + stride; + obj_set_poly(objConstruct, surfID, obj_add_poly(objConstruct, surfID), indicesBuff); + } + } + + obj_write(objConstruct, objExport_path, NULL, 8); + + obj_delete(objConstruct); + + return NO_ERROR; +} diff --git a/src/hmp_export.h b/src/hmp_export.h new file mode 100644 index 0000000..daa1303 --- /dev/null +++ b/src/hmp_export.h @@ -0,0 +1,17 @@ +/** + * \file hmp_export.h + * \date 02/08/2022 + * \author JackCarterSmith + * \copyright GPL-v3.0 + * \brief Export datas to heightmap PNG and Waveform OBJ format. + */ + +#ifndef SRC_HMP_EXPORT_H_ +#define SRC_HMP_EXPORT_H_ + +#ifndef NO_PNG_SUPPORT +unsigned char exportHeightmapPNG(const T_TERRAIN* terrain, const char* out_path, T_PROG_OPTIONS* p_opts); +#endif +unsigned char exportHeightmapOBJ(const T_TERRAIN* terrain_struct, const char *out_path, T_PROG_OPTIONS* p_opts); + +#endif /* SRC_HMP_EXPORT_H_ */ diff --git a/src/hmp_parser.c b/src/hmp_parser.c new file mode 100644 index 0000000..697a8ec --- /dev/null +++ b/src/hmp_parser.c @@ -0,0 +1,180 @@ +/** + * \file hmp_parser.c + * \date 31/07/2022 + * \author JackCarterSmith + * \copyright GPL-v3.0 + * \brief Decode terrain file (hmp) structure. + */ + +#include +#include +#include +#include "errors_types.h" +#include "options.h" +#include "hmp_struct.h" +#include "hmp_parser.h" + + +static void processTilesToHeightmap(T_TERRAIN* terrain, const T_TILE_INDICES* tiles_indices, + const T_HMPFILE_TILE* tiles, const unsigned char negativeOutput); +static void processHeighmapToVertices(T_TERRAIN* terrain, const float h_scale); + +unsigned char parseHMPFile(const char* fileName, T_TERRAIN* hmp_struct, T_PROG_OPTIONS* p_opts) { + unsigned char err = NO_ERROR; + long fileSize; + FILE* fStream = NULL; + char* memFile = NULL; + + float y_scale = 1.0; + unsigned int tiles_offset = 0; + T_TILE_INDICES* tiles_indices = NULL; + T_HMPFILE_TILE* tiles = NULL; + + if (hmp_struct != NULL && fileName != NULL) { + // Open file + fStream = fopen(fileName, "rb"); + + if (fStream != NULL) { + // Determine file size in bytes + fseek(fStream, 0, SEEK_END); + fileSize = ftell(fStream); + fseek(fStream, 0, SEEK_SET); + if (p_opts->verbose_mode) printf("[DBG] > Input file size: %ld bytes\n\n", fileSize); + + memFile = malloc(fileSize + 1); + if (memFile != NULL) { + // Copy file in RAM + fread(memFile, fileSize, 1, fStream); + fclose(fStream); + + // Get header infos + y_scale = ((T_HMPFILE_HEADER *)memFile)->height_scale; + tiles_offset = ((T_HMPFILE_HEADER *)memFile)->tiles_start_offset; + hmp_struct->width = ((T_HMPFILE_HEADER *)memFile)->width_BLK; + hmp_struct->height = ((T_HMPFILE_HEADER *)memFile)->height_BLK; + + if (p_opts->verbose_mode) { + printf("[DBG] > Height scale: %.8f\n", y_scale); + printf("[DBG] > Tiles count: %d\n", ((T_HMPFILE_HEADER *)memFile)->tiles_count); + printf("[DBG] > Tiles offset: 0x%X\n", tiles_offset); + printf("[DBG] > Terrain size: %dx%d\n", hmp_struct->width, hmp_struct->height); + printf("\n"); + } + if (p_opts->god_mode) { + printf("[DBG] > Unknown0: %d\n", ((T_HMPFILE_HEADER *)memFile)->unknown0); + printf("[DBG] > Unknown1: %d\n", ((T_HMPFILE_HEADER *)memFile)->unknown1); + printf("\n"); + } + + // Get tiles indices + tiles_indices = malloc(hmp_struct->width * hmp_struct->height * sizeof(unsigned short)); + memcpy(tiles_indices, memFile + sizeof(T_HMPFILE_HEADER), + hmp_struct->width * hmp_struct->height * sizeof(unsigned short)); + + // Get tiles datas + tiles = malloc(((T_HMPFILE_HEADER *)memFile)->tiles_count * sizeof(T_HMPFILE_TILE)); + memcpy(tiles, memFile + tiles_offset, ((T_HMPFILE_HEADER *)memFile)->tiles_count * sizeof(T_HMPFILE_TILE)); + + // Convert tiles datas to raw heightmap + processTilesToHeightmap(hmp_struct, tiles_indices, tiles, p_opts->inverted_HM); + // Convert tiles datas to terrain vertices + processHeighmapToVertices(hmp_struct, y_scale); + free(tiles); + + free(tiles_indices); + free(memFile); + + } else { + fclose(fStream); + err = ERROR_MEMORY; + printf("[ERR] Can't allocate enough memory for file processing!\n"); + } + } else { + err = ERROR_IO; + printf("[ERR] Input file %s not found!\n", fileName); + } + } else err = ERROR_ARGS_NULL; + + return err; +} + +void cleanUpResources(T_TERRAIN* terrain) { + unsigned int i; + + if (terrain == NULL) return; + + for ( i = 0; i < terrain->width * TERRAIN_TILE_SAMPLING; i++ ) + free(terrain->heightmap[i]); + + free(terrain->heightmap); + free(terrain->verticesmap); + free(terrain); +} + +static void processTilesToHeightmap(T_TERRAIN* terrain, const T_TILE_INDICES* tiles_indices, const T_HMPFILE_TILE* tiles, const unsigned char inv_output) { + T_TILE_INDICES tiles_idx; + unsigned int i,j,k,l; + unsigned int heightmap_size_w = terrain->width * TERRAIN_TILE_SAMPLING; + unsigned int heightmap_size_h = terrain->height * TERRAIN_TILE_SAMPLING; + + // Build 2D array to contain height values + terrain->heightmap = (unsigned char **)malloc(heightmap_size_w * sizeof(unsigned char *)); + for ( i = 0; i < heightmap_size_w; i++ ) { + terrain->heightmap[i] = (unsigned char *)malloc(heightmap_size_h * sizeof(unsigned char)); + } + + // Select tile + for ( i = 0; i < terrain->width; i++ ) { + for ( j = 0; j < terrain->height; j++ ) { + tiles_idx = tiles_indices[j * terrain->width + i]; + + // Get the 5x5 bytes height values for this tile + for ( k = 0; k < TERRAIN_TILE_SAMPLING; k++ ) { + for ( l = 0; l < TERRAIN_TILE_SAMPLING; l++ ) { + if (inv_output) { + // Invert Z to set 0,0 origin at bottom left of terrain + terrain->heightmap[i*TERRAIN_TILE_SAMPLING+k][(heightmap_size_h-1) - (j*TERRAIN_TILE_SAMPLING+l)] = + tiles[tiles_idx].height_values[l][k] + 128; + //terrain->heightmap[i*TERRAIN_TILE_SAMPLING+k][j*TERRAIN_TILE_SAMPLING+l] = + // tiles[tiles_idx].height_values[l][k] + 128; + } else { + // Invert Z to set 0,0 origin at bottom left of terrain + terrain->heightmap[i*TERRAIN_TILE_SAMPLING+k][(heightmap_size_h-1) - (j*TERRAIN_TILE_SAMPLING+l)] = + 255 - (tiles[tiles_idx].height_values[l][k] + 128); + //terrain->heightmap[i*TERRAIN_TILE_SAMPLING+k][j*TERRAIN_TILE_SAMPLING+l] = + // 255 - (tiles[tiles_idx].height_values[l][k] + 128); + } + } + } + } + } +} + +static void processHeighmapToVertices(T_TERRAIN* terrain, const float h_scale) { + unsigned int i,j,k; + float w_half,h_half,_h_scale; + + if (terrain->heightmap == NULL) return; + + terrain->verticesmap_size = terrain->width * TERRAIN_TILE_SAMPLING * terrain->height * TERRAIN_TILE_SAMPLING; + w_half = terrain->width * 2; // Terrain center defined as mesh center -- sould not be used for game application + h_half = terrain->height * 2; + _h_scale = h_scale * 2.0 / 10.0; // Convert read scale to display scale, don't known the real operation in game + + // Build vertex list of the terrain + terrain->verticesmap = (T_VERTEX *)malloc(terrain->verticesmap_size * sizeof(T_VERTEX)); + for ( j = 0; j < terrain->height * TERRAIN_TILE_SAMPLING; j++ ) { + for ( i = 0; i < terrain->width * TERRAIN_TILE_SAMPLING; i++ ) { + k = j * terrain->width * TERRAIN_TILE_SAMPLING + i; + terrain->verticesmap[k].x = TERRAIN_MESH_SCALE * (-w_half + i); + terrain->verticesmap[k].z = TERRAIN_MESH_SCALE * (-h_half + j); + terrain->verticesmap[k].y = terrain->heightmap[i][j] * _h_scale; + } + } +} + + + + + + diff --git a/src/hmp_parser.h b/src/hmp_parser.h new file mode 100644 index 0000000..3cfbba6 --- /dev/null +++ b/src/hmp_parser.h @@ -0,0 +1,18 @@ +/** + * \file hmp_parser.h + * \date 31/07/2022 + * \author JackCarterSmith + * \copyright GPL-v3.0 + * \brief Decode terrain file (hmp) structure. + */ + +#include "hmp_struct.h" + + +#ifndef SRC_HOB_PARSER_H_ +#define SRC_HOB_PARSER_H_ + +unsigned char parseHMPFile(const char* fileName, T_TERRAIN* hmp_struct, T_PROG_OPTIONS* p_opts); +void cleanUpResources(T_TERRAIN* terrain); + +#endif /* SRC_HOB_PARSER_H_ */ diff --git a/src/hmp_struct.h b/src/hmp_struct.h new file mode 100644 index 0000000..d30ddf1 --- /dev/null +++ b/src/hmp_struct.h @@ -0,0 +1,87 @@ +/* + * hmp_struct.h + * + * Created on: 31 juil. 2022 + * Author: JackCarterSmith + */ + +#ifndef SRC_HMP_STRUCT_H_ +#define SRC_HMP_STRUCT_H_ + + +/* + * long = 64bits??? (8 Bytes) + * int = 32bits (4 Bytes) + * short = 16bits (2 Bytes) + * car = 8bits (1 Bytes) + */ + +#if defined(_MSC_VER) +#define PACK +#elif defined(__GNUC__) +#define PACK __attribute__((packed)) +#endif + +/////////////////////////////////////////////////////////////////////////////// +// HMP file structure +/////////////////////////////////////////////////////////////////////////////// + +typedef struct vector3 { float x,y,z; } T_VECTOR3; +typedef T_VECTOR3 T_VERTEX; + +typedef struct terrain { + unsigned short width; // Dimension of the height/vertices map + unsigned short height; + + unsigned char** heightmap; + + unsigned int verticesmap_size; + T_VERTEX* verticesmap; +} T_TERRAIN ; + + +/////////////////////////////////////////////////////////////////////////////// +// Declaration of Memory Mapped Structure +// Caution: the place of variable is important for correct mapping! +/////////////////////////////////////////////////////////////////////////////// + +#if defined(_MSC_VER) +#pragma pack(push, 1) +#endif + +typedef struct PACK hmpfile_header { + unsigned int reserved0; //12B of zeros + unsigned int reserved1; + unsigned int reserved2; + + float reserved3; // Always 0x3F000000 + float height_scale; + float reserved4; // Always 0x3F000000 + + unsigned short tiles_count; + unsigned short unknown0; + + unsigned int tiles_start_offset; + unsigned int unknown1; // Offset to some datas? + + unsigned short width_BLK; + unsigned short height_BLK; +} T_HMPFILE_HEADER; + +typedef unsigned short T_TILE_INDICES; + +typedef struct PACK hmpfile_tile { + unsigned short texmap_id; + + unsigned char unknown0; + unsigned char low_height; // LOD application? Clipping? Terrain render quadrants? + unsigned char high_height; + + unsigned char height_values[5][5]; // first and last row/column overlap with a neighboring tile, "glue" for tiles, need to be identical to avoid "hill" effect +} T_HMPFILE_TILE; + +#if defined(_MSC_VER) +#pragma pack(pop) +#endif + +#endif /* SRC_HMP_STRUCT_H_ */ diff --git a/src/options.h b/src/options.h index 904c2d3..5c3c70b 100644 --- a/src/options.h +++ b/src/options.h @@ -1,9 +1,38 @@ +/** + * \file options.h + * \date 29/07/2022 + * \author JackCarterSmith + * \copyright GPL-v3.0 + * \brief Shared options structure definition and declaration. + */ + #ifndef OPTIONS_H_ #define OPTIONS_H_ -#define VERBOSE_ENABLED 0x0001 -#define OUTPUT_DIR 0x0002 +// Number of height values to take for computing terrain (default: 4) +#define TERRAIN_TILE_SAMPLING 4 -extern int _options; +// Scale value for vertex grid constructor (default: 0.1) +#define TERRAIN_MESH_SCALE 0.1 -#endif +/// Options structure +typedef union u_prog_options { + struct { + unsigned char verbose_mode:1; //!< Output simple details about ID and other "light" things. + + unsigned char output_dir:1; //!< Export extracted datas to a sub-directory. + unsigned char inverted_HM:1; //!< Enable negative heightmap output. + + unsigned char reserved0:6; //!< For future use. + + unsigned char debug_mode:1; //!< Output all values of faces, indices and vertices and others "heavy" things. + unsigned char god_mode:1; //!< Dev only. Output experimental values. + + unsigned char reserved1:6; //!< For future use. + + unsigned short input_files_cnt; //!< Internal files counters. + }; + unsigned int raw; //!< Raw options access for bit-masking or memory copy/compare. +} T_PROG_OPTIONS ; + +#endif /* OPTIONS_H_ */ diff --git a/src/rlk b/src/rlk new file mode 160000 index 0000000..48a6916 --- /dev/null +++ b/src/rlk @@ -0,0 +1 @@ +Subproject commit 48a6916526d043691bb3f9e38676fbc99995da10