E3:接触、求解器与力观测#
Learning home · PhysX contents
Course text is primarily Chinese, with English terminology and summaries. The original content is preserved.
Sim Atlas 学习首页 · 课程路线 · 源码地图 · E3 检查与限制
先修:E1 的 Actor/Shape/COM、惯量和步进边界;E2 可帮助区分 drive 输入与求解后的接触响应。本章连接 A4 与 B1–B5,并补足 B0 的约束行/求解数据基础。应用读者可先读第 1–3、7–9 节,源码读者按顺序阅读。
阅读身份:官方标签 ovphysx-0.6.3,提交 da950a3537927784951853c66618036f332ca0ce,对应 SDK 头文件 5.11.0。这是本文的原生 C++ 源码基线,不能证明历史 Isaac Sim 5.1 / UniSim 1.7.10 内置的 PhysX 二进制、补丁、PGS/TGS、迭代或材料参数。历史批次仍以 DexLab #126 的实际配置证据为准;绝不把本章默认值回填成历史实测值。新源码出现的 API 也不能直接套给旧宿主。
本章只做源码与公式核对、原创 C++ 片段的语法/类型检查。没有链接或运行 PhysX,没有接触实验、基准或训练,也没有得出引擎排名。后续观测验证与实验复用 DexLab。
1. 一次 simulate 到底解决什么#
接触不是一个选项,而是有顺序的职责链:
阶段 |
原生对象与产物 |
不应混淆的概念 |
|---|---|---|
几何与过滤 |
Shape geometry、局部位姿、材料、filter data;候选 shape pair |
视觉网格与 collision geometry 可以不同;overlap 不等于接触冲量 |
碰撞检测 |
narrow phase 生成/维护法向、接触点、separation 与 patch |
PCM 是接触生成/缓存路径,不是 PGS 的另一种名字 |
约束准备 |
材料组合、rest distance、有效质量、恢复/柔顺、摩擦 anchor;solver rows |
同一接触模型可进入不同求解器;恢复系数不是数值容差 |
岛与求解 |
相互耦合的刚体、articulation、contact/joint rows;迭代冲量与速度 |
|
积分与更新 |
位姿、速度、缓存、sleep/CCD/完成任务 |
PGS/TGS 是 solver;不能把它们直接当作 Euler/Backward Euler 积分器标签 |
读回 |
|
一个回调中的 contact impulse 也不是全量六维力传感器 |
从 NpScene::simulate 进入(下列为固定源码入口,而非执行日志):
NpScene::simulateOrCollide检查阶段与正时间步,向 scene 提交任务。Sc::Scene::simulate/collideStep把碰撞任务接到 advance;advanceStep再组织 narrow phase、island、solver、post-solver、after-integration 与可选 CCD/finalization。setContinuation写的是依赖边,阅读时不要把设置顺序当成执行顺序。PxsNphaseImplementationContext依getPCM()分支调用 PCM 或普通 discrete narrow phase。几何 cooking 与 SDF 动态三角网格例外见 E1;并非所有 mesh pair 在所有后端都有同一路径。ScScene构造分支 在 CPU dynamics 下按desc.solverType建立DynamicsContext或DynamicsTGSContext;GPU dynamics 另建 context 并传入 solverType。本文跟踪 CPU 实现,不能用这张 CPU 调用图证明 GPU 内核完全相同。updateDynamics将 contacts、岛、dt与重力交给选中的 dynamics context。PGS/TGS 的实际迭代与积分见第 5–6 节。fetchResults的完成与回调处理承接 E1:提交工作、工作完成、回调送达和应用拿到一致状态是不同时间点。
2. 过滤、距离与法向约定#
2.1 检测、响应、通知分别打开#
PxPairFlag 中,eCONTACT_DEFAULT = eSOLVE_CONTACT | eDETECT_DISCRETE_CONTACT,并不默认要求接触点通知。要接收普通离散接触,应用的 filter shader/callback 应在保留自己的过滤规则后,给目标 pair 加上 eNOTIFY_TOUCH_FOUND/PERSISTS/LOST 和 eNOTIFY_CONTACT_POINTS;scene 还要注册 PxSimulationEventCallback。eNOTIFY_CONTACT_POINTS 单独存在不会凭空产生事件。Trigger 默认只有检测与 found/lost,不施加接触响应。
eKILL 与 eSUPPRESS 的重新过滤条件不同;PxFilterFlag 定义应与应用 filter data 生命周期一起读。不要把 mask 过滤、joint collision 禁用、trigger、sleep 与“solver 没算出力”合并成一个故障。
Sweep CCD 还需 scene eENABLE_CCD、参与动态 body 的 eENABLE_CCD 和 pair eDETECT_CCD_CONTACT;eNOTIFY_TOUCH_CCD 才报告相应 CCD passes。CCD 契约、三层开关。这与 TGS 的内部时间小步不是同一机制,也不能由更大 contactOffset 替代。
2.2 contactOffset 不是刚度,restOffset 不是容差#
令几何表面 separation 为 \(s\),单位长度;负值表示穿透。令两 shape 的 restOffset 总和为 \(r\),contactOffset 总和为 \(c\)。生成接触的距离范围由 \(c\) 控制,期望静止距离由 \(r\) 控制,求解准备使用的间隙是:
每个 shape 的 contactOffset 应为正且大于其 restOffset。默认 contactOffset 为 0.02 * scale.length,restOffset 为 0;这是当前 SDK 的尺度约定,不是所有模型都应使用的米制调参建议。Shape 接口、实际 separation−restDistance。增加接触生成距离不等于改变 penalty stiffness,也不能证明更准确。
PxContactPairPoint 的 normal 从 shape 1 指向 shape 0,position/normal/impulse 均在 world;separation 是几何距离。求合力前固定 actor/shape 身份和取力对象,不按“机器人总在 pair[0]”猜符号。eINTERNAL_CONTACTS_ARE_FLIPPED 在 extraction 中用于 face index 交换,不应由应用再次无条件翻转 normal;看实际提取实现。
2.3 影响接触响应的不同旋钮#
字段 |
单位与对象 |
真实含义 |
|---|---|---|
static/dynamic friction |
无量纲,Material |
摩擦上限参数,经 pair combine 后进入 patch;不是速度阻尼 |
restitution ≥ 0 |
无量纲,Material |
恢复速度目标;低于 bounce threshold 时不反弹 |
restitution < 0、damping |
力型分别质量/时间²、质量/时间;Material |
本版重载为隐式柔顺弹簧刚度的负值与阻尼;acceleration spring 改为 1/时间²、1/时间 |
bounceThresholdVelocity |
长度/时间,Scene |
抑制小碰撞反弹;默认 |
frictionOffsetThreshold |
长度,Scene |
分离较大的点不产生摩擦的距离条件 |
frictionCorrelationDistance |
长度,Scene |
friction anchor 的空间相关/合并距离 |
maxDepenetrationVelocity |
长度/时间,RigidBody |
接触纠正穿透的速度限额,不能当作 force limit |
maxContactImpulse |
质量·长度/时间,RigidBody |
每个 contact 的冲量上限;动态/运动学 pair 取两侧较小值,静态 pair 用动态侧值;不是全物体总力或驱动上限 |
来源:Material、Scene 距离与速度阈值、Body 两种限额。这里的量纲以一致的质量/长度/时间单位制为前提;PxTolerancesScale 不会自动把输入毫米变米。接触修改器还能改 normal、target velocity、max impulse、inv mass/inertia scale;这些是动力学改动,不能在“观测函数”里悄悄启用。修改接口、线程契约。
3. 材料不是取任意一面的系数#
3.1 刚性 pair 的组合规则#
两边分别声明 combine mode,但 pair 采用的是 枚举值较大的 mode:AVERAGE=0、MIN=1、MULTIPLY=2、MAX=3。先选规则,再算系数。它不表示总是选择较大的材料系数。枚举定义、实际 combiner。
mode |
标量组合 \(C(a,b)\) |
阅读例:\(a=0.2,b=0.8\) |
|---|---|---|
AVERAGE |
\((a+b)/2\) |
0.5 |
MIN |
\(\min(a,b)\) |
0.2 |
MULTIPLY |
\(ab\) |
0.16 |
MAX |
\(\max(a,b)\) |
0.8 |
例:一边 MIN、另一边 MULTIPLY,实际用 MULTIPLY,得到 0.16。静/动摩擦分别组合,然后实现约束 muDynamic=max(combinedDynamic,0)、muStatic=max(combinedStatic,muDynamic)。任一材料 eDISABLE_FRICTION 会使 pair 静/动摩擦都为 0,并禁用 strong friction。系数不限定必须 ≤1。组合后的钳制。
这是一套 SDK 规则,不是由各材料单独的“物理摩擦系数”推导出真实表面配对摩擦的普适定律。mesh face material 的身份来自 Shape/face index,见 E1 和 getMaterialFromInternalFaceIndex。
3.2 负 restitution 的特殊分支#
本版 restitution < 0 表示柔顺接触刚度 \(k=-e>0\);damping 为 \(d\ge0\)。这是接触模型的选择,不是“负反弹系数”。实现具体顺序:
仅一侧柔顺时,restitution 强制用 MIN,从而保留负值,不服从用户的普通 combine mode。
两侧柔顺且一侧 force spring、另一侧 acceleration spring 时,后者的 restitution 和 damping 优先。
两侧柔顺用 MULTIPLY 时,结果额外取负,避免两个负数相乘意外回到刚性分支。
damping 的注释/实现差异:头文件概述说刚柔混合取柔顺侧 damping;实际
PxsCombineMaterials在恰有一侧柔顺时使用max(d0,d1)。若刚性侧保存了更大的 damping(即使它单独作为刚性材质时该值不起作用),不能按概述断言它会被忽略。本文记录实现,不用未运行的例子宣称测得缺陷。
力型的一维、固定有效质量说明模型可写成:
这里 \(v_n<0\) 为接近,\(p_n\) 为法向冲量,\(w=1/m_{eff}\);在把 \(g^+=g+h v_n^+\) 代入的隐式离散里,出现分母 \(1+h(d+hk)w\)。若输出按 acceleration 解释,\(k,d\) 单位改为 \(s^{-2},s^{-1}\),分母为 \(1+h(d+hk)\),随后用有效质量转换成冲量。此推导帮助读懂系数,假定无摩擦/恢复/其他约束、单一有效质量,并非完整 PhysX 算法。
实际 computeCompliantContactCoefficients 与 TGS 版本明确构造 a=h*(d+h*k),force/acceleration 使用不同 response;当分离且本步不预计闭合时抑制 damping。最终还受实际累计冲量更新、impulse cap、offset、solver 耦合与迭代影响。因此不能把“隐式弹簧”扩写成“整个 PhysX 一律 Backward Euler penalty 接触”。
4. 从接触点到约束行#
4.1 一个可手算的法向行#
先假定两个普通刚体、单位法向 \(n\)、世界 COM 惯量 \(I_i\)、无质量缩放/锁轴/gyro/柔顺,\(r_i=x_c-x_{COM,i}\)。法向相对速度与 unit response 为:
\(w\) 的单位是质量的倒数,法向冲量增量 \(\Delta p_n\) 的单位是质量·长度/时间。速度响应为 \(\Delta v=M^{-1}J_n^T\Delta p_n\)。无限质量/运动学物体在相应响应项中贡献零,运动学目标速度仍影响相对速度。实际刚体准备使用逆惯量平方根、dominance/mass scale 和 offset slop,见 constructContactConstraint。
固定本行几何与其他行当前结果,理想刚性单边条件可写为 \(p_n\ge0\)、\(v_n^+-v_{target}\ge0\)、\(p_n(v_n^+-v_{target})=0\)。一种说明性的投影更新为:
若设置有限 \(p_{max}\),即使已到上限,非穿透条件仍可能不满足;不能仍宣称解满足未限幅的互补条件。v_target 还会包含恢复目标、接触 target velocity 与位置纠正,真实分支见下节。公式中的 \(w=0\) 不能直接相除;源码显式处理 response 非正情况。
Articulation 不可用“两块独立 link 刚体”公式代替。 link 接触冲量通过 articulation 的树响应计算;不能只读该 link 的质量和惯量。实际 PGS getImpulseResponse_分别调用参与 link 的 getImpulseResponse 并组合响应,随后用 1/(unitResponse+cfm) 准备行。法向准备。
同一 articulation 两个 link 在理论上有交叉响应,但必须追到当前调用路径,不能仅见函数名就宣称已完整计入:该文件另一个 scalar helper 的 self-response 分支被 allowSelfCollision=false 和 &&0 禁用;上述 SIMD helper 没走它。禁用分支。TGS 虽有 scalar self-response 分支,其 SIMD overload 忽略该 bool,已查的 contact prep 调用传入 false。TGS overload、contact 调用。这是具体 response 近似路径的边界,不据此否定所有 articulation 自碰撞,也不把理论全耦合矩阵冒充此实现。
这里的 CFM 是对 response 的正则化,不是 contactOffset、时间步或收敛 tolerance。原生 PxArticulationLink::setCfmScale 默认 0.025、范围 0–1;源码先按场景长度尺度赋值,再乘 link 的测试冲量响应比例,接触准备取双方的较大 CFM。它随模型响应缩放,不能把公开 scale 数字直接当成接触弹簧的 N/m。公开入口、初始缩放、响应缩放、其余 link 缩放、pair CFM。增加正则化改变响应及约束误差,不能称为“同一物理问题只算得更快”。
4.2 恢复、偏置和实际数据布局#
准备代码先算 \(g=s-r\) 与接近速度;只有恢复系数为正、接近速度超过 bounce threshold 且预计本步闭合时才添加 \(-e v_n^-\) 恢复目标。内部传入的 bounce threshold 带其约定符号;公开 API 是正速度大小。穿透位置误差经 \(1/h\) 与 bias coefficient 转为速度纠正,再受 max depenetration bias 限制。刚性/柔顺分支。
PGS 的每个 contact block 不是一个密集全局矩阵:header 包含法向、质量/摩擦等共享量;接着是各法向行、对齐的累计法向冲量数组、friction rows。每行携带 \(r\times n\) 经逆惯量平方根变换后的量、velocity multiplier、biased/unbiased error 和 max impulse。读取与写回布局见 solveContact;准备时法向冲量清零。虽然局部变量叫 forceBuffer/appliedForce,其数值是 impulse,不可直接标 N。
多约束写成 \(A=JM^{-1}J^T\) 可以帮助理解行耦合与病态质量比;此处不是说 CPU contact solver 显式组装并 Cholesky 分解完整 \(A\)。PGS 用各行 response 的倒数与即时速度更新处理耦合;articulation response 由树动力学计算。小 response、长链、几乎冗余约束、质量/惯量尺度差与 finite precision 都可能影响有限迭代的误差。
4.3 Patch friction、strong friction 与 warm start#
本固定版本 PxFrictionType 只剩 ePATCH,frictionType 已 deprecated;不要抄旧教程列举 one-directional/two-directional 选项让读者配置不存在的枚举。一个 patch 最多选择两个 friction anchors,每个 anchor 有两个切向约束。接触点数、patch 数、anchor 数不是同一量。
理想各向同性 Coulomb 写为 \(\|p_t\|_2\le\mu p_n\);这只是参照模型。PGS 实现按切向行分别检查静摩擦界、越界后投影到动摩擦范围;TGS 把一个 anchor 两个方向的累计冲量合成长度后钳制,减小方向分开处理的非对称性。法向容量来自 patch 的 accumulated normal impulse,并含 anchor 缩放,不能给每个 contact point 都复制一份完整摩擦圆。PGS 行钳制、TGS 成对切向计算。
Strong friction 保留跨步的 friction error/anchor 相关性;它不等于无限静摩擦。eDISABLE_STRONG_FRICTION 去掉这种跨步记忆,但不自动令摩擦系数归零。材料说明、上一帧 patch 相关性读取。设置接触 target velocity 的准备路径还会禁用 strong friction,TGS extract 路径。
“有缓存”不能推导成“把上帧所有法向/切向 lambda 原值 warm start”。已跟踪的 CPU PGS 法向 buffer 在准备时清零,TGS 切向 row 的 appliedForce 也初始化为零;接触流/PCM、anchor 记忆、单次 solve 内累计冲量是三个层次。TGS 初始化。本文不由这几处推出所有 joint/GPU 路径均没有 warm start。
TGS 某些单 anchor 情况还可用 torsional patch radius 构造绕法向的纯角向摩擦行;它近似压缩表面的转动摩擦。Shape 契约、TGS 半径和行准备。这会影响第 7 节“仅由点力重建 wrench”的完整性。
5. PGS 与 TGS 的迭代、终止与误差#
5.1 PGS:带投影的顺序冲量更新#
solveDynamicContacts从当前 body velocity 得到本行误差,使用已准备的 multiplier 算冲量增量,限制累计冲量,再立即更新两 body 的线/角速度供后续行使用。柔顺分支还有 impulse multiplier,所以第 4 节的简单刚性投影式不能覆盖所有行。
solveV_Blocks执行 position iterations,最后一遍做 conclude,然后保存 motion velocities,再做 velocity iterations 和 writeback。这里的“position”主要指包含位置纠正 bias 的速度约束阶段,不是每轮直接调用 setGlobalPose。conclude 的实际写入将相应行切换到 unbiased 项,降低纠正穿透造成的最终离开速度;摩擦默认只在最后三次 position iterations 及全部 velocity iterations 执行。Scene flag eENABLE_FRICTION_EVERY_ITERATION 可改变前者。Scene flag。
零 velocity iteration 的细节:公开 setter 允许 minVelocityIters=0;但这个有接触约束的 CPU PGS 路径会至少执行一次 velocity/writeback,平行路径亦如此。不能根据用户填写 0 就标“实际完全不做 velocity pass”。串行保障、平行保障。无约束路径单独处理,不把该结论扩到所有分支。
5.2 TGS:位置迭代推进内部时间#
TGS 收集岛中 body 的位置/速度迭代需求上界,也纳入 articulation;SetStepperTask明确令:
body count 汇聚见preIntegrateBodies,articulation 汇聚见setupArticulations。因此提高岛内一件物体的 iteration 请求可能影响整岛,而非只让该物体独立算更多次。
每轮位置迭代求解后调用 integrateBodies/stepArticulations,累计 elapsedTime。后续 constraint evaluation 使用自准备以来的线/角位移,更新原始 separation/bias 与 target motion;无需把它描述成每个内部小步都重新跑一次完整 narrow phase。最后 velocity iterations 不再推进位姿,之后独立 writeback。实际循环、动态 separation 与冲量钳制。
TGS 对每个 position/velocity iteration 都处理摩擦,与 PGS 默认不同。这些差异意味着“PGS 4 次 = TGS 4 次 = 另一个引擎 4 次”没有成立的数值依据。
5.3 终止不是 tol=某个数字#
PxRigidDynamic::setSolverIterationCounts 的当前默认是 4 position、1 velocity,范围分别 1–255、0–255;articulation 有自己的同名设置。公开契约。上面已追踪的 contact loops 按预算计数结束,不是以用户输入的绝对/相对 residual tolerance 作为提前退出条件。不应为表格整齐而替 PhysX 填一个 1e-9。
可以在纸上定义互补/速度残差或沿当前数据做诊断,但残差量纲、归一化、接触状态、bias 与 friction projection 都必须先固定。没有实际实现与导出,就不要宣称某个“误差值”是 SDK 原生报告。更高 iteration budget 不是更高浮点精度;这里的 PxReal 仍是 float。有限迭代也不是精确满足刚性约束的保证。PGS/TGS 的迭代顺序和离散化会影响误差,文档不据理论期望宣布某个模型已稳定。
6. 积分、外力与三种“子步”#
PGS 普通刚体的 unconstrained velocity 先计入重力,乘 max(1-damping*h,0),再做速度上限处理。bodyCoreComputeUnconstrainedVelocity。接触求解后,位置按保存的 motion velocity 推进;最终存储 velocity 还会计入 velocity iterations 的更新,两者不能总写成同一个 \(v_{k+1}\)。旋转使用常角速度段的闭式 quaternion 更新并归一化。integrateCore。
只在无接触、无阻尼、无 gyro、恒外加速度的简化情况下,可用 semi-implicit 关系解释平移:
对 world angular velocity 为常量的一小段,姿态更新为 \(q^+=\Delta q\otimes q\),其中 \(\Delta q=(\hat\omega\sin(\|\omega\|h/2),\cos(\|\omega\|h/2))\),零角速度取恒等。不能把四元数四个分量分别当独立角坐标积分;实际限制、inertia transform 和 articulation 路径仍需按源码区分。
TGS 的 integrateCoreStep每内部小步推进 position/quaternion 并累积 delta motion。外力默认在一次 simulate(h) 开头施加;eENABLE_EXTERNAL_FORCES_EVERY_ITERATION_TGS 则按内部小步分配。这个开关会改变自由落体的位移,且岛的 iteration 数影响结果。公开说明。
一个不运行引擎即可推导的例子:初速度为零、恒加速度 \(a\)、无阻尼/接触。整步预施加外力后以 \(ah\) 运动,位移是 \(ah^2\);若 \(N\) 次先加速再积分,则
无约束分支与先外力后积分循环支撑上述简化。它说明为什么换开关/岛迭代数会换离散结果;不是自由落体实验结果,也不覆盖约束或非恒力。
名称 |
会重新进入什么 |
读回与控制边界 |
|---|---|---|
应用外层小步 |
调用多次 |
每次可重新提交控制、重新碰撞检测、读回;普通外力需要按 E2 的累加/清理规则重新提供 |
TGS 内部位置小步 |
同一 solve 的约束更新和积分 |
不是独立 public step,没有每小步自动执行应用控制器或重新发布传感器帧 |
CCD passes |
同一 public step 的连续碰撞处理 |
可能额外产生接触通知;不等于 TGS 迭代数,也不保证普通离散报告已涵盖所有碰撞 |
单位尺度、dt、iteration count、位置 bias、柔顺参数和接触几何必须一同记录。把步长减半也会改变每步 impulse cap 所对应的平均力上限。GPU 与宿主的同步/批量机制留待 E5/E6;本文没有证明对接触切换、投影或缓存路径存在可用梯度,PGS/TGS 名称本身不代表可微。
7. 从 solver impulse 到可解释的观测#
7.1 法向、切向和纯力矩分开看#
PGS writeBackContact把累计 normal impulse 写入 contact force buffer;TGS 也有对应 writeback。对应用而言,本版至少要分清:
数据 |
来源 |
能说明什么 |
|---|---|---|
|
|
实现为 |
|
|
world 切向冲量,位置是 friction anchor;它不是与 contact point 数组一一对应的另一列 |
contact force threshold event |
pair 通知/Body threshold |
事件筛选机制,不是完整 wrench 数值或驱动饱和证据 |
|
普通 joint 的 underlying constraint |
最近维持该约束的力/力矩,不是碰撞点列表,也不等于 articulation cache 输入 jointForce |
公开字段与 extraction、法向提取、anchor 提取、constraint 契约。D6 drive 默认不把 drive 力加进 getForce 总量;PxD6JointDriveFlag::eOUTPUT_FORCE 会改变报告总量,连带影响 break 判断。D6 flag。
完整性限制需要保留在数据里。 normal stream 存在、has impulses、friction stream 存在、实际提取条数、事件类型/步编号,应分别记录。CCD 通知路径将 frictionPatches=NULL,ScShapeInteraction 分支;因此 CCD 报告没有 anchor 不证明摩擦为零。Lost/sleep/过滤不报告也不能补成“本步真实零接触力”。删除 actor/shape 的标志需要单独处理,不能在回调中解引用已经移除的对象。
本版 writeBackContactFriction只合并成对的切向行;在三行的“一个 anchor 加 twist”情况下,不把额外纯 torsional row 写成 anchor 的点冲量。所以即使法向点与切向 anchor 都收集了,用 \(r\times p\) 得到的也只是已报告点冲量的力矩,不能无条件宣称完整 contact wrench。 GPU、宿主传感器的聚合与遗漏需另行核实,不能从 CPU 接口存在推断运行覆盖。
7.2 平均力、坐标与采样#
对一个明确的 public step,长度为 \(h>0\),把法向点冲量 \(p_i\)、可用切向 anchor 冲量 \(t_j\) 都按同一作用对象和 world 方向整理。所报告线冲量与关于 world 参考点 \(o\) 的点冲量矩为:
这是时间区间平均量,不是碰撞瞬时峰值;单位分别为质量·长度/时间²与质量·长度²/时间²。若观测覆盖 \(K\) 个 public steps,应先汇总各步冲量,再除以覆盖时间 \(\sum h_k\);不能重复累计同一报告,不能按渲染帧时长或 TGS 内部 \(h/N\) 误除整个 public-step 冲量。CCD 多次碰撞还需依据事件/额外数据分清同一步的多个贡献;本章片段不承诺完成 CCD 聚合。
若传感器 frame 的 world 姿态为 \(R_{WS}\)、原点为 \(o_S\),先在 world 对 \(o_S\) 求矩,再令 \(F_S=R_{WS}^T F_W\)、\(\tau_S=R_{WS}^T\tau_{W,o_S}\)。只旋转 torque 却不平移参考点会漏掉杠杆项。若要 articulation generalized contact effort,还需在正确 COM/tool frame 及 DOF 顺序下使用 \(J^T\);不能把三维 world 接触力直接填入 cache.jointForce。该 cache 字段是 E2 的输入契约,而非传感器读数。
接触事件用当前 step 的显式编号与配置快照绑定,回调复制数据后在合适的 fetchResults 边界消费。onContactModify 是求解前的响应修改,onContact 是通知;不要互换。回调写入与线程限制。质量缩放、dominance、impulse cap、D6 输出 flag 和纯角向摩擦均可能改变解释;这些元数据比笼统列名“force”更重要。
8. 原生 C++ 阅读片段#
examples/e3_contact_readback.cpp只定义 pair flags 和复制报告的函数,没有 main,不初始化、链接或运行引擎。调用方在自己的合法 filter 和 onContact 中使用,负责稳定 actor/shape 身份、回调同步、生命周期与整数 step 编号。示例刻意分别保存 normal points 与 friction anchors,保留“stream/impulse 是否可用”,不把缺失项伪造成零测量。
匹配头文件的 -fsyntax-only 能检查当前 API 名称、字段与类型;它不验证回调是否发生、方向是否与宿主包装一致、冲量是否完整、实际力学或性能。详细结果见 E3 验收记录。
9. 易错点与排查顺序#
有可视化重叠,没有报告:依次核对 collision geometry、shape flags、pair 过滤、detect/solve/notify 三类开关、scene callback、sleep 与 removed 标志,最后才追 solver;不能先把“0”解释成真实力。
改 friction 不影响现象:先查 pair combine 规则、另一材料的 disable flag、static 被抬升到 dynamic、anchor/距离条件、是否走到实际宿主内核。当前源码不替历史配置背书。
改迭代数位置也变了:TGS 的 \(h/N\) 和外力分配、本岛最大请求、位置 bias/积分都可能参与;不是单独“收敛更好了”的证明。
感觉有力,但 contactPoints 只有 normal:本版 extraction 确实这样做;另看 anchors,并记录 CCD/纯 torsion 缺项。不要靠范数从法向重建切向。
把 drive 上限当抓持力上限:drive effort、joint constraint reaction、接触冲量以及力矩是不同量;不能只靠
maxForce推出接触 \(F_n\)。换 h 后报告 force 变了:先核对除数/重复聚合/采样区间,再核 impulse cap、离散恢复/柔顺和迭代;没有固定条件不能作引擎差异结论。
宣称数值稳定或可微:源码分支与片段语法只说明算法和接口;复杂接触的运行稳定性、重复性、梯度与准确度均未验收。
10. 阅读练习与答案#
题 1:A 的 friction mode=MIN、B=MULTIPLY,系数 0.2/0.8;为什么 pair 不是 0.2?
答案:先按枚举最大值选 MULTIPLY,再相乘得 0.16。若 static 组合小于 dynamic,还会被抬到 dynamic;只看单侧数字不够。
题 2:刚性材质 restitution=0,damping=10 与柔顺材质 restitution=-100,damping=2 接触,是否能根据头文件概述说 damping 一定为 2?
答案:不能。当前 combiner 在恰一侧柔顺时 restitution 用 MIN 得 −100,damping 用 MAX 得 10。这里是代码阅读结论,没有运行证明或更改官方 SDK。
题 3:两普通刚体质量均为 2,法向穿过 COM,无其他响应因素。\(w\) 和让 \(v_n=-1\) 变为 0 所需的理想冲量是多少?
答案:\(w=1/2+1/2=1\)(质量单位的倒数),\(p_n=(0-(-1))/w=1\)(质量·长度/时间)。若换成 articulation links,不能继续套独立 body 公式。
题 4:TGS simulate(0.01),岛请求 5 次 position iteration。内部步是多少?是否等价于应用调用 5 次 simulate(0.002)?
答案:内部 \(h_s=0.002\);不等价。外层重新碰撞检测、控制/force 提交、回调与缓存边界不同,内部使用累计 delta motion 更新准备好的约束。
题 5:公开 API 允许 PGS velocity count=0,为何仍能在源码看到一遍 velocity solve?
答案:有约束的 CPU PGS 路径为 writeback 保证至少一遍;应读实际循环,不只转录 setter 范围。这不是所有后端/无约束路径的统一承诺。
题 6:法向报告冲量为 \((0,0.02,0)\),public step \(h=0.001\),能否写“瞬时总接触力 20 N”?
答案:在米/千克/秒制下,只能得到这段时间的已报告平均法向力 \((0,20,0)\) N;切向 anchor、纯 torsional row、缺失报告与事件聚合仍需核对,且不是瞬时峰值。
题 7:报告一个法向点和一个切向 anchor 后,\(\sum r\times p/h\) 是否必然等于真实完整 contact torque?
答案:不必然。参考点/坐标/作用对象先要一致;当前 friction writeback 没把额外纯 twist row 放入 anchor 点冲量,CCD friction stream 还可能缺失。结果只能按数据覆盖称“reported point-impulse moment”。
题 8:PCM、strong friction、累计 lambda 都保留一些信息,是否意味着同一种 warm start?
答案:不是。PCM 属接触生成缓存,strong friction 保留 anchor/friction error,累计 lambda 用于本次 solve 的投影更新。已查的法向 buffer/部分切向 row 在准备阶段清零;不能由缓存存在推断上帧全部冲量被重用。
题 9:能否将 SDK 当前默认 PGS/4+1/patch friction 写入 DexLab 历史 Isaac Sim 5.1 那一行?
答案:不能。必须找历史原生 PhysX binary/补丁、宿主转换与 scene/body/material 实际配置;本源树只证明本章阅读基线。#126 未核实部分继续保持未知。
后续边界#
E3 交付的是原生接触、CPU PGS/TGS、积分与观测的源码课程。运行态通知完整性、宿主版本/传感器映射、GPU 内核逐路径一致性、各种几何组合和硬件稳定性未验收。E4 继续传感器/渲染,E5 处理学习/数据,E6 展开后端与扩展特色,E7 再把完整课程与 DexLab 的已有实验索引连接起来。没有新增独立评分器或声称这些后续专题已完成。