Skip to content

Commit

Permalink
add Elastic Sprite
Browse files Browse the repository at this point in the history
added new drawable, Elastic Sprite, which allows the sprite's corner
vertices to be moved individually and uses an internal shader to
"stretch" the texture or interpolate the colours of the vertices, which
can also be individually coloured.
  • Loading branch information
Hapaxia committed Mar 29, 2017
1 parent bb99e55 commit f53fd9f
Show file tree
Hide file tree
Showing 4 changed files with 483 additions and 1 deletion.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
A collection of SFML drawables
by [Hapaxia](http://github.com/Hapaxia)

Contents: **Bitmap Text**, **Console Screen**, **Crosshair**, **Gallery Sprite**, **Line**, **Nine Patch**, **Pie Chart**, **Progress Bar**, **Ring**, **Spinning Card**, **Spline**, **Sprite 3D**, **Starfield**, **Tile Map**
Contents: **Bitmap Text**, **Console Screen**, **Crosshair**, **Elastic Sprite**, **Gallery Sprite**, **Line**, **Nine Patch**, **Pie Chart**, **Progress Bar**, **Ring**, **Spinning Card**, **Spline**, **Sprite 3D**, **Starfield**, **Tile Map**

## For information, view the [Wiki].

Expand Down
1 change: 1 addition & 0 deletions src/SelbaWard.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "SelbaWard/ConsoleScreen.hpp"
#include "SelbaWard/ConsoleScreenOld.hpp"
#include "SelbaWard/Crosshair.hpp"
#include "SelbaWard/ElasticSprite.hpp"
#include "SelbaWard/GallerySprite.hpp"
#include "SelbaWard/Line.hpp"
#include "SelbaWard/NinePatch.hpp"
Expand Down
377 changes: 377 additions & 0 deletions src/SelbaWard/ElasticSprite.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,377 @@
//////////////////////////////////////////////////////////////////////////////
//
// Selba Ward (https://github.com/Hapaxia/SelbaWard)
// --
//
// Elastic Sprite
//
// Copyright(c) 2017 M.J.Silk
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions :
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software.If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
// M.J.Silk
// MJSilk2@gmail.com
//
//////////////////////////////////////////////////////////////////////////////

#include "ElasticSprite.hpp"

#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/Shader.hpp>
#include <SFML/Graphics/Transform.hpp>
#include <SFML/Graphics/Color.hpp>
#include <SFML/Graphics/Vertex.hpp>
#include <assert.h>

namespace
{

const sf::PrimitiveType primitiveType{ sf::PrimitiveType::Quads };
bool isShaderLoaded{ false };
sf::Shader shader;

const std::string shaderCode
{
"uniform bool useTexture;\nuniform sampler2D texture;\nuniform int ren"
"derTargetHeight;\nuniform vec2 v0;\nuniform vec2 v1;\nuniform vec2 v2"
";\nuniform vec2 v3;\nuniform float textureRectLeftRatio;\nuniform flo"
"at textureRectTopRatio;\nuniform float textureRectWidthRatio;\nunifor"
"m float textureRectHeightRatio;\nuniform vec4 c0;\nuniform vec4 c1;\n"
"uniform vec4 c2;\nuniform vec4 c3;\n\nvec2 linesIntersection(vec2 aSt"
"art, vec2 aEnd, vec2 bStart, vec2 bEnd)\n{\nvec2 a = aEnd - aStart;"
"\nvec2 b = bEnd - bStart;\nfloat aAngle = atan(a.y, a.x);\nfloat bA"
"ngle = atan(b.y, b.x);\nif (abs(aAngle - bAngle) < 0.01)\n{\na = "
"mix(aEnd, bEnd, 0.0001) - aStart;\nb = mix(bEnd, aEnd, 0.0001) - bS"
"tart;\n}\nvec2 c = aStart - bStart;\nfloat alpha = ((b.x * c.y) - "
"(b.y * c.x)) / ((b.y * a.x) - (b.x * a.y));\nreturn aStart + (a * al"
"pha);\n}\n\nvoid main()\n{\nvec2 p = vec2(gl_FragCoord.x, (renderTar"
"getHeight - gl_FragCoord.y));\nvec2 o = linesIntersection(v0, v3, v1"
", v2);\nvec2 n = linesIntersection(v1, v0, v2, v3);\nvec2 l = lines"
"Intersection(o, p, v0, v1);\nvec2 m = linesIntersection(o, p, v3, v2"
");\nvec2 j = linesIntersection(n, p, v0, v3);\nvec2 k = linesInters"
"ection(n, p, v2, v1);\nvec2 ratioCoord = vec2(distance(p, l) / dista"
"nce(m, l), distance(p, j) / distance(k, j));\nvec4 color = mix(mix(c"
"0, c3, ratioCoord.x), mix(c1, c2, ratioCoord.x), ratioCoord.y);\nif "
"(useTexture)\n{\nvec2 texCoord = vec2(ratioCoord.x * textureRectWi"
"dthRatio + textureRectLeftRatio, ratioCoord.y * textureRectHeightRati"
"o + textureRectTopRatio);\nvec4 pixel = texture2D(texture, texCoord"
");\ngl_FragColor = color * pixel;\n}\nelse\ngl_FragColor = colo"
"r;\n}\n"
};

void loadShader()
{
if (!isShaderLoaded && shader.loadFromMemory(shaderCode, sf::Shader::Fragment))
isShaderLoaded = true;
}

inline bool isValidVertexIndex(const unsigned int vertexIndex)
{
return (vertexIndex >= 0 && vertexIndex < 4);
}

} // namespace

namespace selbaward
{

ElasticSprite::ElasticSprite()
: m_vertices(4)
, m_offsets(4)
, m_useShader(sf::Shader::isAvailable())
, m_requiresVerticesUpdate(false)
, m_textureRect()
, m_pTexture(nullptr)
{
if (m_useShader)
loadShader();
}

ElasticSprite::ElasticSprite(const sf::Texture& texture)
: ElasticSprite()
{
setTexture(texture);
}

ElasticSprite::ElasticSprite(const sf::Texture& texture, const sf::FloatRect textureRect)
: ElasticSprite()
{
setTexture(texture);
setTextureRect(textureRect);
}

void ElasticSprite::setTexture(const sf::Texture& texture, const bool resetTextureRect)
{
if (resetTextureRect)
{
resetVertexOffsets();
setTextureRect({ { 0.f, 0.f }, sf::Vector2f(texture.getSize()) });
}
m_pTexture = &texture;
}

void ElasticSprite::setTexture()
{
m_pTexture = nullptr;
}

void ElasticSprite::setTextureRect(const sf::FloatRect textureRect)
{
m_textureRect = textureRect;
m_requiresVerticesUpdate = true;
}

const sf::Texture* ElasticSprite::getTexture() const
{
return m_pTexture;
}

sf::FloatRect ElasticSprite::getTextureRect() const
{
return m_textureRect;
}

bool ElasticSprite::setUseShader(const bool useShader)
{
return m_useShader = (useShader && sf::Shader::isAvailable());
}

bool ElasticSprite::getUseShader() const
{
return m_useShader;
}

void ElasticSprite::setVertexOffset(const unsigned int vertexIndex, const sf::Vector2f offset)
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

m_offsets[vertexIndex] = offset;
m_requiresVerticesUpdate = true;
}

sf::Vector2f ElasticSprite::getVertexOffset(const unsigned int vertexIndex) const
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

return m_offsets[vertexIndex];
}

