20#include "DracoMeshDecompressor.h"
21#include "MeshoptDecompressor.h"
23#include "ExtensionRegistry.h"
25#include "Services/Variables.h"
26#include "Platform/ResourceBufferBackedFileContents.h"
27#include "Resources/AnimationManager.h"
28#include "Resources/DefaultMaterial.h"
29#include "Resources/IModelLoader.h"
30#include "Resources/MaterialManager.h"
31#include "Resources/ResourceStore.h"
32#include "Resources/Skeleton.h"
33#include "Resources/Animation.h"
34#include "Foundation/HashSequence.h"
35#include "Foundation/Logging/Logger.h"
36#include "Foundation/Platform/IO.h"
37#include "Rendering/DataFormat.h"
39#if defined ( __clang__ )
40#pragma clang diagnostic push
41#pragma clang diagnostic ignored "-Wsign-compare"
46#if defined ( __clang__ )
47#pragma clang diagnostic pop
56 std::string getScheme(
const std::string& uri)
58 std::string scheme =
"file";
59 std::string::size_type spos = uri.find(
':');
61 if (spos != std::string::npos) {
62 std::string s = uri.substr(0, spos);
64 for (
const char &c : s) {
65 if (!(std::isupper(c) || std::islower(c))) {
83 va_start(argptr, format);
84 int n = std::vsnprintf(buffer,
sizeof(buffer), format, argptr);
86 if (0 < n && n <
static_cast<int>(
sizeof(buffer))) {
87 loadData.debugMessages[buffer] += 1;
92 void stridedCopy(uint8_t* dst,
size_t dstStride,
const uint8_t* src,
size_t srcStride,
size_t count)
94 for (
size_t i = 0; i < count; i++) {
95 for (
size_t k = 0; k < M; k++) {
96 dst[dstStride * i + k] = src[srcStride * i + k];
101 [[nodiscard]]
bool checkIsUint(
const GltfModelDefinition& loadData,
const char* name,
const Value& value)
103 if (!value.IsUint()) {
104 LOG_ERROR(logger,
"%.*s: %s is not an uint", StringViewFormat(loadData.path), name);
112 return checkIsUint(loadData, member.name.GetString(), member.value);
115 [[nodiscard]]
bool getUint(
const GltfModelDefinition& loadData, uint32_t& result,
const Member& member)
117 if (!checkIsUint(loadData, member.name.GetString(), member.value)) {
120 result = member.value.GetUint();
124 [[nodiscard]]
bool checkIsBool(
const GltfModelDefinition& loadData,
const char* name,
const Value& value)
126 if (!value.IsBool()) {
127 LOG_ERROR(logger,
"%.*s: %s is not a bool", StringViewFormat(loadData.path), name);
135 return checkIsBool(loadData, member.name.GetString(), member.value);
138 [[nodiscard]]
bool getBool(
const GltfModelDefinition& loadData,
bool& result,
const Member& member)
140 if (!checkIsBool(loadData, member.name.GetString(), member.value))
return false;
141 result = member.value.GetBool();
145 [[nodiscard]]
bool checkIsString(
const GltfModelDefinition& loadData,
const char* name,
const Value& value)
147 if (!value.IsString()) {
148 LOG_ERROR(logger,
"%.*s: %s is not a string", StringViewFormat(loadData.path), name);
154 [[nodiscard]]
bool checkIsString(
const GltfModelDefinition& loadData,
const Member& member)
156 return checkIsString(loadData, member.name.GetString(), member.value);
159 [[nodiscard]]
bool checkIsObject(
const GltfModelDefinition& loadData,
const char* name,
const Value& value)
161 if (!value.IsObject()) {
162 LOG_ERROR(logger,
"%.*s: %s is not an object", StringViewFormat(loadData.path), name);
168 [[nodiscard]]
bool checkIsObject(
const GltfModelDefinition& loadData,
const Member& member)
170 return checkIsObject(loadData, member.name.GetString(), member.value);
175 if (!member.value.IsArray()) {
176 LOG_ERROR(logger,
"%.*s: %s is not an array", StringViewFormat(loadData.path), member.name.GetString());
182 template<
typename Vec>
183 [[nodiscard]]
bool tryGetVecN(Vec& result,
const GltfModelDefinition& loadData,
const char* key,
const Object& parent)
185 size_t N = result.length();
187 if (
auto it = parent.FindMember(key); it != parent.MemberEnd()) {
188 if (!it->value.IsArray()) {
189 LOG_ERROR(logger,
"%.*s: %s not an array", StringViewFormat(loadData.path), it->name.GetString());
192 const Array& arr = it->value.GetArray();
193 if (arr.Size() != N) {
194 LOG_ERROR(logger,
"%.*s: %s not an array with length %zu", StringViewFormat(loadData.path), it->name.GetString(), N);
197 for (uint32_t i = 0; i < N; i++) {
198 if (!arr[i].IsNumber()) {
199 LOG_ERROR(logger,
"%.*s: %s array element %u is not a float", StringViewFormat(loadData.path), it->name.GetString(), i);
202 result[i] = arr[i].GetFloat();
208 [[nodiscard]]
bool tryGetBool(
bool& result,
const GltfModelDefinition& loadData,
const char* key,
const Object& parent)
210 if (
auto it = parent.FindMember(key); it != parent.MemberEnd()) {
211 if (!it->value.IsBool()) {
212 LOG_ERROR(logger,
"%.*s: %s not a bool", StringViewFormat(loadData.path), it->name.GetString());
215 result = it->value.GetBool();
222 if (
auto it = parent.FindMember(key); it != parent.MemberEnd()) {
223 if (!it->value.IsString()) {
224 LOG_ERROR(logger,
"%.*s: %s not a string", StringViewFormat(loadData.path), it->name.GetString());
227 result = toView(it->value);
232 [[nodiscard]]
bool tryGetFloat(
float& result,
const GltfModelDefinition& loadData,
const char* key,
const Object& parent)
234 if (
auto it = parent.FindMember(key); it != parent.MemberEnd()) {
235 if (!it->value.IsNumber()) {
236 LOG_ERROR(logger,
"%.*s: %s not a float", StringViewFormat(loadData.path), it->name.GetString());
239 result = it->value.GetFloat();
244 [[nodiscard]]
bool tryGetUint(uint32_t& result,
const GltfModelDefinition& loadData,
const char* key,
const Object& parent)
246 if (
auto it = parent.FindMember(key); it != parent.MemberEnd()) {
247 if (!it->value.IsUint()) {
248 LOG_ERROR(logger,
"%.*s: %s not an uint", StringViewFormat(loadData.path), it->name.GetString());
251 result = it->value.GetUint();
256 [[nodiscard]]
bool getString(std::string& result,
const Cogs::StringView& path,
const Member& member)
258 if (!member.value.IsString()) {
259 LOG_ERROR(logger,
"%.*s: %s s not a string", StringViewFormat(path), member.name.GetString());
262 result = std::string(member.value.GetString());
267 size_t pos = result.find(
"%20");
268 if (pos == std::string::npos) {
271 result.replace(pos, 3,
" ");
279 if (!assetValue.IsObject()) {
280 LOG_ERROR(logger,
"%.*s: JSON asset is not an object value", StringViewFormat(path));
284 for (
const auto& field : assetValue.GetObject()) {
285 if (field.name ==
"version") {
286 std::string v = field.value.GetString();
287 size_t pos = v.find(
'.');
288 std::string v_maj = v.substr(0, pos);
289 std::string v_min = v.substr(pos + 1, v.size() - pos);
290 int32_t major_version = atoi(v_maj.c_str());
291 int32_t minor_version = atoi(v_min.c_str());
292 loadData.version = major_version * 100 + minor_version;
294 else if (field.name ==
"minVersion") {
295 std::string v = field.value.GetString();
296 size_t pos = v.find(
'.');
297 std::string v_maj = v.substr(0, pos);
298 std::string v_min = v.substr(pos + 1, v.size() - pos);
299 int32_t major_version = atoi(v_maj.c_str());
300 int32_t minor_version = atoi(v_min.c_str());
301 loadData.min_version = major_version * 100 + minor_version;
303 else if (field.name ==
"generator") {
306 else if (field.name ==
"copyright") {
309 else if (field.name ==
"extras") {
312 else if(field.name ==
"extensions"){
315 else if(field.name ==
"extensionsRequired"){
319 LOG_WARNING_ONCE(logger,
"Unknown asset field: %s", field.name.GetString());
323 if (loadData.min_version == -1) loadData.min_version = loadData.version;
324 if (loadData.min_version >= 300) {
325 LOG_ERROR(logger,
"Could not load asset file %.*s. (Unsuported min version %d.)", StringViewFormat(path), loadData.min_version);
333 if (!buffersValue.IsArray()) {
334 LOG_ERROR(logger,
"%.*s: JSON buffers member is not an array value", StringViewFormat(path));
338 for (
const auto& bufferValue : buffersValue.GetArray()) {
342 for (
const auto& field : bufferValue.GetObject()) {
343 if (field.name ==
"uri") {
344 uri = field.value.GetString();
345 if (uri.find(
"%") != std::string::npos) {
346 LOG_WARNING_ONCE(logger,
"Unknown buffer field: %s", uri.c_str());
348 else if (uri.find(
"&") != std::string::npos) {
349 LOG_WARNING_ONCE(logger,
"Unknown buffer field: %s", uri.c_str());
353 else if (field.name ==
"byteLength") {
356 else if (field.name ==
"extensions") {
357 auto exts = field.value.GetObject();
358 for (
auto& ext : exts) {
359 if (std::string(ext.name.GetString()) ==
"EXT_meshopt_compression") {
366 LOG_WARNING_ONCE(logger,
"Unknown buffer field: %s", field.name.GetString());
370 std::string scheme = getScheme(uri);
373 loadData.buffer_data.push_back(glbData);
375 else if (scheme ==
"data") {
376 std::string::size_type spos = uri.find(
':');
377 std::string::size_type mpos = uri.find(
';');
378 std::string::size_type dpos = uri.find(
',');
379 std::string media_type = uri.substr(spos + 1, mpos - (spos + 1));
380 std::string base_type = uri.substr(mpos + 1, dpos - (mpos + 1));
381 std::string base64 = uri.substr(dpos + 1);
382 LOG_TRACE(logger,
"Loading data: media type \"%s\", base_type \"%s\"", media_type.c_str(), base_type.c_str());
383 std::string data = base64_decode(base64);
387 std::memcpy(buffer.data(), &data[0], data.size());
389 auto& stored = loadData.buffer_data_store.emplace_back(
ResourceBuffer{ std::make_shared<Cogs::Memory::MemoryBuffer>(std::move(buffer)) });
391 loadData.buffer_data.push_back(std::span<const uint8_t>(stored.data(), stored.data() + stored.size()));
393 else if (scheme ==
"file") {
394 std::string::size_type spos = uri.find(
':');
395 if (spos != std::string::npos) {
396 uri = Cogs::IO::combine(Cogs::IO::parentPath(path), uri.substr(spos + 1));
399 uri = Cogs::IO::combine(Cogs::IO::parentPath(path), uri);
401 auto& stored = loadData.buffer_data_store.emplace_back(context->
resourceStore->getResourceContents(uri));
402 if (stored.empty()) {
403 LOG_ERROR(logger,
"Failed or empty linked uri: %s files.", uri.c_str());
406 loadData.buffer_data.push_back(std::span<const uint8_t>(stored.data(), stored.data() + stored.size()));
409 LOG_ERROR(logger,
"%.*s: Loader cannot handle uri %s files.", StringViewFormat(path), uri.c_str());
418 if (!bufferViewsValue.IsArray()) {
419 LOG_ERROR(logger,
"%.*s: JSON bufferViews is not an array", StringViewFormat(path));
423 for (
const auto& bufferViewValue : bufferViewsValue.GetArray()) {
426 if (!bufferViewValue.IsObject()) {
427 LOG_ERROR(logger,
"%.*s: JSON bufferViews element is not an object", StringViewFormat(path));
431 for (
const auto& field : bufferViewValue.GetObject()) {
432 switch (toView(field.name).
hash()) {
434 if (!field.value.IsUint()) {
435 LOG_ERROR(logger,
"%.*s: JSON bufferView buffer is not an uint", StringViewFormat(path));
438 tmp.buffer = field.value.GetUint();
442 if (!field.value.IsUint()) {
443 LOG_ERROR(logger,
"%.*s: JSON bufferView byteLength is not an uint", StringViewFormat(path));
446 tmp.byteLength = field.value.GetUint();
450 if (!field.value.IsUint()) {
451 LOG_ERROR(logger,
"%.*s: JSON bufferView byteOffset is not an uint", StringViewFormat(path));
454 tmp.byteOffset = field.value.GetUint();
458 if (!field.value.IsUint()) {
459 LOG_ERROR(logger,
"%.*s: JSON bufferView target is not an uint", StringViewFormat(path));
462 tmp.target = field.value.GetUint();
466 if (!field.value.IsUint()) {
467 LOG_ERROR(logger,
"%.*s: JSON bufferView byteStride is not an uint", StringViewFormat(path));
470 tmp.byteStride = field.value.GetUint();
476 for (
const auto& ext : field.value.GetObject()) {
477 if (std::string(ext.name.GetString()) ==
"EXT_meshopt_compression") {
483 LOG_ERROR(logger,
"%.*s: JSON unknown bufferView field: %s", StringViewFormat(path), field.name.GetString());
494 const uint32_t img[2 * 2] = {
498 void* mem = malloc(
sizeof(img));
499 std::memcpy(mem, img,
sizeof(img));
500 gltfImage.decoded.data =
reinterpret_cast<stbi_uc*
>(mem);
501 gltfImage.decoded.width = 2;
502 gltfImage.decoded.height = 2;
503 gltfImage.decoded.comp = 4;
507 switch (mimeType.
hash()) {
510 stbi_set_flip_vertically_on_load_thread(0);
522 LOG_ERROR(logger,
"%.*s: Unsupported mimetype %.*s", StringViewFormat(path), StringViewFormat(mimeType));
523 gltfImage.decoded.failed =
true;
530 const uint32_t img[2 * 2] = {
534 void* mem = malloc(
sizeof(img));
535 std::memcpy(mem, img,
sizeof(img));
536 gltfImage.decoded.data =
reinterpret_cast<stbi_uc*
>(mem);
537 gltfImage.decoded.width = 2;
538 gltfImage.decoded.height = 2;
539 gltfImage.decoded.comp = 4;
543 switch (mimeType.
hash()) {
546 gltfImage.handle = context->textureManager->loadTextureFromMemory(data, size,
"unnamed.extension", NoResourceId, loadFlags);
556 LOG_ERROR(logger,
"%.*s: Unsupported mimetype %.*s", StringViewFormat(path), StringViewFormat(mimeType));
557 gltfImage.decoded.failed =
true;
563 if (!imagesValue.IsArray()) {
564 LOG_ERROR(logger,
"%.*s: Images is not an array", StringViewFormat(loadData.path));
568 Array images = imagesValue.GetArray();
569 loadData.images.resize(images.Size());
571 for(uint32_t i=0; i<images.Size(); i++) {
572 const Value& imageValue = images[i];
573 if (!imageValue.IsObject()) {
574 LOG_ERROR(logger,
"%.*s: Images array item is not an object", StringViewFormat(loadData.path));
577 auto imageObject = imageValue.GetObject();
581 if (
auto uriMember = imageObject.FindMember(
"uri"); uriMember != imageValue.MemberEnd()) {
582 if (!getString(image.uri, loadData.path, *uriMember)) {
587 if (imageUri.
substr(0, 5) ==
"data:") {
594 LOG_TRACE(logger,
"%.*s: Loading embedded data: media type \"%.*s\", base_type \"%.*s\"",
595 StringViewFormat(loadData.path), StringViewFormat(mediaType), StringViewFormat(baseType));
597 image.kind = GltfImage::LoadFromMemory;
598 image.decoded.task = loadData.context->
taskManager->enqueueChild(loadData.rootTask,
599 [&loadData, &image, mediaType, base64, context]() {
600 std::string data = base64_decode(base64.to_string());
601 decodeImageData(context, image, loadData.path, mediaType, data.c_str(), data.size());
605 image.kind = GltfImage::LoadFromUri;
609 else if (
auto bufferViewMember = imageObject.FindMember(
"bufferView"); bufferViewMember != imageValue.MemberEnd()) {
610 auto mimeTypeMember = imageObject.FindMember(
"mimeType");
611 if (mimeTypeMember == imageObject.MemberEnd()) {
612 LOG_ERROR(logger,
"%.*s: Images array item has an bufferView item but no mimeType member", StringViewFormat(loadData.path));
616 uint32_t bufferViewIndex = 0;
617 if (!getUint(loadData, bufferViewIndex, *bufferViewMember)) {
621 std::string mimeType;
622 if (!getString(mimeType, loadData.path, *mimeTypeMember)) {
626 image.kind = GltfImage::LoadFromMemory;
627 image.from_memory_data.bufferViewIndex = bufferViewIndex;
628 image.from_memory_data.mimeType = mimeType;
631 LOG_ERROR(logger,
"%.*s: Images array item has neither an uri nor a bufferView member", StringViewFormat(loadData.path));
641 if (loadData.images.size() <= imageIndex) {
642 LOG_ERROR(logger,
"%.*s: Illegal image index %zu", StringViewFormat(loadData.path), imageIndex);
646 GltfImage& image = loadData.images[imageIndex];
647 const size_t cacheIndex = (linearColor ? 2 : 0) + (mipmapped ? 1 : 0);
649 if (!image.cache[cacheIndex].handle) {
650 switch (image.kind) {
651 case GltfImage::Unset:
652 LOG_WARNING_ONCE(logger,
"%.*s: Image index %zu has no valid data", StringViewFormat(loadData.path), imageIndex);
653 image.cache[cacheIndex].handle = loadData.context->textureManager->white;
655 case GltfImage::LoadFromUri:
656 image.cache[cacheIndex].handle = loadData.context->textureManager->loadTexture(Cogs::IO::combine(Cogs::IO::parentPath(loadData.path), image.uri),
661 case GltfImage::LoadFromMemory:
662 assert(!image.cache[cacheIndex].proxy);
664 const GltfBufferView& bufferView = loadData.buffer_views[image.from_memory_data.bufferViewIndex];
665 std::span<const uint8_t> buffer = loadData.buffer_data[bufferView.buffer];
667 const uint8_t* ptr = &buffer[bufferView.byteOffset];
668 uint32_t size = bufferView.byteLength;
672 image.cache[cacheIndex].handle = image.handle;
675 assert(
false &&
"Invalid image kind");
679 return image.cache[cacheIndex].handle;
684 if (!samplersValue.IsArray()) {
685 LOG_ERROR(logger,
"%.*s: Samplers is not an array", StringViewFormat(path));
688 for (
auto& samplerValue : samplersValue.GetArray()) {
689 if (!samplerValue.IsObject()) {
690 LOG_ERROR(logger,
"%.*s: Sampler is not an object", StringViewFormat(path));
695 bool linearFilter =
true;
698 for (
const auto& field : samplerValue.GetObject()) {
699 switch (toView(field.name).
hash()) {
702 if (!getUint(loadData, magFilter, field)) {
706 case GLTF_NEAREST:
break;
707 case GLTF_LINEAR:
break;
709 LOG_ERROR(logger,
"%.*s: Unrecognized magFilter %u", StringViewFormat(path), magFilter);
717 if (!getUint(loadData, minFilter, field))
return false;
719 case GLTF_NEAREST: sampler.mipmapped =
false; linearFilter =
false;
break;
720 case GLTF_LINEAR: sampler.mipmapped =
false;
break;
721 case GLTF_NEAREST_MIPMAP_NEAREST: linearFilter =
false;
break;
722 case GLTF_LINEAR_MIPMAP_NEAREST:
break;
723 case GLTF_NEAREST_MIPMAP_LINEAR:
break;
724 case GLTF_LINEAR_MIPMAP_LINEAR:
break;
726 LOG_ERROR(logger,
"%.*s: Unrecognized minFilter %u", StringViewFormat(path), minFilter);
734 if (!getUint(loadData, wrapS, field))
return false;
740 LOG_ERROR(logger,
"%.*s: Unrecognized wrapS %u", StringViewFormat(path), wrapS);
748 if (!getUint(loadData, wrapT, field))
return false;
754 LOG_ERROR(logger,
"%.*s: Unrecognized wrapT %u", StringViewFormat(path), wrapT);
761 LOG_WARNING(logger,
"%.*s: Unrecognized sampler field %s", StringViewFormat(path), field.name.GetString());
772 if (!texturesValue.IsArray()) {
773 LOG_ERROR(logger,
"%.*s: Textures is not an array", StringViewFormat(path));
776 for (
auto& texture : texturesValue.GetArray()) {
777 auto& texture_value = loadData.textures.emplace_back(
GltfTexture{ -1, -1 });
779 for (
const auto& field : texture.GetObject()) {
780 if (field.name ==
"sampler") {
781 texture_value.sampler = field.value.GetUint();
782 if (loadData.texture_samplers.size() <= (
size_t)texture_value.sampler) {
783 LOG_ERROR(logger,
"Illegal texture sampler index %u", texture_value.sampler);
787 else if (field.name ==
"source") {
788 texture_value.source = field.value.GetUint();
789 if (loadData.images.size() <= uint32_t(texture_value.source)) {
790 LOG_ERROR(logger,
"Illegal texture source index %u", texture_value.source);
794 else if (field.name ==
"extensions") {
795 auto ext = field.value.GetObject();
796 if (ext.HasMember(
"KHR_texture_basisu")) {
797 auto data = ext[
"KHR_texture_basisu"].GetObject();
798 int altSource = data[
"source"].GetInt();
799 if (!texture.HasMember(
"source")) {
800 texture_value.source = altSource;
805 LOG_WARNING_ONCE(logger,
"Unknown texture field: %s", field.name.GetString());
817 const Object& parent,
818 const char* textureName,
821 const char* extraKey =
nullptr,
822 float* extraValue =
nullptr)
824 if (
auto texInfoIt = parent.FindMember(key); texInfoIt != parent.MemberEnd()) {
825 if (!checkIsObject(loadData, *texInfoIt)) {
828 const Object& texInfoObject = texInfoIt->value.GetObject();
830 if (
auto indexIt = texInfoObject.FindMember(
"index"); indexIt != texInfoObject.MemberEnd()) {
832 if (!getUint(loadData, index, *indexIt)) {
833 LOG_ERROR(logger,
"No 'index' uint element in texInfoObject");
837 uint32_t texcoord = 0;
838 if (!tryGetUint(texcoord, loadData,
"texCoord", texInfoObject)) {
839 LOG_ERROR(logger,
"No 'texCoord' uint element in texInfoObject");
842 cachedModelMaterial.texCoordSets = std::max(cachedModelMaterial.texCoordSets, texcoord + 1);
844 if (extraKey && !tryGetFloat(*extraValue, loadData, extraKey, texInfoObject)) {
845 LOG_ERROR(logger,
"No '%s' float element in texInfoObject", extraKey);
849 if (loadData.textures.size() <= index) {
850 LOG_ERROR(logger,
"%.*s: %s has illegal texture index %u", StringViewFormat(loadData.path), key, index);
854 const GltfTexture& textureDefinition = loadData.textures[index];
855 if (loadData.images.size() <= (
size_t)textureDefinition.source) {
856 LOG_ERROR(logger,
"%.*s: %s has illegal texture index source %u", StringViewFormat(loadData.path), key, textureDefinition.source);
861 if (textureDefinition.sampler != -1) {
862 if (loadData.texture_samplers.size() <= (
size_t)textureDefinition.sampler) {
863 LOG_ERROR(logger,
"%.*s: %s has illegal sampler index %u", StringViewFormat(loadData.path), key, textureDefinition.sampler);
866 sampler = loadData.texture_samplers[textureDefinition.sampler];
869 linearColor, sampler.mipmapped);
871 const std::string texmapName = std::string(textureName) +
"Map";
872 if (instance->
material->getVariantIndex(texmapName) != Material::NoVariantIndex) {
873 instance->setVariant(texmapName,
true);
876 const std::string texUVChannelName = std::string(textureName) +
"UVChannel";
877 if (instance->
material->getVariantIndex(texUVChannelName) != Material::NoVariantIndex) {
878 instance->setVariant(texUVChannelName,
int(texcoord));
881 const std::string texCoordName = std::string(
"TexCoord") + std::to_string(texcoord);
882 if (instance->
material->getVariantIndex(texCoordName) != Material::NoVariantIndex) {
883 instance->setVariant(texCoordName,
true);
886 std::string texKeyName = std::string(textureName) +
"Map";
887 texKeyName[0] = char(std::tolower(texKeyName[0]));
889 const VariableKey texKey = material->getTextureKey(texKeyName);
897 if (
auto extIt = texInfoObject.FindMember(
"extensions"); extIt != texInfoObject.MemberEnd()) {
898 const Object& extObject = extIt->value.GetObject();
899 if (
auto texTrIt = extObject.FindMember(
"KHR_texture_transform"); texTrIt != extObject.MemberEnd()) {
900 const Object& texTrObject = texTrIt->value.GetObject();
902 if (texTrObject.HasMember(
"offset")) {
903 if (!texTrObject[
"offset"].IsArray() || texTrObject[
"offset"].Size() != 2) {
904 LOG_ERROR(logger,
"The 'KHR_texture_transform' member 'offset' is not an array of size 2");
return false;
907 textureTransform.offset = glm::vec2(texTrObject[
"offset"][0].GetFloat(), texTrObject[
"offset"][1].GetFloat());
909 if (texTrObject.HasMember(
"scale")) {
910 if (!texTrObject[
"scale"].IsArray() || texTrObject[
"scale"].Size() != 2) {
911 LOG_ERROR(logger,
"The 'KHR_texture_transform' member 'scale' is not an array of size 2");
return false;
914 textureTransform.scale = glm::vec2(texTrObject[
"scale"][0].GetFloat(), texTrObject[
"scale"][1].GetFloat());
916 if (texTrObject.HasMember(
"rotation")) {
917 textureTransform.rotation = texTrObject[
"rotation"].GetFloat();
920 loadData.textureTransforms.push_back(textureTransform);
929 LOG_ERROR(logger,
"%.*s: %s is missing required index property", StringViewFormat(loadData.path), texInfoIt->name.GetString());
943 glm::vec4 baseColorFactor(1.f, 1.f, 1.f, 1.f);
944 float metallicFactor = 1.f;
945 float roughnessFactor = 1.f;
947 if (
auto pbrMetallicValue = materialObject.FindMember(
"pbrMetallicRoughness"); pbrMetallicValue != materialObject.MemberEnd()) {
948 if (!checkIsObject(loadData, *pbrMetallicValue))
return false;
949 const Object& pbrMetallicObject = pbrMetallicValue->value.GetObject();
951 if (!tryGetVecN(baseColorFactor, loadData,
"baseColorFactor", pbrMetallicObject))
return false;
952 if (!tryGetFloat(metallicFactor, loadData,
"metallicFactor", pbrMetallicObject))
return false;
953 if (!tryGetFloat(roughnessFactor, loadData,
"roughnessFactor", pbrMetallicObject))
return false;
955 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"baseColorTexture", pbrMetallicObject,
"Albedo",
false,
nullptr))
return false;
956 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"metallicRoughnessTexture", pbrMetallicObject,
"MetallicRoughness",
true,
nullptr))
return false;
960 for (
int i = 0; i < 3; i++) {
961 baseColorFactor[i] = std::pow(std::abs(baseColorFactor[i]), 1.0f / 2.2f);
964 instance->
setVec4Property(material->getVec4Key(
"albedo"), baseColorFactor);
965 instance->
setFloatProperty(material->getFloatKey(
"metallic"), metallicFactor);
966 instance->
setFloatProperty(material->getFloatKey(
"roughness"), roughnessFactor);
974 if (!tryGetString(name, loadData,
"name", materialObject))
return false;
980 if (!tryGetString(alphaMode, loadData,
"alphaMode", materialObject))
return false;
981 switch (alphaMode.
hash()) {
985 instance->setVariant(
"AlphaTest",
"Discard");
991 LOG_ERROR(logger,
"%.*s: Unknown alphaMode '%.*s'", StringViewFormat(loadData.path), StringViewFormat(alphaMode));
995 float alphaCutoff = 0.5f;
996 if (!tryGetFloat(alphaCutoff, loadData,
"alphaCutoff", materialObject))
return false;
997 instance->
setFloatProperty(material->getFloatKey(
"alphaThreshold"), alphaCutoff);
999 bool doubleSided =
false;
1000 if (!tryGetBool(doubleSided, loadData,
"doubleSided", materialObject))
return false;
1003 float normalMapFactor = 1.0f;
1004 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"normalTexture", materialObject,
"Normal",
true, &cachedMaterial.needsTangents,
"scale", &normalMapFactor))
return false;
1005 instance->
setFloatProperty(material->getFloatKey(
"normalMapFactor"), normalMapFactor);
1007 float occlusionFactor = 1.0f;
1008 if (!tryGetFloat(occlusionFactor, loadData,
"occlusionFactor", materialObject))
return false;
1009 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"occlusionTexture", materialObject,
"Occlusion",
true,
nullptr,
"strength", &occlusionFactor))
return false;
1010 instance->
setFloatProperty(material->getFloatKey(
"occlusion"), occlusionFactor);
1012 glm::vec3 emissiveFactor = glm::vec3(0.0f, 0.0f, 0.0f);
1013 if (!tryGetVecN(emissiveFactor, loadData,
"emissiveFactor", materialObject))
return false;
1014 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"emissiveTexture", materialObject,
"Emissive",
false,
nullptr))
return false;
1015 instance->
setVec3Property(material->getVec3Key(
"emissive"), emissiveFactor);
1022 glm::vec4 diffuseFactor = glm::vec4(1.f, 1.f, 1.f, 1.f);
1023 if (!tryGetVecN(diffuseFactor, loadData,
"diffuseFactor", specGlossObject))
return false;
1025 for (
int i = 0; i < 3; i++) {
1026 diffuseFactor[i] = std::pow(std::abs(diffuseFactor[i]), 1.0f / 2.2f);
1028 instance->
setVec4Property(material->getVec4Key(
"albedo"), diffuseFactor);
1030 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"diffuseTexture", specGlossObject,
"Albedo",
false,
nullptr))
return false;
1032 glm::vec3 specularFactor = glm::vec3(1.f, 1.f, 1.f);
1033 if (!tryGetVecN(specularFactor, loadData,
"specularFactor", specGlossObject))
return false;
1034 instance->
setVec3Property(material->getVec3Key(
"specular"), specularFactor);
1036 float glossinessFactor = 1.f;
1037 if (!tryGetFloat(glossinessFactor, loadData,
"glossinessFactor", specGlossObject))
return false;
1038 instance->
setFloatProperty(material->getFloatKey(
"gloss"), glossinessFactor);
1040 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"specularGlossinessTexture", specGlossObject,
"SpecularGloss",
false,
nullptr))
return false;
1046 float specularFactor = 1.f;
1047 if (!tryGetFloat(specularFactor, loadData,
"specularFactor", specularObject))
return false;
1048 instance->
setFloatProperty(material->getFloatKey(
"specularFactor"), specularFactor);
1050 glm::vec3 specularColorFactor = glm::vec3(1.f, 1.f, 1.f);
1051 if (!tryGetVecN(specularColorFactor, loadData,
"specularColorFactor", specularObject))
return false;
1052 instance->
setVec3Property(material->getVec3Key(
"specularColor"), specularColorFactor);
1054 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"specularTexture", specularObject,
"SpecularFactor",
false,
nullptr))
return false;
1056 if (!tryHandleTextureInfo(material, instance, loadData, cachedMaterial,
"specularColorTexture", specularObject,
"SpecularColor",
false,
nullptr))
return false;
1063 float idxOfRefraction = 1.5f;
1064 if (!tryGetFloat(idxOfRefraction, loadData,
"ior", iorObject))
return false;
1066 instance->setVariant(
"Ior",
true);
1073 if (
auto extensionsIt = materialObject.FindMember(
"extensions"); extensionsIt != materialObject.MemberEnd()) {
1074 if (!checkIsObject(loadData, *extensionsIt)) {
1078 const Object& extensionsObject = extensionsIt->value.GetObject();
1080 if (
auto specGlossIt = extensionsObject.FindMember(
"KHR_materials_pbrSpecularGlossiness"); specGlossIt != extensionsObject.MemberEnd()) {
1081 if (!checkIsObject(loadData, *specGlossIt)) {
1084 const Object& specGlossObject = specGlossIt->value.GetObject();
1086 if (!handleMaterialPbrSpecularGlossiness(loadData, cachedMaterial, material, instance, specGlossObject)) {
1094 if (
auto specularIt = extensionsObject.FindMember(
"KHR_materials_specular"); specularIt != extensionsObject.MemberEnd()) {
1095 if (!checkIsObject(loadData, *specularIt)) {
1098 const Object& specObject = specularIt->value.GetObject();
1099 if (!handleMaterialSpecular(loadData, cachedMaterial, material, instance, specObject)) {
1104 if (
auto iorIt = extensionsObject.FindMember(
"KHR_materials_ior"); iorIt != extensionsObject.MemberEnd()) {
1105 if (!checkIsObject(loadData, *iorIt)) {
1108 const Object& iorObject = iorIt->value.GetObject();
1109 if (!handleMaterialIdxOfRefraction(loadData, cachedMaterial, material, instance, iorObject)) {
1120 const int32_t gltfMaterialIx,
1121 const bool isSkinned,
1122 const bool albedoPerVertex,
1123 const bool hasBitangents)
1125 if (loadData.materialsArray.size() <
size_t(gltfMaterialIx + 1)) {
1126 LOG_ERROR(logger,
"%.*s: Illegal gltf material index %d", StringViewFormat(loadData.path), gltfMaterialIx);
1129 if (loadData.modelMaterialsCache.empty()) {
1130 loadData.modelMaterialsCache.resize(8 * (loadData.materialsArray.size() + 1));
1134 size_t cacheIx = 8 * size_t(gltfMaterialIx + 1) + (isSkinned ? 4 : 0) + (albedoPerVertex ? 2 : 0) + (hasBitangents ? 1 : 0);
1135 assert(cacheIx < loadData.modelMaterialsCache.size());
1137 cachedMaterial_ = &cachedMaterial;
1140 if (cachedMaterial.modelMaterialIx != -1) {
1147 if (gltfMaterialIx >= 0) {
1148 const Object& materialObject = loadData.materialsArray[gltfMaterialIx];
1151 if (materialObject.HasMember(
"extensions") && materialObject[
"extensions"].HasMember(
"KHR_materials_unlit")) {
1152 MaterialHandle material = loadData.context->materialManager->getMaterial(
"DefaultMaterial");
1153 materialInstance = loadData.context->materialInstanceManager->createMaterialInstance(material);
1154 materialInstance->setPermutation(
"Default");
1155 materialInstance->setVariant(
"DefaultHasTexCoord",
true);
1156 materialInstance->setVariant(
"Textured",
true);
1157 materialInstance->setVariant(
"LightModel",
"BaseColor");
1158 materialInstance->setVariant(
"EnableLighting",
false);
1159 materialInstance->setVariant(
"NoTonemap",
true);
1161 if (
auto pbrMetallicValue = materialObject.FindMember(
"pbrMetallicRoughness"); pbrMetallicValue != materialObject.MemberEnd()) {
1162 if (!checkIsObject(loadData, *pbrMetallicValue)) {
1163 LOG_ERROR(logger,
"The 'KHR_materials_unlit' material does not have a 'pbrMetallicRoughness' section.");
1166 const Object& pbrMetallicObject = pbrMetallicValue->value.GetObject();
1167 glm::vec4 baseColorFactor(1.0, 1.0, 1.0, 1.0);
1168 if (!tryGetVecN(baseColorFactor, loadData,
"baseColorFactor", pbrMetallicObject)) {
1169 LOG_ERROR(logger,
"The 'pbrMetallicRoughness' section does not have a 'baseColorFactor' value.");
1172 materialInstance->
setVec4Property(DefaultMaterial::DiffuseColor, baseColorFactor);
1174 if (!tryHandleTextureInfo(material, materialInstance, loadData, cachedMaterial,
"baseColorTexture", pbrMetallicObject,
"Diffuse",
false,
nullptr)) {
1176 LOG_ERROR(logger,
"The 'pbrMetallicRoughness' section does not have a 'baseColorTexture' value.");
1182 cachedMaterial_->needsNormals =
false;
1185 MaterialHandle material = loadData.context->materialManager->getMaterial(
"StandardMaterial");
1186 materialInstance = loadData.context->materialInstanceManager->createMaterialInstance(material);
1189 materialInstance->setPermutation(
"Metallic");
1190 if (
auto extensionsIt = materialObject.FindMember(
"extensions"); extensionsIt != materialObject.MemberEnd()) {
1191 if (!checkIsObject(loadData, *extensionsIt))
return false;
1192 const Object& extensionsObject = extensionsIt->value.GetObject();
1193 if (
auto specGlossIt = extensionsObject.FindMember(
"KHR_materials_pbrSpecularGlossiness"); specGlossIt != extensionsObject.MemberEnd()) {
1194 materialInstance->setPermutation(
"Specular");
1198 if (
auto extensionsIt = materialObject.FindMember(
"extensions"); extensionsIt != materialObject.MemberEnd()) {
1199 if (!checkIsObject(loadData, *extensionsIt))
return false;
1200 const Object& extensionsObject = extensionsIt->value.GetObject();
1201 if (
auto iorIt = extensionsObject.FindMember(
"KHR_materials_ior"); iorIt != extensionsObject.MemberEnd()) {
1202 materialInstance->setVariant(
"Ior",
true);
1204 if (
auto specularIt = extensionsObject.FindMember(
"KHR_materials_specular"); specularIt != extensionsObject.MemberEnd()) {
1205 materialInstance->setVariant(
"Specular",
true);
1209 materialInstance->setVariant(
"LightModel",
"PBR");
1210 if (isSkinned) materialInstance->setVariant(
"Skinned",
true);
1211 if (albedoPerVertex) materialInstance->setVariant(
"AlbedoPerVertex",
true);
1212 if (hasBitangents) materialInstance->setVariant(
"Bitangents",
true);
1214 if (!handleMaterialPbrMetallicRoughness(loadData, cachedMaterial, material, materialInstance, materialObject))
return false;
1215 if (!handleMaterialGenericFields(loadData, cachedMaterial, material, materialInstance, materialObject))
return false;
1216 if (!handleMaterialExtensions(loadData, cachedMaterial, material, materialInstance, materialObject))
return false;
1218 cachedMaterial_->needsNormals =
true;
1223 MaterialHandle material = loadData.context->materialManager->getMaterial(
"StandardMaterial");
1224 materialInstance = loadData.context->materialInstanceManager->createMaterialInstance(material);
1226 materialInstance->setPermutation(
"Metallic");
1227 if (isSkinned) materialInstance->setVariant(
"Skinned",
true);
1228 if (albedoPerVertex) materialInstance->setVariant(
"AlbedoPerVertex",
true);
1229 if (hasBitangents) materialInstance->setVariant(
"Bitangents",
true);
1230 materialInstance->
setName(
"GLTF default material");
1231 materialInstance->
setOption(
"CullMode",
"Back");
1233 materialInstance->
setVec4Property(material->getVec4Key(
"albedo"), glm::vec4(1.0, 1.0, 1.0, 1.0));
1234 materialInstance->
setFloatProperty(material->getFloatKey(
"metallic"), 1.0f);
1235 materialInstance->
setFloatProperty(material->getFloatKey(
"roughness"), 1.0f);
1236 materialInstance->
setVec3Property(material->getVec3Key(
"emissive"), glm::vec3(0.0, 0.0, 0.0));
1237 materialInstance->
setFloatProperty(material->getFloatKey(
"alphaThreshold"), 0.5f);
1239 cachedMaterial_->needsNormals =
true;
1242 assert(materialInstance &&
"Internal error");
1244 cachedMaterial.modelMaterialIx = int32_t(model.materials.size());
1245 model.materials.push_back(materialInstance);
1251 if (!scenesValue.IsArray()) {
1252 LOG_ERROR(logger,
"%.*s: JSON scenes is not an array value", StringViewFormat(path));
1256 for (
const auto& sceneValue : scenesValue.GetArray()) {
1257 if (!sceneValue.IsObject()) {
1258 LOG_ERROR(logger,
"%.*s: JSON scenes item is not an object", StringViewFormat(path));
1262 GltfScene& scene = loadData.scenes.emplace_back();
1263 for (
const auto& field : sceneValue.GetObject()) {
1264 switch (toView(field.name).
hash()) {
1266 if (!field.value.IsString()) {
1267 LOG_ERROR(logger,
"%.*s: JSON scene name value is not a string", StringViewFormat(path));
1270 scene.name = field.value.GetString();
1275 if (!field.value.IsArray()) {
1276 LOG_ERROR(logger,
"%.*s: JSON scene nodes value is not an array", StringViewFormat(path));
1279 for (
const auto& node : field.value.GetArray()) {
1280 if (!node.IsUint()) {
1281 LOG_ERROR(logger,
"%.*s: JSON scene nodes array item is not an uint", StringViewFormat(path));
1284 scene.nodes.push_back(node.GetUint());
1290 if (!field.value.IsObject()) {
1291 LOG_ERROR(logger,
"%.*s: JSON scene extensions item is not an object", StringViewFormat(path));
1304 auto type = field.value.GetType();
1306 LOG_WARNING_ONCE(logger,
"Unhandled application-specific node under scenes node: Extras[rapidjson::Type: %u]", type );
1310 LOG_WARNING_ONCE(logger,
"%.*s: JSON unknown scene field: %s", StringViewFormat(path), field.name.GetString());
1320 if (!nodesValue.IsArray()) {
1321 LOG_ERROR(logger,
"%.*s: JSON nodes member is not an array value", StringViewFormat(path));
1325 const auto nodeValues = nodesValue.GetArray();
1326 const uint32_t numNodes = nodeValues.Size();
1327 loadData.nodes.resize(numNodes);
1329 for (uint32_t i = 0; i < numNodes; ++i) {
1330 GltfNode& node = loadData.nodes[i];
1331 node.node_index = i;
1333 bool use_matrix_transform =
false;
1335 const Value& nodeValue = nodeValues[i];
1336 if (!nodeValue.IsObject()) {
1337 LOG_ERROR(logger,
"%.*s: JSON nodes array item not an object", StringViewFormat(path));
1341 for (
const auto& field : nodeValue.GetObject()) {
1342 switch (toView(field.name).
hash()) {
1344 if (!field.value.IsString()) {
1345 LOG_ERROR(logger,
"%.*s: JSON node name is not a string", StringViewFormat(path));
1348 node.name = field.value.GetString();
1352 if (!field.value.IsArray()) {
1353 LOG_ERROR(logger,
"%.*s: JSON node children is not an array", StringViewFormat(path));
1356 for (
const auto& child : field.value.GetArray()) {
1357 if (!child.IsUint()) {
1358 LOG_ERROR(logger,
"%.*s: JSON children array element is not an uint", StringViewFormat(path));
1361 uint32_t childIndex = child.GetUint();
1362 node.children.push_back(childIndex);
1363 loadData.nodes[childIndex].parent_node = i;
1368 if (field.value.IsArray() && field.value.GetArray().Size() == 4) {
1369 auto arr = field.value.GetArray();
1370 node.rot = glm::quat(arr[3].GetFloat(), arr[0].GetFloat(), arr[1].GetFloat(), arr[2].GetFloat());
1373 LOG_ERROR(logger,
"%.*s: JSON node rotation value is not an array with 4 elements", StringViewFormat(path));
1379 if (field.value.IsArray() && field.value.GetArray().Size() == 3) {
1380 auto arr = field.value.GetArray();
1381 node.pos = glm::vec3(arr[0].GetFloat(), arr[1].GetFloat(), arr[2].GetFloat());
1384 LOG_ERROR(logger,
"%.*s: JSON node translation value is not an array with 3 elements", StringViewFormat(path));
1390 if (field.value.IsArray() && field.value.GetArray().Size() == 3) {
1391 auto arr = field.value.GetArray();
1392 node.scale = glm::vec3(arr[0].GetFloat(), arr[1].GetFloat(), arr[2].GetFloat());
1395 LOG_ERROR(logger,
"%.*s: JSON node scale value is not an array with 3 elements", StringViewFormat(path));
1401 if (field.value.IsArray() && field.value.GetArray().Size() == 16) {
1402 auto arr = field.value.GetArray();
1403 node.mat = glm::mat4(arr[0].GetFloat(), arr[1].GetFloat(), arr[2].GetFloat(), arr[3].GetFloat(),
1404 arr[4].GetFloat(), arr[5].GetFloat(), arr[6].GetFloat(), arr[7].GetFloat(),
1405 arr[8].GetFloat(), arr[9].GetFloat(), arr[10].GetFloat(), arr[11].GetFloat(),
1406 arr[12].GetFloat(), arr[13].GetFloat(), arr[14].GetFloat(), arr[15].GetFloat());
1408 glm::vec4 perspective;
1409 glm::decompose(node.mat, node.scale, node.rot, node.pos, skew, perspective);
1410 use_matrix_transform =
true;
1413 LOG_ERROR(logger,
"%.*s: JSON node matrix value is not an array with 16 elements", StringViewFormat(path));
1419 if (field.value.IsUint()) {
1420 node.mesh = field.value.GetUint();
1423 LOG_ERROR(logger,
"%.*s: JSON node mesh value is not an uint", StringViewFormat(path));
1429 if (field.value.IsUint()) {
1430 node.skin = field.value.GetUint();
1433 LOG_ERROR(logger,
"%.*s: JSON node skin value is not an uint", StringViewFormat(path));
1442 LOG_DEBUG(logger,
"Unknown node field: %s", field.name.GetString());
1447 if (!use_matrix_transform) {
1449 node.mat = glm::translate(ii, node.pos) * glm::mat4_cast(node.rot) * glm::scale(ii, node.scale);
1459 void parseAccessorMinArray(
GltfAccessor & accessor, Array arr)
1461 accessor.minCount = std::min(16u, arr.Size());
1462 for (uint32_t i = 0; i < accessor.minCount; i++) {
1463 if (arr[i].IsUint() || arr[i].IsInt()) {
1464 if (accessor.componentType == AccessorComponentType::Float) {
1465 accessor.min.s_float[i] = float(arr[i].GetInt());
1468 if (accessor.componentType == AccessorComponentType::UnsignedByte ||
1469 accessor.componentType == AccessorComponentType::UnsignedShort ||
1470 accessor.componentType == AccessorComponentType::UnsignedInt) {
1471 accessor.min.s_uint[i] = arr[i].GetUint();
1474 accessor.min.s_int[i] = arr[i].GetInt();
1479 if (accessor.componentType == AccessorComponentType::Float) {
1480 accessor.min.s_float[i] = arr[i].GetFloat();
1483 if (accessor.componentType == AccessorComponentType::UnsignedByte ||
1484 accessor.componentType == AccessorComponentType::UnsignedShort ||
1485 accessor.componentType == AccessorComponentType::UnsignedInt) {
1486 accessor.min.s_uint[i] = uint32_t(std::floor(arr[i].GetFloat()));
1489 accessor.min.s_int[i] = int32_t(std::floor(arr[i].GetFloat()));
1497 void parseAccessorMaxArray(
GltfAccessor& accessor, Array arr)
1499 accessor.maxCount = std::min(16u, arr.Size());
1500 for (uint32_t i = 0; i < accessor.maxCount; i++) {
1501 if (arr[i].IsInt() || arr[i].IsUint()) {
1502 if (accessor.componentType == AccessorComponentType::Float) {
1503 accessor.max.s_float[i] = float(arr[i].GetInt());
1506 if (accessor.componentType == AccessorComponentType::UnsignedByte ||
1507 accessor.componentType == AccessorComponentType::UnsignedShort ||
1508 accessor.componentType == AccessorComponentType::UnsignedInt) {
1509 accessor.max.s_uint[i] = arr[i].GetUint();
1512 accessor.max.s_int[i] = arr[i].GetInt();
1517 if (accessor.componentType == AccessorComponentType::Float) {
1518 accessor.max.s_float[i] = arr[i].GetFloat();
1521 if (accessor.componentType == AccessorComponentType::UnsignedByte ||
1522 accessor.componentType == AccessorComponentType::UnsignedShort ||
1523 accessor.componentType == AccessorComponentType::UnsignedInt) {
1524 accessor.max.s_uint[i] = uint32_t(std::ceil(arr[i].GetFloat()));
1527 accessor.max.s_int[i] = int32_t(std::ceil(arr[i].GetFloat()));
1536 assert(accessorsValue.IsArray());
1537 for (
const auto& accessorValue : accessorsValue.GetArray()) {
1538 if (!checkIsObject(loadData,
"accessor array item", accessorValue))
return false;
1540 GltfAccessor& accessor = loadData.accessors.emplace_back();
1541 for (
const auto& field : accessorValue.GetObject()) {
1542 switch (toView(field.name).
hash()) {
1544 if (!getUint(loadData, accessor.bufferView, field))
return false;
1548 if (!getUint(loadData, accessor.byteOffset, field))
return false;
1552 if (checkIsUint(loadData, field)) {
1553 switch (field.value.GetUint()) {
1554 case GLTF_BYTE: accessor.componentType = AccessorComponentType::Byte;
break;
1555 case GLTF_UNSIGNED_BYTE: accessor.componentType = AccessorComponentType::UnsignedByte;
break;
1556 case GLTF_SHORT: accessor.componentType = AccessorComponentType::Short;
break;
1557 case GLTF_UNSIGNED_SHORT: accessor.componentType = AccessorComponentType::UnsignedShort;
break;
1558 case GLTF_UNSIGNED_INT: accessor.componentType = AccessorComponentType::UnsignedInt;
break;
1559 case GLTF_FLOAT: accessor.componentType = AccessorComponentType::Float;
break;
1561 LOG_ERROR(logger,
"%.*s:n Unrecognized accessor componentType %u", StringViewFormat(path), field.value.GetUint());
1569 if (!getUint(loadData, accessor.count, field))
return false;
1573 if (checkIsString(loadData, field)) {
1574 switch (toView(field.value).
hash()) {
1575 case Cogs::hash(
"SCALAR"): accessor.type = AccessorType::Scalar;
break;
1576 case Cogs::hash(
"VEC2"): accessor.type = AccessorType::Vec2;
break;
1577 case Cogs::hash(
"VEC3"): accessor.type = AccessorType::Vec3;
break;
1578 case Cogs::hash(
"VEC4"): accessor.type = AccessorType::Vec4;
break;
1579 case Cogs::hash(
"MAT2"): accessor.type = AccessorType::Mat2;
break;
1580 case Cogs::hash(
"MAT3"): accessor.type = AccessorType::Mat3;
break;
1581 case Cogs::hash(
"MAT4"): accessor.type = AccessorType::Mat4;
break;
1583 LOG_ERROR(logger,
"%.*s: Unrecognized accessor type '%s'", StringViewFormat(path), field.value.GetString());
1591 if (checkIsArray(loadData, field)) {
1592 assert(field.value.IsArray());
1593 parseAccessorMinArray(accessor, field.value.GetArray());
1599 if (checkIsArray(loadData, field)) {
1600 assert(field.value.IsArray());
1601 parseAccessorMaxArray(accessor, field.value.GetArray());
1609 if (!getBool(loadData, accessor.normalized, field))
return false;
1617 LOG_WARNING_ONCE(logger,
"%.*s: Unknown accessor key '%s'", StringViewFormat(path), field.name.GetString());
1627 if (!skinsValue.IsArray()) {
1628 LOG_ERROR(logger,
"%.*s: Skins is not an array", StringViewFormat(path));
1631 for (
auto& skin_value : skinsValue.GetArray()) {
1632 auto& skin = loadData.skins.emplace_back();
1634 for (
const auto& field : skin_value.GetObject()) {
1635 if (field.name ==
"inverseBindMatrices")
1636 skin.inverseBindMatrices = field.value.GetUint();
1637 else if (field.name ==
"joints") {
1638 for (
auto& joint : field.value.GetArray()) skin.joints.push_back(joint.GetUint());
1640 else if (field.name ==
"skeleton")
1641 skin.skeleton = field.value.GetUint();
1642 else if (field.name ==
"name")
1643 skin.name = field.value.GetString();
1645 LOG_WARNING_ONCE(logger,
"Unknown skin field: %s", field.name.GetString());
1654 if (!animationsValue.IsArray()) {
1655 LOG_ERROR(logger,
"%.*s: Animations is not an array", StringViewFormat(path));
1658 for (
auto& animation_value : animationsValue.GetArray()) {
1659 auto& animation = loadData.animations.emplace_back();
1661 for (
const auto& field : animation_value.GetObject()) {
1662 if (field.name ==
"channels") {
1663 for (
auto& channel_value : field.value.GetArray()) {
1664 auto& channel = animation.channels.emplace_back();
1666 for (
auto& channel_field : channel_value.GetObject()) {
1667 if (channel_field.name ==
"sampler")
1668 channel.sampler = channel_field.value.GetUint();
1669 else if (channel_field.name ==
"target") {
1670 for (
auto& target : channel_field.value.GetObject()) {
1671 if (target.name ==
"node")
1672 channel.node = target.value.GetUint();
1673 else if (target.name ==
"path")
1674 channel.path = target.value.GetString();
1676 LOG_WARNING_ONCE(logger,
"Unknown animation channel target field: %s", target.name.GetString());
1681 LOG_WARNING_ONCE(logger,
"Unknown animation channel field: %s", channel_field.name.GetString());
1686 else if (field.name ==
"samplers") {
1687 for (
auto& sampler_value : field.value.GetArray()) {
1688 auto& sampler = animation.samplers.emplace_back();
1690 for (
auto& sampler_field : sampler_value.GetObject()) {
1691 if (sampler_field.name ==
"input")
1692 sampler.input = sampler_field.value.GetUint();
1693 else if (sampler_field.name ==
"interpolation")
1694 sampler.interpolation = sampler_field.value.GetString();
1695 else if (sampler_field.name ==
"output")
1696 sampler.output = sampler_field.value.GetUint();
1698 LOG_WARNING_ONCE(logger,
"Unknown animation sampler field: %s", sampler_field.name.GetString());
1703 else if (field.name ==
"name") {
1704 animation.name = field.value.GetString();
1707 LOG_WARNING_ONCE(logger,
"Unknown animation field: %s", field.name.GetString());
1715 uint32_t getAccessorTypeSize(
const GltfAccessor& accessor)
1717 assert(
size_t(accessor.componentType) <
size_t(AccessorComponentType::Count));
1718 assert(
size_t(accessor.type) <
size_t(AccessorType::Count));
1719 return accessorTypeSize[size_t(accessor.type)] * accessorComponentTypeSize[size_t(accessor.componentType)];
1724 if (loadData.buffer_views.size() <= accessor.bufferView) {
1725 LOG_ERROR(logger,
"%.*s: Illegal accessor buffer view index %u", StringViewFormat(loadData.path), accessor.bufferView);
1728 const GltfBufferView& bufferView = loadData.buffer_views[accessor.bufferView];
1730 if (loadData.buffer_data.size() <= bufferView.buffer) {
1731 LOG_ERROR(logger,
"%.*s: Illegal buffer view buffer index %u", StringViewFormat(loadData.path), bufferView.buffer);
1734 std::span<const uint8_t> buffer = loadData.buffer_data[bufferView.buffer];
1736 size_t byteOffset = size_t(accessor.byteOffset) + bufferView.byteOffset;
1737 uint32_t stride = bufferView.byteStride != 0u ? bufferView.byteStride : getAccessorTypeSize(accessor);
1739 if (buffer.size() < byteOffset +
size_t(stride) * accessor.count) {
1740 LOG_ERROR(logger,
"%.*s: Accessor references outside of buffer (bufferSize=%zd, idx=%zd)",
1741 StringViewFormat(loadData.path), buffer.size(), byteOffset +
size_t(stride) * accessor.count);
1746 .data = buffer.data() + byteOffset,
1748 .count = accessor.count
1758 const glm::mat4& parent_transform)
1760 const glm::mat4& localTransform = node.mat;
1761 const glm::mat4 globalTransform = parent_transform * localTransform;
1763 uint32_t boneIndex = (uint32_t)skeleton.bones.size();
1764 node.bone_index = boneIndex;
1766 auto& bone = skeleton.bones.emplace_back();
1767 bone.name = std::to_string(node.node_index) +
"_" + node.name;
1768 bone.parentBone = parent_bone;
1769 bone.pos = node.pos;
1770 bone.scale = node.scale;
1771 bone.rot = node.rot;
1772 bone.relative = localTransform;
1773 bone.absolute = globalTransform;
1774 bone.inverseBindPose = glm::mat4(1.0);
1775 bone.hasOffset =
false;
1777 bone.animated =
false;
1779 skeleton.bonesByName[bone.name] = boneIndex;
1781 for (uint32_t childIndex : node.children) {
1782 if (!loadBone(definition, definition.nodes[childIndex], skeleton, boneIndex, globalTransform)) {
1795 for (uint32_t childIndex : node.children) {
1796 if (!loadJoints(definition, definition.nodes[childIndex], skeleton, meshoptDecomp)) {
1801 if (node.skin != (uint32_t)-1) {
1802 skeleton.bindPose = glm::inverse(skeleton.bones[node.bone_index].absolute);
1803 GltfSkin& skin = definition.skins[node.skin];
1805 if (definition.accessors.size() <=
size_t(skin.inverseBindMatrices)) {
1809 size_t accessorIdx = size_t(skin.inverseBindMatrices);
1810 GltfAccessor accessor = definition.accessors[accessorIdx];
1813 if (meshoptDecomp.isCompressed(definition,
int(accessorIdx))) {
1814 bool ok = meshoptDecomp.decompress(definition,
int(accessorIdx));
1821 if (!getBufferViewData(definition, dataView, accessor)) {
1826 for (uint32_t jointIndex : skin.joints) {
1827 auto& joint = skeleton.bones[definition.nodes[jointIndex].bone_index];
1828 joint.inverseBindPose = *
reinterpret_cast<const glm::mat4*
>(dataView.data + dataView.stride * i++);
1829 joint.hasOffset =
true;
1839 if (loadData.accessors.size() <= accessorIx) {
1840 LOG_ERROR(logger,
"%.*s: Illegal access index %u", StringViewFormat(loadData.path), accessorIx);
1843 const GltfAccessor& accessor = loadData.accessors[accessorIx];
1845 if (!getBufferViewData(loadData, vertexStream.dataView, accessor)) {
1852 switch (key.hash()) {
1854 if (accessor.type == AccessorType::Vec3 &&
1855 (accessor.componentType == AccessorComponentType::Float ||
1856 accessor.componentType == AccessorComponentType::Byte ||
1857 accessor.componentType == AccessorComponentType::Short ||
1858 accessor.componentType == AccessorComponentType::UnsignedShort)) {
1859 streamsState.positionData = vertexStream.dataView;
1860 streamsState.positionPresent =
true;
1861 streamsState.positionCount = accessor.count;
1863 vertexStream.format = Cogs::DataFormat::R32G32B32_FLOAT;
1865 if (accessor.componentType != AccessorComponentType::Float) {
1868 vertexStream.dataView = convertVecBufferToFloats<glm::vec3>(vertexStream.dataView, accessor.componentType, &loadData.positionsScratch.back());
1870 streamsState.positionData = vertexStream.dataView;
1872 if (accessor.minCount == 3 && accessor.maxCount == 3) {
1873 if (accessor.componentType == AccessorComponentType::Float) {
1874 streamsState.bbox.min = glm::make_vec3(accessor.min.s_float);
1875 streamsState.bbox.max = glm::make_vec3(accessor.max.s_float);
1877 else if (accessor.componentType == AccessorComponentType::UnsignedShort) {
1878 streamsState.bbox.min = glm::make_vec3(accessor.min.s_uint);
1879 streamsState.bbox.max = glm::make_vec3(accessor.max.s_uint);
1882 streamsState.bbox.min = glm::make_vec3(accessor.min.s_int);
1883 streamsState.bbox.max = glm::make_vec3(accessor.max.s_int);
1887 LOG_WARNING_ONCE(logger,
"%.*s: POSITION attribute has missing or malformed min and max values", StringViewFormat(loadData.path));
1888 streamsState.bbox.min = glm::vec3(std::numeric_limits<float>::max());
1889 streamsState.bbox.max = -streamsState.bbox.min;
1890 for (
size_t i = 0; i < streamsState.positionCount; i++) {
1891 const glm::vec3& p = *(
const glm::vec3*)(streamsState.positionData.data + streamsState.positionData.stride * i);
1892 if (std::isfinite(p.x) && std::isfinite(p.y) && std::isfinite(p.z)) {
1893 streamsState.bbox.min = min(streamsState.bbox.min, p);
1894 streamsState.bbox.max = max(streamsState.bbox.max, p);
1901 LOG_ERROR(logger,
"%.*s: Illegal POSITION accessor type %s of %s", StringViewFormat(loadData.path), accessorTypeName[
size_t(accessor.type)], accessorComponentTypeName[
size_t(accessor.componentType)]);
1905 if (accessor.type == AccessorType::Vec3 &&
1906 (accessor.componentType == AccessorComponentType::Float ||
1907 accessor.componentType == AccessorComponentType::Byte ||
1908 accessor.componentType == AccessorComponentType::Short)) {
1909 if (accessor.componentType != AccessorComponentType::Float) {
1912 vertexStream.dataView = convertVecBufferToFloats<glm::vec3>(vertexStream.dataView, accessor.componentType, &loadData.normalsScratch.back());
1915 streamsState.normalData = vertexStream.dataView;
1916 streamsState.normalPresent =
true;
1918 vertexStream.format = Cogs::DataFormat::R32G32B32_FLOAT;
1922 LOG_ERROR(logger,
"%.*s: Illegal NORMAL accessor type %s of %s", StringViewFormat(loadData.path), accessorTypeName[
size_t(accessor.type)], accessorComponentTypeName[
size_t(accessor.componentType)]);
1926 if (accessor.type == AccessorType::Vec4 &&
1927 (accessor.componentType == AccessorComponentType::Float ||
1928 accessor.componentType == AccessorComponentType::Byte ||
1929 accessor.componentType == AccessorComponentType::Short)) {
1930 streamsState.tangentPresent =
true;
1932 if (accessor.componentType != AccessorComponentType::Float) {
1935 vertexStream.dataView = convertVecBufferToFloats<glm::vec4>(vertexStream.dataView, accessor.componentType, &loadData.tangentsScratch.back());
1937 vertexStream.format = Cogs::DataFormat::R32G32B32_FLOAT;
1941 LOG_ERROR(logger,
"%.*s: Illegal TANGENT accessor type %s of %s", StringViewFormat(loadData.path), accessorTypeName[
size_t(accessor.type)], accessorComponentTypeName[
size_t(accessor.componentType)]);
1949 if (
size_t underscore = key.find_first_of(
'_');
1951 underscore + 1 < key.length() &&
1952 '0' <= key[underscore + 1] &&
1953 key[underscore + 1] <=
'9')
1955 vertexStream.semanticIndex = 0;
1956 size_t digit = underscore + 1;
1957 for (; digit < key.size() &&
'0' <= key[digit] && key[digit] <=
'9'; digit++) {
1958 vertexStream.semanticIndex = 10 * vertexStream.semanticIndex + uint32_t(key[digit] -
'0');
1961 if (digit == key.length()) {
1962 switch (key.substr(0, underscore).hash()) {
1964 if (vertexStream.semanticIndex == 0) {
1965 if (accessor.componentType != AccessorComponentType::Float) {
1968 vertexStream.dataView = convertVecBufferToFloats<glm::vec2>(vertexStream.dataView, accessor.componentType, &loadData.texCoordsScratch.back());
1973 if (loadData.textureTransforms.size()) {
1974 const TextureTransform& textureTransform = loadData.textureTransforms.back();
1975 float* buf =
reinterpret_cast<float*
>(
const_cast<uint8_t*
>(vertexStream.dataView.data));
1976 size_t num = vertexStream.dataView.count;
1977 const glm::mat3 translation = glm::mat3(1.0, 0, 0, 0, 1.0, 0, textureTransform.offset.x, textureTransform.offset.y, 1.0);
1978 const glm::mat3 rotation = glm::mat3(cos(textureTransform.rotation), sin(textureTransform.rotation), 0,
1979 -sin(textureTransform.rotation), cos(textureTransform.rotation), 0,
1981 const glm::mat3 scale = glm::mat3(textureTransform.scale.x, 0, 0, 0, textureTransform.scale.y, 0, 0, 0, 1.0);
1982 const glm::mat3 xform = translation * rotation * scale;
1984 for (
size_t i = 0; i < num; ++i) {
1985 const glm::vec3 result = xform * glm::vec3(buf[i * 2], buf[i * 2 + 1], 1.0);
1986 buf[i * 2] = result.x;
1987 buf[i * 2 + 1] = result.y;
1991 streamsState.texcoordData = vertexStream.dataView;
1992 streamsState.texcoordStreamCount++;
1994 vertexStream.format = Cogs::DataFormat::R32G32_FLOAT;
1998 streamsState.colorStreamCount++;
2000 if (accessor.type == AccessorType::Vec3) {
2001 switch (accessor.componentType) {
2002 case AccessorComponentType::Float: vertexStream.format = Cogs::DataFormat::R32G32B32_FLOAT;
return true;
2003 case AccessorComponentType::UnsignedByte: vertexStream.format = Cogs::DataFormat::R8G8B8_UNORM;
return true;
2004 case AccessorComponentType::UnsignedShort: vertexStream.format = Cogs::DataFormat::R16G16B16_UNORM;
return true;
2008 else if (accessor.type == AccessorType::Vec4) {
2009 switch (accessor.componentType) {
2010 case AccessorComponentType::Float: vertexStream.format = Cogs::DataFormat::R32G32B32A32_FLOAT;
return true;
2011 case AccessorComponentType::UnsignedByte: vertexStream.format = Cogs::DataFormat::R8G8B8A8_UNORM;
return true;
2012 case AccessorComponentType::UnsignedShort: vertexStream.format = Cogs::DataFormat::R16G16B16A16_UNORM;
return true;
2016 LOG_ERROR(logger,
"%.*s: Illegal COLOR accessor type %s of %s", StringViewFormat(loadData.path), accessorTypeName[
size_t(accessor.type)], accessorComponentTypeName[
size_t(accessor.componentType)]);
2020 if (vertexStream.semanticIndex == 0) {
2021 streamsState.jointsStreamCount = 1;
2023 vertexStream.semanticIndex = 4;
2024 if (accessor.type == AccessorType::Vec4) {
2025 switch (accessor.componentType) {
2026 case AccessorComponentType::UnsignedByte: vertexStream.format = Cogs::DataFormat::R8G8B8A8_UINT;
return true;
2027 case AccessorComponentType::UnsignedShort: vertexStream.format = Cogs::DataFormat::R16G16B16A16_UINT;
return true;
2028 case AccessorComponentType::Float: vertexStream.format = Cogs::DataFormat::R32G32B32A32_FLOAT;
return true;
2032 LOG_ERROR(logger,
"%.*s: Illegal JOINTS accessor type %s of %s", StringViewFormat(loadData.path), accessorTypeName[
size_t(accessor.type)], accessorComponentTypeName[
size_t(accessor.componentType)]);
2038 if (vertexStream.semanticIndex == 0) {
2040 vertexStream.semanticIndex = 5;
2041 if (accessor.type == AccessorType::Vec4) {
2042 switch (accessor.componentType) {
2043 case AccessorComponentType::Float: vertexStream.format = Cogs::DataFormat::R32G32B32A32_FLOAT;
return true;
2044 case AccessorComponentType::UnsignedByte: vertexStream.format = Cogs::DataFormat::R8G8B8A8_UNORM;
return true;
2045 case AccessorComponentType::UnsignedShort: vertexStream.format = Cogs::DataFormat::R16G16B16A16_UNORM;
return true;
2049 LOG_ERROR(logger,
"%.*s: Illegal WEIGHTS accessor type %s of %s", StringViewFormat(loadData.path), accessorTypeName[
size_t(accessor.type)], accessorComponentTypeName[
size_t(accessor.componentType)]);
2062 LOG_WARNING_ONCE(logger,
"Unrecognized attribute semantic %.*s", StringViewFormat(key));
2068 for (
const auto& attributeIt : attributeObject) {
2070 uint32_t accessorIx = 0;
2071 if (!getUint(loadData, accessorIx, attributeIt)) {
2076 if (meshoptDecomp.isCompressed(loadData, accessorIx)) {
2077 meshoptDecomp.decompress(loadData, accessorIx);
2082 if (getVertexElement(loadData, streamsState, vertexStream, toView(attributeIt.name), accessorIx)) {
2083 vertexStreams.push_back(vertexStream);
2087 if (!streamsState.positionPresent) {
2088 LOG_ERROR(logger,
"%.*s: Primitive has no POSITION attribute", StringViewFormat(loadData.path));
2092 for (
const auto& stream : vertexStreams) {
2093 if (stream.dataView.count != streamsState.positionCount) {
2094 LOG_ERROR(logger,
"%.*s: Primitive has attributes with variable counts", StringViewFormat(loadData.path));
2105 LOG_ERROR(logger,
"%.*s: Vertex tangent generator only supports triangle lists (type is %d)", StringViewFormat(loadData.path), primitiveType);
2108 assert(streamsState.positionPresent);
2109 assert(streamsState.normalPresent);
2110 assert(streamsState.texcoordStreamCount);
2112 const size_t byteSize =
sizeof(glm::vec3) * streamsState.positionCount;
2116 memBuf->resize(byteSize);
2117 glm::vec3* tangents = (glm::vec3*) memBuf->data();
2118 std::memset(
static_cast<void*
>(tangents), 0, byteSize);
2120 size_t count = streamsState.indexType != AccessorComponentType::None ? streamsState.indexData.count : streamsState.positionCount;
2121 for (
size_t i = 0; i + 2 < count; i += 3) {
2122 size_t ix[3] = { 0,0,0 };
2123 switch (streamsState.indexType) {
2124 case AccessorComponentType::None:
for (
size_t k = 0; k < 3; k++) { ix[k] = i + k; }
break;
2125 case AccessorComponentType::UnsignedByte:
for (
size_t k = 0; k < 3; k++) { ix[k] = *(streamsState.indexData.data + streamsState.indexData.stride * (i + k)); }
break;
2126 case AccessorComponentType::UnsignedShort:
for (
size_t k = 0; k < 3; k++) { ix[k] = *(
const uint16_t*)(streamsState.indexData.data + streamsState.indexData.stride * (i + k)); }
break;
2127 case AccessorComponentType::UnsignedInt:
for (
size_t k = 0; k < 3; k++) { ix[k] = *(
const uint32_t*)(streamsState.indexData.data + streamsState.indexData.stride * (i + k)); }
break;
2128 default: assert(
false);
return false;
2131 glm::vec3 a = *(
const glm::vec3*)(streamsState.positionData.data + streamsState.positionData.stride * ix[0]);
2132 glm::vec3 b = *(
const glm::vec3*)(streamsState.positionData.data + streamsState.positionData.stride * ix[1]);
2133 glm::vec3 c = *(
const glm::vec3*)(streamsState.positionData.data + streamsState.positionData.stride * ix[2]);
2134 glm::vec2 ta = *(
const glm::vec2*)(streamsState.texcoordData.data + streamsState.texcoordData.stride * ix[0]);
2135 glm::vec2 tb = *(
const glm::vec2*)(streamsState.texcoordData.data + streamsState.texcoordData.stride * ix[1]);
2136 glm::vec2 tc = *(
const glm::vec2*)(streamsState.texcoordData.data + streamsState.texcoordData.stride * ix[2]);
2153 float den = 1.f / (u0.x * u1.y - u0.y * u1.x);
2154 glm::vec3 t0 = u1.y * den * v0 - u0.y * den * v1;
2156 tangents[ix[0]] += t0;
2157 tangents[ix[1]] += t0;
2158 tangents[ix[2]] += t0;
2161 for (
size_t i = 0; i < streamsState.positionCount; i++) {
2162 const glm::vec3 normal = *(
const glm::vec3*)(streamsState.normalData.data + streamsState.normalData.stride * i);
2163 tangents[i] = glm::normalize(tangents[i] - glm::dot(normal, tangents[i]) * normal);
2168 .data = (
const uint8_t*) memBuf->data(),
2169 .stride =
sizeof(glm::vec3),
2170 .count = streamsState.positionCount
2172 .format = Cogs::DataFormat::R32G32B32_FLOAT,
2183 LOG_ERROR(logger,
"%.*s: Vertex normal generator only supports triangle lists", StringViewFormat(loadData.path));
2186 assert(streamsState.positionPresent);
2188 const size_t byteSize =
sizeof(glm::vec3) * streamsState.positionCount;
2192 memBuf->resize(byteSize);
2193 glm::vec3* normals = (glm::vec3*) memBuf->data();
2194 std::memset(
static_cast<void*
>(normals), 0, byteSize);
2196 size_t count = streamsState.indexSize ? streamsState.indexData.count : streamsState.positionCount;
2197 for (
size_t i = 0; i + 2 < count; i += 3) {
2198 size_t ix[3] = { 0,0,0 };
2199 switch (streamsState.indexSize) {
2200 case 0:
for (
size_t k = 0; k < 3; k++) { ix[k] = i + k; }
break;
2201 case 1:
for (
size_t k = 0; k < 3; k++) { ix[k] = *(streamsState.indexData.data + streamsState.indexData.stride * (i + k)); }
break;
2202 case 2:
for (
size_t k = 0; k < 3; k++) { ix[k] = *(
const uint16_t*)(streamsState.indexData.data + streamsState.indexData.stride * (i + k)); }
break;
2203 case 4:
for (
size_t k = 0; k < 3; k++) { ix[k] = *(
const uint32_t*)(streamsState.indexData.data + streamsState.indexData.stride * (i + k)); }
break;
2204 default: assert(
false);
return false;
2206 glm::vec3 a = *(
const glm::vec3*)(streamsState.positionData.data + streamsState.positionData.stride * ix[0]);
2207 glm::vec3 b = *(
const glm::vec3*)(streamsState.positionData.data + streamsState.positionData.stride * ix[1]);
2208 glm::vec3 c = *(
const glm::vec3*)(streamsState.positionData.data + streamsState.positionData.stride * ix[2]);
2210 const glm::vec3 l1 = clockwise ? a - b : b - a;
2211 const glm::vec3 l2 = clockwise ? a - c : c - a;
2212 glm::vec3 n = glm::cross(l1, l2);
2214 if (glm::length(n) <= 0.0) {
2215 LOG_WARNING_ONCE(logger,
"Calculated normal is not valid (normalIdx=%zu)", i);
2216 n = glm::vec3(1, 0, 0);
2219 normals[ix[0]] += n;
2220 normals[ix[1]] += n;
2221 normals[ix[2]] += n;
2224 for (
size_t i = 0; i < streamsState.positionCount; i++) {
2225 normals[i] = glm::normalize(normals[i]);
2230 .data = (
const uint8_t*) normals,
2231 .stride =
sizeof(glm::vec3),
2232 .count = streamsState.positionCount
2234 .format = Cogs::DataFormat::R32G32B32_FLOAT,
2244 uint32_t dstStride = 0;
2245 switch (streamsState.indexType) {
2247 case AccessorComponentType::UnsignedByte: dstStride = 2; streamsState.indexSize = 1;
break;
2248 case AccessorComponentType::UnsignedShort: dstStride = 2; streamsState.indexSize = dstStride;
break;
2249 case AccessorComponentType::UnsignedInt: dstStride = 4; streamsState.indexSize = dstStride;
break;
2251 LOG_ERROR(logger,
"%.*s: Illegal index data component type %s", StringViewFormat(loadData.path), accessorComponentTypeName[
size_t(streamsState.indexType)]);
2255 if (uint8_t* dst = mesh->mapStream(VertexDataType::Indexes, 0, streamsState.indexData.count, dstStride,
true); dst) {
2256 switch (streamsState.indexType) {
2257 case AccessorComponentType::UnsignedByte: stridedCopy<1>(dst, 2, streamsState.indexData.data, streamsState.indexData.stride, streamsState.indexData.count);
break;
2258 case AccessorComponentType::UnsignedShort: stridedCopy<2>(dst, 2, streamsState.indexData.data, streamsState.indexData.stride, streamsState.indexData.count);
break;
2259 case AccessorComponentType::UnsignedInt: stridedCopy<4>(dst, 4, streamsState.indexData.data, streamsState.indexData.stride, streamsState.indexData.count);
break;
2260 default: assert(
false);
break;
2262 mesh->unmap(VertexDataType::Indexes);
2266 mesh->setCount(streamsState.indexData.count);
2276 const Cogs::FormatInfo* formatInfo = Cogs::getFormatInfo(vertexStream.format);
2279 vertexStream.formatSize = formatInfo->
blockSize;
2283 std::vector<Cogs::VertexElement> vertexElements;
2285 offset = (offset + 3) & ~3;
2286 vertexStream.offset = offset;
2288 .
offset = uint16_t(offset),
2289 .format = vertexStream.format,
2290 .semantic = vertexStream.semantic,
2291 .semanticIndex = uint16_t(vertexStream.semanticIndex),
2295 offset += vertexStream.formatSize;
2298 vertexFormat = Cogs::VertexFormats::createVertexFormat(vertexElements.data(), vertexElements.size());
2300 LOG_ERROR(logger,
"%.*s: Failed to create vertex format with %zu elements", StringViewFormat(loadData.path), vertexElements.size());
2305 size_t stride = Cogs::getSize(vertexFormat);
2306 if (uint8_t* base = mesh->mapStream(VertexDataType::Interleaved0, vertexFormat, 0, streamsState.positionCount, stride,
true)) {
2307 for (
const VertexStream& vertexStream : vertexStreams) {
2308 uint8_t* dst = base + vertexStream.offset;
2309 switch (vertexStream.format) {
2310 case Cogs::DataFormat::R32G32B32A32_FLOAT:
2311 assert(vertexStream.formatSize == 16);
2312 stridedCopy<16>(dst, stride, vertexStream.dataView.data, vertexStream.dataView.stride, streamsState.positionCount);
2315 case Cogs::DataFormat::R32G32B32_FLOAT:
2316 assert(vertexStream.formatSize == 12);
2317 stridedCopy<12>(dst, stride, vertexStream.dataView.data, vertexStream.dataView.stride, streamsState.positionCount);
2320 case Cogs::DataFormat::R32G32_FLOAT:
2321 case Cogs::DataFormat::R16G16B16A16_UNORM:
2322 case Cogs::DataFormat::R16G16B16A16_UINT:
2323 assert(vertexStream.formatSize == 8);
2324 stridedCopy<8>(dst, stride, vertexStream.dataView.data, vertexStream.dataView.stride, streamsState.positionCount);
2327 case Cogs::DataFormat::R16G16B16_UNORM:
2328 case Cogs::DataFormat::R16G16B16_UINT:
2329 case Cogs::DataFormat::R16G16B16_SINT:
2330 case Cogs::DataFormat::R16G16B16_SNORM:
2331 assert(vertexStream.formatSize == 6);
2332 stridedCopy<6>(dst, stride, vertexStream.dataView.data, vertexStream.dataView.stride, streamsState.positionCount);
2335 case Cogs::DataFormat::R16G16_UNORM:
2336 case Cogs::DataFormat::R16G16_UINT:
2337 case Cogs::DataFormat::R16G16_SNORM:
2338 case Cogs::DataFormat::R16G16_SINT:
2339 case Cogs::DataFormat::R8G8B8A8_UNORM:
2340 case Cogs::DataFormat::R8G8B8A8_UINT:
2341 assert(vertexStream.formatSize == 4);
2342 stridedCopy<4>(dst, stride, vertexStream.dataView.data, vertexStream.dataView.stride, streamsState.positionCount);
2345 case Cogs::DataFormat::R8G8B8_UNORM:
2346 case Cogs::DataFormat::R8G8B8_UINT:
2347 case Cogs::DataFormat::R8G8B8_SINT:
2348 case Cogs::DataFormat::R8G8B8_SNORM:
2349 assert(vertexStream.formatSize == 3);
2350 stridedCopy<3>(dst, stride, vertexStream.dataView.data, vertexStream.dataView.stride, streamsState.positionCount);
2353 case Cogs::DataFormat::R8G8_UNORM:
2354 assert(vertexStream.formatSize == 3);
2355 stridedCopy<2>(dst, stride, vertexStream.dataView.data, vertexStream.dataView.stride, streamsState.positionCount);
2359 assert(
false &&
"Unhandled DataFormat");
2363 mesh->unmap(VertexDataType::Interleaved0);
2371 std::vector<VertexStream> vertexStreams;
2374 bool needsNormals =
true;
2375 if (
auto materialIt = primitiveObject.FindMember(
"material"); materialIt != primitiveObject.MemberEnd()) {
2376 uint32_t materialIx = 0;
2377 if (!getUint(loadData, materialIx, *materialIt)) {
2378 LOG_ERROR(logger,
"The 'meshes.primitives.material' is not an integer");
2381 if (materialIx >= 0) {
2382 const Object& materialObject = loadData.materialsArray[materialIx];
2383 if (materialObject.HasMember(
"extensions") && materialObject[
"extensions"].HasMember(
"KHR_materials_unlit")) {
2384 needsNormals =
false;
2390 bool useDracoDecompression =
false;
2394 if (primitiveObject.HasMember(
"extensions") && primitiveObject[
"extensions"].HasMember(
"KHR_draco_mesh_compression")) {
2395 auto extSection = primitiveObject[
"extensions"].GetObject();
2396 auto dracoSection = extSection[
"KHR_draco_mesh_compression"].GetObject();
2397 draco.initAttributes(dracoSection);
2398 useDracoDecompression =
true;
2401 if (
const auto& attributesIt = primitiveObject.FindMember(
"attributes"); attributesIt != primitiveObject.MemberEnd()) {
2402 if (!checkIsObject(loadData, *attributesIt)) {
2406 if (useDracoDecompression) {
2407 if (!draco.decompress(loadData, streamsState, vertexStreams, attributesIt->value.GetObject(), !needsNormals)) {
2412 if (!getVertexAttributes(loadData, streamsState, vertexStreams, attributesIt->value.GetObject(), meshoptDecomp)) {
2418 LOG_ERROR(logger,
"%.*s: Primitive has no attribute property", StringViewFormat(loadData.path));
2423 bool isIndexed =
false;
2424 uint32_t indicesAccessorIx = 0;
2426 if (useDracoDecompression) {
2430 if (
auto indicesIt = primitiveObject.FindMember(
"indices"); indicesIt != primitiveObject.MemberEnd()) {
2431 if (!getUint(loadData, indicesAccessorIx, *indicesIt)) {
2432 LOG_ERROR(logger,
"The 'meshes.primitives.indices' is not an integer");
2436 if (loadData.accessors.size() <= indicesAccessorIx) {
2437 LOG_ERROR(logger,
"%.*s: Illegal accessor index %u", StringViewFormat(loadData.path), indicesAccessorIx);
2441 const GltfAccessor& accessor = loadData.accessors[indicesAccessorIx];
2442 streamsState.indexType = accessor.componentType;
2444 if (meshoptDecomp.isCompressed(loadData, indicesAccessorIx)) {
2445 bool ok = meshoptDecomp.decompress(loadData, indicesAccessorIx);
2451 if (!getBufferViewData(loadData, streamsState.indexData, accessor)) {
2460 if (
auto modeIt = primitiveObject.FindMember(
"mode"); modeIt != primitiveObject.MemberEnd()) {
2462 if (!getUint(loadData, mode, *modeIt)) {
2472 case 2: LOG_ERROR(logger,
"%.*s: Unsupported primitive mode 2: LINE_LOOP", StringViewFormat(loadData.path));
return false;
2473 case 6: LOG_ERROR(logger,
"%.*s: Unsupported primitive mode 6: TRIANGLE_FAN", StringViewFormat(loadData.path));
return false;
2474 default: LOG_ERROR(logger,
"%.*s: Illegal primitive mode %u", StringViewFormat(loadData.path), mode);
return false;
2480 if (
auto targetsIt = primitiveObject.FindMember(
"targets"); targetsIt != primitiveObject.MemberEnd()) {
2481 if (!checkIsArray(loadData, *targetsIt)) {
2484 if (!targetsIt->value.GetArray().Empty()) {
2486 LOG_WARNING_ONCE(logger,
"%.*s: Morph targets not implemented yet, ignoring.", StringViewFormat(loadData.path));
2491 bool isSkinned = streamsState.jointsStreamCount != 0;
2492 bool albedoPerVertex = streamsState.colorStreamCount != 0;
2494 if (
auto materialIt = primitiveObject.FindMember(
"material"); materialIt != primitiveObject.MemberEnd()) {
2495 uint32_t materialIx = 0;
2496 if (!getUint(loadData, materialIx, *materialIt)) {
2497 LOG_ERROR(logger,
"The 'meshes.primitives.material' is not an integer");
2500 if (!getMaterialHandle(loadData, model, modelMaterial, materialIx, isSkinned, albedoPerVertex,
false)) {
2505 if (!getMaterialHandle(loadData, model, modelMaterial, -1, isSkinned, albedoPerVertex,
false)) {
2509 assert(modelMaterial);
2510 modelMeshInstance.materialIx = modelMaterial->modelMaterialIx;
2513 if (streamsState.texcoordStreamCount < modelMaterial->texCoordSets) {
2514 LOG_ERROR(logger,
"%.*s: Material requires texture coordinates, something which the mesh doesn't have", StringViewFormat(loadData.path));
2519 if (modelMaterial->needsNormals && !streamsState.normalPresent) {
2520 if (!generateVertexNormals(loadData, streamsState, vertexStreams, primitiveType, clockwise)) {
2526 if (modelMaterial->needsTangents && !streamsState.tangentPresent) {
2527 if (!generateVertexTangents(loadData, streamsState, vertexStreams, primitiveType, clockwise)) {
2533 if (loadData.optimizationLevel >= 1) {
2534 std::vector<VertexStream> culledVertexStreams;
2536 for (
auto& stream : vertexStreams) {
2538 switch (stream.semantic) {
2540 keep = stream.semanticIndex == 0;
2543 keep = modelMaterial->needsNormals ? stream.semanticIndex == 0 :
false;
2546 if (stream.semanticIndex == 4 || stream.semanticIndex == 5) {
2550 keep = albedoPerVertex && stream.semanticIndex == 0;
2554 keep = stream.semanticIndex < modelMaterial->texCoordSets;
2557 keep = stream.semanticIndex == 0;
2563 culledVertexStreams.emplace_back(stream);
2566 debugMessageOnce(loadData,
"Culled unused vertex stream with semantic=%u:%u",
unsigned(stream.semantic), stream.semanticIndex);
2569 vertexStreams = std::move(culledVertexStreams);
2574 size_t N = vertexStreams.size();
2575 std::vector<VertexStream> sortedStreams(N);
2576 for (
const auto& stream : vertexStreams) {
2579 (
unsigned(stream.semantic) <
unsigned(sortedStreams[i - 1].semantic)) ||
2580 ((
unsigned(stream.semantic) ==
unsigned(sortedStreams[i - 1].semantic) && (stream.semanticIndex < sortedStreams[i - 1].semanticIndex)))))
2582 sortedStreams[i] = sortedStreams[i - 1];
2585 sortedStreams[i] = stream;
2587 vertexStreams = std::move(sortedStreams);
2595 clockwise ? uint32_t(1) : uint32_t(0),
2596 isIndexed ? uint32_t(~0) : indicesAccessorIx);
2597 for (
const VertexStream& vertexStream : vertexStreams) {
2598 meshHash =
hash(vertexStream, meshHash);
2601 if (
auto it = loadData.modelMeshCache.find(meshHash); it != loadData.modelMeshCache.end()) {
2602 debugMessageOnce(loadData,
"Found duplicate primitive description, recycling mesh");
2603 modelMeshInstance.mesh = it->second;
2608 modelMeshInstance.mesh.meshIx = uint32_t(model.meshes.size());
2609 model.meshes.push_back(mesh.getHandle());
2610 if (!setVertexAttributes(loadData, mesh, streamsState, vertexStreams)) {
2619 mesh->setMeshFlag(MeshFlags::Skinned);
2622 mesh->primitiveType = primitiveType;
2623 mesh->setBounds(streamsState.bbox);
2624 modelMeshInstance.mesh.bboxIx = uint32_t(model.bounds.size());
2625 model.bounds.push_back(streamsState.bbox);
2628 if (!setMeshIndices(loadData, mesh, streamsState)) {
2633 mesh->setCount(streamsState.positionCount);
2635 modelMeshInstance.mesh.vertexCount = mesh->getCount();
2640 assert(
size_t(skinIx) < loadData.skins.size());
2641 const GltfSkin& skin = loadData.skins[skinIx];
2642 size_t count = skin.joints.size();
2643 if (kMaxBones < count) {
2644 LOG_WARNING_ONCE(logger,
"%.*s: Unable to handle bone count %zu over %zu", StringViewFormat(loadData.path), count, kMaxBones);
2648 std::span<uint32_t> poseIndexes = mesh->mapPoseIndexes(
static_cast<uint32_t
>(count));
2649 for (
size_t i = 0; i < count; i++) {
2650 uint32_t jointIx = skin.joints[i];
2651 poseIndexes[i] = loadData.nodes[jointIx].bone_index;
2655 loadData.modelMeshCache[meshHash] = modelMeshInstance.mesh;
2661 [[nodiscard]]
bool buildMeshes(
GltfModelDefinition& loadData, std::vector<ModelMeshInstance>& modelMeshInstances,
Model& model, int32_t skinIx, int32_t gltfMeshIx,
bool clockwise,
MeshoptDecompressor & meshoptDecomp)
2663 if (loadData.meshesArray.size() <
size_t(gltfMeshIx)) {
2664 LOG_ERROR(logger,
"%.*s: Illegal gltf mesh index %u", StringViewFormat(loadData.path), gltfMeshIx);
2668 const Object& meshObject = loadData.meshesArray[gltfMeshIx];
2670 if (
auto primitivesIt = meshObject.FindMember(
"primitives"); primitivesIt != meshObject.MemberEnd()) {
2671 if (!checkIsArray(loadData, *primitivesIt)) {
2675 const Array& primitivesArray = primitivesIt->value.GetArray();
2676 for (
auto& primitiveItem : primitivesArray) {
2677 if (!checkIsObject(loadData,
"mesh primitive item", primitiveItem)) {
2680 if (!buildPrimitiveMesh(loadData, modelMeshInstances.emplace_back(), model, primitiveItem.GetObject(), skinIx, clockwise, meshoptDecomp)) {
2686 LOG_ERROR(logger,
"%.*s: Mesh %u has no primitives property", StringViewFormat(loadData.path), gltfMeshIx);
2693 bool loadNode(
Context* context,
2697 uint32_t parent_part,
2698 const glm::mat4& parent_transform,
2701 const bool clockwise = glm::determinant(node.mat) < 0.0f;
2702 const glm::mat4& localTransform = node.mat;
2703 const glm::mat4 globalTransform = parent_transform * localTransform;
2705 std::string nodeName = std::to_string(node.node_index) +
"_" + node.name;
2706 uint32_t partIndex = (uint32_t)model.parts.size();
2707 node.part_index = partIndex;
2709 ModelPart& nodePart = model.parts.emplace_back();
2710 nodePart.parentIndex = parent_part;
2711 model.setPartTransform(nodePart, localTransform);
2712 model.setPartName(nodePart, nodeName);
2714 if (node.mesh != (uint32_t)-1) {
2715 std::vector<ModelMeshInstance> meshes;
2716 if (!buildMeshes(definition, meshes, model, node.skin, node.mesh, clockwise, meshoptDecomp)) {
2721 if (meshes.size() == 1) {
2722 nodePart.meshIndex = meshes[0].mesh.meshIx;
2723 nodePart.materialIndex = meshes[0].materialIx;
2724 nodePart.boundsIndex = meshes[0].mesh.bboxIx;
2725 nodePart.vertexCount = meshes[0].mesh.vertexCount;
2729 for (
auto& instance : meshes) {
2730 ModelPart& part = model.parts.emplace_back();
2731 part.parentIndex = partIndex;
2732 part.meshIndex = instance.mesh.meshIx;
2733 part.materialIndex = instance.materialIx;
2734 part.boundsIndex = instance.mesh.bboxIx;
2735 part.vertexCount = instance.mesh.vertexCount;
2740 for (uint32_t child_index : node.children) {
2741 if (!loadNode(context, definition, definition.nodes[child_index], model, partIndex, globalTransform, meshoptDecomp)) {
2751 if (definition.animations.size()) model.animation = context->animationManager->create();
2753 auto& animation = *model.animation.
resolve();
2754 auto& skeleton = model.skeleton;
2756 for (
auto& animationValue : definition.animations) {
2757 auto& clip = animation.clips.emplace_back();
2759 clip.name = animationValue.name;
2760 clip.duration = 0.0f;
2761 clip.resolution = 1.0f;
2763 uint32_t prev_node = (
decltype(prev_node))-1;
2764 for (uint32_t i = 0; i < animationValue.channels.size(); i++) {
2765 GltfChannel& channel = animationValue.channels[i];
2766 GltfSampler& sampler = animationValue.samplers[channel.sampler];
2767 uint32_t nodeIndex = channel.node;
2768 GltfNode& node = definition.nodes[nodeIndex];
2769 uint32_t boneIndex = node.bone_index;
2771 if (sampler.interpolation !=
"" && sampler.interpolation !=
"LINEAR")
2772 LOG_ERROR(logger,
"Unable to handle animation interpolation %s", sampler.interpolation.c_str());
2774 auto& inputAccessor = definition.accessors[sampler.input];
2777 if (meshoptDecomp.isCompressed(definition, sampler.input)) {
2778 bool ok = meshoptDecomp.decompress(definition, sampler.input);
2785 if (!getBufferViewData(definition, dataView, inputAccessor)) {
2789 if (!dataView.data || !dataView.count) {
2793 float t_max = inputAccessor.max.s_float[0];
2795 clip.duration = std::max(t_max, clip.duration);
2797 if (prev_node != nodeIndex) {
2798 clip.tracks.push_back({});
2800 prev_node = nodeIndex;
2801 auto& track = clip.tracks.back();
2802 track.boneIndex = boneIndex;
2804 skeleton.bones[boneIndex].animated =
true;
2806 auto getTime = [](uint32_t i,
const uint8_t* iData,
const size_t iStride) {
2807 return *
reinterpret_cast<const float*
>(iData + iStride * i);
2810 if (definition.accessors.size() <= sampler.output) {
2814 const GltfAccessor& outputAccessor = definition.accessors[sampler.output];
2817 if (meshoptDecomp.isCompressed(definition, sampler.output)) {
2818 bool ok = meshoptDecomp.decompress(definition, sampler.output);
2825 if (!getBufferViewData(definition, oDataView, outputAccessor)) {
2829 if (channel.path ==
"translation") {
2830 assert(outputAccessor.type == AccessorType::Vec3);
2831 track.translations.resize(dataView.count);
2833 for (uint32_t j = 0; j < dataView.count; j++) {
2834 const float time = getTime(j, dataView.data, dataView.stride);
2835 glm::vec3 translation = *
reinterpret_cast<const glm::vec3*
>(oDataView.data + oDataView.stride * j);
2839 else if (channel.path ==
"scale") {
2840 assert(outputAccessor.type == AccessorType::Vec3);
2841 track.scales.resize(dataView.count);
2843 for (uint32_t j = 0; j < dataView.count; j++) {
2844 const float time = getTime(j, dataView.data, dataView.stride);
2845 glm::vec3 scale = *
reinterpret_cast<const glm::vec3*
>(oDataView.data + oDataView.stride * j);
2846 track.scales[j] =
ScaleKey{ time, scale };
2849 else if (channel.path ==
"rotation") {
2850 assert(outputAccessor.type == AccessorType::Vec4);
2851 track.rotations.resize(dataView.count);
2853 if (outputAccessor.componentType == AccessorComponentType::Float) {
2854 for (uint32_t j = 0; j < dataView.count; j++) {
2855 const float time = getTime(j, dataView.data, dataView.stride);
2856 glm::vec4 rot = *
reinterpret_cast<const glm::vec4*
>(oDataView.data + oDataView.stride * j);
2857 track.rotations[j] =
RotationKey{ time, glm::quat(rot.w, rot.x, rot.y, rot.z) };
2862 LOG_ERROR(logger,
"Unknown animation channel path: %s", channel.path.c_str());
2866 if (model.animation) {
2867 model.animation->skeleton = model.skeleton;
2878 if (
Variable* enable = context->
variables->get(enableVariableName); !enable->isEmpty() && enable->getBool() ==
false) {
2885 if (resourcePath.starts_with(
"http")) {
2886 size_t paramPos = resourcePath.find(
'?');
2887 if (paramPos != std::string::npos) {
2888 resourcePath = resourcePath.substr(0, paramPos);
2892 auto extension = Cogs::IO::extension(resourcePath);
2893 if (extension.starts_with(
".gltf"))
return true;
2894 if (extension.starts_with(
".glb"))
return true;
2895 if (extension.starts_with(
".b3dm"))
return true;
2912 uint32_t featureTableJSONLength;
2913 uint32_t featureTableBinaryLength;
2914 uint32_t batchTableJSONLength;
2915 uint32_t batchTableBinaryLength;
2918 const uint32_t b3dmMagic = uint32_t(
'b') + uint32_t(
'3' << 8) + uint32_t(
'd' << 16) + uint32_t(
'm' << 24);
2919 B3DMHeader* header =
reinterpret_cast<B3DMHeader*
>(content);
2921 if (header->magic != b3dmMagic) {
2925 assert(header->length <= size &&
"Invalid length value in header");
2927 if (header->version != 1) {
2928 LOG_ERROR(logger,
"The B3DM file is not version 1.0");
2932 uint32_t featureTableStart =
sizeof(B3DMHeader);
2933 uint32_t batchTableStart = featureTableStart + header->featureTableJSONLength + header->featureTableBinaryLength;
2934 uint32_t glbStart = batchTableStart + header->batchTableJSONLength + header->batchTableBinaryLength;
2936 assert(glbStart < size &&
"Offset out of bounds");
2940 bool GltfLoader::load(
Context* context,
const ModelLoadInfo& loadInfo, std::unique_ptr<Cogs::FileContents> contents)
2942 LOG_TRACE(logger,
"Loading model: '%s'", loadInfo.
resourceName.c_str());
2946 if (!content.size()) {
2947 LOG_ERROR(logger,
"Could not open file '%s'.", loadInfo.
resourcePath.c_str());
2954 const uint8_t* dataPtr = (uint8_t*)(content.data()) + fileOffset;
2963 struct GlbChunkHeader
2971 GlbChunkHeader header;
2975 JsonParseFlags flags = JsonParseFlags::NoCachedContent;
2979 const GlbHeader* header = (
const GlbHeader*) dataPtr;
2980 GltfModelDefinition loadData(context, loadInfo.
resourcePath);
2981 loadData.default_material = -1;
2982 if (
auto var = context->
variables->get(optimizeVariableName); !var->isEmpty()) {
2983 loadData.optimizationLevel = unsigned(std::max(0, var->getInt()));
2986 std::span<const uint8_t> glb_data;
2987 if (
sizeof(GlbHeader) <= content.size() && header->magic == 0x46546C67) {
2988 loadData.version = header->version * 100;
2990 if (header->version != 2) {
2991 LOG_ERROR(logger,
"Unable to load glb format %d", loadData.version);
2995 size_t offset =
sizeof(GlbHeader);
2997 while (offset < (content.size() - fileOffset)) {
2998 const GlbChunk* chunk = (GlbChunk*)(
reinterpret_cast<const char*
>(dataPtr) + offset);
2999 if (chunk->header.type == 0x4E4F534A)
3000 document = parseJson(
Cogs::StringView(chunk->data, chunk->header.length), flags);
3001 else if (chunk->header.type == 0x004E4942)
3002 glb_data = std::span((
const uint8_t*)chunk->data, (
const uint8_t*)chunk->data + chunk->header.length);
3004 LOG_ERROR(logger,
"Unknown glb chunk type 0x%x (index %d)", chunk->header.type, i);
3006 offset += chunk->header.length +
sizeof(GlbChunkHeader);
3011 document = parseJson(
Cogs::StringView(
reinterpret_cast<const char*
>(dataPtr), content.size() - fileOffset), flags);
3014 if (!document.IsObject()) {
3015 LOG_ERROR(logger,
"Could not load asset file %s: JSON root is not an object.", loadInfo.
resourceName.c_str());
3019 auto& modelManager = *context->modelManager;
3020 auto model = modelManager.lock(loadInfo.
handle);
3022 MeshoptDecompressor meshoptDecomp;
3024 auto root = document.GetObject();
3027 if (
const auto& assetItem = root.FindMember(
"asset"); assetItem != root.MemberEnd()) {
3028 if (!parseAsset(loadData, path, assetItem->value))
return false;
3031 LOG_ERROR(logger,
"%.*s: No required JSON asset member", StringViewFormat(path));
3035 if (
const auto& buffersItem = root.FindMember(
"buffers"); buffersItem != root.MemberEnd()) {
3036 if (!parseBuffers(context, loadData, path, buffersItem->value, glb_data)) {
3041 if (
const auto& bufferViewsItem = root.FindMember(
"bufferViews"); bufferViewsItem != root.MemberEnd()) {
3042 if (!parseBufferViews(loadData, path, bufferViewsItem->value, meshoptDecomp)) {
3047 if (
const auto& imagesItem = root.FindMember(
"images"); imagesItem != root.MemberEnd()) {
3048 if (!parseImages(context, loadData, imagesItem->value)) {
3053 if (
const auto& samplersItem = root.FindMember(
"samplers"); samplersItem != root.MemberEnd()) {
3054 if (!parseSamplers(loadData, path, samplersItem->value)) {
3059 if (
const auto& texturesItem = root.FindMember(
"textures"); texturesItem != root.MemberEnd()) {
3060 if (!parseTextures(loadData, path, texturesItem->value)) {
3065 if (
auto materialsItem = root.FindMember(
"materials"); materialsItem != root.MemberEnd()) {
3066 if (!checkIsArray(loadData, *materialsItem))
return false;
3067 for (
const Value& materialsElement : materialsItem->value.GetArray()) {
3068 if (!checkIsObject(loadData,
"materials element", materialsElement)) {
3071 loadData.materialsArray.emplace_back(materialsElement.GetObject());
3075 if (
auto meshesItem = root.FindMember(
"meshes"); meshesItem != root.MemberEnd()) {
3076 if (!checkIsArray(loadData, *meshesItem))
return false;
3077 for (
const Value& meshesElement : meshesItem->value.GetArray()) {
3078 if (!checkIsObject(loadData,
"meshes element", meshesElement)) {
3081 loadData.meshesArray.emplace_back(meshesElement.GetObject());
3085 for (
const auto& section : root) {
3086 switch (toView(section.name).
hash()) {
3099 if (!parseNodes(loadData, path, section.value)) return false;
3103 if (!parseScenes(loadData, path, section.value)) return false;
3107 loadData.load_scene = section.value.GetUint();
3111 if (!checkIsArray(loadData, section)) return false;
3112 if (!parseAccessors(loadData, path, section.value))
return false;
3116 if (!parseSkins(loadData, path, section.value)) return false;
3120 if (!parseAnimations(loadData, path, section.value)) return false;
3124 for (auto& extension : section.value.GetArray()) {
3125 if (extension.GetString() ==
Cogs::StringView(
"KHR_materials_pbrSpecularGlossiness") ||
3140 LOG_ERROR(logger,
"Unknown required extension: %s", extension.GetString());
3148 if (section.value.IsArray()) {
3149 for (
auto& extension : section.value.GetArray()) {
3150 if (extension.GetString() ==
Cogs::StringView(
"KHR_materials_pbrSpecularGlossiness") ||
3162 LOG_WARNING(logger,
"Unknown extension: %s", extension.GetString());
3166 else if (section.value.IsObject()) {
3167 for (
const auto& extension : section.value.GetObject()) {
3168 LOG_WARNING(logger,
"Unknown extension: %s", extension.name.GetString());
3172 LOG_WARNING(logger,
"Unknown extension: %s", section.value.GetString());
3177 LOG_WARNING(logger,
"%.*s: Unrecognized root key %s", StringViewFormat(path), section.name.GetString());
3182 uint32_t sceneIndex = 0;
3183 for (
auto& scene : loadData.scenes) {
3184 if (sceneIndex > 1) {
3185 LOG_ERROR(logger,
"Loader unable to handle more than one scene.");
3189 model->setName(scene.name);
3193 auto& root_ = model->parts.emplace_back();
3194 model->setPartTransform(root_, glm::rotate(glm::mat4(1.0f), glm::half_pi<float>(), glm::vec3(1, 0, 0)));
3195 model->setPartName(root_,
"CogsRoot");
3198 glm::mat4 rootTransform(1.0f);
3200 for (uint32_t nodeIndex : scene.nodes) {
3201 assert(nodeIndex < loadData.nodes.size());
3203 if (!loadBone(loadData, loadData.nodes[nodeIndex], model->skeleton, (
size_t)-1, rootTransform)) {
3207 if (!loadJoints(loadData, loadData.nodes[nodeIndex], model->skeleton, meshoptDecomp)) {
3212 if (!loadNode(context, loadData, loadData.nodes[nodeIndex], *model, 0, rootTransform, meshoptDecomp)) {
3217 if (!loadAnimation(context, loadData, *model, meshoptDecomp)) {
3222 for (
auto const & it : loadData.debugMessages) {
3223 LOG_TRACE(logger,
"%s (x %u)", it.first.c_str(), it.second);
3228 bool GltfLoader::load(Context* context,
const ModelLoadInfo& loadInfo)
3230 JsonParseFlags flags = JsonParseFlags::NoCachedContent;
3232 const auto resourceFlags = ResourceStoreFlags::None;
3234 const auto resourceFlags = (flags & JsonParseFlags::NoCachedContent) == JsonParseFlags::NoCachedContent
3236 : ResourceStoreFlags::None;
3239 std::unique_ptr<Cogs::FileContents> contents;
3241 if (loadInfo.protocol == ResourceProtocol::Archive) {
3243 ResourceBuffer buffer = context->resourceStore->getResourceContents(loadInfo.resourcePath);
3244 if (!buffer.buffer->data()) {
3245 LOG_ERROR(logger,
"Could not load resource data for model %s.", loadInfo.resourcePath.c_str());
3248 contents = std::make_unique<Cogs::Platform::ResourceBufferBackedFileContents>(buffer, loadInfo.resourcePath, Cogs::FileContentsHints::None);
3252 Cogs::FileHandle::Ptr file = Cogs::IO::openFile(loadInfo.resourcePath, Cogs::FileHandle::OpenMode::OpenAlways, Cogs::FileHandle::AccessMode::Read);
3254 LOG_ERROR(logger,
"Could not open file %s for mapping.", loadInfo.resourcePath.c_str());
3258 size_t size = Cogs::IO::fileSize(file);
3260 LOG_ERROR(logger,
"File size invalid: %zd.", size);
3264 const uint8_t* ptr =
static_cast<const uint8_t*
>(Cogs::IO::mapFile(file, Cogs::FileHandle::ProtectionMode::ReadOnly, 0, size));
3266 LOG_ERROR(logger,
"Could not map file %s for reading model data.", loadInfo.resourcePath.c_str());
3270 contents = std::make_unique<Cogs::MMapBackedFileContents>(ptr, size, file, loadInfo.resourcePath, Cogs::FileContentsHints::None);
3275 const ResourceBuffer content = context->resourceStore->getResourceContents(path, resourceFlags);
3277 return load(context, loadInfo, std::move(contents));
3280 inline GltfModelDefinition::GltfModelDefinition(Context* context,
Cogs::StringView path) :
3284 timer = Cogs::Timer::startNew();
3285 rootTask = context->taskManager->createGroup(context->taskManager->ResourceQueue);
3288 inline GltfModelDefinition::~GltfModelDefinition()
3290 context->taskManager->wait(rootTask);
3291 for (
auto& image : images) {
3292 if (image.decoded.data) {
3293 stbi_image_free(image.decoded.data);
3294 image.decoded.data =
nullptr;
3297 LOG_TRACE(logger,
"Gltf elapsed: %f.", timer.elapsedSeconds());
A Context instance contains all the services, systems and runtime components needed to use Cogs.
std::unique_ptr< struct MemoryContext > memory
Memory and allocation info.
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
std::unique_ptr< class Variables > variables
Variables service instance.
std::unique_ptr< class ResourceStore > resourceStore
ResourceStore service instance.
size_t getPotentialB3DMOffset(uint8_t *content, size_t size)
bool registerBufferViewCompression(uint32_t bufferViewIdx, const GltfLoader::Object &properties)
Returns false if something failed.
ResourceProxy< Mesh, ResourceManager > ResourceProxy
Type of resource proxy objects, specialized on the type of resource.
Log implementation class.
Provides a weakly referenced view over the contents of a string.
size_t find_first_of(char character, size_t pos=0) const noexcept
Find the first occurance of the given character from the specified starting position.
constexpr StringView substr(size_t offset, size_t count=NoPosition) const noexcept
Get the given sub string.
static constexpr size_t NoPosition
No position.
constexpr size_t hash() const noexcept
Get the hash code of the string.
@ NoCachedContent
Never use cached data.
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
TextureLoadFlags
Texture loading flags. May be combined with resource load flags.
@ LinearColorSpace
For textures with RGBA format without color space information, mark the data as being in linear color...
@ ColorSpaceFromLoadInfo
by default we want to retrieve colorspace info from the texture data, not from the format specified i...
@ NoMipMaps
Do not generate mipmaps.
@ Back
Back face of primitives discarded before rasterization.
@ None
No primitive culling performed.
uint16_t VariableKey
Used to lookup material properties.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Contains all Cogs related functionality.
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
constexpr size_t hashSequence(const T &t, const U &u)
Hash the last two items in a sequence of objects.
@ VertexData
Per vertex data.
PrimitiveType
Primitive types for interpreting vertex data sent to the graphics pipeline.
@ PointList
List of points.
@ TriangleStrip
Triangle strip.
@ TriangleList
List of triangles.
@ Position
Position semantic.
@ Tangent
Tangent semantic.
@ TextureCoordinate
Texture coordinate semantic.
void setFloatProperty(const VariableKey key, float value)
Set the float property with the given key to value.
void setTransparent()
Set the material instance to transparent, indicating to the renderer that blending should be enabled ...
void setVec3Property(const VariableKey key, glm::vec3 value)
Set the vec3 property with the given key to value.
void setOption(const StringView &key, const StringView &value)
Sets the option with the given key to a value parsed from the value string.
void setTextureAddressMode(const StringView &key, const StringView &addressMode)
Set texture address mode with textual name.
void setTextureProperty(const StringView &key, TextureHandle value)
Set the texture property with the given key to the texture resource held by value.
MaterialOptions options
Material rendering options used by this instance.
void setVec4Property(const VariableKey key, glm::vec4 value)
Set the vec4 property with the given key to value.
Material * material
Material resource this MaterialInstance is created from.
CullMode cullMode
Primitive culling mode to use.
@ IndexesChanged
The index data of the mesh changed.
@ Indexed
The mesh should be drawn indexed, using index data to order the triangle vertexes.
@ ClockwiseWinding
The mesh uses clockwise winding order for it's triangles as opposed to the counter-clockwise default.
Model resources define a template for a set of connected entities, with resources such as meshes,...
void setName(const StringView &name)
Set the user friendly name of the resource.
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
std::string resourcePath
Resource path. Used to locate resource.
std::string resourceName
Desired resource name. If no name is given, a default name will be chosen.
ResourceHandleBase handle
Handle to resource structure for holding actual resource data.
Runtime control variable.
@ Clamp
Texture coordinates are clamped to the [0, 1] range.
@ Mirror
Texture coordinates are mirrored when outside [0, 1] range.
@ MinMagMipPoint
Point sampling for both minification and magnification.
@ MinMagMipLinear
Linear sampling for both minification and magnification.
Vertex element structure used to describe a single data element in a vertex for the input assembler.
uint16_t offset
Offset in bytes from the vertex position in memory.