15#include <spdlog/spdlog.h>
26 constexpr float PI_F = 3.14159265358979323846f;
28 struct PrimitiveGeometry
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;
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)
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});
49 std::shared_ptr<Mesh> createMesh(
const std::shared_ptr<GraphicsDevice>& device,
const PrimitiveGeometry& geometry)
51 if (!device || geometry.positions.empty() || geometry.indices.empty()) {
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);
60 std::numeric_limits<float>::max(),
61 std::numeric_limits<float>::max(),
62 std::numeric_limits<float>::max());
64 std::numeric_limits<float>::lowest(),
65 std::numeric_limits<float>::lowest(),
66 std::numeric_limits<float>::lowest());
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;
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];
88 std::min(minBounds.getX(), px),
89 std::min(minBounds.getY(), py),
90 std::min(minBounds.getZ(), pz));
92 std::max(maxBounds.getX(), px),
93 std::max(maxBounds.getY(), py),
94 std::max(maxBounds.getZ(), pz));
96 interleaved.insert(interleaved.end(), {
106 std::vector<uint8_t> vertexBytes(interleaved.size() *
sizeof(
float));
107 std::memcpy(vertexBytes.data(), interleaved.data(), vertexBytes.size());
109 vbOptions.
data = std::move(vertexBytes);
111 auto vertexFormat = std::make_shared<VertexFormat>(14 *
static_cast<int>(
sizeof(
float)),
true,
false);
112 auto vertexBuffer = device->createVertexBuffer(vertexFormat, vertexCount, vbOptions);
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()),
121 auto mesh = std::make_shared<Mesh>();
122 mesh->setVertexBuffer(vertexBuffer);
123 mesh->setIndexBuffer(indexBuffer, 0);
128 primitive.baseVertex = 0;
129 primitive.count =
static_cast<int>(geometry.indices.size());
130 primitive.indexed =
true;
131 mesh->setPrimitive(primitive, 0);
133 const auto center = (minBounds + maxBounds) * 0.5f;
134 const auto halfExtents = (maxBounds - minBounds) * 0.5f;
137 bounds.setHalfExtents(halfExtents);
138 mesh->setAabb(bounds);
143 PrimitiveGeometry createBoxGeometry()
145 PrimitiveGeometry geometry;
147 constexpr float halfExtent = 0.5f;
148 constexpr int uSegments = 1;
149 constexpr int vSegments = 1;
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}
169 const int faceAxes[6][3] = {
178 const float faceNormals[6][3] = {
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);
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);
199 const auto& c0 = corners[faceAxes[side][0]];
200 const auto& c1 = corners[faceAxes[side][1]];
201 const auto& c2 = corners[faceAxes[side][2]];
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
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
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,
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);
239 PrimitiveGeometry createSphereGeometry()
241 PrimitiveGeometry geometry;
243 constexpr float radius = 0.5f;
244 constexpr int latitudeBands = 48;
245 constexpr int longitudeBands = 48;
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);
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);
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);
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);
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);
286 PrimitiveGeometry createConeBaseGeometry(
const float baseRadius,
const float peakRadius,
const float height,
287 const int heightSegments,
const int capSegments,
const bool roundedCaps)
289 PrimitiveGeometry geometry;
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);
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);
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);
309 pos.getX(), pos.getY(), pos.getZ(),
310 norm.getX(), norm.getY(), norm.getZ(),
311 tangent.getX(), tangent.getY(), tangent.getZ(), 1.0f,
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});
327 const int latitudeBands = std::max(1, capSegments / 2);
328 const int longitudeBands = capSegments;
329 const float capOffset = height * 0.5f;
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);
345 x * peakRadius, y * peakRadius + capOffset, z * peakRadius,
347 -sinPhi, 0.0f, cosPhi, 1.0f,
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
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);
378 x * peakRadius, y * peakRadius - capOffset, z * peakRadius,
380 -sinPhi, 0.0f, cosPhi, 1.0f,
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
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;
408 x * baseRadius, -height * 0.5f, z * baseRadius,
410 1.0f, 0.0f, 0.0f, 1.0f,
413 geometry.indices.insert(geometry.indices.end(), {offset, offset + static_cast<uint32_t>(i), offset + static_cast<uint32_t>(i - 1)});
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;
427 x * peakRadius, height * 0.5f, z * peakRadius,
429 1.0f, 0.0f, 0.0f, 1.0f,
432 geometry.indices.insert(geometry.indices.end(), {offset, offset + static_cast<uint32_t>(i - 1), offset + static_cast<uint32_t>(i)});
441 PrimitiveGeometry createCylinderGeometry()
443 return createConeBaseGeometry(0.5f, 0.5f, 1.0f, 5, 20,
false);
446 PrimitiveGeometry createConeGeometry()
448 return createConeBaseGeometry(0.5f, 0.0f, 1.0f, 5, 20,
false);
451 PrimitiveGeometry createCapsuleGeometry()
454 return createConeBaseGeometry(0.5f, 0.5f, 1.0f, 1, 20,
true);
457 PrimitiveGeometry createPlaneGeometry()
460 PrimitiveGeometry geometry;
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);
470 geometry.indices = {0, 1, 2, 0, 2, 3};
479 _instances.push_back(
this);
484 for (
auto* meshInstance : _meshInstances) {
487 _meshInstances.clear();
488 _ownedMeshes.clear();
490 _instances.erase(std::remove(_instances.begin(), _instances.end(),
this), _instances.end());
499 rebuildPrimitiveMesh();
509 if (_type !=
"asset") {
510 rebuildPrimitiveMesh();
516 _receiveShadows = value;
518 for (
auto* mi : _meshInstances) {
520 mi->setReceiveShadow(value);
527 _castShadows = value;
529 for (
auto* mi : _meshInstances) {
531 mi->setCastShadow(value);
546 _layers = src->_layers;
547 _material = src->_material;
548 _receiveShadows = src->_receiveShadows;
549 _castShadows = src->_castShadows;
554 for (
auto* mi : _meshInstances) {
557 _meshInstances.clear();
559 for (
const auto* srcMi : src->_meshInstances) {
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);
572 void RenderComponent::rebuildPrimitiveMesh()
574 for (
auto* meshInstance : _meshInstances) {
577 _meshInstances.clear();
578 _ownedMeshes.clear();
580 if (_type ==
"asset") {
585 auto* ownerEngine = owner ? owner->engine() :
nullptr;
586 const auto device = ownerEngine ? ownerEngine->graphicsDevice() :
nullptr;
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();
606 spdlog::warn(
"Unsupported render primitive type '{}'", _type);
610 auto mesh = createMesh(device, primitiveGeometry);
615 _ownedMeshes.push_back(mesh);
616 _meshInstances.push_back(
new MeshInstance(mesh.get(), _material, owner));
Axis-Aligned Bounding Box defined by center and half-extents.
void setCenter(const Vector3 ¢er)
Component(IComponentSystem *system, Entity *entity)
virtual void setEnabled(bool value)
IComponentSystem * system() const
ECS entity — a GraphNode that hosts components defining its behavior.
Base class for GPU materials — owns uniform data, texture bindings, blend/depth state,...
Renderable instance of a Mesh with its own material, transform node, and optional GPU instancing.
Material * material() const
void setCastShadows(bool value)
void setType(const std::string &type)
void setReceiveShadows(bool value)
void cloneFrom(const Component *source) override
RenderComponent(IComponentSystem *system, Entity *entity)
~RenderComponent() override
void setMaterial(Material *material)
const std::string & type() const
Describes how vertex and index data should be interpreted for a draw call.
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
Vector3 cross(const Vector3 &other) const
Vector3 normalized() const
std::vector< uint8_t > data