@babylonjs/core 9.6.1 → 9.7.0

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.
Files changed (228) hide show
  1. package/Animations/animatorAvatar.d.ts +2 -0
  2. package/Animations/animatorAvatar.js +163 -94
  3. package/Animations/animatorAvatar.js.map +1 -1
  4. package/Engines/WebGPU/webgpuSnapshotRendering.js +7 -3
  5. package/Engines/WebGPU/webgpuSnapshotRendering.js.map +1 -1
  6. package/Engines/abstractEngine.js +2 -2
  7. package/Engines/abstractEngine.js.map +1 -1
  8. package/Engines/constants.d.ts +10 -0
  9. package/Engines/constants.js +10 -0
  10. package/Engines/constants.js.map +1 -1
  11. package/Events/deviceInputEvents.d.ts +5 -0
  12. package/Events/deviceInputEvents.js.map +1 -1
  13. package/FlowGraph/Blocks/Data/flowGraphIsKeyPressedBlock.d.ts +65 -0
  14. package/FlowGraph/Blocks/Data/flowGraphIsKeyPressedBlock.js +74 -0
  15. package/FlowGraph/Blocks/Data/flowGraphIsKeyPressedBlock.js.map +1 -0
  16. package/FlowGraph/Blocks/Data/index.d.ts +1 -0
  17. package/FlowGraph/Blocks/Data/index.js +1 -0
  18. package/FlowGraph/Blocks/Data/index.js.map +1 -1
  19. package/FlowGraph/Blocks/Event/flowGraphKeyDownEventBlock.d.ts +39 -0
  20. package/FlowGraph/Blocks/Event/flowGraphKeyDownEventBlock.js +42 -0
  21. package/FlowGraph/Blocks/Event/flowGraphKeyDownEventBlock.js.map +1 -0
  22. package/FlowGraph/Blocks/Event/flowGraphKeyUpEventBlock.d.ts +19 -0
  23. package/FlowGraph/Blocks/Event/flowGraphKeyUpEventBlock.js +25 -0
  24. package/FlowGraph/Blocks/Event/flowGraphKeyUpEventBlock.js.map +1 -0
  25. package/FlowGraph/Blocks/Event/flowGraphKeyboardEventBlock.d.ts +64 -0
  26. package/FlowGraph/Blocks/Event/flowGraphKeyboardEventBlock.js +50 -0
  27. package/FlowGraph/Blocks/Event/flowGraphKeyboardEventBlock.js.map +1 -0
  28. package/FlowGraph/Blocks/Event/index.d.ts +3 -0
  29. package/FlowGraph/Blocks/Event/index.js +3 -0
  30. package/FlowGraph/Blocks/Event/index.js.map +1 -1
  31. package/FlowGraph/Blocks/flowGraphBlockFactory.js +7 -0
  32. package/FlowGraph/Blocks/flowGraphBlockFactory.js.map +1 -1
  33. package/FlowGraph/Blocks/flowGraphBlockNames.d.ts +3 -0
  34. package/FlowGraph/Blocks/flowGraphBlockNames.js +3 -0
  35. package/FlowGraph/Blocks/flowGraphBlockNames.js.map +1 -1
  36. package/FlowGraph/flowGraph.d.ts +6 -0
  37. package/FlowGraph/flowGraph.js +10 -1
  38. package/FlowGraph/flowGraph.js.map +1 -1
  39. package/FlowGraph/flowGraphContext.d.ts +8 -0
  40. package/FlowGraph/flowGraphContext.js.map +1 -1
  41. package/FlowGraph/flowGraphEventType.d.ts +2 -0
  42. package/FlowGraph/flowGraphEventType.js +2 -0
  43. package/FlowGraph/flowGraphEventType.js.map +1 -1
  44. package/FlowGraph/flowGraphSceneEventCoordinator.d.ts +14 -0
  45. package/FlowGraph/flowGraphSceneEventCoordinator.js +56 -0
  46. package/FlowGraph/flowGraphSceneEventCoordinator.js.map +1 -1
  47. package/FlowGraph/utils.d.ts +7 -0
  48. package/FlowGraph/utils.js +8 -0
  49. package/FlowGraph/utils.js.map +1 -1
  50. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js +6 -0
  51. package/Materials/GaussianSplatting/gaussianSplattingMaterial.js.map +1 -1
  52. package/Materials/Node/Blocks/Fragment/perturbNormalBlock.js +6 -3
  53. package/Materials/Node/Blocks/Fragment/perturbNormalBlock.js.map +1 -1
  54. package/Materials/PBR/openpbrMaterial.d.ts +8 -0
  55. package/Materials/PBR/openpbrMaterial.js +16 -0
  56. package/Materials/PBR/openpbrMaterial.js.map +1 -1
  57. package/Materials/PBR/pbrBaseMaterial.d.ts +1 -0
  58. package/Materials/PBR/pbrBaseMaterial.js +8 -0
  59. package/Materials/PBR/pbrBaseMaterial.js.map +1 -1
  60. package/Materials/Textures/Procedurals/proceduralTexture.d.ts +6 -0
  61. package/Materials/Textures/Procedurals/proceduralTexture.js +3 -1
  62. package/Materials/Textures/Procedurals/proceduralTexture.js.map +1 -1
  63. package/Materials/Textures/index.d.ts +1 -0
  64. package/Materials/Textures/index.js +1 -0
  65. package/Materials/Textures/index.js.map +1 -1
  66. package/Materials/Textures/textureMerger.js +1 -0
  67. package/Materials/Textures/textureMerger.js.map +1 -1
  68. package/Materials/Textures/textureProcessor.d.ts +315 -0
  69. package/Materials/Textures/textureProcessor.js +792 -0
  70. package/Materials/Textures/textureProcessor.js.map +1 -0
  71. package/Materials/material.d.ts +24 -0
  72. package/Materials/material.js +39 -0
  73. package/Materials/material.js.map +1 -1
  74. package/Materials/standardMaterial.d.ts +1 -0
  75. package/Materials/standardMaterial.js +6 -0
  76. package/Materials/standardMaterial.js.map +1 -1
  77. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js +30 -8
  78. package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js.map +1 -1
  79. package/Misc/snapshotRenderingHelper.d.ts +4 -2
  80. package/Misc/snapshotRenderingHelper.js +33 -22
  81. package/Misc/snapshotRenderingHelper.js.map +1 -1
  82. package/Misc/tools.js +1 -1
  83. package/Misc/tools.js.map +1 -1
  84. package/Particles/thinParticleSystem.d.ts +6 -12
  85. package/Particles/thinParticleSystem.js +23 -21
  86. package/Particles/thinParticleSystem.js.map +1 -1
  87. package/Physics/v2/characterController.d.ts +104 -1
  88. package/Physics/v2/characterController.js +355 -1
  89. package/Physics/v2/characterController.js.map +1 -1
  90. package/Shaders/ShadersInclude/bumpFragment.js +3 -3
  91. package/Shaders/ShadersInclude/bumpFragment.js.map +1 -1
  92. package/Shaders/ShadersInclude/bumpFragmentMainFunctions.js +5 -1
  93. package/Shaders/ShadersInclude/bumpFragmentMainFunctions.js.map +1 -1
  94. package/Shaders/ShadersInclude/defaultFragmentDeclaration.js +3 -0
  95. package/Shaders/ShadersInclude/defaultFragmentDeclaration.js.map +1 -1
  96. package/Shaders/ShadersInclude/defaultUboDeclaration.js +1 -1
  97. package/Shaders/ShadersInclude/defaultUboDeclaration.js.map +1 -1
  98. package/Shaders/ShadersInclude/defaultVertexDeclaration.js +1 -1
  99. package/Shaders/ShadersInclude/defaultVertexDeclaration.js.map +1 -1
  100. package/Shaders/ShadersInclude/openpbrAmbientOcclusionData.js +1 -1
  101. package/Shaders/ShadersInclude/openpbrAmbientOcclusionData.js.map +1 -1
  102. package/Shaders/ShadersInclude/openpbrBackgroundTransmission.js +1 -1
  103. package/Shaders/ShadersInclude/openpbrBackgroundTransmission.js.map +1 -1
  104. package/Shaders/ShadersInclude/openpbrBaseLayerData.js +14 -14
  105. package/Shaders/ShadersInclude/openpbrBaseLayerData.js.map +1 -1
  106. package/Shaders/ShadersInclude/openpbrCoatLayerData.js +6 -6
  107. package/Shaders/ShadersInclude/openpbrCoatLayerData.js.map +1 -1
  108. package/Shaders/ShadersInclude/openpbrDirectLighting.js +1 -1
  109. package/Shaders/ShadersInclude/openpbrDirectLighting.js.map +1 -1
  110. package/Shaders/ShadersInclude/openpbrEnvironmentLighting.js +1 -1
  111. package/Shaders/ShadersInclude/openpbrEnvironmentLighting.js.map +1 -1
  112. package/Shaders/ShadersInclude/openpbrFragmentDeclaration.js +3 -0
  113. package/Shaders/ShadersInclude/openpbrFragmentDeclaration.js.map +1 -1
  114. package/Shaders/ShadersInclude/openpbrFuzzLayerData.js +3 -3
  115. package/Shaders/ShadersInclude/openpbrFuzzLayerData.js.map +1 -1
  116. package/Shaders/ShadersInclude/openpbrNormalMapFragment.js +4 -4
  117. package/Shaders/ShadersInclude/openpbrNormalMapFragment.js.map +1 -1
  118. package/Shaders/ShadersInclude/openpbrSubsurfaceLayerData.js +7 -3
  119. package/Shaders/ShadersInclude/openpbrSubsurfaceLayerData.js.map +1 -1
  120. package/Shaders/ShadersInclude/openpbrThinFilmLayerData.js +2 -2
  121. package/Shaders/ShadersInclude/openpbrThinFilmLayerData.js.map +1 -1
  122. package/Shaders/ShadersInclude/openpbrTransmissionLayerData.js +5 -5
  123. package/Shaders/ShadersInclude/openpbrTransmissionLayerData.js.map +1 -1
  124. package/Shaders/ShadersInclude/openpbrUboDeclaration.js +1 -1
  125. package/Shaders/ShadersInclude/openpbrUboDeclaration.js.map +1 -1
  126. package/Shaders/ShadersInclude/openpbrVertexDeclaration.js +1 -1
  127. package/Shaders/ShadersInclude/openpbrVertexDeclaration.js.map +1 -1
  128. package/Shaders/ShadersInclude/pbrBlockFinalUnlitComponents.js +1 -1
  129. package/Shaders/ShadersInclude/pbrBlockFinalUnlitComponents.js.map +1 -1
  130. package/Shaders/ShadersInclude/pbrBlockLightmapInit.js +1 -1
  131. package/Shaders/ShadersInclude/pbrBlockLightmapInit.js.map +1 -1
  132. package/Shaders/ShadersInclude/pbrFragmentDeclaration.js +3 -0
  133. package/Shaders/ShadersInclude/pbrFragmentDeclaration.js.map +1 -1
  134. package/Shaders/ShadersInclude/pbrHelperFunctions.js +4 -0
  135. package/Shaders/ShadersInclude/pbrHelperFunctions.js.map +1 -1
  136. package/Shaders/ShadersInclude/pbrUboDeclaration.js +1 -1
  137. package/Shaders/ShadersInclude/pbrUboDeclaration.js.map +1 -1
  138. package/Shaders/ShadersInclude/pbrVertexDeclaration.js +1 -1
  139. package/Shaders/ShadersInclude/pbrVertexDeclaration.js.map +1 -1
  140. package/Shaders/ShadersInclude/textureRepetitionFunctions.d.ts +5 -0
  141. package/Shaders/ShadersInclude/textureRepetitionFunctions.js +52 -0
  142. package/Shaders/ShadersInclude/textureRepetitionFunctions.js.map +1 -0
  143. package/Shaders/default.fragment.d.ts +1 -0
  144. package/Shaders/default.fragment.js +8 -6
  145. package/Shaders/default.fragment.js.map +1 -1
  146. package/Shaders/geometry.fragment.js +3 -3
  147. package/Shaders/geometry.fragment.js.map +1 -1
  148. package/Shaders/openpbr.fragment.d.ts +1 -0
  149. package/Shaders/openpbr.fragment.js +4 -2
  150. package/Shaders/openpbr.fragment.js.map +1 -1
  151. package/Shaders/pbr.fragment.d.ts +1 -0
  152. package/Shaders/pbr.fragment.js +24 -22
  153. package/Shaders/pbr.fragment.js.map +1 -1
  154. package/Shaders/textureProcessor.fragment.d.ts +5 -0
  155. package/Shaders/textureProcessor.fragment.js +156 -0
  156. package/Shaders/textureProcessor.fragment.js.map +1 -0
  157. package/ShadersWGSL/ShadersInclude/bumpFragment.js +3 -3
  158. package/ShadersWGSL/ShadersInclude/bumpFragment.js.map +1 -1
  159. package/ShadersWGSL/ShadersInclude/bumpFragmentMainFunctions.js +5 -1
  160. package/ShadersWGSL/ShadersInclude/bumpFragmentMainFunctions.js.map +1 -1
  161. package/ShadersWGSL/ShadersInclude/defaultUboDeclaration.js +1 -1
  162. package/ShadersWGSL/ShadersInclude/defaultUboDeclaration.js.map +1 -1
  163. package/ShadersWGSL/ShadersInclude/openpbrAmbientOcclusionData.js +1 -1
  164. package/ShadersWGSL/ShadersInclude/openpbrAmbientOcclusionData.js.map +1 -1
  165. package/ShadersWGSL/ShadersInclude/openpbrBackgroundTransmission.js +1 -1
  166. package/ShadersWGSL/ShadersInclude/openpbrBackgroundTransmission.js.map +1 -1
  167. package/ShadersWGSL/ShadersInclude/openpbrBaseLayerData.js +15 -15
  168. package/ShadersWGSL/ShadersInclude/openpbrBaseLayerData.js.map +1 -1
  169. package/ShadersWGSL/ShadersInclude/openpbrCoatLayerData.js +7 -7
  170. package/ShadersWGSL/ShadersInclude/openpbrCoatLayerData.js.map +1 -1
  171. package/ShadersWGSL/ShadersInclude/openpbrDirectLighting.js +1 -1
  172. package/ShadersWGSL/ShadersInclude/openpbrDirectLighting.js.map +1 -1
  173. package/ShadersWGSL/ShadersInclude/openpbrEnvironmentLighting.js +1 -1
  174. package/ShadersWGSL/ShadersInclude/openpbrEnvironmentLighting.js.map +1 -1
  175. package/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.js +4 -4
  176. package/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.js.map +1 -1
  177. package/ShadersWGSL/ShadersInclude/openpbrIblFunctions.js +3 -3
  178. package/ShadersWGSL/ShadersInclude/openpbrIblFunctions.js.map +1 -1
  179. package/ShadersWGSL/ShadersInclude/openpbrNormalMapFragment.js +4 -4
  180. package/ShadersWGSL/ShadersInclude/openpbrNormalMapFragment.js.map +1 -1
  181. package/ShadersWGSL/ShadersInclude/openpbrSubsurfaceLayerData.js +7 -3
  182. package/ShadersWGSL/ShadersInclude/openpbrSubsurfaceLayerData.js.map +1 -1
  183. package/ShadersWGSL/ShadersInclude/openpbrThinFilmLayerData.js +2 -2
  184. package/ShadersWGSL/ShadersInclude/openpbrThinFilmLayerData.js.map +1 -1
  185. package/ShadersWGSL/ShadersInclude/openpbrTransmissionLayerData.js +6 -6
  186. package/ShadersWGSL/ShadersInclude/openpbrTransmissionLayerData.js.map +1 -1
  187. package/ShadersWGSL/ShadersInclude/openpbrUboDeclaration.js +1 -1
  188. package/ShadersWGSL/ShadersInclude/openpbrUboDeclaration.js.map +1 -1
  189. package/ShadersWGSL/ShadersInclude/pbrBlockFinalUnlitComponents.js +1 -1
  190. package/ShadersWGSL/ShadersInclude/pbrBlockFinalUnlitComponents.js.map +1 -1
  191. package/ShadersWGSL/ShadersInclude/pbrBlockLightmapInit.js +1 -1
  192. package/ShadersWGSL/ShadersInclude/pbrBlockLightmapInit.js.map +1 -1
  193. package/ShadersWGSL/ShadersInclude/pbrHelperFunctions.js +4 -0
  194. package/ShadersWGSL/ShadersInclude/pbrHelperFunctions.js.map +1 -1
  195. package/ShadersWGSL/ShadersInclude/pbrUboDeclaration.js +1 -1
  196. package/ShadersWGSL/ShadersInclude/pbrUboDeclaration.js.map +1 -1
  197. package/ShadersWGSL/ShadersInclude/textureRepetitionFunctions.d.ts +5 -0
  198. package/ShadersWGSL/ShadersInclude/textureRepetitionFunctions.js +52 -0
  199. package/ShadersWGSL/ShadersInclude/textureRepetitionFunctions.js.map +1 -0
  200. package/ShadersWGSL/default.fragment.d.ts +1 -0
  201. package/ShadersWGSL/default.fragment.js +8 -6
  202. package/ShadersWGSL/default.fragment.js.map +1 -1
  203. package/ShadersWGSL/geometry.fragment.js +3 -3
  204. package/ShadersWGSL/geometry.fragment.js.map +1 -1
  205. package/ShadersWGSL/openpbr.fragment.d.ts +1 -0
  206. package/ShadersWGSL/openpbr.fragment.js +5 -3
  207. package/ShadersWGSL/openpbr.fragment.js.map +1 -1
  208. package/ShadersWGSL/openpbr.vertex.js +1 -1
  209. package/ShadersWGSL/openpbr.vertex.js.map +1 -1
  210. package/ShadersWGSL/pbr.fragment.d.ts +1 -0
  211. package/ShadersWGSL/pbr.fragment.js +24 -22
  212. package/ShadersWGSL/pbr.fragment.js.map +1 -1
  213. package/ShadersWGSL/textureProcessor.fragment.d.ts +5 -0
  214. package/ShadersWGSL/textureProcessor.fragment.js +161 -0
  215. package/ShadersWGSL/textureProcessor.fragment.js.map +1 -0
  216. package/SmartAssets/index.d.ts +2 -0
  217. package/SmartAssets/index.js +2 -0
  218. package/SmartAssets/index.js.map +1 -0
  219. package/SmartAssets/smartAssetManager.d.ts +156 -0
  220. package/SmartAssets/smartAssetManager.js +531 -0
  221. package/SmartAssets/smartAssetManager.js.map +1 -0
  222. package/SmartAssets/smartAssetSerializer.d.ts +61 -0
  223. package/SmartAssets/smartAssetSerializer.js +97 -0
  224. package/SmartAssets/smartAssetSerializer.js.map +1 -0
  225. package/index.d.ts +1 -0
  226. package/index.js +1 -0
  227. package/index.js.map +1 -1
  228. package/package.json +1 -1
