UK
games/open-lemmings / src / View / Backdrop.php · commit 04e2e2a
<?phpnamespace App\View;/** * The sky and the parallax layers of the 3D view as planes far behind the level, pure math (tested without GL). * * A 2D layer scrolls at a fraction of the camera ($speed: 0.15 far, 0.4 near). A plane at the depth where perspective * gives the same fraction (distance x (1 / speed - 1) behind the level) looks exactly like it at 0 degrees. When the * camera turns, the plane turns too around the middle of the view, by TURN of the camera's angle: it is seen a little * from the side and slides with a real parallax, but never at a grazing angle (which would stretch it into a wall). * The plane is cut to what the camera sees (the view's corners cast on it): never a hole, at any angle. Texture * coordinates go beyond 0..1: the layers repeat sideways (the sky mirrored); above and below, the picture's top and * bottom rows go on. */final class Backdrop{    public const SKY = 0.04;    public const FAR = 0.15;    public const NEAR = 0.4;    public const TURN = 0.75;    /**     * @param float $tw picture size in retro pixels @param float $th     * @param float $scrollX the 2D layer's sideways scroll (camX x speed), 0 for the sky     * @return array<int, array{0: float, 1: float, 2: float, 3: float, 4: float}>|null four corners (top left, top     *         right, bottom right, bottom left): 3D x y z, u v     */    public static function plane(Camera3d $cam, float $speed, float $tw, float $th, float $scrollX = 0.0): ?array    {        $depth = $cam->distance * (1 / $speed - 1);        $a = deg2rad($cam->yaw * self::TURN);        $normal = [sin($a), 0.0, cos($a)];          // towards the camera        $side = [cos($a), 0.0, -sin($a)];           // the plane's own x axis        [$tx, $ty3] = $cam->target;        $c = [$tx - $normal[0] * $depth, $ty3, -$normal[2] * $depth];        $e = $cam->eye;        $k = $cam->zoom * $speed;        $m = 6;        $out = [];        // the layer's rows are hung on the point the middle of the view sees: a camera that looks down a little does        // not lift the mountains up into the sky        $mid = null;        foreach ([[Camera3d::VIEW_W / 2, Camera3d::VIEW_H / 2], [-$m, -$m], [Camera3d::VIEW_W + $m, -$m], [Camera3d::VIEW_W + $m, Camera3d::VIEW_H + $m],                  [-$m, Camera3d::VIEW_H + $m]] as [$vx, $vy]) {            $d = $cam->ray($vx, $vy);            $dn = $d[0] * $normal[0] + $d[2] * $normal[2];            if ($dn >= -1e-6) {                return null;            }            $t = (($c[0] - $e[0]) * $normal[0] + ($c[2] - $e[2]) * $normal[2]) / $dn;            $p = [$e[0] + $t * $d[0], $e[1] + $t * $d[1], $e[2] + $t * $d[2]];            // where this point is drawn by the 2D view at 0 degrees, then which pixel of the layer is there            $local = ($p[0] - $c[0]) * $side[0] + ($p[2] - $c[2]) * $side[2];            $sx = Camera3d::VIEW_W / 2 + $local * $k;            if ($mid === null) {                $mid = $p[1];                continue;            }            $sy = Camera3d::VIEW_H / 2 + ($mid - $p[1]) * $k;            $out[] = [$p[0], $p[1], $p[2], ($sx + $scrollX) / $tw, $sy / $th];        }        return $out;    }}