├── .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 |
8 |
--------------------------------------------------------------------------------
/data/images/docs/logo-light.svg:
--------------------------------------------------------------------------------
1 |
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 | # 
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 | # 
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 | # 
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 | # 
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 |
--------------------------------------------------------------------------------