void ElasticSprite::setColor(const sf::Color color)
{
for (auto& vertex : m_vertices)
vertex.color = color;
m_requiresVerticesUpdate = true;
}

void ElasticSprite::setVertexColor(const unsigned int vertexIndex, const sf::Color color)
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

m_vertices[vertexIndex].color = color;
m_requiresVerticesUpdate = true;
}

sf::Color ElasticSprite::getColor() const
{
const unsigned int totalR{ static_cast<unsigned int>(m_vertices[0].color.r) + m_vertices[1].color.r + m_vertices[2].color.r + m_vertices[3].color.r };
const unsigned int totalG{ static_cast<unsigned int>(m_vertices[0].color.g) + m_vertices[1].color.g + m_vertices[2].color.g + m_vertices[3].color.g };
const unsigned int totalB{ static_cast<unsigned int>(m_vertices[0].color.b) + m_vertices[1].color.b + m_vertices[2].color.b + m_vertices[3].color.b };
const unsigned int totalA{ static_cast<unsigned int>(m_vertices[0].color.a) + m_vertices[1].color.a + m_vertices[2].color.a + m_vertices[3].color.a };
return{ static_cast<sf::Uint8>(totalR / 4), static_cast<sf::Uint8>(totalG / 4), static_cast<sf::Uint8>(totalB / 4), static_cast<sf::Uint8>(totalA / 4) };
}

sf::Color ElasticSprite::getVertexColor(const unsigned int vertexIndex) const
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

return m_vertices[vertexIndex].color;
}

void ElasticSprite::resetVertexOffsets()
{
setVertexOffset(0, { 0.f, 0.f });
setVertexOffset(1, { 0.f, 0.f });
setVertexOffset(2, { 0.f, 0.f });
setVertexOffset(3, { 0.f, 0.f });
}

sf::Vector2f ElasticSprite::getVertexLocalPosition(const unsigned int vertexIndex) const
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

return getTransform().transformPoint(priv_getVertexBasePosition(vertexIndex) + m_offsets[vertexIndex]);
}

sf::Vector2f ElasticSprite::getVertexBaseLocalPosition(const unsigned int vertexIndex) const
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

return getTransform().transformPoint(priv_getVertexBasePosition(vertexIndex));
}

sf::Vector2f ElasticSprite::getVertexGlobalPosition(const unsigned int vertexIndex) const
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

return getTransform().transformPoint(priv_getVertexBasePosition(vertexIndex) + m_offsets[vertexIndex]);
}

sf::Vector2f ElasticSprite::getVertexBaseGlobalPosition(const unsigned int vertexIndex) const
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

return getTransform().transformPoint(priv_getVertexBasePosition(vertexIndex));
}

