11import { Vector3 , Quaternion , Matrix , TmpVectors } from "../../Maths/math.vector" ;
22import type { Scene } from "../../scene" ;
33import type { DeepImmutableObject } from "../../types" ;
4- import type { PhysicsBody } from "./physicsBody" ;
4+ import { PhysicsBody } from "./physicsBody" ;
55import { PhysicsShapeCapsule , type PhysicsShape } from "./physicsShape" ;
66import { PhysicsMotionType } from "./IPhysicsEnginePlugin" ;
77import type { HavokPlugin } from "./Plugins/havokPlugin" ;
88import { BuildArray } from "core/Misc/arrayTools" ;
9+ import { TransformNode } from "../../Meshes/transformNode" ;
10+ import { Observable } from "../../Misc/observable" ;
911
1012/**
1113 * Shape properties for the character controller
@@ -25,6 +27,29 @@ export interface CharacterShapeOptions {
2527 */
2628 capsuleRadius ?: number ;
2729}
30+
31+ /**
32+ * Collision event data for the character controller
33+ */
34+ export interface ICharacterControllerCollisionEvent {
35+ /**
36+ * The collider physics body
37+ */
38+ collider : PhysicsBody ;
39+ /**
40+ *
41+ */
42+ colliderIndex : number ;
43+ /**
44+ * Separation force applied to the collider
45+ */
46+ impulse : Vector3 ;
47+ /**
48+ * Position where the impulse is applied
49+ */
50+ impulsePosition : Vector3 ;
51+ }
52+
2853/**
2954 * State of the character on the surface
3055 */
@@ -192,14 +217,16 @@ export class PhysicsCharacterController {
192217 private _velocity : Vector3 ;
193218 private _lastVelocity : Vector3 ;
194219 private _shape : PhysicsShape ;
220+ private _body : PhysicsBody ;
221+ private _transformNode : TransformNode ;
195222 private _ownShape : boolean ;
196223 private _manifold : IContact [ ] = [ ] ;
197224 private _lastDisplacement : Vector3 ;
198225 private _contactAngleSensitivity = 10.0 ;
199226 private _lastInvDeltaTime : number ;
200227 private _scene : Scene ;
201228 private _tmpMatrix = new Matrix ( ) ;
202- private _tmpVecs : Vector3 [ ] = BuildArray ( 31 , Vector3 . Zero ) ;
229+ private _tmpVecs : Vector3 [ ] = BuildArray ( 32 , Vector3 . Zero ) ;
203230
204231 /**
205232 * minimum distance to make contact
@@ -267,9 +294,19 @@ export class PhysicsCharacterController {
267294 * default 0
268295 */
269296 public characterMass = 0 ;
297+
298+ /**
299+ * Observable for trigger entered and trigger exited events
300+ */
301+ public onTriggerCollisionObservable = new Observable < ICharacterControllerCollisionEvent > ( ) ;
302+
270303 private _startCollector ;
271304 private _castCollector ;
272305
306+ // If the difference between the cast displacement and the simplex solver output position is less than this
307+ // value (per component), do not do a second cast to check if it's possible to reach the output position.
308+ private _displacementEps = 1e-4 ;
309+
273310 /**
274311 * instanciate a new characterController
275312 * @param position Initial position
@@ -286,6 +323,12 @@ export class PhysicsCharacterController {
286323 this . _tmpVecs [ 1 ] . set ( 0 , - h * 0.5 + r , 0 ) ;
287324 this . _ownShape = ! characterShapeOptions . shape ;
288325 this . _shape = characterShapeOptions . shape ?? new PhysicsShapeCapsule ( this . _tmpVecs [ 0 ] , this . _tmpVecs [ 1 ] , r , scene ) ;
326+ this . _transformNode = new TransformNode ( "CCTransformNode" , scene ) ;
327+ this . _transformNode . position . copyFrom ( this . _position ) ;
328+ this . _body = new PhysicsBody ( this . _transformNode , PhysicsMotionType . ANIMATED , false , scene ) ;
329+ this . _body . setMassProperties ( { inertia : Vector3 . ZeroReadOnly } ) ;
330+ this . _body . shape = this . _shape ;
331+ this . _body . disablePreStep = false ;
289332 this . _lastInvDeltaTime = 1.0 / 60.0 ;
290333 this . _lastDisplacement = Vector3 . Zero ( ) ;
291334 this . _scene = scene ;
@@ -297,6 +340,22 @@ export class PhysicsCharacterController {
297340 this . _castCollector = hknp . HP_QueryCollector_Create ( 16 ) [ 1 ] ;
298341 }
299342
343+ /**
344+ * Dispose the character controller
345+ */
346+ public dispose ( ) {
347+ if ( this . _ownShape ) {
348+ this . _shape . dispose ( ) ;
349+ }
350+ this . _body . dispose ( ) ;
351+ this . _transformNode . dispose ( ) ;
352+
353+ const hk = this . _scene . getPhysicsEngine ( ) ! . getPhysicsPlugin ( ) as HavokPlugin ;
354+ const hknp = hk . _hknp ;
355+ hknp . HP_QueryCollector_Release ( this . _startCollector ) ;
356+ hknp . HP_QueryCollector_Release ( this . _castCollector ) ;
357+ }
358+
300359 /**
301360 * Get shape used for collision
302361 */
@@ -308,6 +367,7 @@ export class PhysicsCharacterController {
308367 * Set shape used for collision
309368 */
310369 public set shape ( value : PhysicsShape ) {
370+ this . _body . shape = this . _shape ;
311371 if ( this . _ownShape ) {
312372 this . _shape . dispose ( ) ;
313373 }
@@ -329,6 +389,7 @@ export class PhysicsCharacterController {
329389 */
330390 public setPosition ( position : Vector3 ) {
331391 this . _position . copyFrom ( position ) ;
392+ this . _transformNode . position . copyFrom ( this . _position ) ;
332393 }
333394
334395 /**
@@ -496,7 +557,10 @@ export class PhysicsCharacterController {
496557 return numHitBodies ;
497558 }
498559
499- protected _createSurfaceConstraint ( contact : IContact , timeTravelled : number ) : ISurfaceConstraintInfo {
560+ // Store previous positions per body for velocity calculation
561+ protected _bodyPositionTracking = new Map ( ) ;
562+
563+ protected _createSurfaceConstraint ( dt : number , contact : IContact , timeTravelled : number ) : ISurfaceConstraintInfo {
500564 const constraint = {
501565 //let distance = contact.distance - this.keepDistance;
502566 planeNormal : contact . normal . clone ( ) ,
@@ -536,7 +600,49 @@ export class PhysicsCharacterController {
536600 if ( motionType == PhysicsMotionType . STATIC ) {
537601 constraint . priority = 2 ;
538602 } else if ( motionType == PhysicsMotionType . ANIMATED ) {
539- constraint . priority = 1 ;
603+ const bodyTransformNode = contact . bodyB . body . transformNode ;
604+ const bodyId = bodyTransformNode . uniqueId ;
605+
606+ // Retrieve tracking data
607+ let tracking = this . _bodyPositionTracking . get ( bodyId ) ;
608+
609+ const currentFrameWorldMatrix = contact . bodyB . body . transformNode . getWorldMatrix ( ) ;
610+ const frameId = this . _scene . getFrameId ( ) ;
611+
612+ if ( ! tracking ) {
613+ // Initialize tracking
614+ tracking = {
615+ prevWorldMatrix : currentFrameWorldMatrix . clone ( ) ,
616+ frameId : frameId ,
617+ } ;
618+ this . _bodyPositionTracking . set ( bodyId , tracking ) ;
619+ } else {
620+ // Only calculate velocity if this contact existed in the previous frame
621+ // This avoids huge delta spikes when first making contact or after gaps
622+ if ( tracking . frameId + 1 === frameId ) {
623+ const previousFrameWorldMatrix = tracking . prevWorldMatrix ;
624+
625+ const currentFrameWorldMatrixInverse = TmpVectors . Matrix [ 1 ] ;
626+
627+ currentFrameWorldMatrix . invertToRef ( currentFrameWorldMatrixInverse ) ;
628+
629+ const characterPosition = this . getPosition ( ) ;
630+ // compute characterPosition in body local space at previous frame
631+ const characterLocalPosition = this . _tmpVecs [ 21 ] ;
632+ Vector3 . TransformCoordinatesToRef ( characterPosition , currentFrameWorldMatrixInverse , characterLocalPosition ) ;
633+
634+ const characterWorldPosition = this . _tmpVecs [ 22 ] ;
635+ Vector3 . TransformCoordinatesToRef ( characterLocalPosition , previousFrameWorldMatrix , characterWorldPosition ) ;
636+ const playerDeltaPosition = this . _tmpVecs [ 23 ] ;
637+ characterPosition . subtractToRef ( characterWorldPosition , playerDeltaPosition ) ;
638+
639+ constraint . velocity . copyFrom ( playerDeltaPosition ) ;
640+ constraint . velocity . scaleInPlace ( 1 / dt ) ;
641+ constraint . priority = 1 ;
642+ }
643+ tracking . prevWorldMatrix . copyFrom ( currentFrameWorldMatrix ) ;
644+ tracking . frameId = frameId ;
645+ }
540646 }
541647
542648 return constraint ;
@@ -585,7 +691,7 @@ export class PhysicsCharacterController {
585691 protected _createConstraintsFromManifold ( dt : number , timeTravelled : number ) : ISurfaceConstraintInfo [ ] {
586692 const constraints = [ ] ;
587693 for ( let i = 0 ; i < this . _manifold . length ; i ++ ) {
588- const surfaceConstraint = this . _createSurfaceConstraint ( this . _manifold [ i ] , timeTravelled ) ;
694+ const surfaceConstraint = this . _createSurfaceConstraint ( dt , this . _manifold [ i ] , timeTravelled ) ;
589695 constraints . push ( surfaceConstraint ) ;
590696 this . _addMaxSlopePlane ( this . maxSlopeCosine , this . up , i , constraints , this . _manifold [ i ] . allowedPenetration ) ;
591697 this . _resolveConstraintPenetration ( surfaceConstraint , this . penetrationRecoverySpeed ) ;
@@ -1257,7 +1363,7 @@ export class PhysicsCharacterController {
12571363 orientation ,
12581364 this . keepDistance + this . keepContactTolerance , // max distance
12591365 false , // should hit triggers
1260- [ BigInt ( 0 ) ] , // body to ignore //<todo allow for a proxy body!
1366+ [ this . _body . _pluginData . hpBodyId [ 0 ] ] ,
12611367 ] ;
12621368 hknp . HP_World_ShapeProximityWithCollector ( hk . world , startCollector , query ) ;
12631369 }
@@ -1269,7 +1375,7 @@ export class PhysicsCharacterController {
12691375 startNative ,
12701376 [ endPos . x , endPos . y , endPos . z ] ,
12711377 false , // should hit triggers
1272- [ BigInt ( 0 ) ] , // body to ignore //<todo allow for proxy body
1378+ [ this . _body . _pluginData . hpBodyId [ 0 ] ] ,
12731379 ] ;
12741380 hknp . HP_World_ShapeCastWithCollector ( hk . world , castCollector , query ) ;
12751381 }
@@ -1356,6 +1462,14 @@ export class PhysicsCharacterController {
13561462
13571463 //<todo Fire callback to allow user to change impulse + use the info / play sounds
13581464
1465+ const triggerCollisionInfo : ICharacterControllerCollisionEvent = {
1466+ collider : bodyB . body ,
1467+ colliderIndex : bodyB . index ,
1468+ impulse : outputObjectImpulse ,
1469+ impulsePosition : outputImpulsePosition ,
1470+ } ;
1471+ this . onTriggerCollisionObservable . notifyObservers ( triggerCollisionInfo ) ;
1472+
13591473 bodyB . body . applyImpulse ( outputObjectImpulse , outputImpulsePosition , bodyB . index ) ;
13601474 }
13611475 }
@@ -1388,49 +1502,12 @@ export class PhysicsCharacterController {
13881502 return 1 / body . body . getMassProperties ( body . index ) . mass ! ;
13891503 }
13901504
1391- /**
1392- * Update internal state. Must be called once per frame
1393- * @param deltaTime frame delta time in seconds. When using scene.deltaTime divide by 1000.0
1394- * @param surfaceInfo surface information returned by checkSupport
1395- * @param gravity gravity applied to the character. Can be different that world gravity
1396- */
1397- public integrate ( deltaTime : number , surfaceInfo : CharacterSurfaceInfo , gravity : Vector3 ) {
1505+ protected _integrateManifolds ( deltaTime : number , gravity : Vector3 ) : void {
13981506 const hk = this . _scene . getPhysicsEngine ( ) ! . getPhysicsPlugin ( ) as HavokPlugin ;
1399-
1400- const invDeltaTime = 1 / deltaTime ;
1401- let remainingTime = deltaTime ;
1402- let newVelocity = Vector3 . Zero ( ) ;
1403-
1404- // If the difference between the cast displacement and the simplex solver output position is less than this
1405- // value (per component), do not do a second cast to check if it's possible to reach the output position.
1406- const displacementEps = 1e-4 ;
14071507 const epsSqrd = 1e-8 ;
14081508
1409- // Choose the first cast direction. If velocity hasn't changed from the previous integrate, guess that the
1410- // displacement will be the same as last integrate, scaled by relative step length. Otherwise, guess based
1411- // on current velocity.
1412- {
1413- const tolerance = displacementEps * invDeltaTime ;
1414- if ( this . _velocity . equalsWithEpsilon ( this . _lastVelocity , tolerance ) ) {
1415- this . _lastDisplacement . scaleInPlace ( remainingTime * this . _lastInvDeltaTime ) ;
1416- } else {
1417- const displacementVelocity = this . _velocity ;
1418- if ( surfaceInfo . supportedState == CharacterSupportedState . SUPPORTED ) {
1419- const relativeVelocity = this . _tmpVecs [ 28 ] ;
1420- this . _velocity . subtractToRef ( surfaceInfo . averageSurfaceVelocity , relativeVelocity ) ;
1421- const normalDotVelocity = surfaceInfo . averageSurfaceNormal . dot ( relativeVelocity ) ;
1422- if ( normalDotVelocity < 0 ) {
1423- relativeVelocity . subtractInPlace ( surfaceInfo . averageSurfaceNormal . scale ( normalDotVelocity ) ) ;
1424- displacementVelocity . copyFrom ( relativeVelocity ) ;
1425- displacementVelocity . addInPlace ( surfaceInfo . averageSurfaceVelocity ) ;
1426- }
1427- }
1428- this . _lastDisplacement . copyFrom ( displacementVelocity ) ;
1429- this . _lastDisplacement . scaleInPlace ( remainingTime ) ;
1430- }
1431- this . _lastVelocity . copyFrom ( this . _velocity ) ;
1432- this . _lastInvDeltaTime = invDeltaTime ;
1433- }
1509+ let newVelocity = Vector3 . Zero ( ) ;
1510+ let remainingTime = deltaTime ;
14341511
14351512 // Make sure that contact with bodies that have been removed since the call to checkSupport() are removed from the
14361513 // manifold
@@ -1458,7 +1535,7 @@ export class PhysicsCharacterController {
14581535 // If castCollector had hits on different bodies (so we're not sure if some non-closest body could be in our way) OR
14591536 // the simplex has given an output direction different from the cast guess
14601537 // we re-cast to check we can move there. There is no need to get the start points again.
1461- if ( updateResult != 0 || ( newDisplacement . lengthSquared ( ) > epsSqrd && ! this . _lastDisplacement . equalsWithEpsilon ( newDisplacement , displacementEps ) ) ) {
1538+ if ( updateResult != 0 || ( newDisplacement . lengthSquared ( ) > epsSqrd && ! this . _lastDisplacement . equalsWithEpsilon ( newDisplacement , this . _displacementEps ) ) ) {
14621539 this . _castWithCollectors ( this . _position , this . _position . add ( newDisplacement ) , this . _castCollector , this . _startCollector ) ;
14631540 const hknp = hk . _hknp ;
14641541 const numCastHits = hknp . HP_QueryCollector_GetNumHits ( this . _castCollector ) [ 1 ] ;
@@ -1500,6 +1577,66 @@ export class PhysicsCharacterController {
15001577 }
15011578
15021579 this . _velocity . copyFrom ( newVelocity ) ;
1580+ this . _transformNode . position . copyFrom ( this . _position ) ;
1581+ }
1582+
1583+ /**
1584+ * Move the character with collisions
1585+ * @param displacement defines the requested displacement vector
1586+ */
1587+ public moveWithCollisions ( displacement : Vector3 ) : void {
1588+ if ( this . _scene . deltaTime == undefined ) {
1589+ return ;
1590+ }
1591+ const deltaTime = this . _scene . deltaTime / 1000.0 ;
1592+ const invDeltaTime = 1 / deltaTime ;
1593+
1594+ displacement . scaleToRef ( 1 / deltaTime , this . _velocity ) ;
1595+ this . _lastDisplacement . copyFrom ( displacement ) ;
1596+
1597+ this . _lastVelocity . copyFrom ( this . _velocity ) ;
1598+ this . _lastInvDeltaTime = invDeltaTime ;
1599+
1600+ this . _integrateManifolds ( deltaTime , Vector3 . ZeroReadOnly ) ;
1601+ }
1602+
1603+ /**
1604+ * Update internal state. Must be called once per frame
1605+ * @param deltaTime frame delta time in seconds. When using scene.deltaTime divide by 1000.0
1606+ * @param surfaceInfo surface information returned by checkSupport
1607+ * @param gravity gravity applied to the character. Can be different that world gravity
1608+ */
1609+ public integrate ( deltaTime : number , surfaceInfo : CharacterSurfaceInfo , gravity : Vector3 ) {
1610+ const invDeltaTime = 1 / deltaTime ;
1611+ const remainingTime = deltaTime ;
1612+
1613+ // Choose the first cast direction. If velocity hasn't changed from the previous integrate, guess that the
1614+ // displacement will be the same as last integrate, scaled by relative step length. Otherwise, guess based
1615+ // on current velocity.
1616+ {
1617+ const tolerance = this . _displacementEps * invDeltaTime ;
1618+ if ( this . _velocity . equalsWithEpsilon ( this . _lastVelocity , tolerance ) ) {
1619+ this . _lastDisplacement . scaleInPlace ( remainingTime * this . _lastInvDeltaTime ) ;
1620+ } else {
1621+ const displacementVelocity = this . _velocity ;
1622+ if ( surfaceInfo . supportedState == CharacterSupportedState . SUPPORTED ) {
1623+ const relativeVelocity = this . _tmpVecs [ 28 ] ;
1624+ this . _velocity . subtractToRef ( surfaceInfo . averageSurfaceVelocity , relativeVelocity ) ;
1625+ const normalDotVelocity = surfaceInfo . averageSurfaceNormal . dot ( relativeVelocity ) ;
1626+ if ( normalDotVelocity < 0 ) {
1627+ relativeVelocity . subtractInPlace ( surfaceInfo . averageSurfaceNormal . scale ( normalDotVelocity ) ) ;
1628+ displacementVelocity . copyFrom ( relativeVelocity ) ;
1629+ displacementVelocity . addInPlace ( surfaceInfo . averageSurfaceVelocity ) ;
1630+ }
1631+ }
1632+ this . _lastDisplacement . copyFrom ( displacementVelocity ) ;
1633+ this . _lastDisplacement . scaleInPlace ( remainingTime ) ;
1634+ }
1635+ this . _lastVelocity . copyFrom ( this . _velocity ) ;
1636+ this . _lastInvDeltaTime = invDeltaTime ;
1637+ }
1638+
1639+ this . _integrateManifolds ( deltaTime , gravity ) ;
15031640 }
15041641
15051642 /**
0 commit comments