Files
o3de/Gems/LyShine/Code/Source/Sprite.cpp
T
2021-03-08 14:30:57 -08:00

852 lines
30 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "LyShine_precompiled.h"
#include "Sprite.h"
#include <ITexture.h>
#include <CryPath.h>
#include <IRenderer.h>
#include <ISerialize.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <LyShine/Bus/Sprite/UiSpriteBus.h>
namespace
{
const char* const spriteExtension = "sprite";
// Increment this when the Sprite Serialize(TSerialize) function
// changes to be incompatible with previous data
uint32 spriteFileVersionNumber = 2;
const char* spriteVersionNumberTag = "versionNumber";
const char* allowedSpriteTextureExtensions[] = {
"tif", "jpg", "jpeg", "tga", "bmp", "png", "gif", "dds"
};
const int numAllowedSpriteTextureExtensions = AZ_ARRAY_SIZE(allowedSpriteTextureExtensions);
bool IsValidSpriteTextureExtension(const char* extension)
{
for (int i = 0; i < numAllowedSpriteTextureExtensions; ++i)
{
if (strcmp(extension, allowedSpriteTextureExtensions[i]) == 0)
{
return true;
}
}
return false;
}
bool ReplaceSpriteExtensionWithTextureExtension(const string& spritePath, string& texturePath)
{
string testPath;
for (int i = 0; i < numAllowedSpriteTextureExtensions; ++i)
{
testPath = PathUtil::ReplaceExtension(spritePath, allowedSpriteTextureExtensions[i]);
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
if (fileIO && fileIO->Exists(testPath.c_str()))
{
texturePath = testPath;
return true;
}
}
return false;
}
bool GetAssetPaths(const string& pathname, string& spritePath, string& texturePath)
{
// the input string could be in any form. So make it normalized
// NOTE: it should not be a full path at this point. If called from the UI editor it will
// have been transformed to a game path. If being called with a hard coded path it should be a
// game path already - it is not good for code to be using full paths.
AZStd::string assetPath(pathname.c_str());
EBUS_EVENT(AzFramework::ApplicationRequests::Bus, NormalizePath, assetPath);
string gamePath = assetPath.c_str();
// check the extension and work out the pathname of the sprite file and the texture file
// currently it works if the input path is either a sprite file or a texture file
const char* extension = PathUtil::GetExt(gamePath.c_str());
if (strcmp(extension, spriteExtension) == 0)
{
spritePath = gamePath;
// look for a texture file with the same name
if (!ReplaceSpriteExtensionWithTextureExtension(spritePath, texturePath))
{
gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
gamePath.c_str(), "No texture file found for sprite: %s, no sprite will be used", gamePath.c_str());
return false;
}
}
else if (IsValidSpriteTextureExtension(extension))
{
texturePath = gamePath;
spritePath = PathUtil::ReplaceExtension(gamePath, spriteExtension);
}
else
{
gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
gamePath.c_str(), "Invalid file extension for sprite: %s, no sprite will be used", gamePath.c_str());
return false;
}
return true;
}
//! \brief Reads a Vec2 tuple (as a string) into an AZ::Vector2
//!
//! Example XML string data: "1.0 2.0"
void SerializeAzVector2(TSerialize ser, const char* attributeName, AZ::Vector2& azVec2)
{
if (ser.IsReading())
{
string stringVal;
ser.Value(attributeName, stringVal);
stringVal.replace(',', ' ');
char* pEnd = nullptr;
float uVal = strtof(stringVal.c_str(), &pEnd);
float vVal = strtof(pEnd, nullptr);
azVec2.Set(uVal, vVal);
}
else
{
Vec2 legacyVec2(azVec2.GetX(), azVec2.GetY());
ser.Value(attributeName, legacyVec2);
}
}
//! \return The number of child <Cell> tags off the <SpriteSheet> parent tag.
int GetNumSpriteSheetCellTags(const XmlNodeRef& root)
{
int numCellTags = 0;
XmlNodeRef spriteSheetTag = root->findChild("SpriteSheet");
if (spriteSheetTag)
{
numCellTags = spriteSheetTag->getChildCount();
}
return numCellTags;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// STATIC MEMBER DATA
////////////////////////////////////////////////////////////////////////////////////////////////////
CSprite::CSpriteHashMap* CSprite::s_loadedSprites;
AZStd::string CSprite::s_emptyString;
////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
CSprite::CSprite()
: m_texture(nullptr)
, m_numSpriteSheetCellTags(0)
, m_atlas(nullptr)
{
AddRef();
TextureAtlasNamespace::TextureAtlasNotificationBus::Handler::BusConnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
CSprite::~CSprite()
{
ReleaseTexture(m_texture);
s_loadedSprites->erase(m_pathname);
TextureAtlasNamespace::TextureAtlasNotificationBus::Handler::BusDisconnect();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const string& CSprite::GetPathname() const
{
return m_pathname;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const string& CSprite::GetTexturePathname() const
{
return m_texturePathname;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
ISprite::Borders CSprite::GetBorders() const
{
return m_borders;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::SetBorders(Borders borders)
{
m_borders = borders;
NotifyChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::SetCellBorders(int cellIndex, Borders borders)
{
if (CellIndexWithinRange(cellIndex))
{
m_spriteSheetCells[cellIndex].borders = borders;
NotifyChanged();
}
else
{
SetBorders(borders);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
ITexture* CSprite::GetTexture()
{
// Prioritize usage of an atlas
if (m_atlas)
{
return m_atlas->GetTexture();
}
if (!m_texture && !m_pathname.empty())
{
// the render target texture may not have existed when the sprite was created
m_texture = gEnv->pRenderer->EF_GetTextureByName(m_pathname.c_str());
if (m_texture)
{
// increase the reference count to this texture so it doesn't get removed while
// we are using it
m_texture->AddRef();
}
}
return m_texture;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::Serialize(TSerialize ser)
{
// When reading, get sprite-sheet info from XML tag data, otherwise get
// it from this sprite object directly.
const bool hasSpriteSheetCells = ser.IsReading() ? m_numSpriteSheetCellTags > 0 : !GetSpriteSheetCells().empty();
if (!hasSpriteSheetCells && ser.BeginOptionalGroup("Sprite", true))
{
ser.Value("m_left", m_borders.m_left);
ser.Value("m_right", m_borders.m_right);
ser.Value("m_top", m_borders.m_top);
ser.Value("m_bottom", m_borders.m_bottom);
ser.EndGroup();
}
if (hasSpriteSheetCells && ser.BeginOptionalGroup("SpriteSheet", true))
{
const int numSpriteSheetCells = ser.IsReading() ? m_numSpriteSheetCellTags : GetSpriteSheetCells().size();
for (int i = 0; i < numSpriteSheetCells; ++i)
{
ser.BeginOptionalGroup("Cell", true);
if (ser.IsReading())
{
m_spriteSheetCells.push_back(SpriteSheetCell());
}
string aliasTemp;
if (ser.IsReading())
{
ser.Value("alias", aliasTemp);
m_spriteSheetCells[i].alias = aliasTemp.c_str();
}
else
{
aliasTemp = m_spriteSheetCells[i].alias.c_str();
ser.Value("alias", aliasTemp);
}
SerializeAzVector2(ser, "topLeft", m_spriteSheetCells[i].uvCellCoords.TopLeft());
SerializeAzVector2(ser, "topRight", m_spriteSheetCells[i].uvCellCoords.TopRight());
SerializeAzVector2(ser, "bottomRight", m_spriteSheetCells[i].uvCellCoords.BottomRight());
SerializeAzVector2(ser, "bottomLeft", m_spriteSheetCells[i].uvCellCoords.BottomLeft());
if (ser.BeginOptionalGroup("Sprite", true))
{
ser.Value("m_left", m_spriteSheetCells[i].borders.m_left);
ser.Value("m_right", m_spriteSheetCells[i].borders.m_right);
ser.Value("m_top", m_spriteSheetCells[i].borders.m_top);
ser.Value("m_bottom", m_spriteSheetCells[i].borders.m_bottom);
ser.EndGroup();
}
ser.EndGroup();
}
ser.EndGroup();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool CSprite::SaveToXml(const string& pathname)
{
// NOTE: The input pathname has to be a path that can used to save - so not an Asset ID
// because of this we do not store the pathname
XmlNodeRef root = GetISystem()->CreateXmlNode("Sprite");
std::unique_ptr<IXmlSerializer> pSerializer(GetISystem()->GetXmlUtils()->CreateXmlSerializer());
ISerialize* pWriter = pSerializer->GetWriter(root);
TSerialize ser = TSerialize(pWriter);
ser.Value(spriteVersionNumberTag, spriteFileVersionNumber);
Serialize(ser);
return root->saveToFile(pathname);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool CSprite::AreBordersZeroWidth() const
{
return (m_borders.m_left == 0 && m_borders.m_right == 1 && m_borders.m_top == 0 && m_borders.m_bottom == 1);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool CSprite::AreCellBordersZeroWidth(int cellIndex) const
{
if (CellIndexWithinRange(cellIndex))
{
return m_spriteSheetCells[cellIndex].borders.m_left == 0
&& m_spriteSheetCells[cellIndex].borders.m_right == 1
&& m_spriteSheetCells[cellIndex].borders.m_top == 0
&& m_spriteSheetCells[cellIndex].borders.m_bottom == 1;
}
else
{
return AreBordersZeroWidth();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Vector2 CSprite::GetSize()
{
ITexture* texture = GetTexture();
if (texture)
{
if (m_atlas)
{
return AZ::Vector2(static_cast<float>(m_atlasCoordinates.GetWidth()), static_cast<float>(m_atlasCoordinates.GetHeight()));
}
return AZ::Vector2(static_cast<float>(texture->GetWidth()), static_cast<float>(texture->GetHeight()));
}
else
{
return AZ::Vector2(0.0f, 0.0f);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Vector2 CSprite::GetCellSize(int cellIndex)
{
AZ::Vector2 textureSize(GetSize());
if (CellIndexWithinRange(cellIndex))
{
// Assume top width is same as bottom width
const float normalizedCellWidth =
m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() -
m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX();
// Similar, assume height of cell is same for left and right sides
const float normalizedCellHeight =
m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() -
m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY();
textureSize.SetX(textureSize.GetX() * normalizedCellWidth);
textureSize.SetY(textureSize.GetY() * normalizedCellHeight);
}
return textureSize;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const ISprite::SpriteSheetCellContainer& CSprite::GetSpriteSheetCells() const
{
return m_spriteSheetCells;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::SetSpriteSheetCells(const SpriteSheetCellContainer& cells)
{
m_spriteSheetCells = cells;
NotifyChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::ClearSpriteSheetCells()
{
m_spriteSheetCells.clear();
NotifyChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::AddSpriteSheetCell(const SpriteSheetCell& spriteSheetCell)
{
m_spriteSheetCells.push_back(spriteSheetCell);
NotifyChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
AZ::Vector2 CSprite::GetCellUvSize(int cellIndex) const
{
AZ::Vector2 result(1.0f, 1.0f);
if (CellIndexWithinRange(cellIndex))
{
result.SetX(m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() - m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX());
result.SetY(m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() - m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY());
}
if (m_atlas)
{
result.SetX(result.GetX() * m_atlasCoordinates.GetWidth() / m_atlas->GetWidth());
result.SetY(result.GetY() * m_atlasCoordinates.GetHeight() / m_atlas->GetHeight());
}
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiTransformInterface::RectPoints CSprite::GetCellUvCoords(int cellIndex) const
{
if (CellIndexWithinRange(cellIndex))
{
if (m_atlas)
{
return UiTransformInterface::RectPoints(
static_cast<float>(m_atlasCoordinates.GetLeft() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetX() * m_atlasCoordinates.GetWidth()))
/ m_atlas->GetWidth(),
static_cast<float>(m_atlasCoordinates.GetLeft() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopRight().GetX() * m_atlasCoordinates.GetWidth()))
/ m_atlas->GetWidth(),
static_cast<float>(m_atlasCoordinates.GetTop() + (m_spriteSheetCells[cellIndex].uvCellCoords.TopLeft().GetY() * m_atlasCoordinates.GetHeight()))
/ m_atlas->GetHeight(),
static_cast<float>(m_atlasCoordinates.GetTop() + (m_spriteSheetCells[cellIndex].uvCellCoords.BottomLeft().GetY() * m_atlasCoordinates.GetHeight()))
/ m_atlas->GetHeight());
}
return m_spriteSheetCells[cellIndex].uvCellCoords;
}
else if (m_atlas)
{
return UiTransformInterface::RectPoints(
static_cast<float>(m_atlasCoordinates.GetLeft()) / m_atlas->GetWidth(),
static_cast<float>(m_atlasCoordinates.GetRight()) / m_atlas->GetWidth(),
static_cast<float>(m_atlasCoordinates.GetTop()) / m_atlas->GetHeight(),
static_cast<float>(m_atlasCoordinates.GetBottom()) / m_atlas->GetHeight());
}
static UiTransformInterface::RectPoints rectPoints(0.0f, 1.0f, 0.0f, 1.0f);
return rectPoints;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
UiTransformInterface::RectPoints CSprite::GetSourceCellUvCoords(int cellIndex) const
{
if (CellIndexWithinRange(cellIndex))
{
return m_spriteSheetCells[cellIndex].uvCellCoords;
}
static UiTransformInterface::RectPoints rectPoints(0.0f, 1.0f, 0.0f, 1.0f);
return rectPoints;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
ISprite::Borders CSprite::GetCellUvBorders(int cellIndex) const
{
if (CellIndexWithinRange(cellIndex))
{
return m_spriteSheetCells[cellIndex].borders;
}
return m_borders;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
ISprite::Borders CSprite::GetTextureSpaceCellUvBorders(int cellIndex) const
{
Borders textureSpaceBorders(m_borders);
if (CellIndexWithinRange(cellIndex))
{
const float cellWidth = GetCellUvSize(cellIndex).GetX();
const float cellMinUCoord = GetCellUvCoords(cellIndex).TopLeft().GetX();
const float cellNormalizedLeftBorder = GetCellUvBorders(cellIndex).m_left * cellWidth;
textureSpaceBorders.m_left = cellNormalizedLeftBorder;
const float cellMaxUCoord = GetCellUvCoords(cellIndex).TopRight().GetX();
const float cellNormalizedRightBorder = GetCellUvBorders(cellIndex).m_right * cellWidth;
textureSpaceBorders.m_right = cellNormalizedRightBorder;
const float cellHeight = GetCellUvSize(cellIndex).GetY();
const float cellMinVCoord = GetCellUvCoords(cellIndex).TopLeft().GetY();
const float cellNormalizedTopBorder = GetCellUvBorders(cellIndex).m_top * cellHeight;
textureSpaceBorders.m_top = cellNormalizedTopBorder;
const float cellMaxVCoord = GetCellUvCoords(cellIndex).BottomLeft().GetY();
const float cellNormalizedBottomBorder = GetCellUvBorders(cellIndex).m_bottom * cellHeight;
textureSpaceBorders.m_bottom = cellNormalizedBottomBorder;
}
return textureSpaceBorders;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
const AZStd::string& CSprite::GetCellAlias(int cellIndex) const
{
if (CellIndexWithinRange(cellIndex))
{
return m_spriteSheetCells[cellIndex].alias;
}
return s_emptyString;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::SetCellAlias(int cellIndex, const AZStd::string& cellAlias)
{
if (CellIndexWithinRange(cellIndex))
{
m_spriteSheetCells[cellIndex].alias = cellAlias;
NotifyChanged();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool CSprite::IsSpriteSheet() const
{
return m_spriteSheetCells.size() > 1;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::OnAtlasLoaded(const TextureAtlasNamespace::TextureAtlas* atlas)
{
if (!m_atlas)
{
m_atlasCoordinates = atlas->GetAtlasCoordinates(m_pathname.c_str());
if (m_atlasCoordinates.GetWidth() > 0)
{
m_atlas = atlas;
// Release the non-atlas version of the texture
ReleaseTexture(m_texture);
NotifyChanged();
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::OnAtlasUnloaded(const TextureAtlasNamespace::TextureAtlas* atlas)
{
if (atlas == m_atlas)
{
m_atlas = nullptr;
TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(m_atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, m_pathname.c_str());
if (m_atlas)
{
m_atlasCoordinates = m_atlas->GetAtlasCoordinates(m_pathname.c_str());
}
else
{
// No replacement atlas found
// load the texture file
LoadTexture(m_texturePathname, m_pathname, m_texture);
}
}
NotifyChanged();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
int CSprite::GetCellIndexFromAlias(const AZStd::string& cellAlias) const
{
int result = 0;
int indexIter = 0;
for (auto spriteCell : m_spriteSheetCells)
{
if (cellAlias == spriteCell.alias)
{
result = indexIter;
break;
}
++indexIter;
}
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC STATIC MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::Initialize()
{
s_loadedSprites = new CSpriteHashMap;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::Shutdown()
{
delete s_loadedSprites;
s_loadedSprites = nullptr;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
CSprite* CSprite::LoadSprite(const string& pathname)
{
string spritePath;
string texturePath;
bool validAssetPaths = GetAssetPaths(pathname, spritePath, texturePath);
if (!validAssetPaths)
{
return nullptr;
}
// test if the sprite is already loaded, if so return loaded sprite
auto result = s_loadedSprites->find(spritePath);
CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
if (loadedSprite)
{
loadedSprite->AddRef();
return loadedSprite;
}
ITexture* texture = nullptr;
// Try to use a texture atlas instead
TextureAtlasNamespace::TextureAtlas* atlas = nullptr;
TextureAtlasNamespace::AtlasCoordinates atlasCoordinates;
TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, texturePath.c_str());
if (atlas)
{
atlasCoordinates = atlas->GetAtlasCoordinates(texturePath.c_str());
}
else
{
// load the texture file
if (!LoadTexture(texturePath, spritePath, texture))
{
return nullptr;
}
}
// create Sprite object
CSprite* sprite = new CSprite;
sprite->m_texture = texture;
sprite->m_pathname = spritePath;
sprite->m_texturePathname = texturePath;
sprite->m_atlas = atlas;
sprite->m_atlasCoordinates = atlasCoordinates;
// try to load the sprite side-car file if it exists, it is optional and if it does not
// exist we just stay with default values
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
if (fileIO && fileIO->Exists(sprite->m_pathname.c_str()))
{
sprite->LoadFromXmlFile();
}
// add sprite to list of loaded sprites
(*s_loadedSprites)[sprite->m_pathname] = sprite;
return sprite;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
CSprite* CSprite::CreateSprite(const string& renderTargetName)
{
// test if the sprite is already loaded, if so return loaded sprite
auto result = s_loadedSprites->find(renderTargetName);
CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
if (loadedSprite)
{
loadedSprite->AddRef();
return loadedSprite;
}
// create Sprite object
CSprite* sprite = new CSprite;
// the render target texture may not exist yet in which case we will need to load it later
sprite->m_texture = gEnv->pRenderer->EF_GetTextureByName(renderTargetName.c_str());
if (sprite->m_texture)
{
// increase the reference count on this render target texture so it doesn't get deleted
// while we are using it
sprite->m_texture->AddRef();
}
sprite->m_pathname = renderTargetName;
sprite->m_texturePathname.clear();
// add sprite to list of loaded sprites
(*s_loadedSprites)[sprite->m_pathname] = sprite;
return sprite;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool CSprite::DoesSpriteTextureAssetExist(const AZStd::string& pathname)
{
string spritePath;
string texturePath;
bool validAssetPaths = GetAssetPaths(pathname.c_str(), spritePath, texturePath);
if (!validAssetPaths)
{
return false;
}
// Check if the sprite is already loaded
auto result = s_loadedSprites->find(spritePath);
CSprite* loadedSprite = (result == s_loadedSprites->end()) ? nullptr : result->second;
if (loadedSprite)
{
return true;
}
// Try to use a texture atlas instead
TextureAtlasNamespace::TextureAtlas* atlas = nullptr;
TextureAtlasNamespace::AtlasCoordinates atlasCoordinates;
TextureAtlasNamespace::TextureAtlasRequestBus::BroadcastResult(atlas, &TextureAtlasNamespace::TextureAtlasRequests::FindAtlasContainingImage, texturePath.c_str());
if (atlas)
{
return true;
}
// Check if the texture asset exists
string ddsTexturePath = PathUtil::ReplaceExtension(texturePath, ".dds");
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
if (fileIO && fileIO->Exists(ddsTexturePath.c_str()))
{
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::ReplaceSprite(ISprite** baseSprite, ISprite* newSprite)
{
if (baseSprite)
{
if (newSprite)
{
newSprite->AddRef();
}
SAFE_RELEASE(*baseSprite);
*baseSprite = newSprite;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool CSprite::LoadTexture(const string& texturePathname, const string& pathname, ITexture*& texture)
{
uint32 loadTextureFlags = (FT_USAGE_ALLOWREADSRGB | FT_DONT_STREAM);
texture = gEnv->pRenderer->EF_LoadTexture(texturePathname.c_str(), loadTextureFlags);
if (!texture || !texture->IsTextureLoaded())
{
gEnv->pSystem->Warning(
VALIDATOR_MODULE_SHINE,
VALIDATOR_WARNING,
VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
texturePathname.c_str(),
"No texture file found for sprite: %s, no sprite will be used. "
"NOTE: File must be in current project or a gem.",
pathname.c_str());
texture = nullptr;
return false;
}
texture->SetFilter(FILTER_LINEAR);
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::ReleaseTexture(ITexture*& texture)
{
if (texture)
{
// In order to avoid the texture being deleted while there are still commands on the render
// thread command queue that use it, we queue a command to delete the texture onto the
// command queue.
auto pInfo = AZStd::make_unique<SResourceAsync>();
pInfo->eClassName = eRCN_Texture;
pInfo->pResource = texture;
gEnv->pRenderer->ReleaseResourceAsync(AZStd::move(pInfo));
texture = nullptr;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
bool CSprite::CellIndexWithinRange(int cellIndex) const
{
return cellIndex >= 0 && cellIndex < m_spriteSheetCells.size();
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE MEMBER FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
bool CSprite::LoadFromXmlFile()
{
XmlNodeRef root = GetISystem()->LoadXmlFromFile(m_pathname.c_str());
if (!root)
{
gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
m_pathname.c_str(),
"No sprite file found for sprite: %s, default sprite values will be used", m_pathname.c_str());
return false;
}
std::unique_ptr<IXmlSerializer> pSerializer(GetISystem()->GetXmlUtils()->CreateXmlSerializer());
ISerialize* pReader = pSerializer->GetReader(root);
TSerialize ser = TSerialize(pReader);
uint32 versionNumber = spriteFileVersionNumber;
ser.Value(spriteVersionNumberTag, versionNumber);
const bool validVersionNumber = versionNumber >= 1 && versionNumber <= spriteFileVersionNumber;
if (!validVersionNumber)
{
gEnv->pSystem->Warning(VALIDATOR_MODULE_SHINE, VALIDATOR_WARNING, VALIDATOR_FLAG_FILE | VALIDATOR_FLAG_TEXTURE,
m_pathname.c_str(),
"Unsupported version number found for sprite file: %s, default sprite values will be used",
m_pathname.c_str());
return false;
}
// The serializer doesn't seem to have good support for parsing a variable
// number of tags of the same type, so we count up the children ourselves
// before starting serialization.
m_numSpriteSheetCellTags = GetNumSpriteSheetCellTags(root);
Serialize(ser);
NotifyChanged();
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void CSprite::NotifyChanged()
{
EBUS_EVENT_ID(this, UiSpriteSettingsChangeNotificationBus, OnSpriteSettingsChanged);
}