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
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// wireframe3d.hpp - Meshos 3D wireframe i utilitats per dibuixar-los
|
||||||
|
// © 2026 JailDesigner
|
||||||
|
//
|
||||||
|
// Mesh3D = llista de vèrtexs Vec3 + llista d'arestes (parells d'índexs).
|
||||||
|
// drawWireframe() aplica una Transform3D al mesh, projecta amb Camera3D i
|
||||||
|
// emet cada aresta com una línia 2D pel pipeline `Rendering::linea` (mateix
|
||||||
|
// pipeline que la resta del joc: glow verd via ColorOscillator si color.a==0).
|
||||||
|
//
|
||||||
|
// Sense depth buffer: el caller és responsable d'ordenar els meshos per
|
||||||
|
// profunditat decreixent si vol oclusió coherent (la pipeline és LINE_LIST
|
||||||
|
// amb alpha blend additiu).
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/graphics/camera3d.hpp"
|
||||||
|
#include "core/graphics/shape.hpp"
|
||||||
|
#include "core/rendering/render_context.hpp"
|
||||||
|
#include "core/types.hpp"
|
||||||
|
|
||||||
|
namespace Graphics {
|
||||||
|
|
||||||
|
struct Mesh3D {
|
||||||
|
std::vector<Vec3> vertices;
|
||||||
|
std::vector<std::pair<std::uint16_t, std::uint16_t>> edges;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Transform3D {
|
||||||
|
Vec3 position{};
|
||||||
|
// Euler en radians, aplicat en ordre Y (yaw) → X (pitch) → Z (roll).
|
||||||
|
Vec3 rotation_euler{};
|
||||||
|
float scale{1.0F};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Aplica la Transform3D a un vèrtex local del mesh per obtenir-ne la posició
|
||||||
|
// mundial. Ordre: scale → rotate (Y,X,Z) → translate.
|
||||||
|
[[nodiscard]] auto applyTransform(const Transform3D& transform, const Vec3& local) -> Vec3;
|
||||||
|
|
||||||
|
// Dibuixa el mesh en wireframe a través de la càmera donada. Cada aresta es
|
||||||
|
// projecta en CPU i s'emet via `Rendering::linea`. Les arestes amb algun extrem
|
||||||
|
// darrere del near plane es descarten per complet (clipping primitiu).
|
||||||
|
// - brightness: multiplicador aplicat al color de línia.
|
||||||
|
// - color: si alpha == 0, usa el color global del oscil·lador (glow verd).
|
||||||
|
void drawWireframe(Rendering::Renderer* renderer, const Camera3D& camera, const Mesh3D& mesh, const Transform3D& transform, float brightness = 1.0F, SDL_Color color = {.r = 0, .g = 0, .b = 0, .a = 0});
|
||||||
|
|
||||||
|
// Factory: octaedre regular amb 6 vèrtexs als eixos a distància 1 i 12 arestes.
|
||||||
|
// Pensat com a estrella 3D al starfield (escalable amb Transform3D::scale).
|
||||||
|
[[nodiscard]] auto makeOctahedron() -> Mesh3D;
|
||||||
|
|
||||||
|
// Factory: extrusió en Z d'un shape 2D. Cada polyline genera dues còpies
|
||||||
|
// (z = +depth/2 i z = -depth/2) més arestes de connexió frontal↔posterior
|
||||||
|
// per cada vèrtex de la polyline.
|
||||||
|
[[nodiscard]] auto extrudeShape2D(const Shape& shape, float depth) -> Mesh3D;
|
||||||
|
|
||||||
|
} // namespace Graphics
|
||||||
Reference in New Issue
Block a user