| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 |
- // Budget (readme §5): an op + collider regeneration must stay under 2 ms on the server.
- // Each iteration also steps the world, as the server does every tick: planck only
- // flushes its broadphase move buffer during step(), and skipping it makes body
- // destruction quadratic, which never happens in game.
- import { World } from 'planck';
- import { bench, describe } from 'vitest';
- import { loadMap } from '../src/server/assets';
- import { TerrainColliders } from '../src/server/physics/terrainColliders';
- import { Rng } from '../src/shared/rng';
- import { DetachFinder } from '../src/shared/terrain/detach';
- import { extractContours } from '../src/shared/terrain/marchingSquares';
- import { applyTerrainEvent } from '../src/shared/terrain/events';
- import { applyOp } from '../src/shared/terrain/ops';
- import { rleEncode } from '../src/shared/terrain/rle';
- import { simplifyContour } from '../src/shared/terrain/simplify';
- import { ASSETS, realMaterials } from './helpers';
- const materials = realMaterials();
- const { terrain } = loadMap(ASSETS, 'gateau-etages', materials);
- const world = new World();
- const colliders = new TerrainColliders(world, terrain);
- colliders.rebuildDirty();
- const rng = new Rng(7);
- const finder = new DetachFinder(terrain);
- // A copy of the map whose top tier has just been cut loose (search only, the bitmap is never modified).
- const cut = loadMap(ASSETS, 'gateau-etages', materials).terrain;
- for (let x = 760; x <= 1290; x += 12) applyOp(cut, { kind: 'carve', x, y: 462, r: 14, power: 99 }, materials);
- const cutFinder = new DetachFinder(cut);
- /** Removes detached components like the server does, so they are not found again. */
- function removeDetached(region: { x0: number; y0: number; x1: number; y1: number }): void {
- for (const c of finder.find(region)) {
- const { x0, y0, x1, y1 } = c.bounds;
- applyTerrainEvent(terrain, { kind: 'detach', id: 0, x: x0, y: y0, w: x1 - x0, h: y1 - y0, mask: rleEncode(c.mask), debris: true }, materials);
- }
- }
- describe('terrain', () => {
- bench('carve r=40 + detach search + collider rebuild', () => {
- const { changed } = applyOp(terrain, { kind: 'carve', x: rng.int(300, 1700), y: rng.int(300, 900), r: 40, power: 99 }, materials);
- if (changed) removeDetached(changed);
- colliders.rebuildDirty();
- world.step(1 / 60);
- });
- bench('fill r=30 + detach search + collider rebuild', () => {
- const { changed } = applyOp(terrain, { kind: 'fill', x: rng.int(300, 1700), y: rng.int(300, 900), r: 30, material: 1 }, materials);
- if (changed) removeDetached(changed);
- colliders.rebuildDirty();
- world.step(1 / 60);
- });
- bench('marching squares + simplify, one 64 px chunk', () => {
- for (const c of extractContours(terrain, 1024, 704, 1088, 768)) simplifyContour(c, 0.8);
- });
- bench('detach search: a whole tier comes loose (~90k px)', () => {
- if (cutFinder.find({ x0: 746, y0: 448, x1: 1305, y1: 477 }).length === 0) throw new Error('expected a detached tier');
- });
- bench('full map collider build (map load)', () => {
- const w = new World();
- terrain.markDirty({ x0: 0, y0: 0, x1: terrain.width, y1: terrain.height });
- new TerrainColliders(w, terrain).rebuildDirty();
- });
- });
|