17 KiB
坐标系统一架构设计方案
1. 背景
当前项目运行在 Navisworks 宿主环境中,程序直接读取和写入的都是 Navisworks 世界坐标:
Point3DVector3DBoundingBox3DTransform3D- 鼠标点击拾取点
Document.UpVector
这些坐标对程序来说属于外部坐标。
项目目前的问题不是“完全没有坐标系抽象”,而是:
- 有一部分代码已经开始抽象
Y-up / Z-up - 另一部分代码仍然直接写死世界
Z - 还有一部分代码把世界原点直接当作业务球心
结果是三种语义混在一起:
- Navisworks 外部坐标语义
- 程序内部几何计算语义
- 工程业务基准语义(球心、安装基准、轨道参考面)
这会导致:
Y-up/Z-up项目切换后功能不一致- 终端安装仿真把世界原点误当成球心
- Rail 姿态和渲染层偷用世界
Z - 不同模块对同一个点的解释不一致
因此,需要建立一套更成熟、更清晰的坐标系架构。
2. 设计目标
本方案的目标不是“到处加 if (isYUp)”,而是建立一个标准的分层架构:
- Navisworks 世界坐标统一视为外部坐标
- 程序内部统一使用一套规范内部坐标
- 工程语义使用独立的业务基准坐标
- 只有插件自带资源才允许引入资产坐标系
- 坐标转换只发生在少数边界入口
- 业务逻辑禁止直接依赖宿主坐标语义
3. 总体方案
3.1 内部统一坐标
程序内部统一使用:
- Canonical Space(规范内部坐标)
- 固定为
Z-up
选择 Z-up 的原因:
- 当前项目大量成熟逻辑本身就是按
Z-up语义构建的 - 动画、渲染、yaw 语义、很多 helper 都更接近
Z-up - 以
Z-up作为内部标准,改造成本低于整体改成Y-up
注意:
- 这只是程序内部选择
- 不代表客户模型必须是
Z-up - 客户仍可继续使用
Y-up项目
3.2 三层坐标语义
flowchart LR
A["Navisworks Host Space\n外部坐标"] --> B["Host Coordinate Adapter\n边界适配层"]
B --> C["Canonical Space (Z-up)\n内部统一坐标"]
C --> D["Project Reference Frame\n业务基准坐标"]
C --> E["路径规划/几何计算"]
C --> F["Rail 姿态/动画"]
C --> G["碰撞检测/结果恢复"]
C --> H["渲染/辅助线/通行空间"]
D --> I["球心"]
D --> J["终端安装基准"]
D --> K["轨道参考面"]
C --> B
B --> L["Navisworks 输出\n渲染/移动/截图恢复"]
三层职责如下:
A. Navisworks Host Space(外部坐标)
宿主 API 直接提供的坐标。
特点:
- 是程序的输入/输出坐标
- UI 文本框、对话框、日志、鼠标拾取结果,统一按宿主坐标系解释和显示
- 可能来自
Y-up项目,也可能来自Z-up项目 - 不应直接当成内部计算坐标
B. Canonical Space(内部统一坐标)
程序内部唯一允许进行几何计算、路径计算、姿态计算的坐标空间。
特点:
- 固定为
Z-up - 只解决坐标轴和方向语义统一问题
- 不承载业务基准含义
C. Project Reference Frame(业务基准坐标)
建立在内部统一坐标基础上的工程语义层。
负责表达:
- 球心
- 项目 up 方向的业务解释
- 终端安装参考面
- 轨道参考面
3.2.1 术语约束
后续文档、代码注释和日志中,统一使用以下说法:
- 宿主坐标系(Host Space)
- 指 Navisworks 文档坐标系
Y-up/Z-up的判断只属于这一层
- 内部坐标系(Canonical Space)
- 指程序内部统一使用的
Z-up坐标系 - 仅供内部计算使用,禁止直接暴露给 UI
- 指程序内部统一使用的
- 资产坐标系(Asset Space)
- 只用于插件自带资源
- 当前明确只有两类:
虚拟物体与单位圆柱体(参考杆资源) - 用于描述这些资源文件自身的
Forward/Up/Side轴约定
禁止继续使用含糊的“本地坐标系”说法,因为它容易混淆:
- 宿主坐标系
- 内部坐标系
- 资产坐标系
后续凡是 UI 输入/输出,必须明确是宿主坐标系语义;凡是姿态或几何内部计算,必须明确是内部坐标系语义;凡是虚拟物体/单位圆柱体资源朝向,必须明确是资产坐标系语义。
- 业务锚点
这层不能偷用世界原点,也不能偷用宿主世界轴。
3.3 坐标系定义必须包含的语义
在本项目中,Y-up / Z-up 不能只被理解成“点坐标怎么换算”。
一个完整的坐标系定义,至少必须显式包含:
UpAxisElevationAxisHorizontalPlane- 必要时的
Handedness
因此:
Y-up的含义是:Y为 up 轴Y为高程轴XZ为水平平面
Z-up的含义是:Z为 up 轴Z为高程轴XY为水平平面
后续凡是出现:
- 高度
- 底面
- 顶面
- 通行空间高度轴
- 俯仰/法向
都必须基于这组定义来解释,不能继续偷用“世界 Z 就是 up”的旧假设。
3.4 资产坐标系是独立层
除了宿主坐标系和内部统一坐标系,还必须区分资产坐标系。
注意,这一层不是所有模型都有。当前项目里只有插件自带资源需要它:
- 虚拟物体资源
- 单位圆柱体参考杆资源
这是另一层独立定义,至少包含:
AssetForwardAxisAssetUpAxis
例如:
- 虚拟物体资源通常按程序约定构建,可能是
Asset X = Forward, Asset Z = Up - 单位圆柱体参考杆资源当前约定为
Asset X = 杆轴方向, Asset Z = 截面 Up
而对于客户真实模型:
- 不单独引入“资产坐标系”概念
- UI 和数据输入输出仍只认宿主坐标系
- 内部算法如需统一姿态,先进入 Canonical Space,再叠加业务参考信息
这意味着:
- 即使宿主坐标系转换已经正确
- 如果资产坐标系语义没有显式处理
- 动画和姿态仍然会出现“路径对了、通行空间对了、模型自己站歪了”的现象
因此,后续姿态系统必须显式区分:
- 宿主坐标系定义
- 内部统一坐标系定义
- 业务基准定义
- 资产坐标系约定(仅限虚拟物体和单位圆柱体等插件自带资源)
3.5 Rail 局部坐标系与动画跟踪点
Rail 路径不能再只理解成“沿世界 up 做上下偏移”。
对 Rail 来说,必须显式建立一套局部坐标系:
Forward- 沿轨道前进方向
Normal- 安装法向
- 可以是任意空间角度
- 但应尽量贴近项目 up
Lateral- 由
Normal x Forward或正交化后确定
- 由
后续所有 Rail 相关计算都应建立在这套 RailLocalFrame 之上:
- 物体姿态
- 通行空间姿态
- 路径参考点到动画跟踪点的偏移
- 终点贴合语义
3.6 动画跟踪点统一语义
动画系统内部不应混用:
- 底面中心
- 顶面中心
- 包围盒中心
- 路径参考点
统一规则应为:
- 动画跟踪点 = 当前动画主链路使用的唯一位置语义
- 当前阶段建议统一为:几何中心
这样做的原因是:
- 中心点对三维旋转最中性
- 不依赖“当前哪一面朝上”
- 不依赖宿主
Y-up / Z-up - 物体姿态与碰撞恢复、截图回放更容易共用同一套语义
不同路径的业务需求通过“参考点 -> 跟踪中心点”的显式偏移来表达:
- 地面/吊装:通常沿
+up / -up - Rail:沿
RailLocalFrame.Normal
这样:
- 业务层只表达“路径参考点”和“法向偏移”
- 动画层只认“中心点 + 姿态”
4. 核心设计原则
4.1 Navisworks 世界坐标统一视为外部坐标
这是本方案的第一条硬规则。
对程序而言,以下数据统一视为外部输入:
Point3DVector3DBoundingBox3DTransform3DModelItem.BoundingBox()Document.UpVector- 鼠标点击拾取点
无论当前 NWD/NWC 是客户原始模型,还是预先转换保存后的模型, 只要是从 Navisworks API 读出来的,它对程序来说就是外部坐标。
4.1.1 外部坐标不等于内部语义
外部坐标虽然来自 Navisworks,但不能直接拿来推导内部业务语义。
特别是以下概念必须先经过坐标定义解释:
- 哪个轴是 up
- 哪个轴是高程
- 哪个平面是水平面
- 一个
BoundingBox的“底面”到底是哪一面
也就是说:
- 外部坐标是输入
- 坐标定义决定如何解释这个输入
- 业务代码不得跳过这一步
4.2 程序内部一律只认 Canonical Space
业务层不得直接消费 Navisworks 坐标。
禁止这样做:
// ❌ 错误:直接拿宿主包围盒结果开始做业务计算
var bounds = item.BoundingBox();
var center = bounds.Center;
var direction = new Vector3D(center.X, center.Y, center.Z);
正确做法应当是:
// ✅ 正确:先通过边界适配层转换到内部统一坐标
var hostBounds = item.BoundingBox();
var bounds = adapter.ToCanonicalBounds(hostBounds);
var center = bounds.Center;
4.3 坐标系层只解决轴变换,不解决业务语义
坐标适配层只负责:
- 外部坐标到内部坐标的变换
- 内部坐标到外部坐标的逆变换
- up 轴、高程轴、水平面定义
不负责:
- 球心是否在
(0,0,0) - 终端安装是否指向球心
- 顶面/底面对接如何定义
- 轨道参考面在哪里
这些都属于业务基准层。
4.4 项目基准点必须显式配置或显式求解
像“球心”这类工程点不属于坐标系本身。
不能因为某一批模型里球心恰好在世界原点,就长期把它写死。
应使用以下来源之一:
- 项目配置
- 明确的设计资料
- 多条向心轴线拟合
- 其他明确的业务求解方式
4.5 不在业务代码中散落 Y-up / Z-up 分支
不推荐:
// ❌ 错误
if (isYUp)
{
...
}
else
{
...
}
推荐:
- 边界适配层统一处理
Host -> Canonical - 业务层只消费 Canonical 数据
4.6 渲染几何也必须完成坐标转换
坐标系改造不能只停留在:
- 点坐标转换
- 中心点偏移
- 业务锚点转换
对于以下可视化对象,还必须同步改造它们的局部几何轴构造:
- 通行空间长方体
- 辅助线杆体
- 圆形/圆柱标记
- 切向、法向、侧向相关的渲染面片
否则会出现一种典型错误:
- 对象中心点已经在正确位置
- 但渲染几何仍然按世界
Z-up去构造right/up/height - 最终看起来仍然像
Z-up,即使业务点位已经是对的
本项目已经出现过这一类问题:
Y-up模型中,通行空间中心点偏移已经正确- 但长方体本体仍然按世界
XY + Z构造 - 导致通行空间整体看起来仍是
Z-up
因此,渲染层必须遵守以下规则:
- 凡是依赖
up/right/forward/normal的渲染几何,必须基于统一坐标语义构造局部轴。 - 不允许只修改“中心点/偏移量”而保留旧的世界轴构造公式。
- 如果渲染输出面向 Navisworks 宿主,则应先在 Canonical Space 中完成几何语义计算,再转换回宿主坐标输出。
- 对长方体、圆柱体这类实体渲染,必须同时验证:
- 中心点是否正确
- 局部高度轴是否正确
- 局部侧向轴是否正确
- 法向/截面方向是否正确
5. 推荐的技术方案
5.1 使用完整变换矩阵作为适配基础
不推荐继续停留在:
GetElevation()GetHorizontalCoords()CreatePoint()
这类偏二维、局部的接口上。
对三维动画、Rail 姿态、碰撞恢复来说,更成熟的方案是:
- 使用完整的空间变换对象
- 以矩阵/旋转+平移为核心
即:
ExternalToCanonicalCanonicalToExternal
这两套变换应成为边界层的基础能力。
5.2 建议新增的核心组件
1. HostCoordinateAdapter
职责:
- 统一处理 Navisworks 外部坐标到 Canonical Space 的转换
- 统一处理 Canonical Space 到 Navisworks 外部坐标的反向转换
建议能力:
ToCanonicalPoint(...)ToCanonicalVector(...)ToCanonicalBounds(...)ToCanonicalTransform(...)FromCanonicalPoint(...)FromCanonicalVector(...)FromCanonicalBounds(...)FromCanonicalTransform(...)
2. CanonicalTransform
职责:
- 封装
ExternalToCanonical / CanonicalToExternal - 提供点、向量、姿态、包围盒的统一变换
3. ProjectReferenceFrame
职责:
- 管理业务基准语义
建议包含:
SphereCenterInCanonicalProjectUpInCanonicalRailReferencePlaneAssemblyReferenceFrame
5.3 业务层只消费 Canonical 对象
终端安装、Rail、动画、碰撞、渲染层不应直接使用:
- Navisworks
Point3D - Navisworks
BoundingBox3D - Navisworks
Transform3D
而应通过适配层先转成内部统一对象后再计算。
6. 输入输出边界
6.1 必须拦截的输入入口
以下都是必须拦截的宿主输入点:
- 鼠标点击获取路径点
ModelItem.BoundingBox()ModelItem.TransformGeometry.BoundingBoxDocument.UpVectorPathClickToolPlugin等交互工具返回的点
这些点一旦进入业务逻辑,就应先转换到 Canonical Space。
6.2 必须反向转换的输出入口
以下都是必须回写到宿主时做反向转换的输出点:
- 3D 渲染点、线、法向
- 动画对象位置与姿态
- 碰撞点恢复
- 碰撞报告截图定位
- 辅助线/参考杆/通行空间可视化
7. 当前项目中的优先改造模块
以下模块优先级最高,因为它们既参与三维姿态,又直接暴露了世界坐标假设问题:
7.1 必改
- PathEditingViewModel.cs
- 当前终端安装仿真里仍把世界原点当球心
- RailPathPoseHelper.cs
- 当前仍把世界
Z当worldUp
- 当前仍把世界
- PathPointRenderPlugin.cs
- 当前大量渲染法向仍写死
(0,0,1)
- 当前大量渲染法向仍写死
- PathAnimationManager.cs
- 需要逐步统一输入输出边界
- CollisionSceneHelper.cs
- 需要确保碰撞恢复只使用内部语义
7.2 次改
- 自动路径规划相关的高度、坡度、网格构建
- 历史二维
yaw辅助逻辑 - 旧视图辅助和截图辅助
这些部分可以后续逐步纳入统一架构,不必阻塞当前终端安装与 Rail 主线。
8. 迁移策略
不建议一次性全项目重构。
建议按以下顺序推进:
阶段 1
建立边界层:
HostCoordinateAdapterCanonicalTransformProjectReferenceFrame
阶段 2
先接入以下主线功能:
- 终端安装仿真
- Rail 姿态
- 动画播放
- 碰撞恢复
- 辅助线/通行空间渲染
阶段 2 的两个重要约束:
- 真实物体物理尺寸必须固定
- 起点贴合、动画帧生成、通行空间尺寸、碰撞恢复
- 必须共用同一份固定物理尺寸
- 不允许在对象已经旋转后,再从当前世界 AABB 重新推导“真实高度”
否则会产生典型错误:
- 起点贴合正确
- 动画第一帧立刻出现固定间隙
- 渲染与业务计算必须同时改
- 不能只改参考点、中心点、偏移量
- 还必须同步改渲染几何局部轴:
rightupnormalheight axis
否则会出现:
- 业务点位正确
- 但通行空间/辅助杆/长方体仍按旧
Z-up轴构造
阶段 3
再逐步改造:
- 自动路径规划
- 高度检测
- 坡度分析
- 旧二维路径辅助逻辑
部署约束
WPF 插件的最终部署,必须依赖完整主项目构建产物,而不应默认复用测试顺带生成的程序集。
原因:
- DLL 时间戳正确,不代表插件可运行
- 如果
TransportPlugin.g.resources不完整,Navisworks 在创建面板时仍会因缺少.baml崩溃
必须保证:
- 主项目完整构建成功
- 关键视图资源已经编入程序集
最低检查集:
LogisticsControlPanel.bamlPathEditingView.bamlAnimationControlView.bamlLayerManagementView.baml
9. 关键结论
本项目如果要长期稳定支持 Y-up 和 Z-up 项目,正确方向不是:
- 到处散落
if (isYUp) - 强行要求客户把模型先转成
Z-up - 继续混用世界原点和业务球心
而应该是:
- Navisworks 世界坐标统一视为外部坐标
- 程序内部统一使用 Canonical Space(Z-up)
- 业务基准点单独建模
- UI 输入输出统一使用宿主坐标系
- 资产坐标系只属于插件自带资源,不得泛化成“所有模型都有本地轴”
- 坐标转换只发生在边界层
这是一条更接近业界三维软件成熟做法的路线。