• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 "picking.h"
17 
18 #include <algorithm>
19 #include <cinttypes>
20 #include <limits>
21 
22 #include <3d/ecs/components/camera_component.h>
23 #include <3d/ecs/components/joint_matrices_component.h>
24 #include <3d/ecs/components/mesh_component.h>
25 #include <3d/ecs/components/render_mesh_component.h>
26 #include <3d/ecs/components/transform_component.h>
27 #include <3d/ecs/components/world_matrix_component.h>
28 #include <3d/ecs/systems/intf_node_system.h>
29 #include <base/containers/fixed_string.h>
30 #include <base/math/mathf.h>
31 #include <base/math/matrix_util.h>
32 #include <base/math/vector_util.h>
33 #include <core/ecs/intf_ecs.h>
34 #include <core/implementation_uids.h>
35 #include <core/namespace.h>
36 #include <core/plugin/intf_class_factory.h>
37 #include <core/plugin/intf_class_register.h>
38 #include <core/plugin/intf_plugin_register.h>
39 #include <core/property/intf_property_handle.h>
40 
41 CORE3D_BEGIN_NAMESPACE()
42 using namespace BASE_NS;
43 using namespace CORE_NS;
44 using namespace RENDER_NS;
45 
GetCameraViewToProjectionMatrix(const CameraComponent & cameraComponent) const46 Math::Mat4X4 Picking::GetCameraViewToProjectionMatrix(const CameraComponent& cameraComponent) const
47 {
48     switch (cameraComponent.projection) {
49         case CameraComponent::Projection::ORTHOGRAPHIC: {
50             auto orthoProj = Math::OrthoRhZo(cameraComponent.xMag * -0.5f, cameraComponent.xMag * 0.5f,
51                 cameraComponent.yMag * -0.5f, cameraComponent.yMag * 0.5f, cameraComponent.zNear, cameraComponent.zFar);
52             orthoProj[1][1] *= -1.f; // left-hand NDC while Vulkan right-handed -> flip y
53             return orthoProj;
54         }
55 
56         case CameraComponent::Projection::PERSPECTIVE: {
57             auto persProj = Math::PerspectiveRhZo(
58                 cameraComponent.yFov, cameraComponent.aspect, cameraComponent.zNear, cameraComponent.zFar);
59             persProj[1][1] *= -1.f; // left-hand NDC while Vulkan right-handed -> flip y
60             return persProj;
61         }
62 
63         case CameraComponent::Projection::CUSTOM: {
64             return cameraComponent.customProjectionMatrix;
65         }
66 
67         default:
68             CORE_ASSERT(false);
69             return Math::Mat4X4(1.0f);
70     }
71 }
72 
IntersectAabb(Math::Vec3 aabbMin,Math::Vec3 aabbMax,Math::Vec3 start,Math::Vec3 invDirection) const73 constexpr bool Picking::IntersectAabb(
74     Math::Vec3 aabbMin, Math::Vec3 aabbMax, Math::Vec3 start, Math::Vec3 invDirection) const
75 {
76     const float tx1 = (aabbMin.x - start.x) * invDirection.x;
77     const float tx2 = (aabbMax.x - start.x) * invDirection.x;
78 
79     float tmin = Math::min(tx1, tx2);
80     float tmax = Math::max(tx1, tx2);
81 
82     const float ty1 = (aabbMin.y - start.y) * invDirection.y;
83     const float ty2 = (aabbMax.y - start.y) * invDirection.y;
84 
85     tmin = Math::max(tmin, Math::min(ty1, ty2));
86     tmax = Math::min(tmax, Math::max(ty1, ty2));
87 
88     const float tz1 = (aabbMin.z - start.z) * invDirection.z;
89     const float tz2 = (aabbMax.z - start.z) * invDirection.z;
90 
91     tmin = Math::max(tmin, Math::min(tz1, tz2));
92     tmax = Math::min(tmax, Math::max(tz1, tz2));
93 
94     return tmax >= tmin && tmax > 0.0f;
95 }
96 
97 // Calculates AABB using WorldMatrixComponent.
UpdateRecursiveAABB(const IRenderMeshComponentManager & renderMeshComponentManager,const IWorldMatrixComponentManager & worldMatrixComponentManager,const IJointMatricesComponentManager & jointMatricesComponentManager,const IMeshComponentManager & meshManager,const ISceneNode & sceneNode,bool isRecursive,MinAndMax & mamInOut) const98 void Picking::UpdateRecursiveAABB(const IRenderMeshComponentManager& renderMeshComponentManager,
99     const IWorldMatrixComponentManager& worldMatrixComponentManager,
100     const IJointMatricesComponentManager& jointMatricesComponentManager, const IMeshComponentManager& meshManager,
101     const ISceneNode& sceneNode, bool isRecursive, MinAndMax& mamInOut) const
102 {
103     const Entity entity = sceneNode.GetEntity();
104     if (const auto jointMatrices = jointMatricesComponentManager.Read(entity); jointMatrices) {
105         // Take skinning into account.
106         mamInOut.minAABB = Math::min(mamInOut.minAABB, jointMatrices->jointsAabbMin);
107         mamInOut.maxAABB = Math::max(mamInOut.maxAABB, jointMatrices->jointsAabbMax);
108     } else {
109         const auto worldMatrixId = worldMatrixComponentManager.GetComponentId(entity);
110         const auto renderMeshId = renderMeshComponentManager.GetComponentId(entity);
111         if (worldMatrixId != IComponentManager::INVALID_COMPONENT_ID &&
112             renderMeshId != IComponentManager::INVALID_COMPONENT_ID) {
113             const Math::Mat4X4& worldMatrix = worldMatrixComponentManager.Get(worldMatrixId).matrix;
114 
115             const RenderMeshComponent rmc = renderMeshComponentManager.Get(renderMeshId);
116             if (const auto meshHandle = meshManager.Read(rmc.mesh); meshHandle) {
117                 const MinAndMax meshMam = GetWorldAABB(worldMatrix, meshHandle->aabbMin, meshHandle->aabbMax);
118                 mamInOut.minAABB = Math::min(mamInOut.minAABB, meshMam.minAABB);
119                 mamInOut.maxAABB = Math::max(mamInOut.maxAABB, meshMam.maxAABB);
120             }
121         }
122     }
123 
124     if (isRecursive) {
125         for (ISceneNode* child : sceneNode.GetChildren()) {
126             if (child) {
127                 UpdateRecursiveAABB(renderMeshComponentManager, worldMatrixComponentManager,
128                     jointMatricesComponentManager, meshManager, *child, isRecursive, mamInOut);
129             }
130         }
131     }
132 }
133 
134 // Calculates AABB using TransformComponent.
UpdateRecursiveAABB(const IRenderMeshComponentManager & renderMeshComponentManager,const ITransformComponentManager & transformComponentManager,const IMeshComponentManager & meshManager,const ISceneNode & sceneNode,const Math::Mat4X4 & parentWorld,bool isRecursive,MinAndMax & mamInOut) const135 void Picking::UpdateRecursiveAABB(const IRenderMeshComponentManager& renderMeshComponentManager,
136     const ITransformComponentManager& transformComponentManager, const IMeshComponentManager& meshManager,
137     const ISceneNode& sceneNode, const Math::Mat4X4& parentWorld, bool isRecursive, MinAndMax& mamInOut) const
138 {
139     const Entity entity = sceneNode.GetEntity();
140     Math::Mat4X4 worldMatrix = parentWorld;
141 
142     if (const auto transformId = transformComponentManager.GetComponentId(entity);
143         transformId != IComponentManager::INVALID_COMPONENT_ID) {
144         const TransformComponent tc = transformComponentManager.Get(transformId);
145         const Math::Mat4X4 localMatrix = Math::Trs(tc.position, tc.rotation, tc.scale);
146         worldMatrix = worldMatrix * localMatrix;
147     }
148 
149     if (const auto renderMeshId = renderMeshComponentManager.GetComponentId(entity);
150         renderMeshId != IComponentManager::INVALID_COMPONENT_ID) {
151         const RenderMeshComponent rmc = renderMeshComponentManager.Get(renderMeshId);
152         if (const auto meshHandle = meshManager.Read(rmc.mesh); meshHandle) {
153             const MinAndMax meshMam = GetWorldAABB(worldMatrix, meshHandle->aabbMin, meshHandle->aabbMax);
154             mamInOut.minAABB = Math::min(mamInOut.minAABB, meshMam.minAABB);
155             mamInOut.maxAABB = Math::max(mamInOut.maxAABB, meshMam.maxAABB);
156         }
157     }
158 
159     // Recurse to children.
160     if (isRecursive) {
161         for (ISceneNode* child : sceneNode.GetChildren()) {
162             if (child) {
163                 UpdateRecursiveAABB(renderMeshComponentManager, transformComponentManager, meshManager, *child,
164                     worldMatrix, isRecursive, mamInOut);
165             }
166         }
167     }
168 }
169 
HitTestNode(ISceneNode & node,const MeshComponent & mesh,const Math::Mat4X4 & matrix,const Math::Vec3 & start,const Math::Vec3 & invDir) const170 RayCastResult Picking::HitTestNode(ISceneNode& node, const MeshComponent& mesh, const Math::Mat4X4& matrix,
171     const Math::Vec3& start, const Math::Vec3& invDir) const
172 {
173     RayCastResult raycastResult;
174 
175     const MinAndMax meshMinMax = GetWorldAABB(matrix, mesh.aabbMin, mesh.aabbMax);
176     if (IntersectAabb(meshMinMax.minAABB, meshMinMax.maxAABB, start, invDir)) {
177         if (mesh.submeshes.size() > 1) {
178             raycastResult.distance = std::numeric_limits<float>::max();
179 
180             for (auto const& submesh : mesh.submeshes) {
181                 const MinAndMax submeshMinMax = GetWorldAABB(matrix, submesh.aabbMin, submesh.aabbMax);
182                 if (IntersectAabb(submeshMinMax.minAABB, submeshMinMax.maxAABB, start, invDir)) {
183                     const float distance =
184                         Math::Magnitude((submeshMinMax.maxAABB + submeshMinMax.minAABB) / 2.f - start);
185                     if (distance < raycastResult.distance) {
186                         raycastResult.node = &node;
187                         raycastResult.distance = distance;
188                     }
189                 }
190             }
191         } else {
192             raycastResult.distance = Math::Magnitude((meshMinMax.minAABB + meshMinMax.maxAABB) / 2.f - start);
193             raycastResult.node = &node;
194         }
195     }
196 
197     return raycastResult;
198 }
199 
ScreenToWorld(IEcs const & ecs,Entity cameraEntity,Math::Vec3 screenCoordinate) const200 Math::Vec3 Picking::ScreenToWorld(IEcs const& ecs, Entity cameraEntity, Math::Vec3 screenCoordinate) const
201 {
202     if (!EntityUtil::IsValid(cameraEntity)) {
203         return {};
204     }
205 
206     auto cameraComponentManager = GetManager<ICameraComponentManager>(ecs);
207     const auto cameraId = cameraComponentManager->GetComponentId(cameraEntity);
208     if (cameraId == IComponentManager::INVALID_COMPONENT_ID) {
209         return {};
210     }
211 
212     auto worldMatrixComponentManager = GetManager<IWorldMatrixComponentManager>(ecs);
213     const auto worldMatrixId = worldMatrixComponentManager->GetComponentId(cameraEntity);
214     if (worldMatrixId == IComponentManager::INVALID_COMPONENT_ID) {
215         return {};
216     }
217 
218     const CameraComponent cameraComponent = cameraComponentManager->Get(cameraId);
219 
220     screenCoordinate.x = (screenCoordinate.x - 0.5f) * 2.f;
221     screenCoordinate.y = (screenCoordinate.y - 0.5f) * 2.f;
222 
223     Math::Mat4X4 projToView = Math::Inverse(GetCameraViewToProjectionMatrix(cameraComponent));
224 
225     const WorldMatrixComponent worldMatrixComponent = worldMatrixComponentManager->Get(worldMatrixId);
226     auto const& worldFromView = worldMatrixComponent.matrix;
227     const auto viewCoordinate =
228         (projToView * Math::Vec4(screenCoordinate.x, screenCoordinate.y, screenCoordinate.z, 1.f));
229     auto worldCoordinate = worldFromView * viewCoordinate;
230     worldCoordinate /= worldCoordinate.w;
231     return Math::Vec3 { worldCoordinate.x, worldCoordinate.y, worldCoordinate.z };
232 }
233 
WorldToScreen(IEcs const & ecs,Entity cameraEntity,Math::Vec3 worldCoordinate) const234 Math::Vec3 Picking::WorldToScreen(IEcs const& ecs, Entity cameraEntity, Math::Vec3 worldCoordinate) const
235 {
236     if (!EntityUtil::IsValid(cameraEntity)) {
237         return {};
238     }
239 
240     auto cameraComponentManager = GetManager<ICameraComponentManager>(ecs);
241     const auto cameraId = cameraComponentManager->GetComponentId(cameraEntity);
242     if (cameraId == IComponentManager::INVALID_COMPONENT_ID) {
243         return {};
244     }
245 
246     auto worldMatrixComponentManager = GetManager<IWorldMatrixComponentManager>(ecs);
247     const auto worldMatrixId = worldMatrixComponentManager->GetComponentId(cameraEntity);
248     if (worldMatrixId == IComponentManager::INVALID_COMPONENT_ID) {
249         return {};
250     }
251 
252     const CameraComponent cameraComponent = cameraComponentManager->Get(cameraId);
253     Math::Mat4X4 viewToProj = GetCameraViewToProjectionMatrix(cameraComponent);
254 
255     const WorldMatrixComponent worldMatrixComponent = worldMatrixComponentManager->Get(worldMatrixId);
256     auto const worldToView = Math::Inverse(worldMatrixComponent.matrix);
257     const auto viewCoordinate = worldToView * Math::Vec4(worldCoordinate.x, worldCoordinate.y, worldCoordinate.z, 1.f);
258     auto screenCoordinate = viewToProj * viewCoordinate;
259 
260     // Give sane results also when the point is behind the camera.
261     if (screenCoordinate.w < 0.0f) {
262         screenCoordinate.x *= -1.0f;
263         screenCoordinate.y *= -1.0f;
264         screenCoordinate.z *= -1.0f;
265     }
266 
267     screenCoordinate /= screenCoordinate.w;
268     screenCoordinate.x = screenCoordinate.x * 0.5f + 0.5f;
269     screenCoordinate.y = screenCoordinate.y * 0.5f + 0.5f;
270 
271     return Math::Vec3 { screenCoordinate.x, screenCoordinate.y, screenCoordinate.z };
272 }
273 
RayCast(const IEcs & ecs,const Math::Vec3 & start,const Math::Vec3 & direction) const274 vector<RayCastResult> Picking::RayCast(const IEcs& ecs, const Math::Vec3& start, const Math::Vec3& direction) const
275 {
276     vector<RayCastResult> result;
277 
278     auto nodeSystem = GetSystem<INodeSystem>(ecs);
279     auto const& renderMeshComponentManager = GetManager<IRenderMeshComponentManager>(ecs);
280     auto const& worldMatrixComponentManager = GetManager<IWorldMatrixComponentManager>(ecs);
281     auto const& jointMatricesComponentManager = GetManager<IJointMatricesComponentManager>(ecs);
282     auto const& meshComponentManager = *GetManager<IMeshComponentManager>(ecs);
283 
284     auto const invDir = Math::Vec3(1.f / direction.x, 1.f / direction.y, 1.f / direction.z);
285     for (IComponentManager::ComponentId i = 0; i < renderMeshComponentManager->GetComponentCount(); i++) {
286         const Entity id = renderMeshComponentManager->GetEntity(i);
287         if (auto node = nodeSystem->GetNode(id); node) {
288             if (const auto jointMatrices = jointMatricesComponentManager->Read(id); jointMatrices) {
289                 // Use the skinned aabb's.
290                 const auto& jointMatricesComponent = *jointMatrices;
291                 if (IntersectAabb(
292                         jointMatricesComponent.jointsAabbMin, jointMatricesComponent.jointsAabbMax, start, invDir)) {
293                     const float distance = Math::Magnitude(
294                         (jointMatricesComponent.jointsAabbMax + jointMatricesComponent.jointsAabbMin) * 0.5f - start);
295                     result.emplace_back(RayCastResult { node, distance });
296                 }
297                 continue;
298             } else {
299                 if (const auto worldMatrixId = worldMatrixComponentManager->GetComponentId(id);
300                     worldMatrixId != IComponentManager::INVALID_COMPONENT_ID) {
301                     auto const renderMeshComponent = renderMeshComponentManager->Get(i);
302                     if (const auto meshHandle = meshComponentManager.Read(renderMeshComponent.mesh); meshHandle) {
303                         auto const worldMatrixComponent = worldMatrixComponentManager->Get(worldMatrixId);
304                         const auto raycastResult =
305                             HitTestNode(*node, *meshHandle, worldMatrixComponent.matrix, start, invDir);
306                         if (raycastResult.node) {
307                             result.push_back(raycastResult);
308                         }
309                     } else {
310                         CORE_LOG_W("no mesh resource for entity %" PRIx64 ", resource %" PRIx64, id.id,
311                             renderMeshComponent.mesh.id);
312                         continue;
313                     }
314                 }
315             }
316         }
317     }
318 
319     std::sort(
320         result.begin(), result.end(), [](const auto& lhs, const auto& rhs) { return (lhs.distance < rhs.distance); });
321 
322     return result;
323 }
324 
RayCastFromCamera(IEcs const & ecs,Entity camera,const Math::Vec2 & screenPos) const325 vector<RayCastResult> Picking::RayCastFromCamera(IEcs const& ecs, Entity camera, const Math::Vec2& screenPos) const
326 {
327     const auto* worldMatrixManager = GetManager<IWorldMatrixComponentManager>(ecs);
328     const auto* cameraManager = GetManager<ICameraComponentManager>(ecs);
329     if (!worldMatrixManager || !cameraManager) {
330         return vector<RayCastResult>();
331     }
332 
333     const auto wmcId = worldMatrixManager->GetComponentId(camera);
334     const auto ccId = cameraManager->GetComponentId(camera);
335     if (wmcId != IComponentManager::INVALID_COMPONENT_ID && ccId != IComponentManager::INVALID_COMPONENT_ID) {
336         const auto worldMatrixComponent = worldMatrixManager->Get(wmcId);
337         const auto cameraComponent = cameraManager->Get(ccId);
338         if (cameraComponent.projection == CORE3D_NS::CameraComponent::Projection::ORTHOGRAPHIC) {
339             const Math::Vec3 worldPos = ScreenToWorld(ecs, camera, Math::Vec3(screenPos.x, screenPos.y, 0.0f));
340             const auto direction = worldMatrixComponent.matrix * Math::Vec4(0.0f, 0.0f, -1.0f, 0.0f);
341             return RayCast(ecs, worldPos, direction);
342         } else {
343             // Ray origin is the camera world position.
344             const Math::Vec3& rayOrigin = Math::Vec3(worldMatrixComponent.matrix.w);
345             const Math::Vec3 targetPos = ScreenToWorld(ecs, camera, Math::Vec3(screenPos.x, screenPos.y, 1.0f));
346             const Math::Vec3 direction = Math::Normalize(targetPos - rayOrigin);
347             return RayCast(ecs, rayOrigin, direction);
348         }
349     }
350 
351     return vector<RayCastResult>();
352 }
353 
GetWorldAABB(const Math::Mat4X4 & world,const Math::Vec3 & aabbMin,const Math::Vec3 & aabbMax) const354 MinAndMax Picking::GetWorldAABB(const Math::Mat4X4& world, const Math::Vec3& aabbMin, const Math::Vec3& aabbMax) const
355 {
356     auto const aabb0 = Math::MultiplyPoint3X4(world, Math::Vec3(aabbMin.x, aabbMin.y, aabbMin.z));
357     auto const aabb1 = Math::MultiplyPoint3X4(world, Math::Vec3(aabbMax.x, aabbMin.y, aabbMin.z));
358     auto const aabb2 = Math::MultiplyPoint3X4(world, Math::Vec3(aabbMin.x, aabbMax.y, aabbMin.z));
359     auto const aabb3 = Math::MultiplyPoint3X4(world, Math::Vec3(aabbMax.x, aabbMax.y, aabbMin.z));
360     auto const aabb4 = Math::MultiplyPoint3X4(world, Math::Vec3(aabbMin.x, aabbMin.y, aabbMax.z));
361     auto const aabb5 = Math::MultiplyPoint3X4(world, Math::Vec3(aabbMax.x, aabbMin.y, aabbMax.z));
362     auto const aabb6 = Math::MultiplyPoint3X4(world, Math::Vec3(aabbMin.x, aabbMax.y, aabbMax.z));
363     auto const aabb7 = Math::MultiplyPoint3X4(world, Math::Vec3(aabbMax.x, aabbMax.y, aabbMax.z));
364 
365     MinAndMax mam;
366     mam.minAABB = Math::min(aabb0,
367         Math::min(aabb1,
368             Math::min(aabb2,
369                 Math::min(aabb3, Math::min(aabb4, Math::min(aabb4, Math::min(aabb5, Math::min(aabb6, aabb7))))))));
370     mam.maxAABB = Math::max(aabb0,
371         Math::max(aabb1,
372             Math::max(aabb2,
373                 Math::max(aabb3, Math::max(aabb4, Math::max(aabb4, Math::max(aabb5, Math::max(aabb6, aabb7))))))));
374 
375     return mam;
376 }
377 
GetWorldMatrixComponentAABB(Entity entity,bool isRecursive,IEcs & ecs) const378 MinAndMax Picking::GetWorldMatrixComponentAABB(Entity entity, bool isRecursive, IEcs& ecs) const
379 {
380     MinAndMax mam;
381 
382     if (ISceneNode* node = GetSystem<INodeSystem>(ecs)->GetNode(entity); node) {
383         auto& renderMeshComponentManager = *GetManager<IRenderMeshComponentManager>(ecs);
384         auto& worldMatrixComponentManager = *GetManager<IWorldMatrixComponentManager>(ecs);
385         auto& jointworldMatrixComponentManager = *GetManager<IJointMatricesComponentManager>(ecs);
386         auto& meshComponentManager = *GetManager<IMeshComponentManager>(ecs);
387 
388         UpdateRecursiveAABB(renderMeshComponentManager, worldMatrixComponentManager, jointworldMatrixComponentManager,
389             meshComponentManager, *node, isRecursive, mam);
390     }
391 
392     return mam;
393 }
394 
GetTransformComponentAABB(Entity entity,bool isRecursive,IEcs & ecs) const395 MinAndMax Picking::GetTransformComponentAABB(Entity entity, bool isRecursive, IEcs& ecs) const
396 {
397     MinAndMax mam;
398 
399     if (ISceneNode* node = GetSystem<INodeSystem>(ecs)->GetNode(entity); node) {
400         auto& renderMeshComponentManager = *GetManager<IRenderMeshComponentManager>(ecs);
401         auto& transformComponentManager = *GetManager<ITransformComponentManager>(ecs);
402         auto& meshComponentManager = *GetManager<IMeshComponentManager>(ecs);
403 
404         UpdateRecursiveAABB(renderMeshComponentManager, transformComponentManager, meshComponentManager, *node,
405             Math::Mat4X4(1.0f), isRecursive, mam);
406     }
407 
408     return mam;
409 }
410 
GetInterface(const Uid & uid) const411 const IInterface* Picking::GetInterface(const Uid& uid) const
412 {
413     if (uid == IPicking::UID) {
414         return this;
415     }
416     return nullptr;
417 }
418 
GetInterface(const Uid & uid)419 IInterface* Picking::GetInterface(const Uid& uid)
420 {
421     if (uid == IPicking::UID) {
422         return this;
423     }
424     return nullptr;
425 }
426 
Ref()427 void Picking::Ref() {}
428 
Unref()429 void Picking::Unref() {}
430 CORE3D_END_NAMESPACE()
431