diff options
Diffstat (limited to 'src/engine/renderer.cpp')
| -rw-r--r-- | src/engine/renderer.cpp | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/src/engine/renderer.cpp b/src/engine/renderer.cpp new file mode 100644 index 0000000..0be0e53 --- /dev/null +++ b/src/engine/renderer.cpp @@ -0,0 +1,285 @@ +#include "engine/renderer.hpp" +#include "engine/camera.hpp" +#include "stb_image.h" +#include "imgui.h" +#include "imgui_impl_sdl3.h" +#include "imgui_impl_sdlrenderer3.h" + +#include <cstdio> +#include <cstdlib> +#include <cmath> +#include <vector> +#include <algorithm> + +bool Renderer::init(SDL_Window* window, int width, int height) { + width_ = width; + height_ = height; + + sdl_renderer_ = SDL_CreateRenderer(window, NULL); + if (!sdl_renderer_) { + fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError()); + return false; + } + + SDL_SetRenderDrawColor(sdl_renderer_, 0x1a, 0x1a, 0x2e, 0xff); + + const char* name = SDL_GetRendererName(sdl_renderer_); + printf("Renderer: %s %dx%d\n", name ? name : "unknown", width, height); + + // Dear ImGui (dev UI). + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + if (ImGui_ImplSDL3_InitForSDLRenderer(window, sdl_renderer_) && + ImGui_ImplSDLRenderer3_Init(sdl_renderer_)) { + imgui_ready_ = true; + } + return true; +} + +void Renderer::shutdown() { + if (imgui_ready_) { + ImGui_ImplSDLRenderer3_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + imgui_ready_ = false; + } + if (sdl_renderer_) { SDL_DestroyRenderer(sdl_renderer_); sdl_renderer_ = nullptr; } +} + +void Renderer::begin_frame(Camera& camera) { + current_cam_ = &camera; + SDL_SetRenderDrawColor(sdl_renderer_, 0x1a, 0x1a, 0x2e, 0xff); + SDL_RenderClear(sdl_renderer_); + if (imgui_ready_) { + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + } +} + +void Renderer::end_frame() { + if (imgui_ready_) { + ImGui::Render(); + ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), sdl_renderer_); + } + SDL_RenderPresent(sdl_renderer_); + current_cam_ = nullptr; +} + +void Renderer::resize(int width, int height) { + width_ = width; + height_ = height; +} + +void Renderer::draw_box(float cx, float cy, + float half_w, float half_h, + float angle, + float r, float g, float b, float a) { + if (!current_cam_) return; + + // Convert world coords to screen pixels + const auto& cam = *current_cam_; + float zoom = cam.zoom(); + float vp_w = static_cast<float>(cam.viewport_width()); + float vp_h = static_cast<float>(cam.viewport_height()); + + // Screen position of world origin + float ox = vp_w * 0.5f - cam.position().x * zoom; + float oy = vp_h * 0.5f + cam.position().y * zoom; + + // Box center in screen space + float sx = ox + cx * zoom; + float sy = oy - cy * zoom; // y flips + + // Box half-size in screen pixels + float shw = half_w * zoom; + float shh = half_h * zoom; + + SDL_SetRenderDrawColor(sdl_renderer_, + static_cast<uint8_t>(r * 255), + static_cast<uint8_t>(g * 255), + static_cast<uint8_t>(b * 255), + static_cast<uint8_t>(a * 255)); + + if (angle == 0.0f) { + // Fast path: axis-aligned rect + SDL_FRect rect = { + sx - shw, sy - shh, + shw * 2.0f, shh * 2.0f + }; + SDL_RenderFillRect(sdl_renderer_, &rect); + } else { + // Rotated: use SDL_RenderGeometry with 4 vertices + float ca = std::cos(angle); + float sa = std::sin(angle); + + SDL_Vertex verts[4]; + float corners[4][2] = { + {-half_w, -half_h}, { half_w, -half_h}, + { half_w, half_h}, {-half_w, half_h} + }; + for (int i = 0; i < 4; i++) { + float rx = corners[i][0] * ca - corners[i][1] * sa; + float ry = corners[i][0] * sa + corners[i][1] * ca; + verts[i].position.x = ox + (cx + rx) * zoom; + verts[i].position.y = oy - (cy + ry) * zoom; + // SDL_Vertex.color is SDL_FColor (0..1 floats), NOT 0..255. + verts[i].color = {r, g, b, a}; + } + int indices[6] = {0, 1, 2, 0, 2, 3}; + SDL_RenderGeometry(sdl_renderer_, NULL, verts, 4, indices, 6); + } +} + +SDL_Texture* Renderer::load_texture(const std::string& path) { + auto it = tex_cache_.find(path); + if (it != tex_cache_.end()) return it->second; + + // "forts:REL" resolves to the user's own Forts install (bring-your-own game + // files, like a Doom source port + WAD). Nothing is copied into the repo. + std::string file = path; + if (path.rfind("forts:", 0) == 0) { + const char* env = std::getenv("FORTS_DATA"); + std::string root = env ? env + : "/mnt/games/SteamLibrary/steamapps/common/Forts/data"; + file = root + "/" + path.substr(6); + } + + int w = 0, h = 0, n = 0; + unsigned char* px = stbi_load(file.c_str(), &w, &h, &n, 4); + SDL_Texture* tex = nullptr; + if (px) { + tex = SDL_CreateTexture(sdl_renderer_, SDL_PIXELFORMAT_RGBA32, + SDL_TEXTUREACCESS_STATIC, w, h); + if (tex) { + SDL_UpdateTexture(tex, nullptr, px, w * 4); + SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); + SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR); + } + stbi_image_free(px); + printf("Texture: loaded %s (%dx%d)\n", path.c_str(), w, h); + } else { + fprintf(stderr, "Texture: FAILED to load %s\n", path.c_str()); + } + tex_cache_[path] = tex; // cache even nullptr so we don't retry every frame + return tex; +} + +void Renderer::draw_sprite(float cx, float cy, float half_w, float half_h, + float angle, SDL_Texture* tex, + float r, float g, float b, float a) { + if (!current_cam_ || !tex) return; + const auto& cam = *current_cam_; + float zoom = cam.zoom(); + float ox = cam.viewport_width() * 0.5f - cam.position().x * zoom; + float oy = cam.viewport_height() * 0.5f + cam.position().y * zoom; + float ca = std::cos(angle), sa = std::sin(angle); + + const float corners[4][2] = { + {-half_w, -half_h}, { half_w, -half_h}, { half_w, half_h}, {-half_w, half_h} + }; + const float uv[4][2] = { {0,1}, {1,1}, {1,0}, {0,0} }; // flip V (screen y is down) + SDL_FColor col = {r, g, b, a}; + SDL_Vertex v[4]; + for (int i = 0; i < 4; i++) { + float rx = corners[i][0]*ca - corners[i][1]*sa; + float ry = corners[i][0]*sa + corners[i][1]*ca; + v[i].position = { ox + (cx + rx) * zoom, oy - (cy + ry) * zoom }; + v[i].color = col; + v[i].tex_coord = { uv[i][0], uv[i][1] }; + } + int idx[6] = {0, 1, 2, 0, 2, 3}; + SDL_RenderGeometry(sdl_renderer_, tex, v, 4, idx, 6); +} + +void Renderer::draw_arc(float cx, float cy, float radius_world, + float a0, float a1, float r, float g, float b, float a) { + if (!current_cam_) return; + const auto& cam = *current_cam_; + float zoom = cam.zoom(); + float ox = cam.viewport_width() * 0.5f - cam.position().x * zoom; + float oy = cam.viewport_height() * 0.5f + cam.position().y * zoom; + float scx = ox + cx * zoom, scy = oy - cy * zoom; // centre (screen) + + const float PI = 3.14159265358979323846f; + int n = std::max(1, (int)(std::fabs(a1 - a0) / (2.0f * PI) * 64.0f) + 1); + SDL_FColor col = {r, g, b, a}; + fan_scratch_.clear(); + for (int i = 0; i < n; i++) { + float t0 = a0 + (a1 - a0) * (float)i / n; + float t1 = a0 + (a1 - a0) * (float)(i + 1) / n; + // world direction (y up) -> screen (y down) + SDL_Vertex c { {scx, scy}, col, {0, 0} }; + SDL_Vertex p0{ {scx + std::cos(t0) * radius_world * zoom, + scy - std::sin(t0) * radius_world * zoom}, col, {0, 0} }; + SDL_Vertex p1{ {scx + std::cos(t1) * radius_world * zoom, + scy - std::sin(t1) * radius_world * zoom}, col, {0, 0} }; + fan_scratch_.push_back(c); fan_scratch_.push_back(p0); fan_scratch_.push_back(p1); + } + SDL_RenderGeometry(sdl_renderer_, nullptr, fan_scratch_.data(), + (int)fan_scratch_.size(), nullptr, 0); +} + +void Renderer::draw_radial_timer(float cx, float cy, float radius_world, + float frac, + float r, float g, float b, float a) { + if (!current_cam_) return; + if (frac < 0.0f) frac = 0.0f; + if (frac > 1.0f) frac = 1.0f; + + const auto& cam = *current_cam_; + float zoom = cam.zoom(); + float vp_w = static_cast<float>(cam.viewport_width()); + float vp_h = static_cast<float>(cam.viewport_height()); + float ox = vp_w * 0.5f - cam.position().x * zoom; + float oy = vp_h * 0.5f + cam.position().y * zoom; + float sx = ox + cx * zoom; + float sy = oy - cy * zoom; + float rad = radius_world * zoom; + + const int SEG = 32; + const float PI = 3.14159265358979323846f; + + auto fan = [&](float start, float sweep, SDL_FColor col) { + if (sweep <= 0.0f) return; + int n = std::max(1, (int)(SEG * (sweep / (2.0f * PI)) + 0.5f)); + fan_scratch_.clear(); // reused buffer, no per-call alloc + for (int i = 0; i < n; i++) { + float a0 = start + sweep * (float)i / n; + float a1 = start + sweep * (float)(i + 1) / n; + fan_scratch_.push_back({ {sx, sy}, col, {0, 0} }); + fan_scratch_.push_back({ {sx + std::cos(a0) * rad, sy + std::sin(a0) * rad}, col, {0, 0} }); + fan_scratch_.push_back({ {sx + std::cos(a1) * rad, sy + std::sin(a1) * rad}, col, {0, 0} }); + } + SDL_RenderGeometry(sdl_renderer_, NULL, fan_scratch_.data(), + (int)fan_scratch_.size(), NULL, 0); + }; + + // Dim background ring (full circle), then the remaining wedge on top. + // Start at top (-90deg) and sweep clockwise (screen y is down). + fan(-PI * 0.5f, 2.0f * PI, SDL_FColor{0.0f, 0.0f, 0.0f, 0.45f}); + fan(-PI * 0.5f, 2.0f * PI * frac, SDL_FColor{r, g, b, a}); +} + +void Renderer::draw_hud_bar(float x, float y, float fill, + float r, float g, float b) { + const float BAR_W = 100.0f; + const float BAR_H = 14.0f; + const float PAD = 2.0f; + + SDL_FRect bg = {x, y, BAR_W, BAR_H}; + SDL_SetRenderDrawColor(sdl_renderer_, 0x20, 0x20, 0x20, 0xcc); + SDL_RenderFillRect(sdl_renderer_, &bg); + + if (fill > 0.0f) { + if (fill > 1.0f) fill = 1.0f; + SDL_FRect fg = {x + PAD, y + PAD, + (BAR_W - PAD * 2) * fill, BAR_H - PAD * 2}; + SDL_SetRenderDrawColor(sdl_renderer_, + static_cast<uint8_t>(r * 255), + static_cast<uint8_t>(g * 255), + static_cast<uint8_t>(b * 255), 0xcc); + SDL_RenderFillRect(sdl_renderer_, &fg); + } +} |
