Fix selection outline never showing for the first entity

entt entity 0's integral bits are 0, colliding with the "no selection"
sentinel: u32 selected == 0 meant "clear selection", so the scene's
first-created entity could never be outlined. Replaced with
std::optional<u32> + FrameSceneData::selectedEntityValid.

Also restore per-batch instance contiguity broken by the batchMats
flattening (e038cb3): entities sharing a mesh scatter across the flat
instance array in collection order. ViewScratch now tracks per-batch
singly-linked lists and appendView aggregates each batch into a
contiguous range, preserving the flat-array performance intent.

Hierarchy click selection now routes through SelectEntity so outline
stays in sync when picking from the panel.
This commit is contained in:
2026-10-05 00:39:48 +08:00
parent e038cb39fa
commit dec171cd1e
8 changed files with 97 additions and 40 deletions
+4 -3
View File
@@ -164,9 +164,10 @@ namespace Engine {
std::optional<u32> TakePickResult();
// ---- Editor 选中描边(UE 编辑器选中轮廓)----
// UI 段选中变化时调用(bits = 实体位模式,0 = 清选中);
// 下一帧 SelectionMask 只画该实体,Outline 在其轮廓叠加描边色
void SetSelectedEntity(u32 entityBits);
// UI 段选中变化时调用;下一帧 SelectionMask 只画该实体,Outline
// 在其轮廓叠加描边色。nullopt = 清选中(实体 0 号位模式 = 0,
// 不能拿位值兼作哨兵)
void SetSelectedEntity(std::optional<u32> entityBits);
// 垂直同步开关(present mode 切换会重建 swapchain),
// 只应在帧 UI 构建段调用(Render 之前)
+19 -6
View File
@@ -26,6 +26,7 @@
#include <glm/gtc/quaternion.hpp>
#include <vulkan/vulkan.hpp>
#include <array>
#include <optional>
#include <unordered_map>
#include <Engine/Renderer/EditorCamera.h>
@@ -73,8 +74,12 @@ namespace Engine {
{
m_PendingPick = PickRequest{true, ndc};
}
// Editor 选中实体(描边):UI 段写入,BuildFrame 落帧并筛出绘制
void SetSelectedEntity(u32 entityBits) { m_PendingSelected = entityBits; }
// Editor 选中实体(描边):UI 段写入,BuildFrame 落帧并筛出绘制。
// nullopt = 清选中(实体 0 号位模式 = 0,不能拿位值兼作哨兵)
void SetSelectedEntity(std::optional<u32> entityBits)
{
m_PendingSelected = entityBits;
}
private:
// CSM:把相机视锥按实用分割切 N 段,每段独立拟合光源正交投影
@@ -91,7 +96,7 @@ namespace Engine {
FrameSceneData m_Frame;
EditorCamera m_EditorCamera; // 编辑器相机(视口 UI 状态,见 GetEditorCamera)
PickRequest m_PendingPick{}; // 拾取请求缓冲(BuildFrame 落帧后清除)
u32 m_PendingSelected = 0; // 选中实体缓冲(描边)
std::optional<u32> m_PendingSelected; // 选中实体缓冲(nullopt = 无选中)
// ---- 剔除中间量(每帧 clear 复用,拼接进 m_Frame 后保留容量)----
struct ViewScratch {
@@ -102,12 +107,18 @@ namespace Engine {
u32 skinBase = 0; // palette 区段基址(0 = identity 槽)
u32 entityBits = 0; // 实体位模式(HitProxy id pass 消费)
};
// 链表尾哨兵:kMaxInstances 远小于 u32 最大值,安全
static constexpr u32 kNoInstance = 0xFFFFFFFFu;
CullResult result;
// 扁平实例数组(所有 batch 的实例共用一份连续内存)。
// InstanceBatch::first/count 在本数组里切片——大场景下避免
// vector<vector> 的小块分配(每个 batch 自己一个 vector,push_back
// 触发各自重分配,cache miss 严重)。每帧 clear() 复用容量
// 实体收集序下同 batch 实例会散布(多实体共享 mesh 交错插入),
// 组内连接关系由 instanceNext 链表维护——appendView 时按 batch
// 聚合拷成连续区间(firstInstance 连续语义的前提)。大场景下
// 避免 vector<vector> 的小块分配,每帧 clear() 复用容量
std::vector<InstanceMatrices> instances;
std::vector<u32> instanceNext; // 与 instances 平行:同 batch 下一实例
// 每 batch 实例链头(与 result.batches 平行;kNoInstance = 空)
std::vector<u32> batchHead;
// 每 batch 实例距离累加(透明排序用:平均距离降序 = 远→近)
std::vector<f32> batchDistSum;
// batch 查找:数组寻址(下标 = Mesh::GetSlot(),值 = 每 LOD 的
@@ -119,6 +130,8 @@ namespace Engine {
result.batches.clear();
result.instanceCount = 0;
instances.clear(); // 容量保留,复用
instanceNext.clear();
batchHead.clear();
batchDistSum.clear();
for (auto& e : lookup) e.fill(0); // 容量保留,置零复用
}
+4 -1
View File
@@ -167,9 +167,12 @@ namespace Engine {
// HitProxy/HitProxyCopy 两 pass 据此决定是否绘制/拷贝)
PickRequest pickRequest{};
// 选中描边(UE 编辑器语义):选中实体的位模式(0 = 无选中)。
// 选中描边(UE 编辑器语义):选中实体 + 位模式。位模式不能兼作
// "无选中"哨兵——entt 首个实体 0 号的 to_integral == 0("点击
// 场景第一个创建的实体没有描边"的根因),有效性与位值解耦。
// BuildFrame 据此从实例表筛出 selectionDraws,SelectionMask pass
// 只画选中实体 → Outline pass 在 mask 边缘叠加描边色
bool selectedEntityValid = false;
u32 selectedEntityBits = 0;
std::vector<SelectionDraw> selectionDraws;