PythonPlayer, PythonPlayerInput{,Keyboard,Mouse}, PythonPlayerEventHandler,
PythonPlayerSkill, PythonPlayerModule and GameType are verbatim copies; the
`player` stub module and the pending PythonPlayer/GameType stand-ins are gone.
Unported callees (CPythonSkill, CItemData, CItemManager, CPythonItem, ...) are
pending stubs. 64-bit fixes: emotion icon handles use Py_BuildPointer, and
PyDict_Next takes Py_ssize_t.
The actor did not move because GrannyUpdateModelMatrix passed the model matrix
through. Walk/run motions use constant root-motion extraction
(AccumulationFlags=AccumulationExtracted, LoopTranslation e.g. (0,-300,0) for
the warrior run, same as the .msa Accumulation). libgr2 now reads both fields
from TrackGroups[0], and the runtime moves by LoopTranslation over each
control's local-clock span, blended by control weight. VDA extraction is not
implemented.
port.login_flow presses DIK_UP; the actor walks more than 100 cm, and the fake
server parses CG packets and sees CG_MOVE FUNC_MOVE, then FUNC_WAIT at the new
position. port.login_live against the real server walked 132 cm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
302 lines
14 KiB
C++
302 lines
14 KiB
C++
// M0 T7a —— 曲线子类型分类(gr2fuzz 直方图依赖)。T7b(解码)见文末。
|
||
// 见 docs/reference/steps/M0-gr2-reader.md
|
||
// 规格:granny_track_group.cpp (TrackGroupType/TransformTrackType/Curve2Type) /
|
||
// granny_curve.cpp (CurveData*Type,均以 CurveDataHeaderType 开头) /
|
||
// granny_curve.h (curve_data_header{ u8 Format; u8 Degree })
|
||
#include "internal.h"
|
||
#include <cstdio>
|
||
#include <cstdlib>
|
||
#include <cstring>
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
|
||
#define ADBG(...) do { if (getenv("GR2_DEBUG")) fprintf(stderr, "[anim] " __VA_ARGS__); } while (0)
|
||
|
||
namespace gr2 {
|
||
|
||
using detail::Ref;
|
||
using detail::TypeMember;
|
||
|
||
// Metin2 = Granny 2.4:曲线是 OldCurveType(granny_curve.cpp):
|
||
// {Int32 Degree; RefToArray Knots(Real32[]); RefToArray Controls(Real32[])} —— 20B,无压缩变体。
|
||
// curve_obj 指向 transform_track 里 inline 的那个 curve 对象本体。
|
||
// 新版(2.6+)是 curve2{variant CurveData},本函数一并识别、打标签。
|
||
std::string File::Impl::classify_curve_obj(Ref curve_obj, Ref curve_type) {
|
||
if (!curve_obj.valid() || !curve_type.valid()) return "none";
|
||
const auto& ms = parse_type(curve_type);
|
||
if (ms.empty()) return "Unknown";
|
||
|
||
// 老格式:第一个成员是 "Degree"
|
||
if (ms[0].name == "Degree") {
|
||
const TypeMember* mD = &ms[0];
|
||
const TypeMember* mK = find_member(ms, "Knots");
|
||
const TypeMember* mC = find_member(ms, "Controls");
|
||
int32_t degree = rd_i32(member_slot(curve_obj, *mD));
|
||
uint32_t kn = mK ? uint32_t(std::max(0, rd_i32(member_slot(curve_obj, *mK)))) : 0;
|
||
uint32_t cn = mC ? uint32_t(std::max(0, rd_i32(member_slot(curve_obj, *mC)))) : 0;
|
||
uint32_t dim = kn ? cn / kn : 0;
|
||
char buf[64];
|
||
std::snprintf(buf, sizeof buf, "Old(d=%d,dim=%u)", degree, dim);
|
||
return buf;
|
||
}
|
||
// 新格式:curve2{variant CurveData}
|
||
if (ms[0].name == "CurveData") {
|
||
Ref type_ref = follow(member_slot(curve_obj, ms[0])); // variant.Type
|
||
if (!type_ref.valid()) return "Identity";
|
||
const auto& dm = parse_type(type_ref);
|
||
if (dm.empty()) return "Unknown";
|
||
const std::string& n = dm[0].name;
|
||
const char* pfx = "CurveDataHeader_";
|
||
return (n.rfind(pfx, 0) == 0) ? n.substr(std::strlen(pfx)) : n;
|
||
}
|
||
return "Unknown";
|
||
}
|
||
|
||
// 读一条 OldCurveType 曲线({Int32 Degree; RefToArray Knots; RefToArray Controls})到 Curve。
|
||
void File::Impl::read_old_curve(Ref curve_obj, Ref curve_type, Curve& out) {
|
||
if (!curve_obj.valid() || !curve_type.valid()) return;
|
||
const auto& ms = parse_type(curve_type);
|
||
if (ms.empty() || ms[0].name != "Degree") return; // 新格式暂不解
|
||
const TypeMember* mD = &ms[0];
|
||
const TypeMember* mK = find_member(ms, "Knots");
|
||
const TypeMember* mC = find_member(ms, "Controls");
|
||
if (!mK || !mC) return;
|
||
|
||
out.degree = rd_i32(member_slot(curve_obj, *mD));
|
||
|
||
auto read_farr = [&](const TypeMember& m, std::vector<float>& dst) {
|
||
Ref slot = member_slot(curve_obj, m);
|
||
int32_t cnt = rd_i32(slot);
|
||
if (cnt <= 0 || cnt > (1 << 24)) return;
|
||
Ref base = follow({slot.section, slot.offset + 4});
|
||
const uint8_t* p = at(base, size_t(cnt) * 4);
|
||
if (!p) return;
|
||
dst.resize(cnt);
|
||
for (int32_t k = 0; k < cnt; ++k) std::memcpy(&dst[k], p + size_t(k) * 4, 4);
|
||
};
|
||
read_farr(*mK, out.knots);
|
||
read_farr(*mC, out.controls);
|
||
out.dim = out.knots.empty() ? 0 : uint32_t(out.controls.size() / out.knots.size());
|
||
if (out.dim && out.knots.size() * out.dim != out.controls.size()) out.dim = 0; // 不一致 → 弃
|
||
|
||
// 有些资产的曲线控制点本身是 NaN(如 redthief_general/*_damage.gr2 的 finger track)。
|
||
// 不 sanitize 会把 NaN 灌进整条蒙皮链。→ 整条曲线弃(eval 返回空,调用方回退到 bind)。
|
||
for (float v : out.knots) if (!std::isfinite(v)) { out.dim = 0; return; }
|
||
for (float v : out.controls) if (!std::isfinite(v)) { out.dim = 0; return; }
|
||
}
|
||
|
||
// animation: {String Name; Real32 Duration; Real32 TimeStep; Real32 Oversampling;
|
||
// ArrayOfRefs TrackGroups; ...}
|
||
// track_group: {String Name; RefToArray VectorTracks; RefToArray TransformTracks; ...}
|
||
// transform_track: {String Name; Int32 Flags; Inline OrientationCurve(curve2);
|
||
// Inline PositionCurve(curve2); Inline ScaleShearCurve(curve2)}
|
||
bool File::Impl::extract_animation(Ref anim_obj, Ref anim_type, const Skeleton* skel, Animation& out) {
|
||
if (!anim_obj.valid() || !anim_type.valid()) return false;
|
||
const auto& ams = parse_type(anim_type);
|
||
|
||
if (const TypeMember* m = find_member(ams, "Name"))
|
||
out.name = rd_str(member_slot(anim_obj, *m));
|
||
if (const TypeMember* m = find_member(ams, "Duration"))
|
||
out.duration = rd_f32(member_slot(anim_obj, *m));
|
||
if (const TypeMember* m = find_member(ams, "TimeStep"))
|
||
out.time_step = rd_f32(member_slot(anim_obj, *m));
|
||
if (const TypeMember* m = find_member(ams, "Oversampling"))
|
||
out.oversampling = rd_f32(member_slot(anim_obj, *m));
|
||
|
||
const TypeMember* mTG = find_member(ams, "TrackGroups");
|
||
if (!mTG) return true;
|
||
auto groups = read_array_of_refs(anim_obj, *mTG);
|
||
out.track_group_count = int32_t(groups.size());
|
||
const auto& tgms = parse_type(mTG->ref_type);
|
||
|
||
// TrackGroups[0].AccumulationFlags (Int32) + LoopTranslation (Real32[3])
|
||
if (!groups.empty() && groups[0].valid()) {
|
||
if (const TypeMember* m = find_member(tgms, "AccumulationFlags"))
|
||
out.accumulation_flags = rd_i32(member_slot(groups[0], *m));
|
||
if (const TypeMember* m = find_member(tgms, "LoopTranslation")) {
|
||
Ref r = member_slot(groups[0], *m);
|
||
for (int i = 0; i < 3; ++i)
|
||
out.loop_translation[i] = rd_f32(Ref{r.section, r.offset + uint32_t(i) * 4u});
|
||
}
|
||
}
|
||
|
||
// TrackGroups[0].TextTracks: RefToArray text_track{String Name; RefToArray Entries(
|
||
// text_track_entry{Real32 TimeStamp; String Text})}(motion event 用)
|
||
if (!groups.empty() && groups[0].valid())
|
||
if (const TypeMember* mTx = find_member(tgms, "TextTracks")) {
|
||
Ref xb; uint32_t xc = 0, xs = 0;
|
||
if (read_ref_to_array(groups[0], *mTx, xb, xc, xs) && xc && xs) {
|
||
const auto& xms = parse_type(mTx->ref_type);
|
||
const TypeMember* mXN = find_member(xms, "Name");
|
||
const TypeMember* mXE = find_member(xms, "Entries");
|
||
for (uint32_t i = 0; i < xc && i < 4096; ++i) {
|
||
Ref x = {xb.section, xb.offset + i * xs};
|
||
TextTrack tt;
|
||
if (mXN) tt.name = rd_str(member_slot(x, *mXN));
|
||
Ref eb; uint32_t ec = 0, es = 0;
|
||
if (mXE && read_ref_to_array(x, *mXE, eb, ec, es) && ec && es) {
|
||
const auto& ems = parse_type(mXE->ref_type);
|
||
const TypeMember* mTS = find_member(ems, "TimeStamp");
|
||
const TypeMember* mTT = find_member(ems, "Text");
|
||
for (uint32_t k = 0; k < ec && k < 65536; ++k) {
|
||
Ref e = {eb.section, eb.offset + k * es};
|
||
TextTrackEntry te;
|
||
if (mTS) te.time_stamp = rd_f32(member_slot(e, *mTS));
|
||
if (mTT) te.text = rd_str(member_slot(e, *mTT));
|
||
tt.entries.push_back(std::move(te));
|
||
}
|
||
}
|
||
out.text_tracks.push_back(std::move(tt));
|
||
}
|
||
}
|
||
}
|
||
const TypeMember* mTT = find_member(tgms, "TransformTracks");
|
||
if (!mTT) return true;
|
||
|
||
const auto& ttms = parse_type(mTT->ref_type);
|
||
if (getenv("GR2_DEBUG")) {
|
||
fprintf(stderr, "[anim] transform_track type members=%zu:\n", ttms.size());
|
||
for (auto& m : ttms)
|
||
fprintf(stderr, "[anim] %-20s type=%d aw=%d off=%u size=%u reftype=(%d,%u)\n",
|
||
m.name.c_str(), m.type, m.array_width, m.offset, m.size,
|
||
m.ref_type.section, m.ref_type.offset);
|
||
}
|
||
const TypeMember* mName = find_member(ttms, "Name");
|
||
const TypeMember* mOri = find_member(ttms, "OrientationCurve");
|
||
const TypeMember* mPos = find_member(ttms, "PositionCurve");
|
||
const TypeMember* mScl = find_member(ttms, "ScaleShearCurve");
|
||
|
||
for (Ref g : groups) {
|
||
if (!g.valid()) continue;
|
||
Ref base; uint32_t count = 0, stride = 0;
|
||
if (!read_ref_to_array(g, *mTT, base, count, stride) || count == 0) continue;
|
||
for (uint32_t i = 0; i < count; ++i) {
|
||
Ref t = {base.section, base.offset + i * stride};
|
||
BoneTrack bt;
|
||
if (mName) bt.bone_name = rd_str(member_slot(t, *mName));
|
||
if (mPos) {
|
||
bt.pos_type = classify_curve_obj(member_slot(t, *mPos), mPos->ref_type);
|
||
read_old_curve(member_slot(t, *mPos), mPos->ref_type, bt.position);
|
||
}
|
||
if (mOri) {
|
||
bt.rot_type = classify_curve_obj(member_slot(t, *mOri), mOri->ref_type);
|
||
read_old_curve(member_slot(t, *mOri), mOri->ref_type, bt.orientation);
|
||
}
|
||
if (mScl) {
|
||
bt.scale_type = classify_curve_obj(member_slot(t, *mScl), mScl->ref_type);
|
||
read_old_curve(member_slot(t, *mScl), mScl->ref_type, bt.scale_shear);
|
||
}
|
||
if (skel) for (size_t k = 0; k < skel->bones.size(); ++k)
|
||
if (skel->bones[k].name == bt.bone_name) { bt.bone_index = int32_t(k); break; }
|
||
out.tracks.push_back(std::move(bt));
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ── T7b:曲线解码 ─────────────────────────────────────────────────────
|
||
// Metin2 曲线全是 OldCurveType(B 样条:Degree + Knots[] + Controls[knot*dim])。
|
||
// 求值照 granny_bspline.cpp SampleBSpline{0,1,2}xN + granny_bspline_inlines.h
|
||
// {Linear,Quadratic}Coefficients。degree 0=常量,1=线性,2=二次 B 样条。
|
||
// KnotIndex = "第一个 > t 的 knot"(granny CurveFindKnot),窗口越界按端点 clamp。
|
||
//
|
||
// dim==4(四元数):blend 之前对本 span 参与的 degree+1 个控制点做
|
||
// EnsureQuaternionContinuity(granny_math.cpp)—— 逐点若与前一点点积 < 0 则整体取反。
|
||
// 少了这一步,跨半球(q vs -q)的控制点线性混合会收缩到近零,归一后得到一个
|
||
// 合法但方向错约 180° 的旋转(表情动作里脖子/头会折叠塌陷)。
|
||
void Curve::eval(float t, float* out) const {
|
||
const uint32_t n = (uint32_t)knots.size();
|
||
if (n == 0 || dim == 0) return;
|
||
auto Kc = [&](int i) -> float {
|
||
i = i < 0 ? 0 : (i >= (int)n ? (int)n - 1 : i);
|
||
return knots[(uint32_t)i];
|
||
};
|
||
|
||
if (n == 1 || degree <= 0) { // 常量
|
||
for (uint32_t d = 0; d < dim; ++d) out[d] = controls[d];
|
||
return;
|
||
}
|
||
if (t <= knots[0]) t = knots[0];
|
||
if (t >= knots[n - 1]) t = knots[n - 1];
|
||
int i = (int)n - 1;
|
||
for (uint32_t k = 0; k < n; ++k) if (knots[k] > t) { i = (int)k; break; }
|
||
if (i < 1) i = 1;
|
||
|
||
const int span = (degree == 1) ? 2 : 3; // 本 span 的控制点数 = degree+1
|
||
const int first = (degree == 1) ? (i - 1) : (i - 2);
|
||
|
||
// 拷本 span 的控制点(越界按端点 clamp),四元数做半球连续化
|
||
// Metin2 的 ScaleShearCurve 是 9 维;旧的 3*4 缓冲区会在这里发生
|
||
// 栈溢出(degree=2 时写入 27 个 float),随后被 __stack_chk_fail 终止。
|
||
// 目前支持的曲线维度为 position=3、orientation=4、scale/shear=9。
|
||
if (dim > 9) return;
|
||
float cp[3 * 9];
|
||
for (int s = 0; s < span; ++s) {
|
||
int ci = first + s;
|
||
ci = ci < 0 ? 0 : (ci >= (int)n ? (int)n - 1 : ci);
|
||
for (uint32_t d = 0; d < dim; ++d) cp[s * dim + d] = controls[(uint32_t)ci * dim + d];
|
||
}
|
||
if (dim == 4) {
|
||
for (int s = 1; s < span; ++s) {
|
||
const float* p = &cp[(s - 1) * 4];
|
||
float* q = &cp[s * 4];
|
||
if (p[0]*q[0] + p[1]*q[1] + p[2]*q[2] + p[3]*q[3] < 0.0f)
|
||
for (int d = 0; d < 4; ++d) q[d] = -q[d];
|
||
}
|
||
}
|
||
|
||
if (degree == 1) {
|
||
float denom = Kc(i) - Kc(i - 1);
|
||
float c0 = denom != 0.0f ? (t - Kc(i - 1)) / denom : 0.0f;
|
||
c0 = c0 < 0 ? 0 : (c0 > 1 ? 1 : c0);
|
||
float c1 = 1.0f - c0;
|
||
for (uint32_t d = 0; d < dim; ++d)
|
||
out[d] = c1 * cp[0 * dim + d] + c0 * cp[1 * dim + d];
|
||
} else { // degree >= 2 → 二次(Metin2 无 degree 3)
|
||
float ti_2 = Kc(i - 2), ti_1 = Kc(i - 1), ti = Kc(i), ti1 = Kc(i + 1);
|
||
auto fdiv = [](float a, float b) { return b != 0.0f ? a / b : 0.0f; };
|
||
float L0 = fdiv(t - ti_1, ti - ti_1);
|
||
float L1_1 = fdiv(t - ti_2, ti - ti_2);
|
||
float L1_2 = fdiv(t - ti_1, ti1 - ti_1);
|
||
float tmp = (L1_1 + L0) - L0 * L1_1;
|
||
float c0 = L0 * L1_2; // ci
|
||
float c1 = tmp - c0; // ci_1
|
||
float c2 = 1.0f - tmp; // ci_2
|
||
for (uint32_t d = 0; d < dim; ++d)
|
||
out[d] = c2 * cp[0 * dim + d] + c1 * cp[1 * dim + d] + c0 * cp[2 * dim + d];
|
||
}
|
||
|
||
if (dim == 4) { // 四元数:归一
|
||
float m = std::sqrt(out[0]*out[0] + out[1]*out[1] + out[2]*out[2] + out[3]*out[3]);
|
||
if (m > 1e-12f) for (int d = 0; d < 4; ++d) out[d] /= m;
|
||
}
|
||
}
|
||
|
||
// t 处的 SRT。position 缺省 (0,0,0),orientation 缺省单位四元数,scaleshear 缺省 I。
|
||
void BoneTrack::sample(float t, Transform& out) const {
|
||
out = Transform{};
|
||
if (!position.empty()) { position.eval(t, out.position); out.flags |= 0x1; }
|
||
if (!orientation.empty()) { orientation.eval(t, out.orientation); out.flags |= 0x2; }
|
||
if (!scale_shear.empty()) {
|
||
if (scale_shear.dim == 9) { scale_shear.eval(t, out.scale_shear); out.flags |= 0x4; }
|
||
else if (scale_shear.dim == 3) {
|
||
float s[3]; scale_shear.eval(t, s);
|
||
out.scale_shear[0] = s[0]; out.scale_shear[4] = s[1]; out.scale_shear[8] = s[2];
|
||
out.flags |= 0x4;
|
||
}
|
||
}
|
||
}
|
||
|
||
// t 处每个 track 的局部 4x4(BuildCompositeTransform4x4 语义:C 上 3x3 = transpose(R3*SS3),
|
||
// 平移在 elem 12..14)。
|
||
void Animation::sample_local(float t, std::vector<Mat4>& out) const {
|
||
out.resize(tracks.size());
|
||
Transform x;
|
||
for (size_t ti = 0; ti < tracks.size(); ++ti) {
|
||
tracks[ti].sample(t, x);
|
||
out[ti] = compose(x);
|
||
}
|
||
}
|
||
|
||
} // namespace gr2
|