Skip to content

Commit

Permalink
Improve navmesh tile finder
Browse files Browse the repository at this point in the history
  • Loading branch information
starswaitforus authored and solcloud committed Aug 24, 2024
1 parent 7bc6cef commit 2364a25
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 43 deletions.
9 changes: 9 additions & 0 deletions server/src/Core/Graph.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,13 @@ public function generateNeighbors(): void
}
}

/**
* @return array<string,string[]>
* @internal
*/
public function internalGetGeneratedNeighbors(): array
{
return $this->neighbors;
}

}
74 changes: 59 additions & 15 deletions server/src/Core/PathFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected function canFullyMoveTo(Point $candidate, int $angle, int $targetDista
foreach (range(1, 3 * $height) as $i) {
$fallCandidate->addY(-1);
if ($this->world->findFloorSquare($fallCandidate, $radius)) {
$candidate->setY($fallCandidate->y);
$candidate->setY($fallCandidate->y); // side effect
return true;
}
}
Expand Down Expand Up @@ -105,24 +105,70 @@ private function canMoveTo(Point $start, int $angle, int $radius): bool
return false;
}

public function tryFindClosestTile(Point $point): ?Point
public function findTile(Point $pointOnFloor, int $radius): Point
{
$candidate = $point->clone();
$floorNavmeshPoint = $pointOnFloor->clone();
$this->convertToNavMeshNode($floorNavmeshPoint);
if ($this->getGraph()->getNodeById($floorNavmeshPoint->hash())) {
return $floorNavmeshPoint;
}

$maxDistance = $this->tileSize * 2;
$maxY = $this->obstacleOvercomeHeight * 2;
$checkAbove = function (Point $start, int $maxY, int $radius): ?Point {
$yCandidate = $start->clone();
$navMeshCenter = $yCandidate->clone();
$this->convertToNavMeshNode($navMeshCenter);
for ($i = 1; $i <= $maxY; $i++) {
$yCandidate->addY(1);
if ($this->world->findFloorSquare($yCandidate, $radius - 1)) {
break;
}
if ($this->getGraph()->getNodeById($navMeshCenter->setY($yCandidate->y)->hash())) {
return $navMeshCenter;
}
}

return null;
};

// try navmesh above
$above = $checkAbove($pointOnFloor, $maxY, $radius);
if ($above) {
return $above;
}

// try neighbour tiles
$candidate = $pointOnFloor->clone();
$navmesh = $pointOnFloor->clone();
foreach ($this->moves as $angle => $move) {
$candidate->setFrom($point);
$candidate->setFrom($pointOnFloor);

for ($distance = 1; $distance <= $maxDistance; $distance++) {
$candidate->addPart(...$this->moves[$angle]);
if (!$this->canFullyMoveTo($candidate, $angle, 1, $radius, $this->colliderHeight)) {
break;
}

if ($this->canFullyMoveTo($candidate, $angle, $this->tileSize, 0, $this->colliderHeight)) {
$this->convertToNavMeshNode($candidate);
if ($this->getGraph()->getNodeById($candidate->hash())) {
return $candidate;
$prevNavmesh = $navmesh->hash();
$navmesh->setFrom($candidate);
$this->convertToNavMeshNode($navmesh);
if ($this->getGraph()->getNodeById($navmesh->hash())) {
return $navmesh;
}
if ($prevNavmesh !== $navmesh->hash()) {
$above = $checkAbove($candidate, $maxY, $radius);
if ($above) {
return $above;
}
}
}
}

return null;
GameException::notImplementedYet('Should always find something? ' . $pointOnFloor->hash());
}

public function convertToNavMeshNode(Point $point): float
public function convertToNavMeshNode(Point $point): void
{
if ($point->x < 1 || $point->z < 1) {
throw new GameException('World start from 1');
Expand All @@ -132,11 +178,9 @@ public function convertToNavMeshNode(Point $point): float
$fmodZ = fmod($point->z, $this->tileSize);

$x = ((int)floor(($point->x + ($fmodX == 0 ? -1 : +0)) / $this->tileSize) * $this->tileSize) + 1 + $this->tileSizeHalf;
$point->setX($x);
$point->x = $x;
$z = ((int)floor(($point->z + ($fmodZ == 0 ? -1 : +0)) / $this->tileSize) * $this->tileSize) + 1 + $this->tileSizeHalf;
$point->setZ($z);

return (abs($this->tileSizeHalf - $fmodX) + abs($this->tileSizeHalf - $fmodZ)) / 2;
$point->z = $z;
}

public function buildNavigationMesh(Point $start, int $objectHeight): void
Expand Down Expand Up @@ -182,7 +226,7 @@ public function buildNavigationMesh(Point $start, int $objectHeight): void
$this->graph->addNode($currentNode);
}
if (++$this->iterationCount === 10_000) {
GameException::notImplementedYet('New map or bad test (no boundary box, bad starting point)?');
GameException::notImplementedYet('New map, tileSize or bad test (no boundary box, bad starting point)?');
}
}
}
Expand Down
22 changes: 8 additions & 14 deletions server/src/Core/World.php
Original file line number Diff line number Diff line change
Expand Up @@ -493,15 +493,7 @@ public function processFlammableExplosion(Player $thrower, Point $epicentre, Fla
assert($this->grenadeNavMesh !== null);

$epicentreFloor = $epicentre->clone()->addY(-$item->getBoundingRadius());
$floorNavmeshPoint = $epicentreFloor->clone();
$this->grenadeNavMesh->convertToNavMeshNode($floorNavmeshPoint);

if (null === $this->grenadeNavMesh->getGraph()->getNodeById($floorNavmeshPoint->hash())) {
$floorNavmeshPoint = $this->grenadeNavMesh->tryFindClosestTile($epicentreFloor);
if (null === $floorNavmeshPoint) {
return;
}
}
$floorNavmeshPoint = $this->grenadeNavMesh->findTile($epicentreFloor, $item->getBoundingRadius());

$this->game->addGrillEvent(
new GrillEvent(
Expand Down Expand Up @@ -655,7 +647,7 @@ public function playerDiedToFlame(Player $playerCulprit, Player $playerDead, Fla
public function buildNavigationMesh(int $tileSize, int $objectHeight): PathFinder
{
$boundingRadius = Setting::playerBoundingRadius();
if ($tileSize > $boundingRadius - 2) {
if ($tileSize > $boundingRadius - 4) {
throw new GameException('Tile size should be decently lower than player bounding radius.');
}

Expand Down Expand Up @@ -699,10 +691,10 @@ public function checkZSideWallCollision(Point $bottomCenter, int $height, int $r

public function bulletHit(Hittable $hit, Bullet $bullet, bool $wasHeadshot): void
{
if ($hit->getPlayer()) {
$item = $bullet->getShootItem();
assert($item instanceof Item);
$item = $bullet->getShootItem();
assert($item instanceof Item);

if ($hit->getPlayer()) {
$this->playerHit(
$bullet->getPosition()->clone(),
$hit->getPlayer(),
Expand All @@ -720,14 +712,16 @@ public function bulletHit(Hittable $hit, Bullet $bullet, bool $wasHeadshot): voi
$hit,
$bullet->getOriginPlayerId(),
$bullet->getOrigin(),
$item,
$hit->getDamage()
);
}
}

public function surfaceHit(Point $hitPoint, SolidSurface $hit, int $attackerId, Point $origin, int $damage): void
public function surfaceHit(Point $hitPoint, SolidSurface $hit, int $attackerId, Point $origin, Item $item, int $damage): void
{
$soundEvent = new SoundEvent($hitPoint, SoundType::BULLET_HIT);
$soundEvent->setItem($item);
$soundEvent->setSurface($hit);
$soundEvent->addExtra('origin', $origin->toArray());
$soundEvent->addExtra('damage', min(100, $damage));
Expand Down
4 changes: 2 additions & 2 deletions server/src/Event/GrillEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public function __construct(
)
{
$flameArea = ($this->flameRadius * 2 + 1) ** 2;
$this->spawnTickCount = Util::millisecondsToFrames(20);
$this->spawnFlameCount = (int)ceil($this->item->getSpawnAreaMetersSquared() / $flameArea);
$this->spawnTickCount = Util::millisecondsToFrames(30);
$this->spawnFlameCount = (int)ceil($this->item->getSpawnAreaMetersSquared() * 100 / $flameArea);
$this->maxTicksCount = Util::millisecondsToFrames($this->item->getMaxTimeMs());
$this->damageCoolDownTickCount = Util::millisecondsToFrames(100);
$this->maxFlameCount = (int)ceil($this->item->getMaxAreaMetersSquared() / $flameArea);
Expand Down
5 changes: 3 additions & 2 deletions server/src/Event/ThrowEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,13 @@ private function finishLanding(Point $point): void
$this->tickMax = 0;
}
for ($i = 1; $i <= ceil(Util::GRAVITY * 2); $i++) {
if (!$this->world->findFloor($point, $this->radius)) {
if (!$this->world->findFloorSquare($point, $this->radius)) {
$point->addY(-1);
continue;
}

$this->makeEvent($point->addY($this->radius), SoundType::GRENADE_LAND);
$point->addY($this->radius);
$this->makeEvent($point, SoundType::GRENADE_LAND);
$this->runOnCompleteHooks();
return;
}
Expand Down
12 changes: 12 additions & 0 deletions server/src/Map/ArrayMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class ArrayMap extends Map
private array $walls;
/** @var Floor[] */
private array $floors;
/** @var Point[] */
private array $startingPointsForNavigationMesh;

/**
* @param array<string,mixed> $data
Expand All @@ -36,6 +38,16 @@ public function __construct(array $data)
foreach ($data['walls'] as $wallData) { // @phpstan-ignore-line
$this->walls[] = Wall::fromArray($wallData); // @phpstan-ignore-line
}

foreach($data['startingPointsNavMesh'] ?? [] as $pointData) { // @phpstan-ignore-line
$this->startingPointsForNavigationMesh[] = Point::fromArray($pointData); // @phpstan-ignore-line
}
$this->startingPointsForNavigationMesh ??= parent::getStartingPointsForNavigationMesh();
}

public function getStartingPointsForNavigationMesh(): array
{
return $this->startingPointsForNavigationMesh;
}

public function getWalls(): array
Expand Down
1 change: 0 additions & 1 deletion server/src/Map/DefaultMap.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use cs\Core\Box;
use cs\Core\Point;
use cs\Core\Point2D;
use cs\Core\Ramp;
use cs\Enum\RampDirection;

Expand Down
1 change: 1 addition & 0 deletions server/src/Map/Map.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public function toArray(): array
'walls' => array_map(fn(Wall $o) => $o->toArray(), $this->getWalls()),
'spawnAttackers' => array_map(fn(Point $o) => $o->toArray(), $this->getSpawnPositionAttacker()),
'spawnDefenders' => array_map(fn(Point $o) => $o->toArray(), $this->getSpawnPositionDefender()),
'startingPointsNavMesh' => array_map(fn(Point $p) => $p->toArray(), $this->getStartingPointsForNavigationMesh()),
'buyAreaAttackers' => $this->getBuyArea(true)->toArray(),
'buyAreaDefenders' => $this->getBuyArea(false)->toArray(),
'plantArea' => $this->getPlantArea()->toArray(),
Expand Down
2 changes: 1 addition & 1 deletion server/src/Weapon/Knife.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public function getDamageValue(HitBoxType $hitBox, ArmorType $armor): int

public function getKillAward(): int
{
return 1500;
return self::killAward;
}

}
1 change: 0 additions & 1 deletion test/og/Shooting/MolotovGrenadeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use cs\Core\Box;
use cs\Core\Player;
use cs\Core\Point;
use cs\Core\Point2D;
use cs\Core\Ramp;
use cs\Core\Setting;
use cs\Enum\BuyMenuItem;
Expand Down
4 changes: 2 additions & 2 deletions test/og/World/NavigationMeshTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public function testConvertPointToNavMeshPoint(): void
['326,333,326', new Point(333, 333, 333)],
['450,0,295', new Point(450, 0, 285)],
['450,0,295', new Point(461, 0, 285)],
['1566,50,16', new Point(1570,50,26)],
],
];
$world = new World($this->createTestGame());
Expand Down Expand Up @@ -81,8 +82,7 @@ public function testBoundary(): void
$path->convertToNavMeshNode($closestCandidate);
$this->assertNull($path->getGraph()->getNodeById($closestCandidate->hash()));

$validPoint = $path->tryFindClosestTile($candidate);
$this->assertNotNull($validPoint);
$validPoint = $path->findTile($candidate, 1);
$this->assertLessThan($closestCandidate->x, $validPoint->x);
$this->assertNotNull($path->getGraph()->getNodeById($validPoint->hash()));
$this->assertSame('2,0,5', $validPoint->hash());
Expand Down
1 change: 1 addition & 0 deletions www/assets/js/Game.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ export class Game {
const flame = this.#world.spawnFlame(size, height)
this.#flammable[fireId][flameId] = flame
flame.position.set(point.x, point.y + (height / 2), -1 * point.z)
flame.rotation.x = randomInt(-10, 10) / 100;
flame.rotateOnWorldAxis(new THREE.Vector3(0, 1, 0), Math.random() * 6.28)
}

Expand Down
23 changes: 18 additions & 5 deletions www/assets/js/World.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class World {
#modelRepository
#decals = []
#flames = []
#cache = {}
volume = 30

constructor() {
Expand Down Expand Up @@ -173,12 +174,24 @@ export class World {
return dropItem
}

spawnFlame(size, height) {
let mesh = new THREE.Mesh(
new THREE.ConeGeometry(size, height, randomInt(5, 7)),
new THREE.MeshPhongMaterial({color: new THREE.Color(`hsl(53, 100%, ${Math.random() * 70 + 20}%, 1)`)}),
)
loadCache(index, loadCallback) {
if (this.#cache[index] === undefined) {
this.#cache[index] = loadCallback()
}

return this.#cache[index];
}

spawnFlame(size, height) {
const coneDetail = randomInt(5, 7)
const lightnessValue = randomInt(30, 80)
const geometry = this.loadCache(`flame-geo-c-${coneDetail}`, () => new THREE.ConeGeometry(1, 1, coneDetail))
const material = this.loadCache(`flame-mat-${lightnessValue}`, () => new THREE.MeshPhongMaterial({
color: new THREE.Color(`hsl(53, 100%, ${lightnessValue}%, 1)`)
}))

let mesh = new THREE.Mesh(geometry, material)
mesh.scale.set(size, height, size)
mesh.castShadow = false
mesh.receiveShadow = true

Expand Down
36 changes: 36 additions & 0 deletions www/mapGenerator.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use cs\Core\Game;
use cs\Core\Setting;
use cs\Map;

Expand All @@ -9,7 +10,12 @@
$map = new Map\DefaultMap();
////////

$game = new Game();
$game->loadMap($map);
$world = $game->getWorld();

$radarMapGenerator = (isset($_GET['radar']));
$showNavigationMesh = (isset($_GET['navmesh']));
$buyAreas = [
$map->getBuyArea(false)->toArray(),
$map->getBuyArea(true)->toArray(),
Expand Down Expand Up @@ -204,6 +210,36 @@ function plants() {
area.translateY(box.height / 2)
area.translateZ(box.depth / -2)
scene.add(area);

<?php if ($showNavigationMesh): ?>
<?php
$tileSize = $world::GRENADE_NAVIGATION_MESH_TILE_SIZE;
$path = $world->buildNavigationMesh($tileSize, $world::GRENADE_NAVIGATION_MESH_OBJECT_HEIGHT);
$navmesh = [];
foreach ($path->getGraph()->internalGetGeneratedNeighbors() as $nodeId => $ids) {
$navmesh[] = $nodeId;
}
?>
let mesh = null;
const navMeshGeometry = new THREE.BoxGeometry(<?= $tileSize ?>, .5, <?= $tileSize ?>);
const navMeshMaterial = new THREE.MeshStandardMaterial({color: 0xD024A3})
<?php foreach ($navmesh as $coords): ?>
mesh = new THREE.Mesh(navMeshGeometry, navMeshMaterial)
mesh.translateY(navMeshGeometry.parameters.height / 2)
mesh.position.set(<?= $coords ?>)
mesh.position.z *= -1
scene.add(mesh);
<?php endforeach; ?>
if (false) {
mesh = new THREE.Mesh(
new THREE.BoxGeometry(20, 20, 20),
new THREE.MeshStandardMaterial({color: 0xFF0000, transparent: true, opacity: .8})
)
mesh.position.set(1902, 20, -2550)
mesh.translateY(mesh.geometry.parameters.height / 2)
scene.add(mesh)
}
<?php endif; ?>
}

function animate() {
Expand Down

0 comments on commit 2364a25

Please sign in to comment.