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