Skip to content

Camera SystemΒ #29

@LyeZinho

Description

@LyeZinho

πŸŽ₯ Camera System

Fase: 3 β€” O Olho da Engine
Namespace: Caffeine::Render
Arquivo: src/render/Camera2D.hpp
Status: πŸ“… Planejado
RF: RF3.7


VisΓ£o Geral

O Camera System fornece a projeΓ§Γ£o matemΓ‘tica entre o espaΓ§o do mundo (coordenadas do jogo) e o espaΓ§o da tela (pixels). A Camera2D usa projeΓ§Γ£o ortogrΓ‘fica para jogos 2D. Na Fase 5, Camera3D adicionarΓ‘ perspectiva.


API Planejada

namespace Caffeine::Render {

// ============================================================================
// @brief  CΓ’mera 2D com projeΓ§Γ£o ortogrΓ‘fica.
//
//  Funcionalidades:
//  - View matrix (posiΓ§Γ£o + rotaΓ§Γ£o da cΓ’mera)
//  - Projection matrix (ortogrΓ‘fica β€” sem perspectiva)
//  - worldToScreen / screenToWorld
//  - Follow entity com smoothing
//  - Camera shake
//  - World bounds clamp
// ============================================================================
class Camera2D {
public:
    Camera2D();

    // ── Matrizes ───────────────────────────────────────────────
    Mat4 viewMatrix()           const;
    Mat4 projectionMatrix()     const;
    Mat4 viewProjectionMatrix() const;  // cached, recalculated se dirty

    // ── ConversΓ£o de espaΓ§o ────────────────────────────────────
    Vec2 worldToScreen(Vec2 worldPos)   const;
    Vec2 screenToWorld(Vec2 screenPos)  const;
    bool isVisible(Rect2D worldRect)    const;  // frustum culling bΓ‘sico

    // ── ConfiguraΓ§Γ£o ───────────────────────────────────────────
    void setPosition(Vec2 pos);
    void setZoom(f32 zoom);             // 1.0 = normal, 2.0 = 2x zoom in
    void setRotation(f32 degrees);
    void setViewport(Rect2D viewport);  // pixels (x, y, width, height)
    void setAspectRatio(f32 aspect);

    // ── Follow ─────────────────────────────────────────────────
    void follow(ECS::Entity target, f32 smoothing = 0.1f);  // lerp per frame
    void stopFollowing();
    void update(f64 dt, const ECS::World& world);           // processa follow + shake

    // ── Shake ──────────────────────────────────────────────────
    void shake(Vec2 intensity, f32 duration);  // em pixels

    // ── Bounds ─────────────────────────────────────────────────
    void setBounds(Rect2D worldBounds);
    void clearBounds();

    // ── Getters ────────────────────────────────────────────────
    Vec2   position()  const { return m_position; }
    f32    zoom()      const { return m_zoom; }
    f32    rotation()  const { return m_rotation; }
    Rect2D viewport()  const { return m_viewport; }
    Vec2   size()      const { return {m_viewport.size.x / m_zoom,
                                        m_viewport.size.y / m_zoom}; }

private:
    Mat4  calculateViewMatrix()       const;
    Mat4  calculateProjectionMatrix() const;
    Vec2  applyBounds(Vec2 pos)       const;

    Vec2   m_position    = {0, 0};
    f32    m_zoom        = 1.0f;
    f32    m_rotation    = 0.0f;
    Rect2D m_viewport    = {{0, 0}, {1280, 720}};
    f32    m_aspect      = 1280.0f / 720.0f;

    // Follow
    ECS::Entity m_followTarget    = ECS::Entity::INVALID;
    f32          m_followSmoothing = 0.1f;

    // Shake
    Vec2 m_shakeIntensity = {0, 0};
    f32  m_shakeTime      = 0.0f;
    f32  m_shakeDuration  = 0.0f;
    Vec2 m_shakeOffset    = {0, 0};

    // Bounds
    bool   m_hasBounds   = false;
    Rect2D m_worldBounds = {};

    mutable Mat4 m_cachedViewProj;
    mutable bool m_dirty = true;
};

}  // namespace Caffeine::Render

