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/mesh_uid.h>
16#include <scene_plugin/interface/intf_material.h>
17
18#include <3d/ecs/components/mesh_component.h>
19
20#include <meta/ext/object.h>
21
22#include "intf_node_private.h"
23#include "intf_submesh_bridge.h"
24#include "submesh_handler_uid.h"
25#include "task_utils.h"
26
27using SCENE_NS::MakeTask;
28
29namespace {
30class SubMeshHandler : public META_NS::ObjectFwd<SubMeshHandler, SCENE_NS::ClassId::SubMeshHandler,
31                           META_NS::ClassId::Object, SCENE_NS::IEcsProxyObject, SCENE_NS::ISubMeshBridge> {
32public:
33    void Initialize(CORE3D_NS::IMeshComponentManager* componentManager, CORE_NS::Entity entity,
34        META_NS::IProperty::Ptr submeshes, INodeEcsInterfacePrivate::Ptr node) override
35    {
36        node_ = node;
37        if (node) {
38            componentManager_ = componentManager;
39            entity_ = entity;
40            submeshes_ = META_NS::ArrayProperty<SCENE_NS::ISubMesh::Ptr>(submeshes);
41            SetCommonListener(node->SceneHolder()->GetCommonEcsListener());
42            if (auto scene = node->EcsScene()) {
43                scene->AddEngineTask(MakeTask(
44                                         [](auto obj) {
45                                             if (auto self = static_cast<SubMeshHandler*>(obj.get())) {
46                                                 self->DoComponentEvent(
47                                                     CORE_NS::IEcs::ComponentListener::EventType::MODIFIED,
48                                                     *self->componentManager_, self->entity_);
49                                             }
50                                             return false;
51                                         },
52                                         GetSelf()),
53                    false);
54            }
55        }
56    }
57
58    void Destroy() override
59    {
60        if (listener_) {
61            listener_->RemoveEntity(entity_, GetSelf<SCENE_NS::IEcsProxyObject>(),
62                *static_cast<CORE_NS::IComponentManager*>(componentManager_));
63        }
64    }
65
66    // IEcsProxyObject
67
68    void SetCommonListener(BASE_NS::shared_ptr<SCENE_NS::EcsListener> listener) override
69    {
70        if (listener_) {
71            listener_->RemoveEntity(entity_, GetSelf<SCENE_NS::IEcsProxyObject>(),
72                *static_cast<CORE_NS::IComponentManager*>(componentManager_));
73        }
74        listener_ = listener;
75        if (listener_) {
76            listener_->AddEntity(entity_, GetSelf<SCENE_NS::IEcsProxyObject>(),
77                *static_cast<CORE_NS::IComponentManager*>(componentManager_));
78        }
79    }
80
81    void DoEntityEvent(CORE_NS::IEcs::EntityListener::EventType, const CORE_NS::Entity&) override {}
82
83    SCENE_NS::IMaterial::Ptr GetMaterialFromEntity(
84        SCENE_NS::IScene::Ptr scene, SceneHolder::Ptr sceneHolder, CORE_NS::Entity entity)
85    {
86        // First see if we have this material already.
87        auto materials = scene->GetMaterials();
88        for (auto material : materials) {
89            auto ecsObject = interface_pointer_cast<SCENE_NS::IEcsObject>(material);
90            if (ecsObject && ecsObject->GetEntity() == entity) {
91                return material;
92            }
93        }
94
95        // Then try to resolve from uri.
96        BASE_NS::string uri;
97        if (sceneHolder->GetEntityUri(entity, uri)) {
98            return scene->GetMaterial(uri);
99        }
100
101        BASE_NS::string name;
102        if (sceneHolder->GetEntityName(entity, name)) {
103            return scene->GetMaterial(name);
104        }
105
106        return {};
107    }
108
109    // ToDo: This runs on "engine queue", someday the property operations could be decoupled
110    // Another todo, this should presumably be closer to SceneHolder instead interleaved into the node impl
111    void DoComponentEvent(CORE_NS::IEcs::ComponentListener::EventType type,
112        const CORE_NS::IComponentManager& componentManager, const CORE_NS::Entity& entity) override
113    {
114        if (entity != entity_) {
115            CORE_LOG_W("DoComponentEvent called for wrong entity.");
116            return;
117        }
118
119        if (type == CORE_NS::IEcs::ComponentListener::EventType::MODIFIED) {
120            // Read data here, we don't want to keep read lock when we update materials below
121            // as it can cause changes to material components further down the pipeline.
122            auto componentData = componentManager_->Get(entity);
123
124            // This may be nasty from the application point of view, but lessens a risk
125            // that index changes would be propagated incorrectly to client app
126            if (submeshes_->GetSize() > componentData.submeshes.size()) {
127                submeshes_->Reset();
128            }
129
130            while (submeshes_->GetSize() < componentData.submeshes.size()) {
131                auto submesh = GetObjectRegistry().Create<SCENE_NS::ISubMesh>(SCENE_NS::ClassId::SubMesh);
132                submeshes_->AddValue(submesh);
133            }
134
135            for (auto i = 0; i < componentData.submeshes.size(); ++i) {
136                const auto& submesh = componentData.submeshes.at(i);
137                auto ptr = submeshes_->GetValueAt(i);
138                // TODO: These could be initialized only once.
139                ptr->Name()->SetValue(BASE_NS::string("Submesh ") + BASE_NS::to_string(i));
140
141                META_NS::SetValue(ptr->AABBMin(), submesh.aabbMin);
142                META_NS::SetValue(ptr->AABBMax(), submesh.aabbMax);
143                META_NS::SetValue(ptr->RenderSortLayerOrder(), submesh.renderSortLayerOrder);
144                META_NS::SetValue(ptr->Handle(), i);
145
146                auto priv = interface_pointer_cast<SCENE_NS::ISubMeshPrivate>(ptr);
147                if (CORE_NS::EntityUtil::IsValid(submesh.material)) {
148                    if (auto node = node_.lock()) {
149                        // This would appreciate more async approach
150                        auto sceneHolder = node->SceneHolder();
151                        auto scene = interface_pointer_cast<SCENE_NS::IScene>(node->EcsScene());
152
153                        if (scene && sceneHolder) {
154                            auto material = GetMaterialFromEntity(scene, sceneHolder, submesh.material);
155                            priv->SetDefaultMaterial(material);
156                        }
157                    }
158                } else {
159                    priv->SetDefaultMaterial({});
160                }
161                priv->AddSubmeshBrigde(GetSelf<ISubMeshBridge>());
162            }
163        }
164    }
165
166public: // ISubMeshBridge
167    void SetRenderSortLayerOrder(size_t index, uint8_t value) override
168    {
169#ifdef SYNC_ACCESS_ECS
170        if (auto handle = componentManager_->Write(entity_)) {
171            if (index >= 0 && index < handle->submeshes.size()) {
172                handle->submeshes[index].renderSortOrder = value;
173            }
174        }
175#else
176        if (auto node = node_.lock()) {
177            if (auto scene = node->EcsScene()) {
178                scene->AddEngineTask(MakeTask(
179                                         [index, value](auto selfNode) {
180                                             if (auto self = interface_pointer_cast<SCENE_NS::IEcsObject>(selfNode)) {
181                                                 if (auto node = interface_cast<INodeEcsInterfacePrivate>(selfNode)) {
182                                                     if (auto sceneHolder = node->SceneHolder()) {
183                                                         sceneHolder->SetSubmeshRenderSortOrder(
184                                                             self->GetEntity(), index, value);
185                                                     }
186                                                 }
187                                             }
188                                             return false;
189                                         },
190                                         node),
191                    false);
192            }
193        }
194#endif
195        if (index < submeshes_->GetSize()) {
196            META_NS::SetValue(submeshes_->GetValueAt(index)->RenderSortLayerOrder(), value);
197        }
198    }
199
200    void SetMaterialToEcs(size_t index, SCENE_NS::IMaterial::Ptr& material) override
201    {
202        if (auto node = node_.lock()) {
203            if (auto scene = node->EcsScene()) {
204                scene->AddEngineTask(
205                    MakeTask(
206                        [mat = SCENE_NS::IMaterial::WeakPtr(material), index](auto node) {
207                            if (auto self = interface_pointer_cast<SCENE_NS::IEcsObject>(node)) {
208                                CORE_NS::Entity entity {};
209                                if (auto ecsObject = interface_pointer_cast<SCENE_NS::IEcsObject>(mat.lock())) {
210                                    entity = ecsObject->GetEntity();
211                                }
212                                auto sceneHolder = node->SceneHolder();
213                                if (sceneHolder) {
214                                    sceneHolder->SetMaterial(self->GetEntity(), entity, index);
215                                }
216                            }
217                            return false;
218                        },
219                        node_),
220                    false);
221            }
222        }
223    }
224
225    void SetAABBMin(size_t index, BASE_NS::Math::Vec3 vec) override
226    {
227#ifdef SYNC_ACCESS_ECS
228        if (auto handle = componentManager_->Write(entity_)) {
229            if (index >= 0 && index < handle->submeshes.size()) {
230                handle->submeshes[index].aabbMin = vec;
231                handle->aabbMin = BASE_NS::Math::min(handle->aabbMin, vec);
232            }
233        }
234#else
235        if (auto node = node_.lock()) {
236            if (auto scene = node->EcsScene()) {
237                scene->AddEngineTask(MakeTask(
238                                         [index, vec](auto selfNode) {
239                                             if (auto self = interface_pointer_cast<SCENE_NS::IEcsObject>(selfNode)) {
240                                                 if (auto node = interface_cast<INodeEcsInterfacePrivate>(selfNode)) {
241                                                     if (auto sceneHolder = node->SceneHolder()) {
242                                                         sceneHolder->SetSubmeshAABBMin(self->GetEntity(), index, vec);
243                                                     }
244                                                 }
245                                             }
246                                             return false;
247                                         },
248                                         node),
249                    false);
250            }
251        }
252#endif
253        if (index < submeshes_->GetSize()) {
254            META_NS::SetValue(submeshes_->GetValueAt(index)->AABBMin(), vec);
255        }
256    }
257
258    void SetAABBMax(size_t index, BASE_NS::Math::Vec3 vec) override
259    {
260#ifdef SYNC_ACCESS_ECS
261        if (auto handle = componentManager_->Write(entity_)) {
262            if (index >= 0 && index < handle->submeshes.size()) {
263                handle->submeshes[index].aabbMax = vec;
264                handle->aabbMax = BASE_NS::Math::max(handle->aabbMax, vec);
265            }
266        }
267#else
268        if (auto node = node_.lock()) {
269            if (auto scene = node->EcsScene()) {
270                scene->AddEngineTask(MakeTask(
271                                         [index, vec](auto selfNode) {
272                                             if (auto self = interface_pointer_cast<SCENE_NS::IEcsObject>(selfNode)) {
273                                                 if (auto node = interface_cast<INodeEcsInterfacePrivate>(selfNode)) {
274                                                     if (auto sceneHolder = node->SceneHolder()) {
275                                                         sceneHolder->SetSubmeshAABBMax(self->GetEntity(), index, vec);
276                                                     }
277                                                 }
278                                             }
279                                             return false;
280                                         },
281                                         node),
282                    false);
283            }
284        }
285#endif
286        if (index < submeshes_->GetSize()) {
287            META_NS::SetValue(submeshes_->GetValueAt(index)->AABBMax(), vec);
288        }
289    }
290
291    void RemoveSubmesh(int32_t index) override
292    {
293#ifdef SYNC_ACCESS_ECS
294        if (auto handle = componentManager_->Write(entity_)) {
295            if (index < 0) {
296                handle->submeshes.clear();
297                handle->aabbMin = { 0.f, 0.f, 0.f };
298                handle->aabbMax = { 0.f, 0.f, 0.f };
299            } else if (index < handle->submeshes.size()) {
300                handle->submeshes.erase(handle->submeshes.begin() + index);
301                for (const auto& submesh : handle->submeshes) {
302                    handle->aabbMin = BASE_NS::Math::min(handle->aabbMin, submesh.aabbMin);
303                    handle->aabbMax = BASE_NS::Math::max(handle->aabbMax, submesh.aabbMax);
304                }
305            }
306        }
307#else
308        if (auto node = node_.lock()) {
309            if (auto scene = node->EcsScene()) {
310                scene->AddEngineTask(MakeTask(
311                                         [index](auto selfNode) {
312                                             if (auto self = interface_pointer_cast<SCENE_NS::IEcsObject>(selfNode)) {
313                                                 if (auto node = interface_cast<INodeEcsInterfacePrivate>(selfNode)) {
314                                                     if (auto sceneHolder = node->SceneHolder()) {
315                                                         sceneHolder->RemoveSubmesh(self->GetEntity(), index);
316                                                     }
317                                                 }
318                                             }
319                                             return false;
320                                         },
321                                         node_),
322                    false);
323            }
324        }
325#endif
326    }
327
328    CORE_NS::Entity GetEntity() const override
329    {
330        return entity_;
331    }
332
333    BASE_NS::shared_ptr<SCENE_NS::EcsListener> listener_ {};
334    CORE3D_NS::IMeshComponentManager* componentManager_ {};
335    CORE_NS::Entity entity_ {};
336    META_NS::ArrayProperty<SCENE_NS::ISubMesh::Ptr> submeshes_ {};
337    INodeEcsInterfacePrivate::WeakPtr node_ {};
338};
339} // namespace
340SCENE_BEGIN_NAMESPACE()
341
342void RegisterSubMeshHandler()
343{
344    META_NS::GetObjectRegistry().RegisterObjectType<SubMeshHandler>();
345}
346void UnregisterSubMeshHandler()
347{
348    META_NS::GetObjectRegistry().UnregisterObjectType<SubMeshHandler>();
349}
350SCENE_END_NAMESPACE()