<?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; }}