chore(refactor): god-object decomposition and mega-file splits

Split all mega-files by single-responsibility concern and
partially extracting AudioCoordinator and
OverlaySystem from the Renderer facade. No behavioral changes.

Splits:
- game_handler.cpp (5,247 LOC) → core + callbacks + packets (3 files)
- world_packets.cpp (4,453 LOC) → economy/entity/social/world (4 files)
- game_screen.cpp  (5,786 LOC) → core + frames + hud + minimap (4 files)
- m2_renderer.cpp  (3,343 LOC) → core + instance + particles + render (4 files)
- chat_panel.cpp   (3,140 LOC) → core + commands + utils (3 files)
- entity_spawner.cpp (2,750 LOC) → core + player + processing (3 files)

Extractions:
- AudioCoordinator: include/audio/ + src/audio/ (owned by Renderer)
- OverlaySystem: include/rendering/ + src/rendering/overlay_system.*

CMakeLists.txt: registered all 17 new translation units.
Related handler/callback files: minor include fixups post-split.
This commit is contained in:
Paul 2026-04-05 19:30:44 +03:00
parent 6dcc06697b
commit 34c0e3ca28
49 changed files with 29113 additions and 28109 deletions

View file

@ -0,0 +1,364 @@
// m2_renderer_internal.h — shared helpers for the m2_renderer split files.
// All functions are inline to allow inclusion in multiple translation units.
#pragma once
#include "rendering/m2_renderer.hpp"
#include "pipeline/m2_loader.hpp"
#include "core/profiler.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/quaternion.hpp>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <random>
#include <unordered_set>
#include <vector>
namespace wowee {
namespace rendering {
namespace m2_internal {
// ---- RNG helpers ----
inline std::mt19937& rng() {
static std::mt19937 gen(std::random_device{}());
return gen;
}
inline uint32_t randRange(uint32_t maxExclusive) {
if (maxExclusive == 0) return 0;
return std::uniform_int_distribution<uint32_t>(0, maxExclusive - 1)(rng());
}
inline float randFloat(float lo, float hi) {
return std::uniform_real_distribution<float>(lo, hi)(rng());
}
// ---- Constants ----
inline const auto kLavaAnimStart = std::chrono::steady_clock::now();
inline constexpr uint32_t kParticleFlagRandomized = 0x40;
inline constexpr uint32_t kParticleFlagTiled = 0x80;
inline constexpr float kSmokeEmitInterval = 1.0f / 48.0f;
// ---- Geometry / collision helpers ----
inline float computeGroundDetailDownOffset(const M2ModelGPU& model, float scale) {
const float pivotComp = glm::clamp(std::max(0.0f, model.boundMin.z * scale), 0.0f, 0.10f);
const float terrainSink = 0.03f;
return pivotComp + terrainSink;
}
inline void getTightCollisionBounds(const M2ModelGPU& model, glm::vec3& outMin, glm::vec3& outMax) {
glm::vec3 center = (model.boundMin + model.boundMax) * 0.5f;
glm::vec3 half = (model.boundMax - model.boundMin) * 0.5f;
if (model.collisionTreeTrunk) {
float modelHoriz = std::max(model.boundMax.x - model.boundMin.x,
model.boundMax.y - model.boundMin.y);
float trunkHalf = std::clamp(modelHoriz * 0.05f, 0.5f, 5.0f);
half.x = trunkHalf;
half.y = trunkHalf;
half.z = std::min(trunkHalf * 2.5f, 3.5f);
center.z = model.boundMin.z + half.z;
} else if (model.collisionNarrowVerticalProp) {
half.x *= 0.30f;
half.y *= 0.30f;
half.z *= 0.96f;
} else if (model.collisionSmallSolidProp) {
half.x *= 1.00f;
half.y *= 1.00f;
half.z *= 1.00f;
} else if (model.collisionSteppedLowPlatform) {
half.x *= 0.98f;
half.y *= 0.98f;
half.z *= 0.52f;
} else {
half.x *= 0.66f;
half.y *= 0.66f;
half.z *= 0.76f;
}
outMin = center - half;
outMax = center + half;
}
inline float getEffectiveCollisionTopLocal(const M2ModelGPU& model,
const glm::vec3& localPos,
const glm::vec3& localMin,
const glm::vec3& localMax) {
if (!model.collisionSteppedFountain && !model.collisionSteppedLowPlatform) {
return localMax.z;
}
glm::vec2 center((localMin.x + localMax.x) * 0.5f, (localMin.y + localMax.y) * 0.5f);
glm::vec2 half((localMax.x - localMin.x) * 0.5f, (localMax.y - localMin.y) * 0.5f);
if (half.x < 1e-4f || half.y < 1e-4f) {
return localMax.z;
}
float nx = (localPos.x - center.x) / half.x;
float ny = (localPos.y - center.y) / half.y;
float r = std::sqrt(nx * nx + ny * ny);
float h = localMax.z - localMin.z;
if (model.collisionSteppedFountain) {
if (r > 0.85f) return localMin.z + h * 0.18f;
if (r > 0.65f) return localMin.z + h * 0.36f;
if (r > 0.45f) return localMin.z + h * 0.54f;
if (r > 0.28f) return localMin.z + h * 0.70f;
if (r > 0.14f) return localMin.z + h * 0.84f;
return localMin.z + h * 0.96f;
}
float edge = std::max(std::abs(nx), std::abs(ny));
if (edge > 0.92f) return localMin.z + h * 0.06f;
if (edge > 0.72f) return localMin.z + h * 0.30f;
return localMin.z + h * 0.62f;
}
inline bool segmentIntersectsAABB(const glm::vec3& from, const glm::vec3& to,
const glm::vec3& bmin, const glm::vec3& bmax,
float& outEnterT) {
glm::vec3 d = to - from;
float tEnter = 0.0f;
float tExit = 1.0f;
for (int axis = 0; axis < 3; axis++) {
if (std::abs(d[axis]) < 1e-6f) {
if (from[axis] < bmin[axis] || from[axis] > bmax[axis]) {
return false;
}
continue;
}
float inv = 1.0f / d[axis];
float t0 = (bmin[axis] - from[axis]) * inv;
float t1 = (bmax[axis] - from[axis]) * inv;
if (t0 > t1) std::swap(t0, t1);
tEnter = std::max(tEnter, t0);
tExit = std::min(tExit, t1);
if (tEnter > tExit) return false;
}
outEnterT = tEnter;
return tExit >= 0.0f && tEnter <= 1.0f;
}
inline void transformAABB(const glm::mat4& modelMatrix,
const glm::vec3& localMin,
const glm::vec3& localMax,
glm::vec3& outMin,
glm::vec3& outMax) {
const glm::vec3 corners[8] = {
{localMin.x, localMin.y, localMin.z},
{localMin.x, localMin.y, localMax.z},
{localMin.x, localMax.y, localMin.z},
{localMin.x, localMax.y, localMax.z},
{localMax.x, localMin.y, localMin.z},
{localMax.x, localMin.y, localMax.z},
{localMax.x, localMax.y, localMin.z},
{localMax.x, localMax.y, localMax.z}
};
outMin = glm::vec3(std::numeric_limits<float>::max());
outMax = glm::vec3(-std::numeric_limits<float>::max());
for (const auto& c : corners) {
glm::vec3 wc = glm::vec3(modelMatrix * glm::vec4(c, 1.0f));
outMin = glm::min(outMin, wc);
outMax = glm::max(outMax, wc);
}
}
inline float pointAABBDistanceSq(const glm::vec3& p, const glm::vec3& bmin, const glm::vec3& bmax) {
glm::vec3 q = glm::clamp(p, bmin, bmax);
glm::vec3 d = p - q;
return glm::dot(d, d);
}
// MöllerTrumbore ray-triangle intersection.
// Returns distance along ray if hit, negative if miss.
inline float rayTriangleIntersect(const glm::vec3& origin, const glm::vec3& dir,
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2) {
constexpr float EPSILON = 1e-6f;
glm::vec3 e1 = v1 - v0;
glm::vec3 e2 = v2 - v0;
glm::vec3 h = glm::cross(dir, e2);
float a = glm::dot(e1, h);
if (a > -EPSILON && a < EPSILON) return -1.0f;
float f = 1.0f / a;
glm::vec3 s = origin - v0;
float u = f * glm::dot(s, h);
if (u < 0.0f || u > 1.0f) return -1.0f;
glm::vec3 q = glm::cross(s, e1);
float v = f * glm::dot(dir, q);
if (v < 0.0f || u + v > 1.0f) return -1.0f;
float t = f * glm::dot(e2, q);
return t > EPSILON ? t : -1.0f;
}
// Closest point on triangle to a point (Ericson, Real-Time Collision Detection §5.1.5).
inline glm::vec3 closestPointOnTriangle(const glm::vec3& p,
const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) {
glm::vec3 ab = b - a, ac = c - a, ap = p - a;
float d1 = glm::dot(ab, ap), d2 = glm::dot(ac, ap);
if (d1 <= 0.0f && d2 <= 0.0f) return a;
glm::vec3 bp = p - b;
float d3 = glm::dot(ab, bp), d4 = glm::dot(ac, bp);
if (d3 >= 0.0f && d4 <= d3) return b;
float vc = d1 * d4 - d3 * d2;
if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) {
float v = d1 / (d1 - d3);
return a + v * ab;
}
glm::vec3 cp = p - c;
float d5 = glm::dot(ab, cp), d6 = glm::dot(ac, cp);
if (d6 >= 0.0f && d5 <= d6) return c;
float vb = d5 * d2 - d1 * d6;
if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) {
float w = d2 / (d2 - d6);
return a + w * ac;
}
float va = d3 * d6 - d5 * d4;
if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) {
float w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
return b + w * (c - b);
}
float denom = 1.0f / (va + vb + vc);
float v = vb * denom;
float w = vc * denom;
return a + ab * v + ac * w;
}
// ---- Thread-local scratch buffers for collision queries ----
inline thread_local std::vector<size_t> tl_m2_candidateScratch;
inline thread_local std::unordered_set<uint32_t> tl_m2_candidateIdScratch;
inline thread_local std::vector<uint32_t> tl_m2_collisionTriScratch;
// ---- Bone animation helpers ----
inline int findKeyframeIndex(const std::vector<uint32_t>& timestamps, float time) {
if (timestamps.empty()) return -1;
if (timestamps.size() == 1) return 0;
auto it = std::upper_bound(timestamps.begin(), timestamps.end(), time,
[](float t, uint32_t ts) { return t < static_cast<float>(ts); });
if (it == timestamps.begin()) return 0;
size_t idx = static_cast<size_t>(it - timestamps.begin()) - 1;
return static_cast<int>(std::min(idx, timestamps.size() - 2));
}
inline void resolveTrackTime(const pipeline::M2AnimationTrack& track,
int seqIdx, float time,
const std::vector<uint32_t>& globalSeqDurations,
int& outSeqIdx, float& outTime) {
if (track.globalSequence >= 0 &&
static_cast<size_t>(track.globalSequence) < globalSeqDurations.size()) {
outSeqIdx = 0;
float dur = static_cast<float>(globalSeqDurations[track.globalSequence]);
if (dur > 0.0f) {
outTime = time;
while (outTime >= dur) {
outTime -= dur;
}
} else {
outTime = 0.0f;
}
} else {
outSeqIdx = seqIdx;
outTime = time;
}
}
inline glm::vec3 interpVec3(const pipeline::M2AnimationTrack& track,
int seqIdx, float time, const glm::vec3& def,
const std::vector<uint32_t>& globalSeqDurations) {
if (!track.hasData()) return def;
int si; float t;
resolveTrackTime(track, seqIdx, time, globalSeqDurations, si, t);
if (si < 0 || si >= static_cast<int>(track.sequences.size())) return def;
const auto& keys = track.sequences[si];
if (keys.timestamps.empty() || keys.vec3Values.empty()) return def;
auto safe = [&](const glm::vec3& v) -> glm::vec3 {
if (std::isnan(v.x) || std::isnan(v.y) || std::isnan(v.z)) return def;
return v;
};
if (keys.vec3Values.size() == 1) return safe(keys.vec3Values[0]);
int idx = findKeyframeIndex(keys.timestamps, t);
if (idx < 0) return def;
size_t i0 = static_cast<size_t>(idx);
size_t i1 = std::min(i0 + 1, keys.vec3Values.size() - 1);
if (i0 == i1) return safe(keys.vec3Values[i0]);
float t0 = static_cast<float>(keys.timestamps[i0]);
float t1 = static_cast<float>(keys.timestamps[i1]);
float dur = t1 - t0;
float frac = (dur > 0.0f) ? glm::clamp((t - t0) / dur, 0.0f, 1.0f) : 0.0f;
return safe(glm::mix(keys.vec3Values[i0], keys.vec3Values[i1], frac));
}
inline glm::quat interpQuat(const pipeline::M2AnimationTrack& track,
int seqIdx, float time,
const std::vector<uint32_t>& globalSeqDurations) {
glm::quat identity(1.0f, 0.0f, 0.0f, 0.0f);
if (!track.hasData()) return identity;
int si; float t;
resolveTrackTime(track, seqIdx, time, globalSeqDurations, si, t);
if (si < 0 || si >= static_cast<int>(track.sequences.size())) return identity;
const auto& keys = track.sequences[si];
if (keys.timestamps.empty() || keys.quatValues.empty()) return identity;
auto safe = [&](const glm::quat& q) -> glm::quat {
float lenSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w;
if (lenSq < 0.000001f || std::isnan(lenSq)) return identity;
return q;
};
if (keys.quatValues.size() == 1) return safe(keys.quatValues[0]);
int idx = findKeyframeIndex(keys.timestamps, t);
if (idx < 0) return identity;
size_t i0 = static_cast<size_t>(idx);
size_t i1 = std::min(i0 + 1, keys.quatValues.size() - 1);
if (i0 == i1) return safe(keys.quatValues[i0]);
float t0 = static_cast<float>(keys.timestamps[i0]);
float t1 = static_cast<float>(keys.timestamps[i1]);
float dur = t1 - t0;
float frac = (dur > 0.0f) ? glm::clamp((t - t0) / dur, 0.0f, 1.0f) : 0.0f;
return glm::slerp(safe(keys.quatValues[i0]), safe(keys.quatValues[i1]), frac);
}
inline void computeBoneMatrices(const M2ModelGPU& model, M2Instance& instance) {
ZoneScopedN("M2::computeBoneMatrices");
size_t numBones = std::min(model.bones.size(), size_t(128));
if (numBones == 0) return;
instance.boneMatrices.resize(numBones);
const auto& gsd = model.globalSequenceDurations;
for (size_t i = 0; i < numBones; i++) {
const auto& bone = model.bones[i];
glm::vec3 trans = interpVec3(bone.translation, instance.currentSequenceIndex, instance.animTime, glm::vec3(0.0f), gsd);
glm::quat rot = interpQuat(bone.rotation, instance.currentSequenceIndex, instance.animTime, gsd);
glm::vec3 scl = interpVec3(bone.scale, instance.currentSequenceIndex, instance.animTime, glm::vec3(1.0f), gsd);
if (scl.x < 0.001f) scl.x = 1.0f;
if (scl.y < 0.001f) scl.y = 1.0f;
if (scl.z < 0.001f) scl.z = 1.0f;
glm::mat4 local = glm::translate(glm::mat4(1.0f), bone.pivot);
local = glm::translate(local, trans);
local *= glm::toMat4(rot);
local = glm::scale(local, scl);
local = glm::translate(local, -bone.pivot);
if (bone.parentBone >= 0 && static_cast<size_t>(bone.parentBone) < numBones) {
instance.boneMatrices[i] = instance.boneMatrices[bone.parentBone] * local;
} else {
instance.boneMatrices[i] = local;
}
}
instance.bonesDirty[0] = instance.bonesDirty[1] = true;
}
} // namespace m2_internal
// Pull all symbols into the rendering namespace so existing code compiles unchanged
using namespace m2_internal;
} // namespace rendering
} // namespace wowee