#include "CyberRenderer.hpp" #include "hub/HubRoom.hpp" #include "hub/RestRoom.hpp" #include "combat/CombatSystem.hpp" #include "rlgl.h" #include #include #include #include namespace dnr { namespace { // Draw a textured quad in 3D using rlgl void drawTexturedQuad(Texture2D tex, Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3, float uTiles = 1.0f, float vTiles = 1.0f, Color tint = WHITE) { rlDisableBackfaceCulling(); rlSetTexture(tex.id); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); rlTexCoord2f(0.0f, 0.0f); rlVertex3f(v0.x, v0.y, v0.z); rlTexCoord2f(0.0f, vTiles); rlVertex3f(v1.x, v1.y, v1.z); rlTexCoord2f(uTiles, vTiles); rlVertex3f(v2.x, v2.y, v2.z); rlTexCoord2f(uTiles, 0.0f); rlVertex3f(v3.x, v3.y, v3.z); rlEnd(); rlSetTexture(0); rlEnableBackfaceCulling(); } // Draw a textured 3D box using rlgl void drawTexturedBox(Texture2D tex, Vector3 pos, Vector3 size, float uTile = 1.0f, float vTile = 1.0f, Color tint = WHITE) { float x = pos.x - size.x * 0.5f; float y = pos.y - size.y * 0.5f; float z = pos.z - size.z * 0.5f; float w = size.x; float h = size.y; float d = size.z; rlDisableBackfaceCulling(); rlSetTexture(tex.id); rlBegin(RL_QUADS); rlColor4ub(tint.r, tint.g, tint.b, tint.a); // Front face (+Z) rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x, y + h, z + d); rlTexCoord2f(0.0f, vTile); rlVertex3f(x, y, z + d); rlTexCoord2f(uTile, vTile); rlVertex3f(x + w, y, z + d); rlTexCoord2f(uTile, 0.0f); rlVertex3f(x + w, y + h, z + d); // Back face (-Z) rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + w, y + h, z); rlTexCoord2f(0.0f, vTile); rlVertex3f(x + w, y, z); rlTexCoord2f(uTile, vTile); rlVertex3f(x, y, z); rlTexCoord2f(uTile, 0.0f); rlVertex3f(x, y + h, z); // Top face (+Y) rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x, y + h, z); rlTexCoord2f(0.0f, vTile); rlVertex3f(x, y + h, z + d); rlTexCoord2f(uTile, vTile); rlVertex3f(x + w, y + h, z + d); rlTexCoord2f(uTile, 0.0f); rlVertex3f(x + w, y + h, z); // Bottom face (-Y) rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x, y, z + d); rlTexCoord2f(0.0f, vTile); rlVertex3f(x, y, z); rlTexCoord2f(uTile, vTile); rlVertex3f(x + w, y, z); rlTexCoord2f(uTile, 0.0f); rlVertex3f(x + w, y, z + d); // Right face (+X) rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x + w, y + h, z + d); rlTexCoord2f(0.0f, vTile); rlVertex3f(x + w, y, z + d); rlTexCoord2f(uTile, vTile); rlVertex3f(x + w, y, z); rlTexCoord2f(uTile, 0.0f); rlVertex3f(x + w, y + h, z); // Left face (-X) rlTexCoord2f(0.0f, 0.0f); rlVertex3f(x, y + h, z); rlTexCoord2f(0.0f, vTile); rlVertex3f(x, y, z); rlTexCoord2f(uTile, vTile); rlVertex3f(x, y, z + d); rlTexCoord2f(uTile, 0.0f); rlVertex3f(x, y + h, z + d); rlEnd(); rlSetTexture(0); rlEnableBackfaceCulling(); } // Procedural generator: Dark Smoked Walnut Parquet Floor (256x256) Texture2D createParquetTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 36, 28, 24, 255 }); Color* pixels = (Color*)img.data; const int plankHeight = 32; for (int y = 0; y < h; ++y) { int row = y / plankHeight; int rowY = y % plankHeight; bool isHorizontalSeam = (rowY == 0 || rowY == plankHeight - 1); for (int x = 0; x < w; ++x) { int shift = (row % 2 == 0) ? 0 : 48; int plankW = 96; int plankCol = (x + shift) / plankW; int plankX = (x + shift) % plankW; bool isVerticalSeam = (plankX == 0 || plankX == plankW - 1); if (isHorizontalSeam || isVerticalSeam) { pixels[y * w + x] = Color{ 16, 12, 10, 255 }; continue; } int plankSeed = (row * 17 + plankCol * 31); int toneOffset = (plankSeed % 19) - 9; int baseR = 52 + toneOffset; int baseG = 42 + (toneOffset * 3) / 4; int baseB = 36 + toneOffset / 2; float grainSin = std::sin((y * 4.0f + std::sin(x * 0.05f) * 6.0f) * 0.4f); int grainVal = static_cast(grainSin * 6.0f); int fiberNoise = ((x * 13 + y * 29) % 7) - 3; int finalR = std::clamp(baseR + grainVal + fiberNoise, 20, 110); int finalG = std::clamp(baseG + (grainVal * 3) / 4 + fiberNoise, 16, 95); int finalB = std::clamp(baseB + grainVal / 2 + fiberNoise, 12, 85); pixels[y * w + x] = Color{ (unsigned char)finalR, (unsigned char)finalG, (unsigned char)finalB, 255 }; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Luxury Royal Navy Persian/Cyber Rug (256x256) Texture2D createRugTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 22, 32, 54, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { int distEdge = std::min({ x, w - 1 - x, y, h - 1 - y }); int weave = ((x + y) % 2 == 0) ? 6 : -6; Color c = Color{ 24, 35, 58, 255 }; if (distEdge < 4) { c = Color{ 120, 105, 80, 255 }; // Fringes } else if (distEdge >= 6 && distEdge <= 22) { // Wide ornate gold border int pattern = ((x / 6 + y / 6) % 2 == 0) ? 20 : -10; c = Color{ (unsigned char)std::clamp(115 + pattern, 0, 255), (unsigned char)std::clamp(92 + pattern, 0, 255), (unsigned char)std::clamp(48 + pattern, 0, 255), 255 }; } else if (distEdge == 24 || distEdge == 25) { c = Color{ 110, 25, 35, 255 }; // Crimson accent piping } else if (distEdge >= 28 && distEdge <= 38) { if ((x / 4) % 2 == (y / 4) % 2) { c = Color{ 85, 68, 42, 255 }; } else { c = Color{ 22, 32, 54, 255 }; } } else { int cx = w / 2; int cy = h / 2; int dx = std::abs(x - cx); int dy = std::abs(y - cy); int manhattanDist = dx + dy; int eucDist = static_cast(std::sqrt(dx * dx + dy * dy)); if (manhattanDist < 60 && manhattanDist > 54) { c = Color{ 135, 108, 55, 255 }; // Gold diamond outline } else if (manhattanDist <= 54 && manhattanDist >= 40) { c = Color{ 105, 24, 36, 255 }; // Crimson medallion body } else if (eucDist < 25) { if (eucDist < 8) { c = Color{ 155, 135, 78, 255 }; } else if ((dx == dy) || dx == 0 || dy == 0) { c = Color{ 135, 108, 55, 255 }; } else { c = Color{ 16, 24, 40, 255 }; } } else if (manhattanDist < 85 && (dx < 12 || dy < 12)) { c = Color{ 115, 95, 52, 255 }; } } int r = std::clamp(c.r + weave, 0, 255); int g = std::clamp(c.g + weave, 0, 255); int b = std::clamp(c.b + weave, 0, 255); pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Textured Dark Slate Plaster (256x256) Texture2D createWallTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 34, 38, 46, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { float bottomShadow = (y > 220) ? (1.0f - (y - 220) / 36.0f * 0.35f) : 1.0f; float topShadow = (y < 30) ? (0.82f + (y / 30.0f) * 0.18f) : 1.0f; float vLighting = bottomShadow * topShadow; for (int x = 0; x < w; ++x) { int seamMod = x % 64; int seamShade = 0; if (seamMod == 0) seamShade = -14; else if (seamMod == 1) seamShade = +10; int stipple = ((x * 19 + y * 37 + (x ^ y) * 11) % 11) - 5; int baseR = static_cast((36 + stipple + seamShade) * vLighting); int baseG = static_cast((40 + stipple + seamShade) * vLighting); int baseB = static_cast((48 + stipple + seamShade) * vLighting); pixels[y * w + x] = Color{ (unsigned char)std::clamp(baseR, 12, 90), (unsigned char)std::clamp(baseG, 14, 95), (unsigned char)std::clamp(baseB, 18, 110), 255 }; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Cyberpunk Synthwave Neon Wall Art (128x256) Texture2D createPosterTexture() { const int w = 128; const int h = 256; Image img = GenImageColor(w, h, Color{ 15, 10, 24, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { float ny = (float)y / h; for (int x = 0; x < w; ++x) { float nx = (float)x / w; if (x < 4 || x >= w - 4 || y < 4 || y >= h - 4) { pixels[y * w + x] = Color{ 25, 25, 30, 255 }; // Metal frame continue; } Color c; if (ny < 0.65f) { int r = static_cast(20 + ny * 140); int g = static_cast(10 + ny * 20); int b = static_cast(40 + ny * 70); c = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; // Retro Neon Sun float sdx = (float)(x - 64); float sdy = (float)(y - 110); float sDist = std::sqrt(sdx * sdx + sdy * sdy); if (sDist < 38.0f) { bool isCutout = (y > 105 && (y % 6 < 2)); if (!isCutout) { float sunT = (y - 72) / 76.0f; int sr = 255; int sg = static_cast(220 - sunT * 180); int sb = static_cast(40 + sunT * 120); c = Color{ (unsigned char)sr, (unsigned char)sg, (unsigned char)sb, 255 }; } } } else { float gridY = (ny - 0.65f) / 0.35f; c = Color{ 10, 12, 22, 255 }; int lineIndex = static_cast(std::pow(gridY, 0.6f) * 10.0f); int prevIndex = static_cast(std::pow(std::max(0.0f, gridY - 0.015f), 0.6f) * 10.0f); if (lineIndex != prevIndex) { c = Color{ 0, 230, 255, 255 }; } float rayAngle = std::atan2(gridY, nx - 0.5f); int rayId = static_cast((rayAngle + 3.14159f) * 6.0f); int rayIdNext = static_cast((std::atan2(gridY, (nx + 0.03f) - 0.5f) + 3.14159f) * 6.0f); if (rayId != rayIdNext) { c = Color{ 255, 0, 160, 255 }; } } pixels[y * w + x] = c; } } ImageDrawText(&img, "NETRUNNER", 14, 12, 12, Color{ 0, 240, 255, 255 }); ImageDrawText(&img, "2088", 50, 26, 10, Color{ 255, 50, 150, 255 }); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Polished Dark Walnut Desk Wood (256x256) Texture2D createDeskWoodTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 90, 54, 32, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { float grain1 = std::sin((x * 0.4f + std::sin(y * 0.08f) * 4.0f)); float grain2 = std::sin((x * 1.2f)); int gVal = static_cast(grain1 * 12.0f + grain2 * 6.0f); int r = std::clamp(92 + gVal, 30, 200); int g = std::clamp(56 + (gVal * 2) / 3, 20, 150); int b = std::clamp(34 + gVal / 2, 10, 100); int sheen = static_cast(std::sin((x + y * 0.5f) * 0.05f) * 10.0f); if (sheen > 0) { r = std::clamp(r + sheen, 0, 255); g = std::clamp(g + sheen, 0, 255); b = std::clamp(b + sheen, 0, 255); } pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Code Editor / IDE Monitor Screen (512x256) // Procedural generator: Code Editor / IDE Monitor Screen (512x256) Texture2D createMonitorCodeTexture() { const int w = 512; const int h = 256; Image img = GenImageColor(w, h, Color{ 14, 18, 24, 255 }); ImageDrawRectangle(&img, 0, 0, w, 22, Color{ 22, 28, 38, 255 }); ImageDrawRectangle(&img, 10, 7, 7, 7, TacticalColors::Crimson); ImageDrawRectangle(&img, 22, 7, 7, 7, Color{ 120, 130, 145, 255 }); ImageDrawRectangle(&img, 34, 7, 7, 7, TacticalColors::PureWhite); ImageDrawText(&img, "neural_daemon.cpp - [ACTIVE ARASAKA PROTOCOL]", 52, 6, 10, Color{ 210, 220, 235, 255 }); ImageDrawRectangle(&img, 0, 22, 38, h - 38, Color{ 18, 22, 30, 255 }); ImageDrawRectangle(&img, 0, h - 16, w, 16, Color{ 25, 32, 44, 255 }); ImageDrawText(&img, " UTF-8 C++20 NET_LINK: 10 Gbps ICE_SECURITY: ARMED", 8, h - 13, 10, TacticalColors::SilverText); ImageDrawRectangle(&img, w - 45, 22, 45, h - 38, Color{ 18, 22, 30, 255 }); const char* codeLines[] = { "#include ", "namespace netrunner {", " // Primary ICE Breaker protocol", " class NeuralExploit : public IDaemon {", " public:", " bool injectPayload(uint64_t targetNode) {", " CipherBuffer buffer = decryptChamber(6);", " if (!buffer.isValid()) {", " triggerAlarm(ICE_WARNING);", " return false; // lockout", " }", " log(\"JACK IN SEQUENCE CONFIRMED\");", " return m_revolver.spinAndFire();", " }", " private:", " CyberRevolver m_revolver{ 6 };", " };", "} // namespace netrunner", "// Awaiting Neural Sync command..." }; int lineCount = sizeof(codeLines) / sizeof(codeLines[0]); for (int i = 0; i < lineCount; ++i) { int lineY = 28 + i * 11; char numBuf[8]; snprintf(numBuf, sizeof(numBuf), "%02d", i + 1); ImageDrawText(&img, numBuf, 8, lineY, 10, Color{ 85, 95, 115, 255 }); Color syntaxCol = Color{ 210, 218, 230, 255 }; std::string s = codeLines[i]; if (s.rfind("#include", 0) == 0) syntaxCol = TacticalColors::PureWhite; else if (s.find("//") != std::string::npos) syntaxCol = Color{ 110, 120, 135, 255 }; else if (s.find("class") != std::string::npos || s.find("namespace") != std::string::npos) syntaxCol = TacticalColors::SilverText; else if (s.find("bool") != std::string::npos || s.find("uint64_t") != std::string::npos) syntaxCol = Color{ 180, 195, 210, 255 }; else if (s.find("return") != std::string::npos) syntaxCol = TacticalColors::Crimson; else if (s.find("\"") != std::string::npos) syntaxCol = Color{ 230, 230, 235, 255 }; else if (s.find("injectPayload") != std::string::npos || s.find("decryptChamber") != std::string::npos) syntaxCol = TacticalColors::PureWhite; ImageDrawText(&img, codeLines[i], 46, lineY, 10, syntaxCol); ImageDrawRectangle(&img, w - 40, lineY, static_cast(s.length() * 0.8f), 2, syntaxCol); } ImageDrawRectangle(&img, 46 + 220, 28 + 18 * 11, 7, 10, TacticalColors::Crimson); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Cyberspace Telemetry Radar Monitor (256x256) Texture2D createMonitorRadarTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 10, 14, 20, 255 }); ImageDrawRectangle(&img, 0, 0, w, 20, Color{ 18, 24, 34, 255 }); ImageDrawText(&img, "TACTICAL RADAR // SECTOR SCAN", 8, 5, 10, TacticalColors::PureWhite); int cx = w / 2; int cy = h / 2 + 10; ImageDrawRectangleLines(&img, Rectangle{ (float)(cx - 30), (float)(cy - 30), 60.0f, 60.0f }, 1, Color{ 60, 75, 95, 180 }); ImageDrawRectangleLines(&img, Rectangle{ (float)(cx - 60), (float)(cy - 60), 120.0f, 120.0f }, 1, Color{ 60, 75, 95, 140 }); ImageDrawRectangleLines(&img, Rectangle{ (float)(cx - 90), (float)(cy - 90), 180.0f, 180.0f }, 1, Color{ 60, 75, 95, 100 }); ImageDrawLine(&img, cx - 100, cy, cx + 100, cy, Color{ 80, 95, 115, 140 }); ImageDrawLine(&img, cx, cy - 100, cx, cy + 100, Color{ 80, 95, 115, 140 }); ImageDrawLine(&img, cx, cy, cx + 65, cy - 65, TacticalColors::Crimson); Vector2 nodes[] = { { 70.0f, 90.0f }, { 180.0f, 80.0f }, { 160.0f, 190.0f }, { 90.0f, 180.0f }, { 128.0f, 138.0f } }; for (int i = 0; i < 4; ++i) { ImageDrawLine(&img, (int)nodes[i].x, (int)nodes[i].y, (int)nodes[4].x, (int)nodes[4].y, Color{ 80, 95, 115, 120 }); ImageDrawRectangle(&img, (int)nodes[i].x - 2, (int)nodes[i].y - 2, 5, 5, TacticalColors::Crimson); } ImageDrawRectangle(&img, (int)nodes[4].x - 3, (int)nodes[4].y - 3, 7, 7, TacticalColors::PureWhite); ImageDrawText(&img, "HOSTILE THREATS: DETECTED", 10, h - 26, 10, TacticalColors::Crimson); ImageDrawText(&img, "COMBAT READY: ONLINE", 10, h - 14, 10, TacticalColors::SilverText); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Diamond-Quilted Bedspread (256x256) Texture2D createBedQuiltTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 26, 32, 46, 255 }); Color* pixels = (Color*)img.data; const int gridSize = 32; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { int d1 = (x + y) % gridSize; int d2 = (x - y + 1000 * gridSize) % gridSize; bool isSeam = (d1 <= 1 || d2 <= 1); int distToSeam = std::min({ d1, gridSize - d1, d2, gridSize - d2 }); float puff = std::clamp(distToSeam / (gridSize * 0.5f), 0.0f, 1.0f); int puffShade = static_cast(puff * 16.0f); int weave = ((x ^ y) % 3) * 2 - 2; int r = 26 + puffShade + weave; int g = 32 + puffShade + weave; int b = 48 + puffShade + weave; if (isSeam) { bool isStitch = ((x / 4 + y / 4) % 2 == 0); if (isStitch) { r = 65; g = 80; b = 115; } else { r = 16; g = 20; b = 30; } } pixels[y * w + x] = Color{ (unsigned char)std::clamp(r, 0, 255), (unsigned char)std::clamp(g, 0, 255), (unsigned char)std::clamp(b, 0, 255), 255 }; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Megacity Night Skyline Window Backdrop (512x256) Texture2D createCityNightTexture() { const int w = 512; const int h = 256; Image img = GenImageColor(w, h, Color{ 8, 12, 24, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { float t = (float)y / h; int r = static_cast(6 + t * 24); int g = static_cast(10 + t * 16); int b = static_cast(20 + t * 35); for (int x = 0; x < w; ++x) { pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } } // Stars for (int i = 0; i < 70; ++i) { int sx = (i * 73 + 17) % w; int sy = (i * 37 + 5) % (h / 2); int brightness = 140 + (i * 29) % 115; ImageDrawPixel(&img, sx, sy, Color{ (unsigned char)brightness, (unsigned char)brightness, 255, 255 }); } // Distant dark silhouettes for (int b = 0; b < 24; ++b) { int bx = b * 22; int bw = 18 + (b * 7) % 15; int bh = 70 + (b * 31) % 80; int by = h - bh; ImageDrawRectangle(&img, bx, by, bw, bh, Color{ 14, 18, 32, 255 }); } // Foreground lit skyscrapers for (int b = 0; b < 16; ++b) { int bx = b * 32 + (b % 3) * 4; int bw = 24 + (b * 11) % 18; int bh = 110 + (b * 47) % 115; int by = h - bh; Color bldgCol = Color{ 20, 26, 42, 255 }; ImageDrawRectangle(&img, bx, by, bw, bh, bldgCol); if (bh > 160) { ImageDrawLine(&img, bx + bw / 2, by - 25, bx + bw / 2, by, Color{ 60, 70, 90, 255 }); ImageDrawPixel(&img, bx + bw / 2, by - 26, Color{ 255, 40, 40, 255 }); } for (int wy = by + 12; wy < h - 8; wy += 8) { for (int wx = bx + 4; wx < bx + bw - 4; wx += 5) { int litSeed = (wx * 17 + wy * 31 + b * 53); if (litSeed % 5 == 0) { Color winCol = Color{ 255, 230, 130, 255 }; if (litSeed % 3 == 0) winCol = Color{ 140, 230, 255, 255 }; else if (litSeed % 7 == 0) winCol = Color{ 255, 120, 160, 255 }; ImageDrawRectangle(&img, wx, wy, 3, 4, winCol); } } } } // Traffic streaks ImageDrawLine(&img, 0, h - 35, w, h - 25, Color{ 255, 140, 0, 120 }); ImageDrawLine(&img, 0, h - 33, w, h - 23, Color{ 0, 230, 255, 100 }); ImageDrawLine(&img, 0, h - 18, w, h - 12, Color{ 255, 50, 80, 140 }); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Brushed Steel & Hazard Stripe Cyber Gateway (256x256) Texture2D createCyberGateTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 28, 35, 48, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { int metalNoise = ((y * 11 + (x ^ y) * 7) % 9) - 4; Color c = Color{ (unsigned char)std::clamp(32 + metalNoise, 0, 255), (unsigned char)std::clamp(38 + metalNoise, 0, 255), (unsigned char)std::clamp(50 + metalNoise, 0, 255), 255 }; int borderDist = std::min({ x, w - 1 - x, y, h - 1 - y }); if (borderDist < 20) { bool isYellow = (((x + y) / 14) % 2 == 0); if (isYellow) { c = Color{ 240, 195, 25, 255 }; } else { c = Color{ 22, 22, 26, 255 }; } } else if (borderDist >= 20 && borderDist <= 23) { c = Color{ 12, 16, 24, 255 }; } else if (borderDist == 26) { if ((x % 24 == 12 && (y <= 28 || y >= h - 28)) || (y % 24 == 12 && (x <= 28 || x >= w - 28))) { c = Color{ 190, 205, 225, 255 }; } } pixels[y * w + x] = c; } } ImageDrawRectangle(&img, 32, h / 2 - 12, w - 64, 24, TacticalColors::DarkPanel); ImageDrawRectangleLines(&img, Rectangle{ 32.0f, h / 2.0f - 12.0f, (float)(w - 64), 24.0f }, 2, TacticalColors::BorderWhite); ImageDrawText(&img, "TACTICAL BUNKER ACCESS", 46, h / 2 - 8, 12, TacticalColors::PureWhite); ImageDrawText(&img, "SECTOR ARCHIVE // LEVEL 1+", 54, 40, 10, TacticalColors::SilverText); ImageDrawText(&img, "RESTRICTED PERSONNEL ONLY", 50, h - 50, 10, TacticalColors::Crimson); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: High-Tech Gunmetal Dungeon Wall (256x256) // Procedural generator: High-Tech Gunmetal Dungeon Wall (256x256) Texture2D createDungeonWallTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 18, 22, 28, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { int n = ((x * 17 + y * 31) % 15) - 7; int r = std::clamp(20 + n, 0, 255); int g = std::clamp(24 + n, 0, 255); int b = std::clamp(30 + n, 0, 255); Color c = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; // Heavy industrial plate bevel seams (0 rounding) if (x < 3 || x >= w - 3 || y < 3 || y >= h - 3 || y == 84 || y == 85 || y == 170 || y == 171 || (x == 128 && (y < 84 || y > 170))) { c = Color{ 8, 10, 14, 255 }; } // Hex bolts in corners for (int bx : { 12, w - 12 }) { for (int by : { 12, 94, 180, h - 12 }) { if (std::abs(x - bx) <= 2 && std::abs(y - by) <= 2) { c = Color{ 95, 105, 120, 255 }; } } } // Dark recessed vertical conduit slot with subtle red status pip if (x >= 126 && x <= 130) { c = Color{ 10, 12, 16, 255 }; if (y >= 124 && y <= 132) { c = TacticalColors::Crimson; } } // Red & dark gray tactical hazard stencil at bottom if (y >= h - 18 && y < h - 4) { bool isStripe = (((x + y) / 10) % 2 == 0); c = isStripe ? TacticalColors::DarkRed : Color{ 12, 14, 18, 255 }; } pixels[y * w + x] = c; } } // High-contrast stencil warning markings ImageDrawRectangle(&img, 36, 102, 184, 24, Color{ 12, 14, 18, 240 }); ImageDrawRectangleLines(&img, Rectangle{ 36.0f, 102.0f, 184.0f, 24.0f }, 1, TacticalColors::DarkRed); ImageDrawText(&img, "[SEC-04 // RESTRICTED]", 46, 108, 10, TacticalColors::PureWhite); ImageDrawText(&img, "HIGH VOLTAGE", 52, 138, 10, TacticalColors::Crimson); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Industrial Tech Floor Grating (256x256) Texture2D createDungeonFloorTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 14, 17, 22, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { // Anti-slip diamond plate ridges bool isDiamondRidge = (((x + y) % 16 <= 1) || ((x - y + 512) % 16 <= 1)); bool isDrainSlot = (((x + y) % 16 >= 7 && (x + y) % 16 <= 9) && ((x - y + 512) % 16 >= 7 && (x - y + 512) % 16 <= 9)); Color c = Color{ 22, 27, 34, 255 }; if (isDiamondRidge) { c = Color{ 48, 56, 70, 255 }; } else if (isDrainSlot) { c = Color{ 6, 8, 12, 255 }; // Deep sub-floor darkness } // Heavy steel framing plate along edges if (x < 5 || x >= w - 5 || y < 5 || y >= h - 5) { c = Color{ 32, 38, 48, 255 }; if ((x % 32 < 4) && (y % 32 < 4)) { c = Color{ 110, 125, 145, 255 }; // Rivets } } pixels[y * w + x] = c; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Industrial Grid Ceiling with Dim Red Emergency Fixture (256x256) Texture2D createDungeonCeilTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 10, 12, 16, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { Color c = Color{ 12, 15, 20, 255 }; // Structural I-beam ribs if (x % 64 < 8 || y % 64 < 8) { c = Color{ 26, 32, 42, 255 }; } // Central cable tray with dim emergency red light conduit if (x >= 118 && x <= 138 && y >= 24 && y <= 232) { int distFromCenter = std::abs(x - 128); if (distFromCenter <= 3) { c = TacticalColors::Crimson; } else if (distFromCenter <= 6) { c = TacticalColors::DarkRed; } else { c = Color{ 18, 22, 28, 255 }; } } pixels[y * w + x] = c; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Armored Bulkhead Sliding Door (256x256) Texture2D createBulkheadDoorTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 20, 24, 32, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { Color c = Color{ 24, 30, 40, 255 }; if (x < 5 || x >= w - 5 || y < 5 || y >= h - 5 || std::abs(x - 128) <= 2) { c = Color{ 8, 10, 14, 255 }; } // Diagonal crimson/dark hazard stripes if ((y >= 8 && y <= 30) || (y >= h - 32 && y <= h - 10)) { bool isRed = (((x + y) / 14) % 2 == 0); c = isRed ? TacticalColors::Crimson : Color{ 12, 14, 18, 255 }; } if (x == 48 || x == 49 || x == 207 || x == 208) { c = Color{ 55, 68, 85, 255 }; } // Center hydraulic lock housing if (x >= 88 && x <= 168 && y >= 96 && y <= 160) { int dx = std::abs(x - 128); int dy = std::abs(y - 128); if (dx <= 32 && dy <= 24) { c = Color{ 10, 12, 16, 255 }; if (dx <= 18 && dy <= 10) { c = TacticalColors::Crimson; } } } pixels[y * w + x] = c; } } ImageDrawText(&img, "[RESTRICTED // BLAST BULKHEAD]", 44, 42, 10, TacticalColors::PureWhite); Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Structural Door Frame (128x256) Texture2D createDoorFrameTexture() { const int w = 128; const int h = 256; Image img = GenImageColor(w, h, Color{ 24, 30, 40, 255 }); Color* pixels = (Color*)img.data; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { Color c = Color{ 22, 28, 38, 255 }; if (x < 5 || x >= w - 5) { c = Color{ 10, 12, 16, 255 }; } if (x >= 38 && x <= 90) { c = Color{ 45, 55, 72, 255 }; if (x >= 54 && x <= 74) { c = Color{ 120, 135, 155, 255 }; } } if (x >= w - 16 && x <= w - 5) { bool isRed = (((x + y) / 10) % 2 == 0); c = isRed ? TacticalColors::Crimson : Color{ 12, 14, 18, 255 }; } pixels[y * w + x] = c; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Ancient Palace Ashlar Sandstone Wall with Greek Key Meander (256x256) Texture2D createAncientStoneWallTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 185, 170, 142, 255 }); Color* pixels = (Color*)img.data; const int rowH = 64; for (int y = 0; y < h; ++y) { int row = y / rowH; int rowY = y % rowH; bool isHorizMortar = (rowY <= 2 || rowY >= rowH - 2); for (int x = 0; x < w; ++x) { int shift = (row % 2 == 0) ? 0 : 128; int blockCol = (x + shift) / 128; int blockX = (x + shift) % 128; bool isVertMortar = (blockX <= 2 || blockX >= 126); if (isHorizMortar || isVertMortar) { pixels[y * w + x] = Color{ 88, 76, 62, 255 }; continue; } int blockSeed = (row * 23 + blockCol * 41); int blockTone = (blockSeed % 25) - 12; int chiselNoise = ((x * 17 + y * 31 + (x ^ y) * 7) % 17) - 8; int r = std::clamp(188 + blockTone + chiselNoise, 0, 255); int g = std::clamp(172 + blockTone + chiselNoise, 0, 255); int b = std::clamp(144 + blockTone + chiselNoise, 0, 255); if (row == 0 && rowY >= 14 && rowY <= 48) { int fretX = x % 32; bool isFret = (rowY >= 18 && rowY <= 22) || (rowY >= 40 && rowY <= 44) || (fretX >= 14 && fretX <= 18 && rowY >= 22 && rowY <= 40); if (isFret) { r = std::clamp(r - 45, 0, 255); g = std::clamp(g - 50, 0, 255); b = std::clamp(b - 55, 0, 255); } } pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Ancient Roman Opus Sectile Floor (Terracotta & Marble Flagstones) (256x256) Texture2D createAncientFloorTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 175, 100, 65, 255 }); Color* pixels = (Color*)img.data; const int tileSize = 32; for (int y = 0; y < h; ++y) { int tY = y / tileSize; int py = y % tileSize; bool isHorizGrout = (py <= 1 || py >= tileSize - 1); for (int x = 0; x < w; ++x) { int tX = x / tileSize; int px = x % tileSize; bool isVertGrout = (px <= 1 || px >= tileSize - 1); if (isHorizGrout || isVertGrout) { pixels[y * w + x] = Color{ 58, 48, 40, 255 }; continue; } bool isMarble = ((tX + tY) % 2 == 0); int stoneNoise = ((x * 19 + y * 23) % 11) - 5; if (isMarble) { int vein = ((x + y * 2) % 31 == 0 || (x * 2 - y + 500) % 37 == 0) ? -28 : 0; int r = std::clamp(224 + stoneNoise + vein, 0, 255); int g = std::clamp(218 + stoneNoise + vein, 0, 255); int b = std::clamp(205 + stoneNoise + vein, 0, 255); pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } else { int r = std::clamp(178 + stoneNoise, 0, 255); int g = std::clamp(96 + stoneNoise / 2, 0, 255); int b = std::clamp(62 + stoneNoise / 3, 0, 255); pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Ancient Timber Ceiling Beams (256x256) Texture2D createAncientCeilTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 70, 42, 22, 255 }); Color* pixels = (Color*)img.data; const int beamSpan = 64; for (int y = 0; y < h; ++y) { int bY = y % beamSpan; bool isBeam = (bY >= 14 && bY <= 50); for (int x = 0; x < w; ++x) { if (isBeam) { int grain = static_cast(std::sin((x * 0.08f + std::sin(y * 0.3f)) * 4.0f) * 12.0f); int fiber = ((x * 13 + y * 29) % 7) - 3; int r = std::clamp(76 + grain + fiber, 0, 255); int g = std::clamp(46 + grain / 2 + fiber, 0, 255); int b = std::clamp(26 + grain / 3 + fiber, 0, 255); if ((x % 48 >= 22 && x % 48 <= 26) && (bY >= 30 && bY <= 34)) { r = 35; g = 35; b = 40; } pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } else { int stipple = ((x * 19 + y * 23) % 15) - 7; int r = std::clamp(142 + stipple, 0, 255); int g = std::clamp(132 + stipple, 0, 255); int b = std::clamp(116 + stipple, 0, 255); pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // Procedural generator: Weathered Ancient Storage Wood & Chest Planks (256x256) Texture2D createAncientWoodTexture() { const int w = 256; const int h = 256; Image img = GenImageColor(w, h, Color{ 105, 68, 38, 255 }); Color* pixels = (Color*)img.data; const int plankW = 42; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { int px = x % plankW; bool isSeam = (px == 0 || px == plankW - 1); if (isSeam) { pixels[y * w + x] = Color{ 42, 26, 14, 255 }; continue; } int grain = static_cast(std::sin((y * 0.12f + std::sin(x * 0.05f)) * 5.0f) * 14.0f); int noise = ((x * 17 + y * 29) % 9) - 4; int r = std::clamp(115 + grain + noise, 0, 255); int g = std::clamp(75 + (grain * 2) / 3 + noise, 0, 255); int b = std::clamp(45 + grain / 2 + noise, 0, 255); pixels[y * w + x] = Color{ (unsigned char)r, (unsigned char)g, (unsigned char)b, 255 }; } } Texture2D tex = LoadTextureFromImage(img); SetTextureFilter(tex, TEXTURE_FILTER_BILINEAR); UnloadImage(img); return tex; } // 3D Ancient Prop Helpers void drawAncientAmphora(Vector3 pos, float height, Color clayColor) { DrawCylinder({ pos.x, pos.y + height * 0.05f, pos.z }, height * 0.14f, height * 0.18f, height * 0.10f, 12, clayColor); DrawCylinder({ pos.x, pos.y + height * 0.40f, pos.z }, height * 0.18f, height * 0.38f, height * 0.60f, 12, clayColor); DrawCylinder({ pos.x, pos.y + height * 0.72f, pos.z }, height * 0.38f, height * 0.16f, height * 0.20f, 12, clayColor); DrawCylinder({ pos.x, pos.y + height * 0.88f, pos.z }, height * 0.16f, height * 0.18f, height * 0.20f, 12, clayColor); DrawCylinderWires({ pos.x, pos.y + height, pos.z }, height * 0.20f, height * 0.20f, 0.02f, 12, clayColor); // Twin loop handles DrawCube({ pos.x - height * 0.26f, pos.y + height * 0.62f, pos.z }, height * 0.06f, height * 0.28f, height * 0.06f, clayColor); DrawCube({ pos.x + height * 0.26f, pos.y + height * 0.62f, pos.z }, height * 0.06f, height * 0.28f, height * 0.06f, clayColor); } void drawAncientCrate(Texture2D woodTex, Vector3 pos, Vector3 size) { drawTexturedBox(woodTex, pos, size, 1.0f, 1.0f); DrawCubeWires(pos, size.x + 0.015f, size.y + 0.015f, size.z + 0.015f, Color{ 145, 105, 55, 255 }); } void drawMarbleColumn(Vector3 pos, float height, float radius) { DrawCube({ pos.x, pos.y + 0.12f, pos.z }, radius * 2.4f, 0.24f, radius * 2.4f, Color{ 215, 210, 200, 255 }); DrawCylinder({ pos.x, pos.y + height * 0.5f, pos.z }, radius, radius * 0.95f, height - 0.48f, 16, Color{ 232, 226, 216, 255 }); DrawCylinderWires({ pos.x, pos.y + height * 0.5f, pos.z }, radius + 0.01f, radius * 0.95f + 0.01f, height - 0.48f, 16, Color{ 185, 180, 170, 255 }); DrawCube({ pos.x, pos.y + height - 0.12f, pos.z }, radius * 2.4f, 0.24f, radius * 2.4f, Color{ 215, 210, 200, 255 }); } void drawMarbleNectarFountain(Vector3 pos, bool recharged, float animTime) { // Stepped octagonal marble dais DrawCylinder({ pos.x, pos.y + 0.08f, pos.z }, 1.6f, 1.7f, 0.16f, 16, Color{ 210, 205, 195, 255 }); DrawCylinder({ pos.x, pos.y + 0.24f, pos.z }, 1.3f, 1.4f, 0.16f, 16, Color{ 225, 220, 210, 255 }); // Basin pedestal and upper marble bowl DrawCylinder({ pos.x, pos.y + 0.65f, pos.z }, 1.0f, 0.45f, 0.65f, 16, Color{ 240, 235, 225, 255 }); DrawCylinderWires({ pos.x, pos.y + 0.65f, pos.z }, 1.02f, 0.47f, 0.66f, 16, Color{ 185, 155, 95, 255 }); // Shimmering restorative nectar pool float pulse = 0.5f + 0.5f * std::sin(animTime * 3.5f); Color nectarCol = recharged ? Color{ 80, 230, 160, 235 } : Color{ 255, 195, static_cast(60 + pulse * 45), 235 }; DrawCylinder({ pos.x, pos.y + 0.88f, pos.z }, 0.88f, 0.88f, 0.12f, 16, nectarCol); // Spouting urn in center DrawCylinder({ pos.x, pos.y + 1.25f, pos.z }, 0.22f, 0.14f, 0.55f, 12, Color{ 185, 110, 70, 255 }); DrawCylinderWires({ pos.x, pos.y + 1.05f + pulse * 0.12f, pos.z }, 0.55f, 0.55f, 0.02f, 16, nectarCol); DrawSphere({ pos.x, pos.y + 1.55f, pos.z }, 0.12f + pulse * 0.04f, WHITE); } void drawScribeStele(Vector3 pos) { // Stele base plinth DrawCube({ pos.x, pos.y + 0.15f, pos.z }, 0.90f, 0.30f, 0.70f, Color{ 195, 185, 170, 255 }); // Inscribed marble monolith DrawCube({ pos.x, pos.y + 1.15f, pos.z }, 0.60f, 1.70f, 0.30f, Color{ 228, 222, 210, 255 }); DrawCubeWires({ pos.x, pos.y + 1.15f, pos.z }, 0.62f, 1.72f, 0.32f, Color{ 170, 135, 75, 255 }); // Engraved glowing inscription lines on East face (facing room center) for (float ly = 0.55f; ly <= 1.75f; ly += 0.22f) { DrawLine3D({ pos.x + 0.31f, pos.y + ly, pos.z - 0.22f }, { pos.x + 0.31f, pos.y + ly, pos.z + 0.22f }, Color{ 240, 195, 60, 220 }); } // Bronze reading lectern DrawCube({ pos.x + 0.26f, pos.y + 0.88f, pos.z }, 0.26f, 0.06f, 0.55f, Color{ 145, 105, 45, 255 }); // Wax tablet ledger DrawCube({ pos.x + 0.28f, pos.y + 0.93f, pos.z }, 0.18f, 0.02f, 0.38f, Color{ 210, 165, 95, 255 }); } void drawCraftsmanWorkbench(Texture2D woodTex, Vector3 pos) { // Heavy timber workbench top drawTexturedBox(woodTex, { pos.x, pos.y + 0.75f, pos.z }, { 1.0f, 0.14f, 2.6f }); // 4 Heavy timber legs DrawCube({ pos.x - 0.35f, 0.37f, pos.z - 1.05f }, 0.14f, 0.74f, 0.14f, Color{ 65, 42, 22, 255 }); DrawCube({ pos.x + 0.35f, 0.37f, pos.z - 1.05f }, 0.14f, 0.74f, 0.14f, Color{ 65, 42, 22, 255 }); DrawCube({ pos.x - 0.35f, 0.37f, pos.z + 1.05f }, 0.14f, 0.74f, 0.14f, Color{ 65, 42, 22, 255 }); DrawCube({ pos.x + 0.35f, 0.37f, pos.z + 1.05f }, 0.14f, 0.74f, 0.14f, Color{ 65, 42, 22, 255 }); // Bronze gunsmith anvil DrawCube({ pos.x - 0.15f, 0.92f, pos.z - 0.60f }, 0.32f, 0.20f, 0.48f, Color{ 135, 95, 45, 255 }); DrawCylinder({ pos.x - 0.15f, 0.96f, pos.z - 0.90f }, 0.02f, 0.12f, 0.24f, 8, Color{ 165, 120, 60, 255 }); // Cartridge vise and bullet mold DrawCube({ pos.x - 0.12f, 0.88f, pos.z + 0.20f }, 0.22f, 0.12f, 0.36f, Color{ 65, 70, 80, 255 }); DrawCube({ pos.x - 0.14f, 0.96f, pos.z + 0.20f }, 0.04f, 0.08f, 0.20f, Color{ 180, 140, 60, 255 }); // Terracotta crucible with glowing bronze / embers DrawCylinder({ pos.x - 0.08f, 0.88f, pos.z + 0.75f }, 0.16f, 0.12f, 0.16f, 10, Color{ 180, 85, 45, 255 }); DrawSphere({ pos.x - 0.08f, 0.94f, pos.z + 0.75f }, 0.11f, Color{ 255, 160, 45, 255 }); // Ancient clay oil lamp with flame DrawCylinder({ pos.x - 0.10f, 0.86f, pos.z - 0.05f }, 0.12f, 0.08f, 0.08f, 8, Color{ 190, 100, 55, 255 }); DrawSphere({ pos.x - 0.10f, 0.93f, pos.z - 0.05f }, 0.05f, Color{ 255, 225, 110, 255 }); } } // anonymous namespace CyberRenderer::CyberRenderer() = default; CyberRenderer::~CyberRenderer() { cleanup(); } void CyberRenderer::init() { m_camera.position = { 0.0f, 0.8f, 0.0f }; m_camera.target = { 0.0f, 0.8f, -1.0f }; m_camera.up = { 0.0f, 1.0f, 0.0f }; m_camera.fovy = 70.0f; m_camera.projection = CAMERA_PERSPECTIVE; // 1. Offscreen Render Target for 3D Post-Processing m_renderTarget = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); SetTextureFilter(m_renderTarget.texture, TEXTURE_FILTER_BILINEAR); // 2. Post-Processing GLSL Shader std::vector> shaderPaths = { { "assets/shaders/postprocess.vs", "assets/shaders/postprocess.fs" }, { "../assets/shaders/postprocess.vs", "../assets/shaders/postprocess.fs" } }; for (const auto& sp : shaderPaths) { if (FileExists(sp.first.c_str()) && FileExists(sp.second.c_str())) { m_shaderPostProcess = LoadShader(sp.first.c_str(), sp.second.c_str()); if (m_shaderPostProcess.id != 0) { m_locTime = GetShaderLocation(m_shaderPostProcess, "u_time"); m_locGlitch = GetShaderLocation(m_shaderPostProcess, "u_glitchIntensity"); m_locRes = GetShaderLocation(m_shaderPostProcess, "u_resolution"); m_postProcessLoaded = true; break; } } } generateTextures(); } void CyberRenderer::cleanup() { if (m_postProcessLoaded) { UnloadShader(m_shaderPostProcess); UnloadRenderTexture(m_renderTarget); m_postProcessLoaded = false; } unloadTextures(); } void CyberRenderer::generateTextures() { if (m_texturesLoaded) return; m_texDungeonWall = createDungeonWallTexture(); m_texDungeonFloor = createDungeonFloorTexture(); m_texDungeonCeil = createDungeonCeilTexture(); m_texDungeonDoor = createBulkheadDoorTexture(); m_texDungeonDoorFrame = createDoorFrameTexture(); m_texParquet = createParquetTexture(); m_texRug = createRugTexture(); m_texPlasterWall = createWallTexture(); m_texPoster = createPosterTexture(); m_texDeskWood = createDeskWoodTexture(); m_texMonitorCode = createMonitorCodeTexture(); m_texMonitorRadar = createMonitorRadarTexture(); m_texBedQuilt = createBedQuiltTexture(); m_texCityNight = createCityNightTexture(); m_texCyberGate = createCyberGateTexture(); // Load Room_window.jpg picture for window backdrop std::vector winPaths = { "assets/Pictures/Room_window.jpg", "../assets/Pictures/Room_window.jpg", "Pictures/Room_window.jpg", "assets/Room_window.jpg" }; bool loadedWin = false; for (const auto& wp : winPaths) { if (FileExists(wp.c_str())) { m_texWindowView = LoadTexture(wp.c_str()); SetTextureFilter(m_texWindowView, TEXTURE_FILTER_BILINEAR); loadedWin = true; break; } } if (!loadedWin) { m_texWindowView = m_texCityNight; } m_texAncientWall = createAncientStoneWallTexture(); m_texAncientFloor = createAncientFloorTexture(); m_texAncientCeil = createAncientCeilTexture(); m_texAncientWood = createAncientWoodTexture(); m_texturesLoaded = true; } void CyberRenderer::unloadTextures() { if (!m_texturesLoaded) return; UnloadTexture(m_texDungeonWall); UnloadTexture(m_texDungeonFloor); UnloadTexture(m_texDungeonCeil); UnloadTexture(m_texDungeonDoor); UnloadTexture(m_texDungeonDoorFrame); UnloadTexture(m_texParquet); UnloadTexture(m_texRug); UnloadTexture(m_texPlasterWall); UnloadTexture(m_texPoster); UnloadTexture(m_texDeskWood); UnloadTexture(m_texMonitorCode); UnloadTexture(m_texMonitorRadar); UnloadTexture(m_texBedQuilt); UnloadTexture(m_texCityNight); if (m_texWindowView.id != m_texCityNight.id && m_texWindowView.id != 0) { UnloadTexture(m_texWindowView); } UnloadTexture(m_texCyberGate); UnloadTexture(m_texAncientWall); UnloadTexture(m_texAncientFloor); UnloadTexture(m_texAncientCeil); UnloadTexture(m_texAncientWood); m_texturesLoaded = false; } void CyberRenderer::beginPostProcess() { if (m_postProcessLoaded) { BeginTextureMode(m_renderTarget); ClearBackground(Color{ 4, 6, 12, 255 }); } } void CyberRenderer::endPostProcess(float animTime) { if (m_postProcessLoaded) { EndTextureMode(); float dt = GetFrameTime(); m_glitchIntensity = std::max(0.0f, m_glitchIntensity - dt * 2.5f); SetShaderValue(m_shaderPostProcess, m_locTime, &animTime, SHADER_UNIFORM_FLOAT); SetShaderValue(m_shaderPostProcess, m_locGlitch, &m_glitchIntensity, SHADER_UNIFORM_FLOAT); float res[2] = { (float)GetScreenWidth(), (float)GetScreenHeight() }; SetShaderValue(m_shaderPostProcess, m_locRes, res, SHADER_UNIFORM_VEC2); BeginShaderMode(m_shaderPostProcess); DrawTextureRec(m_renderTarget.texture, Rectangle{ 0.0f, 0.0f, (float)m_renderTarget.texture.width, -(float)m_renderTarget.texture.height }, Vector2{ 0.0f, 0.0f }, WHITE); EndShaderMode(); } } void CyberRenderer::triggerGlitch(float intensity) { m_glitchIntensity = std::max(m_glitchIntensity, intensity); } void CyberRenderer::renderCyberDrone(Vector3 pos, Color coreColor, float scale, float animTime, bool isHit, bool isAttacking, bool isElite) { float bob = std::sin(animTime * 3.5f) * 0.12f; Vector3 basePos = { pos.x, pos.y + bob, pos.z }; if (isHit) { float shakeX = ((std::rand() % 100) / 100.0f - 0.5f) * 0.18f; float shakeY = ((std::rand() % 100) / 100.0f - 0.5f) * 0.18f; basePos.x += shakeX; basePos.y += shakeY; } // Elite Tactical Armor Shrouds and Orbiting Blades (0 rounding) if (isElite) { float haloY = basePos.y + 0.48f * scale + 0.04f * std::sin(animTime * 4.0f); for (int k = 0; k < 4; ++k) { float spAngle = animTime * 2.5f + k * (3.14159265f * 0.5f); Vector3 spPos = { basePos.x + std::cos(spAngle) * 0.48f * scale, haloY, basePos.z + std::sin(spAngle) * 0.48f * scale }; DrawCube(spPos, 0.08f * scale, 0.18f * scale, 0.08f * scale, Color{ 30, 36, 48, 255 }); DrawCubeWires(spPos, 0.09f * scale, 0.19f * scale, 0.09f * scale, TacticalColors::Crimson); } } // 1. Armored Faceted Chassis DrawSphere(basePos, 0.42f * scale, Color{ 22, 28, 36, 255 }); DrawSphereWires(basePos, 0.44f * scale, 8, 8, Color{ 55, 65, 80, 255 }); // 2. Tactical Sensor Core / Reactor float pulse = 0.5f + 0.5f * std::sin(animTime * (isAttacking ? 14.0f : 5.0f)); Color pulseCol = isHit ? WHITE : (isAttacking ? TacticalColors::BrightRed : coreColor); DrawSphere(basePos, 0.28f * scale, pulseCol); // 3. Forward Sensory Laser Visor Vector3 visorPos = { basePos.x, basePos.y + 0.05f * scale, basePos.z - 0.38f * scale }; DrawCube(visorPos, 0.38f * scale, 0.12f * scale, 0.12f * scale, Color{ 14, 18, 24, 255 }); DrawCube(visorPos, 0.34f * scale, 0.08f * scale, 0.14f * scale, isAttacking ? TacticalColors::BrightRed : coreColor); DrawCubeWires(visorPos, 0.36f * scale, 0.10f * scale, 0.14f * scale, TacticalColors::PureWhite); // 4. Orbiting Deflection Shields (4 plates rotating around core) float rot = animTime * 1.6f; for (int i = 0; i < 4; ++i) { float angle = rot + i * (3.14159265f * 0.5f); float dist = 0.68f * scale; Vector3 shieldPos = { basePos.x + std::cos(angle) * dist, basePos.y + std::sin(angle * 2.0f) * 0.10f * scale, basePos.z + std::sin(angle) * dist }; DrawCube(shieldPos, 0.18f * scale, 0.38f * scale, 0.06f * scale, Color{ 28, 34, 46, 255 }); DrawCubeWires(shieldPos, 0.19f * scale, 0.39f * scale, 0.07f * scale, isElite ? TacticalColors::Crimson : TacticalColors::BorderMuted); } // 5. Plasma Thruster & Exhaust Plume Vector3 nozzlePos = { basePos.x, basePos.y - 0.40f * scale, basePos.z }; DrawCylinder(nozzlePos, 0.14f * scale, 0.18f * scale, 0.15f * scale, 8, Color{ 35, 42, 54, 255 }); float flameH = 0.22f + pulse * 0.12f; Vector3 flamePos = { basePos.x, basePos.y - (0.45f + flameH * 0.5f) * scale, basePos.z }; DrawCylinder(flamePos, 0.10f * scale, 0.01f * scale, flameH * scale, 8, TacticalColors::Crimson); DrawSphere({ basePos.x, basePos.y - 0.45f * scale, basePos.z }, 0.08f * scale, TacticalColors::PureWhite); } void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float animTime) { if (!m_texturesLoaded) { generateTextures(); } beginPostProcess(); Vector3 eye = player.getVisualPosition(); m_camera.position = eye; float rad = player.getVisualYaw() * (3.14159265f / 180.0f); float dirX = std::sin(rad); float dirZ = -std::cos(rad); m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ }; BeginMode3D(m_camera); const float tileSize = 2.0f; const float wallHeight = 2.0f; int pX = player.getGridX(); int pY = player.getGridY(); int renderDist = 7; int minX = std::max(0, pX - renderDist); int maxX = std::min(dungeon.getWidth() - 1, pX + renderDist); int minY = std::max(0, pY - renderDist); int maxY = std::min(dungeon.getHeight() - 1, pY + renderDist); for (int y = minY; y <= maxY; ++y) { for (int x = minX; x <= maxX; ++x) { Vector3 center = { x * tileSize, 0.0f, y * tileSize }; TileType tile = dungeon.getTile(x, y); // Tactical Helmet Spotlight / Flashlight Cone Falloff float dx = (center.x - eye.x); float dz = (center.z - eye.z); float dist = std::sqrt(dx * dx + dz * dz); float b = 0.12f; // Deep atmospheric shadow baseline if (dist < 0.4f) { b = 1.0f; } else { float dot = (dx * dirX + dz * dirZ) / dist; float cone = std::clamp((dot - 0.20f) / 0.80f, 0.0f, 1.0f); float distFall = std::clamp(1.0f - (dist / (renderDist * 2.0f)), 0.0f, 1.0f); b = std::clamp(0.12f + cone * distFall * 0.88f, 0.08f, 1.0f); } Color tileTint = Color{ static_cast(255 * b), static_cast(255 * b), static_cast(255 * b), 255 }; if (tile == TileType::WALL) { Vector3 wallCenter = { center.x, wallHeight * 0.5f, center.z }; Vector3 wallSize = { tileSize, wallHeight, tileSize }; drawTexturedBox(m_texDungeonWall, wallCenter, wallSize, 1.0f, 1.0f, tileTint); } else { // Textured Industrial Grate Floor drawTexturedQuad(m_texDungeonFloor, { center.x - tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f }, { center.x - tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f }, { center.x + tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f }, { center.x + tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f }, 1.0f, 1.0f, tileTint); // Textured Beam Ceiling with Dim Red Fixtures drawTexturedQuad(m_texDungeonCeil, { center.x + tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f }, { center.x + tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f }, { center.x - tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f }, { center.x - tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f }, 1.0f, 1.0f, tileTint); if (tile == TileType::DOOR_LOCKED || tile == TileType::DOOR_UNLOCKED) { bool locked = (tile == TileType::DOOR_LOCKED); bool wallLeftRight = (dungeon.getTile(x - 1, y) == TileType::WALL && dungeon.getTile(x + 1, y) == TileType::WALL); if (wallLeftRight) { Vector3 leftPost = { center.x - 0.9f, wallHeight * 0.5f, center.z }; drawTexturedBox(m_texDungeonDoorFrame, leftPost, { 0.2f, wallHeight, 0.35f }, 1.0f, 1.0f, tileTint); Vector3 rightPost = { center.x + 0.9f, wallHeight * 0.5f, center.z }; drawTexturedBox(m_texDungeonDoorFrame, rightPost, { 0.2f, wallHeight, 0.35f }, 1.0f, 1.0f, tileTint); Vector3 topLintel = { center.x, wallHeight - 0.1f, center.z }; drawTexturedBox(m_texDungeonDoorFrame, topLintel, { 1.8f, 0.2f, 0.35f }, 2.0f, 1.0f, tileTint); if (locked) { Vector3 doorBody = { center.x, (wallHeight - 0.2f) * 0.5f, center.z }; drawTexturedBox(m_texDungeonDoor, doorBody, { 1.6f, wallHeight - 0.2f, 0.2f }, 1.0f, 1.0f, tileTint); float pulse = 0.5f + 0.5f * std::sin(animTime * 6.0f); Color lockCol = (pulse > 0.5f) ? TacticalColors::Crimson : TacticalColors::DarkRed; Vector3 lockCore = { center.x, 0.9f, center.z }; DrawCube(lockCore, 0.38f, 0.22f, 0.24f, lockCol); DrawCubeWires(lockCore, 0.40f, 0.24f, 0.26f, TacticalColors::BrightRed); } else { Vector3 leftSlab = { center.x - 0.7f, (wallHeight - 0.2f) * 0.5f, center.z }; Vector3 rightSlab = { center.x + 0.7f, (wallHeight - 0.2f) * 0.5f, center.z }; drawTexturedBox(m_texDungeonDoor, leftSlab, { 0.25f, wallHeight - 0.2f, 0.18f }, 0.5f, 1.0f, tileTint); drawTexturedBox(m_texDungeonDoor, rightSlab, { 0.25f, wallHeight - 0.2f, 0.18f }, 0.5f, 1.0f, tileTint); } } else { Vector3 northPost = { center.x, wallHeight * 0.5f, center.z - 0.9f }; drawTexturedBox(m_texDungeonDoorFrame, northPost, { 0.35f, wallHeight, 0.2f }, 1.0f, 1.0f, tileTint); Vector3 southPost = { center.x, wallHeight * 0.5f, center.z + 0.9f }; drawTexturedBox(m_texDungeonDoorFrame, southPost, { 0.35f, wallHeight, 0.2f }, 1.0f, 1.0f, tileTint); Vector3 topLintel = { center.x, wallHeight - 0.1f, center.z }; drawTexturedBox(m_texDungeonDoorFrame, topLintel, { 0.35f, 0.2f, 1.8f }, 1.0f, 2.0f, tileTint); if (locked) { Vector3 doorBody = { center.x, (wallHeight - 0.2f) * 0.5f, center.z }; drawTexturedBox(m_texDungeonDoor, doorBody, { 0.2f, wallHeight - 0.2f, 1.6f }, 1.0f, 1.0f, tileTint); float pulse = 0.5f + 0.5f * std::sin(animTime * 6.0f); Color lockCol = (pulse > 0.5f) ? TacticalColors::Crimson : TacticalColors::DarkRed; Vector3 lockCore = { center.x, 0.9f, center.z }; DrawCube(lockCore, 0.24f, 0.22f, 0.38f, lockCol); DrawCubeWires(lockCore, 0.26f, 0.24f, 0.40f, TacticalColors::BrightRed); } else { Vector3 northSlab = { center.x, (wallHeight - 0.2f) * 0.5f, center.z - 0.7f }; Vector3 southSlab = { center.x, (wallHeight - 0.2f) * 0.5f, center.z + 0.7f }; drawTexturedBox(m_texDungeonDoor, northSlab, { 0.18f, wallHeight - 0.2f, 0.25f }, 0.5f, 1.0f, tileTint); drawTexturedBox(m_texDungeonDoor, southSlab, { 0.18f, wallHeight - 0.2f, 0.25f }, 0.5f, 1.0f, tileTint); } } } else if (tile == TileType::LOOT_CACHE) { float floatY = 0.55f + std::sin(animTime * 3.0f + x) * 0.08f; Vector3 lootPos = { center.x, floatY, center.z }; DrawCube(lootPos, 0.52f, 0.40f, 0.52f, Color{ 24, 30, 40, 255 }); DrawCubeWires(lootPos, 0.54f, 0.42f, 0.54f, TacticalColors::Crimson); DrawCube({ lootPos.x, lootPos.y + 0.12f, lootPos.z }, 0.18f, 0.08f, 0.18f, TacticalColors::BrightRed); } else if (tile == TileType::EXIT_PORTAL) { float pulse = 0.5f + 0.5f * std::sin(animTime * 4.0f); // Tactical Extraction Terminal Platform Vector3 basePos = { center.x, 0.10f, center.z }; DrawCylinder(basePos, 0.95f, 1.0f, 0.20f, 8, Color{ 22, 28, 38, 255 }); DrawCylinderWires(basePos, 0.96f, 1.01f, 0.20f, 8, TacticalColors::Crimson); // 4 Corner Pylons with Crimson Warning Beacons float pylonDist = 0.72f; Vector3 pylons[4] = { { center.x - pylonDist, 0.42f, center.z - pylonDist }, { center.x + pylonDist, 0.42f, center.z - pylonDist }, { center.x - pylonDist, 0.42f, center.z + pylonDist }, { center.x + pylonDist, 0.42f, center.z + pylonDist } }; for (int p = 0; p < 4; ++p) { DrawCylinder(pylons[p], 0.06f, 0.08f, 0.65f, 8, Color{ 34, 42, 56, 255 }); DrawCylinderWires(pylons[p], 0.065f, 0.085f, 0.65f, 8, TacticalColors::DarkRed); DrawCube({ pylons[p].x, 0.76f, pylons[p].z }, 0.12f, 0.12f, 0.12f, TacticalColors::Crimson); } // Central Quantum Extraction Beacon Vector3 corePos = { center.x, 0.85f + pulse * 0.05f, center.z }; DrawCube(corePos, 0.32f, 1.25f, 0.32f, Color{ 230, 25, 45, static_cast(120 + pulse * 70) }); DrawCubeWires(corePos, 0.34f, 1.27f, 0.34f, TacticalColors::PureWhite); // Overhead Extraction Airlock Frame Vector3 signPos = { center.x, wallHeight - 0.20f, center.z }; DrawCube(signPos, 1.3f, 0.18f, 0.06f, Color{ 14, 18, 26, 240 }); DrawCubeWires(signPos, 1.32f, 0.20f, 0.08f, TacticalColors::Crimson); } } } } // Render Laser Barriers for (const auto& l : dungeon.getLasers()) { if (l.disarmed) continue; if (std::abs(l.x - pX) > renderDist || std::abs(l.y - pY) > renderDist) continue; Vector3 lCenter = { l.x * tileSize, 0.9f, l.y * tileSize }; if (l.vertical) { DrawCube({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, 0.25f, 0.25f, 0.10f, Color{ 45, 52, 65, 255 }); DrawCube({ lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.25f, 0.25f, 0.10f, Color{ 45, 52, 65, 255 }); if (l.active) { float pulse = 0.5f + 0.5f * std::sin(animTime * 12.0f); DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.04f, 0.04f, 8, TacticalColors::BrightRed); DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.10f, 0.10f, 8, Color{ 230, 25, 45, static_cast(70 + pulse * 60) }); } else { DrawCylinderEx({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, { lCenter.x, lCenter.y, lCenter.z + 0.95f }, 0.02f, 0.02f, 6, Color{ 80, 90, 105, 70 }); } } else { DrawCube({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, 0.10f, 0.25f, 0.25f, Color{ 45, 52, 65, 255 }); DrawCube({ lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.10f, 0.25f, 0.25f, Color{ 45, 52, 65, 255 }); if (l.active) { float pulse = 0.5f + 0.5f * std::sin(animTime * 12.0f); DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.04f, 0.04f, 8, TacticalColors::BrightRed); DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.10f, 0.10f, 8, Color{ 230, 25, 45, static_cast(70 + pulse * 60) }); } else { DrawCylinderEx({ lCenter.x - 0.95f, lCenter.y, lCenter.z }, { lCenter.x + 0.95f, lCenter.y, lCenter.z }, 0.02f, 0.02f, 6, Color{ 80, 90, 105, 70 }); } } } // Render Proximity Mines (Zero-rounding: rectangular plate with flashing sensor) for (const auto& m : dungeon.getMines()) { if (m.defused || m.triggered) continue; if (std::abs(m.x - pX) > renderDist || std::abs(m.y - pY) > renderDist) continue; Vector3 mCenter = { m.x * tileSize, 0.04f, m.y * tileSize }; DrawCube(mCenter, 0.44f, 0.08f, 0.44f, Color{ 30, 36, 46, 255 }); DrawCubeWires(mCenter, 0.45f, 0.09f, 0.45f, TacticalColors::DarkRed); float blink = 0.5f + 0.5f * std::sin(animTime * 10.0f); Color ledCol = (blink > 0.4f) ? TacticalColors::BrightRed : TacticalColors::DarkRed; DrawCube({ mCenter.x, 0.10f, mCenter.z }, 0.14f, 0.08f, 0.14f, ledCol); DrawCubeWires({ mCenter.x, 0.10f, mCenter.z }, 0.15f, 0.09f, 0.15f, TacticalColors::PureWhite); } // Render Cyber Shrines (Monolithic Arasaka Server Terminal) for (const auto& s : dungeon.getShrines()) { if (std::abs(s.x - pX) > renderDist || std::abs(s.y - pY) > renderDist) continue; Vector3 sCenter = { s.x * tileSize, 0.0f, s.y * tileSize }; DrawCube({ sCenter.x, 0.10f, sCenter.z }, 1.1f, 0.20f, 1.1f, Color{ 22, 28, 38, 255 }); DrawCubeWires({ sCenter.x, 0.10f, sCenter.z }, 1.12f, 0.22f, 1.12f, TacticalColors::Crimson); DrawCube({ sCenter.x, 0.95f, sCenter.z }, 0.58f, 1.50f, 0.58f, Color{ 14, 18, 26, 255 }); DrawCubeWires({ sCenter.x, 0.95f, sCenter.z }, 0.60f, 1.52f, 0.60f, s.used ? TacticalColors::SteelMuted : TacticalColors::Crimson); float pulse = 0.5f + 0.5f * std::sin(animTime * 3.5f); Vector3 terminalPos = { sCenter.x, 1.85f + pulse * 0.04f, sCenter.z }; Color termCol = s.used ? TacticalColors::SteelMuted : TacticalColors::BrightRed; DrawCube(terminalPos, 0.24f, 0.24f, 0.24f, termCol); DrawCubeWires(terminalPos, 0.26f, 0.26f, 0.26f, TacticalColors::PureWhite); } // Render Corridor Patrol Enemies as 3D Cyber-Drones auto& enemies = const_cast(dungeon).getEnemies(); for (const auto& me : enemies) { if (!me.alive) continue; if (std::abs(me.x - pX) > renderDist || std::abs(me.y - pY) > renderDist) continue; Vector3 ePos = { me.x * tileSize, 0.9f, me.y * tileSize }; renderCyberDrone(ePos, me.enemy.getColor(), 1.0f, animTime, false, false, me.enemy.isElite()); } EndMode3D(); endPostProcess(animTime); } void CyberRenderer::renderCombat3D(const Dungeon& dungeon, const Player& player, const CombatSystem& combat, float animTime) { if (!m_texturesLoaded) { generateTextures(); } beginPostProcess(); Vector3 eye = player.getVisualPosition(); m_camera.position = eye; float rad = player.getVisualYaw() * (3.14159265f / 180.0f); float dirX = std::sin(rad); float dirZ = -std::cos(rad); m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ }; BeginMode3D(m_camera); const float tileSize = 2.0f; const float wallHeight = 2.0f; int pX = player.getGridX(); int pY = player.getGridY(); int renderDist = 5; int minX = std::max(0, pX - renderDist); int maxX = std::min(dungeon.getWidth() - 1, pX + renderDist); int minY = std::max(0, pY - renderDist); int maxY = std::min(dungeon.getHeight() - 1, pY + renderDist); // 1. Render surrounding corridor geometry for (int y = minY; y <= maxY; ++y) { for (int x = minX; x <= maxX; ++x) { Vector3 center = { x * tileSize, 0.0f, y * tileSize }; TileType tile = dungeon.getTile(x, y); float dx = (center.x - eye.x); float dz = (center.z - eye.z); float dist = std::sqrt(dx * dx + dz * dz); float b = 0.15f; if (dist < 0.4f) { b = 1.0f; } else { float dot = (dx * dirX + dz * dirZ) / dist; float cone = std::clamp((dot - 0.20f) / 0.80f, 0.0f, 1.0f); float distFall = std::clamp(1.0f - (dist / 10.0f), 0.0f, 1.0f); b = std::clamp(0.15f + cone * distFall * 0.85f, 0.10f, 1.0f); } Color tileTint = Color{ static_cast(255 * b), static_cast(255 * b), static_cast(255 * b), 255 }; if (tile == TileType::WALL) { Vector3 wallCenter = { center.x, wallHeight * 0.5f, center.z }; drawTexturedBox(m_texDungeonWall, wallCenter, { tileSize, wallHeight, tileSize }, 1.0f, 1.0f, tileTint); } else { drawTexturedQuad(m_texDungeonFloor, { center.x - tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f }, { center.x - tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f }, { center.x + tileSize * 0.5f, -0.01f, center.z + tileSize * 0.5f }, { center.x + tileSize * 0.5f, -0.01f, center.z - tileSize * 0.5f }, 1.0f, 1.0f, tileTint); drawTexturedQuad(m_texDungeonCeil, { center.x + tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f }, { center.x + tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f }, { center.x - tileSize * 0.5f, wallHeight, center.z + tileSize * 0.5f }, { center.x - tileSize * 0.5f, wallHeight, center.z - tileSize * 0.5f }, 1.0f, 1.0f, tileTint); } } } // 2. STAGE ENEMY 3D DRONE IN FRONT OF PLAYER const auto& enemy = combat.getEnemy(); if (enemy && enemy->isAlive()) { Vector3 combatDronePos = { eye.x + dirX * 2.5f, eye.y + 0.02f, eye.z + dirZ * 2.5f }; bool isHit = (combat.getPhase() == CombatPhase::PLAYER_ACTION_ANIM); bool isAttacking = (combat.getPhase() == CombatPhase::ENEMY_ACTION_ANIM); renderCyberDrone(combatDronePos, enemy->getColor(), 1.5f, animTime, isHit, isAttacking, enemy->isElite()); // 3. Tactical Muzzle Flash & High-Velocity Projectile Tracer FX (Strict 0-rounding) if (isHit && combat.getLastFiredAmmo() != AmmoType::EMPTY) { AmmoType shotType = combat.getLastFiredAmmo(); Color shotColor = getAmmoInfo(shotType).color; float rightX = -dirZ; float rightZ = dirX; Vector3 muzzlePos = { eye.x + dirX * 0.45f + rightX * 0.22f, eye.y - 0.18f, eye.z + dirZ * 0.45f + rightZ * 0.22f }; // Muzzle flash: faceted diamond prism DrawCube(muzzlePos, 0.09f, 0.09f, 0.09f, shotColor); DrawCubeWires(muzzlePos, 0.12f, 0.12f, 0.12f, TacticalColors::PureWhite); DrawCube({ muzzlePos.x + dirX * 0.05f, muzzlePos.y, muzzlePos.z + dirZ * 0.05f }, 0.06f, 0.06f, 0.14f, Color{ shotColor.r, shotColor.g, shotColor.b, 180 }); // High-velocity projectile tracer beam (Straight beam from muzzle to drone) DrawCylinderEx(muzzlePos, combatDronePos, 0.035f, 0.035f, 6, shotColor); DrawCylinderEx(muzzlePos, combatDronePos, 0.015f, 0.015f, 6, TacticalColors::PureWhite); // Impact sparks on target drone chassis Vector3 impactPos = { combatDronePos.x - dirX * 0.18f, combatDronePos.y, combatDronePos.z - dirZ * 0.18f }; DrawCube(impactPos, 0.11f, 0.11f, 0.11f, shotColor); DrawCubeWires(impactPos, 0.15f, 0.15f, 0.15f, TacticalColors::PureWhite); } } EndMode3D(); endPostProcess(animTime); } void CyberRenderer::renderHub(const HubRoom& hub, const Player& player, float animTime) { if (!m_texturesLoaded) { generateTextures(); } beginPostProcess(); Vector3 eye = player.getVisualPosition(); m_camera.position = eye; float rad = player.getVisualYaw() * (3.14159265f / 180.0f); float dirX = std::sin(rad); float dirZ = -std::cos(rad); m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ }; BeginMode3D(m_camera); const float tileSize = 2.0f; const float roomHeight = 2.8f; const float minCoord = 0.6f; const float maxCoord = 11.4f; // ========================================================================= // PHASE 1: OPAQUE SCENE GEOMETRY (Writes to both Color and Depth buffers) // ========================================================================= // 1. FLOOR: Dark Smoked Walnut Parquet Floor (snug room = 10.8m x 10.8m) drawTexturedQuad(m_texParquet, { minCoord, 0.0f, minCoord }, { minCoord, 0.0f, maxCoord }, { maxCoord, 0.0f, maxCoord }, { maxCoord, 0.0f, minCoord }, 5.5f, 5.5f, Color{ 180, 180, 200, 255 }); // 2. LUXURY RUG: Centered in room (6.0m, 6.0m), size 4.0m x 4.0m Vector3 rugCenter = { 6.0f, 0.02f, 6.0f }; float rw = 2.0f; float rl = 2.0f; drawTexturedQuad(m_texRug, { rugCenter.x - rw, 0.02f, rugCenter.z - rl }, { rugCenter.x - rw, 0.02f, rugCenter.z + rl }, { rugCenter.x + rw, 0.02f, rugCenter.z + rl }, { rugCenter.x + rw, 0.02f, rugCenter.z - rl }, 1.0f, 1.0f, Color{ 180, 180, 200, 255 }); // 3. CEILING: Moody dark matte ceiling DrawCube({ 6.0f, roomHeight + 0.10f, 6.0f }, 11.6f, 0.20f, 11.6f, Color{ 14, 16, 22, 255 }); // 4. SURROUNDING WALLS (Solid textured boxes with 0.24m thickness) const float wallThick = 0.24f; const float halfThick = wallThick * 0.5f; Color wallTint = Color{ 170, 175, 195, 255 }; // South Wall (Z = maxCoord = 11.4f) drawTexturedBox(m_texPlasterWall, { 6.0f, roomHeight * 0.5f, maxCoord + halfThick }, { 10.8f + wallThick * 2.0f, roomHeight, wallThick }, 5.5f, 1.4f, wallTint); // West Wall (X = minCoord = 0.6f) drawTexturedBox(m_texPlasterWall, { minCoord - halfThick, roomHeight * 0.5f, 6.0f }, { wallThick, roomHeight, 10.8f + wallThick * 2.0f }, 5.5f, 1.4f, wallTint); // North Wall: Split around Gateway (Gateway is at X = 6.0f, width 2.6f: X in [4.7, 7.3]) drawTexturedBox(m_texPlasterWall, { 2.65f, roomHeight * 0.5f, minCoord - halfThick }, { 4.1f + wallThick, roomHeight, wallThick }, 2.1f, 1.4f, wallTint); drawTexturedBox(m_texPlasterWall, { 9.35f, roomHeight * 0.5f, minCoord - halfThick }, { 4.1f + wallThick, roomHeight, wallThick }, 2.1f, 1.4f, wallTint); drawTexturedBox(m_texPlasterWall, { 6.0f, 2.6f, minCoord - halfThick }, { 2.6f, 0.4f, wallThick }, 1.3f, 0.2f, wallTint); // East Wall: Split around Window (Window at Z in [3.6f, 8.4f], Y in [0.8f, 2.4f]) drawTexturedBox(m_texPlasterWall, { maxCoord + halfThick, roomHeight * 0.5f, 2.1f }, { wallThick, roomHeight, 3.0f + wallThick }, 1.5f, 1.4f, wallTint); drawTexturedBox(m_texPlasterWall, { maxCoord + halfThick, roomHeight * 0.5f, 9.9f }, { wallThick, roomHeight, 3.0f + wallThick }, 1.5f, 1.4f, wallTint); drawTexturedBox(m_texPlasterWall, { maxCoord + halfThick, 0.4f, 6.0f }, { wallThick, 0.8f, 4.8f }, 2.4f, 0.4f, wallTint); drawTexturedBox(m_texPlasterWall, { maxCoord + halfThick, 2.6f, 6.0f }, { wallThick, 0.4f, 4.8f }, 2.4f, 0.2f, wallTint); // Baseboards along bottom of walls inside room DrawCube({ 6.0f, 0.08f, maxCoord - 0.04f }, 10.8f, 0.16f, 0.08f, Color{ 36, 24, 18, 255 }); DrawCube({ minCoord + 0.04f, 0.08f, 6.0f }, 0.08f, 0.16f, 10.8f, Color{ 36, 24, 18, 255 }); DrawCube({ 2.65f, 0.08f, minCoord + 0.04f }, 4.1f, 0.16f, 0.08f, Color{ 36, 24, 18, 255 }); DrawCube({ 9.35f, 0.08f, minCoord + 0.04f }, 4.1f, 0.16f, 0.08f, Color{ 36, 24, 18, 255 }); DrawCube({ maxCoord - 0.04f, 0.08f, 2.1f }, 0.08f, 0.16f, 3.0f, Color{ 36, 24, 18, 255 }); DrawCube({ maxCoord - 0.04f, 0.08f, 9.9f }, 0.08f, 0.16f, 3.0f, Color{ 36, 24, 18, 255 }); // 5. FRAMED SYNTHWAVE POSTER: South Wall (Z = maxCoord - 0.01f, centered at X = 3.8f, Y = 1.55f) drawTexturedQuad(m_texPoster, { 3.0f, 2.35f, maxCoord - 0.01f }, { 3.0f, 0.75f, maxCoord - 0.01f }, { 4.6f, 0.75f, maxCoord - 0.01f }, { 4.6f, 2.35f, maxCoord - 0.01f }, 1.0f, 1.0f); DrawCubeWires({ 3.8f, 1.55f, maxCoord - 0.005f }, 1.62f, 1.62f, 0.02f, Color{ 20, 20, 25, 255 }); // 6. PANORAMIC WINDOW OPAQUE STRUCTURE (East Wall, X = maxCoord = 11.4f) // Exterior Room_window backdrop quad (sized to 16:9 ratio, seamlessly framed by window opening) drawTexturedQuad(m_texWindowView, { 11.85f, 3.90f, 2.0f }, { 11.85f, -0.70f, 2.0f }, { 11.85f, -0.70f, 10.0f }, { 11.85f, 3.90f, 10.0f }, 1.0f, 1.0f); // Window frame, mullions, and wooden sill in the wall opening DrawCubeWires({ maxCoord, 1.6f, 6.0f }, 0.28f, 1.62f, 4.82f, Color{ 15, 20, 30, 255 }); DrawCube({ maxCoord, 1.6f, 6.0f }, 0.08f, 1.6f, 0.06f, Color{ 22, 28, 40, 255 }); // Center vertical mullion DrawCube({ maxCoord, 1.6f, 6.0f }, 0.08f, 0.06f, 4.8f, Color{ 22, 28, 40, 255 }); // Center horizontal mullion DrawCube({ maxCoord - 0.12f, 0.78f, 6.0f }, 0.32f, 0.06f, 5.0f, Color{ 55, 38, 28, 255 }); // Interior wooden sill // Violet rim highlight on wooden sill reflecting exterior neon DrawCube({ maxCoord - 0.12f, 0.812f, 6.0f }, 0.28f, 0.005f, 4.8f, Color{ 150, 90, 225, 75 }); // 7. COMPUTER WORKSTATION DESK (West Wall: against X = minCoord = 0.6f, Z = 5.0f) Vector3 deskPos = { 1.05f, 0.75f, 5.0f }; Vector3 deskSize = { 0.85f, 0.08f, 2.2f }; drawTexturedBox(m_texDeskWood, deskPos, deskSize, 1.0f, 2.0f, Color{ 180, 180, 195, 255 }); // Metal legs DrawCube({ 0.70f, 0.36f, 4.0f }, 0.08f, 0.72f, 0.08f, Color{ 36, 40, 48, 255 }); DrawCube({ 1.40f, 0.36f, 4.0f }, 0.08f, 0.72f, 0.08f, Color{ 36, 40, 48, 255 }); DrawCube({ 0.70f, 0.36f, 6.0f }, 0.08f, 0.72f, 0.08f, Color{ 36, 40, 48, 255 }); DrawCube({ 1.40f, 0.36f, 6.0f }, 0.08f, 0.72f, 0.08f, Color{ 36, 40, 48, 255 }); // Primary Monitor (IDE code editor): facing East (+X) toward the player Vector3 mon1Pos = { 0.90f, 1.25f, 4.5f }; DrawCube(mon1Pos, 0.08f, 0.75f, 0.95f, Color{ 18, 22, 30, 255 }); DrawCube({ mon1Pos.x, 0.92f, mon1Pos.z }, 0.08f, 0.32f, 0.12f, Color{ 35, 40, 50, 255 }); DrawCube({ mon1Pos.x + 0.05f, 0.79f, mon1Pos.z }, 0.22f, 0.02f, 0.32f, Color{ 35, 40, 50, 255 }); // Primary Monitor Screen Quad with C++ Code drawTexturedQuad(m_texMonitorCode, { mon1Pos.x + 0.045f, mon1Pos.y + 0.34f, mon1Pos.z + 0.44f }, { mon1Pos.x + 0.045f, mon1Pos.y - 0.34f, mon1Pos.z + 0.44f }, { mon1Pos.x + 0.045f, mon1Pos.y - 0.34f, mon1Pos.z - 0.44f }, { mon1Pos.x + 0.045f, mon1Pos.y + 0.34f, mon1Pos.z - 0.44f }, 1.0f, 1.0f); // Secondary Monitor (Cyberspace Radar): angled slightly Vector3 mon2Pos = { 0.92f, 1.25f, 5.5f }; DrawCube(mon2Pos, 0.08f, 0.75f, 0.85f, Color{ 18, 22, 30, 255 }); DrawCube({ mon2Pos.x, 0.92f, mon2Pos.z }, 0.08f, 0.32f, 0.12f, Color{ 35, 40, 50, 255 }); DrawCube({ mon2Pos.x + 0.05f, 0.79f, mon2Pos.z }, 0.22f, 0.02f, 0.32f, Color{ 35, 40, 50, 255 }); // Secondary Monitor Screen Quad with Radar drawTexturedQuad(m_texMonitorRadar, { mon2Pos.x + 0.045f, mon2Pos.y + 0.34f, mon2Pos.z + 0.40f }, { mon2Pos.x + 0.045f, mon2Pos.y - 0.34f, mon2Pos.z + 0.40f }, { mon2Pos.x + 0.045f, mon2Pos.y - 0.34f, mon2Pos.z - 0.40f }, { mon2Pos.x + 0.045f, mon2Pos.y + 0.34f, mon2Pos.z - 0.40f }, 1.0f, 1.0f); // Keyboard & Mouse DrawCube({ 1.20f, 0.80f, 4.7f }, 0.30f, 0.02f, 0.70f, Color{ 25, 28, 36, 255 }); DrawCubeWires({ 1.20f, 0.80f, 4.7f }, 0.30f, 0.02f, 0.70f, Color{ 0, 180, 220, 160 }); DrawCube({ 1.20f, 0.80f, 5.30f }, 0.14f, 0.025f, 0.10f, Color{ 30, 35, 45, 255 }); // PC Tower on floor under desk with pulsing cyan LED Vector3 pcPos = { 0.95f, 0.35f, 3.6f }; DrawCube(pcPos, 0.40f, 0.70f, 0.60f, Color{ 14, 18, 26, 255 }); DrawCubeWires(pcPos, 0.40f, 0.70f, 0.60f, Color{ 35, 45, 60, 255 }); float ledPulse = 0.5f + 0.5f * std::sin(animTime * 3.0f); DrawCube({ pcPos.x + 0.21f, pcPos.y + 0.25f, pcPos.z }, 0.02f, 0.03f, 0.30f, Color{ 0, static_cast(180 + ledPulse * 75), 255, 255 }); // 8. BED (South-East corner: X in [8.5f, 11.2f], Z in [8.8f, 11.2f]) Vector3 bedPos = { 9.8f, 0.30f, 10.0f }; Vector3 bedSize = { 2.3f, 0.45f, 2.2f }; DrawCube({ bedPos.x, 0.08f, bedPos.z }, bedSize.x + 0.1f, 0.16f, bedSize.z + 0.1f, Color{ 38, 26, 18, 255 }); drawTexturedBox(m_texBedQuilt, bedPos, bedSize, 2.0f, 2.0f); // Bed Headboard against South wall DrawCube({ bedPos.x, 0.75f, maxCoord - 0.08f }, bedSize.x + 0.1f, 1.1f, 0.10f, Color{ 38, 26, 18, 255 }); // Two soft pillows at head of bed Vector3 pillow1 = { 9.2f, 0.58f, 10.7f }; Vector3 pillow2 = { 10.4f, 0.58f, 10.7f }; DrawCube(pillow1, 0.9f, 0.12f, 0.50f, Color{ 160, 165, 180, 255 }); DrawCube(pillow2, 0.9f, 0.12f, 0.50f, Color{ 160, 165, 180, 255 }); // 9. CYBERSPACE GATEWAY DOOR (North wall at X = 6.0f, Z = minCoord) Vector3 gateFramePos = { 6.0f, roomHeight * 0.5f, minCoord + 0.05f }; drawTexturedBox(m_texCyberGate, gateFramePos, { 2.6f, roomHeight, 0.30f }, 1.0f, 1.0f); float pulse = 0.5f + 0.5f * std::sin(animTime * 4.0f); Vector3 portalWire = { 6.0f, (roomHeight - 0.4f) * 0.5f, minCoord + 0.06f }; DrawCubeWires(portalWire, 1.82f, roomHeight - 0.38f, 0.10f, Color{ 0, 255, 120, static_cast(180 + pulse * 75) }); Vector3 signPos = { 6.0f, roomHeight - 0.22f, minCoord + 0.24f }; DrawCube(signPos, 1.6f, 0.25f, 0.06f, Color{ 10, 16, 26, 255 }); DrawCubeWires(signPos, 1.6f, 0.25f, 0.06f, CyberColors::NeonCyan); // ========================================================================= // PHASE 2: FLUSH OPAQUE BATCH TO GPU BEFORE TRANSLUCENT PASSES // ========================================================================= rlDrawRenderBatchActive(); // ========================================================================= // PHASE 3: TRANSLUCENT LIGHTING & ATMOSPHERE PASSES // Reads depth from Phase 1 (so bed/walls occlude rays properly), // but does NOT write to depth mask (so godrays never erase/culled scene objects). // ========================================================================= BeginBlendMode(BLEND_ALPHA); rlDisableDepthMask(); rlDisableBackfaceCulling(); // A. Window Glass Reflections & Diagonal Rain Streaks DrawCube({ maxCoord, 1.6f, 6.0f }, 0.01f, 1.56f, 4.74f, Color{ 90, 50, 160, 22 }); DrawLine3D({ maxCoord - 0.01f, 2.25f, 4.3f }, { maxCoord - 0.01f, 0.95f, 5.3f }, Color{ 200, 180, 255, 45 }); DrawLine3D({ maxCoord - 0.01f, 2.15f, 6.5f }, { maxCoord - 0.01f, 0.95f, 7.5f }, Color{ 200, 180, 255, 40 }); // B. Soft Violet Floor Ambient Light Pools from Window rlBegin(RL_QUADS); // Outer broad twilight pool on parquet (fading West into room) rlColor4ub(130, 75, 210, 35); rlVertex3f(11.35f, 0.012f, 3.2f); rlColor4ub(90, 45, 170, 0); rlVertex3f(6.5f, 0.012f, 3.2f); rlColor4ub(90, 45, 170, 0); rlVertex3f(6.5f, 0.012f, 8.8f); rlColor4ub(130, 75, 210, 35); rlVertex3f(11.35f, 0.012f, 8.8f); // Inner brighter pool right beneath window rlColor4ub(160, 95, 235, 55); rlVertex3f(11.35f, 0.015f, 4.0f); rlColor4ub(120, 65, 200, 0); rlVertex3f(8.8f, 0.015f, 4.0f); rlColor4ub(120, 65, 200, 0); rlVertex3f(8.8f, 0.015f, 8.0f); rlColor4ub(160, 95, 235, 55); rlVertex3f(11.35f, 0.015f, 8.0f); // C. Volumetric Godrays streaming through Window Mullions float rayPulse = 0.85f + 0.15f * std::sin(animTime * 1.6f); unsigned char topAlpha = static_cast(38.0f * rayPulse); unsigned char midAlpha = static_cast(18.0f * rayPulse); // Shaft 1 (Left pane: Z in [3.9f, 5.2f]) rlColor4ub(165, 105, 245, topAlpha); rlVertex3f(11.35f, 2.32f, 3.9f); rlColor4ub(165, 105, 245, topAlpha); rlVertex3f(11.35f, 2.32f, 5.2f); rlColor4ub(120, 65, 210, 0); rlVertex3f(6.8f, 0.02f, 5.6f); rlColor4ub(120, 65, 210, 0); rlVertex3f(6.8f, 0.02f, 4.3f); // Shaft 1 cross-sheet rlColor4ub(150, 90, 235, midAlpha); rlVertex3f(11.35f, 1.80f, 4.55f); rlColor4ub(150, 90, 235, midAlpha); rlVertex3f(11.35f, 1.10f, 4.55f); rlColor4ub(110, 55, 195, 0); rlVertex3f(7.2f, 0.02f, 4.95f); rlColor4ub(110, 55, 195, 0); rlVertex3f(7.2f, 0.40f, 4.95f); // Shaft 2 (Center pane: Z in [5.4f, 6.7f]) rlColor4ub(175, 115, 255, topAlpha); rlVertex3f(11.35f, 2.35f, 5.4f); rlColor4ub(175, 115, 255, topAlpha); rlVertex3f(11.35f, 2.35f, 6.7f); rlColor4ub(130, 70, 220, 0); rlVertex3f(6.5f, 0.02f, 7.1f); rlColor4ub(130, 70, 220, 0); rlVertex3f(6.5f, 0.02f, 5.8f); // Shaft 2 cross-sheet rlColor4ub(160, 100, 245, midAlpha); rlVertex3f(11.35f, 1.85f, 6.05f); rlColor4ub(160, 100, 245, midAlpha); rlVertex3f(11.35f, 1.15f, 6.05f); rlColor4ub(120, 65, 205, 0); rlVertex3f(6.9f, 0.02f, 6.45f); rlColor4ub(120, 65, 205, 0); rlVertex3f(6.9f, 0.42f, 6.45f); // Shaft 3 (Right pane: Z in [6.9f, 8.1f]) rlColor4ub(165, 105, 245, topAlpha); rlVertex3f(11.35f, 2.32f, 6.9f); rlColor4ub(165, 105, 245, topAlpha); rlVertex3f(11.35f, 2.32f, 8.1f); rlColor4ub(120, 65, 210, 0); rlVertex3f(6.8f, 0.02f, 8.5f); rlColor4ub(120, 65, 210, 0); rlVertex3f(6.8f, 0.02f, 7.3f); // Shaft 3 cross-sheet rlColor4ub(150, 90, 235, midAlpha); rlVertex3f(11.35f, 1.80f, 7.50f); rlColor4ub(150, 90, 235, midAlpha); rlVertex3f(11.35f, 1.10f, 7.50f); rlColor4ub(110, 55, 195, 0); rlVertex3f(7.2f, 0.02f, 7.90f); rlColor4ub(110, 55, 195, 0); rlVertex3f(7.2f, 0.40f, 7.90f); // D. Intimate Workstation Monitor Glow & Cast Screen Light // Desk surface glow under monitors rlColor4ub(0, 185, 240, 50); rlVertex3f(0.95f, 0.795f, 4.05f); rlColor4ub(0, 185, 240, 50); rlVertex3f(0.95f, 0.795f, 5.95f); rlColor4ub(0, 120, 210, 10); rlVertex3f(1.45f, 0.795f, 5.95f); rlColor4ub(0, 120, 210, 10); rlVertex3f(1.45f, 0.795f, 4.05f); // Floor glow cone cast eastward from monitors into room rlColor4ub(0, 175, 240, 36); rlVertex3f(1.10f, 0.015f, 4.0f); rlColor4ub(0, 175, 240, 36); rlVertex3f(1.10f, 0.015f, 6.0f); rlColor4ub(0, 90, 180, 0); rlVertex3f(3.20f, 0.015f, 6.6f); rlColor4ub(0, 90, 180, 0); rlVertex3f(3.20f, 0.015f, 3.4f); // Subtle volumetric cone of screen light in the air in front of monitors rlColor4ub(0, 200, 255, 18); rlVertex3f(1.02f, 1.45f, 4.2f); rlColor4ub(0, 200, 255, 18); rlVertex3f(1.02f, 1.45f, 5.8f); rlColor4ub(0, 120, 220, 0); rlVertex3f(2.40f, 1.30f, 6.2f); rlColor4ub(0, 120, 220, 0); rlVertex3f(2.40f, 1.30f, 3.8f); rlEnd(); // E. Gateway Portal Energy Field Vector3 portalField = { 6.0f, (roomHeight - 0.4f) * 0.5f, minCoord + 0.06f }; DrawCube(portalField, 1.8f, roomHeight - 0.4f, 0.08f, Color{ 0, 230, 160, 140 }); // Flush translucent batch rlDrawRenderBatchActive(); rlEnableBackfaceCulling(); rlEnableDepthMask(); EndBlendMode(); EndMode3D(); endPostProcess(animTime); } void CyberRenderer::renderRestRoom(const RestRoom& restRoom, const Player& player, float animTime) { if (!m_texturesLoaded) { generateTextures(); } beginPostProcess(); Vector3 eye = player.getVisualPosition(); m_camera.position = eye; float rad = player.getVisualYaw() * (3.14159265f / 180.0f); float dirX = std::sin(rad); float dirZ = -std::cos(rad); m_camera.target = { eye.x + dirX, eye.y, eye.z + dirZ }; BeginMode3D(m_camera); const float roomHeight = 3.2f; const float minCoord = 0.6f; const float maxCoord = 11.4f; // 1. Ancient Terracotta & Marble Floor drawTexturedQuad(m_texAncientFloor, { minCoord, -0.01f, minCoord }, { minCoord, -0.01f, maxCoord }, { maxCoord, -0.01f, maxCoord }, { maxCoord, -0.01f, minCoord }, 5.0f, 5.0f); // 2. Ancient Cedar Timber Ceiling Beams drawTexturedQuad(m_texAncientCeil, { maxCoord, roomHeight, minCoord }, { maxCoord, roomHeight, maxCoord }, { minCoord, roomHeight, maxCoord }, { minCoord, roomHeight, minCoord }, 5.0f, 5.0f); // 3. Ancient Ashlar Stone Walls // South Wall (Z = maxCoord) drawTexturedBox(m_texAncientWall, { 6.0f, roomHeight * 0.5f, maxCoord }, { 11.0f, roomHeight, 0.3f }, 5.5f, 1.0f); // East Wall (X = maxCoord) drawTexturedBox(m_texAncientWall, { maxCoord, roomHeight * 0.5f, 6.0f }, { 0.3f, roomHeight, 11.0f }, 5.5f, 1.0f); // West Wall (X = minCoord) drawTexturedBox(m_texAncientWall, { minCoord, roomHeight * 0.5f, 6.0f }, { 0.3f, roomHeight, 11.0f }, 5.5f, 1.0f); // North Wall: Split around Monumental Palace Portal (X = 6.0f, width 2.6f) drawTexturedBox(m_texAncientWall, { 2.7f, roomHeight * 0.5f, minCoord }, { 4.2f, roomHeight, 0.3f }, 2.0f, 1.0f); drawTexturedBox(m_texAncientWall, { 9.3f, roomHeight * 0.5f, minCoord }, { 4.2f, roomHeight, 0.3f }, 2.0f, 1.0f); drawTexturedBox(m_texAncientWall, { 6.0f, roomHeight - 0.3f, minCoord }, { 2.6f, 0.6f, 0.3f }, 1.3f, 0.3f); // 4. Classical Fluted Marble Columns in 4 corners drawMarbleColumn({ minCoord + 0.5f, 0.0f, minCoord + 0.5f }, roomHeight, 0.24f); drawMarbleColumn({ maxCoord - 0.5f, 0.0f, minCoord + 0.5f }, roomHeight, 0.24f); drawMarbleColumn({ minCoord + 0.5f, 0.0f, maxCoord - 0.5f }, roomHeight, 0.24f); drawMarbleColumn({ maxCoord - 0.5f, 0.0f, maxCoord - 0.5f }, roomHeight, 0.24f); // 5. Wall Torches / Sconces with flickering warm flame auto drawTorchSconce = [&](Vector3 wallPos, Vector3 outDir) { // Bronze mount bracket DrawCube(wallPos, 0.08f, 0.28f, 0.08f, Color{ 145, 105, 45, 255 }); Vector3 torchPos = { wallPos.x + outDir.x * 0.22f, wallPos.y + 0.10f, wallPos.z + outDir.z * 0.22f }; DrawCylinder(torchPos, 0.04f, 0.05f, 0.28f, 8, Color{ 80, 50, 25, 255 }); float flick1 = std::sin(animTime * 14.0f + wallPos.x * 3.5f + wallPos.z * 4.2f) * 0.025f; float flick2 = std::cos(animTime * 18.0f + wallPos.z * 2.8f) * 0.02f; Vector3 flamePos = { torchPos.x, torchPos.y + 0.16f + flick1, torchPos.z }; DrawSphere(flamePos, 0.11f + flick2, Color{ 255, 160, 45, 240 }); DrawSphere({ flamePos.x, flamePos.y + 0.04f, flamePos.z }, 0.06f, Color{ 255, 230, 110, 255 }); }; // Torch sconces on walls drawTorchSconce({ 3.2f, 1.7f, minCoord + 0.16f }, { 0.0f, 0.0f, 1.0f }); drawTorchSconce({ 8.8f, 1.7f, minCoord + 0.16f }, { 0.0f, 0.0f, 1.0f }); drawTorchSconce({ 3.5f, 1.7f, maxCoord - 0.16f }, { 0.0f, 0.0f, -1.0f }); drawTorchSconce({ 8.5f, 1.7f, maxCoord - 0.16f }, { 0.0f, 0.0f, -1.0f }); drawTorchSconce({ minCoord + 0.16f, 1.7f, 4.0f }, { 1.0f, 0.0f, 0.0f }); drawTorchSconce({ minCoord + 0.16f, 1.7f, 8.0f }, { 1.0f, 0.0f, 0.0f }); drawTorchSconce({ maxCoord - 0.16f, 1.7f, 4.0f }, { -1.0f, 0.0f, 0.0f }); drawTorchSconce({ maxCoord - 0.16f, 1.7f, 8.0f }, { -1.0f, 0.0f, 0.0f }); // 6. Monumental Palace Portal to Next Floor (North: X = 6.0f, Z = minCoord) drawMarbleColumn({ 4.7f, 0.0f, minCoord + 0.16f }, roomHeight - 0.2f, 0.22f); drawMarbleColumn({ 7.3f, 0.0f, minCoord + 0.16f }, roomHeight - 0.2f, 0.22f); // Classical marble lintel archway DrawCube({ 6.0f, roomHeight - 0.40f, minCoord + 0.16f }, 3.0f, 0.32f, 0.38f, Color{ 220, 215, 205, 255 }); DrawCubeWires({ 6.0f, roomHeight - 0.40f, minCoord + 0.16f }, 3.02f, 0.34f, 0.40f, Color{ 175, 140, 80, 255 }); // Dark descending stairway passage into catacombs DrawCube({ 6.0f, (roomHeight - 0.6f) * 0.5f, minCoord + 0.06f }, 2.0f, roomHeight - 0.6f, 0.10f, Color{ 18, 14, 22, 255 }); float portalPulse = 0.5f + 0.5f * std::sin(animTime * 3.2f); DrawCube({ 6.0f, (roomHeight - 0.6f) * 0.5f, minCoord + 0.08f }, 1.5f, roomHeight - 0.8f, 0.04f, Color{ 230, 190, 80, static_cast(110 + portalPulse * 70) }); // 7. Corner Storeroom Clusters (Terracotta Amphorae & Weathered Crates) // Northwest Cluster at tile (1, 1) -> (2.0f, 0.0f, 2.0f) drawAncientCrate(m_texAncientWood, { 2.1f, 0.45f, 2.1f }, { 0.90f, 0.90f, 0.90f }); drawAncientCrate(m_texAncientWood, { 2.1f, 1.15f, 2.1f }, { 0.65f, 0.50f, 0.65f }); drawAncientAmphora({ 1.5f, 0.0f, 1.6f }, 1.15f, Color{ 188, 102, 58, 255 }); drawAncientAmphora({ 2.5f, 0.0f, 1.5f }, 0.95f, Color{ 168, 88, 48, 255 }); drawAncientAmphora({ 1.6f, 0.0f, 2.4f }, 1.05f, Color{ 198, 115, 68, 255 }); // Northeast Cluster at tile (5, 1) -> (10.0f, 0.0f, 2.0f) drawAncientCrate(m_texAncientWood, { 9.9f, 0.50f, 1.8f }, { 1.00f, 1.00f, 1.00f }); drawAncientCrate(m_texAncientWood, { 10.4f, 0.40f, 2.4f }, { 0.80f, 0.80f, 0.80f }); drawAncientAmphora({ 9.4f, 0.0f, 2.3f }, 1.20f, Color{ 180, 95, 52, 255 }); drawAncientAmphora({ 10.4f, 0.0f, 1.4f }, 0.90f, Color{ 205, 120, 72, 255 }); // Additional storeroom props along south walls drawAncientAmphora({ 1.4f, 0.0f, 10.2f }, 1.10f, Color{ 175, 92, 50, 255 }); drawAncientAmphora({ 2.2f, 0.0f, 10.5f }, 0.95f, Color{ 192, 108, 62, 255 }); drawAncientCrate(m_texAncientWood, { 10.1f, 0.40f, 10.2f }, { 0.80f, 0.80f, 0.80f }); drawAncientAmphora({ 9.4f, 0.0f, 10.4f }, 1.15f, Color{ 185, 100, 56, 255 }); // 8. Stations: // West: Scribe's Inscribed Stele / Archive at tile (1, 3) -> (2.0f, 0.0f, 6.0f) drawScribeStele({ 2.0f, 0.0f, 6.0f }); // Center: Marble Nectar Fountain at tile (3, 3) -> (6.0f, 0.0f, 6.0f) drawMarbleNectarFountain({ 6.0f, 0.0f, 6.0f }, restRoom.isRecharged(), animTime); // East: Craftsman's Workbench at tile (5, 3) -> (10.0f, 0.0f, 6.0f) drawCraftsmanWorkbench(m_texAncientWood, { 10.0f, 0.0f, 6.0f }); // Water quenching urn & parts basket beside workbench drawAncientAmphora({ 10.0f, 0.0f, 7.6f }, 0.85f, Color{ 140, 110, 85, 255 }); drawAncientCrate(m_texAncientWood, { 10.0f, 0.30f, 4.4f }, { 0.65f, 0.60f, 0.65f }); EndMode3D(); endPostProcess(animTime); } } // namespace dnr