6bfde6c6fb
- gfx::pipeline gains bind_vec3/bind_float for passing arbitrary uniforms to shaders (used for light position/color) - scene base gains on_mouse_drag virtual hook; cuber.cpp polls left-mouse delta each frame and forwards it to the active scene - cornell_box scene: 5-wall room (red/green/white), two pre-rotated white boxes with proportions from the original paper, an unlit ceiling light panel, and a point-light Lambert shader - left-click drag orbits the camera around the scene origin; pitch is clamped to ±80° to prevent gimbal flip - key 3 / --scene cornell_box selects the scene
163 lines
4.9 KiB
C++
163 lines
4.9 KiB
C++
#define GLFW_INCLUDE_NONE
|
|
|
|
#include <csignal>
|
|
#include <chrono>
|
|
#include <string>
|
|
#include <string_view>
|
|
|
|
#include "GLFW/glfw3.h"
|
|
#include "asio.hpp"
|
|
#include "asio/steady_timer.hpp"
|
|
#include "fmt/std.h"
|
|
|
|
#include "cbt/window.hpp"
|
|
#include "cbt/opengl/context.hpp"
|
|
#include "scenes/cube.hpp"
|
|
#include "scenes/sphere.hpp"
|
|
#include "scenes/cornell_box.hpp"
|
|
|
|
auto main(int argc, char const* argv[]) -> int {
|
|
float max_duration_seconds = 0.0f;
|
|
std::string scene_name = "cube"; // "cube", "sphere", "cornell_box", or "1"/"2"/"3"
|
|
bool take_screenshot = false;
|
|
|
|
for (int i = 1; i < argc; ++i) {
|
|
std::string_view arg = argv[i];
|
|
if (arg == "--help" || arg == "-h") {
|
|
fmt::print("Usage: {} [options]\n", argv[0]);
|
|
fmt::print("Flags:\n");
|
|
fmt::print(" --duration <seconds> Auto-terminate after N seconds (for testing/CI)\n");
|
|
fmt::print(" --scene <cube|sphere|cornell_box> Select initial scene (default: cube)\n");
|
|
fmt::print(" --screenshot Render one frame, save screenshot, and exit\n");
|
|
fmt::print("\nKeys (during runtime):\n");
|
|
fmt::print(" S Take screenshot (saved as screenshot.png)\n");
|
|
fmt::print(" 1/2/3 Switch between cube/sphere/cornell_box scene\n");
|
|
fmt::print(" Q Quit\n");
|
|
return 0;
|
|
}
|
|
if (arg == "--duration" && i + 1 < argc) {
|
|
max_duration_seconds = std::stof(std::string(argv[++i]));
|
|
continue;
|
|
}
|
|
if (arg == "--scene" && i + 1 < argc) {
|
|
scene_name = argv[++i]; // std::string to avoid string_view lifetime gotchas with argv
|
|
continue;
|
|
}
|
|
if (arg == "--screenshot") {
|
|
take_screenshot = true;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
auto win = cbt::window("cuber", 1280, 720);
|
|
|
|
if (!win.valid()) {
|
|
return 1;
|
|
}
|
|
|
|
auto ctx = cbt::opengl::context(win);
|
|
|
|
if (!ctx.valid()) {
|
|
return 1;
|
|
}
|
|
|
|
// Wire up resize callback
|
|
win.on_resize([&ctx](int width, int height) {
|
|
ctx.set_size(width, height);
|
|
});
|
|
|
|
auto cube_scn = cbt::scenes::cube();
|
|
auto sphere_scn = cbt::scenes::sphere();
|
|
auto cornell_box_scn = cbt::scenes::cornell_box();
|
|
|
|
cbt::scene* active_scene = nullptr;
|
|
if (!cube_scn.init() || !sphere_scn.init() || !cornell_box_scn.init()) {
|
|
return 1;
|
|
}
|
|
if (scene_name == "sphere" || scene_name == "2") {
|
|
active_scene = &sphere_scn;
|
|
} else if (scene_name == "cornell_box" || scene_name == "3") {
|
|
active_scene = &cornell_box_scn;
|
|
} else {
|
|
active_scene = &cube_scn;
|
|
}
|
|
|
|
// signal handling + optional duration timer (via ASIO)
|
|
asio::io_context io;
|
|
asio::signal_set signals(io, SIGINT, SIGTERM);
|
|
|
|
signals.async_wait([&](auto, auto) {
|
|
win.stop();
|
|
io.stop();
|
|
});
|
|
|
|
asio::steady_timer duration_timer(io);
|
|
if (max_duration_seconds > 0.0f) {
|
|
duration_timer.expires_after(std::chrono::milliseconds(
|
|
static_cast<long long>(max_duration_seconds * 1000.0f)));
|
|
duration_timer.async_wait([&win](auto ec) {
|
|
if (!ec) {
|
|
win.stop();
|
|
}
|
|
});
|
|
}
|
|
|
|
auto process_signals = [&]() -> void {
|
|
while (io.poll()) {}
|
|
};
|
|
|
|
// render loop
|
|
double mouse_x = 0.0, mouse_y = 0.0;
|
|
glfwGetCursorPos(win.raw(), &mouse_x, &mouse_y);
|
|
bool mouse_was_down = false;
|
|
|
|
auto prev = std::chrono::steady_clock::now();
|
|
|
|
while (!win.should_close()) {
|
|
process_signals();
|
|
|
|
if (glfwGetKey(win.raw(), GLFW_KEY_Q) == GLFW_PRESS) {
|
|
win.stop();
|
|
}
|
|
if (glfwGetKey(win.raw(), GLFW_KEY_S) == GLFW_PRESS) {
|
|
win.screenshot();
|
|
}
|
|
if (glfwGetKey(win.raw(), GLFW_KEY_1) == GLFW_PRESS) {
|
|
active_scene = &cube_scn;
|
|
}
|
|
if (glfwGetKey(win.raw(), GLFW_KEY_2) == GLFW_PRESS) {
|
|
active_scene = &sphere_scn;
|
|
}
|
|
if (glfwGetKey(win.raw(), GLFW_KEY_3) == GLFW_PRESS) {
|
|
active_scene = &cornell_box_scn;
|
|
}
|
|
|
|
double cur_x = 0.0, cur_y = 0.0;
|
|
glfwGetCursorPos(win.raw(), &cur_x, &cur_y);
|
|
bool const mouse_down = glfwGetMouseButton(win.raw(), GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS;
|
|
if (mouse_down && mouse_was_down) {
|
|
active_scene->on_mouse_drag(cur_x - mouse_x, cur_y - mouse_y);
|
|
}
|
|
mouse_x = cur_x;
|
|
mouse_y = cur_y;
|
|
mouse_was_down = mouse_down;
|
|
|
|
auto now = std::chrono::steady_clock::now();
|
|
auto dt = std::chrono::duration<float>(now - prev).count();
|
|
prev = now;
|
|
|
|
active_scene->update(dt);
|
|
active_scene->render(win.width(), win.height());
|
|
|
|
win.swap_buffers();
|
|
win.poll_events();
|
|
|
|
if (take_screenshot) {
|
|
win.screenshot();
|
|
win.stop();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|