astra
This commit is contained in:
parent
c074bc2645
commit
244620105a
9 changed files with 2080 additions and 518 deletions
|
|
@ -12,7 +12,7 @@ export abstract class CommandReceiver {
|
|||
public handleCommand(command: Command) {
|
||||
const commandType = command.constructor.name;
|
||||
|
||||
if (commandType in this.commandExecutors) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.commandExecutors, commandType)) {
|
||||
this.commandExecutors[commandType]!(command);
|
||||
} else {
|
||||
this.defaultCommandExecutor(command);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export class CharacterMovementSnapshot {
|
|||
public readonly rightFootNormal: vec2,
|
||||
public readonly groundPlanetId: Id | null,
|
||||
public readonly secondsSinceOnSurface: number,
|
||||
public readonly leapCooldownRemaining: number = 0,
|
||||
) {}
|
||||
|
||||
public toArray(): Array<any> {
|
||||
|
|
@ -23,6 +24,7 @@ export class CharacterMovementSnapshot {
|
|||
this.rightFootNormal,
|
||||
this.groundPlanetId,
|
||||
this.secondsSinceOnSurface,
|
||||
this.leapCooldownRemaining,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,2 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
export const smoothing = (deltaTimeInSeconds: number, seconds: number): number =>
|
||||
1 - Math.exp(-deltaTimeInSeconds / seconds);
|
||||
|
||||
export const easeVec2 = (
|
||||
out: vec2,
|
||||
target: vec2,
|
||||
deltaTimeInSeconds: number,
|
||||
seconds: number,
|
||||
snapDistance: number,
|
||||
): vec2 =>
|
||||
vec2.distance(out, target) > snapDistance
|
||||
? vec2.copy(out, target)
|
||||
: vec2.lerp(out, out, target, smoothing(deltaTimeInSeconds, seconds));
|
||||
|
||||
// Follows a *moving* target. `easeVec2` alone lags one by an amount that depends on the frame
|
||||
// duration, so an uneven frame — or a dropped one — changes the lag and the follower appears to
|
||||
// speed up or stall. Carrying the target's own displacement first leaves only the residual to
|
||||
// decay: a target at a steady speed is tracked exactly, whatever the frame times do, and a
|
||||
// correction still fades out over `seconds`.
|
||||
export const followVec2 = (
|
||||
out: vec2,
|
||||
target: vec2,
|
||||
previousTarget: vec2,
|
||||
deltaTimeInSeconds: number,
|
||||
seconds: number,
|
||||
snapDistance: number,
|
||||
): vec2 => {
|
||||
out[0] += target[0] - previousTarget[0];
|
||||
out[1] += target[1] - previousTarget[1];
|
||||
vec2.copy(previousTarget, target);
|
||||
return easeVec2(out, target, deltaTimeInSeconds, seconds, snapDistance);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,16 +32,15 @@ export const finiteVec2 = (value: unknown, maxMagnitude: number): vec2 | undefin
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const result = vec2.fromValues(x, y);
|
||||
const length = vec2.length(result);
|
||||
const length = Math.hypot(x, y);
|
||||
if (length > maxMagnitude) {
|
||||
vec2.scale(result, result, maxMagnitude / length);
|
||||
return vec2.fromValues((x / length) * maxMagnitude, (y / length) * maxMagnitude);
|
||||
}
|
||||
|
||||
return result;
|
||||
return vec2.fromValues(x, y);
|
||||
};
|
||||
|
||||
export const sanitizeName = (value: unknown, maxLength: number): string => {
|
||||
const text = typeof value === 'string' ? value : String(value ?? '');
|
||||
const text = typeof value === 'string' ? value : '';
|
||||
return text.slice(0, maxLength);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
let seed = Math.random();
|
||||
let seed = (Math.random() * 0x100000000) >>> 0;
|
||||
|
||||
function getRandom(): number {
|
||||
let t = (seed += 0x6d2b79f5);
|
||||
let t = (seed = (seed + 0x6d2b79f5) | 0);
|
||||
t = Math.imul(t ^ (t >>> 15), t | 1);
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
||||
|
|
@ -9,7 +9,7 @@ function getRandom(): number {
|
|||
|
||||
export const Random = {
|
||||
set seed(value: number) {
|
||||
seed = value;
|
||||
seed = value | 0;
|
||||
},
|
||||
getRandom,
|
||||
getRandomInRange(from: number, to: number): number {
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export abstract class GameObject {
|
|||
}
|
||||
}
|
||||
|
||||
public getPropertyUpdates(): PropertyUpdatesForObject | undefined {
|
||||
public getPropertyUpdates(_timeScale = 1): PropertyUpdatesForObject | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,29 +7,11 @@ import { smoothing } from '../helper/ease';
|
|||
export const headRadius = 50;
|
||||
export const feetRadius = 20;
|
||||
|
||||
const desiredHeadOffset = vec2.fromValues(0, 55);
|
||||
const desiredLeftFootOffset = vec2.fromValues(-20, 0);
|
||||
const desiredRightFootOffset = vec2.fromValues(20, 0);
|
||||
const centerOfMass = vec2.scale(
|
||||
vec2.create(),
|
||||
vec2.add(
|
||||
vec2.create(),
|
||||
vec2.add(vec2.create(), desiredHeadOffset, desiredLeftFootOffset),
|
||||
desiredRightFootOffset,
|
||||
),
|
||||
1 / 3,
|
||||
);
|
||||
export const headOffset = vec2.subtract(vec2.create(), desiredHeadOffset, centerOfMass);
|
||||
export const leftFootOffset = vec2.subtract(
|
||||
vec2.create(),
|
||||
desiredLeftFootOffset,
|
||||
centerOfMass,
|
||||
);
|
||||
export const rightFootOffset = vec2.subtract(
|
||||
vec2.create(),
|
||||
desiredRightFootOffset,
|
||||
centerOfMass,
|
||||
);
|
||||
// Imports run before setMatrixArrayType(Array): keep these fractional constants in f64.
|
||||
const centerHeight = 55 * (1 / 3);
|
||||
export const headOffset: vec2 = [0, 55 - centerHeight];
|
||||
export const leftFootOffset: vec2 = [-20, -centerHeight];
|
||||
export const rightFootOffset: vec2 = [20, -centerHeight];
|
||||
export const boundRadius = (headRadius + feetRadius * 2) * 2;
|
||||
|
||||
export interface CharacterMovementState {
|
||||
|
|
@ -52,11 +34,7 @@ export const applyForce = (
|
|||
force: vec2,
|
||||
deltaTimeInSeconds: number,
|
||||
) => {
|
||||
vec2.add(
|
||||
body.velocity,
|
||||
body.velocity,
|
||||
vec2.scale(vec2.create(), force, deltaTimeInSeconds),
|
||||
);
|
||||
vec2.scaleAndAdd(body.velocity, body.velocity, force, deltaTimeInSeconds);
|
||||
};
|
||||
|
||||
// ((head + leftFoot) + rightFoot) / 3 — do not reassociate.
|
||||
|
|
@ -128,20 +106,9 @@ const carryWithRotatingPlanet = (
|
|||
return;
|
||||
}
|
||||
const angle = -planet.angularVelocity * deltaTimeInSeconds;
|
||||
const center = planet.center;
|
||||
state.head.center = vec2.rotate(vec2.create(), state.head.center, center, angle);
|
||||
state.leftFoot.center = vec2.rotate(
|
||||
vec2.create(),
|
||||
state.leftFoot.center,
|
||||
center,
|
||||
angle,
|
||||
);
|
||||
state.rightFoot.center = vec2.rotate(
|
||||
vec2.create(),
|
||||
state.rightFoot.center,
|
||||
center,
|
||||
angle,
|
||||
);
|
||||
for (const body of [state.head, state.leftFoot, state.rightFoot]) {
|
||||
vec2.rotate(body.center, body.center, planet.center, angle);
|
||||
}
|
||||
};
|
||||
|
||||
export const applyLeapImpulse = (state: CharacterMovementState, moveDirection: vec2) => {
|
||||
|
|
@ -234,10 +201,6 @@ export const decayMomentum = (
|
|||
}
|
||||
};
|
||||
|
||||
const decayBodyMomentum = (state: CharacterMovementState, deltaTimeInSeconds: number) => {
|
||||
decayMomentum(state.bodyVelocity, !!state.currentPlanet, deltaTimeInSeconds);
|
||||
};
|
||||
|
||||
export const sumGravity = (grounds: Array<GroundSurface>, position: vec2): vec2 =>
|
||||
grounds.reduce(
|
||||
(sum, ground) => vec2.add(sum, sum, ground.gravityAt(position)),
|
||||
|
|
@ -335,5 +298,5 @@ export const stepCharacterMovement = (
|
|||
latchGround(state, world.stepBody(state.rightFoot, deltaTimeInSeconds));
|
||||
world.stepBody(state.head, deltaTimeInSeconds);
|
||||
|
||||
decayBodyMomentum(state, deltaTimeInSeconds);
|
||||
decayMomentum(state.bodyVelocity, !!state.currentPlanet, deltaTimeInSeconds);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { evaluateSdf } from './evaluate-sdf';
|
|||
import { sdfNormal } from './sdf-normal';
|
||||
|
||||
export interface MarchResult {
|
||||
hitSurface: boolean;
|
||||
travelled: number;
|
||||
normal?: vec2;
|
||||
hitObject?: Sdf;
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ export const marchCircle = (
|
|||
): MarchResult => {
|
||||
const deltaLength = vec2.length(delta);
|
||||
if (!(deltaLength > 0)) {
|
||||
return { hitSurface: false };
|
||||
return { travelled: 0 };
|
||||
}
|
||||
|
||||
const direction = vec2.normalize(vec2.create(), delta);
|
||||
|
|
@ -35,17 +35,17 @@ export const marchCircle = (
|
|||
|
||||
if (minDistance < body.radius) {
|
||||
const intersecting = possibleIntersectors.find(
|
||||
(i) => i.distance(rayEnd) <= body.radius,
|
||||
(i) => i.canCollide && i.distance(rayEnd) < body.radius,
|
||||
)!;
|
||||
|
||||
onHit?.(intersecting);
|
||||
|
||||
vec2.scaleAndAdd(rayEnd, body.center, direction, lastFreeDistance);
|
||||
vec2.copy(body.center, rayEnd);
|
||||
const normal = sdfNormal(rayEnd, [intersecting]);
|
||||
onHit?.(intersecting);
|
||||
|
||||
return {
|
||||
hitSurface: true,
|
||||
normal: sdfNormal(rayEnd, [intersecting]),
|
||||
travelled: lastFreeDistance,
|
||||
normal,
|
||||
hitObject: intersecting,
|
||||
};
|
||||
}
|
||||
|
|
@ -64,5 +64,5 @@ export const marchCircle = (
|
|||
|
||||
vec2.scaleAndAdd(body.center, body.center, direction, lastFreeDistance);
|
||||
|
||||
return { hitSurface: false };
|
||||
return { travelled: lastFreeDistance };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,35 +9,39 @@ export const resolveCircleMovement = (
|
|||
possibleIntersectors: Array<Sdf>,
|
||||
onHit?: (intersecting: Sdf) => void,
|
||||
): Sdf | undefined => {
|
||||
let delta = vec2.scale(vec2.create(), body.velocity, deltaTimeInSeconds);
|
||||
|
||||
depenetrateCircle(body, possibleIntersectors);
|
||||
let remainingSeconds = deltaTimeInSeconds;
|
||||
let firstHit: Sdf | undefined;
|
||||
|
||||
const { normal, hitSurface, hitObject } = marchCircle(
|
||||
body,
|
||||
delta,
|
||||
possibleIntersectors,
|
||||
onHit,
|
||||
);
|
||||
|
||||
if (hitSurface) {
|
||||
vec2.copy(body.lastNormal, normal!);
|
||||
|
||||
vec2.subtract(
|
||||
body.velocity,
|
||||
body.velocity,
|
||||
vec2.scale(
|
||||
normal!,
|
||||
normal!,
|
||||
(1 + body.restitution) * vec2.dot(normal!, body.velocity),
|
||||
),
|
||||
// Resolve at most two contacts per step, spending only the time left after each hit.
|
||||
for (let pass = 0; pass < 2 && remainingSeconds > 0; pass++) {
|
||||
const speed = vec2.length(body.velocity);
|
||||
if (speed === 0) {
|
||||
break;
|
||||
}
|
||||
const delta = vec2.scale(vec2.create(), body.velocity, remainingSeconds);
|
||||
const { normal, hitObject, travelled } = marchCircle(
|
||||
body,
|
||||
delta,
|
||||
possibleIntersectors,
|
||||
onHit,
|
||||
);
|
||||
|
||||
if (vec2.length(body.velocity) > 50) {
|
||||
delta = vec2.scale(vec2.create(), body.velocity, deltaTimeInSeconds);
|
||||
marchCircle(body, delta, possibleIntersectors, onHit);
|
||||
if (!hitObject || !normal) {
|
||||
break;
|
||||
}
|
||||
firstHit ??= hitObject;
|
||||
vec2.copy(body.lastNormal, normal);
|
||||
remainingSeconds -= travelled / speed;
|
||||
const inwardSpeed = vec2.dot(normal, body.velocity);
|
||||
if (inwardSpeed < 0) {
|
||||
vec2.scaleAndAdd(
|
||||
body.velocity,
|
||||
body.velocity,
|
||||
normal,
|
||||
-(1 + body.restitution) * inwardSpeed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return hitObject;
|
||||
return firstHit;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue