Cogs.Core
AssetWriter.cpp
1#include "AssetWriter.h"
2
3#ifdef _WIN32
4#pragma warning(push)
5#pragma warning(disable:4996)
6#endif
7#include "rapidjson/document.h"
8#ifdef _WIN32
9#pragma warning(pop)
10#endif
11
12#include <cstdio> // fopen etc
13#include "zstd.h"
14#include "rapidjson/stringbuffer.h"
15#include "rapidjson/filewritestream.h"
16#include "rapidjson/prettywriter.h"
17#include "rapidjson/error/en.h"
18
19#include "Foundation/Logging/Logger.h"
20
21#include "Context.h"
22#include "Engine.h"
23#include "EntityStore.h"
24#include "EntityDefinition.h"
25
26#include "Editor/IEditor.h"
27
28#include "Components/Core/AssetComponent.h"
29#include "Components/Core/TransformComponent.h"
30
31#include "Resources/Asset.h"
32#include "Resources/ResourceBase.h"
33#include "Resources/Texture.h"
34#include "Resources/MaterialInstance.h"
35
36#include "Services/Variables.h"
37
38#include "JsonMemoryBufferStream.h"
39
40#include "EntityWriter.h"
41#include <span>
42
43using namespace rapidjson;
44
45using namespace Cogs::Reflection;
46
47#ifndef EMSCRIPTEN
48
49namespace
50{
51 using namespace Cogs::Core;
52
53 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("AssetWriter");
54
55 constexpr uint32_t NoValue = static_cast<uint32_t>(-1);
56 const Cogs::Core::StringRef idsString = Cogs::Core::Strings::add("ids");
57 const Cogs::Core::StringRef bboxString = Cogs::Core::Strings::add("bbox");
58 const Cogs::Core::StringRef errorsString = Cogs::Core::Strings::add("errors");
59 const Cogs::Core::StringRef byteSizeString = Cogs::Core::Strings::add("byteSize");
60
61 const char* currentErrnoString()
62 {
63 static char buff[256] = "unknown";
64 #ifdef _WIN32
65 strerror_s(buff, sizeof(buff), errno);
66 #else
67 strerror_r(errno, buff, sizeof(buff));
68 #endif
69 return buff;
70 }
71
72 struct WriterCtx
73 {
74 Context* context = nullptr;
75 MemoryPoolAllocator<CrtAllocator> alloc;
76 AssetWriteFlags writeFlags = AssetWriteFlags::None;
77
78 public:
80 [[nodiscard]] Value encodeString(const std::string& str)
81 {
82 return Value(str.data(), uint32_t(str.length()), alloc);
83 }
84
87 [[nodiscard]] Value encodeString(const Cogs::StringView& view)
88 {
89 return Value(view.data(), uint32_t(view.length()), alloc);
90 }
91
93 template<class ValueType>
94 [[nodiscard]] Value encodeSpan(std::span<ValueType> values)
95 {
96 Value vec(kArrayType);
97 for (size_t i = 0; i < values.size(); i++) {
98 vec.PushBack(values[i], alloc);
99 }
100 return vec;
101 }
102 };
103
104 [[nodiscard]] Value getStringProperty(WriterCtx& ctx, const AssetDefinition& definition, const PropertyInfo& propertyInfo)
105 {
106 Cogs::StringView string = definition.scene.properties.getString(propertyInfo);
107 return ctx.encodeString(string);
108 }
109
110 [[nodiscard]] bool writeDoc(WriterCtx& ctx, Value& entities, Value& resources, const char* path)
111 {
112 Document doc(&ctx.alloc);
113 doc.SetObject();
114
115 if (resources.MemberCount()) {
116 doc.AddMember("resources", resources, ctx.alloc);
117 }
118
119 if (!entities.Empty()) {
120 doc.AddMember("entities", entities, ctx.alloc);
121 }
122
123 Cogs::IO::createDirectories(path);
125
126 if ((ctx.writeFlags & AssetWriteFlags::PrettyPrint) == AssetWriteFlags::PrettyPrint) {
127 JsonMemoryBufferStream stream(buffer);
128 PrettyWriter<JsonMemoryBufferStream> writer(stream);
129 writer.SetIndent(' ', 2);
130 writer.SetMaxDecimalPlaces(4);
131 doc.Accept(writer);
132 }
133 else {
134 JsonMemoryBufferStream stream(buffer);
135 Writer<JsonMemoryBufferStream> writer(stream);
136 writer.SetMaxDecimalPlaces(4);
137 doc.Accept(writer);
138 }
139
140 if ((ctx.writeFlags & AssetWriteFlags::Compress) == AssetWriteFlags::Compress) {
141 int compressionLevel = ctx.context->variables->get("resources.compressionLevel", 0);
142 size_t uncompressedSize = buffer.size();
143 size_t maxSize = ZSTD_compressBound(uncompressedSize);
144 Cogs::Memory::MemoryBuffer compressed(maxSize);
145
146 ZSTD_CCtx* zstd_ctx = ZSTD_createCCtx();
147 size_t result = ZSTD_compressCCtx(zstd_ctx, compressed.data(), maxSize, buffer.data(), uncompressedSize,compressionLevel);
148 if (ZSTD_isError(result)) {
149 LOG_ERROR(logger, "%s: zstd compression failed: %s", path, ZSTD_getErrorName(result));
150 return false;
151 }
152 else if (uncompressedSize <= result) {
153 LOG_WARNING(logger, "%s: compressed size greater or equal to uncompressed size", path);
154 }
155 else {
156 compressed.resize(result);
157 buffer.swap(compressed);
158 }
159 ZSTD_freeCCtx(zstd_ctx);
160 }
161
162 FILE* file = std::fopen(path, "wb");
163 if (!file) {
164 LOG_ERROR(logger, "Failed to open '%s' for writing: %s", path, currentErrnoString());
165 return false;
166 }
167 size_t n = fwrite(buffer.data(), 1, buffer.size(), file);
168 fclose(file);
169 if (n != buffer.size()) {
170 LOG_ERROR(logger, "Error writing to %s, wrote %zu of %zu: %s", path, n, buffer.size(), currentErrnoString());
171 return false;
172 }
173 return true;
174 }
175
176 [[nodiscard]] bool writeEntityItem(WriterCtx& ctx, Value& entity, const AssetDefinition& definition, size_t entityIx);
177
178 [[nodiscard]] bool writeBBoxProperty(WriterCtx& ctx, Value& entityPoperties, const PropertyRange& propertyRange, const char* what, bool required)
179 {
180 if (uint32_t ix = propertyRange.getPropertyIndex(bboxString); ix != PropertyStore::NoProperty) {
181 std::span<const float> bboxValues = propertyRange.store->getFloatArray(ix);
182 if (bboxValues.size() == 6) {
183 Value bboxValue(kArrayType);
184 for (size_t k = 0; k < 6; k++) { bboxValue.PushBack(Value(bboxValues[k]), ctx.alloc); }
185 entityPoperties.AddMember("bbox", bboxValue, ctx.alloc);
186 return true;
187 }
188 LOG_ERROR(logger, "%s item bounding box has wrong number of elements (%zu != 6)", what, bboxValues.size());
189 return false;
190 }
191 else if (!required) {
192 return true;
193 }
194 LOG_ERROR(logger, "%s item is missing bounding box property", what);
195 return false;
196 }
197
198 [[nodiscard]] bool writeLodGroupItem(WriterCtx& ctx, Value& entity, Value& entityPoperties, const AssetDefinition& definition, size_t entityIx)
199 {
200 const SceneEntityDefinition& entityDef = definition.scene.entities[entityIx];
201 PropertyRange propertyRange = definition.scene.getProperties(entityDef);
202
203 entity.AddMember("type", "LodGroup", ctx.alloc);
204
205 if (std::span<const uint32_t> ids = propertyRange.getProperty(idsString, std::span<const uint32_t>()); !ids.empty()) {
206 Value idsValue(kArrayType);
207 for (const uint32_t id : ids) {
208 idsValue.PushBack(id, ctx.alloc);
209 }
210 entityPoperties.AddMember("ids", idsValue, ctx.alloc);
211 }
212
213 if (!writeBBoxProperty(ctx, entityPoperties, propertyRange, "LodGroup", true)) {
214 return false;
215 }
216
217 if (entityDef.lod.numLods) {
218
219 std::span<const float> errorValues = propertyRange.getProperty(errorsString, std::span<const float>());
220 if (errorValues.size() == entityDef.lod.numLods) {
221 Value value(kArrayType);
222 for (size_t k = 0; k < errorValues.size(); k++) { value.PushBack(Value(errorValues[k]), ctx.alloc); }
223 entityPoperties.AddMember("errors", value, ctx.alloc);
224 }
225 else {
226 LOG_ERROR(logger, "Lod level item count (=%u) does not match number of error values (=%zu)", entityDef.lod.numLods, errorValues.size());
227 return false;
228 }
229
230 Value lods(kArrayType);
231
232 uint32_t childIx = entityDef.firstChild;
233 for (size_t k = 0; k < entityDef.lod.numLods; k++) {
234
235 // No entities at this level, store as empty object
236 if (childIx == ::NoValue) {
237 lods.PushBack(Value(kObjectType), ctx.alloc);
238 }
239
240 else {
241
242 // Sanity check
243 if (definition.scene.entities.size() <= childIx) {
244 LOG_ERROR(logger, "Illegal entity first child %u", childIx);
245 return false;
246 }
247
248 // Single entity at this level (nextLodSibling tie together siblings within a lod level)
249 if (definition.scene[childIx].nextLodSibling == ::NoValue) {
250 Value child(kObjectType);
251 if (!writeEntityItem(ctx, child, definition, childIx)) return false;
252 lods.PushBack(child, ctx.alloc);
253 }
254
255 // Multiple entities at this level, group them into an array
256 else {
257 Value children(kArrayType);
258 while (true) {
259
260 Value child(kObjectType);
261 if (!writeEntityItem(ctx, child, definition, childIx)) return false;
262 children.PushBack(child, ctx.alloc);
263
264 if (definition.scene[childIx].nextLodSibling == ::NoValue) break;
265 childIx = definition.scene[childIx].nextLodSibling;
266 }
267
268 lods.PushBack(children, ctx.alloc);
269 }
270
271 childIx = definition.scene[childIx].nextSibling;
272 }
273 }
274 entity.AddMember("lods", lods, ctx.alloc);
275 }
276 return true;
277 }
278
279 [[nodiscard]] bool writeModelItem(WriterCtx& ctx, Value& entity, Value& entityPoperties, const AssetDefinition& definition, size_t entityIx)
280 {
281 const SceneEntityDefinition& entityDef = definition.scene.entities[entityIx];
282 if (entityDef.model.index == ::NoValue) {
283 LOG_ERROR(logger, "Model without unset source");
284 return false;
285 }
286
287 PropertyRange propertyRange = definition.scene.getProperties(entityDef);
288 if (propertyRange.numProperties) {
289 if(!writeBBoxProperty(ctx, entityPoperties, propertyRange, "Model", false)) {
290 return false;
291 }
292 }
293
294
295 Value path;
296 const PropertyInfo& pathInfo = definition.scene.properties.getPropertyByIndex(entityDef.model.index);
297 switch (pathInfo.type) {
298 case PropertyType::UnsignedInteger:
299 path.SetUint(pathInfo.uintValue);
300 break;
301 case PropertyType::String:
302 path = getStringProperty(ctx, definition, pathInfo);
303 break;
304 default:
305 LOG_ERROR(logger, "Model source is neither uint nor string");
306 return false;
307 }
308
309 // No part, we can just store the model as a string
310 if (entityDef.model.part == ::NoValue) {
311 entity.AddMember("model", path, ctx.alloc);
312 }
313
314 // We have a part, create an object with source and part
315 else {
316 Value modelRef(kObjectType);
317 modelRef.AddMember("source", path, ctx.alloc);
318 if (entityDef.model.part != ::NoValue) {
319 modelRef.AddMember("part", entityDef.model.part, ctx.alloc);
320 }
321 entity.AddMember("model", modelRef, ctx.alloc);
322 }
323
324 return true;
325 }
326
327 [[nodiscard]] bool writeAssetItem(WriterCtx& ctx, Value& entity, Value& /*entityPoperties*/, const AssetDefinition& definition, size_t entityIx)
328 {
329 const SceneEntityDefinition& entityDef = definition.scene.entities[entityIx];
330
331 if (entityDef.asset.index == ::NoValue) {
332 LOG_ERROR(logger, "Asset without source");
333 return false;
334 }
335 Value sourceValue;
336 const PropertyInfo& sourceInfo = definition.scene.properties.getPropertyByIndex(entityDef.asset.index);
337 switch (sourceInfo.type) {
338 case PropertyType::UnsignedInteger:
339 sourceValue.SetUint(sourceInfo.uintValue);
340 break;
341 case PropertyType::String: {
342 sourceValue = getStringProperty(ctx, definition, sourceInfo);
343 break;
344 }
345 default:
346 LOG_ERROR(logger, "Source property is neither uint nor string");
347 return false;
348 }
349
350 // If we only have a source, we can store it as string
351 if (entityDef.asset.flags == 0 && entityDef.asset.material == ::NoValue) {
352 entity.AddMember("asset", sourceValue, ctx.alloc);
353 return true;
354 }
355
356 // Create object
357 Value assetValue(kObjectType);
358 if (entityDef.asset.flags != 0) {
359 std::string flagsString;
360 AssetFlags flags = AssetFlags(entityDef.asset.flags);
361 if ((flags & AssetFlags::InstantiateOnDemand) == AssetFlags::InstantiateOnDemand) {
362 flagsString.append("InstantiateOnDemand | ");
364 }
365 if ((flags & AssetFlags::RelativePaths) == AssetFlags::RelativePaths) {
366 flagsString.append("RelativePaths | ");
368 }
369 if ((flags & AssetFlags::OverrideMaterial) == AssetFlags::OverrideMaterial) {
370 flagsString.append("OverrideMaterial | ");
372 }
373 if (flags != AssetFlags::None) {
374 LOG_ERROR(logger, "Unhandled asset flags");
375 return false;
376 }
377
378 assert(3 < flagsString.length()); // Trim last pipe
379 const Cogs::StringView writeFlagsString(flagsString.c_str(), flagsString.size() - 3);
380 assetValue.AddMember("flags", ctx.encodeString(writeFlagsString), ctx.alloc);
381 }
382
383 assetValue.AddMember("source", sourceValue, ctx.alloc);
384
385 if (entityDef.asset.material != ::NoValue) {
386 const PropertyInfo& materialInfo = definition.scene.properties.getPropertyByIndex(entityDef.asset.material);
387 if (materialInfo.type != PropertyType::String) {
388 LOG_ERROR(logger, "Material property type isn't a string");
389 return false;
390 }
391 assetValue.AddMember("material", getStringProperty(ctx, definition, materialInfo), ctx.alloc);
392 }
393
394 entity.AddMember("asset", assetValue, ctx.alloc);
395 return true;
396 }
397
398 [[nodiscard]] Value encodeFieldValue(WriterCtx& ctx, const FieldValue& fieldValue)
399 {
400 switch (fieldValue.type) {
401 case DefaultValueType::Bool: return Value(fieldValue.boolValue);
402 case DefaultValueType::Float: return Value(double(fieldValue.floatValue));
403 case DefaultValueType::Int: return Value(fieldValue.intValue);
404 case DefaultValueType::Enum: return Value(fieldValue.intValue);
405 case DefaultValueType::Vec2: return ctx.encodeSpan(std::span<const float>(glm::value_ptr(fieldValue.float2), 2));
406 case DefaultValueType::Vec3: return ctx.encodeSpan(std::span<const float>(glm::value_ptr(fieldValue.float3), 3));
407 case DefaultValueType::Vec4: return ctx.encodeSpan(std::span<const float>(glm::value_ptr(fieldValue.float4), 4));
408 case DefaultValueType::Mat4: return ctx.encodeSpan(std::span<const float>(glm::value_ptr(fieldValue.mat4), 16));
409 case DefaultValueType::DVec2: return ctx.encodeSpan(std::span<const double>(glm::value_ptr(fieldValue.double2), 2));
410 case DefaultValueType::DVec3: return ctx.encodeSpan(std::span<const double>(glm::value_ptr(fieldValue.double3), 3));
411 case DefaultValueType::DVec4: return ctx.encodeSpan(std::span<const double>(glm::value_ptr(fieldValue.double4), 4));
412 case DefaultValueType::Quaternion: return ctx.encodeSpan(std::span<const float>(glm::value_ptr(fieldValue.quat), 4));
413 case DefaultValueType::String:
414 case DefaultValueType::Model:
415 case DefaultValueType::Asset:
416 case DefaultValueType::Texture:
417 case DefaultValueType::Gui: return ctx.encodeString(fieldValue.value);
418 case DefaultValueType::MaterialInstance: {
419 const MaterialInstanceDefinition* matDef = fieldValue.asMaterialInstance();
420 if (matDef && !matDef->reference.empty()) {
421 return ctx.encodeString(std::string("$") + matDef->reference);
422 }
423 break;
424 }
425 case DefaultValueType::Unknown:
426 case DefaultValueType::Mesh:
427 case DefaultValueType::MultiString:
428 case DefaultValueType::IntArray:
429 case DefaultValueType::FloatArray:
430 case DefaultValueType::Vec2Array:
431 case DefaultValueType::Vec3Array:
432 case DefaultValueType::Vec4Array:
433 case DefaultValueType::Entity:
434 case DefaultValueType::EntityArray:
435 case DefaultValueType::Extension:
436 LOG_ERROR(logger, "Unhandled value type %u", uint32_t(fieldValue.type));
437 break;
438 default:
439 assert(false && "Illegal enum value");
440 }
441 return Value();
442 }
443
444 [[nodiscard]] bool writeEntityItem(WriterCtx& ctx, Value& entity, const AssetDefinition& definition, size_t entityIx)
445 {
446 const SceneEntityDefinition& entityDef = definition.scene.entities[entityIx];
447 PropertyRange propertyRange = definition.scene.getProperties(entityDef);
448
449 if ((ctx.writeFlags & AssetWriteFlags::Strip) != AssetWriteFlags::Strip) {
450 if (entityDef.nameIndex != ::NoValue) {
451 Cogs::StringView name = definition.scene.properties.getString(definition.scene.properties.getPropertyByIndex(entityDef.nameIndex));
452 if (!name.empty()) {
453 entity.AddMember("name", ctx.encodeString(name), ctx.alloc);
454 }
455 }
456 }
457
458 Value entityPoperties(kObjectType);
459 if (entityDef.isLodGroup()) {
460 if (!writeLodGroupItem(ctx, entity, entityPoperties, definition, entityIx)) return false;
461 }
462 else if (entityDef.isModel()) {
463 if (!writeModelItem(ctx, entity, entityPoperties, definition, entityIx)) return false;
464 }
465 else if (entityDef.isAsset()) {
466 if (!writeAssetItem(ctx, entity, entityPoperties, definition, entityIx)) return false;
467 }
468 else if(!entityDef.isEmpty()) {
469
470 const Cogs::StringView entityType = Strings::get(entityDef.type);
471 if (!entityType.empty()) {
472 entity.AddMember("type", ctx.encodeString(entityType), ctx.alloc);
473 }
474 else {
475 LOG_WARNING(logger, "Entity without type");
476 }
477
478
479 if (entityDef.numFields) {
480 for (const FieldValue& entry : std::span(definition.scene.fieldValues).subspan(entityDef.firstField, entityDef.numFields)) {
481
482 if (entry.componentId == Cogs::Reflection::NoType) {
483 LOG_WARNING(logger, "Missing field component type, ignoring");
484 continue;
485 }
486
487 if(entry.fieldId == Cogs::Reflection::NoField) {
488 LOG_WARNING(logger, "Missing field id, ignoring");
489 continue;
490 }
491
492 const Cogs::Reflection::Type& componentType = Cogs::Reflection::TypeDatabase::getType(entry.componentId);
493 const Cogs::Reflection::Field* field = componentType.getField(entry.fieldId);
494 if (!field) {
495 LOG_ERROR(logger, "Field %u not found on type %s.", uint32_t(entry.fieldId), componentType.getName().c_str());
496 continue;
497 }
498
499 std::string key;
500 key.append(componentType.getName().c_str());
501 key.append(1, '.');
502 key.append(field->getName().c_str());
503 Value keyValue = ctx.encodeString(key);
504
505 entity.AddMember(keyValue, encodeFieldValue(ctx, entry), ctx.alloc);
506 }
507 }
508
509 if (entityDef.numProperties) {
510 for (uint32_t i = 0; i < entityDef.numProperties; i++) {
511 const PropertyInfo& propInfo = definition.scene.properties.getPropertyByIndex(entityDef.firstProperty + i);
512
513 Value keyValue = ctx.encodeString(Strings::get(propInfo.key));
514
515 switch (propInfo.type) {
516 case PropertyType::Unknown:
517 LOG_ERROR(logger, "Unhandled property type %u", uint32_t(propInfo.type));
518 break;
519 case PropertyType::Bool:
520 entityPoperties.AddMember(keyValue, propInfo.boolValue, ctx.alloc);
521 break;
522 case PropertyType::Integer:
523 entityPoperties.AddMember(keyValue, propInfo.intValue, ctx.alloc);
524 break;
525 case PropertyType::Int2:
526 entityPoperties.AddMember(keyValue, ctx.encodeSpan(std::span<const int32_t>(propInfo.int2Value, 2)), ctx.alloc);
527 break;
528 case PropertyType::UnsignedInteger:
529 entityPoperties.AddMember(keyValue, propInfo.uintValue, ctx.alloc);
530 break;
531 case PropertyType::UInt2:
532 entityPoperties.AddMember(keyValue, ctx.encodeSpan(std::span<const uint32_t>(propInfo.uint2Value, 2)), ctx.alloc);
533 break;
534 case PropertyType::Float:
535 entityPoperties.AddMember(keyValue, propInfo.floatValue, ctx.alloc);
536 break;
537 case PropertyType::Float2:
538 entityPoperties.AddMember(keyValue, ctx.encodeSpan(std::span<const float>(propInfo.float2Value, 2)), ctx.alloc);
539 break;
540 case PropertyType::Double:
541 entityPoperties.AddMember(keyValue, propInfo.doubleValue, ctx.alloc);
542 break;
543 case PropertyType::StringRef:
544 entityPoperties.AddMember(keyValue, ctx.encodeString(Strings::get(propInfo.stringRefValue)), ctx.alloc);
545 break;
546 case PropertyType::String:
547 entityPoperties.AddMember(keyValue, ctx.encodeString(definition.scene.properties.getString(propInfo)), ctx.alloc);
548 break;
549 case PropertyType::FloatArray:
550 entityPoperties.AddMember(keyValue, ctx.encodeSpan(definition.scene.properties.getFloatArray(propInfo)), ctx.alloc);
551 break;
552 case PropertyType::IntArray:
553 entityPoperties.AddMember(keyValue, ctx.encodeSpan(definition.scene.properties.getIntArray(propInfo)), ctx.alloc);
554 break;
555 case PropertyType::UIntArray:
556 entityPoperties.AddMember(keyValue, ctx.encodeSpan(definition.scene.properties.getUIntArray(propInfo)), ctx.alloc);
557 break;
558 case PropertyType::DoubleArray:
559 entityPoperties.AddMember(keyValue, ctx.encodeSpan(definition.scene.properties.getDoubleArray(propInfo)), ctx.alloc);
560 break;
561 default:
562 assert(false && "Illegal enum value");
563 return false;
564 }
565 }
566 }
567 }
568
569 if (entityPoperties.MemberCount()) {
570 entity.AddMember("properties", entityPoperties, ctx.alloc);
571 }
572 return true;
573 }
574
575}
576
577
578bool Cogs::Core::writeAsset(Context * context, StringView fileName, AssetWriteFlags flags, ComponentModel::Entity * root)
579{
580 std::string saveFileName(fileName);
581 if (!root) {
582 auto file = fopen(saveFileName.c_str(), "w");
583 if (file == nullptr) {
584 LOG_ERROR(logger, "Failed to open output file: %s", saveFileName.c_str());
585 return false;
586 }
587
588 std::vector<char> buffer(16384);
589
590 FileWriteStream stream(file, buffer.data(), buffer.size());
591 PrettyWriter<FileWriteStream> out(stream);
592
593 WriterCtx ctx;
594 ctx.context = context;
595 ctx.writeFlags = flags;
596
597 Document d;
598 auto & a = d.GetAllocator();
599 d.SetObject();
600
601 {
602 Value metaValue(kObjectType);
603
604 metaValue.AddMember("flags", "true", a);
605
606 d.AddMember("meta", metaValue, a);
607 }
608
609 {
610 Value resourcesValue(kObjectType);
611 Value entitiesValue(kArrayType);
612
613 auto store = context->store;
614 auto & entities = store->getEntities();
615
617
618 for (auto & e : entities) {
619 auto entity = e.second;
620
621 if (entity) {
622 auto transformComponent = entity->getComponent<TransformComponent>();
623
624 if (transformComponent && transformComponent->parent) continue;
625
626 Value entityValue(kObjectType);
627
628 writeEntity(context, entity.get(), entityValue, d, &sc, flags);
629
630 entitiesValue.PushBack(entityValue, a);
631 }
632 }
633
634 for (auto& r : sc.assets) {
635 Value resourceValue(kObjectType);
636 resourceValue.AddMember("type", "Asset", a);
637 resourceValue.AddMember("source", ctx.encodeString(r->getSource()), a);
638
639 resourcesValue.AddMember(ctx.encodeString(r->getName()), resourceValue, a);
640 }
641
642 for (auto & r : sc.models) {
643 Value resourceValue(kObjectType);
644 resourceValue.AddMember("type", "Model", a);
645 resourceValue.AddMember("source", ctx.encodeString(r->getSource()), a);
646
647 resourcesValue.AddMember(ctx.encodeString(r->getName()), resourceValue, a);
648 }
649
650 Value materialInstances(kObjectType);
651
652 for (auto & r : sc.materialInstances) {
653 auto instance = (MaterialInstance *)r.get();
654 auto material = instance->material;
655
656 Value resourceValue(kObjectType);
657 resourceValue.AddMember("type", "MaterialInstance", a);
658 resourceValue.AddMember("material", ctx.encodeString(material->getName()), a);
659 resourceValue.AddMember("permutation", ctx.encodeString(material->permutationKeys[instance->permutationIndex]), a);
660
661 for (auto & tProp : material->textureProperties) {
662 auto instanceValue = instance->getTextureProperty(tProp.key);
663 if (HandleIsValid(instanceValue.texture.handle) && instanceValue.texture.handle != tProp.texture.handle) {
664 auto texName = instanceValue.texture.handle->getName();
665 texName = "$" + texName.to_string();
666
667 resourceValue.AddMember(ctx.encodeString(tProp.name), ctx.encodeString(texName), a);
668
669 sc.textures.insert(instanceValue.texture.handle);
670 }
671 }
672
673 for (auto & prop : material->constantBuffers.variables) {
674 switch (prop.type) {
675 case MaterialDataType::Float:
676 {
677 auto instanceValue = instance->getFloatProperty(prop.key);
678
679 if (instanceValue != prop.defaultFloat()) {
680 resourceValue.AddMember(ctx.encodeString(prop.name), Value((double)instanceValue), a);
681 }
682
683 break;
684 }
685 case MaterialDataType::Float4:
686 {
687 auto instanceValue = instance->getVec4Property(prop.key);
688
689 if (instanceValue != prop.defaultVec4()) {
690 Value v;
691 v.SetArray();
692
693 for (int i = 0; i < 4; ++i) {
694 v.PushBack(instanceValue[i], a);
695 }
696
697 resourceValue.AddMember(ctx.encodeString(prop.name), v, a);
698 }
699
700 break;
701 }
702 case MaterialDataType::Float3:
703 {
704 auto instanceValue = instance->getVec3Property(prop.key);
705
706 if (instanceValue != prop.defaultVec3()) {
707 Value v;
708 v.SetArray();
709
710 for (int i = 0; i < 3; ++i) {
711 v.PushBack(instanceValue[i], a);
712 }
713
714 resourceValue.AddMember(ctx.encodeString(prop.name), v, a);
715 }
716
717 break;
718 }
719 case MaterialDataType::Float2:
720 {
721 auto instanceValue = instance->getVec2Property(prop.key);
722
723 if (instanceValue != prop.defaultVec2()) {
724 Value v;
725 v.SetArray();
726
727 for (int i = 0; i < 2; ++i) {
728 v.PushBack(instanceValue[i], a);
729 }
730
731 resourceValue.AddMember(ctx.encodeString(prop.name), v, a);
732 }
733
734 break;
735 }
736 case MaterialDataType::UInt:
737 {
738 uint32_t instanceValue = instance->getProperty<uint32_t>(prop.key);
739 if (instanceValue != prop.defaultUInt()) {
740 resourceValue.AddMember(ctx.encodeString(prop.name), Value(instanceValue), a);
741 }
742
743 break;
744 }
745 case MaterialDataType::Bool:
746 {
747 bool instanceValue = instance->getProperty<bool>(prop.key);
748 if (instanceValue != prop.defaultBool()) {
749 resourceValue.AddMember(ctx.encodeString(prop.name), Value(instanceValue ? "true" : "false", a), a);
750 }
751
752 break;
753 }
754 default:
755 LOG_WARNING(logger, "Skipped serialization of unsupported material property %s. Typeid:%s", prop.name.c_str(), Cogs::Core::DataTypeNames[int(prop.type)]);
756 break;
757 }
758 }
759
760 Value variantsValue(kObjectType);
761
762 const ShaderVariants& variants = material->definition.variants;
763 for (const ShaderVariantSelector& selector : instance->variantSelectors) {
764
765 const ShaderVariantDefinition& variant = variants[selector.index];
766 if (variant.isShared) continue; // Do not store shared variants.
767
768 if (selector.value != variant.defaultValue) {
769 switch (variant.type)
770 {
771 case ShaderVariantType::Bool:
772 {
773 Value b;
774 b.SetBool(selector.value != 0);
775
776 variantsValue.AddMember(ctx.encodeString(variant.name), b, a);
777 }
778 break;
779 case ShaderVariantType::Enum:
780 {
781 Value b;
782 b.SetString(variant.values[selector.value].key.c_str(), a);
783
784 variantsValue.AddMember(ctx.encodeString(variant.name), b, a);
785 }
786 break;
787 case ShaderVariantType::Int:
788 {
789 Value i;
790 i.SetInt((int)selector.value);
791
792 variantsValue.AddMember(ctx.encodeString(variant.name), i, a);
793 }
794 break;
795 default:
796 LOG_WARNING(logger, "Skipped serialization of unsupported material variant %s", variant.name.c_str());
797 break;
798 }
799 }
800 }
801
802 if (variantsValue.MemberCount()) {
803 resourceValue.AddMember("variants", variantsValue, a);
804 }
805
806 materialInstances.AddMember(ctx.encodeString(r->getName()), resourceValue, a);
807 }
808
809 for (auto & r : sc.textures) {
810 auto texture = (Texture *)r.get();
811
812 Value resourceValue(kObjectType);
813 resourceValue.AddMember("type", "Texture", a);
814 resourceValue.AddMember("source", ctx.encodeString(r->getSource()), a);
815
816 if (texture->description.format != TextureFormat::R8G8B8A8_UNORM_SRGB) {
817 resourceValue.AddMember("flags", "LinearColorSpace", a);
818 }
819
820 resourcesValue.AddMember(ctx.encodeString(r->getName()), resourceValue, a);
821 }
822
823 for (auto & m : materialInstances.GetObject()) {
824 resourcesValue.AddMember(m.name, m.value, a);
825 }
826
827 if (resourcesValue.MemberCount()) {
828 d.AddMember("resources", resourcesValue, a);
829 }
830
831 d.AddMember("entities", entitiesValue, a);
832 }
833
834 bool ok = d.Accept(out);
835 if (!ok) {
836 LOG_ERROR(logger, "JSon serialization failed.");
837 }
838
839 stream.Flush();
840
841 fclose(file);
842 return ok;
843 }
844
845 LOG_ERROR(logger, "Write specific entity not supported (only null allowed).");
846 return false;
847}
848
849bool Cogs::Core::writeAsset(class Context* context, const char* path, AssetHandle assetHandle, AssetWriteFlags writeFlags)
850{
851 if (!HandleIsValid(assetHandle)) {
852 LOG_ERROR(logger, "Undefined asset");
853 return false;
854 }
855 Asset* asset = assetHandle.resolve();
856
857 if (!asset->isLoaded()) {
858 LOG_ERROR(logger, "Asset is not loaded");
859 return false;
860 }
861 const AssetDefinition& definition = asset->definition;
862
863 WriterCtx ctx;
864 ctx.context = context;
865 ctx.writeFlags = writeFlags;
866
867 Value resources(kObjectType);
868 for (const ResourceDefinition& resDef : definition.resources) {
869
870 if (resDef.name.empty()) {
871 LOG_ERROR(logger, "Resource without name, ignoring");
872 continue;
873 }
874
875 Value resource(kObjectType);
876 switch (resDef.type) {
877 case ResourceTypes::Unknown:
878 LOG_ERROR(logger, "Unknown resource type encountered, ignoring");
879 continue;
880 case ResourceTypes::Asset: resource.AddMember("type", "Asset", ctx.alloc); break;
881 case ResourceTypes::Model: resource.AddMember("type", "Model", ctx.alloc); break;
882 case ResourceTypes::Texture: resource.AddMember("type", "Texture", ctx.alloc); break;
883 case ResourceTypes::Effect: resource.AddMember("type", "Effect", ctx.alloc); break;
884 case ResourceTypes::Material: resource.AddMember("type", "Material", ctx.alloc); break;
885 case ResourceTypes::MaterialInstance: resource.AddMember("type", "MaterialInstance", ctx.alloc); break;
886 case ResourceTypes::Mesh: resource.AddMember("type", "Mesh", ctx.alloc); break;
887 case ResourceTypes::Buffer: resource.AddMember("type", "Buffer", ctx.alloc); break;
888 case ResourceTypes::Font: resource.AddMember("type", "Font", ctx.alloc);
889 break;
890 case ResourceTypes::Gui:
891 resource.AddMember("type", "Gui", ctx.alloc);
892 break;
893 default:
894 assert(false && "Invalid enum");
895 return false;
896 }
897 if (!resDef.source.empty()) {
898 resource.AddMember("source", ctx.encodeString(resDef.source), ctx.alloc);
899 }
900
901 switch (resDef.type) {
902 case ResourceTypes::Material:
903 case ResourceTypes::MaterialInstance:
904 if (!resDef.material.empty()) resource.AddMember("material", ctx.encodeString(resDef.material), ctx.alloc);
905 if (!resDef.permutation.empty()) resource.AddMember("permutation", ctx.encodeString(resDef.material), ctx.alloc);
906 if (!resDef.options.empty()) {
907 Value options(kObjectType);
908 for (const auto& item : resDef.options) {
909 options.AddMember(ctx.encodeString(item.first), ctx.encodeString(item.second), ctx.alloc);
910 }
911 resource.AddMember("options", options, ctx.alloc);
912 }
913 if (!resDef.variants.empty()) {
914 Value options(kObjectType);
915 for (const auto& item : resDef.variants) {
916 options.AddMember(ctx.encodeString(item.first), ctx.encodeString(item.second), ctx.alloc);
917 }
918 resource.AddMember("variants", options, ctx.alloc);
919 }
920 if (!resDef.properties.empty()) {
921 for (auto& item : resDef.properties) {
922 resource.AddMember(ctx.encodeString(item.first), encodeFieldValue(ctx, item.second), ctx.alloc);
923 }
924 }
925 break;
926 default:
927 break;
928
929
930 }
931
932 resources.AddMember(ctx.encodeString(resDef.name), resource, ctx.alloc);
933 }
934
935 Value roots(kArrayType);
936 for (size_t i = 0; i < definition.scene.entities.size(); i++) {
937 const SceneEntityDefinition& entityDef = definition.scene.entities[i];
938 if (entityDef.parentIndex == ::NoValue) {
939
940 Value entity(kObjectType);
941 if (!writeEntityItem(ctx, entity, definition, i)) return false;
942 roots.PushBack(entity, ctx.alloc);
943 }
944 }
945
946 return writeDoc(ctx, roots, resources, path);
947}
948
949#else
950
951bool Cogs::Core::writeAsset(Context * /*context*/, StringView /*fileName*/, AssetWriteFlags /*flags*/, ComponentModel::Entity * /*root*/)
952{
953 return false;
954}
955
956bool Cogs::Core::writeAsset(class Context* /*context*/, const char* /*path*/, AssetHandle /*asset*/, AssetWriteFlags /*writeFlags*/)
957{
958 return false;
959}
960
961#endif
Container for components, providing composition of dynamic entities.
Definition: Entity.h:18
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class EntityStore * store
Entity store.
Definition: Context.h:231
const std::unordered_map< EntityId, EntityPtr > & getEntities() const
Return map of entities with global ownership.
Definition: EntityStore.h:268
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Log implementation class.
Definition: LogManager.h:139
Field definition describing a single data member of a data structure.
Definition: Field.h:68
const Name & getName() const
Get the name of the field.
Definition: Field.h:136
static const Type & getType()
Get the Type of the given template argument.
Definition: TypeDatabase.h:168
Represents a discrete type definition, describing a native type class.
Definition: Type.h:89
constexpr const Name & getName() const
Get the unique name of the type.
Definition: Type.h:198
const Field * getField(const Name &name) const
Get a pointer to the field info of the field with the given name.
Definition: Type.cpp:53
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
constexpr const char * data() const noexcept
Get the sequence of characters referenced by the string view.
Definition: StringView.h:171
constexpr size_t length() const noexcept
Get the length of the string.
Definition: StringView.h:185
constexpr bool empty() const noexcept
Check if the string is empty.
Definition: StringView.h:122
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.
AssetWriteFlags
Flags that control serialization of assets.
void COGSCORE_DLL_API writeEntity(Context *context, ComponentModel::Entity *entity, rapidjson::Value &object, rapidjson::Document &d, SerializationContext *sc=nullptr, const AssetWriteFlags flags=AssetWriteFlags::None)
Serialize entity adding entity to the given parent 'object'.
AssetFlags
Controls asset system's model instance behaviour.
@ InstantiateOnDemand
Dynamically calculate lod levels and only instantiate what is visible.
@ RelativePaths
Paths in asset file is relative to file's path.
@ OverrideMaterial
Override any model's material with material member of asset component.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
Contains reflection support.
Definition: Component.h:11
constexpr FieldId NoField
No field id.
Definition: Name.h:60
constexpr TypeId NoType
Definition of no type.
Definition: Name.h:51
Defines a value to apply to a field.
DefaultValueType type
Type of the field.
Material instances represent a specialized Material combined with state for all its buffers and prope...
static constexpr uint32_t NoProperty
Return from findProperty if key not found.
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
struct Cogs::Core::SceneEntityDefinition::@28::@31 model
SceneEntityFlags::Model is set.
uint32_t flags
Really enum of SceneEntityFlags.
struct Cogs::Core::SceneEntityDefinition::@28::@32 lod
SceneEntityFlags::LodGroup is set.
StringRef type
Neither SceneEntityFlags::Asset, SceneEntityFlags::Model, nor SceneEntityFlags::LodGroup is set.
struct Cogs::Core::SceneEntityDefinition::@28::@30 asset
SceneEntityFlags::Asset is set.
Texture resources contain raster bitmap data to use for texturing.
Definition: Texture.h:91
const char * c_str() const
Gets the name as a null-terminated string.
Definition: Name.h:116