Accedi
IT
games/open-lemmings / tests / View3dTest.php · commit 04e2e2a
<?phpnamespace Tests;use App\Scene\PlayScene;use App\Sim\Lumi;use App\Sim\Terrain;use App\View\Backdrop;use App\View\Camera3d;use App\View\TerrainMesh;use App\View\World3d;use JumPHP\Testing\GameTestCase;/** The 3D view: its camera math (pure PHP), its terrain mesh, and the arrows in the level. */final class View3dTest extends GameTestCase{    public function testAtZeroDegreesTheCameraSeesTheLevelLikeThe2dView(): void    {        foreach ([[0.0, 0.0, 1.0], [120.0, 0.0, 1.0], [200.0, 30.0, 2.0], [55.5, 12.0, 3.0]] as [$camX, $camY, $zoom]) {            $cam = new Camera3d($camX, $camY, $zoom, 0.0);            foreach ([[0, 0], [240, 116], [479, 231], [33, 200]] as [$vx, $vy]) {                $p = $cam->unproject($vx, $vy);                self::assertNotNull($p);                self::assertEqualsWithDelta($vx / $zoom + $camX, $p[0], 0.01);                self::assertEqualsWithDelta($vy / $zoom + $camY, $p[1], 0.01);                $back = $cam->project($p[0], $p[1]);                self::assertNotNull($back);                self::assertEqualsWithDelta($vx, $back[0], 0.01);                self::assertEqualsWithDelta($vy, $back[1], 0.01);            }        }    }    public function testATurnedCameraUnprojectsAClickBackOntoTheSameLevelPoint(): void    {        foreach ([45.0, -45.0, 20.0] as $yaw) {            $cam = new Camera3d(100.0, 0.0, 1.0, $yaw);            // the middle of the view stays on the middle of the level view: the camera turns around it            $mid = $cam->unproject(240, 135);            self::assertNotNull($mid);            self::assertEqualsWithDelta(340, $mid[0], 0.01);            self::assertEqualsWithDelta(135, $mid[1], 0.01);            foreach ([[150.0, 120.0], [400.0, 200.0], [260.0, 60.0]] as [$x, $y]) {                $v = $cam->project($x, $y);                self::assertNotNull($v);                $p = $cam->unproject($v[0], $v[1]);                self::assertNotNull($p);                self::assertEqualsWithDelta($x, $p[0], 0.01, "yaw $yaw");                self::assertEqualsWithDelta($y, $p[1], 0.01, "yaw $yaw");            }        }        // turned to the right, a point on the right of the level is nearer than one on the left        $cam = new Camera3d(100.0, 0.0, 1.0, 45.0);        self::assertGreaterThan($cam->nearness(200, 100), $cam->nearness(400, 100));    }    public function testABlockOfGroundBecomesAFrontFaceAndFourSides(): void    {        $t = new Terrain(64, 58);        $t->fillRect(10, 20, 8, 5, Terrain::EARTH);        [$v, $quads] = TerrainMesh::chunk($t, 0, 0, 64, 58);        self::assertSame(5, $quads);   // front, top, bottom, left, right        self::assertCount($quads * 4 * TerrainMesh::FLOATS, $v);        $t->digRect(12, 20, 2, 5);     // a shaft through it: two blocks, each with its four sides        [, $quads] = TerrainMesh::chunk($t, 0, 0, 64, 58);        self::assertSame(9, $quads);    }    public function testTheArrowsTurnTheCameraAndZeroDegreesStaysFlat(): void    {        $scene = $this->game->start(PlayScene::class, ['level' => 0]);        self::assertInstanceOf(PlayScene::class, $scene);        $can = $scene->can3d();        self::assertSame($this->game->engine->gl()->isAvailable(), $can);        $this->game->press('turn_right');        self::assertSame($can ? 45.0 : 0.0, $scene->yawTarget());        $this->game->press('turn_right');        self::assertSame($can ? 45.0 : 0.0, $scene->yawTarget(), 'never past 45 degrees');        $this->game->run(90);        self::assertSame($can ? 45.0 : 0.0, $scene->yaw());        self::assertSame($can, $scene->is3d());        $this->game->press('turn_left');        $this->game->run(90);        // back at 0: the 2D view and its plain click mapping        self::assertSame(0.0, $scene->yaw());        self::assertFalse($scene->is3d());        self::assertSame([100 + $scene->camX(), 50 + $scene->camY()], $scene->toLevel(100, 50));        // the arrow buttons work like the keys        [$bx, $by] = PlayScene::viewButtons()['turn_left'];        $mouse = $this->game->engine->input->getMouse();        $mouse->move($bx + 10, $by + 7);        $mouse->down(1);        $this->game->run(1);        $mouse->up(1);        $this->game->run(1);        self::assertSame($can ? -45.0 : 0.0, $scene->yawTarget());    }    public function testAClickInTheTurnedViewStillGivesTheSkillToTheRightLumikin(): void    {        // force3d: the camera, the clicks and the terrain meshes of the 3D view, even without OpenGL (nothing is drawn then)        $scene = $this->game->start(PlayScene::class, ['level' => 0, 'angle' => 45, 'force3d' => true]);        self::assertInstanceOf(PlayScene::class, $scene);        self::assertSame(45.0, $scene->yaw());        self::assertTrue($scene->is3d());        $this->game->run(150);        $lumi = $scene->world()->lumis[0];        self::assertSame(Lumi::WALK, $lumi->state);        $v = $scene->camera3d()->project($lumi->x, $lumi->y - 6);        self::assertNotNull($v);        $mouse = $this->game->engine->input->getMouse();        $mouse->move($v[0], $v[1]);        $mouse->down(1);        $this->game->run(1);        $mouse->up(1);        $this->game->run(2);        self::assertSame(Lumi::DIG, $lumi->state);        // it digs: the chunks it touches are built again in 3D, a few milliseconds a frame at most        $this->game->run(60);        self::assertGreaterThan(120, $lumi->y);        self::assertLessThan(40, $scene->view()->world3d()->buildMs());    }    public function testTheFarLayersAreTheSame2dLayersAtZeroDegreesAndCoverTheTurnedView(): void    {        // at 0 degrees the plane's corners carry the layer pixels the 2D view draws at the view's corners        $cam = new Camera3d(130.0, 0.0, 1.0, 0.0);        $q = Backdrop::plane($cam, Backdrop::FAR, 512, 232, 130 * Backdrop::FAR);        self::assertNotNull($q);        self::assertEqualsWithDelta((-6 + 130 * Backdrop::FAR) / 512, $q[0][3], 1e-6);        self::assertEqualsWithDelta(-6 / 232, $q[0][4], 1e-6);        self::assertEqualsWithDelta((486 + 130 * Backdrop::FAR) / 512, $q[2][3], 1e-6);        self::assertEqualsWithDelta(276 / 232, $q[2][4], 1e-6);        // turned (and on the way), every plane is cut to the whole view, behind the level        foreach ([-45.0, -20.0, 10.0, 45.0] as $yaw) {            $cam = new Camera3d(130.0, 0.0, 1.5, $yaw);            foreach ([Backdrop::SKY, Backdrop::FAR, Backdrop::NEAR] as $speed) {                $q = Backdrop::plane($cam, $speed, 512, 232);                self::assertNotNull($q, "yaw $yaw");                foreach ($q as $corner) {   // farther from the camera than the middle of the level                    self::assertLessThan($cam->nearness($cam->target[0], -$cam->target[1]), $cam->nearness($corner[0], -$corner[1], $corner[2]));                }            }        }    }    public function testALumikinDeepInAShaftIsSeenAsHiddenWhenTheCameraIsTurned(): void    {        $t = new Terrain(200, 100);        $t->fillRect(0, 50, 200, 50, Terrain::EARTH);        $t->digRect(96, 40, 9, 40);   // a shaft 30 px deep        $flat = new Camera3d(0.0, 0.0, 1.0, 0.0);        $turned = new Camera3d(0.0, 0.0, 1.0, 45.0);        self::assertFalse(World3d::hidden($flat, $t, 100, 79));      // seen straight through the shaft        self::assertTrue(World3d::hidden($turned, $t, 100, 79));     // the shaft's wall is in the way        self::assertFalse(World3d::hidden($turned, $t, 30, 50));     // on top of the ground    }    public function testDraggingTheTurnedViewKeepsTheGrabbedPointUnderThePointer(): void    {        $scene = $this->game->start(PlayScene::class, ['level' => 2, 'angle' => 45, 'force3d' => true]);        self::assertInstanceOf(PlayScene::class, $scene);        $this->game->run(2);        $grab = $scene->toLevel(400, 120);        $mouse = $this->game->engine->input->getMouse();        $mouse->move(400, 120);        $mouse->down(1);        $this->game->run(1);        $mouse->move(300, 120);        $this->game->run(2);        $now = $scene->toLevel(300, 120);        $mouse->up(1);        $this->game->run(1);        self::assertEqualsWithDelta($grab[0], $now[0], 0.5);    }}