JA
games/open-lemmings / src / View / TerrainMesh.php · commit 04e2e2a
<?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);    }}