sf::FloatRect ElasticSprite::getLocalBounds() const
{
sf::Vector2f topLeft{ m_offsets[0] };
sf::Vector2f bottomRight{ topLeft };

for (unsigned int i{ 1u }; i < 4; ++i)
{
const sf::Vector2f vertex{ priv_getVertexBasePosition(i) + m_offsets[i] };
if (vertex.x < topLeft.x)
topLeft.x = vertex.x;
else if (vertex.x > bottomRight.x)
bottomRight.x = vertex.x;
if (vertex.y < topLeft.y)
topLeft.y = vertex.y;
else if (vertex.y > bottomRight.y)
bottomRight.y = vertex.y;
}
return{ topLeft, bottomRight - topLeft };
}

sf::FloatRect ElasticSprite::getBaseLocalBounds() const
{
return{ { 0.f, 0.f }, priv_getVertexBasePosition(2) };
}

sf::FloatRect ElasticSprite::getGlobalBounds() const
{
if (m_requiresVerticesUpdate)
priv_updateVertices();

sf::Vector2f topLeft{ m_vertices[0].position };
sf::Vector2f bottomRight{ topLeft };

for (unsigned int i{ 1u }; i < 4; ++i)
{
const sf::Vector2f transformedVertex{ m_vertices[i].position };
if (transformedVertex.x < topLeft.x)
topLeft.x = transformedVertex.x;
else if (transformedVertex.x > bottomRight.x)
bottomRight.x = transformedVertex.x;
if (transformedVertex.y < topLeft.y)
topLeft.y = transformedVertex.y;
else if (transformedVertex.y > bottomRight.y)
bottomRight.y = transformedVertex.y;
}
return{ topLeft, bottomRight - topLeft };
}

sf::FloatRect ElasticSprite::getBaseGlobalBounds() const
{
return getTransform().transformRect(getBaseLocalBounds());
}



// PRIVATE

void ElasticSprite::draw(sf::RenderTarget& target, sf::RenderStates states) const
{
if (m_requiresVerticesUpdate)
priv_updateVertices();

if (!m_useShader)
states.texture = m_pTexture;
else
{
if (m_pTexture == nullptr)
shader.setUniform("useTexture", false);
else
{
shader.setUniform("useTexture", true);
shader.setUniform("texture", *m_pTexture);
const sf::Vector2f textureSize(m_pTexture->getSize());
shader.setUniform("textureRectLeftRatio", m_textureRect.left / textureSize.x);
shader.setUniform("textureRectTopRatio", m_textureRect.top / textureSize.y);
shader.setUniform("textureRectWidthRatio", m_textureRect.width / textureSize.x);
shader.setUniform("textureRectHeightRatio", m_textureRect.height / textureSize.y);
}
shader.setUniform("renderTargetHeight", static_cast<int>(target.getSize().y));
shader.setUniform("v0", sf::Glsl::Vec2(target.mapCoordsToPixel(m_vertices[0].position)));
shader.setUniform("v1", sf::Glsl::Vec2(target.mapCoordsToPixel(m_vertices[1].position)));
shader.setUniform("v2", sf::Glsl::Vec2(target.mapCoordsToPixel(m_vertices[2].position)));
shader.setUniform("v3", sf::Glsl::Vec2(target.mapCoordsToPixel(m_vertices[3].position)));
shader.setUniform("c0", sf::Glsl::Vec4(m_vertices[0].color));
shader.setUniform("c1", sf::Glsl::Vec4(m_vertices[1].color));
shader.setUniform("c2", sf::Glsl::Vec4(m_vertices[2].color));
shader.setUniform("c3", sf::Glsl::Vec4(m_vertices[3].color));
states.shader = &shader;
}

target.draw(m_vertices.data(), m_vertices.size(), primitiveType, states);
}

void ElasticSprite::priv_updateVertices() const
{
m_requiresVerticesUpdate = false;

const sf::Transform transform{ getTransform() };

for (unsigned int i{ 0u }; i < 4; ++i)
m_vertices[i].position = transform.transformPoint(m_offsets[i] + priv_getVertexBasePosition(i));

m_vertices[0].texCoords = { m_textureRect.left, m_textureRect.top };
m_vertices[2].texCoords = { m_textureRect.left + m_textureRect.width, m_textureRect.top + m_textureRect.height };
m_vertices[1].texCoords = { m_vertices[0].texCoords.x, m_vertices[2].texCoords.y };
m_vertices[3].texCoords = { m_vertices[2].texCoords.x, m_vertices[0].texCoords.y };
}

sf::Vector2f ElasticSprite::priv_getVertexBasePosition(const unsigned int vertexIndex) const
{
// must be valid vertex index
assert(isValidVertexIndex(vertexIndex));

switch (vertexIndex)
{
case 1u:
return sf::Vector2f(0.f, m_textureRect.height);
case 2u:
return sf::Vector2f(m_textureRect.width, m_textureRect.height);
case 3u:
return sf::Vector2f(m_textureRect.width, 0.f);
case 0u:
default:
return { 0.f, 0.f };
}
}

} // namespace selbaward
Loading

0 comments on commit f53fd9f

Please sign in to comment.