feat(wireframe3d): mesh3d + drawWireframe + factories octaedre i extrusió
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
// wireframe3d.cpp - Implementació dels meshos 3D wireframe
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "core/graphics/wireframe3d.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/rendering/line_renderer.hpp"
|
||||
|
||||
namespace Graphics {
|
||||
|
||||
auto applyTransform(const Transform3D& transform, const Vec3& local) -> Vec3 {
|
||||
// 1. Escala uniforme.
|
||||
Vec3 v{
|
||||
.x = local.x * transform.scale,
|
||||
.y = local.y * transform.scale,
|
||||
.z = local.z * transform.scale,
|
||||
};
|
||||
|
||||
// 2. Rotació Y (yaw): X i Z.
|
||||
const float CY = std::cos(transform.rotation_euler.y);
|
||||
const float SY = std::sin(transform.rotation_euler.y);
|
||||
{
|
||||
const float NX = (v.x * CY) + (v.z * SY);
|
||||
const float NZ = (-v.x * SY) + (v.z * CY);
|
||||
v.x = NX;
|
||||
v.z = NZ;
|
||||
}
|
||||
|
||||
// 3. Rotació X (pitch): Y i Z.
|
||||
const float CX = std::cos(transform.rotation_euler.x);
|
||||
const float SX = std::sin(transform.rotation_euler.x);
|
||||
{
|
||||
const float NY = (v.y * CX) - (v.z * SX);
|
||||
const float NZ = (v.y * SX) + (v.z * CX);
|
||||
v.y = NY;
|
||||
v.z = NZ;
|
||||
}
|
||||
|
||||
// 4. Rotació Z (roll): X i Y.
|
||||
const float CZ = std::cos(transform.rotation_euler.z);
|
||||
const float SZ = std::sin(transform.rotation_euler.z);
|
||||
{
|
||||
const float NX = (v.x * CZ) - (v.y * SZ);
|
||||
const float NY = (v.x * SZ) + (v.y * CZ);
|
||||
v.x = NX;
|
||||
v.y = NY;
|
||||
}
|
||||
|
||||
// 5. Translació final.
|
||||
v.x += transform.position.x;
|
||||
v.y += transform.position.y;
|
||||
v.z += transform.position.z;
|
||||
return v;
|
||||
}
|
||||
|
||||
void drawWireframe(Rendering::Renderer* renderer, const Camera3D& camera, const Mesh3D& mesh, const Transform3D& transform, float brightness, SDL_Color color) {
|
||||
if (renderer == nullptr || mesh.edges.empty() || mesh.vertices.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Projecta tots els vèrtexs un cop; cau-en si queden darrere del near.
|
||||
std::vector<std::optional<Camera3D::ProjectedPoint>> projected;
|
||||
projected.reserve(mesh.vertices.size());
|
||||
for (const auto& vertex : mesh.vertices) {
|
||||
const Vec3 WORLD = applyTransform(transform, vertex);
|
||||
projected.push_back(camera.project(WORLD));
|
||||
}
|
||||
|
||||
for (const auto& edge : mesh.edges) {
|
||||
const auto& a_proj = projected[edge.first];
|
||||
const auto& b_proj = projected[edge.second];
|
||||
if (!a_proj.has_value() || !b_proj.has_value()) {
|
||||
continue;
|
||||
}
|
||||
Rendering::linea(renderer,
|
||||
static_cast<int>(a_proj->screen.x),
|
||||
static_cast<int>(a_proj->screen.y),
|
||||
static_cast<int>(b_proj->screen.x),
|
||||
static_cast<int>(b_proj->screen.y),
|
||||
brightness,
|
||||
0.0F,
|
||||
color);
|
||||
}
|
||||
}
|
||||
|
||||
auto makeOctahedron() -> Mesh3D {
|
||||
// 6 vèrtexs als eixos: ±X, ±Y, ±Z.
|
||||
Mesh3D mesh;
|
||||
mesh.vertices = {
|
||||
{.x = 1.0F, .y = 0.0F, .z = 0.0F}, // 0: +X
|
||||
{.x = -1.0F, .y = 0.0F, .z = 0.0F}, // 1: -X
|
||||
{.x = 0.0F, .y = 1.0F, .z = 0.0F}, // 2: +Y
|
||||
{.x = 0.0F, .y = -1.0F, .z = 0.0F}, // 3: -Y
|
||||
{.x = 0.0F, .y = 0.0F, .z = 1.0F}, // 4: +Z
|
||||
{.x = 0.0F, .y = 0.0F, .z = -1.0F}, // 5: -Z
|
||||
};
|
||||
// 12 arestes: cada vèrtex axial connecta amb els 4 vèrtexs no oposats.
|
||||
mesh.edges = {
|
||||
// "Equador" XY al voltant de Z.
|
||||
{2, 0},
|
||||
{0, 3},
|
||||
{3, 1},
|
||||
{1, 2},
|
||||
// Piràmide superior (cap a +Z).
|
||||
{2, 4},
|
||||
{0, 4},
|
||||
{3, 4},
|
||||
{1, 4},
|
||||
// Piràmide inferior (cap a -Z).
|
||||
{2, 5},
|
||||
{0, 5},
|
||||
{3, 5},
|
||||
{1, 5},
|
||||
};
|
||||
return mesh;
|
||||
}
|
||||
|
||||
auto extrudeShape2D(const Shape& shape, float depth) -> Mesh3D {
|
||||
Mesh3D mesh;
|
||||
if (!shape.isValid()) {
|
||||
return mesh;
|
||||
}
|
||||
|
||||
const float HALF = depth * 0.5F;
|
||||
const Vec2 CENTRE = shape.getCenter();
|
||||
|
||||
for (const auto& primitive : shape.getPrimitives()) {
|
||||
if (primitive.points.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reserva: 2 còpies (front/back) de cada vèrtex de la primitiva.
|
||||
const auto BASE = static_cast<std::uint16_t>(mesh.vertices.size());
|
||||
const auto N = static_cast<std::uint16_t>(primitive.points.size());
|
||||
|
||||
// Insereix vèrtexs frontals (z = +HALF) i posteriors (z = -HALF).
|
||||
// Còpia centrada respecte al "center" del shape.
|
||||
for (const auto& p : primitive.points) {
|
||||
mesh.vertices.push_back(Vec3{
|
||||
.x = p.x - CENTRE.x,
|
||||
.y = p.y - CENTRE.y,
|
||||
.z = HALF,
|
||||
});
|
||||
}
|
||||
for (const auto& p : primitive.points) {
|
||||
mesh.vertices.push_back(Vec3{
|
||||
.x = p.x - CENTRE.x,
|
||||
.y = p.y - CENTRE.y,
|
||||
.z = -HALF,
|
||||
});
|
||||
}
|
||||
|
||||
// Arestes "frontals": connecten punts consecutius de la polyline al davant.
|
||||
for (std::uint16_t i = 0; i + 1 < N; ++i) {
|
||||
mesh.edges.emplace_back(BASE + i, BASE + i + 1);
|
||||
}
|
||||
// Arestes "posteriors": idem al darrere.
|
||||
for (std::uint16_t i = 0; i + 1 < N; ++i) {
|
||||
mesh.edges.emplace_back(BASE + N + i, BASE + N + i + 1);
|
||||
}
|
||||
// Arestes de connexió front↔posterior per cada vèrtex.
|
||||
// Per polylines tancades (primer == últim punt), el bucle igualment
|
||||
// genera N connexions; el parell duplicat (primer i últim) cau en una
|
||||
// línia idèntica sense efecte visible.
|
||||
for (std::uint16_t i = 0; i < N; ++i) {
|
||||
mesh.edges.emplace_back(BASE + i, BASE + N + i);
|
||||
}
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
} // namespace Graphics
|
||||
Reference in New Issue
Block a user