mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 15:50:20 +00:00
Improve selected-NPC ring visuals, anchoring, and occlusion behavior
Selection ring rendering has been upgraded to better match WoW-like target feedback and remain readable across complex geometry. Visual updates: - Reworked ring mesh/shader from a simple two-band strip to a unit-disc + radial fragment shaping. - Implemented a thinner outer ring profile. - Added an inward color/alpha gradient that fades from the ring toward the center. Placement/anchoring updates: - Added CharacterRenderer::getInstanceFootZ() to query model foot plane from instance bounds. - Added Application::getRenderFootZForGuid() to resolve per-GUID foot height via live instance mapping. - Updated GameScreen target selection placement to anchor the effect at target foot Z. Ground/surface stability: - In renderSelectionCircle(), added floor clamping against terrain, WMO, and M2 floor probes at target XY. - Raised final placement offset to reduce residual clipping on uneven surfaces. Depth/visibility behavior: - Added polygon offset during ring draw to reduce z-fighting. - Disabled depth testing for the selection effect draw pass (with state restore) so the ring remains visible through terrain/WMO occluders, per requested behavior. State safety: - Restored modified GL state after selection pass (depth test / polygon offset / depth mask / cull). Build validation: - Verified with cmake build target "wowee" after each stage; final build succeeds.
This commit is contained in:
parent
1f7c220fdb
commit
f7372a282d
6 changed files with 90 additions and 17 deletions
|
|
@ -70,6 +70,7 @@ public:
|
|||
|
||||
// Render bounds lookup (for click targeting / selection)
|
||||
bool getRenderBoundsForGuid(uint64_t guid, glm::vec3& outCenter, float& outRadius) const;
|
||||
bool getRenderFootZForGuid(uint64_t guid, float& outFootZ) const;
|
||||
|
||||
// Character skin composite state (saved at spawn for re-compositing on equipment change)
|
||||
const std::string& getBodySkinPath() const { return bodySkinPath_; }
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ public:
|
|||
bool getAnimationSequences(uint32_t instanceId, std::vector<pipeline::M2Sequence>& out) const;
|
||||
bool getInstanceModelName(uint32_t instanceId, std::string& modelName) const;
|
||||
bool getInstanceBounds(uint32_t instanceId, glm::vec3& outCenter, float& outRadius) const;
|
||||
bool getInstanceFootZ(uint32_t instanceId, float& outFootZ) const;
|
||||
|
||||
/** Debug: Log all available animations for an instance */
|
||||
void dumpAnimations(uint32_t instanceId) const;
|
||||
|
|
|
|||
|
|
@ -3288,6 +3288,26 @@ bool Application::getRenderBoundsForGuid(uint64_t guid, glm::vec3& outCenter, fl
|
|||
return renderer->getCharacterRenderer()->getInstanceBounds(instanceId, outCenter, outRadius);
|
||||
}
|
||||
|
||||
bool Application::getRenderFootZForGuid(uint64_t guid, float& outFootZ) const {
|
||||
if (!renderer || !renderer->getCharacterRenderer()) return false;
|
||||
uint32_t instanceId = 0;
|
||||
|
||||
if (gameHandler && guid == gameHandler->getPlayerGuid()) {
|
||||
instanceId = renderer->getCharacterInstanceId();
|
||||
}
|
||||
if (instanceId == 0) {
|
||||
auto pit = playerInstances_.find(guid);
|
||||
if (pit != playerInstances_.end()) instanceId = pit->second;
|
||||
}
|
||||
if (instanceId == 0) {
|
||||
auto it = creatureInstances_.find(guid);
|
||||
if (it != creatureInstances_.end()) instanceId = it->second;
|
||||
}
|
||||
if (instanceId == 0) return false;
|
||||
|
||||
return renderer->getCharacterRenderer()->getInstanceFootZ(instanceId, outFootZ);
|
||||
}
|
||||
|
||||
void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation) {
|
||||
if (!renderer || !renderer->getCharacterRenderer() || !assetManager) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -2166,6 +2166,19 @@ bool CharacterRenderer::getInstanceBounds(uint32_t instanceId, glm::vec3& outCen
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CharacterRenderer::getInstanceFootZ(uint32_t instanceId, float& outFootZ) const {
|
||||
auto it = instances.find(instanceId);
|
||||
if (it == instances.end()) return false;
|
||||
auto mIt = models.find(it->second.modelId);
|
||||
if (mIt == models.end()) return false;
|
||||
|
||||
const auto& inst = it->second;
|
||||
const auto& model = mIt->second.data;
|
||||
float scale = std::max(0.001f, inst.scale);
|
||||
outFootZ = inst.position.z + model.boundMin.z * scale;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CharacterRenderer::detachWeapon(uint32_t charInstanceId, uint32_t attachmentId) {
|
||||
auto charIt = instances.find(charInstanceId);
|
||||
if (charIt == instances.end()) return;
|
||||
|
|
|
|||
|
|
@ -2462,21 +2462,35 @@ void Renderer::update(float deltaTime) {
|
|||
void Renderer::initSelectionCircle() {
|
||||
if (selCircleVAO) return;
|
||||
|
||||
// Minimal shader: position + uniform MVP + color
|
||||
// Selection effect shader: thin outer ring + inward fade toward center.
|
||||
const char* vsSrc = R"(
|
||||
#version 330 core
|
||||
layout(location = 0) in vec3 aPos;
|
||||
uniform mat4 uMVP;
|
||||
out vec2 vLocalPos;
|
||||
void main() {
|
||||
vLocalPos = aPos.xy;
|
||||
gl_Position = uMVP * vec4(aPos, 1.0);
|
||||
}
|
||||
)";
|
||||
const char* fsSrc = R"(
|
||||
#version 330 core
|
||||
uniform vec3 uColor;
|
||||
in vec2 vLocalPos;
|
||||
out vec4 FragColor;
|
||||
void main() {
|
||||
FragColor = vec4(uColor, 0.6);
|
||||
float r = clamp(length(vLocalPos), 0.0, 1.0);
|
||||
|
||||
float ringInner = 0.93;
|
||||
float ringOuter = 1.00;
|
||||
float ring = smoothstep(ringInner - 0.01, ringInner + 0.01, r) *
|
||||
(1.0 - smoothstep(ringOuter - 0.008, ringOuter + 0.004, r));
|
||||
|
||||
float inward = smoothstep(0.0, ringInner, r);
|
||||
inward = pow(inward, 1.9) * (1.0 - smoothstep(ringInner - 0.015, ringInner + 0.01, r));
|
||||
|
||||
float alpha = max(ring * 0.9, inward * 0.45);
|
||||
FragColor = vec4(uColor, alpha);
|
||||
}
|
||||
)";
|
||||
|
||||
|
|
@ -2496,24 +2510,23 @@ void Renderer::initSelectionCircle() {
|
|||
glDeleteShader(vs);
|
||||
glDeleteShader(fs);
|
||||
|
||||
// Build ring vertices (two concentric circles forming a strip)
|
||||
// Build a unit disc; fragment shader shapes ring+gradient by radius.
|
||||
constexpr int SEGMENTS = 48;
|
||||
constexpr float INNER = 0.85f;
|
||||
constexpr float OUTER = 1.0f;
|
||||
std::vector<float> verts;
|
||||
for (int i = 0; i <= SEGMENTS; i++) {
|
||||
verts.reserve((SEGMENTS + 2) * 3);
|
||||
|
||||
verts.push_back(0.0f);
|
||||
verts.push_back(0.0f);
|
||||
verts.push_back(0.0f);
|
||||
|
||||
for (int i = 0; i <= SEGMENTS; ++i) {
|
||||
float angle = 2.0f * 3.14159265f * static_cast<float>(i) / static_cast<float>(SEGMENTS);
|
||||
float c = std::cos(angle), s = std::sin(angle);
|
||||
// Outer vertex
|
||||
verts.push_back(c * OUTER);
|
||||
verts.push_back(s * OUTER);
|
||||
verts.push_back(0.0f);
|
||||
// Inner vertex
|
||||
verts.push_back(c * INNER);
|
||||
verts.push_back(s * INNER);
|
||||
verts.push_back(c);
|
||||
verts.push_back(s);
|
||||
verts.push_back(0.0f);
|
||||
}
|
||||
selCircleVertCount = static_cast<int>((SEGMENTS + 1) * 2);
|
||||
selCircleVertCount = static_cast<int>(SEGMENTS + 2);
|
||||
|
||||
glGenVertexArrays(1, &selCircleVAO);
|
||||
glGenBuffers(1, &selCircleVBO);
|
||||
|
|
@ -2540,9 +2553,24 @@ void Renderer::renderSelectionCircle(const glm::mat4& view, const glm::mat4& pro
|
|||
if (!selCircleVisible) return;
|
||||
initSelectionCircle();
|
||||
|
||||
// Small Z offset to prevent clipping under terrain
|
||||
// Clamp the circle to the best floor estimate at target XY to avoid clipping into
|
||||
// terrain/WMO/M2 surfaces, then keep a small visual lift above that plane.
|
||||
float floorZ = selCirclePos.z;
|
||||
if (terrainManager) {
|
||||
auto terrainH = terrainManager->getHeightAt(selCirclePos.x, selCirclePos.y);
|
||||
if (terrainH) floorZ = std::max(floorZ, *terrainH);
|
||||
}
|
||||
if (wmoRenderer) {
|
||||
auto wmoH = wmoRenderer->getFloorHeight(selCirclePos.x, selCirclePos.y, selCirclePos.z + 3.0f);
|
||||
if (wmoH) floorZ = std::max(floorZ, *wmoH);
|
||||
}
|
||||
if (m2Renderer) {
|
||||
auto m2H = m2Renderer->getFloorHeight(selCirclePos.x, selCirclePos.y, selCirclePos.z + 2.0f);
|
||||
if (m2H) floorZ = std::max(floorZ, *m2H);
|
||||
}
|
||||
|
||||
glm::vec3 raisedPos = selCirclePos;
|
||||
raisedPos.z += 0.15f;
|
||||
raisedPos.z = floorZ + 0.17f;
|
||||
glm::mat4 model = glm::translate(glm::mat4(1.0f), raisedPos);
|
||||
model = glm::scale(model, glm::vec3(selCircleRadius));
|
||||
|
||||
|
|
@ -2552,15 +2580,21 @@ void Renderer::renderSelectionCircle(const glm::mat4& view, const glm::mat4& pro
|
|||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDepthMask(GL_FALSE);
|
||||
GLboolean depthTestWasEnabled = glIsEnabled(GL_DEPTH_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
glPolygonOffset(-2.0f, -2.0f);
|
||||
|
||||
glUseProgram(selCircleShader);
|
||||
glUniformMatrix4fv(glGetUniformLocation(selCircleShader, "uMVP"), 1, GL_FALSE, &mvp[0][0]);
|
||||
glUniform3fv(glGetUniformLocation(selCircleShader, "uColor"), 1, &selCircleColor[0]);
|
||||
|
||||
glBindVertexArray(selCircleVAO);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, selCircleVertCount);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, selCircleVertCount);
|
||||
glBindVertexArray(0);
|
||||
|
||||
glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
if (depthTestWasEnabled) glEnable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_CULL_FACE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -404,6 +404,10 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
if (target) {
|
||||
targetGLPos = core::coords::canonicalToRender(
|
||||
glm::vec3(target->getX(), target->getY(), target->getZ()));
|
||||
float footZ = 0.0f;
|
||||
if (core::Application::getInstance().getRenderFootZForGuid(target->getGuid(), footZ)) {
|
||||
targetGLPos.z = footZ;
|
||||
}
|
||||
renderer->setTargetPosition(&targetGLPos);
|
||||
|
||||
// Selection circle color: WoW-canonical level-based colors
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue