Divide Framework 0.1
A free and open-source 3D Framework under heavy development
Loading...
Searching...
No Matches
NavMeshLoader.cpp
Go to the documentation of this file.
1
2
4
15
17 constexpr U16 BYTE_BUFFER_VERSION = 1u;
18
19 constexpr U32 g_cubeFaces[6][4] = {{0, 4, 6, 2},
20 {0, 2, 3, 1},
21 {0, 1, 5, 4},
22 {3, 2, 6, 7},
23 {7, 6, 4, 5},
24 {3, 7, 5, 1}};
25
26char* ParseRow(char* buf, const char* const bufEnd, char* row, const I32 len) noexcept {
27 bool start = true;
28 bool done = false;
29 I32 n = 0;
30
31 while (!done && buf < bufEnd) {
32 const char c = *buf;
33 buf++;
34 // multi row
35 switch (c) {
36 case '\\':
37 break; // multi row
38 case '\n': {
39 if (start) {
40 break;
41 }
42 done = true;
43 } break;
44 case '\r':
45 break;
46 case '\t':
47 case ' ':
48 if (start) {
49 break;
50 }
51 [[fallthrough]];
52 default: {
53 start = false;
54 row[n++] = c;
55 if (n >= len - 1) {
56 done = true;
57 }
58 } break;
59 }
60 }
61
62 row[n] = '\0';
63 return buf;
64}
65
66I32 ParseFace(char* row, I32* data, const I32 n, const I32 vcnt) noexcept {
67 I32 j = 0;
68 while (*row != '\0') {
69 // Skip initial white space
70 while (*row != '\0' && (*row == ' ' || *row == '\t')) {
71 row++;
72 }
73
74 const char* s = row;
75 // Find vertex delimiter and terminated the string there for conversion.
76 while (*row != '\0' && *row != ' ' && *row != '\t') {
77 if (*row == '/') {
78 *row = '\0';
79 }
80 row++;
81 }
82
83 if (*s == '\0') {
84 continue;
85 }
86
87 const I32 vi = atoi(s);
88 data[j++] = vi < 0 ? vi + vcnt : vi - 1;
89 if (j >= n) {
90 return j;
91 }
92 }
93 return j;
94}
95
96bool LoadMeshFile(NavModelData& outData, const ResourcePath& filePath, const char* fileName) {
97 STUBBED("ToDo: Rework load/save to properly use a ByteBuffer instead of this const char* hackery. -Ionut");
98
99 ByteBuffer tempBuffer;
100 if (!tempBuffer.loadFromFile(filePath, fileName))
101 {
102 return false;
103 }
104
105 auto tempVer = decltype(BYTE_BUFFER_VERSION){0};
106 tempBuffer >> tempVer;
107 if (tempVer != BYTE_BUFFER_VERSION) {
108 return false;
109 }
110
111 char* buf = new char[tempBuffer.storageSize()];
112 std::memcpy(buf, reinterpret_cast<const char*>(tempBuffer.contents()), tempBuffer.storageSize());
113 char* srcEnd = buf + tempBuffer.storageSize();
114
115 char* src = buf;
116
117 char row[512] = {};
118 I32 face[32] = {};
119 F32 x, y, z;
120 while (src < srcEnd) {
121 // Parse one row
122 row[0] = '\0';
123 src = ParseRow(src, srcEnd, row, sizeof row / sizeof(char));
124 // Skip comments
125 if (row[0] == '#')
126 continue;
127
128 if (row[0] == 'v' && row[1] != 'n' && row[1] != 't') {
129 // Vertex pos
130 const I32 result = sscanf(row + 1, "%f %f %f", &x, &y, &z);
131 if (result != 0)
132 AddVertex(&outData, vec3<F32>(x, y, z));
133 }
134 if (row[0] == 'f') {
135 // Faces
136 const I32 nv = ParseFace(row + 1, face, 32, outData._vertexCount);
137 for (I32 i = 2; i < nv; ++i) {
138 const I32 a = face[0];
139 const I32 b = face[i - 1];
140 const I32 c = face[i];
141 if (a < 0 || a >= to_I32(outData._vertexCount) || b < 0 ||
142 b >= to_I32(outData._vertexCount) || c < 0 ||
143 c >= to_I32(outData._vertexCount)) {
144 continue;
145 }
146
147 AddTriangle(&outData, vec3<U32>(a, b, c));
148 }
149 }
150 }
151
152 delete[] buf;
153
154 // Calculate normals.
155 outData._normals.resize(outData._triangleCount * 3);
156
157 for (I32 i = 0; i < to_I32(outData._triangleCount) * 3; i += 3) {
158 const F32* v0 = &outData._vertices[outData._triangles[i] * 3];
159 const F32* v1 = &outData._vertices[outData._triangles[i + 1] * 3];
160 const F32* v2 = &outData._vertices[outData._triangles[i + 2] * 3];
161
162 F32 e0[3] = {}, e1[3] = {};
163 for (I32 j = 0; j < 3; ++j) {
164 e0[j] = v1[j] - v0[j];
165 e1[j] = v2[j] - v0[j];
166 }
167
168 F32* n = &outData._normals[i];
169 n[0] = e0[1] * e1[2] - e0[2] * e1[1];
170 n[1] = e0[2] * e1[0] - e0[0] * e1[2];
171 n[2] = e0[0] * e1[1] - e0[1] * e1[0];
172 F32 d = sqrtf(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
173 if (d > 0) {
174 d = 1.0f / d;
175 n[0] *= d;
176 n[1] *= d;
177 n[2] *= d;
178 }
179 }
180
181 return true;
182}
183
184bool SaveMeshFile(const NavModelData& inData, const ResourcePath& filePath, const char* filename) {
185 if (!inData.getVertCount() || !inData.getTriCount())
186 {
187 return false;
188 }
189
190 ByteBuffer tempBuffer;
191 tempBuffer << BYTE_BUFFER_VERSION;
192
193 const F32* vStart = inData._vertices.data();
194 const I32* tStart = inData._triangles.data();
195 for (U32 i = 0; i < inData.getVertCount(); i++) {
196 const F32* vp = vStart + i * 3;
197 tempBuffer << "v " << *vp << " " << *(vp + 1) << " " << *(vp + 2) << "\n";
198 }
199 for (U32 i = 0; i < inData.getTriCount(); i++) {
200 const I32* tp = tStart + i * 3;
201 tempBuffer << "f " << *tp + 1 << " " << *(tp + 1) + 1 << " " << *(tp + 2) + 1 << "\n";
202 }
203
204 return tempBuffer.dumpToFile(filePath, filename);
205}
206
208 NavModelData& b,
209 const bool delOriginals /* = false*/) {
210 NavModelData mergedData;
211 if (a.getVerts() || b.getVerts()) {
212 if (!a.getVerts()) {
213 return b;
214 }
215
216 if (!b.getVerts()) {
217 return a;
218 }
219
220 mergedData.clear();
221
222 const I32 totalVertCt = a.getVertCount() + b.getVertCount();
223 I32 newCap = 8;
224
225 while (newCap < totalVertCt) {
226 newCap *= 2;
227 }
228
229 mergedData._vertices.resize(newCap * 3);
230 mergedData._vertexCapacity = newCap;
231 mergedData._vertexCount = totalVertCt;
232
233 memcpy(mergedData._vertices.data(), a.getVerts(), a.getVertCount() * 3 * sizeof(F32));
234 memcpy(mergedData._vertices.data() + a.getVertCount() * 3, b.getVerts(), b.getVertCount() * 3 * sizeof(F32));
235
236 const I32 totalTriCt = a.getTriCount() + b.getTriCount();
237 newCap = 8;
238
239 while (newCap < totalTriCt) {
240 newCap *= 2;
241 }
242
243 mergedData._triangles.resize(newCap * 3);
244 mergedData._triangleCapacity = newCap;
245 mergedData._triangleCount = totalTriCt;
246 const I32 aFaceSize = a.getTriCount() * 3;
247 memcpy(mergedData._triangles.data(), a.getTris(), aFaceSize * sizeof(I32));
248
249 const I32 bFaceSize = b.getTriCount() * 3;
250 I32* bFacePt = mergedData._triangles.data() + a.getTriCount() * 3; // i like pointing at faces
251 memcpy(bFacePt, b.getTris(), bFaceSize * sizeof(I32));
252
253 for (U32 i = 0; i < to_U32(bFaceSize); i++) {
254 *(bFacePt + i) += a.getVertCount();
255 }
256
257 if (mergedData._vertexCount > 0) {
258 if (delOriginals) {
259 a.clear();
260 b.clear();
261 }
262 } else {
263 mergedData.clear();
264 }
265 }
266
267 mergedData.name(Util::StringFormat("{}+{}", a.name(), b.name() ).c_str());
268 return mergedData;
269}
270
271void AddVertex(NavModelData* modelData, const vec3<F32>& vertex) {
272 assert(modelData != nullptr);
273
274 if (modelData->getVertCount() + 1 > modelData->_vertexCapacity)
275 {
276 modelData->_vertexCapacity = !modelData->_vertexCapacity ? 8 : modelData->_vertexCapacity * 2;
277
278 modelData->_vertices.resize(modelData->_vertexCapacity * 3);
279 }
280
281 F32* dst = &modelData->_vertices[modelData->getVertCount() * 3];
282 *dst++ = vertex.x;
283 *dst++ = vertex.y;
284 *dst++ = vertex.z;
285
286 modelData->_vertexCount++;
287}
288
289void AddTriangle(NavModelData* modelData,
290 const vec3<U32>& triangleIndices,
291 const U32 triangleIndexOffset,
292 const SamplePolyAreas& areaType) {
293 if (modelData->getTriCount() + 1 > modelData->_triangleCapacity)
294 {
295 modelData->_triangleCapacity = !modelData->_triangleCapacity ? 8 : modelData->_triangleCapacity * 2;
296
297 modelData->_triangles.resize(modelData->_triangleCapacity * 3);
298 }
299
300 I32* dst = &modelData->_triangles[modelData->getTriCount() * 3];
301
302 *dst++ = to_I32(triangleIndices.x + triangleIndexOffset);
303 *dst++ = to_I32(triangleIndices.y + triangleIndexOffset);
304 *dst++ = to_I32(triangleIndices.z + triangleIndexOffset);
305
306 modelData->getAreaTypes().push_back(areaType);
307 modelData->_triangleCount++;
308}
309
311bool Parse(const BoundingBox& box, NavModelData& outData, SceneGraphNode* sgn) {
312 assert(sgn != nullptr);
313
314 const NavigationComponent* navComp = sgn->get<NavigationComponent>();
315 if (navComp &&
316 navComp->navigationContext() != NavigationComponent::NavigationContext::NODE_IGNORE && // Ignore if specified
317 box.getHeight() > 0.05f) // Skip small objects
318 {
319 const SceneNodeType nodeType = sgn->getNode().type();
320 const char* resourceName = sgn->getNode().resourceName().c_str();
321
322 if (nodeType != SceneNodeType::TYPE_WATER && !Is3DObject(nodeType))
323 {
324 Console::printfn(LOCALE_STR("WARN_NAV_UNSUPPORTED"), resourceName);
325 goto next;
326 }
327
328 if (nodeType == SceneNodeType::TYPE_MESH)
329 {
330 // Even though we allow Object3Ds, we do not parse MESH nodes, instead we grab its children so we get an accurate triangle list per node
331 goto next;
332 }
333
336 if ( Is3DObject(nodeType)) {
337 // Check if we need to override detail level
338 if ( navComp && !navComp->navMeshDetailOverride() && sgn->usageContext() == NodeUsageContext::NODE_STATIC )
339 {
341 }
342 if ( nodeType == SceneNodeType::TYPE_TERRAIN )
343 {
345 }
346 }
347 else if ( nodeType == SceneNodeType::TYPE_WATER)
348 {
349 if (navComp && !navComp->navMeshDetailOverride()) {
351 }
353 }
354 else
355 {
356 // we should never reach this due to the bit checks above
358 }
359
360 Console::d_printfn(LOCALE_STR("NAV_MESH_CURRENT_NODE"), resourceName, to_base(level));
361
362 const U32 currentTriangleIndexOffset = outData.getVertCount();
363 VertexBuffer* geometry = nullptr;
364 if (level == MeshDetailLevel::MAXIMUM)
365 {
366 Object3D* obj = nullptr;
367 if (Is3DObject(nodeType))
368 {
369 obj = &sgn->getNode<Object3D>();
370 }
371 else if (nodeType == SceneNodeType::TYPE_WATER)
372 {
373 obj = Get(sgn->getNode<WaterPlane>().getQuad());
374 }
375 assert(obj != nullptr);
376 geometry = obj->geometryBuffer().get();
377 assert(geometry != nullptr);
378
379 const auto& vertices = geometry->getVertices();
380 if (vertices.empty()) {
381 Console::printfn(LOCALE_STR("NAV_MESH_NODE_NO_DATA"), resourceName);
382 goto next;
383 }
384
385 const auto& triangles = obj->getTriangles(obj->getGeometryPartitionID(0u));
386 if (triangles.empty()) {
387 Console::printfn(LOCALE_STR("NAV_MESH_NODE_NO_DATA"), resourceName);
388 goto next;
389 }
390
391 mat4<F32> nodeTransform = MAT4_IDENTITY;
392 sgn->get<TransformComponent>()->getWorldMatrix(nodeTransform);
393
394 for (const VertexBuffer::Vertex& vert : vertices) {
395 // Apply the node's transform and add the vertex to the NavMesh
396 AddVertex(&outData, nodeTransform * vert._position);
397 }
398
399 for (const vec3<U32>& triangle : triangles) {
400 AddTriangle(&outData, triangle, currentTriangleIndexOffset, areaType);
401 }
402 } else if (level == MeshDetailLevel::BOUNDINGBOX) {
403 std::array<vec3<F32>, 8> vertices = box.getPoints();
404
405 for (U32 i = 0; i < 8; i++) {
406 AddVertex(&outData, vertices[i]);
407 }
408
409 for (U32 f = 0; f < 6; f++) {
410 for (U32 v = 2; v < 4; v++) {
411 // Note: We reverse the normal on the polygons to prevent things from going inside out
412 AddTriangle(&outData,
413 vec3<U32>(g_cubeFaces[f][0], g_cubeFaces[f][v - 1],
414 g_cubeFaces[f][v]),
415 currentTriangleIndexOffset, areaType);
416 }
417 }
418 } else {
419 Console::errorfn(LOCALE_STR("ERROR_RECAST_LEVEL"), to_base(level));
420 }
421
422 Console::printfn(LOCALE_STR("NAV_MESH_ADD_NODE"), resourceName);
423 }
424
425next: // although labels are bad, skipping here using them is the easiest solution to follow -Ionut
426 const SceneGraphNode::ChildContainer& children = sgn->getChildren();
427 const U32 childCount = children._count;
428 for (U32 i = 0u; i < childCount; ++i) {
429 SceneGraphNode* child = children._data[i];
430 if (!Parse(child->get<BoundsComponent>()->getBoundingBox(), outData, child)) {
431 return false;
432 }
433 }
434
435 return true;
436}
437
438} // namespace Divide::AI::Navigation::NavigationMeshLoader
#define LOCALE_STR(X)
Definition: Localization.h:91
#define STUBBED(x)
#define DIVIDE_UNEXPECTED_CALL()
const F32 * getVerts() const noexcept
Definition: NavMeshLoader.h:86
const I32 * getTris() const noexcept
Definition: NavMeshLoader.h:88
std::array< vec3< F32 >, 8 > getPoints() const noexcept
F32 getHeight() const noexcept
const BoundingBox & getBoundingBox() const noexcept
size_t storageSize() const noexcept
Returns the total size (in bytes) of the underlying storage, regardles of wpos and rpos.
Definition: ByteBuffer.inl:357
const Byte * contents() const noexcept
Returns a raw pointer to the underlying storage data. Does NOT depend on the read head position!...
Definition: ByteBuffer.inl:377
bool loadFromFile(const ResourcePath &path, std::string_view fileName, const U8 version=BUFFER_FORMAT_VERSION)
Definition: ByteBuffer.cpp:57
bool dumpToFile(const ResourcePath &path, std::string_view fileName, const U8 version=BUFFER_FORMAT_VERSION)
Saves the entire buffer contents to file. Always appends the version at the end of the file.
Definition: ByteBuffer.cpp:47
const NavigationContext & navigationContext() const noexcept
bool navMeshDetailOverride() const noexcept
FORCE_INLINE T * get() const
Returns a pointer to a specific component. Returns null if the SGN does not have the component reques...
T & getNode() noexcept
ChildContainer & getChildren() noexcept
const vector< Vertex > & getVertices() const noexcept
bool SaveMeshFile(const NavModelData &inData, const ResourcePath &filePath, const char *filename)
Save the navigation input geometry in Wavefront OBJ format.
void AddVertex(NavModelData *modelData, const vec3< F32 > &vertex)
char * ParseRow(char *buf, const char *const bufEnd, char *row, I32 len) noexcept
bool LoadMeshFile(NavModelData &outData, const ResourcePath &filePath, const char *fileName)
Load the input geometry from file (Wavefront OBJ format) and save it in 'outData'.
bool Parse(const BoundingBox &box, NavModelData &outData, SceneGraphNode *sgn)
Parsing method that calls itself recursively until all geometry has been parsed.
NavModelData MergeModels(NavModelData &a, NavModelData &b, bool delOriginals=false)
Merge the data from two navigation geometry sources.
I32 ParseFace(char *row, I32 *data, I32 n, I32 vcnt) noexcept
void AddTriangle(NavModelData *modelData, const vec3< U32 > &triangleIndices, U32 triangleIndexOffset=0, const SamplePolyAreas &areaType=SamplePolyAreas::SAMPLE_POLYAREA_GROUND)
const vec3< F32 > g_borderOffset(BORDER_PADDING)
constexpr F32 BORDER_PADDING
Str StringFormat(const char *fmt, Args &&...args)
constexpr U32 to_U32(const T value)
int32_t I32
SceneNodeType
ToDo: Move particle emitter to components (it will make them way more dynamic) - Ionut.
Definition: SceneNodeFwd.h:47
uint16_t U16
FORCE_INLINE constexpr bool Is3DObject(const SceneNodeType type) noexcept
Definition: SceneNodeFwd.h:98
constexpr I32 to_I32(const T value)
static const mat4< F32 > MAT4_IDENTITY
Definition: MathMatrices.h:740
FORCE_INLINE T * Get(const Handle< T > handle)
uint32_t U32
constexpr auto to_base(const Type value) -> Type
static NO_INLINE void d_printfn(const char *format, T &&... args)
static NO_INLINE void errorfn(const char *format, T &&... args)
static NO_INLINE void printfn(const char *format, T &&... args)
eastl::fixed_vector< SceneGraphNode *, 32, true > _data