Connexion
FR
games/doom-jumphp / tools / art / Textures.php · commit 8e1cb9f
<?phpnamespace Tools\Art;use App\Engine\Palette as P;/** The wall textures, flats and sky of IRON ABYSS, drawn in code. */final class Textures{    /** @return array<string, Canvas> walls (64 wide; 128 or 64 high) */    public static function walls(): array    {        $t = [];        $t['BRICK'] = self::brick(P::BRICK, 9);        $t['BRICKDK'] = self::brick(P::RUST, 6);        $t['TECH'] = self::tech(P::STEEL, 8);        $t['TECHDK'] = self::tech(P::GREY, 6);        $t['COMPUTER'] = self::computer();        $t['STONE'] = self::stone(P::GREY, 8, 128);        $t['STONETAN'] = self::stone(P::TAN, 8, 128);        $t['ROCK'] = self::rock(P::BROWN, 128);        $t['METAL'] = self::metal();        $t['WOOD'] = self::wood();        $t['FLESH'] = self::flesh();        $t['MARBLE'] = self::marble();        $t['SUPPORT'] = self::support();        $t['DOOR'] = self::door(null);        $t['DOORRED'] = self::door(P::RED);        $t['DOORBLUE'] = self::door(P::BLUE);        $t['DOORYEL'] = self::door(P::YELLOW);        $t['DOORTRAK'] = self::track();        $t['LIFT'] = self::liftWall();        $t['STEP'] = self::step();        $t['LITE'] = self::lite();        $t['SWEXIT0'] = self::exitSwitch(false);        $t['SWEXIT1'] = self::exitSwitch(true);        $t['SWITCH0'] = self::switchPanel(false);        $t['SWITCH1'] = self::switchPanel(true);        $t['GRATE'] = self::grate();        $t['PIPES'] = self::pipes();        return $t;    }    /** @return array<string, Canvas> 64x64 flats */    public static function flats(): array    {        $f = [];        $f['FLOOR1'] = self::tiles(P::GREY, 7, 32);        $f['FLOOR2'] = self::grateFlat();        $f['FLOOR3'] = self::dirt();        $f['FLOOR4'] = self::tiles(P::TAN, 7, 16);        $f['FLOOR5'] = self::tiles(P::BRICK, 6, 32);        $f['CEIL1'] = self::ceilPanel(false);        $f['CEIL2'] = self::ceilPanel(true);        $f['CEIL3'] = self::rock(P::BROWN, 64);        $f['STONEF'] = self::stone(P::GREY, 7, 64);        $f['WOODF'] = self::woodFloor();        $f['FLESHF'] = self::fleshFloor();        $f['LIFTF'] = self::liftFlat();        $f['STEPF'] = self::stepFlat();        $f['DOORF'] = self::tiles(P::STEEL, 6, 64);        for ($i = 0; $i < 3; $i++) {            $f['ACID' . ($i + 1)] = self::liquid(P::GREEN, $i, 9);            $f['LAVA' . ($i + 1)] = self::liquid(P::ORANGE, $i, 11);            $f['BLOOD' . ($i + 1)] = self::liquid(P::RED, $i, 8);        }        return $f;    }    private static function brick(int $ramp, int $shade): Canvas    {        $c = new Canvas(64, 128);        $c->paint(fn (int $x, int $y) => P::index(P::GREY, 3 + (int)(Noise::hash($x, $y) * 2)));        for ($row = 0; $row < 8; $row++) {            $off = ($row % 2) * 16;            for ($b = 0; $b < 2; $b++) {                $tone = $shade + (int)round((Noise::hash($row, $b, 3) - 0.5) * 3);                $x0 = $off + $b * 32;                $c->paint(function (int $x, int $y, int $i, int $j) use ($ramp, $tone): int {                    $n = Noise::fbm($x / 8, $y / 8, 8, 3);                    $s = $tone + (int)round(($n - 0.5) * 5);                    if ($j === 0 || $i === 0) {                        $s += 2;                    } elseif ($j === 13 || $i === 30) {                        $s -= 2;                    }                    return P::index($ramp, $s);                }, $x0 + 1, $row * 16 + 1, 31, 14);            }        }        return $c;    }    private static function tech(int $ramp, int $shade): Canvas    {        $c = new Canvas(64, 128);        $c->plate(0, 0, 64, 128, $ramp, $shade - 3, 1, 1);        foreach ([[2, 2, 60, 44], [2, 48, 28, 50], [32, 48, 30, 50], [2, 100, 60, 26]] as [$x, $y, $w, $h]) {            $c->plate($x, $y, $w, $h, $ramp, $shade, 2, 1.5);            foreach ([[$x + 3, $y + 3], [$x + $w - 5, $y + 3], [$x + 3, $y + $h - 5], [$x + $w - 5, $y + $h - 5]] as [$rx, $ry]) {                $c->rivet($rx, $ry, $ramp, $shade);            }        }        // a light strip and vents        $c->rect(6, 20, 52, 3, P::index(P::YELLOW, 14));        $c->rect(6, 23, 52, 1, P::index(P::YELLOW, 9));        for ($i = 0; $i < 5; $i++) {            $c->rect(8, 58 + $i * 7, 16, 3, P::index($ramp, $shade - 5));            $c->rect(38, 58 + $i * 7, 18, 3, P::index($ramp, $shade - 5));        }        // dirt at the bottom        $c->paint(function (int $x, int $y) use ($c): ?int {            $v = $c->get($x, $y);            return Noise::hash($x, $y, 2) < ($y - 110) / 30 ? ($v & 0xF0) + max(0, ($v & 15) - 2) : null;        }, 0, 110, 64, 18);        return $c;    }    private static function computer(): Canvas    {        $c = new Canvas(64, 128);        $c->plate(0, 0, 64, 128, P::STEEL, 6, 1, 1);        foreach ([[4, 8], [4, 52]] as [$x, $y]) {            $c->plate($x, $y, 56, 36, P::STEEL, 9, 2);            $c->rect($x + 4, $y + 4, 48, 28, P::index(P::GREEN, 1));            for ($l = 0; $l < 6; $l++) {                $len = 10 + (int)(Noise::hash($l, $y) * 34);                $c->rect($x + 6, $y + 7 + $l * 4, $len, 2, P::index(P::GREEN, 9 + ($l % 3) * 2));            }        }        for ($i = 0; $i < 12; $i++) {            $col = [P::RED, P::YELLOW, P::GREEN, P::CYAN][$i % 4];            $c->rect(6 + ($i % 6) * 9, 96 + intdiv($i, 6) * 8, 5, 4, P::index($col, Noise::hash($i, 4) > 0.4 ? 14 : 6));        }        $c->plate(0, 114, 64, 14, P::GREY, 5, 1);        return $c;    }    private static function stone(int $ramp, int $shade, int $h): Canvas    {        $c = new Canvas(64, $h);        // Voronoi cells (tileable): stones, darker grout between them        $pts = [];        Noise::seed($ramp * 31 + $h);        for ($i = 0; $i < ($h === 128 ? 22 : 12); $i++) {            $pts[] = [Noise::rand() * 64, Noise::rand() * $h, (int)round((Noise::rand() - 0.5) * 4)];        }        $c->paint(function (int $x, int $y) use ($pts, $h, $ramp, $shade): int {            $d1 = $d2 = INF;            $tone = 0;            foreach ($pts as [$px, $py, $t]) {                foreach ([-64, 0, 64] as $ox) {                    foreach ([-$h, 0, $h] as $oy) {                        $d = sqrt(($x - $px - $ox) ** 2 + ($y - $py - $oy) ** 2);                        if ($d < $d1) {                            $d2 = $d1;                            $d1 = $d;                            $tone = $t;                        } elseif ($d < $d2) {                            $d2 = $d;                        }                    }                }            }            $edge = $d2 - $d1;            $n = Noise::fbm($x / 6, $y / 6, 64 / 6 > 0 ? 10 : 10, 3);            if ($edge < 1.6) {                return P::index($ramp, $shade - 5);            }            $s = $shade + $tone + (int)round(($n - 0.5) * 4) + ($edge < 3 ? -1 : 0);            return P::index($ramp, $s);        });        return $c;    }    private static function rock(int $ramp, int $h): Canvas    {        $c = new Canvas(64, $h);        $c->paint(function (int $x, int $y) use ($ramp, $h): int {            $n = Noise::fbm($x / 16, $y / 16, 4, 5);            $r = abs(Noise::fbm($x / 10 + 3, $y / 20, 6, 3) - 0.5);            $s = 3 + (int)round($n * 10) - ($r < 0.03 ? 3 : 0);            return P::index($ramp, $s);        });        return $c;    }    private static function metal(): Canvas    {        $c = new Canvas(64, 128);        $c->paint(function (int $x, int $y): int {            $wave = (int)round(sin($x / 8 * M_PI * 2 / 2) * 2);            $rust = Noise::fbm($x / 12, $y / 12, 5, 4);            $streak = Noise::fbm($x / 3, $y / 40, 21, 2);            $s = 8 + $wave - ($streak > 0.6 ? 2 : 0);            return $rust > 0.55 ? P::index(P::RUST, $s - 1) : P::index(P::STEEL, $s - 1);        });        $c->rect(0, 0, 64, 3, P::index(P::GREY, 7));        $c->rect(0, 125, 64, 3, P::index(P::GREY, 4));        return $c;    }    private static function wood(): Canvas    {        $c = new Canvas(64, 128);        $c->paint(function (int $x, int $y): int {            $plank = intdiv($x, 16);            $grain = Noise::fbm($x / 2 + $plank * 7, $y / 24, 32, 3);            $s = 7 + (int)round(($grain - 0.5) * 6) + ($x % 16 === 0 ? -4 : 0) + ($x % 16 === 15 ? -2 : 0);            return P::index(P::BROWN, $s);        });        foreach ([[8, 10], [8, 116], [24, 10], [24, 116], [40, 10], [40, 116], [56, 10], [56, 116]] as [$x, $y]) {            $c->rivet($x - 1, $y, P::GREY, 8);        }        return $c;    }    private static function flesh(): Canvas    {        $c = new Canvas(64, 128);        $c->paint(function (int $x, int $y): int {            $n = Noise::fbm($x / 12, $y / 12, 5, 4);            $vein = abs(Noise::fbm($x / 14 + 9, $y / 14, 5, 3) - 0.5);            if ($vein < 0.025) {                return P::index(P::RED, 6);            }            if ($vein < 0.045) {                return P::index(P::PURPLE, 5);            }            return P::index(P::FLESH, 6 + (int)round($n * 6));        });        return $c;    }    private static function marble(): Canvas    {        $c = new Canvas(64, 128);        $c->paint(function (int $x, int $y): int {            $n = Noise::fbm($x / 10, $y / 10, 7, 4);            $vein = abs(sin(($x + $y * 0.4) / 9 + $n * 7));            return $vein < 0.08 ? P::index(P::GREY, 12) : P::index(P::TEAL, 4 + (int)round($n * 6));        });        $c->plate(0, 0, 64, 6, P::GREY, 8, 1);        $c->plate(0, 120, 64, 8, P::GREY, 7, 1);        return $c;    }    private static function support(): Canvas    {        $c = new Canvas(64, 128);        $c->plate(0, 0, 64, 128, P::STEEL, 7, 3, 1);        $c->rect(28, 0, 8, 128, P::index(P::STEEL, 4));        for ($y = 4; $y < 128; $y += 16) {            $c->rivet(8, $y, P::STEEL, 8);            $c->rivet(54, $y, P::STEEL, 8);        }        return $c;    }    private static function door(?int $key): Canvas    {        $c = new Canvas(64, 128);        $c->plate(0, 0, 64, 128, P::STEEL, 8, 2, 1);        $c->plate(4, 6, 27, 100, P::STEEL, 9, 2);        $c->plate(33, 6, 27, 100, P::STEEL, 9, 2);        $c->rect(31, 0, 2, 128, P::index(P::GREY, 2));        // hazard stripes at the bottom        $c->paint(fn (int $x, int $y) => (intdiv($x + $y, 6) % 2) ? P::index(P::YELLOW, 11) : P::index(P::GREY, 2), 0, 110, 64, 18);        if ($key !== null) {            $c->rect(0, 40, 64, 10, P::index($key, 13));            $c->rect(0, 40, 64, 1, P::index($key, 15));            $c->rect(0, 49, 64, 1, P::index($key, 8));            $c->rect(26, 34, 12, 22, P::index($key, 11));            $c->rect(29, 37, 6, 16, P::index($key, 15));        } else {            $c->rect(10, 30, 12, 4, P::index(P::GREY, 4));            $c->rect(42, 30, 12, 4, P::index(P::GREY, 4));        }        return $c;    }    private static function track(): Canvas    {        $c = new Canvas(64, 128);        $c->plate(0, 0, 64, 128, P::GREY, 5, 1, 2);        for ($y = 0; $y < 128; $y += 8) {            $c->rect(0, $y, 64, 1, P::index(P::GREY, 3));        }        $c->rect(28, 0, 8, 128, P::index(P::YELLOW, 9));        return $c;    }    private static function liftWall(): Canvas    {        $c = new Canvas(64, 128);        $c->plate(0, 0, 64, 128, P::STEEL, 6, 1, 1);        $c->paint(fn (int $x, int $y) => (intdiv($x + $y, 8) % 2) ? P::index(P::YELLOW, 12) : P::index(P::GREY, 3), 0, 0, 64, 10);        for ($y = 16; $y < 128; $y += 16) {            $c->rect(4, $y, 56, 2, P::index(P::STEEL, 3));        }        return $c;    }    private static function step(): Canvas    {        $c = new Canvas(64, 16);        $c->plate(0, 0, 64, 16, P::STEEL, 7, 1);        $c->rect(0, 0, 64, 2, P::index(P::YELLOW, 11));        return $c;    }    private static function lite(): Canvas    {        $c = new Canvas(64, 128);        $c->plate(0, 0, 64, 128, P::STEEL, 6, 1, 1);        $c->plate(12, 16, 40, 96, P::STEEL, 8, 3);        $c->paint(fn (int $x, int $y, int $i, int $j) => P::index(P::GREY, 15 - (int)(abs($i - 14) / 5)), 18, 22, 28, 84);        return $c;    }    private static function exitSwitch(bool $on): Canvas    {        $c = self::tech(P::STEEL, 7);        $c->plate(12, 40, 40, 48, P::GREY, 5, 2);        $c->text(19, 46, 'EXIT', P::index($on ? P::GREEN : P::RED, 14), 2, P::index(P::GREY, 1));        $c->rect(22, 64, 20, 16, P::index(P::GREY, 2));        $c->rect(25, $on ? 72 : 66, 14, 6, P::index($on ? P::GREEN : P::RED, 12));        return $c;    }    private static function switchPanel(bool $on): Canvas    {        $c = self::tech(P::STEEL, 7);        $c->plate(18, 44, 28, 40, P::GREY, 5, 2);        $c->rect(28, $on ? 64 : 50, 8, 14, P::index($on ? P::GREEN : P::RED, 13));        return $c;    }    private static function grate(): Canvas    {        $c = new Canvas(64, 128);        $c->paint(function (int $x, int $y): int {            $bar = $x % 16 < 4 || $y % 32 < 3;            return $bar ? P::index(P::STEEL, 7 + ($x % 16 === 0 ? 2 : 0)) : P::TRANSPARENT;        });        return $c;    }    private static function pipes(): Canvas    {        $c = new Canvas(64, 128);        $c->plate(0, 0, 64, 128, P::GREY, 4, 1, 1);        foreach ([[4, 14, P::RUST], [22, 10, P::STEEL], [36, 12, P::OLIVE], [52, 8, P::STEEL]] as [$x, $w, $ramp]) {            for ($i = 0; $i < $w; $i++) {                $s = 4 + (int)round(sin($i / ($w - 1) * M_PI) * 8);                $c->rect($x + $i, 0, 1, 128, P::index($ramp, $s));            }            for ($y = 20; $y < 128; $y += 48) {                $c->plate($x - 1, $y, $w + 2, 5, P::GREY, 8, 1);            }        }        return $c;    }    private static function tiles(int $ramp, int $shade, int $size): Canvas    {        $c = new Canvas(64, 64);        for ($ty = 0; $ty < 64; $ty += $size) {            for ($tx = 0; $tx < 64; $tx += $size) {                $c->plate($tx, $ty, $size, $size, $ramp, $shade + (int)round((Noise::hash($tx, $ty, 5) - 0.5) * 2), 1, 1.5);            }        }        return $c;    }    private static function grateFlat(): Canvas    {        $c = new Canvas(64, 64);        $c->paint(function (int $x, int $y): int {            $hole = ($x % 8 >= 2 && $y % 8 >= 2);            return $hole ? P::index(P::GREY, 1) : P::index(P::STEEL, 7 + (($x % 8 === 0 || $y % 8 === 0) ? 2 : 0));        });        $c->plate(0, 0, 64, 2, P::STEEL, 9, 1);        return $c;    }    private static function dirt(): Canvas    {        $c = new Canvas(64, 64);        $c->paint(fn (int $x, int $y) => P::index(P::BROWN, 4 + (int)round(Noise::fbm($x / 8, $y / 8, 8, 4) * 6 + (Noise::hash($x, $y) - 0.5) * 2)));        return $c;    }    private static function ceilPanel(bool $light): Canvas    {        $c = new Canvas(64, 64);        $c->plate(0, 0, 64, 64, P::STEEL, 6, 2, 1);        $c->plate(4, 4, 56, 56, P::STEEL, 7, 1);        if ($light) {            $c->plate(12, 12, 40, 40, P::GREY, 10, 2);            $c->paint(fn () => P::index(P::GREY, 15), 16, 16, 32, 32);        } else {            $c->rivet(8, 8, P::STEEL, 7);            $c->rivet(54, 8, P::STEEL, 7);            $c->rivet(8, 54, P::STEEL, 7);            $c->rivet(54, 54, P::STEEL, 7);        }        return $c;    }    private static function woodFloor(): Canvas    {        $c = new Canvas(64, 64);        $c->paint(function (int $x, int $y): int {            $row = intdiv($y, 8);            $grain = Noise::fbm($x / 20 + $row * 3, $y / 2, 4, 3);            $s = 6 + (int)round(($grain - 0.5) * 6) + ($y % 8 === 0 ? -3 : 0) + (($x + $row * 24) % 64 === 0 ? -3 : 0);            return P::index(P::BROWN, $s);        });        return $c;    }    private static function fleshFloor(): Canvas    {        $c = new Canvas(64, 64);        $c->paint(function (int $x, int $y): int {            $n = Noise::fbm($x / 10, $y / 10, 7, 4);            $vein = abs(Noise::fbm($x / 12 + 4, $y / 12, 6, 3) - 0.5);            return $vein < 0.03 ? P::index(P::RED, 5) : P::index(P::FLESH, 4 + (int)round($n * 6));        });        return $c;    }    private static function liftFlat(): Canvas    {        $c = self::tiles(P::STEEL, 7, 64);        $c->paint(fn (int $x, int $y) => (intdiv($x + $y, 8) % 2) ? P::index(P::YELLOW, 12) : P::index(P::GREY, 3), 0, 0, 64, 6);        $c->paint(fn (int $x, int $y) => (intdiv($x + $y, 8) % 2) ? P::index(P::YELLOW, 12) : P::index(P::GREY, 3), 0, 58, 64, 6);        $c->paint(fn (int $x, int $y) => (intdiv($x + $y, 8) % 2) ? P::index(P::YELLOW, 12) : P::index(P::GREY, 3), 0, 0, 6, 64);        $c->paint(fn (int $x, int $y) => (intdiv($x + $y, 8) % 2) ? P::index(P::YELLOW, 12) : P::index(P::GREY, 3), 58, 0, 6, 64);        return $c;    }    private static function stepFlat(): Canvas    {        $c = self::tiles(P::STEEL, 7, 32);        $c->rect(0, 0, 64, 1, P::index(P::YELLOW, 10));        return $c;    }    private static function liquid(int $ramp, int $phase, int $shade): Canvas    {        $c = new Canvas(64, 64);        $c->paint(function (int $x, int $y) use ($ramp, $phase, $shade): int {            $ox = sin(($y / 64 + $phase / 3) * M_PI * 2) * 3;            $oy = cos(($x / 64 + $phase / 3) * M_PI * 2) * 3;            $n = Noise::fbm(($x + $ox) / 16, ($y + $oy) / 16, 4, 4);            return P::index($ramp, $shade - 3 + (int)round($n * 7));        });        return $c;    }    /** The sky: 256 x 128, wraps four times around the view. A red dusk over jagged peaks, two moons. */    public static function sky(): Canvas    {        $c = new Canvas(256, 128);        $c->paint(function (int $x, int $y): int {            $band = $y / 128;            $cloud = Noise::fbm($x / 32, $y / 12, 8, 4);            if ($band < 0.45) {                $s = 2 + (int)round($band * 10 + ($cloud - 0.5) * 4);                return P::index(P::PURPLE, $s);            }            $s = 4 + (int)round(($band - 0.45) * 16 + ($cloud - 0.5) * 4);            return P::index($band > 0.7 ? P::ORANGE : P::RED, $s);        });        // moons        foreach ([[60, 30, 11, P::TAN], [180, 22, 6, P::GREY]] as [$mx, $my, $mr, $ramp]) {            $c->paint(function (int $x, int $y) use ($mx, $my, $mr, $ramp): ?int {                $d = sqrt(($x - $mx) ** 2 + ($y - $my) ** 2);                return $d <= $mr ? P::index($ramp, 14 - (int)(($x - $mx + $mr) / ($mr * 2) * 6) - (Noise::hash($x, $y) > 0.85 ? 2 : 0)) : null;            }, $mx - $mr, $my - $mr, $mr * 2 + 1, $mr * 2 + 1);        }        // mountains (two layers)        for ($x = 0; $x < 256; $x++) {            $far = 82 + (int)round(Noise::fbm($x / 24, 0.5, 11, 4) * 34 - 17);            $near = 98 + (int)round(Noise::fbm($x / 16, 5.5, 16, 4) * 40 - 20);            for ($y = $far; $y < 128; $y++) {                $c->set($x, $y, P::index(P::PURPLE, 2));            }            for ($y = $near; $y < 128; $y++) {                $c->set($x, $y, P::index(P::BRICK, $y === $near ? 4 : 1));            }        }        return $c;    }}