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 "skinning_system.h"
17 
18 #include <PropertyTools/property_api_impl.inl>
19 #include <algorithm>
20 #include <charconv>
21 #include <limits>
22 
23 #include <3d/ecs/components/joint_matrices_component.h>
24 #include <3d/ecs/components/mesh_component.h>
25 #include <3d/ecs/components/node_component.h>
26 #include <3d/ecs/components/render_mesh_component.h>
27 #include <3d/ecs/components/skin_component.h>
28 #include <3d/ecs/components/skin_ibm_component.h>
29 #include <3d/ecs/components/skin_joints_component.h>
30 #include <3d/ecs/components/world_matrix_component.h>
31 #include <3d/implementation_uids.h>
32 #include <3d/util/intf_picking.h>
33 #include <base/containers/fixed_string.h>
34 #include <base/math/matrix_util.h>
35 #include <core/ecs/intf_ecs.h>
36 #include <core/implementation_uids.h>
37 #include <core/intf_engine.h>
38 #include <core/log.h>
39 #include <core/namespace.h>
40 #include <core/plugin/intf_plugin_register.h>
41 #include <render/implementation_uids.h>
42 #include <render/intf_render_context.h>
43 
44 #include "ecs/components/previous_joint_matrices_component.h"
45 #include "ecs/systems/node_system.h"
46 #include "util/string_util.h"
47 
48 CORE3D_BEGIN_NAMESPACE()
49 using namespace BASE_NS;
50 using namespace CORE_NS;
51 using namespace RENDER_NS;
52 
53 namespace {
54 constexpr auto SKIN_INDEX = 0U;
55 constexpr auto SKIN_JOINTS_INDEX = 1U;
56 constexpr auto JOINT_MATS_INDEX = 2U;
57 constexpr auto PREV_JOINT_MATS_INDEX = 3U;
58 constexpr auto RENDER_MESH_INDEX = 4U;
59 
UpdateJointBounds(IPicking & pick,const array_view<const float> & jointBoundsData,const Math::Mat4X4 & skinEntityWorld,JointMatricesComponent & jointMatrices)60 void UpdateJointBounds(IPicking& pick, const array_view<const float>& jointBoundsData,
61     const Math::Mat4X4& skinEntityWorld, JointMatricesComponent& jointMatrices)
62 {
63     const size_t jointBoundsDataSize = jointBoundsData.size();
64     const size_t boundsCount = jointBoundsDataSize / 6;
65 
66     CORE_ASSERT(jointBoundsData.size() % 6 == 0); // 6: should be multiple of 6
67     CORE_ASSERT(jointMatrices.count >= boundsCount);
68 
69     constexpr float maxFloat = std::numeric_limits<float>::max();
70     constexpr Math::Vec3 minDefault(maxFloat, maxFloat, maxFloat);
71     constexpr Math::Vec3 maxDefault(-maxFloat, -maxFloat, -maxFloat);
72 
73     jointMatrices.jointsAabbMin = minDefault;
74     jointMatrices.jointsAabbMax = maxDefault;
75 
76     for (size_t j = 0; j < jointMatrices.count; j++) {
77         if (j < boundsCount) {
78             // Bounds that don't have any vertices will be filled with maxFloat.
79             const float* boundsData = &jointBoundsData[j * 6];
80             if (*boundsData != maxFloat) {
81                 const Math::Vec3 min(boundsData);
82                 const Math::Vec3 max(boundsData + 3);
83                 const Math::Mat4X4& bbWorld = skinEntityWorld * jointMatrices.jointMatrices[j];
84                 const auto mam = pick.GetWorldAABB(bbWorld, min, max);
85                 // Only use bounding box if it's size is > ~zero.
86                 if (Math::Distance2(mam.minAABB, mam.maxAABB) > Math::EPSILON) {
87                     jointMatrices.jointAabbMinArray[j] = mam.minAABB;
88                     jointMatrices.jointAabbMaxArray[j] = mam.maxAABB;
89                     // Update the combined min/max for all joints.
90                     jointMatrices.jointsAabbMin = Math::min(jointMatrices.jointsAabbMin, mam.minAABB);
91                     jointMatrices.jointsAabbMax = Math::max(jointMatrices.jointsAabbMax, mam.maxAABB);
92                     continue;
93                 }
94             }
95         }
96 
97         // This joint is not referenced by any vertex or the bounding box size is zero.
98         jointMatrices.jointAabbMinArray[j] = minDefault;
99         jointMatrices.jointAabbMaxArray[j] = maxDefault;
100     }
101 }
102 
GetPicking(IEcs & ecs)103 IPicking* GetPicking(IEcs& ecs)
104 {
105     if (IEngine* engine = ecs.GetClassFactory().GetInterface<IEngine>(); engine) {
106         if (auto renderContext =
107                 GetInstance<IRenderContext>(*engine->GetInterface<IClassRegister>(), UID_RENDER_CONTEXT);
108             renderContext) {
109             return GetInstance<IPicking>(*renderContext->GetInterface<IClassRegister>(), UID_PICKING);
110         }
111     }
112     return nullptr;
113 }
114 } // namespace
115 
116 class SkinningSystem::SkinTask final : public IThreadPool::ITask {
117 public:
SkinTask(SkinningSystem & system,array_view<const ComponentQuery::ResultRow> results)118     SkinTask(SkinningSystem& system, array_view<const ComponentQuery::ResultRow> results)
119         : system_(system), results_(results) {};
120 
operator ()()121     void operator()() override
122     {
123         for (const ComponentQuery::ResultRow& row : results_) {
124             system_.UpdateSkin(row);
125         }
126     }
127 
128 protected:
Destroy()129     void Destroy() override {}
130 
131 private:
132     SkinningSystem& system_;
133     array_view<const ComponentQuery::ResultRow> results_;
134 };
135 
SkinningSystem(IEcs & ecs)136 SkinningSystem::SkinningSystem(IEcs& ecs)
137     : active_(true), ecs_(ecs), picking_(*GetPicking(ecs)), skinManager_(*GetManager<ISkinComponentManager>(ecs)),
138       skinIbmManager_(*GetManager<ISkinIbmComponentManager>(ecs)),
139       skinJointsManager_(*GetManager<ISkinJointsComponentManager>(ecs)),
140       jointMatricesManager_(*GetManager<IJointMatricesComponentManager>(ecs)),
141       previousJointMatricesManager_(*GetManager<IPreviousJointMatricesComponentManager>(ecs)),
142       worldMatrixManager_(*GetManager<IWorldMatrixComponentManager>(ecs)),
143       nodeManager_(*GetManager<INodeComponentManager>(ecs)),
144       renderMeshManager_(*GetManager<IRenderMeshComponentManager>(ecs)),
145       meshManager_(*GetManager<IMeshComponentManager>(ecs)), threadPool_(ecs.GetThreadPool())
146 {}
147 
SetActive(bool state)148 void SkinningSystem::SetActive(bool state)
149 {
150     active_ = state;
151 }
152 
IsActive() const153 bool SkinningSystem::IsActive() const
154 {
155     return active_;
156 }
157 
Initialize()158 void SkinningSystem::Initialize()
159 {
160     nodeSystem_ = GetSystem<INodeSystem>(ecs_);
161     {
162         const ComponentQuery::Operation operations[] = {
163             { skinJointsManager_, ComponentQuery::Operation::REQUIRE },
164             { jointMatricesManager_, ComponentQuery::Operation::REQUIRE },
165             { previousJointMatricesManager_, ComponentQuery::Operation::OPTIONAL },
166             { renderMeshManager_, ComponentQuery::Operation::OPTIONAL },
167         };
168         componentQuery_.SetEcsListenersEnabled(true);
169         componentQuery_.SetupQuery(skinManager_, operations);
170     }
171 }
172 
GetName() const173 string_view SkinningSystem::GetName() const
174 {
175     return CORE3D_NS::GetName(this);
176 }
177 
GetUid() const178 Uid SkinningSystem::GetUid() const
179 {
180     return UID;
181 }
182 
Uninitialize()183 void SkinningSystem::Uninitialize()
184 {
185     componentQuery_.SetEcsListenersEnabled(false);
186 }
187 
GetProperties()188 IPropertyHandle* SkinningSystem::GetProperties()
189 {
190     return SKINNING_SYSTEM_PROPERTIES.GetData();
191 }
192 
GetProperties() const193 const IPropertyHandle* SkinningSystem::GetProperties() const
194 {
195     return SKINNING_SYSTEM_PROPERTIES.GetData();
196 }
197 
SetProperties(const IPropertyHandle &)198 void SkinningSystem::SetProperties(const IPropertyHandle&) {}
199 
GetECS() const200 const IEcs& SkinningSystem::GetECS() const
201 {
202     return ecs_;
203 }
204 
UpdateJointTransformations(bool isEnabled,const array_view<Entity const> & jointEntities,const array_view<Math::Mat4X4 const> & ibms,JointMatricesComponent & jointMatrices,const Math::Mat4X4 & skinEntityWorldInverse)205 void SkinningSystem::UpdateJointTransformations(bool isEnabled, const array_view<Entity const>& jointEntities,
206     const array_view<Math::Mat4X4 const>& ibms, JointMatricesComponent& jointMatrices,
207     const Math::Mat4X4& skinEntityWorldInverse)
208 {
209     auto matrices = array_view<Math::Mat4X4>(
210         jointMatrices.jointMatrices, std::min(jointMatrices.count, countof(jointMatrices.jointMatrices)));
211 
212     std::transform(jointEntities.begin(), jointEntities.end(), ibms.begin(), matrices.begin(),
213         [&worldMatrixManager = worldMatrixManager_, skinEntityWorldInverse, isEnabled](
214             auto const& jointEntity, auto const& ibm) {
215             if (isEnabled) {
216                 if (const auto worldMatrixId = worldMatrixManager.GetComponentId(jointEntity);
217                     worldMatrixId != IComponentManager::INVALID_COMPONENT_ID) {
218                     auto const& jointGlobal = worldMatrixManager.Get(worldMatrixId).matrix;
219                     auto const jointMatrix = skinEntityWorldInverse * jointGlobal * ibm;
220                     return jointMatrix;
221                 }
222             }
223 
224             return 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 };
225         });
226 }
227 
UpdateSkin(const ComponentQuery::ResultRow & row)228 void SkinningSystem::UpdateSkin(const ComponentQuery::ResultRow& row)
229 {
230     bool isEnabled = true;
231     Math::Mat4X4 skinEntityWorld(1.0f);
232     Math::Mat4X4 skinEntityWorldInverse(1.0f);
233 
234     const SkinComponent skinComponent = skinManager_.Get(row.components[SKIN_INDEX]);
235     if (const auto worldMatrixId = worldMatrixManager_.GetComponentId(skinComponent.skinRoot);
236         worldMatrixId != IComponentManager::INVALID_COMPONENT_ID) {
237         isEnabled = nodeManager_.Get(skinComponent.skinRoot).effectivelyEnabled;
238         skinEntityWorld = worldMatrixManager_.Get(worldMatrixId).matrix;
239         skinEntityWorldInverse = Math::Inverse(skinEntityWorld);
240     }
241 
242     const auto skinIbmHandle = skinIbmManager_.Read(skinComponent.skin);
243     if (!skinIbmHandle) {
244 #if (CORE3D_VALIDATION_ENABLED == 1)
245         auto const onceId = to_hex(row.entity.id);
246         CORE_LOG_ONCE_W(onceId.c_str(), "Invalid skin resource for entity %s", onceId.c_str());
247 #endif
248         return;
249     }
250 
251     auto const skinJointsHandle = skinJointsManager_.Read(row.components[SKIN_JOINTS_INDEX]);
252     auto const jointEntities = array_view<Entity const>(
253         skinJointsHandle->jointEntities, std::min(skinJointsHandle->count, countof(skinJointsHandle->jointEntities)));
254 
255     auto const& ibmMatrices = skinIbmHandle->matrices;
256     if (jointEntities.size() != ibmMatrices.size()) {
257 #if (CORE3D_VALIDATION_ENABLED == 1)
258         auto const onceId = to_hex(row.entity.id);
259         CORE_LOG_ONCE_W(onceId.c_str(), "Entity (%zu) and description (%zu) counts don't match for entity %s",
260             jointEntities.size(), ibmMatrices.size(), onceId.c_str());
261 #endif
262         return;
263     }
264 
265     auto jointMatricesHandle = jointMatricesManager_.Write(row.components[JOINT_MATS_INDEX]);
266     auto& jointMatrices = *jointMatricesHandle;
267     jointMatrices.count = jointEntities.size();
268 
269     UpdateJointTransformations(isEnabled, jointEntities, ibmMatrices, jointMatrices, skinEntityWorldInverse);
270     if (row.IsValidComponentId(RENDER_MESH_INDEX)) {
271         if (const auto renderMeshHandle = renderMeshManager_.Read(row.components[RENDER_MESH_INDEX]);
272             renderMeshHandle) {
273             const RenderMeshComponent& renderMeshComponent = *renderMeshHandle;
274             if (const auto meshHandle = meshManager_.Read(renderMeshComponent.mesh); meshHandle) {
275                 auto& mesh = *meshHandle;
276                 UpdateJointBounds(picking_, mesh.jointBounds, skinEntityWorld, jointMatrices);
277             }
278         }
279     }
280 }
281 
Update(bool frameRenderingQueued,uint64_t,uint64_t)282 bool SkinningSystem::Update(bool frameRenderingQueued, uint64_t, uint64_t)
283 {
284     if (!active_) {
285         return false;
286     }
287 
288     componentQuery_.Execute();
289 
290     // copy joint matrices if they have changed
291     bool missingPrevJointMatrices = false;
292     if (jointMatricesGeneration_ != jointMatricesManager_.GetGenerationCounter()) {
293         jointMatricesGeneration_ = jointMatricesManager_.GetGenerationCounter();
294 
295         for (const auto& row : componentQuery_.GetResults()) {
296             const bool hasPrev = row.IsValidComponentId(PREV_JOINT_MATS_INDEX);
297             if (hasPrev && row.IsValidComponentId(JOINT_MATS_INDEX)) {
298                 auto prev = previousJointMatricesManager_.Write(row.components[PREV_JOINT_MATS_INDEX]);
299                 auto current = jointMatricesManager_.Read(row.components[JOINT_MATS_INDEX]);
300                 prev->count = current->count;
301                 std::copy(current->jointMatrices, current->jointMatrices + current->count, prev->jointMatrices);
302             } else if (!hasPrev) {
303                 missingPrevJointMatrices = true;
304             }
305         }
306     }
307 
308     if (worldMatrixGeneration_ == worldMatrixManager_.GetGenerationCounter()) {
309         return false;
310     }
311 
312     worldMatrixGeneration_ = worldMatrixManager_.GetGenerationCounter();
313 
314     const auto threadCount = threadPool_->GetNumberOfThreads();
315     const auto queryResults = componentQuery_.GetResults();
316     const auto resultCount = queryResults.size();
317     constexpr size_t minTaskSize = 8U;
318     const auto taskSize = Math::max(minTaskSize, resultCount / (threadCount == 0 ? 1 : threadCount));
319     const auto tasks = resultCount / (taskSize == 0 ? 1 : taskSize);
320 
321     tasks_.clear();
322     tasks_.reserve(tasks);
323 
324     taskResults_.clear();
325     taskResults_.reserve(tasks);
326     for (size_t i = 0; i < tasks; ++i) {
327         auto& task = tasks_.emplace_back(*this, array_view(queryResults.data() + i * taskSize, taskSize));
328         taskResults_.push_back(threadPool_->Push(IThreadPool::ITask::Ptr { &task }));
329     }
330 
331     // Skin the tail in the main thread.
332     if (const auto remaining = resultCount - (tasks * taskSize); remaining) {
333         auto finalBatch = array_view(queryResults.data() + tasks * taskSize, remaining);
334         for (const ComponentQuery::ResultRow& row : finalBatch) {
335             UpdateSkin(row);
336         }
337     }
338 
339     for (const auto& result : taskResults_) {
340         result->Wait();
341     }
342 
343     if (missingPrevJointMatrices) {
344         for (const auto& row : componentQuery_.GetResults()) {
345             // Create missing PreviousJointMatricesComponents and initialize with current.
346             if (!row.IsValidComponentId(PREV_JOINT_MATS_INDEX) && row.IsValidComponentId(JOINT_MATS_INDEX)) {
347                 previousJointMatricesManager_.Create(row.entity);
348                 auto prev = previousJointMatricesManager_.Write(row.entity);
349                 auto current = jointMatricesManager_.Read(row.components[JOINT_MATS_INDEX]);
350                 prev->count = current->count;
351                 std::copy(current->jointMatrices, current->jointMatrices + current->count, prev->jointMatrices);
352             }
353         }
354     }
355 
356     return true;
357 }
358 
CreateInstance(Entity const & skinIbmEntity,array_view<const Entity> const & joints,Entity const & entity,Entity const & skeleton)359 void SkinningSystem::CreateInstance(
360     Entity const& skinIbmEntity, array_view<const Entity> const& joints, Entity const& entity, Entity const& skeleton)
361 {
362     if (!EntityUtil::IsValid(skinIbmEntity) ||
363         !std::all_of(joints.begin(), joints.end(), [](const Entity& entity) { return EntityUtil::IsValid(entity); }) ||
364         !EntityUtil::IsValid(entity)) {
365         return;
366     }
367     if (const auto skinIbmHandle = skinIbmManager_.Read(skinIbmEntity); skinIbmHandle) {
368         auto& skinIbm = *skinIbmHandle;
369         if (skinIbm.matrices.size() != joints.size()) {
370             CORE_LOG_E(
371                 "Skin bone count doesn't match the given joints (%zu, %zu)!", skinIbm.matrices.size(), joints.size());
372             return;
373         }
374 
375         // make sure the entity has the needed components
376         skinManager_.Create(entity);
377         skinJointsManager_.Create(entity);
378         jointMatricesManager_.Create(entity);
379 
380         {
381             // set the skin resource handle
382             auto skinComponent = skinManager_.Get(entity);
383             skinComponent.skin = skinIbmEntity;
384             skinComponent.skinRoot = entity;
385             skinComponent.skeleton = skeleton;
386             skinManager_.Set(entity, skinComponent);
387         }
388 
389         if (auto skinInstanceHandle = skinJointsManager_.Write(entity); skinInstanceHandle) {
390             auto& skinInstance = *skinInstanceHandle;
391             skinInstance.count = skinIbm.matrices.size();
392             auto jointEntities = array_view<Entity>(skinInstance.jointEntities, skinInstance.count);
393             std::copy(joints.begin(), joints.end(), jointEntities.begin());
394         }
395     }
396 }
397 
CreateInstance(Entity const & skinIbmEntity,Entity const & entity,Entity const & skeleton)398 void SkinningSystem::CreateInstance(Entity const& skinIbmEntity, Entity const& entity, Entity const& skeleton)
399 {
400     if (!EntityUtil::IsValid(entity) || !skinJointsManager_.HasComponent(skinIbmEntity) ||
401         !skinIbmManager_.HasComponent(skinIbmEntity)) {
402         return;
403     }
404 
405     // validate skin joints
406     if (const auto jointsHandle = skinJointsManager_.Read(skinIbmEntity); jointsHandle) {
407         const auto joints = array_view(jointsHandle->jointEntities, jointsHandle->count);
408         if (!std::all_of(
409                 joints.begin(), joints.end(), [](const Entity& entity) { return EntityUtil::IsValid(entity); })) {
410             return;
411         }
412         if (const auto skinIbmHandle = skinIbmManager_.Read(skinIbmEntity); skinIbmHandle) {
413             if (skinIbmHandle->matrices.size() != joints.size()) {
414                 CORE_LOG_E("Skin bone count doesn't match the given joints (%zu, %zu)!", skinIbmHandle->matrices.size(),
415                     joints.size());
416                 return;
417             }
418         }
419     }
420 
421     skinManager_.Create(entity);
422     if (auto skinHandle = skinManager_.Write(entity); skinHandle) {
423         skinHandle->skin = skinIbmEntity;
424         skinHandle->skinRoot = entity;
425         skinHandle->skeleton = skeleton;
426     }
427 
428     skinJointsManager_.Create(entity);
429     const auto dstJointsHandle = skinJointsManager_.Write(entity);
430     const auto srcJointsHandle = skinJointsManager_.Read(skinIbmEntity);
431     if (dstJointsHandle && srcJointsHandle) {
432         dstJointsHandle->count = srcJointsHandle->count;
433         std::copy(srcJointsHandle->jointEntities,
434             srcJointsHandle->jointEntities + static_cast<ptrdiff_t>(srcJointsHandle->count),
435             dstJointsHandle->jointEntities);
436     }
437 
438     // joint matrices will be written during Update call
439     jointMatricesManager_.Create(entity);
440 }
441 
DestroyInstance(Entity const & entity)442 void SkinningSystem::DestroyInstance(Entity const& entity)
443 {
444     if (skinManager_.HasComponent(entity)) {
445         skinManager_.Destroy(entity);
446     }
447     if (skinJointsManager_.HasComponent(entity)) {
448         skinJointsManager_.Destroy(entity);
449     }
450     if (jointMatricesManager_.HasComponent(entity)) {
451         jointMatricesManager_.Destroy(entity);
452     }
453 }
454 
ISkinningSystemInstance(IEcs & ecs)455 ISystem* ISkinningSystemInstance(IEcs& ecs)
456 {
457     return new SkinningSystem(ecs);
458 }
459 
ISkinningSystemDestroy(ISystem * instance)460 void ISkinningSystemDestroy(ISystem* instance)
461 {
462     delete static_cast<SkinningSystem*>(instance);
463 }
464 CORE3D_END_NAMESPACE()
465