perf: Optimize for iGPU and battery mode with batching, frustum culling, and graphics presets
This commit is contained in:
parent
24885ef7bc
commit
2730ff6720
10 changed files with 481 additions and 177 deletions
|
|
@ -11,6 +11,7 @@ uniform vec4 colDiffuse;
|
|||
uniform float u_time;
|
||||
uniform float u_glitchIntensity;
|
||||
uniform vec2 u_resolution;
|
||||
uniform int u_preset;
|
||||
|
||||
// Output fragment color
|
||||
out vec4 finalColor;
|
||||
|
|
@ -20,7 +21,7 @@ void main()
|
|||
vec2 uv = fragTexCoord;
|
||||
|
||||
// 1. Digital Glitch / Tactical Chromatic Aberration
|
||||
float glitch = clamp(u_glitchIntensity, 0.0, 1.0);
|
||||
float glitch = (u_preset >= 2) ? clamp(u_glitchIntensity, 0.0, 1.0) : 0.0;
|
||||
if (glitch > 0.001)
|
||||
{
|
||||
// Horizontal line slice displacement
|
||||
|
|
@ -66,9 +67,12 @@ void main()
|
|||
float vignette = smoothstep(0.78, 0.32, d);
|
||||
finalColor.rgb *= mix(0.40, 1.0, vignette);
|
||||
|
||||
// 4. Subtle Tactical CRT Scanlines
|
||||
float scanline = sin(uv.y * u_resolution.y * 1.5) * 0.025;
|
||||
finalColor.rgb -= scanline;
|
||||
// 4. Subtle Tactical CRT Scanlines (Ultra mode only)
|
||||
if (u_preset >= 2)
|
||||
{
|
||||
float scanline = sin(uv.y * u_resolution.y * 1.5) * 0.025;
|
||||
finalColor.rgb -= scanline;
|
||||
}
|
||||
|
||||
finalColor.a = 1.0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@ enum class GameState {
|
|||
VICTORY_LEVEL
|
||||
};
|
||||
|
||||
enum class GraphicsQuality {
|
||||
PERFORMANCE, // iGPU / Battery: Direct backbuffer render, no offscreen/postprocess overhead, max culling
|
||||
BALANCED, // Standard: 720p with post-processing pass
|
||||
ULTRA // Max: Full post-process pass with scanlines and glitch
|
||||
};
|
||||
|
||||
enum class AmmoType {
|
||||
EMPTY,
|
||||
KINETIC, // Pierce / Raw physical
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Game::~Game() {
|
|||
void Game::init() {
|
||||
SoundManager::get().init();
|
||||
m_renderer.init();
|
||||
m_renderer.setGraphicsQuality(SoundManager::get().getGraphicsQuality());
|
||||
m_state = GameState::MAIN_MENU;
|
||||
SoundManager::get().playTrack(MusicTrack::MENU_HUB);
|
||||
}
|
||||
|
|
@ -1004,14 +1005,15 @@ void Game::updateSettings(float dt) {
|
|||
float mVol = sm.getMasterVolume();
|
||||
float muVol = sm.getMusicVolume();
|
||||
float sVol = sm.getSfxVolume();
|
||||
GraphicsQuality gQuality = sm.getGraphicsQuality();
|
||||
|
||||
// Keyboard selection & adjustment
|
||||
// Keyboard selection (4 items: 0=Master, 1=Music, 2=SFX, 3=Graphics Preset)
|
||||
if (IsKeyPressed(KEY_W) || IsKeyPressed(KEY_UP)) {
|
||||
m_selectedSlider = (m_selectedSlider + 2) % 3;
|
||||
m_selectedSlider = (m_selectedSlider + 3) % 4;
|
||||
sm.playTumblerClick();
|
||||
}
|
||||
if (IsKeyPressed(KEY_S) || IsKeyPressed(KEY_DOWN)) {
|
||||
m_selectedSlider = (m_selectedSlider + 1) % 3;
|
||||
m_selectedSlider = (m_selectedSlider + 1) % 4;
|
||||
sm.playTumblerClick();
|
||||
}
|
||||
|
||||
|
|
@ -1019,27 +1021,39 @@ void Game::updateSettings(float dt) {
|
|||
if (m_selectedSlider == 0) sm.setMasterVolume(mVol - 0.05f);
|
||||
else if (m_selectedSlider == 1) sm.setMusicVolume(muVol - 0.05f);
|
||||
else if (m_selectedSlider == 2) sm.setSfxVolume(sVol - 0.05f);
|
||||
else if (m_selectedSlider == 3) {
|
||||
int qVal = (static_cast<int>(gQuality) + 2) % 3;
|
||||
auto newQ = static_cast<GraphicsQuality>(qVal);
|
||||
sm.setGraphicsQuality(newQ);
|
||||
m_renderer.setGraphicsQuality(newQ);
|
||||
}
|
||||
sm.playTumblerClick();
|
||||
}
|
||||
if (IsKeyPressed(KEY_D) || IsKeyPressed(KEY_RIGHT)) {
|
||||
if (m_selectedSlider == 0) sm.setMasterVolume(mVol + 0.05f);
|
||||
else if (m_selectedSlider == 1) sm.setMusicVolume(muVol + 0.05f);
|
||||
else if (m_selectedSlider == 2) sm.setSfxVolume(sVol + 0.05f);
|
||||
else if (m_selectedSlider == 3) {
|
||||
int qVal = (static_cast<int>(gQuality) + 1) % 3;
|
||||
auto newQ = static_cast<GraphicsQuality>(qVal);
|
||||
sm.setGraphicsQuality(newQ);
|
||||
m_renderer.setGraphicsQuality(newQ);
|
||||
}
|
||||
sm.playTumblerClick();
|
||||
}
|
||||
|
||||
// Mouse Interaction
|
||||
Vector2 mouse = GetMousePosition();
|
||||
int menuW = 580;
|
||||
int menuH = 430;
|
||||
int menuW = 620;
|
||||
int menuH = 495;
|
||||
int menuX = (GetScreenWidth() - menuW) / 2;
|
||||
int menuY = (GetScreenHeight() - menuH) / 2;
|
||||
int startY = menuY + 85;
|
||||
int startY = menuY + 76;
|
||||
int sliderW = 340;
|
||||
int sliderH = 26;
|
||||
int sliderX = menuX + 180;
|
||||
int sliderH = 24;
|
||||
int sliderX = menuX + 200;
|
||||
|
||||
Rectangle btnBack = { static_cast<float>(menuX + (menuW - 240) / 2), static_cast<float>(menuY + menuH - 65), 240.0f, 44.0f };
|
||||
Rectangle btnBack = { static_cast<float>(menuX + (menuW - 240) / 2), static_cast<float>(menuY + menuH - 60), 240.0f, 40.0f };
|
||||
|
||||
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
||||
if (CheckCollisionPointRec(mouse, btnBack)) {
|
||||
|
|
@ -1048,8 +1062,9 @@ void Game::updateSettings(float dt) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check audio sliders (0, 1, 2)
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int itemY = startY + i * 78;
|
||||
int itemY = startY + i * 68;
|
||||
Rectangle sBar = { static_cast<float>(sliderX - 10), static_cast<float>(itemY - 6), static_cast<float>(sliderW + 20), static_cast<float>(sliderH + 12) };
|
||||
if (CheckCollisionPointRec(mouse, sBar)) {
|
||||
m_selectedSlider = i;
|
||||
|
|
@ -1062,6 +1077,28 @@ void Game::updateSettings(float dt) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check graphics preset (item 3)
|
||||
int item3Y = startY + 3 * 68;
|
||||
Rectangle btnLeft = { static_cast<float>(sliderX), static_cast<float>(item3Y - 2), 28.0f, static_cast<float>(sliderH + 4) };
|
||||
Rectangle btnRight = { static_cast<float>(sliderX + sliderW - 28), static_cast<float>(item3Y - 2), 28.0f, static_cast<float>(sliderH + 4) };
|
||||
Rectangle nameBox = { static_cast<float>(sliderX + 34), static_cast<float>(item3Y - 2), static_cast<float>(sliderW - 68), static_cast<float>(sliderH + 4) };
|
||||
|
||||
if (CheckCollisionPointRec(mouse, btnLeft)) {
|
||||
m_selectedSlider = 3;
|
||||
int qVal = (static_cast<int>(gQuality) + 2) % 3;
|
||||
auto newQ = static_cast<GraphicsQuality>(qVal);
|
||||
sm.setGraphicsQuality(newQ);
|
||||
m_renderer.setGraphicsQuality(newQ);
|
||||
sm.playTumblerClick();
|
||||
} else if (CheckCollisionPointRec(mouse, btnRight) || CheckCollisionPointRec(mouse, nameBox)) {
|
||||
m_selectedSlider = 3;
|
||||
int qVal = (static_cast<int>(gQuality) + 1) % 3;
|
||||
auto newQ = static_cast<GraphicsQuality>(qVal);
|
||||
sm.setGraphicsQuality(newQ);
|
||||
m_renderer.setGraphicsQuality(newQ);
|
||||
sm.playTumblerClick();
|
||||
}
|
||||
}
|
||||
|
||||
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && m_isDraggingSlider) {
|
||||
|
|
@ -1168,6 +1205,7 @@ void Game::draw() {
|
|||
m_uiRenderer.drawSettingsMenu(SoundManager::get().getMasterVolume(),
|
||||
SoundManager::get().getMusicVolume(),
|
||||
SoundManager::get().getSfxVolume(),
|
||||
SoundManager::get().getGraphicsQuality(),
|
||||
m_selectedSlider);
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -224,6 +224,11 @@ void SoundManager::setSfxVolume(float vol) {
|
|||
saveSettings();
|
||||
}
|
||||
|
||||
void SoundManager::setGraphicsQuality(GraphicsQuality q) {
|
||||
m_graphicsQuality = q;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
void SoundManager::saveSettings(const std::string& path) {
|
||||
std::ofstream out(path);
|
||||
if (!out.is_open()) return;
|
||||
|
|
@ -231,6 +236,7 @@ void SoundManager::saveSettings(const std::string& path) {
|
|||
out << "master " << m_volMaster << "\n";
|
||||
out << "music " << m_volMusic << "\n";
|
||||
out << "sfx " << m_volSfx << "\n";
|
||||
out << "graphics " << static_cast<int>(m_graphicsQuality) << "\n";
|
||||
}
|
||||
|
||||
void SoundManager::loadSettings(const std::string& path) {
|
||||
|
|
@ -239,6 +245,7 @@ void SoundManager::loadSettings(const std::string& path) {
|
|||
m_volMaster = 1.0f;
|
||||
m_volMusic = 0.7f;
|
||||
m_volSfx = 0.9f;
|
||||
m_graphicsQuality = GraphicsQuality::BALANCED;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -251,6 +258,12 @@ void SoundManager::loadSettings(const std::string& path) {
|
|||
if (key == "master") in >> m_volMaster;
|
||||
else if (key == "music") in >> m_volMusic;
|
||||
else if (key == "sfx") in >> m_volSfx;
|
||||
else if (key == "graphics") {
|
||||
int gVal = 1;
|
||||
in >> gVal;
|
||||
gVal = std::clamp(gVal, 0, 2);
|
||||
m_graphicsQuality = static_cast<GraphicsQuality>(gVal);
|
||||
}
|
||||
}
|
||||
|
||||
m_volMaster = std::clamp(m_volMaster, 0.0f, 1.0f);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "raylib.h"
|
||||
#include "core/Common.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
|
@ -28,10 +29,12 @@ public:
|
|||
float getMasterVolume() const { return m_volMaster; }
|
||||
float getMusicVolume() const { return m_volMusic; }
|
||||
float getSfxVolume() const { return m_volSfx; }
|
||||
GraphicsQuality getGraphicsQuality() const { return m_graphicsQuality; }
|
||||
|
||||
void setMasterVolume(float vol);
|
||||
void setMusicVolume(float vol);
|
||||
void setSfxVolume(float vol);
|
||||
void setGraphicsQuality(GraphicsQuality q);
|
||||
|
||||
void saveSettings(const std::string& path = "settings.cfg");
|
||||
void loadSettings(const std::string& path = "settings.cfg");
|
||||
|
|
@ -72,6 +75,7 @@ private:
|
|||
float m_volMaster = 1.0f;
|
||||
float m_volMusic = 0.7f;
|
||||
float m_volSfx = 0.9f;
|
||||
GraphicsQuality m_graphicsQuality = GraphicsQuality::BALANCED;
|
||||
|
||||
// Music streaming state
|
||||
MusicTrack m_currentTrack = MusicTrack::NONE;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ int main() {
|
|||
const int screenWidth = 1280;
|
||||
const int screenHeight = 720;
|
||||
|
||||
SetConfigFlags(FLAG_VSYNC_HINT | FLAG_MSAA_4X_HINT);
|
||||
SetConfigFlags(FLAG_VSYNC_HINT);
|
||||
InitWindow(screenWidth, screenHeight, "DoorNroll // Cyber-Revolver Cyberspace Crawler");
|
||||
SetExitKey(KEY_NULL); // Prevent ESC from closing the game window
|
||||
SetTargetFPS(60);
|
||||
|
|
|
|||
|
|
@ -12,32 +12,28 @@ namespace dnr {
|
|||
|
||||
namespace {
|
||||
|
||||
// Direct vertex submission for batched textured quad without state flips
|
||||
inline void submitQuad(Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3, float uTiles = 1.0f, float vTiles = 1.0f, Color tint = WHITE) {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
submitQuad(v0, v1, v2, v3, uTiles, vTiles, tint);
|
||||
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) {
|
||||
// Direct vertex submission for batched textured box without state flips
|
||||
inline void submitBoxQuads(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;
|
||||
|
|
@ -45,47 +41,51 @@ void drawTexturedBox(Texture2D tex, Vector3 pos, Vector3 size, float uTile = 1.0
|
|||
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(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);
|
||||
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(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);
|
||||
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(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);
|
||||
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(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);
|
||||
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(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);
|
||||
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(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);
|
||||
rlTexCoord2f(uTile, 0.0f); rlVertex3f(x, y + h, z + d);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
rlDisableBackfaceCulling();
|
||||
rlSetTexture(tex.id);
|
||||
rlBegin(RL_QUADS);
|
||||
submitBoxQuads(pos, size, uTile, vTile, tint);
|
||||
rlEnd();
|
||||
rlSetTexture(0);
|
||||
rlEnableBackfaceCulling();
|
||||
|
|
@ -1164,6 +1164,7 @@ void CyberRenderer::init() {
|
|||
m_locTime = GetShaderLocation(m_shaderPostProcess, "u_time");
|
||||
m_locGlitch = GetShaderLocation(m_shaderPostProcess, "u_glitchIntensity");
|
||||
m_locRes = GetShaderLocation(m_shaderPostProcess, "u_resolution");
|
||||
m_locPreset = GetShaderLocation(m_shaderPostProcess, "u_preset");
|
||||
m_postProcessLoaded = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1263,6 +1264,10 @@ void CyberRenderer::unloadTextures() {
|
|||
|
||||
|
||||
void CyberRenderer::beginPostProcess() {
|
||||
if (m_graphicsQuality == GraphicsQuality::PERFORMANCE) {
|
||||
ClearBackground(Color{ 4, 6, 12, 255 });
|
||||
return;
|
||||
}
|
||||
if (m_postProcessLoaded) {
|
||||
BeginTextureMode(m_renderTarget);
|
||||
ClearBackground(Color{ 4, 6, 12, 255 });
|
||||
|
|
@ -1270,6 +1275,9 @@ void CyberRenderer::beginPostProcess() {
|
|||
}
|
||||
|
||||
void CyberRenderer::endPostProcess(float animTime) {
|
||||
if (m_graphicsQuality == GraphicsQuality::PERFORMANCE) {
|
||||
return;
|
||||
}
|
||||
if (m_postProcessLoaded) {
|
||||
EndTextureMode();
|
||||
|
||||
|
|
@ -1280,6 +1288,10 @@ void CyberRenderer::endPostProcess(float animTime) {
|
|||
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);
|
||||
int pVal = (m_graphicsQuality == GraphicsQuality::ULTRA) ? 2 : 1;
|
||||
if (m_locPreset >= 0) {
|
||||
SetShaderValue(m_shaderPostProcess, m_locPreset, &pVal, SHADER_UNIFORM_INT);
|
||||
}
|
||||
|
||||
BeginShaderMode(m_shaderPostProcess);
|
||||
DrawTextureRec(m_renderTarget.texture,
|
||||
|
|
@ -1383,21 +1395,52 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float
|
|||
int minY = std::max(0, pY - renderDist);
|
||||
int maxY = std::min(dungeon.getHeight() - 1, pY + renderDist);
|
||||
|
||||
struct QuadSubmission {
|
||||
Vector3 v0, v1, v2, v3;
|
||||
Color tint;
|
||||
};
|
||||
struct BoxSubmission {
|
||||
Vector3 pos;
|
||||
Vector3 size;
|
||||
float uTile;
|
||||
float vTile;
|
||||
Color tint;
|
||||
};
|
||||
|
||||
std::vector<QuadSubmission> batchFloors;
|
||||
batchFloors.reserve(128);
|
||||
std::vector<QuadSubmission> batchCeils;
|
||||
batchCeils.reserve(128);
|
||||
std::vector<BoxSubmission> batchWalls;
|
||||
batchWalls.reserve(128);
|
||||
std::vector<BoxSubmission> batchDoorFrames;
|
||||
batchDoorFrames.reserve(32);
|
||||
std::vector<BoxSubmission> batchDoors;
|
||||
batchDoors.reserve(32);
|
||||
|
||||
for (int y = minY; y <= maxY; ++y) {
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
Vector3 center = { x * tileSize, 0.0f, y * tileSize };
|
||||
|
||||
// Camera Frustum Culling: discard tiles behind camera plane (unless very close to player)
|
||||
float dx = (center.x - eye.x);
|
||||
float dz = (center.z - eye.z);
|
||||
float distSq = dx * dx + dz * dz;
|
||||
float dot = dx * dirX + dz * dirZ;
|
||||
|
||||
if (distSq > 3.5f && dot < -0.2f) {
|
||||
continue; // Outside camera viewing angle
|
||||
}
|
||||
|
||||
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
|
||||
float dist = std::sqrt(distSq);
|
||||
float b = 0.12f;
|
||||
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 cone = std::clamp((dot / dist - 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);
|
||||
}
|
||||
|
|
@ -1406,24 +1449,25 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float
|
|||
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);
|
||||
batchWalls.push_back({ 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);
|
||||
batchFloors.push_back({
|
||||
{ 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 },
|
||||
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);
|
||||
batchCeils.push_back({
|
||||
{ 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 },
|
||||
tileTint
|
||||
});
|
||||
|
||||
if (tile == TileType::DOOR_LOCKED || tile == TileType::DOOR_UNLOCKED) {
|
||||
bool locked = (tile == TileType::DOOR_LOCKED);
|
||||
|
|
@ -1431,102 +1475,171 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float
|
|||
|
||||
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);
|
||||
batchDoorFrames.push_back({ 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);
|
||||
batchDoorFrames.push_back({ 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);
|
||||
batchDoorFrames.push_back({ 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);
|
||||
batchDoors.push_back({ doorBody, { 1.6f, wallHeight - 0.2f, 0.2f }, 1.0f, 1.0f, tileTint });
|
||||
} 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);
|
||||
batchDoors.push_back({ leftSlab, { 0.25f, wallHeight - 0.2f, 0.18f }, 0.5f, 1.0f, tileTint });
|
||||
batchDoors.push_back({ 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);
|
||||
batchDoorFrames.push_back({ 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);
|
||||
batchDoorFrames.push_back({ 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);
|
||||
batchDoorFrames.push_back({ 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);
|
||||
batchDoors.push_back({ doorBody, { 0.2f, wallHeight - 0.2f, 1.6f }, 1.0f, 1.0f, tileTint });
|
||||
} 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);
|
||||
batchDoors.push_back({ northSlab, { 0.18f, wallHeight - 0.2f, 0.25f }, 0.5f, 1.0f, tileTint });
|
||||
batchDoors.push_back({ 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<unsigned char>(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
|
||||
// Submit All Batches to GPU with minimum state changes (5 draw calls total)
|
||||
rlDisableBackfaceCulling();
|
||||
|
||||
if (!batchFloors.empty()) {
|
||||
rlSetTexture(m_texDungeonFloor.id);
|
||||
rlBegin(RL_QUADS);
|
||||
for (const auto& q : batchFloors) {
|
||||
submitQuad(q.v0, q.v1, q.v2, q.v3, 1.0f, 1.0f, q.tint);
|
||||
}
|
||||
rlEnd();
|
||||
}
|
||||
|
||||
if (!batchCeils.empty()) {
|
||||
rlSetTexture(m_texDungeonCeil.id);
|
||||
rlBegin(RL_QUADS);
|
||||
for (const auto& q : batchCeils) {
|
||||
submitQuad(q.v0, q.v1, q.v2, q.v3, 1.0f, 1.0f, q.tint);
|
||||
}
|
||||
rlEnd();
|
||||
}
|
||||
|
||||
if (!batchWalls.empty()) {
|
||||
rlSetTexture(m_texDungeonWall.id);
|
||||
rlBegin(RL_QUADS);
|
||||
for (const auto& b : batchWalls) {
|
||||
submitBoxQuads(b.pos, b.size, b.uTile, b.vTile, b.tint);
|
||||
}
|
||||
rlEnd();
|
||||
}
|
||||
|
||||
if (!batchDoorFrames.empty()) {
|
||||
rlSetTexture(m_texDungeonDoorFrame.id);
|
||||
rlBegin(RL_QUADS);
|
||||
for (const auto& b : batchDoorFrames) {
|
||||
submitBoxQuads(b.pos, b.size, b.uTile, b.vTile, b.tint);
|
||||
}
|
||||
rlEnd();
|
||||
}
|
||||
|
||||
if (!batchDoors.empty()) {
|
||||
rlSetTexture(m_texDungeonDoor.id);
|
||||
rlBegin(RL_QUADS);
|
||||
for (const auto& b : batchDoors) {
|
||||
submitBoxQuads(b.pos, b.size, b.uTile, b.vTile, b.tint);
|
||||
}
|
||||
rlEnd();
|
||||
}
|
||||
|
||||
rlSetTexture(0);
|
||||
rlEnableBackfaceCulling();
|
||||
|
||||
// Render interactive tile objects (Caches, Exit Portal, Door Lock Lights) with frustum check
|
||||
for (int y = minY; y <= maxY; ++y) {
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
Vector3 center = { x * tileSize, 0.0f, y * tileSize };
|
||||
float dx = (center.x - eye.x);
|
||||
float dz = (center.z - eye.z);
|
||||
if (dx * dx + dz * dz > 3.5f && (dx * dirX + dz * dirZ) < -0.2f) continue;
|
||||
|
||||
TileType tile = dungeon.getTile(x, y);
|
||||
|
||||
if (tile == TileType::DOOR_LOCKED) {
|
||||
bool wallLeftRight = (dungeon.getTile(x - 1, y) == TileType::WALL && dungeon.getTile(x + 1, y) == TileType::WALL);
|
||||
float pulse = 0.5f + 0.5f * std::sin(animTime * 6.0f);
|
||||
Color lockCol = (pulse > 0.5f) ? TacticalColors::Crimson : TacticalColors::DarkRed;
|
||||
if (wallLeftRight) {
|
||||
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 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 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<unsigned char>(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 (with frustum culling)
|
||||
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 };
|
||||
float dx = lCenter.x - eye.x;
|
||||
float dz = lCenter.z - eye.z;
|
||||
if (dx * dx + dz * dz > 3.5f && (dx * dirX + dz * dirZ) < -0.2f) continue;
|
||||
|
||||
if (l.vertical) {
|
||||
DrawCube({ lCenter.x, lCenter.y, lCenter.z - 0.95f }, 0.25f, 0.25f, 0.10f, Color{ 45, 52, 65, 255 });
|
||||
|
|
@ -1551,11 +1664,14 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float
|
|||
}
|
||||
}
|
||||
|
||||
// Render Proximity Mines (Zero-rounding: rectangular plate with flashing sensor)
|
||||
// Render Proximity Mines (with frustum culling)
|
||||
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 };
|
||||
float dx = mCenter.x - eye.x;
|
||||
float dz = mCenter.z - eye.z;
|
||||
if (dx * dx + dz * dz > 3.5f && (dx * dirX + dz * dirZ) < -0.2f) continue;
|
||||
|
||||
DrawCube(mCenter, 0.44f, 0.08f, 0.44f, Color{ 30, 36, 46, 255 });
|
||||
DrawCubeWires(mCenter, 0.45f, 0.09f, 0.45f, TacticalColors::DarkRed);
|
||||
|
|
@ -1566,10 +1682,13 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float
|
|||
DrawCubeWires({ mCenter.x, 0.10f, mCenter.z }, 0.15f, 0.09f, 0.15f, TacticalColors::PureWhite);
|
||||
}
|
||||
|
||||
// Render Cyber Shrines (Monolithic Arasaka Server Terminal)
|
||||
// Render Cyber Shrines (with frustum culling)
|
||||
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 };
|
||||
float dx = sCenter.x - eye.x;
|
||||
float dz = sCenter.z - eye.z;
|
||||
if (dx * dx + dz * dz > 3.5f && (dx * dirX + dz * dirZ) < -0.2f) continue;
|
||||
|
||||
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);
|
||||
|
|
@ -1584,13 +1703,17 @@ void CyberRenderer::render3D(const Dungeon& dungeon, const Player& player, float
|
|||
DrawCubeWires(terminalPos, 0.26f, 0.26f, 0.26f, TacticalColors::PureWhite);
|
||||
}
|
||||
|
||||
// Render Corridor Patrol Enemies as 3D Cyber-Drones
|
||||
// Render Corridor Patrol Enemies as 3D Cyber-Drones (with frustum culling)
|
||||
auto& enemies = const_cast<Dungeon&>(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 };
|
||||
float dx = ePos.x - eye.x;
|
||||
float dz = ePos.z - eye.z;
|
||||
if (dx * dx + dz * dz > 3.5f && (dx * dirX + dz * dirZ) < -0.2f) continue;
|
||||
|
||||
renderCyberDrone(ePos, me.enemy.getColor(), 1.0f, animTime, false, false, me.enemy.isElite());
|
||||
}
|
||||
|
||||
|
|
@ -1628,21 +1751,45 @@ void CyberRenderer::renderCombat3D(const Dungeon& dungeon, const Player& player,
|
|||
int minY = std::max(0, pY - renderDist);
|
||||
int maxY = std::min(dungeon.getHeight() - 1, pY + renderDist);
|
||||
|
||||
// 1. Render surrounding corridor geometry
|
||||
struct QuadSubmission {
|
||||
Vector3 v0, v1, v2, v3;
|
||||
Color tint;
|
||||
};
|
||||
struct BoxSubmission {
|
||||
Vector3 pos;
|
||||
Vector3 size;
|
||||
Color tint;
|
||||
};
|
||||
|
||||
std::vector<QuadSubmission> batchFloors;
|
||||
batchFloors.reserve(64);
|
||||
std::vector<QuadSubmission> batchCeils;
|
||||
batchCeils.reserve(64);
|
||||
std::vector<BoxSubmission> batchWalls;
|
||||
batchWalls.reserve(64);
|
||||
|
||||
// 1. Collect surrounding corridor geometry with frustum check
|
||||
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 distSq = dx * dx + dz * dz;
|
||||
float dot = dx * dirX + dz * dirZ;
|
||||
|
||||
if (distSq > 3.5f && dot < -0.2f) {
|
||||
continue; // Behind player in combat
|
||||
}
|
||||
|
||||
TileType tile = dungeon.getTile(x, y);
|
||||
|
||||
float dist = std::sqrt(distSq);
|
||||
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 cone = std::clamp((dot / dist - 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);
|
||||
}
|
||||
|
|
@ -1650,25 +1797,60 @@ void CyberRenderer::renderCombat3D(const Dungeon& dungeon, const Player& player,
|
|||
|
||||
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);
|
||||
batchWalls.push_back({ wallCenter, { tileSize, wallHeight, tileSize }, 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);
|
||||
batchFloors.push_back({
|
||||
{ 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 },
|
||||
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);
|
||||
batchCeils.push_back({
|
||||
{ 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 },
|
||||
tileTint
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Submit corridor geometry in 3 batches
|
||||
rlDisableBackfaceCulling();
|
||||
|
||||
if (!batchFloors.empty()) {
|
||||
rlSetTexture(m_texDungeonFloor.id);
|
||||
rlBegin(RL_QUADS);
|
||||
for (const auto& q : batchFloors) {
|
||||
submitQuad(q.v0, q.v1, q.v2, q.v3, 1.0f, 1.0f, q.tint);
|
||||
}
|
||||
rlEnd();
|
||||
}
|
||||
|
||||
if (!batchCeils.empty()) {
|
||||
rlSetTexture(m_texDungeonCeil.id);
|
||||
rlBegin(RL_QUADS);
|
||||
for (const auto& q : batchCeils) {
|
||||
submitQuad(q.v0, q.v1, q.v2, q.v3, 1.0f, 1.0f, q.tint);
|
||||
}
|
||||
rlEnd();
|
||||
}
|
||||
|
||||
if (!batchWalls.empty()) {
|
||||
rlSetTexture(m_texDungeonWall.id);
|
||||
rlBegin(RL_QUADS);
|
||||
for (const auto& b : batchWalls) {
|
||||
submitBoxQuads(b.pos, b.size, 1.0f, 1.0f, b.tint);
|
||||
}
|
||||
rlEnd();
|
||||
}
|
||||
|
||||
rlSetTexture(0);
|
||||
rlEnableBackfaceCulling();
|
||||
|
||||
// 2. STAGE ENEMY 3D DRONE IN FRONT OF PLAYER
|
||||
const auto& enemy = combat.getEnemy();
|
||||
if (enemy && enemy->isAlive()) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ public:
|
|||
|
||||
void triggerGlitch(float intensity = 0.7f);
|
||||
|
||||
void setGraphicsQuality(GraphicsQuality q) { m_graphicsQuality = q; }
|
||||
GraphicsQuality getGraphicsQuality() const { return m_graphicsQuality; }
|
||||
|
||||
private:
|
||||
void generateTextures();
|
||||
void unloadTextures();
|
||||
|
|
@ -36,6 +39,7 @@ private:
|
|||
|
||||
Camera3D m_camera{};
|
||||
bool m_texturesLoaded = false;
|
||||
GraphicsQuality m_graphicsQuality = GraphicsQuality::BALANCED;
|
||||
|
||||
// Post-Processing Framebuffer & GLSL Shader
|
||||
RenderTexture2D m_renderTarget{};
|
||||
|
|
@ -43,6 +47,7 @@ private:
|
|||
int m_locTime = -1;
|
||||
int m_locGlitch = -1;
|
||||
int m_locRes = -1;
|
||||
int m_locPreset = -1;
|
||||
float m_glitchIntensity = 0.0f;
|
||||
bool m_postProcessLoaded = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -165,13 +165,7 @@ void UIRenderer::drawMatrixDigitalRain(float animTime) {
|
|||
}
|
||||
|
||||
void UIRenderer::drawMatrixOverlay(float animTime) {
|
||||
// Subtle CRT scanlines
|
||||
int screenH = GetScreenHeight();
|
||||
int screenW = GetScreenWidth();
|
||||
|
||||
for (int y = 0; y < screenH; y += 3) {
|
||||
DrawLine(0, y, screenW, y, Color{ 180, 195, 215, 10 });
|
||||
}
|
||||
|
||||
// Top terminal status bar
|
||||
DrawText("[SYS::TACTICAL_OS] [ARASAKA_NET] [PROTOCOL::357_MAGNUM] [SECURITY::ACTIVE]",
|
||||
|
|
@ -1725,11 +1719,11 @@ void UIRenderer::drawMainMenu(bool canContinue) {
|
|||
drawMatrixOverlay(static_cast<float>(GetTime()));
|
||||
}
|
||||
|
||||
void UIRenderer::drawSettingsMenu(float masterVol, float musicVol, float sfxVol, int selectedSlider) {
|
||||
void UIRenderer::drawSettingsMenu(float masterVol, float musicVol, float sfxVol, GraphicsQuality quality, int selectedSlider) {
|
||||
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color{ 4, 8, 16, 230 });
|
||||
|
||||
int menuW = 580;
|
||||
int menuH = 430;
|
||||
int menuW = 620;
|
||||
int menuH = 495;
|
||||
int menuX = (GetScreenWidth() - menuW) / 2;
|
||||
int menuY = (GetScreenHeight() - menuH) / 2;
|
||||
|
||||
|
|
@ -1738,23 +1732,24 @@ void UIRenderer::drawSettingsMenu(float masterVol, float musicVol, float sfxVol,
|
|||
|
||||
// Title Bar
|
||||
DrawRectangle(menuX, menuY, menuW, 52, Color{ 16, 26, 44, 255 });
|
||||
DrawText("// AUDIO CONFIGURATION PROTOCOL", menuX + 25, menuY + 16, 20, CyberColors::NeonCyan);
|
||||
DrawText("// HARDWARE & AUDIO CONFIGURATION PROTOCOL", menuX + 25, menuY + 16, 20, CyberColors::NeonCyan);
|
||||
|
||||
Vector2 mouse = GetMousePosition();
|
||||
|
||||
const char* titles[] = { "MASTER VOLUME", "MUSIC VOLUME", "SFX VOLUME" };
|
||||
float vols[] = { masterVol, musicVol, sfxVol };
|
||||
|
||||
int startY = menuY + 85;
|
||||
int startY = menuY + 76;
|
||||
int sliderW = 340;
|
||||
int sliderH = 26;
|
||||
int sliderX = menuX + 180;
|
||||
int sliderH = 24;
|
||||
int sliderX = menuX + 200;
|
||||
|
||||
// 1. Audio volume sliders (0, 1, 2)
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
int itemY = startY + i * 78;
|
||||
int itemY = startY + i * 68;
|
||||
bool isSel = (i == selectedSlider);
|
||||
|
||||
Rectangle itemBox = { static_cast<float>(menuX + 20), static_cast<float>(itemY - 10), static_cast<float>(menuW - 40), 65.0f };
|
||||
Rectangle itemBox = { static_cast<float>(menuX + 16), static_cast<float>(itemY - 8), static_cast<float>(menuW - 32), 60.0f };
|
||||
bool isHover = CheckCollisionPointRec(mouse, itemBox);
|
||||
|
||||
if (isSel || isHover) {
|
||||
|
|
@ -1762,7 +1757,7 @@ void UIRenderer::drawSettingsMenu(float masterVol, float musicVol, float sfxVol,
|
|||
DrawRectangleLinesEx(itemBox, 1.0f, isSel ? CyberColors::NeonYellow : Color{ 40, 70, 110, 255 });
|
||||
}
|
||||
|
||||
DrawText(titles[i], menuX + 30, itemY + 4, 15, isSel ? CyberColors::NeonYellow : RAYWHITE);
|
||||
DrawText(titles[i], menuX + 28, itemY + 4, 14, isSel ? CyberColors::NeonYellow : RAYWHITE);
|
||||
|
||||
Rectangle sBar = { static_cast<float>(sliderX), static_cast<float>(itemY), static_cast<float>(sliderW), static_cast<float>(sliderH) };
|
||||
DrawRectangleRec(sBar, Color{ 14, 20, 32, 255 });
|
||||
|
|
@ -1776,17 +1771,74 @@ void UIRenderer::drawSettingsMenu(float masterVol, float musicVol, float sfxVol,
|
|||
DrawRectangle(knobX - 4, itemY - 3, 8, sliderH + 6, WHITE);
|
||||
|
||||
std::string pctStr = std::to_string(static_cast<int>(std::round(vols[i] * 100.0f))) + "%";
|
||||
DrawText(pctStr.c_str(), sliderX + sliderW + 15, itemY + 4, 16, isSel ? CyberColors::NeonYellow : RAYWHITE);
|
||||
DrawText(pctStr.c_str(), sliderX + sliderW + 15, itemY + 3, 15, isSel ? CyberColors::NeonYellow : RAYWHITE);
|
||||
}
|
||||
|
||||
Rectangle btnBack = { static_cast<float>(menuX + (menuW - 240) / 2), static_cast<float>(menuY + menuH - 65), 240.0f, 44.0f };
|
||||
// 2. Graphics Preset Item (Index 3)
|
||||
{
|
||||
int itemY = startY + 3 * 68;
|
||||
bool isSel = (selectedSlider == 3);
|
||||
|
||||
Rectangle itemBox = { static_cast<float>(menuX + 16), static_cast<float>(itemY - 8), static_cast<float>(menuW - 32), 62.0f };
|
||||
bool isHover = CheckCollisionPointRec(mouse, itemBox);
|
||||
|
||||
if (isSel || isHover) {
|
||||
DrawRectangleRec(itemBox, Color{ 20, 32, 52, 140 });
|
||||
DrawRectangleLinesEx(itemBox, 1.0f, isSel ? CyberColors::NeonYellow : Color{ 40, 70, 110, 255 });
|
||||
}
|
||||
|
||||
DrawText("GRAPHICS PRESET", menuX + 28, itemY + 4, 14, isSel ? CyberColors::NeonYellow : RAYWHITE);
|
||||
|
||||
// Arrows & Preset Name Box
|
||||
Rectangle btnLeft = { static_cast<float>(sliderX), static_cast<float>(itemY - 2), 28.0f, static_cast<float>(sliderH + 4) };
|
||||
Rectangle btnRight = { static_cast<float>(sliderX + sliderW - 28), static_cast<float>(itemY - 2), 28.0f, static_cast<float>(sliderH + 4) };
|
||||
Rectangle nameBox = { static_cast<float>(sliderX + 34), static_cast<float>(itemY - 2), static_cast<float>(sliderW - 68), static_cast<float>(sliderH + 4) };
|
||||
|
||||
bool hoverL = CheckCollisionPointRec(mouse, btnLeft);
|
||||
bool hoverR = CheckCollisionPointRec(mouse, btnRight);
|
||||
bool hoverName = CheckCollisionPointRec(mouse, nameBox);
|
||||
|
||||
DrawRectangleRec(btnLeft, hoverL ? Color{ 40, 70, 110, 255 } : Color{ 18, 26, 42, 255 });
|
||||
DrawRectangleLinesEx(btnLeft, 1.0f, hoverL ? CyberColors::NeonYellow : Color{ 50, 75, 110, 255 });
|
||||
DrawText("<", static_cast<int>(btnLeft.x) + 10, static_cast<int>(btnLeft.y) + 5, 16, hoverL ? CyberColors::NeonYellow : WHITE);
|
||||
|
||||
DrawRectangleRec(btnRight, hoverR ? Color{ 40, 70, 110, 255 } : Color{ 18, 26, 42, 255 });
|
||||
DrawRectangleLinesEx(btnRight, 1.0f, hoverR ? CyberColors::NeonYellow : Color{ 50, 75, 110, 255 });
|
||||
DrawText(">", static_cast<int>(btnRight.x) + 10, static_cast<int>(btnRight.y) + 5, 16, hoverR ? CyberColors::NeonYellow : WHITE);
|
||||
|
||||
DrawRectangleRec(nameBox, hoverName ? Color{ 25, 40, 65, 255 } : Color{ 14, 20, 32, 255 });
|
||||
DrawRectangleLinesEx(nameBox, 1.0f, isSel ? CyberColors::NeonGreen : Color{ 50, 75, 110, 255 });
|
||||
|
||||
const char* qName = "BALANCED 720p";
|
||||
Color qCol = CyberColors::NeonCyan;
|
||||
const char* qDesc = "Standard 720p mode with CRT vignette and tone grading.";
|
||||
|
||||
if (quality == GraphicsQuality::PERFORMANCE) {
|
||||
qName = "PERFORMANCE (iGPU)";
|
||||
qCol = CyberColors::NeonGreen;
|
||||
qDesc = "Direct backbuffer render. 0 shader blit. Max battery life & 60 FPS.";
|
||||
} else if (quality == GraphicsQuality::ULTRA) {
|
||||
qName = "ULTRA CYBER";
|
||||
qCol = TacticalColors::Crimson;
|
||||
qDesc = "Full post-processing with glitch artifacts and chromatic aberration.";
|
||||
}
|
||||
|
||||
int textW = MeasureText(qName, 13);
|
||||
DrawText(qName, static_cast<int>(nameBox.x + (nameBox.width - textW) * 0.5f), static_cast<int>(nameBox.y + 6), 13, qCol);
|
||||
|
||||
// Explanatory descriptor underneath
|
||||
DrawText(qDesc, menuX + 200, itemY + 30, 11, TacticalColors::MutedText);
|
||||
}
|
||||
|
||||
// Back / Return Button
|
||||
Rectangle btnBack = { static_cast<float>(menuX + (menuW - 240) / 2), static_cast<float>(menuY + menuH - 60), 240.0f, 40.0f };
|
||||
bool hoverBack = CheckCollisionPointRec(mouse, btnBack);
|
||||
DrawRectangleRec(btnBack, hoverBack ? Color{ 30, 60, 95, 255 } : Color{ 16, 26, 42, 255 });
|
||||
DrawRectangleLinesEx(btnBack, hoverBack ? 2.0f : 1.0f, hoverBack ? CyberColors::NeonGreen : CyberColors::NeonCyan);
|
||||
DrawText("[ESC / ENTER] SAVE & RETURN", static_cast<int>(btnBack.x) + 18, static_cast<int>(btnBack.y) + 13, 14, hoverBack ? CyberColors::NeonYellow : RAYWHITE);
|
||||
DrawText("[ESC / ENTER] SAVE & RETURN", static_cast<int>(btnBack.x) + 18, static_cast<int>(btnBack.y) + 12, 14, hoverBack ? CyberColors::NeonYellow : RAYWHITE);
|
||||
|
||||
DrawText("[W/S] SELECT SLIDER | [A/D] ADJUST +/-5% | CLICK & DRAG SLIDERS",
|
||||
menuX + 40, menuY + menuH - 18, 12, GRAY);
|
||||
DrawText("[W/S] SELECT OPTION | [A/D] ADJUST / CYCLE PRESET | CLICK TO SELECT",
|
||||
menuX + 35, menuY + menuH - 16, 11, GRAY);
|
||||
}
|
||||
|
||||
} // namespace dnr
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public:
|
|||
void drawMatrixOverlay(float animTime);
|
||||
void drawPauseMenu(bool hasSavedMessage);
|
||||
void drawMainMenu(bool canContinue);
|
||||
void drawSettingsMenu(float masterVol, float musicVol, float sfxVol, int selectedSlider);
|
||||
void drawSettingsMenu(float masterVol, float musicVol, float sfxVol, GraphicsQuality quality, int selectedSlider);
|
||||
|
||||
private:
|
||||
void drawMinimap(const Dungeon& dungeon, const Player& player, int x, int y, int size);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue