diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..8cdd84d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,57 @@ +name: Bug Report +description: File a bug report +title: "[Bug]: " +labels: ["bug"] +assignees: + - TheWillard +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: checkboxes + id: terms + attributes: + label: Loaded Mods + description: Please confirm that all required mods are actually loaded. + options: + - label: I checked that `@grad_meh` is loaded + required: true + - label: I checked that `@cba_a3` is loaded + required: true + - label: I checked that `@intcept` is loaded + required: true + - type: checkboxes + id: battleye + attributes: + label: Loaded Mods + description: BattlEye can cause problems with grad_meh. Make sure it is disabled! + options: + - label: I disabled BattlEye + required: true + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us what did you expected to happen, and include the workshop links to relevant maps. + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: textarea + id: rpt + attributes: + label: RPT + description: Please copy and paste the relevant RPT. You can find it in the following directory `%localappdata%\Arma 3` + render: shell + validations: + required: true + - type: textarea + id: logs + attributes: + label: grad_meh Logs + description: Please copy and paste the relevant grad_meh log. You can find it in the following directory `%localappdata%\Arma 3` + render: shell + validations: + required: true + diff --git a/.github/actions/update-versionfile/Dockerfile b/.github/actions/update-versionfile/Dockerfile deleted file mode 100644 index c68cc8d..0000000 --- a/.github/actions/update-versionfile/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM alpine:3.10 - -COPY update-versionfile.sh /update-versionfile.sh - -RUN apk update && apk upgrade && \ - apk add --no-cache bash git openssh - -RUN apk add --update bash && rm -rf /var/cache/apk/* - -ENTRYPOINT ["/update-versionfile.sh"] \ No newline at end of file diff --git a/.github/actions/update-versionfile/action.yml b/.github/actions/update-versionfile/action.yml deleted file mode 100644 index 46560f8..0000000 --- a/.github/actions/update-versionfile/action.yml +++ /dev/null @@ -1,3 +0,0 @@ -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/update-versionfile/update-versionfile.sh b/.github/actions/update-versionfile/update-versionfile.sh deleted file mode 100644 index 91a7f9d..0000000 --- a/.github/actions/update-versionfile/update-versionfile.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -l - -echo "versionfile updater. looking for tagā€¦" -version=$(git describe --always --tag) - -versionfilePath="./addons/main/script_version.hpp" - -IFS='.-' read -ra versionbits <<< ${version} -major=${versionbits[0]} -minor=${versionbits[1]} -patch=${versionbits[2]} -build=${versionbits[3]} -commit=$(echo ${versionbits[4]} | tr -d g) - -re='^[0-9]+$' -if ! [[ ${major} =~ $re && ${minor} =~ $re && ${patch} =~ $re ]] ; then - echo "not a release tag: '$version', not writing versionfile." - exit -fi - -echo "we're on version $major.$minor.$patch, build $build (commit $commit ). updating versionfileā€¦" - -printf "#define MAJOR $major\n#define MINOR $minor\n#define PATCHLVL $patch\n#define BUILD 0" > $versionfilePath - -echo "Version file is now:" -echo $(cat $versionfilePath) \ No newline at end of file diff --git a/.github/workflows/add-to-release.yml b/.github/workflows/add-to-release.yml deleted file mode 100644 index f4758bc..0000000 --- a/.github/workflows/add-to-release.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: 'Add to Release' - -on: [release] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - name: 'Checkout the source code from GitHub' - - - uses: ./.github/actions/update-versionfile - name: 'Update script_version.hpp' - - - uses: gruppe-adler/action-release-with-hemtt@1.1.2 - name: 'Build Mod with HEMTT' - id: build - - - uses: Shopify/upload-to-release@1.0.0 - name: 'Add to release' - with: - name: '${{ steps.build.outputs.zip_name }}.zip' - path: ${{ steps.build.outputs.zip_path }} - repo-token: ${{ secrets.GITHUB_TOKEN }} - content-type: application/zip diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..8649de6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,75 @@ +name: C/C++ CI + +on: + push: + pull_request: + +env: + VCPKG_COMMIT: cc97b4536ae749ec0e4f643488b600b217540fb3 + VCPKG_DIR: C:/deps/vcpkg + CMAKE_PRESET: ninja-msvc-vcpkg + +jobs: + job_build_cpp: + name: win + runs-on: windows-latest + + steps: + # Checkout + - uses: actions/checkout@v2 + with: + submodules: true + fetch-depth: 0 + + # Setup MSVC + - uses: ilammy/msvc-dev-cmd@v1 + with: + vsversion: 2022 + toolset: 14 + + # Install latest CMake + - uses: lukka/get-cmake@latest + + - name: Create folder + run: | + mkdir ${{ env.VCPKG_DIR }} + + # Setup vcpkg and build deps + - name: Restore artifacts, or Run vcpkg, build and cache artifacts + uses: lukka/run-vcpkg@v11 + id: runvcpkg + with: + vcpkgGitCommitId: ${{ env.VCPKG_COMMIT }} + vcpkgDirectory: ${{ env.VCPKG_DIR }} + + - name: Run CMake consuming CMakePresets.json and vcpkg.json by mean of vcpkg. + uses: lukka/run-cmake@v10 + with: + configurePreset: ninja-release-vcpkg-win + buildPreset: ninja-msvc-vcpkg-release + + # Build the mod + - uses: gruppe-adler/action-release-with-hemtt@v3 + name: 'Build Mod with HEMTT' + id: build + + # Upload the mod + - uses: actions/upload-artifact@v1 + with: + name: ${{ steps.build.outputs.mod_name }} + path: ${{ steps.build.outputs.release_path }} + + # zip it + - uses: papeloto/action-zip@v1 + with: + files: \@${{ steps.build.outputs.mod_name }}/ + dest: \@${{ steps.build.outputs.mod_name }}.zip + + # release it + - uses: "marvinpinto/action-automatic-releases@latest" + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: true + files: | + \@${{needs.job_build_cpp.outputs.modName}}.zip diff --git a/.github/workflows/buildMod.yml b/.github/workflows/buildMod.yml deleted file mode 100644 index 9dbd0cd..0000000 --- a/.github/workflows/buildMod.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: '[CI] Build Mod' - -on: [push] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: 'Checkout source code' - uses: actions/checkout@master - - name: 'Build Mod with HEMTT' - uses: gruppe-adler/action-release-with-hemtt@1.1.2 - id: build - - name: 'Upload artifact' - uses: actions/upload-artifact@master - with: - name: 'packed-mod' - path: ${{ steps.build.outputs.zip_path }} diff --git a/.gitignore b/.gitignore index f486cde..f9829f4 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ # Others .vs +.vscode .hemtt build *.pbo @@ -49,6 +50,7 @@ cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake +builds/ # CodeBlocks *.cbp @@ -60,4 +62,4 @@ CTestTestfile.cmake releases/* keys/* *.pbo -*.biprivatekey \ No newline at end of file +*.biprivatekey diff --git a/.gitmodules b/.gitmodules index c22f4c5..0e2fa03 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "intercept"] path = intercept - url = git@github.com:intercept/intercept.git + url = https://github.com/intercept/intercept.git +[submodule "src/rvff-cxx"] + path = src/rvff-cxx + url = https://github.com/arma-tools/rvff-cxx.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 21862ec..c58aee5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,15 +23,20 @@ message("COMPILER USED: '${CMAKE_CXX_COMPILER_ID}'") set(CMAKE_CL_64 ${USE_64BIT_BUILD}) if(USE_64BIT_BUILD) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/win64/") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/lib64/") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/lib64/") else() - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/win32/") + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/lib32/") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/lib32/") endif() + set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(Boost_NO_WARN_NEW_VERSIONS 1) + set(CMAKE_INCLUDE_CURRENT_DIR ON) set_property(GLOBAL PROPERTY USE_FOLDERS ON) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..8fcf657 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,65 @@ +{ + "version": 5, + "cmakeMinimumRequired": { + "major": 3, + "minor": 23, + "patch": 0 + }, + "configurePresets": [ + { + "name": "ninja-debug-vcpkg-win", + "displayName": "Ninja/Windows/Debug/VCPKG Config", + "description": "Ninja/Windows/Debug/VCPKG Config", + "binaryDir": "${sourceDir}/builds/${presetName}", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_TOOLCHAIN_FILE": { + "type": "FILEPATH", + "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + }, + "CMAKE_INSTALL_PREFIX": { + "type": "FILEPATH", + "value": "${sourceDir}/install" + }, + "USE_64BIT_BUILD": true, + "VCPKG_TARGET_TRIPLET": "x64-windows-static", + "CMAKE_C_COMPILER": "cl", + "CMAKE_CXX_COMPILER": "cl" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "ninja-release-vcpkg-win", + "displayName": "Ninja/Windows/Release/VCPKG Config", + "inherits": "ninja-debug-vcpkg-win", + "description": "Ninja/Windows/Release/VCPKG Config", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ], + "buildPresets": [ + { + "name": "ninja-msvc-vcpkg-debug", + "configurePreset": "ninja-debug-vcpkg-win", + "displayName": "Build ninja-msvc-vcpkg (Debug)", + "description": "Build ninja-msvc-vcpkg (Debug) Configurations", + "targets": "install", + "configuration": "Debug" + }, + { + "name": "ninja-msvc-vcpkg-release", + "configurePreset": "ninja-release-vcpkg-win", + "displayName": "Build ninja-msvc-vcpkg (Release)", + "description": "Build ninja-msvc-vcpkg Configurations", + "targets": "install", + "configuration": "Release" + } + ] +} diff --git a/README.md b/README.md index 3112ee8..93389fc 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,12 @@ Download the latest version of the mod from our [releases page](https://github.c ## Usage Although we would say that using `grad_meh` is intuitive and mostly self-explanatory, we made this short video on how to use it. -**HINT**: If you have any problems with the export not starting, try disabling BattlEye. + +## Troubleshooting +- Make sure that `@grad_meh`, `@intercept` and `@CBA_A3` are loaded. +- Make sure that BattlEye is disabled. +- If you're still having problems check the rpt and grad_meh log files located in `C:\Users\\AppData\Local\Arma 3` for any errors. +- If all else fails, open a new issue and make sure you include the rpt and grad_meh log files. ## Limitations - To reduce complexity we only allow one export process at a time. diff --git a/addons/main/script_version.hpp b/addons/main/script_version.hpp index 49ca1d0..9ba59aa 100644 --- a/addons/main/script_version.hpp +++ b/addons/main/script_version.hpp @@ -1,4 +1,4 @@ -#define MAJOR 1 -#define MINOR 0 -#define PATCHLVL 0 -#define BUILD 0 \ No newline at end of file +#define MAJOR 0 +#define MINOR 7 +#define PATCHLVL 1 +#define BUILD 8 diff --git a/cmake/version.cmake b/cmake/version.cmake index 0b2b6f3..384f5a0 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -1,6 +1,30 @@ # From https://www.mattkeeter.com/blog/2018-01-06-versioning/ -execute_process(COMMAND git log --pretty=format:'%h' -n 1 +message("Update script_version.hpp") +execute_process(COMMAND ${BASH} ./scripts/update-versionfile.sh) + +message("Writing version.cpp") + +find_program(GIT "git") + +if(GIT) + message("Found git at:") + message(${GIT}) +else() + message("Did not find git!") +endif() + +find_program(BASH "bash") +if(BASH) + message("Found bash at:") + message(${BASH}) +else() + message("Did not find bash!") +endif() + +# Write version.cpp + +execute_process(COMMAND ${GIT} log --pretty=format:'%h' -n 1 OUTPUT_VARIABLE GIT_REV ERROR_QUIET) @@ -14,17 +38,17 @@ if ("${GIT_REV}" STREQUAL "") set(GIT_BRANCH "N/A") else() execute_process( - COMMAND bash -c "git diff --quiet --exit-code || echo +" + COMMAND ${BASH} -c "git diff --quiet --exit-code || echo +" OUTPUT_VARIABLE GIT_DIFF) execute_process( - COMMAND git describe --exact-match --tags + COMMAND ${GIT} describe --exact-match --tags OUTPUT_VARIABLE GIT_TAG ERROR_QUIET) execute_process( - COMMAND git rev-parse --abbrev-ref HEAD + COMMAND ${GIT} rev-parse --abbrev-ref HEAD OUTPUT_VARIABLE GIT_BRANCH) execute_process( - COMMAND git describe --tags --abbrev=0 --match v* + COMMAND ${GIT} describe --tags --abbrev=0 --match v* OUTPUT_VARIABLE GIT_LAST_VERSION ) @@ -54,3 +78,53 @@ endif() if (NOT "${VERSION}" STREQUAL "${VERSION_}") file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/version.cpp "${VERSION}") endif() + +# Write script_version.hpp +if ("${GIT_REV}" STREQUAL "") + set(VERSION_MAJOR "0") + set(VERSION_MINOR "0") + set(VERSION_PATCH "0") + set(VERSION_BUILD "0") +else() + execute_process( + COMMAND ${GIT} describe --tag --always + OUTPUT_VARIABLE GIT_TAG ERROR_QUIET) + string(REGEX REPLACE "^v" "" GIT_TAG "${GIT_TAG}") + message("Using git tag ${GIT_TAG}") + + string(REPLACE "-" ";" GIT_TAG_LIST ${GIT_TAG}) + list(LENGTH GIT_TAG_LIST GIT_TAG_LIST_LENGTH) + + list(GET GIT_TAG_LIST 0 VERSION_TAG) + if(GIT_TAG_LIST_LENGTH GREATER 1) + list(GET GIT_TAG_LIST 1 VERSION_BUILD) + else() + set(VERSION_BUILD "0") + endif() + message("Tag: ${VERSION_TAG}") + message("Build: ${VERSION_BUILD}") + message("") + string(REPLACE "." ";" GIT_VERSION_LIST ${VERSION_TAG}) + list(GET GIT_VERSION_LIST 0 VERSION_MAJOR) + list(GET GIT_VERSION_LIST 1 VERSION_MINOR) + list(GET GIT_VERSION_LIST 2 VERSION_PATCH) +endif() + +set(VERSION "#define MAJOR ${VERSION_MAJOR} +#define MINOR ${VERSION_MINOR} +#define PATCHLVL ${VERSION_PATCH} +#define BUILD ${VERSION_BUILD} +") + +if(EXISTS ${SCRIPTVERSION_PATH}) + file(READ ${SCRIPTVERSION_PATH} VERSION_) +else() + set(VERSION_ "") +endif() + +message("Version: " ${VERSION}) +message("script_version.hpp Path: " ${SCRIPTVERSION_PATH}) + +if (NOT "${VERSION}" STREQUAL "${VERSION_}") + file(WRITE ${SCRIPTVERSION_PATH} "${VERSION}") +endif() diff --git a/docs/geojson_spec.md b/docs/geojson_spec.md index 2cbd2f5..d1ca944 100644 --- a/docs/geojson_spec.md +++ b/docs/geojson_spec.md @@ -81,3 +81,14 @@ Bridges can be split in the same categories as [roads](#11-roads). Like [roads]( - File: `roads/-bridge.geojson.gz` - Geometry-Type: [Polygon](https://tools.ietf.org/html/rfc7946#section-3.1.6) - Properties: none + +## 13. Rivers +- File: `river.geojson.gz` +- Geometry-Type: [Polygon](https://tools.ietf.org/html/rfc7946#section-3.1.6) +- Properties: none + +## 14. Mounts +- File: `mounts.geojson.gz` +- Geometry-Type: [Point](https://tools.ietf.org/html/rfc7946#section-3.1.2) +- Properties: + - `elevation`: The height of the location \ No newline at end of file diff --git a/hemtt.toml b/hemtt.toml index 2997e3e..6557f91 100644 --- a/hemtt.toml +++ b/hemtt.toml @@ -6,9 +6,18 @@ files = [ "mod.cpp", "Adler.paa", "LICENSE", - "README.md", - "*.dll", - "*.pdb" + "README.md" ] key_name = "{{prefix}}-{{version}}" -sig_name = "{{prefix}}-{{version}}" \ No newline at end of file +authority = "{{version}}" + +releasebuild = [ + "!copyInterceptPlugin" +] + +[scripts.copyInterceptPlugin] +steps_windows = [ + 'xcopy build\lib64\*.dll releases\\{{version}}\@{{prefix}}\intercept\ /Y /F' +] + +show_output = true diff --git a/intercept b/intercept index 12a8c12..7c8b365 160000 --- a/intercept +++ b/intercept @@ -1 +1 @@ -Subproject commit 12a8c12992e8dbd5e4e8f1847bb09d079d836921 +Subproject commit 7c8b365d247a3fafad97ac29939ac5139bbfa2d5 diff --git a/rust-cxx.natvis b/rust-cxx.natvis new file mode 100644 index 0000000..a5b4c27 --- /dev/null +++ b/rust-cxx.natvis @@ -0,0 +1,18 @@ + + + + { (char*)repr[1],s8 } + (char*)repr[1],s8 + + + {{size = {repr[2]}}} + + repr[2] + repr[0] + + repr[2] + ($T1*)repr[1] + + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 96cae3a..eb744eb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.12) +cmake_minimum_required (VERSION 3.15) file(GLOB_RECURSE INTERCEPT_PLUGIN_SOURCES *.h *.hpp *.c *.cpp) @@ -10,28 +10,18 @@ SOURCE_GROUP("src" FILES ${INTERCEPT_PLUGIN_SOURCES}) #Here is a example of a "utilities" subdirectory. #----Don't change anything below this line -if(${CMAKE_BUILD_TYPE} STREQUAL "Debug") - set(DEBUG_SUFFIX "d") -else() - set(DEBUG_SUFFIX "") -endif() -# DXT -find_path(SQUISH_INCLUDE_DIR squish.h) -find_library(SQUISH_LIBRARY squish${DEBUG_SUFFIX}) +# fmt +find_package(fmt CONFIG REQUIRED) # nlohmann json find_package(nlohmann_json CONFIG REQUIRED) -find_package(OpenSSL REQUIRED) set(Boost_USE_STATIC_LIBS ON) set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_RUNTIME ON) -find_package(Boost 1.71.0 COMPONENTS system filesystem thread iostreams) - -# LZO -find_package(lzokay CONFIG REQUIRED) +find_package(Boost 1.82.0 REQUIRED COMPONENTS system filesystem thread iostreams) # OIIO if("${INTERCEPT_LINK_TYPE}" STREQUAL "static") @@ -40,35 +30,24 @@ if("${INTERCEPT_LINK_TYPE}" STREQUAL "static") add_definitions(-DGRAD_AFF_USE_OIIO) endif() -# Let OIIO find external libsquish -set (LIBSQUISH_INCLUDES ${SQUISH_INCLUDE_DIR}) -set (LIBSQUISH_LIBRARIES ${SQUISH_LIBRARY}) - -if (NOT TARGET Libsquish::Libsquish) - add_library(Libsquish::Libsquish UNKNOWN IMPORTED) - set_target_properties(Libsquish::Libsquish PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${LIBSQUISH_INCLUDES}") - - set_property(TARGET Libsquish::Libsquish APPEND PROPERTY - IMPORTED_LOCATION "${LIBSQUISH_LIBRARIES}") -endif () - find_package(OpenEXR CONFIG) find_package(PNG MODULE) + +add_definitions(-DOIIO_STATIC_DEFINE) find_package(OpenImageIO CONFIG REQUIRED) +# plog +find_package(plog CONFIG REQUIRED) + # GADL -find_package(GDAL REQUIRED) +find_package(GDAL CONFIG REQUIRED) #PCL -find_package(PCL 1.3 REQUIRED) +find_package(PCL CONFIG REQUIRED) # Clipper -FIND_PATH(CLIPPER_INCLUDE_DIR polyclipping/clipper.hpp) -find_library(CLIPPER_LIBRARY NAMES polyclipping) +find_package(polyclipping CONFIG REQUIRED) -FIND_PATH(GRAD_AFF_INCLUDE_DIR grad_aff/grad_aff.h HINTS ${GRAD_AFF_PATH} PATH_SUFFIXES include) -FIND_LIBRARY(GRAD_AFF_LIB_DIR NAMES grad_aff HINTS ${GRAD_AFF_PATH} PATH_SUFFIXES lib) #include the Intercept headers from the submodule set(INTERCEPT_CLIENT_PATH "${CMAKE_SOURCE_DIR}/intercept/src/client") @@ -90,35 +69,60 @@ endif() file(GLOB INTERCEPT_HOST_SOURCES "${INTERCEPT_CLIENT_PATH}/intercept/client/*.cpp" "${INTERCEPT_CLIENT_PATH}/intercept/client/sqf/*.cpp" "${INTERCEPT_CLIENT_PATH}/intercept/shared/*.cpp") SOURCE_GROUP("intercept" FILES ${INTERCEPT_HOST_SOURCES}) -include_directories(${Boost_INCLUDE_DIRS}) -include_directories(${GRAD_AFF_INCLUDE_DIR}) -include_directories(${GDAL_INCLUDE_DIR}) -include_directories(${PCL_INCLUDE_DIRS}) -include_directories(${CLIPPER_INCLUDE_DIR}) - -link_directories(${PCL_LIBRARY_DIRS}) - # Version file ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.cpp ${CMAKE_CURRENT_BINARY_DIR}/_version.cpp - COMMAND ${CMAKE_COMMAND} -P - ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/version.cmake) + ${CMAKE_CURRENT_BINARY_DIR}/addons/main/script_version.hpp + ${CMAKE_CURRENT_BINARY_DIR}/addons/main/_script_version.hpp + COMMAND ${CMAKE_COMMAND} -DSCRIPTVERSION_PATH=${CMAKE_CURRENT_SOURCE_DIR}/../addons/main/script_version.hpp -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/version.cmake) + +include(FetchContent) + +FetchContent_Declare( + Corrosion + GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git + GIT_TAG master # Optionally specify a commit hash, version tag or branch here +) +# Set any global configuration variables such as `Rust_TOOLCHAIN` before this line! +FetchContent_MakeAvailable(Corrosion) + +# Import targets defined in a package or workspace manifest `Cargo.toml` file + +corrosion_import_crate(MANIFEST_PATH rvff-cxx/Cargo.toml) +corrosion_add_cxxbridge(rust-lib CRATE rvff-cxx MANIFEST_PATH rvff-cxx FILES lib.rs) include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_library(${INTERCEPT_PLUGIN_NAME} SHARED ${INTERCEPT_PLUGIN_SOURCES} ${INTERCEPT_HOST_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/version.cpp) target_include_directories(${INTERCEPT_PLUGIN_NAME} PUBLIC ${OPENIMAGEIO_INCLUDE_DIR}) -target_link_libraries(${INTERCEPT_PLUGIN_NAME} ${GDAL_LIBRARIES}) + +target_link_libraries(${INTERCEPT_PLUGIN_NAME} GDAL::GDAL) + target_link_libraries(${INTERCEPT_PLUGIN_NAME} OpenImageIO::OpenImageIO OpenImageIO::OpenImageIO_Util) + target_link_libraries(${INTERCEPT_PLUGIN_NAME} ${Boost_LIBRARIES}) target_link_libraries(${INTERCEPT_PLUGIN_NAME} nlohmann_json nlohmann_json::nlohmann_json) -target_link_libraries(${INTERCEPT_PLUGIN_NAME} lzokay::lzokay) -target_link_libraries(${INTERCEPT_PLUGIN_NAME} ${SQUISH_LIBRARY}) -target_link_libraries(${INTERCEPT_PLUGIN_NAME} ${GRAD_AFF_LIB_DIR}) -target_link_libraries(${INTERCEPT_PLUGIN_NAME} OpenSSL::SSL OpenSSL::Crypto) +target_link_libraries(${INTERCEPT_PLUGIN_NAME} fmt::fmt-header-only) target_link_libraries(${INTERCEPT_PLUGIN_NAME} ${PCL_LIBRARIES}) -target_link_libraries(${INTERCEPT_PLUGIN_NAME} ${CLIPPER_LIBRARY}) +target_link_libraries(${INTERCEPT_PLUGIN_NAME} polyclipping::polyclipping) + +target_link_libraries(${INTERCEPT_PLUGIN_NAME} plog::plog) + +target_link_libraries(${INTERCEPT_PLUGIN_NAME} rust-lib) + +if(MSVC) + target_link_libraries(${INTERCEPT_PLUGIN_NAME} ntdll.lib) +endif() + +if(MSVC) + # Note: This is required because we use `cxx` which uses `cc` to compile and link C++ code. + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + corrosion_set_env_vars(rvff-cxx "CFLAGS=-MTd" "CXXFLAGS=-MTd") + else() + corrosion_set_env_vars(rvff-cxx "CFLAGS=-MT" "CXXFLAGS=-MT") + endif() +endif() include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${INTERCEPT_INCLUDE_PATH}) @@ -134,10 +138,18 @@ else() set(CMAKE_CXX_FLAGS_RELEASE "/MT /Zi /O2 /Ob1 /EHsc /MP") #with debug info # /FORCE:MULTIPLE requrired to ignore second definition of tiff stuff in gdal (conflicting with oiio?) target_link_options(${INTERCEPT_PLUGIN_NAME} PUBLIC "/FORCE:MULTIPLE") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "/OPT:REF /DEBUG:FULL") + set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "/DEBUG:FULL /NODEFAULT:LIBCMTD") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "/OPT:REF /DEBUG:FULL /NODEFAULT:LIBCMTD") endif() +cmake_host_system_information(RESULT ARMA_PATH QUERY WINDOWS_REGISTRY "HKLM\\SOFTWARE\\WOW6432Node\\Bohemia Interactive\\arma 3" VALUE "main") +message("Arma Path from Registry: " ${ARMA_PATH}) + +if("${PLUGIN_FOLDER}" STREQUAL "") + SET(PLUGIN_FOLDER "${ARMA_PATH}\\@grad_meh\\intercept") +endif() +message("grad_meh plugin folder: " ${PLUGIN_FOLDER}) if(NOT "${PLUGIN_FOLDER}" STREQUAL "") add_custom_command(TARGET ${INTERCEPT_PLUGIN_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${PLUGIN_FOLDER}/${INTERCEPT_PLUGIN_NAME}.dll @@ -147,3 +159,6 @@ if(NOT "${PLUGIN_FOLDER}" STREQUAL "") COMMAND ${CMAKE_COMMAND} -E copy $ ${PLUGIN_FOLDER}/${INTERCEPT_PLUGIN_NAME}.pdb ) endif() + +install(TARGETS ${INTERCEPT_PLUGIN_NAME} RUNTIME DESTINATION bin) + diff --git a/src/SimplePoint.h b/src/SimplePoint.h index 4c4fbdb..7f893d4 100644 --- a/src/SimplePoint.h +++ b/src/SimplePoint.h @@ -3,8 +3,8 @@ #include struct SimplePoint { - float_t x = 0, y = 0; + double x = 0, y = 0; SimplePoint() {}; - SimplePoint(float_t x, float_t y) : x(x), y(y) {}; + SimplePoint(double x, double y) : x(x), y(y) {}; }; \ No newline at end of file diff --git a/src/area.cpp b/src/area.cpp index 590dad6..18a2050 100644 --- a/src/area.cpp +++ b/src/area.cpp @@ -32,21 +32,27 @@ #include - +#include namespace nl = nlohmann; - -void writeArea(grad_aff::Wrp& wrp, fs::path& basePathGeojson, const std::vector>& objectPairs, +void writeArea(rvff::cxx::OprwCxx& wrp, fs::path& basePathGeojson, const std::vector>& objectPairs, uint32_t epsilon, uint32_t minClusterSize, uint32_t buffer, uint32_t simplify, const std::string& name) { size_t nPoints = 0; pcl::PointCloud::Ptr cloudPtr(new pcl::PointCloud()); for (auto& objectPair : objectPairs) { pcl::PointXYZ point; - point.x = objectPair.first.transformMatrix[3][0]; - point.y = objectPair.first.transformMatrix[3][2]; + point.x = objectPair.first.transform_matrx._3.x; + point.y = objectPair.first.transform_matrx._3.z; point.z = 0; + + if (/*point.x < 0 || */std::isnan(point.x) || std::isinf(point.x) || + /*point.y < 0 || */std::isnan(point.y) || std::isinf(point.y)) { + PLOG_WARNING << fmt::format("[writeArea {}] Skipping point X: {} Y: {}", name, point.x, point.y); + continue; + } + cloudPtr->push_back(point); nPoints++; } @@ -129,7 +135,6 @@ void writeArea(grad_aff::Wrp& wrp, fs::path& basePathGeojson, const std::vector< return; } - //auto multiPolygonPtr = multiPolygon.Simplify(simplify); auto unionPtr = multiPolygonPtr->UnionCascaded(); if (unionPtr != nullptr) { multiPolygonPtr = unionPtr; diff --git a/src/area.h b/src/area.h index fe80776..1dd18e5 100644 --- a/src/area.h +++ b/src/area.h @@ -6,10 +6,9 @@ #include #include -#include -#include +#include namespace fs = std::filesystem; -void writeArea(grad_aff::Wrp& wrp, fs::path& basePathGeojson, const std::vector>& objectPairs, +void writeArea(rvff::cxx::OprwCxx& wrp, fs::path& basePathGeojson, const std::vector>& objectPairs, uint32_t epsilon, uint32_t minClusterSize, uint32_t buffer, uint32_t simplify, const std::string& name); diff --git a/src/geojsons.cpp b/src/geojsons.cpp index bca89a2..5bc6a1b 100644 --- a/src/geojsons.cpp +++ b/src/geojsons.cpp @@ -89,413 +89,389 @@ void normalizePolygon(nl::json& arr) } } -void writeHouses(grad_aff::Wrp& wrp, std::filesystem::path& basePathGeojson, std::vector modelInfos) +void writeHouses(rvff::cxx::OprwCxx& wrp, std::filesystem::path& basePathGeojson, std::vector modelInfos) { nl::json house = nl::json::array(); - for (auto& mapInfo : wrp.mapInfo) { - if (mapInfo->mapType == 4) { - auto mapInfo4Ptr = std::static_pointer_cast(mapInfo); - nl::json mapFeature; - mapFeature["type"] = "Feature"; + std::map objectMap = {}; + for (auto& object : wrp.objects) { + objectMap.insert({ object.object_id, object }); + } - auto coordArr = nl::json::array(); - coordArr.push_back(std::vector { mapInfo4Ptr->bounds[0], mapInfo4Ptr->bounds[1] }); - coordArr.push_back(std::vector { mapInfo4Ptr->bounds[2], mapInfo4Ptr->bounds[3] }); - coordArr.push_back(std::vector { mapInfo4Ptr->bounds[6], mapInfo4Ptr->bounds[7] }); - coordArr.push_back(std::vector { mapInfo4Ptr->bounds[4], mapInfo4Ptr->bounds[5] }); - coordArr.push_back(std::vector { mapInfo4Ptr->bounds[0], mapInfo4Ptr->bounds[1] }); - - normalizePolygon(coordArr); - - auto outerArr = nl::json::array(); - outerArr.push_back(coordArr); - - mapFeature["geometry"] = { { "type" , "Polygon" }, { "coordinates" , outerArr } }; - - /** - * Arma takes the color from the config and modifies it to make sure none of the rgb-values is bigger - * than 128. This ensures that the houses have a big enough contrast against the backgorund. - * - * So we'll check if any of r/g/b is bigger than 128 and if that's the case we'll calculate the - * factor we need to apply to reduce it to 128. After that we'll just apply that factor to all - * values, round the result and then we're done. - * - * Oh yeah and the opacity / alpha value is just discarded completely. All houses have a opacity of 100% - * - * Don't ask me why they don't just multiply/overlay the config colors with a base color... Just BI things I guess - * - DZ - */ - - // r, g, b - auto color = std::vector{ mapInfo4Ptr->color[2], mapInfo4Ptr->color[1], mapInfo4Ptr->color[0] }; - - auto maxColorValue = std::max_element(std::begin(color), std::end(color)); - - // make sure every color-value is below or equal to 128 - if (*maxColorValue > 128) { - float_t factor = 128.0f / *maxColorValue; - - std::transform(color.begin(), color.end(), color.begin(), [factor](uint8_t value) { return (uint8_t)std::round(factor * value); }); - } + for (auto& mapInfo : wrp.map_infos_4) { + nl::json mapFeature; + mapFeature["type"] = "Feature"; - float_t houseHeight = 0.0; - XYZTriplet housePos = { 0, 0, 0 }; - if (wrp.objectIdMap.find(mapInfo4Ptr->objectId) != wrp.objectIdMap.end()) { - auto &object = wrp.objectIdMap.at(mapInfo4Ptr->objectId); + auto coordArr = nl::json::array(); + coordArr.push_back(std::vector { mapInfo.bounds.a.x, mapInfo.bounds.a.y }); + coordArr.push_back(std::vector { mapInfo.bounds.b.x, mapInfo.bounds.b.y }); + coordArr.push_back(std::vector { mapInfo.bounds.d.x, mapInfo.bounds.d.y }); + coordArr.push_back(std::vector { mapInfo.bounds.c.x, mapInfo.bounds.c.y }); + coordArr.push_back(std::vector { mapInfo.bounds.a.x, mapInfo.bounds.a.y }); + + normalizePolygon(coordArr); + + auto outerArr = nl::json::array(); + outerArr.push_back(coordArr); + + mapFeature["geometry"] = { { "type" , "Polygon" }, { "coordinates" , outerArr } }; + + /** + * Arma takes the color from the config and modifies it to make sure none of the rgb-values is bigger + * than 128. This ensures that the houses have a big enough contrast against the backgorund. + * + * So we'll check if any of r/g/b is bigger than 128 and if that's the case we'll calculate the + * factor we need to apply to reduce it to 128. After that we'll just apply that factor to all + * values, round the result and then we're done. + * + * Oh yeah and the opacity / alpha value is just discarded completely. All houses have a opacity of 100% + * + * Don't ask me why they don't just multiply/overlay the config colors with a base color... Just BI things I guess + * - DZ + */ + + // r, g, b + auto color = std::vector{ mapInfo.color[2], mapInfo.color[1], mapInfo.color[0] }; + + auto maxColorValue = std::max_element(std::begin(color), std::end(color)); + + // make sure every color-value is below or equal to 128 + if (*maxColorValue > 128) { + float_t factor = 128.0f / *maxColorValue; + + std::transform(color.begin(), color.end(), color.begin(), [factor](uint8_t value) { return (uint8_t)std::round(factor * value); }); + } - housePos[0] = object.transformMatrix[3][0]; - housePos[1] = object.transformMatrix[3][2]; - housePos[2] = object.transformMatrix[3][1]; + float_t houseHeight = 0.0; + std::array housePos = { 0, 0, 0 }; - if (object.modelIndex < wrp.models.size()) { - auto &objectModel = modelInfos[object.modelIndex]; + if (auto objectPair = objectMap.find(mapInfo.object_id); objectPair != objectMap.end()) { + auto object = objectPair->second; - houseHeight = objectModel.bMax[1] - objectModel.bMin[1]; - } - } + housePos[0] = object.transform_matrx._3.x; + housePos[1] = object.transform_matrx._3.z; + housePos[2] = object.transform_matrx._3.y; - mapFeature["properties"] = { { "color", color }, { "height", houseHeight }, { "position", housePos } }; - house.push_back(mapFeature); + if (object.model_index < wrp.models.size()) { + auto& objectModel = modelInfos[object.model_index]; + houseHeight = objectModel.b_max.y - objectModel.b_min.y; + } } + + mapFeature["properties"] = { { "color", color }, { "height", houseHeight }, { "position", housePos } }; + house.push_back(mapFeature); } writeGZJson("house.geojson.gz", basePathGeojson, house); } -void writeObjects(grad_aff::Wrp& wrp, std::filesystem::path& basePathGeojson) +void writeObjects(rvff::cxx::OprwCxx& wrp, std::filesystem::path& basePathGeojson) { - nl::json house = nl::json::array(); - for (auto& mapInfo : wrp.mapInfo) { - if (mapInfo->mapType == 4) { - auto mapInfo4Ptr = std::static_pointer_cast(mapInfo); + //nl::json house = nl::json::array(); + //for (auto& mapInfo : wrp.map_infos_4) { + // //if (mapInfo->mapType == 4) { + // //auto mapInfo4Ptr = std::static_pointer_cast(mapInfo); - nl::json mapFeature; - mapFeature["type"] = "Feature"; - auto posArray = nl::json::array(); - posArray.push_back(mapInfo4Ptr->bounds[0]); - posArray.push_back(mapInfo4Ptr->bounds[1]); + // nl::json mapFeature; + // mapFeature["type"] = "Feature"; - mapFeature["geometry"] = { { "type" , "Point" }, { "coordinates" , posArray } }; - mapFeature["properties"] = { { "text", std::to_string(mapInfo4Ptr->infoType)} }; + // auto posArray = nl::json::array(); + // posArray.push_back(mapInfo.bounds[0]); + // posArray.push_back(mapInfo->bounds[1]); - house.push_back(mapFeature); - } - } - writeGZJson("debug.geojson.gz", basePathGeojson, house); -} + // mapFeature["geometry"] = { { "type" , "Point" }, { "coordinates" , posArray } }; + // mapFeature["properties"] = { { "text", std::to_string(mapInfo->infoType)} }; -void writeRoads(grad_aff::Wrp& wrp, const std::string& worldName, std::filesystem::path& basePathGeojson, const std::map>>& mapObjects) { + // house.push_back(mapFeature); + // //} + //} + //writeGZJson("debug.geojson.gz", basePathGeojson, house); +} - client::invoker_lock threadLock; - auto roadsPath = sqf::get_text(sqf::config_entry(sqf::config_file()) >> "CfgWorlds" >> worldName >> "newRoadsShape"); - threadLock.unlock(); +static std::vector esri_shape_magic = { 0, 0, 0x27, 0x0A }; - // some maps have no roads (e.g. desert) - if (roadsPath.empty()) { - return; - } +void writeRoads( + rvff::cxx::OprwCxx& wrp, + const std::string& worldName, + std::filesystem::path& basePathGeojson, + const std::map>>& mapObjects) { auto basePathGeojsonTemp = basePathGeojson / "temp"; - if (!fs::exists(basePathGeojsonTemp)) { - fs::create_directories(basePathGeojsonTemp); - } - auto basePathGeojsonRoads = basePathGeojson / "roads"; - if (!fs::exists(basePathGeojsonRoads)) { - fs::create_directories(basePathGeojsonRoads); - } + try { + client::invoker_lock threadLock; + auto roadsPath = sqf::get_text(sqf::config_entry(sqf::config_file()) >> "CfgWorlds" >> worldName >> "newRoadsShape"); + threadLock.unlock(); - auto roadsPbo = grad_aff::Pbo::Pbo(findPboPath(roadsPath).string()); - roadsPbo.readPbo(false); + // some maps have no roads (e.g. desert) + if (roadsPath.empty()) { + return; + } - auto roadsPathDir = ((fs::path)roadsPath).remove_filename().string(); - if (boost::starts_with(roadsPathDir, "\\")) { - roadsPathDir = roadsPathDir.substr(1); - } + if (!fs::exists(basePathGeojsonTemp)) { + fs::create_directories(basePathGeojsonTemp); + } - for (auto& [key, val] : roadsPbo.entries) { - if (boost::istarts_with((((fs::path)roadsPbo.productEntries["prefix"]) / key).string(), roadsPathDir)) { - roadsPbo.extractSingleFile(key, basePathGeojsonTemp, false); + auto basePathGeojsonRoads = basePathGeojson / "roads"; + if (!fs::exists(basePathGeojsonRoads)) { + fs::create_directories(basePathGeojsonRoads); } - } -#ifdef _WIN32 - char filePath[MAX_PATH + 1]; - GetModuleFileName(HINST_THISCOMPONENT, filePath, MAX_PATH + 1); -#endif + auto roads_pbo = rvff::cxx::create_pbo_reader_path(findPboPath(roadsPath).string()); - auto gdalPath = fs::path(filePath); - gdalPath = gdalPath.remove_filename(); + auto roadsPathDir = ((fs::path)roadsPath).remove_filename().string(); + if (boost::starts_with(roadsPathDir, "\\")) { + roadsPathDir = roadsPathDir.substr(1); + } -#ifdef _WIN32 - // remove win garbage - gdalPath = gdalPath.string().substr(4); - - // get appdata path - fs::path gdalLogPath; - PWSTR localAppdataPath = NULL; - if (SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppdataPath) != S_OK) { - gdalLogPath = "grad_meh_gdal.log"; - } - else { - gdalLogPath = (fs::path)localAppdataPath / "Arma 3" / "grad_meh_gdal.log"; - }; - CoTaskMemFree(localAppdataPath); + auto prefix = roads_pbo->get_prefix(); -#endif + for (auto& entry : roads_pbo->get_pbo().entries) { + if (boost::istarts_with((((fs::path)static_cast(prefix)) / static_cast(entry.filename)).string(), roadsPathDir)) { + roads_pbo->extract_single_file(static_cast(entry.filename), basePathGeojsonTemp.string(), false); + } + } - CPLSetConfigOption("GDAL_DATA", gdalPath.string().c_str()); - CPLSetConfigOption("CPL_LOG", gdalLogPath.string().c_str()); - CPLSetConfigOption("CPL_LOG_ERRORS", "ON"); - - const char* pszFormat = "GeoJSON"; - GDALDriver* poDriver; - char** papszMetadata; - GDALAllRegister(); - poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat); - if (poDriver == NULL) { - threadLock.lock(); - sqf::diag_log("Couldn't get the GeoJSON Driver"); - threadLock.unlock(); - return; - } - papszMetadata = poDriver->GetMetadata(); + auto gdalPath = getDllPath(); +#ifdef _WIN32 + // get appdata path + fs::path gdalLogPath; + PWSTR localAppdataPath = NULL; + if (SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppdataPath) != S_OK) { + gdalLogPath = "grad_meh_gdal.log"; + } + else { + gdalLogPath = (fs::path)localAppdataPath / "Arma 3" / "grad_meh_gdal.log"; + }; + CoTaskMemFree(localAppdataPath); +#endif - GDALDatasetH poDatasetH = GDALOpenEx((basePathGeojsonTemp / "roads.shp").string().c_str(), GDAL_OF_VECTOR, NULL, NULL, NULL); + CPLSetConfigOption("GDAL_DATA", gdalPath.string().c_str()); + CPLSetConfigOption("CPL_LOG", gdalLogPath.string().c_str()); + CPLSetConfigOption("CPL_LOG_ERRORS", "ON"); + + const char* pszFormat = "GeoJSON"; + GDALDriver* poDriver; + char** papszMetadata; + GDALAllRegister(); + poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat); + if (poDriver == NULL) { + PLOG_ERROR << "Couldn't get the GeoJSON Driver"; + return; + } + papszMetadata = poDriver->GetMetadata(); - GDALDataset* poDataset = (GDALDataset*)poDatasetH; - GDALDataset* poDstDS; - char** papszOptions = NULL; + auto shape_path = (basePathGeojsonTemp / "roads.shp").string(); + auto index_path = (basePathGeojsonTemp / "roads.shx").string(); + auto dbase_path = (basePathGeojsonTemp / "roads.dbf").string(); - fs::remove(basePathGeojsonTemp / "roads.geojson"); - poDstDS = poDriver->Create((basePathGeojsonTemp / "roads.geojson").string().c_str(), - poDataset->GetRasterXSize(), poDataset->GetRasterYSize(), poDataset->GetBands().size(), GDT_Byte, papszOptions); + try { + if (rvff::cxx::check_for_magic_and_decompress_lzss_file(shape_path, esri_shape_magic)) { + PLOG_INFO << fmt::format("Decompressed LZSS compressed shape file ({})", shape_path); + } + if (rvff::cxx::check_for_magic_and_decompress_lzss_file(index_path, esri_shape_magic)) { + PLOG_INFO << fmt::format("Decompressed LZSS compressed index file ({})", index_path); + } + // Magic contains last update date (https://www.dbase.com/Knowledgebase/INT/db7_file_fmt.htm) + if (rvff::cxx::check_for_magic_and_decompress_lzss_file(dbase_path, { 0x03, 0x5F })) { + PLOG_INFO << fmt::format("Decompressed LZSS compressed dbase file ({})", dbase_path); + } + } + catch (const rust::Error& ex) { + PLOG_WARNING << fmt::format("Exception during magic/lzss check: {})", ex.what()); + } + GDALDatasetH poDatasetH = GDALOpenEx(shape_path.c_str(), GDAL_OF_VECTOR, NULL, NULL, NULL); - char* options[] = { "-f", "GeoJSON", nullptr }; + // Invalid shp/shx file (or lzss compressed?) + if (poDatasetH == nullptr) + { + PLOG_ERROR << fmt::format("Couldn't open roads.shp at {}", shape_path); + throw std::runtime_error("Couldn't open roads.shp"); + } - auto gdalOptions = GDALVectorTranslateOptionsNew(options, NULL); + GDALDataset* poDataset = (GDALDataset*)poDatasetH; + GDALDataset* poDstDS; + char** papszOptions = NULL; - int error = 0; - auto dataSet = GDALVectorTranslate((basePathGeojsonTemp / "roads.geojson").string().c_str(), poDstDS, 1, &poDatasetH, gdalOptions, &error); + fs::remove(basePathGeojsonTemp / "roads.geojson"); + poDstDS = poDriver->Create((basePathGeojsonTemp / "roads.geojson").string().c_str(), + poDataset->GetRasterXSize(), poDataset->GetRasterYSize(), static_cast(poDataset->GetBands().size()), GDT_Byte, papszOptions); - if (error != 0) { - threadLock.lock(); - sqf::diag_log("GDALVectorTranslate failed!"); - threadLock.unlock(); - return; - } + char* options[] = { PSTR("-f"), PSTR("GeoJSON"), nullptr }; - GDALClose(poDatasetH); - GDALClose(poDstDS); - GDALVectorTranslateOptionsFree(gdalOptions); + auto gdalOptions = GDALVectorTranslateOptionsNew(options, NULL); - grad_aff::Rap roadConfig; - roadConfig.parseConfig(basePathGeojsonTemp / "roadslib.cfg"); + int error = 0; + auto dataSet = GDALVectorTranslate((basePathGeojsonTemp / "roads.geojson").string().c_str(), poDstDS, 1, &poDatasetH, gdalOptions, &error); - // Parse RoadsLib.cfg and construct with:roadType Map - std::map> roadWidthMap = {}; - for (auto& rootEntry : roadConfig.classEntries) { - if (rootEntry->type == 0 && boost::iequals(rootEntry->name, "RoadTypesLibrary")) { - for (auto& roadEntry : std::static_pointer_cast(rootEntry)->classEntries) { - if (roadEntry->type == 0 && boost::istarts_with(roadEntry->name, "Road")) { - std::pair roadPair = {}; - for (auto roadValues : std::static_pointer_cast(roadEntry)->classEntries) { - if (roadValues->type == 1 && boost::iequals(roadValues->name, "width")) { - if (auto val = std::get_if(&std::static_pointer_cast(roadValues)->value)) { - roadPair.first = (float_t)*val; - } - else { - roadPair.first = std::get(std::static_pointer_cast(roadValues)->value); - } - continue; - } - if (roadValues->type == 1 && boost::iequals(roadValues->name, "map")) { - roadPair.second = boost::replace_all_copy(std::get(std::static_pointer_cast(roadValues)->value), " ", "_"); - continue; - } - } - roadWidthMap.insert({ std::stoi(roadEntry->name.substr(4)), roadPair }); - } - } + if (error != 0) { + PLOG_ERROR << "GDALVectorTranslate failed!"; + return; } - } - // calc additional roads - std::vector roadTypes = { "road", "main road", "track" }; - auto additionalRoads = std::map>>{}; - for (auto& roadType : roadTypes) { - if (mapObjects.find(roadType) != mapObjects.end()) { - for (auto& road : mapObjects.at(roadType)) { - auto correctedRoadType = roadType; - if (roadType == "mainroad" || roadType == "main road") { - // Blame Zade when this blows up - correctedRoadType = "main_road"; - } + GDALClose(poDatasetH); + GDALClose(poDstDS); + GDALVectorTranslateOptionsFree(gdalOptions); + + std::map> roadWidthMap = {}; - auto r11 = road.first.transformMatrix[0][0]; - auto r13 = road.first.transformMatrix[0][2]; + try { + auto road_config = rvff::cxx::create_cfg_path((basePathGeojsonTemp / "roadslib.cfg").string()); - auto r34 = 0; - auto r31 = road.first.transformMatrix[2][0]; - auto r32 = road.first.transformMatrix[2][1]; - auto r33 = road.first.transformMatrix[2][2]; + auto entries = road_config->get_entry_as_entries(std::vector < std::string> { "RoadTypesLibrary" }); + for (auto& entry : entries) + { + try { + auto _class = entry.get_entry_as_class(); + auto class_name = static_cast(_class->get_class_name()); + auto map = static_cast(_class->get_entry_as_string(std::vector < std::string> { "map" })); + auto width = _class->get_entry_as_number(std::vector < std::string> { "width" }); - // calculate rotation in RADIANS - auto theta = std::atan2(-1 * r31, std::sqrt(std::pow(r32, 2) + std::pow(r33, 2))); + auto map_fixed = boost::replace_all_copy(map, " ", "_"); + auto class_name_fixed = std::stoi(class_name.substr(4)); - if (r11 == 1.0f || r11 == -1.0f) { - theta = std::atan2(r13, r34); + std::pair roadPair = {}; + roadWidthMap.insert({ class_name_fixed, std::make_pair(width, map_fixed) }); } - else { - theta = std::atan2(-1 * r31, r11); + catch (const rust::Error& ex) { + PLOG_ERROR << ex.what(); } - - // Corrected center pos - auto centerCorX = road.first.transformMatrix[3][0] - road.second.bCeneter[0]; - auto centerCorY = road.first.transformMatrix[3][2] - road.second.bCeneter[2]; - - // calc rotated pos of corner - // https://gamedev.stackexchange.com/a/86780 - - /* - (x_1,y_1) --- (x_2,y_2) - | | - | | - (x_0,y_0) --- (x_3,y_3) - */ - - XYZTriplet lb; - XYZTriplet le; - XYZTriplet pb; - XYZTriplet pe; - std::optional map3Triplet; - for (auto& namedSelection : road.second.namedSelections) { - if (namedSelection.selectedName == "lb") { - lb = road.second.lodPoints[namedSelection.vertexTableIndexes[0]]; - } - if (namedSelection.selectedName == "le") { - le = road.second.lodPoints[namedSelection.vertexTableIndexes[0]]; - } - if (namedSelection.selectedName == "pb") { - pb = road.second.lodPoints[namedSelection.vertexTableIndexes[0]]; - } - if (namedSelection.selectedName == "pe") { - pe = road.second.lodPoints[namedSelection.vertexTableIndexes[0]]; + } + } + catch (const rust::Error& ex) { + PLOG_ERROR << fmt::format("Exception in roadslib.cfg parsing! Exception {}", ex.what()); + throw; + } + // calc additional roads + std::vector roadTypes = { "road", "main road", "track" }; + auto additionalRoads = std::map>>{}; + for (auto& roadType : roadTypes) { + if (mapObjects.find(roadType) != mapObjects.end()) { + for (auto& road : mapObjects.at(roadType)) { + auto correctedRoadType = roadType; + if (roadType == "mainroad" || roadType == "main road") { + // Blame Zade when this blows up + correctedRoadType = "main_road"; } - } - auto x0 = centerCorX + (lb[0] * std::cos(theta)) - (lb[2] * std::sin(theta)); - auto y0 = centerCorY + (lb[0] * std::sin(theta)) + (lb[2] * std::cos(theta)); + auto r11 = road.first.transform_matrx._0.x; + auto r13 = road.first.transform_matrx._0.z; - auto x1 = centerCorX + (le[0] * std::cos(theta)) - (le[2] * std::sin(theta)); - auto y1 = centerCorY + (le[0] * std::sin(theta)) + (le[2] * std::cos(theta)); + auto r34 = 0; + auto r31 = road.first.transform_matrx._2.x; + auto r32 = road.first.transform_matrx._2.y; + auto r33 = road.first.transform_matrx._2.z; - auto x2 = centerCorX + (pe[0] * std::cos(theta)) - (pe[2] * std::sin(theta)); - auto y2 = centerCorY + (pe[0] * std::sin(theta)) + (pe[2] * std::cos(theta)); + // calculate rotation in RADIANS + auto theta = std::atan2(-1 * r31, std::sqrt(std::pow(r32, 2) + std::pow(r33, 2))); + + if (r11 == 1.0f || r11 == -1.0f) { + theta = std::atan2(r13, r34); + } + else { + theta = std::atan2(-1 * r31, r11); + } - auto x3 = centerCorX + (pb[0] * std::cos(theta)) - (pb[2] * std::sin(theta)); - auto y3 = centerCorY + (pb[0] * std::sin(theta)) + (pb[2] * std::cos(theta)); + // Corrected center pos + auto centerCorX = road.first.transform_matrx._3.x - road.second.b_center.x; + auto centerCorY = road.first.transform_matrx._3.z - road.second.b_center.z; + + // calc rotated pos of corner + // https://gamedev.stackexchange.com/a/86780 + + /* + (x_1,y_1) --- (x_2,y_2) + | | + | | + (x_0,y_0) --- (x_3,y_3) + */ + + rvff::cxx::XYZTripletCxx lb = {}; + rvff::cxx::XYZTripletCxx le = {}; + rvff::cxx::XYZTripletCxx pb = {}; + rvff::cxx::XYZTripletCxx pe = {}; + std::optional map3Triplet = {}; + for (auto& namedSelection : road.second.named_selection) { + if (static_cast(namedSelection.name) == "lb") { + lb = road.second.vertices[namedSelection.selected_vertices.edges[0]]; + } + if (static_cast(namedSelection.name) == "le") { + le = road.second.vertices[namedSelection.selected_vertices.edges[0]]; + } + if (static_cast(namedSelection.name) == "pb") { + pb = road.second.vertices[namedSelection.selected_vertices.edges[0]]; + } + if (static_cast(namedSelection.name) == "pe") { + pe = road.second.vertices[namedSelection.selected_vertices.edges[0]]; + } + } - /* - auto x0 = centerCorX + (road.second.bMin[0] * std::cos(theta)) - (road.second.bMin[2] * std::sin(theta)); - auto y0 = centerCorY + (road.second.bMin[0] * std::sin(theta)) + (road.second.bMin[2] * std::cos(theta)); + auto x0 = centerCorX + (lb.x * std::cos(theta)) - (lb.z * std::sin(theta)); + auto y0 = centerCorY + (lb.x * std::sin(theta)) + (lb.z * std::cos(theta)); - auto x1 = centerCorX + (road.second.bMin[0] * std::cos(theta)) - (road.second.bMax[2] * std::sin(theta)); - auto y1 = centerCorY + (road.second.bMin[0] * std::sin(theta)) + (road.second.bMax[2] * std::cos(theta)); + auto x1 = centerCorX + (le.x * std::cos(theta)) - (le.z * std::sin(theta)); + auto y1 = centerCorY + (le.x * std::sin(theta)) + (le.z * std::cos(theta)); - auto x2 = centerCorX + (road.second.bMax[0] * std::cos(theta)) - (road.second.bMax[2] * std::sin(theta)); - auto y2 = centerCorY + (road.second.bMax[0] * std::sin(theta)) + (road.second.bMax[2] * std::cos(theta)); + auto x2 = centerCorX + (pe.x * std::cos(theta)) - (pe.z * std::sin(theta)); + auto y2 = centerCorY + (pe.x * std::sin(theta)) + (pe.z * std::cos(theta)); - auto x3 = centerCorX + (road.second.bMax[0] * std::cos(theta)) - (road.second.bMin[2] * std::sin(theta)); - auto y3 = centerCorY + (road.second.bMax[0] * std::sin(theta)) + (road.second.bMin[2] * std::cos(theta)); - */ + auto x3 = centerCorX + (pb.x * std::cos(theta)) - (pb.z * std::sin(theta)); + auto y3 = centerCorY + (pb.x * std::sin(theta)) + (pb.z * std::cos(theta)); - std::array rectangle = { SimplePoint(x0,y0), SimplePoint(x1,y1), SimplePoint(x2,y2),SimplePoint(x3,y3) }; + std::array rectangle = { SimplePoint(x0,y0), SimplePoint(x1,y1), SimplePoint(x2,y2),SimplePoint(x3,y3) }; - auto addRoadsResult = additionalRoads.find(correctedRoadType); - if (addRoadsResult == additionalRoads.end()) { - additionalRoads.insert({ correctedRoadType, {rectangle} }); - } - else { - addRoadsResult->second.push_back(rectangle); + auto addRoadsResult = additionalRoads.find(correctedRoadType); + if (addRoadsResult == additionalRoads.end()) { + additionalRoads.insert({ correctedRoadType, {rectangle} }); + } + else { + addRoadsResult->second.push_back(rectangle); + } } } } - } - std::ifstream inGeoJson(basePathGeojsonTemp / "roads.geojson"); - nl::json j; - inGeoJson >> j; - inGeoJson.close(); - - std::map roadMap = {}; - for (auto& feature : j["features"]) { - auto coordsUpdate = nl::json::array(); - for (auto& coords : feature["geometry"]["coordinates"]) { - coords[0] = (float_t)coords[0] - 200000.0f; - coordsUpdate.push_back(coords); - } - feature["geometry"]["coordinates"] = coordsUpdate; - - int32_t jId = feature["properties"]["ID"]; - feature["properties"].clear(); - feature["properties"]["width"] = roadWidthMap[jId].first; - - //auto ogrGeometry = OGRGeometryFactory::createFromGeoJson(feature["geometry"].dump().c_str()); - //ogrGeometry = ogrGeometry->Buffer(roadWidthMap[jId].first * GRAD_MEH_ROAD_WITH_FACTOR); - - //auto ret = ogrGeometry->exportToJson(); - //feature["geometry"] = nl::json::parse(ret); - //CPLFree(ret); - //OGRGeometryFactory::destroyGeometry(ogrGeometry); - - auto kvp = roadMap.find(roadWidthMap[jId].second); - if (kvp == roadMap.end()) { - kvp = roadMap.insert({ roadWidthMap[jId].second, nl::json() }).first; - } - kvp->second.push_back(feature); - } - - // separate roads into main_road, track, etc. - for (auto& [key, value] : roadMap) { - writeGZJson((key + std::string(".geojson.gz")), basePathGeojsonRoads, value); - } - - for (auto& [key, value] : additionalRoads) { - nl::json bridge; - - for (auto& rectangles : value) { - nl::json addtionalRoadJson; - - nl::json geometry; - geometry["type"] = "Polygon"; - geometry["coordinates"].push_back({ nl::json::array() }); - for (auto& point : rectangles) { - nl::json points = nl::json::array(); - points.push_back(point.x); - points.push_back(point.y); - geometry["coordinates"][0].push_back(points); + std::ifstream inGeoJson(basePathGeojsonTemp / "roads.geojson"); + nl::json j; + inGeoJson >> j; + inGeoJson.close(); + + std::map roadMap = {}; + for (auto& feature : j["features"]) { + auto coordsUpdate = nl::json::array(); + for (auto& coords : feature["geometry"]["coordinates"]) { + coords[0] = (float_t)coords[0] - 200000.0f; + coordsUpdate.push_back(coords); + } + feature["geometry"]["coordinates"] = coordsUpdate; + + // empty property happens when dbase file is invalid/compressed + auto propId = feature["properties"]["ID"]; + if (!propId.is_number_integer() && !propId.is_number_float()) { + PLOG_WARNING << "Skipping feature with 'null' ID"; + break; } - normalizePolygon(geometry["coordinates"][0]); + int32_t jId = propId;// feature["properties"]["ID"]; + feature["properties"].clear(); + feature["properties"]["width"] = roadWidthMap[jId].first; - addtionalRoadJson["geometry"] = geometry; - addtionalRoadJson["properties"] = nl::json::object(); - addtionalRoadJson["type"] = "Feature"; + auto kvp = roadMap.find(roadWidthMap[jId].second); + if (kvp == roadMap.end()) { + kvp = roadMap.insert({ roadWidthMap[jId].second, nl::json() }).first; + } + kvp->second.push_back(feature); + } - bridge.push_back(addtionalRoadJson); + // separate roads into main_road, track, etc. + for (auto& [key, value] : roadMap) { + writeGZJson((key + std::string(".geojson.gz")), basePathGeojsonRoads, value); } - writeGZJson((key + std::string("-bridge.geojson.gz")), basePathGeojsonRoads, bridge); - } + for (auto& [key, value] : additionalRoads) { + nl::json bridge; - /* - for (auto& [key, value] : roadMap) { - // Append additional roads - auto kvp = additionalRoads.find(key); - if (kvp != additionalRoads.end()) { - for (auto& rectangles : kvp->second) { + for (auto& rectangles : value) { nl::json addtionalRoadJson; nl::json geometry; @@ -508,27 +484,34 @@ void writeRoads(grad_aff::Wrp& wrp, const std::string& worldName, std::filesyste geometry["coordinates"][0].push_back(points); } + normalizePolygon(geometry["coordinates"][0]); + addtionalRoadJson["geometry"] = geometry; addtionalRoadJson["properties"] = nl::json::object(); addtionalRoadJson["type"] = "Feature"; - value.push_back(addtionalRoadJson); + bridge.push_back(addtionalRoadJson); } + + writeGZJson((key + std::string("-bridge.geojson.gz")), basePathGeojsonRoads, bridge); } - writeGZJson((key + std::string(".geojson.gz")), basePathGeojsonRoads, value); + + } + catch (const rust::Error& ex) { + PLOG_ERROR << fmt::format("Exception in writeRoads"); + PLOG_ERROR << ex.what(); + throw; } - */ // Remove temp dir fs::remove_all(basePathGeojsonTemp); } -void writeSpecialIcons(grad_aff::Wrp& wrp, fs::path& basePathGeojson, uint32_t id, const std::string& name) { +void writeSpecialIcons(rvff::cxx::OprwCxx& wrp, fs::path& basePathGeojson, uint32_t id, const std::string& name) { auto treeLocations = nl::json(); - for (auto& mapInfo : wrp.mapInfo) { - if (mapInfo->mapType == 1 && mapInfo->infoType == id) { - auto mapInfo1Ptr = std::static_pointer_cast(mapInfo); + for (auto& mapInfo : wrp.map_infos_1) { + if (mapInfo.type_id == id) { auto pointFeature = nl::json(); pointFeature["type"] = "Feature"; @@ -537,8 +520,8 @@ void writeSpecialIcons(grad_aff::Wrp& wrp, fs::path& basePathGeojson, uint32_t i geometry["type"] = "Point"; auto posArray = nl::json::array(); - posArray.push_back((float_t)mapInfo1Ptr->x); - posArray.push_back((float_t)mapInfo1Ptr->y); + posArray.push_back((float_t)mapInfo.x); + posArray.push_back((float_t)mapInfo.y); geometry["coordinates"] = posArray; pointFeature["geometry"] = geometry; @@ -566,7 +549,7 @@ float_t calculateDistance(types::auto_array start, SimpleVect return 0; } - return std::cos(angleInRad) * vec2Magnitude; + return static_cast(std::cos(angleInRad) * vec2Magnitude); } float_t calculateMaxDistance(types::auto_array ilsPos, types::auto_array ilsDirection, @@ -589,7 +572,7 @@ float_t calculateMaxDistance(types::auto_array ilsPos, types: return maxDistance; } -nl::json buildRunwayPolygon(sqf::config_entry& runwayConfig) { +nl::json buildRunwayPolygon(sqf::config_entry &runwayConfig) { auto ilsPos = sqf::get_array(runwayConfig >> "ilsPosition").to_array(); auto ilsDirection = sqf::get_array(runwayConfig >> "ilsDirection").to_array(); auto taxiIn = sqf::get_array(runwayConfig >> "ilsTaxiIn").to_array(); @@ -607,7 +590,7 @@ nl::json buildRunwayPolygon(sqf::config_entry& runwayConfig) { auto dx = endPos[0] - startPos[0]; auto dy = endPos[1] - startPos[1]; - auto lineLength = std::sqrt(std::pow(dx, 2) + std::pow(dy, 2)); + auto lineLength = static_cast(std::sqrt(std::pow(dx, 2) + std::pow(dy, 2))); dx /= lineLength; dy /= lineLength; @@ -665,7 +648,7 @@ void writeRunways(fs::path& basePathGeojson, const std::string& worldName) { writeGZJson("runway.geojson.gz", basePathGeojson, runways); } -void writeGenericMapTypes(fs::path& basePathGeojson, const std::vector>& objectPairs, const std::string& name) { +void writeGenericMapTypes(fs::path& basePathGeojson, const std::vector>& objectPairs, const std::string& name) { if (objectPairs.size() == 0) return; @@ -678,8 +661,8 @@ void writeGenericMapTypes(fs::path& basePathGeojson, const std::vectormapType == 5) { - auto mapInfo5Ptr = std::static_pointer_cast(mapInfo); - nl::json mapFeature; - mapFeature["type"] = "Feature"; + for (auto& mapInfo : wrp.map_infos_5) { + nl::json mapFeature; + mapFeature["type"] = "Feature"; - auto coordArr = nl::json::array(); - coordArr.push_back({ mapInfo5Ptr->floats[0], mapInfo5Ptr->floats[1] }); - coordArr.push_back({ mapInfo5Ptr->floats[2], mapInfo5Ptr->floats[3] }); + auto coordArr = nl::json::array(); + coordArr.push_back({ mapInfo.floats[0], mapInfo.floats[1] }); + coordArr.push_back({ mapInfo.floats[2], mapInfo.floats[3] }); - mapFeature["geometry"] = { { "type" , "LineString" }, { "coordinates" , coordArr } }; - mapFeature["properties"] = nl::json::object(); + mapFeature["geometry"] = { { "type" , "LineString" }, { "coordinates" , coordArr } }; + mapFeature["properties"] = nl::json::object(); - powerline.push_back(mapFeature); - } + powerline.push_back(mapFeature); } if (!powerline.empty()) writeGZJson("powerline.geojson.gz", basePathGeojson, powerline); } -void writeRailways(fs::path& basePathGeojson, const std::vector>& objectPairs) { +void writeRailways(fs::path& basePathGeojson, const std::vector>& objectPairs) { if (objectPairs.size() == 0) return; nl::json railways = nl::json::array(); for (auto& objectPair : objectPairs) { - XYZTriplet map1Triplet; - XYZTriplet map2Triplet; - std::optional map3Triplet; - for (auto& namedSelection : objectPair.second.namedSelections) { - if (namedSelection.selectedName == "map1") { - map1Triplet = objectPair.second.lodPoints[namedSelection.vertexTableIndexes[0]]; + rvff::cxx::XYZTripletCxx map1Triplet; + rvff::cxx::XYZTripletCxx map2Triplet; + std::optional map3Triplet; + for (auto& namedSelection : objectPair.second.named_selection) { + rvff::cxx::XYZTripletCxx triplet = {}; + if (!namedSelection.vertex_indices.empty()) { + auto vertex_index = namedSelection.vertex_indices[0]; + triplet = objectPair.second.vertices[vertex_index]; + } + else if (!namedSelection.selected_vertices.edges.empty()) { // for older p3ds + auto vertex_index = namedSelection.selected_vertices.edges[0]; + triplet = objectPair.second.vertices[vertex_index]; } - if (namedSelection.selectedName == "map2") { - map2Triplet = objectPair.second.lodPoints[namedSelection.vertexTableIndexes[0]]; + + if (namedSelection.name == "map1") { + map1Triplet = triplet;//objectPair.second.normals[vertex_index]; + } + if (namedSelection.name == "map2") { + map2Triplet = triplet;//objectPair.second.normals[vertex_index]; } - if (namedSelection.selectedName == "map3") { - map3Triplet = objectPair.second.lodPoints[namedSelection.vertexTableIndexes[0]]; + if (namedSelection.name == "map3") { + map3Triplet = triplet;//objectPair.second.normals[vertex_index]; } } - auto xPos = objectPair.first.transformMatrix[3][0]; - auto yPos = objectPair.first.transformMatrix[3][2]; + auto xPos = objectPair.first.transform_matrx._3.x; + auto yPos = objectPair.first.transform_matrx._3.z; - auto r11 = objectPair.first.transformMatrix[0][0]; - auto r13 = objectPair.first.transformMatrix[0][2]; + auto r11 = objectPair.first.transform_matrx._0.x; + auto r13 = objectPair.first.transform_matrx._0.z; auto r34 = 0; - auto r31 = objectPair.first.transformMatrix[2][0]; - auto r32 = objectPair.first.transformMatrix[2][1]; - auto r33 = objectPair.first.transformMatrix[2][2]; + auto r31 = objectPair.first.transform_matrx._2.x; + auto r32 = objectPair.first.transform_matrx._2.y; + auto r33 = objectPair.first.transform_matrx._2.z; // calculate rotation in RADIANS auto theta = std::atan2(-1 * r31, std::sqrt(std::pow(r32, 2) + std::pow(r33, 2))); @@ -756,11 +746,11 @@ void writeRailways(fs::path& basePathGeojson, const std::vectorexportToJson(); + river["geometry"] = nl::json::parse(ogrJson); + CPLFree(ogrJson); + OGRGeometryFactory::destroyGeometry(hull); + + river["properties"] = nl::json::object(); + + rivers.push_back(river); + } + + writeGZJson("river.geojson.gz", basePathGeojson, rivers); +} + +void writeMounts(rvff::cxx::OprwCxx& wrp, fs::path& basePathGeojson) { + + auto mounts = nl::json(); + for (auto& mount : wrp.mountains) { + auto pointFeature = nl::json(); + pointFeature["type"] = "Feature"; + + auto geometry = nl::json(); + geometry["type"] = "Point"; + + auto posArray = nl::json::array(); + posArray.push_back((float_t)mount.x); + posArray.push_back((float_t)mount.z); + geometry["coordinates"] = posArray; + + pointFeature["geometry"] = geometry; + pointFeature["properties"] = nl::json::object(); + pointFeature["properties"]["elevation"] = mount.y; + + mounts.push_back(pointFeature); + } + if (!mounts.is_null() && mounts.is_array() && !mounts.empty()) { + writeGZJson("mounts.geojson.gz", basePathGeojson, mounts); + } +} + +void writeGeojsons(rvff::cxx::OprwCxx& wrp, std::filesystem::path& basePathGeojson, const std::string& worldName) { - std::vector modelInfos; + using namespace rvff::cxx; + + std::vector modelInfos; modelInfos.resize(wrp.models.size()); - std::vector> modelMapTypes; + std::vector> modelMapTypes; modelMapTypes.resize(wrp.models.size()); - std::map> pboMap; + + std::map> pboMap; for (int i = 0; i < wrp.models.size(); i++) { - auto modelPath = wrp.models[i]; + auto modelPath = static_cast(wrp.models[i]); if (boost::starts_with(modelPath, "\\")) { modelPath = modelPath.substr(1); } auto pboPath = findPboPath(modelPath); + try { + if (pboPath.empty()) { + PLOG_WARNING << fmt::format("Couldn't find path for model: {}", modelPath); + modelMapTypes[i] = {}; + modelInfos[i] = {}; + break; + } - if (pboPath.empty()) { - modelMapTypes[i] = {}; - modelInfos[i] = {}; - break; - } + if (pboMap.count(pboPath) < 1) { + pboMap.emplace(pboPath, std::move(rvff::cxx::create_pbo_reader_path(pboPath.string()))); + } - std::shared_ptr pbo = {}; - auto res = pboMap.find(pboPath); - if (res == pboMap.end()) { - auto pboPtr = std::make_shared(pboPath.string()); - pboMap.insert({ pboPath, pboPtr }); - pbo = pboPtr; - } - else { - pbo = res->second; - } + int rvffIndex = -1; + int affIndex = -1; - auto p3dData = pbo->getEntryData(modelPath); + auto p3dData = pboMap.find(pboPath)->second->get_entry_data(modelPath); - auto odol = grad_aff::Odol(p3dData); - odol.readOdol(false); + if (p3dData.empty()) { + PLOG_WARNING << fmt::format("Couldn't get data for model: {} (PBO: {})", modelPath, pboPath.string()); + modelMapTypes[i] = {}; + modelInfos[i] = {}; + break; + } - odol.peekLodTypes(); + PLOG_INFO << fmt::format("Reading P3D: {} (PBO: {})", modelPath, pboPath.string()); - auto geoIndex = -1; - for (int j = 0; j < odol.lods.size(); j++) { - if (odol.lods[j].lodType == LodType::GEOMETRY) { - geoIndex = j; + if (checkMagic(p3dData, "MLOD")) { + PLOG_INFO << fmt::format("Skipping MLOD!"); + continue; } - } - if (geoIndex != -1) { - auto retLod = odol.readLod(geoIndex); + auto odol_reader = rvff::cxx::create_odol_lazy_reader_vec(p3dData); + auto odol2 = odol_reader->get_odol(); - auto findClass = retLod.tokens.find("map"); - if (findClass != retLod.tokens.end()) { - if (findClass->second == "railway" || findClass->second == "road" || findClass->second == "track" || findClass->second == "main road") { - geoIndex = -1; - for (int j = 0; j < odol.lods.size(); j++) { - if (odol.lods[j].lodType == LodType::SPECIAL_LOD) { - geoIndex = j; - break; - } - if (geoIndex == -1 && odol.lods[j].lodType == LodType::GEOMETRY) { // Fallback - geoIndex = j; + rvff::cxx::LodCxx lod = {}; + + bool foundGeoLod = false; + bool foundMemoryLod = false; + for (auto& res : odol2.resolutions) + { + if (res.res == rvff::cxx::ResolutionEnumCxx::Geometry) { + foundGeoLod = true; + } + else if (res.res == rvff::cxx::ResolutionEnumCxx::Memory) { + foundMemoryLod = true; + } + if (foundGeoLod && foundMemoryLod) { + break; + } + } + + if (foundGeoLod) + { + lod = odol_reader->read_lod(rvff::cxx::ResolutionEnumCxx::Geometry); + rvffIndex = 1; + + for (auto& prop : lod.named_properties) { + if (static_cast(prop.property) == "map") + { + auto val = static_cast(prop.value); + if ((val == "railway" || val == "road" || val == "track" || val == "main road") && foundMemoryLod) { + lod = odol_reader->read_lod(rvff::cxx::ResolutionEnumCxx::Memory); + rvffIndex = 2; } + + modelMapTypes[i] = { static_cast(val), lod }; + modelInfos[i] = lod; + break; } - auto memLod = odol.readLod(geoIndex); - modelMapTypes[i] = { findClass->second, memLod }; - modelInfos[i] = memLod; - } - else { - modelMapTypes[i] = { findClass->second, retLod }; - modelInfos[i] = retLod; } } } + catch (const rust::Error& ex) { + PLOG_ERROR << fmt::format("Exception while handling models P3D: {} (PBO: {})", modelPath, pboPath.string()); + PLOG_ERROR << ex.what(); + throw; + } } // mapType, [object, lod] - std::map>> objectMap = {}; - /* - std::vector forestPositions = {}; - std::vector forestSquarePositions = {}; - std::vector forestTrianglePositions = {}; - std::vector forestBroderPositions = {}; - - std::vector forestFeulStatPositions = {}; - std::vector forestBroderPositions = {}; - */ + std::map>> objectMap = {}; + for (auto& object : wrp.objects) { - auto mapType = modelMapTypes[object.modelIndex].first; + auto mapType = modelMapTypes[object.model_index].first; auto res = objectMap.find(mapType); if (res != objectMap.end()) { - res->second.push_back({ object, modelMapTypes[object.modelIndex].second }); + res->second.push_back({ object, modelMapTypes[object.model_index].second }); } else { - objectMap.insert({ mapType, {{object, modelMapTypes[object.modelIndex].second}} }); - } - /* - if (!forestModels[index].empty()) { - Point_ p; - p.x = object.transformMatrix[3][0]; - p.y = object.transformMatrix[3][2]; - p.z = 0; - p.clusterID = UNCLASSIFIED; - forestPositions.push_back(p); - } - */ + objectMap.insert({ mapType, {{object, modelMapTypes[object.model_index].second}} }); + } } std::vector genericMapTypes = { "bunker", "chapel", "church", "cross", "fuelstation", "lighthouse", "rock", "shipwreck", "transmitter", "tree", "rock", "watertower", @@ -930,7 +980,10 @@ void writeGeojsons(grad_aff::Wrp& wrp, std::filesystem::path& basePathGeojson, c writeSpecialIcons(wrp, basePathGeojson, 2, "bush"); writeSpecialIcons(wrp, basePathGeojson, 11, "rock"); writePowerlines(wrp, basePathGeojson); + writeMounts(wrp, basePathGeojson); writeRunways(basePathGeojson, worldName); + writeRiver(wrp, basePathGeojson); + } diff --git a/src/geojsons.h b/src/geojsons.h index 2c6ccf8..13bcf95 100644 --- a/src/geojsons.h +++ b/src/geojsons.h @@ -4,15 +4,14 @@ #include "util.h" + #include #include #include #include -#include -#include -#include -#include +#include +#include #include #include @@ -29,13 +28,7 @@ #include #include #include - -// https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483 -EXTERN_C IMAGE_DOS_HEADER __ImageBase; -#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) -#else -#error Only Win is supported -#endif // _WIN32 +#endif using namespace intercept; @@ -43,17 +36,19 @@ namespace fs = std::filesystem; namespace nl = nlohmann; void writeLocations(const std::string& worldName, std::filesystem::path& basePathGeojson); -void writeHouses(grad_aff::Wrp& wrp, std::filesystem::path& basePathGeojson); -void writeObjects(grad_aff::Wrp& wrp, std::filesystem::path& basePathGeojson); -void writeRoads(grad_aff::Wrp& wrp, const std::string& worldName, std::filesystem::path& basePathGeojson, - const std::map>>& mapObjects); -void writeSpecialIcons(grad_aff::Wrp& wrp, fs::path& basePathGeojson, uint32_t id, const std::string& name); +void writeHouses(rvff::cxx::OprwCxx& wrp, std::filesystem::path& basePathGeojson); +void writeObjects(rvff::cxx::OprwCxx& wrp, std::filesystem::path& basePathGeojson); +void writeRoads(rvff::cxx::OprwCxx& wrp, const std::string& worldName, std::filesystem::path& basePathGeojson, + const std::map>>& mapObjects); +void writeSpecialIcons(rvff::cxx::OprwCxx& wrp, fs::path& basePathGeojson, uint32_t id, const std::string& name); float_t calculateDistance(types::auto_array start, SimpleVector vector, SimpleVector point); float_t calculateMaxDistance(types::auto_array ilsPos, types::auto_array ilsDirection, types::auto_array taxiIn, types::auto_array taxiOff); nl::json buildRunwayPolygon(sqf::config_entry& runwayConfig); void writeRunways(fs::path& basePathGeojson, const std::string& worldName); -void writeGenericMapTypes(fs::path& basePathGeojson, const std::vector>& objectPairs, const std::string& name); -void writePowerlines(grad_aff::Wrp& wrp, fs::path& basePathGeojson); -void writeRailways(fs::path& basePathGeojson, const std::vector>& objectPairs); -void writeGeojsons(grad_aff::Wrp& wrp, std::filesystem::path& basePathGeojson, const std::string& worldName); \ No newline at end of file +void writeGenericMapTypes(fs::path& basePathGeojson, const std::vector>& objectPairs, const std::string& name); +void writePowerlines(rvff::cxx::OprwCxx& wrp, fs::path& basePathGeojson); +void writeRailways(fs::path& basePathGeojson, const std::vector>& objectPairs); +void writeGeojsons(rvff::cxx::OprwCxx& wrp, std::filesystem::path& basePathGeojson, const std::string& worldName); +void writeRiver(rvff::cxx::OprwCxx& wrp, std::filesystem::path& basePathGeojson); +void writeMounts(rvff::cxx::OprwCxx& wrp, fs::path& basePathGeojson); diff --git a/src/main.cpp b/src/main.cpp index bb22f0f..08949ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ #include "version.h" +#include + // String #include #include @@ -16,6 +18,8 @@ #include #include +// #include +#include #include #include @@ -31,11 +35,14 @@ #include -#include -#include -#include -#include -#include +#include +#include +#include + +#include +#include +#include +#include #include "findPbos.h" #include "SimplePoint.h" @@ -43,6 +50,7 @@ #include "../addons/main/status_codes.hpp" using namespace intercept; +using namespace OIIO; namespace fs = std::filesystem; namespace nl = nlohmann; @@ -54,11 +62,11 @@ using SQFPar = game_value_parameter; static bool gradMehIsRunning = false; -int intercept::api_version() { //This is required for the plugin to work. +int intercept::api_version() { // This is required for the plugin to work. return INTERCEPT_SDK_API_VERSION; } -void writeMeta(const std::string& worldName, const int32_t& worldSize, fs::path& basePath) +void writeMeta(const std::string &worldName, const int32_t &worldSize, fs::path &basePath) { client::invoker_lock threadLock; auto mapConfig = sqf::config_entry(sqf::config_file()) >> "CfgWorlds" >> worldName; @@ -75,13 +83,15 @@ void writeMeta(const std::string& worldName, const int32_t& worldSize, fs::path& meta["gridOffsetY"] = sqf::get_number(mapConfig >> "Grid" >> "offsetY"); auto colorArray = sqf::get_array(mapConfig >> "OutsideTerrain" >> "colorOutside").to_array(); - if (!colorArray.empty()) { + if (!colorArray.empty()) + { meta["colorOutside"] = std::vector(colorArray.begin(), colorArray.end()); } auto gridArray = nl::json::array(); - for (auto& grid : sqf::config_classes("true", (mapConfig >> "Grid"))) { + for (auto &grid : sqf::config_classes("true", (mapConfig >> "Grid"))) + { auto entry = sqf::config_entry(grid); nl::json entryGrid; @@ -103,22 +113,24 @@ void writeMeta(const std::string& worldName, const int32_t& worldSize, fs::path& out.close(); } -void writeDem(fs::path& basePath, grad_aff::Wrp& wrp, const int32_t& worldSize) +void writeDem(fs::path &basePath, rvff::cxx::OprwCxx &wrp, const int32_t &worldSize) { - auto cellsize = (float_t)worldSize / wrp.mapSizeX; + auto cellsize = (float_t)worldSize / wrp.map_size_x; std::stringstream demStringStream; - demStringStream << "ncols " << wrp.mapSizeX << std::endl; - demStringStream << "nrows " << wrp.mapSizeY << std::endl; + demStringStream << "ncols " << wrp.map_size_x << std::endl; + demStringStream << "nrows " << wrp.map_size_y << std::endl; demStringStream << "xllcorner " << 0.0 << std::endl; demStringStream << "yllcorner " << -cellsize << std::endl; demStringStream << "cellsize " << cellsize << std::endl; // worldSize / mapsizex demStringStream << "NODATA_value " << -9999; demStringStream << std::endl; - for (int64_t y = wrp.mapSizeY - 1; y >= 0; y--) { - for (size_t x = 0; x < wrp.mapSizeX; x++) { - demStringStream << wrp.elevation[x + wrp.mapSizeX * y] << " "; + for (int64_t y = wrp.map_size_y - 1; y >= 0; y--) + { + for (size_t x = 0; x < wrp.map_size_x; x++) + { + demStringStream << wrp.elevation[x + wrp.map_size_x * y] << " "; } demStringStream << std::endl; } @@ -132,7 +144,7 @@ void writeDem(fs::path& basePath, grad_aff::Wrp& wrp, const int32_t& worldSize) demOut.close(); } -void writePreviewImage(const std::string& worldName, std::filesystem::path& basePath) +void writePreviewImage(const std::string &worldName, std::filesystem::path &basePath) { client::invoker_lock threadLock; auto mapConfig = sqf::config_entry(sqf::config_file()) >> "CfgWorlds" >> worldName; @@ -148,20 +160,33 @@ void writePreviewImage(const std::string& worldName, std::filesystem::path& base } auto pboPath = findPboPath(picturePath); - if (pboPath.empty()) { return; } - - grad_aff::Pbo prewviewPbo(pboPath.string()); - - auto paa = grad_aff::Paa(); - paa.readPaa(prewviewPbo.getEntryData(picturePath)); - paa.writeImage((basePath / "preview.png").string()); + try { + auto previewPbo = rvff::cxx::create_pbo_reader_path(pboPath.string()); + auto previewData = previewPbo->get_entry_data(picturePath); + auto previewMipmap = rvff::cxx::get_mipmap_from_paa_vec(previewData, 0); + + auto previewFileName = (basePath / "preview.png").string(); + + std::unique_ptr out = ImageOutput::create(previewFileName); + if (!out) + return; + ImageSpec spec(previewMipmap.width, previewMipmap.height, 4, TypeDesc::UINT8); + out->open(previewFileName, spec); + out->write_image(TypeDesc::UINT8, previewMipmap.data.data()); + out->close(); + } + catch (const rust::Error& ex) { + PLOG_ERROR << fmt::format("Exception in writePreviewImage PBO: {} Picture Path: {}", pboPath.string(), picturePath); + PLOG_ERROR << ex.what(); + throw; + } } - -void extractMap(const std::string& worldName, const std::string& worldPath, std::array& steps) { +void extractMap(const std::string &worldName, const std::string &worldPath, std::array &steps) +{ auto lowerWorldName = boost::algorithm::to_lower_copy(worldName); @@ -173,10 +198,11 @@ void extractMap(const std::string& worldName, const std::string& worldPath, std: startMsg << "Starting export of " << worldName << " ["; startMsg << std::boolalpha; - for(auto i = 0; i < steps.size(); i++) + for (auto i = 0; i < steps.size(); i++) { startMsg << steps[i]; - if (i < (steps.size() - 1)) { + if (i < (steps.size() - 1)) + { startMsg << ", "; } } @@ -184,82 +210,96 @@ void extractMap(const std::string& worldName, const std::string& worldPath, std: prettyDiagLog(startMsg.str()); - if (!fs::exists(basePath)) { + if (!fs::exists(basePath)) + { fs::create_directories(basePath); } - if (!fs::exists(basePathGeojson)) { + if (!fs::exists(basePathGeojson)) + { fs::create_directories(basePathGeojson); } - if (!fs::exists(basePathSat)) { + if (!fs::exists(basePathSat)) + { fs::create_directories(basePathSat); } std::string curWorldPath = ""; + try { + // Find Wrp Path + auto wrpPath = findPboPath(worldPath); + curWorldPath = wrpPath.string(); + auto wrpPboReader = rvff::cxx::create_pbo_reader_path(wrpPath.string()); - // Find Wrp Path - auto wrpPath = findPboPath(worldPath); - grad_aff::Pbo wrpPbo(wrpPath.string()); + auto wrp_data = wrpPboReader->get_entry_data(worldPath); - auto wrp = grad_aff::Wrp(wrpPbo.getEntryData(worldPath)); - wrp.wrpName = worldName + ".wrp"; - try { - if (steps[0] || steps[1] || steps[3] || steps[4]) { + auto wrp = rvff::cxx::OprwCxx{}; + // wrp.wrpName = worldName + ".wrp"; + + if (steps[0] || steps[1] || steps[3] || steps[4]) + { reportStatus(worldName, "read_wrp", "running"); - wrp.readWrp(); + wrp = rvff::cxx::create_wrp_from_vec(wrp_data); reportStatus(worldName, "read_wrp", "done"); } - else { + else + { reportStatus(worldName, "read_wrp", "canceled"); } - } - catch (std::exception & ex) { // most likely caused by unknown mapinfo type - client::invoker_lock threadLock; - prettyDiagLog(std::string("exception while reading the wrp: ").append(ex.what())); - sqf::hint(ex.what()); - return; - } - auto worldSize = (uint32_t)wrp.layerCellSize * wrp.layerSizeX; + auto worldSize = (uint32_t)wrp.layer_cell_size * wrp.layer_size_x; - if (steps[0]) { - reportStatus(worldName, "write_sat", "running"); - prettyDiagLog("Exporting sat images"); - writeSatImages(wrp, worldSize, basePathSat, worldName); - reportStatus(worldName, "write_sat", "done"); - } - if (steps[1]) { - reportStatus(worldName, "write_houses", "running"); - prettyDiagLog("Exporting geojson"); - writeGeojsons(wrp, basePathGeojson, worldName); - reportStatus(worldName, "write_houses", "done"); - } - - if (steps[2]) { - reportStatus(worldName, "write_preview", "running"); - prettyDiagLog("Exporting preview image"); - writePreviewImage(worldName, basePath); - reportStatus(worldName, "write_preview", "done"); - } - - if (steps[3]) { - reportStatus(worldName, "write_meta", "running"); - prettyDiagLog("Exporting meta json"); - writeMeta(worldName, worldSize, basePath); - reportStatus(worldName, "write_meta", "done"); + if (steps[0]) + { + reportStatus(worldName, "write_sat", "running"); + prettyDiagLog("Exporting sat images"); + writeSatImages(wrp, worldSize, basePathSat, worldName); + reportStatus(worldName, "write_sat", "done"); + } + if (steps[1]) + { + reportStatus(worldName, "write_houses", "running"); + prettyDiagLog("Exporting geojson"); + writeGeojsons(wrp, basePathGeojson, worldName); + reportStatus(worldName, "write_houses", "done"); + } + + if (steps[2]) + { + reportStatus(worldName, "write_preview", "running"); + prettyDiagLog("Exporting preview image"); + writePreviewImage(worldName, basePath); + reportStatus(worldName, "write_preview", "done"); + } + + if (steps[3]) + { + reportStatus(worldName, "write_meta", "running"); + prettyDiagLog("Exporting meta json"); + writeMeta(worldName, worldSize, basePath); + reportStatus(worldName, "write_meta", "done"); + } + + if (steps[4]) + { + reportStatus(worldName, "write_dem", "running"); + prettyDiagLog("Exporting dem file"); + writeDem(basePath, wrp, worldSize); + reportStatus(worldName, "write_dem", "done"); + } } - - if (steps[4]) { - reportStatus(worldName, "write_dem", "running"); - prettyDiagLog("Exporting dem file"); - writeDem(basePath, wrp, worldSize); - reportStatus(worldName, "write_dem", "done"); + catch (const rust::Error& ex) { + PLOG_ERROR << "Exception in extract map command"; + PLOG_ERROR << fmt::format("WRP Path: {}", curWorldPath); + PLOG_ERROR << ex.what(); + throw; } gradMehIsRunning = false; return; } -game_value exportMapCommand(game_state& gs, SQFPar rightArg) { +game_value exportMapCommand(game_state &gs, SQFPar rightArg) +{ if (gradMehIsRunning) return GRAD_MEH_STATUS_ERR_ALREADY_RUNNING; @@ -272,106 +312,155 @@ game_value exportMapCommand(game_state& gs, SQFPar rightArg) { // [sat image, houses, preview img, meta.json, dem.asc] std::array steps = { true, true, true, true, true }; - if (rightArg.type_enum() == game_data_type::STRING) { + if (rightArg.type_enum() == game_data_type::STRING) + { worldName = r_string(rightArg).c_str(); } - else if (rightArg.type_enum() == game_data_type::ARRAY) { + else if (rightArg.type_enum() == game_data_type::ARRAY) + { auto parArray = rightArg.to_array(); - - if (parArray.size() <= 0 || parArray.size() >= 7) { + + if (parArray.size() <= 0 || parArray.size() >= 7) + { gs.set_script_error(iet::assertion_failed, "Wrong amount of arguments!"sv); return GRAD_MEH_STATUS_ERR_ARGS; } - if (parArray[0].type_enum() == game_data_type::STRING) { + if (parArray[0].type_enum() == game_data_type::STRING) + { worldName = r_string(parArray[0]); - for (int i = 1; i < parArray.size(); i++) { - if (parArray[i].type_enum() == game_data_type::BOOL) { + for (int i = 1; i < parArray.size(); i++) + { + if (parArray[i].type_enum() == game_data_type::BOOL) + { auto b = (bool)parArray[i]; steps[i - 1] = b; } - else { + else + { gs.set_script_error(iet::assertion_failed, types::r_string("Expected bool at index ").append(std::to_string(i)).append("!")); return GRAD_MEH_STATUS_ERR_ARGS; } } } - else { + else + { gs.set_script_error(iet::assertion_failed, "First element in the parameter array has to be a string!"sv); return GRAD_MEH_STATUS_ERR_ARGS; } - } - else { + else + { gs.set_script_error(iet::assertion_failed, "Expected a string or an array!"sv); return GRAD_MEH_STATUS_ERR_ARGS; } auto configWorld = sqf::config_entry(sqf::config_file()) >> "CfgWorlds" >> worldName; - if (!boost::iequals(sqf::config_name(configWorld), worldName)) { + if (!boost::iequals(sqf::config_name(configWorld), worldName)) + { gs.set_script_error(iet::assertion_failed, "Couldn't find the specified world!"sv); return GRAD_MEH_STATUS_ERR_NOT_FOUND; } // check for leading / std::string worldPath = sqf::get_text(configWorld >> "worldName"); - if (boost::starts_with(worldPath, "\\")) { + if (boost::starts_with(worldPath, "\\")) + { worldPath = worldPath.substr(1); } // try to find pbo auto wrpPboPath = findPboPath(worldPath); - if (wrpPboPath == "") { + if (wrpPboPath == "") + { return GRAD_MEH_STATUS_ERR_PBO_NOT_FOUND; } - else { - try { - grad_aff::Pbo wrpPbo(wrpPboPath.string()); - if (!wrpPbo.hasEntry(worldPath)) + else + { + try + { + auto wrpPboReader = rvff::cxx::create_pbo_reader_path(wrpPboPath.string()); + if (!wrpPboReader->has_entry(worldPath)) return GRAD_MEH_STATUS_ERR_PBO_NOT_FOUND; - } catch (std::exception& ex) { + } + catch (std::exception &ex) + { prettyDiagLog(std::string("Exception when opening PBO: ").append(ex.what())); return GRAD_MEH_STATUS_ERR_PBO_NOT_FOUND; } } - if (!gradMehIsRunning) { + if (!gradMehIsRunning) + { gradMehIsRunning = true; std::thread readWrpThread(extractMap, worldName, worldPath, steps); readWrpThread.detach(); return GRAD_MEH_STATUS_OK; } - else { + else + { prettyDiagLog("gradMeh is already running! Aborting!"); return GRAD_MEH_STATUS_ERR_ALREADY_RUNNING; } } -void intercept::pre_start() { +void intercept::pre_start() +{ static auto grad_meh_export_map_string = client::host::register_sqf_command("gradMehExportMap", "Exports the given map", exportMapCommand, game_data_type::SCALAR, game_data_type::STRING); static auto grad_meh_export_map_array = client::host::register_sqf_command("gradMehExportMap", "Exports the given map", exportMapCommand, game_data_type::SCALAR, game_data_type::ARRAY); +#if WIN32 + std::filesystem::path a3_log_path; + PWSTR path_tmp; + + auto get_folder_path_ret = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path_tmp); + + if (get_folder_path_ret == S_OK) { + a3_log_path = path_tmp; + } + CoTaskMemFree(path_tmp); + + a3_log_path = a3_log_path / "Arma 3"; + +#endif + +#if _DEBUG + auto severity = plog::Severity::debug; +#else + auto severity = plog::Severity::warning; +#endif + + plog::init(severity, fmt::format("{}/grad_meh_{:%Y-%m-%d_%H-%M-%S}.log", a3_log_path.string(), std::chrono::system_clock::now()).c_str()); + + PLOG_INFO << "Starting PBO Mapping"; + std::thread mapPopulateThread(populateMap); mapPopulateThread.detach(); } -void intercept::pre_init() { +void intercept::pre_init() +{ std::stringstream preInitMsg; preInitMsg << "The grad_meh plugin is running! ("; - if (GRAD_MEH_GIT_TAG.empty()) { + if (GRAD_MEH_GIT_TAG.empty()) + { preInitMsg << GRAD_MEH_GIT_REV; } - else { + else + { preInitMsg << GRAD_MEH_GIT_TAG; } preInitMsg << "@" << GRAD_MEH_GIT_BRANCH << ")"; intercept::sqf::system_chat(preInitMsg.str()); prettyDiagLog(preInitMsg.str()); + + PLOG_INFO << preInitMsg.str(); + } diff --git a/src/rvff-cxx b/src/rvff-cxx new file mode 160000 index 0000000..ba3029d --- /dev/null +++ b/src/rvff-cxx @@ -0,0 +1 @@ +Subproject commit ba3029dbf020789a305a534ee8adc9912383270b diff --git a/src/satimages.cpp b/src/satimages.cpp index 1c1b6f7..159f4f7 100644 --- a/src/satimages.cpp +++ b/src/satimages.cpp @@ -3,288 +3,327 @@ #include #include -using namespace OpenImageIO_v2_3; +using namespace OpenImageIO_v2_4; // Range TODO: replace with C++20 Range #include -float_t mean(std::vector& equalities) -{ - float_t mean = 0; - std::for_each(equalities.begin(), equalities.end(), [&mean](float_t& val) { - mean += val; - }); - - mean /= equalities.size(); - return mean; -} - -float_t compareColors(uint8_t r1, uint8_t g1, uint8_t b1, uint8_t r2, uint8_t g2, uint8_t b2) { - return (1 - (std::sqrt(std::pow(r2 - r1, 2) + std::pow(g2 - g1, 2) + std::pow(b2 - b1, 2)) / GRAD_MEH_MAX_COLOR_DIF)); -} - -float_t compareRows(std::vector::iterator mipmap1It, std::vector::iterator mipmap2It, uint32_t width) { - std::vector equalities = {}; - for (size_t i = 0; i < width; i++) { - equalities.push_back(compareColors(*mipmap1It, *(mipmap1It + 1), *(mipmap1It + 2), *mipmap2It, *(mipmap2It + 1), *(mipmap2It + 2))); - mipmap1It += 4; - mipmap2It += 4; - } - - return mean(equalities); -} - -uint32_t calcOverLap(MipMap& mipmap1, MipMap& mipmap2) { - std::vector> bestMatchingOverlaps = {}; - auto filter = mipmap1.height > 512 ? 6 : 3; - - for (uint32_t overlap = 1; overlap < (mipmap1.height / filter); overlap++) { - std::vector equalities = {}; +#include - for (size_t i = 0; i < overlap; i++) { - auto beginRowUpper = mipmap1.data.begin() + (mipmap2.height - 1 - (overlap - i)) * (size_t)mipmap1.width * 4; - auto beginRowLower = mipmap2.data.begin() + i * (size_t)mipmap2.width * 4; +#include - equalities.push_back(compareRows(beginRowUpper, - beginRowLower, - (uint32_t)mipmap1.width)); - } - - auto meanVal = mean(equalities); - if (meanVal > GRAD_MEH_OVERLAP_THRESHOLD) { - bestMatchingOverlaps.push_back({ meanVal, overlap }); - } - } +struct SatMapTile { + fs::path path = {}; + TileTransform tt = {}; + rvff::cxx::MipmapCxx mipmap = {}; +}; - uint32_t calculatedOverlap = 0; - float_t bestOverlap = 0; +struct FillerMapTile { + fs::path path = {}; + std::set tt = {}; + rvff::cxx::MipmapCxx mipmap = {}; +}; - for (auto& kvp : bestMatchingOverlaps) { - if (kvp.first > bestOverlap) { - bestOverlap = kvp.first; - calculatedOverlap = kvp.second; - } - } +static const std::vector texture_config_path { "Stage0", "texture" }; +static const std::vector texgen_config_path { "Stage0", "texGen" }; - return calculatedOverlap + 1; -} - -void writeSatImages(grad_aff::Wrp& wrp, const int32_t& worldSize, std::filesystem::path& basePathSat, const std::string& worldName) +void writeSatImages(rvff::cxx::OprwCxx& wrp, const int32_t& worldSize, std::filesystem::path& basePathSat, const std::string& worldName) { std::vector rvmats = {}; - for (auto& rv : wrp.rvmats) { - if (rv != "") { - rvmats.push_back(rv); + for (auto& rv : wrp.texures) { + if (!rv.texture_filename.empty()) { + rvmats.push_back(static_cast(rv.texture_filename)); } } std::sort(rvmats.begin(), rvmats.end()); if (rvmats.size() > 1) { - std::vector lcoPaths = {}; - lcoPaths.reserve(rvmats.size()); - - auto rvmatPbo = grad_aff::Pbo::Pbo(findPboPath(rvmats[0]).string()); - rvmatPbo.readPbo(false); - - // Has to be not empty - std::string lastValidRvMat = "1337"; - std::string fillerTile = ""; - std::string prefix = "s_"; - - uint32_t maxX2 = 0; - uint32_t maxY2 = 0; + std::vector satMapTiles = {}; + satMapTiles.reserve(rvmats.size()); + + std::string pboPath = ""; + try { + pboPath = findPboPath(rvmats[0]).string(); + auto rvmatPbo = rvff::cxx::create_pbo_reader_path(pboPath); + + // Has to be not empty + std::string lastValidRvMat = "1337"; + std::optional fillerTile = std::nullopt; + std::optional firstPos = std::nullopt; + std::string prefix = "s_"; + + std::optional> pbo = std::nullopt; + + for (auto& rvmatPath : rvmats) { + if (!boost::istarts_with(((fs::path)rvmatPath).filename().string(), lastValidRvMat)) { + PLOG_INFO << fmt::format("Getting data for {}", rvmatPath); + auto rap_data = rvmatPbo->get_entry_data(rvmatPath); + if (rap_data.empty()) { + PLOG_ERROR << "Rvmat data was empty!"; + } + PLOG_INFO << fmt::format("Parsing rvmat {}", rvmatPath); + auto rap = rvff::cxx::create_cfg_vec(rap_data); + auto textureStr = static_cast(rap->get_entry_as_string(texture_config_path)); + if (!textureStr.empty()) { + auto rvmatFilename = ((fs::path)textureStr).filename().string(); + if (boost::istarts_with(rvmatFilename, prefix)) { + lastValidRvMat = rvmatFilename.substr(0, 9); + + auto tt = getTileTransform(rap); + + if (!firstPos.has_value()) { + firstPos = tt; + } - // TODO: refactor this when implementing the new rap api - for (auto& rvmatPath : rvmats) { + if (!pbo.has_value()) { + pbo = rvff::cxx::create_pbo_reader_path(findPboPath(textureStr).string()); + } - std::vector parts; - boost::split(parts, ((fs::path)rvmatPath).filename().string(), boost::is_any_of("_")); + auto data = (*pbo)->get_entry_data(textureStr); - if (parts.size() > 1) { - std::vector numbers; - boost::split(numbers, parts[1], boost::is_any_of("-")); + if (data.empty()) { + pbo = rvff::cxx::create_pbo_reader_path(findPboPath(textureStr).string()); + data = (*pbo)->get_entry_data(textureStr); + } - if (numbers.size() > 1) { - auto x = std::stoi(numbers[0]); - auto y = std::stoi(numbers[1]); + auto mipmap = rvff::cxx::get_mipmap_from_paa_vec(data, 0); - if (x > maxX2) { - maxX2 = x; - } + struct SatMapTile tile = { textureStr, tt, mipmap }; + satMapTiles.push_back(tile); + } + else { + if(!fillerTile.has_value()) { + auto fillerPbo = rvff::cxx::create_pbo_reader_path(findPboPath(textureStr).string()); + auto fillerData = fillerPbo->get_entry_data(textureStr); + auto filler_mm = rvff::cxx::get_mipmap_from_paa_vec(fillerData, 0); + + struct FillerMapTile tile = { textureStr, {}, filler_mm }; + fillerTile = tile; + } - if (y > maxY2) { - maxY2 = y; - } - } - } + auto tt = getTileTransform(rap); - if (!boost::istarts_with(((fs::path)rvmatPath).filename().string(), lastValidRvMat)) { - auto rap = grad_aff::Rap::Rap(rvmatPbo.getEntryData(rvmatPath)); - rap.readRap(); - - for (auto& entry : rap.classEntries) { - if (boost::iequals(entry->name, "Stage0")) { - auto rapClassPtr = std::static_pointer_cast(entry); - - for (auto& subEntry : rapClassPtr->classEntries) { - if (boost::iequals(subEntry->name, "texture")) { - auto textureStr = std::get(std::static_pointer_cast(subEntry)->value); - auto rvmatFilename = ((fs::path)textureStr).filename().string(); - if (!boost::istarts_with(rvmatFilename, prefix)) { - if (fillerTile == "") { - fillerTile = textureStr; - } - } - else { - lcoPaths.push_back(textureStr); - lastValidRvMat = rvmatFilename.substr(0, 9); - } - break; + if (!firstPos.has_value()) { + firstPos = tt; } + fillerTile->tt.emplace(tt); } } + } } - } - // Remove Duplicates - std::sort(lcoPaths.begin(), lcoPaths.end()); - auto last = std::unique(lcoPaths.begin(), lcoPaths.end()); - lcoPaths.erase(last, lcoPaths.end()); + // Remove Duplicates + std::sort(satMapTiles.begin(), satMapTiles.end(), [](const auto& lhs, const auto& rhs) { + return lhs.path.string() < rhs.path.string(); + }); + auto last = std::unique(satMapTiles.begin(), satMapTiles.end(), [](const auto& lhs, const auto& rhs) { + return lhs.path == rhs.path; + }); + satMapTiles.erase(last, satMapTiles.end()); + + auto tileSize = 0; + + // Fix wrong file extensions (cup summer has png extension) + for (auto& smt : satMapTiles) { + fs::path lcoPathAsPath = smt.path; + if (!boost::iequals(lcoPathAsPath.extension().string(), ".paa")) { + smt.path = lcoPathAsPath.replace_extension(".paa").string(); + } - // Fix wrong file extensions (cup summer has png exten - for (auto& lcoPath : lcoPaths) { - fs::path lcoPathAsPath = (fs::path)lcoPath; - if (!boost::iequals(lcoPathAsPath.extension().string(), ".paa")) { - lcoPath = lcoPathAsPath.replace_extension(".paa").string(); + auto mmSize = std::max(smt.mipmap.width, smt.mipmap.height); + if (tileSize < mmSize) { + tileSize = mmSize; + } } - } - // "strechted border" overlap is og overlap/2 + if (tileSize == 4 && ba::iequals(worldName, "vr")) { + tileSize = 1024; + } - // find highest x/y - std::vector splitResult = {}; - boost::split(splitResult, ((fs::path)lcoPaths[lcoPaths.size() - 1]).filename().string(), boost::is_any_of("_")); - uint32_t maxX = maxX2; //std::stoi(splitResult[1]); - uint32_t maxY = maxY2; //std::stoi(splitResult[2]); + auto& firstSmt = satMapTiles[0]; - // find two tiles "in the middle" - uint32_t x = maxX / 2; - uint32_t y = maxY / 2; + auto tileWidth = tileSize; + auto tileHeight = tileSize; - prefix = "00"; + TileTransform firstTT = firstSmt.tt; + if (firstPos.has_value()) { + firstTT = *firstPos; + } - if (y > 9 && y < 100) { - prefix = "0"; - } - else if (y >= 100) { - prefix = ""; - } + auto firstWorldPos = firstTT.pos; + auto firstAside = firstTT.aside; - std::stringstream upperLcoPathStream; - upperLcoPathStream << x << "_" << prefix << y << "_lco.paa"; - auto upperPaaPath = upperLcoPathStream.str(); + auto scaleFactor = firstAside[0] * tileWidth; - std::stringstream lowerLcoPathStream; - lowerLcoPathStream << x << "_" << prefix << (y + 1) << "_lco.paa"; - auto lowerPaaPath = lowerLcoPathStream.str(); + if (scaleFactor != 1) { + scaleFactor += 0.5; - MipMap upperMipmap; - MipMap lowerMipmap; + auto newWidth = static_cast(scaleFactor * tileWidth); - for (auto& lcoPath : lcoPaths) { - if (boost::iends_with(lcoPath, upperPaaPath)) { - auto upperPbo = grad_aff::Pbo::Pbo(findPboPath(lcoPath).string()); - auto upperData = upperPbo.getEntryData(lcoPath); - auto upperPaa = grad_aff::Paa::Paa(); - upperPaa.readPaa(upperData); - upperMipmap = upperPaa.mipMaps[0]; - } - if (boost::iends_with(lcoPath, lowerPaaPath)) { - auto lowerPbo = grad_aff::Pbo::Pbo(findPboPath(lcoPath).string()); - auto lowerData = lowerPbo.getEntryData(lcoPath); - auto lowerPaa = grad_aff::Paa::Paa(); - lowerPaa.readPaa(lowerData); - lowerMipmap = lowerPaa.mipMaps[0]; + tileWidth = newWidth; + tileHeight = newWidth; } - } - auto overlap = calcOverLap(upperMipmap, lowerMipmap); + auto firstXPos = static_cast(firstWorldPos[0] * tileWidth); + auto firstYPos = static_cast(firstWorldPos[1] * tileHeight); + + int32_t finalSatMapSize = firstYPos - firstXPos; + + ImageBuf dst(ImageSpec(finalSatMapSize, finalSatMapSize, 4, TypeDesc::UINT8)); + auto& dstSpec = dst.spec(); + + if (fillerTile.has_value()) { + auto filler_mm = fillerTile->mipmap; - ImageBuf dst(ImageSpec(upperMipmap.width + maxX * (upperMipmap.width - overlap), upperMipmap.height + maxY * (upperMipmap.height - overlap), 4, TypeDesc::UINT8)); + auto fillerWidth = filler_mm.width; + auto fillerHeight = filler_mm.height; - if (fillerTile != "") { - auto fillerPbo = grad_aff::Pbo::Pbo(findPboPath(fillerTile).string()); - auto fillerData = fillerPbo.getEntryData(fillerTile); - auto fillerPaa = grad_aff::Paa::Paa(); - fillerPaa.readPaa(fillerData); + ImageBuf src(ImageSpec(fillerWidth, fillerHeight, 4, TypeDesc::UINT8), filler_mm.data.data()); - ImageBuf src(ImageSpec(fillerPaa.mipMaps[0].height, fillerPaa.mipMaps[0].height, 4, TypeDesc::UINT8), fillerPaa.mipMaps[0].data.data()); + int32_t numOfSubTiles = tileWidth / filler_mm.width; - size_t noOfTiles = (worldSize / fillerPaa.mipMaps[0].height); + ImageBuf fullFillerTile(ImageSpec(tileWidth, tileHeight, 4, TypeDesc::UINT8)); - auto cr = boost::counting_range(size_t(0), noOfTiles); - std::for_each(std::execution::par_unseq, cr.begin(), cr.end(), [noOfTiles, &dst, fillerPaa, src](size_t i) { - for (size_t j = 0; j < noOfTiles; j++) { - ImageBufAlgo::paste(dst, (i * fillerPaa.mipMaps[0].height), (j * fillerPaa.mipMaps[0].height), 0, 0, src); + for (int32_t i = 0; i < numOfSubTiles; i++) { + for (int32_t j = 0; j < numOfSubTiles; j++) { + ImageBufAlgo::paste(fullFillerTile, i * fillerWidth, j * fillerHeight, 0, 0, src); + } } - }); - } - auto pbo = grad_aff::Pbo::Pbo(findPboPath(lcoPaths[0]).string()); + #ifdef _DEBUG + auto copy_filler = fullFillerTile.copy(TypeDesc::UINT8); + copy_filler.write((basePathSat / "debug_filler_tile.exr").string()); + #endif + + auto& fillterTTs = fillerTile->tt; - for (auto& lcoPath : lcoPaths) { - auto data = pbo.getEntryData(lcoPath); + for (auto& tt : fillterTTs) { + auto pos = tt.pos; + auto xPos = static_cast(pos[0] * tileWidth * -1); + auto yPos = static_cast(((pos[1] * tileHeight) - dstSpec.width) * -1); + + ImageBufAlgo::paste(dst, xPos, yPos, 0, 0, fullFillerTile); + } - if (data.empty()) { - pbo = grad_aff::Pbo::Pbo(findPboPath(lcoPath).string()); - data = pbo.getEntryData(lcoPath); + //#ifdef _DEBUG + /*auto copy_full = dst.copy(TypeDesc::UINT8); + copy_full.write((basePathSat / "debug_sat_map_only_filler.exr").string());*/ + //#endif } - auto paa = grad_aff::Paa::Paa(); - paa.readPaa(data); + //auto dstOG = dst.copy(dst.spec().format); - std::vector splitResult = {}; - boost::split(splitResult, ((fs::path)lcoPath).filename().string(), boost::is_any_of("_")); + int c = 0; + for (auto& smt : satMapTiles) { + auto mipmap = smt.mipmap; - ImageBuf src(ImageSpec(paa.mipMaps[0].height, paa.mipMaps[0].height, 4, TypeDesc::UINT8), paa.mipMaps[0].data.data()); + ImageBuf src(ImageSpec(mipmap.width, mipmap.height, 4, TypeDesc::UINT8), mipmap.data.data()); - uint32_t x = std::stoi(splitResult[1]); - uint32_t y = std::stoi(splitResult[2]); + if (mipmap.width == 4 && mipmap.height == 4) { + src = ImageBufAlgo::resize(src, "", 0, ROI(0, tileWidth, 0, tileHeight)); + PLOG_INFO << fmt::format("4x4 filler method detected, resizing 4x4 tiles to {}x{} !", tileWidth, tileHeight); + } - ImageBufAlgo::paste(dst, (x * paa.mipMaps[0].height - x * overlap), (y * paa.mipMaps[0].height - y * overlap), 0, 0, src); - } + auto srcWidth = src.spec().width; - // https://github.com/pennyworth12345/A3_MMSI/wiki/Mapframe-Information#stretching-at-edge-tiles - maxX += 1; + auto asideX = smt.tt.aside[0]; + auto scaleFactor = asideX * srcWidth; - auto hasEqualStrechting = (worldSize == (upperMipmap.width * maxX)) || (worldSize == ((upperMipmap.width - overlap) * maxX)); - int32_t strechedBorderSizeStart = overlap / 2; - int32_t strechedBorderSizeEnd = strechedBorderSizeStart; + if (scaleFactor != 1) { + scaleFactor += 0.5; - if (!hasEqualStrechting) { - int32_t tileSizeAfterOverlap = upperMipmap.width - overlap; - int32_t temp = (tileSizeAfterOverlap * maxX) - worldSize; - temp -= (overlap / 2); - int32_t lastTileRealWidth = (tileSizeAfterOverlap - temp) % upperMipmap.width; - strechedBorderSizeEnd = upperMipmap.width - lastTileRealWidth; - } + auto newWidth = static_cast(scaleFactor * srcWidth); - dst = ImageBufAlgo::cut(dst, ROI(strechedBorderSizeStart, dst.spec().width - strechedBorderSizeEnd, strechedBorderSizeStart, dst.spec().height - strechedBorderSizeEnd)); - size_t tileSize = dst.spec().width / 4; + src = ImageBufAlgo::resize(src, "", 0, ROI(0, newWidth, 0, newWidth)); + } - auto cr = boost::counting_range(0, 4); - std::for_each(std::execution::par_unseq, cr.begin(), cr.end(), [basePathSat, dst, tileSize](int i) { - auto curWritePath = basePathSat / std::to_string(i); - if (!fs::exists(curWritePath)) { - fs::create_directories(curWritePath); - } + auto srcSpec = src.spec(); + + auto width = srcSpec.width; + auto height = srcSpec.height; + + auto pos = smt.tt.pos; + + auto xPos = static_cast(pos[0] * width * -1); + auto yPos = static_cast(((pos[1] * height) - dstSpec.width) * -1); + + /*float red[4] = { 1, 0, 0, 1 }; + ImageBufAlgo::render_box(src, 0, 0, width-1, height-1, red);*/ + + /*auto debugText = fmt::format("rvmat: {}\nxPos: {}\nyPos: {}\nwidth: {}\nheight: {}\nscaleFactor: {}\npos[0] {}\npos[1] {}\naside[0] {}", + smt.path.filename().string(), xPos, yPos, width, height, scaleFactor, pos[0], pos[1], asideX); + ImageBufAlgo::render_text(src, 50, 50, debugText, 20, "Arial", red, ImageBufAlgo::TextAlignX::Left);*/ + + ImageBufAlgo::paste(dst, xPos, yPos, 0, 0, src); - for (int32_t j = 0; j < 4; j++) { - ImageBuf out = ImageBufAlgo::cut(dst, ROI(i * tileSize, (i + 1) * tileSize, j * tileSize, (j + 1) * tileSize)); - out.write((curWritePath / std::to_string(j).append(".png")).string()); + /*auto copy = dstOG.copy(dstOG.spec().format); + ImageBufAlgo::paste(copy, xPos, yPos, 0, 0, src); + copy.write((basePathSat / fmt::format("debug_part_{}.exr", c++)).string());*/ + + /*auto copy_party = dst.copy(TypeDesc::UINT8); + copy_party.write((basePathSat / fmt::format("debug_part_{}.exr", c++)).string());*/ } + + //#ifdef _DEBUG + /*auto copy = dst.copy(TypeDesc::UINT8); + copy.write((basePathSat / "debug_full_streched.exr").string());*/ + //#endif + + int32_t finalTileSize = dst.spec().width / 4; + + auto cr = boost::counting_range(0, 4); + std::for_each(std::execution::par_unseq, cr.begin(), cr.end(), [basePathSat, dst, finalTileSize](int i) { + auto curWritePath = basePathSat / std::to_string(i); + if (!fs::exists(curWritePath)) { + fs::create_directories(curWritePath); + } + + for (int32_t j = 0; j < 4; j++) { + ImageBuf out = ImageBufAlgo::cut(dst, ROI(i * finalTileSize, (i + 1) * finalTileSize, j * finalTileSize, (j + 1) * finalTileSize)); + out.write((curWritePath / std::to_string(j).append(".png")).string()); + } }); + } + catch (const rust::Error& ex) { + PLOG_ERROR << fmt::format("Exception in writeSatImages PBO: {}", pboPath); + PLOG_ERROR << ex.what(); + throw; + } + } +} + +TileTransform getTileTransform(rust::Box& rap) { + auto texGenValue = rap->get_entry_as_number(texgen_config_path); + + auto tex_gen_class = fmt::format("TexGen{}", texGenValue); + std::vector texgenDirConfigPath { tex_gen_class, "uvTransform", "pos" }; + std::vector texgenAsideConfigPath { tex_gen_class, "uvTransform", "aside" }; + + auto posArr = rap->get_entry_as_array_float(texgenDirConfigPath); + std::vector posArrStd(posArr.begin(), posArr.end()); + + if (posArrStd.size() < 2) { + auto errMsg = fmt::format("arr size for '{}' was < 2", ba::join(texgenDirConfigPath, " >> ")); + PLOG_ERROR << errMsg; + throw new std::runtime_error(errMsg); } -} \ No newline at end of file + + auto asideArr = rap->get_entry_as_array_float(texgenAsideConfigPath); + std::vector asideArrStd(asideArr.begin(), asideArr.end()); + + if (asideArrStd.size() < 2) { + auto errMsg = fmt::format("arr size for '{}' was < 2", ba::join(texgenAsideConfigPath, " >> ")); + PLOG_ERROR << errMsg; + throw new std::runtime_error(errMsg); + } + + TileTransform tt { + asideArrStd, + posArrStd + }; + + return tt; +} + diff --git a/src/satimages.h b/src/satimages.h index 97bc88b..5e004c4 100644 --- a/src/satimages.h +++ b/src/satimages.h @@ -5,16 +5,25 @@ #include #include #include +#include -#include -#include -#include -#include +#include namespace fs = std::filesystem; -float_t mean(std::vector& equalities); -float_t compareColors(uint8_t r1, uint8_t g1, uint8_t b1, uint8_t r2, uint8_t g2, uint8_t b2); -float_t compareRows(std::vector::iterator mipmap1It, std::vector::iterator mipmap2It, uint32_t width); -uint32_t calcOverLap(MipMap& mipmap1, MipMap& mipmap2); -void writeSatImages(grad_aff::Wrp& wrp, const int32_t& worldSize, std::filesystem::path& basePathSat, const std::string& worldName); \ No newline at end of file +struct TileTransform { + std::vector aside = {}; + std::vector pos = {}; + +}; + +struct CmpTileTransform +{ + bool operator()(const TileTransform& lhs, const TileTransform& rhs) const + { + return (lhs.aside < rhs.aside) || (rhs.pos < lhs.pos); + } +}; + +void writeSatImages(rvff::cxx::OprwCxx& wrp, const int32_t& worldSize, std::filesystem::path& basePathSat, const std::string& worldName); +TileTransform getTileTransform(rust::Box& rap); diff --git a/src/util.cpp b/src/util.cpp index ea5c046..f20cfdf 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -7,11 +7,11 @@ static bool gradMehMapIsPopulating = false; Returns full paths to every *.pbo from mod Paths + A3 Root */ std::vector getFullPboPaths() { - std::vector pboList; + std::vector pboList; #ifdef _WIN32 for (auto& pboW : generate_pbo_list()) { - pboList.push_back(std::wstring_convert, wchar_t>().to_bytes(pboW).substr(4)); + pboList.push_back(pboW.substr(4)); } #else pboList = generate_pbo_list(); @@ -37,28 +37,31 @@ std::vector getFullPboPaths() { void populateMap() { gradMehMapIsPopulating = true; for (auto& p : getFullPboPaths()) { - auto pbo = grad_aff::Pbo::Pbo(p.string()); try { - pbo.readPbo(false); + auto pboReader = rvff::cxx::create_pbo_reader_path(p.string()); + auto pbo = pboReader->get_pbo(); + for (auto& entry : pbo.entries) { + for (auto& prop : pbo.properties) { + if (static_cast(prop.key) == "prefix") { + entryPboMap.insert({ static_cast(prop.value) , p }); + break; + } + } + } } - catch (std::exception& ex) { - client::invoker_lock threadLock; - std::stringstream errorStream; - errorStream << "[grad_meh] Couldn't open PBO: " << p.string() << " error msg: " << ex.what(); - sqf::diag_log(errorStream.str()); - threadLock.unlock(); - } - for (auto& entry : pbo.entries) { - entryPboMap.insert({ pbo.productEntries["prefix"] , p }); + catch (const rust::Error& ex) { + PLOG_WARNING << fmt::format("Couldn't open PBO at: {} (Exception: {})", p.string(), ex.what()); } } gradMehMapIsPopulating = false; + PLOG_INFO << "Finished PBO Mapping"; } /* Finds pbo Path */ fs::path findPboPath(std::string path) { + PLOG_INFO << fmt::format("Searching pbos for: {}", path); size_t matchLength = 0; fs::path retPath; @@ -69,9 +72,18 @@ fs::path findPboPath(std::string path) { for (auto const& [key, val] : entryPboMap) { if (boost::istarts_with(path, key) && key.length() > matchLength) { + matchLength = key.length(); retPath = val; } } + + if (retPath.empty()) { + PLOG_WARNING << fmt::format("No pbo found for: {}", path); + } + else { + PLOG_INFO << fmt::format("Found pbo at: {}", retPath.string()); + } + return retPath; } @@ -90,7 +102,7 @@ void writeGZJson(const std::string& fileName, fs::path path, nl::json& json) { strInStream << std::setw(4) << json; bi::filtering_istream fis; - fis.push(bi::gzip_compressor(bi::gzip_params(bi::gzip::best_compression))); + fis.push(bi::gzip_compressor(bi::gzip_params(bi::gzip::best_speed))); fis.push(strInStream); std::ofstream out(path / fileName, std::ios::binary); @@ -106,4 +118,35 @@ bool isMapPopulating() { void prettyDiagLog(std::string message) { client::invoker_lock thread_lock; sqf::diag_log(sqf::text("[GRAD] (meh): " + message)); -} \ No newline at end of file +} + +bool checkMagic(rust::Vec& data, std::string magic) { + if (data.size() < magic.size()) { + return false; + } + + for (size_t i = 0; i < magic.size(); i++) { + if (data[i] != magic[i]) { + return false; + } + } + return true; +} + +fs::path getDllPath() { +#ifdef _WIN32 + char filePath[MAX_PATH + 1]; + GetModuleFileName(HINST_THISCOMPONENT, filePath, MAX_PATH + 1); +#endif + + auto dllPath = fs::path(filePath); + dllPath = dllPath.remove_filename(); + +#ifdef _WIN32 + // Remove win garbage + dllPath = dllPath.string().substr(4); +#endif + + return dllPath; +} + diff --git a/src/util.h b/src/util.h index 930166f..de95465 100644 --- a/src/util.h +++ b/src/util.h @@ -4,6 +4,12 @@ #include "findPbos.h" +#include + +#include + +#include + // String #include #include @@ -16,15 +22,25 @@ #include #include +#include +#include +#include +#include #include -#include +#include #ifdef _WIN32 #define NOMINMAX #include #include -#endif + + // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483 + EXTERN_C IMAGE_DOS_HEADER __ImageBase; + #define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase) +#else +#error Only Win is supported +#endif // _WIN32 #define GRAD_MEH_FORMAT_VERSION 0.1 #define GRAD_MEH_MAX_COLOR_DIF 441.6729559300637f @@ -50,3 +66,8 @@ void writeGZJson(const std::string& fileName, fs::path path, nl::json& json); bool isMapPopulating(); void prettyDiagLog(std::string message); + +bool checkMagic(rust::Vec& data, std::string magic); + +fs::path getDllPath(); + diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..74584b3 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,27 @@ +{ + "name": "grad-meh", + "version-string": "0.7.1", + "dependencies": [ + "nlohmann-json", + "boost-filesystem", + "boost-iostreams", + { + "name": "openimageio", + "default-features": true, + "features": [ + "freetype" + ] + }, + "plog", + { + "name": "gdal", + "default-features": false, + "features": [ + "geos" + ] + }, + "pcl", + "polyclipping", + "fmt" + ] +} \ No newline at end of file