Skip to content

ADR-029: 物理设置界面重构 — 从布料单页到双系统分治

状态: 已实现 — XPBD 布料/WASM Bullet/碰撞开关统一入口 日期: 2026-07-05 关联: ADR-019 (XPBD 布料模拟), ADR-007(DanceXR 场景菜单设计参考,已删除;场景菜单实现见 ADR-111 场景环境菜单重构), ADR-027 (菜单响应式系统)


0. 问题陈述

物理设置原本分布在两个位置,均存在可用性问题:

位置内容问题
动作菜单 → 物理 → 布料子页布料参数 + 调试 + 变换296 行单页过于臃肿;调试可视化埋没在布料参数中;变换与模型详情页重复
动作菜单 → 模型详情卡片WASM 物理分类开关藏在模型行双击弹窗内,无独立导航入口;无 WASM 主开关 UI

此外,物理设置挂在「动作」菜单下概念错误——物理(重力、碰撞、模拟速度)是场景级全局属性,不因动作播放/切换而变化。


1. 决策概览

四次迭代,累计改动 7 个文件:

迭代内容文件数
1. 调试剥离从布料子页提取调试到独立子页2
2. 根页重构布料主开关/重力/求解质量/模拟速度/碰撞主从/精细调节5
3. WASM 补全新增 WASM 物理子页(主开关+分类toggle)1
4. 搬迁到场景完整搬迁 + 文件重命名3

2. 迭代一:调试剥离

动机

布料子页 motion-cloth-levels.ts 包含 6 个 collapsible 区,其中「调试」区负责材质线框/骨骼线/骨骼关节/粒子球/约束线/碰撞体线框等可视化开关——这些与布料参数(形状/物理/细分)无直接关系。

决策

将调试可视化从布料子页提取为物理根页下的独立调试子页

改前: 动作 → 物理 → 布料 → [折叠] 调试
改后: 动作 → 物理 → 调试 → 6 toggle 平铺

要点

  • 调试仍留在物理域内(未搬去模型详情页),保持上下文聚集
  • 无重复代码,无新增依赖

3. 迭代二:根页重构

动机

物理根页原入口仅一个「布料模拟」folder,用户必须先进入 folder 才能看到参数。布料模拟的产能瓶颈在于预设 chip(两行 8 个)而非参数 slider,根页的 headerToggle + folder 模式增加了不必要的导航深度。

同时,以下物理参数需要全局可见:

参数原位置新位置
布料开关headerToggle 藏在 folder根页独立 toggle
重力强度无全局 UI(仅 WASM 内部调参)根页 slider #1
求解迭代数布料子页参数折叠根页 slider
模拟速度不存在根页 slider (NEW)
碰撞开关不存在(地面碰撞在 cloth-manager 内部)根页 folder + 主从 toggle

决策

布料模拟  [toggle]          ← 独立主开关
├─ 重力强度  1.0            ← WASM Bullet + XPBD 统控
├─ 求解质量  4              ← 原名「求解迭代」
├─ 模拟速度  1.0            ← 时间缩放 0.1x~5x (NEW)
├─ 🛡 碰撞   [folder+toggle]
│  ├─ 地面碰撞  [toggle]    ← 现读 envState
│  └─ 身体碰撞  [toggle]    ← NEW: XPBD 布料-身体碰撞
├─ 精细调节    [→]          ← renamed from 布料(容纳后续非布料物理参数)
└─ 调试        [→]          ← from 迭代一

技术细节

  • envState.solverTimeScale: 新增字段,在 xpbd-cloth.tsbuildClothUpdateFn 闭包内乘以 dt
  • envState.collisionEnabled / bodyCollisionEnabled / groundCollisionEnabled: 碰撞主从状态,cloth-manager._applyCollisionState() 计算有效开关
  • 重力 slider 同时调用 setGravityStrength()(WASM Bullet)和 setClothGravity()(XPBD),实现双系统统控
  • timeScale 通过回调注入 buildClothUpdateFn,避免循环依赖

4. 迭代三:WASM 物理子页

动机

重构后物理根页仍只有 XPBD 布料设置,WASM Bullet 物理(骨骼刚体物理)的开关隐藏在模型详情弹窗中。用户没有统一入口查看/控制 WASM 物理状态。

决策

在物理根页新增「WASM 物理」folder 子页:

WASM 物理 [→]
├─ 模型物理  [toggle]       ← setModelPhysics(id, enabled)
├─ 裙子物理  [toggle]       ← setPhysicsCategory(id, 'skirt', enabled)
├─ 胸部物理  [toggle]       ← setPhysicsCategory(id, 'chest', enabled)
├─ 头发物理  [toggle]       ← setPhysicsCategory(id, 'hair', enabled)
├─ 配件物理  [toggle]       ← setPhysicsCategory(id, 'accessory', enabled)

