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