feat(ship-animator3d): animador 3D de naus per al títol amb extrusió de ship.shp
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
// ship_animator3d.cpp - Implementació de l'animador de naus 3D
|
||||
// © 2026 JailDesigner
|
||||
|
||||
#include "ship_animator3d.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "core/defaults.hpp"
|
||||
#include "core/graphics/shape_loader.hpp"
|
||||
#include "core/math/easing.hpp"
|
||||
|
||||
namespace Title {
|
||||
|
||||
namespace {
|
||||
|
||||
// Profunditat d'extrusió de la silueta 2D de la nau (en unitats mundials).
|
||||
constexpr float SHIP_EXTRUSION_DEPTH = 8.0F;
|
||||
|
||||
// Posicions en l'espai 3D (càmera a (0,0,0) mirant cap a +Z, Y cap amunt).
|
||||
constexpr float SHIP_FLOAT_X = 20.0F; // Separació horitzontal
|
||||
constexpr float SHIP_FLOAT_Y = -8.0F; // Lleugerament per davall del centre
|
||||
constexpr float SHIP_FLOAT_Z = 60.0F; // Distància de flotació
|
||||
|
||||
// Posició d'entrada (a prop de càmera, fora del frustrum lateral).
|
||||
constexpr float SHIP_ENTRY_X = 50.0F;
|
||||
constexpr float SHIP_ENTRY_Y = -15.0F;
|
||||
constexpr float SHIP_ENTRY_Z = 28.0F;
|
||||
|
||||
// Posició de fuga (al fons, centre projectat).
|
||||
constexpr float SHIP_EXIT_Z = 800.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
|
||||
constexpr float ENTRY_DURATION = 2.0F;
|
||||
constexpr float EXIT_DURATION = 1.0F;
|
||||
|
||||
// Oscil·lació en unitats mundials (al voltant del target_position).
|
||||
constexpr float FLOAT_AMPLITUDE_X = 1.5F;
|
||||
constexpr float FLOAT_AMPLITUDE_Y = 1.0F;
|
||||
constexpr float FLOAT_FREQUENCY_X_BASE = 0.5F;
|
||||
constexpr float FLOAT_FREQUENCY_Y_BASE = 0.7F;
|
||||
constexpr float FLOAT_PHASE_OFFSET = 1.57F;
|
||||
|
||||
constexpr float P1_FREQUENCY_MULTIPLIER = 0.88F;
|
||||
constexpr float P2_FREQUENCY_MULTIPLIER = 1.12F;
|
||||
constexpr float P1_ENTRY_DELAY = 0.0F;
|
||||
constexpr float P2_ENTRY_DELAY = 0.5F;
|
||||
|
||||
} // namespace
|
||||
|
||||
ShipAnimator3D::ShipAnimator3D(Rendering::Renderer* renderer,
|
||||
const Graphics::Camera3D* camera)
|
||||
: renderer_(renderer),
|
||||
camera_(camera) {
|
||||
}
|
||||
|
||||
void ShipAnimator3D::init() {
|
||||
auto shape_p1 = Graphics::ShapeLoader::load("ship.shp");
|
||||
auto shape_p2 = Graphics::ShapeLoader::load("ship2.shp");
|
||||
|
||||
ships_[0].player_id = 1;
|
||||
if (shape_p1 && shape_p1->isValid()) {
|
||||
ships_[0].mesh = Graphics::extrudeShape2D(*shape_p1, SHIP_EXTRUSION_DEPTH);
|
||||
}
|
||||
configureShipP1(ships_[0]);
|
||||
|
||||
ships_[1].player_id = 2;
|
||||
if (shape_p2 && shape_p2->isValid()) {
|
||||
ships_[1].mesh = Graphics::extrudeShape2D(*shape_p2, SHIP_EXTRUSION_DEPTH);
|
||||
}
|
||||
configureShipP2(ships_[1]);
|
||||
}
|
||||
|
||||
void ShipAnimator3D::update(float delta_time) {
|
||||
for (auto& ship : ships_) {
|
||||
if (!ship.visible) {
|
||||
continue;
|
||||
}
|
||||
switch (ship.state) {
|
||||
case ShipState3D::ENTERING:
|
||||
updateEntering(ship, delta_time);
|
||||
break;
|
||||
case ShipState3D::FLOATING:
|
||||
updateFloating(ship, delta_time);
|
||||
break;
|
||||
case ShipState3D::EXITING:
|
||||
updateExiting(ship, delta_time);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShipAnimator3D::draw() const {
|
||||
if (camera_ == nullptr || renderer_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& ship : ships_) {
|
||||
if (!ship.visible) {
|
||||
continue;
|
||||
}
|
||||
const Graphics::Transform3D TRANSFORM{
|
||||
.position = ship.current_position,
|
||||
.rotation_euler = Vec3{},
|
||||
.scale = ship.current_scale,
|
||||
};
|
||||
Graphics::drawWireframe(renderer_, *camera_, ship.mesh, TRANSFORM, 1.0F);
|
||||
}
|
||||
}
|
||||
|
||||
void ShipAnimator3D::startEntryAnimation() {
|
||||
for (auto& ship : ships_) {
|
||||
ship.state = ShipState3D::ENTERING;
|
||||
ship.state_time = 0.0F;
|
||||
ship.current_position = ship.initial_position;
|
||||
ship.current_scale = ship.initial_scale;
|
||||
}
|
||||
}
|
||||
|
||||
void ShipAnimator3D::triggerExitAnimation() {
|
||||
for (auto& ship : ships_) {
|
||||
ship.state = ShipState3D::EXITING;
|
||||
ship.state_time = 0.0F;
|
||||
ship.initial_position = ship.current_position;
|
||||
}
|
||||
}
|
||||
|
||||
void ShipAnimator3D::triggerExitAnimationForPlayer(int player_id) {
|
||||
for (auto& ship : ships_) {
|
||||
if (ship.player_id == player_id) {
|
||||
ship.state = ShipState3D::EXITING;
|
||||
ship.state_time = 0.0F;
|
||||
ship.initial_position = ship.current_position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShipAnimator3D::skipToFloatingState() {
|
||||
for (auto& ship : ships_) {
|
||||
ship.state = ShipState3D::FLOATING;
|
||||
ship.state_time = 0.0F;
|
||||
ship.oscillation_phase = 0.0F;
|
||||
ship.current_position = ship.target_position;
|
||||
ship.current_scale = ship.target_scale;
|
||||
}
|
||||
}
|
||||
|
||||
void ShipAnimator3D::setVisible(bool visible) {
|
||||
for (auto& ship : ships_) {
|
||||
ship.visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
auto ShipAnimator3D::isVisible() const -> bool {
|
||||
return std::ranges::any_of(ships_,
|
||||
[](const TitleShip3D& s) { return s.visible; });
|
||||
}
|
||||
|
||||
auto ShipAnimator3D::isAnimationComplete() const -> bool {
|
||||
return std::ranges::all_of(ships_,
|
||||
[](const TitleShip3D& s) { return !s.visible; });
|
||||
}
|
||||
|
||||
void ShipAnimator3D::updateEntering(TitleShip3D& ship, float delta_time) {
|
||||
ship.state_time += delta_time;
|
||||
if (ship.state_time < ship.entry_delay) {
|
||||
ship.current_position = ship.initial_position;
|
||||
ship.current_scale = ship.initial_scale;
|
||||
return;
|
||||
}
|
||||
const float ELAPSED = ship.state_time - ship.entry_delay;
|
||||
const float PROGRESS = std::min(1.0F, ELAPSED / ENTRY_DURATION);
|
||||
const float EASED = Easing::easeOutQuad(PROGRESS);
|
||||
|
||||
ship.current_position.x = Easing::lerp(ship.initial_position.x, ship.target_position.x, EASED);
|
||||
ship.current_position.y = Easing::lerp(ship.initial_position.y, ship.target_position.y, EASED);
|
||||
ship.current_position.z = Easing::lerp(ship.initial_position.z, ship.target_position.z, EASED);
|
||||
ship.current_scale = Easing::lerp(ship.initial_scale, ship.target_scale, EASED);
|
||||
|
||||
if (ELAPSED >= ENTRY_DURATION) {
|
||||
ship.state = ShipState3D::FLOATING;
|
||||
ship.state_time = 0.0F;
|
||||
ship.oscillation_phase = 0.0F;
|
||||
}
|
||||
}
|
||||
|
||||
void ShipAnimator3D::updateFloating(TitleShip3D& ship, float delta_time) {
|
||||
ship.state_time += delta_time;
|
||||
ship.oscillation_phase += delta_time;
|
||||
|
||||
const float TWO_PI = 2.0F * Defaults::Math::PI;
|
||||
const float OFFSET_X = ship.amplitude_x *
|
||||
std::sin(TWO_PI * ship.frequency_x * ship.oscillation_phase);
|
||||
const float OFFSET_Y = ship.amplitude_y *
|
||||
std::sin((TWO_PI * ship.frequency_y * ship.oscillation_phase) + FLOAT_PHASE_OFFSET);
|
||||
|
||||
ship.current_position.x = ship.target_position.x + OFFSET_X;
|
||||
ship.current_position.y = ship.target_position.y + OFFSET_Y;
|
||||
ship.current_position.z = ship.target_position.z;
|
||||
ship.current_scale = ship.target_scale;
|
||||
}
|
||||
|
||||
void ShipAnimator3D::updateExiting(TitleShip3D& ship, float delta_time) {
|
||||
ship.state_time += delta_time;
|
||||
const float PROGRESS = std::min(1.0F, ship.state_time / EXIT_DURATION);
|
||||
const float EASED = Easing::easeInQuad(PROGRESS);
|
||||
|
||||
// Vola cap al centre projectat (x=0, y=0) i a Z gran (lluny).
|
||||
ship.current_position.x = Easing::lerp(ship.initial_position.x, 0.0F, EASED);
|
||||
ship.current_position.y = Easing::lerp(ship.initial_position.y, 0.0F, EASED);
|
||||
ship.current_position.z = Easing::lerp(ship.initial_position.z, SHIP_EXIT_Z, EASED);
|
||||
ship.current_scale = ship.target_scale; // L'escala visual baixa via la perspectiva
|
||||
|
||||
if (PROGRESS >= 1.0F) {
|
||||
ship.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ShipAnimator3D::configureShipP1(TitleShip3D& ship) {
|
||||
ship.state = ShipState3D::FLOATING;
|
||||
ship.state_time = 0.0F;
|
||||
// Esquerra: x negatiu.
|
||||
ship.target_position = Vec3{
|
||||
.x = -SHIP_FLOAT_X,
|
||||
.y = SHIP_FLOAT_Y,
|
||||
.z = SHIP_FLOAT_Z};
|
||||
ship.initial_position = Vec3{
|
||||
.x = -SHIP_ENTRY_X,
|
||||
.y = SHIP_ENTRY_Y,
|
||||
.z = SHIP_ENTRY_Z};
|
||||
ship.current_position = ship.initial_position;
|
||||
ship.target_scale = SHIP_FLOAT_SCALE;
|
||||
ship.current_scale = SHIP_FLOAT_SCALE;
|
||||
ship.initial_scale = SHIP_ENTRY_SCALE;
|
||||
ship.oscillation_phase = 0.0F;
|
||||
ship.entry_delay = P1_ENTRY_DELAY;
|
||||
ship.amplitude_x = FLOAT_AMPLITUDE_X;
|
||||
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.visible = true;
|
||||
}
|
||||
|
||||
void ShipAnimator3D::configureShipP2(TitleShip3D& ship) {
|
||||
ship.state = ShipState3D::FLOATING;
|
||||
ship.state_time = 0.0F;
|
||||
// Dreta: x positiu.
|
||||
ship.target_position = Vec3{
|
||||
.x = SHIP_FLOAT_X,
|
||||
.y = SHIP_FLOAT_Y,
|
||||
.z = SHIP_FLOAT_Z};
|
||||
ship.initial_position = Vec3{
|
||||
.x = SHIP_ENTRY_X,
|
||||
.y = SHIP_ENTRY_Y,
|
||||
.z = SHIP_ENTRY_Z};
|
||||
ship.current_position = ship.initial_position;
|
||||
ship.target_scale = SHIP_FLOAT_SCALE;
|
||||
ship.current_scale = SHIP_FLOAT_SCALE;
|
||||
ship.initial_scale = SHIP_ENTRY_SCALE;
|
||||
ship.oscillation_phase = 0.0F;
|
||||
ship.entry_delay = P2_ENTRY_DELAY;
|
||||
ship.amplitude_x = FLOAT_AMPLITUDE_X;
|
||||
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.visible = true;
|
||||
}
|
||||
|
||||
} // namespace Title
|
||||
@@ -0,0 +1,81 @@
|
||||
// ship_animator3d.hpp - Sistema d'animació de naus 3D per a l'escena de títol
|
||||
// © 2026 JailDesigner
|
||||
//
|
||||
// Equivalent 3D del `Title::ShipAnimator`. Manté la mateixa màquina d'estats
|
||||
// (ENTERING → FLOATING → EXITING) però treballa amb posicions Vec3 i emet
|
||||
// wireframes a través d'una `Camera3D`. La geometria s'extrau de `ship.shp`
|
||||
// (P1) i `ship2.shp` (P2) per extrusió en Z.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/graphics/camera3d.hpp"
|
||||
#include "core/graphics/wireframe3d.hpp"
|
||||
#include "core/rendering/render_context.hpp"
|
||||
#include "core/types.hpp"
|
||||
|
||||
namespace Title {
|
||||
|
||||
enum class ShipState3D : std::uint8_t {
|
||||
ENTERING,
|
||||
FLOATING,
|
||||
EXITING,
|
||||
};
|
||||
|
||||
struct TitleShip3D {
|
||||
int player_id{0};
|
||||
ShipState3D state{ShipState3D::ENTERING};
|
||||
float state_time{0.0F};
|
||||
|
||||
Vec3 initial_position{};
|
||||
Vec3 target_position{};
|
||||
Vec3 current_position{};
|
||||
|
||||
float initial_scale{1.0F};
|
||||
float target_scale{1.0F};
|
||||
float current_scale{1.0F};
|
||||
|
||||
float oscillation_phase{0.0F};
|
||||
float entry_delay{0.0F};
|
||||
|
||||
float amplitude_x{0.0F};
|
||||
float amplitude_y{0.0F};
|
||||
float frequency_x{0.0F};
|
||||
float frequency_y{0.0F};
|
||||
|
||||
Graphics::Mesh3D mesh;
|
||||
bool visible{false};
|
||||
};
|
||||
|
||||
class ShipAnimator3D {
|
||||
public:
|
||||
ShipAnimator3D(Rendering::Renderer* renderer, const Graphics::Camera3D* camera);
|
||||
|
||||
void init();
|
||||
void update(float delta_time);
|
||||
void draw() const;
|
||||
|
||||
void startEntryAnimation();
|
||||
void triggerExitAnimation();
|
||||
void triggerExitAnimationForPlayer(int player_id);
|
||||
void skipToFloatingState();
|
||||
|
||||
void setVisible(bool visible);
|
||||
[[nodiscard]] auto isAnimationComplete() const -> bool;
|
||||
[[nodiscard]] auto isVisible() const -> bool;
|
||||
|
||||
private:
|
||||
Rendering::Renderer* renderer_;
|
||||
const Graphics::Camera3D* camera_;
|
||||
std::array<TitleShip3D, 2> ships_;
|
||||
|
||||
static void updateEntering(TitleShip3D& ship, float delta_time);
|
||||
static void updateFloating(TitleShip3D& ship, float delta_time);
|
||||
static void updateExiting(TitleShip3D& ship, float delta_time);
|
||||
static void configureShipP1(TitleShip3D& ship);
|
||||
static void configureShipP2(TitleShip3D& ship);
|
||||
};
|
||||
|
||||
} // namespace Title
|
||||
Reference in New Issue
Block a user