Technical note
选择高亮与 Selection Layer
记录 CAD/CAE Viewer 中选择、高亮、hover 和基础显示状态分离的工程过程:为什么不能总是直接修改基础 Geometry 的颜色或透明度,以及 Selection Layer 如何配合 RangeTable、DisplayBatch 和 DisplayBucket 保持交互状态可控。
背景:选择高亮不是简单改颜色
在 CAD/CAE Viewer 里,选择高亮看起来是一个很小的功能。
用户点中一个面,把它变黄;鼠标移动到一条边,把它描亮;工程树选中一个对象,三维视图里同步高亮。直觉上,这些操作都像是“改颜色”。
早期 Viewer 也常常这样做:找到被选中的 Geode,直接改它的颜色、透明度或渲染状态。
小模型下,这个做法简单有效。
但模型变大、交互变复杂之后,问题就出来了。
选择、hover、透明度、隐藏、隔离、基础材质、工程树状态、修复预览,这些状态会叠在同一批显示对象上。如果它们都直接修改 base display,很容易出现状态污染:
hover 结束后颜色没有恢复;
selection 覆盖了用户设置的材质;
隐藏对象仍然能被高亮;
透明对象恢复后 alpha 不对;
清除选择后残留临时节点;
局部修复结束后旧高亮还挂在场景里。
所以选择高亮真正要解决的,不只是“怎么画得醒目”,而是:
交互反馈如何不破坏基础显示状态。
这也是 Selection Layer 存在的原因。
旧做法:直接修改基础节点
旧 Viewer 中,选择和高亮常见路径是:
pick / tree selection
↓
find Geode list
↓
set color / transparency
↓
update node state
这个路径一开始是合理的。
因为旧显示结构通常是一个 face 或 edge 对应一个 Geode。既然 Geode 就是业务对象,那选中它时直接改这个 Geode 的颜色,逻辑很自然。
hover 也是类似的。
鼠标移动到某个 face,找到对应 Geode,改成 hover 颜色;鼠标移开,再改回原色。
小模型下,这套做法可用。节点少,状态简单,用户操作也不会一下子覆盖成千上万个面。
但它有一个隐含前提:
基础显示状态容易恢复。
大模型和复杂交互下,这个前提很难成立。
状态污染怎么出现
状态污染通常不是某一行代码造成的,而是多个功能叠加后的结果。
比如一个对象原本有自己的材质颜色。用户选中它后,高亮逻辑把颜色改成黄色。随后用户调整透明度,再 hover 到它的某个面,hover 又改了一次颜色。最后清除选择时,系统需要知道应该恢复到哪个状态:
原始材质颜色?
用户修改后的颜色?
透明度覆盖后的颜色?
hover 之前的 selection 颜色?
隐藏状态下是否应该恢复?
如果这些状态都直接写在基础节点上,恢复逻辑就会越来越复杂。
另一个问题是临时状态和持久状态混在一起。
选择高亮、hover 高亮、框选预览都属于临时反馈;用户设置颜色、透明度、隐藏对象则更接近持久显示状态。它们的生命周期不同,不应该写到同一层里。
工程上更稳的做法是:
Base Display 负责模型本身的基础显示;
Selection Layer 负责临时交互反馈。
这样做不是为了让架构看起来更复杂,而是为了让显示状态有边界。
Selection Layer 的基本判断
Selection Layer 可以理解为一层专门处理交互反馈的显示层。
它的目标不是替代基础显示,而是覆盖在基础显示之上:
Base Display
├─ surface batch
├─ edge batch
├─ material / opacity
└─ visibility
Selection Layer
├─ selected face highlight
├─ selected edge highlight
├─ hover outline
├─ large selection hint
└─ temporary repair highlight
这层结构的核心判断是:
selection、highlight、hover 不应该总是直接修改 base display。
这里要注意,“不应该总是”不是“绝不”。
在某些小范围选择里,直接更新颜色数组可能更快;在某些完整 bucket 操作里,修改批次状态也合理。关键是这些操作必须受控,并且能恢复。
Selection Layer 的意义,是把临时视觉反馈集中管理:
什么时候创建;
什么时候替换;
什么时候清理;
什么时候降级;
什么时候恢复基础显示。
这些事情不应该散落在各个业务流程里。
不同选择入口,不同显示路径
CAD/CAE Viewer 的选择入口很多。
它们看起来都是“选中”,但显示路径并不一样。
鼠标点击通常从拾取开始:
screen point
↓
Drawable + primitiveIndex
↓
RangeTable
↓
face / edge / body target
↓
Selection Layer
工程树选择则是反向路径:
tree item
↓
object / body / face / edge target
↓
RangeTable / DisplayData
↓
highlight target ranges
↓
Selection Layer
框选会产生一组目标。它可能包含很多 face、edge 或 body,因此更依赖批量处理、去重和可见性过滤。
Ctrl+A 更特殊。
它不是用户点到某个区域,而是当前选择模式下“所有可见目标”。如果当前是 face 模式,它可能意味着所有可见 face;如果是 edge 模式,则是所有可见 edge;如果是 object 或 body 模式,还要考虑 body 归属和可见性过滤。
这些入口最终都进入 Selection Layer,但前面的解析过程不同:
点击依赖 RangeTable 从显示命中反查语义;
工程树选择依赖业务目标反查显示范围;
框选依赖屏幕区域和可见对象过滤;
Ctrl+A 依赖当前选择模式和可见性语义;
hover 需要轻量、及时、可替换;
修复流程里的临时选择还要保证能被快速清理。
因此,Selection Layer 不是一个简单的 selected color 开关,而是一套从选择目标到视觉反馈的调度层。
面、边、对象的高亮差异
面选择、边选择、对象选择的高亮方式并不相同。
面选择通常可以生成覆盖面片,或者对对应 face range 做受控颜色更新。小范围面选择可以完整高亮,用户能清楚看到选中的区域。
边选择更适合走独立 edge highlight。
CAD edge 和 surface 不是同一类显示对象。边高亮通常需要更清晰的线段或描边,而不是修改面颜色。
对象或 body 选择则可能展开成很多 face 和 edge。
小对象可以完整高亮;大对象如果包含大量面,完整生成高亮 Geometry 可能会阻塞交互。这时就需要 hint 或 fallback,例如高亮包围盒、关键边、bucket hint,或者分批提交高亮。
这也是 Selection Layer 必须理解选择类型的原因。
它不能只拿到一个“选中集合”就机械改颜色,而要根据目标规模、类型和当前显示状态选择合适的视觉策略。
大模型下不能总是完整高亮
小模型里,完整高亮是最直观的:选中哪些面,就把哪些面都覆盖出来。
大模型下,这不一定可行。
框选一个大对象、Ctrl+A 选择所有可见面、工程树选中一个复杂 body,都可能产生大量高亮目标。如果一次性为所有目标生成高亮 Geometry,或者一次性更新所有颜色范围,前台交互会被拖住。
所以 Selection Layer 需要有分级策略:
小范围选择:
完整高亮。
中等范围选择:
按 bucket 批量高亮,必要时分块提交。
大范围选择:
使用 hint / fallback;
或只显示部分视觉反馈;
或使用更轻量的 tint / outline。
这里的关键不是追求“所有情况都完整画出来”,而是保持交互可控。
用户选择一个巨大对象时,Viewer 至少应该及时给出稳定反馈,而不是卡住等待完整高亮构建。
这种取舍在工程上很常见:视觉完整性、交互响应和实现复杂度之间需要平衡。
清理和恢复比高亮本身更重要
选择高亮最容易被低估的部分,是清理。
hover 要在鼠标移开时清理。
selection 要在清除选择、切换模型、隐藏对象、切换选择模式时清理。
修复、补面、预览这类交互流程,还可能在操作提交或取消时清理临时高亮。
如果清理不及时,会出现很多难定位的问题:
已取消的面还保持高亮;
补面预览结束后残留黄色面;
边选择切换回面选择后仍有旧边高亮;
模型重建后旧 highlight batch 指向旧 Geometry;
隐藏对象后高亮层还在显示它。
因此 Selection Layer 需要明确生命周期:
begin selection
-> cancel previous pending highlight
-> clear old temporary geometry
-> build or schedule new highlight
selection changed
-> replace previous highlight
clear selection / reload / repair end
-> remove temporary highlight
-> restore base display state
高亮本身只是创建视觉反馈。
清理和恢复才决定系统是否稳定。
这也是我后来对选择高亮的一个判断:真正难的不是把对象变黄,而是让它在所有流程结束后干净地恢复。
Selection Layer 和 RangeTable 的关系
RangeTable 提供语义映射,Selection Layer 消费这些语义。
点击场景时,RangeTable 把 Drawable + primitiveIndex 转成 faceTag 或 edgeTag。Selection Layer 再根据这个目标构建高亮。
工程树选择时,Selection Layer 已经拿到 object、body、face 或 edge 目标,但仍然可能需要 RangeTable 定位它所在的 batch 和 primitive range。
可以理解为:
RangeTable:
负责“这个显示命中对应哪个 CAD 对象”;
也负责“这个 CAD 对象当前在哪段显示范围里”。
Selection Layer:
负责“这个 CAD 对象应该如何被视觉反馈”。
两者边界要清楚。
RangeTable 不应该保存选择状态;Selection Layer 也不应该自己发明拓扑映射。否则合批、局部重建和拾取一致性都会变复杂。
Selection Layer 和 DisplayBatch / DisplayBucket 的关系
DisplayBatch 是基础显示批次。
DisplayBucket 是大模型下的空间或逻辑分块。
Selection Layer 需要尊重这两层结构。
如果一个选择目标只影响少量 face,可以直接为这些 face 生成小的高亮 batch。
如果目标跨多个 bucket,Selection Layer 可以按 bucket 聚合,减少高亮 Geometry 数量,也方便分块提交。
如果目标非常大,Selection Layer 可以使用 bucket 级 hint 或对象级 hint,而不是展开所有 primitive。
这让选择高亮不再是“目标数量是多少,就生成多少个节点”,而是按显示批次和空间局部性组织视觉反馈。
不过边界也很明确:
Selection Layer 不应该随意改写基础 DisplayBatch 的拓扑结构。
基础批次、RangeTable 和 bucket 是模型显示的稳定层;Selection Layer 是临时反馈层。必要时可以做受控的 tint 或颜色数组更新,但必须能恢复。
交互式修复里的特殊要求
在交互式修复、补面、边选择这类流程里,选择高亮更容易出问题。
这类操作通常有临时对象、预览对象和真实模型对象。用户可能先选择边,再生成预览面,再确认补面或取消操作。每一步都可能改变当前选择目标和可见对象。
如果高亮层没有及时清理,就会把上一步的交互反馈带到下一步里。
用户看到的高亮不再代表当前状态,后续操作也可能误用旧目标。
所以修复类流程对 Selection Layer 有两个要求:
临时高亮要能快速替换;
操作结束、取消、重建或切换模式时要能完整清理。
这也是为什么选择高亮不能散落在各个业务流程里。
它需要一个统一入口,至少要保证生命周期和基础显示状态是可控的。
边界和代价
Selection Layer 解决了状态污染,但也增加了复杂度。
首先,它需要管理更多显示对象。
除了 base display,还会有 selection batch、hover batch、edge highlight、hint geometry 等临时对象。
其次,它需要选择策略。
小范围完整高亮、大范围 hint、bucket 聚合、分块提交,这些策略都要根据目标规模和当前状态判断。
再次,它必须和 RangeTable、DisplayBatch、DisplayBucket 保持一致。
局部重建后,旧高亮不能继续引用旧 Geometry;隐藏后,高亮不能继续显示隐藏对象;ShowAll 或 ClearScene 时,临时层必须清干净。
最后,Selection Layer 不应该变成新的大杂烩。
选择集合、持久样式、隐藏状态、业务修复状态,都应该有自己的归属。Selection Layer 只负责把当前交互状态转成视觉反馈,并确保可替换、可清理、可恢复。
小结
选择高亮不是简单改颜色。
旧 Viewer 直接修改基础节点,在小模型和早期功能验证中很有效。但当选择、hover、透明度、隐藏、工程树联动和修复预览叠加后,基础显示状态很容易被污染。
Selection Layer 的核心价值,是把临时交互反馈和基础显示数据分开。
Base Display 负责稳定显示模型,Selection Layer 负责选中、hover、提示和临时高亮。
它依赖 RangeTable 找回 CAD 语义,依赖 DisplayBatch 构建高亮批次,依赖 DisplayBucket 控制大模型下的高亮规模。它也必须有清晰的清理和恢复机制。
对于 CAD/CAE Viewer 来说,Selection Layer 不是锦上添花的视觉效果,而是让大模型交互状态保持可控的一层基础结构。
下一篇就可以继续讲空间分桶与局部重建。因为 Selection Layer 解决的是“临时交互状态怎么不污染基础显示”,而空间分桶要解决的是另一个问题:当基础显示已经合批之后,局部变化怎么避免牵动整份模型。