VisuTwin Canvas
C++ 3D Engine — Metal Backend
Loading...
Searching...
No Matches
renderComponent.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025-2026 Arnis Lektauers
3//
4// Created by Arnis Lektauers on 10.02.2026.
5//
6#include "renderComponent.h"
7
8#include <algorithm>
9#include <array>
10#include <cmath>
11#include <cstdint>
12#include <cstring>
13#include <limits>
14
15#include <spdlog/spdlog.h>
16
17#include "framework/engine.h"
18#include "framework/entity.h"
21
22namespace visutwin::canvas
23{
24 namespace
25 {
26 constexpr float PI_F = 3.14159265358979323846f;
27
28 struct PrimitiveGeometry
29 {
30 std::vector<float> positions;
31 std::vector<float> normals;
32 std::vector<float> uvs;
33 std::vector<float> tangents;
34 std::vector<uint32_t> indices;
35 };
36
37 void pushVertex(PrimitiveGeometry& geometry,
38 const float px, const float py, const float pz,
39 const float nx, const float ny, const float nz,
40 const float tx, const float ty, const float tz, const float tw,
41 const float u, const float v)
42 {
43 geometry.positions.insert(geometry.positions.end(), {px, py, pz});
44 geometry.normals.insert(geometry.normals.end(), {nx, ny, nz});
45 geometry.tangents.insert(geometry.tangents.end(), {tx, ty, tz, tw});
46 geometry.uvs.insert(geometry.uvs.end(), {u, v});
47 }
48
49 std::shared_ptr<Mesh> createMesh(const std::shared_ptr<GraphicsDevice>& device, const PrimitiveGeometry& geometry)
50 {
51 if (!device || geometry.positions.empty() || geometry.indices.empty()) {
52 return nullptr;
53 }
54
55 const int vertexCount = static_cast<int>(geometry.positions.size() / 3);
56 std::vector<float> interleaved;
57 interleaved.reserve(static_cast<size_t>(vertexCount) * 14u);
58
59 Vector3 minBounds(
60 std::numeric_limits<float>::max(),
61 std::numeric_limits<float>::max(),
62 std::numeric_limits<float>::max());
63 Vector3 maxBounds(
64 std::numeric_limits<float>::lowest(),
65 std::numeric_limits<float>::lowest(),
66 std::numeric_limits<float>::lowest());
67
68 for (int i = 0; i < vertexCount; ++i) {
69 const size_t posOffset = static_cast<size_t>(i) * 3u;
70 const size_t uvOffset = static_cast<size_t>(i) * 2u;
71
72 const float px = geometry.positions[posOffset];
73 const float py = geometry.positions[posOffset + 1u];
74 const float pz = geometry.positions[posOffset + 2u];
75 const float nx = geometry.normals[posOffset];
76 const float ny = geometry.normals[posOffset + 1u];
77 const float nz = geometry.normals[posOffset + 2u];
78 const size_t tanOffset = static_cast<size_t>(i) * 4u;
79 const bool hasTangents = geometry.tangents.size() >= static_cast<size_t>(vertexCount) * 4u;
80 const float tx = hasTangents ? geometry.tangents[tanOffset] : 0.0f;
81 const float ty = hasTangents ? geometry.tangents[tanOffset + 1u] : 0.0f;
82 const float tz = hasTangents ? geometry.tangents[tanOffset + 2u] : 0.0f;
83 const float tw = hasTangents ? geometry.tangents[tanOffset + 3u] : 1.0f;
84 const float u = geometry.uvs[uvOffset];
85 const float v = geometry.uvs[uvOffset + 1u];
86
87 minBounds = Vector3(
88 std::min(minBounds.getX(), px),
89 std::min(minBounds.getY(), py),
90 std::min(minBounds.getZ(), pz));
91 maxBounds = Vector3(
92 std::max(maxBounds.getX(), px),
93 std::max(maxBounds.getY(), py),
94 std::max(maxBounds.getZ(), pz));
95
96 interleaved.insert(interleaved.end(), {
97 px, py, pz,
98 nx, ny, nz,
99 u, v,
100 tx, ty, tz, tw,
101 // DEVIATION: Primitive mesh path currently mirrors UV0 into UV1.
102 u, v
103 });
104 }
105
106 std::vector<uint8_t> vertexBytes(interleaved.size() * sizeof(float));
107 std::memcpy(vertexBytes.data(), interleaved.data(), vertexBytes.size());
108 VertexBufferOptions vbOptions;
109 vbOptions.data = std::move(vertexBytes);
110
111 auto vertexFormat = std::make_shared<VertexFormat>(14 * static_cast<int>(sizeof(float)), true, false);
112 auto vertexBuffer = device->createVertexBuffer(vertexFormat, vertexCount, vbOptions);
113
114 std::vector<uint8_t> indexBytes(geometry.indices.size() * sizeof(uint32_t));
115 std::memcpy(indexBytes.data(), geometry.indices.data(), indexBytes.size());
116 auto indexBuffer = device->createIndexBuffer(
118 static_cast<int>(geometry.indices.size()),
119 indexBytes);
120
121 auto mesh = std::make_shared<Mesh>();
122 mesh->setVertexBuffer(vertexBuffer);
123 mesh->setIndexBuffer(indexBuffer, 0);
124
125 Primitive primitive;
126 primitive.type = PRIMITIVE_TRIANGLES;
127 primitive.base = 0;
128 primitive.baseVertex = 0;
129 primitive.count = static_cast<int>(geometry.indices.size());
130 primitive.indexed = true;
131 mesh->setPrimitive(primitive, 0);
132
133 const auto center = (minBounds + maxBounds) * 0.5f;
134 const auto halfExtents = (maxBounds - minBounds) * 0.5f;
135 BoundingBox bounds;
136 bounds.setCenter(center);
137 bounds.setHalfExtents(halfExtents);
138 mesh->setAabb(bounds);
139
140 return mesh;
141 }
142
143 PrimitiveGeometry createBoxGeometry()
144 {
145 PrimitiveGeometry geometry;
146
147 constexpr float halfExtent = 0.5f;
148 constexpr int uSegments = 1;
149 constexpr int vSegments = 1;
150
151 struct Vec3f
152 {
153 float x;
154 float y;
155 float z;
156 };
157
158 const std::array<Vec3f, 8> corners = {{
159 {-halfExtent, -halfExtent, halfExtent},
160 {halfExtent, -halfExtent, halfExtent},
161 {halfExtent, halfExtent, halfExtent},
162 {-halfExtent, halfExtent, halfExtent},
163 {halfExtent, -halfExtent, -halfExtent},
164 {-halfExtent, -halfExtent, -halfExtent},
165 {-halfExtent, halfExtent, -halfExtent},
166 {halfExtent, halfExtent, -halfExtent}
167 }};
168
169 const int faceAxes[6][3] = {
170 {0, 1, 3},
171 {4, 5, 7},
172 {3, 2, 6},
173 {1, 0, 4},
174 {1, 4, 2},
175 {5, 0, 6}
176 };
177
178 const float faceNormals[6][3] = {
179 {0.0f, 0.0f, 1.0f},
180 {0.0f, 0.0f, -1.0f},
181 {0.0f, 1.0f, 0.0f},
182 {0.0f, -1.0f, 0.0f},
183 {1.0f, 0.0f, 0.0f},
184 {-1.0f, 0.0f, 0.0f}
185 };
186
187 geometry.positions.reserve(6u * 4u * 3u);
188 geometry.normals.reserve(6u * 4u * 3u);
189 geometry.uvs.reserve(6u * 4u * 2u);
190 geometry.indices.reserve(6u * 6u);
191
192 uint32_t vertexCounter = 0;
193 for (int side = 0; side < 6; ++side) {
194 for (int i = 0; i <= uSegments; ++i) {
195 for (int j = 0; j <= vSegments; ++j) {
196 const float u = static_cast<float>(i) / static_cast<float>(uSegments);
197 const float v = static_cast<float>(j) / static_cast<float>(vSegments);
198
199 const auto& c0 = corners[faceAxes[side][0]];
200 const auto& c1 = corners[faceAxes[side][1]];
201 const auto& c2 = corners[faceAxes[side][2]];
202
203 const Vec3f temp1 = {
204 c0.x + (c1.x - c0.x) * u,
205 c0.y + (c1.y - c0.y) * u,
206 c0.z + (c1.z - c0.z) * u
207 };
208 const Vec3f temp2 = {
209 c0.x + (c2.x - c0.x) * v,
210 c0.y + (c2.y - c0.y) * v,
211 c0.z + (c2.z - c0.z) * v
212 };
213
214 pushVertex(
215 geometry,
216 temp1.x + (temp2.x - c0.x),
217 temp1.y + (temp2.y - c0.y),
218 temp1.z + (temp2.z - c0.z),
219 faceNormals[side][0], faceNormals[side][1], faceNormals[side][2],
220 1.0f, 0.0f, 0.0f, 1.0f,
221 u, 1.0f - v);
222
223 if (i < uSegments && j < vSegments) {
224 geometry.indices.push_back(vertexCounter + static_cast<uint32_t>(vSegments + 1));
225 geometry.indices.push_back(vertexCounter + 1u);
226 geometry.indices.push_back(vertexCounter);
227 geometry.indices.push_back(vertexCounter + static_cast<uint32_t>(vSegments + 1));
228 geometry.indices.push_back(vertexCounter + static_cast<uint32_t>(vSegments + 2));
229 geometry.indices.push_back(vertexCounter + 1u);
230 }
231 vertexCounter++;
232 }
233 }
234 }
235
236 return geometry;
237 }
238
239 PrimitiveGeometry createSphereGeometry()
240 {
241 PrimitiveGeometry geometry;
242
243 constexpr float radius = 0.5f;
244 constexpr int latitudeBands = 48;
245 constexpr int longitudeBands = 48;
246
247 for (int lat = 0; lat <= latitudeBands; ++lat) {
248 const float theta = static_cast<float>(lat) * PI_F / static_cast<float>(latitudeBands);
249 const float sinTheta = std::sin(theta);
250 const float cosTheta = std::cos(theta);
251
252 for (int lon = 0; lon <= longitudeBands; ++lon) {
253 const float phi = static_cast<float>(lon) * 2.0f * PI_F / static_cast<float>(longitudeBands) - PI_F * 0.5f;
254 const float sinPhi = std::sin(phi);
255 const float cosPhi = std::cos(phi);
256
257 const float x = cosPhi * sinTheta;
258 const float y = cosTheta;
259 const float z = sinPhi * sinTheta;
260 const float u = 1.0f - static_cast<float>(lon) / static_cast<float>(longitudeBands);
261 const float v = static_cast<float>(lat) / static_cast<float>(latitudeBands);
262
263 const float tx = -sinPhi;
264 const float ty = 0.0f;
265 const float tz = cosPhi;
266 pushVertex(geometry, x * radius, y * radius, z * radius, x, y, z, tx, ty, tz, 1.0f, u, v);
267 }
268 }
269
270 for (int lat = 0; lat < latitudeBands; ++lat) {
271 for (int lon = 0; lon < longitudeBands; ++lon) {
272 const uint32_t first = static_cast<uint32_t>(lat * (longitudeBands + 1) + lon);
273 const uint32_t second = first + static_cast<uint32_t>(longitudeBands + 1);
274 geometry.indices.push_back(first + 1u);
275 geometry.indices.push_back(second);
276 geometry.indices.push_back(first);
277 geometry.indices.push_back(first + 1u);
278 geometry.indices.push_back(second + 1u);
279 geometry.indices.push_back(second);
280 }
281 }
282
283 return geometry;
284 }
285
286 PrimitiveGeometry createConeBaseGeometry(const float baseRadius, const float peakRadius, const float height,
287 const int heightSegments, const int capSegments, const bool roundedCaps)
288 {
289 PrimitiveGeometry geometry;
290
291 if (height > 0.0f) {
292 for (int i = 0; i <= heightSegments; ++i) {
293 for (int j = 0; j <= capSegments; ++j) {
294 const float theta = (static_cast<float>(j) / static_cast<float>(capSegments)) * 2.0f * PI_F - PI_F;
295 const float sinTheta = std::sin(theta);
296 const float cosTheta = std::cos(theta);
297
298 const Vector3 bottom(sinTheta * baseRadius, -height * 0.5f, cosTheta * baseRadius);
299 const Vector3 top(sinTheta * peakRadius, height * 0.5f, cosTheta * peakRadius);
300 const float t = static_cast<float>(i) / static_cast<float>(heightSegments);
301 const Vector3 pos = bottom + (top - bottom) * t;
302 const Vector3 bottomToTop = (top - bottom).normalized();
303 const Vector3 tangent(cosTheta, 0.0f, -sinTheta);
304 const Vector3 norm = tangent.cross(bottomToTop).normalized();
305
306 const float u = static_cast<float>(j) / static_cast<float>(capSegments);
307 const float v = 1.0f - static_cast<float>(i) / static_cast<float>(heightSegments);
308 pushVertex(geometry,
309 pos.getX(), pos.getY(), pos.getZ(),
310 norm.getX(), norm.getY(), norm.getZ(),
311 tangent.getX(), tangent.getY(), tangent.getZ(), 1.0f,
312 u, v);
313
314 if (i < heightSegments && j < capSegments) {
315 const uint32_t first = static_cast<uint32_t>(i * (capSegments + 1) + j);
316 const uint32_t second = static_cast<uint32_t>(i * (capSegments + 1) + (j + 1));
317 const uint32_t third = static_cast<uint32_t>((i + 1) * (capSegments + 1) + j);
318 const uint32_t fourth = static_cast<uint32_t>((i + 1) * (capSegments + 1) + (j + 1));
319 geometry.indices.insert(geometry.indices.end(), {first, second, third});
320 geometry.indices.insert(geometry.indices.end(), {second, fourth, third});
321 }
322 }
323 }
324 }
325
326 if (roundedCaps) {
327 const int latitudeBands = std::max(1, capSegments / 2);
328 const int longitudeBands = capSegments;
329 const float capOffset = height * 0.5f;
330
331 for (int lat = 0; lat <= latitudeBands; ++lat) {
332 const float theta = (static_cast<float>(lat) * PI_F * 0.5f) / static_cast<float>(latitudeBands);
333 const float sinTheta = std::sin(theta);
334 const float cosTheta = std::cos(theta);
335 for (int lon = 0; lon <= longitudeBands; ++lon) {
336 const float phi = static_cast<float>(lon) * 2.0f * PI_F / static_cast<float>(longitudeBands) - PI_F * 0.5f;
337 const float sinPhi = std::sin(phi);
338 const float cosPhi = std::cos(phi);
339 const float x = cosPhi * sinTheta;
340 const float y = cosTheta;
341 const float z = sinPhi * sinTheta;
342 const float u = 1.0f - static_cast<float>(lon) / static_cast<float>(longitudeBands);
343 const float v = static_cast<float>(lat) / static_cast<float>(latitudeBands);
344 pushVertex(geometry,
345 x * peakRadius, y * peakRadius + capOffset, z * peakRadius,
346 x, y, z,
347 -sinPhi, 0.0f, cosPhi, 1.0f,
348 u, v);
349 }
350 }
351
352 const uint32_t topOffset = static_cast<uint32_t>((heightSegments + 1) * (capSegments + 1));
353 for (int lat = 0; lat < latitudeBands; ++lat) {
354 for (int lon = 0; lon < longitudeBands; ++lon) {
355 const uint32_t first = static_cast<uint32_t>(lat * (longitudeBands + 1) + lon);
356 const uint32_t second = first + static_cast<uint32_t>(longitudeBands + 1);
357 geometry.indices.insert(geometry.indices.end(), {
358 topOffset + first + 1u, topOffset + second, topOffset + first,
359 topOffset + first + 1u, topOffset + second + 1u, topOffset + second
360 });
361 }
362 }
363
364 for (int lat = 0; lat <= latitudeBands; ++lat) {
365 const float theta = PI_F * 0.5f + (static_cast<float>(lat) * PI_F * 0.5f) / static_cast<float>(latitudeBands);
366 const float sinTheta = std::sin(theta);
367 const float cosTheta = std::cos(theta);
368 for (int lon = 0; lon <= longitudeBands; ++lon) {
369 const float phi = static_cast<float>(lon) * 2.0f * PI_F / static_cast<float>(longitudeBands) - PI_F * 0.5f;
370 const float sinPhi = std::sin(phi);
371 const float cosPhi = std::cos(phi);
372 const float x = cosPhi * sinTheta;
373 const float y = cosTheta;
374 const float z = sinPhi * sinTheta;
375 const float u = 1.0f - static_cast<float>(lon) / static_cast<float>(longitudeBands);
376 const float v = static_cast<float>(lat) / static_cast<float>(latitudeBands);
377 pushVertex(geometry,
378 x * peakRadius, y * peakRadius - capOffset, z * peakRadius,
379 x, y, z,
380 -sinPhi, 0.0f, cosPhi, 1.0f,
381 u, v);
382 }
383 }
384
385 const uint32_t bottomOffset = static_cast<uint32_t>(
386 (heightSegments + 1) * (capSegments + 1) +
387 (longitudeBands + 1) * (latitudeBands + 1));
388 for (int lat = 0; lat < latitudeBands; ++lat) {
389 for (int lon = 0; lon < longitudeBands; ++lon) {
390 const uint32_t first = static_cast<uint32_t>(lat * (longitudeBands + 1) + lon);
391 const uint32_t second = first + static_cast<uint32_t>(longitudeBands + 1);
392 geometry.indices.insert(geometry.indices.end(), {
393 bottomOffset + first + 1u, bottomOffset + second, bottomOffset + first,
394 bottomOffset + first + 1u, bottomOffset + second + 1u, bottomOffset + second
395 });
396 }
397 }
398 } else {
399 uint32_t offset = static_cast<uint32_t>((heightSegments + 1) * (capSegments + 1));
400 if (baseRadius > 0.0f) {
401 for (int i = 0; i < capSegments; ++i) {
402 const float theta = static_cast<float>(i) * 2.0f * PI_F / static_cast<float>(capSegments);
403 const float x = std::sin(theta);
404 const float z = std::cos(theta);
405 const float u = 1.0f - (x + 1.0f) * 0.5f;
406 const float v = 1.0f - (z + 1.0f) * 0.5f;
407 pushVertex(geometry,
408 x * baseRadius, -height * 0.5f, z * baseRadius,
409 0.0f, -1.0f, 0.0f,
410 1.0f, 0.0f, 0.0f, 1.0f,
411 u, v);
412 if (i > 1) {
413 geometry.indices.insert(geometry.indices.end(), {offset, offset + static_cast<uint32_t>(i), offset + static_cast<uint32_t>(i - 1)});
414 }
415 }
416 }
417
418 offset += static_cast<uint32_t>(capSegments);
419 if (peakRadius > 0.0f) {
420 for (int i = 0; i < capSegments; ++i) {
421 const float theta = static_cast<float>(i) * 2.0f * PI_F / static_cast<float>(capSegments);
422 const float x = std::sin(theta);
423 const float z = std::cos(theta);
424 const float u = 1.0f - (x + 1.0f) * 0.5f;
425 const float v = 1.0f - (z + 1.0f) * 0.5f;
426 pushVertex(geometry,
427 x * peakRadius, height * 0.5f, z * peakRadius,
428 0.0f, 1.0f, 0.0f,
429 1.0f, 0.0f, 0.0f, 1.0f,
430 u, v);
431 if (i > 1) {
432 geometry.indices.insert(geometry.indices.end(), {offset, offset + static_cast<uint32_t>(i - 1), offset + static_cast<uint32_t>(i)});
433 }
434 }
435 }
436 }
437
438 return geometry;
439 }
440
441 PrimitiveGeometry createCylinderGeometry()
442 {
443 return createConeBaseGeometry(0.5f, 0.5f, 1.0f, 5, 20, false);
444 }
445
446 PrimitiveGeometry createConeGeometry()
447 {
448 return createConeBaseGeometry(0.5f, 0.0f, 1.0f, 5, 20, false);
449 }
450
451 PrimitiveGeometry createCapsuleGeometry()
452 {
453 // Upstream primitive cache defaults: radius=0.5, height=2, heightSegments=1, sides=20.
454 return createConeBaseGeometry(0.5f, 0.5f, 1.0f, 1, 20, true);
455 }
456
457 PrimitiveGeometry createPlaneGeometry()
458 {
459 // createPlane: unit quad on XZ plane, Y up, centered at origin
460 PrimitiveGeometry geometry;
461
462 // 4 vertices: (-0.5, 0, -0.5) to (0.5, 0, 0.5)
463 // Normal pointing up (+Y), tangent along +X
464 pushVertex(geometry, -0.5f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
465 pushVertex(geometry, 0.5f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
466 pushVertex(geometry, 0.5f, 0.0f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
467 pushVertex(geometry, -0.5f, 0.0f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
468
469 // Two triangles
470 geometry.indices = {0, 1, 2, 0, 2, 3};
471
472 return geometry;
473 }
474 }
475
477 : Component(system, entity), _type("asset")
478 {
479 _instances.push_back(this);
480 }
481
483 {
484 for (auto* meshInstance : _meshInstances) {
485 delete meshInstance;
486 }
487 _meshInstances.clear();
488 _ownedMeshes.clear();
489
490 _instances.erase(std::remove(_instances.begin(), _instances.end(), this), _instances.end());
491 }
492
493 void RenderComponent::setType(const std::string& type)
494 {
495 if (_type == type) {
496 return;
497 }
498 _type = type;
499 rebuildPrimitiveMesh();
500 }
501
503 {
504 if (_material == material) {
505 return;
506 }
507 _material = material;
508
509 if (_type != "asset") {
510 rebuildPrimitiveMesh();
511 }
512 }
513
515 {
516 _receiveShadows = value;
517 //set receiveShadows() propagates to all mesh instances.
518 for (auto* mi : _meshInstances) {
519 if (mi) {
520 mi->setReceiveShadow(value);
521 }
522 }
523 }
524
525 void RenderComponent::setCastShadows(const bool value)
526 {
527 _castShadows = value;
528 //set castShadows() propagates to all mesh instances.
529 for (auto* mi : _meshInstances) {
530 if (mi) {
531 mi->setCastShadow(value);
532 }
533 }
534 }
535
537 {
538 const auto* src = dynamic_cast<const RenderComponent*>(source);
539 if (!src) {
540 return;
541 }
542
543 //
544 // Copy scalar properties.
545 _type = src->_type;
546 _layers = src->_layers;
547 _material = src->_material;
548 _receiveShadows = src->_receiveShadows;
549 _castShadows = src->_castShadows;
550 setEnabled(src->enabled());
551
552 // Clone mesh instances: create new MeshInstance per source, sharing mesh and material,
553 // but pointing to this component's entity (the cloned node).
554 for (auto* mi : _meshInstances) {
555 delete mi;
556 }
557 _meshInstances.clear();
558
559 for (const auto* srcMi : src->_meshInstances) {
560 if (!srcMi) {
561 continue;
562 }
563 auto* clonedMi = new MeshInstance(srcMi->mesh(), srcMi->material(), _entity);
564 clonedMi->setCastShadow(srcMi->castShadow());
565 clonedMi->setReceiveShadow(srcMi->receiveShadow());
566 clonedMi->setCull(srcMi->cull());
567 clonedMi->setMask(srcMi->mask());
568 _meshInstances.push_back(clonedMi);
569 }
570 }
571
572 void RenderComponent::rebuildPrimitiveMesh()
573 {
574 for (auto* meshInstance : _meshInstances) {
575 delete meshInstance;
576 }
577 _meshInstances.clear();
578 _ownedMeshes.clear();
579
580 if (_type == "asset") {
581 return;
582 }
583
584 auto* owner = entity();
585 auto* ownerEngine = owner ? owner->engine() : nullptr;
586 const auto device = ownerEngine ? ownerEngine->graphicsDevice() : nullptr;
587 if (!device) {
588 return;
589 }
590
591 PrimitiveGeometry primitiveGeometry;
592 if (_type == "box") {
593 primitiveGeometry = createBoxGeometry();
594 } else if (_type == "sphere") {
595 primitiveGeometry = createSphereGeometry();
596 } else if (_type == "cylinder") {
597 primitiveGeometry = createCylinderGeometry();
598 } else if (_type == "cone") {
599 primitiveGeometry = createConeGeometry();
600 } else if (_type == "capsule") {
601 primitiveGeometry = createCapsuleGeometry();
602 } else if (_type == "plane") {
603 primitiveGeometry = createPlaneGeometry();
604 } else {
605 // DEVIATION: Current C++ RenderComponent primitive port implements box/sphere/cylinder/cone/capsule/plane only.
606 spdlog::warn("Unsupported render primitive type '{}'", _type);
607 return;
608 }
609
610 auto mesh = createMesh(device, primitiveGeometry);
611 if (!mesh) {
612 return;
613 }
614
615 _ownedMeshes.push_back(mesh);
616 _meshInstances.push_back(new MeshInstance(mesh.get(), _material, owner));
617 }
618}
Axis-Aligned Bounding Box defined by center and half-extents.
Definition boundingBox.h:21
void setCenter(const Vector3 &center)
Definition boundingBox.h:28
Entity * entity() const
Definition component.cpp:16
Component(IComponentSystem *system, Entity *entity)
Definition component.cpp:12
virtual void setEnabled(bool value)
Definition component.cpp:21
IComponentSystem * system() const
Definition component.h:47
ECS entity — a GraphNode that hosts components defining its behavior.
Definition entity.h:32
Base class for GPU materials — owns uniform data, texture bindings, blend/depth state,...
Definition material.h:143
Renderable instance of a Mesh with its own material, transform node, and optional GPU instancing.
void setType(const std::string &type)
void cloneFrom(const Component *source) override
RenderComponent(IComponentSystem *system, Entity *entity)
void setMaterial(Material *material)
const std::string & type() const
@ PRIMITIVE_TRIANGLES
Definition mesh.h:23
Describes how vertex and index data should be interpreted for a draw call.
Definition mesh.h:33
PrimitiveType type
Definition mesh.h:34
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
Definition vector3.h:29
Vector3 cross(const Vector3 &other) const
Vector3 normalized() const