17 KiB
Earth Interactable Layer Plan
背景
状态:Phase 1 已经开始落地,Phase 2 的 BGP 事件 / 观测站迁移和 Phase 3 的算力中心迁移也已完成。frontend/public/earth/js/interactable.js 已新增,AIS 船只、BGP 事件、BGP 观测站和算力中心图层已经改为通过 createInteractableLayer() 使用通用批量 Points、hover / locked overlay、默认 glow、状态更新、asset icon 预加载、屏幕空间 picking、固定 / 距离缩放和跨 Interactable 同坐标避让。登陆点因 THREE.Points 边缘深度裁切和贴地层级要求,已退回专用 THREE.Sprite 黄色球路径,并与海缆同高度同 renderOrder。后续阶段聚焦把可复用的扩圈 / 雷达扇形动画正式沉淀成 animations 扩展。
当前实现说明和接入示例见:
当前 AIS 船只图层已经形成了一个适合作为基准的交互图标模式:
- 普通态使用批量
THREE.Points渲染,避免每个对象一个Sprite带来的 draw call 和透明排序压力。 - hover / locked 态使用单点 overlay 叠加 glow,不改变普通批次,交互反馈清晰且成本低。
- moving / anchored 船只通过 canvas 点纹理表达不同形状,moving 船只还按航向分桶。
- 拾取走屏幕空间命中,拖动和惯性期间跳过高频 hover picking。
- 图层高度贴近地表,仅保留很小的深度余量,避免“浮在表面层”的观感。
这个模式不应该只服务船只。后续 BGP 事件、BGP 观测站、算力中心、新闻事件、告警、地面传感器等都可能需要“图标类可交互元素”。如果每个图层继续各写一套 icon、glow、hover、locked、动画、picking 和图例逻辑,视觉会漂移,性能策略也会重复分叉。登陆点已经验证为例外:需要完整贴地且不被球面边缘裁切时,专用 Sprite 路径比通用 Points 更合适。
目标是把船只图层的成功做法抽象成一个通用接口:业务图层只描述“要画什么、在哪里、怎么交互”,底层统一负责批量渲染、默认 glow、状态 overlay、动画槽位、拾取和生命周期。
目标
- 建立统一的 Earth 交互图标接口,作为未来地表图标类元素的默认入口。
- 以 AIS 船只 glow 为默认 glow 视觉,其它图标默认沿用同一套 glow 质感。
- 保留图标颜色、状态颜色、hover 放大、locked 强调、dimmed 聚焦、动画扩展等能力。
- 支持 canvas / SVG / image icon,不强行要求所有图标都可重着色。
- 保持船只当前性能路线:批量绘制普通态,少量 overlay 处理交互态。
- 给 BGP 事件扩圈、BGP 观测站雷达扇形等补充动画留出正式扩展点。
非目标
- 不在第一阶段重写所有 Earth 图层。
- 不把卫星、海缆、国家边界、真实地形这类非图标图层纳入同一个接口。
- 不为了抽象牺牲业务图标的差异表达,例如船只航向、BGP 事件严重级别、观测站雷达扫掠。
- 不要求图片图标支持运行时重着色;图片图标只能通过预制多状态图片或 overlay tint 做有限表达。
核心设计
建议新增一个通用模块,例如:
frontend/public/earth/js/interactable.js
它导出一个工厂或注册函数:
createInteractableLayer({
id,
earth,
renderOrder,
altitudeOffset,
icon,
scale,
glow,
colors,
states,
animations,
picking,
data,
getPosition,
getKind,
getRotation,
getPayload,
});
业务模块仍保留自己的数据加载、图例、详情卡字段和业务语义。例如 vessels.js 负责 AIS 数据和船型映射,但 icon 渲染、hover overlay、locked overlay、默认 glow 和屏幕空间 picking 可以逐步迁入 interactable.js。
参数草案
| 参数 | 类型 / 示例 | 默认值 | 说明 |
|---|---|---|---|
id |
"vessels" |
必填 | 图层唯一标识,用于 debug、picking、legend 和状态缓存。 |
earth |
THREE.Object3D |
必填 | 图层挂载目标,通常是 Earth root。 |
renderOrder |
4.4 |
4 |
普通 icon 批次和 overlay 的基础渲染顺序。 |
altitudeOffset |
0.2 |
0.2 |
图层高度,语义为 CONFIG.earthRadius + altitudeOffset。地表图标默认贴近真实地形基础层。 |
icon |
{ type, source, draw, size, bins } |
必填 | 图标来源。支持 canvas draw、SVG URL、image URL、内置 shape。 |
icon.fitSize |
60 或 { width: 60, height: 60 } |
atlasCellSize |
asset 图标在 atlas canvas 内的最大绘制尺寸,默认居中等比 contain。SVG / 图片文件只负责原始形状,不需要为了显示大小手写 transform。 |
scale |
{ base, min, max } |
{ base: 1 } |
基础缩放和距离稳定范围。当前船只可映射到 VESSEL_POINT_SIZE / baseScale。 |
sizeMode |
"fixed" / "distance" |
"fixed" |
是否固定屏幕像素尺寸;非 fixed 时按相机到地表距离做比例缩放。 |
sizeScale |
{ min, max, referenceFov } |
{ min: 0.12, max: 3, referenceFov: 75 } |
sizeMode !== "fixed" 时的缩放限制和参考视角。 |
glow.enabled |
true / false |
true |
是否启用默认 glow。默认 glow 以船只 hover / locked overlay 为基准。 |
glow.intensity |
0.0 - 2.0 |
1 |
glow 强度,内部映射到 canvas shadowBlur、opacity 或 shader uniform。 |
glow.colorMode |
"state" / "icon" / "fixed" |
"state" |
glow 颜色来源,默认跟随状态颜色。 |
hover.scale |
1.0 - 2.0 |
1.18 |
hover 放大倍率。当前船只保持同尺寸 glow overlay,接口仍保留放大能力供其它图层使用。 |
hover.mode |
"scale" / "glow-only" / "custom" |
"scale" |
hover 反馈方式。船只可用 "glow-only",其它图标默认放大。 |
colors.normal |
"#4A90D9" |
icon 原色 | 普通态颜色。只有可上色 icon 生效。 |
colors.hover |
"#7dd3fc" |
normal | hover 态颜色。 |
colors.locked |
"#ffffff" |
hover | locked 态颜色。 |
colors.dimmed |
"#9B9B9B" |
normal | 聚焦其它对象时的弱化颜色。 |
colors.byKind |
{ cargo: "#4A90D9" } |
{} |
按业务类型着色,如船型、BGP 严重级别。 |
colorable |
true / false |
由 icon 类型推断 | canvas shape 和 SVG mask 通常可上色;图片默认不可上色。 |
opacity |
{ normal, hover, locked, dimmed } |
船只当前值 | 各状态透明度。 |
rotation |
{ enabled, bins, getAngle } |
disabled | 是否按角度分桶,例如船只按 COG 分 32 桶。 |
animations |
IconAnimationSpec[] |
[] |
补充动画列表,例如扩圈、雷达扇形、脉冲、轨迹尾迹。 |
picking.radiusPx |
22 |
20 |
屏幕空间命中半径。 |
picking.throttleMs |
100 |
80 |
hover picking 节流。 |
picking.skipWhileDragging |
true |
true |
拖动和惯性期间跳过 hover picking。 |
zIndexPolicy |
"surface-icon" |
"surface-icon" |
预设层级策略,避免每个业务图层手写高度和 renderOrder。 |
avoidance.enabled |
true / false |
true |
是否参与跨 Interactable 的同坐标避让。默认开启,同一经纬度下的图标会沿地表切平面小幅排开,方便辨认和选择。 |
avoidance.radius |
number |
1.1 |
同坐标避让的第一圈半径,单位为地球本地坐标单位。 |
avoidance.precision |
number |
4 |
经纬度归并精度,默认约等于只处理几乎完全重叠的图标。 |
legend |
{ label, color, shape }[] |
[] |
可选图例声明,业务层也可以继续自己导出。 |
metadata |
object | {} |
业务扩展数据,不参与渲染但参与 tooltip / info-card / search。 |
Icon 规格
图标输入建议分三类:
{
type: "canvas-shape",
size: 128,
draw(ctx, state) {
// draw triangle / dot / custom shape
},
}
{
type: "svg-mask",
source: "/earth/assets/icons/bgp-event-dot.svg",
colorable: true,
}
{
type: "image",
source: "/earth/assets/icons/vendor-logo.png",
colorable: false,
stateSources: {
hover: "/earth/assets/icons/vendor-logo-hover.png",
},
}
颜色策略:
canvas-shape默认可上色,适合船只、事件点、雷达站这类符号。svg-mask如果能作为 mask 使用,则可上色;如果是完整多色 SVG,则按图片处理。image默认不可上色;需要状态变化时使用stateSources或额外 glow / ring。
默认 Glow 规范
默认 glow 以当前船只 overlay 为视觉基准:
- 普通态尽量不启用 glow,保持地图干净。
- hover / locked 态叠加同位置 overlay。
- glow 颜色默认跟随状态颜色或业务类型颜色。
- glow blur 应该稳定,不随 camera zoom 夸张膨胀。
- 允许通过
glow.intensity控制强度,但不要让业务图层各自发明完全不同的光晕语言。
建议内部把 glow 拆成两个层次:
textureGlow:canvas texture 里的shadowBlur,适合小图标 hover / locked。effectGlow:额外 ring / halo / pulse,适合告警、BGP 事件和锁定强调。
状态模型
通用状态至少包含:
| 状态 | 触发 | 默认表现 |
|---|---|---|
normal |
普通显示 | 批量 Points,使用 normal 颜色和 opacity。 |
hover |
指针悬停 | 默认放大并显示 glow;船只可配置为同尺寸 glow-only。 |
locked |
点击锁定 / 详情打开 | 强 glow、更高 opacity,可选 ring 或 pulse。 |
dimmed |
聚焦其它对象 | 降低 opacity,保留上下文。 |
hidden |
图层关闭或过滤 | 不参与绘制和 picking。 |
alert |
业务告警 | 可叠加动画,不替代 locked 状态。 |
状态更新需要增量化:只在 hover 目标、locked 目标、过滤条件、数据版本或相机距离阈值变化时更新,不在每帧遍历全部 icon 写材质属性。
动画扩展
动画不直接塞进 icon 基础参数,而是作为 animations 列表注册。每个动画声明自己的 geometry / material / update 策略:
{
type: "expanding-ring",
when: ["alert", "locked"],
color: "state",
radiusPx: [10, 42],
durationMs: 1400,
opacity: [0.8, 0],
}
{
type: "radar-sweep",
when: ["normal", "hover", "locked"],
angleDeg: 72,
rotationMs: 2600,
opacity: 0.36,
}
首批建议内置动画:
| 动画 | 用例 | 说明 |
|---|---|---|
pulse-ring |
locked、告警点 | 原地呼吸环,强调选中对象。 |
expanding-ring |
BGP 事件 | 向外扩散的事件波纹。 |
radar-sweep |
BGP 观测站 | 扇形扫描,可持续旋转。 |
orbiting-dot |
数据流 / collector 活跃态 | 小点绕 icon 环绕,表达活动状态。 |
trail |
移动目标 | 可选短尾迹,船只或飞机类目标使用。 |
动画必须支持批量或分组绘制,避免为每个对象创建独立的高频更新对象。只有 locked / hover / 少量 alert 对象可以使用单对象 overlay。
渲染策略
普通态
普通态优先使用分桶 THREE.Points:
- 按 icon 类型、可上色策略、旋转分桶、纹理 key 分组。
- 每组一个
BufferGeometry,存position、color、必要的payloadIndex。 PointsMaterial.sizeAttenuation = false,保持屏幕尺寸稳定。depthTest = true,depthWrite = false,避免遮挡关系破坏地表。
交互态
hover / locked 使用少量 overlay:
- overlay 复用
THREE.Points单点对象或小型 ring mesh。 - overlay texture 从统一 cache 获取。
- overlay 更新只写当前 hover / locked 的 position、texture、opacity、size。
高密度升级
当某类图标超过分桶 Points 的舒适区,才考虑升级:
InstancedBufferGeometrybillboard。- 自定义 shader 支持 per-instance rotation / scale / opacity。
- 视口 bbox / LOD / cluster。
这个升级不应该改变业务接口,只替换底层 renderer。
Picking 策略
沿用船只当前方向:
- 默认屏幕空间 picking,而不是 Three.js 对每个 Sprite / Points 做 raycast。
- 每个 icon 保留世界坐标和业务 payload。
- 每次 pointer move 将候选点投影到屏幕,按半径和深度判断命中。
- 拖动、惯性旋转、相机剧烈变化期间跳过 hover picking。
- click 时允许做一次更精确的 picking。
后续可以按图层或经纬度网格增加空间索引,减少候选点数量。
与现有图层的迁移路径
Phase 1:抽出船只基准能力
- 从
vessels.js提取 texture cache、canvas icon draw、overlay glow、分桶 Points 创建、状态增量更新。 - 保持
vessels.js的公开 API 不变:loadVessels()、toggleVessels()、getVesselMarkers()等继续可用。 - 新模块先只服务船只,确保视觉没有回退。
Phase 2:迁移 BGP 事件和观测站
- BGP 事件使用
canvas-shape,已接入Interactable。 - 严重级别映射到
colors.byKind,并通过通用getPointSizeMultiplier保留严重级别尺寸倍率。 - 当前扩圈效果保留在 BGP 业务动画中,并跟随
Interactablemarker 位置更新。 - BGP 观测站主图标已接入
Interactable,活跃度映射到颜色和getPointSizeMultiplier。 - BGP 观测站 halo / 覆盖扇形继续由 BGP 业务动画表达扫描,并跟随
Interactablemarker 位置更新。
Phase 3:迁移算力中心并评估登陆点
- 算力中心保留现有业务 icon,但接入统一 hover / locked / glow。(已完成)
- 登陆点曾接入同一套
Points渲染,但 pin 类 SVG 在地球边缘会被深度测试裁切;当前保留专用THREE.Sprite,并使用 canvas 生成黄色扁平球,贴到海缆层级。 - TODO:登陆点暂不迁移到完整 Interactable。后续若要统一交互接口,优先考虑 Sprite-backed adapter,只对齐
getMarkers()、getPointerIntersections()、setMarkerState()、updateVisualState()等外观协议,不强行复用THREE.Points、atlas 和跨图层避让。 - 检查图例、搜索和 info-card 是否只依赖业务 payload,而不是依赖渲染对象类型。
Phase 4:形成 Earth 图标层规范
- 在
docs/technical/zh/earth-frontend-context.md记录当前实现入口。 - 在
docs/technical/zh/earth-layer-style-reference.md记录默认 glow、状态颜色、默认高度和动画参数。 - 在
docs/technical/zh/earth-render-layer-order.md记录 surface icon renderOrder 范围。
风险与约束
- 过早抽象可能让船只这种高质量基准被平均化,因此第一阶段必须以船只视觉不回退为验收标准。
- 图片 icon 不可上色,接口需要明确
colorable = false的行为,避免业务层误以为颜色一定生效。 - 动画如果默认开启过多,会重新引入 overdraw 和每帧更新压力;默认只给 hover / locked 或少量 alert 使用。
- 地形开启时,贴地 icon 需要在高度、
depthTest、polygonOffset和 renderOrder 之间保持平衡。 - 统一 glow 不等于所有图标一模一样;业务可以调强度和颜色,但不应破坏整体视觉语言。
验收标准
- 船只迁入通用接口后,普通态、hover、locked、航向、颜色、轨迹和 picking 行为保持一致。
- 新增一个 BGP 事件示例图层配置,不需要复制船只渲染代码即可得到 icon、glow、hover 和扩圈动画。
- 新增一个 BGP 观测站示例图层配置,不需要自写独立动画循环即可得到雷达扇形。
- 关闭图层后对应 icon、overlay、动画和 picking 全部停止。
- 高密度数据下普通态仍走批量绘制,hover / locked 只更新少量 overlay。
- 文档同步说明默认高度、默认 glow、状态模型和动画扩展点。
相关文件
| 文件 | 当前角色 | 未来关系 |
|---|---|---|
frontend/public/earth/js/vessels.js |
船只基准实现,包含分桶 Points、hover / locked overlay、默认 glow 形态 | Phase 1 的抽象来源 |
frontend/public/earth/js/constants.js |
保存船只高度、颜色、透明度、轨迹参数 | 后续可加入通用 surface icon 默认配置 |
frontend/public/earth/js/bgp.js |
BGP 事件和观测站视觉逻辑 | BGP 事件和观测站主图标已接入 Interactable;扩圈、halo 和覆盖扇形仍保留业务动画 |
frontend/public/earth/js/compute-centers.js |
算力中心 icon 和交互 | 已通过 Interactable 接入统一 Points、overlay、glow 和 picking |
frontend/public/earth/js/cables.js |
登陆点 icon 和海缆线 | 登陆点当前使用专用 THREE.Sprite 黄色球,不再走 Interactable;海缆线仍独立渲染 |
frontend/public/earth/js/main.js |
当前集中处理 hover、click、locked 和 info-card 入口 | 后续需要接入通用 icon picking 结果 |
docs/technical/zh/earth-layer-style-reference.md |
当前视觉参数参考 | 实现后同步默认 glow 和通用参数 |
docs/technical/zh/earth-render-layer-order.md |
当前层级参考 | 实现后同步 surface icon 层级范围 |