2#include "ModelLoader.h"
5#include "Resources/Buffer.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"
11#include "Resources/AnimationManager.h"
12#include "Resources/BufferManager.h"
14#include "Platform/ResourceBufferBackedFileContents.h"
16#include "Foundation/Logging/Logger.h"
17#include "Foundation/Geometry/BoundingBox.hpp"
18#include "Foundation/Platform/FileHandle.h"
19#include "Foundation/Platform/IO.h"
20#include "Foundation/Platform/MMapBackedFileContents.h"
21#include "Foundation/Platform/Threads.h"
22#include "Foundation/Platform/Timer.h"
32#define COGS_MODELLOADER_MAX_TASKS 20
48 std::atomic<uint32_t> loadCount(0);
53 const T* base =
nullptr;
56 const T& operator[](
size_t i)
const { assert(i < count);
return base[i]; }
60 constexpr bool isValidIndex(T v)
62 static_assert(std::is_integral_v<T>);
69 std::unique_ptr<Cogs::FileContents> contents;
75 std::span<CogsBin3::SectionInfo> sectionInfo;
76 std::vector<const uint8_t*> sectionPtr;
77 std::unique_ptr<Cogs::Mutex[]> sectionLocks;
78 Cogs::Mutex buffersMutex;
80 TypedView<CogsBin3::Buffer> buffers;
81 TypedView<CogsBin3::Texture> textures;
82 TypedView<CogsBin3::String> strings;
83 TypedView<char> stringData;
84 TypedView<CogsBin3::Node> nodes;
85 TypedView<CogsBin3::Transform> transforms;
86 TypedView<Cogs::Geometry::BoundingBox> boundingBoxes;
87 TypedView<CogsBin3::MaterialInstance> materialInstances;
88 TypedView<CogsBin3::Property> props;
89 TypedView<uint8_t> propData;
90 TypedView<CogsBin3::Mesh> meshes;
91 TypedView<CogsBin3::VertexStream> vertexStreams;
92 TypedView<CogsBin3::VertexAttribute> vertexAttributes;
93 TypedView<CogsBin3::Bone> bones;
94 TypedView<CogsBin3::Skeleton> skeletons;
95 TypedView<CogsBin3::AnimTrack> animTracks;
96 TypedView<CogsBin3::AnimClip> animClips;
97 TypedView<CogsBin3::Model> models;
99 std::vector<ResourceBufferHandle> bufferCache;
100 std::vector<TextureHandle> textureCache;
102 std::list<std::unique_ptr<uint8_t[]>> allocations;
107 switch (primitiveType) {
117 case CogsBin3::PrimitiveType::ControlPoint1PatchList:
return Cogs::PrimitiveType::ControlPoint1PatchList;
break;
118 case CogsBin3::PrimitiveType::ControlPoint2PatchList:
return Cogs::PrimitiveType::ControlPoint2PatchList;
break;
119 case CogsBin3::PrimitiveType::ControlPoint3PatchList:
return Cogs::PrimitiveType::ControlPoint3PatchList;
break;
120 case CogsBin3::PrimitiveType::ControlPoint4PatchList:
return Cogs::PrimitiveType::ControlPoint4PatchList;
break;
124 rctx.success =
false;
125 LOG_ERROR(logger,
"Illegal primitive type: %u",
unsigned(primitiveType));
129 const uint8_t* sectionPointer(ReadContext& rctx, uint32_t index)
132 thread_local ZSTD_DCtx* zstd_dctx =
nullptr;
134 assert(index < rctx.header->sectionCount);
136 Cogs::LockGuard lock(rctx.sectionLocks[index]);
137 const auto & sectionInfo = rctx.sectionInfo[index];
138 if (rctx.sectionPtr[index] ==
nullptr) {
146 switch (rctx.sectionInfo[index].compression)
148 case CogsBin3::Compression::None:
149 rctx.sectionPtr[index] = rctx.contents->ptr + rctx.sectionInfo[index].fileOffset;
151 case CogsBin3::Compression::ZSTD: {
152 if (zstd_dctx ==
nullptr) {
153 zstd_dctx = ZSTD_createDCtx();
159 rctx.allocations.emplace_back(
new uint8_t[sectionInfo.uncompressedSize ? sectionInfo.uncompressedSize : 1]);
160 rctx.sectionPtr[index] = rctx.allocations.back().get();
162 auto result = ZSTD_decompressDCtx(zstd_dctx,
163 rctx.allocations.back().get(), sectionInfo.uncompressedSize,
164 rctx.contents->ptr + sectionInfo.fileOffset, sectionInfo.fileSize);
165 if (ZSTD_isError(result)) {
166 LOG_ERROR(logger,
"zstd decompression failed: %s", ZSTD_getErrorName(result));
169 else if (result != sectionInfo.uncompressedSize) {
170 LOG_ERROR(logger,
"Uncompressed data size didn't match size recorded in file.");
176 LOG_ERROR(logger,
"Illegal compression flag on section.");
180 return rctx.sectionPtr[index];
185 if (ix < rctx.strings.count) {
186 auto & str = rctx.strings[ix];
191 else if (!isValidIndex(ix)) {
195 LOG_ERROR(logger,
"Invalid string index %u", ix);
196 rctx.success =
false;
201 const Cogs::Geometry::BoundingBox& getBoundingBox(ReadContext& rctx, uint32_t ix)
203 if (ix < rctx.boundingBoxes.count) {
204 return rctx.boundingBoxes[ix];
207 rctx.success =
false;
208 LOG_ERROR(logger,
"Invalid bounding box index %u", ix);
209 static const Cogs::Geometry::BoundingBox empty = Cogs::Geometry::makeEmptyBoundingBox<Cogs::Geometry::BoundingBox>();
214 Cogs::TextureFormat getTextureFormat(ReadContext& , CogsBin3::Format format)
217 case CogsBin3::Format::Unknown:
return Cogs::TextureFormat::Unknown;
break;
218 case CogsBin3::Format::R8_UINT:
return Cogs::TextureFormat::R8_UINT;
break;
219 case CogsBin3::Format::R8G8_UINT:
return Cogs::TextureFormat::R8G8_UINT;
break;
220 case CogsBin3::Format::R8G8B8_UINT:
return Cogs::TextureFormat::R8G8B8_UINT;
break;
221 case CogsBin3::Format::R8G8B8A8_UINT:
return Cogs::TextureFormat::R8G8B8A8_UINT;
break;
222 case CogsBin3::Format::R16_UINT:
return Cogs::TextureFormat::R16_UINT;
break;
223 case CogsBin3::Format::R16G16_UINT:
return Cogs::TextureFormat::R16G16_UINT;
break;
224 case CogsBin3::Format::R16G16B16_UINT:
return Cogs::TextureFormat::R16G16B16_UINT;
break;
225 case CogsBin3::Format::R16G16B16A16_UINT:
return Cogs::TextureFormat::R16G16B16A16_UINT;
break;
226 case CogsBin3::Format::R32_UINT:
return Cogs::TextureFormat::R32_UINT;
break;
227 case CogsBin3::Format::R32G32_UINT:
return Cogs::TextureFormat::R32G32_UINT;
break;
228 case CogsBin3::Format::R32G32B32_UINT:
return Cogs::TextureFormat::R32G32B32_UINT;
break;
229 case CogsBin3::Format::R32G32B32A32_UINT:
return Cogs::TextureFormat::R32G32B32A32_UINT;
break;
230 case CogsBin3::Format::R8_SINT:
return Cogs::TextureFormat::R8_SINT;
break;
231 case CogsBin3::Format::R8G8_SINT:
return Cogs::TextureFormat::R8G8_SINT;
break;
232 case CogsBin3::Format::R8G8B8_SINT:
return Cogs::TextureFormat::R8G8B8_SINT;
break;
233 case CogsBin3::Format::R8G8B8A8_SINT:
return Cogs::TextureFormat::R8G8B8A8_SINT;
break;
234 case CogsBin3::Format::R16_SINT:
return Cogs::TextureFormat::R16_SINT;
break;
235 case CogsBin3::Format::R16G16_SINT:
return Cogs::TextureFormat::R16G16_SINT;
break;
236 case CogsBin3::Format::R16G16B16_SINT:
return Cogs::TextureFormat::R16G16B16_SINT;
break;
237 case CogsBin3::Format::R16G16B16A16_SINT:
return Cogs::TextureFormat::R16G16B16A16_SINT;
break;
238 case CogsBin3::Format::R32_SINT:
return Cogs::TextureFormat::R32_SINT;
break;
239 case CogsBin3::Format::R32G32_SINT:
return Cogs::TextureFormat::R32G32_SINT;
break;
240 case CogsBin3::Format::R32G32B32_SINT:
return Cogs::TextureFormat::R32G32B32_SINT;
break;
241 case CogsBin3::Format::R32G32B32A32_SINT:
return Cogs::TextureFormat::R32G32B32A32_SINT;
break;
242 case CogsBin3::Format::R16_FLOAT:
return Cogs::TextureFormat::R16_FLOAT;
break;
243 case CogsBin3::Format::R16G16_FLOAT:
return Cogs::TextureFormat::R16G16_FLOAT;
break;
244 case CogsBin3::Format::R16G16B16_FLOAT:
return Cogs::TextureFormat::R16G16B16_FLOAT;
break;
245 case CogsBin3::Format::R16G16B16A16_FLOAT:
return Cogs::TextureFormat::R16G16B16A16_FLOAT;
break;
246 case CogsBin3::Format::R32_FLOAT:
return Cogs::TextureFormat::R32_FLOAT;
break;
247 case CogsBin3::Format::R32G32_FLOAT:
return Cogs::TextureFormat::R32G32_FLOAT;
break;
248 case CogsBin3::Format::R32G32B32_FLOAT:
return Cogs::TextureFormat::R32G32B32_FLOAT;
break;
249 case CogsBin3::Format::R32G32B32A32_FLOAT:
return Cogs::TextureFormat::R32G32B32A32_FLOAT;
break;
250 case CogsBin3::Format::R64_FLOAT: assert(
false &&
"Illegal texture format");
break;
251 case CogsBin3::Format::R64G64_FLOAT: assert(
false &&
"Illegal texture format");
break;
252 case CogsBin3::Format::R64G64B64_FLOAT: assert(
false &&
"Illegal texture format");
break;
253 case CogsBin3::Format::R64G65B64A64_FLOAT: assert(
false &&
"Illegal texture format");
break;
254 case CogsBin3::Format::R8_UNORM:
return Cogs::TextureFormat::R8_UNORM;
break;
255 case CogsBin3::Format::R8G8_UNORM:
return Cogs::TextureFormat::R8G8_UNORM;
break;
256 case CogsBin3::Format::R8G8B8_UNORM:
return Cogs::TextureFormat::R8G8B8_UNORM;
break;
257 case CogsBin3::Format::R8G8B8A8_UNORM:
return Cogs::TextureFormat::R8G8B8A8_UNORM;
break;
258 case CogsBin3::Format::R16_UNORM:
return Cogs::TextureFormat::R16_UNORM;
break;
259 case CogsBin3::Format::R16G16_UNORM:
return Cogs::TextureFormat::R16G16_UNORM;
break;
260 case CogsBin3::Format::R16G16B16_UNORM:
return Cogs::TextureFormat::R16G16B16_UNORM;
break;
261 case CogsBin3::Format::R16G16B16A16_UNORM:
return Cogs::TextureFormat::R16G16B16A16_UNORM;
break;
262 case CogsBin3::Format::R8_SNORM:
return Cogs::TextureFormat::R8_SNORM;
break;
263 case CogsBin3::Format::R8G8_SNORM:
return Cogs::TextureFormat::R8G8_SNORM;
break;
264 case CogsBin3::Format::R8G8B8_SNORM:
return Cogs::TextureFormat::R8G8B8_SNORM;
break;
265 case CogsBin3::Format::R8G8B8A8_SNORM:
return Cogs::TextureFormat::R8G8B8A8_SNORM;
break;
266 case CogsBin3::Format::R16_SNORM:
return Cogs::TextureFormat::R16_SNORM;
break;
267 case CogsBin3::Format::R16G16_SNORM:
return Cogs::TextureFormat::R16G16_SNORM;
break;
268 case CogsBin3::Format::R16G16B16_SNORM:
return Cogs::TextureFormat::R16G16B16_SNORM;
break;
269 case CogsBin3::Format::R16G16B16A16_SNORM:
return Cogs::TextureFormat::R16G16B16A16_SNORM;
break;
270 case CogsBin3::Format::D16_UNORM:
return Cogs::TextureFormat::D16_UNORM;
break;
271 case CogsBin3::Format::D24_UNORM:
return Cogs::TextureFormat::D24_UNORM;
break;
272 case CogsBin3::Format::D24S8_UNORM:
return Cogs::TextureFormat::D24S8_UNORM;
break;
273 case CogsBin3::Format::D32_FLOAT:
return Cogs::TextureFormat::D32_FLOAT;
break;
274 case CogsBin3::Format::R32_TYPELESS:
return Cogs::TextureFormat::R32_TYPELESS;
break;
275 case CogsBin3::Format::R16_TYPELESS:
return Cogs::TextureFormat::R16_TYPELESS;
break;
276 case CogsBin3::Format::R8T:
return Cogs::TextureFormat::R8T;
break;
277 case CogsBin3::Format::R8G8T:
return Cogs::TextureFormat::R8G8T;
break;
278 case CogsBin3::Format::R8G8B8T:
return Cogs::TextureFormat::R8G8B8T;
break;
279 case CogsBin3::Format::R8G8B8A8T:
return Cogs::TextureFormat::R8G8B8A8T;
break;
280 case CogsBin3::Format::B8G8R8:
return Cogs::TextureFormat::B8G8R8;
break;
281 case CogsBin3::Format::B8G8R8A8:
return Cogs::TextureFormat::B8G8R8A8;
break;
282 case CogsBin3::Format::A8_UNORM:
return Cogs::TextureFormat::A8_UNORM;
break;
283 case CogsBin3::Format::BC1_TYPELESS:
return Cogs::TextureFormat::BC1_TYPELESS;
break;
284 case CogsBin3::Format::BC1_UNORM:
return Cogs::TextureFormat::BC1_UNORM;
break;
285 case CogsBin3::Format::BC1_UNORM_SRGB:
return Cogs::TextureFormat::BC1_UNORM_SRGB;
break;
286 case CogsBin3::Format::BC2_TYPELESS:
return Cogs::TextureFormat::BC2_TYPELESS;
break;
287 case CogsBin3::Format::BC2_UNORM:
return Cogs::TextureFormat::BC2_UNORM;
break;
288 case CogsBin3::Format::BC2_UNORM_SRGB:
return Cogs::TextureFormat::BC2_UNORM_SRGB;
break;
289 case CogsBin3::Format::BC3_TYPELESS:
return Cogs::TextureFormat::BC3_TYPELESS;
break;
290 case CogsBin3::Format::BC3_UNORM:
return Cogs::TextureFormat::BC3_UNORM;
break;
291 case CogsBin3::Format::BC3_UNORM_SRGB:
return Cogs::TextureFormat::BC3_UNORM_SRGB;
break;
292 case CogsBin3::Format::BC4_TYPELESS:
return Cogs::TextureFormat::BC4_TYPELESS;
break;
293 case CogsBin3::Format::BC4_UNORM:
return Cogs::TextureFormat::BC4_UNORM;
break;
294 case CogsBin3::Format::BC4_SNORM:
return Cogs::TextureFormat::BC4_SNORM;
break;
295 case CogsBin3::Format::BC5_TYPELESS:
return Cogs::TextureFormat::BC5_TYPELESS;
break;
296 case CogsBin3::Format::BC5_UNORM:
return Cogs::TextureFormat::BC5_UNORM;
break;
297 case CogsBin3::Format::BC5_SNORM:
return Cogs::TextureFormat::BC5_SNORM;
break;
298 case CogsBin3::Format::R8G8B8A8_UNORM_SRGB:
return Cogs::TextureFormat::R8G8B8A8_UNORM_SRGB;
break;
300 assert(
false &&
"Illegal format");
303 return Cogs::TextureFormat::Unknown;
308 const auto * attributes = rctx.vertexAttributes.base + firstAttribute;
310 std::vector<Cogs::VertexElement> elements;
311 elements.resize(attributeCount);
312 for (
unsigned i = 0; i < attributeCount; i++) {
313 const auto & attribute = attributes[i];
315 auto & element = elements[i];
316 element.offset = attribute.offset;
317 element.format = (Cogs::DataFormat)attribute.format;
319 switch (attribute.semantic) {
328 LOG_ERROR(logger,
"Illegal vertex element semantic %u",
unsigned(attribute.semantic));
331 element.semanticIndex = attribute.semanticIndex;
332 if (attribute.instanceStepRate == 0) {
334 element.instanceStep = 0;
338 element.instanceStep = attribute.instanceStepRate - 1;
341 element.instanceStep = attribute.instanceStepRate;
344 return Cogs::VertexFormats::createVertexFormat(elements.data(), elements.size());
347 const uint8_t* resolveDataOffset(ReadContext& rctx, uint64_t offset, uint32_t size)
349 const auto sectionIx = uint16_t(offset >> 48);
350 const auto * section = sectionPointer(rctx, sectionIx);
351 if (section ==
nullptr) {
352 LOG_ERROR(logger,
"Failed to retrieve data section %u",
unsigned(sectionIx));
353 rctx.success =
false;
356 const auto sectionOffset = size_t(offset & 0xFFFFFFFFFFFFull);
357 if (rctx.sectionInfo[sectionIx].uncompressedSize < sectionOffset + size) {
358 LOG_ERROR(logger,
"Stream source offset %zX size %u is outside section memory range.", sectionOffset, size);
359 rctx.success =
false;
362 return section + sectionOffset;
365 template<
typename D,
typename S>
366 void widenAndCopy(std::span<D>& dstSpan,
const S* src, uint32_t count)
368 auto * dst = dstSpan.data();
371 constexpr auto notzero = std::numeric_limits<S>::max();
372 for (uint32_t i = 0; i < count; i++) {
373 const auto v = *src++;
374 if (v == notzero) *dst++ = ~D(0);
379 void populateMesh(ReadContext& rctx,
MeshHandle handle, uint32_t index)
381 const auto & srcMesh = rctx.meshes[index];
382 auto dstMesh = rctx.context->meshManager->lock(handle);
384 if (isValidIndex(srcMesh.name)) {
385 dstMesh->setName(getString(rctx, srcMesh.name));
387 if (isValidIndex(srcMesh.boundingBox)) {
388 dstMesh->setBounds(getBoundingBox(rctx, srcMesh.boundingBox));
390 if ((srcMesh.flags & CogsBin3::MeshFlags::Clockwise) != 0) {
393 if ((srcMesh.flags & CogsBin3::MeshFlags::Skinned) != 0) {
394 dstMesh->setMeshFlag(MeshFlags::Skinned);
397 dstMesh->primitiveType = translatePrimitiveType(rctx, srcMesh.primitiveType);
399 dstMesh->streams.resize(srcMesh.streamCount);
400 for (
size_t i = 0; i < srcMesh.streamCount; i++) {
401 assert(srcMesh.firstStream + i < rctx.vertexStreams.count);
402 const auto & srcStream = rctx.vertexStreams[srcMesh.firstStream + i];
404 auto & dstStream = dstMesh->streams[i];
407 Cogs::LockGuard lock(rctx.buffersMutex);
408 assert(srcStream.buffer < rctx.buffers.count);
410 if (rctx.bufferCache[srcStream.buffer]) {
411 dstStream.buffer = rctx.bufferCache[srcStream.buffer];
413 auto & dstBuffer = rctx.bufferCache[srcStream.buffer] = rctx.context->bufferManager->create();
414 dstStream.buffer = dstBuffer;
416 const auto & srcBuffer = rctx.buffers[srcStream.buffer];
417 const auto * srcData = resolveDataOffset(rctx, srcBuffer.sectionOffset, srcBuffer.size);
419 dstBuffer->reset(srcBuffer.size, rctx.context->memory->resourceAllocator);
420 std::memcpy(dstBuffer->data(), srcData, srcBuffer.size);
422 if ((srcBuffer.flags & CogsBin3::BufferContentFlags::VertexData) != 0) {
426 if ((srcBuffer.flags & CogsBin3::BufferContentFlags::IndexData) != 0) {
432 dstStream.numElements = srcStream.count;
434 dstStream.stride = srcStream.stride;
435 dstStream.offset = srcStream.offset;
437 dstMesh->streamIndexes[dstStream.type] = uint8_t(i);
438 dstMesh->streamsUpdated |=
Mesh::toFlag(dstStream.type);
441 if (srcStream.vertexDataType < VertexDataType::LastVertexType) {
442 dstStream.format = isValidIndex(srcStream.firstAttribute)
443 ? getVertexFormat(rctx, srcStream.firstAttribute, srcStream.attributeCount)
446 if (!dstStream.format) {
447 LOG_ERROR(logger,
"Stream %zu has illegal format", i);
451 if (!dstMesh->isIndexed()) {
452 switch (dstStream.type) {
453 case VertexDataType::Positions:
454 case VertexDataType::Interleaved0:
455 case VertexDataType::Interleaved1:
456 dstMesh->setCount(srcStream.count);
462 }
else if (srcStream.vertexDataType == VertexDataType::Indexes) {
463 dstMesh->setCount(srcStream.count);
466 }
else if (srcStream.vertexDataType == VertexDataType::SubMeshes) {
467 dstMesh->submeshCount = (uint16_t)srcStream.count;
476 if constexpr (
sizeof(T) <=
sizeof(uint32_t)) {
477 return *(
const T*)(&prop.value);
480 if (prop.value < rctx.propData.count) {
481 return *(
const T*)(rctx.propData.base + prop.value);
484 LOG_ERROR(logger,
"Illegal property data offset %u", prop.value);
491 TextureHandle populateEmbeddedTexture(ReadContext& rctx, uint32_t textureIx)
493 assert(textureIx < rctx.textures.count);
494 if (
auto handle = rctx.textureCache[textureIx];
HandleIsValid(handle)) {
497 auto & tex = rctx.textures[textureIx];
500 assert(tex.buffer < rctx.buffers.count);
501 const auto & srcBuffer = rctx.buffers[tex.buffer];
502 const auto * bufferData = resolveDataOffset(rctx, srcBuffer.sectionOffset, srcBuffer.size) + tex.offset;
504 TextureLoadInfo * loadInfo = rctx.context->textureManager->createLoadInfo();
506 loadInfo->
resourceId = (uint32_t)NoResourceId;
508 if (isValidIndex(tex.name)) {
509 loadInfo->
resourceName = getString(rctx, tex.name).to_string();
511 loadInfo->
resourceData.assign(bufferData, bufferData + tex.size);
514 loadInfo->format = getTextureFormat(rctx, tex.format);
515 loadInfo->target = Cogs::ResourceDimensions::Texture2D;
516 loadInfo->width =
static_cast<int>(tex.width);
517 loadInfo->height =
static_cast<int>(tex.height);
518 loadInfo->depth =
static_cast<int>(tex.depth);
519 loadInfo->stride = 0;
522 auto handle = rctx.context->textureManager->loadTexture(loadInfo);
524 rctx.textureCache[textureIx] = handle;
531 const auto & srcMaterialInstance = rctx.materialInstances[materialInstanceIx];
532 if (!isValidIndex(srcMaterialInstance.material)) {
533 LOG_ERROR(logger,
"Material instance without material.");
534 rctx.success =
false;
538 auto materialName = getString(rctx, srcMaterialInstance.material);
539 if (materialName.empty()) {
540 LOG_ERROR(logger,
"Writer should not include empty strings.");
541 rctx.success =
false;
545 auto materialTemplate = rctx.context->materialManager->getMaterial(materialName);
546 if (!materialTemplate) {
547 LOG_ERROR(logger,
"Invalid material template %.*s", StringViewFormat(materialName));
548 rctx.success =
false;
553 auto material = rctx.context->materialInstanceManager->createMaterialInstance(materialTemplate);
555 if (isValidIndex(srcMaterialInstance.permutation)) {
556 material->setPermutation(getString(rctx, srcMaterialInstance.permutation));
559 for (uint32_t i = 0; i < srcMaterialInstance.propertyCount; i++) {
560 if (rctx.props.count <= srcMaterialInstance.propertyFirst + i) {
561 LOG_ERROR(logger,
"Invalid property index.");
562 rctx.success =
false;
566 auto & prop = rctx.props[srcMaterialInstance.propertyFirst + i];
567 switch (prop.keyType) {
568 case CogsBin3::PropertyKeyType::Index:
569 assert(
false &&
"FIXME");
572 case CogsBin3::PropertyKeyType::String: {
574 auto keyString = getString(rctx, prop.key);
575 switch (prop.valueType) {
577 case CogsBin3::PropertyValueType::Bool:
578 if (
auto key = materialTemplate->getBoolKey(keyString); key != NoProperty)
579 material->setBoolProperty(key, getPropertyValue<uint32_t>(rctx, prop));
581 LOG_ERROR(logger,
"Invalid bool key %.*s", StringViewFormat(keyString));
584 case CogsBin3::PropertyValueType::UInt:
585 if (
auto key = materialTemplate->getUIntKey(keyString); key != NoProperty)
586 material->setUIntProperty(key, getPropertyValue<uint32_t>(rctx, prop));
588 LOG_ERROR(logger,
"Invalid uint key %.*s", StringViewFormat(keyString));
591 case CogsBin3::PropertyValueType::Float:
592 if (
auto key = materialTemplate->getFloatKey(keyString); key != NoProperty)
593 material->setFloatProperty(key, getPropertyValue<float>(rctx, prop));
595 LOG_ERROR(logger,
"Invalid float key %.*s", StringViewFormat(keyString));
598 case CogsBin3::PropertyValueType::Float2:
599 if (
auto key = materialTemplate->getVec2Key(keyString); key != NoProperty)
600 material->setVec2Property(key, getPropertyValue<glm::vec2>(rctx, prop));
602 LOG_ERROR(logger,
"Invalid float2 key %.*s", StringViewFormat(keyString));
605 case CogsBin3::PropertyValueType::Float3:
606 if (
auto key = materialTemplate->getVec3Key(keyString); key != NoProperty)
607 material->setVec3Property(key, getPropertyValue<glm::vec3>(rctx, prop));
609 LOG_ERROR(logger,
"Invalid float3 key %.*s", StringViewFormat(keyString));
612 case CogsBin3::PropertyValueType::Float4:
613 if (
auto key = materialTemplate->getVec4Key(keyString); key != NoProperty)
614 material->setVec4Property(key, getPropertyValue<glm::vec4>(rctx, prop));
616 LOG_ERROR(logger,
"Invalid float4 key %.*s", StringViewFormat(keyString));
619 case CogsBin3::PropertyValueType::String:
620 if (
auto key = materialTemplate->getTextureKey(keyString); key != NoProperty) {
621 auto textureFlags = TextureLoadFlags::None;
622 if (
const auto & mprop = materialTemplate->textureProperties[key]; ((
int)mprop.flags & (
int)MaterialPropertyFlags::sRGB) == 0) {
623 textureFlags |= TextureLoadFlags::LinearColorSpace;
625 std::string sourcePath = getString(rctx, prop.value).to_string();
626 if (Cogs::IO::isRelative(sourcePath)) {
627 sourcePath = Cogs::IO::combine(Cogs::IO::parentPath(rctx.path), sourcePath);
629 auto texture = rctx.context->textureManager->loadTexture(sourcePath, NoResourceId, textureFlags);
630 material->setTextureProperty(key, texture);
633 LOG_ERROR(logger,
"Invalid float4 key %.*s", StringViewFormat(keyString));
636 case CogsBin3::PropertyValueType::Variant: {
637 auto key = getString(rctx, prop.key);
638 auto val = getString(rctx, prop.value);
639 if (materialName ==
"DefaultMaterial" && key ==
"NormalMap") {
640 key =
"TangentSpaceNormalMap";
642 material->setVariant(key, val);
648 case CogsBin3::PropertyValueType::EmbeddedTexture:
649 if (
auto key = materialTemplate->getTextureKey(keyString); key != NoProperty) {
650 auto textureIx = getPropertyValue<uint32_t>(rctx, prop);
651 auto texture = populateEmbeddedTexture(rctx, textureIx);
652 material->setTextureProperty(key, texture);
657 LOG_ERROR(logger,
"Invalid property value type.");
658 rctx.success =
false;
664 LOG_ERROR(logger,
"Invalid property key type.");
665 rctx.success =
false;
673 template<
typename ResourceProxy>
679 part.boundsIndex = uint32_t(model->bounds.size());
680 model->bounds.emplace_back(getBoundingBox(rctx, node.
boundingBox));
683 const auto * m = rctx.transforms[node.
transform].m;
684 model->setPartTransform(part, glm::mat4(m[0], m[1], m[2], 0.f,
685 m[3], m[4], m[5], 0.f,
686 m[6], m[7], m[8], 0.f,
687 m[9], m[10], m[11], 1.f));
689 if (isValidIndex(node.
name)) {
690 model->setPartName(part, getString(rctx, node.
name));
692 if (isValidIndex(node.
mesh)) {
693 part.meshIndex = uint32_t(model->meshes.size());
694 model->meshes.emplace_back(rctx.context->meshManager->create());
695 if (rctx.group != NoTask) {
696 rctx.context->taskManager->enqueueChild(rctx.group, [rc = &rctx, h = model->meshes.back(), ix = node.
mesh](){populateMesh(*rc, h, ix); });
699 populateMesh(rctx, model->meshes.back(), node.
mesh);
703 part.materialIndex = uint32_t(model->materials.size());
704 model->materials.emplace_back(populateMaterialInstance(rctx, node.
materialInstance));
706 if (node.
primitiveType != CogsBin3::PrimitiveType::Unknown) {
707 part.primitiveType = translatePrimitiveType(rctx, node.
primitiveType);
709 part.primitiveType = (uint32_t)-1;
715 void populateBone(ReadContext& rctx,
Bone& dstBone,
const uint32_t boneIx,
const uint32_t boneCount)
717 if (rctx.bones.count <= boneIx) {
718 LOG_ERROR(logger,
"Invalid bone index %u (bone count is %zu)", boneIx, rctx.bones.count);
719 rctx.success =
false;
722 const auto & srcBone = rctx.bones[boneIx];
723 dstBone.name = getString(rctx, srcBone.name).to_string();
724 if (isValidIndex(srcBone.parentBone)) {
725 if (boneCount <= srcBone.parentBone) {
726 LOG_ERROR(logger,
"Invalid parent bone index %u (skeleton bone count is %u)",
unsigned(srcBone.parentBone), boneCount);
727 rctx.success =
false;
730 dstBone.parentBone = srcBone.parentBone;
733 dstBone.parentBone = (size_t)-1;
735 dstBone.pos = srcBone.pos;
736 dstBone.scale = srcBone.scale;
737 dstBone.rot = srcBone.rot;
738 dstBone.relative = srcBone.relative;
739 dstBone.inverseBindPose = srcBone.inverseBindPose;
740 dstBone.hasOffset = (srcBone.flags & CogsBin3::BoneFlags::HasOffset) != 0;
741 dstBone.used = (srcBone.flags & CogsBin3::BoneFlags::Used) != 0;
742 dstBone.animated = (srcBone.flags & CogsBin3::BoneFlags::Animated) != 0;
745 void populateSkeleton(ReadContext& rctx,
Skeleton& dstSkeleton,
const uint32_t skeletonIx)
747 if (rctx.skeletons.count <= skeletonIx) {
748 LOG_ERROR(logger,
"Invalid skeleton index %u (skeleton count is %zu)", skeletonIx, rctx.skeletons.count);
749 rctx.success =
false;
752 const auto & srcSkeleton = rctx.skeletons[skeletonIx];
753 dstSkeleton.bindPose = srcSkeleton.bindPose;
754 dstSkeleton.bones.resize(srcSkeleton.boneCount);
755 for (uint32_t i = 0; i < srcSkeleton.boneCount && rctx.success; i++) {
756 populateBone(rctx, dstSkeleton.bones[i], srcSkeleton.firstBone + i, srcSkeleton.boneCount);
757 dstSkeleton.bonesByName[dstSkeleton.bones[i].name] = i;
759 updateSkeletonPose(dstSkeleton);
762 void populateAnimTrack(ReadContext& rctx,
AnimationTrack& dstTrack,
const uint32_t animTrackIx)
764 assert(animTrackIx < rctx.animTracks.count);
765 const auto & srcTrack = rctx.animTracks[animTrackIx];
767 assert(srcTrack.buffer < rctx.buffers.count);
768 const auto & buffer = rctx.buffers[srcTrack.buffer];
769 const auto * bufferData = resolveDataOffset(rctx, buffer.sectionOffset, buffer.size);
771 dstTrack.
boneIndex = isValidIndex(srcTrack.boneIndex) ? srcTrack.boneIndex : -1;
773 const auto * translationSrc = (
const float*)(bufferData + srcTrack.translationOffset);
774 dstTrack.translations.resize(srcTrack.translationCount);
775 for (
unsigned i = 0; i < srcTrack.translationCount; i++) {
776 dstTrack.translations[i].t = *translationSrc++;
777 dstTrack.translations[i].value[0] = *translationSrc++;
778 dstTrack.translations[i].value[1] = *translationSrc++;
779 dstTrack.translations[i].value[2] = *translationSrc++;
782 const auto * rotationSrc = (
const float*)(bufferData + srcTrack.rotationOffset);
783 dstTrack.rotations.resize(srcTrack.rotationCount);
784 for (
unsigned i = 0; i < srcTrack.rotationCount; i++) {
785 dstTrack.rotations[i].t = *rotationSrc++;
786 dstTrack.rotations[i].value[0] = *rotationSrc++;
787 dstTrack.rotations[i].value[1] = *rotationSrc++;
788 dstTrack.rotations[i].value[2] = *rotationSrc++;
789 dstTrack.rotations[i].value[3] = *rotationSrc++;
792 const auto * scaleSrc = (
const float*)(bufferData + srcTrack.scaleOffset);
793 dstTrack.scales.resize(srcTrack.scaleCount);
794 for (
unsigned i = 0; i < srcTrack.scaleCount; i++) {
795 dstTrack.scales[i].t = *scaleSrc++;
796 dstTrack.scales[i].value[0] = *scaleSrc++;
797 dstTrack.scales[i].value[1] = *scaleSrc++;
798 dstTrack.scales[i].value[2] = *scaleSrc++;
802 void populateAnimClip(ReadContext& rctx,
AnimationClip& dstClip,
const uint32_t animClipIx)
804 if (rctx.animClips.count <= animClipIx) {
805 LOG_ERROR(logger,
"Invalid animation clip index %u (animation clip count is %zu)", animClipIx, rctx.animClips.count);
806 rctx.success =
false;
810 dstClip.name = getString(rctx, srcClip.
name).to_string();
811 dstClip.duration = srcClip.duration;
812 dstClip.resolution = srcClip.resolution;
813 dstClip.tracks.resize(srcClip.trackCount);
814 for (
unsigned i = 0; i < srcClip.trackCount && rctx.success; i++) {
815 populateAnimTrack(rctx, dstClip.tracks[i], srcClip.trackFirst + i);
819 void populateModel(ReadContext& rctx,
ResourceHandleBase modelHandle,
const uint32_t modelIx)
821 const auto & srcModel = rctx.models[modelIx];
822 if (srcModel.nodeCount == 0)
return;
824 assert(modelHandle &&
"Model handle without resource");
825 auto dstModel = rctx.context->modelManager->lock(modelHandle);
826 assert(dstModel->parts.empty() &&
"Destination model is not empty");
829 if (isValidIndex(srcModel.skeleton)) populateSkeleton(rctx, dstModel->skeleton, srcModel.skeleton);
832 if (srcModel.animClipCount != 0) {
833 dstModel->animation = rctx.context->animationManager->create();
835 auto * anim = dstModel->animation.resolve();
836 anim->skeleton = dstModel->skeleton;
837 anim->clips.resize(srcModel.animClipCount);
838 for (uint32_t i = 0; i < srcModel.animClipCount && rctx.success; i++) {
839 populateAnimClip(rctx, anim->clips[i], srcModel.animClipFirst + i);
840 anim->clipsByName[anim->clips[i].name] = i;
845 dstModel->parts.resize(srcModel.nodeCount);
846 for (
size_t i = 0; i < srcModel.nodeCount && rctx.success; i++) {
847 if (!isValidIndex(srcModel.firstNode) || rctx.nodes.count <= srcModel.firstNode + i) {
848 LOG_ERROR(logger,
"Invalid node index %u (node count is %zu)", srcModel.firstNode, rctx.nodes.count);
849 rctx.success =
false;
852 const auto & node = rctx.nodes[srcModel.firstNode + i];
853 auto & part = dstModel->parts[i];
854 if (!isValidIndex(node.
parent)) {
855 part.parentIndex = ~0u;
857 else if (node.
parent < srcModel.firstNode || srcModel.firstNode + srcModel.nodeCount <= node.
parent) {
858 LOG_ERROR(logger,
"Illegal parent node index %u (node count is %zu)", node.
parent, rctx.nodes.count);
859 rctx.success =
false;
863 part.parentIndex = node.
parent - srcModel.firstNode;
865 populatePart(rctx, dstModel, part, node);
872bool Cogs::Core::loadCogsBin3(
Context * context,
const ModelLoadInfo & loadInfo, std::unique_ptr<Cogs::FileContents> contents_)
877 rctx.context = context;
878 rctx.contents = std::move(contents_);
880 if (loadCount.fetch_add(1) < COGS_MODELLOADER_MAX_TASKS) {
886 unsigned modelIx = 0u;
887 if (
auto i = rctx.path.find(
'#'); i != std::string::npos) {
888 if (i + 1 < rctx.path.length()) {
889 modelIx = std::atoi(rctx.path.c_str() + i + 1);
895 LOG_ERROR(logger,
"Model file too small to contain a header, unable to load.");
900 if (rctx.header->sectionCount == 0) {
901 LOG_ERROR(logger,
"No sections in file.");
904 if (rctx.header->fileLength != rctx.contents->size) {
905 LOG_ERROR(logger,
"File size mismatch in read context, potentially corrupt or truncated model file.");
911 rctx.sectionPtr.resize(rctx.header->sectionCount);
912 rctx.sectionLocks.reset(
new Cogs::Mutex[rctx.header->sectionCount]);
914 LOG_TRACE(logger,
"Parsed header, %u sections, filesize=%zu", rctx.header->sectionCount, rctx.contents->size);
917 if ((rctx.sectionInfo[0].type & CogsBin3::SectionType::META) == 0) {
918 LOG_ERROR(logger,
"First section is not a meta section.");
922 const auto * sectionBase = sectionPointer(rctx, 0);
923 if (sectionBase ==
nullptr)
return false;
925 for (uint16_t sub = 0; sub < rctx.sectionInfo[0].subSectionCount; sub++) {
927 switch (metaSubSection.type)
929 case CogsBin3::SubSectionType::Buffers:
930 rctx.buffers.base = (
const CogsBin3::Buffer*)(sectionBase + metaSubSection.offset);
932 rctx.bufferCache.resize(rctx.buffers.count);
935 case CogsBin3::SubSectionType::Textures:
936 rctx.textures.base = (
const CogsBin3::Texture*)(sectionBase + metaSubSection.offset);
938 rctx.textureCache.resize(rctx.textures.count);
941 case CogsBin3::SubSectionType::Strings:
942 rctx.strings.base = (
const CogsBin3::String*)(sectionBase + metaSubSection.offset);
946 case CogsBin3::SubSectionType::StringData:
947 rctx.stringData.base = (
char*)(sectionBase + metaSubSection.offset);
948 rctx.stringData.count = metaSubSection.size;
951 case CogsBin3::SubSectionType::Nodes:
952 rctx.nodes.base = (
const CogsBin3::Node*)(sectionBase + metaSubSection.offset);
956 case CogsBin3::SubSectionType::Transforms:
961 case CogsBin3::SubSectionType::BoundingBoxes:
962 rctx.boundingBoxes.base = (
const Cogs::Geometry::BoundingBox*)(sectionBase + metaSubSection.offset);
963 rctx.boundingBoxes.count = metaSubSection.size /
sizeof(Cogs::Geometry::BoundingBox);
966 case CogsBin3::SubSectionType::MaterialInstances:
971 case CogsBin3::SubSectionType::Properties:
976 case CogsBin3::SubSectionType::PropertyData:
977 rctx.propData.base = sectionBase + metaSubSection.offset;
978 rctx.propData.count = metaSubSection.size;
981 case CogsBin3::SubSectionType::Meshes:
982 rctx.meshes.base = (
const CogsBin3::Mesh*)(sectionBase + metaSubSection.offset);
986 case CogsBin3::SubSectionType::VertexStreams:
991 case CogsBin3::SubSectionType::VertexAttributes:
996 case CogsBin3::SubSectionType::Bones:
997 rctx.bones.base = (
const CogsBin3::Bone*)(sectionBase + metaSubSection.offset);
1001 case CogsBin3::SubSectionType::Skeletons:
1002 rctx.skeletons.base = (
const CogsBin3::Skeleton*)(sectionBase + metaSubSection.offset);
1006 case CogsBin3::SubSectionType::AnimTracks:
1011 case CogsBin3::SubSectionType::AnimClips:
1012 rctx.animClips.base = (
const CogsBin3::AnimClip*)(sectionBase + metaSubSection.offset);
1016 case CogsBin3::SubSectionType::Models:
1017 rctx.models.base = (
const CogsBin3::Model*)(sectionBase + metaSubSection.offset);
1022 LOG_ERROR(logger,
"Unknown subsection type %u",
unsigned(metaSubSection.type));
1027 for (uint32_t i = 0; i < rctx.buffers.count; i++) {
1028 const auto & buffer = rctx.buffers[i];
1029 const auto sectionIx = buffer.sectionOffset >> 48;
1030 const auto sectionOffset = size_t(buffer.sectionOffset & 0xFFFFFFFFFFFFull);
1031 if (rctx.header->sectionCount <= sectionIx) {
1032 LOG_ERROR(logger,
"Illegal section index %u (section count is %u)",
unsigned(sectionIx), rctx.header->sectionCount);
1035 const auto & sectionInfo = rctx.sectionInfo[sectionIx];
1036 if (sectionInfo.uncompressedSize < sectionOffset + buffer.size) {
1037 LOG_ERROR(logger,
"Buffer out of bounds");
1042 for (uint32_t i = 0; i < rctx.textures.count; i++) {
1043 const auto & tex = rctx.textures[i];
1044 if (rctx.buffers.count <= tex.buffer) { LOG_ERROR(logger,
"Texture has illegal buffer index %u", tex.buffer);
return false; }
1045 auto & buf = rctx.buffers[tex.buffer];
1046 if (buf.size < tex.offset + tex.size) { LOG_ERROR(logger,
"Texture buffer view outside buffer");
return false; }
1047 if (isValidIndex(tex.name) && rctx.strings.count <= tex.name) { LOG_ERROR(logger,
"Illegal name string %u", tex.name);
return false; }
1048 if (0x10000 < tex.width) { LOG_ERROR(logger,
"Suspicious texture width %u", tex.width);
return false; }
1049 if (0x10000 < tex.height) { LOG_ERROR(logger,
"Suspicious texture height %u", tex.height);
return false; }
1050 if (0x10000 < tex.depth) { LOG_ERROR(logger,
"Suspicious texture depth %u", tex.depth);
return false; }
1051 if (6 < tex.faces) { LOG_ERROR(logger,
"Suspicious texture faces %u", tex.faces);
return false; }
1052 if (0x10000 < tex.layers) { LOG_ERROR(logger,
"Suspicious texture layers %u", tex.layers);
return false; }
1053 if (CogsBin3::Format::EnumSize <= tex.format) { LOG_ERROR(logger,
"Illegal format %u",
unsigned(tex.format));
return false; }
1054 if (CogsBin3::TextureEncoding::EnumSize <= tex.encoding) { LOG_ERROR(logger,
"Illegal encoding %u",
unsigned(tex.encoding));
return false; }
1057 for (uint32_t i = 0; i < rctx.vertexStreams.count; i++) {
1058 const auto & dataStream = rctx.vertexStreams[i];
1059 if (rctx.buffers.count <= dataStream.buffer) {
1060 LOG_ERROR(logger,
"Vertex stream has illegal buffer index %u (count is %zu)", dataStream.buffer, rctx.buffers.count);
1063 if (dataStream.vertexDataType < VertexDataType::LastVertexType) {
1064 if (!isValidIndex(dataStream.firstAttribute)) {
1065 LOG_ERROR(logger,
"Vertex stream without attributes");
1068 if (rctx.vertexAttributes.count <= dataStream.firstAttribute || rctx.vertexAttributes.count < dataStream.firstAttribute + dataStream.attributeCount) {
1069 LOG_ERROR(logger,
"Vertex stream has illegal vertex attribute indices first=%u count=%u (total count is %zu)",
1070 dataStream.firstAttribute, dataStream.attributeCount, rctx.vertexAttributes.count);
1073 }
else if (dataStream.vertexDataType == VertexDataType::Indexes) {
1074 if (dataStream.stride != 4 && dataStream.stride != 2) {
1075 LOG_ERROR(logger,
"Index stream has unsupported stride.");
1080 if (!isValidIndex(dataStream.buffer)) {
1081 LOG_ERROR(logger,
"Data stream without valid buffer.");
1086 for (uint32_t i = 0; i < rctx.animTracks.count; i++) {
1087 const auto & track = rctx.animTracks[i];
1088 if (kMaxBones <= track.boneIndex) {
1089 LOG_ERROR(logger,
"Anim track has illegal bone index %u (kMaxBones is %zu)", track.boneIndex, kMaxBones);
1092 if (rctx.buffers.count <= track.buffer) {
1093 LOG_ERROR(logger,
"Anim track has illegal buffer index %u.", track.buffer);
1096 const auto & buffer = rctx.buffers[track.buffer];
1097 if (buffer.size < track.translationOffset + 4 *
sizeof(
float)*track.translationCount) {
1098 LOG_ERROR(logger,
"Anim track translation buffer view is outside buffer.");
1101 if (buffer.size < track.rotationOffset + 5 *
sizeof(
float)*track.rotationCount) {
1102 LOG_ERROR(logger,
"Anim track translation buffer view is outside buffer.");
1105 if (buffer.size < track.scaleOffset + 4 *
sizeof(
float)*track.scaleCount) {
1106 LOG_ERROR(logger,
"Anim track translation buffer view is outside buffer.");
1111 for (uint32_t i = 0; i < rctx.meshes.count; i++) {
1112 const auto & mesh = rctx.meshes[i];
1113 if (isValidIndex(mesh.name) && rctx.strings.count <= mesh.name) {
1114 LOG_ERROR(logger,
"Mesh name has illegal string index %u (string count is %zu)", mesh.name, rctx.strings.count);
1117 if (isValidIndex(mesh.boundingBox) && rctx.boundingBoxes.count <= mesh.boundingBox) {
1118 LOG_ERROR(logger,
"Mesh name has illegal boundingBox index %u (count is %zu)", mesh.boundingBox, rctx.boundingBoxes.count);
1121 if (isValidIndex(mesh.firstStream) && rctx.vertexStreams.count <= mesh.firstStream) {
1122 LOG_ERROR(logger,
"Mesh name has illegal data stream index %u (count is %zu)", mesh.firstStream, rctx.vertexStreams.count);
1127 if (modelIx < rctx.models.count) {
1128 populateModel(rctx, loadInfo.
handle, modelIx);
1131 LOG_ERROR(logger,
"Invalid model index %u (model count=%zu)", modelIx, rctx.models.count);
1132 rctx.success =
false;
1135 if (rctx.group != NoTask) {
1141 return rctx.success;
A Context instance contains all the services, systems and runtime components needed to use Cogs.
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....
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Contains all Cogs related functionality.
@ InstanceData
Per instance data.
@ VertexData
Per vertex data.
@ Position
Position semantic.
@ Tangent
Tangent semantic.
@ InstanceMatrix
Instance matrix semantic.
@ InstanceVector
Instance vector semantic.
@ TextureCoordinate
Texture coordinate semantic.
uint32_t boneIndex
Specifies which bone of a skeleton that is animated by this track.
@ VertexBuffer
Buffer can be bound as vertex buffer.
@ IndexBuffer
Buffer can be bound as index buffer.
uint32_t name
Offset into strings subsection of current section.
uint32_t mesh
Mesh of node, offset into Meshes subsection.
uint32_t name
Name of node, offset into Strings subsection.
uint32_t transform
Transform of node, offset into Transforms subsection.
PrimitiveType primitiveType
Primitive type to use for this node.
uint32_t materialInstance
Material instance of node, offset into MaterialInstances subsection.
uint32_t vertexCount
Number of vertices to draw.
uint32_t parent
Parent node, offset into Nodes subsection.
uint32_t boundingBox
Bounding box of node, offset into BoundingBoxes subsection.
uint32_t vertexFirst
First vertex of mesh to draw.
@ StreamsChanged
One or more of the data streams in the mesh changed.
@ 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.
static constexpr uint16_t toFlag(VertexDataType::EVertexDataType type)
Convert the given type index to a flag.
Resource handle base class handling reference counting of resources derived from ResourceBase.
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) 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.
ResourceId resourceId
Unique resource identifier. Must be unique among resources of the same kind.
ResourceHandleBase handle
Handle to resource structure for holding actual resource data.
std::vector< uint8_t > resourceData
Resource load data.
Task id struct used to identify unique Task instances.
EVertexDataType
Contains data types.
EPrimitiveType
Primitive type enumeration.
@ LineStripAdjacency
Line strip with adjacency.
@ TriangleStrip
Triangle strip.
@ TriangleListAdjacency
List of triangles with adjacency.
@ PointList
List of points.
@ TriangleStripAdjacency
Triangle strip with adjacency.
@ LineListAdjacency
List of lines with adjacency.
@ TriangleList
List of triangles.