2 * Copyright (c) 2023 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;
34 // Internal Navigation ray structure
37 Dali::Vector3 origin; // Origin of ray
38 Dali::Vector3 direction; // Direction of ray
42 * Helper function calculating intersection point between triangle and ray
44 static bool RayTriangleIntersect(
45 const Vector3& origin, const Vector3& direction, const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2, const Vector3& normal, float& outDistance, Vector3& position)
51 // check if ray and plane are parallel ?
52 float NdotRayDirection = N.Dot(direction);
53 if(Dali::Equals(NdotRayDirection, 0.0f))
55 return false; // they are parallel so they don'outDistance intersect !
58 // compute d parameter using equation 2
59 float d = -N.Dot(vertex0);
61 // compute outDistance (equation 3)
62 outDistance = -(N.Dot(origin) + d) / NdotRayDirection;
64 // check if the triangle is in behind the ray
65 if(outDistance < 0) return false; // the triangle is behind
67 // compute the intersection point using equation 1
68 auto P = origin + (direction * outDistance);
72 auto edge0 = vertex1 - vertex0;
73 auto edge1 = vertex2 - vertex1;
74 auto edge2 = vertex0 - vertex2;
75 auto C0 = P - vertex0;
76 auto C1 = P - vertex1;
77 auto C2 = P - vertex2;
79 auto r0 = N.Dot(edge0.Cross(C0));
80 auto r1 = N.Dot(edge1.Cross(C1));
81 auto r2 = N.Dot(edge2.Cross(C2));
84 r2 > 0) return true; // P is inside the triangle
86 return false; // this ray hits the triangle
89 NavigationMesh::NavigationMesh(const std::vector<uint8_t>& buffer)
91 mBuffer.resize(buffer.size());
92 std::copy(buffer.begin(), buffer.end(), mBuffer.begin());
94 // Setup header from the buffer
95 mHeader = *reinterpret_cast<NavigationMeshHeader_V10*>(mBuffer.data());
96 mCurrentFace = Scene3D::Algorithm::NavigationMesh::NULL_FACE;
99 [[nodiscard]] uint32_t NavigationMesh::GetFaceCount() const
101 return mHeader.polyCount;
104 [[nodiscard]] uint32_t NavigationMesh::GetEdgeCount() const
106 return mHeader.edgeCount;
109 [[nodiscard]] uint32_t NavigationMesh::GetVertexCount() const
111 return mHeader.vertexCount;
114 bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, FaceIndex faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
116 if(faceIndex == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
118 faceIndex = mCurrentFace;
121 // No current face, do full check
122 if(mCurrentFace == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
124 return FindFloor(position, outPosition);
128 ray.origin = PointSceneToLocal(Dali::Vector3(position)); // origin is equal position
130 // Ray direction matches gravity direction
131 ray.direction = Vector3(mHeader.gravityVector);
133 IntersectResult result = NavigationRayFaceIntersection(ray, *GetFace(faceIndex));
136 outPosition = PointLocalToScene(result.point);
141 if(dontCheckNeighbours)
146 // Test neighbouring by edges
147 const auto& poly = GetFace(faceIndex);
149 // collect faces sharing edges
150 std::vector<FaceIndex> neighbourFaces;
152 for(auto edgeIndex : poly->edge)
154 const auto& edge = *GetEdge(edgeIndex);
157 for(auto edgeFaceIndex : edge.face)
159 if(edgeFaceIndex != ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE && edgeFaceIndex != faceIndex)
161 neighbourFaces.emplace_back(edgeFaceIndex);
166 if(neighbourFaces.empty())
171 for(const auto& neighbourFaceIndex : neighbourFaces)
173 if(FindFloorForFace(position, neighbourFaceIndex, true, outPosition))
175 mCurrentFace = neighbourFaceIndex;
185 * Drops observer onto the floor
187 * When dropping observer, the nearest floor face is found
189 bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition)
191 FaceIndex faceIndex = 0u;
192 return FindFloor(position, outPosition, faceIndex);
195 bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, FaceIndex& faceIndex)
197 [[maybe_unused]] auto newPos = PointSceneToLocal(Dali::Vector3(position));
201 ray.origin = PointSceneToLocal(Dali::Vector3(position)); // origin is equal position
203 // Ray direction matches gravity direction
204 ray.direction = Vector3(mHeader.gravityVector);
206 const auto POLY_COUNT = GetFaceCount();
207 std::vector<IntersectResult> results;
208 for(auto faceIndex = 0u; faceIndex < POLY_COUNT; ++faceIndex)
210 auto result = NavigationRayFaceIntersection(ray, *GetFace(faceIndex));
213 result.faceIndex = faceIndex;
214 results.emplace_back(result);
218 // find minimal distance to the floor and return that position and distance
224 std::sort(results.begin(), results.end(), [](const IntersectResult& lhs, const IntersectResult& rhs) { return lhs.distance < rhs.distance; });
226 outPosition = PointLocalToScene(results.front().point);
227 faceIndex = results.front().faceIndex;
228 mCurrentFace = results.front().faceIndex;
233 const Poly* NavigationMesh::GetFace(FaceIndex index) const
235 auto* basePtr = reinterpret_cast<const Poly*>(mBuffer.data() + mHeader.dataOffset + mHeader.polyDataOffset);
236 return &basePtr[index];
239 const Edge* NavigationMesh::GetEdge(EdgeIndex index) const
241 auto* basePtr = reinterpret_cast<const Edge*>(mBuffer.data() + mHeader.dataOffset + mHeader.edgeDataOffset);
242 return &basePtr[index];
245 const Vertex* NavigationMesh::GetVertex(VertexIndex index) const
247 auto* basePtr = reinterpret_cast<const Vertex*>(mBuffer.data() + mHeader.dataOffset + mHeader.vertexDataOffset);
248 return &basePtr[index];
251 NavigationMesh::IntersectResult NavigationMesh::NavigationRayFaceIntersection(NavigationRay& ray, const Face& face) const
253 auto vi0 = *GetVertex(face.vertex[0]);
254 auto vi1 = *GetVertex(face.vertex[1]);
255 auto vi2 = *GetVertex(face.vertex[2]);
257 IntersectResult result{Vector3::ZERO, 0.0f, 0u, false};
259 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);
263 void NavigationMesh::SetTransform(const Dali::Matrix& transform)
265 mTransform = transform;
266 transform.InvertTransform(mTransformInverse);
269 Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point) const
271 // Transform point into navmesh space
272 Dali::Vector4 invNewPos = mTransformInverse * Dali::Vector4(point.x, -point.y, point.z, 1.0f);
273 invNewPos.y *= -1.0f;
275 return Dali::Vector3(invNewPos.AsFloat());
278 Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point) const
280 // Transform point into scene transform space
281 Dali::Vector4 newPos = mTransform * Dali::Vector4(point.x, -point.y, point.z, 1.0f);
284 return Dali::Vector3(newPos.AsFloat());
287 Dali::Vector3 NavigationMesh::GetGravityVector() const
289 return Dali::Vector3(mHeader.gravityVector);
292 } // namespace Dali::Scene3D::Internal::Algorithm