#include "../gen/Def.h" #include "Gen.h" #include "Global.h" #include "Input.h" #include "Instance.h" #include "Level.h" #include "Log.h" #include "Puzzle.h" #include "UI.h" #include "bx/bx.h" #include "bx/debug.h" #include "rendering/Rendering.h" #include "SDL3/SDL_mouse.h" #include "bgfx/bgfx.h" #include "bx/error.h" #include "bx/file.h" #include "bx/filepath.h" #include "bx/string.h" #include "imgui.h" #include #include #include #include using namespace Gen; namespace Game { void EntityRenderData::Render(const Model* models, const Material* materials, const Texture* textures) { if (DebugBreakOnRender) { bx::debugBreak(); DebugBreakOnRender = false; } if (models == nullptr || materials == nullptr || textures == nullptr) return; if (!Gen::IsValid(ModelH) || MaterialHandle >= EMaterial::EntryCount) return; if (!Visible) return; auto& rendering = GameRendering::Get(); UpdateMatrix(Transform); bgfx::setTransform(Transform.M.M); const Model& currentModel = models[ModelH.ModelIdx]; const Material& currentMaterial = materials[(uint16_t)MaterialHandle]; if (!isValid(currentModel.IndexBuffer) || !isValid(currentModel.VertexBuffer)) return; if (!isValid(currentMaterial.Shader)) return; bgfx::setVertexBuffer(0, currentModel.VertexBuffer); bgfx::setIndexBuffer(currentModel.IndexBuffer); bgfx::setState(currentMaterial.State); float timeValues[4]{0.0f}; timeValues[0] = GetInstance().Time.Now; float texInfo[4]{0.0f}; texInfo[0] = textures[0].Info.width; texInfo[1] = textures[0].Info.height; texInfo[2] = rendering.DitherTextures.DitherTexWH; texInfo[3] = rendering.DitherTextures.DitherTexDepth; bgfx::UniformHandle sampler = rendering.DefaultSampler; bgfx::TextureHandle tex = rendering.Textures[0].RenderHandle; if (IsValid(TextureHandle)) { sampler = textures[TextureHandle.TextureIdx].SamplerHandle; tex = textures[TextureHandle.TextureIdx].RenderHandle; texInfo[0] = textures[TextureHandle.TextureIdx].Info.width; texInfo[1] = textures[TextureHandle.TextureIdx].Info.height; } bgfx::setTexture(0, sampler, tex); bgfx::setTexture(1, rendering.DitherTextures.DitherSampler, rendering.DitherTextures.FinalTex); bgfx::setTexture(2, rendering.DitherTextures.RampSampler, rendering.DitherTextures.RampTex); bgfx::setUniform(currentMaterial.Uniforms[Material::UTime], timeValues); bgfx::setUniform(currentMaterial.Uniforms[Material::UDotColor], &DotColor.x); bgfx::setUniform(currentMaterial.Uniforms[Material::UTexInfo], texInfo); bgfx::setUniform(currentMaterial.Uniforms[Material::UBaseColor], &BaseColor.x); bgfx::submit(currentMaterial.ViewID, currentMaterial.Shader); } void EntityRenderData::LoadFromSaved(const Gen::SavedEntityRenderData& saved) { DotColor = saved.HighlightColor; BaseColor = saved.BaseColor; Transform = saved.TF; // TODO: fix handle indices MaterialHandle = saved.Material; TextureHandle = saved.Texture; ModelH = saved.Model; Visible = saved.Visible; } namespace { void UpdatePlayerInputMode() { bool IsGaming = GetInstance().Player.InputM == InputMode::Game; bool captureMouse = IsGaming && GetInstance().Player.InteractionM == InteractionMode::Walk; SDL_SetWindowRelativeMouseMode(GetShared().Window.SDLWindow, captureMouse); auto& rendering = GameRendering::Get(); if (rendering.SetupData.UseImgui) { auto& IO = ImGui::GetIO(); IO.ConfigFlags = FlagBool(IO.ConfigFlags, ImGuiConfigFlags_NoMouse | ImGuiConfigFlags_NoKeyboard, captureMouse); } rendering.UIVisible = IsGaming ? UIVisibilityState::Game : UIVisibilityState::Debug; } } // namespace void Level::Setup(GameData& data) { LOG("Level setup"); uint8_t* storagePtr = data.EntityArena.Base; bool needReset = false; needReset |= Cubes.Setup(storagePtr, needReset); needReset |= Tests.Setup(storagePtr, needReset); needReset |= PuzzleTiles.Setup(storagePtr, needReset); needReset |= PuzzleTileCovers.Setup(storagePtr, needReset); needReset |= UIQuads.Setup(storagePtr, needReset); needReset |= LevelEntities.Setup(storagePtr, needReset); Puzzle::Setup(); UIQuads.Count = 0; PuzzleTiles.Count = 0; bx::Error err; bx::DirectoryReader dirIter; bx::FileInfo info; bx::FilePath puzzleDirPath{Puzzle::PuzzleFileDir}; if (dirIter.open(puzzleDirPath, &err)) { while (true) { int32_t readCount = dirIter.read(&info, sizeof(info), &err); if (readCount == 0) break; if (err.isOk()) { if (info.type != bx::FileType::File) continue; bx::StringView pathEnd = info.filePath.getExt(); if (bx::strCmpI(pathEnd, ".pzl") != 0) continue; bx::FilePath fullPath = puzzleDirPath; fullPath.join(info.filePath); LOG("Loading %s", fullPath.getCPtr()); Gen::Deserializer ser; Gen::PuzzleData dataBuf; if (ser.Init(fullPath, "PZZL") && ser.ReadT(dataBuf)) { if (dataBuf.ID >= BX_COUNTOF(Puzzles)) { LOG_ERROR("Puzzle ID out of bounds: %u", dataBuf.ID); ser.Finish(); continue; } Puzzles[dataBuf.ID].Data = dataBuf; Puzzles[dataBuf.ID].Setup(); } else { LOG_WARN("Failed to load puzzle!"); } ser.Finish(); } else { LOG_ERROR("Failed parsing file name at %s:\n%s", puzzleDirPath.getCPtr(), err.getMessage()); break; } } } else { LOG_ERROR("Failed to open puzzle dir at %s:\n%s", puzzleDirPath.getCPtr(), err.getMessage().getCPtr()); } if (!IsValid(PlayerOutsideViewCube)) { PlayerOutsideViewCube = Cubes.New(); Cubes.Get(PlayerOutsideViewCube).Setup(); } { Deserializer d; d.Init("game/data/static/uiconfig.dat", "UICO"); d.ReadT(GetInstance().Player.Config); d.Finish(); } UIQuads.Count = 0; PuzzleTiles.Count = 0; PuzzleTileCovers.Count = 0; for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i) { if (Puzzles[i].Data.ID != UINT16_MAX) { Puzzles[i].Setup(); } } PuzzleUI.Setup(); ReloadLevelEntities(); UpdatePlayerInputMode(); } bool IsOnGround(Level& level, Vec3 worldPos) { for (auto& puz : level.Puzzles) { Vec3 offsetToPuzzle = worldPos - puz.WorldPosition + (Vec3{0.5f, 0.0f, 0.5f} * Puzzle::Config::CardScaleWorld); Vec3 scaledOffset = offsetToPuzzle / Puzzle::Config::CardScaleWorld; int32_t offsetX = (int32_t)bx::floor(scaledOffset.x); int32_t offsetY = (int32_t)bx::floor(scaledOffset.z); if (offsetX >= 0 && offsetX < puz.Data.WidthTiles / 2 && offsetY >= 0 && offsetY < puz.Data.HeightTiles / 2) { auto& card = puz.Data.PlacedCards[offsetY * Puzzle::Config::MaxPuzzleSizeCards + offsetX]; if (card.RefCard.Idx == UINT16_MAX) { return true; } auto& refCard = Puzzle::GetStaticPuzzleData().Cards[card.RefCard.Idx]; if (!IsValid(refCard.BaseModelHandle)) { LOG_WARN("missing base model! @ %i %i", offsetX, offsetY); return true; } auto& heightmap = GameRendering::Get().Models[refCard.BaseModelHandle.ModelIdx].Height; float fracOffsetX = scaledOffset.x - offsetX; float fracOffsetY = scaledOffset.z - offsetY; int32_t xPos = (int32_t)(fracOffsetX * heightmap.Width); int32_t yPos = (int32_t)(fracOffsetY * heightmap.Height); uint8_t height = 0; switch (card.Rotation) { case 0: height = heightmap.Values[yPos * heightmap.Width + xPos]; break; case 1: height = heightmap.Values[(heightmap.Height - xPos - 1) * heightmap.Width + yPos]; break; case 2: height = heightmap .Values[(heightmap.Height - yPos - 1) * heightmap.Width + (heightmap.Width - xPos - 1)]; break; default: height = heightmap.Values[xPos * heightmap.Width + (heightmap.Height - yPos - 1)]; break; } return height >= 110 && height <= 125; } } return false; } void Level::Update() { ZoneScopedN("Level update"); START_PERF(); PlayerData& player = GetInstance().Player; // Input float delta = GetInstance().Time.Delta; float moveSpeed = player.MovementSpeed; float rotSpeed = player.MouseSensitivity; float forwardInput = (GetKey(ScanCode::W) ? 1.0f : 0.0f) + (GetKey(ScanCode::S) ? -1.0f : 0.0f); float rightInput = (GetKey(ScanCode::D) ? 1.0f : 0.0f) + (GetKey(ScanCode::A) ? -1.0f : 0.0f); bx::Vec3 moveInput = bx::Vec3{rightInput, forwardInput, 0.0f}; moveInput = bx::normalize(moveInput); bx::Vec3 inputVec = {moveInput.x * delta * moveSpeed, 0.0f, moveInput.y * delta * moveSpeed}; Vec2 mouseMovement = GetMouseMovement(); bx::Vec3 rotInput = {mouseMovement.y * delta * -rotSpeed, mouseMovement.x * delta * -rotSpeed, 0.0f}; if (GetKeyPressedNow(ScanCode::F1)) { player.CameraM = player.CameraM == CameraMode::Walk ? CameraMode::Freefly : CameraMode::Walk; } if (GetKeyPressedNow(ScanCode::F2)) { if (player.InputM == InputMode::Game) { player.InputM = InputMode::UI; } else { player.InputM = InputMode::Game; } UpdatePlayerInputMode(); } if (player.CameraM == CameraMode::Freefly) { if (GetMouseButton(MouseButton::Left)) { player.FreeflyXRot += rotInput.x; player.FreeflyYRot += rotInput.y; bx::mtxRotateXYZ(player.FreeflyCamTransform.Rotation.M, player.FreeflyXRot, player.FreeflyYRot, 0.0f); } TranslateLocal(player.FreeflyCamTransform, {0.0f, 0.0f, inputVec.z}); TranslateLocal(player.FreeflyCamTransform, {inputVec.x, 0.0f, 0.0f}); } else if (player.CameraM == CameraMode::Walk) { auto newTransform = player.PlayerCamTransform; TranslateLocal(newTransform, {0.0f, 0.0f, inputVec.z}); TranslateLocal(newTransform, {inputVec.x, 0.0f, 0.0f}); newTransform.Position.y = 3.0f; if (IsOnGround(*this, newTransform.Position)) { player.PlayerCamTransform = newTransform; } player.WalkXRot += rotInput.x; player.WalkYRot += rotInput.y; bx::mtxRotateXYZ(player.PlayerCamTransform.Rotation.M, player.WalkXRot, player.WalkYRot, 0.0f); } if (GetKeyPressedNow(ScanCode::SPACE)) { player.InteractionM = player.InteractionM == InteractionMode::ReadTablet ? InteractionMode::Walk : InteractionMode::ReadTablet; UpdatePlayerInputMode(); } // Cubes for (uint16_t i = 0; i < Cubes.Count; ++i) { Cubes.Get({i}).Update(); } // Puzzle tiles uint16_t activeIdx = GetInstance().DebugData.SelectedDebugLevel; for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i) { Puzzles[i].IsActive = activeIdx == i; Puzzles[i].Update(); } Puzzle::PuzzleSolver solver; bool isPuzzleSolved = solver.IsPuzzleSolved(Puzzles[activeIdx].Data); PuzzleUI.Update(Puzzles[activeIdx].Data, isPuzzleSolved); END_PERF(GetShared().Window.PerfCounters, PerfCounterType::GameLevelUpdate, GetShared().Window.FrameCounter); } void Level::Render(uint16_t viewId, const Model* models, const Material* materials, const Texture* textures) { ZoneScopedN("Level Render"); auto& shared = GetShared(); auto& player = GetInstance().Player; bx::mtxProj(&player.Projection.M[0], 75.0f, float(shared.Window.WindowWidth) / float(shared.Window.WindowHeight), 0.1f, 1000.0f, bgfx::getCaps()->homogeneousDepth); bx::mtxInverse(&player.ProjectionInverse.M[0], &player.Projection.M[0]); bool isFreefly = player.CameraM == CameraMode::Freefly; Cubes.Get(PlayerOutsideViewCube).EData.Visible = isFreefly; if (isFreefly) { UpdateMatrix(player.FreeflyCamTransform); bgfx::setViewTransform(viewId, player.FreeflyCamTransform.MI.M, &player.Projection.M[0]); } else { UpdateMatrix(player.PlayerCamTransform); bgfx::setViewTransform(viewId, player.PlayerCamTransform.MI.M, &player.Projection.M[0]); } bgfx::touch(viewId); Cubes.Render(models, materials, textures); Tests.Render(models, materials, textures); PuzzleTiles.Render(models, materials, textures); PuzzleTileCovers.Render(models, materials, textures); if (player.InteractionM == InteractionMode::ReadTablet) { UIQuads.Render(models, materials, textures); } LevelEntities.Render(models, materials, textures); } void Cube::Setup() { EData.MaterialHandle = EMaterial::UI; EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf"); } void Cube::Update() { EData.Transform.Position = GetInstance().Player.PlayerCamTransform.Position; EData.Transform.Rotation = GetInstance().Player.PlayerCamTransform.Rotation; EData.Transform.Scale = {0.2f, 0.2f, 0.2f}; } void TestEntity::Setup() { EData.MaterialHandle = EMaterial::Default; EData.ModelH = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf"); } void WorldPuzzle::Setup() { auto& level = GetInstance().GameLevel; for (int32_t i = 0; i < Puzzle::Config::MaxCardsInPuzzle; ++i) { TileHandles[i] = level.PuzzleTiles.New(); auto& tile = level.PuzzleTiles.Get(TileHandles[i]); tile.EData.MaterialHandle = EMaterial::Default; for (int32_t j = 0; j < Puzzle::Config::MaxCoversInTile; ++j) { int32_t idx = i * Puzzle::Config::MaxCoversInTile + j; CoverHandles[idx] = level.PuzzleTileCovers.New(); auto& cover = level.PuzzleTileCovers.Get(CoverHandles[idx]); cover.EData.Visible = false; } } IsSetup = true; LOG("finished setup!"); } void WorldPuzzle::Update() { Level& level = GetInstance().GameLevel; auto& staticCards = Puzzle::GetStaticPuzzleData().Cards; auto& visuals = Puzzle::GetStaticPuzzleData().Visuals; // Board for (int8_t y = 0; y < Data.HeightTiles / Puzzle::Config::CardSize; ++y) { for (int8_t x = 0; x < Data.WidthTiles / Puzzle::Config::CardSize; ++x) { int32_t cardIdx = y * Puzzle::Config::MaxPuzzleSizeCards + x; Gen::PlacedPuzzleCard& card = Data.PlacedCards[cardIdx]; auto& tile = level.PuzzleTiles.Get(TileHandles[cardIdx]); bool isValid = Puzzle::IsValid(card.RefCard); auto& staticCard = isValid ? staticCards[card.RefCard.Idx] : staticCards[0]; // World Tile tile.EData.Visible = true; tile.EData.ModelH = staticCard.BaseModelHandle; tile.EData.TextureHandle = staticCard.ModelTextureHandle; tile.EData.DotColor = visuals.TileDotColor; tile.EData.BaseColor = visuals.TileBaseColor; Vec3 cardPos = { (float)card.Position.X * Puzzle::Config::CardScaleWorld, -5.0f, (float)card.Position.Y * Puzzle::Config::CardScaleWorld, }; if (!isValid) { cardPos = {x * Puzzle::Config::CardScaleWorld, -5.0f, y * Puzzle::Config::CardScaleWorld}; } tile.EData.Transform.Position = cardPos + WorldPosition; bx::mtxRotateY(tile.EData.Transform.Rotation.M, card.Rotation * bx::kPi * 0.5f); // Covers if (IsValid(staticCard.BaseModelHandle)) { auto& model = GameRendering::Get().Models[staticCard.BaseModelHandle.ModelIdx]; for (int32_t i = 0; i < model.SocketCount; ++i) { auto& cover = level.PuzzleTileCovers.Get(CoverHandles[cardIdx * Puzzle::Config::MaxCoversInTile + i]); cover.EData.Visible = IsActive; cover.EData.ModelH = staticCard.Sockets[i].Model; cover.EData.Transform = tile.EData.Transform; Gen::TranslateLocal(cover.EData.Transform, model.Sockets[i].Pos); Gen::RotateLocal(cover.EData.Transform, Gen::EulerFromRotation(model.Sockets[i].Rot)); } } } } } void Level::ReloadLevelEntities() { LevelEntities.Count = 0; for (int32_t i = 0; i < BX_COUNTOF(BackgroundEntityHandles); ++i) { BackgroundEntityHandles[i] = LevelEntities.New(); auto& levelBgEntity = LevelEntities.Get(BackgroundEntityHandles[i]); levelBgEntity.EData.LoadFromSaved(GetInstance().Player.Config.BackgroundLevelRenderData[i]); } } } // namespace Game