diff --git a/source/core/graphics/wireframe3d.cpp b/source/core/graphics/wireframe3d.cpp new file mode 100644 index 0000000..2cf126f --- /dev/null +++ b/source/core/graphics/wireframe3d.cpp @@ -0,0 +1,175 @@ +// wireframe3d.cpp - Implementació dels meshos 3D wireframe +// © 2026 JailDesigner + +#include "core/graphics/wireframe3d.hpp" + +#include +#include + +#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> 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(a_proj->screen.x), + static_cast(a_proj->screen.y), + static_cast(b_proj->screen.x), + static_cast(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(mesh.vertices.size()); + const auto N = static_cast(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 diff --git a/source/core/graphics/wireframe3d.hpp b/source/core/graphics/wireframe3d.hpp new file mode 100644 index 0000000..a57d9dd --- /dev/null +++ b/source/core/graphics/wireframe3d.hpp @@ -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 + +#include +#include +#include + +#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 vertices; + std::vector> 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