@onerjs/core 8.51.3 → 8.51.5

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