// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "SpriteAtlas.h" #include "Engine/Core/Log.h" #include "Engine/Threading/Threading.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Content/Loading/Tasks/LoadAssetDataTask.h" #include "Engine/Content/Upgraders/TextureAssetUpgrader.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Graphics/GPUDevice.h" const SpriteHandle SpriteHandle::Invalid = { nullptr, INVALID_INDEX }; REGISTER_BINARY_ASSET_WITH_UPGRADER(SpriteAtlas, "FlaxEngine.SpriteAtlas", TextureAssetUpgrader, true); bool SpriteHandle::GetSprite(Sprite* result) const { if (IsValid()) { *result = Atlas->Sprites[Index]; return true; } return false; } bool SpriteHandle::IsValid() const { return Atlas && Index >= 0 && Atlas->Sprites.Count() > Index; } GPUTexture* SpriteHandle::GetAtlasTexture() const { ASSERT(Atlas); return Atlas->GetTexture(); } SpriteAtlas::SpriteAtlas(const SpawnParams& params, const AssetInfo* info) : TextureBase(params, info) { } int32 SpriteAtlas::GetSpritesCount() const { return Sprites.Count(); } Sprite SpriteAtlas::GetSprite(int32 index) const { CHECK_RETURN(index >= 0 && index < Sprites.Count(), Sprite()); return Sprites.Get()[index]; } void SpriteAtlas::GetSpriteArea(int32 index, Rectangle& result) const { CHECK(index >= 0 && index < Sprites.Count()); result = Sprites.Get()[index].Area; } void SpriteAtlas::SetSprite(int32 index, const Sprite& value) { CHECK(index >= 0 && index < Sprites.Count()); Sprites.Get()[index] = value; } SpriteHandle SpriteAtlas::FindSprite(const StringView& name) const { SpriteHandle result(const_cast(this), -1); for (int32 i = 0; i < Sprites.Count(); i++) { if (name == Sprites[i].Name) { result.Index = i; break; } } return result; } SpriteHandle SpriteAtlas::AddSprite(const Sprite& sprite) { const int32 index = Sprites.Count(); Sprites.Add(sprite); return SpriteHandle(this, index); } void SpriteAtlas::RemoveSprite(int32 index) { Sprites.RemoveAt(index); } #if USE_EDITOR bool SpriteAtlas::SaveSprites() { ScopeLock lock(Locker); // Load whole asset if (LoadChunks(ALL_ASSET_CHUNKS)) { return true; } // Prepare asset data AssetInitData data; data.SerializedVersion = 4; data.CustomData.Copy(_texture.GetHeader()); // Write sprites data MemoryWriteStream stream(1024); stream.WriteInt32(1); // Version stream.WriteInt32(Sprites.Count()); // Sprites Count for (Sprite& t : Sprites) { stream.Write(t.Area); stream.WriteString(t.Name, 49); } // Link sprites data (unlink after safe) auto dataChunk = GetOrCreateChunk(15); dataChunk->Data.Link(stream.GetHandle(), stream.GetLength()); // Save (use silent mode to prevent asset reloading) bool saveResult = SaveAsset(data, true); dataChunk->Data.Unlink(); if (saveResult) { LOG(Warning, "Failed to save sprite atlas \'{0}\'.", GetPath()); return true; } return false; } #endif bool SpriteAtlas::LoadSprites(ReadStream& stream) { ScopeLock lock(Locker); // Sprites may be used on rendering thread so lock drawing for a while #if USE_EDITOR GPUDevice* gpuDevice = GPUDevice::Instance; if (gpuDevice) gpuDevice->Locker.Lock(); #endif Sprites.Clear(); int32 tilesVersion, tilesCount; stream.ReadInt32(&tilesVersion); if (tilesVersion != 1) { #if USE_EDITOR if (gpuDevice) gpuDevice->Locker.Unlock(); #endif LOG(Warning, "Invalid tiles version."); return true; } stream.ReadInt32(&tilesCount); Sprites.Resize(tilesCount); for (Sprite& t : Sprites) { stream.Read(t.Area); stream.ReadString(&t.Name, 49); } #if USE_EDITOR if (gpuDevice) gpuDevice->Locker.Unlock(); #endif return false; } Asset::LoadResult SpriteAtlas::load() { auto spritesDataChunk = GetChunk(15); if (spritesDataChunk == nullptr || spritesDataChunk->IsMissing()) return LoadResult::MissingDataChunk; MemoryReadStream spritesData(spritesDataChunk->Get(), spritesDataChunk->Size()); if (LoadSprites(spritesData)) { LOG(Warning, "Cannot load sprites atlas data."); return LoadResult::Failed; } return TextureBase::load(); } void SpriteAtlas::unload(bool isReloading) { Sprites.Resize(0); // Base TextureBase::unload(isReloading); } AssetChunksFlag SpriteAtlas::getChunksToPreload() const { return GET_CHUNK_FLAG(15); }