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#include <scene_plugin/api/material_uid.h>
16#include <3d/ecs/components/material_component.h>
17#include <atomic>
18#include <meta/api/make_callback.h>
19#include <meta/interface/detail/array_property.h>
20#include <meta/api/property/array_element_bind.h>
21#include <meta/ext/concrete_base_object.h>
22
23#include "bind_templates.inl"
24#include "intf_proxy_object_holder.h"
25#include "node_impl.h"
26#include "task_utils.h"
27
28using CORE3D_NS::MaterialComponent;
29META_BEGIN_NAMESPACE()
30META_TYPE(MaterialComponent::TextureInfo);
31META_END_NAMESPACE()
32
33using SCENE_NS::MakeTask;
34#define GET_HOLDER(info) interface_pointer_cast<SCENE_NS::IProxyObjectHolder>(info)->Holder()
35
36namespace {
37template<typename... Types>
38inline void BindMetaProperty(
39    PropertyHandlerArrayHolder& handler, META_NS::IProperty::Ptr& clone, META_NS::IProperty::Ptr& prop)
40{
41    (BindIfCorrectType<Types>(handler, clone, prop) || ...);
42}
43
44void BindMetaProperty(
45    PropertyHandlerArrayHolder& handler, META_NS::IProperty::Ptr& clone, META_NS::IProperty::Ptr& prop)
46{
47    BindMetaProperty<BASE_NS::Math::Vec4, BASE_NS::Math::UVec4, BASE_NS::Math::IVec4, BASE_NS::Math::Vec3,
48        BASE_NS::Math::UVec3, BASE_NS::Math::IVec3, BASE_NS::Math::Vec2, BASE_NS::Math::UVec2, BASE_NS::Math::IVec2,
49        float, int32_t, uint32_t, BASE_NS::Math::Mat3X3, BASE_NS::Math::Mat4X4, bool, CORE_NS::Entity,
50        CORE_NS::EntityReference>(handler, clone, prop);
51}
52
53bool IsGltfResource(BASE_NS::string_view uri, BASE_NS::string_view resourcePath)
54{
55    // Image uris loaded form a glTF are in a format like this: "file://model.gltf/images/0"
56
57    // There must be an identifier string at the end of the uri after the '/'.
58    const auto idSeparator = uri.find_last_of('/');
59    if (idSeparator == BASE_NS::string_view::npos) {
60        return false;
61    }
62
63    // We don't care what is after the separator, but it should be preceded by <resourcePath>" e.g. /images".
64    auto rest = uri.substr(0, idSeparator);
65    if (!rest.ends_with(resourcePath)) {
66        return false;
67    }
68    rest = rest.substr(0, rest.size() - resourcePath.size());
69
70    // Take the last 5 characters in lower case to check the extension.
71    if (rest.size() < 5) {
72        return false;
73    }
74    const auto extension = BASE_NS::string(rest.substr(rest.size() - 5, 5)).toLower();
75    if (!extension.ends_with(".glb") && !extension.ends_with(".gltf")) {
76        return false;
77    }
78
79    return true;
80}
81
82bool IsGltfImage(BASE_NS::string_view uri)
83{
84    return IsGltfResource(uri, "/images");
85}
86
87bool IsGltfMaterial(BASE_NS::string_view uri)
88{
89    return IsGltfResource(uri, "/materials");
90}
91
92class MaterialImpl : public META_NS::ConcreteBaseMetaObjectFwd<MaterialImpl, NodeImpl, SCENE_NS::ClassId::Material,
93                         SCENE_NS::IMaterial, ITextureStorage> {
94    static constexpr BASE_NS::string_view MATERIAL_COMPONENT_NAME = "MaterialComponent";
95
96    static constexpr BASE_NS::string_view CUSTOM_PREFIX = "MaterialComponent.customProperties.";
97    static constexpr size_t CUSTOM_PREFIX_SIZE = CUSTOM_PREFIX.size();
98    static constexpr BASE_NS::string_view TEXTURE_PREFIX = "MaterialComponent.TextureInfo.";
99    static constexpr size_t TEXTURE_PREFIX_SIZE = TEXTURE_PREFIX.size();
100
101    bool Build(const IMetadata::Ptr& data) override
102    {
103        bool ret = false;
104        if (ret = NodeImpl::Build(data); ret) {
105            // bind everything from material component, otherwise obey the wishes of other components
106            PropertyNameMask()[MATERIAL_COMPONENT_NAME] = {};
107            OnTypeChanged(); // initialize inputs
108        }
109        return ret;
110    }
111
112    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IMaterial, uint8_t, Type, 0)
113
114    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IMaterial, float, AlphaCutoff, 1.f)
115    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IMaterial, uint32_t, LightingFlags,
116        CORE3D_NS::MaterialComponent::LightingFlagBits::SHADOW_RECEIVER_BIT |
117            CORE3D_NS::MaterialComponent::LightingFlagBits::SHADOW_CASTER_BIT |
118            CORE3D_NS::MaterialComponent::LightingFlagBits::PUNCTUAL_LIGHT_RECEIVER_BIT |
119            CORE3D_NS::MaterialComponent::LightingFlagBits::INDIRECT_LIGHT_RECEIVER_BIT)
120    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IMaterial, SCENE_NS::IShader::Ptr, MaterialShader, {})
121    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IMaterial, SCENE_NS::IGraphicsState::Ptr, MaterialShaderState, {})
122    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IMaterial, SCENE_NS::IShader::Ptr, DepthShader, {})
123    META_IMPLEMENT_INTERFACE_PROPERTY(SCENE_NS::IMaterial, SCENE_NS::IGraphicsState::Ptr, DepthShaderState, {})
124
125    META_IMPLEMENT_INTERFACE_READONLY_PROPERTY(SCENE_NS::IMaterial, META_NS::IObject::Ptr, CustomProperties, {})
126    META_IMPLEMENT_INTERFACE_ARRAY_PROPERTY(SCENE_NS::IMaterial, SCENE_NS::ITextureInfo::Ptr, Inputs, {})
127    META_IMPLEMENT_INTERFACE_ARRAY_PROPERTY(SCENE_NS::IMaterial, SCENE_NS::ITextureInfo::Ptr, Textures, {})
128
129
130    bool updatingInputs_ { false };
131
132    void Activate() override
133    {
134        auto sceneHolder = SceneHolder();
135        if (!sceneHolder) {
136            return;
137        }
138
139        auto task = META_NS::MakeCallback<META_NS::ITaskQueueTask>(
140            [e = ecsObject_->GetEntity(), w = BASE_NS::weak_ptr(sceneHolder)] {
141                auto sh = w.lock();
142                if (sh) {
143                    sh->SetEntityActive(e, true);
144                }
145                return false;
146            });
147        sceneHolder->QueueEngineTask(task, false);
148    }
149
150    void Deactivate() override
151    {
152        auto sceneHolder = SceneHolder();
153        if (!sceneHolder) {
154            return;
155        }
156
157        auto task = META_NS::MakeCallback<META_NS::ITaskQueueTask>(
158            [e = ecsObject_->GetEntity(), w = BASE_NS::weak_ptr(sceneHolder)] {
159                auto sh = w.lock();
160                if (sh) {
161                    sh->SetEntityActive(e, false);
162                }
163                return false;
164            });
165        sceneHolder->QueueEngineTask(task, false);
166    }
167
168    void SetPath(const BASE_NS::string& path, const BASE_NS::string& name, CORE_NS::Entity entity) override
169    {
170        META_ACCESS_PROPERTY(Path)->SetValue(path);
171        META_ACCESS_PROPERTY(Name)->SetValue(name);
172
173        if (auto iscene = GetScene()) {
174            iscene->UpdateCachedReference(GetSelf<SCENE_NS::INode>());
175        }
176        if (auto scene = EcsScene()) {
177            SetStatus(SCENE_NS::INode::NODE_STATUS_CONNECTING);
178
179            initializeTaskToken_ = scene->AddEngineTask(MakeTask([me = BASE_NS::weak_ptr(GetSelf()),
180                                                                     materialName = name,
181                                                                     fullpath = path + name, entity]() {
182                if (auto self = static_pointer_cast<NodeImpl>(me.lock())) {
183                    if (auto sceneHolder = self->SceneHolder()) {
184                        CORE_NS::Entity materialEntity = entity;
185
186                        if (CORE_NS::EntityUtil::IsValid(materialEntity)) {
187                            SCENE_PLUGIN_VERBOSE_LOG("binding material: %s", materialName.c_str());
188                            if (auto proxyIf = interface_pointer_cast<SCENE_NS::IEcsProxyObject>(self->EcsObject())) {
189                                proxyIf->SetCommonListener(sceneHolder->GetCommonEcsListener());
190                            }
191                            self->EcsObject()->DefineTargetProperties(self->PropertyNameMask());
192                            self->EcsObject()->SetEntity(sceneHolder->GetEcs(), materialEntity);
193                            sceneHolder->QueueApplicationTask(
194                                MakeTask(
195                                    [fullpath](auto selfObject) {
196                                        if (auto self = static_pointer_cast<NodeImpl>(selfObject)) {
197                                            self->CompleteInitialization(fullpath);
198                                            self->SetStatus(SCENE_NS::INode::NODE_STATUS_CONNECTED);
199                                            if (auto node = interface_pointer_cast<SCENE_NS::INode>(selfObject)) {
200                                                META_NS::Invoke<META_NS::IOnChanged>(node->OnLoaded());
201                                            }
202                                        }
203                                        return false;
204                                    },
205                                    me),
206                                false);
207                        } else {
208                            CORE_LOG_W("Could not find '%s' material", fullpath.c_str());
209                            sceneHolder->QueueApplicationTask(
210                                MakeTask(
211                                    [](auto selfObject) {
212                                        if (auto self = static_pointer_cast<NodeImpl>(selfObject)) {
213                                            self->SetStatus(SCENE_NS::INode::NODE_STATUS_DISCONNECTED);
214                                        }
215                                        return false;
216                                    },
217                                    me),
218                                false);
219                        }
220                    }
221                }
222                return false;
223            }),
224                false);
225        }
226    }
227
228public: // ISerialization
229    void ApplyTextureInfoImage(size_t arrayIndex, SCENE_NS::ITextureInfo& info)
230    {
231        if (auto sceneHolder = SceneHolder()) {
232            bool shouldReset { true };
233            auto value = info.Image()->GetValue();
234            if (auto uriBitmap = interface_cast<SCENE_NS::IBitmap>(value)) {
235                auto uri = META_NS::GetValue(uriBitmap->Uri());
236                if (!uri.empty()) {
237                    shouldReset = false;
238
239                    if (IsGltfImage(uri)) {
240                        return;
241                    }
242
243                    sceneHolder->QueueEngineTask(
244                        MakeTask(
245                            [uri, entityId = EcsObject()->GetEntity().id, arrayIndex](auto sh) {
246                                CORE_NS::Entity target { entityId };
247                                auto image = sh->LoadImage(uri);
248                                sh->SetTexture(arrayIndex, target, image);
249
250                                return false;
251                            },
252                            sceneHolder),
253                        false);
254                }
255            }
256            if (shouldReset) {
257                sceneHolder->QueueEngineTask(MakeTask(
258                                                 [entity = EcsObject()->GetEntity(), arrayIndex](auto sh) {
259                                                     sh->SetTexture(arrayIndex, entity, {});
260                                                     return false;
261                                                 },
262                                                 sceneHolder),
263                    false);
264            }
265        }
266    }
267
268    void SubscribeToTextureInfo(size_t arrayIndex, SCENE_NS::ITextureInfo::Ptr info)
269    {
270        info->SetTextureSlotIndex(arrayIndex);
271
272        // EcsObject would provide use entity ref while we would like to use the enums to define which
273        // sampler to use Perhaps we create new factory method for scene-interface and then apply the direct
274        // biding from now on
275        GET_HOLDER(info)
276            .NewHandler(info->Sampler(), nullptr)
277            .Subscribe(info->Sampler(), [this, prop = info->Sampler(), arrayIndex]() {
278                if (auto sceneHolder = SceneHolder()) {
279                    sceneHolder->QueueEngineTask(
280                        MakeTask(
281                            [value = prop->GetValue(), entityId = EcsObject()->GetEntity().id, arrayIndex](auto sh) {
282                                CORE_NS::Entity target { entityId };
283                                sh->SetSampler(
284                                    arrayIndex, target, static_cast<SCENE_NS::ITextureInfo::SamplerId>(value));
285
286                                return false;
287                            },
288                            sceneHolder),
289                        false);
290                }
291            });
292
293        GET_HOLDER(info)
294            .NewHandler(info->Image(), nullptr)
295            .Subscribe(info->Image(),
296                [this, textureInfo = info.get(), arrayIndex]() { ApplyTextureInfoImage(arrayIndex, *textureInfo); });
297    }
298    /*
299    bool Export(
300        META_NS::Serialization::IExportContext& context, META_NS::Serialization::ClassPrimitive& value) const override
301    {
302        SCENE_PLUGIN_VERBOSE_LOG("MaterialImpl::%s %s", __func__, Name()->Get().c_str());
303
304        // This is not a shared material and therefore we will export it here.
305        return Fwd::Export(context, value);
306    }
307
308    bool Import(
309        META_NS::Serialization::IImportContext& context, const META_NS::Serialization::ClassPrimitive& value) override
310    {
311        META_ACCESS_PROPERTY(Inputs)->Reset();
312        META_ACCESS_PROPERTY(CustomProperties)->Reset();
313
314        if (!Fwd::Import(context, value)) {
315            return false;
316        }
317
318        if (Inputs()) {
319            // Subscribe all texture infos.
320            for (auto i = 0; i < Inputs()->Size(); ++i) {
321                auto info = Inputs()->Get(i);
322
323                auto textureSlotIndex = info->GetTextureSlotIndex();
324                if (textureSlotIndex == ~0u) {
325                    textureSlotIndex = i;
326                }
327
328                info->SetTextureSlotIndex(textureSlotIndex);
329            }
330        }
331
332        return true;
333    }
334    */
335    SCENE_NS::ITextureInfo::Ptr GetTextureInfoByIndex(
336        uint32_t index, BASE_NS::vector<SCENE_NS::ITextureInfo::Ptr>& textures)
337    {
338        // First, look from current inputs.
339        auto existingInfo = GetTextureInfo(index);
340        if (existingInfo) {
341            // Found existing info, we absolutely want to re-use it.
342            // This is to keep e.g. property animations working (they bind to this exact object).
343            auto it = std::find(textures.begin(), textures.end(), existingInfo);
344            if (it == textures.end()) {
345                // Add this info to textures list.
346                textures.push_back(existingInfo);
347            }
348            return existingInfo;
349        }
350
351        for (auto& info : textures) {
352            if (info->GetTextureSlotIndex() == index) {
353                return info;
354            }
355        }
356
357        return {};
358    }
359
360    SCENE_NS::ITextureInfo::Ptr BindTextureSlot(const BASE_NS::string_view& propName, META_NS::IMetadata::Ptr& meta,
361        BASE_NS::vector<SCENE_NS::ITextureInfo::Ptr>& textures)
362    {
363        SCENE_NS::ITextureInfo::Ptr result;
364
365        // fork out the identifier
366        size_t ix = TEXTURE_PREFIX_SIZE;
367        ix = propName.find('.', ix + 1);
368        size_t textureSlotId = 0;
369
370        if (ix == BASE_NS::string_view::npos) {
371            return result;
372        } else {
373            // how cool is this
374            for (int ii = TEXTURE_PREFIX_SIZE; ii < ix; ii++) {
375                textureSlotId += (propName[ii] - '0') * pow(10, (ix - ii - 1)); // 10 exponent
376            }
377        }
378
379        auto textureSlotIndex = textureSlotId - 1;
380
381        bool applyPropertyToEcs = false;
382        if (textureSlotId) {
383            auto info = GetTextureInfoByIndex(textureSlotIndex, textures);
384            if (!info) {
385                info = GetObjectRegistry().Create<SCENE_NS::ITextureInfo>(SCENE_NS::ClassId::TextureInfo);
386                if (!info) {
387                    return {};
388                }
389
390                info->SetTextureSlotIndex(textureSlotIndex);
391                textures.push_back(info);
392            }
393
394            GET_HOLDER(info).SetSceneHolder(SceneHolder());
395
396            {
397                // Update name.
398                BASE_NS::string textureSlotName(propName.substr(ix + 1));
399                size_t dotIndex = textureSlotName.find('.');
400                if (dotIndex != BASE_NS::string_view::npos && dotIndex != 0) {
401                    textureSlotName = textureSlotName.substr(0, dotIndex);
402                }
403                if (GetValue(info->Name()) != textureSlotName) {
404                    SetValue(info->Name(), textureSlotName);
405                }
406            }
407
408            // progress one more dot
409            ix = propName.find('.', ix + 1);
410
411            BASE_NS::string propertyName(propName.substr(ix));
412            if (propertyName == ".factor") {
413                BindChanges<BASE_NS::Math::Vec4>(GET_HOLDER(info), info->Factor(), meta, propName);
414            } else if (propertyName == ".transform.translation") {
415                BindChanges<BASE_NS::Math::Vec2>(GET_HOLDER(info), info->Translation(), meta, propName);
416            } else if (propertyName == ".transform.rotation") {
417                BindChanges<float>(GET_HOLDER(info), info->Rotation(), meta, propName);
418            } else if (propertyName == ".transform.scale") {
419                BindChanges<BASE_NS::Math::Vec2>(GET_HOLDER(info), info->Scale(), meta, propName);
420            } else if (propertyName == ".image") {
421                auto prop = meta->GetPropertyByName(propName);
422
423                // Here we will initialize the image property.
424                if (info->Image()->IsValueSet()) {
425                    // If the property value is set by the user, we will propagate it to ecs.
426                    ApplyTextureInfoImage(textureSlotIndex, *info);
427                } else {
428                    // If the property value is not set, we will fetch it from ecs.
429                    CheckImageHandle(textureSlotIndex, BASE_NS::weak_ptr<SCENE_NS::ITextureInfo>(info));
430                }
431
432                prop->OnChanged()->AddHandler(META_NS::MakeCallback<META_NS::IOnChanged>(
433                    [this, textureSlotIndex, w_info = Base::weak_ptr(info)]() {
434                        CheckImageHandle(textureSlotIndex, w_info);
435                    }));
436                GET_HOLDER(info).MarkRelated(info->Image(), prop);
437
438                SubscribeToTextureInfo(textureSlotIndex, info);
439            } else if (propertyName == ".sampler") {
440                GET_HOLDER(info).MarkRelated(info->Sampler(), meta->GetPropertyByName(propName));
441            }
442
443            result = info;
444        }
445
446        return result;
447    }
448
449    void CheckImageHandle(size_t arrayIx, BASE_NS::weak_ptr<SCENE_NS::ITextureInfo> w_info)
450    {
451        // Queue engine task
452        if (auto sceneHolder = SceneHolder()) {
453            sceneHolder->QueueEngineTask(
454                MakeTask(
455                    [entity = EcsObject()->GetEntity(), arrayIx, w_info](SceneHolder::Ptr sh) {
456                        // resolve entity
457                        CORE_NS::Entity image;
458                        BASE_NS::string name;
459                        RENDER_NS::GpuImageDesc imageDesc;
460                        RENDER_NS::RenderHandleReference handle;
461                        if (sh->GetImageEntity(entity, arrayIx, image)) {
462                            // get entity by uri
463                            sh->GetEntityUri(image, name);
464                            sh->GetImageHandle(image, handle, imageDesc);
465                        }
466                        SCENE_PLUGIN_VERBOSE_LOG("%s: resolved name: %s", __func__, name.c_str());
467                        // if we get something valid out of those
468                        // let the toolkit side check if we have a bitmap and its uri matches
469                        if (!name.empty()) {
470                            sh->QueueEngineTask(
471                                MakeTask(
472                                    [name](auto info, RENDER_NS::RenderHandleReference handle, auto size) {
473                                        if (auto bitmap = META_NS::GetValue(info->Image())) {
474                                            if (auto uriBitmap = interface_cast<SCENE_NS::IBitmap>(bitmap)) {
475                                                if (auto uri = META_NS::GetValue(uriBitmap->Uri());
476                                                    !uri.empty() && (uri == name)) {
477                                                    return false;
478                                                }
479                                            }
480                                        }
481                                        auto uriBitmap = META_NS::GetObjectRegistry().Create<SCENE_NS::IBitmap>(
482                                            SCENE_NS::ClassId::Bitmap);
483                                        META_NS::SetValue(uriBitmap->Uri(), name);
484                                        // TODO: should parse the renderhandle from the entity and set it to the bitmap
485                                        // also. as we "create" it from the ecs, so the image SHOULD already be loaded.
486                                        uriBitmap->SetRenderHandle(handle, size);
487                                        info->Image()->SetDefaultValue(uriBitmap, true);
488                                        return false;
489                                    },
490                                    w_info, handle, BASE_NS::Math::UVec2(imageDesc.width, imageDesc.height)),
491                                false);
492                        }
493                        return false;
494                    },
495                    sceneHolder),
496                false);
497        }
498    }
499
500    SCENE_NS::ITextureInfo::Ptr GetTextureInfo(size_t ix) const override
501    {
502        if (Inputs()) {
503            for (auto& info : Inputs()->GetValue()) {
504                if (info->GetTextureSlotIndex() == ix) {
505                    return info;
506                }
507            }
508        }
509
510        return SCENE_NS::ITextureInfo::Ptr {};
511    }
512
513    void UpdateInputProperties()
514    {
515        auto type = META_NS::GetValue(META_ACCESS_PROPERTY(Type));
516        bool includeDefaultMappings =
517            (type == SCENE_NS::IMaterial::METALLIC_ROUGHNESS || type == SCENE_NS::IMaterial::SPECULAR_GLOSSINESS ||
518                type == SCENE_NS::IMaterial::UNLIT || type == SCENE_NS::IMaterial::UNLIT_SHADOW_ALPHA);
519
520        // Base Color
521        if (auto meta =
522                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::BASE_COLOR))) {
523            if (auto color = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
524                if (!includeDefaultMappings) {
525                    meta->RemoveProperty(color);
526                }
527            } else {
528                auto c = META_NS::ConstructProperty<SCENE_NS::Color>(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR);
529                meta->AddProperty(c);
530            }
531        }
532
533        // Normal
534        if (auto meta =
535                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::NORMAL))) {
536            if (auto scale = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_NORMAL_SCALE)) {
537                if (!includeDefaultMappings) {
538                    meta->RemoveProperty(scale);
539                }
540            } else {
541                auto s = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_NORMAL_SCALE);
542                meta->AddProperty(s);
543            }
544        }
545
546        //  Material
547        if (auto meta =
548                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::MATERIAL))) {
549            // SCENE_NS::IMaterial::METALLIC_ROUGHNESS
550            if (auto roughness = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS)) {
551                if (type != SCENE_NS::IMaterial::METALLIC_ROUGHNESS) {
552                    meta->RemoveProperty(roughness);
553                }
554            } else if (type == SCENE_NS::IMaterial::METALLIC_ROUGHNESS) {
555                auto roughness = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS);
556                meta->AddProperty(roughness);
557            }
558            if (auto metallic = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_METALLIC)) {
559                if (type != SCENE_NS::IMaterial::METALLIC_ROUGHNESS) {
560                    meta->RemoveProperty(metallic);
561                }
562            } else if (type == SCENE_NS::IMaterial::METALLIC_ROUGHNESS) {
563                auto metallic = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_METALLIC);
564                meta->AddProperty(metallic);
565            }
566            if (auto reflectance = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_REFLECTANCE)) {
567                if (type != SCENE_NS::IMaterial::METALLIC_ROUGHNESS) {
568                    meta->RemoveProperty(reflectance);
569                }
570            } else if (type == SCENE_NS::IMaterial::METALLIC_ROUGHNESS) {
571                auto reflectance = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_REFLECTANCE);
572                meta->AddProperty(reflectance);
573            }
574            // SCENE_NS::IMaterial::SPECULAR_GLOSSINESS
575            if (auto colorRGB = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
576                if (type != SCENE_NS::IMaterial::SPECULAR_GLOSSINESS) {
577                    meta->RemoveProperty(colorRGB);
578                }
579            } else if (type == SCENE_NS::IMaterial::SPECULAR_GLOSSINESS) {
580                auto colorRGB = META_NS::ConstructProperty<SCENE_NS::Color>(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR);
581                meta->AddProperty(colorRGB);
582            }
583            if (auto glossiness = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_GLOSSINESS)) {
584                if (type != SCENE_NS::IMaterial::SPECULAR_GLOSSINESS) {
585                    meta->RemoveProperty(glossiness);
586                }
587            } else if (type == SCENE_NS::IMaterial::SPECULAR_GLOSSINESS) {
588                auto glossiness = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_GLOSSINESS);
589                meta->AddProperty(glossiness);
590            }
591        }
592
593        // Emissive
594        if (auto meta =
595                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::EMISSIVE))) {
596            if (auto color = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
597                if (!includeDefaultMappings) {
598                    meta->RemoveProperty(color);
599                }
600            } else {
601                auto c = META_NS::ConstructProperty<SCENE_NS::Color>(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR);
602                meta->AddProperty(c);
603            }
604            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_INTENSITY)) {
605                if (!includeDefaultMappings) {
606                    meta->RemoveProperty(prop);
607                }
608            } else {
609                auto p = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_INTENSITY);
610                meta->AddProperty(p);
611            }
612        }
613
614        // Ambient Occlusion
615        if (auto meta = interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::AO))) {
616            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_STRENGTH)) {
617                if (!includeDefaultMappings) {
618                    meta->RemoveProperty(prop);
619                }
620            } else {
621                auto p = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_STRENGTH);
622                meta->AddProperty(p);
623            }
624        }
625
626        // Clear Coat
627        if (auto meta =
628                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::CLEARCOAT))) {
629            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_INTENSITY)) {
630                if (!includeDefaultMappings) {
631                    meta->RemoveProperty(prop);
632                }
633            } else {
634                auto p = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_INTENSITY);
635                meta->AddProperty(p);
636            }
637        }
638
639        // Clear Coat Roughness
640        if (auto meta = interface_pointer_cast<META_NS::IMetadata>(
641                GetTextureInfo(CORE3D_NS::MaterialComponent::CLEARCOAT_ROUGHNESS))) {
642            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS)) {
643                if (!includeDefaultMappings) {
644                    meta->RemoveProperty(prop);
645                }
646            } else {
647                auto p = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS);
648                meta->AddProperty(p);
649            }
650        }
651
652        // Clear Coat Normal
653        if (auto meta = interface_pointer_cast<META_NS::IMetadata>(
654                GetTextureInfo(CORE3D_NS::MaterialComponent::CLEARCOAT_NORMAL))) {
655            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_NORMAL_SCALE)) {
656                if (!includeDefaultMappings) {
657                    meta->RemoveProperty(prop);
658                }
659            } else {
660                auto p = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_NORMAL_SCALE);
661                meta->AddProperty(p);
662            }
663        }
664
665        // Sheen
666        if (auto meta =
667                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::SHEEN))) {
668            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
669                if (!includeDefaultMappings) {
670                    meta->RemoveProperty(prop);
671                }
672            } else {
673                auto p = META_NS::ConstructProperty<SCENE_NS::Color>(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR);
674                meta->AddProperty(p);
675            }
676            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS)) {
677                if (!includeDefaultMappings) {
678                    meta->RemoveProperty(prop);
679                }
680            } else {
681                auto p = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS);
682                meta->AddProperty(p);
683            }
684        }
685
686        // TRANSMISSION
687        if (auto meta = interface_pointer_cast<META_NS::IMetadata>(
688                GetTextureInfo(CORE3D_NS::MaterialComponent::TRANSMISSION))) {
689            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_TRANSMISSION)) {
690                if (!includeDefaultMappings) {
691                    meta->RemoveProperty(prop);
692                }
693            } else {
694                auto p = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_TRANSMISSION);
695                meta->AddProperty(p);
696            }
697        }
698
699        // Specular
700        if (auto meta =
701                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::SPECULAR))) {
702            if (auto color = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
703                if (!includeDefaultMappings) {
704                    meta->RemoveProperty(color);
705                }
706            } else {
707                auto c = META_NS::ConstructProperty<SCENE_NS::Color>(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR);
708                meta->AddProperty(c);
709            }
710            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_STRENGTH)) {
711                if (!includeDefaultMappings) {
712                    meta->RemoveProperty(prop);
713                }
714            } else {
715                auto p = META_NS::ConstructProperty<float>(SCENE_NS::IMaterial::MAPPED_INPUTS_STRENGTH);
716                meta->AddProperty(p);
717            }
718        }
719    }
720
721    void BindInputProperties()
722    {
723        auto type = META_NS::GetValue(META_ACCESS_PROPERTY(Type));
724
725        // Base Color
726        if (auto meta =
727                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::BASE_COLOR))) {
728            if (auto color = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
729                ConvertBindChanges<SCENE_NS::Color, BASE_NS::Math::Vec4>(GET_HOLDER(meta),
730                    META_NS::Property<SCENE_NS::Color>(color), meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR);
731            }
732        }
733
734        // Normal
735        if (auto meta =
736                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::NORMAL))) {
737            if (auto scale = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_NORMAL_SCALE)) {
738                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(scale), meta,
739                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 0);
740            }
741        }
742
743        //  Material
744        if (auto meta =
745                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::MATERIAL))) {
746            // SCENE_NS::IMaterial::METALLIC_ROUGHNESS
747            if (type == SCENE_NS::IMaterial::METALLIC_ROUGHNESS) {
748                if (auto roughness = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS)) {
749                    BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(roughness),
750                        meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 1);
751                }
752                if (auto metallic = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_METALLIC)) {
753                    BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(metallic),
754                        meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 2); // 2 factor
755                }
756                if (auto reflectance = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_REFLECTANCE)) {
757                    BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(reflectance),
758                        meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 3); // 3 factor
759                }
760            }
761
762            // SCENE_NS::IMaterial::SPECULAR_GLOSSINESS
763            if (type == SCENE_NS::IMaterial::SPECULAR_GLOSSINESS) {
764                if (auto colorRGB = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
765                    BASE_NS::vector<size_t> slots = { 0, 0, 1, 1, 2, 2 };
766                    BindSlottedChanges<BASE_NS::Math::Vec4, SCENE_NS::Color>(GET_HOLDER(meta),
767                        META_NS::Property<SCENE_NS::Color>(colorRGB), meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR,
768                        slots);
769                }
770                if (auto glossiness = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_GLOSSINESS)) {
771                    BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(glossiness),
772                        meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 3);
773                }
774            }
775        }
776
777        // Emissive
778        if (auto meta =
779                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::EMISSIVE))) {
780            if (auto color = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
781                BASE_NS::vector<size_t> slots = { 0, 0, 1, 1, 2, 2 };
782                BindSlottedChanges<BASE_NS::Math::Vec4, SCENE_NS::Color>(GET_HOLDER(meta),
783                    META_NS::Property<SCENE_NS::Color>(color), meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR,
784                    slots);
785            }
786            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_INTENSITY)) {
787                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(prop), meta,
788                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 3);
789            }
790        }
791
792        // Ambient Occlusion
793        if (auto meta = interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::AO))) {
794            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_STRENGTH)) {
795                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(prop), meta,
796                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 0);
797            }
798        }
799
800        // Clear Coat
801        if (auto meta =
802                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::CLEARCOAT))) {
803            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_INTENSITY)) {
804                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(prop), meta,
805                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 0);
806            }
807        }
808
809        // Clear Coat Roughness
810        if (auto meta = interface_pointer_cast<META_NS::IMetadata>(
811                GetTextureInfo(CORE3D_NS::MaterialComponent::CLEARCOAT_ROUGHNESS))) {
812            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS)) {
813                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(prop), meta,
814                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 1);
815            }
816        }
817
818        // Clear Coat Normal
819        if (auto meta = interface_pointer_cast<META_NS::IMetadata>(
820                GetTextureInfo(CORE3D_NS::MaterialComponent::CLEARCOAT_NORMAL))) {
821            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_NORMAL_SCALE)) {
822                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(prop), meta,
823                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 0);
824            }
825        }
826
827        // Sheen
828        if (auto meta =
829                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::SHEEN))) {
830            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
831                BASE_NS::vector<size_t> slots = { 0, 0, 1, 1, 2, 2 };
832                BindSlottedChanges<BASE_NS::Math::Vec4, SCENE_NS::Color>(GET_HOLDER(meta),
833                    META_NS::Property<SCENE_NS::Color>(prop), meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, slots);
834            }
835            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_ROUGHNESS)) {
836                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(prop), meta,
837                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 3);
838            }
839        }
840
841        // TRANSMISSION
842        if (auto meta = interface_pointer_cast<META_NS::IMetadata>(
843                GetTextureInfo(CORE3D_NS::MaterialComponent::TRANSMISSION))) {
844            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_TRANSMISSION)) {
845                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(prop), meta,
846                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 0);
847            }
848        }
849
850        // Specular
851        if (auto meta =
852                interface_pointer_cast<META_NS::IMetadata>(GetTextureInfo(CORE3D_NS::MaterialComponent::SPECULAR))) {
853            if (auto color = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_COLOR)) {
854                BASE_NS::vector<size_t> slots = { 0, 0, 1, 1, 2, 2 };
855                BindSlottedChanges<BASE_NS::Math::Vec4, SCENE_NS::Color>(GET_HOLDER(meta),
856                    META_NS::Property<SCENE_NS::Color>(color), meta, SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR,
857                    slots);
858            }
859            if (auto prop = meta->GetPropertyByName(SCENE_NS::IMaterial::MAPPED_INPUTS_STRENGTH)) {
860                BindChanges<BASE_NS::Math::Vec4, float>(GET_HOLDER(meta), META_NS::Property<float>(prop), meta,
861                    SCENE_NS::IMaterial::MAPPED_INPUTS_FACTOR, 3); // 3 factor
862            }
863        }
864    }
865
866    void OnTypeChanged()
867    {
868        UpdateInputProperties();
869        BindInputProperties();
870    }
871
872    // We intend to define the object only on demand
873    // If we have a previous object, we could try to preserve it
874    void UpdateCustomProperties()
875    {
876        // Store and remove old custom properties.
877        BASE_NS::vector<META_NS::IProperty::Ptr> oldCustomProperties;
878
879        auto properties = interface_pointer_cast<META_NS::IMetadata>(META_ACCESS_PROPERTY(CustomProperties)->GetValue());
880        if (properties) {
881            oldCustomProperties = properties->GetAllProperties();
882            properties->GetPropertyContainer()->RemoveAll();
883        }
884
885        // Find new custom properties.
886        BASE_NS::vector<META_NS::IProperty::Ptr> newCustomProperties;
887
888        auto meta = interface_pointer_cast<META_NS::IMetadata>(ecsObject_);
889        if (meta) {
890            // Collect all custom properties from ECS object.
891            auto metaProps = meta->GetAllProperties();
892            for (auto&& prop : metaProps) {
893                if (prop->GetName().compare(0, CUSTOM_PREFIX_SIZE, CUSTOM_PREFIX) == 0) {
894                    newCustomProperties.push_back(prop);
895                }
896            }
897        }
898
899        // If there are no custom properties, then reset.
900        if (newCustomProperties.empty()) {
901            META_ACCESS_PROPERTY(CustomProperties)->Reset();
902            return;
903        }
904
905        // Otherwise ensure that we have container for the custom properties.
906        if (!properties) {
907            properties = GetObjectRegistry().Create<META_NS::IMetadata>(SCENE_NS::ClassId::CustomPropertiesHolder);
908            META_ACCESS_PROPERTY(CustomProperties)->SetValue(interface_pointer_cast<META_NS::IObject>(properties));
909        }
910
911        GET_HOLDER(properties).SetSceneHolder(sceneHolder_.lock());
912
913        // Then create and bind all the custom properties.
914        for (auto& prop : newCustomProperties) {
915            // This is the new custom property.
916            META_NS::IProperty::Ptr property;
917
918            BASE_NS::string propertyName(prop->GetName().substr(CUSTOM_PREFIX_SIZE));
919
920            // Try to re-use the old custom property if we have one, it may have a meaningful value set.
921            for (auto& existing : oldCustomProperties) {
922                if (existing->GetName() == propertyName && existing->GetTypeId() == prop->GetTypeId()) {
923                    property = existing;
924                    break;
925                }
926            }
927
928            if (!property) {
929                // Clone property.
930                property = META_NS::DuplicatePropertyType(META_NS::GetObjectRegistry(), prop, propertyName);
931            }
932
933            // Add it to target.
934            properties->AddProperty(property);
935
936            // Bind it to ecs property.
937            BindMetaProperty(GET_HOLDER(properties), property, prop);
938        }
939    }
940
941    void UpdateInputs(bool forceRecursive = false, bool forceFullReset = true)
942    {
943        if (updatingInputs_ && !forceRecursive) {
944            return;
945        }
946
947        if (forceFullReset) {
948            updatingInputs_ = true;
949
950            propHandler_.Reset();
951            META_ACCESS_PROPERTY(Inputs)->Reset();
952            META_ACCESS_PROPERTY(CustomProperties)->Reset();
953
954            auto entity = EcsObject()->GetEntity();
955            auto ecs = EcsObject()->GetEcs();
956
957            // Reset the object
958            EcsObject()->BindObject(nullptr, entity);
959
960            auto scene = EcsScene();
961            auto ecsObject = EcsObject();
962
963            Initialize(
964                scene, ecsObject, {}, META_NS::GetValue(Path()), META_NS::GetValue(Name()), sceneHolder_, entity);
965
966            updatingInputs_ = false;
967        } else {
968            // This will potentially go wrong if the proxy properties contain set values
969            // The values previously set will replace the values from engine
970            if (UpdateAllInputProperties()) {
971                updatingInputs_ = true;
972                META_ACCESS_PROPERTY(Inputs)->Reset();
973                META_ACCESS_PROPERTY(CustomProperties)->Reset();
974                CompleteInitialization(META_NS::GetValue(Name()));
975                updatingInputs_ = false;
976            }
977        }
978    }
979
980    static constexpr BASE_NS::string_view MATERIAL_SHADER_NAME { "MaterialComponent.materialShader.shader" };
981    static constexpr BASE_NS::string_view DEPTH_SHADER_NAME { "MaterialComponent.depthShader.shader" };
982
983    SCENE_NS::ITextureInfo::Ptr FindTextureInfo(BASE_NS::string_view name)
984    {
985        if (META_ACCESS_PROPERTY(Inputs)) {
986            for (auto& info : Inputs()->GetValue()) {
987                if (META_NS::GetValue(info->Name()) == name) {
988                    return info;
989                }
990            }
991        }
992        return {};
993    }
994
995    void CopyTextureInfoProperties(SCENE_NS::ITextureInfo::Ptr from, SCENE_NS::ITextureInfo::Ptr to)
996    {
997        if (from->Factor()->IsValueSet()) {
998            to->Factor()->SetValue(from->Factor()->GetValue());
999        }
1000
1001        if (from->Rotation()->IsValueSet()) {
1002            to->Rotation()->SetValue(from->Rotation()->GetValue());
1003        }
1004
1005        if (from->Scale()->IsValueSet()) {
1006            to->Scale()->SetValue(from->Scale()->GetValue());
1007        }
1008
1009        if (from->Translation()->IsValueSet()) {
1010            to->Translation()->SetValue(from->Translation()->GetValue());
1011        }
1012
1013        // Sampler enumeration works now using enums, it should follow uri
1014        // but uri information is not available from engine yet
1015        if (from->Sampler()->IsValueSet()) {
1016            to->Sampler()->SetValue(from->Sampler()->GetValue());
1017        }
1018
1019        // image goes through uri implementation
1020        if (auto data = META_NS::GetValue(from->Image())) {
1021            to->Image()->SetValue(from->Image()->GetValue());
1022        }
1023    }
1024
1025    bool SynchronizeInputsFromMetadata()
1026    {
1027        auto meta = interface_pointer_cast<META_NS::IMetadata>(ecsObject_);
1028        if (!meta) {
1029            return false;
1030        }
1031        // Texture slots need to be bound before other properties
1032	BASE_NS::vector<SCENE_NS::ITextureInfo::Ptr> textures;
1033	auto prop =
1034            meta->GetArrayPropertyByName<CORE3D_NS::MaterialComponent::TextureInfo>("MaterialComponent.textures");
1035	// slot names (unsure-what-they-were-before-but-matches-core3d-enum)
1036	const char* TextureIndexName[] = { "BASE_COLOR", "NORMAL", "MATERIAL", "EMISSIVE", "AO", "CLEARCOAT",
1037	    "CLEARCOAT_ROUGHNESS", "CLEARCOAT_NORMAL", "SHEEN", "TRANSMISSION", "SPECULAR" };
1038	auto v = prop->GetValue();
1039	auto shp = SceneHolder();
1040	for (auto t : v) {
1041	    int textureSlotIndex = textures.size();
1042	    // create wrapping things..
1043	    auto info = GetTextureInfoByIndex(textureSlotIndex, textures);
1044	    if (!info) {
1045	        auto& obr = GetObjectRegistry();
1046		auto params = obr.ConstructMetadata();
1047		using IntfPtr = META_NS::SharedPtrIInterface;
1048		params->AddProperty(META_NS::ConstructProperty<META_NS::IProperty::Ptr>(
1049		    "textureInfoArray", nullptr, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
1050		params->AddProperty(META_NS::ConstructProperty<uint32_t>(
1051		    "textureSlotIndex", 0, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
1052		params->AddProperty(META_NS::ConstructProperty<uintptr_t>(
1053		    "sceneHolder", 0, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
1054
1055		// yes this is ugly.
1056		params->GetPropertyByName<uintptr_t>("sceneHolder")->SetValue((uintptr_t)&shp);
1057		params->GetPropertyByName<IntfPtr>("textureInfoArray")
1058		    ->SetValue(interface_pointer_cast<CORE_NS::IInterface>(prop.GetProperty()));
1059		params->GetPropertyByName<uint32_t>("textureSlotIndex")->SetValue(textureSlotIndex);
1060		info = obr.Create<SCENE_NS::ITextureInfo>(SCENE_NS::ClassId::TextureInfo, params);
1061		if (!info) {
1062		    return {};
1063		}
1064
1065		info->SetTextureSlotIndex(textureSlotIndex);
1066		textures.push_back(info);
1067	    } else {
1068	        textures.push_back(nullptr);
1069	    }
1070	}
1071
1072        BASE_NS::vector<SCENE_NS::ITextureInfo::Ptr> prevInputs;
1073        if (META_ACCESS_PROPERTY(Inputs)) {
1074            auto size = META_ACCESS_PROPERTY(Inputs)->GetSize();
1075            for (size_t ii = 0; ii < size; ii++) {
1076                prevInputs.push_back(META_ACCESS_PROPERTY(Inputs)->GetValueAt(ii));
1077            }
1078        }
1079
1080        META_ACCESS_PROPERTY(Inputs)->Reset();
1081
1082        // Sort texture infos.
1083        std::sort(textures.begin(), textures.end(), [](const auto& a, const auto& b) {
1084            // Sort based on texture-slot index.
1085            return a->GetTextureSlotIndex() < b->GetTextureSlotIndex();
1086        });
1087
1088        // Assign to property.
1089        Inputs()->SetValue(textures);
1090
1091        // Copy values from old inputs to new ones.
1092        for (auto& from : prevInputs) {
1093            if (auto to = FindTextureInfo(META_NS::GetValue(from->Name()))) {
1094                CopyTextureInfoProperties(from, to);
1095            }
1096        }
1097
1098        return true;
1099    }
1100
1101    // return true if something changes
1102    bool UpdateAllInputProperties()
1103    {
1104        auto meta = interface_pointer_cast<META_NS::IMetadata>(EcsObject());
1105        if (!meta) {
1106            return false;
1107        }
1108
1109        auto oldCount = meta->GetPropertyContainer()->GetSize();
1110
1111        // This updates all properties from ecs and detaches properties that do not exist any more.
1112        EcsObject()->DefineTargetProperties(PropertyNameMask());
1113
1114        auto newCount = meta->GetPropertyContainer()->GetSize();
1115
1116        auto allProperties = meta->GetAllProperties();
1117
1118        // if we add or remove something these all cannot match
1119        return !((newCount == oldCount) && (oldCount != meta->GetPropertyContainer()->GetSize()));
1120    }
1121
1122    bool CompleteInitialization(const BASE_NS::string& path) override
1123    {
1124        if (!NodeImpl::CompleteInitialization(path)) {
1125            return false;
1126        }
1127
1128        auto meta = interface_pointer_cast<META_NS::IMetadata>(ecsObject_);
1129        if (!meta) {
1130            return false;
1131        }
1132
1133
1134        if (DepthShader()->GetValue()) {
1135            SceneHolder()->SetShader(
1136                EcsObject()->GetEntity(), SceneHolder::ShaderType::DEPTH_SHADER, DepthShader()->GetValue());
1137        } else {
1138            // Try to introspect depth shader from material.
1139            auto shader = SceneHolder()->GetShader(EcsObject()->GetEntity(), SceneHolder::ShaderType::DEPTH_SHADER);
1140            if (shader) {
1141                DepthShader()->SetValue(shader);
1142            }
1143        }
1144
1145        if (DepthShaderState()->GetValue()) {
1146            SceneHolder()->SetGraphicsState(
1147                EcsObject()->GetEntity(), SceneHolder::ShaderType::DEPTH_SHADER, DepthShaderState()->GetValue());
1148        } else {
1149            // Try to introspect depth shader from material.
1150            auto state =
1151                SceneHolder()->GetGraphicsState(EcsObject()->GetEntity(), SceneHolder::ShaderType::DEPTH_SHADER);
1152            if (state) {
1153                DepthShaderState()->SetValue(state);
1154            }
1155        }
1156
1157        if (MaterialShader()->GetValue()) {
1158            SceneHolder()->SetShader(
1159                EcsObject()->GetEntity(), SceneHolder::ShaderType::MATERIAL_SHADER, MaterialShader()->GetValue());
1160        } else {
1161            // Try to introspect material shader from material.
1162            auto shader = SceneHolder()->GetShader(EcsObject()->GetEntity(), SceneHolder::ShaderType::MATERIAL_SHADER);
1163            if (shader) {
1164                MaterialShader()->SetValue(shader);
1165            }
1166        }
1167
1168        if (MaterialShaderState()->GetValue()) {
1169            SceneHolder()->SetGraphicsState(
1170                EcsObject()->GetEntity(), SceneHolder::ShaderType::MATERIAL_SHADER, MaterialShaderState()->GetValue());
1171        } else {
1172            // Try to introspect depth shader from material.
1173            auto state =
1174                SceneHolder()->GetGraphicsState(EcsObject()->GetEntity(), SceneHolder::ShaderType::MATERIAL_SHADER);
1175            if (state) {
1176                MaterialShaderState()->SetValue(state);
1177            }
1178        }
1179
1180        // Shader may have changed, so update all properties.
1181        UpdateAllInputProperties();
1182
1183        // Properties up-to-date, synchronize all inputs.
1184        SynchronizeInputsFromMetadata();
1185
1186        propHandler_.NewHandler(nullptr, nullptr).Subscribe(META_ACCESS_PROPERTY(Type), [this]() { OnTypeChanged(); });
1187
1188        BindChanges(propHandler_, META_ACCESS_PROPERTY(Type), meta, "MaterialComponent.type");
1189
1190        // Shader will either come as an entity ref from the engine, or from serialized info. have a listener
1191        // that attaches the new ecs object to entity if one appears
1192        propHandler_.NewHandler(nullptr, nullptr).Subscribe(DepthShader(), [this]() {
1193            SceneHolder()->SetShader(
1194                EcsObject()->GetEntity(), SceneHolder::ShaderType::DEPTH_SHADER, DepthShader()->GetValue());
1195            UpdateInputs();
1196        });
1197
1198        propHandler_.NewHandler(nullptr, nullptr).Subscribe(MaterialShader(), [this]() {
1199            // Material shader has changed.
1200            SceneHolder()->SetShader(
1201                EcsObject()->GetEntity(), SceneHolder::ShaderType::MATERIAL_SHADER, MaterialShader()->GetValue());
1202            UpdateInputs();
1203        });
1204
1205        propHandler_.NewHandler(nullptr, nullptr).Subscribe(DepthShaderState(), [this]() {
1206            SceneHolder()->SetGraphicsState(
1207                EcsObject()->GetEntity(), SceneHolder::ShaderType::DEPTH_SHADER, DepthShaderState()->GetValue());
1208        });
1209
1210        propHandler_.NewHandler(nullptr, nullptr).Subscribe(MaterialShaderState(), [this]() {
1211            // Material shader has changed.
1212            SceneHolder()->SetGraphicsState(
1213                EcsObject()->GetEntity(), SceneHolder::ShaderType::MATERIAL_SHADER, MaterialShaderState()->GetValue());
1214        });
1215
1216        propHandler_.MarkRelated(MaterialShader(), meta->GetPropertyByName("MaterialComponent.materialShader.shader"));
1217        propHandler_.MarkRelated(DepthShader(), meta->GetPropertyByName("MaterialComponent.depthShader.shader"));
1218        propHandler_.MarkRelated(
1219            MaterialShaderState(), meta->GetPropertyByName("MaterialComponent.materialShader.graphicsState"));
1220        propHandler_.MarkRelated(
1221            DepthShaderState(), meta->GetPropertyByName("MaterialComponent.depthShader.graphicsState"));
1222
1223        // make sure that inputs are up to date
1224        OnTypeChanged();
1225
1226        // Update custom properties.
1227        UpdateCustomProperties();
1228
1229        BindChanges(propHandler_, META_ACCESS_PROPERTY(AlphaCutoff), meta, "MaterialComponent.alphaCutoff");
1230        BindChanges(propHandler_, META_ACCESS_PROPERTY(LightingFlags), meta, "MaterialComponent.materialLightingFlags");
1231        return true;
1232    }
1233
1234    bool BuildChildren(SCENE_NS::INode::BuildBehavior) override
1235    {
1236        // in typical cases we should not have children
1237        if (META_NS::GetValue(META_ACCESS_PROPERTY(Status)) == SCENE_NS::INode::NODE_STATUS_CONNECTED) {
1238            SetStatus(SCENE_NS::INode::NODE_STATUS_FULLY_CONNECTED);
1239            META_NS::Invoke<META_NS::IOnChanged>(OnBound());
1240            bound_ = true;
1241        }
1242        return true;
1243    }
1244
1245    void SetImage(SCENE_NS::IBitmap::Ptr bitmap, BASE_NS::string_view textureSlot) override
1246    {
1247        auto size = Inputs()->GetSize();
1248        for (size_t ii = 0; ii < size; ii++) {
1249            if (META_NS::GetValue(Inputs()->GetValueAt(ii)->Name()).compare(textureSlot) == 0) {
1250                SetImage(bitmap, ii);
1251                break;
1252            }
1253        }
1254    }
1255
1256    void SetImage(SCENE_NS::IBitmap::Ptr bitmap, size_t index) override
1257    {
1258        if (bitmap) {
1259            auto status = META_NS::GetValue(bitmap->Status());
1260            if (status == SCENE_NS::IBitmap::BitmapStatus::COMPLETED) {
1261                if (auto sceneHolder = SceneHolder()) {
1262                    sceneHolder->QueueEngineTask(
1263                        MakeTask(
1264                            [bitmap, index, weakSelf = BASE_NS::weak_ptr(GetSelf())](auto sh) {
1265                                CORE_NS::Entity imageEntity = sh->BindUIBitmap(bitmap, true);
1266                                auto image = sh->GetEcs()->GetEntityManager().GetReferenceCounted(imageEntity);
1267                                sh->SetTexture(index,
1268                                    interface_pointer_cast<INodeEcsInterfacePrivate>(weakSelf)
1269                                        ->EcsObject()
1270                                        ->GetEntity(),
1271                                    image);
1272                                sh->QueueApplicationTask(MakeTask([bitmap, index, weakSelf]() {
1273                                    if (auto me = interface_pointer_cast<SCENE_NS::IMaterial>(weakSelf)) {
1274                                        if (auto input = me->Inputs()->GetValueAt(index)) {
1275                                            input->Image()->SetValue(bitmap);
1276                                        }
1277                                    }
1278                                    return false;
1279                                }),
1280                                    false);
1281
1282                                return false;
1283                            },
1284                            sceneHolder),
1285                        false);
1286                }
1287
1288            } else {
1289                // should basically subscribe to dynamic content instead
1290                // Give uri based loading a shot
1291                if (auto input = Inputs()->GetValueAt(index)) {
1292                    input->Image()->SetValue(bitmap);
1293                }
1294            }
1295        } else { // reset existing image if there is one
1296            if (auto sceneHolder = SceneHolder()) {
1297                sceneHolder->QueueEngineTask(MakeTask(
1298                                                 [entityId = EcsObject()->GetEntity().id](auto sh) {
1299                                                     CORE_NS::Entity target { entityId };
1300                                                     // The assumption is that using base color ix is correct thing to
1301                                                     // do
1302                                                     sh->SetTexture(
1303                                                         CORE3D_NS::MaterialComponent::BASE_COLOR, target, {});
1304
1305                                                     return false;
1306                                                 },
1307                                                 sceneHolder),
1308                    false);
1309            }
1310        }
1311    }
1312};
1313} // namespace
1314SCENE_BEGIN_NAMESPACE()
1315
1316void RegisterMaterialImpl()
1317{
1318    META_NS::GetObjectRegistry().RegisterObjectType<MaterialImpl>();
1319}
1320void UnregisterMaterialImpl()
1321{
1322    META_NS::GetObjectRegistry().UnregisterObjectType<MaterialImpl>();
1323}
1324
1325SCENE_END_NAMESPACE()
1326