paradiso/examples/pong/pong.cpp
2023-07-01 22:43:05 +02:00

254 lines
No EOL
7.4 KiB
C++

/**
* paradiso - Paradigmen der Softwareentwicklung
*
* (c) Copyright 2023 Hartmut Seichter
*
*/
#include <paradiso/bitmap.hpp>
#include <paradiso/context.hpp>
#include <paradiso/geometry.hpp>
#include <paradiso/renderer.hpp>
#include <paradiso/shader.hpp>
#include <paradiso/sprite.hpp>
#include <paradiso/vector.hpp>
#include <paradiso/window.hpp>
#include <unordered_map>
#include <iomanip>
#include <iostream>
struct PongStage {
using Vec2 = paradiso::Vector2<float>;
Vec2 tl{Vec2::make(-1.0f, -1.0f)};
Vec2 br{Vec2::make(+1.0f, +1.0f)};
enum class TouchPoint { None, Left, Right, Bottom, Top };
TouchPoint touch(const Vec2& o, float eps = 0.001f) noexcept {
std::unordered_map<TouchPoint, float> deltas;
deltas[TouchPoint::Left] = std::abs(o.x() - tl.x());
deltas[TouchPoint::Right] = std::abs(o.x() - br.x());
deltas[TouchPoint::Bottom] = std::abs(o.y() - br.y());
deltas[TouchPoint::Top] = std::abs(o.y() - tl.y());
auto min_el = *std::min_element(
std::begin(deltas), std::end(deltas),
[](const auto& l, const auto& r) { return l.second < r.second; });
auto t = (min_el.second <= eps) ? min_el.first : TouchPoint::None;
return t;
};
// sprite
paradiso::Sprite sprite{
.bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1},
paradiso::RGBA::from_rgb(0x80,0xFF,0xFF))
};
void draw(const paradiso::Shader& shader) {
renderer.draw(sprite, shader);
}
paradiso::Renderer renderer{};
};
struct PongPaddle {
static constexpr float whoopiness = 0.95f;
// sprite
paradiso::Sprite sprite{
.bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1},
paradiso::RGBA::white()),
.pivot = paradiso::Vector2<float>::make(0.0f, -0.9f),
.scale = paradiso::Vector2<float>::make(0.25f, 0.01f)};
// keyboard handler
void on_keyboard(const paradiso::Window::KeyboardInputStack& input) {
if (input.size()) {
if (input.top().key == 'A' || input.top().key == 263) {
velocity_horizontal -= 0.01;
} else if (input.top().key == 'D' || input.top().key == 262) {
velocity_horizontal += 0.01;
}
}
}
void draw(const paradiso::Shader& shader) {
// update internal state
velocity_horizontal *= whoopiness;
sprite.pivot.x() += velocity_horizontal;
std::clamp(sprite.pivot.x(), -0.5f, 0.5f);
// update shader uniforms
shader.set_uniform("pivot", sprite.pivot);
shader.set_uniform("scale", sprite.scale);
shader.set_uniform("rotation", sprite.rotation);
renderer.draw(sprite, shader);
}
float velocity_horizontal{};
paradiso::Renderer renderer{};
};
struct PongBall {
// sprite
paradiso::Sprite sprite{
.bitmap = paradiso::Bitmap::from_data(paradiso::Size{1, 1},
paradiso::RGBA::white()),
.pivot = paradiso::Vector2<float>::make(0.0f, 0.0f),
.scale = paradiso::Vector2<float>::make(0.0125f, 0.0125f),
};
// interaction Stage - Ball
void interact(PongStage& stage) {
auto touch = stage.touch(sprite.pivot);
switch (touch) {
case PongStage::TouchPoint::Top:
case PongStage::TouchPoint::Bottom:
velocity.y() *= -1;
break;
case PongStage::TouchPoint::Left:
case PongStage::TouchPoint::Right:
velocity.x() *= -1;
break;
default:
break;
}
}
// interact ball & paddle
void interact(const PongPaddle& paddle) {
const auto& ps = paddle.sprite.scale;
const auto& pp = paddle.sprite.pivot;
auto left_x = pp.x() - ps.x();
auto right_x = pp.x() + ps.x();
static constexpr float eps{0.01f};
if (sprite.pivot.x() >= left_x && sprite.pivot.x() <= right_x &&
std::abs(pp.y() - sprite.pivot.y()) < eps) {
velocity.y() *= -1;
velocity.x() += paddle.velocity_horizontal * paddle.whoopiness;
}
}
void update() { sprite.pivot += velocity; }
void draw(const paradiso::Shader& shader) {
std::clamp(sprite.pivot.x(), -0.5f, 0.5f);
// update shader uniforms
shader.set_uniform("pivot", sprite.pivot);
shader.set_uniform("scale", sprite.scale);
shader.set_uniform("rotation", sprite.rotation);
renderer.draw(sprite, shader);
}
paradiso::Vector2<float> velocity{};
paradiso::Renderer renderer{};
constexpr void push(const auto& impulse) noexcept { velocity += impulse; }
};
auto main() -> int {
// Ausgabefenster ... sieht aus als wäre es auf dem Stack
auto window = paradiso::Window();
// wir bauen ein Fenster ...
window
.set_size(paradiso::Size{.width = 720, .height = 720}) // ... Grösse
.set_position(paradiso::Point{.x = 100, .y = 100}) // ... Position
.set_title("PardiSO.Pong") // ... Titel
.set_visible(true); // ... und jetzt anzeigen!
// der Fenster Kontext
auto ctx = paradiso::Context{};
// ein Shader (Schattierungsprogramm)
auto shader = paradiso::Shader{};
// wir nutzen einen vorgefertigten shader
shader.load_preset(paradiso::Shader::Preset::Sprite);
auto paddle = PongPaddle{};
auto ball = PongBall{};
auto stage = PongStage{};
ball.push(paradiso::Vector2<float>::make(0.0001f, -0.0005f));
// ein viewport stellt die Sicht der Kamera dar, d.h. bei quadratischen
// Pixeln sollte hier auch eine dementsprechende Grösse eingestellt
// werden
ctx.set_viewport(paradiso::Rectangle{
.position = paradiso::Point{.x = 0, .y = 0},
.size = window.client_size()
.maximal_extent() // wir wollen das
// Seitenverhältnis beibehalten
});
// nothing beats a classic look
ctx.set_clearcolor(paradiso::RGBA::from_rgb(0x00, 0x00, 0x00));
// das update führt den hier mitgegebnen Ausdruck innerhalb der internen
// Updates des Fensters auf. Es wird hier auch explizit ein bool gefordert
// damit das update auch zu jederzeit unterbrochen werden kann
while (window.update([&](paradiso::Window& w) -> bool {
auto me = window.client_size().maximal_extent();
ctx.set_viewport(paradiso::Rectangle{
.position = paradiso::Point{.x = 0, .y = 0},
.size = me // wir wollen das
// Seitenverhältnis beibehalten
});
// hier wird das eigentliche löschen des vorherigen Inhalts ausgelöst
ctx.clear();
paddle.on_keyboard(w.keyboard_input());
// still here
bool want_close =
(w.keyboard_input().size() && w.keyboard_input().top().key == 'Q');
if (!w.keyboard_input().empty()) {
if (w.keyboard_input().top().key == 'R') {
ball.sprite.pivot.x() = 0.0f;
ball.sprite.pivot.y() = 0.9f;
}
}
ball.interact(stage);
ball.interact(paddle);
ball.update();
stage.draw(shader);
paddle.draw(shader);
ball.draw(shader);
// ... signalisiere ob wir weitermachen wollen ...
return !want_close;
})) {
};
return 0;
}