////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2018 Laurent Gomila (laurent@sfml-dev.org)
//
// 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.
//
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include
#include
#include
#include
#include
namespace
{
// Compute the normal of a segment
sf::Vector2f computeNormal(const sf::Vector2f& p1, const sf::Vector2f& p2)
{
sf::Vector2f normal(p1.y - p2.y, p2.x - p1.x);
float length = std::sqrt(normal.x * normal.x + normal.y * normal.y);
if (length != 0.f)
normal /= length;
return normal;
}
// Compute the dot product of two vectors
float dotProduct(const sf::Vector2f& p1, const sf::Vector2f& p2)
{
return p1.x * p2.x + p1.y * p2.y;
}
}
namespace sf
{
////////////////////////////////////////////////////////////
Shape::~Shape()
{
}
////////////////////////////////////////////////////////////
void Shape::setTexture(const Texture* texture, bool resetRect)
{
if (texture)
{
// Recompute the texture area if requested, or if there was no texture & rect before
if (resetRect || (!m_texture && (m_textureRect == IntRect())))
setTextureRect(IntRect(0, 0, texture->getSize().x, texture->getSize().y));
}
// Assign the new texture
m_texture = texture;
}
////////////////////////////////////////////////////////////
const Texture* Shape::getTexture() const
{
return m_texture;
}
////////////////////////////////////////////////////////////
void Shape::setTextureRect(const IntRect& rect)
{
m_textureRect = rect;
updateTexCoords();
}
////////////////////////////////////////////////////////////
const IntRect& Shape::getTextureRect() const
{
return m_textureRect;
}
////////////////////////////////////////////////////////////
void Shape::setFillColor(const Color& color)
{
m_fillColor = color;
updateFillColors();
}
////////////////////////////////////////////////////////////
const Color& Shape::getFillColor() const
{
return m_fillColor;
}
////////////////////////////////////////////////////////////
void Shape::setOutlineColor(const Color& color)
{
m_outlineColor = color;
updateOutlineColors();
}
////////////////////////////////////////////////////////////
const Color& Shape::getOutlineColor() const
{
return m_outlineColor;
}
////////////////////////////////////////////////////////////
void Shape::setOutlineThickness(float thickness)
{
m_outlineThickness = thickness;
update(); // recompute everything because the whole shape must be offset
}
////////////////////////////////////////////////////////////
float Shape::getOutlineThickness() const
{
return m_outlineThickness;
}
////////////////////////////////////////////////////////////
FloatRect Shape::getLocalBounds() const
{
return m_bounds;
}
////////////////////////////////////////////////////////////
FloatRect Shape::getGlobalBounds() const
{
return getTransform().transformRect(getLocalBounds());
}
////////////////////////////////////////////////////////////
Shape::Shape() :
m_texture (NULL),
m_textureRect (),
m_fillColor (255, 255, 255),
m_outlineColor (255, 255, 255),
m_outlineThickness(0),
m_vertices (TriangleFan),
m_outlineVertices (TriangleStrip),
m_insideBounds (),
m_bounds ()
{
}
////////////////////////////////////////////////////////////
void Shape::update()
{
// Get the total number of points of the shape
std::size_t count = getPointCount();
if (count < 3)
{
m_vertices.resize(0);
m_outlineVertices.resize(0);
return;
}
m_vertices.resize(count + 2); // + 2 for center and repeated first point
// Position
for (std::size_t i = 0; i < count; ++i)
m_vertices[i + 1].position = getPoint(i);
m_vertices[count + 1].position = m_vertices[1].position;
// Update the bounding rectangle
m_vertices[0] = m_vertices[1]; // so that the result of getBounds() is correct
m_insideBounds = m_vertices.getBounds();
// Compute the center and make it the first vertex
m_vertices[0].position.x = m_insideBounds.left + m_insideBounds.width / 2;
m_vertices[0].position.y = m_insideBounds.top + m_insideBounds.height / 2;
// Color
updateFillColors();
// Texture coordinates
updateTexCoords();
// Outline
updateOutline();
}
////////////////////////////////////////////////////////////
void Shape::draw(RenderTarget& target, RenderStates states) const
{
states.transform *= getTransform();
// Render the inside
states.texture = m_texture;
target.draw(m_vertices, states);
// Render the outline
if (m_outlineThickness != 0)
{
states.texture = NULL;
target.draw(m_outlineVertices, states);
}
}
////////////////////////////////////////////////////////////
void Shape::updateFillColors()
{
for (std::size_t i = 0; i < m_vertices.getVertexCount(); ++i)
m_vertices[i].color = m_fillColor;
}
////////////////////////////////////////////////////////////
void Shape::updateTexCoords()
{
for (std::size_t i = 0; i < m_vertices.getVertexCount(); ++i)
{
float xratio = m_insideBounds.width > 0 ? (m_vertices[i].position.x - m_insideBounds.left) / m_insideBounds.width : 0;
float yratio = m_insideBounds.height > 0 ? (m_vertices[i].position.y - m_insideBounds.top) / m_insideBounds.height : 0;
m_vertices[i].texCoords.x = m_textureRect.left + m_textureRect.width * xratio;
m_vertices[i].texCoords.y = m_textureRect.top + m_textureRect.height * yratio;
}
}
////////////////////////////////////////////////////////////
void Shape::updateOutline()
{
// Return if there is no outline
if (m_outlineThickness == 0.f)
{
m_outlineVertices.clear();
m_bounds = m_insideBounds;
return;
}
std::size_t count = m_vertices.getVertexCount() - 2;
m_outlineVertices.resize((count + 1) * 2);
for (std::size_t i = 0; i < count; ++i)
{
std::size_t index = i + 1;
// Get the two segments shared by the current point
Vector2f p0 = (i == 0) ? m_vertices[count].position : m_vertices[index - 1].position;
Vector2f p1 = m_vertices[index].position;
Vector2f p2 = m_vertices[index + 1].position;
// Compute their normal
Vector2f n1 = computeNormal(p0, p1);
Vector2f n2 = computeNormal(p1, p2);
// Make sure that the normals point towards the outside of the shape
// (this depends on the order in which the points were defined)
if (dotProduct(n1, m_vertices[0].position - p1) > 0)
n1 = -n1;
if (dotProduct(n2, m_vertices[0].position - p1) > 0)
n2 = -n2;
// Combine them to get the extrusion direction
float factor = 1.f + (n1.x * n2.x + n1.y * n2.y);
Vector2f normal = (n1 + n2) / factor;
// Update the outline points
m_outlineVertices[i * 2 + 0].position = p1;
m_outlineVertices[i * 2 + 1].position = p1 + normal * m_outlineThickness;
}
// Duplicate the first point at the end, to close the outline
m_outlineVertices[count * 2 + 0].position = m_outlineVertices[0].position;
m_outlineVertices[count * 2 + 1].position = m_outlineVertices[1].position;
// Update outline colors
updateOutlineColors();
// Update the shape's bounds
m_bounds = m_outlineVertices.getBounds();
}
////////////////////////////////////////////////////////////
void Shape::updateOutlineColors()
{
for (std::size_t i = 0; i < m_outlineVertices.getVertexCount(); ++i)
m_outlineVertices[i].color = m_outlineColor;
}
} // namespace sf