feat(ship-3d): look-at dinàmic, naus alineades amb el path (punta+cul)

This commit is contained in:
2026-05-22 09:52:14 +02:00
parent b45390a8d1
commit 54702a5afe
3 changed files with 65 additions and 17 deletions
+46 -7
View File
@@ -18,12 +18,6 @@ namespace Title {
// 0.0F → emet només la silueta plana. >0 emet volum extrudit.
constexpr float SHIP_EXTRUSION_DEPTH = 1.0F;
// Rotació pitch que aplica el draw() per orientar la silueta cap al punt
// de fuga: el Y2D negatiu del shape (la punta) passa a +Z mundial.
// Inclinem ~30° més enllà de -π/2 perquè el "cul" baixe i la punta puje;
// així la càmera (a Y=0, una mica per damunt de les naus) veu el dors.
constexpr float SHIP_PITCH_RAD = -2.0944F; // -120°
// Posicions en l'espai 3D (càmera a (0,0,0) mirant cap a +Z, Y cap amunt).
constexpr float SHIP_FLOAT_X = 25.0F; // Separació horitzontal
constexpr float SHIP_FLOAT_Y = -8.0F; // Lleugerament per davall del centre
@@ -37,6 +31,43 @@ namespace Title {
// Posició de fuga (al fons, centre projectat).
constexpr float SHIP_EXIT_Z = 800.0F;
// Punt cap a on apunten les naus durant FLOATING i EXITING (centre lluny).
constexpr Vec3 VANISHING_POINT{.x = 0.0F, .y = 0.0F, .z = SHIP_EXIT_Z};
// Look-at: calcula pitch+yaw que duen (0,-1,0) local a forward_dir mundial.
// Requereix l'ordre de rotació X→Y→Z al applyTransform de wireframe3d.
// Si forward és quasi vertical (sin(pitch) ≈ 0), retorna yaw=0 (qualsevol).
auto computePitchYawForLookAt(const Vec3& forward_dir) -> Vec2 {
const float DY = std::clamp(forward_dir.y, -1.0F, 1.0F);
const float PITCH = -std::acos(-DY); // ∈ [-π, 0]
const float SIN_PITCH = std::sin(PITCH);
if (std::abs(SIN_PITCH) < 1.0E-5F) {
return Vec2{.x = PITCH, .y = 0.0F};
}
const float SY = -forward_dir.x / SIN_PITCH;
const float CY = -forward_dir.z / SIN_PITCH;
return Vec2{.x = PITCH, .y = std::atan2(SY, CY)};
}
auto safeNormalize(const Vec3& v, const Vec3& fallback) -> Vec3 {
return v.lengthSquared() > 0.0F ? v.normalized() : fallback;
}
auto entryForward(const TitleShip3D& ship) -> Vec3 {
return safeNormalize(ship.target_position - ship.initial_position,
Vec3{.x = 0.0F, .y = 0.0F, .z = 1.0F});
}
auto floatingForward(const Vec3& target) -> Vec3 {
return safeNormalize(VANISHING_POINT - target,
Vec3{.x = 0.0F, .y = 0.0F, .z = 1.0F});
}
auto exitForward(const Vec3& current) -> Vec3 {
return safeNormalize(VANISHING_POINT - current,
Vec3{.x = 0.0F, .y = 0.0F, .z = 1.0F});
}
// Mida visual i animació.
constexpr float SHIP_FLOAT_SCALE = 1.0F;
constexpr float SHIP_ENTRY_SCALE = 1.0F; // Mida mundial idèntica; la perspectiva fa la resta
@@ -107,9 +138,10 @@ namespace Title {
if (!ship.visible) {
continue;
}
const Vec2 EULER = computePitchYawForLookAt(ship.forward_dir);
const Graphics::Transform3D TRANSFORM{
.position = ship.current_position,
.rotation_euler = Vec3{.x = SHIP_PITCH_RAD, .y = 0.0F, .z = 0.0F},
.rotation_euler = Vec3{.x = EULER.x, .y = EULER.y, .z = 0.0F},
.scale = ship.current_scale,
};
Graphics::drawWireframe(renderer_, *camera_, ship.mesh, TRANSFORM, 1.0F);
@@ -122,6 +154,7 @@ namespace Title {
ship.state_time = 0.0F;
ship.current_position = ship.initial_position;
ship.current_scale = ship.initial_scale;
ship.forward_dir = entryForward(ship);
}
}
@@ -130,6 +163,7 @@ namespace Title {
ship.state = ShipState3D::EXITING;
ship.state_time = 0.0F;
ship.initial_position = ship.current_position;
ship.forward_dir = exitForward(ship.current_position);
}
}
@@ -139,6 +173,7 @@ namespace Title {
ship.state = ShipState3D::EXITING;
ship.state_time = 0.0F;
ship.initial_position = ship.current_position;
ship.forward_dir = exitForward(ship.current_position);
break;
}
}
@@ -151,6 +186,7 @@ namespace Title {
ship.oscillation_phase = 0.0F;
ship.current_position = ship.target_position;
ship.current_scale = ship.target_scale;
ship.forward_dir = floatingForward(ship.target_position);
}
}
@@ -207,6 +243,7 @@ namespace Title {
ship.state_time = 0.0F;
// No resetegem oscillation_phase: així updateFloating continua
// l'oscil·lació iniciada durant ENTERING sense salt.
ship.forward_dir = floatingForward(ship.target_position);
}
}
@@ -264,6 +301,7 @@ namespace Title {
ship.amplitude_y = FLOAT_AMPLITUDE_Y;
ship.frequency_x = FLOAT_FREQUENCY_X_BASE * P1_FREQUENCY_MULTIPLIER;
ship.frequency_y = FLOAT_FREQUENCY_Y_BASE * P1_FREQUENCY_MULTIPLIER;
ship.forward_dir = entryForward(ship);
ship.visible = true;
}
@@ -289,6 +327,7 @@ namespace Title {
ship.amplitude_y = FLOAT_AMPLITUDE_Y;
ship.frequency_x = FLOAT_FREQUENCY_X_BASE * P2_FREQUENCY_MULTIPLIER;
ship.frequency_y = FLOAT_FREQUENCY_Y_BASE * P2_FREQUENCY_MULTIPLIER;
ship.forward_dir = entryForward(ship);
ship.visible = true;
}