|
| 1 | +#include "generated/tween.hpp" |
| 2 | + |
| 3 | +#include <flight/host_sdl/gl.hpp> |
| 4 | +#include <flight/host_sdl/host.hpp> |
| 5 | +#include <flight/host_sdl/window.hpp> |
| 6 | + |
| 7 | +#include <SDL3/SDL_keycode.h> |
| 8 | +#include <SDL3/SDL_opengles2.h> |
| 9 | + |
| 10 | +#include <algorithm> |
| 11 | +#include <array> |
| 12 | +#include <chrono> |
| 13 | +#include <cmath> |
| 14 | +#include <cstddef> |
| 15 | +#include <exception> |
| 16 | +#include <iostream> |
| 17 | +#include <stdexcept> |
| 18 | +#include <string_view> |
| 19 | +#include <thread> |
| 20 | +#include <type_traits> |
| 21 | + |
| 22 | +namespace { |
| 23 | + |
| 24 | +struct Color { |
| 25 | + GLfloat red; |
| 26 | + GLfloat green; |
| 27 | + GLfloat blue; |
| 28 | +}; |
| 29 | + |
| 30 | +template <typename Function> |
| 31 | +Function load_gl_function(const flight::host_sdl::GlContext& context, std::string_view name) { |
| 32 | + static_assert(std::is_pointer_v<Function>); |
| 33 | + return reinterpret_cast<Function>(context.function_address(name)); |
| 34 | +} |
| 35 | + |
| 36 | +class GlFunctions final { |
| 37 | + public: |
| 38 | + explicit GlFunctions(const flight::host_sdl::GlContext& context) |
| 39 | + : clear(load_gl_function<PFNGLCLEARPROC>(context, "glClear")), |
| 40 | + clear_color(load_gl_function<PFNGLCLEARCOLORPROC>(context, "glClearColor")), |
| 41 | + disable(load_gl_function<PFNGLDISABLEPROC>(context, "glDisable")), |
| 42 | + enable(load_gl_function<PFNGLENABLEPROC>(context, "glEnable")), |
| 43 | + get_error(load_gl_function<PFNGLGETERRORPROC>(context, "glGetError")), |
| 44 | + scissor(load_gl_function<PFNGLSCISSORPROC>(context, "glScissor")), |
| 45 | + viewport(load_gl_function<PFNGLVIEWPORTPROC>(context, "glViewport")) {} |
| 46 | + |
| 47 | + PFNGLCLEARPROC clear; |
| 48 | + PFNGLCLEARCOLORPROC clear_color; |
| 49 | + PFNGLDISABLEPROC disable; |
| 50 | + PFNGLENABLEPROC enable; |
| 51 | + PFNGLGETERRORPROC get_error; |
| 52 | + PFNGLSCISSORPROC scissor; |
| 53 | + PFNGLVIEWPORTPROC viewport; |
| 54 | +}; |
| 55 | + |
| 56 | +void fill_rectangle( |
| 57 | + const GlFunctions& gl, |
| 58 | + flight::host_sdl::WindowSize viewport, |
| 59 | + int x, |
| 60 | + int y, |
| 61 | + int width, |
| 62 | + int height, |
| 63 | + Color color) { |
| 64 | + const int left = std::clamp(x, 0, viewport.width); |
| 65 | + const int bottom = std::clamp(y, 0, viewport.height); |
| 66 | + const int right = std::clamp(x + width, 0, viewport.width); |
| 67 | + const int top = std::clamp(y + height, 0, viewport.height); |
| 68 | + if (right <= left || top <= bottom) return; |
| 69 | + gl.scissor(left, bottom, right - left, top - bottom); |
| 70 | + gl.clear_color(color.red, color.green, color.blue, 1.0F); |
| 71 | + gl.clear(GL_COLOR_BUFFER_BIT); |
| 72 | +} |
| 73 | + |
| 74 | +void fill_circle( |
| 75 | + const GlFunctions& gl, |
| 76 | + flight::host_sdl::WindowSize viewport, |
| 77 | + int center_x, |
| 78 | + int center_y, |
| 79 | + int radius, |
| 80 | + Color color) { |
| 81 | + const int radius_squared = radius * radius; |
| 82 | + for (int offset_y = -radius; offset_y <= radius; ++offset_y) { |
| 83 | + const auto extent = static_cast<int>( |
| 84 | + std::sqrt(static_cast<double>(radius_squared - (offset_y * offset_y)))); |
| 85 | + fill_rectangle( |
| 86 | + gl, |
| 87 | + viewport, |
| 88 | + center_x - extent, |
| 89 | + center_y + offset_y, |
| 90 | + (extent * 2) + 1, |
| 91 | + 1, |
| 92 | + color); |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +void draw_frame( |
| 97 | + const GlFunctions& gl, |
| 98 | + flight::host_sdl::WindowSize size, |
| 99 | + double progress) { |
| 100 | + constexpr Color background{0.035F, 0.047F, 0.075F}; |
| 101 | + constexpr Color track{0.16F, 0.19F, 0.25F}; |
| 102 | + constexpr std::array<Color, 15> colors{{ |
| 103 | + {0.96F, 0.38F, 0.38F}, |
| 104 | + {0.96F, 0.55F, 0.30F}, |
| 105 | + {0.96F, 0.72F, 0.28F}, |
| 106 | + {0.78F, 0.80F, 0.28F}, |
| 107 | + {0.53F, 0.82F, 0.34F}, |
| 108 | + {0.30F, 0.82F, 0.53F}, |
| 109 | + {0.25F, 0.80F, 0.72F}, |
| 110 | + {0.23F, 0.72F, 0.88F}, |
| 111 | + {0.30F, 0.58F, 0.96F}, |
| 112 | + {0.45F, 0.48F, 0.96F}, |
| 113 | + {0.62F, 0.42F, 0.94F}, |
| 114 | + {0.77F, 0.38F, 0.88F}, |
| 115 | + {0.90F, 0.36F, 0.72F}, |
| 116 | + {0.96F, 0.36F, 0.56F}, |
| 117 | + {0.96F, 0.42F, 0.44F}, |
| 118 | + }}; |
| 119 | + |
| 120 | + gl.viewport(0, 0, size.width, size.height); |
| 121 | + gl.disable(GL_SCISSOR_TEST); |
| 122 | + gl.clear_color(background.red, background.green, background.blue, 1.0F); |
| 123 | + gl.clear(GL_COLOR_BUFFER_BIT); |
| 124 | + gl.enable(GL_SCISSOR_TEST); |
| 125 | + |
| 126 | + const auto values = flighthq_examples_tween::sample_tween_curves(progress); |
| 127 | + if (values.size() != colors.size()) throw std::runtime_error("unexpected tween curve count"); |
| 128 | + |
| 129 | + const int horizontal_margin = std::max(28, size.width / 16); |
| 130 | + const int vertical_margin = std::max(20, size.height / 24); |
| 131 | + const int available_height = std::max(1, size.height - (vertical_margin * 2)); |
| 132 | + const int row_height = std::max(1, available_height / static_cast<int>(colors.size())); |
| 133 | + const int radius = std::clamp(row_height / 4, 4, 12); |
| 134 | + const int track_left = horizontal_margin + radius; |
| 135 | + const int track_right = std::max(track_left, size.width - horizontal_margin - radius); |
| 136 | + const int track_width = track_right - track_left; |
| 137 | + |
| 138 | + for (std::size_t index = 0; index < colors.size(); ++index) { |
| 139 | + const int center_y = |
| 140 | + size.height - vertical_margin - |
| 141 | + static_cast<int>((static_cast<double>(index) + 0.5) * static_cast<double>(row_height)); |
| 142 | + fill_rectangle(gl, size, track_left, center_y - 1, track_width, 3, track); |
| 143 | + const double value = std::clamp(values[index], 0.0, 1.0); |
| 144 | + const int center_x = track_left + static_cast<int>(std::lround(value * static_cast<double>(track_width))); |
| 145 | + fill_circle(gl, size, center_x, center_y, radius, colors[index]); |
| 146 | + } |
| 147 | + gl.disable(GL_SCISSOR_TEST); |
| 148 | + if (gl.get_error() != GL_NO_ERROR) throw std::runtime_error("OpenGL rejected the tween frame"); |
| 149 | +} |
| 150 | + |
| 151 | +int run(bool smoke) { |
| 152 | + using namespace std::chrono_literals; |
| 153 | + |
| 154 | + flight::host_sdl::Host host; |
| 155 | + flight::host_sdl::WindowOptions options; |
| 156 | + options.title = "Flight tween - SDL + OpenGL ES"; |
| 157 | + options.width = 960; |
| 158 | + options.height = 720; |
| 159 | + options.graphics_api = flight::host_sdl::GraphicsApi::open_gl; |
| 160 | + options.hidden = smoke; |
| 161 | + options.open_gl.major_version = 2; |
| 162 | + options.open_gl.minor_version = 0; |
| 163 | + options.open_gl.profile = flight::host_sdl::OpenGlProfile::es; |
| 164 | + |
| 165 | + flight::host_sdl::Window window(options); |
| 166 | + flight::host_sdl::GlContext context(window); |
| 167 | + context.make_current(window); |
| 168 | + const GlFunctions gl(context); |
| 169 | + |
| 170 | + const std::uint64_t started = flight::host_sdl::Host::ticks_nanoseconds(); |
| 171 | + std::size_t frames = 0; |
| 172 | + bool running = true; |
| 173 | + while (running) { |
| 174 | + SDL_Event event{}; |
| 175 | + while (host.poll_event(event)) { |
| 176 | + if (event.type == SDL_EVENT_QUIT || |
| 177 | + (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE)) { |
| 178 | + running = false; |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + const std::uint64_t elapsed = flight::host_sdl::Host::ticks_nanoseconds() - started; |
| 183 | + const double seconds = static_cast<double>(elapsed) / 1'000'000'000.0; |
| 184 | + const double cycle = std::fmod(seconds / 2.0, 2.0); |
| 185 | + const double progress = cycle <= 1.0 ? cycle : 2.0 - cycle; |
| 186 | + draw_frame(gl, window.pixel_size(), progress); |
| 187 | + context.swap(window); |
| 188 | + |
| 189 | + ++frames; |
| 190 | + if (smoke && frames >= 3) running = false; |
| 191 | + std::this_thread::sleep_for(8ms); |
| 192 | + } |
| 193 | + return 0; |
| 194 | +} |
| 195 | + |
| 196 | +} // namespace |
| 197 | + |
| 198 | +int main(int argc, char** argv) { |
| 199 | + if (argc > 2 || (argc == 2 && std::string_view(argv[1]) != "--smoke")) { |
| 200 | + std::cerr << "usage: flight_cpp_tween_sdl_gl_example [--smoke]\n"; |
| 201 | + return 2; |
| 202 | + } |
| 203 | + try { |
| 204 | + return run(argc == 2); |
| 205 | + } catch (const std::exception& error) { |
| 206 | + std::cerr << "Flight SDL tween failed: " << error.what() << '\n'; |
| 207 | + return 1; |
| 208 | + } |
| 209 | +} |
0 commit comments