阶段性合并:动画重定向、碎裂共享库、第三人称玩法、编辑器面板与 vendored ufbx
- 动画:HumanoidRig 三层架构(standard 标准槽 / dialects 方言映射 / mapper 配对), 指/趾统一走世界增量两遍式烘焙;AnimationAttach 拆为纯算法 + facade;修正 HumanoidBasis 行走基朝向与跨骨架去重、根运动通道 - 物理:CharacterController 角色互碰撞 / shapeOffset / 接触事件;PhysicsWorld 接触合成与 字符冲量判定 - 渲染与材质:Model 多材质按材质拆子 mesh 与贴图路径解析、DDS 直传、GLB 内嵌纹理落盘、 不透明通道 alpha 固定 1.0、glTF FlipUVs 对齐;SceneSerializer 支持 meshIndex 与材质覆盖 - 脚本:FractureLib 抽为共享库(HostView 桥接)、WallFracture 参数直绑、新增 ThirdPersonCamera / Follower / WASDMove 玩法;移除 GameDirector / PlayerController / Target - 编辑器:新增 GamepadPanel、LogPanel(LogRing),改进 Inspector / 材质 / 统计 / 视口面板 - 依赖:vendored ufbx(FBX 5.x/6.x/7.x 全版本解析,替代 assimp 对 FBX 6.1 的拒读)
This commit is contained in:
+165
-23
@@ -17,8 +17,10 @@
|
||||
// 墙脚"的观感。
|
||||
//
|
||||
// 用法(宿主 = 挂在可碎实体上的脚本):
|
||||
// Fracture::Params p_; // DeclareParams 直绑成员(pb.Field)
|
||||
// Fracture::Burst burst_; // 碎片簿记 + 生命周期
|
||||
// DeclareParams 直绑 burst_.p 各字段(pb.Field("Count",
|
||||
// &burst_.p.count, ...))——参数必须与碎裂逻辑读的是同一对象,
|
||||
// 绑到独立副本会让 Count/Lifetime 等全部失效
|
||||
// HostView HV() { // 桥接(脚本自身成员函数内可访问
|
||||
// return {App(), EntityBits(),// protected 基类设施,库内只走公共 API)
|
||||
// [this](auto& n) { return Spawn(n); },
|
||||
@@ -27,8 +29,9 @@
|
||||
// OnCollisionEnter → burst_.TryFire(HV(), other, point, normal)
|
||||
// OnUpdate → burst_.Tick(HV(), dt)
|
||||
// OnDestroy → burst_.Shutdown(HV())
|
||||
// 外观:Fire 前改 burst_.look(默认砖色不透明;玻璃靶 = 半透明
|
||||
// + 自发光)
|
||||
// 外观:默认继承宿主(墙)的生效材质(底色/贴图/透明),碎块与墙面
|
||||
// 一致;要独立外观(玻璃靶 = 半透明 + 自发光)Fire 前置
|
||||
// burst_.look.inheritFromHost = false 再填 baseColor/emissive
|
||||
// HostView 每次调用现填(不存指针:组件存储重排/实例重建均安全)
|
||||
//
|
||||
// 占位局限(真碎片资产接入后本路径退役):碎片网格顶点按宿主世界
|
||||
@@ -148,23 +151,32 @@ namespace Engine::Fracture {
|
||||
std::vector<glm::vec3> cpuPositions;
|
||||
};
|
||||
|
||||
inline PrismMesh CellToPrism(const std::vector<glm::vec2>& cell, f32 hz) {
|
||||
// hw/hh = 宿主半尺寸:顶/底面 UV 由 cell 的墙局部坐标归一化到 [0,1]
|
||||
//(墙面矩形 UV 约定,与立方体面 UV 对齐)——继承墙材质贴图时,碎片
|
||||
// 贴图与墙面连续,不会各碎片自成一格
|
||||
inline PrismMesh CellToPrism(const std::vector<glm::vec2>& cell, f32 hz,
|
||||
f32 hw, f32 hh) {
|
||||
PrismMesh m;
|
||||
const size_t n = cell.size();
|
||||
const size_t topBase = 0;
|
||||
const size_t botBase = n;
|
||||
m.vertices.reserve(n * 2);
|
||||
const f32 invW = hw > 1e-6f ? 0.5f / hw : 0.0f;
|
||||
const f32 invH = hh > 1e-6f ? 0.5f / hh : 0.0f;
|
||||
const auto uvOf = [&](const glm::vec2& p) {
|
||||
return glm::vec2{p.x * invW + 0.5f, p.y * invH + 0.5f};
|
||||
};
|
||||
|
||||
for (const glm::vec2& p : cell) {
|
||||
const Engine::Vertex v{
|
||||
{p.x, p.y, hz}, {0.0f, 0.0f, 1.0f},
|
||||
{p.x, p.y}, {1.0f, 0.0f, 0.0f}};
|
||||
uvOf(p), {1.0f, 0.0f, 0.0f}};
|
||||
m.vertices.push_back(v);
|
||||
}
|
||||
for (const glm::vec2& p : cell) {
|
||||
const Engine::Vertex v{
|
||||
{p.x, p.y, -hz}, {0.0f, 0.0f, -1.0f},
|
||||
{p.x, p.y}, {1.0f, 0.0f, 0.0f}};
|
||||
uvOf(p), {1.0f, 0.0f, 0.0f}};
|
||||
m.vertices.push_back(v);
|
||||
}
|
||||
|
||||
@@ -216,6 +228,47 @@ namespace Engine::Fracture {
|
||||
return m;
|
||||
}
|
||||
|
||||
// ---- 程序化纯色还原(引擎零改动:颜色只存在于伪路径里)----
|
||||
// 程序化 Cube/Plane 把颜色烘进 1x1 sRGB 纹理,MaterialParams 留白、
|
||||
// 内建材质快照也没有颜色字段,故从伪路径尾段 "r,g,b[,a]" 还原线性
|
||||
// 底色——与 GPU 采样 sRGB 纹理的硬件线性化同式,直乘 baseColor 才等价
|
||||
inline f32 SrgbByteToLinear(u8 c) {
|
||||
const f32 s = static_cast<f32>(c) / 255.0f;
|
||||
return s <= 0.04045f ? s / 12.92f
|
||||
: std::pow((s + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
// "procedural://cube/0.5/140,92,72" → 取最后一个 '/' 之后的纯数字
|
||||
// 逗号段;段数 < 3 或含非数字(如 halfSize 段)判失败
|
||||
inline bool ParseProceduralColor(const std::string& path, glm::vec4& out) {
|
||||
if (path.rfind("procedural://", 0) != 0) return false;
|
||||
const size_t slash = path.find_last_of('/');
|
||||
if (slash == std::string::npos) return false;
|
||||
const std::string tail = path.substr(slash + 1);
|
||||
int c[3] = {0, 0, 0};
|
||||
int n = 0;
|
||||
size_t pos = 0;
|
||||
while (n < 3 && pos < tail.size()) {
|
||||
const size_t comma = tail.find(',', pos);
|
||||
const size_t end =
|
||||
(comma == std::string::npos) ? tail.size() : comma;
|
||||
if (end == pos) return false;
|
||||
int v = 0;
|
||||
for (size_t i = pos; i < end; ++i) {
|
||||
const char ch = tail[i];
|
||||
if (ch < '0' || ch > '9') return false;
|
||||
v = v * 10 + (ch - '0');
|
||||
}
|
||||
c[n++] = v;
|
||||
if (comma == std::string::npos) break;
|
||||
pos = comma + 1;
|
||||
}
|
||||
if (n < 3) return false;
|
||||
out = glm::vec4{SrgbByteToLinear(static_cast<u8>(c[0])),
|
||||
SrgbByteToLinear(static_cast<u8>(c[1])),
|
||||
SrgbByteToLinear(static_cast<u8>(c[2])), 1.0f};
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- 参数(原 WallFracture 全量参数,字段类型随 Inspector 控件)----
|
||||
struct Params {
|
||||
i32 count = 30; // Voronoi 种子数 = 碎片数
|
||||
@@ -230,9 +283,15 @@ namespace Engine::Fracture {
|
||||
f32 minImpactSpeed = 2.0f; // 触发阈值(m/s)
|
||||
};
|
||||
|
||||
// ---- 碎片外观(Fire 时套用到全部碎片网格;默认砖色不透明 =
|
||||
// 原 WallFracture 观感)----
|
||||
// ---- 碎片外观 ----
|
||||
// 默认 inheritFromHost = true:碎块继承宿主(墙)的"生效材质"——
|
||||
// 底色/贴图/透明/自发光全套沿用,碎掉后与墙面目视一致。宿主材质
|
||||
// 取实体级覆盖(.material 资产)优先、模型内建材质兜底,与
|
||||
// SceneRenderer 收集期的 resolve 同序
|
||||
// 需要独立外观(玻璃靶 = 半透明 + 自发光等)时置 false,改用下面
|
||||
// 的显式字段(Fire 前设置)
|
||||
struct Appearance {
|
||||
bool inheritFromHost = true; // true = 材质沿用宿主,忽略下列字段
|
||||
glm::vec4 baseColor{140.0f / 255.0f, 92.0f / 255.0f,
|
||||
72.0f / 255.0f, 1.0f};
|
||||
bool transparent = false; // 透明绘制队列(玻璃碎片)
|
||||
@@ -254,20 +313,36 @@ namespace Engine::Fracture {
|
||||
const glm::vec3& normal) {
|
||||
if (m_Fired) return false;
|
||||
|
||||
// 对方须是活的 Dynamic 刚体且速度够快(过滤静止贴靠/
|
||||
// 角色轻碰——子弹/抛体必中)
|
||||
// 对方须"够快"且是可动的撞击来源,二选一:
|
||||
// - 动态刚体(子弹/抛体):用实时线速度
|
||||
// - 角色(CharacterVirtual,非刚体、无 RigidBodyComponent):
|
||||
// 用"扫掠前"速度——扫掠中朝墙面的分量会被 Jolt 抵消,
|
||||
// 实时速度往往已归零,会漏判(见
|
||||
// PhysicsWorld::GetCharacterImpactVelocity)
|
||||
// 速度低于阈值(静止贴靠/轻碰)则忽略
|
||||
auto& registry = host.app.GetScene().GetRegistry();
|
||||
const ECS::Entity otherE = EntityFromBits(other);
|
||||
if (!registry.valid(otherE)) return false;
|
||||
const auto* otherRb =
|
||||
registry.try_get<Engine::ECS::RigidBodyComponent>(otherE);
|
||||
if (!otherRb ||
|
||||
otherRb->type !=
|
||||
f32 speed = 0.0f;
|
||||
if (const auto* otherRb =
|
||||
registry.try_get<Engine::ECS::RigidBodyComponent>(
|
||||
otherE)) {
|
||||
if (otherRb->type !=
|
||||
Engine::ECS::RigidBodyComponent::BodyType::Dynamic) {
|
||||
return false;
|
||||
return false; // 静态/运动学体不算撞击
|
||||
}
|
||||
speed = glm::length(
|
||||
host.app.GetPhysics().GetLinearVelocity(other));
|
||||
} else if (registry.all_of<
|
||||
Engine::ECS::CharacterControllerComponent>(
|
||||
otherE)) {
|
||||
// 玩家/角色撞击:角色碰撞事件由 PhysicsWorld 合成
|
||||
// (CharacterVirtual 不进 ContactListener)
|
||||
speed = glm::length(
|
||||
host.app.GetPhysics().GetCharacterImpactVelocity(other));
|
||||
} else {
|
||||
return false; // 无物理来源(纯 Transform 实体)
|
||||
}
|
||||
const f32 speed = glm::length(
|
||||
host.app.GetPhysics().GetLinearVelocity(other));
|
||||
if (speed < p.minImpactSpeed) return false;
|
||||
|
||||
m_Fired = true;
|
||||
@@ -335,6 +410,52 @@ namespace Engine::Fracture {
|
||||
const f32 hh = 0.5f * wallSize.y;
|
||||
const f32 hz = 0.5f * wallSize.z;
|
||||
|
||||
// ---- 碎片外观:解析宿主(墙)的生效材质 ----
|
||||
// 与 SceneRenderer 收集期 resolve 同序:实体级覆盖
|
||||
//(.material 资产,按 mesh 下标查表)优先,模型内建材质兜底
|
||||
//(MeshMaterialInfo 为权威只读描述)。不继承则碎片只剩默认
|
||||
// 砖色,与墙面目视不符
|
||||
Engine::MaterialAssetData effMat;
|
||||
glm::vec4 effSolid{0.0f}; // 程序化纯色 albedo(a>0 = 有效)
|
||||
std::string hostOverridePath; // 宿主 .material 覆盖(空 = 内建材质)
|
||||
bool hasEffMat = false;
|
||||
if (look.inheritFromHost) {
|
||||
const auto* mr = registry.try_get<
|
||||
Engine::ECS::MeshRendererComponent>(self);
|
||||
const int slot =
|
||||
(mr && mr->meshIndex >= 0) ? mr->meshIndex : 0;
|
||||
if (mr) {
|
||||
const auto oIt =
|
||||
mr->materialOverrides.find(static_cast<u32>(slot));
|
||||
if (oIt != mr->materialOverrides.end()) {
|
||||
if (auto* asset =
|
||||
host.app.GetAssets().FindMaterialAsset(
|
||||
oIt->second)) {
|
||||
effMat = asset->GetData();
|
||||
hostOverridePath = oIt->second;
|
||||
hasEffMat = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasEffMat && mr && mr->model.Valid()) {
|
||||
if (auto* model =
|
||||
host.app.GetAssets().GetModel(mr->model)) {
|
||||
const auto& infos = model->GetMeshMaterialInfos();
|
||||
if (slot < static_cast<int>(infos.size())) {
|
||||
effMat.texPaths = infos[slot].texPaths;
|
||||
effMat.params = infos[slot].params;
|
||||
effMat.transparent = infos[slot].transparent;
|
||||
hasEffMat = true;
|
||||
}
|
||||
}
|
||||
// 程序化纯色墙:颜色在生成的 1x1 纹理里(快照无颜色
|
||||
// 字段),改由脚本从模型伪路径还原线性底色
|
||||
ParseProceduralColor(
|
||||
host.app.GetAssets().Models().Path(mr->model),
|
||||
effSolid);
|
||||
}
|
||||
}
|
||||
|
||||
// 命中点 → 宿主局部(逆旋转 + 平移),XY 即裂纹平面坐标
|
||||
const glm::quat invRot = glm::conjugate(
|
||||
wall.GetWorldRotationQuat());
|
||||
@@ -391,7 +512,7 @@ namespace Engine::Fracture {
|
||||
paths.emplace_back();
|
||||
continue;
|
||||
}
|
||||
PrismMesh pm = CellToPrism(cell, hz);
|
||||
PrismMesh pm = CellToPrism(cell, hz, hw, hh);
|
||||
glm::vec2 c2{0.0f};
|
||||
for (const glm::vec2& q : cell) c2 += q;
|
||||
c2 /= static_cast<f32>(cell.size());
|
||||
@@ -407,11 +528,27 @@ namespace Engine::Fracture {
|
||||
mesh.lods.push_back(std::move(pm.indices));
|
||||
mesh.cpuPositions = std::move(pm.cpuPositions);
|
||||
mesh.name = "frag" + std::to_string(s);
|
||||
mesh.params.baseColor = look.baseColor;
|
||||
mesh.transparent = look.transparent;
|
||||
mesh.params.emissiveFactor =
|
||||
glm::vec4{look.emissive, 1.0f};
|
||||
mesh.params.surface.z = look.emissiveStrength;
|
||||
if (hasEffMat) {
|
||||
// 继承墙的生效材质:底色/贴图/UV 变换/透明全套沿用,
|
||||
// 碎块与墙面目视一致(UV 已在 CellToPrism 归一到墙面)
|
||||
mesh.params = effMat.params;
|
||||
mesh.texPaths = effMat.texPaths;
|
||||
mesh.transparent = effMat.transparent;
|
||||
// 程序化纯色墙(无贴图路径):用脚本解析出的线性底色当
|
||||
// baseColor——碎片 albedo 走默认白纹理,直乘即该色;
|
||||
// 不走这步会退回 MaterialParams 的白色默认值
|
||||
if (effSolid.a > 0.0f) {
|
||||
mesh.params.baseColor = effSolid;
|
||||
}
|
||||
} else {
|
||||
// 无宿主材质可继承(或 look.inheritFromHost=false):
|
||||
// 用显式外观(玻璃靶 = 半透明 + 自发光)
|
||||
mesh.params.baseColor = look.baseColor;
|
||||
mesh.transparent = look.transparent;
|
||||
mesh.params.emissiveFactor =
|
||||
glm::vec4{look.emissive, 1.0f};
|
||||
mesh.params.surface.z = look.emissiveStrength;
|
||||
}
|
||||
|
||||
const Engine::ModelRef ref =
|
||||
assets.LoadModel(parsed.path, std::move(parsed));
|
||||
@@ -436,6 +573,11 @@ namespace Engine::Fracture {
|
||||
frag.AddComponent<
|
||||
Engine::ECS::MeshRendererComponent>();
|
||||
fmr.model = refs[s];
|
||||
// 宿主墙用 .material 覆盖时,碎块直接复用同一份资产:外观
|
||||
//(含透明通道)与墙完全一致,且不需要引擎改判定
|
||||
if (!hostOverridePath.empty()) {
|
||||
fmr.materialOverrides[0] = hostOverridePath;
|
||||
}
|
||||
|
||||
// Dynamic MeshCollider → ConvexHull(数据源 = 刚上传
|
||||
// 的 cpuPositions;Box 字段不参与 Mesh 形状)
|
||||
|
||||
Reference in New Issue
Block a user