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