Complete remaining client gap features
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
#include <godot_cpp/classes/array_mesh.hpp>
|
||||
#include <godot_cpp/classes/box_shape3d.hpp>
|
||||
#include <godot_cpp/classes/collision_shape3d.hpp>
|
||||
#include <godot_cpp/classes/concave_polygon_shape3d.hpp>
|
||||
#include <godot_cpp/classes/cylinder_shape3d.hpp>
|
||||
#include <godot_cpp/classes/file_access.hpp>
|
||||
#include <godot_cpp/classes/geometry_instance3d.hpp>
|
||||
#include <godot_cpp/classes/height_map_shape3d.hpp>
|
||||
@@ -13,6 +15,7 @@
|
||||
#include <godot_cpp/classes/multi_mesh_instance3d.hpp>
|
||||
#include <godot_cpp/classes/performance.hpp>
|
||||
#include <godot_cpp/classes/standard_material3d.hpp>
|
||||
#include <godot_cpp/classes/sphere_shape3d.hpp>
|
||||
#include <godot_cpp/classes/time.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/variant/basis.hpp>
|
||||
@@ -31,6 +34,7 @@
|
||||
#include <property.h>
|
||||
#include <splat.h>
|
||||
#include <terrain_mesh.h>
|
||||
#include <attribute_data.h>
|
||||
|
||||
#include "dxt.h"
|
||||
#include "environment_builder.h"
|
||||
@@ -51,6 +55,149 @@ using namespace godot;
|
||||
|
||||
namespace mtgodot {
|
||||
|
||||
namespace {
|
||||
|
||||
// AttributeData stores rotations as D3DX quaternions in Metin2's Z-up
|
||||
// coordinate system. Convert through the same axis change as m2coord positions.
|
||||
Basis attribute_quaternion_basis(const std::array<float, 4> &q) {
|
||||
double x = q[0], y = q[1], z = q[2], w = q[3];
|
||||
double length = std::sqrt(x * x + y * y + z * z + w * w);
|
||||
if (length < 1e-8)
|
||||
return Basis();
|
||||
x /= length; y /= length; z /= length; w /= length;
|
||||
const double r[9] = {
|
||||
1.0 - 2.0 * (y * y + z * z), 2.0 * (x * y - z * w), 2.0 * (x * z + y * w),
|
||||
2.0 * (x * y + z * w), 1.0 - 2.0 * (x * x + z * z), 2.0 * (y * z - x * w),
|
||||
2.0 * (x * z - y * w), 2.0 * (y * z + x * w), 1.0 - 2.0 * (x * x + y * y),
|
||||
};
|
||||
const fmt::m2coord::Mat3 m2{{r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8]}};
|
||||
const fmt::m2coord::Mat3 c{{1, 0, 0, 0, 0, 1, 0, -1, 0}};
|
||||
const fmt::m2coord::Mat3 ci{{1, 0, 0, 0, 0, -1, 0, 1, 0}};
|
||||
auto mul = [](const fmt::m2coord::Mat3 &a, const fmt::m2coord::Mat3 &b) {
|
||||
fmt::m2coord::Mat3 result{};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
for (int j = 0; j < 3; ++j)
|
||||
for (int k = 0; k < 3; ++k)
|
||||
result.m[i * 3 + j] += a.m[i * 3 + k] * b.m[k * 3 + j];
|
||||
return result;
|
||||
};
|
||||
const fmt::m2coord::Mat3 godot = mul(mul(c, m2), ci);
|
||||
return Basis(Vector3(godot.m[0], godot.m[3], godot.m[6]),
|
||||
Vector3(godot.m[1], godot.m[4], godot.m[7]),
|
||||
Vector3(godot.m[2], godot.m[5], godot.m[8]));
|
||||
}
|
||||
|
||||
Vector3 attribute_position(const std::array<float, 3> &p) {
|
||||
const fmt::m2coord::Vec3 v = fmt::m2coord::position_to_godot(p[0], p[1], p[2]);
|
||||
return Vector3((float)v.x, (float)v.y, (float)v.z);
|
||||
}
|
||||
|
||||
bool add_attribute_collision(const fmt::AttributeCollision &collision,
|
||||
StaticBody3D *body) {
|
||||
using Type = fmt::AttributeCollisionType;
|
||||
const auto type = static_cast<Type>(collision.type);
|
||||
Ref<Shape3D> shape;
|
||||
Transform3D local(attribute_quaternion_basis(collision.quaternion),
|
||||
attribute_position(collision.position));
|
||||
const float cm = 0.01f;
|
||||
const float thickness = 0.04f; // finite-plane approximation, 4 cm
|
||||
|
||||
switch (type) {
|
||||
case Type::Plane: {
|
||||
Ref<BoxShape3D> plane;
|
||||
plane.instantiate();
|
||||
plane->set_size(Vector3(std::max(0.02f, std::abs(collision.dimensions[0]) * cm),
|
||||
thickness, std::max(0.02f, std::abs(collision.dimensions[1]) * cm)));
|
||||
shape = plane;
|
||||
break;
|
||||
}
|
||||
case Type::Box:
|
||||
case Type::Aabb:
|
||||
case Type::Obb: {
|
||||
Ref<BoxShape3D> box;
|
||||
box.instantiate();
|
||||
// CollisionData stores these as half-extents. Swap source y/z for Godot.
|
||||
box->set_size(Vector3(std::max(0.02f, std::abs(collision.dimensions[0]) * 2.0f * cm),
|
||||
std::max(0.02f, std::abs(collision.dimensions[2]) * 2.0f * cm),
|
||||
std::max(0.02f, std::abs(collision.dimensions[1]) * 2.0f * cm)));
|
||||
shape = box;
|
||||
if (type == Type::Aabb)
|
||||
local.basis = Basis();
|
||||
break;
|
||||
}
|
||||
case Type::Sphere: {
|
||||
Ref<SphereShape3D> sphere;
|
||||
sphere.instantiate();
|
||||
sphere->set_radius(std::max(0.01f, std::abs(collision.dimensions[0]) * cm));
|
||||
shape = sphere;
|
||||
local.basis = Basis();
|
||||
break;
|
||||
}
|
||||
case Type::Cylinder: {
|
||||
Ref<CylinderShape3D> cylinder;
|
||||
cylinder.instantiate();
|
||||
cylinder->set_radius(std::max(0.01f, std::abs(collision.dimensions[0]) * cm));
|
||||
cylinder->set_height(std::max(0.02f, std::abs(collision.dimensions[1]) * cm));
|
||||
shape = cylinder;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
CollisionShape3D *node = memnew(CollisionShape3D);
|
||||
node->set_name(String(collision.name.c_str()));
|
||||
node->set_shape(shape);
|
||||
node->set_transform(local);
|
||||
body->add_child(node);
|
||||
return true;
|
||||
}
|
||||
|
||||
int add_attribute_data(const std::string &path, const Transform3D &place,
|
||||
Node3D *root, MeshInstance3D *mesh) {
|
||||
fmt::AttributeData data;
|
||||
std::string error;
|
||||
if (!fmt::parse_attribute_data_file(path, data, &error)) {
|
||||
UtilityFunctions::push_warning(String("[mdatr] load failed: ") + path.c_str() +
|
||||
" (" + error.c_str() + ")");
|
||||
return 0;
|
||||
}
|
||||
StaticBody3D *body = memnew(StaticBody3D);
|
||||
body->set_name(String("AttributeBody_") + String(path.c_str()).get_file().get_basename());
|
||||
body->set_collision_layer(2);
|
||||
body->set_collision_mask(0);
|
||||
body->set_transform(place);
|
||||
int shapes = 0;
|
||||
for (const auto &collision : data.collisions)
|
||||
if (add_attribute_collision(collision, body))
|
||||
++shapes;
|
||||
for (const auto &height : data.heights) {
|
||||
if (height.vertices.size() < 3)
|
||||
continue;
|
||||
PackedVector3Array faces;
|
||||
faces.resize((int)height.vertices.size());
|
||||
for (int i = 0; i < (int)height.vertices.size(); ++i)
|
||||
faces[i] = attribute_position(height.vertices[(size_t)i]);
|
||||
Ref<ConcavePolygonShape3D> concave;
|
||||
concave.instantiate();
|
||||
concave->set_faces(faces);
|
||||
CollisionShape3D *node = memnew(CollisionShape3D);
|
||||
node->set_name(String(height.name.c_str()));
|
||||
node->set_shape(concave);
|
||||
body->add_child(node);
|
||||
++shapes;
|
||||
}
|
||||
if (shapes == 0) {
|
||||
body->queue_free();
|
||||
return 0;
|
||||
}
|
||||
body->set_meta("occ_mesh", mesh);
|
||||
root->add_child(body);
|
||||
return shapes;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Metin2World::Metin2World() {}
|
||||
Metin2World::~Metin2World() {}
|
||||
|
||||
@@ -424,17 +571,23 @@ void Metin2World::place_chunk_objects(int tx, int ty, Node3D *root, int &n_obj,
|
||||
pids.push_back(pid);
|
||||
mi->set_meta("portal_ids", pids);
|
||||
}
|
||||
// .mdatr 静态碰撞:未实现。有同名 .mdatr 的计数上报(SHINSOO §9-W3 第 7 项)
|
||||
{
|
||||
String md = String(rp.c_str()).get_basename() + ".mdatr";
|
||||
if (FileAccess::file_exists(md))
|
||||
++objects_mdatr_pending;
|
||||
}
|
||||
root->add_child(mi);
|
||||
// §8.2/§8.3: 盒碰撞体(层 2 = 静态遮挡物)。相机用它做防穿 + 遮挡淡出。
|
||||
// 盒尺寸/中心直接算到「米 / Y-up」,body 只带 place(旋转+平移,无 conv),
|
||||
// 避免把碰撞形状放在 0.01 缩放节点下(Godot 缩放 shape 不稳)。
|
||||
bool mdatr_built = false;
|
||||
if (collision_enabled) {
|
||||
String md = String(rp.c_str()).get_basename() + ".mdatr";
|
||||
if (FileAccess::file_exists(md)) {
|
||||
const int built = add_attribute_data(std::string(md.utf8().get_data()), place, root, mi);
|
||||
mdatr_built = built > 0;
|
||||
if (mdatr_built)
|
||||
++objects_mdatr_built;
|
||||
}
|
||||
}
|
||||
// A malformed/missing attribute resource still gets a conservative
|
||||
// GR2 AABB fallback. A valid .mdatr owns the exact collision set.
|
||||
if (collision_enabled && !mdatr_built) {
|
||||
AABB ab = mesh->get_aabb(); // gr2 原始 cm / Z-up
|
||||
if (ab.size.length() > 0.001f) {
|
||||
Vector3 c_cm = ab.position + ab.size * 0.5f; // 中心 cm Z-up
|
||||
@@ -701,7 +854,7 @@ void Metin2World::unload_map() {
|
||||
chunks_built = chunks_failed = chunks_splatted = 0;
|
||||
objects_placed = objects_skipped = objects_missing_model = 0;
|
||||
trees_placed = tree_species = 0;
|
||||
objects_mdatr_pending = 0;
|
||||
objects_mdatr_built = 0;
|
||||
build_ms = 0;
|
||||
splat_ready = registry_ok = false;
|
||||
last_error = "";
|
||||
@@ -844,7 +997,7 @@ Dictionary Metin2World::get_load_report() const {
|
||||
d["ambience_sources"] = (int)ambience_sources.size();
|
||||
d["trees_placed"] = trees_placed;
|
||||
d["tree_species"] = tree_species;
|
||||
d["objects_mdatr_pending"] = objects_mdatr_pending; // 有 .mdatr 但未建碰撞
|
||||
d["objects_mdatr_built"] = objects_mdatr_built; // 使用 .mdatr 精确碰撞的对象数
|
||||
d["static_meshes"] = static_cache.loaded;
|
||||
d["build_ms"] = build_ms;
|
||||
d["resident_chunks"] = (int)chunks.size();
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <texture_set.h>
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "static_object.h"
|
||||
@@ -142,7 +143,7 @@ private:
|
||||
int chunks_built = 0, chunks_failed = 0, chunks_splatted = 0;
|
||||
int objects_placed = 0, objects_skipped = 0, objects_missing_model = 0;
|
||||
int trees_placed = 0, tree_species = 0;
|
||||
int objects_mdatr_pending = 0;
|
||||
int objects_mdatr_built = 0;
|
||||
godot::Node3D *objects_root = nullptr;
|
||||
double build_ms = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user