1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "gltf2_exporter.h"
17 
18 #include <charconv>
19 
20 #include <3d/ecs/components/animation_component.h>
21 #include <3d/ecs/components/animation_input_component.h>
22 #include <3d/ecs/components/animation_output_component.h>
23 #include <3d/ecs/components/animation_track_component.h>
24 #include <3d/ecs/components/camera_component.h>
25 #include <3d/ecs/components/light_component.h>
26 #include <3d/ecs/components/material_component.h>
27 #include <3d/ecs/components/mesh_component.h>
28 #include <3d/ecs/components/morph_component.h>
29 #include <3d/ecs/components/name_component.h>
30 #include <3d/ecs/components/node_component.h>
31 #include <3d/ecs/components/render_configuration_component.h>
32 #include <3d/ecs/components/render_handle_component.h>
33 #include <3d/ecs/components/render_mesh_component.h>
34 #include <3d/ecs/components/skin_component.h>
35 #include <3d/ecs/components/skin_ibm_component.h>
36 #include <3d/ecs/components/skin_joints_component.h>
37 #include <3d/ecs/components/transform_component.h>
38 #include <3d/ecs/components/uri_component.h>
39 #include <3d/ecs/systems/intf_animation_system.h>
40 #include <3d/ecs/systems/intf_morphing_system.h>
41 #include <3d/ecs/systems/intf_node_system.h>
42 #include <base/containers/fixed_string.h>
43 #include <base/containers/string.h>
44 #include <base/containers/unordered_map.h>
45 #include <base/containers/vector.h>
46 #include <base/math/matrix_util.h>
47 #include <base/math/quaternion_util.h>
48 #include <base/util/compile_time_hashes.h>
49 #include <core/ecs/intf_ecs.h>
50 #include <core/intf_engine.h>
51 #include <core/io/intf_file_manager.h>
52 #include <core/log.h>
53 #include <core/namespace.h>
54 #include <core/plugin/intf_class_register.h>
55 #include <core/property/intf_property_handle.h>
56 #include <render/device/intf_gpu_resource_manager.h>
57 #include <render/device/intf_shader_manager.h>
58 #include <render/implementation_uids.h>
59 #include <render/intf_render_context.h>
60 
61 #include "ecs/systems/animation_playback.h"
62 #include "gltf/data.h"
63 #include "gltf/gltf2_data_structures.h"
64 #include "gltf/gltf2_util.h"
65 #include "util/json_util.h"
66 
67 template<>
hash(const bool & val)68 uint64_t BASE_NS::hash(const bool& val)
69 {
70     return static_cast<uint64_t>(val);
71 }
72 
73 template<>
hash(const CORE3D_NS::GLTF2::ComponentType & val)74 uint64_t BASE_NS::hash(const CORE3D_NS::GLTF2::ComponentType& val)
75 {
76     return static_cast<uint64_t>(val);
77 }
78 
79 template<>
hash(const CORE3D_NS::GLTF2::DataType & val)80 uint64_t BASE_NS::hash(const CORE3D_NS::GLTF2::DataType& val)
81 {
82     return static_cast<uint64_t>(val);
83 }
84 
85 template<>
hash(const CORE3D_NS::GLTF2::BufferTarget & val)86 uint64_t BASE_NS::hash(const CORE3D_NS::GLTF2::BufferTarget& val)
87 {
88     return static_cast<uint64_t>(val);
89 }
90 
91 CORE3D_BEGIN_NAMESPACE()
92 using namespace BASE_NS;
93 using namespace CORE_NS;
94 using namespace RENDER_NS;
95 
96 // internal method from
97 GLTFLoadResult LoadGLTF(IFileManager& fileManager, const string_view uri);
98 
AppendUnique(json::value & jsonData,const string_view value)99 void AppendUnique(json::value& jsonData, const string_view value)
100 {
101     if (jsonData.is_array()) {
102         const auto cmpValue = [value](const json::value& it) { return it.string_ == value; };
103         if (std::find_if(jsonData.array_.begin(), jsonData.array_.end(), cmpValue) == jsonData.array_.end()) {
104             jsonData.array_.push_back(value);
105         }
106     }
107 }
108 
109 // Some GLTF object names are not exported at the moment. Placeholder code has been added behind
110 // EXPORT_OTHER_OBJECT_NAMES macro to easily spot the missing names and add support if the need arises.
GetFilterMode(Filter filter)111 GLTF2::FilterMode GetFilterMode(Filter filter)
112 {
113     if (filter == CORE_FILTER_NEAREST) {
114         return GLTF2::FilterMode::NEAREST;
115     } else if (filter == CORE_FILTER_LINEAR) {
116         return GLTF2::FilterMode::LINEAR;
117     }
118 
119     return GLTF2::FilterMode::NEAREST;
120 }
121 
GetFilterMode(Filter filter,Filter mipmapMode)122 GLTF2::FilterMode GetFilterMode(Filter filter, Filter mipmapMode)
123 {
124     /* NOTE: how to figure out should mips be used from SamplerDesc? */
125     if (filter == CORE_FILTER_NEAREST && mipmapMode == CORE_FILTER_NEAREST) {
126         return GLTF2::FilterMode::NEAREST_MIPMAP_NEAREST;
127     } else if (filter == CORE_FILTER_LINEAR && mipmapMode == CORE_FILTER_NEAREST) {
128         return GLTF2::FilterMode::LINEAR_MIPMAP_NEAREST;
129     } else if (filter == CORE_FILTER_NEAREST && mipmapMode == CORE_FILTER_LINEAR) {
130         return GLTF2::FilterMode::NEAREST_MIPMAP_LINEAR;
131     } else if (filter == CORE_FILTER_LINEAR && mipmapMode == CORE_FILTER_LINEAR) {
132         return GLTF2::FilterMode::LINEAR_MIPMAP_LINEAR;
133     }
134 
135     return GLTF2::FilterMode::NEAREST;
136 }
137 
GetWrapMode(SamplerAddressMode mode)138 GLTF2::WrapMode GetWrapMode(SamplerAddressMode mode)
139 {
140     switch (mode) {
141         default:
142         case CORE_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE:
143         case CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER:
144         case CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:
145             return GLTF2::WrapMode::CLAMP_TO_EDGE;
146 
147         case CORE_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT:
148             return GLTF2::WrapMode::MIRRORED_REPEAT;
149 
150         case CORE_SAMPLER_ADDRESS_MODE_REPEAT:
151             return GLTF2::WrapMode::REPEAT;
152     }
153 }
154 
GetAnimationPath(const AnimationTrackComponent & track)155 GLTF2::AnimationPath GetAnimationPath(const AnimationTrackComponent& track)
156 {
157     if (track.component == ITransformComponentManager::UID) {
158         if (track.property == "position") {
159             return GLTF2::AnimationPath::TRANSLATION;
160         } else if (track.property == "rotation") {
161             return GLTF2::AnimationPath::ROTATION;
162         } else if (track.property == "scale") {
163             return GLTF2::AnimationPath::SCALE;
164         }
165     } else if (track.component == IMorphComponentManager::UID) {
166         if (track.property == "morphWeights") {
167             return GLTF2::AnimationPath::WEIGHTS;
168         }
169     }
170     return GLTF2::AnimationPath::INVALID;
171 }
172 
GetAnimationInterpolation(AnimationTrackComponent::Interpolation interpolation)173 GLTF2::AnimationInterpolation GetAnimationInterpolation(AnimationTrackComponent::Interpolation interpolation)
174 {
175     switch (interpolation) {
176         case AnimationTrackComponent::Interpolation::STEP:
177             return GLTF2::AnimationInterpolation::STEP;
178         case AnimationTrackComponent::Interpolation::LINEAR:
179             return GLTF2::AnimationInterpolation::LINEAR;
180         case AnimationTrackComponent::Interpolation::SPLINE:
181             return GLTF2::AnimationInterpolation::SPLINE;
182         default:
183             return GLTF2::AnimationInterpolation::INVALID;
184     }
185 }
186 
187 namespace GLTF2 {
188 ExportResult::~ExportResult() = default;
189 
190 namespace {
191 constexpr auto const DEFAULT_BASECOLOR_FACTOR = Math::Vec4(1.f, 1.f, 1.f, 1.f);
192 constexpr auto const DEFAULT_DIFFUSE_FACTOR = Math::Vec4(1.f, 1.f, 1.f, 1.f);
193 constexpr auto const DEFAULT_SPECULAR_FACTOR = Math::Vec3(1.f, 1.f, 1.f);
194 constexpr auto const DEFAULT_EMISSIVE_FACTOR = Math::Vec3(0.f, 0.f, 0.f);
195 
196 constexpr auto const DEFAULT_TRANSLATION = Math::Vec3(0.f, 0.f, 0.f);
197 constexpr auto const DEFAULT_SCALE = Math::Vec3(1.f, 1.f, 1.f);
198 constexpr auto const DEFAULT_ROTATION = Math::Quat(0.f, 0.f, 0.f, 1.f);
199 
200 constexpr auto const IDENTITY_MATRIX =
201     Math::Mat4X4(1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f);
202 
203 /* Entity which have skin, camera, and/or light attached are stored here for further procesing. */
204 struct Entities {
205     vector<Entity> nodes;
206     vector<Entity> withSkin;
207     vector<Entity> withCamera;
208     vector<Entity> withLight;
209 };
210 
operator ==(const RenderHandleReference & lhs,const RenderHandleReference & rhs)211 inline bool operator==(const RenderHandleReference& lhs, const RenderHandleReference& rhs) noexcept
212 {
213     return lhs.GetHandle() == rhs.GetHandle();
214 }
215 
operator <(const RenderHandleReference & lhs,const RenderHandleReference & rhs)216 inline bool operator<(const RenderHandleReference& lhs, const RenderHandleReference& rhs) noexcept
217 {
218     return lhs.GetHandle().id < rhs.GetHandle().id;
219 }
220 
221 /* Returns the index where the given object is in the vector. */
222 template<typename T>
FindObjectIndex(vector<unique_ptr<T>> const & array,T const & object)223 int FindObjectIndex(vector<unique_ptr<T>> const& array, T const& object)
224 {
225     auto const comparePointers = [ptr = &object](auto const& aUniqueObject) { return aUniqueObject.get() == ptr; };
226     if (auto const pos = std::find_if(array.begin(), array.end(), comparePointers); pos != array.end()) {
227         return static_cast<int>(std::distance(array.begin(), pos));
228     }
229 
230     return -1;
231 }
232 
233 /* Returns the index where the given handle is in the vector. */
234 template<typename T>
FindHandleIndex(vector<T> const & handles,T handle)235 uint32_t FindHandleIndex(vector<T> const& handles, T handle)
236 {
237     if (auto const handlePos = std::find(handles.begin(), handles.end(), handle); handlePos != handles.end()) {
238         return static_cast<uint32_t>(std::distance(handles.begin(), handlePos));
239     }
240     return ~0u;
241 }
242 
243 /* Returns the index where the given handle is in the vector. If the handle was not in the vector it is added. */
244 template<typename T>
FindOrAddIndex(vector<T> & handles,const T & handle)245 uint32_t FindOrAddIndex(vector<T>& handles, const T& handle)
246 {
247     if (auto const handlePos =
248             std::find_if(handles.begin(), handles.end(), [handle](const auto& rhs) { return handle == rhs; });
249         handlePos != handles.end()) {
250         return static_cast<uint32_t>(std::distance(handles.begin(), handlePos));
251     } else {
252         handles.push_back(handle);
253         return static_cast<uint32_t>(handles.size() - 1);
254     }
255 }
256 
257 class BufferHelper {
258 public:
BufferHelper(Buffer & buffer,vector<unique_ptr<BufferView>> & usedBufferViews,vector<unique_ptr<Accessor>> & usedAccessors)259     BufferHelper(
260         Buffer& buffer, vector<unique_ptr<BufferView>>& usedBufferViews, vector<unique_ptr<Accessor>>& usedAccessors)
261         : usedBufferViews_(usedBufferViews), usedAccessors_(usedAccessors), buffer_(buffer) {};
262 
263     ~BufferHelper() = default;
264 
265     /* StoreBufferView hashes bufferView and stores it if needed. If dataAlignment is non-zero, data from aBufferView is
266      * written to the buffer. */
StoreBufferView(const BufferView & bufferView,uint32_t dataAlignment)267     BufferView* StoreBufferView(const BufferView& bufferView, uint32_t dataAlignment)
268     {
269         auto const bufferViewHash = BASE_NS::Hash(
270             bufferView.buffer, bufferView.byteLength, bufferView.byteOffset, bufferView.byteStride, bufferView.target);
271         auto const bufferViewIndex = FindOrAddIndex(bufferViewHashes_, bufferViewHash);
272         if ((bufferViewIndex + 1) > usedBufferViews_.size()) {
273             usedBufferViews_.resize(bufferViewIndex + 1);
274             usedBufferViews_[bufferViewIndex] = make_unique<BufferView>(bufferView);
275             usedBufferViews_[bufferViewIndex]->buffer = &buffer_;
276             if (dataAlignment) {
277                 auto const pad = buffer_.data.size() % dataAlignment;
278                 buffer_.data.insert(buffer_.data.end(), pad, 0);
279                 usedBufferViews_[bufferViewIndex]->byteOffset = buffer_.data.size();
280                 buffer_.data.insert(buffer_.data.end(), bufferView.data, bufferView.data + bufferView.byteLength);
281             }
282         }
283         return usedBufferViews_[bufferViewIndex].get();
284     }
285 
286     /* StoreAccessor first stores the bufferView used by accessor, then hashes it and stores if needed. */
StoreAccessor(const Accessor & accessor)287     Accessor* StoreAccessor(const Accessor& accessor)
288     {
289         BufferView* bufferView = nullptr;
290         if (accessor.bufferView) {
291             bufferView = StoreBufferView(*accessor.bufferView, GetComponentByteSize(accessor.componentType));
292         }
293         auto const accessorHash = BASE_NS::Hash(bufferView, accessor.componentType, accessor.count, accessor.type,
294             accessor.byteOffset, accessor.normalized, accessor.sparse.count, accessor.sparse.indices.bufferView,
295             accessor.sparse.indices.byteOffset, accessor.sparse.indices.componentType,
296             accessor.sparse.values.bufferView, accessor.sparse.values.byteOffset);
297 
298         auto const accessorIndex = FindOrAddIndex(accessorHashes_, accessorHash);
299         if ((accessorIndex + 1) > usedAccessors_.size()) {
300             usedAccessors_.resize(accessorIndex + 1);
301             usedAccessors_[accessorIndex] = make_unique<Accessor>(accessor);
302             usedAccessors_[accessorIndex]->bufferView = bufferView;
303         }
304         return usedAccessors_[accessorIndex].get();
305     };
306 
GetBuffer() const307     Buffer& GetBuffer() const
308     {
309         return buffer_;
310     }
311 
312 private:
313     vector<uint64_t> bufferViewHashes_;
314     vector<uint64_t> accessorHashes_;
315 
316     vector<unique_ptr<BufferView>>& usedBufferViews_;
317     vector<unique_ptr<Accessor>>& usedAccessors_;
318     Buffer& buffer_;
319 };
320 
321 struct ResourceEntity {
322     Entity entity;
323     RenderHandleReference handle;
324 };
325 
326 class TextureHelper {
327 public:
GetSamplerIndex(const RenderHandleReference & sampler)328     auto GetSamplerIndex(const RenderHandleReference& sampler)
329     {
330         return FindOrAddIndex(usedSamplers_, sampler);
331     }
332 
GetImageIndex(const RenderHandleReference & image)333     auto GetImageIndex(const RenderHandleReference& image)
334     {
335         return FindOrAddIndex(usedImages_, image);
336     }
337 
338     // Helper which returns the index of the "texture", i.e. sampler and image index pair.
GetTextureIndex(uint32_t samplerIndex,uint32_t imageIndex)339     auto GetTextureIndex(uint32_t samplerIndex, uint32_t imageIndex)
340     {
341         auto const textureHash = BASE_NS::Hash(samplerIndex, imageIndex);
342         auto const textureIndex = FindOrAddIndex(textureHashes_, textureHash);
343         if ((textureIndex + 1) >= usedTextures_.size()) {
344             usedTextures_.resize(textureIndex + 1);
345             usedTextures_[textureIndex] = { samplerIndex, imageIndex };
346         }
347         return textureIndex;
348     }
349 
HasSamplers() const350     bool HasSamplers() const
351     {
352         return !usedSamplers_.empty();
353     }
354 
HasImages() const355     bool HasImages() const
356     {
357         return !usedImages_.empty();
358     }
359 
HasTextures() const360     bool HasTextures() const
361     {
362         return !usedTextures_.empty();
363     }
364 
365     /* Generate GLTF2::Samplers from gathered sampler resource handlers. */
GenerateGltfSamplers(IGpuResourceManager const & gpuResourceManager) const366     vector<unique_ptr<Sampler>> GenerateGltfSamplers(IGpuResourceManager const& gpuResourceManager) const
367     {
368         vector<unique_ptr<Sampler>> samplerArray;
369         samplerArray.reserve(usedSamplers_.size());
370         for (const auto& gpuSamplerHandle : usedSamplers_) {
371             auto& exportSampler = samplerArray.emplace_back(make_unique<Sampler>());
372             auto const& samplerDesc =
373                 gpuResourceManager.GetSamplerDescriptor(gpuResourceManager.Get(gpuSamplerHandle.GetHandle()));
374             exportSampler->magFilter = GetFilterMode(samplerDesc.magFilter);
375             exportSampler->minFilter = GetFilterMode(samplerDesc.minFilter, samplerDesc.mipMapMode);
376             exportSampler->wrapS = GetWrapMode(samplerDesc.addressModeU);
377             exportSampler->wrapT = GetWrapMode(samplerDesc.addressModeV);
378         }
379         return samplerArray;
380     }
381 
382     /* Generate GLTF2::Images from gathered image resource handlers. */
GenerateGltfImages(const vector<ResourceEntity> & resourceEnties,const IUriComponentManager & uriManager) const383     vector<unique_ptr<Image>> GenerateGltfImages(
384         const vector<ResourceEntity>& resourceEnties, const IUriComponentManager& uriManager) const
385     {
386         vector<unique_ptr<Image>> imageArray;
387         imageArray.reserve(usedImages_.size());
388 
389         for (const auto& gpuImageHandle : usedImages_) {
390             auto& exportImage = imageArray.emplace_back(make_unique<Image>());
391             // sorted for find performance
392             if (auto const pos = std::lower_bound(resourceEnties.begin(), resourceEnties.end(), gpuImageHandle,
393                     [](ResourceEntity const& info, const RenderHandleReference& gpuImageHandle) {
394                         return info.handle < gpuImageHandle;
395                     });
396                 pos != resourceEnties.end() && pos->handle == gpuImageHandle) {
397                 if (auto id = uriManager.GetComponentId(pos->entity); id != IComponentManager::INVALID_COMPONENT_ID) {
398                     exportImage->uri = uriManager.Get(id).uri;
399                 }
400             }
401         }
402         return imageArray;
403     }
404 
405     /* Combines Sampler Image -pairs into GLTF2::Textures. */
GenerateGltfTextures(vector<unique_ptr<Sampler>> const & samplers,vector<unique_ptr<Image>> const & image) const406     vector<unique_ptr<Texture>> GenerateGltfTextures(
407         vector<unique_ptr<Sampler>> const& samplers, vector<unique_ptr<Image>> const& image) const
408     {
409         vector<unique_ptr<Texture>> textureArray;
410         textureArray.reserve(usedTextures_.size());
411         for (auto const& samplerImage : usedTextures_) {
412             auto& exportTexture = textureArray.emplace_back(make_unique<Texture>());
413             if (samplerImage.first < samplers.size()) {
414                 exportTexture->sampler = samplers[samplerImage.first].get();
415             }
416             exportTexture->image = image[samplerImage.second].get();
417         }
418         return textureArray;
419     }
420 
421 private:
422     vector<RenderHandleReference> usedSamplers_;
423     vector<RenderHandleReference> usedImages_;
424 
425     // Each "texture" is an index pair into usedSamplers and usedImages.
426     vector<std::pair<size_t, size_t>> usedTextures_;
427     vector<uint64_t> textureHashes_;
428 };
429 
430 /* Tries to load the glTF resource pointed by URI (<file URI/resource type/resource index>).
431  * If the file can be loaded data and index of the resource are returned. */
ResolveGltfAndResourceIndex(string_view uri,IFileManager & fileManager,unordered_map<string,IGLTFData::Ptr> & originalGltfs)432 std::pair<Data*, size_t> ResolveGltfAndResourceIndex(
433     string_view uri, IFileManager& fileManager, unordered_map<string, IGLTFData::Ptr>& originalGltfs)
434 {
435     auto const resolveGltfAndResourceIndex =
436         [](string_view originalUri, string_view extension, IFileManager& fileManager,
437             unordered_map<string, IGLTFData::Ptr>& originalGltfs) -> std::pair<Data*, size_t> {
438         const auto gltfPos = originalUri.find(extension);
439         if (gltfPos == string_view::npos) {
440             return { nullptr, GLTF_INVALID_INDEX };
441         }
442         auto uri = string_view(originalUri);
443         const auto resourceIndexString = uri.substr(uri.rfind('/') + 1);
444         uri.remove_suffix(uri.size() - gltfPos - extension.size());
445 
446         size_t resourceIndex = GLTF_INVALID_INDEX;
447         if (auto const result = std::from_chars(
448                 resourceIndexString.data(), resourceIndexString.data() + resourceIndexString.size(), resourceIndex);
449             result.ec != std::errc()) {
450             return { nullptr, GLTF_INVALID_INDEX };
451         }
452 
453         auto pos = originalGltfs.find(uri);
454         if (pos == originalGltfs.end()) {
455             auto loadResult = LoadGLTF(fileManager, uri);
456             if (!loadResult.success || !loadResult.data->LoadBuffers()) {
457                 return { nullptr, GLTF_INVALID_INDEX };
458             }
459             auto inserted = originalGltfs.insert({ uri, move(loadResult.data) });
460             pos = inserted.first;
461         }
462         if (pos != originalGltfs.end()) {
463             return { static_cast<Data*>(pos->second.get()), resourceIndex };
464         } else {
465             return { nullptr, GLTF_INVALID_INDEX };
466         }
467     };
468     if (auto result = resolveGltfAndResourceIndex(uri, ".gltf", fileManager, originalGltfs); result.first) {
469         return result;
470     }
471 
472     if (auto result = resolveGltfAndResourceIndex(uri, ".glb", fileManager, originalGltfs); result.first) {
473         return result;
474     }
475 
476     return { nullptr, GLTF_INVALID_INDEX };
477 }
478 
ExportGltfCameras(const IEcs & ecs,const Entities & entities,ExportResult & result)479 void ExportGltfCameras(const IEcs& ecs, const Entities& entities, ExportResult& result)
480 {
481     if (!entities.withCamera.empty() && entities.withCamera.size() == result.data->cameras.size()) {
482         if (auto const cameraManager = GetManager<ICameraComponentManager>(ecs); cameraManager) {
483             auto cameraIterator = result.data->cameras.begin();
484 
485             for (auto const cameraEntity : entities.withCamera) {
486                 auto& exportCamera = *cameraIterator++;
487 
488                 auto const cameraComponent = cameraManager->Get(cameraEntity);
489                 switch (cameraComponent.projection) {
490                     case CameraComponent::Projection::ORTHOGRAPHIC: {
491                         exportCamera->type = CameraType::ORTHOGRAPHIC;
492                         exportCamera->attributes.ortho.xmag = cameraComponent.xMag;
493                         exportCamera->attributes.ortho.ymag = cameraComponent.yMag;
494                         exportCamera->attributes.ortho.zfar = cameraComponent.zFar;
495                         exportCamera->attributes.ortho.znear = cameraComponent.zNear;
496                         break;
497                     }
498                     case CameraComponent::Projection::PERSPECTIVE: {
499                         exportCamera->type = CameraType::PERSPECTIVE;
500                         exportCamera->attributes.perspective.aspect = cameraComponent.aspect;
501                         exportCamera->attributes.perspective.yfov = cameraComponent.yFov;
502                         exportCamera->attributes.perspective.zfar = cameraComponent.zFar;
503                         exportCamera->attributes.perspective.znear = cameraComponent.zNear;
504                         break;
505                     }
506                     case CameraComponent::Projection::CUSTOM:
507                     default: {
508                         exportCamera->type = CameraType::INVALID;
509 
510                         CORE_LOG_E("cannot export camera %u", static_cast<uint32_t>(cameraComponent.projection));
511 
512                         result.error += "failed to export camera";
513                         result.success = false;
514                         break;
515                     }
516                 }
517             }
518         }
519     }
520 }
521 
ExportGltfLight(const IEcs & ecs,const Entities & entities,ExportResult & result)522 void ExportGltfLight(const IEcs& ecs, const Entities& entities, ExportResult& result)
523 {
524 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
525 
526     if (!entities.withLight.empty() && entities.withLight.size() == result.data->lights.size()) {
527         if (auto const lightManager = GetManager<ILightComponentManager>(ecs); lightManager) {
528             auto lightIterator = result.data->lights.begin();
529 
530             for (auto const lightEntity : entities.withLight) {
531                 auto& exportLight = *lightIterator++;
532 
533                 auto const lightComponent = lightManager->Get(lightEntity);
534                 switch (lightComponent.type) {
535                     default:
536                         CORE_LOG_E("cannot export light %u", static_cast<uint32_t>(lightComponent.type));
537                         exportLight->type = LightType::DIRECTIONAL;
538 
539                         result.error += "failed to export light";
540                         result.success = false;
541                         break;
542                     case LightComponent::Type::DIRECTIONAL:
543                         exportLight->type = LightType::DIRECTIONAL;
544                         break;
545                     case LightComponent::Type::POINT:
546                         exportLight->type = LightType::POINT;
547                         break;
548                     case LightComponent::Type::SPOT:
549                         exportLight->type = LightType::SPOT;
550                         break;
551                 }
552                 exportLight->color = lightComponent.color;
553                 exportLight->intensity = lightComponent.intensity;
554                 exportLight->positional.range = lightComponent.range;
555 
556                 exportLight->positional.spot.innerAngle = lightComponent.spotInnerAngle;
557                 exportLight->positional.spot.outerAngle = lightComponent.spotOuterAngle;
558 
559                 exportLight->shadow.shadowCaster = lightComponent.shadowEnabled;
560             }
561         }
562     }
563 #endif
564 }
565 
StoreInverseBindMatrices(array_view<const Math::Mat4X4> ibls,BufferHelper & bufferHelper)566 Accessor* StoreInverseBindMatrices(array_view<const Math::Mat4X4> ibls, BufferHelper& bufferHelper)
567 {
568     vector<Math::Mat4X4> inverseBindMatrices(ibls.cbegin(), ibls.cend());
569     if (!inverseBindMatrices.empty()) {
570         auto matrixData = array_view(
571             reinterpret_cast<uint8_t*>(inverseBindMatrices.data()), inverseBindMatrices.size() * sizeof(Math::Mat4X4));
572         BufferView bufferView;
573         bufferView.buffer = &bufferHelper.GetBuffer();
574         bufferView.byteLength = matrixData.size();
575         bufferView.data = matrixData.data();
576 
577         Accessor accessor;
578         accessor.bufferView = &bufferView;
579         accessor.byteOffset = 0;
580         accessor.componentType = ComponentType::FLOAT;
581         accessor.count = static_cast<uint32_t>(inverseBindMatrices.size());
582         accessor.type = DataType::MAT4;
583         return bufferHelper.StoreAccessor(accessor);
584     }
585     return nullptr;
586 }
587 
588 // helper for evaluating skeleton property for a skin
589 struct NodeDepth {
590     uint32_t depth;
591     GLTF2::Node* node;
592 
operator >GLTF2::__anon9a5f352c0210::NodeDepth593     inline bool operator>(const NodeDepth& rhs) const noexcept
594     {
595         if (depth > rhs.depth) {
596             return true;
597         }
598         if (depth < rhs.depth) {
599             return false;
600         } else if (node < rhs.node) {
601             return true;
602         }
603         return false;
604     }
605 
operator ==GLTF2::__anon9a5f352c0210::NodeDepth606     inline bool operator==(const NodeDepth& rhs) const noexcept
607     {
608         return (depth == rhs.depth) && (node == rhs.node);
609     }
610 };
611 
FindSkeletonRoot(array_view<GLTF2::Node * > joints)612 Node* FindSkeletonRoot(array_view<GLTF2::Node*> joints)
613 {
614     // find the skeleton root node
615     if (!joints.empty()) {
616         // for each joint calculate distance to the root
617         vector<NodeDepth> depths;
618         depths.reserve(joints.size());
619         for (Node* joint : joints) {
620             uint32_t depth = 0;
621             for (Node* parent = joint->parent; parent; parent = parent->parent) {
622                 ++depth;
623             }
624             depths.push_back({ depth, joint });
625         }
626 
627         // sort by depth and node
628         std::sort(depths.begin(), depths.end(), std::greater<NodeDepth>());
629 
630         // reduce the numer of nodes until one remains (or there are multiple roots)
631         for (auto start = depths.begin(); depths.size() > 1 && start->depth; start = depths.begin()) {
632             // select a range of nodes at an equal depth
633             auto end = std::upper_bound(start, depths.end(), start->depth,
634                 [](uint32_t depth, const NodeDepth& current) { return current.depth < depth; });
635             // replace each node with its parent
636             for (auto& data : array_view(start.ptr(), end.ptr())) {
637                 data.node = data.node->parent;
638                 data.depth -= 1;
639             }
640             // sort again according to updated depths and nodes
641             std::sort(depths.begin(), depths.end(), std::greater<NodeDepth>());
642 
643             // remove duplicates
644             depths.erase(std::unique(start, depths.end()), depths.end());
645         }
646 
647         return (depths.size() == 1) ? depths.front().node : nullptr;
648     }
649     return nullptr;
650 }
651 
ExportGltfSkins(const IEcs & ecs,const Entities & entities,const vector<unique_ptr<Node>> & nodeArray,ExportResult & result,BufferHelper & bufferHelper)652 void ExportGltfSkins(const IEcs& ecs, const Entities& entities, const vector<unique_ptr<Node>>& nodeArray,
653     ExportResult& result, BufferHelper& bufferHelper)
654 {
655     if (!entities.withSkin.empty() && entities.withSkin.size() == result.data->skins.size()) {
656         if (auto const skinManager = GetManager<ISkinComponentManager>(ecs); skinManager) {
657             auto const skinIbmManager = GetManager<ISkinIbmComponentManager>(ecs);
658             auto const skinJointsManager = GetManager<ISkinJointsComponentManager>(ecs);
659 
660             auto skinIterator = result.data->skins.begin();
661 
662             for (auto const skinnedEntity : entities.withSkin) {
663                 auto& exportSkin = *skinIterator++;
664                 auto const skinComponent = skinManager->Get(skinnedEntity);
665                 if (EntityUtil::IsValid(skinComponent.skin)) {
666                     // store IBMs in the buffer handled by BufferHelper
667                     if (const auto ibmHandle = skinIbmManager->Read(skinComponent.skin); ibmHandle) {
668                         exportSkin->inverseBindMatrices = StoreInverseBindMatrices(ibmHandle->matrices, bufferHelper);
669                     }
670 
671                     // gather all the joint nodes
672                     if (auto const skinJointsHandle = skinJointsManager->Read(skinnedEntity); skinJointsHandle) {
673                         exportSkin->joints.reserve(skinJointsHandle->count);
674                         for (auto jointEntity : array_view(skinJointsHandle->jointEntities, skinJointsHandle->count)) {
675                             if (auto const jointIndex = FindHandleIndex(entities.nodes, jointEntity);
676                                 jointIndex < nodeArray.size()) {
677                                 exportSkin->joints.push_back(nodeArray[jointIndex].get());
678                             } else {
679                                 CORE_LOG_D("joint node not exported");
680                             }
681                         }
682                     }
683 
684                     // find the skeleton root node
685                     exportSkin->skeleton = FindSkeletonRoot(exportSkin->joints);
686                     if (!exportSkin->skeleton) {
687                         CORE_LOG_D("Couldn't find common root for skinned entity %s", to_hex(skinnedEntity.id).data());
688                     }
689                 }
690             }
691         }
692     }
693 }
694 
GetAnimationTarget(const INodeSystem & nodeSystem,const INameComponentManager & nameManager,const Entities & entities,array_view<const unique_ptr<Node>> nodes,const Entity trackEntity,const AnimationTrackComponent & trackComponent)695 Node* GetAnimationTarget(const INodeSystem& nodeSystem, const INameComponentManager& nameManager,
696     const Entities& entities, array_view<const unique_ptr<Node>> nodes, const Entity trackEntity,
697     const AnimationTrackComponent& trackComponent)
698 {
699     Node* target = nullptr;
700     if (auto animatedNode = nodeSystem.GetNode(trackComponent.target); animatedNode) {
701         if (auto const nodeIndex = FindHandleIndex(entities.nodes, static_cast<Entity>(trackComponent.target));
702             nodeIndex < nodes.size()) {
703             target = nodes[nodeIndex].get();
704         }
705     }
706     if (!target) {
707         string_view nodePath;
708         if (const auto nameHandle = nameManager.Read(trackEntity); nameHandle) {
709             nodePath = nameHandle->name;
710         }
711         CORE_LOG_W("couldn't resolve node path: %s", nodePath.data());
712     }
713     return target;
714 }
715 
AnimationInput(const IAnimationInputComponentManager & inputManager,const Entity & animationInput,BufferHelper & bufferHelper)716 Accessor* AnimationInput(
717     const IAnimationInputComponentManager& inputManager, const Entity& animationInput, BufferHelper& bufferHelper)
718 {
719     if (auto inputHandle = inputManager.Read(animationInput); inputHandle) {
720         // BufferView data is not const although at this point it's not modified.
721         auto inputData = array_view(reinterpret_cast<uint8_t*>(const_cast<float*>(inputHandle->timestamps.data())),
722             inputHandle->timestamps.size_in_bytes());
723 
724         BufferView bufferView;
725         bufferView.buffer = reinterpret_cast<Buffer*>(static_cast<uintptr_t>(animationInput.id));
726         bufferView.byteLength = inputData.size();
727         bufferView.data = inputData.data();
728 
729         Accessor accessor;
730         accessor.bufferView = &bufferView;
731         accessor.byteOffset = 0;
732         accessor.componentType = ComponentType::FLOAT;
733         accessor.count = static_cast<uint32_t>(inputHandle->timestamps.size());
734         accessor.type = DataType::SCALAR;
735 
736         auto inputAccessor = bufferHelper.StoreAccessor(accessor);
737         // The accessor used for animation.sampler.input requires min and max values.
738         if (inputAccessor && (inputAccessor->min.empty() || inputAccessor->max.empty())) {
739             auto input =
740                 array_view(reinterpret_cast<float const*>(inputAccessor->bufferView->data + inputAccessor->byteOffset),
741                     inputAccessor->count);
742             auto inputMin = std::numeric_limits<float>::max();
743             auto inputMax = std::numeric_limits<float>::lowest();
744             for (auto const value : input) {
745                 inputMin = std::min(value, inputMin);
746                 inputMax = std::max(value, inputMax);
747             }
748             inputAccessor->min.push_back(inputMin);
749             inputAccessor->max.push_back(inputMax);
750         }
751 
752         return inputAccessor;
753     }
754     return {};
755 }
756 
AnimationOutput(const IAnimationOutputComponentManager & outputManager,const Entity & animationOutput,AnimationPath type,BufferHelper & bufferHelper)757 Accessor* AnimationOutput(const IAnimationOutputComponentManager& outputManager, const Entity& animationOutput,
758     AnimationPath type, BufferHelper& bufferHelper)
759 {
760     Accessor accessor;
761     // Setup the accessor to match the keyframe data for current animation type. Translation and scale are vec3s,
762     // rotation is quaternions, and morph animation is floats.
763     auto outputData = array_view<const uint8_t>();
764     if (auto outputHandle = outputManager.Read(animationOutput); outputHandle) {
765         outputData = outputHandle->data;
766         switch (type) {
767             case AnimationPath::TRANSLATION:
768             case AnimationPath::SCALE: {
769                 accessor.count = static_cast<uint32_t>(outputData.size() / sizeof(Math::Vec3));
770                 accessor.type = DataType::VEC3;
771             } break;
772             case AnimationPath::ROTATION: {
773                 accessor.count = static_cast<uint32_t>(outputData.size() / sizeof(Math::Vec4));
774                 accessor.type = DataType::VEC4;
775             } break;
776             case AnimationPath::WEIGHTS: {
777                 accessor.count = static_cast<uint32_t>(outputData.size() / sizeof(float));
778                 accessor.type = DataType::SCALAR;
779             } break;
780             default:
781                 return nullptr;
782         }
783     }
784     BufferView bufferView;
785     bufferView.buffer = reinterpret_cast<Buffer*>(static_cast<uintptr_t>(animationOutput.id));
786     bufferView.byteLength = outputData.size();
787     bufferView.data = outputData.data();
788 
789     accessor.bufferView = &bufferView;
790     accessor.byteOffset = 0;
791     accessor.componentType = ComponentType::FLOAT;
792 
793     return bufferHelper.StoreAccessor(accessor);
794 }
795 
CreateAnimationSampler(const AnimationTrackComponent & trackComponent,const IAnimationInputComponentManager & animationInputManager,const IAnimationOutputComponentManager & animationOutputManager,BufferHelper & bufferHelper)796 unique_ptr<AnimationSampler> CreateAnimationSampler(const AnimationTrackComponent& trackComponent,
797     const IAnimationInputComponentManager& animationInputManager,
798     const IAnimationOutputComponentManager& animationOutputManager, BufferHelper& bufferHelper)
799 {
800     auto exportSampler = make_unique<AnimationSampler>();
801     exportSampler->interpolation = GetAnimationInterpolation(trackComponent.interpolationMode);
802     exportSampler->input = AnimationInput(animationInputManager, trackComponent.timestamps, bufferHelper);
803     exportSampler->output =
804         AnimationOutput(animationOutputManager, trackComponent.data, GetAnimationPath(trackComponent), bufferHelper);
805     return exportSampler;
806 }
807 
CleanupAnimation(Animation & exportAnimation)808 void CleanupAnimation(Animation& exportAnimation)
809 {
810     // Remove all tracks that don't have a node, sampler or sampler is missing input or output.
811     exportAnimation.tracks.erase(std::find_if(exportAnimation.tracks.begin(), exportAnimation.tracks.end(),
812                                     [](const AnimationTrack& track) {
813                                         return !track.channel.node || !track.sampler || !track.sampler->input ||
814                                                !track.sampler->output;
815                                     }),
816         exportAnimation.tracks.end());
817 
818     // Remove all samplers missing input or output.
819     exportAnimation.samplers.erase(
820         std::find_if(exportAnimation.samplers.begin(), exportAnimation.samplers.end(),
821             [](const unique_ptr<AnimationSampler>& sampler) { return !sampler->input || !sampler->output; }),
822         exportAnimation.samplers.end());
823 }
824 
Hash(const AnimationTrackComponent & trackComponent)825 uint64_t Hash(const AnimationTrackComponent& trackComponent)
826 {
827     return BASE_NS::Hash(static_cast<const Entity&>(trackComponent.timestamps).id,
828         static_cast<const Entity&>(trackComponent.data).id, static_cast<uint32_t>(trackComponent.interpolationMode));
829 }
830 
ExportGltfAnimations(const IEcs & ecs,const Entities & entities,ExportResult & result,BufferHelper & bufferHelper)831 void ExportGltfAnimations(const IEcs& ecs, const Entities& entities, ExportResult& result, BufferHelper& bufferHelper)
832 {
833     auto const nodeSystem = GetSystem<INodeSystem>(ecs);
834     auto const animationSystem = GetSystem<IAnimationSystem>(ecs);
835     auto const animationManager = GetManager<IAnimationComponentManager>(ecs);
836     auto const animationInputManager = GetManager<IAnimationInputComponentManager>(ecs);
837     auto const animationOutputManager = GetManager<IAnimationOutputComponentManager>(ecs);
838     auto const animationTrackManager = GetManager<IAnimationTrackComponentManager>(ecs);
839     auto const nameManager = GetManager<INameComponentManager>(ecs);
840     if (nodeSystem && animationSystem && animationManager && animationInputManager && animationOutputManager &&
841         animationTrackManager && nameManager) {
842         auto const animationCount = animationManager->GetComponentCount();
843         auto& animationArray = result.data->animations;
844         animationArray.reserve(animationCount);
845 
846         for (IComponentManager::ComponentId i = 0U; i < animationCount; ++i) {
847             auto& exportAnimation = animationArray.emplace_back(make_unique<Animation>());
848             if (auto nameHandle = nameManager->Read(animationManager->GetEntity(i)); nameHandle) {
849                 exportAnimation->name = nameHandle->name;
850             }
851 
852             // animation.samplers can be shared between channels. Identify samplers by hashing input, output and
853             // interpolationMode.
854             vector<uint64_t> samplerHashes;
855 
856             if (const auto animationHandle = animationManager->Read(i); animationHandle) {
857                 for (auto const& trackEntity : animationHandle->tracks) {
858                     if (const auto trackHandle = animationTrackManager->Read(trackEntity); trackHandle) {
859                         auto const samplerIndex = FindOrAddIndex(samplerHashes, Hash(*trackHandle));
860                         if ((samplerIndex + 1) >= exportAnimation->samplers.size()) {
861                             exportAnimation->samplers.resize(samplerIndex + 1);
862                             exportAnimation->samplers[samplerIndex] = CreateAnimationSampler(
863                                 *trackHandle, *animationInputManager, *animationOutputManager, bufferHelper);
864                         }
865                         const auto target = GetAnimationTarget(
866                             *nodeSystem, *nameManager, entities, result.data->nodes, trackEntity, *trackHandle);
867                         exportAnimation->tracks.push_back(AnimationTrack { { target, GetAnimationPath(*trackHandle) },
868                             exportAnimation->samplers[samplerIndex].get() });
869                     }
870                 }
871             }
872 
873             CleanupAnimation(*exportAnimation);
874 
875             // Pop the animation if it's empty.
876             if (exportAnimation->samplers.empty() || exportAnimation->tracks.empty()) {
877                 animationArray.pop_back();
878             }
879         }
880     }
881 }
882 
883 struct MeshPrimitiveGenerator {
884     const IMaterialComponentManager& materialManager;
885     BufferHelper& buffer;
886     vector<Entity>& usedMaterials;
887 
operator ()GLTF2::__anon9a5f352c0210::MeshPrimitiveGenerator888     MeshPrimitive operator()(const MeshComponent::Submesh& submesh, const MeshPrimitive& original)
889     {
890         MeshPrimitive copy;
891         if (original.indices) {
892             copy.indices = buffer.StoreAccessor(*original.indices);
893         }
894         for (auto const& attrib : original.attributes) {
895             copy.attributes.push_back(Attribute { attrib.attribute, buffer.StoreAccessor(*attrib.accessor) });
896         }
897         if (materialManager.HasComponent(submesh.material)) {
898             copy.materialIndex = FindOrAddIndex(usedMaterials, submesh.material);
899         }
900         for (auto const& target : original.targets) {
901             MorphTarget morphTarget { target.name, {} };
902             for (auto const& attribute : target.target) {
903                 morphTarget.target.push_back(
904                     Attribute { attribute.attribute, buffer.StoreAccessor(*attribute.accessor) });
905             }
906             copy.targets.push_back(move(morphTarget));
907         }
908 
909         return copy;
910     }
911 };
912 
ExportGltfMeshes(const IMeshComponentManager & meshManager,const INameComponentManager & nameManager,const IUriComponentManager & uriManager,const IMaterialComponentManager & materialManager,IFileManager & fileManager,const vector<Entity> & usedMeshes,ExportResult & result,BufferHelper & buffer,unordered_map<string,IGLTFData::Ptr> & originalGltfs)913 vector<Entity> ExportGltfMeshes(const IMeshComponentManager& meshManager, const INameComponentManager& nameManager,
914     const IUriComponentManager& uriManager, const IMaterialComponentManager& materialManager, IFileManager& fileManager,
915     const vector<Entity>& usedMeshes, ExportResult& result, BufferHelper& buffer,
916     unordered_map<string, IGLTFData::Ptr>& originalGltfs)
917 {
918     vector<Entity> usedMaterials;
919     CORE_ASSERT(usedMeshes.size() == result.data->meshes.size());
920     if (!usedMeshes.empty() && usedMeshes.size() == result.data->meshes.size()) {
921         auto meshIterator = result.data->meshes.begin();
922 
923         // Create GLTF2::Meshes from mesh entities
924         for (auto const meshEntity : usedMeshes) {
925             auto& exportMesh = *meshIterator++;
926 
927             if (auto const uriId = uriManager.GetComponentId(meshEntity);
928                 uriId != IComponentManager::INVALID_COMPONENT_ID) {
929                 auto const uc = uriManager.Get(uriId);
930                 // mesh data is copied from the original glTF pointer by UriComponent
931                 if (const auto [originalGltf, meshIndex] =
932                         ResolveGltfAndResourceIndex(uc.uri, fileManager, originalGltfs);
933                     originalGltf && meshIndex < originalGltf->meshes.size()) {
934                     auto const meshData = meshManager.GetData(meshEntity);
935                     auto const& mesh = *static_cast<const MeshComponent*>(meshData->RLock());
936 
937                     auto const& submeshes = mesh.submeshes;
938                     auto const& originalPrimitives = originalGltf->meshes[meshIndex]->primitives;
939                     std::transform(submeshes.begin(), submeshes.end(), originalPrimitives.begin(),
940                         std::back_inserter(exportMesh->primitives),
941                         MeshPrimitiveGenerator { materialManager, buffer, usedMaterials });
942 
943                     if (const auto nameId = nameManager.GetComponentId(meshEntity);
944                         nameId != IComponentManager::INVALID_COMPONENT_ID) {
945                         exportMesh->name = nameManager.Get(nameId).name;
946                     }
947                     // NOTE: exportMesh->weights
948                 } else {
949                     // couldn't find original glTF.
950                     for (const auto& node : result.data->nodes) {
951                         if (node->mesh == exportMesh.get()) {
952                             node->mesh = nullptr;
953                         }
954                     }
955                     exportMesh.reset();
956                 }
957             }
958         }
959         result.data->meshes.erase(
960             std::remove(result.data->meshes.begin(), result.data->meshes.end(), nullptr), result.data->meshes.end());
961     }
962     return usedMaterials;
963 }
964 
GetTextureIndex(const MaterialComponent & materialDesc,const MaterialComponent::TextureIndex textureIndex,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)965 inline uint32_t GetTextureIndex(const MaterialComponent& materialDesc,
966     const MaterialComponent::TextureIndex textureIndex, TextureHelper& textureHelper,
967     const IRenderHandleComponentManager& gpuHandleManager)
968 {
969     RenderHandleReference image;
970     if (auto handle = gpuHandleManager.Read(materialDesc.textures[textureIndex].image); handle) {
971         image = handle->reference;
972     }
973     if (image) {
974         auto const imageIndex = textureHelper.GetImageIndex(image);
975         RenderHandleReference sampler;
976         if (auto handle = gpuHandleManager.Read(materialDesc.textures[textureIndex].sampler); handle) {
977             sampler = handle->reference;
978         }
979         auto const samplerIndex = (sampler) ? textureHelper.GetSamplerIndex(sampler) : 0xFFFFffff;
980         return textureHelper.GetTextureIndex(samplerIndex, imageIndex);
981     }
982     return GLTF_INVALID_INDEX;
983 }
984 
ExportGltfMaterialMetallicRoughness(Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)985 void ExportGltfMaterialMetallicRoughness(Material& exportMaterial, const MaterialComponent& materialDesc,
986     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
987 {
988     exportMaterial.type = Material::Type::MetallicRoughness;
989     exportMaterial.metallicRoughness.baseColorFactor =
990         materialDesc.textures[MaterialComponent::TextureIndex::BASE_COLOR].factor;
991     exportMaterial.metallicRoughness.baseColorTexture.index =
992         GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::BASE_COLOR, textureHelper, gpuHandleManager);
993     exportMaterial.metallicRoughness.metallicFactor =
994         materialDesc.textures[MaterialComponent::TextureIndex::MATERIAL].factor.z;
995     exportMaterial.metallicRoughness.roughnessFactor =
996         materialDesc.textures[MaterialComponent::TextureIndex::MATERIAL].factor.y;
997     exportMaterial.metallicRoughness.metallicRoughnessTexture.index =
998         GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::MATERIAL, textureHelper, gpuHandleManager);
999 }
1000 
1001 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_CLEARCOAT) || defined(GLTF2_EXTRAS_CLEAR_COAT_MATERIAL)
ExportGltfMaterialClearcoat(Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)1002 void ExportGltfMaterialClearcoat(Material& exportMaterial, const MaterialComponent& materialDesc,
1003     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
1004 {
1005     // Clearcoat (must not be used with pbrSpecularGlossiness or unlit).
1006     if (materialDesc.textures[MaterialComponent::TextureIndex::CLEARCOAT].factor.x > 0.0f) {
1007         exportMaterial.clearcoat.factor = materialDesc.textures[MaterialComponent::TextureIndex::CLEARCOAT].factor.x;
1008         exportMaterial.clearcoat.roughness =
1009             materialDesc.textures[MaterialComponent::TextureIndex::CLEARCOAT_ROUGHNESS].factor.y;
1010         exportMaterial.clearcoat.texture.index =
1011             GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::CLEARCOAT, textureHelper, gpuHandleManager);
1012         exportMaterial.clearcoat.roughnessTexture.index = GetTextureIndex(
1013             materialDesc, MaterialComponent::TextureIndex::CLEARCOAT_ROUGHNESS, textureHelper, gpuHandleManager);
1014         exportMaterial.clearcoat.normalTexture.textureInfo.index = GetTextureIndex(
1015             materialDesc, MaterialComponent::TextureIndex::CLEARCOAT_NORMAL, textureHelper, gpuHandleManager);
1016         exportMaterial.clearcoat.normalTexture.scale =
1017             materialDesc.textures[MaterialComponent::TextureIndex::CLEARCOAT_NORMAL].factor.x;
1018     }
1019 }
1020 #endif
1021 
1022 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_IOR)
ExportGltfMaterialIor(Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)1023 void ExportGltfMaterialIor(Material& exportMaterial, const MaterialComponent& materialDesc,
1024     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
1025 {
1026     // IOR (must not be used with pbrSpecularGlossiness or unlit).
1027     if (materialDesc.textures[MaterialComponent::TextureIndex::MATERIAL].factor.w != 0.04f) {
1028         const auto refSqr = Math::sqrt(materialDesc.textures[MaterialComponent::TextureIndex::MATERIAL].factor.w);
1029         exportMaterial.ior.ior = (1.f + refSqr) / (1.f - refSqr);
1030     }
1031 }
1032 #endif
1033 
1034 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SHEEN)
ExportGltfMaterialSheen(Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)1035 void ExportGltfMaterialSheen(Material& exportMaterial, const MaterialComponent& materialDesc,
1036     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
1037 {
1038     // Sheen (must not be used with pbrSpecularGlossiness or unlit).
1039     if ((materialDesc.textures[MaterialComponent::TextureIndex::SHEEN].factor.x > 0.0f) ||
1040         (materialDesc.textures[MaterialComponent::TextureIndex::SHEEN].factor.y > 0.0f) ||
1041         (materialDesc.textures[MaterialComponent::TextureIndex::SHEEN].factor.z > 0.0f)) {
1042         exportMaterial.sheen.factor = materialDesc.textures[MaterialComponent::TextureIndex::SHEEN].factor;
1043         exportMaterial.sheen.texture.index =
1044             GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::SHEEN, textureHelper, gpuHandleManager);
1045     }
1046 }
1047 #endif
1048 
1049 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SPECULAR)
ExportGltfMaterialSpecular(Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)1050 void ExportGltfMaterialSpecular(Material& exportMaterial, const MaterialComponent& materialDesc,
1051     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
1052 {
1053     // Specular (must not be used with pbrSpecularGlossiness or unlit).
1054     if (materialDesc.textures[MaterialComponent::TextureIndex::SPECULAR].factor != Math::Vec4(1.f, 1.f, 1.f, 1.f)) {
1055         exportMaterial.specular.factor = materialDesc.textures[MaterialComponent::TextureIndex::SPECULAR].factor.w;
1056         exportMaterial.specular.texture.index =
1057             GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::SPECULAR, textureHelper, gpuHandleManager);
1058         exportMaterial.specular.color = materialDesc.textures[MaterialComponent::TextureIndex::SPECULAR].factor;
1059         exportMaterial.specular.colorTexture.index =
1060             GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::SPECULAR, textureHelper, gpuHandleManager);
1061     }
1062 }
1063 #endif
1064 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_TRANSMISSION)
ExportGltfMaterialTransmission(Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)1065 void ExportGltfMaterialTransmission(Material& exportMaterial, const MaterialComponent& materialDesc,
1066     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
1067 {
1068     // Transmission (must not be used with pbrSpecularGlossiness or unlit).
1069     if (materialDesc.textures[MaterialComponent::TextureIndex::TRANSMISSION].factor.x > 0.0f) {
1070         exportMaterial.transmission.factor =
1071             materialDesc.textures[MaterialComponent::TextureIndex::TRANSMISSION].factor.x;
1072         exportMaterial.transmission.texture.index = GetTextureIndex(
1073             materialDesc, MaterialComponent::TextureIndex::TRANSMISSION, textureHelper, gpuHandleManager);
1074     }
1075 }
1076 #endif
1077 
1078 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_PBRSPECULARGLOSSINESS)
ExportGltfMaterialSpecularGlossiness(Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)1079 void ExportGltfMaterialSpecularGlossiness(Material& exportMaterial, const MaterialComponent& materialDesc,
1080     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
1081 {
1082     exportMaterial.type = Material::Type::SpecularGlossiness;
1083     exportMaterial.specularGlossiness.diffuseFactor =
1084         materialDesc.textures[MaterialComponent::TextureIndex::BASE_COLOR].factor;
1085     exportMaterial.specularGlossiness.diffuseTexture.index =
1086         GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::BASE_COLOR, textureHelper, gpuHandleManager);
1087     exportMaterial.specularGlossiness.specularFactor =
1088         materialDesc.textures[MaterialComponent::TextureIndex::MATERIAL].factor;
1089     exportMaterial.specularGlossiness.specularGlossinessTexture.index =
1090         GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::MATERIAL, textureHelper, gpuHandleManager);
1091     exportMaterial.specularGlossiness.glossinessFactor =
1092         materialDesc.textures[MaterialComponent::TextureIndex::MATERIAL].factor.w;
1093 }
1094 #endif
1095 
1096 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_UNLIT)
ExportGltfMaterialUnlit(Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)1097 void ExportGltfMaterialUnlit(Material& exportMaterial, const MaterialComponent& materialDesc,
1098     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
1099 {
1100     exportMaterial.type = Material::Type::Unlit;
1101     exportMaterial.metallicRoughness.baseColorFactor =
1102         materialDesc.textures[MaterialComponent::TextureIndex::BASE_COLOR].factor;
1103     exportMaterial.metallicRoughness.baseColorTexture.index =
1104         GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::BASE_COLOR, textureHelper, gpuHandleManager);
1105 }
1106 #endif
1107 
UpdateShaderStateToGltfMaterial(const IDevice * device,Material & exportMaterial,const MaterialComponent & materialDesc,const IRenderHandleComponentManager & gpuHandleManager)1108 void UpdateShaderStateToGltfMaterial(const IDevice* device, Material& exportMaterial,
1109     const MaterialComponent& materialDesc, const IRenderHandleComponentManager& gpuHandleManager)
1110 {
1111     if ((materialDesc.materialShader.shader || materialDesc.materialShader.graphicsState) && device) {
1112         const IShaderManager& shaderMgr = device->GetShaderManager();
1113         if (materialDesc.materialShader.graphicsState) {
1114             const auto handle = gpuHandleManager.GetRenderHandleReference(materialDesc.materialShader.graphicsState);
1115             if (handle.GetHandleType() == RenderHandleType::GRAPHICS_STATE && handle) {
1116                 const GraphicsState gfxState = shaderMgr.GetGraphicsState(handle);
1117                 if (gfxState.rasterizationState.cullModeFlags == CullModeFlagBits::CORE_CULL_MODE_NONE) {
1118                     exportMaterial.doubleSided = true;
1119                 }
1120                 if (gfxState.colorBlendState.colorAttachmentCount > 0) {
1121                     if (gfxState.colorBlendState.colorAttachments[0].enableBlend) {
1122                         exportMaterial.alphaMode = AlphaMode::BLEND;
1123                     }
1124                 }
1125             }
1126         } else if (materialDesc.materialShader.shader) {
1127             const auto handle = gpuHandleManager.GetRenderHandleReference(materialDesc.materialShader.shader);
1128             if (handle.GetHandleType() == RenderHandleType::SHADER_STATE_OBJECT && handle) {
1129                 const RenderHandleReference gfxHandle = shaderMgr.GetGraphicsStateHandleByShaderHandle(handle);
1130                 const GraphicsState gfxState = shaderMgr.GetGraphicsState(gfxHandle);
1131                 if (gfxState.rasterizationState.cullModeFlags == CullModeFlagBits::CORE_CULL_MODE_NONE) {
1132                     exportMaterial.doubleSided = true;
1133                 }
1134                 if (gfxState.colorBlendState.colorAttachmentCount > 0) {
1135                     if (gfxState.colorBlendState.colorAttachments[0].enableBlend) {
1136                         exportMaterial.alphaMode = AlphaMode::BLEND;
1137                     }
1138                 }
1139             }
1140         }
1141     }
1142 }
1143 
ExportGltfMaterial(const IDevice * device,Material & exportMaterial,const MaterialComponent & materialDesc,TextureHelper & textureHelper,const IRenderHandleComponentManager & gpuHandleManager)1144 void ExportGltfMaterial(const IDevice* device, Material& exportMaterial, const MaterialComponent& materialDesc,
1145     TextureHelper& textureHelper, const IRenderHandleComponentManager& gpuHandleManager)
1146 {
1147     if (materialDesc.type == MaterialComponent::Type::METALLIC_ROUGHNESS) {
1148         ExportGltfMaterialMetallicRoughness(exportMaterial, materialDesc, textureHelper, gpuHandleManager);
1149 
1150 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_CLEARCOAT) || defined(GLTF2_EXTRAS_CLEAR_COAT_MATERIAL)
1151         ExportGltfMaterialClearcoat(exportMaterial, materialDesc, textureHelper, gpuHandleManager);
1152 #endif
1153 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_IOR)
1154         ExportGltfMaterialIor(exportMaterial, materialDesc, textureHelper, gpuHandleManager);
1155 #endif
1156 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SHEEN)
1157         ExportGltfMaterialSheen(exportMaterial, materialDesc, textureHelper, gpuHandleManager);
1158 #endif
1159 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SPECULAR)
1160         ExportGltfMaterialSpecular(exportMaterial, materialDesc, textureHelper, gpuHandleManager);
1161 #endif
1162 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_TRANSMISSION)
1163         ExportGltfMaterialTransmission(exportMaterial, materialDesc, textureHelper, gpuHandleManager);
1164 #endif
1165     } else if (materialDesc.type == MaterialComponent::Type::SPECULAR_GLOSSINESS) {
1166 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_PBRSPECULARGLOSSINESS)
1167         ExportGltfMaterialSpecularGlossiness(exportMaterial, materialDesc, textureHelper, gpuHandleManager);
1168 #endif
1169     } else if (materialDesc.type == MaterialComponent::Type::UNLIT) {
1170 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_UNLIT)
1171         ExportGltfMaterialUnlit(exportMaterial, materialDesc, textureHelper, gpuHandleManager);
1172 #endif
1173     }
1174 
1175     // Normal texture.
1176     exportMaterial.normalTexture.textureInfo.index =
1177         GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::NORMAL, textureHelper, gpuHandleManager);
1178     exportMaterial.normalTexture.scale = materialDesc.textures[MaterialComponent::TextureIndex::NORMAL].factor.x;
1179 
1180     // Occlusion texture.
1181     exportMaterial.occlusionTexture.textureInfo.index =
1182         GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::AO, textureHelper, gpuHandleManager);
1183     exportMaterial.occlusionTexture.strength = materialDesc.textures[MaterialComponent::TextureIndex::AO].factor.x;
1184 
1185     // Emissive texture.
1186     exportMaterial.emissiveTexture.index =
1187         GetTextureIndex(materialDesc, MaterialComponent::TextureIndex::EMISSIVE, textureHelper, gpuHandleManager);
1188 
1189     exportMaterial.emissiveFactor = materialDesc.textures[MaterialComponent::TextureIndex::EMISSIVE].factor;
1190     exportMaterial.alphaCutoff = materialDesc.alphaCutoff;
1191     exportMaterial.alphaMode = AlphaMode::OPAQUE;
1192 
1193     UpdateShaderStateToGltfMaterial(device, exportMaterial, materialDesc, gpuHandleManager);
1194 
1195     // check if alpha is using cutoff / mask (Lume default is 1.0 -> no mask)
1196     if (materialDesc.alphaCutoff < 1.0f) {
1197         exportMaterial.alphaMode = AlphaMode::MASK;
1198     }
1199 }
1200 
ExportGltfMaterials(const IEngine & engine,const IMaterialComponentManager & materialManager,const INameComponentManager & nameManager,const IUriComponentManager & uriManager,const vector<Entity> & usedMaterials,ExportResult & result,BufferHelper & bufferHelper)1201 void ExportGltfMaterials(const IEngine& engine, const IMaterialComponentManager& materialManager,
1202     const INameComponentManager& nameManager, const IUriComponentManager& uriManager,
1203     const vector<Entity>& usedMaterials, ExportResult& result, BufferHelper& bufferHelper)
1204 {
1205     if (!usedMaterials.empty()) {
1206         IDevice* device = nullptr;
1207         if (auto context = GetInstance<IRenderContext>(*engine.GetInterface<IClassRegister>(), UID_RENDER_CONTEXT);
1208             context) {
1209             device = &context->GetDevice();
1210         }
1211         IRenderHandleComponentManager& gpuHandleManager =
1212             *GetManager<IRenderHandleComponentManager>(materialManager.GetEcs());
1213 
1214         TextureHelper textureHash;
1215 
1216         auto& materialArray = result.data->materials;
1217         materialArray.reserve(usedMaterials.size());
1218 
1219         // Create Materials and gather used samplers and images.
1220         for (auto const materialEntity : usedMaterials) {
1221             string_view name;
1222             if (const auto nameHandle = nameManager.Read(materialEntity); nameHandle) {
1223                 name = nameHandle->name;
1224             }
1225             if (const auto materialHandle = materialManager.Read(materialEntity); materialHandle) {
1226                 auto& exportMaterial = materialArray.emplace_back(make_unique<Material>());
1227                 const auto& material = *materialHandle;
1228                 exportMaterial->name = name;
1229                 ExportGltfMaterial(device, *exportMaterial, material, textureHash, gpuHandleManager);
1230             }
1231         }
1232 
1233         vector<ResourceEntity> resourceEnties;
1234         const auto gpuHandleComponents = gpuHandleManager.GetComponentCount();
1235         resourceEnties.reserve(gpuHandleComponents);
1236         // sorted for find performance
1237         for (auto i = 0u; i < gpuHandleComponents; ++i) {
1238             resourceEnties.push_back({ gpuHandleManager.GetEntity(i), gpuHandleManager.Get(i).reference });
1239         }
1240         std::sort(resourceEnties.begin(), resourceEnties.end(),
1241             [](const ResourceEntity& lhs, const ResourceEntity& rhs) { return lhs.handle < rhs.handle; });
1242 
1243         // Create Samplers
1244         if (device && textureHash.HasSamplers()) {
1245             result.data->samplers = textureHash.GenerateGltfSamplers(device->GetGpuResourceManager());
1246         }
1247 
1248         // Create Images
1249         if (textureHash.HasImages()) {
1250             result.data->images = textureHash.GenerateGltfImages(resourceEnties, uriManager);
1251         }
1252 
1253         // Create Textures
1254         if (textureHash.HasTextures()) {
1255             result.data->textures = textureHash.GenerateGltfTextures(result.data->samplers, result.data->images);
1256         }
1257     }
1258 }
1259 
ExportImageData(IFileManager & fileManager,ExportResult & result,BufferHelper & bufferHelper,unordered_map<string,IGLTFData::Ptr> & originalGltfs)1260 void ExportImageData(IFileManager& fileManager, ExportResult& result, BufferHelper& bufferHelper,
1261     unordered_map<string, IGLTFData::Ptr>& originalGltfs)
1262 {
1263     for (auto& image : result.data->images) {
1264         if (!image->uri.empty()) {
1265             // First check if the URI is from ResourceManager in the form of <file URI/resource type/resource
1266             // index>. If that fails, try to open the URI as a file.
1267             if (const auto [originalGltf, imageIndex] =
1268                     ResolveGltfAndResourceIndex(image->uri, fileManager, originalGltfs);
1269                 originalGltf && imageIndex < originalGltf->images.size()) {
1270                 // We can store data from the loaded bufferView.
1271                 auto& originalImage = originalGltf->images[imageIndex];
1272                 image->bufferView = bufferHelper.StoreBufferView(
1273                     *originalImage->bufferView, GetComponentByteSize(ComponentType::UNSIGNED_INT));
1274                 image->bufferView->target = BufferTarget::NOT_DEFINED;
1275                 image->type = originalImage->type;
1276                 image->uri.clear();
1277             } else if (auto imageFile = fileManager.OpenFile(image->uri); imageFile) {
1278                 auto uri = string_view(image->uri);
1279                 // Leave only the file extension.
1280                 if (auto const ext = uri.rfind('.'); ext != string_view::npos) {
1281                     uri.remove_prefix(ext + 1);
1282                 }
1283 
1284                 // Resolve image type from the file extension
1285                 if (uri == "png") {
1286                     image->type = MimeType::PNG;
1287                 } else if (uri == "jpg") {
1288                     image->type = MimeType::JPEG;
1289                 } else if (uri == "ktx") {
1290                     image->type = MimeType::KTX;
1291                 } else if (uri == "ktx2") {
1292                     image->type = MimeType::KTX2;
1293                 } else if (uri == "dds") {
1294                     image->type = MimeType::DDS;
1295                 }
1296 
1297                 if (image->type != MimeType::INVALID) {
1298                     // Read the image directly to the data buffer.
1299                     const size_t imageSize = static_cast<const size_t>(imageFile->GetLength());
1300                     auto& dataBuffer = bufferHelper.GetBuffer().data;
1301                     auto const imageOffset = dataBuffer.size();
1302 
1303                     dataBuffer.resize(imageOffset + imageSize);
1304 
1305                     imageFile->Read(dataBuffer.data() + imageOffset, imageSize);
1306 
1307                     BufferView bufferView;
1308                     bufferView.buffer = &bufferHelper.GetBuffer();
1309                     bufferView.byteLength = imageSize;
1310                     bufferView.byteOffset = imageOffset;
1311                     bufferView.data = dataBuffer.data() + imageOffset;
1312 
1313                     // The special zero data aligment skips copying the data again.
1314                     image->bufferView = bufferHelper.StoreBufferView(bufferView, 0);
1315 
1316                     image->uri.clear();
1317                 }
1318             }
1319         }
1320     }
1321 }
1322 
1323 // The following Export* functions return a JSON object containing the related parts of the GLTF2::Data.
ExportAccessorSparse(const Data & data,const Accessor & accessor)1324 json::value ExportAccessorSparse(const Data& data, const Accessor& accessor)
1325 {
1326     json::value jsonSparse = json::value::object {};
1327     {
1328         jsonSparse["count"] = accessor.sparse.count;
1329         {
1330             json::value jsonSparseIndices = json::value::array {};
1331             jsonSparseIndices["bufferView"] = FindObjectIndex(data.bufferViews, *accessor.sparse.indices.bufferView);
1332             jsonSparseIndices["byteOffset"] = accessor.sparse.indices.byteOffset;
1333             jsonSparseIndices["componentType"] = static_cast<int>(accessor.sparse.indices.componentType);
1334             jsonSparse["indices"] = move(jsonSparseIndices);
1335         }
1336         {
1337             json::value jsonSparseValues = json::value::array {};
1338             jsonSparseValues["bufferView"] = FindObjectIndex(data.bufferViews, *accessor.sparse.values.bufferView);
1339             jsonSparseValues["byteOffset"] = accessor.sparse.values.byteOffset;
1340             jsonSparse["values"] = move(jsonSparseValues);
1341         }
1342     }
1343     return jsonSparse;
1344 }
1345 
ExportAccessors(Data const & data)1346 json::value ExportAccessors(Data const& data)
1347 {
1348     json::value jsonAccessors = json::value::array {};
1349     for (auto const& accessor : data.accessors) {
1350         json::value jsonAccessor = json::value::object {};
1351         if (accessor->bufferView) {
1352             jsonAccessor["bufferView"] = FindObjectIndex(data.bufferViews, *accessor->bufferView);
1353         }
1354         if (accessor->byteOffset) {
1355             jsonAccessor["byteOffset"] = accessor->byteOffset;
1356         }
1357         jsonAccessor["componentType"] = static_cast<int>(accessor->componentType);
1358         if (accessor->normalized) {
1359             jsonAccessor["normalized"] = accessor->normalized;
1360         }
1361         jsonAccessor["count"] = accessor->count;
1362         jsonAccessor["type"] = GetDataType(accessor->type);
1363 
1364         if (!accessor->max.empty()) {
1365             jsonAccessor["max"] = accessor->max;
1366         }
1367         if (!accessor->min.empty()) {
1368             jsonAccessor["min"] = accessor->min;
1369         }
1370         if (accessor->sparse.count) {
1371             jsonAccessor["sparse"] = ExportAccessorSparse(data, *accessor);
1372         }
1373 #ifdef EXPORT_OTHER_OBJECT_NAMES
1374         if (accessor->Name) {
1375             jsonAccessor["name"] = accessor->normalized;
1376         }
1377 #endif
1378 
1379         jsonAccessors.array_.push_back(move(jsonAccessor));
1380     }
1381     return jsonAccessors;
1382 }
1383 
ExportAnimations(Data const & data)1384 json::value ExportAnimations(Data const& data)
1385 {
1386     json::value jsonAnimations = json::value::array {};
1387     for (auto const& animation : data.animations) {
1388         json::value jsonAnimation = json::value::object {};
1389         {
1390             json::value jsonSamplers = json::value::array {};
1391             for (auto const& sampler : animation->samplers) {
1392                 json::value jsonSampler = json::value::object {};
1393                 jsonSampler["input"] = FindObjectIndex(data.accessors, *sampler->input);
1394                 if (sampler->interpolation != AnimationInterpolation::LINEAR) {
1395                     jsonSampler["interpolation"] = GetAnimationInterpolation(sampler->interpolation);
1396                 }
1397                 jsonSampler["output"] = FindObjectIndex(data.accessors, *sampler->output);
1398                 jsonSamplers.array_.push_back(move(jsonSampler));
1399             }
1400             jsonAnimation["samplers"] = move(jsonSamplers);
1401         }
1402         {
1403             json::value jsonChannels = json::value::array {};
1404             for (auto const& track : animation->tracks) {
1405                 json::value jsonChannel = json::value::object {};
1406                 jsonChannel["sampler"] = FindObjectIndex(animation->samplers, *track.sampler);
1407                 {
1408                     json::value jsonTarget = json::value::object {};
1409                     if (auto const nodeIndex = FindObjectIndex(data.nodes, *track.channel.node); 0 <= nodeIndex) {
1410                         jsonTarget["node"] = nodeIndex;
1411                     }
1412                     jsonTarget["path"] = GetAnimationPath(track.channel.path);
1413                     jsonChannel["target"] = move(jsonTarget);
1414                 }
1415                 jsonChannels.array_.push_back(move(jsonChannel));
1416             }
1417             jsonAnimation["channels"] = move(jsonChannels);
1418         }
1419         if (!animation->name.empty()) {
1420             jsonAnimation["name"] = string_view(animation->name);
1421         }
1422         jsonAnimations.array_.push_back(move(jsonAnimation));
1423     }
1424     return jsonAnimations;
1425 }
1426 
ExportBuffers(Data const & data,vector<string> & strings)1427 json::value ExportBuffers(Data const& data, vector<string>& strings)
1428 {
1429     json::value jsonBuffers = json::value::array {};
1430     for (auto const& buffer : data.buffers) {
1431         json::value jsonBuffer = json::value::object {};
1432         if (!buffer->uri.empty()) {
1433             jsonBuffer["uri"] = string_view(buffer->uri);
1434         }
1435         jsonBuffer["byteLength"] = buffer->byteLength;
1436 #ifdef EXPORT_OTHER_OBJECT_NAMES
1437         if (!buffer->name.empty()) {
1438             jsonBuffer["name"] = buffer->name;
1439         }
1440 #endif
1441         jsonBuffers.array_.push_back(move(jsonBuffer));
1442     }
1443     return jsonBuffers;
1444 }
1445 
ExportBufferViews(Data const & data)1446 json::value ExportBufferViews(Data const& data)
1447 {
1448     json::value jsonBufferViews = json::value::array {};
1449     for (auto const& bufferView : data.bufferViews) {
1450         json::value jsonBufferView = json::value::object {};
1451         jsonBufferView["buffer"] = FindObjectIndex(data.buffers, *bufferView->buffer);
1452         if (bufferView->byteOffset) {
1453             jsonBufferView["byteOffset"] = bufferView->byteOffset;
1454         }
1455         jsonBufferView["byteLength"] = bufferView->byteLength;
1456         if (bufferView->byteStride) {
1457             jsonBufferView["byteStride"] = bufferView->byteStride;
1458         }
1459         if (bufferView->target != BufferTarget::NOT_DEFINED) {
1460             jsonBufferView["target"] = static_cast<int>(bufferView->target);
1461         }
1462 #ifdef EXPORT_OTHER_OBJECT_NAMES
1463         if (!bufferView->name.empty()) {
1464             jsonBufferView["name"] = bufferView->name;
1465         }
1466 #endif
1467         jsonBufferViews.array_.push_back(move(jsonBufferView));
1468     }
1469     return jsonBufferViews;
1470 }
1471 
ExportCameras(Data const & data)1472 json::value ExportCameras(Data const& data)
1473 {
1474     json::value jsonCameras = json::value::array {};
1475 
1476     for (auto const& camera : data.cameras) {
1477         json::value jsonCamera = json::value::object {};
1478         jsonCamera["type"] = json::value { GetCameraType(camera->type) };
1479 
1480         if (!camera->name.empty()) {
1481             jsonCamera["name"] = json::value { camera->name };
1482         }
1483         if (camera->type == CameraType::PERSPECTIVE) {
1484             json::value jsonPerspective = json::value::object {};
1485             if (camera->attributes.perspective.aspect > 0.f) {
1486                 jsonPerspective["aspectRatio"] = camera->attributes.perspective.aspect;
1487             }
1488             jsonPerspective["yfov"] = camera->attributes.perspective.yfov;
1489             if (camera->attributes.perspective.zfar > 0.f) {
1490                 jsonPerspective["zfar"] = camera->attributes.perspective.zfar;
1491             }
1492             jsonPerspective["znear"] = camera->attributes.perspective.znear;
1493             jsonCamera["perspective"] = move(jsonPerspective);
1494         } else if (camera->type == CameraType::ORTHOGRAPHIC) {
1495             json::value jsonOrthographic = json::value::object {};
1496             jsonOrthographic["xmag"] = json::value { camera->attributes.ortho.xmag };
1497             jsonOrthographic["ymag"] = json::value { camera->attributes.ortho.ymag };
1498             jsonOrthographic["zfar"] = camera->attributes.ortho.zfar;
1499             jsonOrthographic["znear"] = camera->attributes.ortho.znear;
1500             jsonCamera["orthographic"] = move(jsonOrthographic);
1501         }
1502 
1503         jsonCameras.array_.push_back(move(jsonCamera));
1504     }
1505 
1506     return jsonCameras;
1507 }
1508 
ExportImages(Data const & data)1509 json::value ExportImages(Data const& data)
1510 {
1511     json::value jsonImages = json::value::array {};
1512     for (auto const& image : data.images) {
1513         json::value jsonImage = json::value::object {};
1514 
1515         if (!image->uri.empty()) {
1516             jsonImage["uri"] = string_view(image->uri);
1517         } else if (image->bufferView) {
1518             jsonImage["mimeType"] = GetMimeType(image->type);
1519             jsonImage["bufferView"] = FindObjectIndex(data.bufferViews, *image->bufferView);
1520         }
1521 #ifdef EXPORT_OTHER_OBJECT_NAMES
1522         if (!image->name.empty()) {
1523             jsonImage["name"] = image->name;
1524         }
1525 #endif
1526         jsonImages.array_.push_back(move(jsonImage));
1527     }
1528     return jsonImages;
1529 }
1530 
ExportTextureInfo(TextureInfo const & textureInfo)1531 json::value ExportTextureInfo(TextureInfo const& textureInfo)
1532 {
1533     json::value jsonTextureInfo = json::value::object {};
1534     jsonTextureInfo["index"] = textureInfo.index;
1535     if (textureInfo.texCoordIndex != 0 && textureInfo.texCoordIndex != GLTF_INVALID_INDEX) {
1536         jsonTextureInfo["texCoord"] = textureInfo.texCoordIndex;
1537     }
1538     return jsonTextureInfo;
1539 }
1540 
ExportMetallicRoughness(const Material & material)1541 json::value ExportMetallicRoughness(const Material& material)
1542 {
1543     json::value jsonMetallicRoughness = json::value::object {};
1544     if (material.metallicRoughness.baseColorFactor != DEFAULT_BASECOLOR_FACTOR) {
1545         jsonMetallicRoughness["baseColorFactor"] = material.metallicRoughness.baseColorFactor.data;
1546     }
1547     if (material.metallicRoughness.baseColorTexture.index != GLTF_INVALID_INDEX) {
1548         jsonMetallicRoughness["baseColorTexture"] = ExportTextureInfo(material.metallicRoughness.baseColorTexture);
1549     }
1550     if (material.metallicRoughness.metallicFactor < 1.f) {
1551         jsonMetallicRoughness["metallicFactor"] = material.metallicRoughness.metallicFactor;
1552     }
1553     if (material.metallicRoughness.roughnessFactor < 1.f) {
1554         jsonMetallicRoughness["roughnessFactor"] = material.metallicRoughness.roughnessFactor;
1555     }
1556     if (material.metallicRoughness.metallicRoughnessTexture.index != GLTF_INVALID_INDEX) {
1557         jsonMetallicRoughness["metallicRoughnessTexture"] =
1558             ExportTextureInfo(material.metallicRoughness.metallicRoughnessTexture);
1559     }
1560     return jsonMetallicRoughness;
1561 }
1562 
1563 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_PBRSPECULARGLOSSINESS)
ExportSpecularGlossiness(const Material & material)1564 json::value ExportSpecularGlossiness(const Material& material)
1565 {
1566     json::value jsonSpecularGlossiness = json::value::object {};
1567     if (material.specularGlossiness.diffuseFactor != DEFAULT_DIFFUSE_FACTOR) {
1568         jsonSpecularGlossiness["diffuseFactor"] = material.specularGlossiness.diffuseFactor.data;
1569     }
1570     if (material.specularGlossiness.diffuseTexture.index != GLTF_INVALID_INDEX) {
1571         jsonSpecularGlossiness["diffuseTexture"] = ExportTextureInfo(material.specularGlossiness.diffuseTexture);
1572     }
1573     if (material.specularGlossiness.specularFactor != DEFAULT_SPECULAR_FACTOR) {
1574         jsonSpecularGlossiness["specularFactor"] = material.specularGlossiness.specularFactor.data;
1575     }
1576     if (material.specularGlossiness.glossinessFactor != 1.f) {
1577         jsonSpecularGlossiness["glossinessFactor"] = material.specularGlossiness.glossinessFactor;
1578     }
1579     if (material.specularGlossiness.specularGlossinessTexture.index != GLTF_INVALID_INDEX) {
1580         jsonSpecularGlossiness["specularGlossinessTexture"] =
1581             ExportTextureInfo(material.specularGlossiness.specularGlossinessTexture);
1582     }
1583     return jsonSpecularGlossiness;
1584 }
1585 #endif
1586 
1587 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_CLEARCOAT)
ExportClearcoat(const Material::Clearcoat & clearcoat)1588 json::value ExportClearcoat(const Material::Clearcoat& clearcoat)
1589 {
1590     json::value jsonClearcoat = json::value::object {};
1591     if (clearcoat.factor != 0.f) {
1592         jsonClearcoat["clearcoatFactor"] = clearcoat.factor;
1593     }
1594     if (clearcoat.texture.index != GLTF_INVALID_INDEX) {
1595         jsonClearcoat["clearcoatTexture"] = ExportTextureInfo(clearcoat.texture);
1596     }
1597     if (clearcoat.roughness != 0.f) {
1598         jsonClearcoat["clearcoatRoughnessFactor"] = clearcoat.roughness;
1599     }
1600     if (clearcoat.roughnessTexture.index != GLTF_INVALID_INDEX) {
1601         jsonClearcoat["clearcoatRoughnessTexture"] = ExportTextureInfo(clearcoat.roughnessTexture);
1602     }
1603     if (clearcoat.normalTexture.textureInfo.index != GLTF_INVALID_INDEX) {
1604         auto& jsonNormalTexture = jsonClearcoat["clearcoatNormalTexture"] =
1605             ExportTextureInfo(clearcoat.normalTexture.textureInfo);
1606         if (clearcoat.normalTexture.scale != 1.f) {
1607             jsonNormalTexture["scale"] = clearcoat.normalTexture.scale;
1608         }
1609     }
1610     return jsonClearcoat;
1611 }
1612 #endif
1613 
1614 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_EMISSIVE_STRENGTH)
ExportEmissiveStrength(const float strength)1615 json::value ExportEmissiveStrength(const float strength)
1616 {
1617     json::value jsonEmissiveStrength = json::value::object {};
1618     if (strength != 1.f) {
1619         jsonEmissiveStrength["emissiveStrength"] = strength;
1620     }
1621     return jsonEmissiveStrength;
1622 }
1623 #endif
1624 
1625 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_IOR)
ExportIor(const Material::Ior & ior)1626 json::value ExportIor(const Material::Ior& ior)
1627 {
1628     json::value jsonIor = json::value::object {};
1629     if (ior.ior != 1.5f) {
1630         jsonIor["ior"] = ior.ior;
1631     }
1632     return jsonIor;
1633 }
1634 #endif
1635 
1636 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SHEEN)
ExportSheen(const Material::Sheen & sheen)1637 json::value ExportSheen(const Material::Sheen& sheen)
1638 {
1639     json::value jsonSheen = json::value::object {};
1640     if (sheen.factor != Math::Vec3 {}) {
1641         jsonSheen["sheenColorFactor"] = sheen.factor.data;
1642     }
1643     if (sheen.texture.index != GLTF_INVALID_INDEX) {
1644         jsonSheen["sheenColorTexture"] = ExportTextureInfo(sheen.texture);
1645     }
1646     if (sheen.roughness != 0.f) {
1647         jsonSheen["sheenRoughnessFactor"] = sheen.roughness;
1648     }
1649     if (sheen.roughnessTexture.index != GLTF_INVALID_INDEX) {
1650         jsonSheen["sheenRoughnessTexture"] = ExportTextureInfo(sheen.roughnessTexture);
1651     }
1652     return jsonSheen;
1653 }
1654 #endif
1655 
1656 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SPECULAR)
ExportSpecular(const Material::Specular & specular)1657 json::value ExportSpecular(const Material::Specular& specular)
1658 {
1659     json::value jsonSpecular = json::value::object {};
1660     if (specular.factor != 1.f) {
1661         jsonSpecular["specularFactor"] = specular.factor;
1662     }
1663     if (specular.texture.index != GLTF_INVALID_INDEX) {
1664         jsonSpecular["specularTexture"] = ExportTextureInfo(specular.texture);
1665     }
1666     if (specular.color != Math::Vec3(1.f, 1.f, 1.f)) {
1667         jsonSpecular["specularColorFactor"] = specular.color.data;
1668     }
1669     if (specular.colorTexture.index != GLTF_INVALID_INDEX) {
1670         jsonSpecular["specularColorTexture"] = ExportTextureInfo(specular.colorTexture);
1671     }
1672     return jsonSpecular;
1673 }
1674 #endif
1675 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_TRANSMISSION)
ExportTransmission(const Material::Transmission & transmission)1676 json::value ExportTransmission(const Material::Transmission& transmission)
1677 {
1678     json::value jsonTransmission = json::value::object {};
1679     if (transmission.factor != 0.f) {
1680         jsonTransmission["transmissionFactor"] = transmission.factor;
1681     }
1682     if (transmission.texture.index != GLTF_INVALID_INDEX) {
1683         jsonTransmission["transmissionTexture"] = ExportTextureInfo(transmission.texture);
1684     }
1685     return jsonTransmission;
1686 }
1687 #endif
1688 
ExportMaterialExtensions(const Material & material,json::value & jsonExtensionsUsed,json::value & jsonExtensionsRequired)1689 json::value ExportMaterialExtensions(
1690     const Material& material, json::value& jsonExtensionsUsed, json::value& jsonExtensionsRequired)
1691 {
1692     json::value jsonExtensions = json::value::object {};
1693     if (material.type == Material::Type::SpecularGlossiness) {
1694 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_PBRSPECULARGLOSSINESS)
1695         jsonExtensions["KHR_materials_pbrSpecularGlossiness"] = ExportSpecularGlossiness(material);
1696         AppendUnique(jsonExtensionsUsed, "KHR_materials_pbrSpecularGlossiness");
1697 #endif
1698     } else if (material.type == Material::Type::Unlit) {
1699 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_UNLIT)
1700         jsonExtensions["KHR_materials_unlit"] = json::value::object {};
1701         AppendUnique(jsonExtensionsUsed, "KHR_materials_unlit");
1702 #endif
1703     } else if (material.type == Material::Type::TextureSheetAnimation) {
1704     }
1705 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_CLEARCOAT)
1706     if (auto clearcoat = ExportClearcoat(material.clearcoat); !clearcoat.empty()) {
1707         jsonExtensions["KHR_materials_clearcoat"] = move(clearcoat);
1708         AppendUnique(jsonExtensionsUsed, "KHR_materials_clearcoat");
1709     }
1710 #endif
1711 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_EMISSIVE_STRENGTH)
1712     if (auto emissiveStrength = ExportEmissiveStrength(material.emissiveFactor.w); !emissiveStrength.empty()) {
1713         jsonExtensions["KHR_materials_emissive_strength"] = move(emissiveStrength);
1714         AppendUnique(jsonExtensionsUsed, "KHR_materials_emissive_strength");
1715     }
1716 #endif
1717 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_IOR)
1718     if (auto ior = ExportIor(material.ior); !ior.empty()) {
1719         jsonExtensions["KHR_materials_ior"] = move(ior);
1720         AppendUnique(jsonExtensionsUsed, "KHR_materials_ior");
1721     }
1722 #endif
1723 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SHEEN)
1724     if (auto sheen = ExportSheen(material.sheen); !sheen.empty()) {
1725         jsonExtensions["KHR_materials_sheen"] = move(sheen);
1726         AppendUnique(jsonExtensionsUsed, "KHR_materials_sheen");
1727     }
1728 #endif
1729 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_SPECULAR)
1730     if (auto specular = ExportSpecular(material.specular); !specular.empty()) {
1731         jsonExtensions["KHR_materials_specular"] = move(specular);
1732         AppendUnique(jsonExtensionsUsed, "KHR_materials_specular");
1733     }
1734 #endif
1735 #if defined(GLTF2_EXTENSION_KHR_MATERIALS_TRANSMISSION)
1736     if (auto transmission = ExportTransmission(material.transmission); !transmission.empty()) {
1737         jsonExtensions["KHR_materials_transmission"] = move(transmission);
1738         AppendUnique(jsonExtensionsUsed, "KHR_materials_transmission");
1739     }
1740 #endif
1741     return jsonExtensions;
1742 }
1743 
ExportMaterialExtras(const Material & material)1744 json::value ExportMaterialExtras(const Material& material)
1745 {
1746     auto jsonExtras = json::value::object {};
1747     return jsonExtras;
1748 }
1749 
ExportMaterials(Data const & data,json::value & jsonExtensionsUsed,json::value & jsonExtensionsRequired)1750 json::value ExportMaterials(Data const& data, json::value& jsonExtensionsUsed, json::value& jsonExtensionsRequired)
1751 {
1752     json::value jsonMaterials = json::value::array {};
1753     for (auto const& material : data.materials) {
1754         json::value jsonMaterial = json::value::object {};
1755         if (!material->name.empty()) {
1756             jsonMaterial["name"] = string_view(material->name);
1757         }
1758         if (material->type == Material::Type::MetallicRoughness || material->type == Material::Type::Unlit) {
1759             jsonMaterial["pbrMetallicRoughness"] = ExportMetallicRoughness(*material);
1760         }
1761         if (auto jsonExtensions = ExportMaterialExtensions(*material, jsonExtensionsUsed, jsonExtensionsRequired);
1762             !jsonExtensions.empty()) {
1763             jsonMaterial["extensions"] = move(jsonExtensions);
1764         }
1765 
1766         if (material->normalTexture.textureInfo.index != GLTF_INVALID_INDEX) {
1767             auto& jsonNormalTexture = jsonMaterial["normalTexture"] =
1768                 ExportTextureInfo(material->normalTexture.textureInfo);
1769             if (material->normalTexture.scale != 1.f) {
1770                 jsonNormalTexture["scale"] = material->normalTexture.scale;
1771             }
1772         }
1773         if (material->occlusionTexture.textureInfo.index != GLTF_INVALID_INDEX) {
1774             auto& jsonOcclusionTexture = jsonMaterial["occlusionTexture"] =
1775                 ExportTextureInfo(material->occlusionTexture.textureInfo);
1776             if (material->occlusionTexture.strength < 1.f) {
1777                 jsonOcclusionTexture["strength"] = material->occlusionTexture.strength;
1778             }
1779         }
1780         if (material->emissiveTexture.index != GLTF_INVALID_INDEX) {
1781             jsonMaterial["emissiveTexture"] = ExportTextureInfo(material->emissiveTexture);
1782         }
1783         if (Math::Vec3 emissiveFactor(
1784                 material->emissiveFactor.x, material->emissiveFactor.y, material->emissiveFactor.z);
1785             emissiveFactor != DEFAULT_EMISSIVE_FACTOR) {
1786             jsonMaterial["emissiveFactor"] = emissiveFactor.data;
1787         }
1788         if (material->alphaMode != AlphaMode::OPAQUE) {
1789             jsonMaterial["alphaMode"] = GetAlphaMode(material->alphaMode);
1790         }
1791         if (material->alphaCutoff != 0.5f) {
1792             jsonMaterial["alphaCutoff"] = material->alphaCutoff;
1793         }
1794         if (material->doubleSided) {
1795             jsonMaterial["doubleSided"] = material->doubleSided;
1796         }
1797         if (auto jsonExtras = ExportMaterialExtras(*material); !jsonExtras.empty()) {
1798             jsonMaterial["extras"] = move(jsonExtras);
1799         }
1800         jsonMaterials.array_.push_back(move(jsonMaterial));
1801     }
1802     return jsonMaterials;
1803 }
1804 
ExportMeshPrimitive(const MeshPrimitive & primitive,const vector<unique_ptr<Accessor>> & accessors,json::value & jsonTargetNames)1805 json::value ExportMeshPrimitive(
1806     const MeshPrimitive& primitive, const vector<unique_ptr<Accessor>>& accessors, json::value& jsonTargetNames)
1807 {
1808     json::value jsonPrimitive = json::value::object {};
1809     {
1810         json::value jsonAttributes = json::value::object {};
1811         for (auto const& attribute : primitive.attributes) {
1812             auto type = GetAttributeType(attribute.attribute);
1813             jsonAttributes[type] = FindObjectIndex(accessors, *attribute.accessor);
1814         }
1815         jsonPrimitive["attributes"] = move(jsonAttributes);
1816     }
1817     if (primitive.indices) {
1818         jsonPrimitive["indices"] = FindObjectIndex(accessors, *primitive.indices);
1819     }
1820     if (primitive.materialIndex != GLTF_INVALID_INDEX) {
1821         jsonPrimitive["material"] = primitive.materialIndex;
1822     }
1823     if (primitive.mode != RenderMode::TRIANGLES) {
1824         jsonPrimitive["mode"] = static_cast<int>(primitive.mode);
1825     }
1826     if (!primitive.targets.empty()) {
1827         json::value jsonTargets = json::value::array {};
1828         for (auto const& target : primitive.targets) {
1829             json::value jsonTarget = json::value::object {};
1830             for (auto const& attribute : target.target) {
1831                 auto type = GetAttributeType(attribute.attribute);
1832                 jsonTarget[type] = FindObjectIndex(accessors, *attribute.accessor);
1833             }
1834             jsonTargets.array_.push_back(move(jsonTarget));
1835             if (!target.name.empty()) {
1836                 jsonTargetNames.array_.push_back(string_view(target.name));
1837             }
1838         }
1839         jsonPrimitive["targets"] = move(jsonTargets);
1840     }
1841     return jsonPrimitive;
1842 }
1843 
ExportMeshes(Data const & data)1844 json::value ExportMeshes(Data const& data)
1845 {
1846     json::value jsonMeshes = json::value::array {};
1847     for (auto const& mesh : data.meshes) {
1848         json::value jsonMesh = json::value::object {};
1849         json::value jsonExtras = json::value::object {};
1850         {
1851             json::value jsonPrimitives = json::value::array {};
1852             json::value jsonTargetNames = json::value::array {};
1853             for (auto const& primitive : mesh->primitives) {
1854                 jsonPrimitives.array_.push_back(ExportMeshPrimitive(primitive, data.accessors, jsonTargetNames));
1855             }
1856             jsonMesh["primitives"] = move(jsonPrimitives);
1857             if (!jsonTargetNames.empty()) {
1858                 jsonExtras["targetNames"] = move(jsonTargetNames);
1859             }
1860         }
1861         if (!mesh->weights.empty()) {
1862             jsonMesh["weights"] = mesh->weights;
1863         }
1864         if (!mesh->name.empty()) {
1865             jsonMesh["name"] = string_view(mesh->name);
1866         }
1867         if (!jsonExtras.empty()) {
1868             jsonMesh["extras"] = move(jsonExtras);
1869         }
1870         jsonMeshes.array_.push_back(move(jsonMesh));
1871     }
1872     return jsonMeshes;
1873 }
1874 
ExportNodeExtensions(Data const & data,const Node & node,json::value & jsonExtensionsUsed,json::value & jsonExtensionsRequired)1875 json::value ExportNodeExtensions(
1876     Data const& data, const Node& node, json::value& jsonExtensionsUsed, json::value& jsonExtensionsRequired)
1877 {
1878     json::value jsonExtensions = json::value::object {};
1879 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
1880     if (node.light) {
1881         json::value jsonKHRLights = json::value::object {};
1882         jsonKHRLights["light"] = FindObjectIndex(data.lights, *node.light);
1883         jsonExtensions["KHR_lights_punctual"] = move(jsonKHRLights);
1884         AppendUnique(jsonExtensionsUsed, "KHR_lights_punctual");
1885     }
1886 #endif
1887     return jsonExtensions;
1888 }
1889 
ExportNodes(Data const & data,json::value & jsonExtensionsUsed,json::value & jsonExtensionsRequired)1890 json::value ExportNodes(Data const& data, json::value& jsonExtensionsUsed, json::value& jsonExtensionsRequired)
1891 {
1892     json::value jsonNodes = json::value::array {};
1893 
1894     for (auto const& node : data.nodes) {
1895         json::value jsonNodeObject = json::value::object {};
1896 
1897         if (node->camera) {
1898             jsonNodeObject["camera"] = FindObjectIndex(data.cameras, *node->camera);
1899         }
1900 
1901         if (!node->tmpChildren.empty()) {
1902             jsonNodeObject["children"] = node->tmpChildren;
1903         }
1904 
1905         if (!node->name.empty()) {
1906             jsonNodeObject["name"] = string_view(node->name);
1907         }
1908 
1909         if (node->skin) {
1910             jsonNodeObject["skin"] = FindObjectIndex(data.skins, *node->skin);
1911         }
1912 
1913         if (node->mesh) {
1914             jsonNodeObject["mesh"] = FindObjectIndex(data.meshes, *node->mesh);
1915         }
1916 
1917         if (node->usesTRS) {
1918             if (node->translation != DEFAULT_TRANSLATION) {
1919                 jsonNodeObject["translation"] = node->translation.data;
1920             }
1921             if (node->rotation != DEFAULT_ROTATION) {
1922                 jsonNodeObject["rotation"] = node->rotation.data;
1923             }
1924             if (node->scale != DEFAULT_SCALE) {
1925                 jsonNodeObject["scale"] = node->scale.data;
1926             }
1927         } else {
1928             if (node->matrix != IDENTITY_MATRIX) {
1929                 jsonNodeObject["matrix"] = node->matrix.data;
1930             }
1931         }
1932 
1933         if (!node->weights.empty()) {
1934             jsonNodeObject["weights"] = node->weights;
1935         }
1936 
1937         if (auto jsonExtensions = ExportNodeExtensions(data, *node, jsonExtensionsUsed, jsonExtensionsRequired);
1938             !jsonExtensions.empty()) {
1939             jsonNodeObject["extensions"] = move(jsonExtensions);
1940         }
1941 
1942         jsonNodes.array_.push_back(move(jsonNodeObject));
1943     }
1944 
1945     return jsonNodes;
1946 }
1947 
ExportSamplers(Data const & data)1948 json::value ExportSamplers(Data const& data)
1949 {
1950     json::value jsonSamplers = json::value::array {};
1951     for (auto const& sampler : data.samplers) {
1952         json::value jsonSampler = json::value::object {};
1953         if (sampler->magFilter != FilterMode::LINEAR) {
1954             jsonSampler["magFilter"] = static_cast<int>(sampler->magFilter);
1955         }
1956         if (sampler->minFilter != FilterMode::LINEAR) {
1957             jsonSampler["minFilter"] = static_cast<int>(sampler->minFilter);
1958         }
1959         if (sampler->wrapS != WrapMode::REPEAT) {
1960             jsonSampler["wrapS"] = static_cast<int>(sampler->wrapS);
1961         }
1962         if (sampler->wrapT != WrapMode::REPEAT) {
1963             jsonSampler["wrapT"] = static_cast<int>(sampler->wrapT);
1964         }
1965 #ifdef EXPORT_OTHER_OBJECT_NAMES
1966         if (!sampler->name.empty()) {
1967             jsonSampler["name"] = sampler->name;
1968         }
1969 #endif
1970         jsonSamplers.array_.push_back(move(jsonSampler));
1971     }
1972     return jsonSamplers;
1973 }
1974 
ExportScenes(Data const & data)1975 json::value ExportScenes(Data const& data)
1976 {
1977     json::value jsonScenes = json::value::array {};
1978     for (auto const& scene : data.scenes) {
1979         json::value jsonScene = json::value::object {};
1980 
1981         if (!scene->name.empty()) {
1982             jsonScene["name"] = string_view(scene->name);
1983         }
1984 
1985         json::value jsonNodes = json::value::array {};
1986         for (auto const node : scene->nodes) {
1987             jsonNodes.array_.push_back(FindObjectIndex(data.nodes, *node));
1988         }
1989         jsonScene["nodes"] = move(jsonNodes);
1990 
1991 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
1992         if (scene->light) {
1993             json::value jsonExtensions = json::value::object {};
1994 
1995             json::value jsonKHRLights = json::value::object {};
1996             jsonKHRLights["light"] = FindObjectIndex(data.lights, *scene->light);
1997             jsonExtensions["KHR_lights_punctual"] = move(jsonKHRLights);
1998 
1999             jsonScene["extensions"] = move(jsonExtensions);
2000         }
2001 #endif
2002         jsonScenes.array_.push_back(move(jsonScene));
2003     }
2004     return jsonScenes;
2005 }
2006 
ExportSkins(Data const & data)2007 json::value ExportSkins(Data const& data)
2008 {
2009     json::value jsonSkins = json::value::array {};
2010     for (auto const& skin : data.skins) {
2011         json::value jsonSkin = json::value::object {};
2012         if (skin->inverseBindMatrices) {
2013             jsonSkin["inverseBindMatrices"] = FindObjectIndex(data.accessors, *skin->inverseBindMatrices);
2014         }
2015         if (skin->skeleton) {
2016             jsonSkin["skeleton"] = FindObjectIndex(data.nodes, *skin->skeleton);
2017         }
2018         json::value jsonJoints = json::value::array {};
2019         for (auto const joint : skin->joints) {
2020             jsonJoints.array_.push_back(FindObjectIndex(data.nodes, *joint));
2021         }
2022         jsonSkin["joints"] = move(jsonJoints);
2023         if (!skin->name.empty()) {
2024             jsonSkin["name"] = skin->name.empty();
2025         }
2026         jsonSkins.array_.push_back(move(jsonSkin));
2027     }
2028     return jsonSkins;
2029 }
2030 
ExportTextures(Data const & data,json::value & jsonExtensionsUsed,json::value & jsonExtensionsRequired)2031 json::value ExportTextures(Data const& data, json::value& jsonExtensionsUsed, json::value& jsonExtensionsRequired)
2032 {
2033     json::value jsonTextures = json::value::array {};
2034     for (auto const& texture : data.textures) {
2035         json::value jsonTexture = json::value::object {};
2036         if (texture->sampler) {
2037             jsonTexture["sampler"] = FindObjectIndex(data.samplers, *texture->sampler);
2038         }
2039         if (texture->image) {
2040             switch (texture->image->type) {
2041                 default:
2042                 case MimeType::INVALID:
2043                 case MimeType::JPEG:
2044                 case MimeType::PNG:
2045                 case MimeType::KTX: // NOTE: this is incorrect, but there's no extension for .ktx
2046                     jsonTexture["source"] = FindObjectIndex(data.images, *texture->image);
2047                     break;
2048                 case MimeType::DDS: {
2049                     json::value jsonMsftTextureDds = json::value::object {};
2050                     jsonMsftTextureDds["source"] = FindObjectIndex(data.images, *texture->image);
2051 
2052                     json::value jsonExtensions = json::value::object {};
2053                     jsonExtensions["MSFT_texture_dds"] = move(jsonMsftTextureDds);
2054 
2055                     jsonTexture["extensions"] = move(jsonExtensions);
2056 
2057                     AppendUnique(jsonExtensionsUsed, "MSFT_texture_dds");
2058                     AppendUnique(jsonExtensionsRequired, "MSFT_texture_dds");
2059                     break;
2060                 }
2061                 case MimeType::KTX2: {
2062                     json::value jsonKHRtextureBasisU = json::value::object {};
2063                     jsonKHRtextureBasisU["source"] = FindObjectIndex(data.images, *texture->image);
2064 
2065                     json::value jsonExtensions = json::value::object {};
2066                     jsonExtensions["KHR_texture_basisu"] = move(jsonKHRtextureBasisU);
2067 
2068                     jsonTexture["extensions"] = move(jsonExtensions);
2069 
2070                     AppendUnique(jsonExtensionsUsed, "KHR_texture_basisu");
2071                     AppendUnique(jsonExtensionsRequired, "KHR_texture_basisu");
2072                     break;
2073                 }
2074             }
2075         }
2076 #ifdef EXPORT_OTHER_OBJECT_NAMES
2077         if (!texture->name.empty()) {
2078             jsonTexture["name"] = texture->name;
2079         }
2080 #endif
2081         jsonTextures.array_.push_back(move(jsonTexture));
2082     }
2083     return jsonTextures;
2084 }
2085 
ExportKHRLights(Data const & data)2086 json::value ExportKHRLights(Data const& data)
2087 {
2088     json::value jsonLightArray = json::value::array {};
2089 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2090 
2091     for (auto const& light : data.lights) {
2092         if (light->type == LightType::AMBIENT || light->type == LightType::INVALID) {
2093             continue;
2094         }
2095 
2096         json::value jsonLightObject = json::value::object {};
2097         if (!light->name.empty()) {
2098             jsonLightObject["name"] = string_view(light->name);
2099         }
2100         if (light->color != Math::Vec3(1.f, 1.f, 1.f)) {
2101             jsonLightObject["color"] = light->color.data;
2102         }
2103         if (light->intensity != 1.f) {
2104             jsonLightObject["intensity"] = light->intensity;
2105         }
2106         jsonLightObject["type"] = GetLightType(light->type);
2107         if ((light->type == LightType::POINT || light->type == LightType::SPOT) && light->positional.range > 0.f) {
2108             jsonLightObject["range"] = light->positional.range;
2109         }
2110         if (light->type == LightType::SPOT) {
2111             json::value jsonSpotObject = json::value::object {};
2112             if (light->positional.spot.innerAngle != 0.f) {
2113                 jsonSpotObject["innerConeAngle"] = light->positional.spot.innerAngle;
2114             }
2115             if (light->positional.spot.outerAngle != 0.785398163397448f) {
2116                 jsonSpotObject["outerConeAngle"] = light->positional.spot.outerAngle;
2117             }
2118             if (!jsonSpotObject.empty()) {
2119                 jsonLightObject["spot"] = move(jsonSpotObject);
2120             }
2121         }
2122         jsonLightArray.array_.push_back(move(jsonLightObject));
2123     }
2124 #endif
2125     json::value jsonLights = json::value::object {};
2126     jsonLights["lights"] = move(jsonLightArray);
2127     return jsonLights;
2128 }
2129 
ExportExtensions(Data const & data,json::value & jsonExtensionsUsed,json::value & jsonExtensionsRequired)2130 json::value ExportExtensions(Data const& data, json::value& jsonExtensionsUsed, json::value& jsonExtensionsRequired)
2131 {
2132     json::value jsonExtensions = json::value::object {};
2133 
2134 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2135     if (!data.lights.empty()) {
2136         if (auto jsonKHRLights = ExportKHRLights(data); !jsonKHRLights.empty()) {
2137             jsonExtensions["KHR_lights_punctual"] = move(jsonKHRLights);
2138             AppendUnique(jsonExtensionsUsed, "KHR_lights_punctual");
2139         }
2140     }
2141 #endif
2142     return jsonExtensions;
2143 }
2144 
ExportAsset(string_view versionString,vector<string> & strings)2145 json::value ExportAsset(string_view versionString, vector<string>& strings)
2146 {
2147     auto jsonAsset = json::value { json::value::object {} };
2148     jsonAsset["version"] = string_view("2.0");
2149     strings.push_back("CoreEngine " + versionString);
2150     jsonAsset["generator"] = string_view(strings.back());
2151     return jsonAsset;
2152 }
2153 
2154 /* Returns a JSON string generated from given GLTF2::Data. The above Export* helpers are used to convert different
2155  * parts of the data into JSON objects. */
ExportGLTFData(Data const & data,string_view versionString)2156 auto ExportGLTFData(Data const& data, string_view versionString)
2157 {
2158     vector<string> strings;
2159     auto jsonGltf = json::value { json::value::object {} };
2160 
2161     auto jsonExtensionsUsed = json::value { json::value::array {} };
2162     auto jsonExtensionsRequired = json::value { json::value::array {} };
2163     jsonGltf["asset"] = ExportAsset(versionString, strings);
2164 
2165     if (!data.animations.empty()) {
2166         jsonGltf["animations"] = ExportAnimations(data);
2167     }
2168 
2169     if (!data.cameras.empty()) {
2170         jsonGltf["cameras"] = ExportCameras(data);
2171     }
2172 
2173     if (!data.images.empty()) {
2174         jsonGltf["images"] = ExportImages(data);
2175     }
2176 
2177     if (!data.materials.empty()) {
2178         jsonGltf["materials"] = ExportMaterials(data, jsonExtensionsUsed, jsonExtensionsRequired);
2179     }
2180 
2181     if (!data.meshes.empty()) {
2182         jsonGltf["meshes"] = ExportMeshes(data);
2183     }
2184 
2185     if (!data.nodes.empty()) {
2186         jsonGltf["nodes"] = ExportNodes(data, jsonExtensionsUsed, jsonExtensionsRequired);
2187     }
2188 
2189     if (!data.samplers.empty()) {
2190         jsonGltf["samplers"] = ExportSamplers(data);
2191     }
2192 
2193     if (!data.scenes.empty()) {
2194         jsonGltf["scenes"] = ExportScenes(data);
2195         jsonGltf["scene"] = 0;
2196     }
2197 
2198     if (!data.skins.empty()) {
2199         jsonGltf["skins"] = ExportSkins(data);
2200     }
2201 
2202     if (!data.textures.empty()) {
2203         jsonGltf["textures"] = ExportTextures(data, jsonExtensionsUsed, jsonExtensionsRequired);
2204     }
2205 
2206     if (auto jsonExtensions = ExportExtensions(data, jsonExtensionsUsed, jsonExtensionsRequired);
2207         !jsonExtensions.empty()) {
2208         jsonGltf["extensions"] = move(jsonExtensions);
2209     }
2210 
2211     if (!data.accessors.empty()) {
2212         jsonGltf["accessors"] = ExportAccessors(data);
2213     }
2214 
2215     if (!data.bufferViews.empty()) {
2216         jsonGltf["bufferViews"] = ExportBufferViews(data);
2217     }
2218 
2219     if (!data.buffers.empty()) {
2220         jsonGltf["buffers"] = ExportBuffers(data, strings);
2221     }
2222 
2223     if (!jsonExtensionsUsed.empty()) {
2224         jsonGltf["extensionsUsed"] = move(jsonExtensionsUsed);
2225     }
2226     if (!jsonExtensionsRequired.empty()) {
2227         jsonGltf["extensionsRequired"] = move(jsonExtensionsRequired);
2228     }
2229 
2230     return to_string(jsonGltf);
2231 }
2232 } // namespace
2233 
2234 /* Writes the GLTF2::Data as a GLB file. */
SaveGLB(Data const & data,IFile & file,string_view versionString)2235 void SaveGLB(Data const& data, IFile& file, string_view versionString)
2236 {
2237     auto jsonString = ExportGLTFData(data, versionString);
2238     if (jsonString.empty()) {
2239         return;
2240     }
2241     if (auto const pad = (jsonString.size() % 4); pad) {
2242         jsonString.append(4 - pad, ' ');
2243     }
2244 
2245     auto const jsonSize = static_cast<uint32_t>(jsonString.size());
2246     auto const binarySize = [](auto const& aBuffers) {
2247         size_t totalSize = 0;
2248         for (auto const& buffer : aBuffers) {
2249             totalSize += buffer->data.size();
2250         }
2251         return static_cast<uint32_t>(totalSize);
2252     }(data.buffers);
2253 
2254     auto const header = GLBHeader { GLTF_MAGIC, 2,
2255         static_cast<uint32_t>(sizeof(GLBHeader) + sizeof(GLBChunk) + jsonSize + sizeof(GLBChunk) + binarySize) };
2256     file.Write(&header, sizeof(header));
2257 
2258     auto const jsonChunk = GLBChunk { jsonSize, static_cast<uint32_t>(ChunkType::JSON) };
2259     file.Write(&jsonChunk, sizeof(jsonChunk));
2260 
2261     file.Write(jsonString.data(), jsonSize);
2262 
2263     auto const binaryChunk = GLBChunk { binarySize, static_cast<uint32_t>(ChunkType::BIN) };
2264     file.Write(&binaryChunk, sizeof(binaryChunk));
2265 
2266     file.Write(data.buffers.front()->data.data(), binarySize);
2267 }
2268 
2269 /* Writes the GLTF2::Data as a glTF file. */
SaveGLTF(Data const & data,IFile & file,string_view versionString)2270 void SaveGLTF(Data const& data, IFile& file, string_view versionString)
2271 {
2272     auto const jsonString = ExportGLTFData(data, versionString);
2273     file.Write(jsonString.data(), jsonString.size());
2274 }
2275 
2276 /* Returns true if the scene node has a node component and it hasn't been excluded from export. */
IsExportable(ISceneNode const & node,IEcs const & ecs)2277 bool IsExportable(ISceneNode const& node, IEcs const& ecs)
2278 {
2279     auto const nodeEntity = node.GetEntity();
2280     if (auto const nodeManager = GetManager<INodeComponentManager>(ecs); nodeManager) {
2281         return nodeManager->HasComponent(nodeEntity) && nodeManager->Get(nodeEntity).exported;
2282     }
2283     return false;
2284 }
2285 
2286 namespace {
2287 /* Returns the first parent which can be exported or null if no such parent exists. */
FindExportedParent(ISceneNode const & node,IEcs const & ecs)2288 ISceneNode* FindExportedParent(ISceneNode const& node, IEcs const& ecs)
2289 {
2290     auto parent = node.GetParent();
2291     while (parent) {
2292         if (IsExportable(*parent, ecs)) {
2293             return parent;
2294         }
2295         parent = parent->GetParent();
2296     }
2297     return parent;
2298 }
2299 
2300 /* Gathers transformations of nodes which haven't been exported and applies them to the exported child. */
CombineSkippedParentTransformations(TransformComponent & transformComponent,ISceneNode const & node,IEcs const & ecs,vector<Entity> const & nodeEntities)2301 void CombineSkippedParentTransformations(
2302     TransformComponent& transformComponent, ISceneNode const& node, IEcs const& ecs, vector<Entity> const& nodeEntities)
2303 {
2304     auto parent = node.GetParent();
2305     auto const transformManager = GetManager<ITransformComponentManager>(ecs);
2306     while (parent) {
2307         if (auto const parentIndex = FindHandleIndex(nodeEntities, parent->GetEntity());
2308             parentIndex < nodeEntities.size()) {
2309             // found an exported node and no need to continue.
2310             parent = nullptr;
2311         } else {
2312             // apply the transformation of a node which wasn't exported.
2313             if (transformManager->HasComponent(parent->GetEntity())) {
2314                 auto const parentTransformComponent = transformManager->Get(parent->GetEntity());
2315 
2316                 auto const transformation =
2317                     Math::Trs(parentTransformComponent.position, parentTransformComponent.rotation,
2318                         parentTransformComponent.scale) *
2319                     Math::Trs(transformComponent.position, transformComponent.rotation, transformComponent.scale);
2320 
2321                 Math::Vec3 skew;
2322                 Math::Vec4 perspective;
2323                 Math::Decompose(transformation, transformComponent.scale, transformComponent.rotation,
2324                     transformComponent.position, skew, perspective);
2325             }
2326             parent = parent->GetParent();
2327         }
2328     }
2329 }
2330 
GetNode(vector<unique_ptr<Node>> & nodeArray,size_t index)2331 Node& GetNode(vector<unique_ptr<Node>>& nodeArray, size_t index)
2332 {
2333     if (index < nodeArray.size()) {
2334         return *nodeArray[index];
2335     } else {
2336         return *nodeArray.emplace_back(make_unique<Node>());
2337     }
2338 }
2339 
AttachMesh(IEcs const & ecs,const Entity nodeEntity,Node & exportNode,decltype (Data::meshes)& meshArray,vector<Entity> & usedMeshes)2340 void AttachMesh(IEcs const& ecs, const Entity nodeEntity, Node& exportNode, decltype(Data::meshes)& meshArray,
2341     vector<Entity>& usedMeshes)
2342 {
2343     if (auto const meshManager = GetManager<IRenderMeshComponentManager>(ecs);
2344         meshManager && meshManager->HasComponent(nodeEntity)) {
2345         auto const meshHandle = meshManager->Get(nodeEntity).mesh;
2346         if (auto const meshIndex = FindOrAddIndex(usedMeshes, meshHandle); meshIndex < meshArray.size()) {
2347             exportNode.mesh = meshArray[meshIndex].get();
2348         } else {
2349             exportNode.mesh = meshArray.emplace_back(make_unique<Mesh>()).get();
2350         }
2351     }
2352 }
2353 
AttachCamera(IEcs const & ecs,const Entity nodeEntity,Node & exportNode,decltype (Data::cameras)& cameraArray,Entities & entities)2354 void AttachCamera(IEcs const& ecs, const Entity nodeEntity, Node& exportNode, decltype(Data::cameras)& cameraArray,
2355     Entities& entities)
2356 {
2357     if (auto const cameraManager = GetManager<ICameraComponentManager>(ecs);
2358         cameraManager && cameraManager->HasComponent(nodeEntity)) {
2359         if (auto const cameraIndex = FindOrAddIndex(entities.withCamera, nodeEntity);
2360             cameraIndex < cameraArray.size()) {
2361             exportNode.camera = cameraArray[cameraIndex].get();
2362         } else {
2363             exportNode.camera = cameraArray.emplace_back(make_unique<Camera>()).get();
2364         }
2365     }
2366 }
2367 
2368 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
AttachLight(IEcs const & ecs,const Entity nodeEntity,Node & exportNode,decltype (Data::lights)& lightArray,Entities & entities)2369 void AttachLight(
2370     IEcs const& ecs, const Entity nodeEntity, Node& exportNode, decltype(Data::lights)& lightArray, Entities& entities)
2371 {
2372     if (auto const lightManager = GetManager<ILightComponentManager>(ecs);
2373         lightManager && lightManager->HasComponent(nodeEntity)) {
2374         if (auto const lightIndex = FindOrAddIndex(entities.withLight, nodeEntity); lightIndex < lightArray.size()) {
2375             exportNode.light = lightArray[lightIndex].get();
2376         } else {
2377             exportNode.light = lightArray.emplace_back(make_unique<KHRLight>()).get();
2378         }
2379     }
2380 }
2381 #endif
2382 
AttachParent(const ISceneNode & node,const IEcs & ecs,Scene & scene,Node & exportNode,uint32_t nodeIndex,const vector<Entity> & nodeEntities,decltype (Data::nodes)& nodeArray)2383 void AttachParent(const ISceneNode& node, const IEcs& ecs, Scene& scene, Node& exportNode, uint32_t nodeIndex,
2384     const vector<Entity>& nodeEntities, decltype(Data::nodes)& nodeArray)
2385 {
2386     if (const auto* parent = FindExportedParent(node, ecs); parent) {
2387         if (auto const parentIndex = FindHandleIndex(nodeEntities, parent->GetEntity());
2388             parentIndex < nodeArray.size()) {
2389             // Parent has been exported -> node has a parent and will be added to the parents list of children.
2390             exportNode.parent = nodeArray[parentIndex].get();
2391             if (std::none_of(exportNode.parent->children.begin(), exportNode.parent->children.end(),
2392                 [&exportNode](const auto childNode) { return childNode == &exportNode; })) {
2393                 exportNode.parent->children.push_back(&exportNode);
2394                 exportNode.parent->tmpChildren.push_back(nodeIndex);
2395             }
2396         } else {
2397             // Parent hasn't been exported i.e. it's outside this scene hierarchy -> add node as a scene root.
2398             scene.nodes.push_back(&exportNode);
2399         }
2400     } else {
2401         // Parent marked to be excluded from exporting -> add node as a scene root.
2402         scene.nodes.push_back(&exportNode);
2403     }
2404 }
2405 
AttachSkin(IEcs const & ecs,const Entity nodeEntity,Node & exportNode,decltype (Data::skins)& skinArray,Entities & entities)2406 void AttachSkin(
2407     IEcs const& ecs, const Entity nodeEntity, Node& exportNode, decltype(Data::skins)& skinArray, Entities& entities)
2408 {
2409     if (auto const skinManager = GetManager<ISkinComponentManager>(ecs);
2410         skinManager && skinManager->HasComponent(nodeEntity)) {
2411         if (auto const entityIndex = FindOrAddIndex(entities.withSkin, nodeEntity); entityIndex < skinArray.size()) {
2412             exportNode.skin = skinArray[entityIndex].get();
2413         } else {
2414             exportNode.skin = skinArray.emplace_back(make_unique<Skin>()).get();
2415         }
2416     }
2417 }
2418 
2419 /* Export scene node hierarcy as a glTF scene. Only nodes and indices to other resources are written to GLTF2::Data.
2420  * Mesh, Image etc. have to be written to GLTF2::Data separately based on the usedMeshes, and entities output
2421  * parameters.
2422  * @param node Scene node to consider to be exported.
2423  * @param ecs ECS instance where node and related data lives.
2424  * @param scene Scene where nodes will be included.
2425  * @param data Exported nodes and placeholders for meshes, cameras, lights etc. will be stored here.
2426  * @param nodeEntities Entities with NodeComponents which were exported.
2427  * @param usedMeshes Handles to meshes used by exported entities.
2428  * @param entities Collections of entities having special components attached such as cameras or lights.
2429  */
RecursivelyExportNode(ISceneNode const & node,IEcs const & ecs,Scene & scene,Data & data,vector<Entity> & nodeEntities,vector<Entity> & usedMeshes,Entities & entities)2430 void RecursivelyExportNode(ISceneNode const& node, IEcs const& ecs, Scene& scene, Data& data,
2431     vector<Entity>& nodeEntities, vector<Entity>& usedMeshes, Entities& entities)
2432 {
2433     if (!IsExportable(node, ecs)) {
2434         // if this node shouldn't be exported, try exporting the child nodes.
2435         for (const auto* child : node.GetChildren()) {
2436             RecursivelyExportNode(*child, ecs, scene, data, nodeEntities, usedMeshes, entities);
2437         }
2438         return;
2439     }
2440 
2441     auto const nodeEntity = node.GetEntity();
2442     auto const nodeIndex = FindOrAddIndex(nodeEntities, nodeEntity);
2443     auto& exportNode = GetNode(data.nodes, nodeIndex);
2444 
2445     // name
2446     exportNode.name = node.GetName();
2447 
2448     // mesh
2449     AttachMesh(ecs, nodeEntity, exportNode, data.meshes, usedMeshes);
2450 
2451     // camera
2452     AttachCamera(ecs, nodeEntity, exportNode, data.cameras, entities);
2453 
2454 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2455     // light
2456     AttachLight(ecs, nodeEntity, exportNode, data.lights, entities);
2457 #endif
2458 
2459     // parent
2460     AttachParent(node, ecs, scene, exportNode, nodeIndex, nodeEntities, data.nodes);
2461 
2462     // isJoint
2463     // children, tmpChildren, the child will actually add itself to the parents list of children
2464     for (const auto* child : node.GetChildren()) {
2465         RecursivelyExportNode(*child, ecs, scene, data, nodeEntities, usedMeshes, entities);
2466     }
2467 
2468     // skin
2469     // tmpSkin
2470     AttachSkin(ecs, nodeEntity, exportNode, data.skins, entities);
2471 
2472     // usesTRS, translation, rotation, scale
2473     if (const auto* transformManager = GetManager<ITransformComponentManager>(ecs);
2474         transformManager && transformManager->HasComponent(nodeEntity)) {
2475         exportNode.usesTRS = true;
2476 
2477         auto transformComponent = transformManager->Get(nodeEntity);
2478         CombineSkippedParentTransformations(transformComponent, node, ecs, nodeEntities);
2479         transformComponent.rotation = Math::Normalize(transformComponent.rotation);
2480         exportNode.translation = transformComponent.position;
2481         exportNode.rotation = transformComponent.rotation;
2482         exportNode.scale = transformComponent.scale;
2483     }
2484 
2485     // NOTE: weights, defaults are not exported
2486 }
2487 } // namespace
2488 
2489 // Internal exporting function.
ExportGLTF(IEngine & engine,const IEcs & ecs)2490 ExportResult ExportGLTF(IEngine& engine, const IEcs& ecs)
2491 {
2492     auto result = ExportResult(make_unique<Data>(engine.GetFileManager()));
2493 
2494     // We write all the binary data to a single buffer.
2495     auto& exportBuffer = result.data->buffers.emplace_back(make_unique<Buffer>());
2496 
2497     // Helper for gathering bufferViews and accessors, and packing data in the buffer.
2498     BufferHelper bufferHelper(*exportBuffer, result.data->bufferViews, result.data->accessors);
2499 
2500     Entities entities;
2501     vector<Entity> usedMeshes;
2502 
2503     // Create Nodes and Scenes.
2504     auto const nameManager = GetManager<INameComponentManager>(ecs);
2505     auto const nodeManager = GetManager<INodeComponentManager>(ecs);
2506     auto const nodeSystem = GetSystem<INodeSystem>(ecs);
2507     if (nodeManager && nodeSystem) {
2508         auto& sceneArray = result.data->scenes;
2509 
2510         auto const nodeCount = nodeManager->GetComponentCount();
2511         auto& nodeArray = result.data->nodes;
2512         nodeArray.reserve(nodeCount);
2513         entities.nodes.reserve(nodeCount);
2514 
2515         auto& exportScene = *sceneArray.emplace_back(make_unique<Scene>());
2516 
2517         for (const auto* child : nodeSystem->GetRootNode().GetChildren()) {
2518             RecursivelyExportNode(*child, ecs, exportScene, *result.data, entities.nodes, usedMeshes, entities);
2519         }
2520         if (exportScene.nodes.empty()) {
2521             sceneArray.pop_back();
2522         }
2523 
2524         // Create Skins.
2525         ExportGltfSkins(ecs, entities, nodeArray, result, bufferHelper);
2526 
2527         // Create Cameras.
2528         ExportGltfCameras(ecs, entities, result);
2529 
2530 #if defined(GLTF2_EXTENSION_KHR_LIGHTS) || defined(GLTF2_EXTENSION_KHR_LIGHTS_PBR)
2531         // Create KHRLights.
2532         ExportGltfLight(ecs, entities, result);
2533 #endif
2534         // Create Animations.
2535         ExportGltfAnimations(ecs, entities, result, bufferHelper);
2536     }
2537     unordered_map<string, IGLTFData::Ptr> originalGltfs;
2538     // Create Meshes for the mesh handles referenced by exported nodes. Materials referenced by the meshes will
2539     // be gathered for exporting.
2540     auto meshManager = GetManager<IMeshComponentManager>(ecs);
2541     auto materialManager = GetManager<IMaterialComponentManager>(ecs);
2542     auto uriManager = GetManager<IUriComponentManager>(ecs);
2543     auto usedMaterials = ExportGltfMeshes(*meshManager, *nameManager, *uriManager, *materialManager,
2544         engine.GetFileManager(), usedMeshes, result, bufferHelper, originalGltfs);
2545 
2546     ExportGltfMaterials(engine, *materialManager, *nameManager, *uriManager, usedMaterials, result, bufferHelper);
2547 
2548     // Write image data to the buffer
2549     ExportImageData(engine.GetFileManager(), result, bufferHelper, originalGltfs);
2550 
2551     exportBuffer->byteLength = exportBuffer->data.size();
2552 
2553     return result;
2554 }
2555 } // namespace GLTF2
2556 
2557 CORE3D_END_NAMESPACE()
2558