2 * Copyright (c) 2024 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali-scene3d/internal/algorithm/navigation-mesh-impl.h>
21 #include <dali/integration-api/debug.h>
28 namespace Dali::Scene3D::Internal::Algorithm
30 using Poly = Dali::Scene3D::Algorithm::NavigationMesh::Face;
31 using Edge = Dali::Scene3D::Algorithm::NavigationMesh::Edge;
32 using Vertex = Dali::Scene3D::Algorithm::NavigationMesh::Vertex;
35 * Helper function calculating intersection point between triangle and ray
37 static bool RayTriangleIntersect(
38 const Vector3& origin, const Vector3& direction, const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2, const Vector3& normal, float& outDistance, Vector3& position)
44 // check if ray and plane are parallel ?
45 float NdotRayDirection = N.Dot(direction);
46 if(Dali::Equals(NdotRayDirection, 0.0f))
48 return false; // they are parallel so they don'outDistance intersect !
51 // compute d parameter using equation 2
52 float d = -N.Dot(vertex0);
54 // compute outDistance (equation 3)
55 outDistance = -(N.Dot(origin) + d) / NdotRayDirection;
57 // check if the triangle is in behind the ray
58 if(outDistance < 0) return false; // the triangle is behind
60 // compute the intersection point using equation 1
61 auto P = origin + (direction * outDistance);
65 auto edge0 = vertex1 - vertex0;
66 auto edge1 = vertex2 - vertex1;
67 auto edge2 = vertex0 - vertex2;
68 auto C0 = P - vertex0;
69 auto C1 = P - vertex1;
70 auto C2 = P - vertex2;
72 auto r0 = N.Dot(edge0.Cross(C0));
73 auto r1 = N.Dot(edge1.Cross(C1));
74 auto r2 = N.Dot(edge2.Cross(C2));
77 r2 > 0) return true; // P is inside the triangle
79 return false; // this ray hits the triangle
82 NavigationMesh::NavigationMesh(const std::vector<uint8_t>& buffer)
84 mBuffer.resize(buffer.size());
85 std::copy(buffer.begin(), buffer.end(), mBuffer.begin());
87 // Setup header from the buffer
88 mHeader = *reinterpret_cast<NavigationMeshHeader_V10*>(mBuffer.data());
89 mCurrentFace = Scene3D::Algorithm::NavigationMesh::NULL_FACE;
92 [[nodiscard]] uint32_t NavigationMesh::GetFaceCount() const
94 return mHeader.polyCount;
97 [[nodiscard]] uint32_t NavigationMesh::GetEdgeCount() const
99 return mHeader.edgeCount;
102 [[nodiscard]] uint32_t NavigationMesh::GetVertexCount() const
104 return mHeader.vertexCount;
107 bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
109 if(faceIndex == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
111 faceIndex = mCurrentFace;
114 // No current face, do full check
115 if(mCurrentFace == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
117 return FindFloor(position, outPosition);
121 ray.origin = PointSceneToLocal(Dali::Vector3(position)); // origin is equal position
123 // Ray direction matches gravity direction
124 ray.direction = Vector3(mHeader.gravityVector);
126 IntersectResult result = NavigationRayFaceIntersection(ray, *GetFace(faceIndex));
129 outPosition = PointLocalToScene(result.point);
134 if(dontCheckNeighbours)
139 // Test neighbouring by edges
140 const auto& poly = GetFace(faceIndex);
142 // collect faces sharing edges
143 std::vector<FaceIndex> neighbourFaces;
145 for(auto edgeIndex : poly->edge)
147 const auto& edge = *GetEdge(edgeIndex);
150 for(auto edgeFaceIndex : edge.face)
152 if(edgeFaceIndex != ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE && edgeFaceIndex != faceIndex)
154 neighbourFaces.emplace_back(edgeFaceIndex);
159 if(neighbourFaces.empty())
164 for(const auto& neighbourFaceIndex : neighbourFaces)
166 if(FindFloorForFace(position, neighbourFaceIndex, true, outPosition))
168 mCurrentFace = neighbourFaceIndex;
178 * Drops observer onto the floor
180 * When dropping observer, the nearest floor face is found
182 bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition)
184 FaceIndex faceIndex = 0u;
185 return FindFloor(position, outPosition, faceIndex);
188 bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& outFaceIndex)
190 [[maybe_unused]] auto newPos = PointSceneToLocal(Dali::Vector3(position));
194 ray.origin = PointSceneToLocal(Dali::Vector3(position)); // origin is equal position
196 // Ray direction matches gravity direction
197 ray.direction = Vector3(mHeader.gravityVector);
199 const auto POLY_COUNT = GetFaceCount();
200 std::vector<IntersectResult> results;
201 for(auto faceIndex = 0u; faceIndex < POLY_COUNT; ++faceIndex)
203 auto result = NavigationRayFaceIntersection(ray, *GetFace(faceIndex));
206 result.faceIndex = faceIndex;
207 results.emplace_back(result);
211 // find minimal distance to the floor and return that position and distance
217 std::sort(results.begin(), results.end(), [](const IntersectResult& lhs, const IntersectResult& rhs) { return lhs.distance < rhs.distance; });
219 outPosition = PointLocalToScene(results.front().point);
220 outFaceIndex = results.front().faceIndex;
221 mCurrentFace = outFaceIndex;
226 const Poly* NavigationMesh::GetFace(FaceIndex index) const
228 auto* basePtr = reinterpret_cast<const Poly*>(mBuffer.data() + mHeader.dataOffset + mHeader.polyDataOffset);
229 return &basePtr[index];
232 const Edge* NavigationMesh::GetEdge(EdgeIndex index) const
234 auto* basePtr = reinterpret_cast<const Edge*>(mBuffer.data() + mHeader.dataOffset + mHeader.edgeDataOffset);
235 return &basePtr[index];
238 const Vertex* NavigationMesh::GetVertex(VertexIndex index) const
240 auto* basePtr = reinterpret_cast<const Vertex*>(mBuffer.data() + mHeader.dataOffset + mHeader.vertexDataOffset);
241 return &basePtr[index];
244 NavigationMesh::IntersectResult NavigationMesh::NavigationRayFaceIntersection(NavigationRay& ray, const Face& face) const
246 auto vi0 = *GetVertex(face.vertex[0]);
247 auto vi1 = *GetVertex(face.vertex[1]);
248 auto vi2 = *GetVertex(face.vertex[2]);
250 IntersectResult result{Vector3::ZERO, 0.0f, 0u, false};
252 result.result = RayTriangleIntersect(ray.origin, ray.direction, Vector3(vi0.x, vi0.y, vi0.z), Vector3(vi1.x, vi1.y, vi1.z), Vector3(vi2.x, vi2.y, vi2.z), Vector3(face.normal), result.distance, result.point);
256 NavigationMesh::IntersectResult NavigationMesh::RayCastIntersect(NavigationRay& rayOrig) const
258 auto faceCount = GetFaceCount();
259 std::list<IntersectResult> results;
263 ray.origin = PointSceneToLocal(rayOrig.origin); // origin is equal position
265 // Ray direction matches gravity direction
266 ray.direction = PointSceneToLocal(rayOrig.origin + rayOrig.direction) - ray.origin;
267 ray.direction.Normalize();
268 for(auto i = 0u; i < faceCount; ++i)
270 auto result = NavigationRayFaceIntersection(ray, *GetFace(i));
273 result.faceIndex = i;
276 results.push_back(result);
280 for(auto it = results.begin(); it != results.end(); ++it)
282 if((*it).distance > result.distance)
284 results.insert(it, result);
293 return results.front();
297 return IntersectResult{Vector3::ZERO, 0.0f, 0u, false};
301 void NavigationMesh::SetTransform(const Dali::Matrix& transform)
303 mTransform = transform;
304 mTransformInverse = mTransform;
305 mTransformInverse.Invert();
308 Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point) const
310 Dali::Vector4 invNewPos = mTransformInverse * Dali::Vector4(point.x, point.y, point.z, 1.0f);
312 return Dali::Vector3(invNewPos.AsFloat());
315 Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point) const
317 // Transform point into scene transform space
318 Dali::Vector4 newPos = mTransform * Dali::Vector4(point.x, point.y, point.z, 1.0f);
320 return Dali::Vector3(newPos.AsFloat());
323 Dali::Vector3 NavigationMesh::GetGravityVector() const
325 return Dali::Vector3(mHeader.gravityVector);
328 } // namespace Dali::Scene3D::Internal::Algorithm