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 解决的是“临时交互状态怎么不污染基础显示”,而空间分桶要解决的是另一个问题:当基础显示已经合批之后,局部变化怎么避免牵动整份模型。