This commit is contained in:
Schmelczer András 2020-10-11 20:32:56 +02:00
parent 37954e2ef1
commit cd75bdbfde
20 changed files with 253 additions and 131 deletions

View file

@ -25,9 +25,6 @@ Random.seed = 42;
const objects = new PhysicalContainer();
createDungeon(objects);
createDungeon(objects);
createDungeon(objects);
createDungeon(objects);
objects.initialize();
@ -99,15 +96,6 @@ const handlePhysics = () => {
console.log(players.map((p) => p.latency));
}
if (deltas.length > 100) {
deltas.sort((a, b) => a - b);
console.log(`Median physics time: ${deltas[50].toFixed(2)} ms`);
console.log(
`Memory used: ${(process.memoryUsage().rss / 1024 / 1024).toFixed(2)} MB`,
);
deltas = [];
}
objects.sendCommand(step);
players.forEach((p) => p.sendCommand(step));

View file

@ -1,45 +1,61 @@
import { vec2, vec3 } from 'gl-matrix';
import { Random } from 'shared';
import { LampPhysical } from '../objects/lamp-physical';
import { TunnelPhysical } from '../objects/tunnel-physical';
import { PhysicalContainer } from '../physics/containers/physical-container';
export const createDungeon = (objects: PhysicalContainer) => {
let previousRadius = 350;
let previousEnd = vec2.create();
const lightPositions: Array<vec2> = [];
let tunnelsCountSinceLastLight = 0;
for (let i = 0; i < 50000; i += 500) {
const deltaHeight = (Random.getRandom() - 0.5) * 500;
const height = previousEnd.y + deltaHeight;
const currentEnd = vec2.fromValues(i, height);
const currentToRadius = Random.getRandom() * 300 + 150;
const tunnel = new TunnelPhysical(
previousEnd,
currentEnd,
previousRadius,
currentToRadius,
for (let j = 0; j < 6; j++) {
let previousRadius = 500;
let previousEnd = vec2.fromValues(
j === 0 ? 0 : Random.getRandomInRange(-1000, 1000),
j === 0 ? 0 : Random.getRandomInRange(-1000, 1000),
);
for (let i = 0; i < 500; i++) {
const delta = vec2.fromValues(j % 2 ? 1 : -1, Random.getRandomInRange(-1, 1));
objects.addObject(tunnel);
vec2.normalize(delta, delta);
vec2.scale(delta, delta, 500);
if (++tunnelsCountSinceLastLight > 3 && Random.getRandom() > 0.7) {
objects.addObject(
new LampPhysical(
currentEnd,
vec3.normalize(
vec3.create(),
vec3.fromValues(Random.getRandom(), 0, Random.getRandom()),
),
0.5,
),
const currentEnd = vec2.add(delta, delta, previousEnd);
const currentToRadius = Random.getRandom() * 250 + 150;
const tunnel = new TunnelPhysical(
previousEnd,
currentEnd,
previousRadius,
currentToRadius,
);
tunnelsCountSinceLastLight = 0;
}
previousEnd = currentEnd;
previousRadius = currentToRadius;
objects.addObject(tunnel);
if (Random.getRandom() > 0.7) {
const position = currentEnd;
if (!lightPositions.find((p) => vec2.dist(p, position) < 2000)) {
lightPositions.push(position);
objects.addObject(
new LampPhysical(
currentEnd,
vec3.normalize(
vec3.create(),
vec3.fromValues(
Random.getRandomInRange(0.5, 1),
0,
Random.getRandomInRange(0.5, 1),
),
),
Random.getRandomInRange(0.5, 1),
),
);
}
}
previousEnd = currentEnd;
previousRadius = currentToRadius;
}
}
};

View file

@ -44,19 +44,22 @@ export class CharacterPhysical extends CharacterBase implements Physical {
constructor(private readonly container: PhysicalContainer) {
super(id());
this.head = new CirclePhysical(
vec2.clone(CharacterPhysical.headOffset),
//vec2.clone(CharacterPhysical.headOffset),
[-2952.911, 215.241],
50,
this,
container,
);
this.leftFoot = new CirclePhysical(
vec2.clone(CharacterPhysical.leftFootOffset),
// vec2.clone(CharacterPhysical.leftFootOffset),
[-2930.603, 162.542],
20,
this,
container,
);
this.rightFoot = new CirclePhysical(
vec2.clone(CharacterPhysical.rightFootOffset),
//vec2.clone(CharacterPhysical.rightFootOffset),
[-2973.152, 167.921],
20,
this,
container,

View file

@ -136,12 +136,6 @@ export class CirclePhysical implements Circle, Physical {
if (hitSurface) {
vec2.scale(this.velocity, tangent!, vec2.dot(tangent!, this.velocity));
if (
vec2.length(this.velocity) <
settings.frictionMinVelocity * deltaTimeInSeconds
) {
this.velocity = vec2.create();
}
wasHit = true;
}
}

View file

@ -0,0 +1,61 @@
import { vec2 } from 'gl-matrix';
import {
id,
StepCommand,
settings,
CommandExecutors,
serializesTo,
ProjectileBase,
} from 'shared';
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
import { CirclePhysical } from './circle-physical';
import { Physical } from '../physics/physical';
import { PhysicalContainer } from '../physics/containers/physical-container';
@serializesTo(ProjectileBase)
export class ProjectilePhysical extends ProjectileBase implements Physical {
public readonly canCollide = true;
public readonly isInverted = false;
public readonly canMove = true;
public object: CirclePhysical;
protected commandExecutors: CommandExecutors = {
[StepCommand.type]: this.step.bind(this),
};
constructor(
center: vec2,
radius: number,
startingForce: vec2,
readonly container: PhysicalContainer,
) {
super(id(), center, radius);
this.object = new CirclePhysical(center, radius, this, container);
this.object.applyForce(startingForce, 1000);
}
private _boundingBox?: ImmutableBoundingBox;
public get boundingBox(): ImmutableBoundingBox {
if (!this._boundingBox) {
this._boundingBox = (this.object as CirclePhysical).boundingBox;
}
return this._boundingBox;
}
public get gameObject(): this {
return this;
}
public distance(target: vec2): number {
return this.object.distance(target);
}
public step(c: StepCommand) {
const deltaTime = c.deltaTimeInMiliseconds / 1000;
this.object.applyForce(settings.gravitationalForce, deltaTime);
this.object.step(deltaTime);
}
}

View file

@ -1,5 +1,5 @@
import { vec2 } from 'gl-matrix';
import { rotate90Deg, settings } from 'shared';
import { Circle, rotate90Deg } from 'shared';
import { CirclePhysical } from '../objects/circle-physical';
import { Physical } from './physical';
@ -13,71 +13,47 @@ export const moveCircle = (
normal?: vec2;
tangent?: vec2;
} => {
circle.center = vec2.add(circle.center, circle.center, delta);
const nextCircle = new Circle(vec2.clone(circle.center), circle.radius);
vec2.add(nextCircle.center, nextCircle.center, delta);
const intersecting = possibleIntersectors.filter(
(b) =>
b.gameObject !== circle.gameObject && circle.areIntersecting(b) && b.canCollide,
possibleIntersectors = possibleIntersectors.filter(
(b) => b.gameObject !== circle.gameObject && b.canCollide,
);
if (intersecting.length === 0) {
const getSdfAtPoint = (point: vec2): number => {
const sdf = possibleIntersectors
.filter((i) => i.isInverted)
.reduce((min, i) => (min = Math.max(min, -i.distance(point))), -1000);
return possibleIntersectors
.filter((i) => !i.isInverted)
.reduce((min, i) => (min = Math.min(min, i.distance(point))), sdf);
};
const sdfAtCenter = getSdfAtPoint(nextCircle.center);
if (sdfAtCenter > nextCircle.radius) {
circle.center = vec2.add(circle.center, circle.center, delta);
return {
realDelta: delta,
hitSurface: false,
};
}
const points = circle.getPerimeterPoints(settings.hitDetectionCirclePointCount);
const dx =
getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(1, 0))) -
sdfAtCenter;
const dy =
getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0, 1))) -
sdfAtCenter;
const normal = vec2.fromValues(dx, dy);
const distancesOfPoints = points
.map((point) => ({
point,
closest: intersecting
.map((i) => ({
inverted: i.isInverted,
distance: i.distance(point),
}))
.sort((a, b) => a.distance - b.distance)[0],
}))
.filter((i) => i.closest);
vec2.normalize(normal, normal);
const distancesOfIntersectingPoints = distancesOfPoints.filter(
(d) =>
(d.closest.distance > 0 && d.closest.inverted) ||
(d.closest.distance < 0 && !d.closest.inverted),
);
if (distancesOfIntersectingPoints.length === 0) {
return {
realDelta: delta,
hitSurface: false,
};
}
const deltas = distancesOfIntersectingPoints.map((pointDistance) => {
vec2.subtract(pointDistance.point, circle.center, pointDistance.point);
vec2.normalize(pointDistance.point, pointDistance.point);
vec2.scale(
pointDistance.point,
pointDistance.point,
(pointDistance.closest.inverted ? 1 : -1) * pointDistance.closest.distance,
);
return pointDistance.point;
});
const approxNormal = deltas.reduce(
(sum, current) => vec2.add(sum, sum, current),
vec2.create(),
);
vec2.scale(approxNormal, approxNormal, 1 / deltas.length);
circle.center = vec2.add(circle.center, circle.center, approxNormal);
vec2.normalize(approxNormal, approxNormal);
return {
realDelta: delta,
hitSurface: true,
normal: approxNormal,
tangent: rotate90Deg(approxNormal),
normal,
tangent: rotate90Deg(normal),
};
};

View file

@ -1,3 +1,4 @@
import { vec2 } from 'gl-matrix';
import {
CommandExecutors,
CommandReceiver,
@ -11,9 +12,11 @@ import {
StepCommand,
SetAspectRatioActionCommand,
calculateViewArea,
SecondaryActionCommand,
} from 'shared';
import { getTimeInMilliseconds } from '../helper/get-time-in-milliseconds';
import { CharacterPhysical } from '../objects/character-physical';
import { ProjectilePhysical } from '../objects/projectile-physical';
import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
import { PhysicalContainer } from '../physics/containers/physical-container';
@ -46,6 +49,15 @@ export class Player extends CommandReceiver {
[SetAspectRatioActionCommand.type]: (v: SetAspectRatioActionCommand) =>
(this.aspectRatio = v.aspectRatio),
[MoveActionCommand.type]: (c: MoveActionCommand) => this.character.sendCommand(c),
[SecondaryActionCommand.type]: (c: SecondaryActionCommand) => {
const start = vec2.clone(this.character.center);
const direction = vec2.subtract(vec2.create(), c.position, start);
vec2.normalize(direction, direction);
vec2.add(start, start, vec2.scale(vec2.create(), direction, 100));
const force = vec2.scale(direction, direction, 1000);
const projectile = new ProjectilePhysical(start, 20, force, this.objects);
this.objects.addObject(projectile);
},
};
constructor(
@ -70,7 +82,6 @@ export class Player extends CommandReceiver {
);
this.measureLatency();
this.sendObjects();
}
@ -113,7 +124,7 @@ export class Player extends CommandReceiver {
);
}
this.socket.emit(
this.socket.volatile.emit(
TransportEvents.ServerToPlayer,
serialize(
new UpdateObjectsCommand([