@nebula-spatial/viewer 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  All notable changes to this package are documented in this file.
4
4
 
5
+ ## 0.4.3
6
+
7
+ ### Added
8
+
9
+ - 公开 `AssetPhysicsState` 类型,在 `AssetPreviewSnapshot` 和 `AssetSession` 中增加可选 `physics` 状态,并提供可选 `AssetLoaderContext.reportSessionState()` 通知;现有 loader 无需实现这些新增成员。
10
+ - FBX 外置纹理接入本地文件包与异步 `resourceResolver`,增加受限 TGA/DDS 解码、命名 UV 集、二维纹理变换及受限颜色分层合成;具体边界见 `FBX-MATERIAL-CONVERSION.md`。
11
+
12
+ ### Improved
13
+
14
+ - 按 FBX 材质 ID、连接顺序及模板恢复材质参数,完善标量贴图通道、事务回滚、取消和资源释放;不支持或近似转换通过警告及诊断报告暴露。
15
+ - 改善 USD 的 OmniPBR/PxrSurface 材质适配、索引 UV、GeomSubset 材质绑定与粗糙度贴图通道,区分视觉适配警告和物理故障。
16
+ - 完善 URDF、MJCF、USD 碰撞几何与位姿同步,保留源格式碰撞定义,补充关节与运动学预览支持及回归测试。
17
+
18
+ ### Fixed
19
+
20
+ - 物理编译或初始化失败时,在能够独立构建视觉预览的前提下保留资产;运行中的 step/reset 失败停止仿真、保留最后显示的视觉位姿并刷新能力状态,不再自动重启物理。
21
+ - 物理不可用时保留置灰播放按钮,悬停显示物理故障原因并拦截播放,不被材质警告覆盖。
22
+
23
+ ### Behavior notes
24
+
25
+ - `phase === 'ready'` 仅表示资产预览就绪,不保证物理可运行。宿主应检查 `capabilities.canPlay`,并使用 `physics.status === 'unavailable'` 下的 `issue` 展示原因;未提供物理状态不等于 `not-authored`。
26
+ - FBX 加载完成等待纹理解码和转换;缺失、歧义、鉴权或解码失败按加载失败处理,不保证旧资产继续以缺贴图模型成功加载。
27
+ - 本版本不承诺任意 FBX/自定义着色器无损转换或 Blender 像素一致;USD/URDF/MJCF 物理降级不掩盖取消或视觉加载失败。
28
+
29
+ ## 0.4.2
30
+
31
+ ### Fixed
32
+
33
+ - 修复 `assets.openUsdBaseUrl` 指向跨域 runtime 时,OpenUSD pthread Worker 因入口同源限制抛出 `SecurityError` 的问题;通过 Blob 模块入口导入远端 runtime,保留同源与 Node 加载行为,并补充回归测试及 CORS、CSP 部署说明。
34
+
5
35
  ## 0.4.1
6
36
 
7
37
  ### Added