MatemΓ‘tica da ProjeΓ§Γ£o OrtogrΓ‘fica

Projection Matrix (ortho):
    left   = -viewport.width  / 2 / zoom
    right  = +viewport.width  / 2 / zoom
    bottom = -viewport.height / 2 / zoom
    top    = +viewport.height / 2 / zoom
    near   = -1000
    far    = +1000

    Mat4::ortho(left, right, bottom, top, near, far)

View Matrix:
    Mat4::translate(-position) * Mat4::rotateZ(-rotation)

Nota: A projeΓ§Γ£o ortogrΓ‘fica nΓ£o possui perspectiva β€” objetos distantes tΓͺm o mesmo tamanho que objetos prΓ³ximos. Correto para jogos 2D.


Camera Follow

// Smooth follow usando lerp:
void Camera2D::update(f64 dt, const ECS::World& world) {
    if (m_followTarget.isValid()) {
        if (auto* pos = world.get<Position2D>(m_followTarget)) {
            Vec2 target = {pos->x, pos->y};
            m_position = Vec2::lerp(m_position, target, m_followSmoothing);
            m_position = applyBounds(m_position);
            m_dirty = true;
        }
    }
    // Shake decay
    if (m_shakeTime > 0) {
        m_shakeTime -= dt;
        f32 t = m_shakeTime / m_shakeDuration;
        m_shakeOffset = m_shakeIntensity * t * randomDir();
        m_dirty = true;
    }
}

Exemplos de Uso

// ── Setup bΓ‘sico ─────────────────────────────────────────────
Caffeine::Render::Camera2D camera;
camera.setViewport({ {0, 0}, {1280, 720} });
camera.setZoom(1.0f);

// ── Follow jogador ────────────────────────────────────────────
camera.follow(playerEntity, 0.08f);  // 8% do caminho por frame
camera.setBounds({ {0, 0}, {5000, 3000} });  // limites do mapa

// ── Por frame ────────────────────────────────────────────────
camera.update(dt, world);
batchRenderer.setCamera(camera);

// ── Shake ao levar dano ───────────────────────────────────────
eventBus.subscribe<OnPlayerHit>([&](const OnPlayerHit& e) {
    camera.shake({5, 5}, 0.3f);  // 5px, 300ms
});

// ── ConversΓ£o de espaΓ§o para UI ───────────────────────────────
Vec2 screenPos = camera.worldToScreen(enemyWorldPos);
// Usa screenPos para renderizar health bar sobre o inimigo

// ── Culling manual ────────────────────────────────────────────
if (!camera.isVisible(entityBounds)) return;  // skip render

Camera 3D (Fase 5)

Na Fase 5, Camera3D serΓ‘ adicionada com a mesma interface mas projeΓ§Γ£o perspectiva:

// Fase 5:
class Camera3D {
    Mat4 viewMatrix()       const;  // lookAt(eye, target, up)
    Mat4 projectionMatrix() const;  // perspective(fovY, aspect, near, far)
    // ... mesma interface de setPosition/setZoom β†’ serΓ‘ setFOV
};

O Batch Renderer aceita Camera2D e Camera3D via polimorfismo.


DecisΓ΅es de Design

DecisΓ£o Justificativa
Ortho para 2D Sem distorΓ§Γ£o perspectiva β€” correto para jogos 2D
Cache m_dirty Evita recalcular VP matrix quando cΓ’mera nΓ£o moveu
Follow com lerp Suave, sem "snap" para posiΓ§Γ£o do jogador
isVisible(Rect2D) Frustum culling bΓ‘sico para 2D
setBounds CΓ’mera nunca mostra Γ‘rea fora do mapa

CritΓ©rio de AceitaΓ§Γ£o

  • CΓ’mera segue entidade com smoothing = 0.1f sem jitter a 60fps
  • worldToScreen e screenToWorld sΓ£o operaΓ§Γ΅es inversas corretas
  • Camera shake decai corretamente atΓ© zero
  • setBounds impede cΓ’mera de mostrar Γ‘rea fora do mapa
  • VP matrix cached β€” nΓ£o recalcula quando cΓ’mera nΓ£o moveu

DependΓͺncias


ReferΓͺncias

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions