181 lines
6.1 KiB
C++
181 lines
6.1 KiB
C++
// 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();
|
|
// Si depth <= 0, emetem només un pla (sense vèrtexs back ni connexions)
|
|
// per evitar arestes degenerades i acumulació additiva de brightness.
|
|
const bool FLAT = (depth <= 0.0F);
|
|
|
|
for (const auto& primitive : shape.getPrimitives()) {
|
|
if (primitive.points.size() < 2) {
|
|
continue;
|
|
}
|
|
|
|
const auto BASE = static_cast<std::uint16_t>(mesh.vertices.size());
|
|
const auto N = static_cast<std::uint16_t>(primitive.points.size());
|
|
|
|
// Vèrtexs frontals (z = +HALF, o z = 0 si FLAT).
|
|
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.
|
|
for (std::uint16_t i = 0; i + 1 < N; ++i) {
|
|
mesh.edges.emplace_back(BASE + i, BASE + i + 1);
|
|
}
|
|
|
|
if (FLAT) {
|
|
continue;
|
|
}
|
|
|
|
// Vèrtexs posteriors (z = -HALF) i arestes corresponents.
|
|
for (const auto& p : primitive.points) {
|
|
mesh.vertices.push_back(Vec3{
|
|
.x = p.x - CENTRE.x,
|
|
.y = p.y - CENTRE.y,
|
|
.z = -HALF,
|
|
});
|
|
}
|
|
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
|