11#include " assert.hpp"
22#include " gltf_model.hpp"
33#include " texture.hpp"
4- #include " vector_set.hpp"
54
65#include < cgltf.h>
76#include < fmt/core.h>
1211#include < stb_image.h>
1312
1413#include < algorithm>
14+ #include < cstddef>
1515#include < cstdint>
1616#include < cstring>
1717#include < filesystem>
@@ -70,6 +70,195 @@ void traverseNodeHierarchy(
7070 }
7171 }
7272}
73+
74+ Texture textureFromGltfImage (const cgltf_image* const image, const fs::path& gltfPath)
75+ {
76+ if (image->buffer_view )
77+ {
78+ const auto pixelData = [image]() -> std::span<const std::uint8_t > {
79+ // NOTE: cgltf_buffer_view_data used as a reference for this function
80+ const cgltf_buffer_view* const bufferView = image->buffer_view ;
81+ NLRS_ASSERT (bufferView != nullptr );
82+ const std::size_t byteLength = bufferView->size ;
83+
84+ // See cgltf_buffer_view::data comment, overrides buffer->data if
85+ // present
86+ if (bufferView->data != nullptr )
87+ {
88+ const std::uint8_t * const bufferPtr =
89+ static_cast <const std::uint8_t *>(bufferView->data );
90+ return std::span (bufferPtr, byteLength);
91+ }
92+
93+ NLRS_ASSERT (bufferView->buffer != nullptr );
94+ const std::size_t bufferOffset = bufferView->offset ;
95+ const std::uint8_t * const bufferPtr =
96+ static_cast <const std::uint8_t *>(bufferView->buffer ->data );
97+
98+ return std::span (bufferPtr + bufferOffset, byteLength);
99+ }();
100+ return Texture::fromMemory (pixelData);
101+ }
102+ else
103+ {
104+ NLRS_ASSERT (image->uri );
105+ const fs::path imagePath = gltfPath.parent_path () / image->uri ;
106+ if (!fs::exists (imagePath))
107+ {
108+ throw std::runtime_error (
109+ fmt::format (" The image {} does not exist." , imagePath.string ()));
110+ }
111+
112+ const std::size_t fileSize = static_cast <std::size_t >(fs::file_size (imagePath));
113+ NLRS_ASSERT (fileSize > 0 );
114+ std::ifstream file (imagePath, std::ios::binary);
115+ NLRS_ASSERT (file.is_open ());
116+
117+ std::vector<std::uint8_t > fileData (fileSize, 0 );
118+ file.read (reinterpret_cast <char *>(fileData.data ()), fileSize);
119+ return Texture::fromMemory (std::span<const std::uint8_t >(fileData));
120+ }
121+ }
122+
123+ std::uint32_t fnv1a (const void * data, std::size_t size)
124+ {
125+ const std::uint32_t fnvPrime = 16777619 ;
126+ const std::uint32_t fnvOffsetBasis = 2166136261 ;
127+ const unsigned char * ptr = static_cast <const unsigned char *>(data);
128+ std::uint32_t hash = fnvOffsetBasis;
129+ for (std::size_t i = 0 ; i < size; ++i)
130+ {
131+ hash ^= ptr[i];
132+ hash *= fnvPrime;
133+ }
134+ return hash;
135+ }
136+
137+ template <std::size_t N>
138+ std::uint32_t fnv1a (const float (&data)[N])
139+ {
140+ return fnv1a (data, N * sizeof (float ));
141+ }
142+
143+ class BaseColorTextureBuilder
144+ {
145+ public:
146+ BaseColorTextureBuilder (const fs::path gltfPath, const std::span<const cgltf_image> gltfImages)
147+ : mGltfPath (gltfPath),
148+ mImages (gltfImages),
149+ mTextures(),
150+ mImageLookups(),
151+ mBaseColorFactorLookups(),
152+ mMeshTextureIndices()
153+ {
154+ }
155+ ~BaseColorTextureBuilder () = default ;
156+
157+ BaseColorTextureBuilder (const BaseColorTextureBuilder&) = delete;
158+ BaseColorTextureBuilder& operator =(const BaseColorTextureBuilder&) = delete ;
159+
160+ BaseColorTextureBuilder (BaseColorTextureBuilder&&) = delete;
161+ BaseColorTextureBuilder& operator =(BaseColorTextureBuilder&&) = delete ;
162+
163+ std::tuple<std::vector<std::size_t >, std::vector<Texture>> build ()
164+ {
165+ mImageLookups .clear ();
166+ mBaseColorFactorLookups .clear ();
167+ return std::make_tuple (std::move (mMeshTextureIndices ), std::move (mTextures ));
168+ }
169+
170+ void addBaseColor (const cgltf_pbr_metallic_roughness& pbrMetallicRoughness)
171+ {
172+ NLRS_ASSERT (pbrMetallicRoughness.base_color_texture .texcoord == 0 );
173+ NLRS_ASSERT (pbrMetallicRoughness.base_color_texture .has_transform == false );
174+
175+ const std::size_t textureIdx = [&pbrMetallicRoughness, this ]() -> std::size_t {
176+ if (pbrMetallicRoughness.base_color_texture .texture != nullptr )
177+ {
178+ const cgltf_texture& baseColorTexture =
179+ *pbrMetallicRoughness.base_color_texture .texture ;
180+ // Look up OpenGL texture wrap modes:
181+ // https://registry.khronos.org/OpenGL/api/GL/glcorearb.h
182+ // GL_REPEAT: 10497
183+ // GL_MIRRORED_REPEAT: 33648
184+ // GL_CLAMP_TO_EDGE: 33071
185+ // GL_CLAMP_TO_BORDER: 33069
186+ NLRS_ASSERT (baseColorTexture.sampler ->wrap_s == 10497 );
187+ NLRS_ASSERT (baseColorTexture.sampler ->wrap_t == 10497 );
188+ NLRS_ASSERT (baseColorTexture.image );
189+
190+ const cgltf_image* const baseColorImage = baseColorTexture.image ;
191+ const auto distance = std::distance (mImages .data (), baseColorImage);
192+ NLRS_ASSERT (distance >= 0 );
193+ const std::size_t imageIndex = static_cast <std::size_t >(distance);
194+ NLRS_ASSERT (imageIndex < mImages .size ());
195+ const auto imageLookup = std::find_if (
196+ mImageLookups .begin (),
197+ mImageLookups .end (),
198+ [imageIndex](const ImageLookup& lookup) -> bool {
199+ return lookup.gltfImageIndex == imageIndex;
200+ });
201+ if (imageLookup == mImageLookups .end ())
202+ {
203+ const std::size_t textureIdx = mTextures .size ();
204+ mImageLookups .push_back ({imageIndex, textureIdx});
205+ mTextures .emplace_back (textureFromGltfImage (baseColorImage, mGltfPath ));
206+ return textureIdx;
207+ }
208+ else
209+ {
210+ return imageLookup->textureIndex ;
211+ }
212+ }
213+ else
214+ {
215+ const std::uint32_t hash = fnv1a (pbrMetallicRoughness.base_color_factor );
216+ const auto colorLookup = std::find_if (
217+ mBaseColorFactorLookups .begin (),
218+ mBaseColorFactorLookups .end (),
219+ [hash](const BaseColorFactorLookup& lookup) -> bool {
220+ return lookup.hash == hash;
221+ });
222+ if (colorLookup == mBaseColorFactorLookups .end ())
223+ {
224+ const std::size_t textureIdx = mTextures .size ();
225+ mBaseColorFactorLookups .push_back ({hash, textureIdx});
226+
227+ const float r = pbrMetallicRoughness.base_color_factor [0 ];
228+ const float g = pbrMetallicRoughness.base_color_factor [1 ];
229+ const float b = pbrMetallicRoughness.base_color_factor [2 ];
230+ const float a = pbrMetallicRoughness.base_color_factor [3 ];
231+ mTextures .emplace_back (Texture::fromPixel (r, g, b, a));
232+ return textureIdx;
233+ }
234+ else
235+ {
236+ return colorLookup->textureIndex ;
237+ }
238+ }
239+ }();
240+ mMeshTextureIndices .push_back (textureIdx);
241+ }
242+
243+ private:
244+ struct ImageLookup
245+ {
246+ std::size_t gltfImageIndex;
247+ std::size_t textureIndex;
248+ };
249+
250+ struct BaseColorFactorLookup
251+ {
252+ std::uint32_t hash;
253+ std::size_t textureIndex;
254+ };
255+ fs::path mGltfPath ;
256+ std::span<const cgltf_image> mImages ;
257+ std::vector<Texture> mTextures ;
258+ std::vector<ImageLookup> mImageLookups ;
259+ std::vector<BaseColorFactorLookup> mBaseColorFactorLookups ;
260+ std::vector<std::size_t > mMeshTextureIndices ;
261+ };
73262} // namespace
74263
75264GltfModel::GltfModel (const fs::path gltfPath)
@@ -121,8 +310,9 @@ GltfModel::GltfModel(const fs::path gltfPath)
121310 std::vector<std::vector<glm::vec3>> meshNormals;
122311 std::vector<std::vector<glm::vec2>> meshTexCoords;
123312 std::vector<std::vector<std::uint32_t >> meshIndices;
124- std::vector<const cgltf_image*> meshBaseColorImageAttributes;
125- VectorSet<const cgltf_image*> uniqueBaseColorImages;
313+
314+ BaseColorTextureBuilder baseColorTextureBuilder{
315+ gltfPath, std::span<const cgltf_image>(data->images , data->images_count )};
126316
127317 const std::size_t meshCount = data->meshes_count ;
128318 for (std::size_t meshIdx = 0 ; meshIdx < meshCount; ++meshIdx)
@@ -139,25 +329,7 @@ GltfModel::GltfModel(const fs::path gltfPath)
139329 NLRS_ASSERT (primitive.material ->has_pbr_metallic_roughness );
140330 const cgltf_pbr_metallic_roughness& pbrMetallicRoughness =
141331 primitive.material ->pbr_metallic_roughness ;
142-
143- NLRS_ASSERT (pbrMetallicRoughness.base_color_texture .texcoord == 0 );
144- NLRS_ASSERT (pbrMetallicRoughness.base_color_texture .scale == 1 .f );
145- NLRS_ASSERT (pbrMetallicRoughness.base_color_texture .texture );
146-
147- const cgltf_texture& baseColorTexture =
148- *pbrMetallicRoughness.base_color_texture .texture ;
149- // Look up OpenGL texture wrap modes:
150- // https://registry.khronos.org/OpenGL/api/GL/glcorearb.h
151- // GL_REPEAT: 10497
152- // GL_MIRRORED_REPEAT: 33648
153- // GL_CLAMP_TO_EDGE: 33071
154- // GL_CLAMP_TO_BORDER: 33069
155- NLRS_ASSERT (baseColorTexture.sampler ->wrap_s == 10497 );
156- NLRS_ASSERT (baseColorTexture.sampler ->wrap_t == 10497 );
157- NLRS_ASSERT (baseColorTexture.image );
158- const cgltf_image* const baseColorImage = baseColorTexture.image ;
159- meshBaseColorImageAttributes.push_back (baseColorImage);
160- uniqueBaseColorImages.insert (baseColorImage);
332+ baseColorTextureBuilder.addBaseColor (pbrMetallicRoughness);
161333 }
162334
163335 // Indices
@@ -265,81 +437,13 @@ GltfModel::GltfModel(const fs::path gltfPath)
265437 }
266438 }
267439
268- // Mesh texture indices
269-
270- NLRS_ASSERT (meshPositions.size () == meshBaseColorImageAttributes.size ());
271- std::vector<std::uint32_t > meshBaseColorTextureIndices;
272- meshBaseColorTextureIndices.reserve (meshBaseColorImageAttributes.size ());
273- std::transform (
274- meshBaseColorImageAttributes.begin (),
275- meshBaseColorImageAttributes.end (),
276- std::back_inserter (meshBaseColorTextureIndices),
277- [&](const cgltf_image* image) -> std::uint32_t {
278- const auto imageIter = uniqueBaseColorImages.find (image);
279- NLRS_ASSERT (imageIter != uniqueBaseColorImages.end ());
280- const auto distance = std::distance (uniqueBaseColorImages.begin (), imageIter);
281- NLRS_ASSERT (distance >= 0 );
282- const auto textureIdx = static_cast <std::uint32_t >(distance);
283- NLRS_ASSERT (textureIdx < meshBaseColorImageAttributes.size ());
284- return textureIdx;
285- });
286-
287- // Model textures
288-
289- auto bufferViewData =
290- [](const cgltf_buffer_view* const bufferView) -> std::span<const std::uint8_t > {
291- // NOTE: cgltf_buffer_view_data used as a reference for this function
292- NLRS_ASSERT (bufferView != nullptr );
293- const std::size_t byteLength = bufferView->size ;
294-
295- // See cgltf_buffer_view::data comment, overrides buffer->data if present
296- if (bufferView->data != nullptr )
297- {
298- const std::uint8_t * const bufferPtr =
299- static_cast <const std::uint8_t *>(bufferView->data );
300- return std::span (bufferPtr, byteLength);
301- }
302-
303- NLRS_ASSERT (bufferView->buffer != nullptr );
304- const std::size_t bufferOffset = bufferView->offset ;
305- const std::uint8_t * const bufferPtr =
306- static_cast <const std::uint8_t *>(bufferView->buffer ->data );
307-
308- return std::span (bufferPtr + bufferOffset, byteLength);
309- };
310-
311- baseColorTextures.reserve (uniqueBaseColorImages.size ());
312- std::transform (
313- uniqueBaseColorImages.begin (),
314- uniqueBaseColorImages.end (),
315- std::back_inserter (baseColorTextures),
316- [&](const cgltf_image* image) -> Texture {
317- if (image->buffer_view )
318- {
319- const auto pixelData = bufferViewData (image->buffer_view );
320- return Texture::fromMemory (pixelData);
321- }
322- else
323- {
324- NLRS_ASSERT (image->uri );
325- const fs::path imagePath = gltfPath.parent_path () / image->uri ;
326- if (!fs::exists (imagePath))
327- {
328- throw std::runtime_error (
329- fmt::format (" The image {} does not exist." , imagePath.string ()));
330- }
331-
332- const std::size_t fileSize = static_cast <std::size_t >(fs::file_size (imagePath));
333- NLRS_ASSERT (fileSize > 0 );
334- std::ifstream file (imagePath, std::ios::binary);
335- NLRS_ASSERT (file.is_open ());
336-
337- std::vector<std::uint8_t > fileData (fileSize, 0 );
338- file.read (reinterpret_cast <char *>(fileData.data ()), fileSize);
339- return Texture::fromMemory (std::span (fileData));
340- }
341- });
440+ auto [meshBaseColorTextureIndices, textures] = baseColorTextureBuilder.build ();
441+ baseColorTextures = std::move (textures);
342442
443+ NLRS_ASSERT (meshPositions.size () == meshNormals.size ());
444+ NLRS_ASSERT (meshPositions.size () == meshTexCoords.size ());
445+ NLRS_ASSERT (meshPositions.size () == meshIndices.size ());
446+ NLRS_ASSERT (meshPositions.size () == meshBaseColorTextureIndices.size ());
343447 meshes.reserve (meshPositions.size ());
344448 for (std::size_t i = 0 ; i < meshPositions.size (); ++i)
345449 {
0 commit comments