@@ -0,0 +1,97 @@
1
+ # FBX 常规材质转换:当前契约与验收边界
2
+
3
+ 策略标识:`blender-compatible-v1`。这是导入兼容策略,不是无损 BRDF 转换,也不承诺与 Blender 像素一致。
4
+
5
+ ## 已实现的加固
6
+
7
+ - 材质声明按 shading model 选择对应模板,不把 Phong 模板套给 Lambert。
8
+ - 标量贴图当前明确采用原始 R 通道、identity 映射;复制至 RGBA 以适配不同目标槽位。此规则不推广为所有导出器的通用语义。
9
+ - 派生结果使用 DataTexture,避免再次经 Canvas 的预乘 Alpha 存储;按源 flipY 显式翻转行,保留 UV 与采样参数。
10
+ - 派生纹理按源纹理缓存。同一目标槽位多个输入采用连接顺序的首个可用输入,并报告冲突,不静默覆盖。
11
+ - 转换先准备全部替换对象,全部成功后才写入场景;失败释放新材质及派生纹理,保留原材质。
12
+ - 材质 userData.fbxConversionReport 与模型包装节点 userData.fbxConversionReports 保存策略、材质 ID、映射规则及近似/不支持项。这是内部诊断数据,不是新增的公共 Viewer API。
13
+ - 未解析的纹理连接明确报告。加载资源错误仍通过 opened rejection 传播,尚未统一成稳定的公共错误代码。
14
+ - 取消和首个加载失败触发同批请求取消;不等待不响应取消的 resolver/decode。晚到解码结果不写回场景,并释放纹理。
15
+ - 释放额外加载纹理时也处理可关闭的共享图像,避免遗漏或重复关闭。
16
+
17
+ ## 本轮自动化验证
18
+
19
+ 像素字节布局、R/Alpha 通道与反转、行翻转和采样状态;材质共享、转换回滚及派生纹理释放;模板选择;异步解析、取消、晚到解码结果;共享图像释放。
20
+
21
+ ## 初始阶段待验收项(后续进展见下文)
22
+
23
+ - 真实浏览器 PNG/JPEG 解码、GPU 采样读回与透明边缘测试。输入仍经过 Canvas 解码取样,完全透明像素的隐藏 RGB 以及浏览器色彩管理限制尚未解决。
24
+ - 不要求 Blender 逐像素对照;以真实浏览器的加载、显示、采样和异常恢复为验收依据。
25
+ - 通道选择、透明极性、颜色到标量转换与各导出器实物样本的完整对照;当前 R/identity 是已命名策略,不是这些差异已经解决。
26
+ - 全部 FBX 参数及资源错误的结构化诊断覆盖。
27
+ - FBXLoader 内部 parse 抛错前已构建且未返回的几何/材质,以及未使用内嵌 Video object URL 的所有权审计。
28
+ - 多 UV、复杂纹理变换、TGA/DDS 专用解码、分层贴图和自定义着色器。
29
+
30
+ 完成更广泛的真实解码/GPU 与资产回归后,再扩大多 UV 和纹理变换覆盖;不能仅凭单元测试宣称常规材质全量正确。
31
+
32
+
33
+ ## 2026-09-17 真实浏览器验证
34
+
35
+ 环境:Windows,Chromium 153,WebGL 2.0(OpenGL ES 3.0 Chromium)。
36
+
37
+ - PNG 编码 → HTMLImageElement 实际解码 → 生产标量转换 → WebGLRenderTarget 像素读回:通过。2×2 输入 R 值为 1/64/128/255,输出 RGBA 与行翻转后的预期完全一致。
38
+ - 货架 A:3 个网格、1 个材质、2 张内嵌纹理,加载并显示蓝色立柱和橙红横梁,无全黑现象。
39
+ - 工作台 A:1 个网格、5 个材质,显示灰绿色台面、白色框架和深色脚垫。窄视口下存在横向构图裁切,未归因于材质,也未在本轮修改相机适应逻辑。
40
+ - 料箱 a_1:1 个网格、1 个材质,灰色箱体及明暗层次可见。
41
+ - 货架 → 工作台 → 料箱 → 货架的加载/释放检查通过;货架的两张纹理分别只收到一次 dispose。此检查不是长时间 GPU 内存泄漏压力测试。
42
+ - 构造的无效纹理连接显示降级详情;含 Video 引用但图片不存在的样本进入失败状态。Vite fallback 返回非图片内容,实际覆盖的是图片解码失败,不是 HTTP 404 状态分支。
43
+ - 发现并修复图片解码错误显示为 `[object Event]` 的问题;现在显示 `FBX texture decode failed: missing.png`。
44
+ - 失败后重新打开货架,恢复正常显示,无残留错误提示。
45
+
46
+ 可复用浏览器验证入口见 `playground/fbx-validation/README.md`:TGA/DDS 与分层检查使用仓库内的合成夹具;真实 FBX 回归需自行准备本地模型,商业资产不随仓库分发。下述浏览器结果保留为此前阶段的验证记录,不代表每次提交都已重新执行。
47
+
48
+ 本轮未覆盖所有 PBR 槽位的完整着色器效果、半透明 PNG 隐藏 RGB、其他浏览器/显卡、长时间内存压力和真实网络取消;不将以上结果解释为任意 FBX 均转换正确。
49
+
50
+ ## 扩展第一阶段:多 UV 与二维纹理变换(2026-09-17)
51
+
52
+ - 读取 Geometry/LayerElementUV 的名称,按 FBXLoader r182 的连续层索引规则映射到 uv、uv1、uv2、uv3;不读取或解压几何 UV 数组。
53
+ - Texture.UVSet 按模型所连接的几何名称匹配;未知、重名、缺少属性或超出支持通道时明确报告。未声明 UVSet 或未匹配的 default 使用首通道。
54
+ - 同一材质被不同 UV 布局的网格使用时,仅对需要改变绑定的实例克隆材质与纹理;图像源仍共享,不修改原纹理。保留转换失败时的事务回滚。
55
+ - 支持平移和缩放;W 轴旋转采用显式二维兼容规则 T * Rccw * S,以 UV 原点为中心、角度单位为度。非等比缩放直接构造矩阵,避免矩阵组合顺序错误。
56
+ - 旋转仍标记为 TEXTURE_ROTATION_PROFILE 近似项,并在页面提示;尚无覆盖各导出器的旋转语义实物基准,不能宣称所有 FBX 旋转都精确还原。
57
+ - 三维旋转、非零 RotationPivot/ScalingPivot、三维平移/缩放分量不支持时报告,不把它们当成普通二维变换。
58
+ - 浏览器通过实际 MeshPhysicalMaterial 的 emissiveMap 着色器验证 uv1:输出绿色 [0,255,0,255];W 旋转和平移后输出红色 [255,0,0,255]。这是生产转换后 GPU 采样验证,而非只验证 JavaScript 数值。
59
+ - 货架/工作台/料箱/货架加载释放回归仍通过。多 UV 的专项样本为合成几何与声明,仍需后续增加真实导出器多 UV 文件。
60
+
61
+ ## 扩展第二阶段:TGA / DDS(2026-09-17)
62
+
63
+ - 新增按原始扩展名选择的专用解码器,保留 FBXLoader 同步返回纹理的身份、ID、色彩空间、UV 和包裹方式。已接入异步 resolver、fetch AbortSignal、失败释放流程。
64
+ - TGA:非交错 24/32 位真彩色、8/16 位灰度,未压缩及 RLE;调色板、16 位真彩色暂不支持。解析前校验头、数据长度、RLE 边界和尺寸。
65
+ - DDS:传统二维 DXT1/BC1、DXT3/BC2、DXT5/BC3,以及明确 RGB/BGR 24 位和 RGBA/BGRA 32 位掩码布局;支持未压缩行间距。DX10、BC4/5/6/7、立方体、体积及其他布局明确报错。
66
+ - DDS 在 CPU 解码为 RGBA8,不依赖 GPU 压缩纹理扩展;因此可以进入现有标量贴图转换。代价是更高的解码后内存占用,限制单图最多 16M 像素。
67
+ - 只读取 DDS 基础 mip,后续 mip 由 GPU 重建,不保留源文件手工制作的 mip 链。DDS 采用顶行优先、显式翻行为 GPU 行序的约定;不同生产管线仍需实物样本确认方向。
68
+ - TGA 原点标志由 TGALoader 归一化,再显式翻转 GPU 行序,避免 typed-array 上传依赖 flipY。
69
+ - 外部 TGA/DDS 已通过真实 ASCII FBXLoader、本地文件包、材质转换链路。handler 可接收 TGA 的 blob/data 地址;FBXLoader r182 本身不解析内嵌 DDS,因此不宣称内嵌 DDS 可用。
70
+ - 浏览器 Chromium 153 / WebGL2 对 TGA、TGA-RLE、DXT1、DXT3、DXT5、未压缩 BGRA 的颜色、Alpha、派生标量纹理逐字节 GPU 读回全部通过;真实 FBX 文件包加载也通过。
71
+ - 使用自编微型格式样本;尚未完成各导出器真实 TGA/DDS 资产验收。不把纹理 Alpha 保留等同于自动推断材质透明模式,也不把本轮结果解释为任意 DDS 都受支持。
72
+
73
+ ## 扩展第三阶段 A:分层贴图解析与诊断(2026-09-17)
74
+
75
+ - 新增 LayeredTexture 声明,保留 OO 源连接顺序、BlendModes 原始数值和 Alphas,不按 ID 排序、不猜测堆叠方向或混合方程。
76
+ - 支持 ASCII 数组、二进制 FBX 7400/7500 的整数/浮点数组和 zlib 压缩。仅解压层参数数组,不加载几何数组;单个数组限制 4096 元素。
77
+ - 检查空层、缺失源、嵌套、循环引用、参数数量不匹配、非法权重及 UV/变换差异,并设置图遍历深度和节点限制。
78
+ - 材质转换报告增加 layerStacks,细化每层 ID、模式、权重和诊断。所有分层仍报告 LAYERED_TEXTURE_NOT_COMPOSITED,经现有 session 警告链路传递。
79
+ - 本阶段尚未合成分层,保留 FBXLoader 的首层预览,不把该预览标为完整支持。诊断在材质转换阶段生成;FBXLoader 对畸形空层等文件可能提前抛错,不能保证所有坏文件都进入诊断阶段。
80
+ - 自动测试覆盖连接顺序、数量不匹配、权重越界、循环、缺失源、材质报告以及四种二进制版本/压缩组合。
81
+ - Chromium 153 实际 FBXLoader 文件包验证:两层 ID 4/6、模式 0/4、权重 1/0.5 被完整保留,明确返回未合成诊断。原 TGA/DDS GPU 回归全部通过。
82
+
83
+ 下一步为阶段 B:以经过确认的模式编号、层方向、线性色彩计算与 Alpha 规则实现受限合成;仅接受可证明兼容的 UV/采样配置,对不同 UV、嵌套、法线混合等继续明确拒绝或降级。
84
+
85
+ ## 常用范围收尾:受限颜色分层合成(2026-09-17)
86
+
87
+ 本节是当前状态,覆盖前述“尚未合成”的阶段记录。
88
+
89
+ - 仅 DiffuseColor / EmissiveColor;1–8 层,同尺寸、同 UV 声明/变换、同采样配置,单层最多 4M 像素。
90
+ - 首个连接为底层,要求底层完全不透明且权重为 1;后续层按连接顺序叠加。模式 0/4/5 按普通覆盖处理,1 为加法,2 为乘法。
91
+ - 在 linear RGB 中混合,层权重乘以该层像素 Alpha,输出 sRGB RGBA8。不声明不同导出器的任意堆叠语义都等价。
92
+ - 输出属于 linear-color-v1 烘焙兼容策略,记录 LAYERED_TEXTURE_BAKED 近似提示;烘焙后过滤与逐层实时采样不严格等价。源未声明色彩空间时按普通颜色 sRGB 解释。
93
+ - 不支持的模式、半透明底层、不同尺寸/UV、嵌套、法线及标量分层保留原预览并报告拒绝原因,不继续扩展。
94
+ - 合成纹理进入事务回滚与释放管理,并使用底层纹理声明进行多 UV 绑定。
95
+ - 验证:红/蓝 50% 线性混合得到 [188,0,188];普通覆盖/加法/乘法/层 Alpha/拒绝分支测试通过。真实浏览器的 FBX 文件包 → 分层合成 → GPU 读回通过;半透明底层拒绝诊断及 TGA/DDS 回归通过。
96
+
97
+ 当前按常用情况收口,不将任意 FBX/自定义着色器无损转换作为交付条件;不要求 Blender 像素对比。新增复杂需求以实际资产复现为驱动。
package/README.md CHANGED
@@ -244,7 +244,7 @@ const viewer = createViewer({
244
244
 
245
245
  URDF 和 MJCF 以 MuJoCo WASM 运行,仍由 Viewer 唯一的 scene/runtime/RAF 驱动。多文件模型请传
246
246
  `{ kind: 'files', files, entry }`;`entry` 是包内 URDF 或 MJCF XML 路径。USD 会加载视觉与可可靠映射的物理数据,远端 URL 会以根 USD 文件的完整 URL 作为相对子层、纹理和其他资源的解析基址。
247
- 当前明确支持动态刚体以及树形拓扑的 `PhysicsFixedJoint`、`PhysicsRevoluteJoint`、`PhysicsPrismaticJoint`,包括局部锚点、轴向、有限/无限限位、零宽锁定限位和 force drive;这些资产会启用 play/pause/reset、碰撞代理和拖拽施力。闭环/多父关节、其他关节类型或无法保持物理语义的属性不会被静默编译成错误仿真:当 OpenUSD 已经成功构建完整视觉场景、且失败属于 Viewer 明确识别的 USD 到 MuJoCo 物理语义缺口时,会显式降级为仅视觉预览,禁用播放、复位、碰撞代理和拖拽,并在场景顶部展示降级原因;网络请求、USD/视觉解析、安全上下文、单位校验、取消操作、MuJoCo 初始化及未知错误仍按加载失败处理。没有刚体的 USD 仍作为正常的静态视觉资产预览,不显示降级警告。
247
+ 当前明确支持动态刚体以及树形拓扑的 `PhysicsFixedJoint`、`PhysicsRevoluteJoint`、`PhysicsPrismaticJoint`,包括局部锚点、轴向、有限/无限限位、零宽锁定限位和 force drive;这些资产会启用 play/pause/reset、碰撞代理和拖拽施力。闭环/多父关节、其他关节类型或无法保持物理语义的属性不会被静默编译成错误仿真:当 OpenUSD 已成功构建完整视觉场景、物理编译或 MuJoCo 初始化失败时,会显式降级为仅视觉预览,禁用播放、复位和拖拽,并报告降级原因;碰撞代理是否可用取决于源格式碰撞定义能否独立构建。物理准备边界之外的网络请求、USD/视觉解析、安全上下文、单位校验等错误仍按加载失败处理;取消操作不作为物理降级。没有刚体的 USD 仍作为正常的静态视觉资产预览,不显示降级警告。
248
248
 
249
249
  ```ts
250
250
  const preview = viewer.openAsset({
@@ -258,12 +258,36 @@ preview.reset();
258
258
  preview.play();
259
259
  ```
260
260
 
261
+ ### 公开物理状态与会话通知
262
+
263
+ 包入口导出 `AssetPhysicsState`,可通过 `preview.getSnapshot().physics` 或原有订阅回调读取:
264
+
265
+ ```ts
266
+ import type { AssetPhysicsState } from '@nebula-spatial/viewer';
267
+
268
+ const physics: AssetPhysicsState | null | undefined = preview.getSnapshot().physics;
269
+ if (physics?.status === 'unavailable') {
270
+ console.warn(physics.issue.message);
271
+ }
272
+ ```
273
+
274
+ - `not-authored`:未声明驱动当前仿真的动力学定义,作为静态或运动学资产预览。
275
+ - `ready`:物理已就绪;播放操作仍应检查 `snapshot.capabilities.canPlay`。
276
+ - `unavailable`:物理准备或运行失败,`issue` 提供原因。
277
+ - `null` 或未提供:会话未报告物理状态,不能等同于 `not-authored`。
278
+
279
+ 资产的 `phase === 'ready'` 与物理可用性相互独立。运行中的 step/reset 失败会停止仿真,
280
+ 保留视觉对象及最后显示的位姿,更新能力与物理状态,并上报非致命错误;不会自动重启物理。
281
+ 自定义 loader 可通过可选的 `AssetSession.physics` 提供状态,并在就绪后调用
282
+ `context.reportSessionState?.()` 通知 Viewer 刷新 capabilities、physics 和 warning。
283
+ 现有 loader 不必实现这些可选成员;自定义 UI 应订阅状态变化,而不是仅以加载完成判断能否播放。
284
+
261
285
  ### 远端仿真资产与格式识别边界
262
286
 
263
287
  省略 `format` 与 `format: 'auto'` 等价:只按明确扩展名或已注册 loader 的明确 `match()` 结果识别。
264
288
  通用 `.xml`、无扩展名 URL、目录 URL 不会自动猜测为 MJCF、URDF 或 CAD;无法识别时显示
265
289
  “不支持的资产格式”,调用方应指定 `format`。格式识别成功不代表文件内容或全部仿真语义一定受支持;
266
- 读取、解析、依赖资源或运行时初始化失败显示“资产加载失败”。
290
+ 读取、解析或视觉依赖资源失败显示“资产加载失败”。物理编译或运行时初始化失败时,仅在该格式可独立构建视觉预览的前提下保留预览并报告物理不可用;否则仍加载失败。
267
291
 
268
292
  远端单文件应传实际入口文件 URL,而不是目录 URL,例如:
269
293
 
@@ -300,10 +324,64 @@ await preview.ready;
300
324
 
301
325
  `{ kind: 'files', files, entry }` 只表示浏览器已经持有的本地 `File | Blob` 文件包,不能把远端
302
326
  URL 字符串放进 `files[].file`。业务层负责入口选择、必要的格式判断、鉴权和依赖映射;不能只依赖
303
- Viewer 自动识别所有资产形态,也不能把本地样本验证等同于任意远端资产都兼容。普通 glTF FBX
304
- 外部资源仍由对应 Three.js loader 按 URL 请求,不应假设这些格式的子资源都经过 `resourceResolver`;
305
- 私有 glTF/FBX 资产优先采用同源或可独立鉴权的资源目录。单个本地 FBX 文件适合使用内嵌纹理;
306
- 若纹理是外置文件,应使用可保持相对目录结构的远端 URL。
327
+ Viewer 自动识别所有资产形态,也不能把本地样本验证等同于任意远端资产都兼容。普通 glTF 的外部资源仍由 GLTFLoader 按 URL 请求;FBX 外置纹理经过资源解析器,支持本地文件包与异步 resourceResolver。
328
+
329
+ ### USD 视觉材质适配与诊断
330
+
331
+ USD 仿真继续以 OpenUSD Stage 和 USD Physics 为语义权威;视觉适配层只在
332
+ `@openusd-wasm/three-loader` 完成标准几何、材质绑定、`UsdPreviewSurface`、受支持纹理和
333
+ PBR 参数转换之后查漏补缺。它不会重建 loader 已正确生成的材质,也不会修改刚体、关节、碰撞体
334
+ 或 MuJoCo 绑定。
335
+
336
+ 视觉拓扑补齐遵循原始 USD 数据,不进行猜测:
337
+
338
+ - 在三角面、四边形或多边形的三角化面角与 Loader 输出可逐项核对时,按 `primvars:st:indices`
339
+ 展开 indexed `faceVarying` UV,并沿已验证的面角映射传递到输出三角形;当前用于无整网格材质及单位 UV 变换的 OmniPBR 路径。
340
+ - 将 `familyName = materialBind`、`elementType = face` 的 `GeomSubset` 映射为 Three.js
341
+ geometry groups 和材质数组;材质继续复用 Loader 转换及现有增量适配。
342
+ - 保留已存在的多材质输出;未分配面保留原材质。面索引越界、重叠或无法确认三角化对应关系时,
343
+ 输出 `USD_VISUAL_TOPOLOGY_UNSUPPORTED`,不猜测面与材质的对应关系。
344
+
345
+ 对于 renderer-specific 材质,当前只映射资产明确声明且能确定转换的常量输入,以及资产包中
346
+ 真实存在、浏览器可解码且无需猜测 renderer-specific 坐标/颜色语义的颜色纹理。适配器支持:
347
+
348
+ - `PxrSurface` 的明确常量,以及满足严格条件的简单 `PxrTexture` 颜色网络;
349
+ - 明确声明 `info:mdl:sourceAsset = OmniPBR.mdl` 的已绑定材质,包括 Base Color、Albedo Map、
350
+ roughness、metallic、opacity 和关闭 emission 的语义;Albedo 按 OmniPBR 的 sRGB 颜色贴图处理;
351
+ - OmniPBR 纹理只有在 UV set 0、无投影且 scale/translate/rotate 为单位变换时才会挂接;其他 UV
352
+ 变换会被诊断,不由 Viewer 猜测转换。
353
+
354
+ 缺失资源、RenderMan `.tex`、`PxrFractal`、`PxrBump` 等不能由浏览器等价执行的内容会保留现有
355
+ authored fallback,并产生诊断;Viewer **不会**生成替代纹理、程序噪声、normal/roughness map、
356
+ 自动绑定未绑定材质或按资产名称套用预设。
357
+
358
+ 对于 `UsdPreviewSurface.roughness` 直接连接的 `UsdUVTexture`,适配器支持将明确声明为
359
+ `raw` 的 R/G/B/A 标量通道确定性重排到 Three.js 的 G 通道;仅处理默认 UV 或直接 `st`
360
+ 读取器,以及明确的 repeat/mirror/clamp 包裹模式。已有贴图不覆盖,复杂坐标或色彩空间不猜测。
361
+ 转换成功后在 `resolvedLoaderDiagnostics` 记录已解决项,原始 `loaderDiagnostics` 仍保留。
362
+ 这只是已有像素的通道转换,不是生成缺失的粗糙度纹理。
363
+
364
+ 页面按警告阶段区分标题:物理失败才显示“已降级为视觉预览”;视觉适配限制显示
365
+ “部分材质细节未完全还原”,不暗示物理功能已关闭。
366
+
367
+ 成功的有限范围材质映射记录在 `approximations` 和 `visualAdaptation` 中,不单独触发页面警告。
368
+ 适配层已处理的“Loader 不支持 surface shader”原始诊断仍保留在逐材质报告中;其他 Loader
369
+ 限制、资源缺失、解码失败、未支持节点及拓扑补齐失败继续显示警告。此分级不表示与原渲染器完全等价。
370
+
371
+ 主要诊断代码:
372
+
373
+ - `USD_VISUAL_RESOURCE_MISSING`:USD 引用了不存在或无法取得的依赖;
374
+ - `USD_VISUAL_TEXTURE_FORMAT_UNSUPPORTED`:资源存在,但格式不能由浏览器直接消费,需要离线确定性转码;
375
+ - `USD_VISUAL_RESOURCE_UNRESOLVED`:资源可用性无法确认;
376
+ - `USD_VISUAL_TEXTURE_DECODE_FAILED`:资源取得成功,但浏览器解码失败;
377
+ - `USD_VISUAL_SHADER_NODE_UNSUPPORTED`:材质节点无法映射到 Three.js stock material;
378
+ - `USD_VISUAL_LOADER_DIAGNOSTIC`:loader 保留了标准材质,但报告存在 stock material 无法完全表达的细节;
379
+ - `USD_VISUAL_MATERIAL_AUGMENTED`:仅使用资产明确提供的常量/纹理做了有界补齐。
380
+
381
+ 完整报告保存在内部 `AssetIr.extension.visualAdaptation`;运行时物理资产可通过
382
+ `getVisualDiagnostics()` 读取完整报告和诊断;有物理运行时的资产也可通过
383
+ `getPhysicsProperties().diagnostics` 读取诊断列表。首个视觉降级也会作为 asset warning 发布,`stage` 为 `visual-adaptation`。如果同时发生物理降级,
384
+ 物理 warning 优先显示,完整视觉诊断仍保留在 diagnostics 中。
307
385
 
308
386
  OpenUSD 使用 pthread WASM,部署必须在安全上下文中提供以下响应头:
309
387
 
@@ -316,6 +394,9 @@ Cross-Origin-Embedder-Policy: require-corp
316
394
  包中的 Node-only 裸模块 `module` 精确 alias 到一个导出 `createRequire()` 的浏览器拒绝模块;
317
395
  仓库的 `vite.config.ts` 和独立消费者验证脚本包含该配置。可通过 `assets.mujocoBaseUrl` 与
318
396
  `assets.openUsdBaseUrl` 指向自行托管的 runtime 文件目录。
397
+ 跨域 OpenUSD runtime 使用 Blob 模块 Worker 入口导入原始 runtime;CDN 仍需允许 CORS,
398
+ 若部署配置了 CSP,还需允许 `worker-src blob:` 及对应 CDN 的脚本加载。该入口不替代上述跨源隔离响应头。
399
+
319
400
 
320
401
  ## CAD 快速开始
321
402
 
@@ -596,3 +677,31 @@ npm run build --workspace @nebula-spatial/viewer
596
677
  查看器遵循 OpenUSD `UsdPhysics` 的基础刚体语义:`PhysicsCollisionAPI` 单独应用表示静态碰撞体,和 `PhysicsRigidBodyAPI` 同时应用表示动态刚体;同一刚体下可以包含多个碰撞形状。场景单位、Z/Y-up、Cube/Sphere/Cylinder/Capsule/Mesh 碰撞体、静态碰撞体、质量/密度、自由刚体初始速度和基础关节会在可验证的范围内映射到 MuJoCo。
597
678
 
598
679
  Isaac Sim/PhysX 专有的碰撞近似、碰撞组、simulation owner、求解器/CCD、接触模型和逐对关节碰撞过滤不保证等价转换。无法可靠映射的属性会写入 `diagnostics.droppedFeatures`,不会伪造一个看似正确的 MJCF 结果;若基础几何或刚体语义无法建立,则按 USD 物理降级策略处理。
680
+
681
+
682
+ ### FBX 材质转换边界
683
+
684
+ Viewer 按 FBX Material ID 与 Model 的连接顺序恢复材质声明,不按名称匹配;支持 ASCII、
685
+ Binary 7400/7500 节点布局与 Properties70 模板继承。以 Blender FBX 导入的 Principled
686
+ 映射为参考:颜色贴图直接提供 base color(不再与 DiffuseColor 叠乘),无贴图时读取源
687
+ DiffuseColor;ReflectionFactor 映射 metalness,Shininess 映射 roughness,自发光、透明度、
688
+ 法线强度读取对应原始参数。SpecularFactor 映射为等效法线入射反射率(通过 IOR 表达),
689
+ 并不意味着两个 BRDF 在所有角度完全相同。
690
+
691
+ 这是传统 FBX 材质的 Blender 兼容预览策略,不是所有导出器的通用物理解释。
692
+ ReflectionFactor/ReflectionColor 纹理映射金属度,SpecularFactor/SpecularColor 映射高光强度,
693
+ ShininessExponent 按 Blender 导入约定直接映射粗糙度,透明通道映射 alpha。
694
+ 数值贴图读取 R 并复制到 RGBA 以适配 Three.js 的 G/B/A 采样,不进行 sRGB 解码;
695
+ 颜色纹理继续保持 sRGB。Bump 使用高度凹凸语义,不自动当作切线法线图。
696
+
697
+ FBX 外置纹理支持入口相对路径、本地文件包和异步 resourceResolver。仅在文件名唯一时
698
+ 容许扁平文件选择;重名歧义、缺失、鉴权/解码错误按加载失败处理,不静默忽略。
699
+ ready 等待纹理解码及转换完成;中断和关闭清理源纹理、转换纹理与临时 object URL。
700
+ 支持命名 UV 集到 uv/uv1/uv2/uv3 的绑定及二维平移、缩放;W 轴旋转采用显式兼容规则并报告近似。
701
+ 颜色分层仅支持同尺寸、同 UV/采样配置及不透明底层下的受限合成,不支持任意分层或自定义 shader。
702
+ 近似与不支持项通过 AssetPreviewSnapshot.warning 和 material.userData.fbxConversionReport 报告。
703
+ 除浏览器可解码图像外,已接入受限 TGA/DDS 专用解码;不支持任意 DDS 编码或内嵌 DDS。
704
+ 详细支持范围、资源所有权及验证边界见 [FBX-MATERIAL-CONVERSION.md](./FBX-MATERIAL-CONVERSION.md)。
705
+
706
+ 材质参数对齐不等于截图逐像素对齐:还需相同 HDR、环境旋转、曝光、视角与色彩管理。
707
+ Viewer 不会为了匹配单个 Blender 截图而覆盖全局曝光或灯光。
@@ -86,6 +86,13 @@ export interface AssetWarningSnapshot {
86
86
  readonly message: string;
87
87
  readonly stage: string;
88
88
  }
89
+ /** Physics availability is independent of the visual asset's ready phase. */
90
+ export type AssetPhysicsState = {
91
+ readonly status: 'not-authored' | 'ready';
92
+ } | {
93
+ readonly status: 'unavailable';
94
+ readonly issue: AssetWarningSnapshot;
95
+ };
89
96
  export interface AssetPreviewSnapshot {
90
97
  readonly revision: number;
91
98
  readonly phase: AssetPreviewPhase;
@@ -97,6 +104,8 @@ export interface AssetPreviewSnapshot {
97
104
  readonly error: AssetErrorSnapshot | null;
98
105
  /** Present when loading succeeded with an explicit, user-visible degradation. */
99
106
  readonly warning?: AssetWarningSnapshot | null;
107
+ /** Missing/null means the session does not report physics availability. */
108
+ readonly physics?: AssetPhysicsState | null;
100
109
  /**
101
110
  * Display name for the asset (`OpenAssetOptions.filename`, else the file name
102
111
  * derived from the normalized source). Hosts should render this rather than
@@ -117,6 +126,8 @@ export interface AssetControlState {
117
126
  }
118
127
  export interface AssetPreview {
119
128
  getPhysicsProperties(): Readonly<Record<string, unknown>> | null;
129
+ /** USD visual report and diagnostics, including static assets. */
130
+ getVisualDiagnostics?(): Readonly<Record<string, unknown>> | null;
120
131
  setCollisionVisible(visible: boolean): void;
121
132
  /** Re-orients a plain model's authored up axis without moving the camera. */
122
133
  setUpAxis(axis: AssetUpAxis): void;
@@ -181,11 +192,14 @@ export interface AssetLoaderContext {
181
192
  readonly code?: string;
182
193
  }): void;
183
194
  reportProgress?(stage: string, message?: string): void;
195
+ /** Publish changed session capabilities/physics without failing its visual asset. */
196
+ reportSessionState?(): void;
184
197
  }
185
198
  export interface AssetSession {
186
199
  setTheme?(theme: 'dark' | 'light'): void;
187
200
  beforeRender?(renderer: WebGLRenderer, scene: Scene, camera: Camera): (() => void) | void;
188
201
  getPhysicsProperties?(): Readonly<Record<string, unknown>> | null;
202
+ getVisualDiagnostics?(): Readonly<Record<string, unknown>> | null;
189
203
  setCollisionVisible?(visible: boolean): void;
190
204
  setUpAxis?(axis: AssetUpAxis): void;
191
205
  getUpAxis?(): AssetUpAxis | null;
@@ -200,6 +214,8 @@ export interface AssetSession {
200
214
  readonly presentation?: AssetPresentationProfile;
201
215
  /** Non-fatal degradation decided by the loader after the session opened. */
202
216
  readonly warning?: AssetWarningSnapshot | null;
217
+ /** Missing/null means the session does not report physics availability. */
218
+ readonly physics?: AssetPhysicsState | null;
203
219
  beginDispose(): void;
204
220
  dispose(): Promise<void>;
205
221
  play?(): void;