Cogs.Core
CogsBin1Loader.cpp
1#include "ModelLoader.h"
2#include "CogsBin1.h"
3
4#include "../Context.h"
5
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"
11
12#include "Foundation/Logging/Logger.h"
13#include "Foundation/Platform/IO.h"
14#include "Foundation/Platform/Timer.h"
15
16#include <cinttypes>
17#include <span>
18
19#if defined( EMSCRIPTEN )
20 #include "Foundation/Platform/MemoryBufferBackedFileContents.h"
21#else
22 // Toggle using memory mapped IO when reading file.
23 #define COGS_USE_MMAP
24#endif
25
26namespace
27{
28 using namespace Cogs::Core;
29
30 Cogs::Logging::Log logger = Cogs::Logging::getLogger("CogsBin1Loader");
31
32 template<typename T>
33 std::span<const T> readView(const uint8_t *& data, uint64_t num, const uint8_t * dataEnd = nullptr)
34 {
35 const size_t size = num * sizeof(T);
36
37 auto currentAlignment = (intptr_t)data % CogsBin1::CogsSectionAlignment;
38
39 if (currentAlignment) {
40 data += CogsBin1::CogsSectionAlignment - currentAlignment;
41 }
42
43 auto begin = reinterpret_cast<const T *>(data);
44
45 if (dataEnd && (data + num) > dataEnd) {
46 LOG_WARNING(logger, "View outside data range.");
47 return {};
48 }
49
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));
52 }
53
54 std::span<const T> view(begin, num);
55
56 data += size;
57
58 return view;
59 }
60
61}
62
63bool Cogs::Core::loadCogsBin1(Context * context, const ModelLoadInfo & loadInfo, std::unique_ptr<FileContents> contents)
64{
65 if (!contents) return false;
66
67#ifdef EMSCRIPTEN
68 if (size_t(contents->ptr) & 0xF) {
69
70 auto other = std::make_unique<MemoryBufferBackedFileContents>(contents->size + 0xF, contents->origin().to_string(), Cogs::FileContentsHints::None);
71
72 auto alignedData = (uint8_t *)((size_t(other->bytes.data()) + 15) & ~size_t(0xF));
73 std::memcpy(alignedData, contents->ptr, contents->size);
74
75 other->ptr = alignedData;
76 other->size = contents->size;
77 contents = std::move(other);
78 }
79#endif
80 assert(!((uintptr_t)contents->ptr % 16) && "Cogsbin data misaligned.");
81
82 const auto * data = contents->ptr;
83 const auto fileSize = contents->size;
84 auto & header = *(CogsBin1::CogsHeader *)data;
85 data += sizeof(CogsBin1::CogsHeader);
86
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());
89 return false;
90 }
91
92 if (header.version > 1) {
93 LOG_ERROR(logger, "Invalid or unsupported model version %d.", header.version);
94 return false;
95 }
96
97 if (header.length != fileSize) {
98 LOG_ERROR(logger, "Mismatch between file size (%zd) and expected content length (%" PRIu64 ")", fileSize, header.length);
99 }
100
101 auto & descriptor = *(CogsBin1::CogsDescriptor *)data;
102 data += sizeof(CogsBin1::CogsDescriptor);
103
104 auto dataSize = descriptor.dataSize;
105 if (header.version == 1) {
106 // In version 1, mesh data size is stored in this field.
107 dataSize = fileSize - sizeof(CogsBin1::CogsHeader);
108 }
109
110 const auto dataEnd = data + dataSize;
111
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);
121
122 // Remainder of file is mesh data.
123 const uint8_t * meshData = data;
124
125 // FIXME: 2018-06-12 chrisdy: Is this correct?
126 const uint8_t * meshDataEnd = meshData + descriptor.dataSize;
127
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;
132
133 if (str.start + str.length > stringData.size()) {
134 LOG_ERROR(logger, "String data out of bounds.");
135 return false;
136 }
137
138 stringViews[i] = StringView(begin, str.length);
139 }
140
141 if (descriptor.numMetadataProperties) {
142 LOG_TRACE(logger, "Model metadata:");
143
144 for (size_t i = 0; i < descriptor.numMetadataProperties; ++i) {
145 auto & cprop = properties[descriptor.firstMetadataProperty + i];
146
147 auto & ckey = stringViews[cprop.key];
148 auto & cvalue = stringViews[cprop.value];
149
150 LOG_TRACE(logger, " %.*s: %.*s", StringViewFormat(ckey), StringViewFormat(cvalue));
151 }
152 }
153
154 auto modelResource = context->modelManager->lock(loadInfo.handle);
155 modelResource->meshes.resize(meshes.size());
156
157 auto & modelMeshes = modelResource->meshes;
158
159 for (size_t i = 0; i < meshes.size(); ++i) {
160 modelMeshes[i] = context->meshManager->create();
161 }
162
163 std::shared_ptr<FileContents> contents_shared = std::move(contents); // share ownership between this function and the mesh builder task
164
165 context->taskManager->enqueue(TaskManager::ResourceQueue, [=]() {
166 auto f = contents_shared;
167
168 for (size_t i = 0; i < meshes.size(); ++i) {
169 auto & cmesh = meshes[i];
170
171 auto mesh = context->meshManager->lock(modelMeshes[i]);
172
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.");
176 return;
177 }
178
179 const auto & cstream = streams[j];
180
181 auto begin = meshData + cstream.offset;
182 auto end = meshData + cstream.offset + cstream.size;
183
184 if (cstream.size == 0) {
185 LOG_WARNING(logger, "Skipping empty stream %zu for mesh %zu.", j, i);
186 continue;
187 }
188
189 if (begin >= meshDataEnd || end > meshDataEnd) {
190 LOG_ERROR(logger, "Mesh stream data out of range.");
191 return;
192 }
193
194 if (cstream.format == CogsBin1::CogsVertexFormat::Position3f) {
195 mesh->setPositions((const glm::vec3 *)begin, (const glm::vec3 *)end);
196 }
197 else if (cstream.format == CogsBin1::CogsVertexFormat::Normals3f) {
198 mesh->setNormals((const glm::vec3 *)begin, (const glm::vec3 *)end);
199 }
200 else if (cstream.format == CogsBin1::CogsVertexFormat::Tangent3f) {
201 mesh->setTangents((const glm::vec3 *)begin, (const glm::vec3 *)end);
202 }
203 else if (cstream.format == CogsBin1::CogsVertexFormat::TexCoord2f) {
204 mesh->setTexCoords((const glm::vec2 *)begin, (const glm::vec2 *)end);
205 }
206 else if (cstream.format == CogsBin1::CogsVertexFormat::Color4f) {
207 mesh->set(VertexDataType::Colors0, VertexFormats::Color4f, (const glm::vec4 *)begin, (const glm::vec4 *)end);
208 }
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);
212 }
213 else if (cstream.format == CogsBin1::CogsVertexFormat::Indexes32) {
214 mesh->setIndexData((uint32_t *)begin, cstream.size / sizeof(uint32_t));
215 }
216 else {
217 LOG_ERROR(logger, "Unsupported stream format %d.", cstream.format);
218 }
219 }
220
221 if (cmesh.flags & CogsBin1::CogsMeshFlags::ClockwiseWinding) {
222 mesh->setMeshFlag(MeshFlags::ClockwiseWinding);
223 }
224
225 Geometry::BoundingBox bounds;
226 bounds.min = cmesh.boundsMin;
227 bounds.max = cmesh.boundsMax;
228
229 mesh->setBounds(bounds);
230 }
231 });
232
233 const bool skipMaterials = ((ModelLoadFlags)loadInfo.loadFlags & ModelLoadFlags::SkipMaterials) != 0;
234
235 if (!skipMaterials) {
236 modelResource->materials.resize(materials.size());
237
238 auto & modelMaterials = modelResource->materials;
239
240 for (size_t i = 0; i < materials.size(); ++i) {
241 auto & cmat = materials[i];
242
243 if (cmat.type >= stringViews.size()) {
244 LOG_ERROR(logger, "String index out of range.");
245 return false;
246 }
247
248 auto & typeName = stringViews[cmat.type];
249
250 if (typeName.empty()) {
251 LOG_ERROR(logger, "Empty material name.");
252 continue;
253 }
254
255#ifdef EMSCRIPTEN
256 auto materialTemplate = context->materialManager->getDefaultMaterial();
257#else
258 auto materialTemplate = context->materialManager->getMaterial(typeName);
259
260 if (!materialTemplate) {
261 LOG_ERROR(logger, "Invalid material template. Skipping material instance.");
262 continue;
263 }
264#endif
265
266 auto material = context->materialInstanceManager->createMaterialInstance(materialTemplate);
267
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.");
272 continue;
273 }
274
275 auto & cprop = properties[j];
276
277 if (cprop.keyType != CogsBin1::CogsPropertyKeyType::String) {
278 LOG_ERROR(logger, "Only string keys supported for material properties.");
279 continue;
280 }
281
282 if (cprop.key >= stringViews.size()) {
283 LOG_ERROR(logger, "Material property key index out of range.");
284 continue;
285 }
286
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));
290 }
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));
294 }
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));
298 }
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));
302 }
303 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::String) {
304 auto key = materialTemplate->getTextureKey(stringViews[cprop.key]);
305 auto & source = stringViews[cprop.value];
306
307 //TODO:
308 // Get existing if present
309 // Check if texture should be linear/sRGB
310 auto & prop = materialTemplate->textureProperties[key];
311
312 auto textureFlags = TextureLoadFlags::None;
313
314 if (((int)prop.flags & (int)MaterialPropertyFlags::sRGB) == 0) {
315 textureFlags |= TextureLoadFlags::LinearColorSpace;
316 }
317
318 std::string sourcePath = source.to_string();
319 if (IO::isRelative(sourcePath)) {
320 sourcePath = IO::combine(IO::parentPath(loadInfo.resourcePath), sourcePath);
321 }
322
323 auto texture = context->textureManager->loadTexture(sourcePath, NoResourceId, textureFlags);
324
325 material->setTextureProperty(key, texture);
326 }
327 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::Variant) {
328 material->setVariant(stringViews[cprop.key], stringViews[cprop.value]);
329 }
330 else if (cprop.valueType == CogsBin1::CogsPropertyFormat::Permutation) {
331 material->setPermutation(stringViews[cprop.value]);
332 }
333 }
334 }
335
336 modelMaterials[i] = material;
337 }
338 }
339
340 modelResource->bounds.resize(nodes.size());
341 modelResource->parts.resize(nodes.size());
342
343 for (size_t i = 0; i < nodes.size(); ++i) {
344 modelResource->bounds[i] = nodeBounds[i];
345 }
346
347 for (size_t i = 0; i < nodes.size(); ++i) {
348 auto & cnode = nodes[i];
349
350 auto & child = modelResource->parts[i];
351
352 child.parentIndex = cnode.parent;
353 child.startIndex = cnode.startIndex;
354 child.vertexCount = cnode.vertexCount;
355
356 if (cnode.meshIndex != CogsBin1::CogsNoIndex) {
357 child.meshIndex = cnode.meshIndex;
358
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;
362 }
363 else {
364 child.materialIndex = skipMaterials ? NoIndex : cnode.materialIndex;
365 }
366 }
367
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)));
369
370 if (cnode.nameIndex != CogsBin1::CogsNoIndex) {
371 modelResource->setPartName(child, stringViews[cnode.nameIndex]);
372 }
373 }
374
375 return true;
376}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Bounds > bounds
Bounds service instance.
Definition: Context.h:216
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
static constexpr TaskQueueId ResourceQueue
Resource task queue.
Definition: TaskManager.h:232
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
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
Definition: LogManager.h:180
@ ClockwiseWinding
The mesh uses clockwise winding order for it's triangles as opposed to the counter-clockwise default.
Definition: Mesh.h:63
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.