mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-06 17:13:51 +00:00
feat(woc): add WMO collision meshes to exported zone collision
WoweeCollision previously only contained terrain triangles; placed WMO buildings had no collision in the exported zone, so players could walk through walls. Added WoweeCollisionBuilder::addMesh() that transforms a local-space mesh into world space with slope-based walkability flags, and the editor's exportZone now walks every placed WMO and feeds each group's geometry through it. Indoor vs outdoor groups are tagged via the WMO group flag.
This commit is contained in:
parent
fdd527b373
commit
eadb6a5886
3 changed files with 83 additions and 1 deletions
|
|
@ -40,6 +40,16 @@ public:
|
|||
static WoweeCollision fromTerrain(const ADTTerrain& terrain,
|
||||
float steepAngle = 50.0f);
|
||||
|
||||
// Append a transformed mesh to an existing collision (for WMO/M2 instances).
|
||||
// `vertices` are local-space; `transform` puts them into world space.
|
||||
// Triangles are classified by slope using `steepAngle`.
|
||||
static void addMesh(WoweeCollision& collision,
|
||||
const std::vector<glm::vec3>& vertices,
|
||||
const std::vector<uint32_t>& indices,
|
||||
const glm::mat4& transform,
|
||||
uint8_t extraFlags = 0,
|
||||
float steepAngle = 50.0f);
|
||||
|
||||
// Save collision mesh to binary file
|
||||
static bool save(const WoweeCollision& collision, const std::string& path);
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,41 @@ WoweeCollision WoweeCollisionBuilder::fromTerrain(const ADTTerrain& terrain,
|
|||
return col;
|
||||
}
|
||||
|
||||
void WoweeCollisionBuilder::addMesh(WoweeCollision& collision,
|
||||
const std::vector<glm::vec3>& vertices,
|
||||
const std::vector<uint32_t>& indices,
|
||||
const glm::mat4& transform,
|
||||
uint8_t extraFlags,
|
||||
float steepAngle) {
|
||||
if (vertices.empty() || indices.size() < 3) return;
|
||||
const float steepCos = std::cos(glm::radians(steepAngle));
|
||||
|
||||
collision.triangles.reserve(collision.triangles.size() + indices.size() / 3);
|
||||
for (size_t i = 0; i + 2 < indices.size(); i += 3) {
|
||||
uint32_t i0 = indices[i], i1 = indices[i + 1], i2 = indices[i + 2];
|
||||
if (i0 >= vertices.size() || i1 >= vertices.size() || i2 >= vertices.size()) continue;
|
||||
|
||||
WoweeCollision::Triangle tri;
|
||||
tri.v0 = glm::vec3(transform * glm::vec4(vertices[i0], 1.0f));
|
||||
tri.v1 = glm::vec3(transform * glm::vec4(vertices[i1], 1.0f));
|
||||
tri.v2 = glm::vec3(transform * glm::vec4(vertices[i2], 1.0f));
|
||||
|
||||
glm::vec3 n = glm::cross(tri.v1 - tri.v0, tri.v2 - tri.v0);
|
||||
float len = glm::length(n);
|
||||
tri.flags = extraFlags;
|
||||
if (len > 1e-6f) {
|
||||
float nz = n.z / len;
|
||||
if (nz >= steepCos) tri.flags |= 0x01; // walkable
|
||||
else if (nz < 0.0f) tri.flags |= 0x04; // steep / facing-down
|
||||
}
|
||||
|
||||
collision.bounds.expand(tri.v0);
|
||||
collision.bounds.expand(tri.v1);
|
||||
collision.bounds.expand(tri.v2);
|
||||
collision.triangles.push_back(tri);
|
||||
}
|
||||
}
|
||||
|
||||
bool WoweeCollisionBuilder::save(const WoweeCollision& collision, const std::string& path) {
|
||||
namespace fs = std::filesystem;
|
||||
fs::create_directories(fs::path(path).parent_path());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "sql_exporter.hpp"
|
||||
#include "server_module_gen.hpp"
|
||||
#include "core/coordinates.hpp"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "rendering/vk_context.hpp"
|
||||
#include "pipeline/adt_loader.hpp"
|
||||
|
|
@ -1196,8 +1197,44 @@ void EditorApp::exportZone(const std::string& outputDir) {
|
|||
WoweeTerrain::exportOpen(terrain_, openBase, loadedTileX_, loadedTileY_);
|
||||
WoweeTerrain::exportNormalMap(terrain_, openBase + "_normals.png");
|
||||
|
||||
// Export collision mesh (.woc)
|
||||
// Export collision mesh (.woc) — terrain plus placed WMO/M2 meshes so that
|
||||
// movement queries on the exported zone respect buildings and large props.
|
||||
auto collision = pipeline::WoweeCollisionBuilder::fromTerrain(terrain_);
|
||||
{
|
||||
std::unordered_set<std::string> visitedWMOcol;
|
||||
std::unordered_set<std::string> visitedM2col;
|
||||
for (const auto& obj : objectPlacer_.getObjects()) {
|
||||
if (obj.type == PlaceableType::WMO && visitedWMOcol.insert(obj.path).second) {
|
||||
auto data = assetManager_->readFile(obj.path);
|
||||
if (data.empty()) continue;
|
||||
auto wmo = pipeline::WMOLoader::load(data);
|
||||
std::string wmoBase = obj.path;
|
||||
if (wmoBase.size() > 4) wmoBase = wmoBase.substr(0, wmoBase.size() - 4);
|
||||
for (uint32_t gi = 0; gi < wmo.nGroups; gi++) {
|
||||
char suffix[16];
|
||||
std::snprintf(suffix, sizeof(suffix), "_%03u.wmo", gi);
|
||||
auto gd = assetManager_->readFile(wmoBase + suffix);
|
||||
if (!gd.empty()) pipeline::WMOLoader::loadGroup(gd, wmo, gi);
|
||||
}
|
||||
glm::mat4 t = glm::translate(glm::mat4(1.0f), obj.position);
|
||||
glm::vec3 r = glm::radians(obj.rotation);
|
||||
t = glm::rotate(t, r.x, glm::vec3(1, 0, 0));
|
||||
t = glm::rotate(t, r.y, glm::vec3(0, 1, 0));
|
||||
t = glm::rotate(t, r.z, glm::vec3(0, 0, 1));
|
||||
t = glm::scale(t, glm::vec3(obj.scale));
|
||||
for (const auto& g : wmo.groups) {
|
||||
std::vector<glm::vec3> verts;
|
||||
verts.reserve(g.vertices.size());
|
||||
for (const auto& v : g.vertices) verts.push_back(v.position);
|
||||
std::vector<uint32_t> idx;
|
||||
idx.reserve(g.indices.size());
|
||||
for (uint16_t i : g.indices) idx.push_back(i);
|
||||
uint8_t flags = (g.flags & 0x08) ? 0 : 0x08; // indoor flag
|
||||
pipeline::WoweeCollisionBuilder::addMesh(collision, verts, idx, t, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (collision.isValid())
|
||||
pipeline::WoweeCollisionBuilder::save(collision, openBase + ".woc");
|
||||
WoweeTerrain::exportAlphaMaps(terrain_, base + "/alphamaps");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue