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>
31 namespace Dali::Scene3D::Internal::Algorithm
33 using Poly = Dali::Scene3D::Algorithm::NavigationMesh::Face;
34 using Edge = Dali::Scene3D::Algorithm::NavigationMesh::Edge;
35 using Vertex = Dali::Scene3D::Algorithm::NavigationMesh::Vertex;
37 // Internal Navigation ray structure
40 Dali::Vector3 origin; // Origin of ray
41 Dali::Vector3 direction; // Direction of ray
45 * Helper function calculating intersection point between triangle and ray
47 static bool RayTriangleIntersect(
48 const Vector3& origin, const Vector3& direction, const Vector3& vertex0, const Vector3& vertex1, const Vector3& vertex2, const Vector3& normal, float& outDistance, Vector3& position)
54 // check if ray and plane are parallel ?
55 float NdotRayDirection = N.Dot(direction);
56 if(Dali::Equals(NdotRayDirection, 0.0f))
58 return false; // they are parallel so they don'outDistance intersect !
61 // compute d parameter using equation 2
62 float d = -N.Dot(vertex0);
64 // compute outDistance (equation 3)
65 outDistance = -(N.Dot(origin) + d) / NdotRayDirection;
67 // check if the triangle is in behind the ray
68 if(outDistance < 0) return false; // the triangle is behind
70 // compute the intersection point using equation 1
71 auto P = origin + (direction * outDistance);
75 auto edge0 = vertex1 - vertex0;
76 auto edge1 = vertex2 - vertex1;
77 auto edge2 = vertex0 - vertex2;
78 auto C0 = P - vertex0;
79 auto C1 = P - vertex1;
80 auto C2 = P - vertex2;
82 auto r0 = N.Dot(edge0.Cross(C0));
83 auto r1 = N.Dot(edge1.Cross(C1));
84 auto r2 = N.Dot(edge2.Cross(C2));
87 r2 > 0) return true; // P is inside the triangle
89 return false; // this ray hits the triangle
92 NavigationMesh::NavigationMesh(const std::vector<uint8_t>& buffer)
94 mBuffer.resize(buffer.size());
95 std::copy(buffer.begin(), buffer.end(), mBuffer.begin());
97 // Setup header from the buffer
98 mHeader = *reinterpret_cast<NavigationMeshHeader_V10*>(mBuffer.data());
99 mCurrentFace = Scene3D::Algorithm::NavigationMesh::NULL_FACE;
102 [[nodiscard]] uint32_t NavigationMesh::GetFaceCount() const
104 return mHeader.polyCount;
107 [[nodiscard]] uint32_t NavigationMesh::GetEdgeCount() const
109 return mHeader.edgeCount;
112 [[nodiscard]] uint32_t NavigationMesh::GetVertexCount() const
114 return mHeader.vertexCount;
117 bool NavigationMesh::FindFloorForFace(const Dali::Vector3& position, uint32_t faceIndex, bool dontCheckNeighbours, Dali::Vector3& outPosition)
119 if(faceIndex == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
121 faceIndex = mCurrentFace;
124 // No current face, do full check
125 if(mCurrentFace == ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE)
127 return FindFloor(position, outPosition);
131 ray.origin = PointSceneToLocal(Dali::Vector3(position)); // origin is equal position
133 // Ray direction matches gravity direction
134 ray.direction = Vector3(mHeader.gravityVector);
136 IntersectResult result = NavigationRayFaceIntersection(ray, *GetFace(uint16_t(faceIndex)));
139 outPosition = PointLocalToScene(result.point);
144 if(dontCheckNeighbours)
149 // Test neighbouring by edges
150 const auto& poly = GetFace(uint16_t(faceIndex));
152 // collect faces sharing edges
153 std::vector<uint32_t> faces;
155 for(unsigned short i : poly->edge)
157 const auto& edge = *GetEdge(i);
160 for(unsigned short j : edge.face)
162 if(j != ::Dali::Scene3D::Algorithm::NavigationMesh::NULL_FACE && j != faceIndex)
164 faces.emplace_back(j);
174 for(const auto& p : faces)
176 if(FindFloorForFace(position, p, true, outPosition))
188 * Drops observer onto the floor
190 * When dropping observer, the nearest floor face is found
192 bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition)
194 uint32_t polyIndex = 0;
195 return FindFloor(position, outPosition, polyIndex);
198 bool NavigationMesh::FindFloor(const Dali::Vector3& position, Dali::Vector3& outPosition, uint32_t& faceIndex)
200 [[maybe_unused]] auto newPos = PointSceneToLocal(Dali::Vector3(position));
204 ray.origin = PointSceneToLocal(Dali::Vector3(position)); // origin is equal position
206 // Ray direction matches gravity direction
207 ray.direction = Vector3(mHeader.gravityVector);
209 const auto POLY_COUNT = GetFaceCount();
210 std::vector<IntersectResult> results;
211 for(auto i = 0u; i < POLY_COUNT; ++i)
213 auto result = NavigationRayFaceIntersection(ray, *GetFace(i));
216 result.faceIndex = i;
217 results.emplace_back(result);
221 // find minimal distance to the floor and return that position and distance
227 std::sort(results.begin(), results.end(), [](const IntersectResult& lhs, const IntersectResult& rhs) { return lhs.distance < rhs.distance; });
229 outPosition = PointLocalToScene(results.front().point);
230 faceIndex = results.front().faceIndex;
231 mCurrentFace = results.front().faceIndex;
236 const Poly* NavigationMesh::GetFace(int index) const
238 auto* basePtr = reinterpret_cast<const Poly*>(mBuffer.data() + mHeader.dataOffset + mHeader.polyDataOffset);
239 return &basePtr[index];
242 const Edge* NavigationMesh::GetEdge(int index) const
244 auto* basePtr = reinterpret_cast<const Edge*>(mBuffer.data() + mHeader.dataOffset + mHeader.edgeDataOffset);
245 return &basePtr[index];
248 const Vertex* NavigationMesh::GetVertex(int index) const
250 auto* basePtr = reinterpret_cast<const Vertex*>(mBuffer.data() + mHeader.dataOffset + mHeader.vertexDataOffset);
251 return &basePtr[index];
254 NavigationMesh::IntersectResult NavigationMesh::NavigationRayFaceIntersection(NavigationRay& ray, const Face& face)
256 auto vi0 = *GetVertex(face.vertex[0]);
257 auto vi1 = *GetVertex(face.vertex[1]);
258 auto vi2 = *GetVertex(face.vertex[2]);
260 IntersectResult result{Vector3::ZERO, 0.0f, 0u, false};
262 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);
266 void NavigationMesh::SetTransform(const Dali::Matrix& transform)
268 mTransform = transform;
269 transform.InvertTransform(mTransformInverse);
272 Dali::Vector3 NavigationMesh::PointSceneToLocal(const Dali::Vector3& point)
274 // Transform point into navmesh space
275 Dali::Vector4 invNewPos = mTransformInverse * Dali::Vector4(point.x, -point.y, point.z, 1.0f);
276 invNewPos.y *= -1.0f;
278 return Dali::Vector3(invNewPos.AsFloat());
281 Dali::Vector3 NavigationMesh::PointLocalToScene(const Dali::Vector3& point)
283 // Transform point into scene transform space
284 Dali::Vector4 newPos = mTransform * Dali::Vector4(point.x, -point.y, point.z, 1.0f);
287 return Dali::Vector3(newPos.AsFloat());
290 Dali::Vector3 NavigationMesh::GetGravityVector() const
292 return Dali::Vector3(mHeader.gravityVector);
295 } // namespace Dali::Scene3D::Internal::Algorithm