仅在有聚焦模型时显示具体 toggle,否则显示提示行。

局限

WASM Bullet 物理(babylon-mmdMmdWasmPhysics)仅暴露 rigidBodyStates: Int32Array(每个刚体的 0/1 开关),无 stiffness/damping/friction 等参数的运行时 API。当前 UI 已覆盖 100% 可用 API。


5. 迭代四:搬迁到场景菜单

动机

物理是场景级全局属性——重力、碰撞、模拟速度不因动作或模型切换而变化。挂在「动作」菜单下概念错误,且动作菜单以模型/动作文件操作为主,物理设置与上下文无关。

决策

将物理设置从动作菜单完整搬迁到场景菜单:

改前: 动作 → 物理 → ...
改后: 场景 → 物理 → ...

附带操作:

  • 文件重命名 motion-physics-levels.tsscene-physics-levels.ts
  • getMotionMenu()getSceneMenu(), refreshMotionRoot()refreshSceneRoot()
  • motion-popup.ts 移除 scene:physics 路由和物理 folder 条目
  • scene-menu.ts 新增 scene:physics 路由和物理 folder 条目
  • 清理含 onFolderEnterPopupLevel 返回对象(该字段不在类型定义中,路由已由 SlideMenu 构造参数处理)

最终导航结构

场景
├─ 预设场景
├─ 保存场景
├─ 后处理
├─ 舞台
├─ 物理
│  ├─ 重力强度(WASM + 布料)  [slider]
│  ├─ WASM 物理                [→]
│  │  ├─ 模型物理主开关         [toggle]
│  │  ├─ 裙子/胸部/头发/配件     [toggle × 4]
│  ├─ 布料模拟                  [toggle]
│  ├─ 求解质量                  [slider]
│  ├─ 模拟速度                  [slider]
│  ├─ 碰撞                      [folder + toggle]
│  │  ├─ 地面碰撞               [toggle]
│  │  └─ 身体碰撞               [toggle]
│  ├─ 精细调节                  [→] (原布料参数子页)
│  └─ 调试                      [→]
│     ├─ 材质线框/骨骼线/关节球   [toggle × 3]
│     └─ 粒子球/约束线/碰撞体线框 [toggle × 3]
├─ 截图

6. 影响范围

文件改动
frontend/src/core/config.tsEnvState 新增 4 字段
frontend/src/physics/cloth-manager.ts新增 timeScale/collision/gravity API + _applyCollisionState
frontend/src/physics/xpbd-cloth.ts闭包接收 getTimeScale 回调
frontend/src/menus/motion-popup.ts移除物理 folder + 路由
frontend/src/menus/scene-menu.ts新增物理 folder + 路由
frontend/src/menus/motion-cloth-levels.ts移除重力倍率 preset;更新 import 路径
frontend/src/menus/motion-physics-levels.ts重命名 → scene-physics-levels.ts
frontend/src/menus/scene-physics-levels.ts重构根页 + buildCollisionLevel + buildWasmPhysicsLevel + buildPhysicsDebugLevel

7. 未解决的问题

问题原因后续方向
WASM 物理无参数级调参babylon-mmd 运行时仅暴露刚体 0/1 开关,无 stiffness/damping/friction API需上游 PR 或自行 hack WASM 内存
碰撞主从开关仅作用 XPBDWASM Bullet 无独立碰撞 toggle(只有主物理开关和分类开关)若 WASM 未来暴露碰撞组掩码,可接入
布料/非布料物理参数混在同一个「精细调节」当前只有布料子页,未来可能有非布料 XPBD 参数子页拆分留给后续演进

8. 后续修正

2026-07-06 — 布料材质预设物理参数反向修复

  • 原预设中「硬质」compliance=0.008(在 XPBD 中为最软)「丝绸」compliance=0.0005(最硬),值与中文命名完全相反
  • XPBD compliance=0 为完美刚性,值越大约束越软
  • 修正后顺序(从硬到软):硬质 0.0002 → 皮革 0.0005 → 棉布 0.0015 → 丝绸 0.004
  • bendCompliance 同步修正:硬质 0.0008(最难弯折)→ 丝绸 0.025(最易褶皱)
  • totalMass 微调:丝绸 0.25 → 硬质 0.9(更符合物理:丝绸轻、硬质重)

关联: ADR-019 (SDF 碰撞胶囊半径公式修正)

9. 验证

  • tsc --noEmit 通过(仅预存测试 mock 错误,无源代码错误)
  • vite build 通过(~1.5s,仅 babylon.js 预存 chunk size warning)
  • 运行时:所有物理 toggle/slider 正确读写 envState,重力同时影响 WASM + XPBD,碰撞主从级联有效