Skip to content

Commit

Permalink
Merge pull request #3422 from webaverse/Vis/VelocityFrameRatePr
Browse files Browse the repository at this point in the history
Fix wrong physical speed and animations jitter in low fps issues ( Make them "frame rate independent" ).
  • Loading branch information
Avaer Kazmer authored Aug 12, 2022
2 parents e4065a5 + 913dcb4 commit a983336
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 91 deletions.
8 changes: 1 addition & 7 deletions avatars/animationHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,14 @@ import {
// getNextPhysicsId,
} from '../util.js';

import {
// idleFactorSpeed,
// walkFactorSpeed,
// runFactorSpeed,
narutoRunTimeFactor,
} from './constants.js';

import {
crouchMaxTime,
// useMaxTime,
aimMaxTime,
// avatarInterpolationFrameRate,
// avatarInterpolationTimeDelay,
// avatarInterpolationNumFrames,
narutoRunTimeFactor,
} from '../constants.js';

const localVector = new Vector3();
Expand Down
45 changes: 21 additions & 24 deletions avatars/avatars.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import {
// useMaxTime,
aimMaxTime,
aimTransitionMaxTime,
idleSpeed,
walkSpeed,
runSpeed,
// avatarInterpolationFrameRate,
// avatarInterpolationTimeDelay,
// avatarInterpolationNumFrames,
Expand All @@ -27,12 +30,6 @@ import {
import * as avatarCruncher from '../avatar-cruncher.js';
import * as avatarSpriter from '../avatar-spriter.js';
// import * as sceneCruncher from '../scene-cruncher.js';
import {
idleFactorSpeed,
walkFactorSpeed,
runFactorSpeed,
// narutoRunTimeFactor,
} from './constants.js';
import {
getSkinnedMeshes,
getSkeleton,
Expand Down Expand Up @@ -449,8 +446,6 @@ class Avatar {
this.vrmExtension = object?.parser?.json?.extensions?.VRM;
this.firstPersonCurves = getFirstPersonCurves(this.vrmExtension);

this.lastVelocity = new THREE.Vector3();

const {
skinnedMeshes,
skeleton,
Expand Down Expand Up @@ -978,6 +973,7 @@ class Avatar {
this.sprintFactor = 0;
this.lastPosition = new THREE.Vector3();
this.velocity = new THREE.Vector3();
// this.testVelocity = new THREE.Vector3();
this.lastMoveTime = 0;
this.lastEmoteTime = 0;
this.lastEmoteAnimation = 0;
Expand Down Expand Up @@ -1501,12 +1497,12 @@ class Avatar {
const positionDiff = localVector.copy(lastPosition)
.sub(currentPosition)
.divideScalar(Math.max(timeDiffS, 0.001))
.multiplyScalar(0.1);
// .multiplyScalar(0.1);
localEuler.setFromQuaternion(currentQuaternion, 'YXZ');
localEuler.set(0, -(localEuler.y + Math.PI), 0);
positionDiff.applyEuler(localEuler);
this.velocity.copy(positionDiff);
this.lastVelocity.copy(this.velocity);
// this.testVelocity.copy(positionDiff); // For testing only, check if the physics.velocity correct. Can't use this in formal, to calc such as idleWalkFactor/walkRunFactor, will cause aniamtions jitter in low fps.
// this.testVelocity.y *= -1;
this.direction.copy(positionDiff).normalize();
this.lastPosition.copy(currentPosition);

Expand All @@ -1518,11 +1514,22 @@ class Avatar {
update(timestamp, timeDiff) {
const now = timestamp;
const timeDiffS = timeDiff / 1000;

// for the local player we want to update the velocity immediately
// on remote players this is called from the RemotePlayer -> observePlayerFn
if (this.isLocalPlayer) {
this.setVelocity(
timeDiffS,
this.lastPosition,
this.inputs.hmd.position,
this.inputs.hmd.quaternion
);
}

const currentSpeed = localVector.set(this.velocity.x, 0, this.velocity.z).length();

this.idleWalkFactor = Math.min(Math.max((currentSpeed - idleFactorSpeed) / (walkFactorSpeed - idleFactorSpeed), 0), 1);
this.walkRunFactor = Math.min(Math.max((currentSpeed - walkFactorSpeed) / (runFactorSpeed - walkFactorSpeed), 0), 1);
this.idleWalkFactor = Math.min(Math.max((currentSpeed - idleSpeed) / (walkSpeed - idleSpeed), 0), 1);
this.walkRunFactor = Math.min(Math.max((currentSpeed - walkSpeed) / (runSpeed - walkSpeed), 0), 1);
this.crouchFactor = Math.min(Math.max(1 - (this.crouchTime / crouchMaxTime), 0), 1);
// console.log('current speed', currentSpeed, idleWalkFactor, walkRunFactor);
this.aimRightFactor = this.aimRightTransitionTime / aimTransitionMaxTime;
Expand Down Expand Up @@ -1926,17 +1933,7 @@ class Avatar {
if (this.getTopEnabled() || this.getHandEnabled(0) || this.getHandEnabled(1)) {
_motionControls.call(this)
}

// for the local player we want to update the velocity immediately
// on remote players this is called from the RemotePlayer -> observePlayerFn
if (this.isLocalPlayer) {
this.setVelocity(
timeDiffS,
this.lastPosition,
this.inputs.hmd.position,
this.inputs.hmd.quaternion
);
}

_applyAnimation(this, now);

if (this.poseAnimation) {
Expand Down
4 changes: 0 additions & 4 deletions avatars/constants.js
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
export const idleFactorSpeed = 0;
export const walkFactorSpeed = 0.25;
export const runFactorSpeed = 0.7;
export const narutoRunTimeFactor = 2;
86 changes: 66 additions & 20 deletions character-physics.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const localQuaternion = new THREE.Quaternion();
const localQuaternion2 = new THREE.Quaternion();
// const localEuler = new THREE.Euler();
const localMatrix = new THREE.Matrix4();
const localVector2D = new THREE.Vector2();
const localVector2D2 = new THREE.Vector2();
const localVector2D3 = new THREE.Vector2();

// const localOffset = new THREE.Vector3();
// const localOffset2 = new THREE.Vector3();
Expand All @@ -41,11 +44,20 @@ class CharacterPhysics {
constructor(player) {
this.player = player;

this.targetVelocity = new THREE.Vector3();
this.lastTargetVelocity = new THREE.Vector3();
this.wantVelocity = new THREE.Vector3();
this.velocity = new THREE.Vector3();
this.targetMoveDistancePerFrame = new THREE.Vector3();
this.lastTargetMoveDistancePerFrame = new THREE.Vector3();
this.wantMoveDistancePerFrame = new THREE.Vector3();
this.lastGrounded = null;
this.lastGroundedTime = 0;
this.lastCharacterControllerY = null;
this.sitOffset = new THREE.Vector3();
this.lastFallLoopAction = false;
this.fallLoopStartTimeS = 0;
this.lastGravityH = 0;

this.lastPistolUse = false;
this.lastPistolUseStartTime = -Infinity;
Expand All @@ -56,23 +68,37 @@ class CharacterPhysics {
physicsScene.setCharacterControllerPosition(this.player.characterController, localVector);
}
/* apply the currently held keys to the character */
applyWasd(keysDirection) {
applyWasd(velocity, timeDiff) {
if (this.player.avatar) {
this.velocity.add(keysDirection);
this.targetVelocity.copy(velocity);
this.targetMoveDistancePerFrame.copy(this.targetVelocity).multiplyScalar(timeDiff / 1000);
}
}
applyGravity(timeDiffS) {
applyGravity(nowS) {
// if (this.player) {
if ((this.player.hasAction('jump') || this.player.hasAction('fallLoop')) && !this.player.hasAction('fly') && !this.player.hasAction('swim')) {
localVector.copy(physicsScene.getGravity())
.multiplyScalar(timeDiffS);
this.velocity.add(localVector);
const fallLoopAction = this.player.getAction('fallLoop');
if (fallLoopAction) {
if (!this.lastFallLoopAction) {
this.fallLoopStartTimeS = nowS;
this.lastGravityH = 0;
if (fallLoopAction.from === 'jump') {
const aestheticJumpBias = 1;
const t = flatGroundJumpAirTime / 1000 / 2 + aestheticJumpBias;
this.fallLoopStartTimeS -= t;
this.lastGravityH = 0.5 * physicsScene.getGravity().y * t * t;
}
}
const t = nowS - this.fallLoopStartTimeS;
const h = 0.5 * physicsScene.getGravity().y * t * t;
this.wantMoveDistancePerFrame.y = h - this.lastGravityH;

this.lastGravityH = h;
}
this.lastFallLoopAction = fallLoopAction;
// }
}
updateVelocity(timeDiffS) {
const timeDiff = timeDiffS * 1000;
this.applyVelocityDamping(this.velocity, timeDiff);
this.applyVelocityDamping(this.velocity, timeDiffS);
}
applyAvatarPhysicsDetail(
velocityAvatarDirection,
Expand All @@ -84,9 +110,9 @@ class CharacterPhysics {
// console.log('apply avatar physics', this.player);
// move character controller
const minDist = 0;
localVector3.copy(this.velocity)
.multiplyScalar(timeDiffS);
localVector3.copy(this.wantMoveDistancePerFrame)

// aesthetic jump
const jumpAction = this.player.getAction('jump');
if (jumpAction?.trigger === 'jump') {
const doubleJumpAction = this.player.getAction('doubleJump');
Expand All @@ -111,13 +137,24 @@ class CharacterPhysics {
localVector3.y = 0;
}
}

const positionXZBefore = localVector2D.set(this.player.characterController.position.x, this.player.characterController.position.z);
const flags = physicsScene.moveCharacterController(
this.player.characterController,
localVector3,
minDist,
timeDiffS,
this.player.characterController.position
);
const positionXZAfter = localVector2D2.set(this.player.characterController.position.x, this.player.characterController.position.z);
const wantMoveDistancePerFrameXZ = localVector2D3.set(this.wantMoveDistancePerFrame.x, this.wantMoveDistancePerFrame.z);
const wantMoveDistancePerFrameXZLength = wantMoveDistancePerFrameXZ.length();
if (wantMoveDistancePerFrameXZLength > 0) {
const movedRatio = (positionXZAfter.sub(positionXZBefore).length()) / wantMoveDistancePerFrameXZLength;
this.velocity.copy(this.wantVelocity);
if (movedRatio < 1) this.velocity.multiplyScalar(movedRatio);
}

// const collided = flags !== 0;
let grounded = !!(flags & 0x1);

Expand Down Expand Up @@ -268,19 +305,24 @@ class CharacterPhysics {
}
}
/* dampen the velocity to make physical sense for the current avatar state */
applyVelocityDamping(velocity, timeDiff) {
applyVelocityDamping(velocity, timeDiffS) {
const doDamping = (factor) => {
this.wantMoveDistancePerFrame.x = THREE.MathUtils.damp(this.wantMoveDistancePerFrame.x, this.lastTargetMoveDistancePerFrame.x, factor, timeDiffS);
this.wantMoveDistancePerFrame.z = THREE.MathUtils.damp(this.wantMoveDistancePerFrame.z, this.lastTargetMoveDistancePerFrame.z, factor, timeDiffS);
this.wantMoveDistancePerFrame.y = THREE.MathUtils.damp(this.wantMoveDistancePerFrame.y, this.lastTargetMoveDistancePerFrame.y, factor, timeDiffS);

this.wantVelocity.x = THREE.MathUtils.damp(this.wantVelocity.x, this.lastTargetVelocity.x, factor, timeDiffS);
this.wantVelocity.z = THREE.MathUtils.damp(this.wantVelocity.z, this.lastTargetVelocity.z, factor, timeDiffS);
this.wantVelocity.y = THREE.MathUtils.damp(this.wantVelocity.y, this.lastTargetVelocity.y, factor, timeDiffS);
}
if (this.player.hasAction('fly')) {
const factor = getVelocityDampingFactor(flyFriction, timeDiff);
velocity.multiplyScalar(factor);
doDamping(flyFriction);
}
else if(this.player.hasAction('swim')){
const factor = getVelocityDampingFactor(swimFriction, timeDiff);
velocity.multiplyScalar(factor);
doDamping(swimFriction);
}
else {
const factor = getVelocityDampingFactor(groundFriction, timeDiff);
velocity.x *= factor;
velocity.z *= factor;
doDamping(groundFriction);
}
}
applyAvatarPhysics(now, timeDiffS) {
Expand Down Expand Up @@ -465,10 +507,14 @@ class CharacterPhysics {
_updateBowIkAnimation();
}
update(now, timeDiffS) {
this.applyGravity(timeDiffS);
const nowS = now / 1000;
this.updateVelocity(timeDiffS);
this.applyGravity(nowS);
this.applyAvatarPhysics(now, timeDiffS);
this.applyAvatarActionKinematics(now, timeDiffS);

this.lastTargetVelocity.copy(this.targetVelocity);
this.lastTargetMoveDistancePerFrame.copy(this.targetMoveDistancePerFrame);
}
reset() {
if (this.player.avatar) {
Expand Down
16 changes: 7 additions & 9 deletions character-sfx.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@ import Avatar from './avatars/avatars.js';
import * as sounds from './sounds.js';
import audioManager from './audio-manager.js';

import {
idleFactorSpeed,
walkFactorSpeed,
runFactorSpeed,
narutoRunTimeFactor,
} from './avatars/constants.js';
import {
crouchMaxTime,
eatFrameIndices,
drinkFrameIndices,
idleSpeed,
walkSpeed,
runSpeed,
narutoRunTimeFactor,
} from './constants.js';
import {
mod,
Expand Down Expand Up @@ -110,8 +108,8 @@ class CharacterSfx {
const timeSeconds = timestamp/1000;
const currentSpeed = localVector.set(this.player.avatar.velocity.x, 0, this.player.avatar.velocity.z).length();

const idleWalkFactor = Math.min(Math.max((currentSpeed - idleFactorSpeed) / (walkFactorSpeed - idleFactorSpeed), 0), 1);
const walkRunFactor = Math.min(Math.max((currentSpeed - walkFactorSpeed) / (runFactorSpeed - walkFactorSpeed), 0), 1);
const idleWalkFactor = Math.min(Math.max((currentSpeed - idleSpeed) / (walkSpeed - idleSpeed), 0), 1);
const walkRunFactor = Math.min(Math.max((currentSpeed - walkSpeed) / (runSpeed - walkSpeed), 0), 1);
const crouchFactor = Math.min(Math.max(1 - (this.player.avatar.crouchTime / crouchMaxTime), 0), 1);

const soundFiles = sounds.getSoundFiles();
Expand Down Expand Up @@ -139,7 +137,7 @@ class CharacterSfx {

// step
const _handleStep = () => {
if (idleWalkFactor > 0.7 && !this.player.avatar.jumpState && !this.player.avatar.fallLoopState && !this.player.avatar.flyState && !this.player.hasAction('swim')) {
if (idleWalkFactor > 0.5 && !this.player.avatar.jumpState && !this.player.avatar.fallLoopState && !this.player.avatar.flyState && !this.player.hasAction('swim')) {
const isRunning = walkRunFactor > 0.5;
const isCrouching = crouchFactor > 0.5;
const isNarutoRun = this.player.avatar.narutoRunState;
Expand Down
21 changes: 18 additions & 3 deletions constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ export const metaverseProfileDefinition = `kjzl6cwe1jw145wm7u2sy1wpa33hglvmuy6th

export const audioTimeoutTime = 10 * 1000;

export const idleSpeed = 0;
export const walkSpeed = 2.5;
export const runSpeed = walkSpeed * 3;
export const narutoRunSpeed = walkSpeed * 20;
export const crouchSpeed = walkSpeed * 0.7;
export const flySpeed = walkSpeed * 5;

export const narutoRunTimeFactor = 2;

export const crouchMaxTime = 200;
export const activateMaxTime = 750;
export const useMaxTime = 750;
Expand All @@ -120,10 +129,16 @@ export const minFov = 60;
export const maxFov = 120;
export const midFov = 90;
export const initialPosY = 1.5;
export const groundFriction = 0.28;

// Now friction only affect damping, don't affect full speed moving.
// export const groundFriction = 0.28;
export const groundFriction = 15;
export const airFriction = groundFriction;
export const flyFriction = 0.5;
export const swimFriction = 0.2;
// export const flyFriction = 0.5;
// export const swimFriction = 0.2;
export const flyFriction = groundFriction;
export const swimFriction = groundFriction;

export const aimTransitionMaxTime = 150;

export const jumpHeight = 3;
Expand Down
Loading

0 comments on commit a983336

Please sign in to comment.