#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_NCURSES #include #endif #include "fb/chfb.h" #include "fb/pixfb.h" #include "o3d/scene.h" #include "o3d/mesh.h" #include "o3d/obj3d.h" #include "o3d/vertex_data.h" #include "o3d/tri.h" #include "o3d/camera.h" #include "math/vector.h" #include "math/mat4.h" #include "math/quat.h" #include "math/tform.h" #include "ctrl/keyboard.h" #include "ctrl/mouse.h" #include "renderer.h" #include "obj_parser.h" using engine::Renderer, engine::fb::CharacterFrameBuffer, engine::fb::PixelFrameBuffer, engine::o3d::Scene, engine::o3d::Mesh, engine::o3d::Triangle, engine::o3d::Camera, engine::o3d::VertexData, engine::math::Vector2, engine::math::Vector3, engine::math::Vector4, engine::math::Matrix4, engine::math::Quaternion, engine::controllers::Keyboard, engine::controllers::KeyboardKey, engine::controllers::Mouse; #define FPS 60 #define PI 3.1415926535f #define MODE_HELP 0 #define MODE_TERM 1 #define MODE_GRAPHICAL 2 #define GAME_PLANE 0 #define GAME_SUZANNE 1 #define GAME_PHYSICS 2 #define GAME GAME_PHYSICS static void print_usage(std::ostream& output_stream) { output_stream << "Usage: ./engine [-htg] [--help] [--term] [--graphical]\n" << " -h, --help show usage (this)\n" << " -t, --term terminal mode\n" << " -g, --graphical graphical mode (default)\n" << std::flush; } [[noreturn]] static void usage_error_exit() { print_usage(std::cerr); std::exit(EXIT_FAILURE); } extern Camera* camera; Camera* camera; template static void scene_main(Renderer& renderer, const Matrix4& final_transform_mat, UpdateFrameFn update_frame) { bool cont = true; Scene scene{ {90.f * PI / 180.f, {{0.f, 1.8f, 7.f}, Quaternion::one(), {1.f, 1.f, 1.f}}}, { #if GAME == GAME_PLANE { Mesh::plane(2.f, 2.f), { Vector3(0.f, 0.f, 0.f), Quaternion::one(), Vector3(1.f, 1.f, 1.f), } }, #elif GAME == GAME_SUZANNE { engine::parse_object("../assets/suzanne.obj"), { Vector3(0.f, 0.f, 0.f), Quaternion::one(), Vector3(1.f, 1.f, 1.f), } }, #elif GAME == GAME_PHYSICS { Mesh::plane(10.f, 10.f), { Vector3(0.f, 0.f, 0.f), Quaternion::one(), Vector3(1.f, 1.f, 1.f), } }, { engine::parse_object("../assets/suzanne.obj"), { Vector3(0.f, 1.f, 0.f), Quaternion::one(), Vector3(1.f, 1.f, 1.f), } }, #endif } }; float rx = 0.f, ry = 0.f; Keyboard kb{[&](KeyboardKey key) { (void) key; }, [&](KeyboardKey key) { (void) key; }}; Mouse mouse{[&](Vector2 rel) { rx += -rel.y; ry += -rel.x; if (rx < -PI / 2.f) rx = -PI / 2.f; if (rx > PI / 2.f) rx = PI / 2.f; scene.camera.transform.rot = Quaternion::euler_zxy(rx, ry, 0.f); }}; camera = &scene.camera; while (cont) { renderer.clear(); auto pre_final_mat = final_transform_mat * scene.camera.to_mat4(static_cast(renderer.height()) / static_cast(renderer.width()), .5f, 12.f); for (const auto& obj : scene.objs) { auto obj_mat = obj.transform.to_mat4(); auto final_mat = pre_final_mat * obj_mat; const auto& mesh = obj.mesh; std::vector vertices; std::vector vertices_data; for (const auto& vertex : mesh.vertices) { vertices.push_back(final_mat * vertex); vertices_data.push_back(VertexData((obj_mat * vertex).xyz())); } for (const auto& triangle_indices : mesh.indices) { [&](std::integer_sequence) { renderer.draw_triangle({{vertices[triangle_indices[j][0]], mesh.normals[triangle_indices[j][1]], vertices_data[triangle_indices[j][0]]}...}); }(std::make_integer_sequence()); } } cont = update_frame(scene, kb, mouse); Vector3 movement(0.f, 0.f, 0.f); if (kb.is_down(KeyboardKey::fw)) movement.z += -1.f; if (kb.is_down(KeyboardKey::key_left)) movement.x += -1.f; if (kb.is_down(KeyboardKey::bw)) movement.z += +1.f; if (kb.is_down(KeyboardKey::key_right)) movement.x += +1.f; if (kb.is_down(KeyboardKey::fw) || kb.is_down(KeyboardKey::key_left) || kb.is_down(KeyboardKey::bw) || kb.is_down(KeyboardKey::key_right)) movement.normalize(); scene.camera.transform.loc += movement.rot(Quaternion::rot_y(ry)) * .05f; scene.camera.fov = (kb.is_down(KeyboardKey::zoom) ? 40.f : 80.f) * PI / 180.f; } } #ifdef ENABLE_NCURSES #define MKEY_ESC 27 static int main_term() { // init std::setlocale(LC_ALL, ""); initscr(); cbreak(); noecho(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE); set_escdelay(0); curs_set(0); int w, h; getmaxyx(stdscr, h, w); Renderer renderer{CharacterFrameBuffer{static_cast(w), static_cast(h)}}; scene_main(renderer, Matrix4::scale(Vector3(2.f, 1.f, 1.f)), [&](Scene& scene, auto& kb, auto& mouse) { (void) scene; mvaddnstr(0, 0, renderer.fb.chars(), renderer.width() * renderer.height()); bool cont = true; std::optional key; std::optional rel; //timeout(1000 / FPS); timeout(10); int c = getch(); switch (c) { case 'z': key = KeyboardKey::fw; break; case 'q': key = KeyboardKey::key_left; break; case 's': key = KeyboardKey::bw; break; case 'd': key = KeyboardKey::key_right; break; case 'p': key = KeyboardKey::zoom; break; case KEY_UP: rel = Vector2(0.f, -.1f); break; case KEY_LEFT: rel = Vector2(-.1f, 0.f); break; case KEY_DOWN: rel = Vector2(0.f, +.1f); break; case KEY_RIGHT: rel = Vector2(+.1f, 0.f); break; case MKEY_ESC: return false; } if (key && *key == KeyboardKey::fw) { if (!kb.is_down(KeyboardKey::fw)) kb.key_down_event(KeyboardKey::fw); } else { if (kb.is_down(KeyboardKey::fw)) kb.key_up_event(KeyboardKey::fw); } if (key && *key == KeyboardKey::key_left) { if (!kb.is_down(KeyboardKey::key_left)) kb.key_down_event(KeyboardKey::key_left); } else { if (kb.is_down(KeyboardKey::key_left)) kb.key_up_event(KeyboardKey::key_left); } if (key && *key == KeyboardKey::bw) { if (!kb.is_down(KeyboardKey::bw)) kb.key_down_event(KeyboardKey::bw); } else { if (kb.is_down(KeyboardKey::bw)) kb.key_up_event(KeyboardKey::bw); } if (key && *key == KeyboardKey::key_right) { if (!kb.is_down(KeyboardKey::key_right)) kb.key_down_event(KeyboardKey::key_right); } else { if (kb.is_down(KeyboardKey::key_right)) kb.key_up_event(KeyboardKey::key_right); } if (key && *key == KeyboardKey::zoom) { if (kb.is_down(KeyboardKey::zoom)) kb.key_up_event(KeyboardKey::zoom); else kb.key_down_event(KeyboardKey::zoom); } if (rel) mouse.mouse_motion_event(*rel); getmaxyx(stdscr, h, w); renderer.resize(static_cast(w), static_cast(h)); return cont; } ); // terminate endwin(); return EXIT_SUCCESS; } #endif #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 static int main_graphical() { SDL_Window* window = nullptr; SDL_Renderer* renderer = nullptr; SDL_Texture* texture = nullptr; // init if (SDL_Init(SDL_INIT_VIDEO) < 0) { std::cerr << "Error: SDL_Init error: " << SDL_GetError() << std::endl; return EXIT_FAILURE; } window = SDL_CreateWindow("Engine", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); if (window == nullptr) { SDL_Quit(); std::cerr << "Error: SDL_CreateWindow error: " << SDL_GetError() << std::endl; return EXIT_FAILURE; } renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, SCREEN_WIDTH, SCREEN_HEIGHT); Renderer engine_renderer{PixelFrameBuffer{SCREEN_WIDTH, SCREEN_HEIGHT}}; SDL_Event e; scene_main(engine_renderer, Matrix4::idty(), [&](Scene& scene, auto& kb, auto& mouse) { (void) scene; SDL_UpdateTexture(texture, nullptr, engine_renderer.fb.pixels(), SCREEN_WIDTH * 4); SDL_RenderClear(renderer); SDL_RenderCopy(renderer, texture, nullptr, nullptr); SDL_RenderPresent(renderer); SDL_UpdateWindowSurface(window); SDL_SetRelativeMouseMode(SDL_TRUE); bool cont = true; if (SDL_WaitEventTimeout(&e, 10)) { do { switch (e.type) { case SDL_QUIT: cont = false; break; case SDL_KEYDOWN: switch (e.key.keysym.sym) { case SDLK_z: kb.key_down_event(KeyboardKey::fw); break; case SDLK_q: kb.key_down_event(KeyboardKey::key_left); break; case SDLK_s: kb.key_down_event(KeyboardKey::bw); break; case SDLK_d: kb.key_down_event(KeyboardKey::key_right); break; case SDLK_LCTRL: kb.key_down_event(KeyboardKey::zoom); break; } break; case SDL_KEYUP: switch (e.key.keysym.sym) { case SDLK_z: kb.key_up_event(KeyboardKey::fw); break; case SDLK_q: kb.key_up_event(KeyboardKey::key_left); break; case SDLK_s: kb.key_up_event(KeyboardKey::bw); break; case SDLK_d: kb.key_up_event(KeyboardKey::key_right); break; case SDLK_LCTRL: kb.key_up_event(KeyboardKey::zoom); break; case SDLK_ESCAPE: cont = false; break; } break; case SDL_MOUSEMOTION: mouse.mouse_motion_event(Vector2( static_cast(e.motion.xrel) * .01f, static_cast(e.motion.yrel) * .01f)); break; } } while (SDL_PollEvent(&e)); } return cont; } ); // terminate SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_SUCCESS; } static std::vector convert_args(int argc, char *argv[]) { std::vector args(argc); for (int i = 0; i < argc; i++) args[i] = argv[i]; return args; } static void parse_args(const std::vector& args, int& mode) { for (auto args_iter = std::next(args.begin()); args_iter != args.end(); args_iter++) { const auto& arg = *args_iter; if (arg.size() >= 1 && arg[0] == '-') { if (arg.size() >= 2 && arg[1] == '-') { auto long_opt = arg.substr(2); if (long_opt == "help") { mode = MODE_HELP; } else if (long_opt == "term") { mode = MODE_TERM; } else if (long_opt == "graphical") { mode = MODE_GRAPHICAL; } else { std::cerr << "Error: Unexpected option `--" << long_opt << "'." << std::endl; usage_error_exit(); } } else { std::size_t arg_len = arg.size(); if (arg_len == 1) { std::cerr << "Error: Unexpected argument `-'." << std::endl; usage_error_exit(); } for (auto arg_iter = std::next(arg.begin()); arg_iter != arg.end(); arg_iter++) { const auto& opt = *arg_iter; switch (opt) { case 'h': mode = MODE_HELP; break; case 't': mode = MODE_TERM; break; case 'g': mode = MODE_GRAPHICAL; break; default: std::cerr << "Error: Unexpected option `-" << opt << "'." << std::endl; usage_error_exit(); } } } } else { std::cerr << "Error: Unexpected argument `" << arg << "'." << std::endl; usage_error_exit(); } } } int main(int argc, char *argv[]) { int mode = MODE_GRAPHICAL; parse_args(convert_args(argc, argv), mode); switch (mode) { case MODE_HELP: print_usage(std::cout); return EXIT_SUCCESS; case MODE_TERM: #ifdef ENABLE_NCURSES return main_term(); #else std::cerr << "Error: ncurses was not enabled during compilation." << std::endl; return EXIT_FAILURE; #endif case MODE_GRAPHICAL: return main_graphical(); default: std::unreachable(); } }