mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-07 09:33:51 +00:00
363 lines
14 KiB
C++
363 lines
14 KiB
C++
// 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öller–Trumbore 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 ----
|
||
// Defined in m2_renderer_instance.cpp (inline thread_local causes LLD linker
|
||
// errors on Windows ARM64, so the definitions live in the single TU that uses them).
|
||
|
||
// ---- 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
|