@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.
- package/Animations/animatorAvatar.d.ts +2 -0
- package/Animations/animatorAvatar.js +163 -94
- package/Animations/animatorAvatar.js.map +1 -1
- package/Engines/WebGPU/webgpuSnapshotRendering.js +7 -3
- package/Engines/WebGPU/webgpuSnapshotRendering.js.map +1 -1
- package/Engines/abstractEngine.js +2 -2
- package/Engines/abstractEngine.js.map +1 -1
- package/Engines/constants.d.ts +10 -0
- package/Engines/constants.js +10 -0
- package/Engines/constants.js.map +1 -1
- package/Events/deviceInputEvents.d.ts +5 -0
- package/Events/deviceInputEvents.js.map +1 -1
- package/FlowGraph/Blocks/Data/flowGraphIsKeyPressedBlock.d.ts +65 -0
- package/FlowGraph/Blocks/Data/flowGraphIsKeyPressedBlock.js +74 -0
- package/FlowGraph/Blocks/Data/flowGraphIsKeyPressedBlock.js.map +1 -0
- package/FlowGraph/Blocks/Data/index.d.ts +1 -0
- package/FlowGraph/Blocks/Data/index.js +1 -0
- package/FlowGraph/Blocks/Data/index.js.map +1 -1
- package/FlowGraph/Blocks/Event/flowGraphKeyDownEventBlock.d.ts +39 -0
- package/FlowGraph/Blocks/Event/flowGraphKeyDownEventBlock.js +42 -0
- package/FlowGraph/Blocks/Event/flowGraphKeyDownEventBlock.js.map +1 -0
- package/FlowGraph/Blocks/Event/flowGraphKeyUpEventBlock.d.ts +19 -0
- package/FlowGraph/Blocks/Event/flowGraphKeyUpEventBlock.js +25 -0
- package/FlowGraph/Blocks/Event/flowGraphKeyUpEventBlock.js.map +1 -0
- package/FlowGraph/Blocks/Event/flowGraphKeyboardEventBlock.d.ts +64 -0
- package/FlowGraph/Blocks/Event/flowGraphKeyboardEventBlock.js +50 -0
- package/FlowGraph/Blocks/Event/flowGraphKeyboardEventBlock.js.map +1 -0
- package/FlowGraph/Blocks/Event/index.d.ts +3 -0
- package/FlowGraph/Blocks/Event/index.js +3 -0
- package/FlowGraph/Blocks/Event/index.js.map +1 -1
- package/FlowGraph/Blocks/flowGraphBlockFactory.js +7 -0
- package/FlowGraph/Blocks/flowGraphBlockFactory.js.map +1 -1
- package/FlowGraph/Blocks/flowGraphBlockNames.d.ts +3 -0
- package/FlowGraph/Blocks/flowGraphBlockNames.js +3 -0
- package/FlowGraph/Blocks/flowGraphBlockNames.js.map +1 -1
- package/FlowGraph/flowGraph.d.ts +6 -0
- package/FlowGraph/flowGraph.js +10 -1
- package/FlowGraph/flowGraph.js.map +1 -1
- package/FlowGraph/flowGraphContext.d.ts +8 -0
- package/FlowGraph/flowGraphContext.js.map +1 -1
- package/FlowGraph/flowGraphEventType.d.ts +2 -0
- package/FlowGraph/flowGraphEventType.js +2 -0
- package/FlowGraph/flowGraphEventType.js.map +1 -1
- package/FlowGraph/flowGraphSceneEventCoordinator.d.ts +14 -0
- package/FlowGraph/flowGraphSceneEventCoordinator.js +56 -0
- package/FlowGraph/flowGraphSceneEventCoordinator.js.map +1 -1
- package/FlowGraph/utils.d.ts +7 -0
- package/FlowGraph/utils.js +8 -0
- package/FlowGraph/utils.js.map +1 -1
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.js +6 -0
- package/Materials/GaussianSplatting/gaussianSplattingMaterial.js.map +1 -1
- package/Materials/Node/Blocks/Fragment/perturbNormalBlock.js +6 -3
- package/Materials/Node/Blocks/Fragment/perturbNormalBlock.js.map +1 -1
- package/Materials/PBR/openpbrMaterial.d.ts +8 -0
- package/Materials/PBR/openpbrMaterial.js +16 -0
- package/Materials/PBR/openpbrMaterial.js.map +1 -1
- package/Materials/PBR/pbrBaseMaterial.d.ts +1 -0
- package/Materials/PBR/pbrBaseMaterial.js +8 -0
- package/Materials/PBR/pbrBaseMaterial.js.map +1 -1
- package/Materials/Textures/Procedurals/proceduralTexture.d.ts +6 -0
- package/Materials/Textures/Procedurals/proceduralTexture.js +3 -1
- package/Materials/Textures/Procedurals/proceduralTexture.js.map +1 -1
- package/Materials/Textures/index.d.ts +1 -0
- package/Materials/Textures/index.js +1 -0
- package/Materials/Textures/index.js.map +1 -1
- package/Materials/Textures/textureMerger.js +1 -0
- package/Materials/Textures/textureMerger.js.map +1 -1
- package/Materials/Textures/textureProcessor.d.ts +315 -0
- package/Materials/Textures/textureProcessor.js +792 -0
- package/Materials/Textures/textureProcessor.js.map +1 -0
- package/Materials/material.d.ts +24 -0
- package/Materials/material.js +39 -0
- package/Materials/material.js.map +1 -1
- package/Materials/standardMaterial.d.ts +1 -0
- package/Materials/standardMaterial.js +6 -0
- package/Materials/standardMaterial.js.map +1 -1
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js +30 -8
- package/Meshes/GaussianSplatting/gaussianSplattingMeshBase.js.map +1 -1
- package/Misc/snapshotRenderingHelper.d.ts +4 -2
- package/Misc/snapshotRenderingHelper.js +33 -22
- package/Misc/snapshotRenderingHelper.js.map +1 -1
- package/Misc/tools.js +1 -1
- package/Misc/tools.js.map +1 -1
- package/Particles/thinParticleSystem.d.ts +6 -12
- package/Particles/thinParticleSystem.js +23 -21
- package/Particles/thinParticleSystem.js.map +1 -1
- package/Physics/v2/characterController.d.ts +104 -1
- package/Physics/v2/characterController.js +355 -1
- package/Physics/v2/characterController.js.map +1 -1
- package/Shaders/ShadersInclude/bumpFragment.js +3 -3
- package/Shaders/ShadersInclude/bumpFragment.js.map +1 -1
- package/Shaders/ShadersInclude/bumpFragmentMainFunctions.js +5 -1
- package/Shaders/ShadersInclude/bumpFragmentMainFunctions.js.map +1 -1
- package/Shaders/ShadersInclude/defaultFragmentDeclaration.js +3 -0
- package/Shaders/ShadersInclude/defaultFragmentDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/defaultUboDeclaration.js +1 -1
- package/Shaders/ShadersInclude/defaultUboDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/defaultVertexDeclaration.js +1 -1
- package/Shaders/ShadersInclude/defaultVertexDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrAmbientOcclusionData.js +1 -1
- package/Shaders/ShadersInclude/openpbrAmbientOcclusionData.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrBackgroundTransmission.js +1 -1
- package/Shaders/ShadersInclude/openpbrBackgroundTransmission.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrBaseLayerData.js +14 -14
- package/Shaders/ShadersInclude/openpbrBaseLayerData.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrCoatLayerData.js +6 -6
- package/Shaders/ShadersInclude/openpbrCoatLayerData.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrDirectLighting.js +1 -1
- package/Shaders/ShadersInclude/openpbrDirectLighting.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrEnvironmentLighting.js +1 -1
- package/Shaders/ShadersInclude/openpbrEnvironmentLighting.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrFragmentDeclaration.js +3 -0
- package/Shaders/ShadersInclude/openpbrFragmentDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrFuzzLayerData.js +3 -3
- package/Shaders/ShadersInclude/openpbrFuzzLayerData.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrNormalMapFragment.js +4 -4
- package/Shaders/ShadersInclude/openpbrNormalMapFragment.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrSubsurfaceLayerData.js +7 -3
- package/Shaders/ShadersInclude/openpbrSubsurfaceLayerData.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrThinFilmLayerData.js +2 -2
- package/Shaders/ShadersInclude/openpbrThinFilmLayerData.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrTransmissionLayerData.js +5 -5
- package/Shaders/ShadersInclude/openpbrTransmissionLayerData.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrUboDeclaration.js +1 -1
- package/Shaders/ShadersInclude/openpbrUboDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/openpbrVertexDeclaration.js +1 -1
- package/Shaders/ShadersInclude/openpbrVertexDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/pbrBlockFinalUnlitComponents.js +1 -1
- package/Shaders/ShadersInclude/pbrBlockFinalUnlitComponents.js.map +1 -1
- package/Shaders/ShadersInclude/pbrBlockLightmapInit.js +1 -1
- package/Shaders/ShadersInclude/pbrBlockLightmapInit.js.map +1 -1
- package/Shaders/ShadersInclude/pbrFragmentDeclaration.js +3 -0
- package/Shaders/ShadersInclude/pbrFragmentDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/pbrHelperFunctions.js +4 -0
- package/Shaders/ShadersInclude/pbrHelperFunctions.js.map +1 -1
- package/Shaders/ShadersInclude/pbrUboDeclaration.js +1 -1
- package/Shaders/ShadersInclude/pbrUboDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/pbrVertexDeclaration.js +1 -1
- package/Shaders/ShadersInclude/pbrVertexDeclaration.js.map +1 -1
- package/Shaders/ShadersInclude/textureRepetitionFunctions.d.ts +5 -0
- package/Shaders/ShadersInclude/textureRepetitionFunctions.js +52 -0
- package/Shaders/ShadersInclude/textureRepetitionFunctions.js.map +1 -0
- package/Shaders/default.fragment.d.ts +1 -0
- package/Shaders/default.fragment.js +8 -6
- package/Shaders/default.fragment.js.map +1 -1
- package/Shaders/geometry.fragment.js +3 -3
- package/Shaders/geometry.fragment.js.map +1 -1
- package/Shaders/openpbr.fragment.d.ts +1 -0
- package/Shaders/openpbr.fragment.js +4 -2
- package/Shaders/openpbr.fragment.js.map +1 -1
- package/Shaders/pbr.fragment.d.ts +1 -0
- package/Shaders/pbr.fragment.js +24 -22
- package/Shaders/pbr.fragment.js.map +1 -1
- package/Shaders/textureProcessor.fragment.d.ts +5 -0
- package/Shaders/textureProcessor.fragment.js +156 -0
- package/Shaders/textureProcessor.fragment.js.map +1 -0
- package/ShadersWGSL/ShadersInclude/bumpFragment.js +3 -3
- package/ShadersWGSL/ShadersInclude/bumpFragment.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/bumpFragmentMainFunctions.js +5 -1
- package/ShadersWGSL/ShadersInclude/bumpFragmentMainFunctions.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/defaultUboDeclaration.js +1 -1
- package/ShadersWGSL/ShadersInclude/defaultUboDeclaration.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrAmbientOcclusionData.js +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrAmbientOcclusionData.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrBackgroundTransmission.js +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrBackgroundTransmission.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrBaseLayerData.js +15 -15
- package/ShadersWGSL/ShadersInclude/openpbrBaseLayerData.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrCoatLayerData.js +7 -7
- package/ShadersWGSL/ShadersInclude/openpbrCoatLayerData.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrDirectLighting.js +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrDirectLighting.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrEnvironmentLighting.js +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrEnvironmentLighting.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.js +4 -4
- package/ShadersWGSL/ShadersInclude/openpbrFuzzLayerData.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrIblFunctions.js +3 -3
- package/ShadersWGSL/ShadersInclude/openpbrIblFunctions.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrNormalMapFragment.js +4 -4
- package/ShadersWGSL/ShadersInclude/openpbrNormalMapFragment.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrSubsurfaceLayerData.js +7 -3
- package/ShadersWGSL/ShadersInclude/openpbrSubsurfaceLayerData.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrThinFilmLayerData.js +2 -2
- package/ShadersWGSL/ShadersInclude/openpbrThinFilmLayerData.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrTransmissionLayerData.js +6 -6
- package/ShadersWGSL/ShadersInclude/openpbrTransmissionLayerData.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrUboDeclaration.js +1 -1
- package/ShadersWGSL/ShadersInclude/openpbrUboDeclaration.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/pbrBlockFinalUnlitComponents.js +1 -1
- package/ShadersWGSL/ShadersInclude/pbrBlockFinalUnlitComponents.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/pbrBlockLightmapInit.js +1 -1
- package/ShadersWGSL/ShadersInclude/pbrBlockLightmapInit.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/pbrHelperFunctions.js +4 -0
- package/ShadersWGSL/ShadersInclude/pbrHelperFunctions.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/pbrUboDeclaration.js +1 -1
- package/ShadersWGSL/ShadersInclude/pbrUboDeclaration.js.map +1 -1
- package/ShadersWGSL/ShadersInclude/textureRepetitionFunctions.d.ts +5 -0
- package/ShadersWGSL/ShadersInclude/textureRepetitionFunctions.js +52 -0
- package/ShadersWGSL/ShadersInclude/textureRepetitionFunctions.js.map +1 -0
- package/ShadersWGSL/default.fragment.d.ts +1 -0
- package/ShadersWGSL/default.fragment.js +8 -6
- package/ShadersWGSL/default.fragment.js.map +1 -1
- package/ShadersWGSL/geometry.fragment.js +3 -3
- package/ShadersWGSL/geometry.fragment.js.map +1 -1
- package/ShadersWGSL/openpbr.fragment.d.ts +1 -0
- package/ShadersWGSL/openpbr.fragment.js +5 -3
- package/ShadersWGSL/openpbr.fragment.js.map +1 -1
- package/ShadersWGSL/openpbr.vertex.js +1 -1
- package/ShadersWGSL/openpbr.vertex.js.map +1 -1
- package/ShadersWGSL/pbr.fragment.d.ts +1 -0
- package/ShadersWGSL/pbr.fragment.js +24 -22
- package/ShadersWGSL/pbr.fragment.js.map +1 -1
- package/ShadersWGSL/textureProcessor.fragment.d.ts +5 -0
- package/ShadersWGSL/textureProcessor.fragment.js +161 -0
- package/ShadersWGSL/textureProcessor.fragment.js.map +1 -0
- package/SmartAssets/index.d.ts +2 -0
- package/SmartAssets/index.js +2 -0
- package/SmartAssets/index.js.map +1 -0
- package/SmartAssets/smartAssetManager.d.ts +156 -0
- package/SmartAssets/smartAssetManager.js +531 -0
- package/SmartAssets/smartAssetManager.js.map +1 -0
- package/SmartAssets/smartAssetSerializer.d.ts +61 -0
- package/SmartAssets/smartAssetSerializer.js +97 -0
- package/SmartAssets/smartAssetSerializer.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- 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
|