From 6dba10f18b94d2606498aaac28bd47df632657ef Mon Sep 17 00:00:00 2001 From: SirLynix Date: Fri, 8 Dec 2023 23:12:06 +0100 Subject: [PATCH] Begin to work on ship editor --- include/CommonLib/Ship.hpp | 62 ++++ include/CommonLib/Ship.inl | 47 +++ src/CommonLib/Ship.cpp | 83 +++++ src/Game/States/ShipEditionState.cpp | 535 +++++++++++++++++++++++++++ src/Game/States/ShipEditionState.hpp | 73 ++++ src/Game/States/ShipEditionState.inl | 7 + 6 files changed, 807 insertions(+) create mode 100644 include/CommonLib/Ship.hpp create mode 100644 include/CommonLib/Ship.inl create mode 100644 src/CommonLib/Ship.cpp create mode 100644 src/Game/States/ShipEditionState.cpp create mode 100644 src/Game/States/ShipEditionState.hpp create mode 100644 src/Game/States/ShipEditionState.inl diff --git a/include/CommonLib/Ship.hpp b/include/CommonLib/Ship.hpp new file mode 100644 index 00000000..2d0b8ab1 --- /dev/null +++ b/include/CommonLib/Ship.hpp @@ -0,0 +1,62 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "This Space Of Mine" project +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef TSOM_COMMONLIB_SHIP_HPP +#define TSOM_COMMONLIB_SHIP_HPP + +#include +#include +#include +#include +#include +#include + +namespace tsom +{ + class BlockLibrary; + + class TSOM_COMMONLIB_API Ship : public ChunkContainer + { + public: + Ship(BlockLibrary& blockManager, const Nz::Vector3ui& gridSize, float tileSize); + Ship(const Ship&) = delete; + Ship(Ship&&) = delete; + ~Ship() = default; + + Chunk& AddChunk(const Nz::Vector3ui& indices, const Nz::FunctionRef& initCallback = nullptr); + + inline Nz::Vector3f GetCenter() const override; + inline Chunk* GetChunk(std::size_t chunkIndex) override; + inline const Chunk* GetChunk(std::size_t chunkIndex) const override; + inline Chunk& GetChunk(const Nz::Vector3ui& indices) override; + inline const Chunk& GetChunk(const Nz::Vector3ui& indices) const override; + inline Nz::Vector3ui GetChunkIndices(std::size_t chunkIndex) const; + inline std::size_t GetChunkCount() const override; + + void RemoveChunk(const Nz::Vector3ui& indices); + + Ship& operator=(const Ship&) = delete; + Ship& operator=(Ship&&) = delete; + + static constexpr unsigned int ChunkSize = 32; + + private: + void SetupChunks(BlockLibrary& blockManager); + + struct ChunkData + { + std::unique_ptr chunk; + + NazaraSlot(Chunk, OnBlockUpdated, onUpdated); + }; + + std::vector m_chunks; + }; +} + +#include + +#endif // TSOM_COMMONLIB_SHIP_HPP diff --git a/include/CommonLib/Ship.inl b/include/CommonLib/Ship.inl new file mode 100644 index 00000000..ffd25fa8 --- /dev/null +++ b/include/CommonLib/Ship.inl @@ -0,0 +1,47 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "This Space Of Mine" project +// For conditions of distribution and use, see copyright notice in Config.hpp + +namespace tsom +{ + inline Nz::Vector3f Ship::GetCenter() const + { + return Nz::Vector3f::Zero(); + } + + inline Chunk* Ship::GetChunk(std::size_t chunkIndex) + { + return m_chunks[chunkIndex].chunk.get(); + } + + inline const Chunk* Ship::GetChunk(std::size_t chunkIndex) const + { + return m_chunks[chunkIndex].chunk.get(); + } + + inline Chunk& Ship::GetChunk(const Nz::Vector3ui& indices) + { + return *m_chunks[GetChunkIndex(indices)].chunk; + } + + inline const Chunk& Ship::GetChunk(const Nz::Vector3ui& indices) const + { + return *m_chunks[GetChunkIndex(indices)].chunk; + } + + inline Nz::Vector3ui Ship::GetChunkIndices(std::size_t chunkIndex) const + { + Nz::Vector3ui indices; + indices.x = chunkIndex % ChunkSize; + indices.y = (chunkIndex / ChunkSize) % ChunkSize; + indices.z = chunkIndex / (ChunkSize * ChunkSize); + + return indices; + } + + inline std::size_t Ship::GetChunkCount() const + { + return m_chunks.size(); + } +} + diff --git a/src/CommonLib/Ship.cpp b/src/CommonLib/Ship.cpp new file mode 100644 index 00000000..01d720d8 --- /dev/null +++ b/src/CommonLib/Ship.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "This Space Of Mine" project +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +namespace tsom +{ + Ship::Ship(BlockLibrary& blockManager, const Nz::Vector3ui& gridSize, float tileSize) : + ChunkContainer(gridSize, tileSize) + { + m_chunks.resize(m_chunkCount.x * m_chunkCount.y * m_chunkCount.z); + + SetupChunks(blockManager); + } + + Chunk& Ship::AddChunk(const Nz::Vector3ui& indices, const Nz::FunctionRef& initCallback) + { + std::size_t index = GetChunkIndex(indices); + assert(!m_chunks[index].chunk); + m_chunks[index].chunk = std::make_unique(*this, indices, Nz::Vector3ui{ ChunkSize }, m_tileSize); + + if (initCallback) + m_chunks[index].chunk->InitBlocks(initCallback); + + m_chunks[index].onUpdated.Connect(m_chunks[index].chunk->OnBlockUpdated, [this](Chunk* chunk, const Nz::Vector3ui& /*indices*/, BlockIndex /*newBlock*/) + { + OnChunkUpdated(this, chunk); + }); + + OnChunkAdded(this, m_chunks[index].chunk.get()); + + return *m_chunks[index].chunk; + } + + void Ship::RemoveChunk(const Nz::Vector3ui& indices) + { + std::size_t index = GetChunkIndex(indices); + assert(m_chunks[index].chunk); + + OnChunkRemove(this, m_chunks[index].chunk.get()); + + m_chunks[index].chunk = nullptr; + m_chunks[index].onUpdated.Disconnect(); + } + + void Ship::SetupChunks(BlockLibrary& blockManager) + { + constexpr unsigned int freespace = 5; + + BlockIndex hullIndex = blockManager.GetBlockIndex("hull"); + if (hullIndex == InvalidBlockIndex) + return; + + Chunk& chunk = AddChunk({ 0, 0, 0 }); + + constexpr unsigned int boxSize = 5; + Nz::Vector3ui startPos = chunk.GetSize() / 2 - Nz::Vector3ui(boxSize / 2); + + for (unsigned int z = 0; z < boxSize; ++z) + { + for (unsigned int y = 0; y < boxSize; ++y) + { + for (unsigned int x = 0; x < boxSize; ++x) + { + if (x != 0 && x != boxSize - 1 && + y != 0 && y != boxSize - 1 && + z != 0 && z != boxSize - 1) + { + continue; + } + + chunk.UpdateBlock(startPos + Nz::Vector3ui{ x, y, z }, hullIndex); + } + } + } + } +} diff --git a/src/Game/States/ShipEditionState.cpp b/src/Game/States/ShipEditionState.cpp new file mode 100644 index 00000000..6a699b68 --- /dev/null +++ b/src/Game/States/ShipEditionState.cpp @@ -0,0 +1,535 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "This Space Of Mine" project +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tsom +{ + constexpr std::array s_availableBlocks = { "empty", "hull", "hull2" }; + + ShipEditionState::ShipEditionState(std::shared_ptr stateDataPtr) : + WidgetState(std::move(stateDataPtr)), + m_cameraMovement(false) + { + StateData& stateData = GetStateData(); + auto& filesystem = stateData.app->GetComponent(); + + m_cameraEntity = CreateEntity(); + { + auto& cameraNode = m_cameraEntity.emplace(); + cameraNode.SetPosition(Nz::Vector3f::Up() * 16.f); + + auto passList = filesystem.Load("assets/3d.passlist"); + + auto& cameraComponent = m_cameraEntity.emplace(stateData.renderTarget, std::move(passList)); + cameraComponent.UpdateClearColor(Nz::Color::Gray()); + cameraComponent.UpdateRenderMask(0x0000FFFF); + cameraComponent.UpdateZNear(0.1f); + } + + m_skyboxEntity = CreateEntity(); + { + // Create a new material (custom properties + shaders) for the skybox + Nz::MaterialSettings skyboxSettings; + skyboxSettings.AddValueProperty("BaseColor", Nz::Color::White()); + skyboxSettings.AddTextureProperty("BaseColorMap", Nz::ImageType::Cubemap); + skyboxSettings.AddPropertyHandler(std::make_unique("BaseColorMap", "HasBaseColorTexture")); + skyboxSettings.AddPropertyHandler(std::make_unique("BaseColor")); + + // Setup only a forward pass (using the SkyboxMaterial module) + Nz::MaterialPass forwardPass; + forwardPass.states.depthBuffer = true; + forwardPass.shaders.push_back(std::make_shared(nzsl::ShaderStageType::Fragment | nzsl::ShaderStageType::Vertex, "SkyboxMaterial")); + skyboxSettings.AddPass("ForwardPass", forwardPass); + + // Finalize the material (using SkyboxMaterial module as a reference for shader reflection) + std::shared_ptr skyboxMaterial = std::make_shared(std::move(skyboxSettings), "SkyboxMaterial"); + + // Load skybox + Nz::Image skyboxImage(Nz::ImageType::Cubemap, Nz::PixelFormat::RGBA8_SRGB, 2048, 2048); + skyboxImage.LoadFaceFromImage(Nz::CubemapFace::PositiveX, *filesystem.Load("assets/DeepSpaceGreenSkybox/leftImage.png")); + skyboxImage.LoadFaceFromImage(Nz::CubemapFace::NegativeX, *filesystem.Load("assets/DeepSpaceGreenSkybox/rightImage.png")); + skyboxImage.LoadFaceFromImage(Nz::CubemapFace::PositiveY, *filesystem.Load("assets/DeepSpaceGreenSkybox/upImage.png")); + skyboxImage.LoadFaceFromImage(Nz::CubemapFace::NegativeY, *filesystem.Load("assets/DeepSpaceGreenSkybox/downImage.png")); + skyboxImage.LoadFaceFromImage(Nz::CubemapFace::PositiveZ, *filesystem.Load("assets/DeepSpaceGreenSkybox/frontImage.png")); + skyboxImage.LoadFaceFromImage(Nz::CubemapFace::NegativeZ, *filesystem.Load("assets/DeepSpaceGreenSkybox/backImage.png")); + + std::shared_ptr skyboxTexture = Nz::Texture::CreateFromImage(skyboxImage, *filesystem.GetDefaultResourceParameters()); + + // Instantiate the material to use it, and configure it (texture + cull front faces as the render is from the inside) + std::shared_ptr skyboxMat = skyboxMaterial->Instantiate(); + skyboxMat->SetTextureProperty("BaseColorMap", skyboxTexture); + skyboxMat->UpdatePassesStates([](Nz::RenderStates& states) + { + states.faceCulling = Nz::FaceCulling::Front; + return true; + }); + + // Create a cube mesh with only position + Nz::MeshParams meshPrimitiveParams; + meshPrimitiveParams.vertexDeclaration = Nz::VertexDeclaration::Get(Nz::VertexLayout::XYZ); + + std::shared_ptr skyboxMeshGfx = Nz::GraphicalMesh::Build(Nz::Primitive::Box(Nz::Vector3f::Unit() * 10.f, Nz::Vector2ui(0u), Nz::Matrix4f::Identity(), Nz::Rectf(0.f, 0.f, 1.f, 1.f)), meshPrimitiveParams); + + // Setup the model (mesh + material instance) + std::shared_ptr skyboxModel = std::make_shared(std::move(skyboxMeshGfx)); + skyboxModel->SetMaterial(0, skyboxMat); + + // Attach the model to the entity + m_skyboxEntity.emplace(std::move(skyboxModel), 0x0000FFFF); + + // Setup entity position and attach it to the camera (position only, camera rotation does not impact skybox) + auto& skyboxNode = m_skyboxEntity.emplace(); + skyboxNode.SetInheritRotation(false); + skyboxNode.SetParent(m_cameraEntity); + } + + m_sunLightEntity = CreateEntity(); + { + m_sunLightEntity.emplace(Nz::Vector3f::Zero(), Nz::EulerAnglesf(-30.f, 80.f, 0.f)); + + auto& lightComponent = m_sunLightEntity.emplace(); + auto& dirLight = lightComponent.AddLight(0x0000FFFF); + dirLight.UpdateAmbientFactor(0.05f); + dirLight.EnableShadowCasting(true); + dirLight.UpdateShadowMapSize(2048); + } + + m_ship = std::make_unique(*stateData.blockLibrary, Nz::Vector3ui(20, 20, 20), 2.f); + + m_infoLabel = CreateWidget(); + + m_blockSelectionWidget = CreateWidget(Nz::BoxLayoutOrientation::LeftToRight); + m_blockSelectionWidget->EnableBackground(true); + m_blockSelectionWidget->SetBackgroundColor(Nz::Color(1.f, 1.f, 1.f, 0.8f)); + + for (std::string_view blockName : s_availableBlocks) + { + BlockIndex blockIndex = stateData.blockLibrary->GetBlockIndex(blockName); + if (blockIndex == InvalidBlockIndex) + continue; + + std::shared_ptr inventoryMaterial = Nz::MaterialInstance::Instantiate(Nz::MaterialType::Basic); + inventoryMaterial->SetTextureProperty("BaseColorMap", stateData.blockLibrary->GetPreviewTexture(blockIndex)); + + Nz::ImageButtonWidget* button = m_blockSelectionWidget->Add(inventoryMaterial); + + button->OnButtonTrigger.Connect([this, blockIndex](const Nz::ImageButtonWidget*) + { + m_currentBlock = blockIndex; + }); + } + } + + ShipEditionState::~ShipEditionState() + { + } + + void ShipEditionState::Enter(Nz::StateMachine& fsm) + { + WidgetState::Enter(fsm); + + StateData& stateData = GetStateData(); + m_shipEntities = std::make_unique(*stateData.app, *stateData.world, *m_ship, *stateData.blockLibrary); + + Nz::WindowEventHandler& eventHandler = GetStateData().window->GetEventHandler(); + ConnectSignal(stateData.canvas->OnUnhandledMouseMoved, [&, camAngles = Nz::EulerAnglesf(0.f, 0.f, 0.f)](const Nz::WindowEventHandler*, const Nz::WindowEvent::MouseMoveEvent& event) mutable + { + if (!m_cameraMovement) + return; + + // Gestion de la caméra free-fly (Rotation) + float sensitivity = 0.3f; // Sensibilité de la souris + + // On modifie l'angle de la caméra grâce au déplacement relatif sur X de la souris + camAngles.yaw = camAngles.yaw - event.deltaX * sensitivity; + camAngles.yaw.Normalize(); + + // Idem, mais pour éviter les problèmes de calcul de la matrice de vue, on restreint les angles + camAngles.pitch = Nz::Clamp(camAngles.pitch - event.deltaY * sensitivity, -89.f, 89.f); + + /*auto& playerRotNode = registry.get(playerRotation); + playerRotNode.SetRotation(camAngles);*/ + auto& playerRotNode = m_cameraEntity.get(); + playerRotNode.SetRotation(camAngles); + }); + + ConnectSignal(stateData.canvas->OnUnhandledMouseButtonPressed, [&](const Nz::WindowEventHandler*, const Nz::WindowEvent::MouseButtonEvent& event) + { + if (event.button != Nz::Mouse::Right) + return; + + m_cameraMovement = true; + + Nz::Mouse::SetRelativeMouseMode(true); + }); + + ConnectSignal(stateData.canvas->OnUnhandledMouseButtonReleased, [&](const Nz::WindowEventHandler*, const Nz::WindowEvent::MouseButtonEvent& event) + { + if (event.button != Nz::Mouse::Left && event.button != Nz::Mouse::Right) + return; + + if (event.button == Nz::Mouse::Right) + { + m_cameraMovement = false; + Nz::Mouse::SetRelativeMouseMode(false); + } + + Nz::Vector3f hitPos, hitNormal; + auto filter = [&](const Nz::JoltPhysics3DSystem::RaycastHit& hitInfo) -> std::optional + { + //if (hitInfo.hitEntity != m_planetEntity) + // return std::nullopt; + + hitPos = hitInfo.hitPosition; + hitNormal = hitInfo.hitNormal; + return hitInfo.fraction; + }; + + auto& camera = m_cameraEntity.get(); + + Nz::Vector2f mousePos = Nz::Vector2f(event.x, event.y); + + Nz::Vector3f startPos = camera.Unproject({ mousePos.x, mousePos.y, 0.f }); + Nz::Vector3f endPos = camera.Unproject({ mousePos.x, mousePos.y, 1.f }); + + auto& physSystem = stateData.world->GetSystem(); + if (physSystem.RaycastQuery(startPos, endPos, filter)) + { + if (event.button == Nz::Mouse::Left) + { + float sign = (m_currentBlock != EmptyBlockIndex) ? 1.f : -1.f; + + Nz::Vector3f localPos; + Chunk* chunk = m_ship->GetChunkByPosition(hitPos + sign * hitNormal * m_ship->GetTileSize() * 0.25f, &localPos); + if (!chunk) + return; + + auto coordinates = chunk->ComputeCoordinates(localPos); + if (!coordinates) + return; + + std::lock_guard lock(m_integrityMutex); + + chunk->UpdateBlock(*coordinates, m_currentBlock); + CheckHullIntegrity(); + } + } + }); + + CheckHullIntegrity(); + } + + void ShipEditionState::Leave(Nz::StateMachine& fsm) + { + if (m_integrityThread.joinable()) + m_integrityThread.join(); + + WidgetState::Leave(fsm); + + Nz::Mouse::SetRelativeMouseMode(false); + + m_shipEntities.reset(); + } + + bool ShipEditionState::Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) + { + constexpr unsigned int ChunkBlockCount = Ship::ChunkSize * Ship::ChunkSize * Ship::ChunkSize; + + WidgetState::Update(fsm, elapsedTime); + + m_shipEntities->Update(); + + auto& debugDrawer = GetStateData().world->GetSystem().GetFramePipeline().GetDebugDrawer(); + for (const Area& area : m_shipAreas) + { + for (std::size_t blockIndex : area.blocks.IterBits()) + { + std::size_t localBlockIndex = blockIndex % ChunkBlockCount; + std::size_t chunkIndex = blockIndex / ChunkBlockCount; + Chunk* chunk = m_ship->GetChunk(chunkIndex); + if (!chunk) + continue; + + Nz::Vector3f offset = m_ship->GetChunkOffset(m_ship->GetChunkIndices(chunkIndex)); + Nz::EnumArray blockCorners = chunk->ComputeVoxelCorners(chunk->GetBlockIndices(localBlockIndex)); + for (Nz::Vector3f& corner : blockCorners) + corner += offset; + + debugDrawer.DrawBoxCorners(blockCorners, Nz::Color::Blue()); + } + } + + float cameraSpeed = (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::LShift)) ? 50.f : 10.f; + float updateTime = elapsedTime.AsSeconds(); + + auto& cameraNode = m_cameraEntity.get(); + if (m_cameraMovement) + { + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Space)) + cameraNode.Move(Nz::Vector3f::Up() * cameraSpeed * updateTime, Nz::CoordSys::Global); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Z)) + cameraNode.Move(Nz::Vector3f::Forward() * cameraSpeed * updateTime, Nz::CoordSys::Local); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::S)) + cameraNode.Move(Nz::Vector3f::Backward() * cameraSpeed * updateTime, Nz::CoordSys::Local); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::Q)) + cameraNode.Move(Nz::Vector3f::Left() * cameraSpeed * updateTime, Nz::CoordSys::Local); + + if (Nz::Keyboard::IsKeyPressed(Nz::Keyboard::VKey::D)) + cameraNode.Move(Nz::Vector3f::Right() * cameraSpeed * updateTime, Nz::CoordSys::Local); + } + + DrawHoveredFace(); + + return true; + } + + auto ShipEditionState::BuildArea(std::size_t firstBlockIndex, Nz::Bitset& remainingBlocks) const -> Area + { + constexpr unsigned int ChunkBlockCount = Ship::ChunkSize * Ship::ChunkSize * Ship::ChunkSize; + + std::size_t chunkCount = m_ship->GetChunkCount(); + + Nz::Bitset areaBlocks; + + std::vector candidateBlocks; + candidateBlocks.push_back(firstBlockIndex); + + while (!candidateBlocks.empty()) + { + std::size_t blockIndex = candidateBlocks.back(); + candidateBlocks.pop_back(); + + areaBlocks.UnboundedSet(blockIndex); + + std::size_t localBlockIndex = blockIndex % ChunkBlockCount; + std::size_t chunkIndex = blockIndex / ChunkBlockCount; + Chunk* chunk = m_ship->GetChunk(chunkIndex); + if (!chunk) + continue; + + remainingBlocks[blockIndex] = false; + + Nz::Vector3ui chunkSize = chunk->GetSize(); + + BlockIndex block = chunk->GetBlockContent(localBlockIndex); + bool isEmpty = block == EmptyBlockIndex; + + auto AddCandidateBlock = [&](const Nz::Vector3ui& blockIndices) + { + std::size_t blockIndex = chunk->GetBlockIndex(blockIndices); + if (remainingBlocks[blockIndex]) + { + if (!isEmpty) + { + // Non-empty blocks can look at other non-empty blocks + if (chunk->GetBlockContent(blockIndex) != EmptyBlockIndex) + candidateBlocks.push_back(blockIndex); + } + else + candidateBlocks.push_back(blockIndex); + } + }; + + Nz::Vector3ui blockIndices = chunk->GetBlockIndices(blockIndex); + for (int zOffset = -1; zOffset <= 1; ++zOffset) + { + for (int yOffset = -1; yOffset <= 1; ++yOffset) + { + for (int xOffset = -1; xOffset <= 1; ++xOffset) + { + if (xOffset == 0 && yOffset == 0 && zOffset == 0) + continue; + + Nz::Vector3i candidateIndicesSigned = Nz::Vector3i(blockIndices); + candidateIndicesSigned.x += xOffset; + candidateIndicesSigned.y += yOffset; + candidateIndicesSigned.z += zOffset; + + Nz::Vector3ui candidateIndices = Nz::Vector3ui(candidateIndicesSigned); + if (candidateIndices.x < chunkSize.x && candidateIndices.y < chunkSize.y && candidateIndices.z < chunkSize.z) + AddCandidateBlock(candidateIndices); + } + } + } + } + + Area area; + area.blocks = std::move(areaBlocks); + + return area; + } + + void ShipEditionState::CheckHullIntegrity() + { + if (m_integrityThread.joinable()) + m_integrityThread.join(); + + m_integrityThread = std::thread([this] + { + constexpr unsigned int ChunkBlockCount = Ship::ChunkSize * Ship::ChunkSize * Ship::ChunkSize; + + std::lock_guard lock(m_integrityMutex); + + Nz::HighPrecisionClock clock; + + std::size_t chunkCount = m_ship->GetChunkCount(); + + Nz::Bitset remainingBlocks(chunkCount * ChunkBlockCount, true); + + // Find first candidate (= a random empty block) + auto FindFirstCandidate = [&] + { + for (std::size_t chunkIndex = 0; chunkIndex < chunkCount; ++chunkIndex) + { + const Chunk* chunk = m_ship->GetChunk(chunkIndex); + if (!chunk) + continue; //< TODO: Empty chunks should be handled in visitedBlock/candidateBlock (?) + + const Nz::Bitset& collisionCellMask = chunk->GetCollisionCellMask(); + for (std::size_t i = 0; i < collisionCellMask.GetBlockCount(); ++i) + { + Nz::UInt64 mask = collisionCellMask.GetBlock(i); + mask = ~mask; + + unsigned int fsb = Nz::FindFirstBit(mask); + if (fsb != 0) + { + unsigned int localBlockIndex = i * Nz::BitCount() + fsb - 1; + return chunkIndex * ChunkBlockCount + localBlockIndex; + } + } + } + + return std::numeric_limits::max(); + }; + + m_shipAreas.clear(); + std::size_t firstCandidate = FindFirstCandidate(); + if (firstCandidate != std::numeric_limits::max()) + { + Area outside = BuildArea(FindFirstCandidate(), remainingBlocks); + + while (remainingBlocks.TestAny()) + m_shipAreas.push_back(BuildArea(remainingBlocks.FindFirst(), remainingBlocks)); + } + + fmt::print("integrity check took {}\n", fmt::streamed(clock.GetElapsedTime())); + + Nz::RichTextDrawer richText; + Nz::RichTextBuilder builder(richText); + builder << Nz::Color::Yellow() << "Hull integrity: "; + + if (m_shipAreas.empty()) + builder << Nz::Color::Red() << "KO"; + else + builder << Nz::Color::Green() << "OK"; + + builder << Nz::Color::White() << " (" << std::to_string(m_shipAreas.size()) << " area(s))"; + + UpdateStatus(richText); + }); + } + + void ShipEditionState::DrawHoveredFace() + { + auto& camera = m_cameraEntity.get(); + + Nz::Vector2f mousePos = Nz::Vector2f(Nz::Mouse::GetPosition(*GetStateData().window)); + + Nz::Vector3f startPos = camera.Unproject({ mousePos.x, mousePos.y, 0.f }); + Nz::Vector3f endPos = camera.Unproject({ mousePos.x, mousePos.y, 1.f }); + + // Raycast + auto& physSystem = GetStateData().world->GetSystem(); + Nz::Vector3f hitPos, hitNormal; + if (physSystem.RaycastQuery(startPos, endPos, [&](const Nz::JoltPhysics3DSystem::RaycastHit& hitInfo) -> std::optional + { + //if (hitInfo.hitEntity != m_planetEntity) + // return std::nullopt; + + hitPos = hitInfo.hitPosition; + hitNormal = hitInfo.hitNormal; + return hitInfo.fraction; + })) + { + Nz::Vector3f localPos; + if (const Chunk* chunk = m_ship->GetChunkByPosition(hitPos - hitNormal * m_ship->GetTileSize() * 0.25f, &localPos)) + { + auto coordinates = chunk->ComputeCoordinates(localPos); + if (coordinates) + { + auto cornerPos = chunk->ComputeVoxelCorners(*coordinates); + Nz::Vector3f offset = m_ship->GetChunkOffset(chunk->GetIndices()); + + constexpr Nz::EnumArray> directionToCorners = { + // Back + std::array{ Nz::BoxCorner::NearLeftTop, Nz::BoxCorner::NearRightTop, Nz::BoxCorner::NearRightBottom, Nz::BoxCorner::NearLeftBottom }, + // Down + std::array{ Nz::BoxCorner::NearLeftBottom, Nz::BoxCorner::FarLeftBottom, Nz::BoxCorner::FarRightBottom, Nz::BoxCorner::NearRightBottom }, + // Front + std::array{ Nz::BoxCorner::FarLeftTop, Nz::BoxCorner::FarRightTop, Nz::BoxCorner::FarRightBottom, Nz::BoxCorner::FarLeftBottom }, + // Left + std::array{ Nz::BoxCorner::FarLeftTop, Nz::BoxCorner::NearLeftTop, Nz::BoxCorner::NearLeftBottom, Nz::BoxCorner::FarLeftBottom }, + // Right + std::array{ Nz::BoxCorner::FarRightTop, Nz::BoxCorner::NearRightTop, Nz::BoxCorner::NearRightBottom, Nz::BoxCorner::FarRightBottom }, + // Up + std::array{ Nz::BoxCorner::NearLeftTop, Nz::BoxCorner::FarLeftTop, Nz::BoxCorner::FarRightTop, Nz::BoxCorner::NearRightTop } + }; + + auto& corners = directionToCorners[DirectionFromNormal(hitNormal)]; + + auto& debugDrawer = GetStateData().world->GetSystem().GetFramePipeline().GetDebugDrawer(); + debugDrawer.DrawLine(offset + cornerPos[corners[0]], offset + cornerPos[corners[1]], Nz::Color::Green()); + debugDrawer.DrawLine(offset + cornerPos[corners[1]], offset + cornerPos[corners[2]], Nz::Color::Green()); + debugDrawer.DrawLine(offset + cornerPos[corners[2]], offset + cornerPos[corners[3]], Nz::Color::Green()); + debugDrawer.DrawLine(offset + cornerPos[corners[3]], offset + cornerPos[corners[0]], Nz::Color::Green()); + } + } + } + } + + void ShipEditionState::LayoutWidgets(const Nz::Vector2f& newSize) + { + m_blockSelectionWidget->Resize(Nz::Vector2f(s_availableBlocks.size() * 128.f, 128.f)); + m_blockSelectionWidget->SetPosition({ 0.f, newSize.y * 0.5f - m_blockSelectionWidget->GetHeight() * 0.5f, 0.f }); + + m_infoLabel->SetPosition(newSize * Nz::Vector2f(0.5f, 0.98f) - m_infoLabel->GetSize() * 0.5f); + } + + void ShipEditionState::UpdateStatus(const Nz::AbstractTextDrawer& textDrawer) + { + m_infoLabel->UpdateText(textDrawer); + m_infoLabel->Center(); + m_infoLabel->Show(); + + LayoutWidgets(GetStateData().canvas->GetSize()); + } +} diff --git a/src/Game/States/ShipEditionState.hpp b/src/Game/States/ShipEditionState.hpp new file mode 100644 index 00000000..0243c2d5 --- /dev/null +++ b/src/Game/States/ShipEditionState.hpp @@ -0,0 +1,73 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "This Space Of Mine" project +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef TSOM_CLIENT_SHIPEDITIONSTATE_HPP +#define TSOM_CLIENT_SHIPEDITIONSTATE_HPP + +#include +#include +#include +#include +#include +#include + +namespace Nz +{ + class AbstractTextDrawer; + class BoxLayout; + class LabelWidget; + class ScrollAreaWidget; +} + +namespace tsom +{ + class ShipEditionState : public WidgetState + { + public: + ShipEditionState(std::shared_ptr stateDataPtr); + ShipEditionState(const ShipEditionState&) = delete; + ShipEditionState(ShipEditionState&&) = delete; + ~ShipEditionState(); + + void Enter(Nz::StateMachine& fsm) override; + void Leave(Nz::StateMachine& fsm) override; + bool Update(Nz::StateMachine& fsm, Nz::Time elapsedTime) override; + + ShipEditionState& operator=(const ShipEditionState&) = delete; + ShipEditionState& operator=(ShipEditionState&&) = delete; + + private: + struct Area; + + Area BuildArea(std::size_t firstBlockIndex, Nz::Bitset& remainingBlocks) const; + void CheckHullIntegrity(); + void DrawHoveredFace(); + void LayoutWidgets(const Nz::Vector2f& newSize) override; + void UpdateStatus(const Nz::AbstractTextDrawer& textDrawer); + + struct Area + { + Nz::Bitset blocks; + }; + + BlockIndex m_currentBlock = EmptyBlockIndex; + entt::handle m_cameraEntity; + entt::handle m_skyboxEntity; + entt::handle m_sunLightEntity; + std::mutex m_integrityMutex; + std::thread m_integrityThread; + std::vector m_shipAreas; + std::unique_ptr m_ship; + std::unique_ptr m_shipEntities; + Nz::BoxLayout* m_blockSelectionWidget; + Nz::LabelWidget* m_infoLabel; + bool m_cameraMovement; + }; +} + +#include + +#endif // TSOM_CLIENT_SHIPEDITIONSTATE_HPP diff --git a/src/Game/States/ShipEditionState.inl b/src/Game/States/ShipEditionState.inl new file mode 100644 index 00000000..4c2eb238 --- /dev/null +++ b/src/Game/States/ShipEditionState.inl @@ -0,0 +1,7 @@ +// Copyright (C) 2023 Jérôme "Lynix" Leclercq (lynix680@gmail.com) +// This file is part of the "This Space Of Mine" project +// For conditions of distribution and use, see copyright notice in Config.hpp + +namespace tsom +{ +}