mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Compare commits
6 commits
cefb05c027
...
d6de60e413
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6de60e413 | ||
|
|
8e01c3da3e | ||
|
|
4cbceced67 | ||
|
|
ee4e6a31ce | ||
|
|
7971e71d1b | ||
|
|
ba8f89d76d |
11 changed files with 80 additions and 23 deletions
|
|
@ -15,6 +15,8 @@ RUN apt-get update && \
|
|||
libavcodec-dev \
|
||||
libswscale-dev \
|
||||
libavutil-dev \
|
||||
libvulkan-dev \
|
||||
vulkan-tools \
|
||||
libstorm-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ struct WMOGroup {
|
|||
std::vector<WMOVertex> vertices;
|
||||
std::vector<uint16_t> indices;
|
||||
std::vector<WMOBatch> batches;
|
||||
std::vector<uint8_t> triFlags; // Per-triangle MOPY flags (0x04 = detail/no-collide)
|
||||
|
||||
// Portals
|
||||
std::vector<WMOPortal> portals;
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ struct M2Instance {
|
|||
bool cachedIsGroundDetail = false;
|
||||
bool cachedIsInvisibleTrap = false;
|
||||
bool cachedIsValid = false;
|
||||
bool skipCollision = false; // WMO interior doodads — skip player wall collision
|
||||
float cachedBoundRadius = 0.0f;
|
||||
|
||||
// Frame-skip optimization (update distant animations less frequently)
|
||||
|
|
@ -287,6 +288,7 @@ public:
|
|||
void setInstanceAnimationFrozen(uint32_t instanceId, bool frozen);
|
||||
void removeInstance(uint32_t instanceId);
|
||||
void removeInstances(const std::vector<uint32_t>& instanceIds);
|
||||
void setSkipCollision(uint32_t instanceId, bool skip);
|
||||
void clear();
|
||||
void cleanupUnusedModels();
|
||||
|
||||
|
|
|
|||
|
|
@ -409,6 +409,9 @@ private:
|
|||
// Pre-computed per-triangle normals (unit length, indexed by triStart/3)
|
||||
std::vector<glm::vec3> triNormals;
|
||||
|
||||
// Per-collision-triangle MOPY flags (indexed by collision tri index, i.e. triStart/3)
|
||||
std::vector<uint8_t> triMopyFlags;
|
||||
|
||||
// Scratch bitset for deduplicating triangle queries (sized to numTriangles)
|
||||
mutable std::vector<uint8_t> triVisited;
|
||||
|
||||
|
|
|
|||
|
|
@ -3719,7 +3719,8 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
|
||||
uint32_t doodadModelId = static_cast<uint32_t>(std::hash<std::string>{}(m2Path));
|
||||
m2Renderer->loadModel(m2Model, doodadModelId);
|
||||
m2Renderer->createInstance(doodadModelId, worldPos, glm::vec3(0.0f), doodad.scale);
|
||||
uint32_t doodadInstId = m2Renderer->createInstance(doodadModelId, worldPos, glm::vec3(0.0f), doodad.scale);
|
||||
if (doodadInstId) m2Renderer->setSkipCollision(doodadInstId, true);
|
||||
loadedDoodads++;
|
||||
}
|
||||
LOG_INFO("Loaded ", loadedDoodads, " instance WMO doodads");
|
||||
|
|
@ -6735,6 +6736,7 @@ void Application::processPendingTransportDoodads() {
|
|||
m2Renderer->loadModel(m2Model, doodadModelId);
|
||||
uint32_t m2InstanceId = m2Renderer->createInstance(doodadModelId, glm::vec3(0.0f), glm::vec3(0.0f), 1.0f);
|
||||
if (m2InstanceId == 0) continue;
|
||||
m2Renderer->setSkipCollision(m2InstanceId, true);
|
||||
|
||||
wmoRenderer->addDoodadToInstance(it->instanceId, m2InstanceId, doodadTemplate.localTransform);
|
||||
it->spawnedDoodads++;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <ranges>
|
||||
|
||||
namespace wowee {
|
||||
namespace core {
|
||||
|
|
@ -42,15 +45,16 @@ void Logger::ensureFile() {
|
|||
}
|
||||
}
|
||||
if (const char* level = std::getenv("WOWEE_LOG_LEVEL")) {
|
||||
std::string v(level);
|
||||
std::transform(v.begin(), v.end(), v.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
if (v == "debug") setLogLevel(LogLevel::DEBUG);
|
||||
else if (v == "info") setLogLevel(LogLevel::INFO);
|
||||
else if (v == "warn" || v == "warning") setLogLevel(LogLevel::WARNING);
|
||||
else if (v == "error") setLogLevel(kLogLevelError);
|
||||
else if (v == "fatal") setLogLevel(LogLevel::FATAL);
|
||||
auto toLower = [] (unsigned char c) { return std::tolower(c); };
|
||||
using namespace std::literals;
|
||||
|
||||
auto v = std::string_view{level} | std::views::transform(toLower);
|
||||
if (std::ranges::equal(v, "debug"sv)) setLogLevel(LogLevel::DEBUG);
|
||||
else if (std::ranges::equal(v, "info"sv)) setLogLevel(LogLevel::INFO);
|
||||
else if (std::ranges::equal(v, "warn"sv) || std::ranges::equal(v, "warning"sv))
|
||||
setLogLevel(LogLevel::WARNING);
|
||||
else if (std::ranges::equal(v, "error"sv)) setLogLevel(kLogLevelError);
|
||||
else if (std::ranges::equal(v, "fatal"sv)) setLogLevel(LogLevel::FATAL);
|
||||
}
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories("logs", ec);
|
||||
|
|
|
|||
|
|
@ -8809,21 +8809,13 @@ void GameHandler::checkAreaTriggers() {
|
|||
areaTriggerSuppressFirst_ = false;
|
||||
}
|
||||
|
||||
// Deeprun Tram entrance triggers need extended range because WMO
|
||||
// collision walls block the player from reaching the trigger center.
|
||||
static const std::unordered_set<uint32_t> extendedRangeTriggers = {
|
||||
712, 713, // Stormwind/Ironforge → Deeprun Tram
|
||||
2166, 2171, // Tram interior exit triggers
|
||||
};
|
||||
|
||||
for (const auto& at : areaTriggers_) {
|
||||
if (at.mapId != currentMapId_) continue;
|
||||
|
||||
const bool extended = extendedRangeTriggers.count(at.id) > 0;
|
||||
bool inside = false;
|
||||
if (at.radius > 0.0f) {
|
||||
// Sphere trigger — small minimum so player must be near the portal
|
||||
float effectiveRadius = std::max(at.radius, extended ? 45.0f : 12.0f);
|
||||
float effectiveRadius = std::max(at.radius, 12.0f);
|
||||
float dx = px - at.x;
|
||||
float dy = py - at.y;
|
||||
float dz = pz - at.z;
|
||||
|
|
@ -8831,7 +8823,7 @@ void GameHandler::checkAreaTriggers() {
|
|||
inside = (distSq <= effectiveRadius * effectiveRadius);
|
||||
} else if (at.boxLength > 0.0f || at.boxWidth > 0.0f || at.boxHeight > 0.0f) {
|
||||
// Box trigger — small minimum so player must walk into the portal area
|
||||
float boxMin = extended ? 60.0f : 16.0f;
|
||||
float boxMin = 16.0f;
|
||||
float effLength = std::max(at.boxLength, boxMin);
|
||||
float effWidth = std::max(at.boxWidth, boxMin);
|
||||
float effHeight = std::max(at.boxHeight, boxMin);
|
||||
|
|
|
|||
|
|
@ -498,6 +498,16 @@ bool WMOLoader::loadGroup(const std::vector<uint8_t>& groupData,
|
|||
group.indices.push_back(read<uint16_t>(groupData, mogpOffset));
|
||||
}
|
||||
}
|
||||
else if (subChunkId == 0x4D4F5059) { // MOPY - Triangle material info
|
||||
// 2 bytes per triangle: flags (uint8) + materialId (uint8)
|
||||
// flag 0x04 = detail/decorative geometry (no collision)
|
||||
uint32_t triCount = subChunkSize / 2;
|
||||
group.triFlags.resize(triCount);
|
||||
for (uint32_t i = 0; i < triCount; i++) {
|
||||
group.triFlags[i] = read<uint8_t>(groupData, mogpOffset);
|
||||
read<uint8_t>(groupData, mogpOffset); // materialId (skip)
|
||||
}
|
||||
}
|
||||
else if (subChunkId == 0x4D4F4E52) { // MONR - Normals
|
||||
uint32_t normalCount = subChunkSize / 12;
|
||||
core::Logger::getInstance().debug(" MONR: ", normalCount, " normals for ", group.vertices.size(), " vertices");
|
||||
|
|
|
|||
|
|
@ -3321,6 +3321,15 @@ void M2Renderer::removeInstance(uint32_t instanceId) {
|
|||
}
|
||||
}
|
||||
|
||||
void M2Renderer::setSkipCollision(uint32_t instanceId, bool skip) {
|
||||
for (auto& inst : instances) {
|
||||
if (inst.id == instanceId) {
|
||||
inst.skipCollision = skip;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void M2Renderer::removeInstances(const std::vector<uint32_t>& instanceIds) {
|
||||
if (instanceIds.empty() || instances.empty()) {
|
||||
return;
|
||||
|
|
@ -3649,6 +3658,7 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ,
|
|||
|
||||
const M2ModelGPU& model = it->second;
|
||||
if (model.collisionNoBlock || model.isInvisibleTrap || model.isSpellEffect) continue;
|
||||
if (instance.skipCollision) continue;
|
||||
|
||||
// --- Mesh-based floor: vertical ray vs collision triangles ---
|
||||
// Does NOT skip the AABB path — both contribute and highest wins.
|
||||
|
|
@ -3803,6 +3813,7 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
|
||||
const M2ModelGPU& model = it->second;
|
||||
if (model.collisionNoBlock || model.isInvisibleTrap || model.isSpellEffect) continue;
|
||||
if (instance.skipCollision) continue;
|
||||
if (instance.scale <= 0.001f) continue;
|
||||
|
||||
// --- Mesh-based wall collision: closest-point push ---
|
||||
|
|
|
|||
|
|
@ -865,7 +865,10 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
|
|||
m2Renderer->loadModel(doodad.model, doodad.modelId);
|
||||
uint32_t wmoDoodadInstId = m2Renderer->createInstanceWithMatrix(
|
||||
doodad.modelId, doodad.modelMatrix, doodad.worldPosition);
|
||||
if (wmoDoodadInstId) ft.m2InstanceIds.push_back(wmoDoodadInstId);
|
||||
if (wmoDoodadInstId) {
|
||||
m2Renderer->setSkipCollision(wmoDoodadInstId, true);
|
||||
ft.m2InstanceIds.push_back(wmoDoodadInstId);
|
||||
}
|
||||
ft.wmoDoodadIndex++;
|
||||
if (ft.wmoDoodadIndex < pending->wmoDoodads.size()) return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1871,12 +1871,24 @@ bool WMORenderer::createGroupResources(const pipeline::WMOGroup& group, GroupRes
|
|||
resources.indexBuffer = idxBuf.buffer;
|
||||
resources.indexAlloc = idxBuf.allocation;
|
||||
|
||||
// Store collision geometry for floor raycasting
|
||||
// Store collision geometry for floor raycasting.
|
||||
// Use MOPY per-triangle flags to exclude detail/decorative geometry (flag 0x04)
|
||||
// from collision — these are things like gears, railings, etc.
|
||||
resources.collisionVertices.reserve(group.vertices.size());
|
||||
for (const auto& v : group.vertices) {
|
||||
resources.collisionVertices.push_back(v.position);
|
||||
}
|
||||
if (!group.triFlags.empty()) {
|
||||
// Store all triangles but tag each with MOPY flags for collision filtering
|
||||
resources.collisionIndices = group.indices;
|
||||
size_t numTris = group.indices.size() / 3;
|
||||
resources.triMopyFlags.resize(numTris, 0);
|
||||
for (size_t t = 0; t < numTris; t++) {
|
||||
resources.triMopyFlags[t] = (t < group.triFlags.size()) ? group.triFlags[t] : 0;
|
||||
}
|
||||
} else {
|
||||
resources.collisionIndices = group.indices;
|
||||
}
|
||||
|
||||
// Compute actual bounding box from vertices (WMO header bboxes can be unreliable)
|
||||
if (!resources.collisionVertices.empty()) {
|
||||
|
|
@ -3087,6 +3099,21 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
float triHeight = tb.maxZ - tb.minZ;
|
||||
if (triHeight < 1.0f && tb.maxZ <= localFeetZ + 1.2f) continue;
|
||||
|
||||
// Use MOPY flags to filter wall collision.
|
||||
// Only RENDERED triangles (flag 0x20) with collision intent (0x01)
|
||||
// should block the player. Skip invisible collision hulls (0x08/0x48)
|
||||
// and non-collidable render-only geometry.
|
||||
uint32_t triIdx = triStart / 3;
|
||||
if (!group.triMopyFlags.empty() && triIdx < group.triMopyFlags.size()) {
|
||||
uint8_t mopy = group.triMopyFlags[triIdx];
|
||||
// Must be rendered (0x20) AND have base collision flag (0x01)
|
||||
bool rendered = (mopy & 0x20) != 0;
|
||||
bool collidable = (mopy & 0x01) != 0;
|
||||
if (mopy != 0 && !(rendered && collidable)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const glm::vec3& v0 = verts[indices[triStart]];
|
||||
const glm::vec3& v1 = verts[indices[triStart + 1]];
|
||||
const glm::vec3& v2 = verts[indices[triStart + 2]];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue