1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following conditions are met:
12
13* Redistributions of source code must retain the above
14copyright notice, this list of conditions and the
15following disclaimer.
16
17* Redistributions in binary form must reproduce the above
18copyright notice, this list of conditions and the
19following disclaimer in the documentation and/or other
20materials provided with the distribution.
21
22* Neither the name of the assimp team, nor the names of its
23contributors may be used to endorse or promote products
24derived from this software without specific prior
25written permission of the assimp team.
26
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39----------------------------------------------------------------------
40*/
41
42#include <rapidjson/stringbuffer.h>
43#include <rapidjson/writer.h>
44#include <rapidjson/prettywriter.h>
45
46namespace glTF {
47
48 using rapidjson::StringBuffer;
49 using rapidjson::PrettyWriter;
50 using rapidjson::Writer;
51 using rapidjson::StringRef;
52 using rapidjson::StringRef;
53
54 namespace {
55
56 template<size_t N>
57 inline Value& MakeValue(Value& val, float(&r)[N], MemoryPoolAllocator<>& al) {
58 val.SetArray();
59 val.Reserve(N, al);
60 for (decltype(N) i = 0; i < N; ++i) {
61 val.PushBack(r[i], al);
62 }
63 return val;
64 }
65
66 inline Value& MakeValue(Value& val, const std::vector<float> & r, MemoryPoolAllocator<>& al) {
67 val.SetArray();
68 val.Reserve(static_cast<rapidjson::SizeType>(r.size()), al);
69 for (unsigned int i = 0; i < r.size(); ++i) {
70 val.PushBack(r[i], al);
71 }
72 return val;
73 }
74
75 template<class T>
76 inline void AddRefsVector(Value& obj, const char* fieldId, std::vector< Ref<T> >& v, MemoryPoolAllocator<>& al) {
77 if (v.empty()) return;
78 Value lst;
79 lst.SetArray();
80 lst.Reserve(unsigned(v.size()), al);
81 for (size_t i = 0; i < v.size(); ++i) {
82 lst.PushBack(StringRef(v[i]->id), al);
83 }
84 obj.AddMember(StringRef(fieldId), lst, al);
85 }
86
87
88 }
89
90 inline void Write(Value& obj, Accessor& a, AssetWriter& w)
91 {
92 obj.AddMember("bufferView", Value(a.bufferView->id, w.mAl).Move(), w.mAl);
93 obj.AddMember("byteOffset", a.byteOffset, w.mAl);
94 obj.AddMember("byteStride", a.byteStride, w.mAl);
95 obj.AddMember("componentType", int(a.componentType), w.mAl);
96 obj.AddMember("count", a.count, w.mAl);
97 obj.AddMember("type", StringRef(AttribType::ToString(a.type)), w.mAl);
98
99 Value vTmpMax, vTmpMin;
100 obj.AddMember("max", MakeValue(vTmpMax, a.max, w.mAl), w.mAl);
101 obj.AddMember("min", MakeValue(vTmpMin, a.min, w.mAl), w.mAl);
102 }
103
104 inline void Write(Value& obj, Animation& a, AssetWriter& w)
105 {
106 /****************** Channels *******************/
107 Value channels;
108 channels.SetArray();
109 channels.Reserve(unsigned(a.Channels.size()), w.mAl);
110
111 for (size_t i = 0; i < unsigned(a.Channels.size()); ++i) {
112 Animation::AnimChannel& c = a.Channels[i];
113 Value valChannel;
114 valChannel.SetObject();
115 {
116 valChannel.AddMember("sampler", c.sampler, w.mAl);
117
118 Value valTarget;
119 valTarget.SetObject();
120 {
121 valTarget.AddMember("id", StringRef(c.target.id->id), w.mAl);
122 valTarget.AddMember("path", c.target.path, w.mAl);
123 }
124 valChannel.AddMember("target", valTarget, w.mAl);
125 }
126 channels.PushBack(valChannel, w.mAl);
127 }
128 obj.AddMember("channels", channels, w.mAl);
129
130 /****************** Parameters *******************/
131 Value valParameters;
132 valParameters.SetObject();
133 {
134 if (a.Parameters.TIME) {
135 valParameters.AddMember("TIME", StringRef(a.Parameters.TIME->id), w.mAl);
136 }
137 if (a.Parameters.rotation) {
138 valParameters.AddMember("rotation", StringRef(a.Parameters.rotation->id), w.mAl);
139 }
140 if (a.Parameters.scale) {
141 valParameters.AddMember("scale", StringRef(a.Parameters.scale->id), w.mAl);
142 }
143 if (a.Parameters.translation) {
144 valParameters.AddMember("translation", StringRef(a.Parameters.translation->id), w.mAl);
145 }
146 }
147 obj.AddMember("parameters", valParameters, w.mAl);
148
149 /****************** Samplers *******************/
150 Value valSamplers;
151 valSamplers.SetObject();
152
153 for (size_t i = 0; i < unsigned(a.Samplers.size()); ++i) {
154 Animation::AnimSampler& s = a.Samplers[i];
155 Value valSampler;
156 valSampler.SetObject();
157 {
158 valSampler.AddMember("input", s.input, w.mAl);
159 valSampler.AddMember("interpolation", s.interpolation, w.mAl);
160 valSampler.AddMember("output", s.output, w.mAl);
161 }
162 valSamplers.AddMember(StringRef(s.id), valSampler, w.mAl);
163 }
164 obj.AddMember("samplers", valSamplers, w.mAl);
165 }
166
167 inline void Write(Value& obj, Buffer& b, AssetWriter& w)
168 {
169 const char* type;
170 switch (b.type) {
171 case Buffer::Type_text:
172 type = "text"; break;
173 default:
174 type = "arraybuffer";
175 }
176
177 obj.AddMember("byteLength", static_cast<uint64_t>(b.byteLength), w.mAl);
178 obj.AddMember("type", StringRef(type), w.mAl);
179 obj.AddMember("uri", Value(b.GetURI(), w.mAl).Move(), w.mAl);
180 }
181
182 inline void Write(Value& obj, BufferView& bv, AssetWriter& w)
183 {
184 obj.AddMember("buffer", Value(bv.buffer->id, w.mAl).Move(), w.mAl);
185 obj.AddMember("byteOffset", static_cast<uint64_t>(bv.byteOffset), w.mAl);
186 obj.AddMember("byteLength", static_cast<uint64_t>(bv.byteLength), w.mAl);
187 obj.AddMember("target", int(bv.target), w.mAl);
188 }
189
190 inline void Write(Value& /*obj*/, Camera& /*c*/, AssetWriter& /*w*/)
191 {
192
193 }
194
195 inline void Write(Value& obj, Image& img, AssetWriter& w)
196 {
197 std::string uri;
198 if (w.mAsset.extensionsUsed.KHR_binary_glTF && img.bufferView) {
199 Value exts, ext;
200 exts.SetObject();
201 ext.SetObject();
202
203 ext.AddMember("bufferView", StringRef(img.bufferView->id), w.mAl);
204
205 if (!img.mimeType.empty())
206 ext.AddMember("mimeType", StringRef(img.mimeType), w.mAl);
207
208 exts.AddMember("KHR_binary_glTF", ext, w.mAl);
209 obj.AddMember("extensions", exts, w.mAl);
210 return;
211 }
212 else if (img.HasData()) {
213 uri = "data:" + (img.mimeType.empty() ? "application/octet-stream" : img.mimeType);
214 uri += ";base64,";
215 Util::EncodeBase64(img.GetData(), img.GetDataLength(), uri);
216 }
217 else {
218 uri = img.uri;
219 }
220
221 obj.AddMember("uri", Value(uri, w.mAl).Move(), w.mAl);
222 }
223
224 namespace {
225 inline void WriteColorOrTex(Value& obj, TexProperty& prop, const char* propName, MemoryPoolAllocator<>& al)
226 {
227 if (prop.texture)
228 obj.AddMember(StringRef(propName), Value(prop.texture->id, al).Move(), al);
229 else {
230 Value col;
231 obj.AddMember(StringRef(propName), MakeValue(col, prop.color, al), al);
232 }
233 }
234 }
235
236 inline void Write(Value& obj, Material& m, AssetWriter& w)
237 {
238 Value v;
239 v.SetObject();
240 {
241 WriteColorOrTex(v, m.ambient, "ambient", w.mAl);
242 WriteColorOrTex(v, m.diffuse, "diffuse", w.mAl);
243 WriteColorOrTex(v, m.specular, "specular", w.mAl);
244 WriteColorOrTex(v, m.emission, "emission", w.mAl);
245
246 if (m.transparent)
247 v.AddMember("transparency", m.transparency, w.mAl);
248
249 v.AddMember("shininess", m.shininess, w.mAl);
250 }
251 obj.AddMember("values", v, w.mAl);
252 }
253
254 namespace {
255 inline void WriteAttrs(AssetWriter& w, Value& attrs, Mesh::AccessorList& lst,
256 const char* semantic, bool forceNumber = false)
257 {
258 if (lst.empty()) return;
259 if (lst.size() == 1 && !forceNumber) {
260 attrs.AddMember(StringRef(semantic), Value(lst[0]->id, w.mAl).Move(), w.mAl);
261 }
262 else {
263 for (size_t i = 0; i < lst.size(); ++i) {
264 char buffer[32];
265 ai_snprintf(buffer, 32, "%s_%d", semantic, int(i));
266 attrs.AddMember(Value(buffer, w.mAl).Move(), Value(lst[i]->id, w.mAl).Move(), w.mAl);
267 }
268 }
269 }
270 }
271
272 inline void Write(Value& obj, Mesh& m, AssetWriter& w)
273 {
274 /********************* Name **********************/
275 obj.AddMember("name", m.name, w.mAl);
276
277 /**************** Mesh extensions ****************/
278 if(m.Extension.size() > 0)
279 {
280 Value json_extensions;
281
282 json_extensions.SetObject();
283 for(Mesh::SExtension* ptr_ext : m.Extension)
284 {
285 switch(ptr_ext->Type)
286 {
287#ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC
288 case Mesh::SExtension::EType::Compression_Open3DGC:
289 {
290 Value json_comp_data;
291 Mesh::SCompression_Open3DGC* ptr_ext_comp = (Mesh::SCompression_Open3DGC*)ptr_ext;
292
293 // filling object "compressedData"
294 json_comp_data.SetObject();
295 json_comp_data.AddMember("buffer", ptr_ext_comp->Buffer, w.mAl);
296 json_comp_data.AddMember("byteOffset", ptr_ext_comp->Offset, w.mAl);
297 json_comp_data.AddMember("componentType", 5121, w.mAl);
298 json_comp_data.AddMember("type", "SCALAR", w.mAl);
299 json_comp_data.AddMember("count", ptr_ext_comp->Count, w.mAl);
300 if(ptr_ext_comp->Binary)
301 json_comp_data.AddMember("mode", "binary", w.mAl);
302 else
303 json_comp_data.AddMember("mode", "ascii", w.mAl);
304
305 json_comp_data.AddMember("indicesCount", ptr_ext_comp->IndicesCount, w.mAl);
306 json_comp_data.AddMember("verticesCount", ptr_ext_comp->VerticesCount, w.mAl);
307 // filling object "Open3DGC-compression"
308 Value json_o3dgc;
309
310 json_o3dgc.SetObject();
311 json_o3dgc.AddMember("compressedData", json_comp_data, w.mAl);
312 // add member to object "extensions"
313 json_extensions.AddMember("Open3DGC-compression", json_o3dgc, w.mAl);
314 }
315
316 break;
317#endif
318 default:
319 throw DeadlyImportError("GLTF: Can not write mesh: unknown mesh extension, only Open3DGC is supported.");
320 }// switch(ptr_ext->Type)
321 }// for(Mesh::SExtension* ptr_ext : m.Extension)
322
323 // Add extensions to mesh
324 obj.AddMember("extensions", json_extensions, w.mAl);
325 }// if(m.Extension.size() > 0)
326
327 /****************** Primitives *******************/
328 Value primitives;
329 primitives.SetArray();
330 primitives.Reserve(unsigned(m.primitives.size()), w.mAl);
331
332 for (size_t i = 0; i < m.primitives.size(); ++i) {
333 Mesh::Primitive& p = m.primitives[i];
334 Value prim;
335 prim.SetObject();
336 {
337 prim.AddMember("mode", Value(int(p.mode)).Move(), w.mAl);
338
339 if (p.material)
340 prim.AddMember("material", p.material->id, w.mAl);
341
342 if (p.indices)
343 prim.AddMember("indices", Value(p.indices->id, w.mAl).Move(), w.mAl);
344
345 Value attrs;
346 attrs.SetObject();
347 {
348 WriteAttrs(w, attrs, p.attributes.position, "POSITION");
349 WriteAttrs(w, attrs, p.attributes.normal, "NORMAL");
350 WriteAttrs(w, attrs, p.attributes.texcoord, "TEXCOORD", true);
351 WriteAttrs(w, attrs, p.attributes.color, "COLOR");
352 WriteAttrs(w, attrs, p.attributes.joint, "JOINT");
353 WriteAttrs(w, attrs, p.attributes.jointmatrix, "JOINTMATRIX");
354 WriteAttrs(w, attrs, p.attributes.weight, "WEIGHT");
355 }
356 prim.AddMember("attributes", attrs, w.mAl);
357 }
358 primitives.PushBack(prim, w.mAl);
359 }
360
361 obj.AddMember("primitives", primitives, w.mAl);
362 }
363
364 inline void Write(Value& obj, Node& n, AssetWriter& w)
365 {
366
367 if (n.matrix.isPresent) {
368 Value val;
369 obj.AddMember("matrix", MakeValue(val, n.matrix.value, w.mAl).Move(), w.mAl);
370 }
371
372 if (n.translation.isPresent) {
373 Value val;
374 obj.AddMember("translation", MakeValue(val, n.translation.value, w.mAl).Move(), w.mAl);
375 }
376
377 if (n.scale.isPresent) {
378 Value val;
379 obj.AddMember("scale", MakeValue(val, n.scale.value, w.mAl).Move(), w.mAl);
380 }
381 if (n.rotation.isPresent) {
382 Value val;
383 obj.AddMember("rotation", MakeValue(val, n.rotation.value, w.mAl).Move(), w.mAl);
384 }
385
386 AddRefsVector(obj, "children", n.children, w.mAl);
387
388 AddRefsVector(obj, "meshes", n.meshes, w.mAl);
389
390 AddRefsVector(obj, "skeletons", n.skeletons, w.mAl);
391
392 if (n.skin) {
393 obj.AddMember("skin", Value(n.skin->id, w.mAl).Move(), w.mAl);
394 }
395
396 if (!n.jointName.empty()) {
397 obj.AddMember("jointName", n.jointName, w.mAl);
398 }
399 }
400
401 inline void Write(Value& /*obj*/, Program& /*b*/, AssetWriter& /*w*/)
402 {
403
404 }
405
406 inline void Write(Value& obj, Sampler& b, AssetWriter& w)
407 {
408 if (b.wrapS) {
409 obj.AddMember("wrapS", b.wrapS, w.mAl);
410 }
411 if (b.wrapT) {
412 obj.AddMember("wrapT", b.wrapT, w.mAl);
413 }
414 if (b.magFilter) {
415 obj.AddMember("magFilter", b.magFilter, w.mAl);
416 }
417 if (b.minFilter) {
418 obj.AddMember("minFilter", b.minFilter, w.mAl);
419 }
420 }
421
422 inline void Write(Value& scene, Scene& s, AssetWriter& w)
423 {
424 AddRefsVector(scene, "nodes", s.nodes, w.mAl);
425 }
426
427 inline void Write(Value& /*obj*/, Shader& /*b*/, AssetWriter& /*w*/)
428 {
429
430 }
431
432 inline void Write(Value& obj, Skin& b, AssetWriter& w)
433 {
434 /****************** jointNames *******************/
435 Value vJointNames;
436 vJointNames.SetArray();
437 vJointNames.Reserve(unsigned(b.jointNames.size()), w.mAl);
438
439 for (size_t i = 0; i < unsigned(b.jointNames.size()); ++i) {
440 vJointNames.PushBack(StringRef(b.jointNames[i]->jointName), w.mAl);
441 }
442 obj.AddMember("jointNames", vJointNames, w.mAl);
443
444 if (b.bindShapeMatrix.isPresent) {
445 Value val;
446 obj.AddMember("bindShapeMatrix", MakeValue(val, b.bindShapeMatrix.value, w.mAl).Move(), w.mAl);
447 }
448
449 if (b.inverseBindMatrices) {
450 obj.AddMember("inverseBindMatrices", Value(b.inverseBindMatrices->id, w.mAl).Move(), w.mAl);
451 }
452
453 }
454
455 inline void Write(Value& /*obj*/, Technique& /*b*/, AssetWriter& /*w*/)
456 {
457
458 }
459
460 inline void Write(Value& obj, Texture& tex, AssetWriter& w)
461 {
462 if (tex.source) {
463 obj.AddMember("source", Value(tex.source->id, w.mAl).Move(), w.mAl);
464 }
465 if (tex.sampler) {
466 obj.AddMember("sampler", Value(tex.sampler->id, w.mAl).Move(), w.mAl);
467 }
468 }
469
470 inline void Write(Value& /*obj*/, Light& /*b*/, AssetWriter& /*w*/)
471 {
472
473 }
474
475
476 inline AssetWriter::AssetWriter(Asset& a)
477 : mDoc()
478 , mAsset(a)
479 , mAl(mDoc.GetAllocator())
480 {
481 mDoc.SetObject();
482
483 WriteMetadata();
484 WriteExtensionsUsed();
485
486 // Dump the contents of the dictionaries
487 for (size_t i = 0; i < a.mDicts.size(); ++i) {
488 a.mDicts[i]->WriteObjects(*this);
489 }
490
491 // Add the target scene field
492 if (mAsset.scene) {
493 mDoc.AddMember("scene", StringRef(mAsset.scene->id), mAl);
494 }
495 }
496
497 inline void AssetWriter::WriteFile(const char* path)
498 {
499 std::unique_ptr<IOStream> jsonOutFile(mAsset.OpenFile(path, "wt", true));
500
501 if (jsonOutFile == 0) {
502 throw DeadlyExportError("Could not open output file: " + std::string(path));
503 }
504
505 StringBuffer docBuffer;
506
507 PrettyWriter<StringBuffer> writer(docBuffer);
508 mDoc.Accept(writer);
509
510 if (jsonOutFile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) {
511 throw DeadlyExportError("Failed to write scene data!");
512 }
513
514 // Write buffer data to separate .bin files
515 for (unsigned int i = 0; i < mAsset.buffers.Size(); ++i) {
516 Ref<Buffer> b = mAsset.buffers.Get(i);
517
518 std::string binPath = b->GetURI();
519
520 std::unique_ptr<IOStream> binOutFile(mAsset.OpenFile(binPath, "wb", true));
521
522 if (binOutFile == 0) {
523 throw DeadlyExportError("Could not open output file: " + binPath);
524 }
525
526 if (b->byteLength > 0) {
527 if (binOutFile->Write(b->GetPointer(), b->byteLength, 1) != 1) {
528 throw DeadlyExportError("Failed to write binary file: " + binPath);
529 }
530 }
531 }
532 }
533
534 inline void AssetWriter::WriteGLBFile(const char* path)
535 {
536 std::unique_ptr<IOStream> outfile(mAsset.OpenFile(path, "wb", true));
537
538 if (outfile == 0) {
539 throw DeadlyExportError("Could not open output file: " + std::string(path));
540 }
541
542 // we will write the header later, skip its size
543 outfile->Seek(sizeof(GLB_Header), aiOrigin_SET);
544
545 StringBuffer docBuffer;
546 Writer<StringBuffer> writer(docBuffer);
547 mDoc.Accept(writer);
548
549 if (outfile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) {
550 throw DeadlyExportError("Failed to write scene data!");
551 }
552
553 WriteBinaryData(outfile.get(), docBuffer.GetSize());
554 }
555
556 inline void AssetWriter::WriteBinaryData(IOStream* outfile, size_t sceneLength)
557 {
558 //
559 // write the body data
560 //
561
562 size_t bodyLength = 0;
563 if (Ref<Buffer> b = mAsset.GetBodyBuffer()) {
564 bodyLength = b->byteLength;
565
566 if (bodyLength > 0) {
567 size_t bodyOffset = sizeof(GLB_Header) + sceneLength;
568 bodyOffset = (bodyOffset + 3) & ~3; // Round up to next multiple of 4
569
570 outfile->Seek(bodyOffset, aiOrigin_SET);
571
572 if (outfile->Write(b->GetPointer(), b->byteLength, 1) != 1) {
573 throw DeadlyExportError("Failed to write body data!");
574 }
575 }
576 }
577
578 //
579 // write the header
580 //
581
582 GLB_Header header;
583 memcpy(header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic));
584
585 header.version = 1;
586 AI_SWAP4(header.version);
587
588 header.length = uint32_t(sizeof(header) + sceneLength + bodyLength);
589 AI_SWAP4(header.length);
590
591 header.sceneLength = uint32_t(sceneLength);
592 AI_SWAP4(header.sceneLength);
593
594 header.sceneFormat = SceneFormat_JSON;
595 AI_SWAP4(header.sceneFormat);
596
597 outfile->Seek(0, aiOrigin_SET);
598
599 if (outfile->Write(&header, 1, sizeof(header)) != sizeof(header)) {
600 throw DeadlyExportError("Failed to write the header!");
601 }
602 }
603
604
605 inline void AssetWriter::WriteMetadata()
606 {
607 Value asset;
608 asset.SetObject();
609 asset.AddMember("version", Value(mAsset.asset.version, mAl).Move(), mAl);
610 asset.AddMember("generator", Value(mAsset.asset.generator, mAl).Move(), mAl);
611 mDoc.AddMember("asset", asset, mAl);
612 }
613
614 inline void AssetWriter::WriteExtensionsUsed()
615 {
616 Value exts;
617 exts.SetArray();
618 {
619 if (false)
620 exts.PushBack(StringRef("KHR_binary_glTF"), mAl);
621
622 if (false)
623 exts.PushBack(StringRef("KHR_materials_common"), mAl);
624 }
625
626 if (!exts.Empty())
627 mDoc.AddMember("extensionsUsed", exts, mAl);
628 }
629
630 template<class T>
631 void AssetWriter::WriteObjects(LazyDict<T>& d)
632 {
633 if (d.mObjs.empty()) return;
634
635 Value* container = &mDoc;
636
637 if (d.mExtId) {
638 Value* exts = FindObject(mDoc, "extensions");
639 if (!exts) {
640 mDoc.AddMember("extensions", Value().SetObject().Move(), mDoc.GetAllocator());
641 exts = FindObject(mDoc, "extensions");
642 }
643
644 if (!(container = FindObject(*exts, d.mExtId))) {
645 exts->AddMember(StringRef(d.mExtId), Value().SetObject().Move(), mDoc.GetAllocator());
646 container = FindObject(*exts, d.mExtId);
647 }
648 }
649
650 Value* dict;
651 if (!(dict = FindObject(*container, d.mDictId))) {
652 container->AddMember(StringRef(d.mDictId), Value().SetObject().Move(), mDoc.GetAllocator());
653 dict = FindObject(*container, d.mDictId);
654 }
655
656 for (size_t i = 0; i < d.mObjs.size(); ++i) {
657 if (d.mObjs[i]->IsSpecial()) continue;
658
659 Value obj;
660 obj.SetObject();
661
662 if (!d.mObjs[i]->name.empty()) {
663 obj.AddMember("name", StringRef(d.mObjs[i]->name.c_str()), mAl);
664 }
665
666 Write(obj, *d.mObjs[i], *this);
667
668 dict->AddMember(StringRef(d.mObjs[i]->id), obj, mAl);
669 }
670 }
671
672 template<class T>
673 void WriteLazyDict(LazyDict<T>& d, AssetWriter& w)
674 {
675 w.WriteObjects(d);
676 }
677
678}
679
680
681