mirror of
https://github.com/SFML/SFML.git
synced 2024-12-01 07:41:05 +08:00
Bevel shape outline corners beyond threshold
This commit is contained in:
parent
6886d1686b
commit
0b9c80d50e
@ -134,6 +134,37 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
void setOutlineThickness(float thickness);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Set the limit on the ratio between miter length and outline thickness
|
||||
///
|
||||
/// Outline segments around each shape corner are joined either
|
||||
/// with a miter or a bevel join.
|
||||
/// - A miter join is formed by extending outline segments until
|
||||
/// they intersect. The distance between the point of
|
||||
/// intersection and the shape's corner is the miter length.
|
||||
/// - A bevel join is formed by connecting outline segments with
|
||||
/// a straight line perpendicular to the corner's bissector.
|
||||
///
|
||||
/// The miter limit is used to determine whether ouline segments
|
||||
/// around a corner are joined with a bevel or a miter.
|
||||
/// When the ratio between the miter length and outline thickness
|
||||
/// exceeds the miter limit, a bevel is used instead of a miter.
|
||||
///
|
||||
/// The miter limit is linked to the maximum inner angle of a
|
||||
/// corner below which a bevel is used by the following formula:
|
||||
///
|
||||
/// miterLimit = 1 / sin(angle / 2)
|
||||
///
|
||||
/// The miter limit must be greater than or equal to 1.
|
||||
/// By default, the miter limit is 10.
|
||||
///
|
||||
/// \param miterLimit New miter limit
|
||||
///
|
||||
/// \see getMiterLimit
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
void setMiterLimit(float miterLimit);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the source texture of the shape
|
||||
///
|
||||
@ -188,6 +219,16 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
float getOutlineThickness() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the limit on the ratio between miter length and outline thickness
|
||||
///
|
||||
/// \return Limit on the ratio between miter length and outline thickness
|
||||
///
|
||||
/// \see setMiterLimit
|
||||
///
|
||||
////////////////////////////////////////////////////////////
|
||||
float getMiterLimit() const;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// \brief Get the total number of points of the shape
|
||||
///
|
||||
@ -315,6 +356,7 @@ private:
|
||||
Color m_fillColor{Color::White}; //!< Fill color
|
||||
Color m_outlineColor{Color::White}; //!< Outline color
|
||||
float m_outlineThickness{}; //!< Thickness of the shape's outline
|
||||
float m_miterLimit{10.f}; //!< Limit on the ratio between miter length and outline thickness
|
||||
VertexArray m_vertices{PrimitiveType::TriangleFan}; //!< Vertex array containing the fill geometry
|
||||
VertexArray m_outlineVertices{PrimitiveType::TriangleStrip}; //!< Vertex array containing the outline geometry
|
||||
FloatRect m_insideBounds; //!< Bounding rectangle of the inside (fill)
|
||||
|
@ -32,20 +32,19 @@
|
||||
#include <algorithm>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Compute the normal of a segment
|
||||
sf::Vector2f computeNormal(const sf::Vector2f& p1, const sf::Vector2f& p2, bool flipped)
|
||||
// Compute the direction of a segment
|
||||
sf::Vector2f computeDirection(const sf::Vector2f& p1, const sf::Vector2f& p2)
|
||||
{
|
||||
sf::Vector2f normal = (p2 - p1).perpendicular();
|
||||
const float length = normal.length();
|
||||
sf::Vector2f direction = p2 - p1;
|
||||
const float length = direction.length();
|
||||
if (length != 0.f)
|
||||
normal /= length;
|
||||
if (flipped)
|
||||
normal = -normal;
|
||||
return normal;
|
||||
direction /= length;
|
||||
return direction;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@ -123,7 +122,7 @@ const Color& Shape::getOutlineColor() const
|
||||
void Shape::setOutlineThickness(float thickness)
|
||||
{
|
||||
m_outlineThickness = thickness;
|
||||
update(); // recompute everything because the whole shape must be offset
|
||||
updateOutline();
|
||||
}
|
||||
|
||||
|
||||
@ -134,6 +133,22 @@ float Shape::getOutlineThickness() const
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
void Shape::setMiterLimit(float miterLimit)
|
||||
{
|
||||
assert(miterLimit >= 1.f && "Shape::setMiterLimit(float) cannot set miter limit to a value lower than 1");
|
||||
m_miterLimit = miterLimit;
|
||||
updateOutline();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
float Shape::getMiterLimit() const
|
||||
{
|
||||
return m_miterLimit;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
Vector2f Shape::getGeometricCenter() const
|
||||
{
|
||||
@ -284,8 +299,8 @@ void Shape::updateTexCoords()
|
||||
////////////////////////////////////////////////////////////
|
||||
void Shape::updateOutline()
|
||||
{
|
||||
// Return if there is no outline
|
||||
if (m_outlineThickness == 0.f)
|
||||
// Return if there is no outline or no vertices
|
||||
if (m_outlineThickness == 0.f || m_vertices.getVertexCount() < 2)
|
||||
{
|
||||
m_outlineVertices.clear();
|
||||
m_bounds = m_insideBounds;
|
||||
@ -293,7 +308,8 @@ void Shape::updateOutline()
|
||||
}
|
||||
|
||||
const std::size_t count = m_vertices.getVertexCount() - 2;
|
||||
m_outlineVertices.resize((count + 1) * 2);
|
||||
m_outlineVertices.resize((count + 1) * 2); // We need at least that many vertices.
|
||||
// We will add two more vertices each time we need a bevel.
|
||||
|
||||
// Determine if points are defined clockwise or counterclockwise. This will impact normals computation.
|
||||
const bool flipNormals = [this, count]()
|
||||
@ -304,6 +320,7 @@ void Shape::updateOutline()
|
||||
return twiceArea >= 0.f;
|
||||
}();
|
||||
|
||||
std::size_t outlineIndex = 0;
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
const std::size_t index = i + 1;
|
||||
@ -313,22 +330,55 @@ void Shape::updateOutline()
|
||||
const Vector2f p1 = m_vertices[index].position;
|
||||
const Vector2f p2 = m_vertices[index + 1].position;
|
||||
|
||||
// Compute their direction
|
||||
const Vector2f d1 = computeDirection(p0, p1);
|
||||
const Vector2f d2 = computeDirection(p1, p2);
|
||||
|
||||
// Compute their normal pointing towards the outside of the shape
|
||||
const Vector2f n1 = computeNormal(p0, p1, flipNormals);
|
||||
const Vector2f n2 = computeNormal(p1, p2, flipNormals);
|
||||
const Vector2f n1 = flipNormals ? -d1.perpendicular() : d1.perpendicular();
|
||||
const Vector2f n2 = flipNormals ? -d2.perpendicular() : d2.perpendicular();
|
||||
|
||||
// Combine them to get the extrusion direction
|
||||
const float factor = 1.f + (n1.x * n2.x + n1.y * n2.y);
|
||||
const Vector2f normal = (n1 + n2) / factor;
|
||||
// Decide whether to add a bevel or not
|
||||
const float twoCos2 = 1.f + n1.dot(n2);
|
||||
const float squaredLengthRatio = m_miterLimit * m_miterLimit * twoCos2 / 2.f;
|
||||
const bool isConvexCorner = d1.dot(n2) * m_outlineThickness >= 0.f;
|
||||
const bool needsBevel = twoCos2 == 0.f || (squaredLengthRatio < 1.f && isConvexCorner);
|
||||
|
||||
// Update the outline points
|
||||
m_outlineVertices[i * 2 + 0].position = p1;
|
||||
m_outlineVertices[i * 2 + 1].position = p1 + normal * m_outlineThickness;
|
||||
if (needsBevel)
|
||||
{
|
||||
// Make room for two more vertices
|
||||
m_outlineVertices.resize(m_outlineVertices.getVertexCount() + 2);
|
||||
|
||||
// Combine normals to get bevel edge's direction and normal vector pointing towards the outside of the shape
|
||||
const float twoSin2 = 1.f - n1.dot(n2);
|
||||
const Vector2f direction = (n2 - n1) / twoSin2; // Length is 1 / sin
|
||||
const Vector2f extrusion = (flipNormals != (d1.dot(n2) >= 0.f) ? direction : -direction).perpendicular();
|
||||
|
||||
// Compute bevel corner position in (direction, extrusion) coordinates
|
||||
const float sin = std::sqrt(twoSin2 / 2.f);
|
||||
const float u = m_miterLimit * sin;
|
||||
const float v = 1.f - std::sqrt(squaredLengthRatio);
|
||||
|
||||
// Update the outline points
|
||||
m_outlineVertices[outlineIndex++].position = p1;
|
||||
m_outlineVertices[outlineIndex++].position = p1 + (u * extrusion - v * direction) * m_outlineThickness;
|
||||
m_outlineVertices[outlineIndex++].position = p1;
|
||||
m_outlineVertices[outlineIndex++].position = p1 + (u * extrusion + v * direction) * m_outlineThickness;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Combine normals to get the extrusion direction
|
||||
const Vector2f extrusion = (n1 + n2) / twoCos2;
|
||||
|
||||
// Update the outline points
|
||||
m_outlineVertices[outlineIndex++].position = p1;
|
||||
m_outlineVertices[outlineIndex++].position = p1 + extrusion * 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;
|
||||
m_outlineVertices[outlineIndex++].position = m_outlineVertices[0].position;
|
||||
m_outlineVertices[outlineIndex++].position = m_outlineVertices[1].position;
|
||||
|
||||
// Update outline colors
|
||||
updateOutlineColors();
|
||||
|
@ -60,6 +60,7 @@ TEST_CASE("[Graphics] sf::Shape", runDisplayTests())
|
||||
CHECK(triangleShape.getFillColor() == sf::Color::White);
|
||||
CHECK(triangleShape.getOutlineColor() == sf::Color::White);
|
||||
CHECK(triangleShape.getOutlineThickness() == 0.0f);
|
||||
CHECK(triangleShape.getMiterLimit() == 10.0f);
|
||||
CHECK(triangleShape.getLocalBounds() == sf::FloatRect());
|
||||
CHECK(triangleShape.getGlobalBounds() == sf::FloatRect());
|
||||
}
|
||||
@ -100,6 +101,13 @@ TEST_CASE("[Graphics] sf::Shape", runDisplayTests())
|
||||
CHECK(triangleShape.getOutlineThickness() == 3.14f);
|
||||
}
|
||||
|
||||
SECTION("Set/get miter limit")
|
||||
{
|
||||
TriangleShape triangleShape({});
|
||||
triangleShape.setMiterLimit(6.28f);
|
||||
CHECK(triangleShape.getMiterLimit() == 6.28f);
|
||||
}
|
||||
|
||||
SECTION("Virtual functions: getPoint, getPointCount, getGeometricCenter")
|
||||
{
|
||||
const TriangleShape triangleShape({2, 2});
|
||||
@ -130,5 +138,13 @@ TEST_CASE("[Graphics] sf::Shape", runDisplayTests())
|
||||
CHECK(triangleShape.getLocalBounds() == Approx(sf::FloatRect({-7.2150f, -14.2400f}, {44.4300f, 59.2400f})));
|
||||
CHECK(triangleShape.getGlobalBounds() == Approx(sf::FloatRect({-7.2150f, -14.2400f}, {44.4300f, 59.2400f})));
|
||||
}
|
||||
|
||||
SECTION("Add beveled outline")
|
||||
{
|
||||
triangleShape.setMiterLimit(2);
|
||||
triangleShape.setOutlineThickness(5);
|
||||
CHECK(triangleShape.getLocalBounds() == Approx(sf::FloatRect({-7.2150f, -10.f}, {44.4300f, 55.f})));
|
||||
CHECK(triangleShape.getGlobalBounds() == Approx(sf::FloatRect({-7.2150f, -10.f}, {44.4300f, 55.f})));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user