mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-28 09:33:52 +00:00
Add spellbook, fix WMO floor clipping, and polish UI/visuals
- Add spellbook screen (P key) with Spell.dbc name lookup and action bar assignment - Default Attack and Hearthstone spells available in single player - Fix WMO floor clipping (gryphon roost) by tightening ceiling rejection threshold - Darken ocean water, increase wave motion and opacity - Add M2 model distance fade-in to prevent pop-in - Reposition chat window, add slash/enter key focus - Remove debug key commands (keep only F1 perf HUD, N minimap) - Performance: return chat history by const ref, use deque for O(1) pop_front
This commit is contained in:
parent
c49bb58e47
commit
4bc5064515
17 changed files with 486 additions and 431 deletions
|
|
@ -72,10 +72,12 @@ float getEffectiveCollisionTopLocal(const M2ModelGPU& model,
|
|||
|
||||
float h = localMax.z - localMin.z;
|
||||
if (model.collisionSteppedFountain) {
|
||||
if (r > 0.88f) return localMin.z + h * 0.20f; // outer lip
|
||||
if (r > 0.62f) return localMin.z + h * 0.42f; // mid step
|
||||
if (r > 0.36f) return localMin.z + h * 0.66f; // inner step
|
||||
return localMin.z + h * 0.90f; // center/top approach
|
||||
if (r > 0.85f) return localMin.z + h * 0.18f; // outer lip
|
||||
if (r > 0.65f) return localMin.z + h * 0.36f; // mid step
|
||||
if (r > 0.45f) return localMin.z + h * 0.54f; // inner step
|
||||
if (r > 0.28f) return localMin.z + h * 0.70f; // center platform / statue base
|
||||
if (r > 0.14f) return localMin.z + h * 0.84f; // statue body / sword
|
||||
return localMin.z + h * 0.96f; // statue head / top
|
||||
}
|
||||
|
||||
// Low square curb/planter profile:
|
||||
|
|
@ -239,6 +241,7 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) {
|
|||
uniform sampler2D uTexture;
|
||||
uniform bool uHasTexture;
|
||||
uniform bool uAlphaTest;
|
||||
uniform float uFadeAlpha;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
|
|
@ -255,6 +258,12 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) {
|
|||
discard;
|
||||
}
|
||||
|
||||
// Distance fade - discard nearly invisible fragments
|
||||
float finalAlpha = texColor.a * uFadeAlpha;
|
||||
if (finalAlpha < 0.02) {
|
||||
discard;
|
||||
}
|
||||
|
||||
vec3 normal = normalize(Normal);
|
||||
vec3 lightDir = normalize(uLightDir);
|
||||
|
||||
|
|
@ -265,7 +274,7 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) {
|
|||
vec3 diffuse = diff * texColor.rgb;
|
||||
|
||||
vec3 result = ambient + diffuse;
|
||||
FragColor = vec4(result, texColor.a);
|
||||
FragColor = vec4(result, finalAlpha);
|
||||
}
|
||||
)";
|
||||
|
||||
|
|
@ -364,7 +373,13 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
|
||||
bool isPlanter = (lowerName.find("planter") != std::string::npos);
|
||||
gpuModel.collisionPlanter = isPlanter;
|
||||
bool statueName =
|
||||
(lowerName.find("statue") != std::string::npos) ||
|
||||
(lowerName.find("monument") != std::string::npos) ||
|
||||
(lowerName.find("sculpture") != std::string::npos);
|
||||
gpuModel.collisionStatue = statueName;
|
||||
bool smallSolidPropName =
|
||||
statueName ||
|
||||
(lowerName.find("crate") != std::string::npos) ||
|
||||
(lowerName.find("box") != std::string::npos) ||
|
||||
(lowerName.find("chest") != std::string::npos) ||
|
||||
|
|
@ -402,7 +417,8 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
!gpuModel.collisionSteppedLowPlatform &&
|
||||
(narrowVerticalName || narrowVerticalShape);
|
||||
bool genericSolidPropShape =
|
||||
(horiz > 0.6f && horiz < 6.0f && vert > 0.30f && vert < 4.0f && vert > horiz * 0.16f);
|
||||
(horiz > 0.6f && horiz < 6.0f && vert > 0.30f && vert < 4.0f && vert > horiz * 0.16f) ||
|
||||
statueName;
|
||||
bool curbLikeName =
|
||||
(lowerName.find("curb") != std::string::npos) ||
|
||||
(lowerName.find("planter") != std::string::npos) ||
|
||||
|
|
@ -619,12 +635,10 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
// Set up GL state for M2 rendering
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
glDisable(GL_BLEND); // No blend leaking from prior renderers
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_CULL_FACE); // Some M2 geometry is single-sided
|
||||
|
||||
// Make models render with a bright color for debugging
|
||||
// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Wireframe mode
|
||||
|
||||
// Build frustum for culling
|
||||
Frustum frustum;
|
||||
frustum.extractFromMatrix(projection * view);
|
||||
|
|
@ -640,6 +654,7 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
// Distance-based culling threshold for M2 models
|
||||
const float maxRenderDistance = 180.0f; // Aggressive culling for city performance
|
||||
const float maxRenderDistanceSq = maxRenderDistance * maxRenderDistance;
|
||||
const float fadeStartFraction = 0.75f; // Start fading at 75% of max distance
|
||||
const glm::vec3 camPos = camera.getPosition();
|
||||
|
||||
for (const auto& instance : instances) {
|
||||
|
|
@ -669,9 +684,25 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
continue;
|
||||
}
|
||||
|
||||
// Distance-based fade alpha for smooth pop-in
|
||||
float fadeAlpha = 1.0f;
|
||||
float fadeStartDistSq = effectiveMaxDistSq * fadeStartFraction * fadeStartFraction;
|
||||
if (distSq > fadeStartDistSq) {
|
||||
float dist = std::sqrt(distSq);
|
||||
float effectiveMaxDist = std::sqrt(effectiveMaxDistSq);
|
||||
float fadeStartDist = effectiveMaxDist * fadeStartFraction;
|
||||
fadeAlpha = std::clamp((effectiveMaxDist - dist) / (effectiveMaxDist - fadeStartDist), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
shader->setUniform("uModel", instance.modelMatrix);
|
||||
shader->setUniform("uTime", instance.animTime);
|
||||
shader->setUniform("uAnimScale", 0.0f); // Disabled - proper M2 animation needs bone/particle systems
|
||||
shader->setUniform("uFadeAlpha", fadeAlpha);
|
||||
|
||||
// Disable depth writes for fading objects to avoid z-fighting
|
||||
if (fadeAlpha < 1.0f) {
|
||||
glDepthMask(GL_FALSE);
|
||||
}
|
||||
|
||||
glBindVertexArray(model.vao);
|
||||
|
||||
|
|
@ -694,22 +725,15 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
lastDrawCallCount++;
|
||||
}
|
||||
|
||||
// Check for GL errors (only first draw)
|
||||
static bool checkedOnce = false;
|
||||
if (!checkedOnce) {
|
||||
checkedOnce = true;
|
||||
GLenum err = glGetError();
|
||||
if (err != GL_NO_ERROR) {
|
||||
LOG_ERROR("GL error after M2 draw: ", err);
|
||||
} else {
|
||||
LOG_INFO("M2 draw successful: ", model.indexCount, " indices");
|
||||
}
|
||||
}
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
if (fadeAlpha < 1.0f) {
|
||||
glDepthMask(GL_TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore cull face state
|
||||
// Restore state
|
||||
glDisable(GL_BLEND);
|
||||
glEnable(GL_CULL_FACE);
|
||||
}
|
||||
|
||||
|
|
@ -942,8 +966,12 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ)
|
|||
|
||||
// Reachability filter: allow a bit more climb for stepped low platforms.
|
||||
float maxStepUp = 1.0f;
|
||||
if (model.collisionSmallSolidProp) {
|
||||
if (model.collisionStatue) {
|
||||
maxStepUp = 2.5f;
|
||||
} else if (model.collisionSmallSolidProp) {
|
||||
maxStepUp = 2.0f;
|
||||
} else if (model.collisionSteppedFountain) {
|
||||
maxStepUp = 2.5f;
|
||||
} else if (model.collisionSteppedLowPlatform) {
|
||||
maxStepUp = model.collisionPlanter ? 3.0f : 2.4f;
|
||||
}
|
||||
|
|
@ -1020,9 +1048,13 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
// Swept hard clamp for taller blockers only.
|
||||
// Low/stepable objects should be climbable and not "shove" the player off.
|
||||
float maxStepUp = 1.20f;
|
||||
if (model.collisionSmallSolidProp) {
|
||||
if (model.collisionStatue) {
|
||||
maxStepUp = 2.5f;
|
||||
} else if (model.collisionSmallSolidProp) {
|
||||
// Keep box/crate-class props hard-solid to prevent phase-through.
|
||||
maxStepUp = 0.75f;
|
||||
} else if (model.collisionSteppedFountain) {
|
||||
maxStepUp = 2.5f;
|
||||
} else if (model.collisionSteppedLowPlatform) {
|
||||
maxStepUp = model.collisionPlanter ? 2.8f : 2.4f;
|
||||
}
|
||||
|
|
@ -1070,7 +1102,7 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
if (allowEscapeRelax) {
|
||||
continue;
|
||||
}
|
||||
if (model.collisionSteppedLowPlatform && stepableLowObject) {
|
||||
if ((model.collisionSteppedLowPlatform || model.collisionSteppedFountain) && stepableLowObject) {
|
||||
// Already on/near top surface: don't apply lateral push that ejects
|
||||
// the player from the object when landing.
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -974,17 +974,17 @@ void Renderer::renderWorld(game::World* world) {
|
|||
swimEffects->render(*camera);
|
||||
}
|
||||
|
||||
// Compute view/projection once for all sub-renderers
|
||||
const glm::mat4& view = camera ? camera->getViewMatrix() : glm::mat4(1.0f);
|
||||
const glm::mat4& projection = camera ? camera->getProjectionMatrix() : glm::mat4(1.0f);
|
||||
|
||||
// Render characters (after weather)
|
||||
if (characterRenderer && camera) {
|
||||
glm::mat4 view = camera->getViewMatrix();
|
||||
glm::mat4 projection = camera->getProjectionMatrix();
|
||||
characterRenderer->render(*camera, view, projection);
|
||||
}
|
||||
|
||||
// Render WMO buildings (after characters, before UI)
|
||||
if (wmoRenderer && camera) {
|
||||
glm::mat4 view = camera->getViewMatrix();
|
||||
glm::mat4 projection = camera->getProjectionMatrix();
|
||||
auto wmoStart = std::chrono::steady_clock::now();
|
||||
wmoRenderer->render(*camera, view, projection);
|
||||
auto wmoEnd = std::chrono::steady_clock::now();
|
||||
|
|
@ -993,8 +993,6 @@ void Renderer::renderWorld(game::World* world) {
|
|||
|
||||
// Render M2 doodads (trees, rocks, etc.)
|
||||
if (m2Renderer && camera) {
|
||||
glm::mat4 view = camera->getViewMatrix();
|
||||
glm::mat4 projection = camera->getProjectionMatrix();
|
||||
auto m2Start = std::chrono::steady_clock::now();
|
||||
m2Renderer->render(*camera, view, projection);
|
||||
auto m2End = std::chrono::steady_clock::now();
|
||||
|
|
|
|||
|
|
@ -936,9 +936,10 @@ std::optional<std::string> TerrainManager::getDominantTextureAt(float glX, float
|
|||
int alphaY = glm::clamp(static_cast<int>((fracY / 8.0f) * 63.0f), 0, 63);
|
||||
int alphaIndex = alphaY * 64 + alphaX;
|
||||
|
||||
std::vector<int> weights(chunk.layers.size(), 0);
|
||||
int weights[4] = {0, 0, 0, 0};
|
||||
size_t numLayers = std::min(chunk.layers.size(), static_cast<size_t>(4));
|
||||
int accum = 0;
|
||||
for (size_t layerIdx = 1; layerIdx < chunk.layers.size(); layerIdx++) {
|
||||
for (size_t layerIdx = 1; layerIdx < numLayers; layerIdx++) {
|
||||
int alpha = 0;
|
||||
if (decodeLayerAlpha(chunk, layerIdx, alphaScratch) && alphaIndex < static_cast<int>(alphaScratch.size())) {
|
||||
alpha = alphaScratch[alphaIndex];
|
||||
|
|
@ -950,7 +951,7 @@ std::optional<std::string> TerrainManager::getDominantTextureAt(float glX, float
|
|||
|
||||
size_t bestLayer = 0;
|
||||
int bestWeight = weights[0];
|
||||
for (size_t i = 1; i < weights.size(); i++) {
|
||||
for (size_t i = 1; i < numLayers; i++) {
|
||||
if (weights[i] > bestWeight) {
|
||||
bestWeight = weights[i];
|
||||
bestLayer = i;
|
||||
|
|
|
|||
|
|
@ -336,9 +336,12 @@ void TerrainRenderer::render(const Camera& camera) {
|
|||
frustum.extractFromMatrix(viewProj);
|
||||
}
|
||||
|
||||
// Render each chunk
|
||||
// Render each chunk — track last-bound textures to skip redundant binds
|
||||
renderedChunks = 0;
|
||||
culledChunks = 0;
|
||||
GLuint lastBound[7] = {0, 0, 0, 0, 0, 0, 0};
|
||||
int lastLayerConfig = -1; // track hasLayer1|hasLayer2|hasLayer3 bitmask
|
||||
|
||||
for (const auto& chunk : chunks) {
|
||||
if (!chunk.isValid()) {
|
||||
continue;
|
||||
|
|
@ -350,48 +353,63 @@ void TerrainRenderer::render(const Camera& camera) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// Bind textures
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.baseTexture);
|
||||
shader->setUniform("uBaseTexture", 0);
|
||||
// Bind base texture (slot 0) — skip if same as last chunk
|
||||
if (chunk.baseTexture != lastBound[0]) {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.baseTexture);
|
||||
lastBound[0] = chunk.baseTexture;
|
||||
}
|
||||
|
||||
// Bind layer textures and alphas
|
||||
// Layer configuration
|
||||
bool hasLayer1 = chunk.layerTextures.size() > 0;
|
||||
bool hasLayer2 = chunk.layerTextures.size() > 1;
|
||||
bool hasLayer3 = chunk.layerTextures.size() > 2;
|
||||
int layerConfig = (hasLayer1 ? 1 : 0) | (hasLayer2 ? 2 : 0) | (hasLayer3 ? 4 : 0);
|
||||
|
||||
shader->setUniform("uHasLayer1", hasLayer1 ? 1 : 0);
|
||||
shader->setUniform("uHasLayer2", hasLayer2 ? 1 : 0);
|
||||
shader->setUniform("uHasLayer3", hasLayer3 ? 1 : 0);
|
||||
if (layerConfig != lastLayerConfig) {
|
||||
shader->setUniform("uHasLayer1", hasLayer1 ? 1 : 0);
|
||||
shader->setUniform("uHasLayer2", hasLayer2 ? 1 : 0);
|
||||
shader->setUniform("uHasLayer3", hasLayer3 ? 1 : 0);
|
||||
lastLayerConfig = layerConfig;
|
||||
}
|
||||
|
||||
if (hasLayer1) {
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.layerTextures[0]);
|
||||
shader->setUniform("uLayer1Texture", 1);
|
||||
|
||||
glActiveTexture(GL_TEXTURE4);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.alphaTextures[0]);
|
||||
shader->setUniform("uLayer1Alpha", 4);
|
||||
if (chunk.layerTextures[0] != lastBound[1]) {
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.layerTextures[0]);
|
||||
lastBound[1] = chunk.layerTextures[0];
|
||||
}
|
||||
if (chunk.alphaTextures[0] != lastBound[4]) {
|
||||
glActiveTexture(GL_TEXTURE4);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.alphaTextures[0]);
|
||||
lastBound[4] = chunk.alphaTextures[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLayer2) {
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.layerTextures[1]);
|
||||
shader->setUniform("uLayer2Texture", 2);
|
||||
|
||||
glActiveTexture(GL_TEXTURE5);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.alphaTextures[1]);
|
||||
shader->setUniform("uLayer2Alpha", 5);
|
||||
if (chunk.layerTextures[1] != lastBound[2]) {
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.layerTextures[1]);
|
||||
lastBound[2] = chunk.layerTextures[1];
|
||||
}
|
||||
if (chunk.alphaTextures[1] != lastBound[5]) {
|
||||
glActiveTexture(GL_TEXTURE5);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.alphaTextures[1]);
|
||||
lastBound[5] = chunk.alphaTextures[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLayer3) {
|
||||
glActiveTexture(GL_TEXTURE3);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.layerTextures[2]);
|
||||
shader->setUniform("uLayer3Texture", 3);
|
||||
|
||||
glActiveTexture(GL_TEXTURE6);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.alphaTextures[2]);
|
||||
shader->setUniform("uLayer3Alpha", 6);
|
||||
if (chunk.layerTextures[2] != lastBound[3]) {
|
||||
glActiveTexture(GL_TEXTURE3);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.layerTextures[2]);
|
||||
lastBound[3] = chunk.layerTextures[2];
|
||||
}
|
||||
if (chunk.alphaTextures[2] != lastBound[6]) {
|
||||
glActiveTexture(GL_TEXTURE6);
|
||||
glBindTexture(GL_TEXTURE_2D, chunk.alphaTextures[2]);
|
||||
lastBound[6] = chunk.alphaTextures[2];
|
||||
}
|
||||
}
|
||||
|
||||
// Draw chunk
|
||||
|
|
|
|||
|
|
@ -355,10 +355,7 @@ void WaterRenderer::render(const Camera& camera, float time) {
|
|||
return;
|
||||
}
|
||||
|
||||
GLboolean cullEnabled = glIsEnabled(GL_CULL_FACE);
|
||||
if (cullEnabled) {
|
||||
glDisable(GL_CULL_FACE);
|
||||
}
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
||||
// Enable alpha blending for transparent water
|
||||
glEnable(GL_BLEND);
|
||||
|
|
@ -395,10 +392,10 @@ void WaterRenderer::render(const Camera& camera, float time) {
|
|||
// City/canal liquid profile: clearer water + stronger ripples/sun shimmer.
|
||||
// Stormwind canals typically use LiquidType 5 in this data set.
|
||||
bool canalProfile = (surface.wmoId != 0) || (surface.liquidType == 5);
|
||||
float waveAmp = canalProfile ? 0.07f : 0.038f;
|
||||
float waveFreq = canalProfile ? 0.30f : 0.22f;
|
||||
float waveSpeed = canalProfile ? 1.20f : 0.90f;
|
||||
float shimmerStrength = canalProfile ? 0.95f : 0.35f;
|
||||
float waveAmp = canalProfile ? 0.07f : 0.12f;
|
||||
float waveFreq = canalProfile ? 0.30f : 0.18f;
|
||||
float waveSpeed = canalProfile ? 1.20f : 1.60f;
|
||||
float shimmerStrength = canalProfile ? 0.95f : 0.50f;
|
||||
float alphaScale = canalProfile ? 0.72f : 1.00f;
|
||||
|
||||
waterShader->setUniform("waterColor", color);
|
||||
|
|
@ -418,9 +415,7 @@ void WaterRenderer::render(const Camera& camera, float time) {
|
|||
// Restore state
|
||||
glDepthMask(GL_TRUE);
|
||||
glDisable(GL_BLEND);
|
||||
if (cullEnabled) {
|
||||
glEnable(GL_CULL_FACE);
|
||||
}
|
||||
glEnable(GL_CULL_FACE);
|
||||
}
|
||||
|
||||
void WaterRenderer::createWaterMesh(WaterSurface& surface) {
|
||||
|
|
@ -747,7 +742,7 @@ glm::vec4 WaterRenderer::getLiquidColor(uint16_t liquidType) const {
|
|||
case 0: // Water
|
||||
return glm::vec4(0.2f, 0.4f, 0.6f, 1.0f);
|
||||
case 1: // Ocean
|
||||
return glm::vec4(0.14f, 0.36f, 0.58f, 1.0f);
|
||||
return glm::vec4(0.06f, 0.18f, 0.34f, 1.0f);
|
||||
case 2: // Magma
|
||||
return glm::vec4(0.9f, 0.3f, 0.05f, 1.0f);
|
||||
case 3: // Slime
|
||||
|
|
@ -760,7 +755,7 @@ glm::vec4 WaterRenderer::getLiquidColor(uint16_t liquidType) const {
|
|||
float WaterRenderer::getLiquidAlpha(uint16_t liquidType) const {
|
||||
uint8_t basicType = (liquidType == 0) ? 0 : ((liquidType - 1) % 4);
|
||||
switch (basicType) {
|
||||
case 1: return 0.48f; // Ocean
|
||||
case 1: return 0.68f; // Ocean
|
||||
case 2: return 0.72f; // Magma
|
||||
case 3: return 0.62f; // Slime
|
||||
default: return 0.38f; // Water
|
||||
|
|
|
|||
|
|
@ -316,6 +316,16 @@ uint32_t WMORenderer::createInstance(uint32_t modelId, const glm::vec3& position
|
|||
transformAABB(instance.modelMatrix, model.boundingBoxMin, model.boundingBoxMax,
|
||||
instance.worldBoundsMin, instance.worldBoundsMax);
|
||||
|
||||
// Pre-compute world-space group bounds to avoid per-frame transformAABB
|
||||
instance.worldGroupBounds.reserve(model.groups.size());
|
||||
for (const auto& group : model.groups) {
|
||||
glm::vec3 gMin, gMax;
|
||||
transformAABB(instance.modelMatrix, group.boundingBoxMin, group.boundingBoxMax, gMin, gMax);
|
||||
gMin -= glm::vec3(0.5f);
|
||||
gMax += glm::vec3(0.5f);
|
||||
instance.worldGroupBounds.emplace_back(gMin, gMax);
|
||||
}
|
||||
|
||||
instances.push_back(instance);
|
||||
size_t idx = instances.size() - 1;
|
||||
instanceIndexById[instance.id] = idx;
|
||||
|
|
@ -480,22 +490,16 @@ void WMORenderer::render(const Camera& camera, const glm::mat4& view, const glm:
|
|||
const ModelData& model = modelIt->second;
|
||||
shader->setUniform("uModel", instance.modelMatrix);
|
||||
|
||||
// Render all groups
|
||||
for (const auto& group : model.groups) {
|
||||
// Proper frustum culling using AABB test
|
||||
if (frustumCulling) {
|
||||
// Transform all AABB corners to avoid false culling on rotated groups.
|
||||
glm::vec3 actualMin, actualMax;
|
||||
transformAABB(instance.modelMatrix, group.boundingBoxMin, group.boundingBoxMax, actualMin, actualMax);
|
||||
// Small pad reduces edge flicker from precision/camera jitter.
|
||||
actualMin -= glm::vec3(0.5f);
|
||||
actualMax += glm::vec3(0.5f);
|
||||
if (!frustum.intersectsAABB(actualMin, actualMax)) {
|
||||
// Render all groups using cached world-space bounds
|
||||
for (size_t gi = 0; gi < model.groups.size(); ++gi) {
|
||||
if (frustumCulling && gi < instance.worldGroupBounds.size()) {
|
||||
const auto& [gMin, gMax] = instance.worldGroupBounds[gi];
|
||||
if (!frustum.intersectsAABB(gMin, gMax)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
renderGroup(group, model, instance.modelMatrix, view, projection);
|
||||
renderGroup(model.groups[gi], model, instance.modelMatrix, view, projection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -991,8 +995,10 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
glm::vec3 hitLocal = localOrigin + localDir * t;
|
||||
glm::vec3 hitWorld = glm::vec3(instance.modelMatrix * glm::vec4(hitLocal, 1.0f));
|
||||
|
||||
// Only use floors below or near the query point
|
||||
if (hitWorld.z <= glZ + 2.0f) {
|
||||
// Only use floors below or near the query point.
|
||||
// Callers already elevate glZ by +5..+6; keep buffer small
|
||||
// to avoid selecting ceilings above the player.
|
||||
if (hitWorld.z <= glZ + 0.5f) {
|
||||
if (!bestFloor || hitWorld.z > *bestFloor) {
|
||||
bestFloor = hitWorld.z;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue