From a0054def7bf1195f4e2dd7ca1eae79c1eb9069a0 Mon Sep 17 00:00:00 2001 From: Lynix Date: Sat, 17 Jan 2015 00:16:52 +0100 Subject: [PATCH] Added TextSprites Former-commit-id: 85bef5ec14336710b2fdc782d3d0d77787ab65dd --- include/Nazara/Graphics/Enums.hpp | 9 +- include/Nazara/Graphics/TextSprite.hpp | 75 +++++ src/Nazara/Graphics/TextSprite.cpp | 407 +++++++++++++++++++++++++ 3 files changed, 487 insertions(+), 4 deletions(-) create mode 100644 include/Nazara/Graphics/TextSprite.hpp create mode 100644 src/Nazara/Graphics/TextSprite.cpp diff --git a/include/Nazara/Graphics/Enums.hpp b/include/Nazara/Graphics/Enums.hpp index 4f4cb1472..257928a66 100644 --- a/include/Nazara/Graphics/Enums.hpp +++ b/include/Nazara/Graphics/Enums.hpp @@ -71,10 +71,11 @@ enum nzRenderTechniqueType enum nzSceneNodeType { - nzSceneNodeType_Light, // NzLight - nzSceneNodeType_Model, // NzModel - nzSceneNodeType_Root, // NzSceneRoot - nzSceneNodeType_Sprite, // NzSprite + nzSceneNodeType_Light, // NzLight + nzSceneNodeType_Model, // NzModel + nzSceneNodeType_Root, // NzSceneRoot + nzSceneNodeType_Sprite, // NzSprite + nzSceneNodeType_TextSprite, // NzTextSprite nzSceneNodeType_User, nzSceneNodeType_Max = nzSceneNodeType_User diff --git a/include/Nazara/Graphics/TextSprite.hpp b/include/Nazara/Graphics/TextSprite.hpp new file mode 100644 index 000000000..be8df8b69 --- /dev/null +++ b/include/Nazara/Graphics/TextSprite.hpp @@ -0,0 +1,75 @@ +// Copyright (C) 2014 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_TEXTSPRITE_HPP +#define NAZARA_TEXTSPRITE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class NAZARA_API NzTextSprite : public NzSceneNode, NzAbstractAtlas::Listener +{ + public: + NzTextSprite(); + NzTextSprite(const NzTextSprite& sprite); + ~NzTextSprite(); + + void AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const override; + + void Clear(); + + const NzBoundingVolumef& GetBoundingVolume() const override; + const NzColor& GetColor() const; + NzMaterial* GetMaterial() const; + nzSceneNodeType GetSceneNodeType() const override; + + void InvalidateVertices(); + bool IsDrawable() const; + + void SetColor(const NzColor& color); + void SetDefaultMaterial(); + void SetMaterial(NzMaterial* material); + void SetText(const NzAbstractTextDrawer& drawer); + + NzTextSprite& operator=(const NzTextSprite& text); + + private: + void ClearAtlases(); + void InvalidateNode() override; + bool OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata) override; + bool OnAtlasLayerChange(const NzAbstractAtlas* atlas, NzAbstractImage* oldLayer, NzAbstractImage* newLayer, void* userdata) override; + void OnAtlasReleased(const NzAbstractAtlas* atlas, void* userdata) override; + void Register() override; + void Unregister() override; + void UpdateBoundingVolume() const; + void UpdateVertices() const; + + struct RenderIndices + { + unsigned int first; + unsigned int count; + }; + + std::set m_atlases; + mutable std::unordered_map m_renderInfos; + mutable std::vector m_localVertices; + mutable std::vector m_vertices; + mutable NzBoundingVolumef m_boundingVolume; + NzColor m_color; + NzMaterialRef m_material; + NzRectui m_localBounds; + mutable bool m_boundingVolumeUpdated; + mutable bool m_verticesUpdated; +}; + +#endif // NAZARA_TEXTSPRITE_HPP diff --git a/src/Nazara/Graphics/TextSprite.cpp b/src/Nazara/Graphics/TextSprite.cpp new file mode 100644 index 000000000..ab028ec91 --- /dev/null +++ b/src/Nazara/Graphics/TextSprite.cpp @@ -0,0 +1,407 @@ +// Copyright (C) 2014 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include + +///TODO: Gérer allocation nouvel atlas + +NzTextSprite::NzTextSprite() : +m_boundingVolume(NzBoundingVolumef::Null()), +m_color(NzColor::White), +m_verticesUpdated(false) +{ + SetDefaultMaterial(); +} + +NzTextSprite::NzTextSprite(const NzTextSprite& sprite) : +NzSceneNode(sprite), +m_atlases(sprite.m_atlases), +m_renderInfos(sprite.m_renderInfos), +m_localVertices(sprite.m_localVertices), +m_vertices(sprite.m_vertices), +m_boundingVolume(sprite.m_boundingVolume), +m_color(sprite.m_color), +m_material(sprite.m_material), +m_localBounds(sprite.m_localBounds), +m_boundingVolumeUpdated(sprite.m_boundingVolumeUpdated), +m_verticesUpdated(sprite.m_verticesUpdated) +{ + SetParent(sprite.GetParent()); + + for (const NzAbstractAtlas* atlas : m_atlases) + atlas->AddListener(this); +} + +NzTextSprite::~NzTextSprite() +{ + ClearAtlases(); +} + +void NzTextSprite::AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const +{ + if (!m_verticesUpdated) + UpdateVertices(); + + for (auto& pair : m_renderInfos) + { + NzTexture* overlay = pair.first; + RenderIndices& indices = pair.second; + + if (indices.count > 0) + renderQueue->AddSprites(m_material, &m_vertices[indices.first*4], indices.count, overlay); + } +} + +void NzTextSprite::Clear() +{ + ClearAtlases(); + m_boundingVolume.MakeNull(); + m_localVertices.clear(); + m_renderInfos.clear(); + m_vertices.clear(); +} + +const NzBoundingVolumef& NzTextSprite::GetBoundingVolume() const +{ + if (!m_boundingVolumeUpdated) + UpdateBoundingVolume(); + + return m_boundingVolume; +} + +const NzColor& NzTextSprite::GetColor() const +{ + return m_color; +} + +NzMaterial* NzTextSprite::GetMaterial() const +{ + return m_material; +} + +nzSceneNodeType NzTextSprite::GetSceneNodeType() const +{ + return nzSceneNodeType_TextSprite; +} + +void NzTextSprite::InvalidateVertices() +{ + m_verticesUpdated = false; +} + +bool NzTextSprite::IsDrawable() const +{ + return m_material != nullptr; +} + +void NzTextSprite::SetColor(const NzColor& color) +{ + m_color = color; + m_verticesUpdated = false; +} + +void NzTextSprite::SetDefaultMaterial() +{ + std::unique_ptr material(new NzMaterial); + material->Enable(nzRendererParameter_Blend, true); + material->Enable(nzRendererParameter_DepthWrite, false); + material->Enable(nzRendererParameter_FaceCulling, false); + material->EnableLighting(false); + material->SetDstBlend(nzBlendFunc_InvSrcAlpha); + material->SetSrcBlend(nzBlendFunc_SrcAlpha); + + SetMaterial(material.get()); + + material->SetPersistent(false); + material.release(); +} + +void NzTextSprite::SetMaterial(NzMaterial* material) +{ + m_material = material; +} + +void NzTextSprite::SetText(const NzAbstractTextDrawer& drawer) +{ + ClearAtlases(); + + unsigned int fontCount = drawer.GetFontCount(); + for (unsigned int i = 0; i < fontCount; ++i) + { + const NzAbstractAtlas* atlas = drawer.GetFont(i)->GetAtlas(); + if (m_atlases.insert(atlas).second) + atlas->AddListener(this); + } + + unsigned int glyphCount = drawer.GetGlyphCount(); + m_localVertices.resize(glyphCount * 4); + m_vertices.resize(glyphCount * 4); + + NzTexture* lastTexture = nullptr; + unsigned int* count = nullptr; + for (unsigned int i = 0; i < glyphCount; ++i) + { + const NzAbstractTextDrawer::Glyph& glyph = drawer.GetGlyph(i); + + NzTexture* texture = static_cast(glyph.atlas); + if (lastTexture != texture) + { + auto pair = m_renderInfos.insert(std::make_pair(texture, RenderIndices{0U, 0U})); + + count = &pair.first->second.count; + lastTexture = texture; + } + + (*count)++; + } + + // Attribution des indices + unsigned int index = 0; + for (auto& pair : m_renderInfos) + { + RenderIndices& indices = pair.second; + + indices.first = index; + + index += indices.count; + indices.count = 0; // On réinitialise count à zéro (on va s'en servir pour compteur dans la boucle suivante) + } + + NzSparsePtr texCoordPtr(&m_vertices[0].uv, sizeof(NzVertexStruct_XYZ_Color_UV)); + + lastTexture = nullptr; + RenderIndices* indices = nullptr; + for (unsigned int i = 0; i < glyphCount; ++i) + { + const NzAbstractTextDrawer::Glyph& glyph = drawer.GetGlyph(i); + + NzTexture* texture = static_cast(glyph.atlas); + if (lastTexture != texture) + { + indices = &m_renderInfos[texture]; // On a changé de texture, on ajuste le pointeur + lastTexture = texture; + } + + // Affectation des positions et couleurs (locaux) + for (unsigned int j = 0; j < 4; ++j) + { + m_localVertices[i*4 + j].color = glyph.color; + m_localVertices[i*4 + j].position.Set(glyph.corners[j]); + } + + // Calcul des coordonnées de texture (globaux) + + // On commence par transformer les coordonnées entières en flottantes: + NzVector2ui size(texture->GetSize()); + float invWidth = 1.f/size.x; + float invHeight = 1.f/size.y; + + NzRectf uvRect(glyph.atlasRect); + uvRect.x *= invWidth; + uvRect.y *= invHeight; + uvRect.width *= invWidth; + uvRect.height *= invHeight; + + // Extraction des quatre coins et attribution + NzSparsePtr texCoord = texCoordPtr + indices->first*4 + indices->count*4; + if (!glyph.flipped) + { + // Le glyphe n'est pas retourné, l'ordre des UV suit celui des sommets + *texCoord++ = uvRect.GetCorner(nzRectCorner_LeftTop); + *texCoord++ = uvRect.GetCorner(nzRectCorner_RightTop); + *texCoord++ = uvRect.GetCorner(nzRectCorner_LeftBottom); + *texCoord++ = uvRect.GetCorner(nzRectCorner_RightBottom); + } + else + { + // Le glyphe a subit une rotation de 90° (sens antihoraire), on adapte les UV en conséquence + *texCoord++ = uvRect.GetCorner(nzRectCorner_LeftBottom); + *texCoord++ = uvRect.GetCorner(nzRectCorner_LeftTop); + *texCoord++ = uvRect.GetCorner(nzRectCorner_RightBottom); + *texCoord++ = uvRect.GetCorner(nzRectCorner_RightTop); + } + + // Et on passe au prochain + indices->count++; + } + + m_localBounds = drawer.GetBounds(); + m_boundingVolume.MakeNull(); + m_boundingVolumeUpdated = false; + m_verticesUpdated = false; +} + +NzTextSprite& NzTextSprite::operator=(const NzTextSprite& text) +{ + NzSceneNode::operator=(text); + + m_atlases = text.m_atlases; + m_color = text.m_color; + m_material = text.m_material; + m_renderInfos = text.m_renderInfos; + m_localBounds = text.m_localBounds; + m_localVertices = text.m_localVertices; + + // On ne copie pas les sommets finaux car il est très probable que nos paramètres soient modifiés et qu'ils doivent être régénérés de toute façon + m_boundingVolumeUpdated = false; + m_verticesUpdated = false; + + return *this; +} + +void NzTextSprite::ClearAtlases() +{ + for (const NzAbstractAtlas* atlas : m_atlases) + atlas->RemoveListener(this); + + m_atlases.clear(); +} + +void NzTextSprite::InvalidateNode() +{ + NzSceneNode::InvalidateNode(); + + m_boundingVolumeUpdated = false; + m_verticesUpdated = false; +} + +bool NzTextSprite::OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata) +{ + NazaraUnused(userdata); + + #ifdef NAZARA_DEBUG + if (m_atlases.find(atlas) == m_atlases.end()) + { + NazaraInternalError("Not listening to " + NzString::Pointer(atlas)); + return false; + } + #endif + + NazaraWarning("TextSprite " + NzString::Pointer(this) + " has been cleared because atlas " + NzString::Pointer(atlas) + " that was under use has been cleared"); + Clear(); + + return false; +} + +bool NzTextSprite::OnAtlasLayerChange(const NzAbstractAtlas* atlas, NzAbstractImage* oldLayer, NzAbstractImage* newLayer, void* userdata) +{ + NazaraUnused(atlas); + NazaraUnused(userdata); + + #ifdef NAZARA_DEBUG + if (m_atlases.find(atlas) == m_atlases.end()) + { + NazaraInternalError("Not listening to " + NzString::Pointer(atlas)); + return false; + } + #endif + + NzTexture* oldTexture = static_cast(oldLayer); + NzTexture* newTexture = static_cast(newLayer); + + auto it = m_renderInfos.find(oldTexture); + if (it != m_renderInfos.end()) + { + // Nous utilisons bien cette texture, nous devons mettre à jour les coordonnées de texture + RenderIndices indices = std::move(it->second); + + NzVector2ui oldSize(oldTexture->GetSize()); + NzVector2ui newSize(newTexture->GetSize()); + NzVector2f scale = NzVector2f(oldSize)/NzVector2f(newSize); + + NzSparsePtr texCoordPtr(&m_vertices[indices.first].uv, sizeof(NzVertexStruct_XYZ_Color_UV)); + for (unsigned int i = 0; i < indices.count; ++i) + { + for (unsigned int j = 0; j < 4; ++j) + texCoordPtr[i*4 + j] *= scale; + } + + // Nous enlevons l'ancienne texture et rajoutons la nouvelle à sa place (pour les mêmes indices) + m_renderInfos.erase(it); + m_renderInfos.insert(std::make_pair(newTexture, std::move(indices))); + } + + return true; +} + +void NzTextSprite::OnAtlasReleased(const NzAbstractAtlas* atlas, void* userdata) +{ + NazaraUnused(userdata); + + #ifdef NAZARA_DEBUG + if (m_atlases.find(atlas) == m_atlases.end()) + { + NazaraInternalError("Not listening to " + NzString::Pointer(atlas)); + return; + } + #endif + + NazaraWarning("TextSprite " + NzString::Pointer(this) + " has been cleared because atlas " + NzString::Pointer(atlas) + " that was under use has been released"); + Clear(); +} + +void NzTextSprite::Register() +{ + // Le changement de scène peut affecter les sommets + m_verticesUpdated = false; +} + +void NzTextSprite::Unregister() +{ +} + +void NzTextSprite::UpdateBoundingVolume() const +{ + if (m_boundingVolume.IsNull()) + { + NzVector3f down = m_scene->GetDown(); + NzVector3f right = m_scene->GetRight(); + + m_boundingVolume.Set(NzVector3f(0.f), static_cast(m_localBounds.width)*right + static_cast(m_localBounds.height)*down); + } + + if (!m_transformMatrixUpdated) + UpdateTransformMatrix(); + + m_boundingVolume.Update(m_transformMatrix); + m_boundingVolumeUpdated = true; +} + +void NzTextSprite::UpdateVertices() const +{ + if (!m_transformMatrixUpdated) + UpdateTransformMatrix(); + + NzVector3f down = m_scene->GetDown(); + NzVector3f right = m_scene->GetRight(); + + NzSparsePtr colorPtr(&m_vertices[0].color, sizeof(NzVertexStruct_XYZ_Color_UV)); + NzSparsePtr posPtr(&m_vertices[0].position, sizeof(NzVertexStruct_XYZ_Color_UV)); + + for (auto& pair : m_renderInfos) + { + RenderIndices& indices = pair.second; + + NzSparsePtr color = colorPtr + indices.first*4; + NzSparsePtr pos = posPtr + indices.first*4; + NzVertexStruct_XY_Color* localVertex = &m_localVertices[indices.first*4]; + for (unsigned int i = 0; i < indices.count; ++i) + { + for (unsigned int j = 0; j < 4; ++j) + { + *pos++ = m_transformMatrix.Transform(localVertex->position.x*right + localVertex->position.y*down); + *color++ = m_color * localVertex->color; + + localVertex++; + } + } + } + + m_verticesUpdated = true; +}