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