@@ -30,7 +30,7 @@ export interface ICharacterControllerCollisionEvent {
30
30
  */
31
31
  collider: PhysicsBody;
32
32
  /**
33
- *
33
+ * Index of the collider in instances
34
34
  */
35
35
  colliderIndex: number;
36
36
  /**
@@ -184,6 +184,7 @@ export declare class PhysicsCharacterController {
184
184
  private _transformNode;
185
185
  private _ownShape;
186
186
  private _manifold;
187
+ private _stepUpSavedManifold;
187
188
  private _lastDisplacement;
188
189
  private _contactAngleSensitivity;
189
190
  private _lastInvDeltaTime;
@@ -226,6 +227,47 @@ export declare class PhysicsCharacterController {
226
227
  * default 0.5 (value for a 60deg angle)
227
228
  */
228
229
  maxSlopeCosine: number;
230
+ /**
231
+ * Maximum height the character can automatically step up onto a walkable surface.
232
+ * When greater than 0 the controller enforces this as a strict cap on step climbing,
233
+ * independent of the collision shape's geometry:
234
+ *
235
+ * - Obstacles whose top is at most maxStepHeight above the character's foot are
236
+ * climbed (either rolled over naturally by the capsule, or snapped up via the
237
+ * step-up sweep when the simplex would otherwise be blocked).
238
+ * - Obstacles taller than maxStepHeight are blocked, even ones the capsule's
239
+ * rounded bottom would otherwise glide over.
240
+ *
241
+ * This is enforced by demoting any "walkable" contact that sits more than
242
+ * maxStepHeight above the foot into an extra horizontal wall constraint, so the
243
+ * step-height limit does not depend on the capsule radius. As a documented side
244
+ * effect, slopes whose contact rises above maxStepHeight (roughly when
245
+ * `capsuleRadius * (1 - cos(slopeAngle)) > maxStepHeight`) are also treated as
246
+ * walls. Pick maxStepHeight large enough to clear the slope angles you want to
247
+ * remain walkable, or rely on `maxSlopeCosine` alone (with maxStepHeight = 0)
248
+ * when the rounded-capsule riding behavior is acceptable.
249
+ *
250
+ * Step-up only triggers against STATIC and ANIMATED bodies. Dynamic bodies fall
251
+ * through to normal contact resolution and pushing behavior.
252
+ *
253
+ * Thin walls / fences with floor behind them are not considered steppable: the
254
+ * landing must be measurably higher than the starting position along `up`.
255
+ *
256
+ * The foot is computed as `position - up * footOffset`. Override `footOffset` if
257
+ * you supply a custom collision shape whose center is not at half-height.
258
+ *
259
+ * Assumes `up` is a unit vector.
260
+ *
261
+ * default 0 (disabled)
262
+ */
263
+ maxStepHeight: number;
264
+ /**
265
+ * Distance from the body's `position` to the character's foot along `up`.
266
+ * Used by `maxStepHeight` to measure how high a contact sits above the foot.
267
+ * Defaults to half the capsule height passed at construction. Override when
268
+ * supplying a custom collision shape whose center is not at half-height.
269
+ */
270
+ footOffset: number;
229
271
  /**
230
272
  * character maximum speed
231
273
  * default 10
@@ -307,6 +349,22 @@ export declare class PhysicsCharacterController {
307
349
  protected _bodyPositionTracking: Map<any, any>;
308
350
  protected _createSurfaceConstraint(dt: number, contact: IContact, timeTravelled: number): ISurfaceConstraintInfo;
309
351
  protected _addMaxSlopePlane(maxSlopeCos: number, up: Vector3, index: number, constraints: ISurfaceConstraintInfo[], allowedPenetration: number): boolean;
352
+ /**
353
+ * Adds an extra horizontal wall constraint when a "walkable" contact sits more than
354
+ * `maxStepHeight` above the character's foot along `up`. Mirrors the structure of
355
+ * `_addMaxSlopePlane` but gates on contact height rather than slope steepness.
356
+ *
357
+ * This makes `maxStepHeight` a strict cap on step climbing independent of the
358
+ * capsule's curved bottom: without this, the rounded hemisphere produces an up-tilted
359
+ * (walkable) contact normal for any obstacle shorter than the capsule radius, and
360
+ * the simplex rides over it regardless of `maxStepHeight`.
361
+ * @param constraints constraint list being assembled for the current manifold
362
+ * @param contact source manifold contact backing `constraints[index]`
363
+ * @param index index of the constraint in `constraints` whose contact is under test
364
+ * @param allowedPenetration allowed penetration distance for this contact
365
+ * @returns true if an extra wall constraint was appended
366
+ */
367
+ protected _addStepHeightWallPlane(constraints: ISurfaceConstraintInfo[], contact: IContact, index: number, allowedPenetration: number): boolean;
310
368
  protected _resolveConstraintPenetration(constraint: ISurfaceConstraintInfo, penetrationRecoverySpeed: number): void;
311
369
  protected _createConstraintsFromManifold(dt: number, timeTravelled: number): ISurfaceConstraintInfo[];
312
370
  protected _simplexSolverSortInfo(info: SimplexSolverInfo): void;
@@ -331,6 +389,51 @@ export declare class PhysicsCharacterController {
331
389
  */
332
390
  checkSupportToRef(deltaTime: number, direction: Vector3, surfaceInfo: CharacterSurfaceInfo): void;
333
391
  protected _castWithCollectors(startPos: Vector3, endPos: Vector3, castCollector: any, startCollector?: any): void;
392
+ /**
393
+ * Rebuild the contact manifold from a proximity query at the given position.
394
+ * Used by step-up to validate a candidate landing without the merging logic of `_updateManifold`,
395
+ * which is not suited to a zero-length cast.
396
+ * @param position position at which to run the proximity query
397
+ */
398
+ protected _refreshManifoldAtPosition(position: Vector3): void;
399
+ /**
400
+ * Search the simplex solver output for a constraint that blocks horizontal motion:
401
+ * touched by the solver, non-walkable along `up`, and opposing the requested horizontal direction.
402
+ * @param simplexOutput output of `_simplexSolverSolve`
403
+ * @param constraints constraint array passed to the solver
404
+ * @param horizDir normalized horizontal direction of intent
405
+ * @returns the index of the first matching constraint, or -1
406
+ */
407
+ protected _findBlockingConstraintIndex(simplexOutput: SimplexSolverOutput, constraints: ISurfaceConstraintInfo[], horizDir: Vector3): number;
408
+ /**
409
+ * Iterate hits in the cast collector to find the closest one.
410
+ * @returns object with fraction, normal, body and index of the closest hit; null if there were no hits
411
+ */
412
+ protected _getClosestCastHit(): {
413
+ fraction: number;
414
+ normal: Vector3;
415
+ body: {
416
+ body: PhysicsBody;
417
+ index: number;
418
+ } | null;
419
+ } | null;
420
+ /**
421
+ * Attempt a step-up sweep when the character is blocked by a vertical-ish obstacle.
422
+ * Runs three shape casts (up, forward, down) and, if a valid walkable landing is found,
423
+ * commits a new position, refreshes the manifold and updates `_lastDisplacement`.
424
+ *
425
+ * Caller responsibilities on success:
426
+ * - subtract the returned time from `remainingTime`
427
+ * - skip `_resolveContacts`, the recast block and the position update for the iteration
428
+ * (the step is a teleport, not a contact-resolution motion)
429
+ *
430
+ * @param remainingTime time budget left in the current `_integrateManifolds` iteration
431
+ * @param inputVelocity character velocity at the start of the integration call
432
+ * @param simplexOutput output of the iteration's simplex solve
433
+ * @param constraints constraint array passed to the solver
434
+ * @returns time consumed by the step on success, -1 on failure (no state mutated)
435
+ */
436
+ protected _tryStepUp(remainingTime: number, inputVelocity: Vector3, simplexOutput: SimplexSolverOutput, constraints: ISurfaceConstraintInfo[]): number;
334
437
  protected _resolveContacts(deltaTime: number, gravity: Vector3): void;
335
438
  protected _getInverseInertiaWorld(body: {
336
439
  body: PhysicsBody;
@@ -73,6 +73,7 @@ export class PhysicsCharacterController {
73
73
  constructor(position, characterShapeOptions, scene) {
74
74
  this._orientation = Quaternion.Identity();
75
75
  this._manifold = [];
76
+ this._stepUpSavedManifold = [];
76
77
  this._contactAngleSensitivity = 10.0;
77
78
  this._tmpMatrix = new Matrix();
78
79
  this._tmpVecs = BuildArray(32, Vector3.Zero);
@@ -112,6 +113,40 @@ export class PhysicsCharacterController {
112
113
  * default 0.5 (value for a 60deg angle)
113
114
  */
114
115
  this.maxSlopeCosine = 0.5;
116
+ /**
117
+ * Maximum height the character can automatically step up onto a walkable surface.
118
+ * When greater than 0 the controller enforces this as a strict cap on step climbing,
119
+ * independent of the collision shape's geometry:
120
+ *
121
+ * - Obstacles whose top is at most maxStepHeight above the character's foot are
122
+ * climbed (either rolled over naturally by the capsule, or snapped up via the
123
+ * step-up sweep when the simplex would otherwise be blocked).
124
+ * - Obstacles taller than maxStepHeight are blocked, even ones the capsule's
125
+ * rounded bottom would otherwise glide over.
126
+ *
127
+ * This is enforced by demoting any "walkable" contact that sits more than
128
+ * maxStepHeight above the foot into an extra horizontal wall constraint, so the
129
+ * step-height limit does not depend on the capsule radius. As a documented side
130
+ * effect, slopes whose contact rises above maxStepHeight (roughly when
131
+ * `capsuleRadius * (1 - cos(slopeAngle)) > maxStepHeight`) are also treated as
132
+ * walls. Pick maxStepHeight large enough to clear the slope angles you want to
133
+ * remain walkable, or rely on `maxSlopeCosine` alone (with maxStepHeight = 0)
134
+ * when the rounded-capsule riding behavior is acceptable.
135
+ *
136
+ * Step-up only triggers against STATIC and ANIMATED bodies. Dynamic bodies fall
137
+ * through to normal contact resolution and pushing behavior.
138
+ *
139
+ * Thin walls / fences with floor behind them are not considered steppable: the
140
+ * landing must be measurably higher than the starting position along `up`.
141
+ *
142
+ * The foot is computed as `position - up * footOffset`. Override `footOffset` if
143
+ * you supply a custom collision shape whose center is not at half-height.
144
+ *
145
+ * Assumes `up` is a unit vector.
146
+ *
147
+ * default 0 (disabled)
148
+ */
149
+ this.maxStepHeight = 0;
115
150
  /**
116
151
  * character maximum speed
117
152
  * default 10
@@ -153,6 +188,7 @@ export class PhysicsCharacterController {
153
188
  this._lastVelocity = Vector3.Zero();
154
189
  const r = characterShapeOptions.capsuleRadius ?? 0.6;
155
190
  const h = characterShapeOptions.capsuleHeight ?? 1.8;
191
+ this.footOffset = h * 0.5;
156
192
  this._tmpVecs[0].set(0, h * 0.5 - r, 0);
157
193
  this._tmpVecs[1].set(0, -h * 0.5 + r, 0);
158
194
  this._ownShape = !characterShapeOptions.shape;
@@ -473,6 +509,63 @@ export class PhysicsCharacterController {
473
509
  }
474
510
  return false;
475
511
  }
512
+ /**
513
+ * Adds an extra horizontal wall constraint when a "walkable" contact sits more than
514
+ * `maxStepHeight` above the character's foot along `up`. Mirrors the structure of
515
+ * `_addMaxSlopePlane` but gates on contact height rather than slope steepness.
516
+ *
517
+ * This makes `maxStepHeight` a strict cap on step climbing independent of the
518
+ * capsule's curved bottom: without this, the rounded hemisphere produces an up-tilted
519
+ * (walkable) contact normal for any obstacle shorter than the capsule radius, and
520
+ * the simplex rides over it regardless of `maxStepHeight`.
521
+ * @param constraints constraint list being assembled for the current manifold
522
+ * @param contact source manifold contact backing `constraints[index]`
523
+ * @param index index of the constraint in `constraints` whose contact is under test
524
+ * @param allowedPenetration allowed penetration distance for this contact
525
+ * @returns true if an extra wall constraint was appended
526
+ */
527
+ _addStepHeightWallPlane(constraints, contact, index, allowedPenetration) {
528
+ const verticalComponent = constraints[index].planeNormal.dot(this.up);
529
+ // Skip near-flat (≈1) contacts (don't demote plain ground) and near-horizontal
530
+ // contacts (already wall-like — the regular constraint handles them).
531
+ if (verticalComponent <= 0.01 || verticalComponent >= 1 - 1e-3) {
532
+ return false;
533
+ }
534
+ // Height of the contact point above the character's foot, projected onto `up`.
535
+ const contactDelta = this._tmpVecs[24];
536
+ contact.position.subtractToRef(this._position, contactDelta);
537
+ const stepHeight = contactDelta.dot(this.up) + this.footOffset;
538
+ if (stepHeight <= this.maxStepHeight) {
539
+ return false;
540
+ }
541
+ const newConstraint = {
542
+ planeNormal: constraints[index].planeNormal.clone(),
543
+ planeDistance: constraints[index].planeDistance,
544
+ velocity: constraints[index].velocity.clone(),
545
+ angularVelocity: constraints[index].angularVelocity.clone(),
546
+ priority: constraints[index].priority,
547
+ dynamicFriction: constraints[index].dynamicFriction,
548
+ staticFriction: constraints[index].staticFriction,
549
+ extraDownStaticFriction: constraints[index].extraDownStaticFriction,
550
+ extraUpStaticFriction: constraints[index].extraUpStaticFriction,
551
+ };
552
+ const distance = newConstraint.planeDistance;
553
+ const stepWallVerticalProjection = TmpVectors.Vector3[0];
554
+ this.up.scaleToRef(verticalComponent, stepWallVerticalProjection);
555
+ newConstraint.planeNormal.subtractInPlace(stepWallVerticalProjection);
556
+ newConstraint.planeNormal.normalize();
557
+ if (distance >= 0) {
558
+ newConstraint.planeDistance = distance * newConstraint.planeNormal.dot(constraints[index].planeNormal);
559
+ }
560
+ else {
561
+ const penetrationToResolve = Math.min(0, distance + allowedPenetration);
562
+ newConstraint.planeDistance = penetrationToResolve / newConstraint.planeNormal.dot(constraints[index].planeNormal);
563
+ constraints[index].planeDistance = 0;
564
+ this._resolveConstraintPenetration(newConstraint, this.penetrationRecoverySpeed);
565
+ }
566
+ constraints.push(newConstraint);
567
+ return true;
568
+ }
476
569
  _resolveConstraintPenetration(constraint, penetrationRecoverySpeed) {
477
570
  // If penetrating we add extra velocity to push the character back out
478
571
  const eps = 1e-6;
@@ -486,7 +579,14 @@ export class PhysicsCharacterController {
486
579
  for (let i = 0; i < this._manifold.length; i++) {
487
580
  const surfaceConstraint = this._createSurfaceConstraint(dt, this._manifold[i], timeTravelled);
488
581
  constraints.push(surfaceConstraint);
489
- this._addMaxSlopePlane(this.maxSlopeCosine, this.up, i, constraints, this._manifold[i].allowedPenetration);
582
+ const slopeDemoted = this._addMaxSlopePlane(this.maxSlopeCosine, this.up, i, constraints, this._manifold[i].allowedPenetration);
583
+ // Step-height filter: when the contact sits above the foot by more than
584
+ // `maxStepHeight`, add an extra horizontal wall constraint so the simplex
585
+ // cannot ride over the contact via the capsule's rounded bottom. Skip when
586
+ // the slope check already added a wall for the same contact.
587
+ if (!slopeDemoted && this.maxStepHeight > 0) {
588
+ this._addStepHeightWallPlane(constraints, this._manifold[i], i, this._manifold[i].allowedPenetration);
589
+ }
490
590
  this._resolveConstraintPenetration(surfaceConstraint, this.penetrationRecoverySpeed);
491
591
  }
492
592
  return constraints;
@@ -1067,6 +1167,242 @@ export class PhysicsCharacterController {
1067
1167
  hknp.HP_World_ShapeCastWithCollector(hk.world, castCollector, query);
1068
1168
  }
1069
1169
  }
1170
+ /**
1171
+ * Rebuild the contact manifold from a proximity query at the given position.
1172
+ * Used by step-up to validate a candidate landing without the merging logic of `_updateManifold`,
1173
+ * which is not suited to a zero-length cast.
1174
+ * @param position position at which to run the proximity query
1175
+ */
1176
+ _refreshManifoldAtPosition(position) {
1177
+ const hk = this._scene.getPhysicsEngine().getPhysicsPlugin();
1178
+ const hknp = hk._hknp;
1179
+ const bodyMap = hk._bodies;
1180
+ const startNative = [position.x, position.y, position.z];
1181
+ const orientation = [this._orientation.x, this._orientation.y, this._orientation.z, this._orientation.w];
1182
+ const query /*: ShapeProximityInput*/ = [
1183
+ this._shape._pluginData,
1184
+ startNative,
1185
+ orientation,
1186
+ this.keepDistance + this.keepContactTolerance,
1187
+ false,
1188
+ [this._body._pluginData.hpBodyId[0]],
1189
+ ];
1190
+ hknp.HP_World_ShapeProximityWithCollector(hk.world, this._startCollector, query);
1191
+ this._manifold.length = 0;
1192
+ const numHits = hknp.HP_QueryCollector_GetNumHits(this._startCollector)[1];
1193
+ for (let i = 0; i < numHits; i++) {
1194
+ const [distance, , contactWorld] = hknp.HP_QueryCollector_GetShapeProximityResult(this._startCollector, i)[1];
1195
+ this._manifold.push({
1196
+ position: Vector3.FromArray(contactWorld[3]),
1197
+ normal: Vector3.FromArray(contactWorld[4]),
1198
+ distance: distance,
1199
+ fraction: 0,
1200
+ bodyB: bodyMap.get(contactWorld[0][0]),
1201
+ allowedPenetration: Math.min(Math.max(this.keepDistance - distance, 0.0), this.keepDistance),
1202
+ });
1203
+ }
1204
+ }
1205
+ /**
1206
+ * Search the simplex solver output for a constraint that blocks horizontal motion:
1207
+ * touched by the solver, non-walkable along `up`, and opposing the requested horizontal direction.
1208
+ * @param simplexOutput output of `_simplexSolverSolve`
1209
+ * @param constraints constraint array passed to the solver
1210
+ * @param horizDir normalized horizontal direction of intent
1211
+ * @returns the index of the first matching constraint, or -1
1212
+ */
1213
+ _findBlockingConstraintIndex(simplexOutput, constraints, horizDir) {
1214
+ const oppositionEps = 1e-3;
1215
+ const ceilingThreshold = -0.5;
1216
+ const maxSlopeCosEps = 0.1;
1217
+ const maxSlope = Math.max(this.maxSlopeCosine, maxSlopeCosEps);
1218
+ for (let i = 0; i < constraints.length; i++) {
1219
+ if (!simplexOutput.planeInteractions[i].touched) {
1220
+ continue;
1221
+ }
1222
+ const n = constraints[i].planeNormal;
1223
+ const normalDotUp = n.dot(this.up);
1224
+ if (normalDotUp >= maxSlope) {
1225
+ continue;
1226
+ }
1227
+ if (normalDotUp <= ceilingThreshold) {
1228
+ continue;
1229
+ }
1230
+ if (n.dot(horizDir) > -oppositionEps) {
1231
+ continue;
1232
+ }
1233
+ return i;
1234
+ }
1235
+ return -1;
1236
+ }
1237
+ /**
1238
+ * Iterate hits in the cast collector to find the closest one.
1239
+ * @returns object with fraction, normal, body and index of the closest hit; null if there were no hits
1240
+ */
1241
+ _getClosestCastHit() {
1242
+ const hk = this._scene.getPhysicsEngine().getPhysicsPlugin();
1243
+ const hknp = hk._hknp;
1244
+ const bodyMap = hk._bodies;
1245
+ const numHits = hknp.HP_QueryCollector_GetNumHits(this._castCollector)[1];
1246
+ if (numHits <= 0) {
1247
+ return null;
1248
+ }
1249
+ let bestFrac = Number.POSITIVE_INFINITY;
1250
+ const bestNormal = TmpVectors.Vector3[0];
1251
+ let hasBestNormal = false;
1252
+ let bestBody = null;
1253
+ for (let i = 0; i < numHits; i++) {
1254
+ const [frac, , hitWorld] = hknp.HP_QueryCollector_GetShapeCastResult(this._castCollector, i)[1];
1255
+ if (frac < bestFrac) {
1256
+ bestFrac = frac;
1257
+ Vector3.FromArrayToRef(hitWorld[4], 0, bestNormal);
1258
+ hasBestNormal = true;
1259
+ bestBody = bodyMap.get(hitWorld[0][0]) ?? null;
1260
+ }
1261
+ }
1262
+ if (!hasBestNormal) {
1263
+ return null;
1264
+ }
1265
+ return { fraction: bestFrac, normal: bestNormal.clone(), body: bestBody };
1266
+ }
1267
+ /**
1268
+ * Attempt a step-up sweep when the character is blocked by a vertical-ish obstacle.
1269
+ * Runs three shape casts (up, forward, down) and, if a valid walkable landing is found,
1270
+ * commits a new position, refreshes the manifold and updates `_lastDisplacement`.
1271
+ *
1272
+ * Caller responsibilities on success:
1273
+ * - subtract the returned time from `remainingTime`
1274
+ * - skip `_resolveContacts`, the recast block and the position update for the iteration
1275
+ * (the step is a teleport, not a contact-resolution motion)
1276
+ *
1277
+ * @param remainingTime time budget left in the current `_integrateManifolds` iteration
1278
+ * @param inputVelocity character velocity at the start of the integration call
1279
+ * @param simplexOutput output of the iteration's simplex solve
1280
+ * @param constraints constraint array passed to the solver
1281
+ * @returns time consumed by the step on success, -1 on failure (no state mutated)
1282
+ */
1283
+ _tryStepUp(remainingTime, inputVelocity, simplexOutput, constraints) {
1284
+ const eps = 1e-4;
1285
+ const stepHeight = this.maxStepHeight;
1286
+ if (stepHeight <= 0 || remainingTime <= 0) {
1287
+ return -1;
1288
+ }
1289
+ // 1. Intended horizontal displacement (assumes `up` is unit-length)
1290
+ const upUnit = this.up;
1291
+ const upDotVel = inputVelocity.dot(upUnit);
1292
+ const horizVel = this._tmpVecs[28];
1293
+ upUnit.scaleToRef(upDotVel, this._tmpVecs[29]);
1294
+ inputVelocity.subtractToRef(this._tmpVecs[29], horizVel);
1295
+ const horizVelLenSqr = horizVel.lengthSquared();
1296
+ if (horizVelLenSqr < eps * eps) {
1297
+ return -1;
1298
+ }
1299
+ const horizVelLen = Math.sqrt(horizVelLenSqr);
1300
+ const horizDir = this._tmpVecs[30];
1301
+ horizVel.scaleToRef(1 / horizVelLen, horizDir);
1302
+ const horizDist = horizVelLen * remainingTime;
1303
+ if (horizDist <= eps) {
1304
+ return -1;
1305
+ }
1306
+ // 2. Solver must have hit a blocking, non-walkable, opposing constraint
1307
+ if (this._findBlockingConstraintIndex(simplexOutput, constraints, horizDir) < 0) {
1308
+ return -1;
1309
+ }
1310
+ // 3. Up-cast: find available head-room up to stepHeight
1311
+ const upEnd = this._tmpVecs[31];
1312
+ upUnit.scaleToRef(stepHeight, upEnd);
1313
+ upEnd.addInPlace(this._position);
1314
+ this._castWithCollectors(this._position, upEnd, this._castCollector);
1315
+ const upHit = this._getClosestCastHit();
1316
+ let stepClear = stepHeight;
1317
+ if (upHit != null) {
1318
+ if (upHit.body && upHit.body.body.getMotionType(upHit.body.index) === 2 /* PhysicsMotionType.DYNAMIC */) {
1319
+ return -1;
1320
+ }
1321
+ stepClear = Math.max(0, upHit.fraction * stepHeight - this.keepDistance);
1322
+ }
1323
+ if (stepClear <= eps) {
1324
+ return -1;
1325
+ }
1326
+ // 4. Forward-cast: sweep from the elevated position by the requested horizontal distance
1327
+ const elevated = upUnit.scale(stepClear);
1328
+ elevated.addInPlace(this._position);
1329
+ const fwdEnd = horizDir.scale(horizDist);
1330
+ fwdEnd.addInPlace(elevated);
1331
+ this._castWithCollectors(elevated, fwdEnd, this._castCollector);
1332
+ let fwdFrac = 1.0;
1333
+ const fwdHit = this._getClosestCastHit();
1334
+ if (fwdHit != null) {
1335
+ if (fwdHit.body && fwdHit.body.body.getMotionType(fwdHit.body.index) === 2 /* PhysicsMotionType.DYNAMIC */) {
1336
+ return -1;
1337
+ }
1338
+ fwdFrac = fwdHit.fraction;
1339
+ }
1340
+ // Backoff by keepDistance along horizDir
1341
+ const fwdFracAdjusted = Math.max(0, Math.min(1, fwdFrac - this.keepDistance / horizDist));
1342
+ if (fwdFracAdjusted <= eps) {
1343
+ return -1;
1344
+ }
1345
+ const fwdEndAdjusted = horizDir.scale(horizDist * fwdFracAdjusted);
1346
+ fwdEndAdjusted.addInPlace(elevated);
1347
+ // 5. Down-cast: drop from forward position to find walkable landing
1348
+ const downDist = stepClear + 2 * this.keepDistance;
1349
+ const downEnd = upUnit.scale(-downDist);
1350
+ downEnd.addInPlace(fwdEndAdjusted);
1351
+ this._castWithCollectors(fwdEndAdjusted, downEnd, this._castCollector);
1352
+ const downHit = this._getClosestCastHit();
1353
+ if (downHit == null) {
1354
+ // No ground found within reach: stepping into the void (e.g., over a fence). Reject.
1355
+ return -1;
1356
+ }
1357
+ if (downHit.body == null) {
1358
+ return -1;
1359
+ }
1360
+ const landingMotion = downHit.body.body.getMotionType(downHit.body.index);
1361
+ if (landingMotion === 2 /* PhysicsMotionType.DYNAMIC */) {
1362
+ return -1;
1363
+ }
1364
+ const maxSlopeCosEps = 0.1;
1365
+ const landingNormalDotUp = downHit.normal.dot(upUnit);
1366
+ if (landingNormalDotUp < Math.max(this.maxSlopeCosine, maxSlopeCosEps)) {
1367
+ return -1;
1368
+ }
1369
+ // Landing position = downStart - upUnit * (downFrac * downDist - keepDistance)
1370
+ const landingDrop = downHit.fraction * downDist - this.keepDistance;
1371
+ const landingPos = TmpVectors.Vector3[0];
1372
+ upUnit.scaleToRef(-landingDrop, landingPos);
1373
+ landingPos.addInPlace(fwdEndAdjusted);
1374
+ // Thin-wall guard: landing must be measurably higher than where we started.
1375
+ const landingDelta = TmpVectors.Vector3[1];
1376
+ landingPos.subtractToRef(this._position, landingDelta);
1377
+ const heightDelta = landingDelta.dot(upUnit);
1378
+ if (heightDelta < eps) {
1379
+ return -1;
1380
+ }
1381
+ // 6. Snapshot manifold, refresh at landing, validate no unacceptable penetration
1382
+ const savedManifold = this._stepUpSavedManifold;
1383
+ savedManifold.length = this._manifold.length;
1384
+ for (let i = 0; i < this._manifold.length; i++) {
1385
+ savedManifold[i] = this._manifold[i];
1386
+ }
1387
+ this._refreshManifoldAtPosition(landingPos);
1388
+ const maxSlope = Math.max(this.maxSlopeCosine, maxSlopeCosEps);
1389
+ for (let i = 0; i < this._manifold.length; i++) {
1390
+ const c = this._manifold[i];
1391
+ if (c.normal.dot(upUnit) < maxSlope && c.distance < -this.keepDistance) {
1392
+ // Penetrating a non-walkable surface at landing — reject and restore manifold
1393
+ this._manifold.length = savedManifold.length;
1394
+ for (let j = 0; j < savedManifold.length; j++) {
1395
+ this._manifold[j] = savedManifold[j];
1396
+ }
1397
+ return -1;
1398
+ }
1399
+ }
1400
+ // 7. Commit
1401
+ const displacement = landingPos.subtract(this._position);
1402
+ this._lastDisplacement.copyFrom(displacement);
1403
+ this._position.copyFrom(landingPos);
1404
+ return remainingTime * fwdFracAdjusted;
1405
+ }
1070
1406
  _resolveContacts(deltaTime, gravity) {
1071
1407
  const eps = 1e-12;
1072
1408
  //<todo object interactions out
@@ -1173,6 +1509,10 @@ export class PhysicsCharacterController {
1173
1509
  const epsSqrd = 1e-8;
1174
1510
  let newVelocity = Vector3.Zero();
1175
1511
  let remainingTime = deltaTime;
1512
+ // Snapshot of the input velocity, used to preserve user intent if a step-up succeeds
1513
+ // and the loop exits without running another full solver pass.
1514
+ const inputVelocity = this._velocity;
1515
+ let didStepUp = false;
1176
1516
  // Make sure that contact with bodies that have been removed since the call to checkSupport() are removed from the
1177
1517
  // manifold
1178
1518
  this._validateManifold();
@@ -1188,6 +1528,20 @@ export class PhysicsCharacterController {
1188
1528
  const newDisplacement = solveResults.position;
1189
1529
  const solverDeltaTime = solveResults.deltaTime;
1190
1530
  newVelocity = solveResults.velocity;
1531
+ // Attempt step-up at most once per integrate() call when blocked by a vertical-ish obstacle.
1532
+ if (!didStepUp && this.maxStepHeight > 0) {
1533
+ const timeConsumed = this._tryStepUp(remainingTime, inputVelocity, solveResults, constraints);
1534
+ if (timeConsumed >= 0) {
1535
+ remainingTime -= timeConsumed;
1536
+ // Preserve original input velocity so the final `_velocity` reflects user intent
1537
+ // even if the loop exits without another solver pass.
1538
+ newVelocity = inputVelocity;
1539
+ didStepUp = true;
1540
+ // Skip the normal contact resolution / recast / integrate-position block:
1541
+ // step-up is a teleport that already updated position, manifold and lastDisplacement.
1542
+ continue;
1543
+ }
1544
+ }
1191
1545
  this._resolveContacts(deltaTime, gravity);
1192
1546
  let newContactIndex = -1;
1193
1547
  // todo if (updateResult == hit multiple bodies) ... cast again