1#include "ModelLoader.h"
6#include "../Resources/ResourceStore.h"
7#include "../Resources/ModelManager.h"
8#include "../Resources/MeshManager.h"
9#include "../Resources/MaterialManager.h"
10#include "../Resources/TextureManager.h"
12#include "Foundation/Logging/Logger.h"
13#include "Foundation/Platform/IO.h"
14#include "Foundation/Platform/Timer.h"
19#if defined( EMSCRIPTEN )
20 #include "Foundation/Platform/MemoryBufferBackedFileContents.h"
33 std::span<const T> readView(
const uint8_t *& data, uint64_t num,
const uint8_t * dataEnd =
nullptr)
35 const size_t size = num *
sizeof(T);
37 auto currentAlignment = (intptr_t)data % CogsBin1::CogsSectionAlignment;
39 if (currentAlignment) {
40 data += CogsBin1::CogsSectionAlignment - currentAlignment;
43 auto begin =
reinterpret_cast<const T *
>(data);
45 if (dataEnd && (data + num) > dataEnd) {
46 LOG_WARNING(logger,
"View outside data range.");
50 if (
reinterpret_cast<intptr_t
>(begin) %
alignof(T) != 0) {
51 LOG_WARNING(logger,
"Misaligned section data 0x%p for structure with alignment %zd.", begin,
alignof(T));
54 std::span<const T> view(begin, num);
63bool Cogs::Core::loadCogsBin1(
Context * context,
const ModelLoadInfo & loadInfo, std::unique_ptr<FileContents> contents)
65 if (!contents)
return false;
68 if (
size_t(contents->ptr) & 0xF) {
70 auto other = std::make_unique<MemoryBufferBackedFileContents>(contents->size + 0xF, contents->origin().to_string(), Cogs::FileContentsHints::None);
72 auto alignedData = (uint8_t *)((
size_t(other->bytes.data()) + 15) & ~size_t(0xF));
73 std::memcpy(alignedData, contents->ptr, contents->size);
75 other->ptr = alignedData;
76 other->size = contents->size;
77 contents = std::move(other);
80 assert(!((uintptr_t)contents->ptr % 16) &&
"Cogsbin data misaligned.");
82 const auto * data = contents->ptr;
83 const auto fileSize = contents->size;
87 if (header.magic != CogsBin1::CogsHeaderMagic) {
88 LOG_ERROR(logger,
"Model %s contains invalid header signature. File will not be loaded.", loadInfo.
resourcePath.c_str());
92 if (header.version > 1) {
93 LOG_ERROR(logger,
"Invalid or unsupported model version %d.", header.version);
97 if (header.length != fileSize) {
98 LOG_ERROR(logger,
"Mismatch between file size (%zd) and expected content length (%" PRIu64
")", fileSize, header.length);
104 auto dataSize = descriptor.dataSize;
105 if (header.version == 1) {
110 const auto dataEnd = data + dataSize;
112 const auto stringData = readView<uint8_t>(data, descriptor.numStringBytes, dataEnd);
113 const auto strings = readView<CogsBin1::CogsString>(data, descriptor.numStrings, dataEnd);
114 const auto propertyData = readView<uint8_t>(data, descriptor.numPropertyBytes, dataEnd);
115 const auto properties = readView<CogsBin1::CogsProperty>(data, descriptor.numProperties, dataEnd);
116 const auto streams = readView<CogsBin1::CogsStream>(data, descriptor.numStreams, dataEnd);
117 const auto meshes = readView<CogsBin1::CogsMesh>(data, descriptor.numMeshes, dataEnd);
118 const auto materials = readView<CogsBin1::CogsMaterial>(data, descriptor.numMaterials, dataEnd);
119 const auto nodeBounds = readView<Geometry::BoundingBox>(data, descriptor.numNodes, dataEnd);
120 const auto nodes = readView<CogsBin1::CogsNode>(data, descriptor.numNodes, dataEnd);
123 const uint8_t * meshData = data;
126 const uint8_t * meshDataEnd = meshData + descriptor.dataSize;
128 std::vector<StringView> stringViews(strings.size());
129 for (
size_t i = 0; i < stringViews.size(); ++i) {
130 auto & str = strings[i];
131 const char * begin = (
const char *)stringData.data() + str.start;
133 if (str.start + str.length > stringData.size()) {
134 LOG_ERROR(logger,
"String data out of bounds.");
138 stringViews[i] =
StringView(begin, str.length);
141 if (descriptor.numMetadataProperties) {
142 LOG_TRACE(logger,
"Model metadata:");
144 for (
size_t i = 0; i < descriptor.numMetadataProperties; ++i) {
145 auto & cprop = properties[descriptor.firstMetadataProperty + i];
147 auto & ckey = stringViews[cprop.key];
148 auto & cvalue = stringViews[cprop.value];
150 LOG_TRACE(logger,
" %.*s: %.*s", StringViewFormat(ckey), StringViewFormat(cvalue));
154 auto modelResource = context->modelManager->lock(loadInfo.
handle);
155 modelResource->meshes.resize(meshes.size());
157 auto & modelMeshes = modelResource->meshes;
159 for (
size_t i = 0; i < meshes.size(); ++i) {
160 modelMeshes[i] = context->meshManager->create();
163 std::shared_ptr<FileContents> contents_shared = std::move(contents);
166 auto f = contents_shared;
168 for (
size_t i = 0; i < meshes.size(); ++i) {
169 auto & cmesh = meshes[i];
171 auto mesh = context->meshManager->lock(modelMeshes[i]);
173 for (
size_t j = cmesh.firstStream; j < cmesh.lastStream + 1; ++j) {
174 if (j >= streams.size()) {
175 LOG_ERROR(logger,
"Stream index out of range.");
179 const auto & cstream = streams[j];
181 auto begin = meshData + cstream.offset;
182 auto end = meshData + cstream.offset + cstream.size;
184 if (cstream.size == 0) {
185 LOG_WARNING(logger,
"Skipping empty stream %zu for mesh %zu.", j, i);
189 if (begin >= meshDataEnd || end > meshDataEnd) {
190 LOG_ERROR(logger,
"Mesh stream data out of range.");
194 if (cstream.format == CogsBin1::CogsVertexFormat::Position3f) {
195 mesh->setPositions((
const glm::vec3 *)begin, (
const glm::vec3 *)end);
197 else if (cstream.format == CogsBin1::CogsVertexFormat::Normals3f) {
198 mesh->setNormals((
const glm::vec3 *)begin, (
const glm::vec3 *)end);
200 else if (cstream.format == CogsBin1::CogsVertexFormat::Tangent3f) {
201 mesh->setTangents((
const glm::vec3 *)begin, (
const glm::vec3 *)end);
203 else if (cstream.format == CogsBin1::CogsVertexFormat::TexCoord2f) {
204 mesh->setTexCoords((
const glm::vec2 *)begin, (
const glm::vec2 *)end);
206 else if (cstream.format == CogsBin1::CogsVertexFormat::Color4f) {
207 mesh->set(VertexDataType::Colors0, VertexFormats::Color4f, (
const glm::vec4 *)begin, (
const glm::vec4 *)end);
209 else if (cstream.format == CogsBin1::CogsVertexFormat::Pos3fNorm3fTex2f) {
210 struct float8 {
float v[8]; };
211 mesh->setVertexData((float8 *)begin, cstream.size /
sizeof(float8), VertexFormats::Pos3fNorm3fTex2f);
213 else if (cstream.format == CogsBin1::CogsVertexFormat::Indexes32) {
214 mesh->setIndexData((uint32_t *)begin, cstream.size /
sizeof(uint32_t));
217 LOG_ERROR(logger,
"Unsupported stream format %d.", cstream.format);
221 if (cmesh.flags & CogsBin1::CogsMeshFlags::ClockwiseWinding) {
225 Geometry::BoundingBox bounds;
226 bounds.min = cmesh.boundsMin;
227 bounds.max = cmesh.boundsMax;
229 mesh->setBounds(bounds);
235 if (!skipMaterials) {
236 modelResource->materials.resize(materials.size());
238 auto & modelMaterials = modelResource->materials;
240 for (
size_t i = 0; i < materials.size(); ++i) {
241 auto & cmat = materials[i];
243 if (cmat.type >= stringViews.size()) {
244 LOG_ERROR(logger,
"String index out of range.");
248 auto & typeName = stringViews[cmat.type];
250 if (typeName.empty()) {
251 LOG_ERROR(logger,
"Empty material name.");
256 auto materialTemplate = context->materialManager->getDefaultMaterial();
258 auto materialTemplate = context->materialManager->getMaterial(typeName);
260 if (!materialTemplate) {
261 LOG_ERROR(logger,
"Invalid material template. Skipping material instance.");
266 auto material = context->materialInstanceManager->createMaterialInstance(materialTemplate);
268 if (cmat.firstProperty != CogsBin1::CogsNoIndex) {
269 for (
size_t j = cmat.firstProperty; j < cmat.lastProperty + 1; ++j) {
270 if (j >= properties.size()) {
271 LOG_ERROR(logger,
"Material property index out of range.");
275 auto & cprop = properties[j];
277 if (cprop.keyType != CogsBin1::CogsPropertyKeyType::String) {
278 LOG_ERROR(logger,
"Only string keys supported for material properties.");
282 if (cprop.key >= stringViews.size()) {
283 LOG_ERROR(logger,
"Material property key index out of range.");
287 if (cprop.valueType == CogsBin1::CogsPropertyFormat::Float4) {
288 auto key = materialTemplate->getVec4Key(stringViews[cprop.key]);
289 material->setVec4Property(key, *
reinterpret_cast<const glm::vec4 *
>(propertyData.data() + cprop.value));
291 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::Float3) {
292 auto key = materialTemplate->getVec3Key(stringViews[cprop.key]);
293 material->setVec3Property(key, *
reinterpret_cast<const glm::vec3 *
>(propertyData.data() + cprop.value));
295 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::Float2) {
296 auto key = materialTemplate->getVec2Key(stringViews[cprop.key]);
297 material->setVec2Property(key, *
reinterpret_cast<const glm::vec3 *
>(propertyData.data() + cprop.value));
299 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::Float) {
300 auto key = materialTemplate->getFloatKey(stringViews[cprop.key]);
301 material->setFloatProperty(key, *
reinterpret_cast<const float *
>(propertyData.data() + cprop.value));
303 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::String) {
304 auto key = materialTemplate->getTextureKey(stringViews[cprop.key]);
305 auto & source = stringViews[cprop.value];
310 auto & prop = materialTemplate->textureProperties[key];
312 auto textureFlags = TextureLoadFlags::None;
314 if (((
int)prop.flags & (
int)MaterialPropertyFlags::sRGB) == 0) {
315 textureFlags |= TextureLoadFlags::LinearColorSpace;
318 std::string sourcePath = source.to_string();
319 if (IO::isRelative(sourcePath)) {
320 sourcePath = IO::combine(IO::parentPath(loadInfo.
resourcePath), sourcePath);
323 auto texture = context->textureManager->loadTexture(sourcePath, NoResourceId, textureFlags);
325 material->setTextureProperty(key, texture);
327 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::Variant) {
328 material->setVariant(stringViews[cprop.key], stringViews[cprop.value]);
330 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::Permutation) {
331 material->setPermutation(stringViews[cprop.value]);
336 modelMaterials[i] = material;
340 modelResource->
bounds.resize(nodes.size());
341 modelResource->parts.resize(nodes.size());
343 for (
size_t i = 0; i < nodes.size(); ++i) {
344 modelResource->bounds[i] = nodeBounds[i];
347 for (
size_t i = 0; i < nodes.size(); ++i) {
348 auto & cnode = nodes[i];
350 auto & child = modelResource->parts[i];
352 child.parentIndex = cnode.parent;
353 child.startIndex = cnode.startIndex;
354 child.vertexCount = cnode.vertexCount;
356 if (cnode.meshIndex != CogsBin1::CogsNoIndex) {
357 child.meshIndex = cnode.meshIndex;
359 if (!skipMaterials && cnode.materialIndex != CogsBin1::CogsNoMaterialIndex && cnode.materialIndex >= modelResource->materials.size()) {
360 LOG_ERROR(logger,
"Invalid material index in part %zu", i);
361 child.materialIndex = NoIndex;
364 child.materialIndex = skipMaterials ? NoIndex : cnode.materialIndex;
368 modelResource->setPartTransform(child, glm::translate(glm::mat4(), glm::vec3(cnode.translation)) * glm::mat4_cast(cnode.rotation) * glm::scale(glm::mat4(), glm::vec3(cnode.scale)));
370 if (cnode.nameIndex != CogsBin1::CogsNoIndex) {
371 modelResource->setPartName(child, stringViews[cnode.nameIndex]);
A Context instance contains all the services, systems and runtime components needed to use Cogs.
std::unique_ptr< class Bounds > bounds
Bounds service instance.
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
static constexpr TaskQueueId ResourceQueue
Resource task queue.
Log implementation class.
Provides a weakly referenced view over the contents of a string.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
ModelLoadFlags
Model loading flags. May be combined with resource loading flags.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
@ ClockwiseWinding
The mesh uses clockwise winding order for it's triangles as opposed to the counter-clockwise default.
std::string resourcePath
Resource path. Used to locate resource.
ResourceHandleBase handle
Handle to resource structure for holding actual resource data.
ResourceLoadFlags loadFlags
Desired loading flags. Used to specify how the resource will be loaded.