碎片形状随机化:唯一代键替代命中点量化缓存键(每次命中重新随机 Voronoi),配套碎片模型生命周期回收(销毁次帧卸载 + OnDestroy 兜底)
This commit is contained in:
@@ -25,9 +25,9 @@
|
|||||||
// OnUpdate:碎片簿记——body 建好(下一帧 Step 对账,同 Bullet
|
// OnUpdate:碎片簿记——body 建好(下一帧 Step 对账,同 Bullet
|
||||||
// OnCreate 施速的时序契约)即统一施加初速;寿命到期统一 Destroy;
|
// OnCreate 施速的时序契约)即统一施加初速;寿命到期统一 Destroy;
|
||||||
// 总量上限防泄漏
|
// 总量上限防泄漏
|
||||||
// 碎片模型缓存驻留(与场景模型同策略;同代碎片共享缓存 ref,不
|
// 碎片模型生命周期:每代唯一缓存键(形状每次随机)+ 碎片销毁的
|
||||||
// 能 per-fragment 卸载,门面亦无按路径卸载——TODO 归资产系统
|
// 下一帧 UnloadModelByPath 回收(Destroy 帧尾延迟,本帧渲染收集
|
||||||
// 侧线:AssetCache 伪资产 LRU/引用计数)
|
// 仍引用模型,同帧卸载 = 渲染悬空)+ OnDestroy 兜底
|
||||||
//
|
//
|
||||||
// 占位局限(真碎片资产接入后本路径退役):碎片网格顶点按墙世界
|
// 占位局限(真碎片资产接入后本路径退役):碎片网格顶点按墙世界
|
||||||
// 尺寸烘焙、碎片实体复用墙 world pos/rot(scale 恒 1,防 ConvexHull
|
// 尺寸烘焙、碎片实体复用墙 world pos/rot(scale 恒 1,防 ConvexHull
|
||||||
@@ -71,6 +71,11 @@ namespace {
|
|||||||
return static_cast<ECS::Entity>(bits);
|
return static_cast<ECS::Entity>(bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 碎片模型代序号(全局递增):每代碎片一组唯一缓存键 → 每次
|
||||||
|
// 命中都重新随机生成形状(命中点量化键会让同位置重复命中
|
||||||
|
// 复用首次几何——缓存去重把新几何丢弃,碎片就"固定"了)
|
||||||
|
u32 s_GenSeq = 0;
|
||||||
|
|
||||||
// 实体句柄 → bits(EntityBits() 只覆盖自身绑定实体,碎片簿记
|
// 实体句柄 → bits(EntityBits() 只覆盖自身绑定实体,碎片簿记
|
||||||
// 需要任意实体的同款换算)
|
// 需要任意实体的同款换算)
|
||||||
u32 BitsOf(const ECS::EntityHandle& e) {
|
u32 BitsOf(const ECS::EntityHandle& e) {
|
||||||
@@ -248,10 +253,27 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OnUpdate(f32 dt) override {
|
void OnUpdate(f32 dt) override {
|
||||||
|
ApplyPendingUnload();
|
||||||
ApplyPendingVelocities();
|
ApplyPendingVelocities();
|
||||||
AgeAndCleanup(dt);
|
AgeAndCleanup(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 墙销毁(PIE stop 换血 / 手动删除):碎片实体由场景清亡处理,
|
||||||
|
// 独占碎片模型全部回收(模型不被快照恢复的场景引用——唯一代键)
|
||||||
|
void OnDestroy() override {
|
||||||
|
auto& assets = App().GetAssets();
|
||||||
|
for (const Fragment& f : m_Fragments) {
|
||||||
|
if (!f.modelPath.empty()) {
|
||||||
|
assets.UnloadModelByPath(f.modelPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_Fragments.clear();
|
||||||
|
for (const std::string& p : m_PendingUnload) {
|
||||||
|
assets.UnloadModelByPath(p);
|
||||||
|
}
|
||||||
|
m_PendingUnload.clear();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// ---- 触发碎裂:藏墙 + Voronoi 切分 + spawn 碎片群 ----
|
// ---- 触发碎裂:藏墙 + Voronoi 切分 + spawn 碎片群 ----
|
||||||
void Fire(const glm::vec3& hitPoint, const glm::vec3& hitNormal) {
|
void Fire(const glm::vec3& hitPoint, const glm::vec3& hitNormal) {
|
||||||
@@ -311,16 +333,11 @@ namespace {
|
|||||||
if (seeds.size() < 4) return; // 墙面太小采样失败:不碎
|
if (seeds.size() < 4) return; // 墙面太小采样失败:不碎
|
||||||
|
|
||||||
// ---- 每种子 → cell → 棱柱 mesh → ParsedModel ----
|
// ---- 每种子 → cell → 棱柱 mesh → ParsedModel ----
|
||||||
// 缓存键量化命中点(0.25m)+ 墙尺寸:同条件重复命中复用
|
// 唯一代键(见 s_GenSeq 注释):形状每次随机;模型回收走
|
||||||
// 同一批模型缓存(Load 去重,loader 不再执行)
|
// Fragment.modelPath + 待卸载队列(ApplyPendingUnload)
|
||||||
auto q = [](f32 v) { return int(std::round(v * 4.0f)); };
|
|
||||||
const std::string keyBase =
|
const std::string keyBase =
|
||||||
"procedural://vorfrag/" + std::to_string(q(wallSize.x)) +
|
"procedural://vorfrag/" +
|
||||||
'x' + std::to_string(q(wallSize.y)) +
|
std::to_string(s_GenSeq++) + '/';
|
||||||
'x' + std::to_string(q(wallSize.z)) +
|
|
||||||
'/' + std::to_string(count) +
|
|
||||||
'/' + std::to_string(q(uvHit.x)) +
|
|
||||||
'/' + std::to_string(q(uvHit.y)) + '/';
|
|
||||||
// 砖色 baseColor(glTF baseColorFactor 语义:rgb 乘进 albedo)
|
// 砖色 baseColor(glTF baseColorFactor 语义:rgb 乘进 albedo)
|
||||||
const glm::vec4 brick{140.0f / 255.0f, 92.0f / 255.0f,
|
const glm::vec4 brick{140.0f / 255.0f, 92.0f / 255.0f,
|
||||||
72.0f / 255.0f, 1.0f};
|
72.0f / 255.0f, 1.0f};
|
||||||
@@ -328,14 +345,17 @@ namespace {
|
|||||||
auto& assets = App().GetAssets();
|
auto& assets = App().GetAssets();
|
||||||
std::vector<Engine::ModelRef> refs;
|
std::vector<Engine::ModelRef> refs;
|
||||||
std::vector<glm::vec3> centroids; // 碎片质心(世界系,初速径向用)
|
std::vector<glm::vec3> centroids; // 碎片质心(世界系,初速径向用)
|
||||||
|
std::vector<std::string> paths; // 模型缓存键(回收用,与 refs 对齐)
|
||||||
refs.reserve(seeds.size());
|
refs.reserve(seeds.size());
|
||||||
centroids.reserve(seeds.size());
|
centroids.reserve(seeds.size());
|
||||||
|
paths.reserve(seeds.size());
|
||||||
for (size_t s = 0; s < seeds.size(); ++s) {
|
for (size_t s = 0; s < seeds.size(); ++s) {
|
||||||
const std::vector<glm::vec2> cell =
|
const std::vector<glm::vec2> cell =
|
||||||
VoronoiCell(seeds[s], seeds, hw, hh);
|
VoronoiCell(seeds[s], seeds, hw, hh);
|
||||||
if (cell.size() < 3) {
|
if (cell.size() < 3) {
|
||||||
refs.push_back({}); // 退化 cell:占位,spawn 跳过
|
refs.push_back({}); // 退化 cell:占位,spawn 跳过
|
||||||
centroids.emplace_back();
|
centroids.emplace_back();
|
||||||
|
paths.emplace_back();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
PrismMesh pm = CellToPrism(cell, hz);
|
PrismMesh pm = CellToPrism(cell, hz);
|
||||||
@@ -359,6 +379,7 @@ namespace {
|
|||||||
const Engine::ModelRef ref =
|
const Engine::ModelRef ref =
|
||||||
assets.LoadModel(parsed.path, std::move(parsed));
|
assets.LoadModel(parsed.path, std::move(parsed));
|
||||||
refs.push_back(ref);
|
refs.push_back(ref);
|
||||||
|
paths.push_back(parsed.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- spawn 碎片实体:pos/rot 复用墙位姿(mesh 已真尺寸,
|
// ---- spawn 碎片实体:pos/rot 复用墙位姿(mesh 已真尺寸,
|
||||||
@@ -412,17 +433,32 @@ namespace {
|
|||||||
0.0f};
|
0.0f};
|
||||||
|
|
||||||
// 总量上限:超限立即回收最老碎片(防泄漏的兜底闸;
|
// 总量上限:超限立即回收最老碎片(防泄漏的兜底闸;
|
||||||
// 寿命清理才是常态回收)
|
// 寿命清理才是常态回收)。模型延迟一帧卸载
|
||||||
const u32 bits = BitsOf(frag);
|
const u32 bits = BitsOf(frag);
|
||||||
if (m_Fragments.size() >=
|
if (m_Fragments.size() >=
|
||||||
static_cast<size_t>(m_MaxFragments)) {
|
static_cast<size_t>(m_MaxFragments)) {
|
||||||
Destroy(EntityFromBits(m_Fragments.front().bits));
|
Destroy(EntityFromBits(m_Fragments.front().bits));
|
||||||
|
if (!m_Fragments.front().modelPath.empty()) {
|
||||||
|
m_PendingUnload.push_back(
|
||||||
|
std::move(m_Fragments.front().modelPath));
|
||||||
|
}
|
||||||
m_Fragments.erase(m_Fragments.begin());
|
m_Fragments.erase(m_Fragments.begin());
|
||||||
}
|
}
|
||||||
m_Fragments.push_back({bits, vel, 0.0f});
|
m_Fragments.push_back(
|
||||||
|
Fragment{bits, vel, 0.0f, false, paths[s]});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 模型回收:延迟一帧执行(Destroy 是帧尾延迟,碎片实体
|
||||||
|
// 本帧渲染收集仍会引用模型;下一帧实体已灭,卸载才安全)----
|
||||||
|
void ApplyPendingUnload() {
|
||||||
|
auto& assets = App().GetAssets();
|
||||||
|
for (const std::string& p : m_PendingUnload) {
|
||||||
|
assets.UnloadModelByPath(p);
|
||||||
|
}
|
||||||
|
m_PendingUnload.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 施速待办:body 建好(bodyValid)即注入,勿重复 ----
|
// ---- 施速待办:body 建好(bodyValid)即注入,勿重复 ----
|
||||||
void ApplyPendingVelocities() {
|
void ApplyPendingVelocities() {
|
||||||
auto& registry = App().GetScene().GetRegistry();
|
auto& registry = App().GetScene().GetRegistry();
|
||||||
@@ -431,6 +467,11 @@ namespace {
|
|||||||
if (it->applied) { ++it; continue; }
|
if (it->applied) { ++it; continue; }
|
||||||
const ECS::Entity e = EntityFromBits(it->bits);
|
const ECS::Entity e = EntityFromBits(it->bits);
|
||||||
if (!registry.valid(e)) { // 实体没了(外部销毁)
|
if (!registry.valid(e)) { // 实体没了(外部销毁)
|
||||||
|
// 模型是独占资源(一代一键),实体没了模型也回收
|
||||||
|
if (!it->modelPath.empty()) {
|
||||||
|
m_PendingUnload.push_back(
|
||||||
|
std::move(it->modelPath));
|
||||||
|
}
|
||||||
it = m_Fragments.erase(it);
|
it = m_Fragments.erase(it);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -456,6 +497,10 @@ namespace {
|
|||||||
if (registry.valid(e)) {
|
if (registry.valid(e)) {
|
||||||
Destroy(e);
|
Destroy(e);
|
||||||
}
|
}
|
||||||
|
// 模型延迟一帧卸载(本帧渲染收集还引用它)
|
||||||
|
if (!it->modelPath.empty()) {
|
||||||
|
m_PendingUnload.push_back(std::move(it->modelPath));
|
||||||
|
}
|
||||||
it = m_Fragments.erase(it);
|
it = m_Fragments.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -470,6 +515,7 @@ namespace {
|
|||||||
glm::vec3 velocity{0.0f};// 待施初速(body 建好后注入)
|
glm::vec3 velocity{0.0f};// 待施初速(body 建好后注入)
|
||||||
f32 age = 0.0f; // 存活秒数(寿命清理用)
|
f32 age = 0.0f; // 存活秒数(寿命清理用)
|
||||||
bool applied = false; // 初速已注入
|
bool applied = false; // 初速已注入
|
||||||
|
std::string modelPath; // 独占模型缓存键(回收时按路径卸载)
|
||||||
};
|
};
|
||||||
|
|
||||||
int m_Count = 30;
|
int m_Count = 30;
|
||||||
@@ -485,6 +531,9 @@ namespace {
|
|||||||
|
|
||||||
bool m_Fired = false; // 一次性触发保护
|
bool m_Fired = false; // 一次性触发保护
|
||||||
std::vector<Fragment> m_Fragments; // 活碎片簿记
|
std::vector<Fragment> m_Fragments; // 活碎片簿记
|
||||||
|
// 待卸载模型路径(碎片销毁的下一帧执行——Destroy 帧尾延迟,
|
||||||
|
// 本帧渲染收集仍引用模型,同帧卸载会渲染悬空指针)
|
||||||
|
std::vector<std::string> m_PendingUnload;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -182,6 +182,12 @@ namespace Engine
|
|||||||
ModelRef LoadModel(const std::string& path, ParsedModel&& parsed);
|
ModelRef LoadModel(const std::string& path, ParsedModel&& parsed);
|
||||||
Model* GetModel(ModelRef h) const { return m_Models.Get(h); }
|
Model* GetModel(ModelRef h) const { return m_Models.Get(h); }
|
||||||
void UnloadModel(ModelRef h) { m_Models.Unload(h); }
|
void UnloadModel(ModelRef h) { m_Models.Unload(h); }
|
||||||
|
// 按路径卸载模型(未加载为 no-op;手上只有路径的场景,
|
||||||
|
// 如脚本程序化碎片模型回收)
|
||||||
|
void UnloadModelByPath(const std::string& path)
|
||||||
|
{
|
||||||
|
m_Models.UnloadByPath(path);
|
||||||
|
}
|
||||||
// 按路径查已加载的模型(未加载返回 null,不触发加载)——
|
// 按路径查已加载的模型(未加载返回 null,不触发加载)——
|
||||||
// 面板 hover 信息卡用
|
// 面板 hover 信息卡用
|
||||||
Model* GetModelByPath(const std::string& path) const
|
Model* GetModelByPath(const std::string& path) const
|
||||||
|
|||||||
Reference in New Issue
Block a user