Complete remaining client gap features

This commit is contained in:
shenlei
2026-09-04 13:25:05 +09:00
parent 13ccb8d02d
commit 9a4a044da5
47 changed files with 6667 additions and 140 deletions
+161 -8
View File
@@ -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();
+2 -1
View File
@@ -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;