// 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(); // 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(mesh.vertices.size()); const auto N = static_cast(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