three.js地图视口瓦片(二):如何形成完整视口多边形

地面 footprint:四条射线如何形成完整视口多边形

屏幕角点射线给出 r(t)=o+t dr(t)=o+t\,d r(t)=o+td。对于普通二维瓦片源,本书按零高程教学平面 Y=0 求交,把四个命中点转换为 normalized Mercator,得到 cover 的唯一地面输入。

1. 为什么地面固定为 Y=0

当前手册只讨论 planar / Web Mercator 普通 source。没有 terrain 或显式 elevation envelope 时,瓦片 geometry 的空间支持面就是零高程平面。

不能用完整三维视锥 AABB 代替地面求交。那样会把位于视锥上方、但不与地图地面相交的 tile 纳入 ideal cover,污染成员关系和请求优先级。地基契约 F2 因此要求 flat source 采用零高程交点。

2. 射线与平面求交

射线 y 分量为:
ry(t)=oy+t dy r_y(t)=o_y+t\,d_y ry(t)=oy+tdy

令 ry(t)=0 r_y(t)=0 ry(t)=0:
t=− oydy t=-\frac{o_y}{d_y} t=−dyoy

判断顺序必须明确:

  1. 若 ∣dy∣<ε |d_y|<\varepsilon ∣dy∣<ε,射线与地面平行,失败;
  2. 计算 tt t;
  3. 若 tt t 非有限或 t<0t<0 t<0,交点不在前向射线上,失败;
  4. 否则命中点为 h=o+t dh=o+t\,d h=o+td。

教学实现使用 1e-12 作为方向平行阈值。它只用于识别数值上无法可靠求交的射线,不用于裁剪 cover 或限制地面距离。

3. 从 render-local 回到 normalized Mercator

h=(h_x,0,h_z) 位于当前 render-local 坐标。snapshot origin 记录地图中心的 Mercator 位置,worldSize 把一个完整世界换算成当前 world units:
xm=originx+ hxworldSize x_m=origin_x+\frac{h_x}{worldSize} xm=originx+worldSizehx
ym=originy+ hzworldSize y_m=origin_y+\frac{h_z}{worldSize} ym=originy+worldSizehz

这里 Three.js +Z 对应 Mercator 向南的 +y,所以第二式没有负号。

不要在这里把 xm x_m xm clamp 到 [0,1]。超出范围代表视口看到了相邻水平世界,后续会把连续 unwrappedX 拆成 canonical x 与 wrap。垂直 Mercator 边界则由投影与 tile 枚举规则处理。

4. 四个点为何按顺时针排列

角点固定为:

text 复制代码
1 top-left
2 top-right
3 bottom-right
4 bottom-left

在 screen 与 normalized Mercator 的"y 向下/向南"约定中,这个顺序是顺时针。顶视时,z2、512×512 视口、中心 (0°,0°) 得到:

text 复制代码
1 (0.375, 0.375)
2 (0.625, 0.375)
3 (0.625, 0.625)
4 (0.375, 0.625)

pitch 与 bearing 会把正方形变成拉伸、旋转的凸四边形,但中心投影不会任意交换角点。保留输入顺序有三项价值:

  • polygon clipping 不需要重新计算凸包;
  • 边 i → i+1 始终对应屏幕同一条边;
  • 相同 snapshot 的序列化输入稳定,可形成可恢复遍历 identity。

5. 为什么成功 footprint 是凸的

屏幕矩形是凸集。所有角点射线从同一个相机中心出发,并与同一平面在相机前方相交;这种中心投影把屏幕矩形映射成平面上的凸四边形,只要整个矩形都处在可求交半空间内。

这项凸性让下一阶段可以使用确定性的 convex polygon clipping:

text 复制代码
viewport footprint ∩ tile rectangle

若投影退化到少于三个唯一 Mercator 点,就没有有面积的 polygon。resolver 返回 degenerate-ground-footprint,而不是交给 clipping 猜测。

6. all-or-none 完整性

四条射线按顺序计算,但中间数组不是公共结果。只有四个点全部成功并通过退化检查时,才原子发布:

