Files
orni-attack/source/core/graphics/wireframe3d.cpp
T

176 lines
6.0 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();
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