Physics Character Controller Shape Stuck

Yo @Cedric … I have implemented a Unity Style character controller built with BABYLON.PhysicsCharacterController

It works pretty great… However i am encountering sometime, the capsule shape for my character can sometimes get stuck in corners or edges of walls:

Are there any settings to control the physics shape interaction with wall colliders ?

(Cedric is oof for vacations so please bear with us for a little while ;))

1 Like

Hi @MackeyK24 ! I’m back!
Do you have a repro? I’ve never seen such behavior before. Anything weird with Physics debug viewer? Maybe some bad geometry leading to not visible collision shape.

Hard to repo pn playground, is an export of the unity starter asset level that i used many times before which my own rigidbody based character controller which works smoothly, you can brush up walls and edegs just fine:

But using the the new BABYLON.PhysicsCharacterController on same exact physics enabled scene gets stuck alot. I am just passing radius info and the controller is actualling creating the shape that is getting used.

I will try and SOMEHOW get a test character controller with new havok version setup in the playground

The documentation page for Character Controller provides a PG with a scene featuring varied geometry situation.
If you know the type of geometry/topology that is a problem, maybe you can find a repro location.

Hmmmm… This playground does not seem to get stuck using the playground character and my Unity exported level:

But my version does:

    export enum CharacterState {
        IN_AIR = 'IN_AIR',
        ON_GROUND = 'ON_GROUND',
        IS_SLIDING = 'IS_SLIDING',
        START_JUMP = 'START_JUMP',
    }
    export class CharacterControllerState {
        public state: TOOLKIT.CharacterState = TOOLKIT.CharacterState.ON_GROUND;
        public easing:number = 0.8;
        public wantJump:boolean = false;
        public isJumping:boolean = false;
        public jumpForce:number = 9.81;
        public jumpHeight:number = 1.0;
        public moveVelocity:BABYLON.Vector3 = new BABYLON.Vector3(0, 0, 0);
        public inAirVelocity:BABYLON.Vector3 = new BABYLON.Vector3(0, 0, 0);
        public forwardLocalSpace:BABYLON.Vector3 = new BABYLON.Vector3(0, 0, 1);
        public characterGravity:BABYLON.Vector3 = new BABYLON.Vector3(0, -29.43, 0);
        public characterOrientation:BABYLON.Quaternion = BABYLON.Quaternion.Identity();
        public maxAirControlFactor:number = 1.0;
        public minAirControlFactor:number = 0.5;
        public noAirControlFactor:number = 1.0;
        public enableInAirControl:boolean = false;
        public rawInputMagnatude:number = 0;
        public getNextState:(supportInfo: BABYLON.CharacterSurfaceInfo) => TOOLKIT.CharacterState = null;
        public getDesiredVelocity:(deltaTime: number, supportInfo: BABYLON.CharacterSurfaceInfo, characterOrientation: BABYLON.Quaternion, currentVelocity: BABYLON.Vector3) => BABYLON.Vector3 = null;

        protected characterController: BABYLON.PhysicsCharacterController = null;
        public constructor(characterController: BABYLON.PhysicsCharacterController) {
            this.characterController = characterController
            this.getNextState = this.defaultGetNextState.bind(this);
            this.getDesiredVelocity = this.defaultGetDesiredVelocity.bind(this);
        }

        // TODO: SUPPORT IS SLIDING STATE

        protected defaultGetNextState(supportInfo: BABYLON.CharacterSurfaceInfo): TOOLKIT.CharacterState {
            if (this.state == TOOLKIT.CharacterState.IN_AIR) {
                if (supportInfo.supportedState == BABYLON.CharacterSupportedState.SUPPORTED) {
                    return TOOLKIT.CharacterState.ON_GROUND;
                }
                if (this.wantJump == true) {
                    this.wantJump = false;
                    this.isJumping = true;
                    return TOOLKIT.CharacterState.START_JUMP;
                }
                return TOOLKIT.CharacterState.IN_AIR;
            } else if (this.state == TOOLKIT.CharacterState.ON_GROUND || this.state == TOOLKIT.CharacterState.IS_SLIDING) {
                if (supportInfo.supportedState == BABYLON.CharacterSupportedState.UNSUPPORTED) {
                   return TOOLKIT.CharacterState.IN_AIR;
                }
                if (this.wantJump == true) {
                    this.wantJump = false;
                    this.isJumping = true;
                    return TOOLKIT.CharacterState.START_JUMP;
                }
                return TOOLKIT.CharacterState.ON_GROUND;
            } else if (this.state == TOOLKIT.CharacterState.START_JUMP) {
                return TOOLKIT.CharacterState.IN_AIR;
            }
        }

        // TODO: FIX WITH TO REFS

        protected defaultGetDesiredVelocity(deltaTime: number, supportInfo: BABYLON.CharacterSurfaceInfo, characterOrientation: BABYLON.Quaternion, currentVelocity: BABYLON.Vector3): BABYLON.Vector3 {
            let nextState = this.getNextState(supportInfo);
            if (nextState != this.state) {
                this.state = nextState;
            }
            let upWorld = this.characterGravity.normalizeToNew();
            upWorld.scaleInPlace(-1.0);
            let forwardWorld = this.forwardLocalSpace.applyRotationQuaternion(characterOrientation);
            if (this.state == TOOLKIT.CharacterState.IN_AIR) {
                let currentAirControlFactor = 1.0;
                if (this.isJumping === true) {
                    currentAirControlFactor = this.noAirControlFactor;
                    if (this.enableInAirControl === true) {
                        if (this.rawInputMagnatude > 0) {
                            currentAirControlFactor = Math.max((this.maxAirControlFactor * this.rawInputMagnatude), this.minAirControlFactor);
                        } else {
                            currentAirControlFactor = this.minAirControlFactor;
                        } 
                    }
                }
                this.moveVelocity.scaleToRef(currentAirControlFactor, this.inAirVelocity); // Note: Scale In Air Velocity By Air Control Factor
                let desiredAirVelocity = this.inAirVelocity.applyRotationQuaternion(characterOrientation);
                let outputAirVelocity = this.characterController.calculateMovement(deltaTime, forwardWorld, upWorld, currentVelocity, BABYLON.Vector3.ZeroReadOnly, desiredAirVelocity, upWorld);
                outputAirVelocity.addInPlace(upWorld.scale(-outputAirVelocity.dot(upWorld)));
                outputAirVelocity.addInPlace(upWorld.scale(currentVelocity.dot(upWorld)));
                outputAirVelocity.addInPlace(this.characterGravity.scale(deltaTime));
                return outputAirVelocity;
            } else if (this.state == TOOLKIT.CharacterState.ON_GROUND || this.state == TOOLKIT.CharacterState.IS_SLIDING) {
                this.isJumping = false;
                if (this.moveVelocity.lengthSquared() <= 0 && this.easing > 0) return BABYLON.Vector3.Lerp(this.characterController.getVelocity(), BABYLON.Vector3.Zero(), this.easing);
                let desiredMoveVelocity = this.moveVelocity.applyRotationQuaternion(characterOrientation);
                let outputMoveVelocity = this.characterController.calculateMovement(deltaTime, forwardWorld, supportInfo.averageSurfaceNormal, currentVelocity, supportInfo.averageSurfaceVelocity, desiredMoveVelocity, upWorld);
                outputMoveVelocity.subtractInPlace(supportInfo.averageSurfaceVelocity);
                let inv1k = 1e-3;
                if (outputMoveVelocity.dot(upWorld) > inv1k) {
                    let velLen = outputMoveVelocity.length();
                    outputMoveVelocity.normalizeFromLength(velLen);
                    let horizLen = velLen / supportInfo.averageSurfaceNormal.dot(upWorld);
                    let c = supportInfo.averageSurfaceNormal.cross(outputMoveVelocity);
                    outputMoveVelocity = c.cross(upWorld);
                    outputMoveVelocity.scaleInPlace(horizLen);
                }
                outputMoveVelocity.addInPlace(supportInfo.averageSurfaceVelocity);
                return outputMoveVelocity;
            } else if (this.state == TOOLKIT.CharacterState.START_JUMP) {
                this.isJumping = true;
                let u = Math.sqrt(this.jumpForce * this.jumpHeight);
                let curRelVel = currentVelocity.dot(upWorld);
                return currentVelocity.add(upWorld.scale(u - curRelVel));
            }
            return BABYLON.Vector3.Zero();
        }
    }