@onerjs/core 8.51.4 → 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.
- package/Animations/animatorAvatar.d.ts +2 -0
- package/Animations/animatorAvatar.js +163 -94
- package/Animations/animatorAvatar.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/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/Physics/v2/characterController.d.ts +104 -7
- package/Physics/v2/characterController.js +355 -7
- 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
|
@@ -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
|