#include "config.h" #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 "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::Vector3, engine::math::Vector4, engine::math::Matrix4, engine::math::Quaternion; #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 } }; 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); } } #ifdef ENABLE_NCURSES #define MKEY_Z 122 #define MKEY_Q 113 #define MKEY_S 115 #define MKEY_D 100 #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) { (void) scene; mvaddnstr(0, 0, renderer.fb.chars(), renderer.width() * renderer.height()); bool cont = true; //timeout(1000 / FPS); timeout(10); int c = getch(); if (c == MKEY_ESC) return false; switch (c) { case KEY_UP: // a.x += 0.1f; // dist += .1f; break; case KEY_DOWN: // a.x -= 0.1f; // dist -= .1f; break; case KEY_LEFT: // a.y += 0.1f; break; case KEY_RIGHT: // a.y -= 0.1f; break; case MKEY_Q: // a.z += 0.1f; break; case MKEY_D: // a.z -= 0.1f; break; } 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; float rx = 0.f, ry = 0.f; bool fw_down = false, left_down = false, bw_down = false, right_down = false, zoom_down = false; scene_main(engine_renderer, Matrix4::idty(), [&](Scene& scene) { (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: fw_down = true; break; case SDLK_q: left_down = true; break; case SDLK_s: bw_down = true; break; case SDLK_d: right_down = true; break; case SDLK_LCTRL: zoom_down = true; break; } break; case SDL_KEYUP: switch (e.key.keysym.sym) { case SDLK_z: fw_down = false; break; case SDLK_q: left_down = false; break; case SDLK_s: bw_down = false; break; case SDLK_d: right_down = false; break; case SDLK_LCTRL: zoom_down = false; break; case SDLK_ESCAPE: cont = false; break; } break; case SDL_MOUSEMOTION: rx += -static_cast(e.motion.yrel) * .01f; ry += -static_cast(e.motion.xrel) * .01f; 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); break; } } while (SDL_PollEvent(&e)); } Vector3 movement(0.f, 0.f, 0.f); if (fw_down) movement.z += -1.f; if (left_down) movement.x += -1.f; if (bw_down) movement.z += +1.f; if (right_down) movement.x += +1.f; if (fw_down || left_down || bw_down || right_down) movement.normalize(); scene.camera.transform.loc += movement.rot(Quaternion::rot_y(ry)) * .05f; scene.camera.fov = (zoom_down ? 40.f : 80.f) * PI / 180.f; 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(); } }