1 | /* |
---|---|
2 | Open Asset Import Library (assimp) |
3 | ---------------------------------------------------------------------- |
4 | |
5 | Copyright (c) 2006-2024, assimp team |
6 | |
7 | All rights reserved. |
8 | |
9 | Redistribution and use of this software in source and binary forms, |
10 | with or without modification, are permitted provided that the |
11 | following conditions are met: |
12 | |
13 | * Redistributions of source code must retain the above |
14 | copyright notice, this list of conditions and the |
15 | following disclaimer. |
16 | |
17 | * Redistributions in binary form must reproduce the above |
18 | copyright notice, this list of conditions and the |
19 | following disclaimer in the documentation and/or other |
20 | materials provided with the distribution. |
21 | |
22 | * Neither the name of the assimp team, nor the names of its |
23 | contributors may be used to endorse or promote products |
24 | derived from this software without specific prior |
25 | written permission of the assimp team. |
26 | |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | |
39 | ---------------------------------------------------------------------- |
40 | */ |
41 | |
42 | #include "AssetLib/glTF/glTFCommon.h" |
43 | |
44 | #include <assimp/MemoryIOWrapper.h> |
45 | #include <assimp/StringUtils.h> |
46 | #include <assimp/DefaultLogger.hpp> |
47 | #include <assimp/Base64.hpp> |
48 | #include <rapidjson/document.h> |
49 | #include <rapidjson/schema.h> |
50 | #include <rapidjson/stringbuffer.h> |
51 | |
52 | // clang-format off |
53 | #ifdef ASSIMP_ENABLE_DRACO |
54 | |
55 | // Google draco library headers spew many warnings. Bad Google, no cookie |
56 | # if _MSC_VER |
57 | # pragma warning(push) |
58 | # pragma warning(disable : 4018) // Signed/unsigned mismatch |
59 | # pragma warning(disable : 4804) // Unsafe use of type 'bool' |
60 | # elif defined(__clang__) |
61 | # pragma clang diagnostic push |
62 | # pragma clang diagnostic ignored "-Wsign-compare" |
63 | # elif defined(__GNUC__) |
64 | # pragma GCC diagnostic push |
65 | # if (__GNUC__ > 4) |
66 | # pragma GCC diagnostic ignored "-Wbool-compare" |
67 | # endif |
68 | # pragma GCC diagnostic ignored "-Wsign-compare" |
69 | #endif |
70 | |
71 | #include "draco/compression/decode.h" |
72 | #include "draco/core/decoder_buffer.h" |
73 | |
74 | #if _MSC_VER |
75 | # pragma warning(pop) |
76 | #elif defined(__clang__) |
77 | # pragma clang diagnostic pop |
78 | #elif defined(__GNUC__) |
79 | # pragma GCC diagnostic pop |
80 | #endif |
81 | #ifndef DRACO_MESH_COMPRESSION_SUPPORTED |
82 | # error glTF: KHR_draco_mesh_compression: draco library must have DRACO_MESH_COMPRESSION_SUPPORTED |
83 | #endif |
84 | #endif |
85 | // clang-format on |
86 | |
87 | using namespace Assimp; |
88 | |
89 | namespace glTF2 { |
90 | using glTFCommon::FindStringInContext; |
91 | using glTFCommon::FindNumberInContext; |
92 | using glTFCommon::FindUIntInContext; |
93 | using glTFCommon::FindArrayInContext; |
94 | using glTFCommon::FindObjectInContext; |
95 | using glTFCommon::FindExtensionInContext; |
96 | using glTFCommon::MemberOrDefault; |
97 | using glTFCommon::ReadMember; |
98 | using glTFCommon::FindMember; |
99 | using glTFCommon::FindObject; |
100 | using glTFCommon::FindUInt; |
101 | using glTFCommon::FindArray; |
102 | using glTFCommon::FindArray; |
103 | |
104 | namespace { |
105 | |
106 | // |
107 | // JSON Value reading helpers |
108 | // |
109 | inline CustomExtension ReadExtensions(const char *name, Value &obj) { |
110 | CustomExtension ret; |
111 | ret.name = name; |
112 | if (obj.IsObject()) { |
113 | ret.mValues.isPresent = true; |
114 | for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { |
115 | auto &val = it->value; |
116 | ret.mValues.value.push_back(x: ReadExtensions(name: it->name.GetString(), obj&: val)); |
117 | } |
118 | } else if (obj.IsArray()) { |
119 | ret.mValues.value.reserve(n: obj.Size()); |
120 | ret.mValues.isPresent = true; |
121 | for (unsigned int i = 0; i < obj.Size(); ++i) { |
122 | ret.mValues.value.push_back(x: ReadExtensions(name, obj&: obj[i])); |
123 | } |
124 | } else if (obj.IsNumber()) { |
125 | if (obj.IsUint64()) { |
126 | ret.mUint64Value.value = obj.GetUint64(); |
127 | ret.mUint64Value.isPresent = true; |
128 | } else if (obj.IsInt64()) { |
129 | ret.mInt64Value.value = obj.GetInt64(); |
130 | ret.mInt64Value.isPresent = true; |
131 | } else if (obj.IsDouble()) { |
132 | ret.mDoubleValue.value = obj.GetDouble(); |
133 | ret.mDoubleValue.isPresent = true; |
134 | } |
135 | } else if (obj.IsString()) { |
136 | ReadValue(val&: obj, out&: ret.mStringValue); |
137 | ret.mStringValue.isPresent = true; |
138 | } else if (obj.IsBool()) { |
139 | ret.mBoolValue.value = obj.GetBool(); |
140 | ret.mBoolValue.isPresent = true; |
141 | } |
142 | return ret; |
143 | } |
144 | |
145 | inline Extras ReadExtras(Value &obj) { |
146 | Extras ret; |
147 | |
148 | ret.mValues.reserve(n: obj.MemberCount()); |
149 | for (auto it = obj.MemberBegin(); it != obj.MemberEnd(); ++it) { |
150 | auto &val = it->value; |
151 | ret.mValues.emplace_back(args: ReadExtensions(name: it->name.GetString(), obj&: val)); |
152 | } |
153 | |
154 | return ret; |
155 | } |
156 | |
157 | inline void CopyData(size_t count, const uint8_t *src, size_t src_stride, |
158 | uint8_t *dst, size_t dst_stride) { |
159 | if (src_stride == dst_stride) { |
160 | memcpy(dest: dst, src: src, n: count * src_stride); |
161 | return; |
162 | } |
163 | |
164 | size_t sz = std::min(a: src_stride, b: dst_stride); |
165 | for (size_t i = 0; i < count; ++i) { |
166 | memcpy(dest: dst, src: src, n: sz); |
167 | if (sz < dst_stride) { |
168 | memset(s: dst + sz, c: 0, n: dst_stride - sz); |
169 | } |
170 | src += src_stride; |
171 | dst += dst_stride; |
172 | } |
173 | } |
174 | |
175 | void SetVector(vec4 &v, const float (&in)[4]) { |
176 | v[0] = in[0]; |
177 | v[1] = in[1]; |
178 | v[2] = in[2]; |
179 | v[3] = in[3]; |
180 | } |
181 | |
182 | void SetVector(vec3 &v, const float (&in)[3]) { |
183 | v[0] = in[0]; |
184 | v[1] = in[1]; |
185 | v[2] = in[2]; |
186 | } |
187 | |
188 | template <int N> |
189 | inline int Compare(const char *attr, const char (&str)[N]) { |
190 | return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0; |
191 | } |
192 | |
193 | #if _MSC_VER |
194 | #pragma warning(push) |
195 | #pragma warning(disable : 4706) |
196 | #endif // _MSC_VER |
197 | |
198 | inline bool GetAttribVector(Mesh::Primitive &p, const char *attr, Mesh::AccessorList *&v, int &pos) { |
199 | if ((pos = Compare(attr, str: "POSITION"))) { |
200 | v = &(p.attributes.position); |
201 | } else if ((pos = Compare(attr, str: "NORMAL"))) { |
202 | v = &(p.attributes.normal); |
203 | } else if ((pos = Compare(attr, str: "TANGENT"))) { |
204 | v = &(p.attributes.tangent); |
205 | } else if ((pos = Compare(attr, str: "TEXCOORD"))) { |
206 | v = &(p.attributes.texcoord); |
207 | } else if ((pos = Compare(attr, str: "COLOR"))) { |
208 | v = &(p.attributes.color); |
209 | } else if ((pos = Compare(attr, str: "JOINTS"))) { |
210 | v = &(p.attributes.joint); |
211 | } else if ((pos = Compare(attr, str: "JOINTMATRIX"))) { |
212 | v = &(p.attributes.jointmatrix); |
213 | } else if ((pos = Compare(attr, str: "WEIGHTS"))) { |
214 | v = &(p.attributes.weight); |
215 | } else |
216 | return false; |
217 | return true; |
218 | } |
219 | |
220 | inline bool GetAttribTargetVector(Mesh::Primitive &p, const int targetIndex, const char *attr, Mesh::AccessorList *&v, int &pos) { |
221 | if ((pos = Compare(attr, str: "POSITION"))) { |
222 | v = &(p.targets[targetIndex].position); |
223 | } else if ((pos = Compare(attr, str: "NORMAL"))) { |
224 | v = &(p.targets[targetIndex].normal); |
225 | } else if ((pos = Compare(attr, str: "TANGENT"))) { |
226 | v = &(p.targets[targetIndex].tangent); |
227 | } else |
228 | return false; |
229 | return true; |
230 | } |
231 | |
232 | } // namespace |
233 | |
234 | inline Value *Object::FindString(Value &val, const char *memberId) { |
235 | return FindStringInContext(val, memberId, context: id.c_str(), extraContext: name.c_str()); |
236 | } |
237 | |
238 | inline Value *Object::FindNumber(Value &val, const char *memberId) { |
239 | return FindNumberInContext(val, memberId, context: id.c_str(), extraContext: name.c_str()); |
240 | } |
241 | |
242 | inline Value *Object::FindUInt(Value &val, const char *memberId) { |
243 | return FindUIntInContext(val, memberId, context: id.c_str(), extraContext: name.c_str()); |
244 | } |
245 | |
246 | inline Value *Object::FindArray(Value &val, const char *memberId) { |
247 | return FindArrayInContext(val, memberId, context: id.c_str(), extraContext: name.c_str()); |
248 | } |
249 | |
250 | inline Value *Object::FindObject(Value &val, const char *memberId) { |
251 | return FindObjectInContext(val, memberId, context: id.c_str(), extraContext: name.c_str()); |
252 | } |
253 | |
254 | inline Value *Object::FindExtension(Value &val, const char *extensionId) { |
255 | return FindExtensionInContext(val, extensionId, context: id.c_str(), extraContext: name.c_str()); |
256 | } |
257 | |
258 | inline void Object::ReadExtensions(Value &val) { |
259 | if (Value *curExtensions = FindObject(val, memberId: "extensions")) { |
260 | this->customExtensions = glTF2::ReadExtensions(name: "extensions", obj&: *curExtensions); |
261 | } |
262 | } |
263 | |
264 | inline void Object::ReadExtras(Value &val) { |
265 | if (Value *curExtras = FindObject(val, memberId: "extras")) { |
266 | this->extras = glTF2::ReadExtras(obj&: *curExtras); |
267 | } |
268 | } |
269 | |
270 | #ifdef ASSIMP_ENABLE_DRACO |
271 | |
272 | template <typename T> |
273 | inline void CopyFaceIndex_Draco(Buffer &decodedIndexBuffer, const draco::Mesh &draco_mesh) { |
274 | const size_t faceStride = sizeof(T) * 3; |
275 | for (draco::FaceIndex f(0); f < draco_mesh.num_faces(); ++f) { |
276 | const draco::Mesh::Face &face = draco_mesh.face(f); |
277 | T indices[3] = { static_cast<T>(face[0].value()), static_cast<T>(face[1].value()), static_cast<T>(face[2].value()) }; |
278 | memcpy(decodedIndexBuffer.GetPointer() + (f.value() * faceStride), &indices[0], faceStride); |
279 | } |
280 | } |
281 | |
282 | inline void SetDecodedIndexBuffer_Draco(const draco::Mesh &dracoMesh, Mesh::Primitive &prim) { |
283 | if (!prim.indices || dracoMesh.num_faces() == 0) |
284 | return; |
285 | |
286 | // Create a decoded Index buffer (if there is one) |
287 | size_t componentBytes = prim.indices->GetBytesPerComponent(); |
288 | |
289 | std::unique_ptr<Buffer> decodedIndexBuffer(new Buffer()); |
290 | decodedIndexBuffer->Grow(dracoMesh.num_faces() * 3 * componentBytes); |
291 | |
292 | // If accessor uses the same size as draco implementation, copy the draco buffer directly |
293 | |
294 | // Usually uint32_t but shouldn't assume |
295 | if (sizeof(dracoMesh.face(draco::FaceIndex(0))[0]) == componentBytes) { |
296 | memcpy(decodedIndexBuffer->GetPointer(), &dracoMesh.face(draco::FaceIndex(0))[0], decodedIndexBuffer->byteLength); |
297 | return; |
298 | } |
299 | |
300 | // Not same size, convert |
301 | switch (componentBytes) { |
302 | case sizeof(uint32_t): |
303 | CopyFaceIndex_Draco<uint32_t>(*decodedIndexBuffer, dracoMesh); |
304 | break; |
305 | case sizeof(uint16_t): |
306 | CopyFaceIndex_Draco<uint16_t>(*decodedIndexBuffer, dracoMesh); |
307 | break; |
308 | case sizeof(uint8_t): |
309 | CopyFaceIndex_Draco<uint8_t>(*decodedIndexBuffer, dracoMesh); |
310 | break; |
311 | default: |
312 | ai_assert(false); |
313 | break; |
314 | } |
315 | |
316 | // Assign this alternate data buffer to the accessor |
317 | prim.indices->decodedBuffer.swap(decodedIndexBuffer); |
318 | } |
319 | |
320 | template <typename T> |
321 | static bool GetAttributeForAllPoints_Draco(const draco::Mesh &dracoMesh, |
322 | const draco::PointAttribute &dracoAttribute, |
323 | Buffer &outBuffer) { |
324 | size_t byteOffset = 0; |
325 | T values[4] = { 0, 0, 0, 0 }; |
326 | for (draco::PointIndex i(0); i < dracoMesh.num_points(); ++i) { |
327 | const draco::AttributeValueIndex val_index = dracoAttribute.mapped_index(i); |
328 | if (!dracoAttribute.ConvertValue<T>(val_index, dracoAttribute.num_components(), values)) { |
329 | return false; |
330 | } |
331 | |
332 | memcpy(outBuffer.GetPointer() + byteOffset, &values[0], sizeof(T) * dracoAttribute.num_components()); |
333 | byteOffset += sizeof(T) * dracoAttribute.num_components(); |
334 | } |
335 | |
336 | return true; |
337 | } |
338 | |
339 | inline void SetDecodedAttributeBuffer_Draco(const draco::Mesh &dracoMesh, uint32_t dracoAttribId, Accessor &accessor) { |
340 | // Create decoded buffer |
341 | const draco::PointAttribute *pDracoAttribute = dracoMesh.GetAttributeByUniqueId(dracoAttribId); |
342 | if (pDracoAttribute == nullptr) { |
343 | throw DeadlyImportError("GLTF: Invalid draco attribute id: ", dracoAttribId); |
344 | } |
345 | |
346 | size_t componentBytes = accessor.GetBytesPerComponent(); |
347 | |
348 | std::unique_ptr<Buffer> decodedAttribBuffer(new Buffer()); |
349 | decodedAttribBuffer->Grow(dracoMesh.num_points() * pDracoAttribute->num_components() * componentBytes); |
350 | |
351 | switch (accessor.componentType) { |
352 | case ComponentType_BYTE: |
353 | GetAttributeForAllPoints_Draco<int8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); |
354 | break; |
355 | case ComponentType_UNSIGNED_BYTE: |
356 | GetAttributeForAllPoints_Draco<uint8_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); |
357 | break; |
358 | case ComponentType_SHORT: |
359 | GetAttributeForAllPoints_Draco<int16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); |
360 | break; |
361 | case ComponentType_UNSIGNED_SHORT: |
362 | GetAttributeForAllPoints_Draco<uint16_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); |
363 | break; |
364 | case ComponentType_UNSIGNED_INT: |
365 | GetAttributeForAllPoints_Draco<uint32_t>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); |
366 | break; |
367 | case ComponentType_FLOAT: |
368 | GetAttributeForAllPoints_Draco<float>(dracoMesh, *pDracoAttribute, *decodedAttribBuffer); |
369 | break; |
370 | default: |
371 | ai_assert(false); |
372 | break; |
373 | } |
374 | |
375 | // Assign this alternate data buffer to the accessor |
376 | accessor.decodedBuffer.swap(decodedAttribBuffer); |
377 | } |
378 | |
379 | #endif // ASSIMP_ENABLE_DRACO |
380 | |
381 | // |
382 | // LazyDict methods |
383 | // |
384 | |
385 | template <class T> |
386 | inline LazyDict<T>::LazyDict(Asset &asset, const char *dictId, const char *extId) : |
387 | mDictId(dictId), |
388 | mExtId(extId), |
389 | mDict(nullptr), |
390 | mAsset(asset) { |
391 | asset.mDicts.push_back(this); // register to the list of dictionaries |
392 | } |
393 | |
394 | template <class T> |
395 | inline LazyDict<T>::~LazyDict() { |
396 | for (size_t i = 0; i < mObjs.size(); ++i) { |
397 | delete mObjs[i]; |
398 | } |
399 | } |
400 | |
401 | template <class T> |
402 | inline void LazyDict<T>::AttachToDocument(Document &doc) { |
403 | Value *container = nullptr; |
404 | const char *context = nullptr; |
405 | |
406 | if (mExtId) { |
407 | if (Value *exts = FindObject(doc, memberId: "extensions")) { |
408 | container = FindObjectInContext(val&: *exts, memberId: mExtId, context: "extensions"); |
409 | context = mExtId; |
410 | } |
411 | } else { |
412 | container = &doc; |
413 | context = "the document"; |
414 | } |
415 | |
416 | if (container) { |
417 | mDict = FindArrayInContext(val&: *container, memberId: mDictId, context); |
418 | } |
419 | } |
420 | |
421 | template <class T> |
422 | inline void LazyDict<T>::DetachFromDocument() { |
423 | mDict = nullptr; |
424 | } |
425 | |
426 | template <class T> |
427 | unsigned int LazyDict<T>::Remove(const char *id) { |
428 | id = T::TranslateId(mAsset, id); |
429 | |
430 | typename IdDict::iterator objIt = mObjsById.find(x: id); |
431 | |
432 | if (objIt == mObjsById.end()) { |
433 | throw DeadlyExportError("GLTF: Object with id \""+ std::string(id) + "\" is not found"); |
434 | } |
435 | |
436 | const unsigned int index = objIt->second; |
437 | |
438 | mAsset.mUsedIds[id] = false; |
439 | mObjsById.erase(x: id); |
440 | mObjsByOIndex.erase(x: index); |
441 | delete mObjs[index]; |
442 | mObjs.erase(mObjs.begin() + index); |
443 | |
444 | //update index of object in mObjs; |
445 | for (unsigned int i = index; i < mObjs.size(); ++i) { |
446 | T *obj = mObjs[i]; |
447 | |
448 | obj->index = i; |
449 | } |
450 | |
451 | for (IdDict::iterator it = mObjsById.begin(); it != mObjsById.end(); ++it) { |
452 | if (it->second <= index) { |
453 | continue; |
454 | } |
455 | |
456 | mObjsById[it->first] = it->second - 1; |
457 | } |
458 | |
459 | for (Dict::iterator it = mObjsByOIndex.begin(); it != mObjsByOIndex.end(); ++it) { |
460 | if (it->second <= index) { |
461 | continue; |
462 | } |
463 | |
464 | mObjsByOIndex[it->first] = it->second - 1; |
465 | } |
466 | |
467 | return index; |
468 | } |
469 | |
470 | template <class T> |
471 | Ref<T> LazyDict<T>::Retrieve(unsigned int i) { |
472 | |
473 | typename Dict::iterator it = mObjsByOIndex.find(x: i); |
474 | if (it != mObjsByOIndex.end()) { // already created? |
475 | return Ref<T>(mObjs, it->second); |
476 | } |
477 | |
478 | // read it from the JSON object |
479 | if (!mDict) { |
480 | throw DeadlyImportError("GLTF: Missing section \"", mDictId, "\""); |
481 | } |
482 | |
483 | if (!mDict->IsArray()) { |
484 | throw DeadlyImportError("GLTF: Field \"", mDictId, "\" is not an array"); |
485 | } |
486 | |
487 | if (i >= mDict->Size()) { |
488 | throw DeadlyImportError("GLTF: Array index ", i, " is out of bounds (", mDict->Size(), ") for \"", mDictId, "\""); |
489 | } |
490 | |
491 | Value &obj = (*mDict)[i]; |
492 | |
493 | if (!obj.IsObject()) { |
494 | throw DeadlyImportError("GLTF: Object at index ", i, " in array \"", mDictId, "\" is not a JSON object"); |
495 | } |
496 | |
497 | if (mRecursiveReferenceCheck.find(x: i) != mRecursiveReferenceCheck.end()) { |
498 | throw DeadlyImportError("GLTF: Object at index ", i, " in array \"", mDictId, "\" has recursive reference to itself"); |
499 | } |
500 | mRecursiveReferenceCheck.insert(x: i); |
501 | |
502 | // Unique ptr prevents memory leak in case of Read throws an exception |
503 | auto inst = std::unique_ptr<T>(new T()); |
504 | // Try to make this human readable so it can be used in error messages. |
505 | inst->id = std::string(mDictId) + "["+ ai_to_string(value: i) + "]"; |
506 | inst->oIndex = i; |
507 | ReadMember(obj, "name", inst->name); |
508 | inst->Read(obj, mAsset); |
509 | inst->ReadExtensions(obj); |
510 | inst->ReadExtras(obj); |
511 | |
512 | Ref<T> result = Add(obj: inst.release()); |
513 | mRecursiveReferenceCheck.erase(x: i); |
514 | return result; |
515 | } |
516 | |
517 | template <class T> |
518 | Ref<T> LazyDict<T>::Get(unsigned int i) { |
519 | return Ref<T>(mObjs, i); |
520 | } |
521 | |
522 | template <class T> |
523 | Ref<T> LazyDict<T>::Get(const char *id) { |
524 | id = T::TranslateId(mAsset, id); |
525 | |
526 | typename IdDict::iterator it = mObjsById.find(x: id); |
527 | if (it != mObjsById.end()) { // already created? |
528 | return Ref<T>(mObjs, it->second); |
529 | } |
530 | |
531 | return Ref<T>(); |
532 | } |
533 | |
534 | template <class T> |
535 | Ref<T> LazyDict<T>::Add(T *obj) { |
536 | unsigned int idx = unsigned(mObjs.size()); |
537 | mObjs.push_back(obj); |
538 | mObjsByOIndex[obj->oIndex] = idx; |
539 | mObjsById[obj->id] = idx; |
540 | mAsset.mUsedIds[obj->id] = true; |
541 | return Ref<T>(mObjs, idx); |
542 | } |
543 | |
544 | template <class T> |
545 | Ref<T> LazyDict<T>::Create(const char *id) { |
546 | Asset::IdMap::iterator it = mAsset.mUsedIds.find(x: id); |
547 | if (it != mAsset.mUsedIds.end()) { |
548 | throw DeadlyImportError("GLTF: two objects with the same ID exist"); |
549 | } |
550 | T *inst = new T(); |
551 | unsigned int idx = unsigned(mObjs.size()); |
552 | inst->id = id; |
553 | inst->index = idx; |
554 | inst->oIndex = idx; |
555 | return Add(obj: inst); |
556 | } |
557 | |
558 | // |
559 | // glTF dictionary objects methods |
560 | // |
561 | inline Buffer::Buffer() : |
562 | byteLength(0), |
563 | type(Type_arraybuffer), |
564 | EncodedRegion_Current(nullptr), |
565 | mIsSpecial(false) {} |
566 | |
567 | inline Buffer::~Buffer() { |
568 | for (SEncodedRegion *reg : EncodedRegion_List) |
569 | delete reg; |
570 | } |
571 | |
572 | inline const char *Buffer::TranslateId(Asset & /*r*/, const char *id) { |
573 | return id; |
574 | } |
575 | |
576 | inline void Buffer::Read(Value &obj, Asset &r) { |
577 | size_t statedLength = MemberOrDefault<size_t>(obj, id: "byteLength", defaultValue: 0); |
578 | byteLength = statedLength; |
579 | |
580 | Value *it = FindString(val&: obj, memberId: "uri"); |
581 | if (!it) { |
582 | if (statedLength > 0) { |
583 | throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute"); |
584 | } |
585 | return; |
586 | } |
587 | |
588 | const char *uri = it->GetString(); |
589 | |
590 | glTFCommon::Util::DataURI dataURI; |
591 | if (ParseDataURI(const_uri: uri, uriLen: it->GetStringLength(), out&: dataURI)) { |
592 | if (dataURI.base64) { |
593 | uint8_t *data = nullptr; |
594 | this->byteLength = Base64::Decode(in: dataURI.data, inLength: dataURI.dataLength, out&: data); |
595 | this->mData.reset(p: data, d: std::default_delete<uint8_t[]>()); |
596 | |
597 | if (statedLength > 0 && this->byteLength != statedLength) { |
598 | throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(value: statedLength), |
599 | " bytes, but found ", ai_to_string(value: dataURI.dataLength)); |
600 | } |
601 | } else { // assume raw data |
602 | if (statedLength != dataURI.dataLength) { |
603 | throw DeadlyImportError("GLTF: buffer \"", id, "\", expected ", ai_to_string(value: statedLength), |
604 | " bytes, but found ", ai_to_string(value: dataURI.dataLength)); |
605 | } |
606 | |
607 | this->mData.reset(p: new uint8_t[dataURI.dataLength], d: std::default_delete<uint8_t[]>()); |
608 | memcpy(dest: this->mData.get(), src: dataURI.data, n: dataURI.dataLength); |
609 | } |
610 | } else { // Local file |
611 | if (byteLength > 0) { |
612 | std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir.back() == '/' ? r.mCurrentAssetDir : r.mCurrentAssetDir + '/') : ""; |
613 | |
614 | IOStream *file = r.OpenFile(path: dir + uri, mode: "rb"); |
615 | if (file) { |
616 | bool ok = LoadFromStream(stream&: *file, length: byteLength); |
617 | delete file; |
618 | |
619 | if (!ok) |
620 | throw DeadlyImportError("GLTF: error while reading referenced file \"", uri, "\""); |
621 | } else { |
622 | throw DeadlyImportError("GLTF: could not open referenced file \"", uri, "\""); |
623 | } |
624 | } |
625 | } |
626 | } |
627 | |
628 | inline bool Buffer::LoadFromStream(IOStream &stream, size_t length, size_t baseOffset) { |
629 | byteLength = length ? length : stream.FileSize(); |
630 | |
631 | if (byteLength > stream.FileSize()) { |
632 | throw DeadlyImportError("GLTF: Invalid byteLength exceeds size of actual data."); |
633 | } |
634 | |
635 | if (baseOffset) { |
636 | stream.Seek(pOffset: baseOffset, pOrigin: aiOrigin_SET); |
637 | } |
638 | |
639 | mData.reset(p: new uint8_t[byteLength], d: std::default_delete<uint8_t[]>()); |
640 | |
641 | if (stream.Read(pvBuffer: mData.get(), pSize: byteLength, pCount: 1) != 1) { |
642 | return false; |
643 | } |
644 | return true; |
645 | } |
646 | |
647 | inline void Buffer::EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string &pID) { |
648 | // Check pointer to data |
649 | if (pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided."); |
650 | |
651 | // Check offset |
652 | if (pOffset > byteLength) { |
653 | const uint8_t val_size = 32; |
654 | |
655 | char val[val_size]; |
656 | |
657 | ai_snprintf(s: val, maxlen: val_size, AI_SIZEFMT, pOffset); |
658 | throw DeadlyImportError("GLTF: incorrect offset value (", val, ") for marking encoded region."); |
659 | } |
660 | |
661 | // Check length |
662 | if ((pOffset + pEncodedData_Length) > byteLength) { |
663 | const uint8_t val_size = 64; |
664 | |
665 | char val[val_size]; |
666 | |
667 | ai_snprintf(s: val, maxlen: val_size, AI_SIZEFMT "/"AI_SIZEFMT, pOffset, pEncodedData_Length); |
668 | throw DeadlyImportError("GLTF: encoded region with offset/length (", val, ") is out of range."); |
669 | } |
670 | |
671 | // Add new region |
672 | EncodedRegion_List.push_back(x: new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID)); |
673 | // And set new value for "byteLength" |
674 | byteLength += (pDecodedData_Length - pEncodedData_Length); |
675 | } |
676 | |
677 | inline void Buffer::EncodedRegion_SetCurrent(const std::string &pID) { |
678 | if ((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) { |
679 | return; |
680 | } |
681 | |
682 | for (SEncodedRegion *reg : EncodedRegion_List) { |
683 | if (reg->ID == pID) { |
684 | EncodedRegion_Current = reg; |
685 | return; |
686 | } |
687 | } |
688 | |
689 | throw DeadlyImportError("GLTF: EncodedRegion with ID: \"", pID, "\" not found."); |
690 | } |
691 | |
692 | inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) { |
693 | |
694 | if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) { |
695 | return false; |
696 | } |
697 | |
698 | const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count; |
699 | uint8_t *new_data = new uint8_t[new_data_size]; |
700 | // Copy data which place before replacing part. |
701 | ::memcpy(dest: new_data, src: mData.get(), n: pBufferData_Offset); |
702 | // Copy new data. |
703 | ::memcpy(dest: &new_data[pBufferData_Offset], src: pReplace_Data, n: pReplace_Count); |
704 | // Copy data which place after replacing part. |
705 | ::memcpy(dest: &new_data[pBufferData_Offset + pReplace_Count], src: &mData.get()[pBufferData_Offset + pBufferData_Count], n: pBufferData_Offset); |
706 | // Apply new data |
707 | mData.reset(p: new_data, d: std::default_delete<uint8_t[]>()); |
708 | byteLength = new_data_size; |
709 | |
710 | return true; |
711 | } |
712 | |
713 | inline bool Buffer::ReplaceData_joint(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t *pReplace_Data, const size_t pReplace_Count) { |
714 | if ((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) { |
715 | return false; |
716 | } |
717 | |
718 | const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count; |
719 | uint8_t *new_data = new uint8_t[new_data_size]; |
720 | // Copy data which place before replacing part. |
721 | memcpy(dest: new_data, src: mData.get(), n: pBufferData_Offset); |
722 | // Copy new data. |
723 | memcpy(dest: &new_data[pBufferData_Offset], src: pReplace_Data, n: pReplace_Count); |
724 | // Copy data which place after replacing part. |
725 | memcpy(dest: &new_data[pBufferData_Offset + pReplace_Count], src: &mData.get()[pBufferData_Offset + pBufferData_Count], n: new_data_size - (pBufferData_Offset + pReplace_Count)); |
726 | // Apply new data |
727 | mData.reset(p: new_data, d: std::default_delete<uint8_t[]>()); |
728 | byteLength = new_data_size; |
729 | |
730 | return true; |
731 | } |
732 | |
733 | inline size_t Buffer::AppendData(uint8_t *data, size_t length) { |
734 | const size_t offset = this->byteLength; |
735 | |
736 | // Force alignment to 4 bits |
737 | const size_t paddedLength = (length + 3) & ~3; |
738 | Grow(amount: paddedLength); |
739 | memcpy(dest: mData.get() + offset, src: data, n: length); |
740 | memset(s: mData.get() + offset + length, c: 0, n: paddedLength - length); |
741 | return offset; |
742 | } |
743 | |
744 | inline void Buffer::Grow(size_t amount) { |
745 | if (amount <= 0) { |
746 | return; |
747 | } |
748 | |
749 | // Capacity is big enough |
750 | if (capacity >= byteLength + amount) { |
751 | byteLength += amount; |
752 | return; |
753 | } |
754 | |
755 | // Just allocate data which we need |
756 | capacity = byteLength + amount; |
757 | |
758 | uint8_t *b = new uint8_t[capacity]; |
759 | if (nullptr != mData) { |
760 | memcpy(dest: b, src: mData.get(), n: byteLength); |
761 | } |
762 | mData.reset(p: b, d: std::default_delete<uint8_t[]>()); |
763 | byteLength += amount; |
764 | } |
765 | |
766 | // |
767 | // struct BufferView |
768 | // |
769 | inline void BufferView::Read(Value &obj, Asset &r) { |
770 | if (Value *bufferVal = FindUInt(val&: obj, memberId: "buffer")) { |
771 | buffer = r.buffers.Retrieve(i: bufferVal->GetUint()); |
772 | } |
773 | |
774 | if (!buffer) { |
775 | throw DeadlyImportError("GLTF: Buffer view without valid buffer."); |
776 | } |
777 | |
778 | byteOffset = MemberOrDefault(obj, id: "byteOffset", defaultValue: size_t(0)); |
779 | byteLength = MemberOrDefault(obj, id: "byteLength", defaultValue: size_t(0)); |
780 | byteStride = MemberOrDefault(obj, id: "byteStride", defaultValue: 0u); |
781 | |
782 | // Check length |
783 | if ((byteOffset + byteLength) > buffer->byteLength) { |
784 | throw DeadlyImportError("GLTF: Buffer view with offset/length (", byteOffset, "/", byteLength, ") is out of range."); |
785 | } |
786 | } |
787 | |
788 | inline uint8_t *BufferView::GetPointerAndTailSize(size_t accOffset, size_t& outTailSize) { |
789 | if (!buffer) { |
790 | outTailSize = 0; |
791 | return nullptr; |
792 | } |
793 | uint8_t * const basePtr = buffer->GetPointer(); |
794 | if (!basePtr) { |
795 | outTailSize = 0; |
796 | return nullptr; |
797 | } |
798 | |
799 | size_t offset = accOffset + byteOffset; |
800 | if (buffer->EncodedRegion_Current != nullptr) { |
801 | const size_t begin = buffer->EncodedRegion_Current->Offset; |
802 | const size_t end = begin + buffer->EncodedRegion_Current->DecodedData_Length; |
803 | if ((offset >= begin) && (offset < end)) { |
804 | outTailSize = end - offset; |
805 | return &buffer->EncodedRegion_Current->DecodedData[offset - begin]; |
806 | } |
807 | } |
808 | |
809 | if (offset >= buffer->byteLength) |
810 | { |
811 | outTailSize = 0; |
812 | return nullptr; |
813 | } |
814 | |
815 | outTailSize = buffer->byteLength - offset; |
816 | return basePtr + offset; |
817 | } |
818 | |
819 | // |
820 | // struct Accessor |
821 | // |
822 | inline void Accessor::Sparse::PopulateData(size_t numBytes, const uint8_t *bytes) { |
823 | if (bytes) { |
824 | data.assign(first: bytes, last: bytes + numBytes); |
825 | } else { |
826 | data.resize(new_size: numBytes, x: 0x00); |
827 | } |
828 | } |
829 | |
830 | inline void Accessor::Sparse::PatchData(unsigned int elementSize) { |
831 | size_t indicesTailDataSize; |
832 | uint8_t *pIndices = indices->GetPointerAndTailSize(accOffset: indicesByteOffset, outTailSize&: indicesTailDataSize); |
833 | const unsigned int indexSize = int(ComponentTypeSize(t: indicesType)); |
834 | uint8_t *indicesEnd = pIndices + count * indexSize; |
835 | |
836 | if ((uint64_t)indicesEnd > (uint64_t)pIndices + indicesTailDataSize) { |
837 | throw DeadlyImportError("Invalid sparse accessor. Indices outside allocated memory."); |
838 | } |
839 | |
840 | size_t valuesTailDataSize; |
841 | uint8_t* pValues = values->GetPointerAndTailSize(accOffset: valuesByteOffset, outTailSize&: valuesTailDataSize); |
842 | |
843 | if (elementSize * count > valuesTailDataSize) { |
844 | throw DeadlyImportError("Invalid sparse accessor. Indices outside allocated memory."); |
845 | } |
846 | while (pIndices != indicesEnd) { |
847 | size_t offset; |
848 | switch (indicesType) { |
849 | case ComponentType_UNSIGNED_BYTE: |
850 | offset = *pIndices; |
851 | break; |
852 | case ComponentType_UNSIGNED_SHORT: |
853 | offset = *reinterpret_cast<uint16_t *>(pIndices); |
854 | break; |
855 | case ComponentType_UNSIGNED_INT: |
856 | offset = *reinterpret_cast<uint32_t *>(pIndices); |
857 | break; |
858 | default: |
859 | // have fun with float and negative values from signed types as indices. |
860 | throw DeadlyImportError("Unsupported component type in index."); |
861 | } |
862 | |
863 | offset *= elementSize; |
864 | |
865 | if (offset + elementSize > data.size()) { |
866 | throw DeadlyImportError("Invalid sparse accessor. Byte offset for patching points outside allocated memory."); |
867 | } |
868 | |
869 | std::memcpy(dest: data.data() + offset, src: pValues, n: elementSize); |
870 | |
871 | pValues += elementSize; |
872 | pIndices += indexSize; |
873 | } |
874 | } |
875 | |
876 | inline void Accessor::Read(Value &obj, Asset &r) { |
877 | if (Value *bufferViewVal = FindUInt(val&: obj, memberId: "bufferView")) { |
878 | bufferView = r.bufferViews.Retrieve(i: bufferViewVal->GetUint()); |
879 | } |
880 | |
881 | byteOffset = MemberOrDefault(obj, id: "byteOffset", defaultValue: size_t(0)); |
882 | componentType = MemberOrDefault(obj, id: "componentType", defaultValue: ComponentType_BYTE); |
883 | { |
884 | const Value *countValue = FindUInt(val&: obj, memberId: "count"); |
885 | if (!countValue) { |
886 | throw DeadlyImportError("A count value is required, when reading ", id.c_str(), name.empty() ? "": " ("+ name + ")"); |
887 | } |
888 | count = countValue->GetUint(); |
889 | } |
890 | |
891 | const char *typestr; |
892 | type = ReadMember(obj, id: "type", out&: typestr) ? AttribType::FromString(str: typestr) : AttribType::SCALAR; |
893 | |
894 | if (bufferView) { |
895 | // Check length |
896 | unsigned long long byteLength = (unsigned long long)GetBytesPerComponent() * (unsigned long long)count; |
897 | |
898 | // handle integer overflow |
899 | if (byteLength < count) { |
900 | throw DeadlyImportError("GLTF: Accessor with offset/count (", byteOffset, "/", count, ") is out of range."); |
901 | } |
902 | |
903 | if ((byteOffset + byteLength) > bufferView->byteLength || (bufferView->byteOffset + byteOffset + byteLength) > bufferView->buffer->byteLength) { |
904 | throw DeadlyImportError("GLTF: Accessor with offset/length (", byteOffset, "/", byteLength, ") is out of range."); |
905 | } |
906 | } |
907 | |
908 | if (Value *sparseValue = FindObject(val&: obj, memberId: "sparse")) { |
909 | sparse.reset(p: new Sparse); |
910 | // count |
911 | ReadMember(obj&: *sparseValue, id: "count", out&: sparse->count); |
912 | |
913 | // indices |
914 | if (Value *indicesValue = FindObject(val&: *sparseValue, memberId: "indices")) { |
915 | //indices bufferView |
916 | Value *indiceViewID = FindUInt(val&: *indicesValue, memberId: "bufferView"); |
917 | if (!indiceViewID) { |
918 | throw DeadlyImportError("A bufferView value is required, when reading ", id.c_str(), name.empty() ? "": " ("+ name + ")"); |
919 | } |
920 | sparse->indices = r.bufferViews.Retrieve(i: indiceViewID->GetUint()); |
921 | //indices byteOffset |
922 | sparse->indicesByteOffset = MemberOrDefault(obj&: *indicesValue, id: "byteOffset", defaultValue: size_t(0)); |
923 | //indices componentType |
924 | sparse->indicesType = MemberOrDefault(obj&: *indicesValue, id: "componentType", defaultValue: ComponentType_BYTE); |
925 | //sparse->indices->Read(*indicesValue, r); |
926 | } else { |
927 | // indicesType |
928 | sparse->indicesType = MemberOrDefault(obj&: *sparseValue, id: "componentType", defaultValue: ComponentType_UNSIGNED_SHORT); |
929 | } |
930 | |
931 | // value |
932 | if (Value *valuesValue = FindObject(val&: *sparseValue, memberId: "values")) { |
933 | //value bufferView |
934 | Value *valueViewID = FindUInt(val&: *valuesValue, memberId: "bufferView"); |
935 | if (!valueViewID) { |
936 | throw DeadlyImportError("A bufferView value is required, when reading ", id.c_str(), name.empty() ? "": " ("+ name + ")"); |
937 | } |
938 | sparse->values = r.bufferViews.Retrieve(i: valueViewID->GetUint()); |
939 | //value byteOffset |
940 | sparse->valuesByteOffset = MemberOrDefault(obj&: *valuesValue, id: "byteOffset", defaultValue: size_t(0)); |
941 | //sparse->values->Read(*valuesValue, r); |
942 | } |
943 | |
944 | |
945 | const unsigned int elementSize = GetElementSize(); |
946 | const size_t dataSize = count * elementSize; |
947 | if (bufferView) { |
948 | size_t bufferViewTailSize; |
949 | const uint8_t* bufferViewPointer = bufferView->GetPointerAndTailSize(accOffset: byteOffset, outTailSize&: bufferViewTailSize); |
950 | if (dataSize > bufferViewTailSize) { |
951 | throw DeadlyImportError("Invalid buffer when reading ", id.c_str(), name.empty() ? "": " ("+ name + ")"); |
952 | } |
953 | sparse->PopulateData(numBytes: dataSize, bytes: bufferViewPointer); |
954 | } |
955 | else { |
956 | sparse->PopulateData(numBytes: dataSize, bytes: nullptr); |
957 | } |
958 | sparse->PatchData(elementSize); |
959 | } |
960 | } |
961 | |
962 | inline unsigned int Accessor::GetNumComponents() { |
963 | return AttribType::GetNumComponents(type); |
964 | } |
965 | |
966 | inline unsigned int Accessor::GetBytesPerComponent() { |
967 | return int(ComponentTypeSize(t: componentType)); |
968 | } |
969 | |
970 | inline unsigned int Accessor::GetElementSize() { |
971 | return GetNumComponents() * GetBytesPerComponent(); |
972 | } |
973 | |
974 | inline uint8_t *Accessor::GetPointer() { |
975 | if (decodedBuffer) |
976 | return decodedBuffer->GetPointer(); |
977 | |
978 | if (sparse) |
979 | return sparse->data.data(); |
980 | |
981 | if (!bufferView || !bufferView->buffer) return nullptr; |
982 | uint8_t *basePtr = bufferView->buffer->GetPointer(); |
983 | if (!basePtr) return nullptr; |
984 | |
985 | size_t offset = byteOffset + bufferView->byteOffset; |
986 | |
987 | // Check if region is encoded. |
988 | if (bufferView->buffer->EncodedRegion_Current != nullptr) { |
989 | const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset; |
990 | const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length; |
991 | |
992 | if ((offset >= begin) && (offset < end)) |
993 | return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin]; |
994 | } |
995 | |
996 | return basePtr + offset; |
997 | } |
998 | |
999 | inline size_t Accessor::GetStride() { |
1000 | // Decoded buffer is always packed |
1001 | if (decodedBuffer) |
1002 | return GetElementSize(); |
1003 | |
1004 | // Sparse and normal bufferView |
1005 | return (bufferView && bufferView->byteStride ? bufferView->byteStride : GetElementSize()); |
1006 | } |
1007 | |
1008 | inline size_t Accessor::GetMaxByteSize() { |
1009 | if (decodedBuffer) |
1010 | return decodedBuffer->byteLength; |
1011 | |
1012 | return (bufferView ? bufferView->byteLength : sparse->data.size()); |
1013 | } |
1014 | |
1015 | template <class T> |
1016 | size_t Accessor::ExtractData(T *&outData, const std::vector<unsigned int> *remappingIndices) { |
1017 | uint8_t *data = GetPointer(); |
1018 | if (!data) { |
1019 | throw DeadlyImportError("GLTF2: data is null when extracting data from ", getContextForErrorMessages(id, name)); |
1020 | } |
1021 | |
1022 | const size_t usedCount = (remappingIndices != nullptr) ? remappingIndices->size() : count; |
1023 | const size_t elemSize = GetElementSize(); |
1024 | const size_t totalSize = elemSize * usedCount; |
1025 | |
1026 | const size_t stride = GetStride(); |
1027 | |
1028 | const size_t targetElemSize = sizeof(T); |
1029 | |
1030 | if (elemSize > targetElemSize) { |
1031 | throw DeadlyImportError("GLTF: elemSize ", elemSize, " > targetElemSize ", targetElemSize, " in ", getContextForErrorMessages(id, name)); |
1032 | } |
1033 | |
1034 | const size_t maxSize = GetMaxByteSize(); |
1035 | |
1036 | outData = new T[usedCount]; |
1037 | |
1038 | if (remappingIndices != nullptr) { |
1039 | const unsigned int maxIndexCount = static_cast<unsigned int>(maxSize / stride); |
1040 | for (size_t i = 0; i < usedCount; ++i) { |
1041 | size_t srcIdx = (*remappingIndices)[i]; |
1042 | if (srcIdx >= maxIndexCount) { |
1043 | throw DeadlyImportError("GLTF: index*stride ", (srcIdx * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name)); |
1044 | } |
1045 | memcpy(outData + i, data + srcIdx * stride, elemSize); |
1046 | } |
1047 | } else { // non-indexed cases |
1048 | if (usedCount * stride > maxSize) { |
1049 | throw DeadlyImportError("GLTF: count*stride ", (usedCount * stride), " > maxSize ", maxSize, " in ", getContextForErrorMessages(id, name)); |
1050 | } |
1051 | if (stride == elemSize && targetElemSize == elemSize) { |
1052 | memcpy(outData, data, totalSize); |
1053 | } else { |
1054 | for (size_t i = 0; i < usedCount; ++i) { |
1055 | memcpy(outData + i, data + i * stride, elemSize); |
1056 | } |
1057 | } |
1058 | } |
1059 | return usedCount; |
1060 | } |
1061 | |
1062 | inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t src_stride) { |
1063 | uint8_t *buffer_ptr = bufferView->buffer->GetPointer(); |
1064 | size_t offset = byteOffset + bufferView->byteOffset; |
1065 | |
1066 | size_t dst_stride = GetNumComponents() * GetBytesPerComponent(); |
1067 | |
1068 | const uint8_t *src = reinterpret_cast<const uint8_t *>(src_buffer); |
1069 | uint8_t *dst = reinterpret_cast<uint8_t *>(buffer_ptr + offset); |
1070 | |
1071 | ai_assert(dst + _count * dst_stride <= buffer_ptr + bufferView->buffer->byteLength); |
1072 | CopyData(count: _count, src, src_stride, dst, dst_stride); |
1073 | } |
1074 | |
1075 | inline void Accessor::WriteSparseValues(size_t _count, const void *src_data, size_t src_dataStride) { |
1076 | if (!sparse) |
1077 | return; |
1078 | |
1079 | // values |
1080 | uint8_t *value_buffer_ptr = sparse->values->buffer->GetPointer(); |
1081 | size_t value_offset = sparse->valuesByteOffset + sparse->values->byteOffset; |
1082 | size_t value_dst_stride = GetNumComponents() * GetBytesPerComponent(); |
1083 | const uint8_t *value_src = reinterpret_cast<const uint8_t *>(src_data); |
1084 | uint8_t *value_dst = reinterpret_cast<uint8_t *>(value_buffer_ptr + value_offset); |
1085 | ai_assert(value_dst + _count * value_dst_stride <= value_buffer_ptr + sparse->values->buffer->byteLength); |
1086 | CopyData(count: _count, src: value_src, src_stride: src_dataStride, dst: value_dst, dst_stride: value_dst_stride); |
1087 | } |
1088 | |
1089 | inline void Accessor::WriteSparseIndices(size_t _count, const void *src_idx, size_t src_idxStride) { |
1090 | if (!sparse) |
1091 | return; |
1092 | |
1093 | // indices |
1094 | uint8_t *indices_buffer_ptr = sparse->indices->buffer->GetPointer(); |
1095 | size_t indices_offset = sparse->indicesByteOffset + sparse->indices->byteOffset; |
1096 | size_t indices_dst_stride = 1 * sizeof(unsigned short); |
1097 | const uint8_t *indices_src = reinterpret_cast<const uint8_t *>(src_idx); |
1098 | uint8_t *indices_dst = reinterpret_cast<uint8_t *>(indices_buffer_ptr + indices_offset); |
1099 | ai_assert(indices_dst + _count * indices_dst_stride <= indices_buffer_ptr + sparse->indices->buffer->byteLength); |
1100 | CopyData(count: _count, src: indices_src, src_stride: src_idxStride, dst: indices_dst, dst_stride: indices_dst_stride); |
1101 | } |
1102 | |
1103 | inline Accessor::Indexer::Indexer(Accessor &acc) : |
1104 | accessor(acc), |
1105 | data(acc.GetPointer()), |
1106 | elemSize(acc.GetElementSize()), |
1107 | stride(acc.GetStride()) { |
1108 | } |
1109 | |
1110 | //! Accesses the i-th value as defined by the accessor |
1111 | template <class T> |
1112 | T Accessor::Indexer::GetValue(int i) { |
1113 | ai_assert(data); |
1114 | if (i * stride >= accessor.GetMaxByteSize()) { |
1115 | throw DeadlyImportError("GLTF: Invalid index ", i, ", count out of range for buffer with stride ", stride, " and size ", accessor.GetMaxByteSize(), "."); |
1116 | } |
1117 | // Ensure that the memcpy doesn't overwrite the local. |
1118 | const size_t sizeToCopy = std::min(a: elemSize, b: sizeof(T)); |
1119 | T value = T(); |
1120 | // Assume platform endianness matches GLTF binary data (which is little-endian). |
1121 | memcpy(&value, data + i * stride, sizeToCopy); |
1122 | return value; |
1123 | } |
1124 | |
1125 | inline Image::Image() : |
1126 | width(0), |
1127 | height(0), |
1128 | mDataLength(0) { |
1129 | } |
1130 | |
1131 | inline void Image::Read(Value &obj, Asset &r) { |
1132 | //basisu: no need to handle .ktx2, .basis, load as is |
1133 | if (!mDataLength) { |
1134 | Value *curUri = FindString(val&: obj, memberId: "uri"); |
1135 | if (nullptr != curUri) { |
1136 | const char *uristr = curUri->GetString(); |
1137 | |
1138 | glTFCommon::Util::DataURI dataURI; |
1139 | if (ParseDataURI(const_uri: uristr, uriLen: curUri->GetStringLength(), out&: dataURI)) { |
1140 | mimeType = dataURI.mediaType; |
1141 | if (dataURI.base64) { |
1142 | uint8_t *ptr = nullptr; |
1143 | mDataLength = Base64::Decode(in: dataURI.data, inLength: dataURI.dataLength, out&: ptr); |
1144 | mData.reset(p: ptr); |
1145 | } |
1146 | } else { |
1147 | this->uri = uristr; |
1148 | } |
1149 | } else if (Value *bufferViewVal = FindUInt(val&: obj, memberId: "bufferView")) { |
1150 | this->bufferView = r.bufferViews.Retrieve(i: bufferViewVal->GetUint()); |
1151 | if (Value *mtype = FindString(val&: obj, memberId: "mimeType")) { |
1152 | this->mimeType = mtype->GetString(); |
1153 | } |
1154 | if (!this->bufferView || this->mimeType.empty()) { |
1155 | throw DeadlyImportError("GLTF2: ", getContextForErrorMessages(id, name), " does not have a URI, so it must have a valid bufferView and mimetype"); |
1156 | } |
1157 | |
1158 | Ref<Buffer> buffer = this->bufferView->buffer; |
1159 | |
1160 | this->mDataLength = this->bufferView->byteLength; |
1161 | // maybe this memcpy could be avoided if aiTexture does not delete[] pcData at destruction. |
1162 | |
1163 | this->mData.reset(p: new uint8_t[this->mDataLength]); |
1164 | memcpy(dest: this->mData.get(), src: buffer->GetPointer() + this->bufferView->byteOffset, n: this->mDataLength); |
1165 | } else { |
1166 | throw DeadlyImportError("GLTF2: ", getContextForErrorMessages(id, name), " should have either a URI of a bufferView and mimetype"); |
1167 | } |
1168 | } |
1169 | } |
1170 | |
1171 | inline uint8_t *Image::StealData() { |
1172 | mDataLength = 0; |
1173 | return mData.release(); |
1174 | } |
1175 | |
1176 | // Never take over the ownership of data whenever binary or not |
1177 | inline void Image::SetData(uint8_t *data, size_t length, Asset &r) { |
1178 | Ref<Buffer> b = r.GetBodyBuffer(); |
1179 | if (b) { // binary file: append to body |
1180 | std::string bvId = r.FindUniqueID(str: this->id, suffix: "imgdata"); |
1181 | bufferView = r.bufferViews.Create(id: bvId); |
1182 | |
1183 | bufferView->buffer = b; |
1184 | bufferView->byteLength = length; |
1185 | bufferView->byteOffset = b->AppendData(data, length); |
1186 | } else { // text file: will be stored as a data uri |
1187 | uint8_t *temp = new uint8_t[length]; |
1188 | memcpy(dest: temp, src: data, n: length); |
1189 | this->mData.reset(p: temp); |
1190 | this->mDataLength = length; |
1191 | } |
1192 | } |
1193 | |
1194 | inline void Sampler::Read(Value &obj, Asset & /*r*/) { |
1195 | SetDefaults(); |
1196 | |
1197 | ReadMember(obj, id: "name", out&: name); |
1198 | ReadMember(obj, id: "magFilter", out&: magFilter); |
1199 | ReadMember(obj, id: "minFilter", out&: minFilter); |
1200 | ReadMember(obj, id: "wrapS", out&: wrapS); |
1201 | ReadMember(obj, id: "wrapT", out&: wrapT); |
1202 | } |
1203 | |
1204 | inline void Sampler::SetDefaults() { |
1205 | //only wrapping modes have defaults |
1206 | wrapS = SamplerWrap::Repeat; |
1207 | wrapT = SamplerWrap::Repeat; |
1208 | magFilter = SamplerMagFilter::UNSET; |
1209 | minFilter = SamplerMinFilter::UNSET; |
1210 | } |
1211 | |
1212 | inline void Texture::Read(Value &obj, Asset &r) { |
1213 | if (Value *sourceVal = FindUInt(val&: obj, memberId: "source")) { |
1214 | source = r.images.Retrieve(i: sourceVal->GetUint()); |
1215 | } |
1216 | |
1217 | if (Value *samplerVal = FindUInt(val&: obj, memberId: "sampler")) { |
1218 | sampler = r.samplers.Retrieve(i: samplerVal->GetUint()); |
1219 | } |
1220 | } |
1221 | |
1222 | void Material::SetTextureProperties(Asset &r, Value *prop, TextureInfo &out) { |
1223 | if (r.extensionsUsed.KHR_texture_transform) { |
1224 | if (Value *pKHR_texture_transform = FindExtension(val&: *prop, extensionId: "KHR_texture_transform")) { |
1225 | out.textureTransformSupported = true; |
1226 | if (Value *array = FindArray(val&: *pKHR_texture_transform, memberId: "offset")) { |
1227 | out.TextureTransformExt_t.offset[0] = (*array)[0].GetFloat(); |
1228 | out.TextureTransformExt_t.offset[1] = (*array)[1].GetFloat(); |
1229 | } else { |
1230 | out.TextureTransformExt_t.offset[0] = 0; |
1231 | out.TextureTransformExt_t.offset[1] = 0; |
1232 | } |
1233 | |
1234 | if (!ReadMember(obj&: *pKHR_texture_transform, id: "rotation", out&: out.TextureTransformExt_t.rotation)) { |
1235 | out.TextureTransformExt_t.rotation = 0; |
1236 | } |
1237 | |
1238 | if (Value *array = FindArray(val&: *pKHR_texture_transform, memberId: "scale")) { |
1239 | out.TextureTransformExt_t.scale[0] = (*array)[0].GetFloat(); |
1240 | out.TextureTransformExt_t.scale[1] = (*array)[1].GetFloat(); |
1241 | } else { |
1242 | out.TextureTransformExt_t.scale[0] = 1; |
1243 | out.TextureTransformExt_t.scale[1] = 1; |
1244 | } |
1245 | } |
1246 | } |
1247 | |
1248 | if (Value *indexProp = FindUInt(val&: *prop, memberId: "index")) { |
1249 | out.texture = r.textures.Retrieve(i: indexProp->GetUint()); |
1250 | } |
1251 | |
1252 | if (Value *texcoord = FindUInt(val&: *prop, memberId: "texCoord")) { |
1253 | out.texCoord = texcoord->GetUint(); |
1254 | } |
1255 | } |
1256 | |
1257 | inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, TextureInfo &out) { |
1258 | if (Value *prop = FindMember(val&: vals, id: propName)) { |
1259 | SetTextureProperties(r, prop, out); |
1260 | } |
1261 | } |
1262 | |
1263 | inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, NormalTextureInfo &out) { |
1264 | if (Value *prop = FindMember(val&: vals, id: propName)) { |
1265 | SetTextureProperties(r, prop, out); |
1266 | |
1267 | if (Value *scale = FindNumber(val&: *prop, memberId: "scale")) { |
1268 | out.scale = static_cast<float>(scale->GetDouble()); |
1269 | } |
1270 | } |
1271 | } |
1272 | |
1273 | inline void Material::ReadTextureProperty(Asset &r, Value &vals, const char *propName, OcclusionTextureInfo &out) { |
1274 | if (Value *prop = FindMember(val&: vals, id: propName)) { |
1275 | SetTextureProperties(r, prop, out); |
1276 | |
1277 | if (Value *strength = FindNumber(val&: *prop, memberId: "strength")) { |
1278 | out.strength = static_cast<float>(strength->GetDouble()); |
1279 | } |
1280 | } |
1281 | } |
1282 | |
1283 | inline void Material::Read(Value &material, Asset &r) { |
1284 | SetDefaults(); |
1285 | |
1286 | if (Value *curPbrMetallicRoughness = FindObject(val&: material, memberId: "pbrMetallicRoughness")) { |
1287 | ReadMember(obj&: *curPbrMetallicRoughness, id: "baseColorFactor", out&: this->pbrMetallicRoughness.baseColorFactor); |
1288 | ReadTextureProperty(r, vals&: *curPbrMetallicRoughness, propName: "baseColorTexture", out&: this->pbrMetallicRoughness.baseColorTexture); |
1289 | ReadTextureProperty(r, vals&: *curPbrMetallicRoughness, propName: "metallicRoughnessTexture", out&: this->pbrMetallicRoughness.metallicRoughnessTexture); |
1290 | ReadMember(obj&: *curPbrMetallicRoughness, id: "metallicFactor", out&: this->pbrMetallicRoughness.metallicFactor); |
1291 | ReadMember(obj&: *curPbrMetallicRoughness, id: "roughnessFactor", out&: this->pbrMetallicRoughness.roughnessFactor); |
1292 | } |
1293 | |
1294 | ReadTextureProperty(r, vals&: material, propName: "normalTexture", out&: this->normalTexture); |
1295 | ReadTextureProperty(r, vals&: material, propName: "occlusionTexture", out&: this->occlusionTexture); |
1296 | ReadTextureProperty(r, vals&: material, propName: "emissiveTexture", out&: this->emissiveTexture); |
1297 | ReadMember(obj&: material, id: "emissiveFactor", out&: this->emissiveFactor); |
1298 | |
1299 | ReadMember(obj&: material, id: "doubleSided", out&: this->doubleSided); |
1300 | ReadMember(obj&: material, id: "alphaMode", out&: this->alphaMode); |
1301 | ReadMember(obj&: material, id: "alphaCutoff", out&: this->alphaCutoff); |
1302 | |
1303 | if (Value *extensions = FindObject(val&: material, memberId: "extensions")) { |
1304 | if (r.extensionsUsed.KHR_materials_pbrSpecularGlossiness) { |
1305 | if (Value *curPbrSpecularGlossiness = FindObject(val&: *extensions, memberId: "KHR_materials_pbrSpecularGlossiness")) { |
1306 | PbrSpecularGlossiness pbrSG; |
1307 | |
1308 | ReadMember(obj&: *curPbrSpecularGlossiness, id: "diffuseFactor", out&: pbrSG.diffuseFactor); |
1309 | ReadTextureProperty(r, vals&: *curPbrSpecularGlossiness, propName: "diffuseTexture", out&: pbrSG.diffuseTexture); |
1310 | ReadTextureProperty(r, vals&: *curPbrSpecularGlossiness, propName: "specularGlossinessTexture", out&: pbrSG.specularGlossinessTexture); |
1311 | ReadMember(obj&: *curPbrSpecularGlossiness, id: "specularFactor", out&: pbrSG.specularFactor); |
1312 | ReadMember(obj&: *curPbrSpecularGlossiness, id: "glossinessFactor", out&: pbrSG.glossinessFactor); |
1313 | |
1314 | this->pbrSpecularGlossiness = Nullable<PbrSpecularGlossiness>(pbrSG); |
1315 | } |
1316 | } |
1317 | |
1318 | if (r.extensionsUsed.KHR_materials_specular) { |
1319 | if (Value *curMatSpecular = FindObject(val&: *extensions, memberId: "KHR_materials_specular")) { |
1320 | MaterialSpecular specular; |
1321 | |
1322 | ReadMember(obj&: *curMatSpecular, id: "specularFactor", out&: specular.specularFactor); |
1323 | ReadTextureProperty(r, vals&: *curMatSpecular, propName: "specularTexture", out&: specular.specularTexture); |
1324 | ReadMember(obj&: *curMatSpecular, id: "specularColorFactor", out&: specular.specularColorFactor); |
1325 | ReadTextureProperty(r, vals&: *curMatSpecular, propName: "specularColorTexture", out&: specular.specularColorTexture); |
1326 | |
1327 | this->materialSpecular = Nullable<MaterialSpecular>(specular); |
1328 | } |
1329 | } |
1330 | |
1331 | // Extension KHR_texture_transform is handled in ReadTextureProperty |
1332 | |
1333 | if (r.extensionsUsed.KHR_materials_sheen) { |
1334 | if (Value *curMaterialSheen = FindObject(val&: *extensions, memberId: "KHR_materials_sheen")) { |
1335 | MaterialSheen sheen; |
1336 | |
1337 | ReadMember(obj&: *curMaterialSheen, id: "sheenColorFactor", out&: sheen.sheenColorFactor); |
1338 | ReadTextureProperty(r, vals&: *curMaterialSheen, propName: "sheenColorTexture", out&: sheen.sheenColorTexture); |
1339 | ReadMember(obj&: *curMaterialSheen, id: "sheenRoughnessFactor", out&: sheen.sheenRoughnessFactor); |
1340 | ReadTextureProperty(r, vals&: *curMaterialSheen, propName: "sheenRoughnessTexture", out&: sheen.sheenRoughnessTexture); |
1341 | |
1342 | this->materialSheen = Nullable<MaterialSheen>(sheen); |
1343 | } |
1344 | } |
1345 | |
1346 | if (r.extensionsUsed.KHR_materials_clearcoat) { |
1347 | if (Value *curMaterialClearcoat = FindObject(val&: *extensions, memberId: "KHR_materials_clearcoat")) { |
1348 | MaterialClearcoat clearcoat; |
1349 | |
1350 | ReadMember(obj&: *curMaterialClearcoat, id: "clearcoatFactor", out&: clearcoat.clearcoatFactor); |
1351 | ReadTextureProperty(r, vals&: *curMaterialClearcoat, propName: "clearcoatTexture", out&: clearcoat.clearcoatTexture); |
1352 | ReadMember(obj&: *curMaterialClearcoat, id: "clearcoatRoughnessFactor", out&: clearcoat.clearcoatRoughnessFactor); |
1353 | ReadTextureProperty(r, vals&: *curMaterialClearcoat, propName: "clearcoatRoughnessTexture", out&: clearcoat.clearcoatRoughnessTexture); |
1354 | ReadTextureProperty(r, vals&: *curMaterialClearcoat, propName: "clearcoatNormalTexture", out&: clearcoat.clearcoatNormalTexture); |
1355 | |
1356 | this->materialClearcoat = Nullable<MaterialClearcoat>(clearcoat); |
1357 | } |
1358 | } |
1359 | |
1360 | if (r.extensionsUsed.KHR_materials_transmission) { |
1361 | if (Value *curMaterialTransmission = FindObject(val&: *extensions, memberId: "KHR_materials_transmission")) { |
1362 | MaterialTransmission transmission; |
1363 | |
1364 | ReadMember(obj&: *curMaterialTransmission, id: "transmissionFactor", out&: transmission.transmissionFactor); |
1365 | ReadTextureProperty(r, vals&: *curMaterialTransmission, propName: "transmissionTexture", out&: transmission.transmissionTexture); |
1366 | |
1367 | this->materialTransmission = Nullable<MaterialTransmission>(transmission); |
1368 | } |
1369 | } |
1370 | |
1371 | if (r.extensionsUsed.KHR_materials_volume) { |
1372 | if (Value *curMaterialVolume = FindObject(val&: *extensions, memberId: "KHR_materials_volume")) { |
1373 | MaterialVolume volume; |
1374 | |
1375 | ReadMember(obj&: *curMaterialVolume, id: "thicknessFactor", out&: volume.thicknessFactor); |
1376 | ReadTextureProperty(r, vals&: *curMaterialVolume, propName: "thicknessTexture", out&: volume.thicknessTexture); |
1377 | ReadMember(obj&: *curMaterialVolume, id: "attenuationDistance", out&: volume.attenuationDistance); |
1378 | ReadMember(obj&: *curMaterialVolume, id: "attenuationColor", out&: volume.attenuationColor); |
1379 | |
1380 | this->materialVolume = Nullable<MaterialVolume>(volume); |
1381 | } |
1382 | } |
1383 | |
1384 | if (r.extensionsUsed.KHR_materials_ior) { |
1385 | if (Value *curMaterialIOR = FindObject(val&: *extensions, memberId: "KHR_materials_ior")) { |
1386 | MaterialIOR ior; |
1387 | |
1388 | ReadMember(obj&: *curMaterialIOR, id: "ior", out&: ior.ior); |
1389 | |
1390 | this->materialIOR = Nullable<MaterialIOR>(ior); |
1391 | } |
1392 | } |
1393 | |
1394 | if (r.extensionsUsed.KHR_materials_emissive_strength) { |
1395 | if (Value *curMaterialEmissiveStrength = FindObject(val&: *extensions, memberId: "KHR_materials_emissive_strength")) { |
1396 | MaterialEmissiveStrength emissiveStrength; |
1397 | |
1398 | ReadMember(obj&: *curMaterialEmissiveStrength, id: "emissiveStrength", out&: emissiveStrength.emissiveStrength); |
1399 | |
1400 | this->materialEmissiveStrength = Nullable<MaterialEmissiveStrength>(emissiveStrength); |
1401 | } |
1402 | } |
1403 | |
1404 | unlit = nullptr != FindObject(val&: *extensions, memberId: "KHR_materials_unlit"); |
1405 | } |
1406 | } |
1407 | |
1408 | inline void Material::SetDefaults() { |
1409 | //pbr materials |
1410 | SetVector(v&: pbrMetallicRoughness.baseColorFactor, in: defaultBaseColor); |
1411 | pbrMetallicRoughness.metallicFactor = 1.0f; |
1412 | pbrMetallicRoughness.roughnessFactor = 1.0f; |
1413 | |
1414 | SetVector(v&: emissiveFactor, in: defaultEmissiveFactor); |
1415 | alphaMode = "OPAQUE"; |
1416 | alphaCutoff = 0.5f; |
1417 | doubleSided = false; |
1418 | unlit = false; |
1419 | } |
1420 | |
1421 | inline void PbrSpecularGlossiness::SetDefaults() { |
1422 | //pbrSpecularGlossiness properties |
1423 | SetVector(v&: diffuseFactor, in: defaultDiffuseFactor); |
1424 | SetVector(v&: specularFactor, in: defaultSpecularFactor); |
1425 | glossinessFactor = 1.0f; |
1426 | } |
1427 | |
1428 | inline void MaterialSpecular::SetDefaults() { |
1429 | //KHR_materials_specular properties |
1430 | SetVector(v&: specularColorFactor, in: defaultSpecularColorFactor); |
1431 | specularFactor = 1.f; |
1432 | } |
1433 | |
1434 | inline void MaterialSheen::SetDefaults() { |
1435 | //KHR_materials_sheen properties |
1436 | SetVector(v&: sheenColorFactor, in: defaultSheenFactor); |
1437 | sheenRoughnessFactor = 0.f; |
1438 | } |
1439 | |
1440 | inline void MaterialVolume::SetDefaults() { |
1441 | //KHR_materials_volume properties |
1442 | thicknessFactor = 0.f; |
1443 | attenuationDistance = std::numeric_limits<float>::infinity(); |
1444 | SetVector(v&: attenuationColor, in: defaultAttenuationColor); |
1445 | } |
1446 | |
1447 | inline void MaterialIOR::SetDefaults() { |
1448 | //KHR_materials_ior properties |
1449 | ior = 1.5f; |
1450 | } |
1451 | |
1452 | inline void MaterialEmissiveStrength::SetDefaults() { |
1453 | //KHR_materials_emissive_strength properties |
1454 | emissiveStrength = 0.f; |
1455 | } |
1456 | |
1457 | inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { |
1458 | Value *curName = FindMember(val&: pJSON_Object, id: "name"); |
1459 | if (nullptr != curName && curName->IsString()) { |
1460 | name = curName->GetString(); |
1461 | } |
1462 | |
1463 | /****************** Mesh primitives ******************/ |
1464 | Value *curPrimitives = FindArray(val&: pJSON_Object, memberId: "primitives"); |
1465 | if (nullptr != curPrimitives) { |
1466 | this->primitives.resize(new_size: curPrimitives->Size()); |
1467 | for (unsigned int i = 0; i < curPrimitives->Size(); ++i) { |
1468 | Value &primitive = (*curPrimitives)[i]; |
1469 | |
1470 | Primitive &prim = this->primitives[i]; |
1471 | prim.mode = MemberOrDefault(obj&: primitive, id: "mode", defaultValue: PrimitiveMode_TRIANGLES); |
1472 | |
1473 | if (Value *indices = FindUInt(val&: primitive, memberId: "indices")) { |
1474 | prim.indices = pAsset_Root.accessors.Retrieve(i: indices->GetUint()); |
1475 | } |
1476 | |
1477 | if (Value *material = FindUInt(val&: primitive, memberId: "material")) { |
1478 | prim.material = pAsset_Root.materials.Retrieve(i: material->GetUint()); |
1479 | } |
1480 | |
1481 | if (Value *attrs = FindObject(val&: primitive, memberId: "attributes")) { |
1482 | for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) { |
1483 | if (!it->value.IsUint()) continue; |
1484 | const char *attr = it->name.GetString(); |
1485 | // Valid attribute semantics include POSITION, NORMAL, TANGENT, TEXCOORD, COLOR, JOINT, JOINTMATRIX, |
1486 | // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc. |
1487 | |
1488 | int undPos = 0; |
1489 | Mesh::AccessorList *vec = nullptr; |
1490 | if (GetAttribVector(p&: prim, attr, v&: vec, pos&: undPos)) { |
1491 | size_t idx = (attr[undPos] == '_') ? atoi(nptr: attr + undPos + 1) : 0; |
1492 | if ((*vec).size() != idx) { |
1493 | throw DeadlyImportError("GLTF: Invalid attribute in mesh: ", name, " primitive: ", i, "attrib: ", attr, |
1494 | ". All indices for indexed attribute semantics must start with 0 and be continuous positive integers: TEXCOORD_0, TEXCOORD_1, etc."); |
1495 | } |
1496 | (*vec).resize(new_size: idx + 1); |
1497 | (*vec)[idx] = pAsset_Root.accessors.Retrieve(i: it->value.GetUint()); |
1498 | } |
1499 | } |
1500 | } |
1501 | |
1502 | #ifdef ASSIMP_ENABLE_DRACO |
1503 | // KHR_draco_mesh_compression spec: Draco can only be used for glTF Triangles or Triangle Strips |
1504 | if (pAsset_Root.extensionsUsed.KHR_draco_mesh_compression && (prim.mode == PrimitiveMode_TRIANGLES || prim.mode == PrimitiveMode_TRIANGLE_STRIP)) { |
1505 | // Look for draco mesh compression extension and bufferView |
1506 | // Skip if any missing |
1507 | if (Value *dracoExt = FindExtension(primitive, "KHR_draco_mesh_compression")) { |
1508 | if (Value *bufView = FindUInt(*dracoExt, "bufferView")) { |
1509 | // Attempt to load indices and attributes using draco compression |
1510 | auto bufferView = pAsset_Root.bufferViews.Retrieve(bufView->GetUint()); |
1511 | // Attempt to perform the draco decode on the buffer data |
1512 | const char *bufferViewData = reinterpret_cast<const char *>(bufferView->buffer->GetPointer() + bufferView->byteOffset); |
1513 | draco::DecoderBuffer decoderBuffer; |
1514 | decoderBuffer.Init(bufferViewData, bufferView->byteLength); |
1515 | draco::Decoder decoder; |
1516 | auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer); |
1517 | if (!decodeResult.ok()) { |
1518 | // A corrupt Draco isn't actually fatal if the primitive data is also provided in a standard buffer, but does anyone do that? |
1519 | throw DeadlyImportError("GLTF: Invalid Draco mesh compression in mesh: ", name, " primitive: ", i, ": ", decodeResult.status().error_msg_string()); |
1520 | } |
1521 | |
1522 | // Now we have a draco mesh |
1523 | const std::unique_ptr<draco::Mesh> &pDracoMesh = decodeResult.value(); |
1524 | |
1525 | // Redirect the accessors to the decoded data |
1526 | |
1527 | // Indices |
1528 | SetDecodedIndexBuffer_Draco(*pDracoMesh, prim); |
1529 | |
1530 | // Vertex attributes |
1531 | if (Value *attrs = FindObject(*dracoExt, "attributes")) { |
1532 | for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) { |
1533 | if (!it->value.IsUint()) continue; |
1534 | const char *attr = it->name.GetString(); |
1535 | |
1536 | int undPos = 0; |
1537 | Mesh::AccessorList *vec = nullptr; |
1538 | if (GetAttribVector(prim, attr, vec, undPos)) { |
1539 | size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; |
1540 | if (idx >= (*vec).size()) { |
1541 | throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr, |
1542 | ". All indices for indexed attribute semantics must start with 0 and be continuous positive integers: TEXCOORD_0, TEXCOORD_1, etc."); |
1543 | } |
1544 | |
1545 | if (!(*vec)[idx]) { |
1546 | throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr, |
1547 | ". All draco-encoded attributes must also define an accessor."); |
1548 | } |
1549 | |
1550 | Accessor &attribAccessor = *(*vec)[idx]; |
1551 | if (attribAccessor.count == 0) |
1552 | throw DeadlyImportError("GLTF: Invalid draco attribute in mesh: ", name, " primitive: ", i, " attrib: ", attr); |
1553 | |
1554 | // Redirect this accessor to the appropriate Draco vertex attribute data |
1555 | const uint32_t dracoAttribId = it->value.GetUint(); |
1556 | SetDecodedAttributeBuffer_Draco(*pDracoMesh, dracoAttribId, attribAccessor); |
1557 | } |
1558 | } |
1559 | } |
1560 | } |
1561 | } |
1562 | } |
1563 | #endif |
1564 | |
1565 | Value *targetsArray = FindArray(val&: primitive, memberId: "targets"); |
1566 | if (nullptr != targetsArray) { |
1567 | prim.targets.resize(new_size: targetsArray->Size()); |
1568 | for (unsigned int j = 0; j < targetsArray->Size(); ++j) { |
1569 | Value &target = (*targetsArray)[j]; |
1570 | if (!target.IsObject()) { |
1571 | continue; |
1572 | } |
1573 | for (Value::MemberIterator it = target.MemberBegin(); it != target.MemberEnd(); ++it) { |
1574 | if (!it->value.IsUint()) { |
1575 | continue; |
1576 | } |
1577 | const char *attr = it->name.GetString(); |
1578 | // Valid attribute semantics include POSITION, NORMAL, TANGENT |
1579 | int undPos = 0; |
1580 | Mesh::AccessorList *vec = nullptr; |
1581 | if (GetAttribTargetVector(p&: prim, targetIndex: j, attr, v&: vec, pos&: undPos)) { |
1582 | size_t idx = (attr[undPos] == '_') ? atoi(nptr: attr + undPos + 1) : 0; |
1583 | if ((*vec).size() <= idx) { |
1584 | (*vec).resize(new_size: idx + 1); |
1585 | } |
1586 | (*vec)[idx] = pAsset_Root.accessors.Retrieve(i: it->value.GetUint()); |
1587 | } |
1588 | } |
1589 | } |
1590 | } |
1591 | |
1592 | if(this->targetNames.empty()) |
1593 | { |
1594 | Value *curExtras = FindObject(val&: primitive, memberId: "extras"); |
1595 | if (nullptr != curExtras) { |
1596 | if (Value *curTargetNames = FindArray(val&: *curExtras, memberId: "targetNames")) { |
1597 | this->targetNames.resize(new_size: curTargetNames->Size()); |
1598 | for (unsigned int j = 0; j < curTargetNames->Size(); ++j) { |
1599 | Value &targetNameValue = (*curTargetNames)[j]; |
1600 | if (targetNameValue.IsString()) { |
1601 | this->targetNames[j] = targetNameValue.GetString(); |
1602 | } |
1603 | } |
1604 | } |
1605 | } |
1606 | } |
1607 | } |
1608 | } |
1609 | |
1610 | Value *curWeights = FindArray(val&: pJSON_Object, memberId: "weights"); |
1611 | if (nullptr != curWeights) { |
1612 | this->weights.resize(new_size: curWeights->Size()); |
1613 | for (unsigned int i = 0; i < curWeights->Size(); ++i) { |
1614 | Value &weightValue = (*curWeights)[i]; |
1615 | if (weightValue.IsNumber()) { |
1616 | this->weights[i] = weightValue.GetFloat(); |
1617 | } |
1618 | } |
1619 | } |
1620 | |
1621 | Value *curExtras = FindObject(val&: pJSON_Object, memberId: "extras"); |
1622 | if (nullptr != curExtras) { |
1623 | if (Value *curTargetNames = FindArray(val&: *curExtras, memberId: "targetNames")) { |
1624 | this->targetNames.resize(new_size: curTargetNames->Size()); |
1625 | for (unsigned int i = 0; i < curTargetNames->Size(); ++i) { |
1626 | Value &targetNameValue = (*curTargetNames)[i]; |
1627 | if (targetNameValue.IsString()) { |
1628 | this->targetNames[i] = targetNameValue.GetString(); |
1629 | } |
1630 | } |
1631 | } |
1632 | } |
1633 | } |
1634 | |
1635 | inline void Camera::Read(Value &obj, Asset & /*r*/) { |
1636 | std::string type_string = std::string(MemberOrDefault(obj, id: "type", defaultValue: "perspective")); |
1637 | if (type_string == "orthographic") { |
1638 | type = Camera::Orthographic; |
1639 | } else { |
1640 | type = Camera::Perspective; |
1641 | } |
1642 | |
1643 | const char *subobjId = (type == Camera::Orthographic) ? "orthographic": "perspective"; |
1644 | |
1645 | Value *it = FindObject(val&: obj, memberId: subobjId); |
1646 | if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters"); |
1647 | |
1648 | if (type == Camera::Perspective) { |
1649 | cameraProperties.perspective.aspectRatio = MemberOrDefault(obj&: *it, id: "aspectRatio", defaultValue: 0.f); |
1650 | cameraProperties.perspective.yfov = MemberOrDefault(obj&: *it, id: "yfov", defaultValue: 3.1415f / 2.f); |
1651 | cameraProperties.perspective.zfar = MemberOrDefault(obj&: *it, id: "zfar", defaultValue: 100.f); |
1652 | cameraProperties.perspective.znear = MemberOrDefault(obj&: *it, id: "znear", defaultValue: 0.01f); |
1653 | } else { |
1654 | cameraProperties.ortographic.xmag = MemberOrDefault(obj&: *it, id: "xmag", defaultValue: 1.f); |
1655 | cameraProperties.ortographic.ymag = MemberOrDefault(obj&: *it, id: "ymag", defaultValue: 1.f); |
1656 | cameraProperties.ortographic.zfar = MemberOrDefault(obj&: *it, id: "zfar", defaultValue: 100.f); |
1657 | cameraProperties.ortographic.znear = MemberOrDefault(obj&: *it, id: "znear", defaultValue: 0.01f); |
1658 | } |
1659 | } |
1660 | |
1661 | inline void Light::Read(Value &obj, Asset & /*r*/) { |
1662 | #ifndef M_PI |
1663 | const float M_PI = 3.14159265358979323846f; |
1664 | #endif |
1665 | |
1666 | std::string type_string; |
1667 | ReadMember(obj, id: "type", out&: type_string); |
1668 | if (type_string == "directional") |
1669 | type = Light::Directional; |
1670 | else if (type_string == "point") |
1671 | type = Light::Point; |
1672 | else |
1673 | type = Light::Spot; |
1674 | |
1675 | name = MemberOrDefault(obj, id: "name", defaultValue: ""); |
1676 | |
1677 | SetVector(v&: color, in: vec3{ 1.0f, 1.0f, 1.0f }); |
1678 | ReadMember(obj, id: "color", out&: color); |
1679 | |
1680 | intensity = MemberOrDefault(obj, id: "intensity", defaultValue: 1.0f); |
1681 | |
1682 | ReadMember(obj, id: "range", out&: range); |
1683 | |
1684 | if (type == Light::Spot) { |
1685 | Value *spot = FindObject(val&: obj, memberId: "spot"); |
1686 | if (!spot) throw DeadlyImportError("GLTF: Light missing its spot parameters"); |
1687 | innerConeAngle = MemberOrDefault(obj&: *spot, id: "innerConeAngle", defaultValue: 0.0f); |
1688 | outerConeAngle = MemberOrDefault(obj&: *spot, id: "outerConeAngle", defaultValue: static_cast<float>(M_PI / 4.0f)); |
1689 | } |
1690 | } |
1691 | |
1692 | inline void Node::Read(Value &obj, Asset &r) { |
1693 | if (name.empty()) { |
1694 | name = id; |
1695 | } |
1696 | |
1697 | Value *curChildren = FindArray(val&: obj, memberId: "children"); |
1698 | if (nullptr != curChildren) { |
1699 | this->children.reserve(n: curChildren->Size()); |
1700 | for (unsigned int i = 0; i < curChildren->Size(); ++i) { |
1701 | Value &child = (*curChildren)[i]; |
1702 | if (child.IsUint()) { |
1703 | // get/create the child node |
1704 | Ref<Node> chn = r.nodes.Retrieve(i: child.GetUint()); |
1705 | if (chn) { |
1706 | this->children.push_back(x: chn); |
1707 | } |
1708 | } |
1709 | } |
1710 | } |
1711 | |
1712 | Value *curMatrix = FindArray(val&: obj, memberId: "matrix"); |
1713 | if (nullptr != curMatrix) { |
1714 | ReadValue(val&: *curMatrix, out&: this->matrix); |
1715 | } else { |
1716 | ReadMember(obj, id: "translation", out&: translation); |
1717 | ReadMember(obj, id: "scale", out&: scale); |
1718 | ReadMember(obj, id: "rotation", out&: rotation); |
1719 | } |
1720 | |
1721 | Value *curMesh = FindUInt(val&: obj, memberId: "mesh"); |
1722 | if (nullptr != curMesh) { |
1723 | unsigned int numMeshes = 1; |
1724 | this->meshes.reserve(n: numMeshes); |
1725 | Ref<Mesh> meshRef = r.meshes.Retrieve(i: (*curMesh).GetUint()); |
1726 | if (meshRef) { |
1727 | this->meshes.push_back(x: meshRef); |
1728 | } |
1729 | } |
1730 | |
1731 | // Do not retrieve a skin here, just take a reference, to avoid infinite recursion |
1732 | // Skins will be properly loaded later |
1733 | Value *curSkin = FindUInt(val&: obj, memberId: "skin"); |
1734 | if (nullptr != curSkin) { |
1735 | this->skin = r.skins.Get(i: curSkin->GetUint()); |
1736 | } |
1737 | |
1738 | Value *curCamera = FindUInt(val&: obj, memberId: "camera"); |
1739 | if (nullptr != curCamera) { |
1740 | this->camera = r.cameras.Retrieve(i: curCamera->GetUint()); |
1741 | if (this->camera) { |
1742 | this->camera->id = this->id; |
1743 | } |
1744 | } |
1745 | |
1746 | Value *curExtensions = FindObject(val&: obj, memberId: "extensions"); |
1747 | if (nullptr != curExtensions) { |
1748 | if (r.extensionsUsed.KHR_lights_punctual) { |
1749 | if (Value *ext = FindObject(val&: *curExtensions, memberId: "KHR_lights_punctual")) { |
1750 | Value *curLight = FindUInt(val&: *ext, memberId: "light"); |
1751 | if (nullptr != curLight) { |
1752 | this->light = r.lights.Retrieve(i: curLight->GetUint()); |
1753 | if (this->light) { |
1754 | this->light->id = this->id; |
1755 | } |
1756 | } |
1757 | } |
1758 | } |
1759 | } |
1760 | } |
1761 | |
1762 | inline void Scene::Read(Value &obj, Asset &r) { |
1763 | if (Value *scene_name = FindString(val&: obj, memberId: "name")) { |
1764 | if (scene_name->IsString()) { |
1765 | this->name = scene_name->GetString(); |
1766 | } |
1767 | } |
1768 | if (Value *array = FindArray(val&: obj, memberId: "nodes")) { |
1769 | for (unsigned int i = 0; i < array->Size(); ++i) { |
1770 | if (!(*array)[i].IsUint()) continue; |
1771 | Ref<Node> node = r.nodes.Retrieve(i: (*array)[i].GetUint()); |
1772 | if (node) |
1773 | this->nodes.push_back(x: node); |
1774 | } |
1775 | } |
1776 | } |
1777 | |
1778 | inline void Skin::Read(Value &obj, Asset &r) { |
1779 | if (Value *matrices = FindUInt(val&: obj, memberId: "inverseBindMatrices")) { |
1780 | inverseBindMatrices = r.accessors.Retrieve(i: matrices->GetUint()); |
1781 | } |
1782 | |
1783 | if (Value *joints = FindArray(val&: obj, memberId: "joints")) { |
1784 | for (unsigned i = 0; i < joints->Size(); ++i) { |
1785 | if (!(*joints)[i].IsUint()) continue; |
1786 | Ref<Node> node = r.nodes.Retrieve(i: (*joints)[i].GetUint()); |
1787 | if (node) { |
1788 | this->jointNames.push_back(x: node); |
1789 | } |
1790 | } |
1791 | } |
1792 | } |
1793 | |
1794 | inline void Animation::Read(Value &obj, Asset &r) { |
1795 | Value *curSamplers = FindArray(val&: obj, memberId: "samplers"); |
1796 | if (nullptr != curSamplers) { |
1797 | for (unsigned i = 0; i < curSamplers->Size(); ++i) { |
1798 | Value &sampler = (*curSamplers)[i]; |
1799 | |
1800 | Sampler s; |
1801 | if (Value *input = FindUInt(val&: sampler, memberId: "input")) { |
1802 | s.input = r.accessors.Retrieve(i: input->GetUint()); |
1803 | } |
1804 | if (Value *output = FindUInt(val&: sampler, memberId: "output")) { |
1805 | s.output = r.accessors.Retrieve(i: output->GetUint()); |
1806 | } |
1807 | s.interpolation = Interpolation_LINEAR; |
1808 | if (Value *interpolation = FindString(val&: sampler, memberId: "interpolation")) { |
1809 | const std::string interp = interpolation->GetString(); |
1810 | if (interp == "LINEAR") { |
1811 | s.interpolation = Interpolation_LINEAR; |
1812 | } else if (interp == "STEP") { |
1813 | s.interpolation = Interpolation_STEP; |
1814 | } else if (interp == "CUBICSPLINE") { |
1815 | s.interpolation = Interpolation_CUBICSPLINE; |
1816 | } |
1817 | } |
1818 | this->samplers.push_back(x: s); |
1819 | } |
1820 | } |
1821 | |
1822 | Value *curChannels = FindArray(val&: obj, memberId: "channels"); |
1823 | if (nullptr != curChannels) { |
1824 | for (unsigned i = 0; i < curChannels->Size(); ++i) { |
1825 | Value &channel = (*curChannels)[i]; |
1826 | |
1827 | Channel c; |
1828 | Value *curSampler = FindUInt(val&: channel, memberId: "sampler"); |
1829 | if (nullptr != curSampler) { |
1830 | c.sampler = curSampler->GetUint(); |
1831 | } |
1832 | |
1833 | if (Value *target = FindObject(val&: channel, memberId: "target")) { |
1834 | if (Value *node = FindUInt(val&: *target, memberId: "node")) { |
1835 | c.target.node = r.nodes.Retrieve(i: node->GetUint()); |
1836 | } |
1837 | if (Value *path = FindString(val&: *target, memberId: "path")) { |
1838 | const std::string p = path->GetString(); |
1839 | if (p == "translation") { |
1840 | c.target.path = AnimationPath_TRANSLATION; |
1841 | } else if (p == "rotation") { |
1842 | c.target.path = AnimationPath_ROTATION; |
1843 | } else if (p == "scale") { |
1844 | c.target.path = AnimationPath_SCALE; |
1845 | } else if (p == "weights") { |
1846 | c.target.path = AnimationPath_WEIGHTS; |
1847 | } |
1848 | } |
1849 | } |
1850 | this->channels.push_back(x: c); |
1851 | } |
1852 | } |
1853 | } |
1854 | |
1855 | inline void AssetMetadata::Read(Document &doc) { |
1856 | if (Value *obj = FindObject(doc, memberId: "asset")) { |
1857 | ReadMember(obj&: *obj, id: "copyright", out&: copyright); |
1858 | ReadMember(obj&: *obj, id: "generator", out&: generator); |
1859 | |
1860 | if (Value *versionString = FindStringInContext(val&: *obj, memberId: "version", context: "\"asset\"")) { |
1861 | version = versionString->GetString(); |
1862 | } |
1863 | Value *curProfile = FindObjectInContext(val&: *obj, memberId: "profile", context: "\"asset\""); |
1864 | if (nullptr != curProfile) { |
1865 | ReadMember(obj&: *curProfile, id: "api", out&: this->profile.api); |
1866 | ReadMember(obj&: *curProfile, id: "version", out&: this->profile.version); |
1867 | } |
1868 | } |
1869 | |
1870 | if (version.empty() || version[0] != '2') { |
1871 | throw DeadlyImportError("GLTF: Unsupported glTF version: ", version); |
1872 | } |
1873 | } |
1874 | |
1875 | // |
1876 | // Asset methods implementation |
1877 | // |
1878 | |
1879 | inline void Asset::ReadBinaryHeader(IOStream &stream, std::vector<char> &sceneData) { |
1880 | ASSIMP_LOG_DEBUG("Reading GLTF2 binary"); |
1881 | GLB_Header header; |
1882 | if (stream.Read(pvBuffer: &header, pSize: sizeof(header), pCount: 1) != 1) { |
1883 | throw DeadlyImportError("GLTF: Unable to read the file header"); |
1884 | } |
1885 | |
1886 | if (strncmp(s1: (char *)header.magic, AI_GLB_MAGIC_NUMBER, n: sizeof(header.magic)) != 0) { |
1887 | throw DeadlyImportError("GLTF: Invalid binary glTF file"); |
1888 | } |
1889 | |
1890 | AI_SWAP4(header.version); |
1891 | asset.version = ai_to_string(value: header.version); |
1892 | if (header.version != 2) { |
1893 | throw DeadlyImportError("GLTF: Unsupported binary glTF version"); |
1894 | } |
1895 | |
1896 | GLB_Chunk chunk; |
1897 | if (stream.Read(pvBuffer: &chunk, pSize: sizeof(chunk), pCount: 1) != 1) { |
1898 | throw DeadlyImportError("GLTF: Unable to read JSON chunk"); |
1899 | } |
1900 | |
1901 | AI_SWAP4(chunk.chunkLength); |
1902 | AI_SWAP4(chunk.chunkType); |
1903 | |
1904 | if (chunk.chunkType != ChunkType_JSON) { |
1905 | throw DeadlyImportError("GLTF: JSON chunk missing"); |
1906 | } |
1907 | |
1908 | // read the scene data, ensure null termination |
1909 | static_assert(std::numeric_limits<uint32_t>::max() <= std::numeric_limits<size_t>::max(), "size_t must be at least 32bits"); |
1910 | mSceneLength = chunk.chunkLength; // Can't be larger than 4GB (max. uint32_t) |
1911 | sceneData.resize(new_size: mSceneLength + 1); |
1912 | sceneData[mSceneLength] = '\0'; |
1913 | |
1914 | if (stream.Read(pvBuffer: &sceneData[0], pSize: 1, pCount: mSceneLength) != mSceneLength) { |
1915 | throw DeadlyImportError("GLTF: Could not read the file contents"); |
1916 | } |
1917 | |
1918 | uint32_t padding = ((chunk.chunkLength + 3) & ~3) - chunk.chunkLength; |
1919 | if (padding > 0) { |
1920 | stream.Seek(pOffset: padding, pOrigin: aiOrigin_CUR); |
1921 | } |
1922 | |
1923 | AI_SWAP4(header.length); |
1924 | mBodyOffset = 12 + 8 + chunk.chunkLength + padding + 8; |
1925 | if (header.length >= mBodyOffset) { |
1926 | if (stream.Read(pvBuffer: &chunk, pSize: sizeof(chunk), pCount: 1) != 1) { |
1927 | throw DeadlyImportError("GLTF: Unable to read BIN chunk"); |
1928 | } |
1929 | |
1930 | AI_SWAP4(chunk.chunkLength); |
1931 | AI_SWAP4(chunk.chunkType); |
1932 | |
1933 | if (chunk.chunkType != ChunkType_BIN) { |
1934 | throw DeadlyImportError("GLTF: BIN chunk missing"); |
1935 | } |
1936 | |
1937 | mBodyLength = chunk.chunkLength; |
1938 | } else { |
1939 | mBodyOffset = mBodyLength = 0; |
1940 | } |
1941 | } |
1942 | |
1943 | inline rapidjson::Document Asset::ReadDocument(IOStream &stream, bool isBinary, std::vector<char> &sceneData) { |
1944 | ASSIMP_LOG_DEBUG("Loading GLTF2 asset"); |
1945 | |
1946 | // is binary? then read the header |
1947 | if (isBinary) { |
1948 | SetAsBinary(); // also creates the body buffer |
1949 | ReadBinaryHeader(stream, sceneData); |
1950 | } else { |
1951 | mSceneLength = stream.FileSize(); |
1952 | mBodyLength = 0; |
1953 | |
1954 | // Binary format only supports up to 4GB of JSON, use that as a maximum |
1955 | if (mSceneLength >= std::numeric_limits<uint32_t>::max()) { |
1956 | throw DeadlyImportError("GLTF: JSON size greater than 4GB"); |
1957 | } |
1958 | |
1959 | // read the scene data, ensure null termination |
1960 | sceneData.resize(new_size: mSceneLength + 1); |
1961 | sceneData[mSceneLength] = '\0'; |
1962 | |
1963 | if (stream.Read(pvBuffer: &sceneData[0], pSize: 1, pCount: mSceneLength) != mSceneLength) { |
1964 | throw DeadlyImportError("GLTF: Could not read the file contents"); |
1965 | } |
1966 | } |
1967 | |
1968 | // Smallest legal JSON file is "{}" Smallest loadable glTF file is larger than that but catch it later |
1969 | if (mSceneLength < 2) { |
1970 | throw DeadlyImportError("GLTF: No JSON file contents"); |
1971 | } |
1972 | |
1973 | // parse the JSON document |
1974 | ASSIMP_LOG_DEBUG("Parsing GLTF2 JSON"); |
1975 | Document doc; |
1976 | doc.ParseInsitu(str: &sceneData[0]); |
1977 | |
1978 | if (doc.HasParseError()) { |
1979 | char buffer[32]; |
1980 | ai_snprintf(s: buffer, maxlen: 32, format: "%d", static_cast<int>(doc.GetErrorOffset())); |
1981 | throw DeadlyImportError("GLTF: JSON parse error, offset ", buffer, ": ", GetParseError_En(parseErrorCode: doc.GetParseError())); |
1982 | } |
1983 | |
1984 | if (!doc.IsObject()) { |
1985 | throw DeadlyImportError("GLTF: JSON document root must be a JSON object"); |
1986 | } |
1987 | |
1988 | return doc; |
1989 | } |
1990 | |
1991 | inline void Asset::Load(const std::string &pFile, bool isBinary) |
1992 | { |
1993 | mCurrentAssetDir.clear(); |
1994 | if (0 != strncmp(s1: pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { |
1995 | mCurrentAssetDir = glTFCommon::getCurrentAssetDir(pFile); |
1996 | } |
1997 | |
1998 | shared_ptr<IOStream> stream(OpenFile(path: pFile.c_str(), mode: "rb", absolute: true)); |
1999 | if (!stream) { |
2000 | throw DeadlyImportError("GLTF: Could not open file for reading"); |
2001 | } |
2002 | |
2003 | std::vector<char> sceneData; |
2004 | rapidjson::Document doc = ReadDocument(stream&: *stream, isBinary, sceneData); |
2005 | |
2006 | // If a schemaDocumentProvider is available, see if the glTF schema is present. |
2007 | // If so, use it to validate the document. |
2008 | if (mSchemaDocumentProvider) { |
2009 | if (const rapidjson::SchemaDocument *gltfSchema = mSchemaDocumentProvider->GetRemoteDocument(uri: "glTF.schema.json", length: 16)) { |
2010 | // The schemas are found here: https://github.com/KhronosGroup/glTF/tree/main/specification/2.0/schema |
2011 | rapidjson::SchemaValidator validator(*gltfSchema); |
2012 | if (!doc.Accept(handler&: validator)) { |
2013 | rapidjson::StringBuffer pathBuffer; |
2014 | validator.GetInvalidSchemaPointer().StringifyUriFragment(os&: pathBuffer); |
2015 | rapidjson::StringBuffer argumentBuffer; |
2016 | validator.GetInvalidDocumentPointer().StringifyUriFragment(os&: argumentBuffer); |
2017 | throw DeadlyImportError("GLTF: The JSON document did not satisfy the glTF2 schema. Schema keyword: ", validator.GetInvalidSchemaKeyword(), ", document path: ", pathBuffer.GetString(), ", argument: ", argumentBuffer.GetString()); |
2018 | } |
2019 | } |
2020 | } |
2021 | |
2022 | // Fill the buffer instance for the current file embedded contents |
2023 | if (mBodyLength > 0) { |
2024 | if (!mBodyBuffer->LoadFromStream(stream&: *stream, length: mBodyLength, baseOffset: mBodyOffset)) { |
2025 | throw DeadlyImportError("GLTF: Unable to read gltf file"); |
2026 | } |
2027 | } |
2028 | |
2029 | // Load the metadata |
2030 | asset.Read(doc); |
2031 | ReadExtensionsUsed(doc); |
2032 | ReadExtensionsRequired(doc); |
2033 | |
2034 | #ifndef ASSIMP_ENABLE_DRACO |
2035 | // Is Draco required? |
2036 | if (extensionsRequired.KHR_draco_mesh_compression) { |
2037 | throw DeadlyImportError("GLTF: Draco mesh compression not supported."); |
2038 | } |
2039 | #endif |
2040 | |
2041 | // Prepare the dictionaries |
2042 | for (size_t i = 0; i < mDicts.size(); ++i) { |
2043 | mDicts[i]->AttachToDocument(doc); |
2044 | } |
2045 | |
2046 | // Read the "scene" property, which specifies which scene to load |
2047 | // and recursively load everything referenced by it |
2048 | unsigned int sceneIndex = 0; |
2049 | Value *curScene = FindUInt(doc, memberId: "scene"); |
2050 | if (nullptr != curScene) { |
2051 | sceneIndex = curScene->GetUint(); |
2052 | } |
2053 | |
2054 | if (Value *scenesArray = FindArray(val&: doc, memberId: "scenes")) { |
2055 | if (sceneIndex < scenesArray->Size()) { |
2056 | this->scene = scenes.Retrieve(i: sceneIndex); |
2057 | } |
2058 | } |
2059 | |
2060 | if (Value *skinsArray = FindArray(val&: doc, memberId: "skins")) { |
2061 | for (unsigned int i = 0; i < skinsArray->Size(); ++i) { |
2062 | skins.Retrieve(i); |
2063 | } |
2064 | } |
2065 | |
2066 | if (Value *animsArray = FindArray(val&: doc, memberId: "animations")) { |
2067 | for (unsigned int i = 0; i < animsArray->Size(); ++i) { |
2068 | animations.Retrieve(i); |
2069 | } |
2070 | } |
2071 | |
2072 | // Clean up |
2073 | for (size_t i = 0; i < mDicts.size(); ++i) { |
2074 | mDicts[i]->DetachFromDocument(); |
2075 | } |
2076 | } |
2077 | |
2078 | inline bool Asset::CanRead(const std::string &pFile, bool isBinary) { |
2079 | try { |
2080 | shared_ptr<IOStream> stream(OpenFile(path: pFile.c_str(), mode: "rb", absolute: true)); |
2081 | if (!stream) { |
2082 | return false; |
2083 | } |
2084 | std::vector<char> sceneData; |
2085 | rapidjson::Document doc = ReadDocument(stream&: *stream, isBinary, sceneData); |
2086 | asset.Read(doc); |
2087 | } catch (...) { |
2088 | return false; |
2089 | } |
2090 | return true; |
2091 | } |
2092 | |
2093 | inline void Asset::SetAsBinary() { |
2094 | if (!mBodyBuffer) { |
2095 | mBodyBuffer = buffers.Create(id: "binary_glTF"); |
2096 | mBodyBuffer->MarkAsSpecial(); |
2097 | } |
2098 | } |
2099 | |
2100 | // As required extensions are only a concept in glTF 2.0, this is here |
2101 | // instead of glTFCommon.h |
2102 | #define CHECK_REQUIRED_EXT(EXT) \ |
2103 | if (exts.find(#EXT) != exts.end()) extensionsRequired.EXT = true; |
2104 | |
2105 | inline void Asset::ReadExtensionsRequired(Document &doc) { |
2106 | Value *extsRequired = FindArray(val&: doc, memberId: "extensionsRequired"); |
2107 | if (nullptr == extsRequired) { |
2108 | return; |
2109 | } |
2110 | |
2111 | std::gltf_unordered_map<std::string, bool> exts; |
2112 | for (unsigned int i = 0; i < extsRequired->Size(); ++i) { |
2113 | if ((*extsRequired)[i].IsString()) { |
2114 | exts[(*extsRequired)[i].GetString()] = true; |
2115 | } |
2116 | } |
2117 | |
2118 | CHECK_REQUIRED_EXT(KHR_draco_mesh_compression); |
2119 | |
2120 | #undef CHECK_REQUIRED_EXT |
2121 | } |
2122 | |
2123 | inline void Asset::ReadExtensionsUsed(Document &doc) { |
2124 | Value *extsUsed = FindArray(val&: doc, memberId: "extensionsUsed"); |
2125 | if (!extsUsed) return; |
2126 | |
2127 | std::gltf_unordered_map<std::string, bool> exts; |
2128 | |
2129 | for (unsigned int i = 0; i < extsUsed->Size(); ++i) { |
2130 | if ((*extsUsed)[i].IsString()) { |
2131 | exts[(*extsUsed)[i].GetString()] = true; |
2132 | } |
2133 | } |
2134 | |
2135 | CHECK_EXT(KHR_materials_pbrSpecularGlossiness); |
2136 | CHECK_EXT(KHR_materials_specular); |
2137 | CHECK_EXT(KHR_materials_unlit); |
2138 | CHECK_EXT(KHR_lights_punctual); |
2139 | CHECK_EXT(KHR_texture_transform); |
2140 | CHECK_EXT(KHR_materials_sheen); |
2141 | CHECK_EXT(KHR_materials_clearcoat); |
2142 | CHECK_EXT(KHR_materials_transmission); |
2143 | CHECK_EXT(KHR_materials_volume); |
2144 | CHECK_EXT(KHR_materials_ior); |
2145 | CHECK_EXT(KHR_materials_emissive_strength); |
2146 | CHECK_EXT(KHR_draco_mesh_compression); |
2147 | CHECK_EXT(KHR_texture_basisu); |
2148 | |
2149 | #undef CHECK_EXT |
2150 | } |
2151 | |
2152 | inline IOStream *Asset::OpenFile(const std::string &path, const char *mode, bool /*absolute*/) { |
2153 | #ifdef ASSIMP_API |
2154 | return mIOSystem->Open(pFile: path, pMode: mode); |
2155 | #else |
2156 | if (path.size() < 2) return nullptr; |
2157 | if (!absolute && path[1] != ':' && path[0] != '/') { // relative? |
2158 | path = mCurrentAssetDir + path; |
2159 | } |
2160 | FILE *f = fopen(path.c_str(), mode); |
2161 | return f ? new IOStream(f) : nullptr; |
2162 | #endif |
2163 | } |
2164 | |
2165 | inline std::string Asset::FindUniqueID(const std::string &str, const char *suffix) { |
2166 | std::string id = str; |
2167 | |
2168 | if (!id.empty()) { |
2169 | if (mUsedIds.find(x: id) == mUsedIds.end()) |
2170 | return id; |
2171 | |
2172 | id += "_"; |
2173 | } |
2174 | |
2175 | id += suffix; |
2176 | |
2177 | Asset::IdMap::iterator it = mUsedIds.find(x: id); |
2178 | if (it == mUsedIds.end()) { |
2179 | return id; |
2180 | } |
2181 | |
2182 | std::vector<char> buffer; |
2183 | buffer.resize(new_size: id.size() + 16); |
2184 | int offset = ai_snprintf(s: buffer.data(), maxlen: buffer.size(), format: "%s_", id.c_str()); |
2185 | for (int i = 0; it != mUsedIds.end(); ++i) { |
2186 | ai_snprintf(s: buffer.data() + offset, maxlen: buffer.size() - offset, format: "%d", i); |
2187 | id = buffer.data(); |
2188 | it = mUsedIds.find(x: id); |
2189 | } |
2190 | |
2191 | return id; |
2192 | } |
2193 | |
2194 | #if _MSC_VER |
2195 | # pragma warning(pop) |
2196 | #endif // _MSC_VER |
2197 | |
2198 | } // namespace glTF2 |
2199 |
Definitions
- ReadExtensions
- ReadExtras
- CopyData
- SetVector
- SetVector
- Compare
- GetAttribVector
- GetAttribTargetVector
- FindString
- FindNumber
- FindUInt
- FindArray
- FindObject
- FindExtension
- ReadExtensions
- ReadExtras
- LazyDict
- ~LazyDict
- AttachToDocument
- DetachFromDocument
- Remove
- Retrieve
- Get
- Get
- Add
- Create
- Buffer
- ~Buffer
- TranslateId
- Read
- LoadFromStream
- EncodedRegion_Mark
- EncodedRegion_SetCurrent
- ReplaceData
- ReplaceData_joint
- AppendData
- Grow
- Read
- GetPointerAndTailSize
- PopulateData
- PatchData
- Read
- GetNumComponents
- GetBytesPerComponent
- GetElementSize
- GetPointer
- GetStride
- GetMaxByteSize
- ExtractData
- WriteData
- WriteSparseValues
- WriteSparseIndices
- Indexer
- GetValue
- Image
- Read
- StealData
- SetData
- Read
- SetDefaults
- Read
- SetTextureProperties
- ReadTextureProperty
- ReadTextureProperty
- ReadTextureProperty
- Read
- SetDefaults
- SetDefaults
- SetDefaults
- SetDefaults
- SetDefaults
- SetDefaults
- SetDefaults
- Read
- Read
- Read
- Read
- Read
- Read
- Read
- Read
- ReadBinaryHeader
- ReadDocument
- Load
- CanRead
- SetAsBinary
- ReadExtensionsRequired
- ReadExtensionsUsed
- OpenFile
Learn to use CMake with our Intro Training
Find out more