1 /*
2 * Copyright (c) 2022 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 const auto queryResults = componentQuery_.GetResults();
322 const auto resultCount = queryResults.size();
323 constexpr size_t minTaskSize = 8U;
324 const auto taskSize = Math::max(minTaskSize, resultCount / threadCount);
325 const auto tasks = resultCount / taskSize;
326
327 tasks_.clear();
328 tasks_.reserve(tasks);
329
330 taskResults_.clear();
331 taskResults_.reserve(tasks);
332 for (size_t i = 0; i < tasks; ++i) {
333 auto& task = tasks_.emplace_back(*this, array_view(queryResults.data() + i * taskSize, taskSize));
334 taskResults_.push_back(threadPool_->Push(IThreadPool::ITask::Ptr { &task }));
335 }
336
337 // Skin the tail in the main thread.
338 if (const auto remaining = resultCount - (tasks * taskSize); remaining) {
339 auto finalBatch = array_view(queryResults.data() + tasks * taskSize, remaining);
340 for (const ComponentQuery::ResultRow& row : finalBatch) {
341 UpdateSkin(row);
342 }
343 }
344
345 for (const auto& result : taskResults_) {
346 result->Wait();
347 }
348
349 if (missingPrevJointMatrices) {
350 for (const auto& row : componentQuery_.GetResults()) {
351 // Create missing PreviousJointMatricesComponents and initialize with current.
352 if (!row.IsValidComponentId(PREV_JOINT_MATS_INDEX) && row.IsValidComponentId(JOINT_MATS_INDEX)) {
353 previousJointMatricesManager_.Create(row.entity);
354 auto prev = previousJointMatricesManager_.Write(row.entity);
355 auto current = jointMatricesManager_.Read(row.components[JOINT_MATS_INDEX]);
356 prev->count = current->count;
357 std::copy(current->jointMatrices, current->jointMatrices + current->count, prev->jointMatrices);
358 }
359 }
360 }
361
362 return true;
363 }
364
CreateInstance(Entity const & skinIbmEntity,array_view<const Entity> const & joints,Entity const & entity,Entity const & skeleton)365 void SkinningSystem::CreateInstance(
366 Entity const& skinIbmEntity, array_view<const Entity> const& joints, Entity const& entity, Entity const& skeleton)
367 {
368 if (!EntityUtil::IsValid(skinIbmEntity) ||
369 !std::all_of(joints.begin(), joints.end(), [](const Entity& entity) { return EntityUtil::IsValid(entity); }) ||
370 !EntityUtil::IsValid(entity)) {
371 return;
372 }
373 if (const auto skinIbmHandle = skinIbmManager_.Read(skinIbmEntity); skinIbmHandle) {
374 auto& skinIbm = *skinIbmHandle;
375 if (skinIbm.matrices.size() != joints.size()) {
376 CORE_LOG_E(
377 "Skin bone count doesn't match the given joints (%zu, %zu)!", skinIbm.matrices.size(), joints.size());
378 return;
379 }
380
381 // make sure the entity has the needed components
382 skinManager_.Create(entity);
383 skinJointsManager_.Create(entity);
384 jointMatricesManager_.Create(entity);
385
386 {
387 // set the skin resource handle
388 auto skinComponent = skinManager_.Get(entity);
389 skinComponent.skin = skinIbmEntity;
390 skinComponent.skinRoot = entity;
391 skinComponent.skeleton = skeleton;
392 skinManager_.Set(entity, skinComponent);
393 }
394
395 if (auto skinInstanceHandle = skinJointsManager_.Write(entity); skinInstanceHandle) {
396 auto& skinInstance = *skinInstanceHandle;
397 skinInstance.count = skinIbm.matrices.size();
398 auto jointEntities = array_view<Entity>(skinInstance.jointEntities, skinInstance.count);
399 std::copy(joints.begin(), joints.end(), jointEntities.begin());
400 }
401 }
402 }
403
CreateInstance(Entity const & skinIbmEntity,Entity const & entity,Entity const & skeleton)404 void SkinningSystem::CreateInstance(Entity const& skinIbmEntity, Entity const& entity, Entity const& skeleton)
405 {
406 if (!EntityUtil::IsValid(entity) || !skinJointsManager_.HasComponent(skinIbmEntity) ||
407 !skinIbmManager_.HasComponent(skinIbmEntity)) {
408 return;
409 }
410
411 // validate skin joints
412 if (const auto jointsHandle = skinJointsManager_.Read(skinIbmEntity); jointsHandle) {
413 const auto joints = array_view(jointsHandle->jointEntities, jointsHandle->count);
414 if (!std::all_of(
415 joints.begin(), joints.end(), [](const Entity& entity) { return EntityUtil::IsValid(entity); })) {
416 return;
417 }
418 if (const auto skinIbmHandle = skinIbmManager_.Read(skinIbmEntity); skinIbmHandle) {
419 if (skinIbmHandle->matrices.size() != joints.size()) {
420 CORE_LOG_E("Skin bone count doesn't match the given joints (%zu, %zu)!", skinIbmHandle->matrices.size(),
421 joints.size());
422 return;
423 }
424 }
425 }
426
427 skinManager_.Create(entity);
428 if (auto skinHandle = skinManager_.Write(entity); skinHandle) {
429 skinHandle->skin = skinIbmEntity;
430 skinHandle->skinRoot = entity;
431 skinHandle->skeleton = skeleton;
432 }
433
434 skinJointsManager_.Create(entity);
435 const auto dstJointsHandle = skinJointsManager_.Write(entity);
436 const auto srcJointsHandle = skinJointsManager_.Read(skinIbmEntity);
437 if (dstJointsHandle && srcJointsHandle) {
438 dstJointsHandle->count = srcJointsHandle->count;
439 std::copy(srcJointsHandle->jointEntities,
440 srcJointsHandle->jointEntities + static_cast<ptrdiff_t>(srcJointsHandle->count),
441 dstJointsHandle->jointEntities);
442 }
443
444 // joint matrices will be written during Update call
445 jointMatricesManager_.Create(entity);
446 }
447
DestroyInstance(Entity const & entity)448 void SkinningSystem::DestroyInstance(Entity const& entity)
449 {
450 if (skinManager_.HasComponent(entity)) {
451 skinManager_.Destroy(entity);
452 }
453 if (skinJointsManager_.HasComponent(entity)) {
454 skinJointsManager_.Destroy(entity);
455 }
456 if (jointMatricesManager_.HasComponent(entity)) {
457 jointMatricesManager_.Destroy(entity);
458 }
459 }
460
ISkinningSystemInstance(IEcs & ecs)461 ISystem* ISkinningSystemInstance(IEcs& ecs)
462 {
463 return new SkinningSystem(ecs);
464 }
465
ISkinningSystemDestroy(ISystem * instance)466 void ISkinningSystemDestroy(ISystem* instance)
467 {
468 delete static_cast<SkinningSystem*>(instance);
469 }
470 CORE3D_END_NAMESPACE()
471