├── .clang-format ├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── README.md ├── cmake ├── RapidObjConfig.cmake └── RapidObjGetVersion.cmake ├── data └── images │ ├── Powerplant.jpg │ ├── Rungholt.jpg │ ├── SanMiguel.jpg │ ├── SpriteFright.jpg │ ├── benchmarks │ ├── powerplant-dark.svg │ ├── powerplant-light.svg │ ├── rungholt-dark.svg │ ├── rungholt-light.svg │ ├── san-miguel-dark.svg │ ├── san-miguel-light.svg │ ├── sprite-fright-dark.svg │ └── sprite-fright-light.svg │ └── docs │ ├── logo-dark.svg │ ├── logo-light.svg │ ├── result-dark.png │ └── result-light.png ├── docs ├── BENCHMARKS.md ├── result-dark.txt └── result-light.txt ├── example ├── CMakeLists.txt └── readobj │ ├── CMakeLists.txt │ └── src │ └── readobj.cpp ├── external └── CMakeLists.txt ├── include ├── CMakeLists.txt ├── RapidObj.pc.in └── rapidobj │ └── rapidobj.hpp ├── rapidobj.natvis ├── tests ├── CMakeLists.txt ├── data │ ├── color │ │ ├── color.obj │ │ ├── color.obj.ref │ │ ├── color.obj.test │ │ ├── color.obj.tri.ref │ │ └── color.obj.tri.test │ ├── cube │ │ ├── cube.obj │ │ ├── cube.obj.ref │ │ ├── cube.obj.test │ │ ├── cube.obj.tri.ref │ │ └── cube.obj.tri.test │ ├── mario │ │ ├── Mario.mtl │ │ ├── mario.obj │ │ ├── mario.obj.ref │ │ ├── mario.obj.test │ │ ├── mario.obj.tri.ref │ │ └── mario.obj.tri.test │ ├── mtllib │ │ ├── blue │ │ │ └── cube.mtl │ │ ├── cube.mtl │ │ ├── cube.obj │ │ ├── cube_mtllib_missing.obj │ │ ├── green │ │ │ └── cube.mtl │ │ ├── red │ │ │ └── cube.mtl │ │ └── yellow.mtl │ ├── primitives │ │ ├── primitives.obj │ │ ├── primitives.obj.ref │ │ ├── primitives.obj.test │ │ ├── primitives.obj.tri.ref │ │ └── primitives.obj.tri.test │ ├── sponza │ │ ├── copyright.txt │ │ ├── sponza.mtl │ │ ├── sponza.obj │ │ ├── sponza.obj.ref │ │ ├── sponza.obj.test │ │ ├── sponza.obj.tri.ref │ │ └── sponza.obj.tri.test │ └── teapot │ │ ├── teapot.obj │ │ ├── teapot.obj.ref │ │ ├── teapot.obj.test │ │ ├── teapot.obj.tri.ref │ │ └── teapot.obj.tri.test ├── func-tests │ ├── CMakeLists.txt │ └── src │ │ ├── test_main.cpp │ │ └── test_rapidobj.cpp └── unit-tests │ ├── CMakeLists.txt │ └── src │ ├── test_main.cpp │ ├── test_material_parsing.cpp │ ├── test_mtllib.cpp │ └── test_parsing.cpp └── tools ├── CMakeLists.txt ├── bench ├── CMakeLists.txt └── src │ └── bench.cpp ├── compare-test ├── CMakeLists.txt ├── include │ └── compare-test │ │ └── compare-test.hpp └── src │ └── compare-test.cpp ├── make-test ├── CMakeLists.txt └── src │ └── make-test.cpp ├── scripts └── benchmark.py └── serializer ├── CMakeLists.txt ├── include └── serializer │ └── serializer.hpp └── src └── serializer.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: AlwaysBreak 3 | AlignConsecutiveAssignments: true 4 | AlignConsecutiveMacros: true 5 | AlignConsecutiveDeclarations: true 6 | AlignEscapedNewlines: DontAlign 7 | AlignTrailingComments: true 8 | AllowAllArgumentsOnNextLine: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortCaseLabelsOnASingleLine: true 11 | AllowShortFunctionsOnASingleLine: InlineOnly 12 | AlwaysBreakAfterDefinitionReturnType: None 13 | AlwaysBreakAfterReturnType: None 14 | AlwaysBreakTemplateDeclarations: true 15 | BinPackParameters: false 16 | BinPackArguments: false 17 | BraceWrapping: 18 | AfterFunction: true 19 | SplitEmptyFunction: false 20 | SplitEmptyRecord: false 21 | BreakBeforeBraces: Custom 22 | ColumnLimit: 120 23 | Cpp11BracedListStyle: false 24 | FixNamespaceComments: true 25 | IndentWidth: 4 26 | KeepEmptyLinesAtTheStartOfBlocks: false 27 | PenaltyBreakAssignment: 20 28 | PenaltyReturnTypeOnItsOwnLine: 200 29 | PointerAlignment: Left 30 | SpaceAfterTemplateKeyword: true 31 | SpacesInContainerLiterals: true 32 | UseTab: Never 33 | 34 | ... 35 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.obj -text 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "docs/**" 7 | - "*.md" 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ ubuntu-latest, windows-latest, macos-latest ] 16 | build-type: [ Debug, Release ] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: lukka/get-cmake@latest 20 | 21 | - name: Configure 22 | run: > 23 | cmake 24 | -B ${{ github.workspace }}/build 25 | -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} 26 | -DRAPIDOBJ_BuildTools=ON 27 | -DRAPIDOBJ_BuildTests=ON 28 | -DRAPIDOBJ_BuildExamples=ON . 29 | 30 | - name: Build 31 | run: cmake --build ${{ github.workspace }}/build --config ${{ matrix.build-type }} 32 | 33 | - name: Test 34 | run: | 35 | cd ${{ github.workspace }}/build 36 | ctest --verbose -C ${{ matrix.build-type }} 37 | 38 | build-extra: 39 | runs-on: ubuntu-latest 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | compiler: [ { cxx: g++-9, c: gcc-9 }, { cxx: clang++-11, c: clang-11 } ] 44 | build-type: [ Debug, Release ] 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: lukka/get-cmake@latest 48 | 49 | - name: Install compiler 50 | run: sudo apt install -y ${{ startsWith(matrix.compiler.c, 'gcc') && matrix.compiler.cxx || matrix.compiler.c }} 51 | 52 | - name: Configure 53 | run: > 54 | cmake -B ${{ github.workspace }}/build 55 | -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} 56 | -DCMAKE_C_COMPILER=${{ matrix.compiler.c }} 57 | -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} 58 | -DRAPIDOBJ_BuildTools=ON 59 | -DRAPIDOBJ_BuildTests=ON 60 | -DRAPIDOBJ_BuildExamples=ON . 61 | 62 | - name: Build 63 | run: cmake --build ${{ github.workspace }}/build 64 | 65 | - name: Test 66 | run: | 67 | cd ${{ github.workspace }}/build 68 | ctest 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vs/ 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | set(CMAKE_POLICY_DEFAULT_CMP0048 NEW) 4 | set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) 5 | 6 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake) 7 | 8 | include(RapidObjGetVersion) 9 | 10 | rapidobj_get_version(VERSION) 11 | 12 | project(RapidObj VERSION ${VERSION} LANGUAGES CXX) 13 | 14 | include(GNUInstallDirs) 15 | include(CMakePackageConfigHelpers) 16 | 17 | option(RAPIDOBJ_BuildTests "Build Tests." OFF) 18 | option(RAPIDOBJ_BuildTools "Build Tools." OFF) 19 | option(RAPIDOBJ_BuildExamples "Build Examples." OFF) 20 | 21 | set(RAPIDOBJ_INCLUDE_DIR "${CMAKE_INSTALL_INCLUDEDIR}" CACHE STRING "Header Include Folder.") 22 | set(RAPIDOBJ_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake" CACHE STRING "CMake Config Folder.") 23 | 24 | if (RAPIDOBJ_BuildTests OR RAPIDOBJ_BuildTools) 25 | add_subdirectory(external EXCLUDE_FROM_ALL) 26 | endif() 27 | 28 | add_subdirectory(include) 29 | 30 | if (RAPIDOBJ_BuildTests) 31 | include(CTest) 32 | add_subdirectory(tests) 33 | endif() 34 | 35 | if (RAPIDOBJ_BuildTools) 36 | add_subdirectory(tools) 37 | endif() 38 | 39 | if (RAPIDOBJ_BuildExamples) 40 | add_subdirectory(example) 41 | endif() 42 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 20, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "base-config", 11 | "hidden": true, 12 | "cacheVariables": { 13 | "RAPIDOBJ_BuildExamples": true, 14 | "RAPIDOBJ_BuildTests": true, 15 | "RAPIDOBJ_BuildTools": true 16 | } 17 | }, 18 | { 19 | "name": "arch-x64", 20 | "hidden": true, 21 | "architecture": { 22 | "value": "x64", 23 | "strategy": "external" 24 | } 25 | }, 26 | { 27 | "name": "arch-arm64", 28 | "hidden": true, 29 | "architecture": { 30 | "value": "ARM64", 31 | "strategy": "external" 32 | } 33 | }, 34 | { 35 | "name": "os-linux", 36 | "hidden": true, 37 | "inherits": "base-config", 38 | "generator": "Unix Makefiles", 39 | "vendor": { 40 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 41 | "hostOS": [ "Linux" ] 42 | } 43 | } 44 | }, 45 | { 46 | "name": "os-windows", 47 | "hidden": true, 48 | "inherits": "base-config", 49 | "generator": "Ninja", 50 | "vendor": { 51 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 52 | "hostOS": [ "Windows" ] 53 | } 54 | } 55 | }, 56 | { 57 | "name": "os-mac", 58 | "hidden": true, 59 | "inherits": "base-config", 60 | "generator": "Unix Makefiles", 61 | "vendor": { 62 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 63 | "hostOS": [ "macOS" ] 64 | } 65 | } 66 | }, 67 | { 68 | "name": "compiler-clang", 69 | "hidden": true, 70 | "cacheVariables": { 71 | "CMAKE_C_COMPILER": "clang", 72 | "CMAKE_CXX_COMPILER": "clang++", 73 | "CMAKE_CXX_FLAGS": "-Werror -Wall -Wextra -Wconversion -Wsign-conversion -pedantic-errors" 74 | } 75 | }, 76 | { 77 | "name": "compiler-gcc", 78 | "hidden": true, 79 | "cacheVariables": { 80 | "CMAKE_C_COMPILER": "gcc", 81 | "CMAKE_CXX_COMPILER": "g++", 82 | "CMAKE_CXX_FLAGS": "-Werror -Wall -Wextra -Wconversion -Wsign-conversion -pedantic-errors" 83 | } 84 | }, 85 | { 86 | "name": "compiler-msvc", 87 | "hidden": true, 88 | "cacheVariables": { 89 | "CMAKE_C_COMPILER": "cl", 90 | "CMAKE_CXX_COMPILER": "cl", 91 | "CMAKE_CXX_FLAGS": "/EHsc /W4 /permissive-" 92 | } 93 | }, 94 | { 95 | "name": "type-debug", 96 | "hidden": true, 97 | "cacheVariables": { 98 | "CMAKE_BUILD_TYPE": "Debug" 99 | } 100 | }, 101 | { 102 | "name": "type-release", 103 | "hidden": true, 104 | "cacheVariables": { 105 | "CMAKE_BUILD_TYPE": "Release" 106 | } 107 | }, 108 | { 109 | "name": "x64-linux-clang-release", 110 | "inherits": [ "arch-x64", "os-linux", "compiler-clang", "type-release" ], 111 | "binaryDir": "${sourceDir}/build/${presetName}" 112 | }, 113 | { 114 | "name": "x64-linux-clang-debug", 115 | "inherits": [ "arch-x64", "os-linux", "compiler-clang", "type-debug" ], 116 | "binaryDir": "${sourceDir}/build/${presetName}" 117 | }, 118 | { 119 | "name": "x64-linux-gcc-release", 120 | "inherits": [ "arch-x64", "os-linux", "compiler-gcc", "type-release" ], 121 | "binaryDir": "${sourceDir}/build/${presetName}" 122 | }, 123 | { 124 | "name": "x64-linux-gcc-debug", 125 | "inherits": [ "arch-x64", "os-linux", "compiler-gcc", "type-debug" ], 126 | "binaryDir": "${sourceDir}/build/${presetName}" 127 | }, 128 | { 129 | "name": "x64-windows-msvc-release", 130 | "inherits": [ "arch-x64", "os-windows", "compiler-msvc", "type-release" ], 131 | "binaryDir": "${sourceDir}/build/${presetName}" 132 | }, 133 | { 134 | "name": "x64-windows-msvc-debug", 135 | "inherits": [ "arch-x64", "os-windows", "compiler-msvc", "type-debug" ], 136 | "binaryDir": "${sourceDir}/build/${presetName}" 137 | }, 138 | { 139 | "name": "x64-windows-clang-release", 140 | "inherits": [ "arch-x64", "os-windows", "compiler-clang", "type-release" ], 141 | "binaryDir": "${sourceDir}/build/${presetName}" 142 | }, 143 | { 144 | "name": "x64-windows-clang-debug", 145 | "inherits": [ "arch-x64", "os-windows", "compiler-clang", "type-debug" ], 146 | "binaryDir": "${sourceDir}/build/${presetName}" 147 | }, 148 | { 149 | "name": "arm64-mac-clang-release", 150 | "inherits": [ "arch-arm64", "os-mac", "compiler-clang", "type-release" ], 151 | "binaryDir": "${sourceDir}/build/${presetName}" 152 | }, 153 | { 154 | "name": "arm64-mac-clang-debug", 155 | "inherits": [ "arch-arm64", "os-mac", "compiler-clang", "type-debug" ], 156 | "binaryDir": "${sourceDir}/build/${presetName}" 157 | } 158 | ] 159 | } 160 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Slobodan Pavlic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmake/RapidObjConfig.cmake: -------------------------------------------------------------------------------- 1 | if (CMAKE_SYSTEM_NAME MATCHES "Linux") 2 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) 3 | find_package(Threads REQUIRED) 4 | endif() 5 | 6 | include(${CMAKE_CURRENT_LIST_DIR}/RapidObj.cmake) 7 | -------------------------------------------------------------------------------- /cmake/RapidObjGetVersion.cmake: -------------------------------------------------------------------------------- 1 | function(rapidobj_get_version Arg) 2 | 3 | file(STRINGS include/rapidobj/rapidobj.hpp rapidobj_version_defines 4 | REGEX "#define RAPIDOBJ_VERSION_(MAJOR|MINOR|PATCH)") 5 | 6 | foreach(version_define ${rapidobj_version_defines}) 7 | if(version_define MATCHES "#define RAPIDOBJ_VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$") 8 | set(RAPIDOBJ_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "") 9 | endif() 10 | endforeach() 11 | 12 | set(VERSION ${RAPIDOBJ_VERSION_MAJOR}.${RAPIDOBJ_VERSION_MINOR}.${RAPIDOBJ_VERSION_PATCH}) 13 | 14 | message(STATUS "RapidObj version ${VERSION}") 15 | 16 | set(${Arg} ${VERSION} PARENT_SCOPE) 17 | 18 | endfunction() 19 | -------------------------------------------------------------------------------- /data/images/Powerplant.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/data/images/Powerplant.jpg -------------------------------------------------------------------------------- /data/images/Rungholt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/data/images/Rungholt.jpg -------------------------------------------------------------------------------- /data/images/SanMiguel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/data/images/SanMiguel.jpg -------------------------------------------------------------------------------- /data/images/SpriteFright.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/data/images/SpriteFright.jpg -------------------------------------------------------------------------------- /data/images/docs/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/images/docs/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/images/docs/result-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/data/images/docs/result-dark.png -------------------------------------------------------------------------------- /data/images/docs/result-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/data/images/docs/result-light.png -------------------------------------------------------------------------------- /docs/BENCHMARKS.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | The libraries benchmarked are: 4 | 5 | - **fast_obj** - https://github.com/thisistherk/fast_obj (master 85778da) 6 | - **rapidobj** - https://github.com/guybrush77/rapidobj (master 7a86567) 7 | - **tinyobjloader** - https://github.com/tinyobjloader/tinyobjloader (release 2f94771) 8 | 9 | ## Hardware 10 | 11 | - CPU: **AMD 7950X** 12 | - Memory: **DDR5 5200** 13 | - SSD: **WD SN8500X NVMe** 14 | 15 | ## Software 16 | 17 | - OS: **Ubuntu 22.04.1 LTS** (*Kernel 5.15.0-56-generic*) 18 | - Compiler: **g++-12** (*-O3 -DNDEBUG*) 19 | 20 | ## Methodology 21 | 22 | - Each test result is the average of ten runs. 23 | - Standard deviation is displayed as a narrow black bar. 24 | - OS page cache is cleared before each run. 25 | 26 | ## Results 27 | 28 | 29 | 30 | URL: https://casual-effects.com/g3d/data10/research/model/rungholt/rungholt.zip 31 | 32 | Shapes: 84 33 | 34 | Triangles: 6,704,264 35 | 36 | Size on disk: 236,055,639 bytes 37 | 38 |
39 | 40 | # ![rungholt.obj](../data/images/benchmarks/rungholt-light.svg#gh-light-mode-only)![rungholt.obj](../data/images/benchmarks/rungholt-dark.svg#gh-dark-mode-only) 41 | 42 | 43 | 44 | URL: https://casual-effects.com/g3d/data10/research/model/powerplant/powerplant.zip 45 | 46 | Shapes: 21 47 | 48 | Triangles: 12,759,246 49 | 50 | Size on disk: 817,891,724 bytes 51 | 52 |
53 | 54 | # ![powerplant.obj](../data/images/benchmarks/powerplant-light.svg#gh-light-mode-only)![powerplant.obj](../data/images/benchmarks/powerplant-dark.svg#gh-dark-mode-only) 55 | 56 | 57 | 58 | URL: https://casual-effects.com/g3d/data10/research/model/San_Miguel/San_Miguel.zip 59 | 60 | Shapes: 2203 61 | 62 | Triangles: 9,980,699 63 | 64 | Size on disk: 1,143,041,382 bytes 65 | 66 |
67 | 68 | # ![san-miguel.obj](../data/images/benchmarks/san-miguel-light.svg#gh-light-mode-only)![san-miguel.obj](../data/images/benchmarks/san-miguel-dark.svg#gh-dark-mode-only) 69 | 70 | 71 | 72 | URL: [Blender 3.zip](https://storage.googleapis.com/5649de716dcaf85da2faee95/_%2Fcd30f78645644eb1a5a27031eea2b7bd.zip?GoogleAccessId=956532172770-27ie9eb8e4u326l89p7b113gcb04cdgd%40developer.gserviceaccount.com&Expires=1678735382&Signature=kKVu7kvq7EhblJyVpna2P9zV7sXOeOuSBL82PWcJLiM9LpSXA9o%2FTEKKTZW8s7c%2FUTcIuldTUSBg%2FtZkPDnL64wqpK%2BDrZY3yigEo5zqWdV4IwIYYO4mu93hRpJZpAOs8XOlfX13%2B9zjnbqTRdGXDpCGOQ%2FK0i3YViOyVdhkKpvUokQeNrNxq%2FcxBCzJ6pG0fRZ56Klk2QIXwD7VBxsEBgbt1cymHL5zHPiLcLtNZ7Unxt7HZ%2F%2Faq3T%2BlL%2BVJ9TTwts68%2F5a%2Fuv9HrglFAY%2B6Gn5CmYoUJsHk%2FSb0YaiC9xPb6VednvfZDE%2BLjojWqRZvT4%2BhjrxqV0%2F8sCoa8En3g%3D%3D) (Export to .obj using Blender) 73 | 74 | Shapes: 24,257 75 | 76 | Triangles: 28,501,624 77 | 78 | Size on disk: 2,564,449,006 bytes 79 | 80 |
81 | 82 | # ![sprite-fright.obj](../data/images/benchmarks/sprite-fright-light.svg#gh-light-mode-only)![sprite-fright.obj](../data/images/benchmarks/sprite-fright-dark.svg#gh-dark-mode-only) 83 | -------------------------------------------------------------------------------- /docs/result-dark.txt: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | hide circle 4 | 5 | skinparam dpi 200 6 | 7 | skinparam tabSize 1 8 | 9 | skinparam ArrowColor DimGrey 10 | skinparam ArrowFontName DejaVu Serif 11 | skinparam ArrowFontSize 11 12 | skinparam ArrowFontStyle Bold 13 | skinparam ArrowThickness 2 14 | 15 | skinparam BackgroundColor Transparent 16 | 17 | skinparam ClassFontName DejaVu Sans Mono 18 | skinparam ClassFontStyle Bold 19 | skinparam ClassFontSize 14 20 | skinparam ClassFontColor White 21 | 22 | skinparam ClassAttributeFontName DejaVu Sans Mono 23 | skinparam ClassAttributeFontStyle Bold 24 | skinparam ClassAttributeFontColor LightGrey 25 | 26 | skinparam ClassBackgroundColor #202040 27 | 28 | skinparam ClassBorderColor Beige 29 | 30 | class Result { 31 | attributes\t: Attributes 32 | shapes\t\t\t\t\t: std::vector 33 | materials\t\t: std::vector 34 | error\t\t\t\t\t\t: Error 35 | } 36 | 37 | class Attributes { 38 | positions\t: Array 39 | texcoords\t: Array 40 | normals\t\t\t: Array 41 | colors\t\t\t\t: Array 42 | } 43 | 44 | class Shape { 45 | name\t\t\t: std::string 46 | mesh\t\t\t: Mesh 47 | lines\t\t: Lines 48 | points\t: Points 49 | } 50 | 51 | class Mesh { 52 | indices\t\t\t\t\t\t\t\t\t\t\t\t\t: Array 53 | num_face_vertices\t\t\t: Array 54 | material_ids\t\t\t\t\t\t\t\t: Array 55 | smoothing_group_ids\t: Array 56 | } 57 | 58 | class Lines { 59 | indices\t\t\t\t\t\t\t\t\t\t\t: Array 60 | num_line_vertices\t: Array 61 | } 62 | 63 | class Points { 64 | indices\t: Array 65 | } 66 | 67 | class Material { 68 | name\t\t\t\t\t\t\t: std::string 69 | parameters\t: ParameterTypes 70 | } 71 | 72 | class Error { 73 | code\t\t\t\t\t: std::error_code 74 | line\t\t\t\t\t: std::string 75 | line_num\t: std::size_t 76 | } 77 | 78 | class Index { 79 | position_index\t: int 80 | texcoord_index\t: int 81 | normal_index\t\t\t: int 82 | } 83 | 84 | Result *-- "1" Attributes #text:SandyBrown 85 | Result *-- "1..*" Shape #text:SandyBrown 86 | Result *-- "0..* " Material #text:SandyBrown 87 | Result *-- "1 " Error #text:SandyBrown 88 | 89 | Shape *-- "0..1" Mesh #text:SandyBrown 90 | Shape *-- "0..1" Lines #text:SandyBrown 91 | Shape *-- "0..1 " Points #text:SandyBrown 92 | 93 | hide methods 94 | @enduml 95 | 96 | -------------------------------------------------------------------------------- /docs/result-light.txt: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | hide circle 4 | 5 | skinparam dpi 200 6 | 7 | skinparam tabSize 1 8 | 9 | skinparam ArrowColor DarkSlateGrey 10 | skinparam ArrowFontName DejaVu Serif 11 | skinparam ArrowFontSize 11 12 | skinparam ArrowFontStyle Bold 13 | skinparam ArrowThickness 2 14 | 15 | skinparam BackgroundColor Transparent 16 | 17 | skinparam ClassFontName DejaVu Sans Mono 18 | skinparam ClassFontStyle Bold 19 | skinparam ClassFontSize 14 20 | skinparam ClassFontColor Black 21 | 22 | skinparam ClassAttributeFontName DejaVu Sans Mono 23 | skinparam ClassAttributeFontStyle Bold 24 | skinparam ClassAttributeFontColor Black 25 | 26 | skinparam ClassBackgroundColor SeaShell 27 | 28 | skinparam ClassBorderColor Navy 29 | 30 | class Result { 31 | attributes\t: Attributes 32 | shapes\t\t\t\t\t: std::vector 33 | materials\t\t: std::vector 34 | error\t\t\t\t\t\t: Error 35 | } 36 | 37 | class Attributes { 38 | positions\t: Array 39 | texcoords\t: Array 40 | normals\t\t\t: Array 41 | colors\t\t\t\t: Array 42 | } 43 | 44 | class Shape { 45 | name\t\t\t: std::string 46 | mesh\t\t\t: Mesh 47 | lines\t\t: Lines 48 | points\t: Points 49 | } 50 | 51 | class Mesh { 52 | indices\t\t\t\t\t\t\t\t\t\t\t\t\t: Array 53 | num_face_vertices\t\t\t: Array 54 | material_ids\t\t\t\t\t\t\t\t: Array 55 | smoothing_group_ids\t: Array 56 | } 57 | 58 | class Lines { 59 | indices\t\t\t\t\t\t\t\t\t\t\t: Array 60 | num_line_vertices\t: Array 61 | } 62 | 63 | class Points { 64 | indices\t: Array 65 | } 66 | 67 | class Material { 68 | name\t\t\t\t\t\t\t: std::string 69 | parameters\t: ParameterTypes 70 | } 71 | 72 | class Error { 73 | code\t\t\t\t\t: std::error_code 74 | line\t\t\t\t\t: std::string 75 | line_num\t: std::size_t 76 | } 77 | 78 | class Index { 79 | position_index\t: int 80 | texcoord_index\t: int 81 | normal_index\t\t\t: int 82 | } 83 | 84 | Result *-- "1" Attributes #text:DarkRed 85 | Result *-- "1..*" Shape #text:DarkRed 86 | Result *-- "0..* " Material #text:DarkRed 87 | Result *-- "1 " Error #text:DarkRed 88 | 89 | Shape *-- "0..1" Mesh #text:DarkRed 90 | Shape *-- "0..1" Lines #text:DarkRed 91 | Shape *-- "0..1 " Points #text:DarkRed 92 | 93 | hide methods 94 | @enduml 95 | 96 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_subdirectory(readobj) 4 | -------------------------------------------------------------------------------- /example/readobj/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_executable(readobj) 4 | 5 | target_sources(readobj PRIVATE "src/readobj.cpp") 6 | 7 | target_compile_features(readobj PRIVATE cxx_std_17) 8 | 9 | target_link_libraries(readobj PRIVATE rapidobj) 10 | -------------------------------------------------------------------------------- /example/readobj/src/readobj.cpp: -------------------------------------------------------------------------------- 1 | #include "rapidobj/rapidobj.hpp" 2 | 3 | #include 4 | 5 | void ReportError(const rapidobj::Error& error) 6 | { 7 | std::cout << error.code.message() << "\n"; 8 | if (!error.line.empty()) { 9 | std::cout << "On line " << error.line_num << ": \"" << error.line << "\"\n"; 10 | } 11 | } 12 | 13 | int main(int argc, char** argv) 14 | { 15 | if (argc != 2) { 16 | std::cout << "Usage: " << *argv << " input-file\n"; 17 | return EXIT_FAILURE; 18 | } 19 | 20 | auto result = rapidobj::ParseFile(argv[1]); 21 | 22 | if (result.error) { 23 | ReportError(result.error); 24 | return EXIT_FAILURE; 25 | } 26 | 27 | rapidobj::Triangulate(result); 28 | 29 | if (result.error) { 30 | ReportError(result.error); 31 | return EXIT_FAILURE; 32 | } 33 | 34 | auto num_triangles = size_t(); 35 | 36 | for (const auto& shape : result.shapes) { 37 | num_triangles += shape.mesh.num_face_vertices.size(); 38 | } 39 | 40 | std::cout << "Shapes: " << result.shapes.size() << '\n'; 41 | std::cout << "Triangles: " << num_triangles << '\n'; 42 | 43 | return EXIT_SUCCESS; 44 | } 45 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | include(FetchContent) 4 | 5 | 6 | #------------------------------------------------------------------------------------ 7 | # Fetch cereal 8 | #------------------------------------------------------------------------------------ 9 | 10 | set(JUST_INSTALL_CEREAL ON) 11 | 12 | FetchContent_Declare( 13 | cereal 14 | GIT_REPOSITORY https://github.com/USCiLab/cereal 15 | GIT_TAG v1.3.2 16 | GIT_SHALLOW TRUE 17 | ) 18 | 19 | FetchContent_MakeAvailable(cereal) 20 | 21 | 22 | #------------------------------------------------------------------------------------ 23 | # Fetch cxxopt 24 | #------------------------------------------------------------------------------------ 25 | 26 | set(CXXOPTS_BUILD_EXAMPLES OFF) 27 | set(CXXOPTS_BUILD_TESTS OFF) 28 | 29 | FetchContent_Declare( 30 | cxxopts 31 | GIT_REPOSITORY https://github.com/jarro2783/cxxopts 32 | GIT_TAG v2.2.1 33 | GIT_SHALLOW TRUE 34 | ) 35 | 36 | FetchContent_MakeAvailable(cxxopts) 37 | 38 | 39 | #------------------------------------------------------------------------------------ 40 | # Fetch doctest 41 | #------------------------------------------------------------------------------------ 42 | 43 | set(DOCTEST_WITH_TESTS OFF) 44 | set(DOCTEST_NO_INSTALL ON) 45 | 46 | FetchContent_Declare( 47 | doctest 48 | GIT_REPOSITORY https://github.com/onqtam/doctest 49 | GIT_TAG 2.4.6 50 | GIT_SHALLOW TRUE 51 | ) 52 | 53 | FetchContent_MakeAvailable(doctest) 54 | 55 | 56 | #------------------------------------------------------------------------------------ 57 | # Fetch fast_obj 58 | #------------------------------------------------------------------------------------ 59 | 60 | FetchContent_Declare( 61 | fast_obj 62 | GIT_REPOSITORY https://github.com/thisistherk/fast_obj 63 | GIT_TAG v1.2 64 | GIT_SHALLOW TRUE 65 | ) 66 | 67 | FetchContent_MakeAvailable(fast_obj) 68 | 69 | 70 | #------------------------------------------------------------------------------------ 71 | # Fetch tinyobjloader 72 | #------------------------------------------------------------------------------------ 73 | 74 | FetchContent_Declare( 75 | tinyobjloader 76 | GIT_REPOSITORY https://github.com/tinyobjloader/tinyobjloader 77 | GIT_TAG origin/release 78 | GIT_SHALLOW TRUE 79 | ) 80 | 81 | FetchContent_MakeAvailable(tinyobjloader) 82 | 83 | 84 | #------------------------------------------------------------------------------------ 85 | # Fetch xxhash 86 | #------------------------------------------------------------------------------------ 87 | 88 | FetchContent_Declare( 89 | xxhash 90 | GIT_REPOSITORY https://github.com/Cyan4973/xxHash 91 | GIT_TAG v0.8.0 92 | GIT_SHALLOW TRUE 93 | ) 94 | 95 | FetchContent_MakeAvailable(xxhash) 96 | 97 | add_library(xxhash INTERFACE) 98 | 99 | target_include_directories(xxhash INTERFACE "${xxhash_SOURCE_DIR}") 100 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_library(rapidobj INTERFACE) 4 | add_library(rapidobj::rapidobj ALIAS rapidobj) 5 | 6 | target_compile_features(rapidobj INTERFACE cxx_std_17) 7 | 8 | target_include_directories(rapidobj SYSTEM INTERFACE 9 | $ 10 | $ 11 | ) 12 | 13 | if(CMAKE_SYSTEM_NAME MATCHES "Linux") 14 | find_package(Threads REQUIRED) 15 | target_link_libraries(rapidobj INTERFACE Threads::Threads) 16 | target_link_libraries(rapidobj INTERFACE $<$,$,9.0>>:stdc++fs>) 17 | endif() 18 | 19 | write_basic_package_version_file(RapidObjConfigVersion.cmake VERSION ${VERSION} COMPATIBILITY AnyNewerVersion) 20 | 21 | set(DESTINATION_CMAKE_DIR "${RAPIDOBJ_CMAKE_DIR}/RapidObj") 22 | set(DESTINATION_INCLUDE_DIR "${RAPIDOBJ_INCLUDE_DIR}/rapidobj") 23 | 24 | if(MSVC) 25 | target_sources(rapidobj INTERFACE 26 | $ 27 | $ 28 | ) 29 | endif() 30 | 31 | install(TARGETS rapidobj EXPORT RapidObj) 32 | install(EXPORT RapidObj DESTINATION ${DESTINATION_CMAKE_DIR} NAMESPACE rapidobj::) 33 | install(FILES ${PROJECT_SOURCE_DIR}/cmake/RapidObjConfig.cmake DESTINATION ${DESTINATION_CMAKE_DIR}) 34 | install(FILES ${PROJECT_BINARY_DIR}/include/RapidObjConfigVersion.cmake DESTINATION ${DESTINATION_CMAKE_DIR}) 35 | if(CMAKE_SYSTEM_NAME MATCHES "Linux") 36 | # pkg-config file 37 | configure_file(${PROJECT_NAME}.pc.in 38 | ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc 39 | @ONLY 40 | ) 41 | install(FILES 42 | ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc 43 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig 44 | ) 45 | endif() 46 | if(MSVC) 47 | install(FILES ${PROJECT_SOURCE_DIR}/rapidobj.natvis DESTINATION ${DESTINATION_CMAKE_DIR}) 48 | endif() 49 | 50 | install(FILES rapidobj/rapidobj.hpp DESTINATION ${DESTINATION_INCLUDE_DIR}) 51 | -------------------------------------------------------------------------------- /include/RapidObj.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 3 | 4 | Name: @PROJECT_NAME@ 5 | Version: @PROJECT_VERSION@ 6 | 7 | Requires: 8 | Libs: -pthread 9 | Cflags: -I${includedir} 10 | -------------------------------------------------------------------------------- /rapidobj.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ size={m_state.size} }} 7 | 8 | 9 | m_state.size 10 | m_state.data._Mypair._Myval2 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data) 4 | 5 | add_subdirectory(func-tests) 6 | add_subdirectory(unit-tests) 7 | 8 | add_test(NAME unit-tests COMMAND $) 9 | add_test(NAME func-tests COMMAND $) 10 | -------------------------------------------------------------------------------- /tests/data/color/color.obj: -------------------------------------------------------------------------------- 1 | # color.obj 2 | # 3 | 4 | g cube 5 | 6 | v 0.0 0.0 0.0 0 0 0 7 | v 0.0 0.0 1.0 0 0 1 8 | v 0.0 1.0 0.0 0 1 0 9 | v 0.0 1.0 1.0 0 1 1 10 | v 1.0 0.0 0.0 1 0 0 11 | v 1.0 0.0 1.0 1 0 1 12 | v 1.0 1.0 0.0 1 1 0 13 | v 1.0 1.0 1.0 1 1 1 14 | 15 | f 1 3 7 5 16 | f 1 2 4 3 17 | f 5 6 8 7 18 | f 3 4 8 7 19 | f 1 2 6 5 20 | f 2 6 8 4 -------------------------------------------------------------------------------- /tests/data/color/color.obj.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/color/color.obj.ref -------------------------------------------------------------------------------- /tests/data/color/color.obj.test: -------------------------------------------------------------------------------- 1 | color.obj 2 | 0BA3ABD221A1EA5C2773E90AC68667DE 3 | -------------------------------- 4 | color.obj.ref 5 | F54640F2CABBF7F5F6F52A14499F9415 6 | -------------------------------------------------------------------------------- /tests/data/color/color.obj.tri.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/color/color.obj.tri.ref -------------------------------------------------------------------------------- /tests/data/color/color.obj.tri.test: -------------------------------------------------------------------------------- 1 | color.obj 2 | 0BA3ABD221A1EA5C2773E90AC68667DE 3 | -------------------------------- 4 | color.obj.tri.ref 5 | A76D71D444DAF3AAD433F6B0F07F2AA1 6 | -------------------------------------------------------------------------------- /tests/data/cube/cube.obj: -------------------------------------------------------------------------------- 1 | # cube.obj 2 | # 3 | 4 | g cube 5 | 6 | v 0.0 0.0 0.0 7 | v 0.0 0.0 1.0 8 | v 0.0 1.0 0.0 9 | v 0.0 1.0 1.0 10 | v 1.0 0.0 0.0 11 | v 1.0 0.0 1.0 12 | v 1.0 1.0 0.0 13 | v 1.0 1.0 1.0 14 | 15 | f 1 7 5 16 | f 1 3 7 17 | f 1 4 3 18 | f 1 2 4 19 | f 3 8 7 20 | f 3 4 8 21 | f 5 7 8 22 | f 5 8 6 23 | f 1 5 6 24 | f 1 6 2 25 | f 2 6 8 26 | f 2 8 4 27 | -------------------------------------------------------------------------------- /tests/data/cube/cube.obj.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/cube/cube.obj.ref -------------------------------------------------------------------------------- /tests/data/cube/cube.obj.test: -------------------------------------------------------------------------------- 1 | cube.obj 2 | 303900B12958C6BBD5374B36F77C1BC8 3 | -------------------------------- 4 | cube.obj.ref 5 | F88F2E36B029E2AAA50FFA11D8369D55 6 | -------------------------------------------------------------------------------- /tests/data/cube/cube.obj.tri.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/cube/cube.obj.tri.ref -------------------------------------------------------------------------------- /tests/data/cube/cube.obj.tri.test: -------------------------------------------------------------------------------- 1 | cube.obj 2 | 303900B12958C6BBD5374B36F77C1BC8 3 | -------------------------------- 4 | cube.obj.tri.ref 5 | F88F2E36B029E2AAA50FFA11D8369D55 6 | -------------------------------------------------------------------------------- /tests/data/mario/Mario.mtl: -------------------------------------------------------------------------------- 1 | newmtl defaultMat 2 | Ns 100.000 3 | d 1.00000 4 | illum 2 5 | Kd 1.00000 1.00000 1.00000 6 | Ka 0.00000 0.00000 0.00000 7 | Ks 1.00000 1.00000 1.00000 8 | Ke 0.00000e+0 0.00000e+0 0.00000e+0 9 | map_Kd Mario_Albedo.png 10 | bump Mario_Height.png 11 | map_bump Mario_Normal.png 12 | -------------------------------------------------------------------------------- /tests/data/mario/mario.obj.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/mario/mario.obj.ref -------------------------------------------------------------------------------- /tests/data/mario/mario.obj.test: -------------------------------------------------------------------------------- 1 | mario.obj 2 | AC78BE02DFF4EDD77448A3A153435EEB 3 | -------------------------------- 4 | mario.obj.ref 5 | F88C35632A55876C6B342AF8F387DFF4 6 | -------------------------------------------------------------------------------- /tests/data/mario/mario.obj.tri.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/mario/mario.obj.tri.ref -------------------------------------------------------------------------------- /tests/data/mario/mario.obj.tri.test: -------------------------------------------------------------------------------- 1 | mario.obj 2 | AC78BE02DFF4EDD77448A3A153435EEB 3 | -------------------------------- 4 | mario.obj.tri.ref 5 | 1EE04A6051553BDF284F686E331A2246 6 | -------------------------------------------------------------------------------- /tests/data/mtllib/blue/cube.mtl: -------------------------------------------------------------------------------- 1 | newmtl foo 2 | Kd 0 0 1 3 | 4 | newmtl bar 5 | Kd 0 0 1 6 | 7 | newmtl baz 8 | Kd 0 0 1 9 | -------------------------------------------------------------------------------- /tests/data/mtllib/cube.mtl: -------------------------------------------------------------------------------- 1 | newmtl foo 2 | Kd 1 1 1 3 | 4 | newmtl bar 5 | Kd 1 1 1 6 | 7 | newmtl baz 8 | Kd 1 1 1 9 | -------------------------------------------------------------------------------- /tests/data/mtllib/cube.obj: -------------------------------------------------------------------------------- 1 | # cube.obj 2 | # 3 | 4 | mtllib cube.mtl 5 | 6 | g cube 7 | 8 | v 0.0 0.0 0.0 0 0 0 9 | v 0.0 0.0 1.0 0 0 1 10 | v 0.0 1.0 0.0 0 1 0 11 | v 0.0 1.0 1.0 0 1 1 12 | v 1.0 0.0 0.0 1 0 0 13 | v 1.0 0.0 1.0 1 0 1 14 | v 1.0 1.0 0.0 1 1 0 15 | v 1.0 1.0 1.0 1 1 1 16 | 17 | usemtl foo 18 | f 1 3 7 5 19 | f 1 2 4 3 20 | 21 | usemtl bar 22 | f 5 6 8 7 23 | f 3 4 8 7 24 | 25 | usemtl baz 26 | f 1 2 6 5 27 | f 2 6 8 4 28 | -------------------------------------------------------------------------------- /tests/data/mtllib/cube_mtllib_missing.obj: -------------------------------------------------------------------------------- 1 | # cube.obj 2 | # 3 | 4 | mtllib missing.mtl 5 | 6 | g cube 7 | 8 | v 0.0 0.0 0.0 0 0 0 9 | v 0.0 0.0 1.0 0 0 1 10 | v 0.0 1.0 0.0 0 1 0 11 | v 0.0 1.0 1.0 0 1 1 12 | v 1.0 0.0 0.0 1 0 0 13 | v 1.0 0.0 1.0 1 0 1 14 | v 1.0 1.0 0.0 1 1 0 15 | v 1.0 1.0 1.0 1 1 1 16 | 17 | usemtl foo 18 | f 1 3 7 5 19 | f 1 2 4 3 20 | 21 | usemtl bar 22 | f 5 6 8 7 23 | f 3 4 8 7 24 | 25 | usemtl baz 26 | f 1 2 6 5 27 | f 2 6 8 4 28 | -------------------------------------------------------------------------------- /tests/data/mtllib/green/cube.mtl: -------------------------------------------------------------------------------- 1 | newmtl foo 2 | Kd 0 1 0 3 | 4 | newmtl bar 5 | Kd 0 1 0 6 | 7 | newmtl baz 8 | Kd 0 1 0 9 | -------------------------------------------------------------------------------- /tests/data/mtllib/red/cube.mtl: -------------------------------------------------------------------------------- 1 | newmtl foo 2 | Kd 1 0 0 3 | 4 | newmtl bar 5 | Kd 1 0 0 6 | 7 | newmtl baz 8 | Kd 1 0 0 9 | -------------------------------------------------------------------------------- /tests/data/mtllib/yellow.mtl: -------------------------------------------------------------------------------- 1 | newmtl foo 2 | Kd 1 1 0 3 | 4 | newmtl bar 5 | Kd 1 1 0 6 | 7 | newmtl baz 8 | Kd 1 1 0 9 | -------------------------------------------------------------------------------- /tests/data/primitives/primitives.obj: -------------------------------------------------------------------------------- 1 | v 0 0 0 2 | v 0 1 0 3 | v 1 1 0 4 | v 1 0 0 5 | v 0 0 1 6 | v 0 1 1 7 | v 1 1 1 8 | v 1 0 1 9 | 10 | f 1 2 3 4 11 | f -1 -2 -3 -4 12 | 13 | l 1 3 5 7 14 | l -1 -5 15 | 16 | p 1 17 | p 2 3 18 | p -1 19 | -------------------------------------------------------------------------------- /tests/data/primitives/primitives.obj.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/primitives/primitives.obj.ref -------------------------------------------------------------------------------- /tests/data/primitives/primitives.obj.test: -------------------------------------------------------------------------------- 1 | primitives.obj 2 | 7D8CD2DF4563C15910824F45E65AE7C5 3 | -------------------------------- 4 | primitives.obj.ref 5 | F34D608D65A4E6C23FB5D4AD543EC012 6 | -------------------------------------------------------------------------------- /tests/data/primitives/primitives.obj.tri.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/primitives/primitives.obj.tri.ref -------------------------------------------------------------------------------- /tests/data/primitives/primitives.obj.tri.test: -------------------------------------------------------------------------------- 1 | primitives.obj 2 | 7D8CD2DF4563C15910824F45E65AE7C5 3 | -------------------------------- 4 | primitives.obj.tri.ref 5 | 9DFDC2DC37F2EE59508B9F1841117812 6 | -------------------------------------------------------------------------------- /tests/data/sponza/copyright.txt: -------------------------------------------------------------------------------- 1 | http://hdri.cgtechniques.com/~sponza/files/ 2 | 3 | Sponza modeled by Marko Dabrovic, with UVs and crack errors fixed by Kenzie Lamar at Vicarious Visions. 4 | Bump maps painted by Morgan McGuire. 5 | -------------------------------------------------------------------------------- /tests/data/sponza/sponza.mtl: -------------------------------------------------------------------------------- 1 | # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware 2 | # File Created: 26.07.2011 17:00:30 3 | 4 | newmtl sp_00_luk_mali 5 | Ns 50.000000 6 | Ni 1.500000 7 | d 1.000000 8 | Tr 0.000000 9 | Tf 1.000000 1.000000 1.000000 10 | illum 2 11 | Ka 0.000000 0.000000 0.000000 12 | Kd 0.745098 0.709804 0.674510 13 | Ks 0.000000 0.000000 0.000000 14 | Ke 0.000000 0.000000 0.000000 15 | map_Ka SP_LUK.JPG 16 | map_Kd SP_LUK.JPG 17 | map_bump sp_luk-bump.JPG 18 | bump sp_luk-bump.JPG 19 | 20 | newmtl sp_svod_kapitel 21 | Ns 50.000000 22 | Ni 1.500000 23 | d 1.000000 24 | Tr 0.000000 25 | Tf 1.000000 1.000000 1.000000 26 | illum 2 27 | Ka 0.000000 0.000000 0.000000 28 | Kd 0.713726 0.705882 0.658824 29 | Ks 0.000000 0.000000 0.000000 30 | Ke 0.000000 0.000000 0.000000 31 | map_Ka 00_SKAP.JPG 32 | map_Kd 00_SKAP.JPG 33 | map_bump 00_SKAP.JPG 34 | bump 00_SKAP.JPG 35 | 36 | newmtl sp_01_stub_baza_ 37 | Ns 19.999998 38 | Ni 1.500000 39 | d 1.000000 40 | Tr 0.000000 41 | Tf 1.000000 1.000000 1.000000 42 | illum 2 43 | Ka 0.000000 0.000000 0.000000 44 | Kd 0.784314 0.784314 0.784314 45 | Ks 0.000000 0.000000 0.000000 46 | Ke 0.000000 0.000000 0.000000 47 | 48 | newmtl sp_01_stub_kut 49 | Ns 50.000000 50 | Ni 1.500000 51 | d 1.000000 52 | Tr 0.000000 53 | Tf 1.000000 1.000000 1.000000 54 | illum 2 55 | Ka 0.000000 0.000000 0.000000 56 | Kd 0.737255 0.709804 0.670588 57 | Ks 0.000000 0.000000 0.000000 58 | Ke 0.000000 0.000000 0.000000 59 | map_Ka 01_STUB.JPG 60 | map_Kd 01_STUB.JPG 61 | map_bump 01_STUB-bump.jpg 62 | bump 01_STUB-bump.jpg 63 | 64 | newmtl sp_00_stup 65 | Ns 50.000000 66 | Ni 1.500000 67 | d 1.000000 68 | Tr 0.000000 69 | Tf 1.000000 1.000000 1.000000 70 | illum 2 71 | Ka 0.000000 0.000000 0.000000 72 | Kd 0.737255 0.709804 0.670588 73 | Ks 0.000000 0.000000 0.000000 74 | Ke 0.000000 0.000000 0.000000 75 | map_Ka 01_STUB.JPG 76 | map_Kd 01_STUB.JPG 77 | map_bump 01_STUB-bump.jpg 78 | bump 01_STUB-bump.jpg 79 | 80 | newmtl sp_01_stub_baza 81 | Ns 50.000000 82 | Ni 1.500000 83 | d 1.000000 84 | Tr 0.000000 85 | Tf 1.000000 1.000000 1.000000 86 | illum 2 87 | Ka 0.000000 0.000000 0.000000 88 | Kd 0.800000 0.784314 0.749020 89 | Ks 0.000000 0.000000 0.000000 90 | Ke 0.000000 0.000000 0.000000 91 | map_Ka 01_S_BA.JPG 92 | map_Kd 01_S_BA.JPG 93 | map_bump 01_S_BA.JPG 94 | bump 01_S_BA.JPG 95 | 96 | newmtl sp_00_luk_mal1 97 | Ns 50.000000 98 | Ni 1.500000 99 | d 1.000000 100 | Tr 0.000000 101 | Tf 1.000000 1.000000 1.000000 102 | illum 2 103 | Ka 0.000000 0.000000 0.000000 104 | Kd 0.745098 0.709804 0.674510 105 | Ks 0.000000 0.000000 0.000000 106 | Ke 0.000000 0.000000 0.000000 107 | map_Ka 01_ST_KP.JPG 108 | map_Kd 01_ST_KP.JPG 109 | map_bump 01_St_kp-bump.jpg 110 | bump 01_St_kp-bump.jpg 111 | 112 | newmtl sp_01_stub 113 | Ns 50.000000 114 | Ni 1.500000 115 | d 1.000000 116 | Tr 0.000000 117 | Tf 1.000000 1.000000 1.000000 118 | illum 2 119 | Ka 0.000000 0.000000 0.000000 120 | Kd 0.737255 0.709804 0.670588 121 | Ks 0.000000 0.000000 0.000000 122 | Ke 0.000000 0.000000 0.000000 123 | map_Ka 01_STUB.JPG 124 | map_Kd 01_STUB.JPG 125 | map_bump 01_STUB-bump.jpg 126 | bump 01_STUB-bump.jpg 127 | 128 | newmtl sp_01_stup 129 | Ns 50.000000 130 | Ni 1.500000 131 | d 1.000000 132 | Tr 0.000000 133 | Tf 1.000000 1.000000 1.000000 134 | illum 2 135 | Ka 0.000000 0.000000 0.000000 136 | Kd 0.827451 0.800000 0.768628 137 | Ks 0.000000 0.000000 0.000000 138 | Ke 0.000000 0.000000 0.000000 139 | map_Ka X01_ST.JPG 140 | map_Kd X01_ST.JPG 141 | 142 | newmtl sp_vijenac 143 | Ns 50.000000 144 | Ni 1.500000 145 | d 1.000000 146 | Tr 0.000000 147 | Tf 1.000000 1.000000 1.000000 148 | illum 2 149 | Ka 0.000000 0.000000 0.000000 150 | Kd 0.713726 0.705882 0.658824 151 | Ks 0.000000 0.000000 0.000000 152 | Ke 0.000000 0.000000 0.000000 153 | map_Ka 00_SKAP.JPG 154 | map_Kd 00_SKAP.JPG 155 | map_bump 00_SKAP.JPG 156 | bump 00_SKAP.JPG 157 | 158 | newmtl sp_00_svod 159 | Ns 1.000000 160 | Ni 1.500000 161 | d 1.000000 162 | Tr 0.000000 163 | Tf 1.000000 1.000000 1.000000 164 | illum 2 165 | Ka 0.145098 0.145098 0.145098 166 | Kd 0.941177 0.866667 0.737255 167 | Ks 0.034039 0.032314 0.029333 168 | Ke 0.000000 0.000000 0.000000 169 | map_Kd KAMEN-stup.JPG 170 | map_Ka KAMEN-stup.JPG 171 | map_bump KAMEN-stup.jpg 172 | bump KAMEN-stup.jpg 173 | 174 | newmtl sp_02_reljef 175 | Ns 50.000000 176 | Ni 1.500000 177 | d 1.000000 178 | Tr 0.000000 179 | Tf 1.000000 1.000000 1.000000 180 | illum 2 181 | Ka 0.000000 0.000000 0.000000 182 | Kd 0.529412 0.498039 0.490196 183 | Ks 0.000000 0.000000 0.000000 184 | Ke 0.000000 0.000000 0.000000 185 | map_Ka RELJEF.JPG 186 | map_Kd RELJEF.JPG 187 | map_bump reljef-bump.jpg 188 | bump reljef-bump.jpg 189 | 190 | newmtl sp_01_luk_a 191 | Ns 50.000000 192 | Ni 1.500000 193 | d 1.000000 194 | Tr 0.000000 195 | Tf 1.000000 1.000000 1.000000 196 | illum 2 197 | Ka 0.000000 0.000000 0.000000 198 | Kd 0.745098 0.709804 0.674510 199 | Ks 0.000000 0.000000 0.000000 200 | Ke 0.000000 0.000000 0.000000 201 | map_Ka SP_LUK.JPG 202 | map_Kd SP_LUK.JPG 203 | map_bump sp_luk-bump.JPG 204 | bump sp_luk-bump.JPG 205 | 206 | newmtl sp_zid_vani 207 | Ns 50.000000 208 | Ni 1.500000 209 | d 1.000000 210 | Tr 0.000000 211 | Tf 1.000000 1.000000 1.000000 212 | illum 2 213 | Ka 0.000000 0.000000 0.000000 214 | Kd 0.627451 0.572549 0.560784 215 | Ks 0.000000 0.000000 0.000000 216 | Ke 0.000000 0.000000 0.000000 217 | map_Ka KAMEN.JPG 218 | map_Kd KAMEN.JPG 219 | map_bump KAMEN-bump.jpg 220 | bump KAMEN-bump.jpg 221 | 222 | newmtl sp_01_stup_baza 223 | Ns 50.000000 224 | Ni 1.500000 225 | d 1.000000 226 | Tr 0.000000 227 | Tf 1.000000 1.000000 1.000000 228 | illum 2 229 | Ka 0.000000 0.000000 0.000000 230 | Kd 0.800000 0.784314 0.749020 231 | Ks 0.000000 0.000000 0.000000 232 | Ke 0.000000 0.000000 0.000000 233 | map_Ka 01_S_BA.JPG 234 | map_Kd 01_S_BA.JPG 235 | map_bump 01_S_BA.JPG 236 | bump 01_S_BA.JPG 237 | 238 | newmtl sp_00_zid 239 | Ns 50.000000 240 | Ni 1.500000 241 | d 1.000000 242 | Tr 0.000000 243 | Tf 1.000000 1.000000 1.000000 244 | illum 2 245 | Ka 0.000000 0.000000 0.000000 246 | Kd 0.627451 0.572549 0.560784 247 | Ks 0.000000 0.000000 0.000000 248 | Ke 0.000000 0.000000 0.000000 249 | map_Ka KAMEN.JPG 250 | map_Kd KAMEN.JPG 251 | map_bump KAMEN-bump.jpg 252 | bump KAMEN-bump.jpg 253 | 254 | newmtl sp_00_prozor 255 | Ns 50.000000 256 | Ni 1.500000 257 | d 1.000000 258 | Tr 0.000000 259 | Tf 1.000000 1.000000 1.000000 260 | illum 2 261 | Ka 0.000000 0.000000 0.000000 262 | Kd 1.000000 1.000000 1.000000 263 | Ks 0.000000 0.000000 0.000000 264 | Ke 0.000000 0.000000 0.000000 265 | map_Ka PROZOR1.JPG 266 | map_Kd PROZOR1.JPG 267 | map_bump PROZOR1.JPG 268 | bump PROZOR1.JPG 269 | 270 | newmtl sp_00_vrata_krug 271 | Ns 19.999998 272 | Ni 1.500000 273 | d 1.000000 274 | Tr 0.000000 275 | Tf 1.000000 1.000000 1.000000 276 | illum 2 277 | Ka 0.000000 0.000000 0.000000 278 | Kd 0.784314 0.784314 0.784314 279 | Ks 0.000000 0.000000 0.000000 280 | Ke 0.000000 0.000000 0.000000 281 | map_Ka VRATA_KR.JPG 282 | map_Kd VRATA_KR.JPG 283 | map_bump VRATA_KR.JPG 284 | bump VRATA_KR.JPG 285 | 286 | newmtl sp_00_pod 287 | Ns 50.000000 288 | Ni 1.500000 289 | d 1.000000 290 | Tr 0.000000 291 | Tf 1.000000 1.000000 1.000000 292 | illum 2 293 | Ka 0.000000 0.000000 0.000000 294 | Kd 0.627451 0.572549 0.560784 295 | Ks 0.000000 0.000000 0.000000 296 | Ke 0.000000 0.000000 0.000000 297 | map_Ka KAMEN.JPG 298 | map_Kd KAMEN.JPG 299 | map_bump KAMEN-bump.jpg 300 | bump KAMEN-bump.jpg 301 | 302 | newmtl sp_00_vrata_kock 303 | Ns 19.999998 304 | Ni 1.500000 305 | d 1.000000 306 | Tr 0.000000 307 | Tf 1.000000 1.000000 1.000000 308 | illum 2 309 | Ka 0.000000 0.000000 0.000000 310 | Kd 0.784314 0.784314 0.784314 311 | Ks 0.000000 0.000000 0.000000 312 | Ke 0.000000 0.000000 0.000000 313 | map_Ka VRATA_KO.JPG 314 | map_Kd VRATA_KO.JPG 315 | map_bump VRATA_KO.JPG 316 | bump VRATA_KO.JPG 317 | -------------------------------------------------------------------------------- /tests/data/sponza/sponza.obj.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/sponza/sponza.obj.ref -------------------------------------------------------------------------------- /tests/data/sponza/sponza.obj.test: -------------------------------------------------------------------------------- 1 | sponza.obj 2 | 010F68E7D4E5F538CCCB886896D8A97E 3 | -------------------------------- 4 | sponza.obj.ref 5 | 14E17D6049EEAB80CBD28BB536164D41 6 | -------------------------------------------------------------------------------- /tests/data/sponza/sponza.obj.tri.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/sponza/sponza.obj.tri.ref -------------------------------------------------------------------------------- /tests/data/sponza/sponza.obj.tri.test: -------------------------------------------------------------------------------- 1 | sponza.obj 2 | 010F68E7D4E5F538CCCB886896D8A97E 3 | -------------------------------- 4 | sponza.obj.tri.ref 5 | C1D9F130C1A52F33EB56B16A413300D7 6 | -------------------------------------------------------------------------------- /tests/data/teapot/teapot.obj.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/teapot/teapot.obj.ref -------------------------------------------------------------------------------- /tests/data/teapot/teapot.obj.test: -------------------------------------------------------------------------------- 1 | teapot.obj 2 | C73FC18DB86D89A839BD11ADEAE0A636 3 | -------------------------------- 4 | teapot.obj.ref 5 | C45F977896551FDAD9F6FE543099DE9D 6 | -------------------------------------------------------------------------------- /tests/data/teapot/teapot.obj.tri.ref: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guybrush77/rapidobj/744374a5d21fe01704eab0f36e633e8d620265e5/tests/data/teapot/teapot.obj.tri.ref -------------------------------------------------------------------------------- /tests/data/teapot/teapot.obj.tri.test: -------------------------------------------------------------------------------- 1 | teapot.obj 2 | C73FC18DB86D89A839BD11ADEAE0A636 3 | -------------------------------- 4 | teapot.obj.tri.ref 5 | BC610C71CDAD96C0A405597CC9FBFC9E 6 | -------------------------------------------------------------------------------- /tests/func-tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_executable(func-tests) 4 | 5 | target_sources(func-tests PRIVATE 6 | "src/test_main.cpp" 7 | "src/test_rapidobj.cpp" 8 | ) 9 | 10 | target_compile_features(func-tests PRIVATE cxx_std_17) 11 | 12 | target_compile_definitions(func-tests PUBLIC TEST_DATA_DIR=${TEST_DATA_DIR}) 13 | 14 | target_link_libraries(func-tests PRIVATE doctest compare-test) 15 | -------------------------------------------------------------------------------- /tests/func-tests/src/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /tests/func-tests/src/test_rapidobj.cpp: -------------------------------------------------------------------------------- 1 | #include "doctest/doctest.h" 2 | 3 | #include "compare-test/compare-test.hpp" 4 | 5 | #ifndef TEST_DATA_DIR 6 | #error Cannot find test files; TEST_DATA_DIR is not defined 7 | #endif 8 | 9 | #define Q(x) #x 10 | #define QUOTE(x) Q(x) 11 | 12 | static auto MakeTest(const std::string& path) 13 | { 14 | static const std::string basepath = QUOTE(TEST_DATA_DIR); 15 | 16 | return rapidobj::test::ParseTest(basepath + "/" + path); 17 | } 18 | 19 | using rapidobj::test::Triangulate; 20 | 21 | TEST_CASE("color") 22 | { 23 | auto test = MakeTest("color/color.obj.test"); 24 | 25 | CHECK(test.IsTestValid()); 26 | 27 | CHECK(test.ParseFile(Triangulate::No)); 28 | CHECK(test.ParseStream(Triangulate::No)); 29 | } 30 | 31 | TEST_CASE("color-triangulated") 32 | { 33 | auto test = MakeTest("color/color.obj.tri.test"); 34 | 35 | CHECK(test.IsTestValid()); 36 | 37 | CHECK(test.ParseFile(Triangulate::Yes)); 38 | CHECK(test.ParseStream(Triangulate::Yes)); 39 | } 40 | 41 | TEST_CASE("cube") 42 | { 43 | auto test = MakeTest("cube/cube.obj.test"); 44 | 45 | CHECK(test.IsTestValid()); 46 | 47 | CHECK(test.ParseFile(Triangulate::No)); 48 | CHECK(test.ParseStream(Triangulate::No)); 49 | } 50 | 51 | TEST_CASE("cube-triangulated") 52 | { 53 | auto test = MakeTest("cube/cube.obj.tri.test"); 54 | 55 | CHECK(test.IsTestValid()); 56 | 57 | CHECK(test.ParseFile(Triangulate::Yes)); 58 | CHECK(test.ParseStream(Triangulate::Yes)); 59 | } 60 | 61 | TEST_CASE("mario") 62 | { 63 | auto test = MakeTest("mario/mario.obj.test"); 64 | 65 | CHECK(test.IsTestValid()); 66 | 67 | CHECK(test.ParseFile(Triangulate::No)); 68 | CHECK(test.ParseStream(Triangulate::No)); 69 | } 70 | 71 | TEST_CASE("mario-triangulated") 72 | { 73 | auto test = MakeTest("mario/mario.obj.tri.test"); 74 | 75 | CHECK(test.IsTestValid()); 76 | 77 | CHECK(test.ParseFile(Triangulate::Yes)); 78 | CHECK(test.ParseStream(Triangulate::Yes)); 79 | } 80 | 81 | TEST_CASE("primitives") 82 | { 83 | auto test = MakeTest("primitives/primitives.obj.test"); 84 | 85 | CHECK(test.IsTestValid()); 86 | 87 | CHECK(test.ParseFile(Triangulate::No)); 88 | CHECK(test.ParseStream(Triangulate::No)); 89 | } 90 | 91 | TEST_CASE("primitives-triangulated") 92 | { 93 | auto test = MakeTest("primitives/primitives.obj.tri.test"); 94 | 95 | CHECK(test.IsTestValid()); 96 | 97 | CHECK(test.ParseFile(Triangulate::Yes)); 98 | CHECK(test.ParseStream(Triangulate::Yes)); 99 | } 100 | 101 | TEST_CASE("sponza") 102 | { 103 | auto test = MakeTest("sponza/sponza.obj.test"); 104 | 105 | CHECK(test.IsTestValid()); 106 | 107 | CHECK(test.ParseFile(Triangulate::No)); 108 | CHECK(test.ParseStream(Triangulate::No)); 109 | } 110 | 111 | TEST_CASE("sponza-triangulated") 112 | { 113 | auto test = MakeTest("sponza/sponza.obj.tri.test"); 114 | 115 | CHECK(test.IsTestValid()); 116 | 117 | CHECK(test.ParseFile(Triangulate::Yes)); 118 | CHECK(test.ParseStream(Triangulate::Yes)); 119 | } 120 | 121 | TEST_CASE("teapot") 122 | { 123 | auto test = MakeTest("teapot/teapot.obj.test"); 124 | 125 | CHECK(test.IsTestValid()); 126 | 127 | CHECK(test.ParseFile(Triangulate::No)); 128 | CHECK(test.ParseStream(Triangulate::No)); 129 | } 130 | 131 | TEST_CASE("teapot-triangulated") 132 | { 133 | auto test = MakeTest("teapot/teapot.obj.tri.test"); 134 | 135 | CHECK(test.IsTestValid()); 136 | 137 | CHECK(test.ParseFile(Triangulate::Yes)); 138 | CHECK(test.ParseStream(Triangulate::Yes)); 139 | } 140 | -------------------------------------------------------------------------------- /tests/unit-tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_executable(unit-tests) 4 | 5 | target_sources(unit-tests PRIVATE 6 | "src/test_main.cpp" 7 | "src/test_material_parsing.cpp" 8 | "src/test_mtllib.cpp" 9 | "src/test_parsing.cpp" 10 | ) 11 | 12 | target_compile_features(unit-tests PRIVATE cxx_std_17) 13 | 14 | target_compile_definitions(unit-tests PUBLIC TEST_DATA_DIR=${TEST_DATA_DIR}) 15 | 16 | target_link_libraries(unit-tests PRIVATE doctest rapidobj) 17 | -------------------------------------------------------------------------------- /tests/unit-tests/src/test_main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /tests/unit-tests/src/test_material_parsing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace rapidobj; 5 | using namespace rapidobj::detail; 6 | 7 | static constexpr auto test_material = R"( 8 | 9 | newmtl foo 10 | 11 | Ka 0 1 2 12 | Kd 3 4 5 13 | Ks 6 7 8 14 | Kt 9 10 11 15 | Ke 12 13 14 16 | 17 | Ns 2.0 18 | Ni 3.0 19 | d 4.0 20 | 21 | illum 42 22 | 23 | map_Ka -texres 1 ambient.jpg 24 | map_Kd -texres 2 diffuse.jpg 25 | map_Ks -texres 3 specular.jpg 26 | map_Ns -texres 4 highlight.jpg 27 | map_bump -texres 5 bump.bmp 28 | disp -texres 6 displacement.png 29 | map_d -texres 7 alpha.png 30 | refl -texres 8 refl.jpg 31 | 32 | )"; 33 | 34 | static constexpr auto pbr_material = R"( 35 | 36 | newmtl bar 37 | 38 | Pr 1.0 39 | Pm 2.0 40 | Ps 3.0 41 | Pc 4.0 42 | Pcr 5.0 43 | 44 | aniso 6.0 45 | anisor 7.0 46 | 47 | map_Pr roughness.jpg 48 | map_Pm metallic.jpg 49 | map_Ps sheen.jpg 50 | map_Ke emissive.hdr 51 | norm normal.png 52 | 53 | )"; 54 | 55 | static constexpr auto bad_material_1 = R"( 56 | 57 | newmtl bad 58 | 59 | Pr 1.0 60 | $%!* 61 | Ps 3.0 62 | 63 | )"; 64 | 65 | static constexpr auto bad_material_2 = R"( 66 | 67 | newmtl bad 68 | 69 | Pr 1.0 70 | Ps 3.0 71 | illum 3 foo 72 | 73 | )"; 74 | 75 | TEST_CASE("rapidobj::detail::ParseTextureOption") 76 | { 77 | // option -blendu 78 | { 79 | auto texture_option = TextureOption{}; 80 | auto [line, success] = ParseTextureOption("-blendu on", &texture_option); 81 | 82 | CHECK(success == true); 83 | CHECK(texture_option.blendu == true); 84 | } 85 | 86 | { 87 | auto texture_option = TextureOption{}; 88 | auto [line, success] = ParseTextureOption("-blendu off", &texture_option); 89 | 90 | CHECK(success == true); 91 | CHECK(texture_option.blendu == false); 92 | } 93 | 94 | { 95 | auto texture_option = TextureOption{}; 96 | auto [line, success] = ParseTextureOption("-blendu", &texture_option); 97 | 98 | CHECK(success == false); 99 | } 100 | 101 | { 102 | auto texture_option = TextureOption{}; 103 | auto [line, success] = ParseTextureOption("-blendu foo", &texture_option); 104 | 105 | CHECK(success == false); 106 | } 107 | 108 | { 109 | auto texture_option = TextureOption{}; 110 | auto [line, success] = ParseTextureOption("-blendu offten", &texture_option); 111 | 112 | CHECK(success == false); 113 | } 114 | 115 | // option -blendv 116 | { 117 | auto texture_option = TextureOption{}; 118 | auto [line, success] = ParseTextureOption("-blendv on", &texture_option); 119 | 120 | CHECK(success == true); 121 | CHECK(texture_option.blendv == true); 122 | } 123 | 124 | { 125 | auto texture_option = TextureOption{}; 126 | auto [line, success] = ParseTextureOption("-blendv off", &texture_option); 127 | 128 | CHECK(success == true); 129 | CHECK(texture_option.blendv == false); 130 | } 131 | 132 | { 133 | auto texture_option = TextureOption{}; 134 | auto [line, success] = ParseTextureOption("-blendv", &texture_option); 135 | 136 | CHECK(success == false); 137 | } 138 | 139 | { 140 | auto texture_option = TextureOption{}; 141 | auto [line, success] = ParseTextureOption("-blendv onn", &texture_option); 142 | 143 | CHECK(success == false); 144 | } 145 | 146 | { 147 | auto texture_option = TextureOption{}; 148 | auto [line, success] = ParseTextureOption("-blendv foo", &texture_option); 149 | 150 | CHECK(success == false); 151 | } 152 | 153 | // option -boost 154 | { 155 | auto texture_option = TextureOption{}; 156 | auto [line, success] = ParseTextureOption("-boost 1.75", &texture_option); 157 | 158 | CHECK(success == true); 159 | CHECK(texture_option.sharpness == 1.75f); 160 | } 161 | 162 | { 163 | auto texture_option = TextureOption{}; 164 | auto [line, success] = ParseTextureOption("-boost", &texture_option); 165 | 166 | CHECK(success == false); 167 | } 168 | 169 | { 170 | auto texture_option = TextureOption{}; 171 | auto [line, success] = ParseTextureOption("-boost on", &texture_option); 172 | 173 | CHECK(success == false); 174 | } 175 | 176 | // option -mm 177 | { 178 | auto texture_option = TextureOption{}; 179 | auto [line, success] = ParseTextureOption("-mm 2.5 4.0", &texture_option); 180 | 181 | CHECK(success == true); 182 | CHECK(texture_option.brightness == 2.5f); 183 | CHECK(texture_option.contrast == 4.0f); 184 | } 185 | 186 | { 187 | auto texture_option = TextureOption{}; 188 | auto [line, success] = ParseTextureOption("-mm", &texture_option); 189 | 190 | CHECK(success == false); 191 | } 192 | 193 | { 194 | auto texture_option = TextureOption{}; 195 | auto [line, success] = ParseTextureOption("-mm 2.5", &texture_option); 196 | 197 | CHECK(success == false); 198 | } 199 | 200 | { 201 | auto texture_option = TextureOption{}; 202 | auto [line, success] = ParseTextureOption("-mm 2.5 4.0?", &texture_option); 203 | 204 | CHECK(success == false); 205 | } 206 | 207 | // option -o 208 | { 209 | auto texture_option = TextureOption{}; 210 | auto [line, success] = ParseTextureOption("-o 5.0", &texture_option); 211 | 212 | CHECK(success == true); 213 | CHECK(texture_option.origin_offset[0] == 5.0f); 214 | CHECK(texture_option.origin_offset[1] == 0.0f); 215 | CHECK(texture_option.origin_offset[2] == 0.0f); 216 | } 217 | 218 | { 219 | auto texture_option = TextureOption{}; 220 | auto [line, success] = ParseTextureOption("-o 5.0 6.0", &texture_option); 221 | 222 | CHECK(success == true); 223 | CHECK(texture_option.origin_offset[0] == 5.0f); 224 | CHECK(texture_option.origin_offset[1] == 6.0f); 225 | CHECK(texture_option.origin_offset[2] == 0.0f); 226 | } 227 | 228 | { 229 | auto texture_option = TextureOption{}; 230 | auto [line, success] = ParseTextureOption("-o 5.0 6.0 7.0", &texture_option); 231 | 232 | CHECK(success == true); 233 | CHECK(texture_option.origin_offset[0] == 5.0f); 234 | CHECK(texture_option.origin_offset[1] == 6.0f); 235 | CHECK(texture_option.origin_offset[2] == 7.0f); 236 | } 237 | 238 | { 239 | auto texture_option = TextureOption{}; 240 | auto [line, success] = ParseTextureOption("-o", &texture_option); 241 | 242 | CHECK(success == false); 243 | } 244 | 245 | { 246 | auto texture_option = TextureOption{}; 247 | auto [line, success] = ParseTextureOption("-o ?", &texture_option); 248 | 249 | CHECK(success == false); 250 | } 251 | 252 | // option -s 253 | { 254 | auto texture_option = TextureOption{}; 255 | auto [line, success] = ParseTextureOption("-s 5.0", &texture_option); 256 | 257 | CHECK(success == true); 258 | CHECK(texture_option.scale[0] == 5.0f); 259 | CHECK(texture_option.scale[1] == 1.0f); 260 | CHECK(texture_option.scale[2] == 1.0f); 261 | } 262 | 263 | { 264 | auto texture_option = TextureOption{}; 265 | auto [line, success] = ParseTextureOption("-s 5.0 6.0", &texture_option); 266 | 267 | CHECK(success == true); 268 | CHECK(texture_option.scale[0] == 5.0f); 269 | CHECK(texture_option.scale[1] == 6.0f); 270 | CHECK(texture_option.scale[2] == 1.0f); 271 | } 272 | 273 | { 274 | auto texture_option = TextureOption{}; 275 | auto [line, success] = ParseTextureOption("-s 5.0 6.0 7.0", &texture_option); 276 | 277 | CHECK(success == true); 278 | CHECK(texture_option.scale[0] == 5.0f); 279 | CHECK(texture_option.scale[1] == 6.0f); 280 | CHECK(texture_option.scale[2] == 7.0f); 281 | } 282 | 283 | { 284 | auto texture_option = TextureOption{}; 285 | auto [line, success] = ParseTextureOption("-s", &texture_option); 286 | 287 | CHECK(success == false); 288 | } 289 | 290 | { 291 | auto texture_option = TextureOption{}; 292 | auto [line, success] = ParseTextureOption("-s 2.g?", &texture_option); 293 | 294 | CHECK(success == false); 295 | } 296 | 297 | // option -t 298 | { 299 | auto texture_option = TextureOption{}; 300 | auto [line, success] = ParseTextureOption("-t 5.0", &texture_option); 301 | 302 | CHECK(success == true); 303 | CHECK(texture_option.turbulence[0] == 5.0f); 304 | CHECK(texture_option.turbulence[1] == 0.0f); 305 | CHECK(texture_option.turbulence[2] == 0.0f); 306 | } 307 | 308 | { 309 | auto texture_option = TextureOption{}; 310 | auto [line, success] = ParseTextureOption("-t 5.0 6.0", &texture_option); 311 | 312 | CHECK(success == true); 313 | CHECK(texture_option.turbulence[0] == 5.0f); 314 | CHECK(texture_option.turbulence[1] == 6.0f); 315 | CHECK(texture_option.turbulence[2] == 0.0f); 316 | } 317 | 318 | { 319 | auto texture_option = TextureOption{}; 320 | auto [line, success] = ParseTextureOption("-t 5.0 6.0 7.0", &texture_option); 321 | 322 | CHECK(success == true); 323 | CHECK(texture_option.turbulence[0] == 5.0f); 324 | CHECK(texture_option.turbulence[1] == 6.0f); 325 | CHECK(texture_option.turbulence[2] == 7.0f); 326 | } 327 | 328 | { 329 | auto texture_option = TextureOption{}; 330 | auto [line, success] = ParseTextureOption("-t", &texture_option); 331 | 332 | CHECK(success == false); 333 | } 334 | 335 | { 336 | auto texture_option = TextureOption{}; 337 | auto [line, success] = ParseTextureOption("-t 12345-", &texture_option); 338 | 339 | CHECK(success == false); 340 | } 341 | 342 | // option -texres 343 | { 344 | auto texture_option = TextureOption{}; 345 | auto [line, success] = ParseTextureOption("-texres 256", &texture_option); 346 | 347 | CHECK(success == true); 348 | CHECK(texture_option.texture_resolution == 256); 349 | } 350 | 351 | { 352 | auto texture_option = TextureOption{}; 353 | auto [line, success] = ParseTextureOption("-texres", &texture_option); 354 | 355 | CHECK(success == false); 356 | } 357 | 358 | { 359 | auto texture_option = TextureOption{}; 360 | auto [line, success] = ParseTextureOption("-texres true", &texture_option); 361 | 362 | CHECK(success == false); 363 | } 364 | 365 | { 366 | auto texture_option = TextureOption{}; 367 | auto [line, success] = ParseTextureOption("-texres 1.0", &texture_option); 368 | 369 | CHECK(success == false); 370 | } 371 | 372 | // option -clamp 373 | { 374 | auto texture_option = TextureOption{}; 375 | auto [line, success] = ParseTextureOption("-clamp off", &texture_option); 376 | 377 | CHECK(success == true); 378 | CHECK(texture_option.clamp == false); 379 | } 380 | 381 | { 382 | auto texture_option = TextureOption{}; 383 | auto [line, success] = ParseTextureOption("-clamp", &texture_option); 384 | 385 | CHECK(success == false); 386 | } 387 | 388 | { 389 | auto texture_option = TextureOption{}; 390 | auto [line, success] = ParseTextureOption("-clamp foo", &texture_option); 391 | 392 | CHECK(success == false); 393 | } 394 | 395 | { 396 | auto texture_option = TextureOption{}; 397 | auto [line, success] = ParseTextureOption("-clamp offten", &texture_option); 398 | 399 | CHECK(success == false); 400 | } 401 | 402 | // option -bm 403 | { 404 | auto texture_option = TextureOption{}; 405 | auto [line, success] = ParseTextureOption("-bm 0.5", &texture_option); 406 | 407 | CHECK(success == true); 408 | CHECK(texture_option.bump_multiplier == 0.5f); 409 | } 410 | 411 | { 412 | auto texture_option = TextureOption{}; 413 | auto [line, success] = ParseTextureOption("-bm", &texture_option); 414 | 415 | CHECK(success == false); 416 | } 417 | 418 | { 419 | auto texture_option = TextureOption{}; 420 | auto [line, success] = ParseTextureOption("-bm on", &texture_option); 421 | 422 | CHECK(success == false); 423 | } 424 | 425 | // option -imfchan 426 | { 427 | auto texture_option = TextureOption{}; 428 | auto [line, success] = ParseTextureOption("-imfchan r", &texture_option); 429 | 430 | CHECK(success == true); 431 | CHECK(texture_option.imfchan == 'r'); 432 | } 433 | 434 | { 435 | auto texture_option = TextureOption{}; 436 | auto [line, success] = ParseTextureOption("-imfchan g", &texture_option); 437 | 438 | CHECK(success == true); 439 | CHECK(texture_option.imfchan == 'g'); 440 | } 441 | 442 | { 443 | auto texture_option = TextureOption{}; 444 | auto [line, success] = ParseTextureOption("-imfchan b", &texture_option); 445 | 446 | CHECK(success == true); 447 | CHECK(texture_option.imfchan == 'b'); 448 | } 449 | 450 | { 451 | auto texture_option = TextureOption{}; 452 | auto [line, success] = ParseTextureOption("-imfchan m", &texture_option); 453 | 454 | CHECK(success == true); 455 | CHECK(texture_option.imfchan == 'm'); 456 | } 457 | 458 | { 459 | auto texture_option = TextureOption{}; 460 | auto [line, success] = ParseTextureOption("-imfchan l", &texture_option); 461 | 462 | CHECK(success == true); 463 | CHECK(texture_option.imfchan == 'l'); 464 | } 465 | 466 | { 467 | auto texture_option = TextureOption{}; 468 | auto [line, success] = ParseTextureOption("-imfchan z", &texture_option); 469 | 470 | CHECK(success == true); 471 | CHECK(texture_option.imfchan == 'z'); 472 | } 473 | 474 | { 475 | auto texture_option = TextureOption{}; 476 | auto [line, success] = ParseTextureOption("-imfchan", &texture_option); 477 | 478 | CHECK(success == false); 479 | } 480 | 481 | { 482 | auto texture_option = TextureOption{}; 483 | auto [line, success] = ParseTextureOption("-imfchan a", &texture_option); 484 | 485 | CHECK(success == false); 486 | } 487 | 488 | { 489 | auto texture_option = TextureOption{}; 490 | auto [line, success] = ParseTextureOption("-imfchan 1", &texture_option); 491 | 492 | CHECK(success == false); 493 | } 494 | 495 | { 496 | auto texture_option = TextureOption{}; 497 | auto [line, success] = ParseTextureOption("-imfchan -", &texture_option); 498 | 499 | CHECK(success == false); 500 | } 501 | 502 | // option -type 503 | { 504 | auto texture_option = TextureOption{}; 505 | auto [line, success] = ParseTextureOption("-type sphere", &texture_option); 506 | 507 | CHECK(success == true); 508 | CHECK(texture_option.type == TextureType::Sphere); 509 | } 510 | 511 | { 512 | auto texture_option = TextureOption{}; 513 | auto [line, success] = ParseTextureOption("-type cube_top", &texture_option); 514 | 515 | CHECK(success == true); 516 | CHECK(texture_option.type == TextureType::CubeTop); 517 | } 518 | 519 | { 520 | auto texture_option = TextureOption{}; 521 | auto [line, success] = ParseTextureOption("-type cube_bottom", &texture_option); 522 | 523 | CHECK(success == true); 524 | CHECK(texture_option.type == TextureType::CubeBottom); 525 | } 526 | 527 | { 528 | auto texture_option = TextureOption{}; 529 | auto [line, success] = ParseTextureOption("-type cube_front", &texture_option); 530 | 531 | CHECK(success == true); 532 | CHECK(texture_option.type == TextureType::CubeFront); 533 | } 534 | 535 | { 536 | auto texture_option = TextureOption{}; 537 | auto [line, success] = ParseTextureOption("-type cube_back", &texture_option); 538 | 539 | CHECK(success == true); 540 | CHECK(texture_option.type == TextureType::CubeBack); 541 | } 542 | 543 | { 544 | auto texture_option = TextureOption{}; 545 | auto [line, success] = ParseTextureOption("-type cube_left", &texture_option); 546 | 547 | CHECK(success == true); 548 | CHECK(texture_option.type == TextureType::CubeLeft); 549 | } 550 | 551 | { 552 | auto texture_option = TextureOption{}; 553 | auto [line, success] = ParseTextureOption("-type cube_right", &texture_option); 554 | 555 | CHECK(success == true); 556 | CHECK(texture_option.type == TextureType::CubeRight); 557 | } 558 | 559 | { 560 | auto texture_option = TextureOption{}; 561 | auto [line, success] = ParseTextureOption("-type", &texture_option); 562 | 563 | CHECK(success == false); 564 | } 565 | 566 | { 567 | auto texture_option = TextureOption{}; 568 | auto [line, success] = ParseTextureOption("-type cube", &texture_option); 569 | 570 | CHECK(success == false); 571 | } 572 | 573 | // two options 574 | { 575 | auto texture_option = TextureOption{}; 576 | auto [line, success] = ParseTextureOption("-blendu off -blendv off", &texture_option); 577 | 578 | CHECK(success == true); 579 | CHECK(texture_option.blendu == false); 580 | CHECK(texture_option.blendv == false); 581 | } 582 | 583 | // three options 584 | { 585 | auto texture_option = TextureOption{}; 586 | auto [line, success] = ParseTextureOption("-boost 2.0 -clamp off -imfchan z", &texture_option); 587 | 588 | CHECK(success == true); 589 | CHECK(texture_option.sharpness == 2.0f); 590 | CHECK(texture_option.clamp == false); 591 | CHECK(texture_option.imfchan == 'z'); 592 | } 593 | 594 | // no options 595 | { 596 | auto texture_option = TextureOption{}; 597 | auto [line, success] = ParseTextureOption("", &texture_option); 598 | 599 | CHECK(success == true); 600 | CHECK(line == ""); 601 | } 602 | 603 | { 604 | auto texture_option = TextureOption{}; 605 | auto [line, success] = ParseTextureOption("foo.jpg", &texture_option); 606 | 607 | CHECK(success == true); 608 | CHECK(line == "foo.jpg"); 609 | } 610 | 611 | { 612 | auto texture_option = TextureOption{}; 613 | auto [line, success] = ParseTextureOption("\t foo.jpg \t", &texture_option); 614 | 615 | CHECK(success == true); 616 | CHECK(line == "foo.jpg"); 617 | } 618 | 619 | // bad options 620 | { 621 | auto texture_option = TextureOption{}; 622 | auto [line, success] = ParseTextureOption("-", &texture_option); 623 | 624 | CHECK(success == false); 625 | } 626 | 627 | { 628 | auto texture_option = TextureOption{}; 629 | auto [line, success] = ParseTextureOption("-foo on", &texture_option); 630 | 631 | CHECK(success == false); 632 | } 633 | } 634 | 635 | TEST_CASE("rapidobj::detail::ParseMaterials") 636 | { 637 | { 638 | auto [map, materials, error] = ParseMaterials(test_material); 639 | 640 | CHECK(error.code == std::error_code()); 641 | 642 | auto& material = materials.front(); 643 | 644 | CHECK(material.name == "foo"); 645 | 646 | CHECK(material.ambient[0] == 0.0f); 647 | CHECK(material.ambient[1] == 1.0f); 648 | CHECK(material.ambient[2] == 2.0f); 649 | 650 | CHECK(material.diffuse[0] == 3.0f); 651 | CHECK(material.diffuse[1] == 4.0f); 652 | CHECK(material.diffuse[2] == 5.0f); 653 | 654 | CHECK(material.specular[0] == 6.0f); 655 | CHECK(material.specular[1] == 7.0f); 656 | CHECK(material.specular[2] == 8.0f); 657 | 658 | CHECK(material.transmittance[0] == 9.0f); 659 | CHECK(material.transmittance[1] == 10.0f); 660 | CHECK(material.transmittance[2] == 11.0f); 661 | 662 | CHECK(material.emission[0] == 12.0f); 663 | CHECK(material.emission[1] == 13.0f); 664 | CHECK(material.emission[2] == 14.0f); 665 | 666 | CHECK(material.shininess == 2.0f); 667 | CHECK(material.ior == 3.0f); 668 | CHECK(material.dissolve == 4.0f); 669 | CHECK(material.illum == 42); 670 | 671 | CHECK(material.ambient_texname == "ambient.jpg"); 672 | CHECK(material.ambient_texopt.texture_resolution == 1); 673 | CHECK(material.ambient_texopt.imfchan == 'm'); 674 | 675 | CHECK(material.diffuse_texname == "diffuse.jpg"); 676 | CHECK(material.diffuse_texopt.texture_resolution == 2); 677 | CHECK(material.diffuse_texopt.imfchan == 'm'); 678 | 679 | CHECK(material.specular_texname == "specular.jpg"); 680 | CHECK(material.specular_texopt.texture_resolution == 3); 681 | CHECK(material.specular_texopt.imfchan == 'm'); 682 | 683 | CHECK(material.specular_highlight_texname == "highlight.jpg"); 684 | CHECK(material.specular_highlight_texopt.texture_resolution == 4); 685 | CHECK(material.specular_highlight_texopt.imfchan == 'm'); 686 | 687 | CHECK(material.bump_texname == "bump.bmp"); 688 | CHECK(material.bump_texopt.texture_resolution == 5); 689 | CHECK(material.bump_texopt.imfchan == 'l'); 690 | 691 | CHECK(material.displacement_texname == "displacement.png"); 692 | CHECK(material.displacement_texopt.texture_resolution == 6); 693 | CHECK(material.displacement_texopt.imfchan == 'm'); 694 | 695 | CHECK(material.alpha_texname == "alpha.png"); 696 | CHECK(material.alpha_texopt.texture_resolution == 7); 697 | CHECK(material.alpha_texopt.imfchan == 'm'); 698 | 699 | CHECK(material.reflection_texname == "refl.jpg"); 700 | CHECK(material.reflection_texopt.texture_resolution == 8); 701 | CHECK(material.reflection_texopt.imfchan == 'm'); 702 | } 703 | 704 | { 705 | auto [map, materials, error] = ParseMaterials(pbr_material); 706 | 707 | CHECK(error.code == std::error_code()); 708 | 709 | auto& material = materials.front(); 710 | 711 | CHECK(material.name == "bar"); 712 | 713 | CHECK(material.roughness == 1.0f); 714 | CHECK(material.metallic == 2.0f); 715 | CHECK(material.sheen == 3.0f); 716 | CHECK(material.clearcoat_thickness == 4.0f); 717 | CHECK(material.clearcoat_roughness == 5.0f); 718 | CHECK(material.anisotropy == 6.0f); 719 | CHECK(material.anisotropy_rotation == 7.0f); 720 | 721 | CHECK(material.roughness_texname == "roughness.jpg"); 722 | CHECK(material.roughness_texopt.imfchan == 'm'); 723 | 724 | CHECK(material.metallic_texname == "metallic.jpg"); 725 | CHECK(material.metallic_texopt.imfchan == 'm'); 726 | 727 | CHECK(material.sheen_texname == "sheen.jpg"); 728 | CHECK(material.sheen_texopt.imfchan == 'm'); 729 | 730 | CHECK(material.emissive_texname == "emissive.hdr"); 731 | CHECK(material.emissive_texopt.imfchan == 'm'); 732 | 733 | CHECK(material.normal_texname == "normal.png"); 734 | CHECK(material.normal_texopt.imfchan == 'm'); 735 | } 736 | 737 | { 738 | auto [map, materials, error] = ParseMaterials(bad_material_1); 739 | 740 | CHECK(error.code == rapidobj_errc::MaterialParseError); 741 | CHECK(error.line == "$%!*"); 742 | CHECK(error.line_num == 6); 743 | } 744 | 745 | { 746 | auto [map, materials, error] = ParseMaterials(bad_material_2); 747 | 748 | CHECK(error.code == rapidobj_errc::MaterialParseError); 749 | CHECK(error.line == "illum 3 foo"); 750 | CHECK(error.line_num == 7); 751 | } 752 | } 753 | -------------------------------------------------------------------------------- /tests/unit-tests/src/test_mtllib.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | using namespace rapidobj; 7 | 8 | #ifndef TEST_DATA_DIR 9 | #error Cannot find test files; TEST_DATA_DIR is not defined 10 | #endif 11 | 12 | #define Q(x) #x 13 | #define QUOTE(x) Q(x) 14 | 15 | static const std::string objpath = QUOTE(TEST_DATA_DIR) "/mtllib/cube.obj"; 16 | 17 | static const std::string objpath_mtllib_missing = QUOTE(TEST_DATA_DIR) "/mtllib/cube_mtllib_missing.obj"; 18 | 19 | static constexpr auto purple_materials = R"( 20 | 21 | newmtl foo 22 | Kd 1 0 1 23 | 24 | newmtl bar 25 | Kd 1 0 1 26 | 27 | newmtl baz 28 | Kd 1 0 1 29 | 30 | )"; 31 | 32 | static const auto kSuccess = std::error_code(); 33 | 34 | static const auto kWhite = Float3{ 1, 1, 1 }; 35 | static const auto kYellow = Float3{ 1, 1, 0 }; 36 | static const auto kPurple = Float3{ 1, 0, 1 }; 37 | static const auto kRed = Float3{ 1, 0, 0 }; 38 | static const auto kGreen = Float3{ 0, 1, 0 }; 39 | static const auto kBlue = Float3{ 0, 0, 1 }; 40 | 41 | namespace fs = std::filesystem; 42 | 43 | static bool IDsOkay(const Array& material_ids) 44 | { 45 | if (6 == material_ids.size() && 0 == material_ids[0] && 0 == material_ids[1] && 1 == material_ids[2] && 46 | 1 == material_ids[3] && 2 == material_ids[4] && 2 == material_ids[5]) { 47 | return true; 48 | } 49 | return false; 50 | } 51 | 52 | TEST_CASE("rapidobj::ParseFile(MaterialLibrary)") 53 | { 54 | // ParseFile, implicit MaterialLibrary::Default 55 | { 56 | auto result = ParseFile(objpath); 57 | 58 | CHECK(kSuccess == result.error.code); 59 | 60 | CHECK(3 == result.materials.size()); 61 | 62 | CHECK("foo" == result.materials.front().name); 63 | CHECK(kWhite == result.materials.front().diffuse); 64 | 65 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 66 | } 67 | 68 | // ParseFile, explicit MaterialLibrary::Default 69 | { 70 | auto mtllib = MaterialLibrary::Default(); 71 | 72 | auto result = ParseFile(objpath, mtllib); 73 | 74 | CHECK(kSuccess == result.error.code); 75 | 76 | CHECK(3 == result.materials.size()); 77 | 78 | CHECK("foo" == result.materials.front().name); 79 | CHECK(kWhite == result.materials.front().diffuse); 80 | 81 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 82 | } 83 | 84 | // ParseFile, explicit MaterialLibrary::Default, loading optional 85 | { 86 | auto mtllib = MaterialLibrary::Default(Load::Optional); 87 | 88 | auto result = ParseFile(objpath, mtllib); 89 | 90 | CHECK(kSuccess == result.error.code); 91 | 92 | CHECK(3 == result.materials.size()); 93 | 94 | CHECK("foo" == result.materials.front().name); 95 | CHECK(kWhite == result.materials.front().diffuse); 96 | 97 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 98 | } 99 | 100 | // ParseFile, explicit MaterialLibrary::Default, loading mandatory 101 | { 102 | auto mtllib = MaterialLibrary::Default(Load::Mandatory); 103 | 104 | auto result = ParseFile(objpath, mtllib); 105 | 106 | CHECK(kSuccess == result.error.code); 107 | 108 | CHECK(3 == result.materials.size()); 109 | 110 | CHECK("foo" == result.materials.front().name); 111 | CHECK(kWhite == result.materials.front().diffuse); 112 | 113 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 114 | } 115 | 116 | // ParseFile, implicit MaterialLibrary::Default, loading mandatory, missing mtllib 117 | { 118 | auto result = ParseFile(objpath_mtllib_missing); 119 | 120 | CHECK(rapidobj_errc::MaterialFileError == result.error.code); 121 | 122 | CHECK(result.materials.empty()); 123 | } 124 | 125 | // ParseFile, explicit MaterialLibrary::Default, loading mandatory, missing mtllib 126 | { 127 | auto mtllib = MaterialLibrary::Default(); 128 | 129 | auto result = ParseFile(objpath_mtllib_missing, mtllib); 130 | 131 | CHECK(rapidobj_errc::MaterialFileError == result.error.code); 132 | 133 | CHECK(result.materials.empty()); 134 | } 135 | 136 | // ParseFile, explicit MaterialLibrary::Default, loading optional, missing mtllib 137 | { 138 | auto mtllib = MaterialLibrary::Default(Load::Optional); 139 | 140 | auto result = ParseFile(objpath_mtllib_missing, mtllib); 141 | 142 | CHECK(kSuccess == result.error.code); 143 | 144 | CHECK(result.materials.empty()); 145 | 146 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 147 | } 148 | 149 | // ParseFile, explicit MaterialLibrary::Default, loading mandatory, missing mtllib 150 | { 151 | auto mtllib = MaterialLibrary::Default(Load::Mandatory); 152 | 153 | auto result = ParseFile(objpath_mtllib_missing, mtllib); 154 | 155 | CHECK(rapidobj_errc::MaterialFileError == result.error.code); 156 | 157 | CHECK(result.materials.empty()); 158 | } 159 | 160 | // ParseFile, MaterialLibrary::SearchPath, loading mandatory 161 | { 162 | auto mtllib = MaterialLibrary::SearchPath("red"); 163 | 164 | auto result = ParseFile(objpath, mtllib); 165 | 166 | CHECK(kSuccess == result.error.code); 167 | 168 | CHECK(3 == result.materials.size()); 169 | 170 | CHECK("foo" == result.materials.front().name); 171 | CHECK(kRed == result.materials.front().diffuse); 172 | 173 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 174 | } 175 | 176 | // ParseFile, MaterialLibrary::SearchPath, loading optional 177 | { 178 | auto mtllib = MaterialLibrary::SearchPath("red", Load::Optional); 179 | 180 | auto result = ParseFile(objpath, mtllib); 181 | 182 | CHECK(kSuccess == result.error.code); 183 | 184 | CHECK(3 == result.materials.size()); 185 | 186 | CHECK("foo" == result.materials.front().name); 187 | CHECK(kRed == result.materials.front().diffuse); 188 | 189 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 190 | } 191 | 192 | // ParseFile, MaterialLibrary::SearchPath, loading mandatory, missing file 193 | { 194 | auto mtllib = MaterialLibrary::SearchPath("bad/path"); 195 | 196 | auto result = ParseFile(objpath, mtllib); 197 | 198 | CHECK(rapidobj_errc::MaterialFileError == result.error.code); 199 | 200 | CHECK(result.materials.empty()); 201 | } 202 | 203 | // ParseFile, MaterialLibrary::SearchPath, loading optional, missing file 204 | { 205 | auto mtllib = MaterialLibrary::SearchPath("bad/path", Load::Optional); 206 | 207 | auto result = ParseFile(objpath, mtllib); 208 | 209 | CHECK(kSuccess == result.error.code); 210 | 211 | CHECK(result.materials.empty()); 212 | 213 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 214 | } 215 | 216 | // ParseFile, MaterialLibrary::SearchPath, loading mandatory, material file override 217 | { 218 | auto mtllib = MaterialLibrary::SearchPath("yellow.mtl"); 219 | 220 | auto result = ParseFile(objpath, mtllib); 221 | 222 | CHECK(kSuccess == result.error.code); 223 | 224 | CHECK(3 == result.materials.size()); 225 | 226 | CHECK("foo" == result.materials.front().name); 227 | CHECK(kYellow == result.materials.front().diffuse); 228 | 229 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 230 | } 231 | 232 | // ParseFile, MaterialLibrary::SearchPath, loading optional, material file override 233 | { 234 | auto mtllib = MaterialLibrary::SearchPath("yellow.mtl", Load::Optional); 235 | 236 | auto result = ParseFile(objpath, mtllib); 237 | 238 | CHECK(kSuccess == result.error.code); 239 | 240 | CHECK(3 == result.materials.size()); 241 | 242 | CHECK("foo" == result.materials.front().name); 243 | CHECK(kYellow == result.materials.front().diffuse); 244 | 245 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 246 | } 247 | 248 | // ParseFile, MaterialLibrary::SearchPaths, loading mandatory 249 | { 250 | auto mtllib = MaterialLibrary::SearchPaths({ "blue", "green" }); 251 | 252 | auto result = ParseFile(objpath, mtllib); 253 | 254 | CHECK(kSuccess == result.error.code); 255 | 256 | CHECK(3 == result.materials.size()); 257 | 258 | CHECK("foo" == result.materials.front().name); 259 | CHECK(kBlue == result.materials.front().diffuse); 260 | 261 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 262 | } 263 | 264 | // ParseFile, MaterialLibrary::SearchPaths, loading optional 265 | { 266 | auto mtllib = MaterialLibrary::SearchPaths({ "blue", "green" }, Load::Optional); 267 | 268 | auto result = ParseFile(objpath, mtllib); 269 | 270 | CHECK(kSuccess == result.error.code); 271 | 272 | CHECK(3 == result.materials.size()); 273 | 274 | CHECK("foo" == result.materials.front().name); 275 | CHECK(kBlue == result.materials.front().diffuse); 276 | 277 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 278 | } 279 | 280 | // ParseFile, MaterialLibrary::SearchPaths, no paths 281 | { 282 | auto mtllib = MaterialLibrary::SearchPaths({}, Load::Optional); 283 | 284 | auto result = ParseFile(objpath, mtllib); 285 | 286 | CHECK(rapidobj_errc::InvalidArgumentsError == result.error.code); 287 | } 288 | 289 | // ParseFile, MaterialLibrary::SearchPaths, loading mandatory, mix of good and bad paths 290 | { 291 | auto mtllib = MaterialLibrary::SearchPaths({ "bad/path", "bad.mtl", ".", "red" }); 292 | 293 | auto result = ParseFile(objpath, mtllib); 294 | 295 | CHECK(kSuccess == result.error.code); 296 | 297 | CHECK(3 == result.materials.size()); 298 | 299 | CHECK("foo" == result.materials.front().name); 300 | CHECK(kWhite == result.materials.front().diffuse); 301 | 302 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 303 | } 304 | 305 | // ParseFile, MaterialLibrary::SearchPaths, loading optional, mix of good and bad paths 306 | { 307 | auto mtllib = MaterialLibrary::SearchPaths({ "bad/path", "bad.mtl", "green", "." }, Load::Optional); 308 | 309 | auto result = ParseFile(objpath, mtllib); 310 | 311 | CHECK(kSuccess == result.error.code); 312 | 313 | CHECK(3 == result.materials.size()); 314 | 315 | CHECK("foo" == result.materials.front().name); 316 | CHECK(kGreen == result.materials.front().diffuse); 317 | 318 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 319 | } 320 | 321 | // ParseFile, MaterialLibrary::SearchPaths, loading optional, mix of good and bad paths, material file override 322 | { 323 | auto mtllib = MaterialLibrary::SearchPaths({ "bad.mtl", "yellow.mtl", "red" }); 324 | 325 | auto result = ParseFile(objpath, mtllib); 326 | 327 | CHECK(kSuccess == result.error.code); 328 | 329 | CHECK(3 == result.materials.size()); 330 | 331 | CHECK("foo" == result.materials.front().name); 332 | CHECK(kYellow == result.materials.front().diffuse); 333 | 334 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 335 | } 336 | 337 | // ParseFile, MaterialLibrary::SearchPaths, loading mandatory, bad paths 338 | { 339 | auto mtllib = MaterialLibrary::SearchPaths({ "bad.mtl", "bad/path/1", "bad/path/2" }); 340 | 341 | auto result = ParseFile(objpath, mtllib); 342 | 343 | CHECK(rapidobj_errc::MaterialFileError == result.error.code); 344 | 345 | CHECK(result.materials.empty()); 346 | CHECK(result.shapes.empty()); 347 | } 348 | 349 | // ParseFile, MaterialLibrary::SearchPaths, loading optional, bad paths 350 | { 351 | auto mtllib = MaterialLibrary::SearchPaths({ "bad.mtl", "bad/path/1", "bad/path/2" }, Load::Optional); 352 | 353 | auto result = ParseFile(objpath, mtllib); 354 | 355 | CHECK(kSuccess == result.error.code); 356 | 357 | CHECK(result.materials.empty()); 358 | 359 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 360 | } 361 | 362 | // ParseFile, MaterialLibrary::String 363 | { 364 | auto mtllib = MaterialLibrary::String(purple_materials); 365 | 366 | auto result = ParseFile(objpath, mtllib); 367 | 368 | CHECK(kSuccess == result.error.code); 369 | 370 | CHECK(3 == result.materials.size()); 371 | 372 | CHECK("foo" == result.materials.front().name); 373 | CHECK(kPurple == result.materials.front().diffuse); 374 | 375 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 376 | } 377 | 378 | // ParseFile, MaterialLibrary::String, empty string 379 | { 380 | auto mtllib = MaterialLibrary::String(""); 381 | 382 | auto result = ParseFile(objpath, mtllib); 383 | 384 | CHECK(rapidobj_errc::MaterialNotFoundError == result.error.code); 385 | 386 | CHECK(result.materials.empty()); 387 | } 388 | 389 | // ParseFile, MaterialLibrary::Ignore 390 | { 391 | auto mtllib = MaterialLibrary::Ignore(); 392 | 393 | auto result = ParseFile(objpath, mtllib); 394 | 395 | CHECK(kSuccess == result.error.code); 396 | 397 | CHECK(result.materials.empty()); 398 | CHECK(result.shapes.front().mesh.material_ids.empty()); 399 | } 400 | } 401 | 402 | TEST_CASE("rapidobj::ParseStream(MaterialLibrary)") 403 | { 404 | // ParseStream, implicit MaterialLibrary::Default 405 | { 406 | auto stream = std::ifstream(objpath); 407 | 408 | auto result = ParseStream(stream); 409 | 410 | CHECK(kSuccess == result.error.code); 411 | 412 | CHECK(result.materials.empty()); 413 | 414 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 415 | } 416 | 417 | // ParseStream, explicit MaterialLibrary::Default 418 | { 419 | auto stream = std::ifstream(objpath); 420 | 421 | auto mtllib = MaterialLibrary::Default(); 422 | 423 | auto result = ParseStream(stream, mtllib); 424 | 425 | CHECK(kSuccess == result.error.code); 426 | 427 | CHECK(result.materials.empty()); 428 | 429 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 430 | } 431 | 432 | // ParseStream, implicit MaterialLibrary::Default, missing mtllib 433 | { 434 | auto stream = std::ifstream(objpath_mtllib_missing); 435 | 436 | auto result = ParseStream(stream); 437 | 438 | CHECK(kSuccess == result.error.code); 439 | 440 | CHECK(result.materials.empty()); 441 | 442 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 443 | } 444 | 445 | // ParseStream, explicit MaterialLibrary::Default, loading optional 446 | { 447 | auto stream = std::ifstream(objpath); 448 | 449 | auto mtllib = MaterialLibrary::Default(Load::Optional); 450 | 451 | auto result = ParseStream(stream, mtllib); 452 | 453 | CHECK(rapidobj_errc::InvalidArgumentsError == result.error.code); 454 | } 455 | 456 | // ParseStream, explicit MaterialLibrary::Default, loading mandatory 457 | { 458 | auto stream = std::ifstream(objpath); 459 | 460 | auto mtllib = MaterialLibrary::Default(Load::Mandatory); 461 | 462 | auto result = ParseStream(stream, mtllib); 463 | 464 | CHECK(rapidobj_errc::InvalidArgumentsError == result.error.code); 465 | } 466 | 467 | // ParseStream, MaterialLibrary::SearchPath, loading mandatory 468 | { 469 | auto stream = std::ifstream(objpath); 470 | 471 | auto path = fs::path(objpath).parent_path() / "red"; 472 | auto mtllib = MaterialLibrary::SearchPath(path); 473 | 474 | auto result = ParseStream(stream, mtllib); 475 | 476 | CHECK(kSuccess == result.error.code); 477 | 478 | CHECK(3 == result.materials.size()); 479 | 480 | CHECK("foo" == result.materials.front().name); 481 | CHECK(kRed == result.materials.front().diffuse); 482 | 483 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 484 | } 485 | 486 | // ParseStream, MaterialLibrary::SearchPath, loading optional 487 | { 488 | auto stream = std::ifstream(objpath); 489 | 490 | auto path = fs::path(objpath).parent_path() / "red"; 491 | auto mtllib = MaterialLibrary::SearchPath(path, Load::Optional); 492 | 493 | auto result = ParseStream(stream, mtllib); 494 | 495 | CHECK(kSuccess == result.error.code); 496 | 497 | CHECK(3 == result.materials.size()); 498 | 499 | CHECK("foo" == result.materials.front().name); 500 | CHECK(kRed == result.materials.front().diffuse); 501 | 502 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 503 | } 504 | 505 | // ParseStream, MaterialLibrary::SearchPath, loading mandatory, missing file 506 | { 507 | auto stream = std::ifstream(objpath); 508 | 509 | auto path = fs::path(objpath).parent_path() / "bad/path"; 510 | auto mtllib = MaterialLibrary::SearchPath(path); 511 | 512 | auto result = ParseStream(stream, mtllib); 513 | 514 | CHECK(rapidobj_errc::MaterialFileError == result.error.code); 515 | 516 | CHECK(result.materials.empty()); 517 | CHECK(result.shapes.empty()); 518 | } 519 | 520 | // ParseStream, MaterialLibrary::SearchPath, loading optional, missing file 521 | { 522 | auto stream = std::ifstream(objpath); 523 | 524 | auto path = fs::path(objpath).parent_path() / "bad/path"; 525 | auto mtllib = MaterialLibrary::SearchPath(path, Load::Optional); 526 | 527 | auto result = ParseStream(stream, mtllib); 528 | 529 | CHECK(kSuccess == result.error.code); 530 | 531 | CHECK(result.materials.empty()); 532 | 533 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 534 | } 535 | 536 | // ParseStream, MaterialLibrary::SearchPath, loading mandatory, relative file path 537 | { 538 | auto stream = std::ifstream(objpath); 539 | 540 | auto mtllib = MaterialLibrary::SearchPath("red"); 541 | 542 | auto result = ParseStream(stream, mtllib); 543 | 544 | CHECK(rapidobj_errc::MaterialRelativePathError == result.error.code); 545 | 546 | CHECK(result.materials.empty()); 547 | } 548 | 549 | // ParseStream, MaterialLibrary::SearchPath, loading mandatory, material file override 550 | { 551 | auto stream = std::ifstream(objpath); 552 | 553 | auto path = fs::path(objpath).parent_path() / "yellow.mtl"; 554 | auto mtllib = MaterialLibrary::SearchPath(path); 555 | 556 | auto result = ParseStream(stream, mtllib); 557 | 558 | CHECK(kSuccess == result.error.code); 559 | 560 | CHECK(3 == result.materials.size()); 561 | 562 | CHECK("foo" == result.materials.front().name); 563 | CHECK(kYellow == result.materials.front().diffuse); 564 | 565 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 566 | } 567 | 568 | // ParseStream, MaterialLibrary::SearchPath, loading optional, material file override 569 | { 570 | auto stream = std::ifstream(objpath); 571 | 572 | auto path = fs::path(objpath).parent_path() / "yellow.mtl"; 573 | auto mtllib = MaterialLibrary::SearchPath(path, Load::Optional); 574 | 575 | auto result = ParseStream(stream, mtllib); 576 | 577 | CHECK(kSuccess == result.error.code); 578 | 579 | CHECK(3 == result.materials.size()); 580 | 581 | CHECK("foo" == result.materials.front().name); 582 | CHECK(kYellow == result.materials.front().diffuse); 583 | 584 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 585 | } 586 | 587 | // ParseStream, MaterialLibrary::SearchPaths, loading mandatory 588 | { 589 | auto stream = std::ifstream(objpath); 590 | 591 | auto path_1 = fs::path(objpath).parent_path() / "blue"; 592 | auto path_2 = fs::path(objpath).parent_path() / "green"; 593 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2 }); 594 | 595 | auto result = ParseStream(stream, mtllib); 596 | 597 | CHECK(kSuccess == result.error.code); 598 | 599 | CHECK(3 == result.materials.size()); 600 | 601 | CHECK("foo" == result.materials.front().name); 602 | CHECK(kBlue == result.materials.front().diffuse); 603 | 604 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 605 | } 606 | 607 | // ParseStream, MaterialLibrary::SearchPaths, loading optional 608 | { 609 | auto stream = std::ifstream(objpath); 610 | 611 | auto path_1 = fs::path(objpath).parent_path() / "blue"; 612 | auto path_2 = fs::path(objpath).parent_path() / "green"; 613 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2 }, Load::Optional); 614 | 615 | auto result = ParseStream(stream, mtllib); 616 | 617 | CHECK(kSuccess == result.error.code); 618 | 619 | CHECK(3 == result.materials.size()); 620 | 621 | CHECK("foo" == result.materials.front().name); 622 | CHECK(kBlue == result.materials.front().diffuse); 623 | 624 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 625 | } 626 | 627 | // ParseStream, MaterialLibrary::SearchPaths, loading mandatory, mix of good and bad paths 628 | { 629 | auto stream = std::ifstream(objpath); 630 | 631 | auto path_1 = fs::path(objpath).parent_path() / "bad/path"; 632 | auto path_2 = fs::path(objpath).parent_path() / "cube.mtl"; 633 | auto path_3 = fs::path(objpath).parent_path() / "red"; 634 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2, path_3 }); 635 | 636 | auto result = ParseStream(stream, mtllib); 637 | 638 | CHECK(kSuccess == result.error.code); 639 | 640 | CHECK(3 == result.materials.size()); 641 | 642 | CHECK("foo" == result.materials.front().name); 643 | CHECK(kWhite == result.materials.front().diffuse); 644 | 645 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 646 | } 647 | 648 | // ParseStream, MaterialLibrary::SearchPaths, loading optional, mix of good and bad paths 649 | { 650 | auto stream = std::ifstream(objpath); 651 | 652 | auto path_1 = fs::path(objpath).parent_path() / "bad/path"; 653 | auto path_2 = fs::path(objpath).parent_path() / "green"; 654 | auto path_3 = fs::path(objpath).parent_path(); 655 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2, path_3 }, Load::Optional); 656 | 657 | auto result = ParseStream(stream, mtllib); 658 | 659 | CHECK(kSuccess == result.error.code); 660 | 661 | CHECK(3 == result.materials.size()); 662 | 663 | CHECK("foo" == result.materials.front().name); 664 | CHECK(kGreen == result.materials.front().diffuse); 665 | 666 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 667 | } 668 | 669 | // ParseStream, MaterialLibrary::SearchPaths, loading optional, mix of good and bad paths, material file override 670 | { 671 | auto stream = std::ifstream(objpath); 672 | 673 | auto path_1 = fs::path(objpath).parent_path() / "bad.mtl"; 674 | auto path_2 = fs::path(objpath).parent_path() / "yellow.mtl"; 675 | auto path_3 = fs::path(objpath).parent_path() / "red"; 676 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2, path_3 }); 677 | 678 | auto result = ParseStream(stream, mtllib); 679 | 680 | CHECK(kSuccess == result.error.code); 681 | 682 | CHECK(3 == result.materials.size()); 683 | 684 | CHECK("foo" == result.materials.front().name); 685 | CHECK(kYellow == result.materials.front().diffuse); 686 | 687 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 688 | } 689 | 690 | // ParseStream, MaterialLibrary::SearchPaths, loading mandatory, bad paths 691 | { 692 | auto stream = std::ifstream(objpath); 693 | 694 | auto path_1 = fs::path(objpath).parent_path() / "bad.mtl"; 695 | auto path_2 = fs::path(objpath).parent_path() / "bad/path"; 696 | auto path_3 = fs::path(objpath).parent_path() / "another/bad/path"; 697 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2, path_3 }); 698 | 699 | auto result = ParseStream(stream, mtllib); 700 | 701 | CHECK(rapidobj_errc::MaterialFileError == result.error.code); 702 | 703 | CHECK(result.materials.empty()); 704 | } 705 | 706 | // ParseStream, MaterialLibrary::SearchPaths, loading optional, bad paths 707 | { 708 | auto stream = std::ifstream(objpath); 709 | 710 | auto path_1 = fs::path(objpath).parent_path() / "bad.mtl"; 711 | auto path_2 = fs::path(objpath).parent_path() / "bad/path"; 712 | auto path_3 = fs::path(objpath).parent_path() / "another/bad/path"; 713 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2, path_3 }, Load::Optional); 714 | 715 | auto result = ParseStream(stream, mtllib); 716 | 717 | CHECK(kSuccess == result.error.code); 718 | 719 | CHECK(result.materials.empty()); 720 | 721 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 722 | } 723 | 724 | // ParseStream, MaterialLibrary::SearchPaths, loading manadatory, relative path 725 | { 726 | auto stream = std::ifstream(objpath); 727 | 728 | auto path_1 = fs::path(objpath).parent_path() / "blue"; 729 | auto path_2 = "relative/path"; 730 | auto path_3 = fs::path(objpath).parent_path() / "green"; 731 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2, path_3 }); 732 | 733 | auto result = ParseStream(stream, mtllib); 734 | 735 | CHECK(rapidobj_errc::MaterialRelativePathError == result.error.code); 736 | } 737 | 738 | // ParseStream, MaterialLibrary::SearchPaths, loading optional, relative path 739 | { 740 | auto stream = std::ifstream(objpath); 741 | 742 | auto path_1 = fs::path(objpath).parent_path() / "blue"; 743 | auto path_2 = "relative/path"; 744 | auto path_3 = fs::path(objpath).parent_path() / "green"; 745 | auto mtllib = MaterialLibrary::SearchPaths({ path_1, path_2, path_3 }, Load::Optional); 746 | 747 | auto result = ParseStream(stream, mtllib); 748 | 749 | CHECK(rapidobj_errc::MaterialRelativePathError == result.error.code); 750 | } 751 | 752 | // ParseStream, MaterialLibrary::SearchPaths, loading manadatory, no paths 753 | { 754 | auto stream = std::ifstream(objpath); 755 | 756 | auto mtllib = MaterialLibrary::SearchPaths({}); 757 | 758 | auto result = ParseStream(stream, mtllib); 759 | 760 | CHECK(rapidobj_errc::InvalidArgumentsError == result.error.code); 761 | } 762 | 763 | // ParseStream, MaterialLibrary::SearchPaths, loading optional, no paths 764 | { 765 | auto stream = std::ifstream(objpath); 766 | 767 | auto mtllib = MaterialLibrary::SearchPaths({}, Load::Optional); 768 | 769 | auto result = ParseStream(stream, mtllib); 770 | 771 | CHECK(rapidobj_errc::InvalidArgumentsError == result.error.code); 772 | } 773 | 774 | // ParseStream, MaterialLibrary::String 775 | { 776 | auto stream = std::ifstream(objpath); 777 | 778 | auto mtllib = MaterialLibrary::String(purple_materials); 779 | 780 | auto result = ParseStream(stream, mtllib); 781 | 782 | CHECK(kSuccess == result.error.code); 783 | 784 | CHECK(3 == result.materials.size()); 785 | 786 | CHECK("foo" == result.materials.front().name); 787 | CHECK(kPurple == result.materials.front().diffuse); 788 | 789 | CHECK(IDsOkay(result.shapes.front().mesh.material_ids)); 790 | } 791 | 792 | // ParseStream, MaterialLibrary::String, empty string 793 | { 794 | auto stream = std::ifstream(objpath); 795 | 796 | auto mtllib = MaterialLibrary::String(""); 797 | 798 | auto result = ParseStream(stream, mtllib); 799 | 800 | CHECK(rapidobj_errc::MaterialNotFoundError == result.error.code); 801 | 802 | CHECK(result.materials.empty()); 803 | } 804 | 805 | // ParseStream, MaterialLibrary::Ignore 806 | { 807 | auto stream = std::ifstream(objpath); 808 | 809 | auto mtllib = MaterialLibrary::Ignore(); 810 | 811 | auto result = ParseStream(stream, mtllib); 812 | 813 | CHECK(kSuccess == result.error.code); 814 | 815 | CHECK(result.materials.empty()); 816 | CHECK(result.shapes.front().mesh.material_ids.empty()); 817 | } 818 | } 819 | -------------------------------------------------------------------------------- /tests/unit-tests/src/test_parsing.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | bool operator==(const rapidobj::Index& lhs, const rapidobj::Index& rhs) noexcept 6 | { 7 | return lhs.position_index == rhs.position_index && lhs.texcoord_index == rhs.texcoord_index && 8 | lhs.normal_index == rhs.normal_index; 9 | } 10 | 11 | bool operator==(rapidobj::detail::OffsetFlags lhs, const rapidobj::detail::ApplyOffset& rhs) noexcept 12 | { 13 | return lhs == static_cast(rhs); 14 | } 15 | 16 | using namespace rapidobj; 17 | using namespace rapidobj::detail; 18 | 19 | static const auto kAllowPosition = static_cast(ApplyOffset::Position); 20 | static const auto kAllowPositionAndTexture = static_cast(ApplyOffset::Position | ApplyOffset::Texcoord); 21 | static const auto kAllowAll = static_cast(ApplyOffset::All); 22 | 23 | TEST_CASE("rapidobj::detail::ParseReals") 24 | { 25 | auto floats = std::array{}; 26 | 27 | { 28 | auto count = ParseReals("1.0", 3, floats.data()); 29 | 30 | CHECK(count == 1); 31 | CHECK(floats[0] == 1.0f); 32 | } 33 | 34 | { 35 | auto count = ParseReals(" 1.0", 3, floats.data()); 36 | 37 | CHECK(count == 1); 38 | CHECK(floats[0] == 1.0f); 39 | } 40 | 41 | { 42 | auto count = ParseReals("1.0 ", 3, floats.data()); 43 | 44 | CHECK(count == 1); 45 | CHECK(floats[0] == 1.0f); 46 | } 47 | 48 | { 49 | auto count = ParseReals(" 1.0 ", 3, floats.data()); 50 | 51 | CHECK(count == 1); 52 | CHECK(floats[0] == 1.0f); 53 | } 54 | 55 | { 56 | auto count = ParseReals("1.0 2.0", 3, floats.data()); 57 | 58 | CHECK(count == 2); 59 | CHECK(floats[0] == 1.0f); 60 | CHECK(floats[1] == 2.0f); 61 | } 62 | 63 | { 64 | auto count = ParseReals("1.0 2.0 3.0", 3, floats.data()); 65 | 66 | CHECK(count == 3); 67 | CHECK(floats[0] == 1.0f); 68 | CHECK(floats[1] == 2.0f); 69 | CHECK(floats[2] == 3.0f); 70 | } 71 | 72 | { 73 | auto count = ParseReals("1.0 2.0 3.0 4.0", 3, floats.data()); 74 | 75 | CHECK(count == 0); 76 | } 77 | 78 | { 79 | auto count = ParseReals("", 3, floats.data()); 80 | 81 | CHECK(count == 0); 82 | } 83 | 84 | { 85 | auto count = ParseReals(" ", 3, floats.data()); 86 | 87 | CHECK(count == 0); 88 | } 89 | 90 | { 91 | auto count = ParseReals("?", 3, floats.data()); 92 | 93 | CHECK(count == 0); 94 | } 95 | 96 | { 97 | auto count = ParseReals("?5.0", 3, floats.data()); 98 | 99 | CHECK(count == 0); 100 | } 101 | 102 | { 103 | auto count = ParseReals("5.0?", 3, floats.data()); 104 | 105 | CHECK(count == 0); 106 | } 107 | 108 | { 109 | auto count = ParseReals("5.?0", 3, floats.data()); 110 | 111 | CHECK(count == 0); 112 | } 113 | 114 | { 115 | auto count = ParseReals("5.0 ?", 3, floats.data()); 116 | 117 | CHECK(count == 0); 118 | } 119 | } 120 | 121 | TEST_CASE("rapidobj::detail::ParseFace") 122 | { 123 | auto indices = Buffer(); 124 | auto flags = Buffer(); 125 | 126 | SUBCASE("") 127 | { 128 | auto [count, ec] = ParseFace("1 2 3", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 129 | 130 | CHECK(count == 3); 131 | CHECK(ec == rapidobj_errc()); 132 | 133 | CHECK(indices.data()[0] == Index{ 0, -1, -1 }); 134 | CHECK(indices.data()[1] == Index{ 1, -1, -1 }); 135 | CHECK(indices.data()[2] == Index{ 2, -1, -1 }); 136 | 137 | CHECK(flags.data()[0] == ApplyOffset::None); 138 | CHECK(flags.data()[1] == ApplyOffset::None); 139 | CHECK(flags.data()[2] == ApplyOffset::None); 140 | } 141 | 142 | SUBCASE("") 143 | { 144 | auto [count, ec] = ParseFace("1 2 3 4", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 145 | 146 | CHECK(count == 4); 147 | CHECK(ec == rapidobj_errc()); 148 | 149 | CHECK(indices.data()[0] == Index{ 0, -1, -1 }); 150 | CHECK(indices.data()[1] == Index{ 1, -1, -1 }); 151 | CHECK(indices.data()[2] == Index{ 2, -1, -1 }); 152 | CHECK(indices.data()[3] == Index{ 3, -1, -1 }); 153 | 154 | CHECK(flags.data()[0] == ApplyOffset::None); 155 | CHECK(flags.data()[1] == ApplyOffset::None); 156 | CHECK(flags.data()[2] == ApplyOffset::None); 157 | CHECK(flags.data()[3] == ApplyOffset::None); 158 | } 159 | 160 | SUBCASE("") 161 | { 162 | auto [count, ec] = ParseFace( 163 | "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 " 164 | "39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 " 165 | "74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 " 166 | "107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 " 167 | "133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 " 168 | "159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 " 169 | "185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 " 170 | "211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 " 171 | "237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255", 172 | 0, 173 | 0, 174 | 0, 175 | 3, 176 | 255, 177 | kAllowAll, 178 | &indices, 179 | &flags); 180 | 181 | CHECK(count == 255); 182 | CHECK(ec == rapidobj_errc()); 183 | 184 | CHECK(indices.data()[0] == Index{ 0, -1, -1 }); 185 | CHECK(indices.data()[254] == Index{ 254, -1, -1 }); 186 | 187 | CHECK(flags.data()[0] == ApplyOffset::None); 188 | CHECK(flags.data()[254] == ApplyOffset::None); 189 | } 190 | 191 | SUBCASE("") 192 | { 193 | auto [count, ec] = ParseFace("-42 -43 -44", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 194 | 195 | CHECK(count == 3); 196 | CHECK(ec == rapidobj_errc()); 197 | 198 | CHECK(indices.data()[0] == Index{ -42, -1, -1 }); 199 | CHECK(indices.data()[1] == Index{ -43, -1, -1 }); 200 | CHECK(indices.data()[2] == Index{ -44, -1, -1 }); 201 | 202 | CHECK(flags.data()[0] == ApplyOffset::Position); 203 | CHECK(flags.data()[1] == ApplyOffset::Position); 204 | CHECK(flags.data()[2] == ApplyOffset::Position); 205 | } 206 | 207 | SUBCASE("") 208 | { 209 | auto [count, ec] = ParseFace("-42 -43 -44", 42, 0, 0, 3, 255, kAllowAll, &indices, &flags); 210 | 211 | CHECK(count == 3); 212 | CHECK(ec == rapidobj_errc()); 213 | 214 | CHECK(indices.data()[0] == Index{ 0, -1, -1 }); 215 | CHECK(indices.data()[1] == Index{ -1, -1, -1 }); 216 | CHECK(indices.data()[2] == Index{ -2, -1, -1 }); 217 | 218 | CHECK(flags.data()[0] == ApplyOffset::Position); 219 | CHECK(flags.data()[1] == ApplyOffset::Position); 220 | CHECK(flags.data()[2] == ApplyOffset::Position); 221 | } 222 | 223 | SUBCASE("") 224 | { 225 | auto [count, ec] = ParseFace(" 42 43\t44 ", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 226 | 227 | CHECK(count == 3); 228 | CHECK(ec == rapidobj_errc()); 229 | 230 | CHECK(indices.data()[0] == Index{ 41, -1, -1 }); 231 | CHECK(indices.data()[1] == Index{ 42, -1, -1 }); 232 | CHECK(indices.data()[2] == Index{ 43, -1, -1 }); 233 | 234 | CHECK(flags.data()[0] == ApplyOffset::None); 235 | CHECK(flags.data()[1] == ApplyOffset::None); 236 | CHECK(flags.data()[2] == ApplyOffset::None); 237 | } 238 | 239 | SUBCASE("") 240 | { 241 | auto [count, ec] = ParseFace("1\t1\t1", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 242 | 243 | CHECK(count == 3); 244 | CHECK(ec == rapidobj_errc()); 245 | 246 | CHECK(indices.data()[0] == Index{ 0, -1, -1 }); 247 | CHECK(indices.data()[1] == Index{ 0, -1, -1 }); 248 | CHECK(indices.data()[2] == Index{ 0, -1, -1 }); 249 | 250 | CHECK(flags.data()[0] == ApplyOffset::None); 251 | CHECK(flags.data()[1] == ApplyOffset::None); 252 | CHECK(flags.data()[2] == ApplyOffset::None); 253 | } 254 | 255 | SUBCASE("") 256 | { 257 | auto [count, ec] = ParseFace("42/43 44/45 46/47", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 258 | 259 | CHECK(count == 3); 260 | CHECK(ec == rapidobj_errc()); 261 | 262 | CHECK(indices.data()[0] == Index{ 41, 42, -1 }); 263 | CHECK(indices.data()[1] == Index{ 43, 44, -1 }); 264 | CHECK(indices.data()[2] == Index{ 45, 46, -1 }); 265 | 266 | CHECK(flags.data()[0] == ApplyOffset::None); 267 | CHECK(flags.data()[1] == ApplyOffset::None); 268 | CHECK(flags.data()[2] == ApplyOffset::None); 269 | } 270 | 271 | SUBCASE("") 272 | { 273 | auto [count, ec] = ParseFace("42/43 44/45 46/47 48/49", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 274 | 275 | CHECK(count == 4); 276 | CHECK(ec == rapidobj_errc()); 277 | 278 | CHECK(indices.data()[0] == Index{ 41, 42, -1 }); 279 | CHECK(indices.data()[1] == Index{ 43, 44, -1 }); 280 | CHECK(indices.data()[2] == Index{ 45, 46, -1 }); 281 | CHECK(indices.data()[3] == Index{ 47, 48, -1 }); 282 | 283 | CHECK(flags.data()[0] == ApplyOffset::None); 284 | CHECK(flags.data()[1] == ApplyOffset::None); 285 | CHECK(flags.data()[2] == ApplyOffset::None); 286 | CHECK(flags.data()[3] == ApplyOffset::None); 287 | } 288 | 289 | SUBCASE("") 290 | { 291 | auto [count, ec] = ParseFace("-42/42 1/1 2/2", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 292 | 293 | CHECK(count == 3); 294 | CHECK(ec == rapidobj_errc()); 295 | 296 | CHECK(indices.data()[0] == Index{ -42, 41, -1 }); 297 | CHECK(indices.data()[1] == Index{ 0, 0, -1 }); 298 | CHECK(indices.data()[2] == Index{ 1, 1, -1 }); 299 | 300 | CHECK(flags.data()[0] == ApplyOffset::Position); 301 | CHECK(flags.data()[1] == ApplyOffset::None); 302 | CHECK(flags.data()[2] == ApplyOffset::None); 303 | } 304 | 305 | SUBCASE("") 306 | { 307 | auto [count, ec] = ParseFace("42/-42 1/-1 2/-2", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 308 | 309 | CHECK(count == 3); 310 | CHECK(ec == rapidobj_errc()); 311 | 312 | CHECK(indices.data()[0] == Index{ 41, -42, -1 }); 313 | CHECK(indices.data()[1] == Index{ 0, -1, -1 }); 314 | CHECK(indices.data()[2] == Index{ 1, -2, -1 }); 315 | 316 | CHECK(flags.data()[0] == ApplyOffset::Texcoord); 317 | CHECK(flags.data()[1] == ApplyOffset::Texcoord); 318 | CHECK(flags.data()[2] == ApplyOffset::Texcoord); 319 | } 320 | 321 | SUBCASE("") 322 | { 323 | auto [count, ec] = ParseFace("-42/-42 -43/-43 -44/-44", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 324 | 325 | CHECK(count == 3); 326 | CHECK(ec == rapidobj_errc()); 327 | 328 | CHECK(indices.data()[0] == Index{ -42, -42, -1 }); 329 | CHECK(indices.data()[1] == Index{ -43, -43, -1 }); 330 | CHECK(indices.data()[2] == Index{ -44, -44, -1 }); 331 | 332 | CHECK(flags.data()[0] == (ApplyOffset::Position | ApplyOffset::Texcoord)); 333 | CHECK(flags.data()[1] == (ApplyOffset::Position | ApplyOffset::Texcoord)); 334 | CHECK(flags.data()[2] == (ApplyOffset::Position | ApplyOffset::Texcoord)); 335 | } 336 | 337 | SUBCASE("") 338 | { 339 | auto [count, ec] = ParseFace("1/2/3 4/5/6 7/8/9", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 340 | 341 | CHECK(count == 3); 342 | CHECK(ec == rapidobj_errc()); 343 | 344 | CHECK(indices.data()[0] == Index{ 0, 1, 2 }); 345 | CHECK(indices.data()[1] == Index{ 3, 4, 5 }); 346 | CHECK(indices.data()[2] == Index{ 6, 7, 8 }); 347 | 348 | CHECK(flags.data()[0] == ApplyOffset::None); 349 | CHECK(flags.data()[1] == ApplyOffset::None); 350 | CHECK(flags.data()[2] == ApplyOffset::None); 351 | } 352 | 353 | SUBCASE("") 354 | { 355 | auto [count, ec] = ParseFace("1/2/3 4/5/6 7/8/9 10/11/12", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 356 | 357 | CHECK(count == 4); 358 | CHECK(ec == rapidobj_errc()); 359 | 360 | CHECK(indices.data()[0] == Index{ 0, 1, 2 }); 361 | CHECK(indices.data()[1] == Index{ 3, 4, 5 }); 362 | CHECK(indices.data()[2] == Index{ 6, 7, 8 }); 363 | CHECK(indices.data()[3] == Index{ 9, 10, 11 }); 364 | 365 | CHECK(flags.data()[0] == ApplyOffset::None); 366 | CHECK(flags.data()[1] == ApplyOffset::None); 367 | CHECK(flags.data()[2] == ApplyOffset::None); 368 | CHECK(flags.data()[3] == ApplyOffset::None); 369 | } 370 | 371 | SUBCASE("") 372 | { 373 | auto [count, ec] = ParseFace("1/2/-1 3/4/-2 5/6/-3", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 374 | 375 | CHECK(count == 3); 376 | CHECK(ec == rapidobj_errc()); 377 | 378 | CHECK(indices.data()[0] == Index{ 0, 1, -1 }); 379 | CHECK(indices.data()[1] == Index{ 2, 3, -2 }); 380 | CHECK(indices.data()[2] == Index{ 4, 5, -3 }); 381 | 382 | CHECK(flags.data()[0] == ApplyOffset::Normal); 383 | CHECK(flags.data()[1] == ApplyOffset::Normal); 384 | CHECK(flags.data()[2] == ApplyOffset::Normal); 385 | } 386 | 387 | SUBCASE("") 388 | { 389 | auto [count, ec] = ParseFace("1/-2/3 4/-5/6 7/-8/9", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 390 | 391 | CHECK(count == 3); 392 | CHECK(ec == rapidobj_errc()); 393 | 394 | CHECK(indices.data()[0] == Index{ 0, -2, 2 }); 395 | CHECK(indices.data()[1] == Index{ 3, -5, 5 }); 396 | CHECK(indices.data()[2] == Index{ 6, -8, 8 }); 397 | 398 | CHECK(flags.data()[0] == ApplyOffset::Texcoord); 399 | CHECK(flags.data()[1] == ApplyOffset::Texcoord); 400 | CHECK(flags.data()[2] == ApplyOffset::Texcoord); 401 | } 402 | 403 | SUBCASE("") 404 | { 405 | auto [count, ec] = ParseFace("2/-3/-4 5/-6/-7 8/-9/-10", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 406 | 407 | CHECK(count == 3); 408 | CHECK(ec == rapidobj_errc()); 409 | 410 | CHECK(indices.data()[0] == Index{ 1, -3, -4 }); 411 | CHECK(indices.data()[1] == Index{ 4, -6, -7 }); 412 | CHECK(indices.data()[2] == Index{ 7, -9, -10 }); 413 | 414 | CHECK(flags.data()[0] == (ApplyOffset::Texcoord | ApplyOffset::Normal)); 415 | CHECK(flags.data()[1] == (ApplyOffset::Texcoord | ApplyOffset::Normal)); 416 | CHECK(flags.data()[2] == (ApplyOffset::Texcoord | ApplyOffset::Normal)); 417 | } 418 | 419 | SUBCASE("") 420 | { 421 | auto [count, ec] = ParseFace("\t-2/3/4\t-5/6/7\t-8/9/9\t", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 422 | 423 | CHECK(count == 3); 424 | CHECK(ec == rapidobj_errc()); 425 | 426 | CHECK(indices.data()[0] == Index{ -2, 2, 3 }); 427 | CHECK(indices.data()[1] == Index{ -5, 5, 6 }); 428 | CHECK(indices.data()[2] == Index{ -8, 8, 8 }); 429 | 430 | CHECK(flags.data()[0] == ApplyOffset::Position); 431 | CHECK(flags.data()[1] == ApplyOffset::Position); 432 | CHECK(flags.data()[2] == ApplyOffset::Position); 433 | } 434 | 435 | SUBCASE("") 436 | { 437 | auto [count, ec] = ParseFace("-2/3/-4 -5/6/-7 -8/9/-9", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 438 | 439 | CHECK(count == 3); 440 | CHECK(ec == rapidobj_errc()); 441 | 442 | CHECK(indices.data()[0] == Index{ -2, 2, -4 }); 443 | CHECK(indices.data()[1] == Index{ -5, 5, -7 }); 444 | CHECK(indices.data()[2] == Index{ -8, 8, -9 }); 445 | 446 | CHECK(flags.data()[0] == (ApplyOffset::Position | ApplyOffset::Normal)); 447 | CHECK(flags.data()[1] == (ApplyOffset::Position | ApplyOffset::Normal)); 448 | CHECK(flags.data()[2] == (ApplyOffset::Position | ApplyOffset::Normal)); 449 | } 450 | 451 | SUBCASE("") 452 | { 453 | auto [count, ec] = ParseFace("-2/-3/4 -5/-6/7 -8/-9/10", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 454 | 455 | CHECK(count == 3); 456 | CHECK(ec == rapidobj_errc()); 457 | 458 | CHECK(indices.data()[0] == Index{ -2, -3, 3 }); 459 | CHECK(indices.data()[1] == Index{ -5, -6, 6 }); 460 | CHECK(indices.data()[2] == Index{ -8, -9, 9 }); 461 | 462 | CHECK(flags.data()[0] == (ApplyOffset::Position | ApplyOffset::Texcoord)); 463 | CHECK(flags.data()[1] == (ApplyOffset::Position | ApplyOffset::Texcoord)); 464 | CHECK(flags.data()[2] == (ApplyOffset::Position | ApplyOffset::Texcoord)); 465 | } 466 | 467 | SUBCASE("") 468 | { 469 | auto [count, ec] = ParseFace("-2/-3/-4 -5/-6/-7 -8/-9/-1", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 470 | 471 | CHECK(count == 3); 472 | CHECK(ec == rapidobj_errc()); 473 | 474 | CHECK(indices.data()[0] == Index{ -2, -3, -4 }); 475 | CHECK(indices.data()[1] == Index{ -5, -6, -7 }); 476 | CHECK(indices.data()[2] == Index{ -8, -9, -1 }); 477 | 478 | CHECK(flags.data()[0] == (ApplyOffset::Position | ApplyOffset::Texcoord | ApplyOffset::Normal)); 479 | CHECK(flags.data()[1] == (ApplyOffset::Position | ApplyOffset::Texcoord | ApplyOffset::Normal)); 480 | CHECK(flags.data()[2] == (ApplyOffset::Position | ApplyOffset::Texcoord | ApplyOffset::Normal)); 481 | } 482 | 483 | SUBCASE("") 484 | { 485 | auto [count, ec] = 486 | ParseFace("-12/-13/-14 -15/-16/-17 -18/-19/-11", 10, 10, 10, 3, 255, kAllowAll, &indices, &flags); 487 | 488 | CHECK(count == 3); 489 | CHECK(ec == rapidobj_errc()); 490 | 491 | CHECK(indices.data()[0] == Index{ -2, -3, -4 }); 492 | CHECK(indices.data()[1] == Index{ -5, -6, -7 }); 493 | CHECK(indices.data()[2] == Index{ -8, -9, -1 }); 494 | 495 | CHECK(flags.data()[0] == (ApplyOffset::Position | ApplyOffset::Texcoord | ApplyOffset::Normal)); 496 | CHECK(flags.data()[1] == (ApplyOffset::Position | ApplyOffset::Texcoord | ApplyOffset::Normal)); 497 | CHECK(flags.data()[2] == (ApplyOffset::Position | ApplyOffset::Texcoord | ApplyOffset::Normal)); 498 | } 499 | 500 | SUBCASE("") 501 | { 502 | auto [count, ec] = ParseFace("2//3 4//5 6//7", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 503 | 504 | CHECK(count == 3); 505 | CHECK(ec == rapidobj_errc()); 506 | 507 | CHECK(indices.data()[0] == Index{ 1, -1, 2 }); 508 | CHECK(indices.data()[1] == Index{ 3, -1, 4 }); 509 | CHECK(indices.data()[2] == Index{ 5, -1, 6 }); 510 | 511 | CHECK(flags.data()[0] == ApplyOffset::None); 512 | CHECK(flags.data()[1] == ApplyOffset::None); 513 | CHECK(flags.data()[2] == ApplyOffset::None); 514 | } 515 | 516 | SUBCASE("") 517 | { 518 | auto [count, ec] = ParseFace("2//3 4//5 6//7 8//9", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 519 | 520 | CHECK(count == 4); 521 | CHECK(ec == rapidobj_errc()); 522 | 523 | CHECK(indices.data()[0] == Index{ 1, -1, 2 }); 524 | CHECK(indices.data()[1] == Index{ 3, -1, 4 }); 525 | CHECK(indices.data()[2] == Index{ 5, -1, 6 }); 526 | CHECK(indices.data()[3] == Index{ 7, -1, 8 }); 527 | 528 | CHECK(flags.data()[0] == ApplyOffset::None); 529 | CHECK(flags.data()[1] == ApplyOffset::None); 530 | CHECK(flags.data()[2] == ApplyOffset::None); 531 | CHECK(flags.data()[3] == ApplyOffset::None); 532 | } 533 | 534 | SUBCASE("") 535 | { 536 | auto [count, ec] = ParseFace("2//-3 4//-5 6//-7", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 537 | 538 | CHECK(count == 3); 539 | CHECK(ec == rapidobj_errc()); 540 | 541 | CHECK(indices.data()[0] == Index{ 1, -1, -3 }); 542 | CHECK(indices.data()[1] == Index{ 3, -1, -5 }); 543 | CHECK(indices.data()[2] == Index{ 5, -1, -7 }); 544 | 545 | CHECK(flags.data()[0] == ApplyOffset::Normal); 546 | CHECK(flags.data()[1] == ApplyOffset::Normal); 547 | CHECK(flags.data()[2] == ApplyOffset::Normal); 548 | } 549 | 550 | SUBCASE("") 551 | { 552 | auto [count, ec] = ParseFace("-2//3 -4//5 -6//7", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 553 | 554 | CHECK(count == 3); 555 | CHECK(ec == rapidobj_errc()); 556 | 557 | CHECK(indices.data()[0] == Index{ -2, -1, 2 }); 558 | CHECK(indices.data()[1] == Index{ -4, -1, 4 }); 559 | CHECK(indices.data()[2] == Index{ -6, -1, 6 }); 560 | 561 | CHECK(flags.data()[0] == ApplyOffset::Position); 562 | CHECK(flags.data()[1] == ApplyOffset::Position); 563 | CHECK(flags.data()[2] == ApplyOffset::Position); 564 | } 565 | 566 | SUBCASE("") 567 | { 568 | auto [count, ec] = ParseFace("-2//-3 -4//-5 -6//-7", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 569 | 570 | CHECK(count == 3); 571 | CHECK(ec == rapidobj_errc()); 572 | 573 | CHECK(indices.data()[0] == Index{ -2, -1, -3 }); 574 | CHECK(indices.data()[1] == Index{ -4, -1, -5 }); 575 | CHECK(indices.data()[2] == Index{ -6, -1, -7 }); 576 | 577 | CHECK(flags.data()[0] == (ApplyOffset::Position | ApplyOffset::Normal)); 578 | CHECK(flags.data()[1] == (ApplyOffset::Position | ApplyOffset::Normal)); 579 | CHECK(flags.data()[2] == (ApplyOffset::Position | ApplyOffset::Normal)); 580 | } 581 | 582 | SUBCASE("") 583 | { 584 | auto [count, ec] = ParseFace("", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 585 | 586 | CHECK(count == 0); 587 | CHECK(ec == rapidobj_errc::TooFewIndicesError); 588 | } 589 | 590 | SUBCASE("") 591 | { 592 | auto [count, ec] = ParseFace(" ", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 593 | 594 | CHECK(count == 0); 595 | CHECK(ec == rapidobj_errc::TooFewIndicesError); 596 | } 597 | 598 | SUBCASE("") 599 | { 600 | auto [count, ec] = ParseFace("1", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 601 | 602 | CHECK(count == 0); 603 | CHECK(ec == rapidobj_errc::TooFewIndicesError); 604 | } 605 | 606 | SUBCASE("") 607 | { 608 | auto [count, ec] = ParseFace("1 2", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 609 | 610 | CHECK(count == 0); 611 | CHECK(ec == rapidobj_errc::TooFewIndicesError); 612 | } 613 | 614 | SUBCASE("") 615 | { 616 | auto [count, ec] = ParseFace("1 2 3 0", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 617 | 618 | CHECK(count == 0); 619 | CHECK(ec == rapidobj_errc::IndexOutOfBoundsError); 620 | } 621 | 622 | SUBCASE("") 623 | { 624 | auto [count, ec] = ParseFace( 625 | "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 " 626 | "39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 " 627 | "74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 " 628 | "107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 " 629 | "133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 " 630 | "159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 " 631 | "185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 " 632 | "211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 " 633 | "237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256", 634 | 0, 635 | 0, 636 | 0, 637 | 3, 638 | 255, 639 | kAllowAll, 640 | &indices, 641 | &flags); 642 | 643 | CHECK(count == 0); 644 | CHECK(ec == rapidobj_errc::TooManyIndicesError); 645 | } 646 | 647 | SUBCASE("") 648 | { 649 | auto [count, ec] = ParseFace("/", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 650 | 651 | CHECK(count == 0); 652 | CHECK(ec == rapidobj_errc::ParseError); 653 | } 654 | 655 | SUBCASE("") 656 | { 657 | auto [count, ec] = ParseFace(" / ", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 658 | 659 | CHECK(count == 0); 660 | CHECK(ec == rapidobj_errc::ParseError); 661 | } 662 | 663 | SUBCASE("") 664 | { 665 | auto [count, ec] = ParseFace("//", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 666 | 667 | CHECK(count == 0); 668 | CHECK(ec == rapidobj_errc::ParseError); 669 | } 670 | 671 | SUBCASE("") 672 | { 673 | auto [count, ec] = ParseFace("/42", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 674 | 675 | CHECK(count == 0); 676 | CHECK(ec == rapidobj_errc::ParseError); 677 | } 678 | 679 | SUBCASE("") 680 | { 681 | auto [count, ec] = ParseFace("42/", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 682 | 683 | CHECK(count == 0); 684 | CHECK(ec == rapidobj_errc::ParseError); 685 | } 686 | 687 | SUBCASE("") 688 | { 689 | auto [count, ec] = ParseFace("42//", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 690 | 691 | CHECK(count == 0); 692 | CHECK(ec == rapidobj_errc::ParseError); 693 | } 694 | 695 | SUBCASE("") 696 | { 697 | auto [count, ec] = ParseFace("42/?/52", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 698 | 699 | CHECK(count == 0); 700 | CHECK(ec == rapidobj_errc::ParseError); 701 | } 702 | 703 | SUBCASE("") 704 | { 705 | auto [count, ec] = ParseFace("42///52", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 706 | 707 | CHECK(count == 0); 708 | CHECK(ec == rapidobj_errc::ParseError); 709 | } 710 | 711 | SUBCASE("") 712 | { 713 | auto [count, ec] = ParseFace("?", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 714 | 715 | CHECK(count == 0); 716 | CHECK(ec == rapidobj_errc::ParseError); 717 | } 718 | 719 | SUBCASE("") 720 | { 721 | auto [count, ec] = ParseFace("? 41", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 722 | 723 | CHECK(count == 0); 724 | CHECK(ec == rapidobj_errc::ParseError); 725 | } 726 | 727 | SUBCASE("") 728 | { 729 | auto [count, ec] = ParseFace("41?", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 730 | 731 | CHECK(count == 0); 732 | CHECK(ec == rapidobj_errc::ParseError); 733 | } 734 | 735 | SUBCASE("") 736 | { 737 | auto [count, ec] = ParseFace("4?1", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 738 | 739 | CHECK(count == 0); 740 | CHECK(ec == rapidobj_errc::ParseError); 741 | } 742 | 743 | SUBCASE("") 744 | { 745 | auto [count, ec] = ParseFace("41 ?", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 746 | 747 | CHECK(count == 0); 748 | CHECK(ec == rapidobj_errc::ParseError); 749 | } 750 | 751 | SUBCASE("") 752 | { 753 | auto [count, ec] = ParseFace("1 2 3?", 0, 0, 0, 3, 255, kAllowAll, &indices, &flags); 754 | 755 | CHECK(count == 0); 756 | CHECK(ec == rapidobj_errc::ParseError); 757 | } 758 | 759 | SUBCASE("") 760 | { 761 | auto [count, ec] = ParseFace("1 2", 0, 0, 0, 2, 255, kAllowPosition, &indices, &flags); 762 | 763 | CHECK(count == 2); 764 | CHECK(ec == rapidobj_errc()); 765 | } 766 | 767 | SUBCASE("") 768 | { 769 | auto [count, ec] = ParseFace("1/1 2/2", 0, 0, 0, 2, 255, kAllowPosition, &indices, &flags); 770 | 771 | CHECK(count == 0); 772 | CHECK(ec == rapidobj_errc::ParseError); 773 | } 774 | 775 | SUBCASE("") 776 | { 777 | auto [count, ec] = ParseFace("1/1/1 2/2/2", 0, 0, 0, 2, 255, kAllowPosition, &indices, &flags); 778 | 779 | CHECK(count == 0); 780 | CHECK(ec == rapidobj_errc::ParseError); 781 | } 782 | 783 | SUBCASE("") 784 | { 785 | auto [count, ec] = ParseFace("1//1 2//2", 0, 0, 0, 2, 255, kAllowPosition, &indices, &flags); 786 | 787 | CHECK(count == 0); 788 | CHECK(ec == rapidobj_errc::ParseError); 789 | } 790 | 791 | SUBCASE("") 792 | { 793 | auto [count, ec] = ParseFace("1 2", 0, 0, 0, 2, 255, kAllowPositionAndTexture, &indices, &flags); 794 | 795 | CHECK(count == 2); 796 | CHECK(ec == rapidobj_errc()); 797 | } 798 | 799 | SUBCASE("") 800 | { 801 | auto [count, ec] = ParseFace("1/1 2/2", 0, 0, 0, 2, 255, kAllowPositionAndTexture, &indices, &flags); 802 | 803 | CHECK(count == 2); 804 | CHECK(ec == rapidobj_errc()); 805 | } 806 | 807 | SUBCASE("") 808 | { 809 | auto [count, ec] = ParseFace("1/1/1 2/2/2", 0, 0, 0, 2, 255, kAllowPositionAndTexture, &indices, &flags); 810 | 811 | CHECK(count == 0); 812 | CHECK(ec == rapidobj_errc::ParseError); 813 | } 814 | 815 | SUBCASE("") 816 | { 817 | auto [count, ec] = ParseFace("1//1 2//2", 0, 0, 0, 2, 255, kAllowPositionAndTexture, &indices, &flags); 818 | 819 | CHECK(count == 0); 820 | CHECK(ec == rapidobj_errc::ParseError); 821 | } 822 | } 823 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | #-------------------------------------------------------------------- 4 | # Add tools 5 | #-------------------------------------------------------------------- 6 | 7 | add_subdirectory(bench) 8 | add_subdirectory(compare-test) 9 | add_subdirectory(make-test) 10 | add_subdirectory(serializer) 11 | -------------------------------------------------------------------------------- /tools/bench/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_executable(bench) 4 | 5 | target_sources(bench PRIVATE "src/bench.cpp") 6 | 7 | target_compile_features(bench PRIVATE cxx_std_17) 8 | 9 | target_link_libraries(bench PRIVATE cxxopts fast_obj rapidobj tinyobjloader) 10 | -------------------------------------------------------------------------------- /tools/bench/src/bench.cpp: -------------------------------------------------------------------------------- 1 | // clang-format off 2 | 3 | #if defined(__clang__) 4 | 5 | #define BEGIN_DISABLE_WARNINGS \ 6 | _Pragma("clang diagnostic push") \ 7 | _Pragma("clang diagnostic ignored \"-Wsign-conversion\"") \ 8 | _Pragma("clang diagnostic ignored \"-Wconversion\"") \ 9 | _Pragma("clang diagnostic ignored \"-Wsign-compare\"") \ 10 | _Pragma("clang diagnostic ignored \"-Wfloat-conversion\"") \ 11 | _Pragma("clang diagnostic ignored \"-Wunused-value\"") 12 | 13 | #define END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") 14 | 15 | #elif _MSC_VER 16 | 17 | #define BEGIN_DISABLE_WARNINGS \ 18 | __pragma(warning(push)) \ 19 | __pragma(warning(disable : 4244)) /* conversion from 'T1' to 'T2', possible loss of data */ \ 20 | __pragma(warning(disable : 4701)) /* potentially uninitialized local variable used */ \ 21 | __pragma(warning(disable : 4996)) /* fopen: This function or variable may be unsafe */ 22 | 23 | #define END_DISABLE_WARNINGS __pragma(warning(pop)) 24 | 25 | #elif defined(__GNUC__) 26 | 27 | #define BEGIN_DISABLE_WARNINGS \ 28 | _Pragma("GCC diagnostic push") \ 29 | _Pragma("GCC diagnostic ignored \"-Wsign-conversion\"") \ 30 | _Pragma("GCC diagnostic ignored \"-Wconversion\"") \ 31 | _Pragma("GCC diagnostic ignored \"-Wsign-compare\"") \ 32 | _Pragma("GCC diagnostic ignored \"-Wfloat-conversion\"") \ 33 | _Pragma("GCC diagnostic ignored \"-Wunused-value\"") \ 34 | _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") 35 | 36 | #define END_DISABLE_WARNINGS _Pragma("GCC diagnostic pop") 37 | 38 | #endif 39 | 40 | // clang-format on 41 | 42 | BEGIN_DISABLE_WARNINGS 43 | 44 | #define FAST_OBJ_IMPLEMENTATION 45 | #include "fast_obj.h" 46 | 47 | #define TINYOBJLOADER_IMPLEMENTATION 48 | #include "tiny_obj_loader.h" 49 | 50 | #include "cxxopts.hpp" 51 | 52 | END_DISABLE_WARNINGS 53 | 54 | #include "rapidobj/rapidobj.hpp" 55 | 56 | int Parse(int argc, char* argv[]) 57 | { 58 | using cxxopts::value; 59 | namespace fs = std::filesystem; 60 | 61 | auto options = cxxopts::Options(argv[0], "This tool is used to measure the time to load and parse an .obj file."); 62 | 63 | options.positional_help("input-file"); 64 | 65 | options.add_options()("p,parser", "Which parser to use.", value(), "fast|rapid|tiny"); 66 | options.add_options()("h,help", "Show help."); 67 | options.add_options()("input-file", "", value()); 68 | 69 | options.parse_positional("input-file"); 70 | 71 | auto result = options.parse(argc, argv); 72 | 73 | if (result.count("help")) { 74 | std::cout << options.help() << '\n'; 75 | return EXIT_SUCCESS; 76 | } 77 | 78 | if (0 == result.count("parser")) { 79 | std::cout << "Error: parser not specified\n"; 80 | return EXIT_FAILURE; 81 | } 82 | 83 | const auto parser = result["parser"].as(); 84 | 85 | if (parser != "fast" && parser != "rapid" && parser != "tiny") { 86 | std::cout << "Error: parser not recognized (must be: fast, rapid, or tiny)\n"; 87 | return EXIT_FAILURE; 88 | } 89 | 90 | if (0 == result.count("input-file")) { 91 | std::cout << "Error: input-file missing\n"; 92 | return EXIT_FAILURE; 93 | } 94 | 95 | const auto input_file = fs::path(result["input-file"].as()); 96 | 97 | if (!fs::exists(input_file)) { 98 | std::cout << "File " << input_file << " does not exist.\n"; 99 | return EXIT_FAILURE; 100 | } 101 | 102 | auto input_filepath = fs::canonical(input_file); 103 | 104 | if (!fs::is_regular_file(input_filepath)) { 105 | std::cout << "Path " << input_filepath << " is not a file.\n"; 106 | return EXIT_FAILURE; 107 | } 108 | 109 | auto duration = std::chrono::milliseconds(); 110 | 111 | if (parser == "fast") { 112 | auto t1 = std::chrono::system_clock::now(); 113 | 114 | auto* fast_obj = fast_obj_read(input_filepath.string().c_str()); 115 | 116 | if (!fast_obj) { 117 | std::cout << "fast_obj error\n"; 118 | return EXIT_FAILURE; 119 | } 120 | 121 | auto t2 = std::chrono::system_clock::now(); 122 | 123 | duration = std::chrono::duration_cast(t2 - t1); 124 | } 125 | 126 | if (parser == "rapid") { 127 | auto t1 = std::chrono::system_clock::now(); 128 | 129 | auto rapid_result = rapidobj::ParseFile(input_filepath); 130 | 131 | if (rapid_result.error) { 132 | std::cout << rapid_result.error.code.message() << "\n"; 133 | return EXIT_FAILURE; 134 | } 135 | 136 | auto t2 = std::chrono::system_clock::now(); 137 | 138 | duration = std::chrono::duration_cast(t2 - t1); 139 | } 140 | 141 | if (parser == "tiny") { 142 | auto tiny_reader = tinyobj::ObjReader(); 143 | auto tiny_config = tinyobj::ObjReaderConfig(); 144 | 145 | tiny_config.triangulate = false; 146 | 147 | auto t1 = std::chrono::system_clock::now(); 148 | 149 | bool success = tiny_reader.ParseFromFile(input_filepath.string(), tiny_config); 150 | 151 | if (!success) { 152 | std::cout << tiny_reader.Error() << "\n"; 153 | return EXIT_FAILURE; 154 | } 155 | 156 | auto t2 = std::chrono::system_clock::now(); 157 | 158 | duration = std::chrono::duration_cast(t2 - t1); 159 | } 160 | 161 | std::cout << "Parse time [ms]:" << duration.count() << "\n"; 162 | 163 | return EXIT_SUCCESS; 164 | } 165 | 166 | int main(int argc, char* argv[]) 167 | { 168 | auto rc = EXIT_SUCCESS; 169 | 170 | try { 171 | rc = Parse(argc, argv); 172 | } catch (const cxxopts::OptionException& e) { 173 | std::cout << "Error parsing options: " << e.what() << std::endl; 174 | return EXIT_FAILURE; 175 | } 176 | 177 | return rc; 178 | } 179 | -------------------------------------------------------------------------------- /tools/compare-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_library(compare-test STATIC) 4 | 5 | target_sources(compare-test PRIVATE 6 | "include/compare-test/compare-test.hpp" 7 | "src/compare-test.cpp" 8 | ) 9 | 10 | target_include_directories(compare-test PUBLIC include) 11 | 12 | target_compile_features(compare-test PRIVATE cxx_std_17) 13 | 14 | target_link_libraries(compare-test PUBLIC rapidobj PRIVATE serializer) 15 | -------------------------------------------------------------------------------- /tools/compare-test/include/compare-test/compare-test.hpp: -------------------------------------------------------------------------------- 1 | #include "rapidobj/rapidobj.hpp" 2 | 3 | namespace rapidobj::test { 4 | 5 | enum class Triangulate { No, Yes }; 6 | 7 | class ParseTest final { 8 | public: 9 | ParseTest(std::filesystem::path test_file); 10 | 11 | bool IsTestValid() const; 12 | 13 | bool ParseFile(Triangulate) const; 14 | 15 | bool ParseStream(Triangulate) const; 16 | 17 | private: 18 | std::filesystem::path m_input_file; 19 | rapidobj::Result m_reference; 20 | }; 21 | 22 | } // namespace rapidobj::test 23 | -------------------------------------------------------------------------------- /tools/compare-test/src/compare-test.cpp: -------------------------------------------------------------------------------- 1 | #include "compare-test/compare-test.hpp" 2 | 3 | #include "serializer/serializer.hpp" 4 | 5 | #include 6 | 7 | namespace rapidobj::test { 8 | 9 | struct Files { 10 | std::filesystem::path input; 11 | std::filesystem::path reference; 12 | }; 13 | 14 | std::optional GetFiles(const std::filesystem::path& test_file) 15 | { 16 | namespace fs = std::filesystem; 17 | 18 | if (!fs::exists(test_file) || !fs::is_regular_file(test_file)) { 19 | return std::nullopt; 20 | } 21 | 22 | auto file = std::ifstream(test_file); 23 | 24 | if (file.bad()) { 25 | return std::nullopt; 26 | } 27 | 28 | auto input = std::array(); 29 | 30 | for (size_t i = 0; i != 5; ++i) { 31 | file >> input[i]; 32 | } 33 | 34 | auto& [filename, filehash, separator, refname, refhash] = input; 35 | 36 | if (filename.empty() || refname.empty() || filehash.length() != 32 || refhash.length() != 32 || 37 | separator != std::string(32, '-')) { 38 | return std::nullopt; 39 | } 40 | 41 | auto filepath = test_file.parent_path() / filename; 42 | auto refpath = test_file.parent_path() / refname; 43 | 44 | if (!fs::exists(filepath) || !fs::exists(refpath) || !fs::is_regular_file(filepath) || 45 | !fs::is_regular_file(refpath)) { 46 | return std::nullopt; 47 | } 48 | 49 | if (rapidobj::serializer::ComputeFileHash(filepath) != filehash || 50 | rapidobj::serializer::ComputeFileHash(refpath) != refhash) { 51 | return std::nullopt; 52 | } 53 | 54 | return Files{ filepath, refpath }; 55 | } 56 | 57 | ParseTest::ParseTest(std::filesystem::path test_file) 58 | { 59 | if (auto files = GetFiles(test_file)) { 60 | m_input_file = files->input; 61 | m_reference = rapidobj::serializer::Deserialize(files->reference); 62 | } 63 | } 64 | 65 | bool ParseTest::IsTestValid() const 66 | { 67 | return !m_input_file.empty(); 68 | } 69 | 70 | bool ParseTest::ParseFile(Triangulate triangulate) const 71 | { 72 | auto result = rapidobj::ParseFile(m_input_file); 73 | 74 | if (triangulate == Triangulate::Yes) { 75 | rapidobj::Triangulate(result); 76 | } 77 | 78 | return result == m_reference; 79 | } 80 | 81 | bool ParseTest::ParseStream(Triangulate triangulate) const 82 | { 83 | auto stream = std::ifstream(m_input_file); 84 | 85 | auto mtllib = MaterialLibrary::SearchPath(m_input_file.parent_path()); 86 | 87 | auto result = rapidobj::ParseStream(stream, mtllib); 88 | 89 | if (triangulate == Triangulate::Yes) { 90 | rapidobj::Triangulate(result); 91 | } 92 | 93 | return result == m_reference; 94 | } 95 | 96 | } // namespace rapidobj::test 97 | -------------------------------------------------------------------------------- /tools/make-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_executable(make-test) 4 | 5 | target_sources(make-test PRIVATE "src/make-test.cpp") 6 | 7 | target_compile_features(make-test PRIVATE cxx_std_17) 8 | 9 | target_link_libraries(make-test PRIVATE cxxopts serializer tinyobjloader) 10 | -------------------------------------------------------------------------------- /tools/make-test/src/make-test.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | 3 | // clang-format off 4 | 5 | #define BEGIN_DISABLE_WARNINGS \ 6 | __pragma(warning(push)) \ 7 | __pragma(warning(disable:4244)) /* conversion from 'T1' to 'T2', possible loss of data */ \ 8 | __pragma(warning(disable:4701)) /* potentially uninitialized local variable used */ 9 | 10 | #define END_DISABLE_WARNINGS __pragma(warning(pop)) 11 | 12 | #elif defined(__clang__) 13 | 14 | #define BEGIN_DISABLE_WARNINGS 15 | 16 | #define END_DISABLE_WARNINGS 17 | 18 | #elif defined(__GNUC__) 19 | 20 | #define BEGIN_DISABLE_WARNINGS \ 21 | _Pragma("GCC diagnostic push") \ 22 | _Pragma("GCC diagnostic ignored \"-Wconversion\"") 23 | 24 | // clang-format on 25 | 26 | #define END_DISABLE_WARNINGS _Pragma("GCC diagnostic pop") 27 | 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | BEGIN_DISABLE_WARNINGS 38 | 39 | #include "cxxopts.hpp" 40 | 41 | END_DISABLE_WARNINGS 42 | 43 | #include "serializer/serializer.hpp" 44 | 45 | #define TINYOBJLOADER_IMPLEMENTATION 46 | #include "tiny_obj_loader.h" 47 | 48 | std::ostream& operator<<(std::ostream& os, const rapidobj::Index& index) 49 | { 50 | using std::to_string; 51 | 52 | const auto& [iposition, itex, inormal] = index; 53 | 54 | auto out = std::string("{ " + to_string(iposition) + ", " + to_string(itex) + ", " + to_string(inormal) + " }"); 55 | 56 | os << out; 57 | 58 | return os; 59 | } 60 | 61 | std::ostream& operator<<(std::ostream& os, const tinyobj::index_t& index) 62 | { 63 | using std::to_string; 64 | 65 | const auto& [ivertex, inormal, itex] = index; 66 | 67 | os << std::string("{ " + to_string(ivertex) + ", " + to_string(itex) + ", " + to_string(inormal) + " }"); 68 | 69 | return os; 70 | } 71 | 72 | std::ostream& operator<<(std::ostream& os, const rapidobj::Float3& f3) 73 | { 74 | using std::to_string; 75 | 76 | os << std::string("{ " + to_string(f3[0]) + ", " + to_string(f3[1]) + ", " + to_string(f3[2]) + " }"); 77 | 78 | return os; 79 | } 80 | 81 | std::ostream& operator<<(std::ostream& os, const float (&f3)[3]) 82 | { 83 | using std::to_string; 84 | 85 | os << std::string("{ " + to_string(f3[0]) + ", " + to_string(f3[1]) + ", " + to_string(f3[2]) + " }"); 86 | 87 | return os; 88 | } 89 | 90 | bool operator==(const rapidobj::Float3& lhs, const float (&rhs)[3]) 91 | { 92 | return lhs[0] == rhs[0] && lhs[1] == rhs[1] && lhs[2] == rhs[2]; 93 | } 94 | 95 | bool operator!=(const rapidobj::Float3& lhs, const float (&rhs)[3]) 96 | { 97 | return !operator==(lhs, rhs); 98 | } 99 | 100 | bool operator==(const rapidobj::TextureOption& lhs, const tinyobj::texture_option_t& rhs) 101 | { 102 | return static_cast(lhs.type) == static_cast(rhs.type) && lhs.sharpness == rhs.sharpness && 103 | lhs.brightness == rhs.brightness && lhs.contrast == rhs.contrast && lhs.origin_offset == rhs.origin_offset && 104 | lhs.scale == rhs.scale && lhs.turbulence == rhs.turbulence && 105 | lhs.texture_resolution == rhs.texture_resolution && lhs.clamp == rhs.clamp && lhs.imfchan == rhs.imfchan && 106 | lhs.blendu == rhs.blendu && lhs.blendv == rhs.blendv && lhs.bump_multiplier == rhs.bump_multiplier; 107 | } 108 | 109 | bool operator==(const rapidobj::Material& lhs, const tinyobj::material_t& rhs) 110 | { 111 | return lhs.name == rhs.name && lhs.ambient == rhs.ambient && lhs.diffuse == rhs.diffuse && 112 | lhs.specular == rhs.specular && lhs.transmittance == rhs.transmittance && lhs.emission == rhs.emission && 113 | lhs.shininess == rhs.shininess && lhs.ior == rhs.ior && lhs.dissolve == rhs.dissolve && 114 | lhs.illum == rhs.illum && lhs.ambient_texname == rhs.ambient_texname && 115 | lhs.diffuse_texname == rhs.diffuse_texname && lhs.specular_texname == rhs.specular_texname && 116 | lhs.specular_highlight_texname == rhs.specular_highlight_texname && lhs.bump_texname == rhs.bump_texname && 117 | lhs.displacement_texname == rhs.displacement_texname && lhs.alpha_texname == rhs.alpha_texname && 118 | lhs.reflection_texname == rhs.reflection_texname && lhs.ambient_texopt == rhs.ambient_texopt && 119 | lhs.diffuse_texopt == rhs.diffuse_texopt && lhs.specular_texopt == rhs.specular_texopt && 120 | lhs.specular_highlight_texopt == rhs.specular_highlight_texopt && lhs.bump_texopt == rhs.bump_texopt && 121 | lhs.displacement_texopt == rhs.displacement_texopt && lhs.alpha_texopt == rhs.alpha_texopt && 122 | lhs.reflection_texopt == rhs.reflection_texopt && lhs.roughness == rhs.roughness && 123 | lhs.metallic == rhs.metallic && lhs.sheen == rhs.sheen && 124 | lhs.clearcoat_thickness == rhs.clearcoat_thickness && lhs.clearcoat_roughness == rhs.clearcoat_roughness && 125 | lhs.anisotropy == rhs.anisotropy && lhs.anisotropy_rotation == rhs.anisotropy_rotation && 126 | lhs.roughness_texname == rhs.roughness_texname && lhs.metallic_texname == rhs.metallic_texname && 127 | lhs.sheen_texname == rhs.sheen_texname && lhs.emissive_texname == rhs.emissive_texname && 128 | lhs.normal_texname == rhs.normal_texname && lhs.roughness_texopt == rhs.roughness_texopt && 129 | lhs.metallic_texopt == rhs.metallic_texopt && lhs.sheen_texopt == rhs.sheen_texopt && 130 | lhs.emissive_texopt == rhs.emissive_texopt && lhs.normal_texopt == rhs.normal_texopt; 131 | } 132 | 133 | bool operator!=(const rapidobj::Material& lhs, const tinyobj::material_t& rhs) 134 | { 135 | return !operator==(lhs, rhs); 136 | } 137 | 138 | bool operator==(const rapidobj::Index lhs, const tinyobj::index_t rhs) 139 | { 140 | return lhs.position_index == rhs.vertex_index && lhs.texcoord_index == rhs.texcoord_index && 141 | lhs.normal_index == rhs.normal_index; 142 | } 143 | 144 | bool operator!=(const rapidobj::Index lhs, const tinyobj::index_t rhs) 145 | { 146 | return !operator==(lhs, rhs); 147 | } 148 | 149 | template 150 | bool operator==(const rapidobj::Array& lhs, const std::vector& rhs) 151 | { 152 | auto cmp = [](const R& lhs, const T& rhs) { return lhs == rhs; }; 153 | 154 | return (lhs.size() == rhs.size()) && std::equal(lhs.begin(), lhs.end(), rhs.begin(), cmp); 155 | } 156 | 157 | bool operator==(const rapidobj::Mesh& lhs, const tinyobj::mesh_t& rhs) 158 | { 159 | return lhs.indices == rhs.indices && lhs.num_face_vertices == rhs.num_face_vertices && 160 | lhs.material_ids == rhs.material_ids && lhs.smoothing_group_ids == rhs.smoothing_group_ids; 161 | } 162 | 163 | bool operator==(const rapidobj::Lines& lhs, const tinyobj::lines_t& rhs) 164 | { 165 | return lhs.indices == rhs.indices && lhs.num_line_vertices == rhs.num_line_vertices; 166 | } 167 | 168 | bool operator==(const rapidobj::Points& lhs, const tinyobj::points_t& rhs) 169 | { 170 | return lhs.indices == rhs.indices; 171 | } 172 | 173 | bool operator==(const rapidobj::Shape& lhs, const tinyobj::shape_t& rhs) 174 | { 175 | return lhs.name == rhs.name && lhs.mesh == rhs.mesh && lhs.lines == rhs.lines && lhs.points == rhs.points; 176 | } 177 | 178 | template 179 | bool operator==(const std::vector& lhs, const std::vector& rhs) 180 | { 181 | return (lhs.size() == rhs.size()) && 182 | std::equal(lhs.begin(), lhs.end(), rhs.begin(), [](const R& lhs, const T& rhs) { return lhs == rhs; }); 183 | } 184 | 185 | enum class Silent { False, True }; 186 | 187 | template 188 | void PrintDifferences(T lhs, size_t lhs_size, U rhs, size_t rhs_size) 189 | { 190 | static constexpr auto kMaxDiffs = 5; 191 | 192 | using std::cout; 193 | using std::setw; 194 | 195 | if (lhs_size != rhs_size) { 196 | cout << " Size: " << lhs_size << " (rapidobj) vs " << rhs_size << " (tinyobj)\n"; 197 | } 198 | 199 | cout << " {\n"; 200 | 201 | auto ndiffs = 0; 202 | auto size = std::min(lhs_size, rhs_size); 203 | auto num_element = std::string(); 204 | bool print_header = true; 205 | 206 | for (size_t i = 0; i != size; ++i) { 207 | if (*lhs != *rhs) { 208 | if (print_header) { 209 | cout << setw(9) << "index"; 210 | cout << setw(35) << "rapidobj"; 211 | cout << setw(35) << "tinyobj"; 212 | cout << "\n\n"; 213 | print_header = false; 214 | } 215 | cout << setw(9) << i; 216 | cout << setw(35); 217 | if constexpr (std::is_convertible_v) { 218 | cout << static_cast(*lhs); 219 | } else { 220 | cout << *lhs; 221 | } 222 | cout << setw(35); 223 | if constexpr (std::is_constructible_v) { 224 | cout << static_cast(*rhs); 225 | } else { 226 | cout << *rhs; 227 | } 228 | cout << '\n'; 229 | ++ndiffs; 230 | if (ndiffs >= kMaxDiffs) { 231 | cout << setw(9) << "..."; 232 | cout << setw(35) << "..."; 233 | cout << setw(35) << "..."; 234 | cout << '\n'; 235 | break; 236 | } 237 | } 238 | ++lhs, ++rhs; 239 | } 240 | cout << " }\n"; 241 | } 242 | 243 | template 244 | bool Compare(std::string_view intro, const T& lhs, const U& rhs, Silent silent) 245 | { 246 | if (silent == Silent::False) { 247 | std::cout << intro; 248 | } 249 | 250 | if (lhs == rhs) { 251 | if (silent == Silent::False) { 252 | std::cout << "Equal\n"; 253 | } 254 | return true; 255 | } 256 | 257 | if (silent == Silent::False) { 258 | std::cout << "Not Equal\n"; 259 | 260 | PrintDifferences(lhs.begin(), lhs.size(), rhs.begin(), rhs.size()); 261 | } 262 | 263 | return false; 264 | } 265 | 266 | bool CompareColors( 267 | std::string_view intro, 268 | const rapidobj::Array& lhs, 269 | const std::vector& rhs, 270 | Silent silent) 271 | { 272 | if (silent == Silent::False) { 273 | std::cout << intro; 274 | } 275 | 276 | bool all_white = rhs.end() == std::find_if(rhs.begin(), rhs.end(), [](float f) { return f != 1.0f; }); 277 | bool is_equal = (all_white && lhs.empty()) || (lhs == rhs); 278 | 279 | if (is_equal) { 280 | if (silent == Silent::False) { 281 | std::cout << "Equal\n"; 282 | } 283 | return true; 284 | } 285 | 286 | if (silent == Silent::False) { 287 | std::cout << "Not Equal\n"; 288 | 289 | PrintDifferences(lhs.begin(), lhs.size(), rhs.begin(), rhs.size()); 290 | } 291 | 292 | return false; 293 | } 294 | 295 | template <> 296 | void PrintDifferences( 297 | std::vector::const_iterator lhs, 298 | size_t lhs_size, 299 | std::vector::const_iterator rhs, 300 | size_t rhs_size) 301 | { 302 | std::cout << '\n'; 303 | 304 | if (lhs_size != rhs_size) { 305 | std::cout << "Different number of shapes: " << lhs_size << " (rapidobj) vs " << rhs_size << " (tinyobj)\n\n"; 306 | } 307 | 308 | auto num_shapes = std::min(lhs_size, rhs_size); 309 | 310 | for (size_t i = 0; i != num_shapes; ++i) { 311 | bool names_equal = lhs->name == rhs->name; 312 | bool mesh_indices_equal = Compare("", lhs->mesh.indices, rhs->mesh.indices, Silent::True); 313 | bool mesh_num_face_vertices_equal = 314 | Compare("", lhs->mesh.num_face_vertices, rhs->mesh.num_face_vertices, Silent::True); 315 | bool mesh_material_ids_equal = Compare("", lhs->mesh.material_ids, rhs->mesh.material_ids, Silent::True); 316 | bool mesh_smoothing_group_ids_equal = 317 | Compare("", lhs->mesh.smoothing_group_ids, rhs->mesh.smoothing_group_ids, Silent::True); 318 | bool lines_indices_equal = Compare("", lhs->lines.indices, rhs->lines.indices, Silent::True); 319 | bool lines_num_line_vertices = 320 | Compare("", lhs->lines.num_line_vertices, rhs->lines.num_line_vertices, Silent::True); 321 | bool points_indices_equal = Compare("", lhs->points.indices, rhs->points.indices, Silent::True); 322 | 323 | auto mesh_equal = mesh_indices_equal && mesh_num_face_vertices_equal && mesh_material_ids_equal && 324 | mesh_smoothing_group_ids_equal; 325 | 326 | auto lines_equal = lines_indices_equal && lines_num_line_vertices; 327 | 328 | auto points_equal = points_indices_equal; 329 | 330 | if (names_equal && mesh_equal && lines_equal && points_equal) { 331 | ++lhs, ++rhs; 332 | continue; 333 | } 334 | 335 | std::cout << "Shape " << (i + 1) << '\n'; 336 | 337 | if (names_equal) { 338 | std::cout << " Name: Equal '" << lhs->name << "'\n"; 339 | } else { 340 | std::cout << " Name: Not Equal '" << lhs->name << "' (rapidobj) vs '" << rhs->name 341 | << "' (tinyobj)\n"; 342 | } 343 | 344 | if (mesh_equal) { 345 | std::cout << " Mesh: Equal\n"; 346 | } else { 347 | std::cout << " Mesh: Not Equal\n"; 348 | 349 | Compare(" Indices: ", lhs->mesh.indices, rhs->mesh.indices, Silent::False); 350 | 351 | Compare( 352 | " Num Face Vertices: ", 353 | lhs->mesh.num_face_vertices, 354 | rhs->mesh.num_face_vertices, 355 | Silent::False); 356 | 357 | Compare(" Material Ids: ", lhs->mesh.material_ids, rhs->mesh.material_ids, Silent::False); 358 | 359 | Compare( 360 | " Smoothing Group Ids: ", 361 | lhs->mesh.smoothing_group_ids, 362 | rhs->mesh.smoothing_group_ids, 363 | Silent::False); 364 | } 365 | 366 | if (lines_equal) { 367 | std::cout << " Lines: Equal\n"; 368 | } else { 369 | std::cout << " Lines: Not Equal\n\n"; 370 | 371 | Compare(" Indices: ", lhs->lines.indices, rhs->lines.indices, Silent::False); 372 | 373 | Compare( 374 | " Num Line Vertices: ", 375 | lhs->lines.num_line_vertices, 376 | rhs->lines.num_line_vertices, 377 | Silent::False); 378 | } 379 | 380 | if (points_equal) { 381 | std::cout << " Points: Equal\n"; 382 | } else { 383 | std::cout << " Points: Not Equal\n\n"; 384 | 385 | Compare(" Indices: ", lhs->points.indices, rhs->points.indices, Silent::False); 386 | } 387 | 388 | ++lhs, ++rhs; 389 | 390 | std::cout << '\n'; 391 | } 392 | } 393 | 394 | template 395 | void PrintField(const std::string& field, const R& lhs, const T& rhs) 396 | { 397 | if (lhs != rhs) { 398 | std::cout << " " << field << '\n'; 399 | std::cout << std::setw(39) << lhs; 400 | std::cout << std::setw(39) << rhs; 401 | std::cout << '\n'; 402 | } 403 | } 404 | 405 | void PrintField(const std::string& field, const rapidobj::TextureOption& lhs, const tinyobj::texture_option_t& rhs) 406 | { 407 | PrintField(field + "type", static_cast(lhs.type), static_cast(rhs.type)); 408 | PrintField(field + "sharpness", lhs.sharpness, rhs.sharpness); 409 | PrintField(field + "brightness", lhs.brightness, rhs.brightness); 410 | PrintField(field + "contrast", lhs.contrast, rhs.contrast); 411 | PrintField(field + "origin_offset", lhs.origin_offset, rhs.origin_offset); 412 | PrintField(field + "scale", lhs.scale, rhs.scale); 413 | PrintField(field + "turbulence", lhs.turbulence, rhs.turbulence); 414 | PrintField(field + "texture_resolution", lhs.texture_resolution, rhs.texture_resolution); 415 | PrintField(field + "clamp", lhs.clamp, rhs.clamp); 416 | PrintField(field + "imfchan", lhs.imfchan, rhs.imfchan); 417 | PrintField(field + "blendu", lhs.blendu, rhs.blendu); 418 | PrintField(field + "blendv", lhs.blendv, rhs.blendv); 419 | PrintField(field + "bump_multiplier", lhs.bump_multiplier, rhs.bump_multiplier); 420 | } 421 | 422 | template <> 423 | void PrintDifferences( 424 | std::vector::const_iterator lhs, 425 | size_t lhs_size, 426 | std::vector::const_iterator rhs, 427 | size_t rhs_size) 428 | { 429 | using std::cout; 430 | using std::setw; 431 | 432 | cout << '\n'; 433 | 434 | if (lhs_size != rhs_size) { 435 | std::cout << "Different number of materials: " << lhs_size << " (rapidobj) vs " << rhs_size << " (tinyobj)\n\n"; 436 | } 437 | 438 | auto num_materials = std::min(lhs_size, rhs_size); 439 | 440 | for (size_t i = 0; i != num_materials; ++i) { 441 | if (*lhs != *rhs) { 442 | cout << " Material " << i << "\n"; 443 | 444 | cout << setw(39) << "rapidobj"; 445 | cout << setw(39) << "tinyobj"; 446 | cout << "\n"; 447 | 448 | cout << " name\n"; 449 | cout << setw(39) << lhs->name; 450 | cout << setw(39) << rhs->name; 451 | cout << '\n'; 452 | 453 | PrintField("ambient", lhs->ambient, rhs->ambient); 454 | PrintField("diffuse", lhs->diffuse, rhs->diffuse); 455 | PrintField("specular", lhs->specular, rhs->specular); 456 | PrintField("transmittance", lhs->transmittance, rhs->transmittance); 457 | PrintField("emission", lhs->emission, rhs->emission); 458 | PrintField("shininess", lhs->shininess, rhs->shininess); 459 | PrintField("ior", lhs->ior, rhs->ior); 460 | PrintField("dissolve", lhs->dissolve, rhs->dissolve); 461 | PrintField("illum", lhs->illum, rhs->illum); 462 | PrintField("ambient_texname", lhs->ambient_texname, rhs->ambient_texname); 463 | PrintField("diffuse_texname", lhs->diffuse_texname, rhs->diffuse_texname); 464 | PrintField("specular_texname", lhs->specular_texname, rhs->specular_texname); 465 | PrintField("specular_highlight_texname", lhs->specular_highlight_texname, rhs->specular_highlight_texname); 466 | PrintField("bump_texname", lhs->bump_texname, rhs->bump_texname); 467 | PrintField("displacement_texname", lhs->displacement_texname, rhs->displacement_texname); 468 | PrintField("alpha_texname", lhs->alpha_texname, rhs->alpha_texname); 469 | PrintField("reflection_texname", lhs->reflection_texname, rhs->reflection_texname); 470 | PrintField("ambient_texopt.", lhs->ambient_texopt, rhs->ambient_texopt); 471 | PrintField("diffuse_texopt.", lhs->diffuse_texopt, rhs->diffuse_texopt); 472 | PrintField("specular_texopt.", lhs->specular_texopt, rhs->specular_texopt); 473 | PrintField("specular_highlight_texopt.", lhs->specular_highlight_texopt, rhs->specular_highlight_texopt); 474 | PrintField("bump_texopt.", lhs->bump_texopt, rhs->bump_texopt); 475 | PrintField("displacement_texopt.", lhs->displacement_texopt, rhs->displacement_texopt); 476 | PrintField("alpha_texopt.", lhs->alpha_texopt, rhs->alpha_texopt); 477 | PrintField("reflection_texopt.", lhs->reflection_texopt, rhs->reflection_texopt); 478 | PrintField("roughness", lhs->roughness, rhs->roughness); 479 | PrintField("metallic", lhs->metallic, rhs->metallic); 480 | PrintField("sheen", lhs->sheen, rhs->sheen); 481 | PrintField("clearcoat_thickness", lhs->clearcoat_thickness, rhs->clearcoat_thickness); 482 | PrintField("clearcoat_roughness", lhs->clearcoat_roughness, rhs->clearcoat_roughness); 483 | PrintField("anisotropy", lhs->anisotropy, rhs->anisotropy); 484 | PrintField("anisotropy_rotation", lhs->anisotropy_rotation, rhs->anisotropy_rotation); 485 | PrintField("roughness_texname", lhs->roughness_texname, rhs->roughness_texname); 486 | PrintField("metallic_texname", lhs->metallic_texname, rhs->metallic_texname); 487 | PrintField("sheen_texname", lhs->sheen_texname, rhs->sheen_texname); 488 | PrintField("emissive_texname", lhs->emissive_texname, rhs->emissive_texname); 489 | PrintField("normal_texname", lhs->normal_texname, rhs->normal_texname); 490 | PrintField("roughness_texopt.", lhs->roughness_texopt, rhs->roughness_texopt); 491 | PrintField("metallic_texopt.", lhs->metallic_texopt, rhs->metallic_texopt); 492 | PrintField("sheen_texopt.", lhs->sheen_texopt, rhs->sheen_texopt); 493 | PrintField("emissive_texopt.", lhs->emissive_texopt, rhs->emissive_texopt); 494 | PrintField("normal_texopt.", lhs->normal_texopt, rhs->normal_texopt); 495 | } 496 | 497 | ++lhs, ++rhs; 498 | } 499 | } 500 | 501 | void GenerateReferenceFile(const rapidobj::Result& result, const std::filesystem::path& filepath) 502 | { 503 | rapidobj::serializer::Serialize(result, filepath); 504 | } 505 | 506 | void ReportError(const rapidobj::Error& error) 507 | { 508 | std::cout << error.code.message() << "\n"; 509 | if (!error.line.empty()) { 510 | std::cout << "On line " << error.line_num << ": \"" << error.line << "\"\n"; 511 | } 512 | } 513 | 514 | int Parse(int argc, char* argv[]) 515 | { 516 | using cxxopts::value; 517 | namespace fs = std::filesystem; 518 | 519 | auto options = cxxopts::Options(argv[0], "This tool is used to generate test files."); 520 | 521 | options.positional_help("input-file"); 522 | 523 | options.add_options()("o,out-dir", "Use to specify output folder for test files.", value(), "path"); 524 | options.add_options()("s,skip-xref", "Do not cross-reference parser results."); 525 | options.add_options()("x,only-xref", "Compare parser results but do not generate test files."); 526 | options.add_options()("t,triangulate", "Triangulate meshes after parsing."); 527 | options.add_options()("h,help", "Show help."); 528 | options.add_options()("input-file", "", value()); 529 | 530 | options.parse_positional("input-file"); 531 | 532 | auto result = options.parse(argc, argv); 533 | 534 | if (result.count("help")) { 535 | std::cout << options.help() << '\n'; 536 | return EXIT_SUCCESS; 537 | } 538 | 539 | if (0 == result.count("input-file")) { 540 | std::cout << "Error: input-file missing\n"; 541 | return EXIT_FAILURE; 542 | } 543 | 544 | const auto& out_dir = result.count("out-dir") ? fs::path(result["out-dir"].as()) : fs::path(); 545 | const bool skip_xref = result.count("skip-xref"); 546 | const bool only_xref = result.count("only-xref"); 547 | const bool triangulate = result.count("triangulate"); 548 | const auto& input_file = fs::path(result["input-file"].as()); 549 | 550 | if (!fs::exists(input_file)) { 551 | std::cout << "File " << input_file << " does not exist.\n"; 552 | return EXIT_FAILURE; 553 | } 554 | 555 | auto input_filepath = fs::canonical(input_file); 556 | 557 | if (!fs::is_regular_file(input_filepath)) { 558 | std::cout << "Path " << input_filepath << " is not a file.\n"; 559 | return EXIT_FAILURE; 560 | } 561 | 562 | auto out_dirpath = out_dir; 563 | 564 | if (out_dirpath.empty()) { 565 | out_dirpath = input_filepath.parent_path().empty() ? "." : input_filepath.parent_path(); 566 | } 567 | 568 | out_dirpath = fs::canonical(out_dirpath); 569 | 570 | if (!fs::exists(out_dirpath)) { 571 | std::cout << "Directory " << out_dirpath << " does not exist.\n"; 572 | return EXIT_FAILURE; 573 | } 574 | 575 | if (!fs::is_directory(out_dirpath)) { 576 | std::cout << "Path " << out_dirpath << " is not a directory.\n"; 577 | return EXIT_FAILURE; 578 | } 579 | 580 | if (skip_xref && only_xref) { 581 | std::cout << "Options --skip-xref and --only-xref are mutually exclusive.\n"; 582 | return EXIT_FAILURE; 583 | } 584 | 585 | if (!out_dir.empty() && only_xref) { 586 | std::cout << "Warning: --out-dir will have no effect because --only-xref is specified.\n"; 587 | } 588 | 589 | if (input_filepath.extension() != ".obj") { 590 | std::cout << "Warning: input file has non-standard extension " << input_filepath.extension() << ".\n"; 591 | } 592 | 593 | std::cout << "\n"; 594 | 595 | std::cout << "Processing file:\n" << input_filepath << "\n\n"; 596 | 597 | std::cout << "With rapidobj:\n"; 598 | 599 | auto rapid_result = rapidobj::Result{}; 600 | { 601 | auto t1 = std::chrono::system_clock::now(); 602 | 603 | rapid_result = rapidobj::ParseFile(input_filepath); 604 | 605 | if (rapid_result.error.code) { 606 | ReportError(rapid_result.error); 607 | return EXIT_FAILURE; 608 | } 609 | 610 | auto t2 = std::chrono::system_clock::now(); 611 | 612 | auto duration = std::chrono::duration_cast(t2 - t1); 613 | 614 | std::cout << "Parsed in: " << duration.count() << "ms\n"; 615 | } 616 | 617 | if (triangulate) { 618 | auto t1 = std::chrono::system_clock::now(); 619 | 620 | bool success = rapidobj::Triangulate(rapid_result); 621 | 622 | if (!success) { 623 | ReportError(rapid_result.error); 624 | return EXIT_FAILURE; 625 | } 626 | 627 | auto t2 = std::chrono::system_clock::now(); 628 | 629 | auto duration = std::chrono::duration_cast(t2 - t1); 630 | 631 | std::cout << "Triangulated in: " << duration.count() << "ms\n"; 632 | } 633 | 634 | std::cout << "\n"; 635 | 636 | if (skip_xref) { 637 | std::cout << "Skipping parse results comparison.\n"; 638 | } else { 639 | std::cout << "With tinyobj:\n"; 640 | 641 | auto tiny_reader = tinyobj::ObjReader(); 642 | auto tiny_config = tinyobj::ObjReaderConfig(); 643 | 644 | tiny_config.triangulate = triangulate; 645 | 646 | { 647 | auto t1 = std::chrono::system_clock::now(); 648 | 649 | bool success = tiny_reader.ParseFromFile(input_filepath.string(), tiny_config); 650 | 651 | if (!success) { 652 | std::cout << tiny_reader.Error() << "\n"; 653 | return EXIT_FAILURE; 654 | } 655 | 656 | auto t2 = std::chrono::system_clock::now(); 657 | 658 | auto duration = std::chrono::duration_cast(t2 - t1); 659 | 660 | if (triangulate) { 661 | std::cout << "Parsed and triangulated in: " << duration.count() << "ms\n"; 662 | } else { 663 | std::cout << "Parsed in: " << duration.count() << "ms\n"; 664 | } 665 | } 666 | 667 | std::cout << "\n"; 668 | 669 | std::cout.imbue(std::locale("")); 670 | 671 | const auto& tiny_vertices = tiny_reader.GetAttrib().vertices; 672 | const auto& tiny_texcoords = tiny_reader.GetAttrib().texcoords; 673 | const auto& tiny_normals = tiny_reader.GetAttrib().normals; 674 | const auto& tiny_colors = tiny_reader.GetAttrib().colors; 675 | const auto& tiny_shapes = tiny_reader.GetShapes(); 676 | const auto& tiny_materials = tiny_reader.GetMaterials(); 677 | 678 | bool positions_equal = 679 | Compare("Position attributes: ", rapid_result.attributes.positions, tiny_vertices, Silent::False); 680 | bool texcoords_equal = 681 | Compare("Texcoord attributes: ", rapid_result.attributes.texcoords, tiny_texcoords, Silent::False); 682 | bool normals_equal = 683 | Compare("Normal attributes: ", rapid_result.attributes.normals, tiny_normals, Silent::False); 684 | bool colors_equal = 685 | CompareColors("Color attributes: ", rapid_result.attributes.colors, tiny_colors, Silent::False); 686 | bool mesh_shapes_all_equal = 687 | Compare("Shapes: ", rapid_result.shapes, tiny_shapes, Silent::False); 688 | bool materials_all_equal = 689 | Compare("Materials: ", rapid_result.materials, tiny_materials, Silent::False); 690 | 691 | std::cout << "\n"; 692 | 693 | bool all_equal = positions_equal && texcoords_equal && normals_equal && colors_equal && mesh_shapes_all_equal && 694 | materials_all_equal; 695 | 696 | if (!all_equal) { 697 | if (only_xref) { 698 | std::cout << "Parse results did not compare equal." << '\n'; 699 | } else { 700 | std::cout << "Parse results did not compare equal. No test files have been generated." << '\n'; 701 | } 702 | return EXIT_FAILURE; 703 | } 704 | 705 | std::cout << "Parse results compared equal.\n"; 706 | } 707 | 708 | if (!only_xref) { 709 | auto input_filename = input_filepath.filename().string(); 710 | 711 | auto ref_filename = input_filename + (triangulate ? ".tri.ref" : ".ref"); 712 | auto ref_filepath = out_dirpath / ref_filename; 713 | 714 | auto testfile = std::ostringstream(); 715 | 716 | auto obj_filehash = rapidobj::serializer::ComputeFileHash(input_filepath); 717 | 718 | std::cout << "\n"; 719 | 720 | std::cout << "Generating reference file:\n" << ref_filepath << '\n'; 721 | 722 | GenerateReferenceFile(rapid_result, ref_filepath); 723 | 724 | auto ref_filehash = rapidobj::serializer::ComputeFileHash(ref_filepath); 725 | 726 | auto test_filename = input_filename + (triangulate ? ".tri.test" : ".test"); 727 | auto test_filepath = out_dirpath / test_filename; 728 | 729 | std::cout << "\n"; 730 | 731 | std::cout << "Generating test file:\n" << test_filepath << '\n'; 732 | 733 | auto ofs = std::ofstream(test_filepath); 734 | 735 | auto separator = std::string(32, '-'); 736 | 737 | ofs << input_filename << '\n'; 738 | ofs << obj_filehash << '\n'; 739 | ofs << separator << '\n'; 740 | ofs << ref_filename << '\n'; 741 | ofs << ref_filehash << '\n'; 742 | 743 | std::cout << "\n"; 744 | std::cout << "Done.\n"; 745 | } 746 | 747 | return EXIT_SUCCESS; 748 | } 749 | 750 | int main(int argc, char* argv[]) 751 | { 752 | auto rc = EXIT_SUCCESS; 753 | 754 | try { 755 | rc = Parse(argc, argv); 756 | } catch (const cxxopts::OptionException& e) { 757 | std::cout << "Error parsing options: " << e.what() << std::endl; 758 | return EXIT_FAILURE; 759 | } 760 | 761 | return rc; 762 | } -------------------------------------------------------------------------------- /tools/scripts/benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import matplotlib.pyplot as plt 3 | import argparse, os, subprocess, statistics, sys, time 4 | 5 | def measure(bench, parser, file, iterations, clear_cache): 6 | times = [] 7 | 8 | prefix = 'Parse time [ms]:' 9 | 10 | for i in range(int(iterations)): 11 | if clear_cache: 12 | try: 13 | command = ['sudo', 'echo', '3', '>', '/proc/sys/vm/drop_caches'] 14 | subprocess.run(command, stdout=subprocess.DEVNULL, check=True) 15 | except: 16 | sys.exit() 17 | 18 | if i == 0: 19 | match parser: 20 | case 'fast': 21 | print('\nParser: fast_obj') 22 | case 'rapid': 23 | print('\nParser: rapidobj') 24 | case 'tiny': 25 | print('\nParser: tinyobjloader') 26 | 27 | time.sleep(1) 28 | result = subprocess.run([bench, '--parser', parser, file], stdout=subprocess.PIPE).stdout.decode('ascii') 29 | lines = result.split('\n') 30 | t = None 31 | 32 | for line in lines: 33 | if line.startswith(prefix): 34 | line = line[len(prefix):] 35 | t = int(line) 36 | break 37 | 38 | if not t: 39 | sys.exit(f'unexpected output from bench executable: {result!r}') 40 | 41 | times.append(t) 42 | print(f'Iteration {i+1}: {t}ms') 43 | time.sleep(2) 44 | 45 | tmin = min(times) 46 | tavg = statistics.mean(times) 47 | tstdev = statistics.stdev(times) if i > 0 else 0 48 | 49 | print(f'Minimum: {tmin}ms') 50 | print(f'Average: {tavg}ms') 51 | print(f'Standard Deviation: {tstdev}') 52 | 53 | return [tavg, tstdev] 54 | 55 | def parse_args(): 56 | arg_parser = argparse.ArgumentParser() 57 | arg_parser.add_argument("file", help="path to .obj input file") 58 | arg_parser.add_argument("-b", "--bench", help="path to bench executable", metavar="path") 59 | arg_parser.add_argument("-o", "--out", help="output path", metavar="path") 60 | arg_parser.add_argument("-i", "--iterations", help="number of iterations per run", metavar="num") 61 | arg_parser.add_argument("-c", "--clear-cache", action='store_true', help="clear page cache before each run") 62 | arg_parser.add_argument("-s", "--style", choices=['default', 'light', 'dark', 'both'], default="default", metavar="choice", help="choice is one of: default, light, dark, both") 63 | args = arg_parser.parse_args() 64 | 65 | bench = args.bench or '.' 66 | iterations = args.iterations or 10 67 | file = args.file 68 | out = args.out 69 | clear_cache = args.clear_cache 70 | style = args.style 71 | 72 | if os.path.isdir(bench): 73 | if os.name == 'nt': 74 | bench = os.path.join(bench, 'bench.exe') 75 | else: 76 | bench = os.path.join(bench, 'bench') 77 | 78 | bench = os.path.abspath(bench) 79 | 80 | if not os.path.exists(bench): 81 | sys.exit(f'bench executable {bench!r} does not exist') 82 | 83 | if not os.path.exists(file): 84 | sys.exit(f'{file!r} does not exist') 85 | 86 | if not os.path.isfile(file): 87 | sys.exit(f'{file!r} is not a file') 88 | 89 | outdir = os.path.dirname(file) 90 | outfile = os.path.splitext(os.path.basename(file))[0] + '.svg' 91 | 92 | if out: 93 | if os.path.splitext(out)[1]: 94 | outfile = os.path.basename(out) 95 | parent = os.path.dirname(out) 96 | if parent: 97 | outdir = parent 98 | else: 99 | outdir = out 100 | 101 | if not os.path.exists(outdir): 102 | sys.exit(f'output directory {outdir!r} does not exist') 103 | 104 | outfile = os.path.join(outdir, outfile) 105 | 106 | return [bench, iterations, file, outfile, clear_cache, style] 107 | 108 | def configure_plot(parsers, times, errors, file, style): 109 | 110 | green = [0.18, 0.72, 0.47, 1.0] 111 | orange = [1.0, 0.5, 0.0, 1.0] 112 | blue = [0.12, 0.47, 0.71, 1.0] 113 | 114 | plt.rcdefaults() 115 | plt.style.use(style) 116 | 117 | fig, ax = plt.subplots() 118 | 119 | fig.set_figwidth(6) 120 | fig.set_figheight(3) 121 | 122 | bars = ax.barh(parsers, times, xerr = errors, align='center', color=[green, orange, blue]) 123 | 124 | ax.set_xlabel('time in ms (lower is better)') 125 | ax.invert_yaxis() 126 | ax.set_title(file) 127 | ax.bar_label(bars, padding=5) 128 | 129 | def plot(filename, outfile, parsers, times, errors, style): 130 | 131 | [_dir, name] = os.path.split(filename) 132 | [dir, file] = os.path.split(outfile) 133 | [file, _ext] = os.path.splitext(file) 134 | 135 | default = style == 'default' 136 | light = style == 'light' or style == 'both' 137 | dark = style == 'dark' or style == 'both' 138 | 139 | if default: 140 | configure_plot(parsers, times, errors, name, 'bmh') 141 | print(f'Saving figure {outfile!r}') 142 | plt.savefig(fname=outfile, transparent=False, bbox_inches='tight') 143 | 144 | if light: 145 | configure_plot(parsers, times, errors, name, 'default') 146 | outfile = os.path.join(dir, file + '-light.svg') 147 | print(f'Saving figure {outfile!r}') 148 | plt.savefig(fname=outfile, transparent=True, bbox_inches='tight') 149 | 150 | if dark: 151 | configure_plot(parsers, times, errors, name, 'dark_background') 152 | outfile = os.path.join(dir, file + '-dark.svg') 153 | print(f'Saving figure {outfile!r}') 154 | plt.savefig(fname=outfile, transparent=True, bbox_inches='tight') 155 | 156 | def main(): 157 | 158 | bench, iterations, file, outfile, clear_cache, style = parse_args() 159 | 160 | if not clear_cache: 161 | print("Warming page cache...") 162 | with open(file) as f: 163 | data = f.readlines() 164 | 165 | fast_min, fast_stdev = measure(bench, 'fast', file, iterations, clear_cache) 166 | 167 | time.sleep(3) 168 | 169 | rapid_min, rapid_stdev = measure(bench, 'rapid', file, iterations, clear_cache) 170 | 171 | time.sleep(3) 172 | 173 | tiny_min, tiny_stdev = measure(bench, 'tiny', file, iterations, clear_cache) 174 | 175 | filename = os.path.basename(file) 176 | parsers = ('fast_obj', 'rapidobj', 'tinyobjloader') 177 | times = [fast_min, rapid_min, tiny_min] 178 | errors = [fast_stdev, rapid_stdev, tiny_stdev] 179 | 180 | plot(filename, outfile, parsers, times, errors, style) 181 | 182 | print('Done') 183 | 184 | main() 185 | -------------------------------------------------------------------------------- /tools/serializer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | add_library(serializer STATIC) 4 | 5 | target_sources(serializer PRIVATE 6 | "include/serializer/serializer.hpp" 7 | "src/serializer.cpp" 8 | ) 9 | 10 | target_include_directories(serializer PUBLIC include) 11 | 12 | target_compile_features(serializer PRIVATE cxx_std_17) 13 | 14 | target_link_libraries(serializer PUBLIC rapidobj PRIVATE cereal xxhash) 15 | -------------------------------------------------------------------------------- /tools/serializer/include/serializer/serializer.hpp: -------------------------------------------------------------------------------- 1 | #include "rapidobj/rapidobj.hpp" 2 | 3 | namespace rapidobj { 4 | 5 | // 6 | // Helper functions used to compare rapidobj::Result for equality 7 | // 8 | inline bool operator==(const Index& lhs, const Index& rhs) 9 | { 10 | return lhs.position_index == rhs.position_index && lhs.texcoord_index == rhs.texcoord_index && 11 | lhs.normal_index == rhs.normal_index; 12 | } 13 | 14 | inline bool operator!=(const Index& lhs, const Index& rhs) 15 | { 16 | return !operator==(lhs, rhs); 17 | } 18 | 19 | inline bool operator==(const Mesh& lhs, const Mesh& rhs) 20 | { 21 | return lhs.indices == rhs.indices && lhs.num_face_vertices == rhs.num_face_vertices && 22 | lhs.material_ids == rhs.material_ids && lhs.smoothing_group_ids == rhs.smoothing_group_ids; 23 | } 24 | 25 | inline bool operator!=(const Mesh& lhs, const Mesh& rhs) 26 | { 27 | return !operator==(lhs, rhs); 28 | } 29 | 30 | inline bool operator==(const Shape& lhs, const Shape& rhs) 31 | { 32 | return lhs.name == rhs.name && lhs.mesh == rhs.mesh; 33 | } 34 | 35 | inline bool operator!=(const Shape& lhs, const Shape& rhs) 36 | { 37 | return !operator==(lhs, rhs); 38 | } 39 | 40 | inline bool operator==(const Attributes& lhs, const Attributes& rhs) 41 | { 42 | return lhs.positions == rhs.positions && lhs.texcoords == rhs.texcoords && lhs.normals == rhs.normals; 43 | } 44 | 45 | inline bool operator==(const Result& lhs, const Result& rhs) 46 | { 47 | return lhs.attributes == rhs.attributes && lhs.shapes == rhs.shapes; 48 | } 49 | 50 | inline bool operator!=(const Result& lhs, const Result& rhs) 51 | { 52 | return !operator==(lhs, rhs); 53 | } 54 | 55 | namespace serializer { 56 | 57 | void Serialize(const rapidobj::Result& result, const std::filesystem::path& filepath); 58 | 59 | Result Deserialize(const std::filesystem::path& filepath); 60 | 61 | std::string ComputeFileHash(std::filesystem::path filepath); 62 | 63 | } // namespace serializer 64 | 65 | } // namespace rapidobj 66 | -------------------------------------------------------------------------------- /tools/serializer/src/serializer.cpp: -------------------------------------------------------------------------------- 1 | // clang-format off 2 | 3 | #if defined(__clang__) 4 | 5 | #define BEGIN_DISABLE_WARNINGS \ 6 | _Pragma("clang diagnostic push") \ 7 | _Pragma("clang diagnostic ignored \"-Wlanguage-extension-token\"") \ 8 | 9 | #define END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") 10 | 11 | #elif _MSC_VER 12 | 13 | #define BEGIN_DISABLE_WARNINGS 14 | 15 | #define END_DISABLE_WARNINGS 16 | 17 | #elif defined(__GNUC__) 18 | 19 | #define BEGIN_DISABLE_WARNINGS 20 | 21 | #define END_DISABLE_WARNINGS 22 | 23 | #endif 24 | 25 | // clang-format on 26 | 27 | #include "serializer/serializer.hpp" 28 | 29 | #include "cereal/archives/portable_binary.hpp" 30 | #include "cereal/types/array.hpp" 31 | #include "cereal/types/string.hpp" 32 | #include "cereal/types/vector.hpp" 33 | 34 | BEGIN_DISABLE_WARNINGS 35 | 36 | #define XXH_STATIC_LINKING_ONLY 37 | #define XXH_IMPLEMENTATION 38 | #include "xxhash.h" 39 | 40 | END_DISABLE_WARNINGS 41 | 42 | static std::string ToBase16(XXH128_hash_t hash) 43 | { 44 | std::stringstream ss; 45 | 46 | ss.width(16); 47 | ss.fill('0'); 48 | 49 | ss << std::uppercase << std::hex << hash.high64 << hash.low64; 50 | 51 | return ss.str(); 52 | } 53 | 54 | namespace rapidobj { 55 | 56 | // 57 | // Helper functions used to serialize rapidobj::Result 58 | // 59 | template 60 | void save(Archive& archive, const Array& arr) 61 | { 62 | archive(static_cast(arr.size()), cereal::binary_data(arr.data(), sizeof(T) * arr.size())); 63 | } 64 | 65 | template 66 | void load(Archive& archive, Array& arr) 67 | { 68 | auto size = uint64_t{}; 69 | archive(size); 70 | auto temp = Array(size); 71 | archive(cereal::binary_data(temp.data(), sizeof(T) * temp.size())); 72 | arr = std::move(temp); 73 | } 74 | 75 | template 76 | void serialize(Archive& archive, Index& index) 77 | { 78 | archive(index.position_index, index.texcoord_index, index.normal_index); 79 | } 80 | 81 | template 82 | void serialize(Archive& archive, Mesh& mesh) 83 | { 84 | archive(mesh.indices, mesh.num_face_vertices, mesh.material_ids, mesh.smoothing_group_ids); 85 | } 86 | 87 | template 88 | void serialize(Archive& archive, Shape& shape) 89 | { 90 | archive(shape.name, shape.mesh); 91 | } 92 | 93 | template 94 | void serialize(Archive& archive, Attributes& attributes) 95 | { 96 | archive(attributes.positions, attributes.texcoords, attributes.normals); 97 | } 98 | 99 | template 100 | void serialize(Archive& archive, TextureOption& texture_option) 101 | { 102 | archive( 103 | texture_option.type, 104 | texture_option.sharpness, 105 | texture_option.brightness, 106 | texture_option.contrast, 107 | texture_option.origin_offset, 108 | texture_option.scale, 109 | texture_option.turbulence, 110 | texture_option.texture_resolution, 111 | texture_option.clamp, 112 | texture_option.imfchan, 113 | texture_option.blendu, 114 | texture_option.blendv, 115 | texture_option.bump_multiplier); 116 | } 117 | 118 | template 119 | void serialize(Archive& archive, Material& material) 120 | { 121 | archive( 122 | material.name, 123 | material.ambient, 124 | material.diffuse, 125 | material.specular, 126 | material.transmittance, 127 | material.emission, 128 | material.shininess, 129 | material.ior, 130 | material.dissolve, 131 | material.illum, 132 | material.ambient_texname, 133 | material.diffuse_texname, 134 | material.specular_texname, 135 | material.specular_highlight_texname, 136 | material.bump_texname, 137 | material.displacement_texname, 138 | material.alpha_texname, 139 | material.reflection_texname, 140 | material.ambient_texopt, 141 | material.diffuse_texopt, 142 | material.specular_texopt, 143 | material.specular_highlight_texopt, 144 | material.bump_texopt, 145 | material.displacement_texopt, 146 | material.alpha_texopt, 147 | material.reflection_texopt, 148 | material.roughness, 149 | material.metallic, 150 | material.sheen, 151 | material.clearcoat_thickness, 152 | material.clearcoat_roughness, 153 | material.anisotropy, 154 | material.anisotropy_rotation, 155 | material.roughness_texname, 156 | material.metallic_texname, 157 | material.sheen_texname, 158 | material.emissive_texname, 159 | material.normal_texname, 160 | material.roughness_texopt, 161 | material.metallic_texopt, 162 | material.sheen_texopt, 163 | material.emissive_texopt, 164 | material.normal_texopt); 165 | } 166 | 167 | template 168 | void serialize(Archive& archive, Error& error) 169 | { 170 | archive(error.code.value(), error.line, error.line_num); 171 | } 172 | 173 | template 174 | void serialize(Archive& archive, Result& result) 175 | { 176 | archive(result.attributes, result.shapes, result.materials, result.error); 177 | } 178 | 179 | namespace serializer { 180 | 181 | std::string ComputeFileHash(std::filesystem::path filepath) 182 | { 183 | auto oss = std::ostringstream(); 184 | auto ifs = std::ifstream(filepath, std::ios::binary); 185 | 186 | if (ifs.bad()) { 187 | throw std::runtime_error("Failed to open file."); 188 | } 189 | 190 | oss << ifs.rdbuf(); 191 | 192 | auto data = oss.str(); 193 | 194 | return ToBase16(XXH3_128bits(data.data(), data.size())); 195 | } 196 | 197 | void Serialize(const rapidobj::Result& result, const std::filesystem::path& filepath) 198 | { 199 | auto ofs = std::ofstream(filepath, std::ios::binary); 200 | auto archive = cereal::PortableBinaryOutputArchive(ofs); 201 | 202 | archive(result); 203 | } 204 | 205 | Result Deserialize(const std::filesystem::path& filepath) 206 | { 207 | auto ifs = std::ifstream(filepath, std::ios::binary); 208 | auto archive = cereal::PortableBinaryInputArchive(ifs); 209 | auto result = Result{}; 210 | 211 | archive(result); 212 | 213 | return result; 214 | } 215 | 216 | } // namespace serializer 217 | 218 | } // namespace rapidobj 219 | --------------------------------------------------------------------------------