js 复制代码
Object.freeze({
  valid: true,
  coverageComplete: true,
  points: Object.freeze(points),
  reason: null,
});

若第 4 条射线失败,第 1--3 条虽然在局部变量中存在,返回值仍是:

js 复制代码
{
  valid: false,
  coverageComplete: false,
  points: [],
  reason: "viewport-ray-misses-ground",
}

这不是保守程度的偏好,而是 ideal cover 的形成条件:只有 valid=true && coverageComplete=true 才能产生目标 keys。部分 polygon 会漏掉可见区域,违反 F3。

7. 四类 fail-closed reason

reason 触发条件 对外结果
invalid-transform-snapshot snapshot 缺字段、矩阵长度错误或存在非有限数 invalid、empty points
invalid-homogeneous-coordinate 逆投影得到不可除的 w 或非有限坐标 invalid、empty points
viewport-ray-misses-ground 射线平行、朝天或交点在相机背后 invalid、empty points
degenerate-ground-footprint 四次命中少于三个唯一 Mercator 点 invalid、empty points

reason code 描述几何失败,不描述网络、内容或 GPU 状态。相同 snapshot 必须得到相同 reason。

8. GroundFootprint 的不可变结构

成功 point 的结构是:

js 复制代码
{
  ndc: { x, y, name },
  renderLocal: { x, y, z },
  mercator: { x, y },
  distance,
}

顶层对象、points 数组、每个 point 及其三个嵌套对象全部冻结。这样 cover、overlay 和 inspector 可以共享同一结果,却不能相互改写。

同时保存三个空间不是冗余:

  • ndc 说明点来自哪个屏幕角;
  • renderLocal 直接用于 Three.js 调试线;
  • mercator 用于 tile clipping;
  • distance 用于稳定优先级与诊断。

生产算法不应从渲染对象 userData 反推这些事实。

9. 与单点拾取的边界

PlanarTransform.screenPointToGround(x,y) 服务 cursor 拾取:某个点 miss 时返回 null,其他 screen point 仍可单独查询。

resolveGroundFootprint(snapshot) 服务视口完整性:任一角 miss 时整个结果 invalid。

二者可以复用相同数学,但不能共享错误语义。为避免 live camera 读取,getGroundFootprint() 直接委托 snapshot resolver;单点拾取函数保持浏览器交互 API 不变。

10. 本章建立的不变量

  1. 普通二维 source 只与零高程平面求交;
  2. 交点参数为 t=−oy/dy t=-o_y/d_y t=−oy/dy,且必须有限、非负;
  3. Mercator x 不因 world wrap 被 clamp;
  4. polygon 保持 TL → TR → BR → BL 的稳定顺序;
  5. 成功结果至少有三个唯一点并且完整冻结;
  6. 任一失败返回 invalid、incomplete、empty points 与确定性 reason;
  7. footprint 不读取 request、record、cache、GPU 或相机 settling 状态。

相关推荐
Dovis(誓平步青云)几秒前
现场问题先关闭还是先验收,建筑任务怎样处理依赖
前端·javascript·数据库·mysql·音视频
用户84298142418104 分钟前
压缩 ≠ 加密:前端代码保护的三层实践与踩坑记录
前端·javascript
怕浪猫1 小时前
12个UI组件库,Star最低的下载最多
前端·javascript·面试
溪语流沙1 小时前
【Web全栈进阶】Vue3起步:组件、模板语法、响应式数据
前端·javascript·vue.js·css3
ZoeLandia2 小时前
TypeScript 01:类型注解与类型推断
前端·javascript·typescript
第六种自由3 小时前
[路飞]_Leetcode 22. 括号生成
javascript·算法·leetcode
纸片人3 小时前
甲方只给了一张卫星截图,我用 Three.js + Blender 建了一座 3D 能源站
three.js·数据可视化
纸片人3 小时前
从一座城市钻进一个房间:Three.js + Blender 做一个五级钻取的数字楼宇大屏
three.js·数据可视化
JAVA老架构AI智能化3 小时前
多图叠加比对
前端·javascript·chrome
踏月了无痕3 小时前
01写一个简单的API服务
javascript