mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-02 15:53:51 +00:00
Parallel animation updates, thread-safe collision, M2 pop-in fix, shadow stabilization
- Overlap M2 and character animation updates via std::async (~2-5ms saved) - Thread-local collision scratch buffers for concurrent floor queries - Parallel terrain/WMO/M2 floor queries in camera controller - Seed new M2 instance bones from existing siblings to eliminate pop-in flash - Fix shadow flicker: snap center along stable light axes instead of in view space - Increase shadow distance default to 300 units (slider max 500)
This commit is contained in:
parent
a4966e486f
commit
4cb03c38fe
9 changed files with 160 additions and 87 deletions
|
|
@ -475,9 +475,7 @@ private:
|
||||||
static constexpr float SPATIAL_CELL_SIZE = 64.0f;
|
static constexpr float SPATIAL_CELL_SIZE = 64.0f;
|
||||||
std::unordered_map<GridCell, std::vector<uint32_t>, GridCellHash> spatialGrid;
|
std::unordered_map<GridCell, std::vector<uint32_t>, GridCellHash> spatialGrid;
|
||||||
std::unordered_map<uint32_t, size_t> instanceIndexById;
|
std::unordered_map<uint32_t, size_t> instanceIndexById;
|
||||||
mutable std::vector<size_t> candidateScratch;
|
// Collision scratch buffers are thread_local (see m2_renderer.cpp) for thread-safety.
|
||||||
mutable std::unordered_set<uint32_t> candidateIdScratch;
|
|
||||||
mutable std::vector<uint32_t> collisionTriScratch_;
|
|
||||||
|
|
||||||
// Collision query profiling (per frame).
|
// Collision query profiling (per frame).
|
||||||
mutable double queryTimeMs = 0.0;
|
mutable double queryTimeMs = 0.0;
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ private:
|
||||||
glm::vec3 shadowCenter = glm::vec3(0.0f);
|
glm::vec3 shadowCenter = glm::vec3(0.0f);
|
||||||
bool shadowCenterInitialized = false;
|
bool shadowCenterInitialized = false;
|
||||||
bool shadowsEnabled = true;
|
bool shadowsEnabled = true;
|
||||||
float shadowDistance_ = 72.0f; // Shadow frustum half-extent (default: 72 units)
|
float shadowDistance_ = 300.0f; // Shadow frustum half-extent (default: 300 units)
|
||||||
uint32_t shadowFrameCounter_ = 0;
|
uint32_t shadowFrameCounter_ = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -257,7 +257,7 @@ public:
|
||||||
|
|
||||||
void setShadowsEnabled(bool enabled) { shadowsEnabled = enabled; }
|
void setShadowsEnabled(bool enabled) { shadowsEnabled = enabled; }
|
||||||
bool areShadowsEnabled() const { return shadowsEnabled; }
|
bool areShadowsEnabled() const { return shadowsEnabled; }
|
||||||
void setShadowDistance(float dist) { shadowDistance_ = glm::clamp(dist, 40.0f, 200.0f); }
|
void setShadowDistance(float dist) { shadowDistance_ = glm::clamp(dist, 40.0f, 500.0f); }
|
||||||
float getShadowDistance() const { return shadowDistance_; }
|
float getShadowDistance() const { return shadowDistance_; }
|
||||||
void setMsaaSamples(VkSampleCountFlagBits samples);
|
void setMsaaSamples(VkSampleCountFlagBits samples);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -711,9 +711,7 @@ private:
|
||||||
static constexpr float SPATIAL_CELL_SIZE = 64.0f;
|
static constexpr float SPATIAL_CELL_SIZE = 64.0f;
|
||||||
std::unordered_map<GridCell, std::vector<uint32_t>, GridCellHash> spatialGrid;
|
std::unordered_map<GridCell, std::vector<uint32_t>, GridCellHash> spatialGrid;
|
||||||
std::unordered_map<uint32_t, size_t> instanceIndexById;
|
std::unordered_map<uint32_t, size_t> instanceIndexById;
|
||||||
mutable std::vector<size_t> candidateScratch;
|
// Collision scratch buffers are thread_local (see wmo_renderer.cpp) for thread-safety.
|
||||||
mutable std::vector<uint32_t> triScratch_; // Scratch for collision grid queries
|
|
||||||
mutable std::unordered_set<uint32_t> candidateIdScratch;
|
|
||||||
|
|
||||||
// Parallel visibility culling
|
// Parallel visibility culling
|
||||||
uint32_t numCullThreads_ = 1;
|
uint32_t numCullThreads_ = 1;
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ private:
|
||||||
bool pendingVsync = false;
|
bool pendingVsync = false;
|
||||||
int pendingResIndex = 0;
|
int pendingResIndex = 0;
|
||||||
bool pendingShadows = true;
|
bool pendingShadows = true;
|
||||||
float pendingShadowDistance = 72.0f;
|
float pendingShadowDistance = 300.0f;
|
||||||
bool pendingWaterRefraction = false;
|
bool pendingWaterRefraction = false;
|
||||||
int pendingMasterVolume = 100;
|
int pendingMasterVolume = 100;
|
||||||
int pendingMusicVolume = 30;
|
int pendingMusicVolume = 30;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "rendering/camera_controller.hpp"
|
#include "rendering/camera_controller.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <future>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include "rendering/terrain_manager.hpp"
|
#include "rendering/terrain_manager.hpp"
|
||||||
#include "rendering/wmo_renderer.hpp"
|
#include "rendering/wmo_renderer.hpp"
|
||||||
|
|
@ -808,25 +809,53 @@ void CameraController::update(float deltaTime) {
|
||||||
if (useCached) {
|
if (useCached) {
|
||||||
groundH = cachedFloorHeight_;
|
groundH = cachedFloorHeight_;
|
||||||
} else {
|
} else {
|
||||||
// Full collision check
|
// Full collision check — run terrain/WMO/M2 queries in parallel
|
||||||
std::optional<float> terrainH;
|
std::optional<float> terrainH;
|
||||||
std::optional<float> wmoH;
|
std::optional<float> wmoH;
|
||||||
std::optional<float> m2H;
|
std::optional<float> m2H;
|
||||||
if (terrainManager) {
|
|
||||||
terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y);
|
|
||||||
}
|
|
||||||
// When airborne, anchor probe to last ground level so the
|
// When airborne, anchor probe to last ground level so the
|
||||||
// ceiling doesn't rise with the jump and catch roof geometry.
|
// ceiling doesn't rise with the jump and catch roof geometry.
|
||||||
float wmoBaseZ = grounded ? std::max(targetPos.z, lastGroundZ) : lastGroundZ;
|
float wmoBaseZ = grounded ? std::max(targetPos.z, lastGroundZ) : lastGroundZ;
|
||||||
float wmoProbeZ = wmoBaseZ + stepUpBudget + 0.5f;
|
float wmoProbeZ = wmoBaseZ + stepUpBudget + 0.5f;
|
||||||
float wmoNormalZ = 1.0f;
|
float wmoNormalZ = 1.0f;
|
||||||
|
|
||||||
|
// Launch WMO + M2 floor queries asynchronously while terrain runs on this thread.
|
||||||
|
// Collision scratch buffers are thread_local so concurrent calls are safe.
|
||||||
|
using FloorResult = std::pair<std::optional<float>, float>;
|
||||||
|
std::future<FloorResult> wmoFuture;
|
||||||
|
std::future<FloorResult> m2Future;
|
||||||
|
bool wmoAsync = false, m2Async = false;
|
||||||
|
float px = targetPos.x, py = targetPos.y;
|
||||||
if (wmoRenderer) {
|
if (wmoRenderer) {
|
||||||
wmoH = wmoRenderer->getFloorHeight(targetPos.x, targetPos.y, wmoProbeZ, &wmoNormalZ);
|
wmoAsync = true;
|
||||||
|
wmoFuture = std::async(std::launch::async,
|
||||||
|
[this, px, py, wmoProbeZ]() -> FloorResult {
|
||||||
|
float nz = 1.0f;
|
||||||
|
auto h = wmoRenderer->getFloorHeight(px, py, wmoProbeZ, &nz);
|
||||||
|
return {h, nz};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (m2Renderer && !externalFollow_) {
|
if (m2Renderer && !externalFollow_) {
|
||||||
float m2NormalZ = 1.0f;
|
m2Async = true;
|
||||||
m2H = m2Renderer->getFloorHeight(targetPos.x, targetPos.y, wmoProbeZ, &m2NormalZ);
|
m2Future = std::async(std::launch::async,
|
||||||
if (m2H && m2NormalZ < MIN_WALKABLE_NORMAL_M2) {
|
[this, px, py, wmoProbeZ]() -> FloorResult {
|
||||||
|
float nz = 1.0f;
|
||||||
|
auto h = m2Renderer->getFloorHeight(px, py, wmoProbeZ, &nz);
|
||||||
|
return {h, nz};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (terrainManager) {
|
||||||
|
terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y);
|
||||||
|
}
|
||||||
|
if (wmoAsync) {
|
||||||
|
auto [h, nz] = wmoFuture.get();
|
||||||
|
wmoH = h;
|
||||||
|
wmoNormalZ = nz;
|
||||||
|
}
|
||||||
|
if (m2Async) {
|
||||||
|
auto [h, nz] = m2Future.get();
|
||||||
|
m2H = h;
|
||||||
|
if (m2H && nz < MIN_WALKABLE_NORMAL_M2) {
|
||||||
m2H = std::nullopt;
|
m2H = std::nullopt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,14 @@ glm::vec3 closestPointOnTriangle(const glm::vec3& p,
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// Thread-local scratch buffers for collision queries (allows concurrent getFloorHeight calls)
|
||||||
|
static thread_local std::vector<size_t> tl_m2_candidateScratch;
|
||||||
|
static thread_local std::unordered_set<uint32_t> tl_m2_candidateIdScratch;
|
||||||
|
static thread_local std::vector<uint32_t> tl_m2_collisionTriScratch;
|
||||||
|
|
||||||
|
// Forward declaration (defined after animation helpers)
|
||||||
|
static void computeBoneMatrices(const M2ModelGPU& model, M2Instance& instance);
|
||||||
|
|
||||||
void M2Instance::updateModelMatrix() {
|
void M2Instance::updateModelMatrix() {
|
||||||
modelMatrix = glm::mat4(1.0f);
|
modelMatrix = glm::mat4(1.0f);
|
||||||
modelMatrix = glm::translate(modelMatrix, position);
|
modelMatrix = glm::translate(modelMatrix, position);
|
||||||
|
|
@ -1673,6 +1681,21 @@ uint32_t M2Renderer::createInstance(uint32_t modelId, const glm::vec3& position,
|
||||||
instance.animDuration = static_cast<float>(mdl.sequences[0].duration);
|
instance.animDuration = static_cast<float>(mdl.sequences[0].duration);
|
||||||
instance.animTime = static_cast<float>(rand() % std::max(1u, mdl.sequences[0].duration));
|
instance.animTime = static_cast<float>(rand() % std::max(1u, mdl.sequences[0].duration));
|
||||||
instance.variationTimer = 3000.0f + static_cast<float>(rand() % 8000);
|
instance.variationTimer = 3000.0f + static_cast<float>(rand() % 8000);
|
||||||
|
|
||||||
|
// Seed bone matrices from an existing instance of the same model so the
|
||||||
|
// new instance renders immediately instead of being invisible until the
|
||||||
|
// next update() computes bones (prevents pop-in flash).
|
||||||
|
for (const auto& existing : instances) {
|
||||||
|
if (existing.modelId == modelId && !existing.boneMatrices.empty()) {
|
||||||
|
instance.boneMatrices = existing.boneMatrices;
|
||||||
|
instance.bonesDirty = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no sibling exists yet, compute bones immediately
|
||||||
|
if (instance.boneMatrices.empty()) {
|
||||||
|
computeBoneMatrices(mdlRef, instance);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register in dedup map before pushing (uses original position, not ground-adjusted)
|
// Register in dedup map before pushing (uses original position, not ground-adjusted)
|
||||||
|
|
@ -1764,6 +1787,18 @@ uint32_t M2Renderer::createInstanceWithMatrix(uint32_t modelId, const glm::mat4&
|
||||||
instance.animDuration = static_cast<float>(mdl2.sequences[0].duration);
|
instance.animDuration = static_cast<float>(mdl2.sequences[0].duration);
|
||||||
instance.animTime = static_cast<float>(rand() % std::max(1u, mdl2.sequences[0].duration));
|
instance.animTime = static_cast<float>(rand() % std::max(1u, mdl2.sequences[0].duration));
|
||||||
instance.variationTimer = 3000.0f + static_cast<float>(rand() % 8000);
|
instance.variationTimer = 3000.0f + static_cast<float>(rand() % 8000);
|
||||||
|
|
||||||
|
// Seed bone matrices from an existing sibling so the instance renders immediately
|
||||||
|
for (const auto& existing : instances) {
|
||||||
|
if (existing.modelId == modelId && !existing.boneMatrices.empty()) {
|
||||||
|
instance.boneMatrices = existing.boneMatrices;
|
||||||
|
instance.bonesDirty = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (instance.boneMatrices.empty()) {
|
||||||
|
computeBoneMatrices(mdl2, instance);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
instance.animTime = static_cast<float>(rand()) / RAND_MAX * 10000.0f;
|
instance.animTime = static_cast<float>(rand()) / RAND_MAX * 10000.0f;
|
||||||
}
|
}
|
||||||
|
|
@ -2380,12 +2415,15 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload bone matrices to SSBO if model has skeletal animation.
|
// Upload bone matrices to SSBO if model has skeletal animation.
|
||||||
// Bone buffers are pre-allocated by prepareRender() on the main thread.
|
// Skip animated instances entirely until bones are computed + buffers allocated
|
||||||
// If not yet allocated (race/timing), skip this instance entirely to avoid
|
// to prevent bind-pose/T-pose flash on first appearance.
|
||||||
// a bind-pose flash — it will render correctly next frame.
|
bool modelNeedsAnimation = model.hasAnimation && !model.disableAnimation;
|
||||||
bool needsBones = model.hasAnimation && !model.disableAnimation && !instance.boneMatrices.empty();
|
if (modelNeedsAnimation && instance.boneMatrices.empty()) {
|
||||||
|
continue; // Bones not yet computed — skip to avoid bind-pose flash
|
||||||
|
}
|
||||||
|
bool needsBones = modelNeedsAnimation && !instance.boneMatrices.empty();
|
||||||
if (needsBones && (!instance.boneBuffer[frameIndex] || !instance.boneSet[frameIndex])) {
|
if (needsBones && (!instance.boneBuffer[frameIndex] || !instance.boneSet[frameIndex])) {
|
||||||
continue;
|
continue; // Bone buffers not yet allocated — skip to avoid bind-pose flash
|
||||||
}
|
}
|
||||||
bool useBones = needsBones;
|
bool useBones = needsBones;
|
||||||
if (useBones) {
|
if (useBones) {
|
||||||
|
|
@ -3620,7 +3658,7 @@ void M2Renderer::rebuildSpatialIndex() {
|
||||||
void M2Renderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& queryMax,
|
void M2Renderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& queryMax,
|
||||||
std::vector<size_t>& outIndices) const {
|
std::vector<size_t>& outIndices) const {
|
||||||
outIndices.clear();
|
outIndices.clear();
|
||||||
candidateIdScratch.clear();
|
tl_m2_candidateIdScratch.clear();
|
||||||
|
|
||||||
GridCell minCell = toCell(queryMin);
|
GridCell minCell = toCell(queryMin);
|
||||||
GridCell maxCell = toCell(queryMax);
|
GridCell maxCell = toCell(queryMax);
|
||||||
|
|
@ -3630,7 +3668,7 @@ void M2Renderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& qu
|
||||||
auto it = spatialGrid.find(GridCell{x, y, z});
|
auto it = spatialGrid.find(GridCell{x, y, z});
|
||||||
if (it == spatialGrid.end()) continue;
|
if (it == spatialGrid.end()) continue;
|
||||||
for (uint32_t id : it->second) {
|
for (uint32_t id : it->second) {
|
||||||
if (!candidateIdScratch.insert(id).second) continue;
|
if (!tl_m2_candidateIdScratch.insert(id).second) continue;
|
||||||
auto idxIt = instanceIndexById.find(id);
|
auto idxIt = instanceIndexById.find(id);
|
||||||
if (idxIt != instanceIndexById.end()) {
|
if (idxIt != instanceIndexById.end()) {
|
||||||
outIndices.push_back(idxIt->second);
|
outIndices.push_back(idxIt->second);
|
||||||
|
|
@ -3803,9 +3841,9 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ,
|
||||||
|
|
||||||
glm::vec3 queryMin(glX - 2.0f, glY - 2.0f, glZ - 6.0f);
|
glm::vec3 queryMin(glX - 2.0f, glY - 2.0f, glZ - 6.0f);
|
||||||
glm::vec3 queryMax(glX + 2.0f, glY + 2.0f, glZ + 8.0f);
|
glm::vec3 queryMax(glX + 2.0f, glY + 2.0f, glZ + 8.0f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_m2_candidateScratch);
|
||||||
|
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_m2_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (collisionFocusEnabled &&
|
if (collisionFocusEnabled &&
|
||||||
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
||||||
|
|
@ -3827,14 +3865,14 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ,
|
||||||
model.collision.getFloorTrisInRange(
|
model.collision.getFloorTrisInRange(
|
||||||
localPos.x - 1.0f, localPos.y - 1.0f,
|
localPos.x - 1.0f, localPos.y - 1.0f,
|
||||||
localPos.x + 1.0f, localPos.y + 1.0f,
|
localPos.x + 1.0f, localPos.y + 1.0f,
|
||||||
collisionTriScratch_);
|
tl_m2_collisionTriScratch);
|
||||||
|
|
||||||
glm::vec3 rayOrigin(localPos.x, localPos.y, localPos.z + 5.0f);
|
glm::vec3 rayOrigin(localPos.x, localPos.y, localPos.z + 5.0f);
|
||||||
glm::vec3 rayDir(0.0f, 0.0f, -1.0f);
|
glm::vec3 rayDir(0.0f, 0.0f, -1.0f);
|
||||||
float bestHitZ = -std::numeric_limits<float>::max();
|
float bestHitZ = -std::numeric_limits<float>::max();
|
||||||
bool hitAny = false;
|
bool hitAny = false;
|
||||||
|
|
||||||
for (uint32_t ti : collisionTriScratch_) {
|
for (uint32_t ti : tl_m2_collisionTriScratch) {
|
||||||
if (ti >= model.collision.triCount) continue;
|
if (ti >= model.collision.triCount) continue;
|
||||||
if (model.collision.triBounds[ti].maxZ < localPos.z - 10.0f ||
|
if (model.collision.triBounds[ti].maxZ < localPos.z - 10.0f ||
|
||||||
model.collision.triBounds[ti].minZ > localPos.z + 5.0f) continue;
|
model.collision.triBounds[ti].minZ > localPos.z + 5.0f) continue;
|
||||||
|
|
@ -3949,10 +3987,10 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
|
|
||||||
glm::vec3 queryMin = glm::min(from, to) - glm::vec3(7.0f, 7.0f, 5.0f);
|
glm::vec3 queryMin = glm::min(from, to) - glm::vec3(7.0f, 7.0f, 5.0f);
|
||||||
glm::vec3 queryMax = glm::max(from, to) + glm::vec3(7.0f, 7.0f, 5.0f);
|
glm::vec3 queryMax = glm::max(from, to) + glm::vec3(7.0f, 7.0f, 5.0f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_m2_candidateScratch);
|
||||||
|
|
||||||
// Check against all M2 instances in local space (rotation-aware).
|
// Check against all M2 instances in local space (rotation-aware).
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_m2_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (collisionFocusEnabled &&
|
if (collisionFocusEnabled &&
|
||||||
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
||||||
|
|
@ -3985,14 +4023,14 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
std::min(localFrom.y, localPos.y) - localRadius - 1.0f,
|
std::min(localFrom.y, localPos.y) - localRadius - 1.0f,
|
||||||
std::max(localFrom.x, localPos.x) + localRadius + 1.0f,
|
std::max(localFrom.x, localPos.x) + localRadius + 1.0f,
|
||||||
std::max(localFrom.y, localPos.y) + localRadius + 1.0f,
|
std::max(localFrom.y, localPos.y) + localRadius + 1.0f,
|
||||||
collisionTriScratch_);
|
tl_m2_collisionTriScratch);
|
||||||
|
|
||||||
constexpr float PLAYER_HEIGHT = 2.0f;
|
constexpr float PLAYER_HEIGHT = 2.0f;
|
||||||
constexpr float MAX_TOTAL_PUSH = 0.02f; // Cap total push per instance
|
constexpr float MAX_TOTAL_PUSH = 0.02f; // Cap total push per instance
|
||||||
bool pushed = false;
|
bool pushed = false;
|
||||||
float totalPushX = 0.0f, totalPushY = 0.0f;
|
float totalPushX = 0.0f, totalPushY = 0.0f;
|
||||||
|
|
||||||
for (uint32_t ti : collisionTriScratch_) {
|
for (uint32_t ti : tl_m2_collisionTriScratch) {
|
||||||
if (ti >= model.collision.triCount) continue;
|
if (ti >= model.collision.triCount) continue;
|
||||||
if (localPos.z + PLAYER_HEIGHT < model.collision.triBounds[ti].minZ ||
|
if (localPos.z + PLAYER_HEIGHT < model.collision.triBounds[ti].minZ ||
|
||||||
localPos.z > model.collision.triBounds[ti].maxZ) continue;
|
localPos.z > model.collision.triBounds[ti].maxZ) continue;
|
||||||
|
|
@ -4190,9 +4228,9 @@ float M2Renderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3&
|
||||||
glm::vec3 rayEnd = origin + direction * maxDistance;
|
glm::vec3 rayEnd = origin + direction * maxDistance;
|
||||||
glm::vec3 queryMin = glm::min(origin, rayEnd) - glm::vec3(1.0f);
|
glm::vec3 queryMin = glm::min(origin, rayEnd) - glm::vec3(1.0f);
|
||||||
glm::vec3 queryMax = glm::max(origin, rayEnd) + glm::vec3(1.0f);
|
glm::vec3 queryMax = glm::max(origin, rayEnd) + glm::vec3(1.0f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_m2_candidateScratch);
|
||||||
|
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_m2_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (collisionFocusEnabled &&
|
if (collisionFocusEnabled &&
|
||||||
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <future>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace rendering {
|
namespace rendering {
|
||||||
|
|
@ -2678,16 +2679,23 @@ void Renderer::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Update character animations
|
// Launch M2 doodad animation on background thread (overlaps with character animation + audio)
|
||||||
|
std::future<void> m2AnimFuture;
|
||||||
|
bool m2AnimLaunched = false;
|
||||||
|
if (m2Renderer && camera) {
|
||||||
|
float m2DeltaTime = deltaTime;
|
||||||
|
glm::vec3 m2CamPos = camera->getPosition();
|
||||||
|
glm::mat4 m2ViewProj = camera->getProjectionMatrix() * camera->getViewMatrix();
|
||||||
|
m2AnimFuture = std::async(std::launch::async,
|
||||||
|
[this, m2DeltaTime, m2CamPos, m2ViewProj]() {
|
||||||
|
m2Renderer->update(m2DeltaTime, m2CamPos, m2ViewProj);
|
||||||
|
});
|
||||||
|
m2AnimLaunched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update character animations (runs in parallel with M2 animation above)
|
||||||
if (characterRenderer && camera) {
|
if (characterRenderer && camera) {
|
||||||
auto charAnimStart = std::chrono::steady_clock::now();
|
|
||||||
characterRenderer->update(deltaTime, camera->getPosition());
|
characterRenderer->update(deltaTime, camera->getPosition());
|
||||||
float charAnimMs = std::chrono::duration<float, std::milli>(
|
|
||||||
std::chrono::steady_clock::now() - charAnimStart).count();
|
|
||||||
if (charAnimMs > 5.0f) {
|
|
||||||
LOG_WARNING("SLOW characterRenderer->update: ", charAnimMs, "ms (",
|
|
||||||
characterRenderer->getInstanceCount(), " instances)");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update AudioEngine (cleanup finished sounds, etc.)
|
// Update AudioEngine (cleanup finished sounds, etc.)
|
||||||
|
|
@ -2872,17 +2880,9 @@ void Renderer::update(float deltaTime) {
|
||||||
ambientSoundManager->update(deltaTime, camPos, isIndoor, isSwimming, isBlacksmith);
|
ambientSoundManager->update(deltaTime, camPos, isIndoor, isSwimming, isBlacksmith);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update M2 doodad animations (pass camera for frustum-culling bone computation)
|
// Wait for M2 doodad animation to finish (was launched earlier in parallel with character anim)
|
||||||
if (m2Renderer && camera) {
|
if (m2AnimLaunched) {
|
||||||
auto m2Start = std::chrono::steady_clock::now();
|
m2AnimFuture.get();
|
||||||
m2Renderer->update(deltaTime, camera->getPosition(),
|
|
||||||
camera->getProjectionMatrix() * camera->getViewMatrix());
|
|
||||||
float m2Ms = std::chrono::duration<float, std::milli>(
|
|
||||||
std::chrono::steady_clock::now() - m2Start).count();
|
|
||||||
if (m2Ms > 3.0f) {
|
|
||||||
LOG_WARNING("SLOW m2Renderer->update: ", m2Ms, "ms (",
|
|
||||||
m2Renderer->getInstanceCount(), " instances)");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: play zone music, dispatching local files (file: prefix) vs MPQ paths
|
// Helper: play zone music, dispatching local files (file: prefix) vs MPQ paths
|
||||||
|
|
@ -4338,27 +4338,32 @@ glm::mat4 Renderer::computeLightSpaceMatrix() {
|
||||||
shadowCenter = desiredCenter;
|
shadowCenter = desiredCenter;
|
||||||
glm::vec3 center = shadowCenter;
|
glm::vec3 center = shadowCenter;
|
||||||
|
|
||||||
// Snap to shadow texel grid to keep projection stable while moving.
|
// Snap shadow frustum to texel grid so the projection is perfectly stable
|
||||||
|
// while moving. We compute the light's right/up axes from the sun direction
|
||||||
|
// (these are constant per frame regardless of center) and snap center along
|
||||||
|
// them before building the view matrix.
|
||||||
float halfExtent = kShadowHalfExtent;
|
float halfExtent = kShadowHalfExtent;
|
||||||
float texelWorld = (2.0f * halfExtent) / static_cast<float>(SHADOW_MAP_SIZE);
|
float texelWorld = (2.0f * halfExtent) / static_cast<float>(SHADOW_MAP_SIZE);
|
||||||
|
|
||||||
// Build light view to get stable axes
|
// Stable light-space axes (independent of center position)
|
||||||
glm::vec3 up(0.0f, 0.0f, 1.0f);
|
glm::vec3 up(0.0f, 0.0f, 1.0f);
|
||||||
// If sunDir is nearly parallel to up, pick a different up vector
|
|
||||||
if (std::abs(glm::dot(sunDir, up)) > 0.99f) {
|
if (std::abs(glm::dot(sunDir, up)) > 0.99f) {
|
||||||
up = glm::vec3(0.0f, 1.0f, 0.0f);
|
up = glm::vec3(0.0f, 1.0f, 0.0f);
|
||||||
}
|
}
|
||||||
glm::mat4 lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up);
|
glm::vec3 lightRight = glm::normalize(glm::cross(sunDir, up));
|
||||||
|
glm::vec3 lightUp = glm::normalize(glm::cross(lightRight, sunDir));
|
||||||
|
|
||||||
// Stable texel snapping in light space removes movement shimmer.
|
// Snap center along light's right and up axes to align with texel grid.
|
||||||
glm::vec4 centerLS = lightView * glm::vec4(center, 1.0f);
|
// This eliminates sub-texel shifts that cause shadow shimmer.
|
||||||
centerLS.x = std::round(centerLS.x / texelWorld) * texelWorld;
|
float dotR = glm::dot(center, lightRight);
|
||||||
centerLS.y = std::round(centerLS.y / texelWorld) * texelWorld;
|
float dotU = glm::dot(center, lightUp);
|
||||||
glm::vec4 snappedCenter = glm::inverse(lightView) * centerLS;
|
dotR = std::floor(dotR / texelWorld) * texelWorld;
|
||||||
center = glm::vec3(snappedCenter);
|
dotU = std::floor(dotU / texelWorld) * texelWorld;
|
||||||
|
float dotD = glm::dot(center, sunDir); // depth axis unchanged
|
||||||
|
center = lightRight * dotR + lightUp * dotU + sunDir * dotD;
|
||||||
shadowCenter = center;
|
shadowCenter = center;
|
||||||
lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up);
|
|
||||||
|
|
||||||
|
glm::mat4 lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up);
|
||||||
glm::mat4 lightProj = glm::ortho(-halfExtent, halfExtent, -halfExtent, halfExtent,
|
glm::mat4 lightProj = glm::ortho(-halfExtent, halfExtent, -halfExtent, halfExtent,
|
||||||
kShadowNearPlane, kShadowFarPlane);
|
kShadowNearPlane, kShadowFarPlane);
|
||||||
lightProj[1][1] *= -1.0f; // Vulkan Y-flip for shadow pass
|
lightProj[1][1] *= -1.0f; // Vulkan Y-flip for shadow pass
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,11 @@ size_t envSizeOrDefault(const char* name, size_t defValue) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// Thread-local scratch buffers for collision queries (allows concurrent getFloorHeight/checkWallCollision calls)
|
||||||
|
static thread_local std::vector<size_t> tl_candidateScratch;
|
||||||
|
static thread_local std::vector<uint32_t> tl_triScratch;
|
||||||
|
static thread_local std::unordered_set<uint32_t> tl_candidateIdScratch;
|
||||||
|
|
||||||
static void transformAABB(const glm::mat4& modelMatrix,
|
static void transformAABB(const glm::mat4& modelMatrix,
|
||||||
const glm::vec3& localMin,
|
const glm::vec3& localMin,
|
||||||
const glm::vec3& localMax,
|
const glm::vec3& localMax,
|
||||||
|
|
@ -1288,7 +1293,7 @@ void WMORenderer::rebuildSpatialIndex() {
|
||||||
void WMORenderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& queryMax,
|
void WMORenderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& queryMax,
|
||||||
std::vector<size_t>& outIndices) const {
|
std::vector<size_t>& outIndices) const {
|
||||||
outIndices.clear();
|
outIndices.clear();
|
||||||
candidateIdScratch.clear();
|
tl_candidateIdScratch.clear();
|
||||||
|
|
||||||
GridCell minCell = toCell(queryMin);
|
GridCell minCell = toCell(queryMin);
|
||||||
GridCell maxCell = toCell(queryMax);
|
GridCell maxCell = toCell(queryMax);
|
||||||
|
|
@ -1298,7 +1303,7 @@ void WMORenderer::gatherCandidates(const glm::vec3& queryMin, const glm::vec3& q
|
||||||
auto it = spatialGrid.find(GridCell{x, y, z});
|
auto it = spatialGrid.find(GridCell{x, y, z});
|
||||||
if (it == spatialGrid.end()) continue;
|
if (it == spatialGrid.end()) continue;
|
||||||
for (uint32_t id : it->second) {
|
for (uint32_t id : it->second) {
|
||||||
if (!candidateIdScratch.insert(id).second) continue;
|
if (!tl_candidateIdScratch.insert(id).second) continue;
|
||||||
auto idxIt = instanceIndexById.find(id);
|
auto idxIt = instanceIndexById.find(id);
|
||||||
if (idxIt != instanceIndexById.end()) {
|
if (idxIt != instanceIndexById.end()) {
|
||||||
outIndices.push_back(idxIt->second);
|
outIndices.push_back(idxIt->second);
|
||||||
|
|
@ -2830,9 +2835,9 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
||||||
group.getTrianglesInRange(
|
group.getTrianglesInRange(
|
||||||
localOrigin.x - 1.0f, localOrigin.y - 1.0f,
|
localOrigin.x - 1.0f, localOrigin.y - 1.0f,
|
||||||
localOrigin.x + 1.0f, localOrigin.y + 1.0f,
|
localOrigin.x + 1.0f, localOrigin.y + 1.0f,
|
||||||
triScratch_);
|
tl_triScratch);
|
||||||
|
|
||||||
for (uint32_t triStart : triScratch_) {
|
for (uint32_t triStart : tl_triScratch) {
|
||||||
const glm::vec3& v0 = verts[indices[triStart]];
|
const glm::vec3& v0 = verts[indices[triStart]];
|
||||||
const glm::vec3& v1 = verts[indices[triStart + 1]];
|
const glm::vec3& v1 = verts[indices[triStart + 1]];
|
||||||
const glm::vec3& v2 = verts[indices[triStart + 2]];
|
const glm::vec3& v2 = verts[indices[triStart + 2]];
|
||||||
|
|
@ -2906,9 +2911,9 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
||||||
// early-returned because overlapping WMO instances need full coverage).
|
// early-returned because overlapping WMO instances need full coverage).
|
||||||
glm::vec3 queryMin(glX - 2.0f, glY - 2.0f, glZ - 8.0f);
|
glm::vec3 queryMin(glX - 2.0f, glY - 2.0f, glZ - 8.0f);
|
||||||
glm::vec3 queryMax(glX + 2.0f, glY + 2.0f, glZ + 10.0f);
|
glm::vec3 queryMax(glX + 2.0f, glY + 2.0f, glZ + 10.0f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_candidateScratch);
|
||||||
|
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (collisionFocusEnabled &&
|
if (collisionFocusEnabled &&
|
||||||
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
||||||
|
|
@ -3081,9 +3086,9 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
|
|
||||||
glm::vec3 queryMin = glm::min(from, to) - glm::vec3(8.0f, 8.0f, 5.0f);
|
glm::vec3 queryMin = glm::min(from, to) - glm::vec3(8.0f, 8.0f, 5.0f);
|
||||||
glm::vec3 queryMax = glm::max(from, to) + glm::vec3(8.0f, 8.0f, 5.0f);
|
glm::vec3 queryMax = glm::max(from, to) + glm::vec3(8.0f, 8.0f, 5.0f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_candidateScratch);
|
||||||
|
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (collisionFocusEnabled &&
|
if (collisionFocusEnabled &&
|
||||||
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
||||||
|
|
@ -3149,9 +3154,9 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
||||||
float rangeMinY = std::min(localFrom.y, localTo.y) - PLAYER_RADIUS - 1.5f;
|
float rangeMinY = std::min(localFrom.y, localTo.y) - PLAYER_RADIUS - 1.5f;
|
||||||
float rangeMaxX = std::max(localFrom.x, localTo.x) + PLAYER_RADIUS + 1.5f;
|
float rangeMaxX = std::max(localFrom.x, localTo.x) + PLAYER_RADIUS + 1.5f;
|
||||||
float rangeMaxY = std::max(localFrom.y, localTo.y) + PLAYER_RADIUS + 1.5f;
|
float rangeMaxY = std::max(localFrom.y, localTo.y) + PLAYER_RADIUS + 1.5f;
|
||||||
group.getTrianglesInRange(rangeMinX, rangeMinY, rangeMaxX, rangeMaxY, triScratch_);
|
group.getTrianglesInRange(rangeMinX, rangeMinY, rangeMaxX, rangeMaxY, tl_triScratch);
|
||||||
|
|
||||||
for (uint32_t triStart : triScratch_) {
|
for (uint32_t triStart : tl_triScratch) {
|
||||||
// Use pre-computed Z bounds for fast vertical reject
|
// Use pre-computed Z bounds for fast vertical reject
|
||||||
const auto& tb = group.triBounds[triStart / 3];
|
const auto& tb = group.triBounds[triStart / 3];
|
||||||
|
|
||||||
|
|
@ -3319,9 +3324,9 @@ void WMORenderer::updateActiveGroup(float glX, float glY, float glZ) {
|
||||||
|
|
||||||
glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f);
|
glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f);
|
||||||
glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f);
|
glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_candidateScratch);
|
||||||
|
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x ||
|
if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x ||
|
||||||
glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y ||
|
glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y ||
|
||||||
|
|
@ -3365,9 +3370,9 @@ bool WMORenderer::isInsideWMO(float glX, float glY, float glZ, uint32_t* outMode
|
||||||
QueryTimer timer(&queryTimeMs, &queryCallCount);
|
QueryTimer timer(&queryTimeMs, &queryCallCount);
|
||||||
glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f);
|
glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f);
|
||||||
glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f);
|
glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_candidateScratch);
|
||||||
|
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (collisionFocusEnabled &&
|
if (collisionFocusEnabled &&
|
||||||
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
||||||
|
|
@ -3414,9 +3419,9 @@ bool WMORenderer::isInsideWMO(float glX, float glY, float glZ, uint32_t* outMode
|
||||||
bool WMORenderer::isInsideInteriorWMO(float glX, float glY, float glZ) const {
|
bool WMORenderer::isInsideInteriorWMO(float glX, float glY, float glZ) const {
|
||||||
glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f);
|
glm::vec3 queryMin(glX - 0.5f, glY - 0.5f, glZ - 0.5f);
|
||||||
glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f);
|
glm::vec3 queryMax(glX + 0.5f, glY + 0.5f, glZ + 0.5f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_candidateScratch);
|
||||||
|
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (collisionFocusEnabled &&
|
if (collisionFocusEnabled &&
|
||||||
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
||||||
|
|
@ -3470,9 +3475,9 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3
|
||||||
glm::vec3 rayEnd = origin + direction * maxDistance;
|
glm::vec3 rayEnd = origin + direction * maxDistance;
|
||||||
glm::vec3 queryMin = glm::min(origin, rayEnd) - glm::vec3(1.0f);
|
glm::vec3 queryMin = glm::min(origin, rayEnd) - glm::vec3(1.0f);
|
||||||
glm::vec3 queryMax = glm::max(origin, rayEnd) + glm::vec3(1.0f);
|
glm::vec3 queryMax = glm::max(origin, rayEnd) + glm::vec3(1.0f);
|
||||||
gatherCandidates(queryMin, queryMax, candidateScratch);
|
gatherCandidates(queryMin, queryMax, tl_candidateScratch);
|
||||||
|
|
||||||
for (size_t idx : candidateScratch) {
|
for (size_t idx : tl_candidateScratch) {
|
||||||
const auto& instance = instances[idx];
|
const auto& instance = instances[idx];
|
||||||
if (collisionFocusEnabled &&
|
if (collisionFocusEnabled &&
|
||||||
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
pointAABBDistanceSq(collisionFocusPos, instance.worldBoundsMin, instance.worldBoundsMax) > collisionFocusRadiusSq) {
|
||||||
|
|
@ -3526,9 +3531,9 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3
|
||||||
float rMinY = std::min(localOrigin.y, localEnd.y) - 1.0f;
|
float rMinY = std::min(localOrigin.y, localEnd.y) - 1.0f;
|
||||||
float rMaxX = std::max(localOrigin.x, localEnd.x) + 1.0f;
|
float rMaxX = std::max(localOrigin.x, localEnd.x) + 1.0f;
|
||||||
float rMaxY = std::max(localOrigin.y, localEnd.y) + 1.0f;
|
float rMaxY = std::max(localOrigin.y, localEnd.y) + 1.0f;
|
||||||
group.getWallTrianglesInRange(rMinX, rMinY, rMaxX, rMaxY, triScratch_);
|
group.getWallTrianglesInRange(rMinX, rMinY, rMaxX, rMaxY, tl_triScratch);
|
||||||
|
|
||||||
for (uint32_t triStart : triScratch_) {
|
for (uint32_t triStart : tl_triScratch) {
|
||||||
const glm::vec3& v0 = verts[indices[triStart]];
|
const glm::vec3& v0 = verts[indices[triStart]];
|
||||||
const glm::vec3& v1 = verts[indices[triStart + 1]];
|
const glm::vec3& v1 = verts[indices[triStart + 1]];
|
||||||
const glm::vec3& v2 = verts[indices[triStart + 2]];
|
const glm::vec3& v2 = verts[indices[triStart + 2]];
|
||||||
|
|
|
||||||
|
|
@ -6270,7 +6270,7 @@ void GameScreen::renderSettingsWindow() {
|
||||||
if (pendingShadows) {
|
if (pendingShadows) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetNextItemWidth(150.0f);
|
ImGui::SetNextItemWidth(150.0f);
|
||||||
if (ImGui::SliderFloat("Distance##shadow", &pendingShadowDistance, 40.0f, 200.0f, "%.0f")) {
|
if (ImGui::SliderFloat("Distance##shadow", &pendingShadowDistance, 40.0f, 500.0f, "%.0f")) {
|
||||||
if (renderer) renderer->setShadowDistance(pendingShadowDistance);
|
if (renderer) renderer->setShadowDistance(pendingShadowDistance);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
@ -6387,7 +6387,7 @@ void GameScreen::renderSettingsWindow() {
|
||||||
pendingFullscreen = kDefaultFullscreen;
|
pendingFullscreen = kDefaultFullscreen;
|
||||||
pendingVsync = kDefaultVsync;
|
pendingVsync = kDefaultVsync;
|
||||||
pendingShadows = kDefaultShadows;
|
pendingShadows = kDefaultShadows;
|
||||||
pendingShadowDistance = 72.0f;
|
pendingShadowDistance = 300.0f;
|
||||||
pendingGroundClutterDensity = kDefaultGroundClutterDensity;
|
pendingGroundClutterDensity = kDefaultGroundClutterDensity;
|
||||||
pendingAntiAliasing = 0;
|
pendingAntiAliasing = 0;
|
||||||
pendingNormalMapping = true;
|
pendingNormalMapping = true;
|
||||||
|
|
@ -7505,7 +7505,7 @@ void GameScreen::loadSettings() {
|
||||||
else if (key == "auto_loot") pendingAutoLoot = (std::stoi(val) != 0);
|
else if (key == "auto_loot") pendingAutoLoot = (std::stoi(val) != 0);
|
||||||
else if (key == "ground_clutter_density") pendingGroundClutterDensity = std::clamp(std::stoi(val), 0, 150);
|
else if (key == "ground_clutter_density") pendingGroundClutterDensity = std::clamp(std::stoi(val), 0, 150);
|
||||||
else if (key == "shadows") pendingShadows = (std::stoi(val) != 0);
|
else if (key == "shadows") pendingShadows = (std::stoi(val) != 0);
|
||||||
else if (key == "shadow_distance") pendingShadowDistance = std::clamp(std::stof(val), 40.0f, 200.0f);
|
else if (key == "shadow_distance") pendingShadowDistance = std::clamp(std::stof(val), 40.0f, 500.0f);
|
||||||
else if (key == "water_refraction") pendingWaterRefraction = (std::stoi(val) != 0);
|
else if (key == "water_refraction") pendingWaterRefraction = (std::stoi(val) != 0);
|
||||||
else if (key == "antialiasing") pendingAntiAliasing = std::clamp(std::stoi(val), 0, 3);
|
else if (key == "antialiasing") pendingAntiAliasing = std::clamp(std::stoi(val), 0, 3);
|
||||||
else if (key == "normal_mapping") pendingNormalMapping = (std::stoi(val) != 0);
|
else if (key == "normal_mapping") pendingNormalMapping = (std::stoi(val) != 0);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue