<?phpnamespace App\View;use App\Sim\Terrain;/** * The 3D shape of one chunk of a Terrain, pure PHP: the chunk's picture on a front face (its holes are cut by the * picture's alpha in the shader), and the faces of the extrusion (tops, bottoms, left and right sides) made from the * outline of the mask, merged into runs. Quads of 4 vertices: x y z, u v (in the chunk's picture), shade. */final class TerrainMesh{ public const FLOATS = 6; public const SHADE_FRONT = 1.0; public const SHADE_TOP = 1.18; public const SHADE_BOTTOM = 0.42; public const SHADE_LEFT = 0.78; public const SHADE_RIGHT = 0.58; /** * @return array{0: float[], 1: int} vertex floats, number of quads */ public static function chunk(Terrain $t, int $cx, int $cy, int $w, int $h, float $depth = Camera3d::DEPTH): array { $mask = $t->mask; $tw = $t->width; $th = $t->height; $zf = $depth / 2; $zb = -$depth / 2; $v = []; $quads = 0; // the front face: the whole chunk, the picture's alpha cuts the holes self::quad($v, [$cx, -$cy, $zf], [$cx + $w, -$cy, $zf], [$cx + $w, -$cy - $h, $zf], [$cx, -$cy - $h, $zf], 0, 0, 1, 0, 1, 1, 0, 1, self::SHADE_FRONT); $quads++; // tops and bottoms: row runs for ($y = $cy; $y < $cy + $h; $y++) { $row = $y * $tw; $vv = ($y - $cy + 0.5) / $h; foreach ([-1, 1] as $side) { $ny = $y + $side; if ($side > 0 && $ny >= $th) { continue; // the bottom of the level: never seen } $start = -1; for ($x = $cx; $x <= $cx + $w; $x++) { $face = $x < $cx + $w && $mask[$row + $x] !== "\0" && ($ny < 0 || $mask[$ny * $tw + $x] === "\0"); if ($face && $start < 0) { $start = $x; } elseif (!$face && $start >= 0) { $u0 = ($start - $cx) / $w; $u1 = ($x - $cx) / $w; $py = $side < 0 ? -$y : -$y - 1; self::quad($v, [$start, $py, $zf], [$x, $py, $zf], [$x, $py, $zb], [$start, $py, $zb], $u0, $vv, $u1, $vv, $u1, $vv, $u0, $vv, $side < 0 ? self::SHADE_TOP : self::SHADE_BOTTOM); $quads++; $start = -1; } } } } // left and right sides: column runs for ($x = $cx; $x < $cx + $w; $x++) { foreach ([-1, 1] as $side) { $nx = $x + $side; // the picture's border column is a dark edge line: the side takes the colour one pixel inside $inner = min($cx + $w - 1, max($cx, $x - $side)); $uu = ($inner - $cx + 0.5) / $w; $px = $side < 0 ? $x : $x + 1; $start = -1; for ($y = $cy; $y <= $cy + $h; $y++) { $face = $y < $cy + $h && $mask[$y * $tw + $x] !== "\0" && ($nx < 0 || $nx >= $tw || $mask[$y * $tw + $nx] === "\0"); if ($face && $start < 0) { $start = $y; } elseif (!$face && $start >= 0) { $v0 = ($start - $cy) / $h; $v1 = ($y - $cy) / $h; self::quad($v, [$px, -$start, $zf], [$px, -$start, $zb], [$px, -$y, $zb], [$px, -$y, $zf], $uu, $v0, $uu, $v0, $uu, $v1, $uu, $v1, $side < 0 ? self::SHADE_LEFT : self::SHADE_RIGHT); $quads++; $start = -1; } } } } return [$v, $quads]; } /** @param array<int, mixed> $v @param-out array<int, mixed> $v @param float[] $a @param float[] $b @param float[] $c @param float[] $d */ private static function quad(array &$v, array $a, array $b, array $c, array $d, float $ua, float $va, float $ub, float $vb, float $uc, float $vc, float $ud, float $vd, float $shade): void { array_push($v, $a[0], $a[1], $a[2], $ua, $va, $shade, $b[0], $b[1], $b[2], $ub, $vb, $shade, $c[0], $c[1], $c[2], $uc, $vc, $shade, $d[0], $d[1], $d[2], $ud, $vd, $shade); }}