CARLA
Loading...
Searching...
No Matches
Mesh.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
7#include <carla/geom/Mesh.h>
8
9#include <string>
10#include <sstream>
11#include <ios>
12#include <iostream>
13#include <fstream>
14
15#include <carla/geom/Math.h>
16
17namespace carla {
18namespace geom {
19
20 bool Mesh::IsValid() const {
21 // should be at least some one vertex
22 if (_vertices.empty()) {
23 std::cout << "Mesh validation error: there are no vertices in the mesh." << std::endl;
24 return false;
25 }
26
27 // if there are indices, the amount must be multiple of 3
28 if (!_indexes.empty() && _indexes.size() % 3 != 0) {
29 std::cout << "Mesh validation error: the index amount must be multiple of 3." << std::endl;
30 return false;
31 }
32
33 if (!_materials.empty() && _materials.back().index_end == 0) {
34 std::cout << "Mesh validation error: last material was not closed." << std::endl;
35 return false;
36 }
37
38 return true;
39 }
40
41 // e.g:
42 // 1 3 5 7
43 // #---#---#---#
44 // | / | / | / |
45 // #---#---#---#
46 // 2 4 6 8
47 void Mesh::AddTriangleStrip(const std::vector<Mesh::vertex_type> &vertices) {
48 if (vertices.size() == 0) {
49 return;
50 }
51 DEBUG_ASSERT(vertices.size() >= 3);
52 size_t i = GetVerticesNum() + 2;
53 AddVertices(vertices);
54 bool index_clockwise = true;
55 while (i < GetVerticesNum()) {
56 index_clockwise = !index_clockwise;
57 if (index_clockwise) {
58 AddIndex(i + 1);
59 AddIndex(i);
60 AddIndex(i - 1);
61 } else {
62 AddIndex(i - 1);
63 AddIndex(i);
64 AddIndex(i + 1);
65 }
66 ++i;
67 }
68 }
69
70 // e.g:
71 // 2 1 6
72 // #---#---#
73 // | / | \ |
74 // #---#---#
75 // 3 4 5
76 void Mesh::AddTriangleFan(const std::vector<Mesh::vertex_type> &vertices) {
77 DEBUG_ASSERT(vertices.size() >= 3);
78 const size_t initial_index = GetVerticesNum() + 1;
79 size_t i = GetVerticesNum() + 2;
80 AddVertices(vertices);
81 while (i < GetVerticesNum()) {
82 AddIndex(initial_index);
83 AddIndex(i);
84 AddIndex(i + 1);
85 ++i;
86 }
87 }
88
90 _vertices.push_back(vertex);
91 }
92
93 void Mesh::AddVertices(const std::vector<Mesh::vertex_type> &vertices) {
94 std::copy(vertices.begin(), vertices.end(), std::back_inserter(_vertices));
95 }
96
98 _normals.push_back(normal);
99 }
100
102 _indexes.push_back(index);
103 }
104
106 _uvs.push_back(uv);
107 }
108
109 void Mesh::AddUVs(const std::vector<uv_type> & uv) {
110 std::copy(uv.begin(), uv.end(), std::back_inserter(_uvs));
111 }
112
113 void Mesh::AddMaterial(const std::string &material_name) {
114 const size_t open_index = _indexes.size();
115 if (!_materials.empty()) {
116 if (_materials.back().index_end == 0) {
117 // @todo: change this comment to a debug warning
118 // std::cout << "last material was not closed, closing it..." << std::endl;
119 EndMaterial();
120 }
121 }
122 if (open_index % 3 != 0) {
123 std::cout << "open_index % 3 != 0" << std::endl;
124 return;
125 }
126 _materials.emplace_back(material_name, open_index, 0);
127 }
128
130 const size_t close_index = _indexes.size();
131 if (_materials.empty() ||
132 _materials.back().index_start == close_index ||
133 _materials.back().index_end != 0) {
134 // @todo: change this comment to a debug warning
135 // std::cout << "WARNING: Bad end of material. Material not started." << std::endl;
136 return;
137 }
138 if (_indexes.empty() || close_index % 3 != 0) {
139 // @todo: change this comment to a debug warning
140 // std::cout << "WARNING: Bad end of material. Face not started/ended." << std::endl;
141 return;
142 }
143 _materials.back().index_end = close_index;
144 }
145
146 std::string Mesh::GenerateOBJ() const {
147 if (!IsValid()) {
148 return "";
149 }
150 std::stringstream out;
151 out << std::fixed; // Avoid using scientific notation
152
153 out << "# List of geometric vertices, with (x, y, z) coordinates." << std::endl;
154 for (auto &v : _vertices) {
155 out << "v " << v.x << " " << v.y << " " << v.z << std::endl;
156 }
157
158 if (!_uvs.empty()) {
159 out << std::endl << "# List of texture coordinates, in (u, v) coordinates, these will vary between 0 and 1." << std::endl;
160 for (auto &vt : _uvs) {
161 out << "vt " << vt.x << " " << vt.y << std::endl;
162 }
163 }
164
165 if (!_normals.empty()) {
166 out << std::endl << "# List of vertex normals in (x, y, z) form; normals might not be unit vectors." << std::endl;
167 for (auto &vn : _normals) {
168 out << "vn " << vn.x << " " << vn.y << " " << vn.z << std::endl;
169 }
170 }
171
172 if (!_indexes.empty()) {
173 out << std::endl << "# Polygonal face element." << std::endl;
174 auto it_m = _materials.begin();
175 auto it = _indexes.begin();
176 size_t index_counter = 0u;
177 while (it != _indexes.end()) {
178 // While exist materials
179 if (it_m != _materials.end()) {
180 // If the current material ends at this index
181 if (it_m->index_end == index_counter) {
182 ++it_m;
183 }
184 // If the current material start at this index
185 if (it_m->index_start == index_counter) {
186 out << "\nusemtl " << it_m->name << std::endl;
187 }
188 }
189
190 // Add the actual face using the 3 consecutive indices
191 out << "f " << *it; ++it;
192 out << " " << *it; ++it;
193 out << " " << *it << std::endl; ++it;
194
195 index_counter += 3;
196 }
197 }
198
199 return out.str();
200 }
201
202 std::string Mesh::GenerateOBJForRecast() const {
203 if (!IsValid()) {
204 return "";
205 }
206 std::stringstream out;
207 out << std::fixed; // Avoid using scientific notation
208
209 // A handful of road-generation edge cases (e.g. a near-zero-length
210 // direction vector normalized to a unit vector, or a polynomial
211 // evaluated far outside its intended range) can occasionally produce a
212 // vertex tens of thousands of kilometres from the rest of the map.
213 // RecastBuilder's InputGeom computes its mesh bounding box with
214 // rcCalcBounds() over *every* vertex in the OBJ, regardless of whether
215 // any face still references it, and Recast then sizes its voxel grid
216 // off that bounding box -- so a single such outlier makes navmesh
217 // building computationally intractable even though its own geometry is
218 // dropped below. Confirmed: on a real corrupted Town03 export this
219 // produced a target tile grid of ~2.6M x 45K tiles and RecastBuilder
220 // never finished (pegged a CPU core for 10+ minutes, no output);
221 // zeroing the ~4 offending vertices dropped that to a sane 12x12 grid
222 // and RecastBuilder completed in under a second. Every shipped CARLA
223 // map is a few kilometres across at most, so any vertex beyond this
224 // (generous, two-orders-of-magnitude-safe) bound is corrupt: its
225 // coordinates are zeroed (rather than left in place) so it can't
226 // distort the bounding box, and any face still referencing it is
227 // dropped too, degrading to a small hole in the navmesh rather than a
228 // hung/impossible build.
229 constexpr float MaxSaneCoordinate = 1e5f; // 100 km
230 const auto is_sane_vertex = [](const vertex_type &v) {
231 return std::fabs(v.x) <= MaxSaneCoordinate &&
232 std::fabs(v.y) <= MaxSaneCoordinate &&
233 std::fabs(v.z) <= MaxSaneCoordinate;
234 };
235
236 size_t sanitized_vertices = 0u;
237 out << "# List of geometric vertices, with (x, y, z) coordinates." << std::endl;
238 for (auto &v : _vertices) {
239 if (is_sane_vertex(v)) {
240 // Switched "y" and "z" for Recast library
241 out << "v " << v.x << " " << v.z << " " << v.y << std::endl;
242 } else {
243 out << "v 0 0 0" << std::endl;
244 ++sanitized_vertices;
245 }
246 }
247
248 if (!_indexes.empty()) {
249 out << std::endl << "# Polygonal face element." << std::endl;
250 auto it_m = _materials.begin();
251 auto it = _indexes.begin();
252 size_t index_counter = 0u;
253 size_t skipped_faces = 0u;
254 while (it != _indexes.end()) {
255 // While exist materials
256 if (it_m != _materials.end()) {
257 // If the current material ends at this index
258 if (it_m->index_end == index_counter) {
259 ++it_m;
260 }
261 // If the current material start at this index
262 if (it_m->index_start == index_counter) {
263 out << "\nusemtl " << it_m->name << std::endl;
264 }
265 }
266 // Read the 3 consecutive indices making up this face.
267 const auto i_1 = *it; ++it;
268 const auto i_2 = *it; ++it;
269 const auto i_3 = *it; ++it;
270 index_counter += 3;
271
272 // Indexes are 1-based; guard against out-of-range indices too.
273 if (i_1 == 0 || i_2 == 0 || i_3 == 0 ||
274 i_1 > _vertices.size() || i_2 > _vertices.size() || i_3 > _vertices.size() ||
275 !is_sane_vertex(_vertices[i_1 - 1]) ||
276 !is_sane_vertex(_vertices[i_2 - 1]) ||
277 !is_sane_vertex(_vertices[i_3 - 1])) {
278 ++skipped_faces;
279 continue;
280 }
281
282 // Add the actual face using the 3 consecutive indices
283 // Changes the face build direction to clockwise since
284 // the space has changed.
285 out << "f " << i_1 << " " << i_3 << " " << i_2 << std::endl;
286 }
287 if (skipped_faces > 0u) {
288 std::cout << "Mesh::GenerateOBJForRecast: sanitized " << sanitized_vertices
289 << " out-of-range vertex/vertices and skipped " << skipped_faces
290 << " face(s) referencing them (beyond "
291 << MaxSaneCoordinate << "m from the origin)." << std::endl;
292 }
293 }
294
295 return out.str();
296 }
297
298 std::string Mesh::GeneratePLY() const {
299 if (!IsValid()) {
300 return "Invalid Mesh";
301 }
302 // Generate header
303 std::stringstream out;
304 return out.str();
305 }
306
307 const std::vector<Mesh::vertex_type> &Mesh::GetVertices() const {
308 return _vertices;
309 }
310
311 std::vector<Mesh::vertex_type> &Mesh::GetVertices() {
312 return _vertices;
313 }
314
315 size_t Mesh::GetVerticesNum() const {
316 return _vertices.size();
317 }
318
319 const std::vector<Mesh::normal_type> &Mesh::GetNormals() const {
320 return _normals;
321 }
322
323 const std::vector<Mesh::index_type> &Mesh::GetIndexes() const {
324 return _indexes;
325 }
326
327 std::vector<Mesh::index_type>& Mesh::GetIndexes() {
328 return _indexes;
329 }
330 size_t Mesh::GetIndexesNum() const {
331 return _indexes.size();
332 }
333
334 const std::vector<Mesh::uv_type> &Mesh::GetUVs() const {
335 return _uvs;
336 }
337
338 const std::vector<Mesh::material_type> &Mesh::GetMaterials() const {
339 return _materials;
340 }
341
343 return _vertices.size();
344 }
345
346 Mesh& Mesh::ConcatMesh(const Mesh& rhs, int num_vertices_to_link) {
347
348 if (!rhs.IsValid()){
349 return *this += rhs;
350 }
351 const size_t v_num = GetVerticesNum();
352 const size_t i_num = GetIndexesNum();
353
354 _vertices.insert(
355 _vertices.end(),
356 rhs.GetVertices().begin(),
357 rhs.GetVertices().end());
358
359 _normals.insert(
360 _normals.end(),
361 rhs.GetNormals().begin(),
362 rhs.GetNormals().end());
363
364 const size_t vertices_to_link = static_cast<size_t>(num_vertices_to_link);
365 const size_t vertex_to_start_concating = v_num - vertices_to_link;
366 for( size_t i = 1; i < vertices_to_link; ++i ) {
367 _indexes.push_back( vertex_to_start_concating + i );
368 _indexes.push_back( vertex_to_start_concating + i + 1 );
369 _indexes.push_back( v_num + i );
370
371 _indexes.push_back( vertex_to_start_concating + i + 1);
372 _indexes.push_back( v_num + i + 1);
373 _indexes.push_back( v_num + i);
374 }
375
376 std::transform(
377 rhs.GetIndexes().begin(),
378 rhs.GetIndexes().end(),
379 std::back_inserter(_indexes),
380 [=](size_t index) {return index + v_num; });
381
382 _uvs.insert(
383 _uvs.end(),
384 rhs.GetUVs().begin(),
385 rhs.GetUVs().end());
386
387 std::transform(
388 rhs.GetMaterials().begin(),
389 rhs.GetMaterials().end(),
390 std::back_inserter(_materials),
391 [=](MeshMaterial mat) {
392 mat.index_start += i_num;
393 mat.index_end += i_num;
394 return mat;
395 });
396
397 return *this;
398 }
399
401 const size_t v_num = GetVerticesNum();
402 const size_t i_num = GetIndexesNum();
403
404 _vertices.insert(
405 _vertices.end(),
406 rhs.GetVertices().begin(),
407 rhs.GetVertices().end());
408
409 _normals.insert(
410 _normals.end(),
411 rhs.GetNormals().begin(),
412 rhs.GetNormals().end());
413
414 std::transform(
415 rhs.GetIndexes().begin(),
416 rhs.GetIndexes().end(),
417 std::back_inserter(_indexes),
418 [=](size_t index) {return index + v_num;});
419
420 _uvs.insert(
421 _uvs.end(),
422 rhs.GetUVs().begin(),
423 rhs.GetUVs().end());
424
425 std::transform(
426 rhs.GetMaterials().begin(),
427 rhs.GetMaterials().end(),
428 std::back_inserter(_materials),
429 [=](MeshMaterial mat) {
430 mat.index_start += i_num;
431 mat.index_end += i_num;
432 return mat;
433 });
434
435 return *this;
436 }
437
438 Mesh operator+(const Mesh &lhs, const Mesh &rhs) {
439 Mesh m = lhs;
440 return m += rhs;
441 }
442
443} // namespace geom
444} // namespace carla
#define DEBUG_ASSERT(predicate)
Definition Debug.h:66
Mesh data container, validator and exporter.
Definition Mesh.h:44
void AddIndex(index_type index)
Appends a index to the indexes list.
Definition Mesh.cpp:101
void AddVertex(vertex_type vertex)
Appends a vertex to the vertices list.
Definition Mesh.cpp:89
std::vector< vertex_type > _vertices
Definition Mesh.h:243
void AddVertices(const std::vector< vertex_type > &vertices)
Appends a vertex to the vertices list.
Definition Mesh.cpp:93
std::string GenerateOBJ() const
Returns a string containing the mesh encoded in OBJ.
Definition Mesh.cpp:146
void AddUV(uv_type uv)
Appends a vertex to the vertices list, they will be read 3 in 3.
Definition Mesh.cpp:105
Mesh & ConcatMesh(const Mesh &rhs, int num_vertices_to_link)
Merges two meshes into a single mesh.
Definition Mesh.cpp:346
std::vector< index_type > _indexes
Definition Mesh.h:247
const std::vector< uv_type > & GetUVs() const
Definition Mesh.cpp:334
const std::vector< index_type > & GetIndexes() const
Definition Mesh.cpp:323
std::string GeneratePLY() const
Returns a string containing the mesh encoded in PLY.
Definition Mesh.cpp:298
bool IsValid() const
Check if the mesh can be valid or not.
Definition Mesh.cpp:20
void AddUVs(const std::vector< uv_type > &uv)
Appends uvs.
Definition Mesh.cpp:109
std::string GenerateOBJForRecast() const
Returns a string containing the mesh encoded in OBJ.
Definition Mesh.cpp:202
size_t GetVerticesNum() const
Definition Mesh.cpp:315
void AddNormal(normal_type normal)
Appends a normal to the normal list.
Definition Mesh.cpp:97
std::vector< normal_type > _normals
Definition Mesh.h:245
std::vector< material_type > _materials
Definition Mesh.h:251
void AddMaterial(const std::string &material_name)
Starts applying a new material to the new added triangles.
Definition Mesh.cpp:113
void EndMaterial()
Stops applying the material to the new added triangles.
Definition Mesh.cpp:129
size_t index_type
Definition Mesh.h:49
Mesh & operator+=(const Mesh &rhs)
Merges two meshes into a single mesh.
Definition Mesh.cpp:400
const std::vector< material_type > & GetMaterials() const
Definition Mesh.cpp:338
const std::vector< normal_type > & GetNormals() const
Definition Mesh.cpp:319
size_t GetLastVertexIndex() const
Returns the index of the last added vertex (number of vertices).
Definition Mesh.cpp:342
void AddTriangleFan(const std::vector< vertex_type > &vertices)
Adds a triangle fan to the mesh, vertex order is counterclockwise.
Definition Mesh.cpp:76
std::vector< uv_type > _uvs
Definition Mesh.h:249
size_t GetIndexesNum() const
Definition Mesh.cpp:330
void AddTriangleStrip(const std::vector< vertex_type > &vertices)
Adds a triangle strip to the mesh, vertex order is counterclockwise.
Definition Mesh.cpp:47
const std::vector< vertex_type > & GetVertices() const
Definition Mesh.cpp:307
Mesh operator+(const Mesh &lhs, const Mesh &rhs)
Definition Mesh.cpp:438
This file contains definitions of common data structures used in traffic manager.
Material that references the vertex index start and end of a mesh where it is affecting.
Definition Mesh.h:25