feat(ship-3d): look-at dinàmic, naus alineades amb el path (punta+cul)
This commit is contained in:
@@ -18,17 +18,12 @@ namespace Graphics {
|
||||
.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;
|
||||
}
|
||||
// Ordre X → Y → Z: amb aquest ordre, una rotació pitch+yaw pot dur el
|
||||
// vector local (0,-1,0) a qualsevol direcció mundial — necessari perquè
|
||||
// les naus calculen pitch+yaw look-at per alinear-se amb el seu path.
|
||||
// L'ordre invers (Y→X) no permet X arbitrari en vectors sobre l'eix Y.
|
||||
|
||||
// 3. Rotació X (pitch): Y i Z.
|
||||
// 2. Rotació X (pitch): Y i Z.
|
||||
const float CX = std::cos(transform.rotation_euler.x);
|
||||
const float SX = std::sin(transform.rotation_euler.x);
|
||||
{
|
||||
@@ -38,6 +33,16 @@ namespace Graphics {
|
||||
v.z = NZ;
|
||||
}
|
||||
|
||||
// 3. 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,10 @@ namespace Title {
|
||||
float frequency_y{0.0F};
|
||||
|
||||
Graphics::Mesh3D mesh;
|
||||
// Vector mundial cap a on apunta el front del shape. Recalculat a cada
|
||||
// transició d'estat perquè draw() oriente la nau (look-at) en la
|
||||
// direcció del seu path actual.
|
||||
Vec3 forward_dir{.x = 0.0F, .y = 0.0F, .z = 1.0F};
|
||||
bool visible{false};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user