first implementation of arc borders

This commit is contained in:
Uwe Rathmann 2024-07-16 10:42:13 +02:00
parent 9086c94715
commit e937e3941f
4 changed files with 197 additions and 79 deletions

View File

@ -41,7 +41,7 @@ namespace
addItem( slider, 1, 0 ); addItem( slider, 1, 0 );
} }
{ {
auto slider = new Slider( "Border", 0, 10, 1, arc->borderWidth() ); auto slider = new Slider( "Border", 0, 50, 1, arc->borderWidth() );
connect( slider, &Slider::valueChanged, arc, &ShadowedArc::setBorderWidth ); connect( slider, &Slider::valueChanged, arc, &ShadowedArc::setBorderWidth );
addItem( slider, 1, 1); addItem( slider, 1, 1);
@ -59,7 +59,7 @@ namespace
setThickness( 10.0 ); setThickness( 10.0 );
setBorderWidth( 2.0 ); setBorderWidth( 2.0 );
setBorderColor( Qt::black ); setBorderColor( QColor( 0, 0, 0, 150 ) );
#if 0 #if 0
const QskGradientStops stops = const QskGradientStops stops =

View File

@ -17,7 +17,7 @@
#include <qpainterpath.h> #include <qpainterpath.h>
// #define ARC_BORDER_NODE #define ARC_BORDER_NODE
#ifdef ARC_BORDER_NODE #ifdef ARC_BORDER_NODE
using BorderNode = QskArcRenderNode; using BorderNode = QskArcRenderNode;

View File

@ -47,15 +47,29 @@ namespace
const auto cos = qFastCos( radians ); const auto cos = qFastCos( radians );
const auto sin = qFastSin( radians ); const auto sin = qFastSin( radians );
const auto x = m_cx + m_rx * cos;
const auto y = m_cy - m_ry * sin;
const auto v = normalVector( cos, sin ) * m_l2;
line.setLine( x + v.x(), y - v.y(), x - v.x(), y + v.y(), color );
}
inline QPointF normalVector( const qreal radians ) const
{
return normalVector( qFastCos( radians ), qFastSin( radians ) );
}
inline QPointF normalVector( const qreal cos, const qreal sin ) const
{
/* /*
The inner/outer points are found by shifting orthogonally along the The inner/outer points are found by shifting orthogonally along the
ellipse tangent: ellipse tangent:
m = w / h * tan( angle ) m = w / h * tan( angle )
y = m * x; y = m * x;
x² + y² = dt x² + y² = 1.0
=> x = dt / sqrt( 1.0 + m² ); => x = 1.0 / sqrt( 1.0 + m² );
Note: the angle of the orthogonal vector could Note: the angle of the orthogonal vector could
also be found ( first quadrant ) by: also be found ( first quadrant ) by:
@ -63,26 +77,32 @@ namespace
atan2( tan( angle ), h / w ); atan2( tan( angle ), h / w );
*/ */
qreal dx, dy;
if ( qFuzzyIsNull( cos ) ) if ( qFuzzyIsNull( cos ) )
{ return { 0.0, ( sin > 0.0 ) ? 1.0 : -1.0 };
dx = 0.0;
dy = ( sin > 0.0 ) ? m_l2 : -m_l2;
}
else
{
const qreal m = m_aspectRatio * ( sin / cos );
const qreal t = m_l2 / qSqrt( 1.0 + m * m );
dx = ( cos >= 0.0 ) ? t : -t; const qreal m = m_aspectRatio * ( sin / cos );
dy = m * dx; const qreal t = 1.0 / qSqrt( 1.0 + m * m );
const auto dx = ( cos >= 0.0 ) ? t : -t;
return { dx, m * dx };
} }
inline void setBorderLines( const qreal radians, qreal border,
const QskVertex::Color color, QskVertex::ColoredLine& outer,
QskVertex::ColoredLine& inner ) const
{
const auto cos = qFastCos( radians );
const auto sin = qFastSin( radians );
const auto x = m_cx + m_rx * cos; const auto x = m_cx + m_rx * cos;
const auto y = m_cy - m_ry * sin; const auto y = m_cy - m_ry * sin;
const auto v = normalVector( cos, sin );
line.setLine( x + dx, y - dy, x - dx, y + dy, color ); const auto v1 = v * m_l2;
const auto v2 = v * ( m_l2 - border );
outer.setLine( x + v1.x(), y - v1.y(), x + v2.x(), y - v2.y(), color );
inner.setLine( x - v1.x(), y + v1.y(), x - v2.x(), y + v2.y(), color );
} }
const qreal m_l2; // half of the length const qreal m_l2; // half of the length
@ -136,52 +156,23 @@ namespace
const qreal m_cx; const qreal m_cx;
const qreal m_cy; const qreal m_cy;
}; };
}
class ArcStroker namespace
{
class StrokerBase
{ {
public: protected:
ArcStroker( const QRectF&, const QskArcMetrics&, bool radial, const QskGradient& ); StrokerBase( const QRectF& rect, const QskArcMetrics& metrics, bool radial )
int fillCount() const;
int borderCount() const;
int setBorderLines( QskVertex::ColoredLine*, const QskVertex::Color ) const;
int setFillLines( int length, QskVertex::ColoredLine* ) const;
private:
int arcLineCount() const;
qreal radiansAt( qreal progress ) const;
template< class LineStroker >
int renderFillLines( const LineStroker&, QskVertex::ColoredLine* ) const;
// radius
const QRectF& m_rect;
const qreal m_radians1;
const qreal m_radians2;
const bool m_radial; // for circular arcs radial/orthogonal does not differ
const QskGradient& m_gradient;
};
ArcStroker::ArcStroker( const QRectF& rect,
const QskArcMetrics& metrics, bool radial, const QskGradient& gradient )
: m_rect( rect ) : m_rect( rect )
, m_radians1( qDegreesToRadians( metrics.startAngle() ) ) , m_radians1( qDegreesToRadians( metrics.startAngle() ) )
, m_radians2( qDegreesToRadians( metrics.endAngle() ) ) , m_radians2( qDegreesToRadians( metrics.endAngle() ) )
, m_radial( qFuzzyCompare( rect.width(), rect.height() ) ? true : radial ) , m_radial( qFuzzyCompare( rect.width(), rect.height() ) ? true : radial )
, m_gradient( gradient ) , m_closed( metrics.isClosed() )
{ {
} }
int ArcStroker::fillCount() const int arcLineCount() const
{
return arcLineCount() + m_gradient.stepCount() - 1;
}
int ArcStroker::arcLineCount() const
{ {
// not very sophisticated - TODO ... // not very sophisticated - TODO ...
@ -192,37 +183,68 @@ namespace
return qBound( 3, count, 160 ); return qBound( 3, count, 160 );
} }
int ArcStroker::borderCount() const inline qreal radiansAt( qreal progress ) const
{ {
return 0; return m_radians1 + progress * ( m_radians2 - m_radians1 );
} }
int ArcStroker::setBorderLines( QskVertex::ColoredLine* lines, const QRectF& m_rect;
const QskVertex::Color color ) const
{
Q_UNUSED( lines );
Q_UNUSED( color );
return 0; const qreal m_radians1;
const qreal m_radians2;
const bool m_radial; // for circular arcs radial/orthogonal does not differ
const bool m_closed;
};
}
namespace
{
class FillStroker : private StrokerBase
{
public:
FillStroker( const QRectF&, const QskArcMetrics&,
bool radial, const QskGradient& );
int lineCount() const;
int setLines( qreal length, QskVertex::ColoredLine* ) const;
private:
template< class LineStroker >
int renderLines( const LineStroker&, QskVertex::ColoredLine* ) const;
const QskGradient& m_gradient;
};
FillStroker::FillStroker( const QRectF& rect,
const QskArcMetrics& metrics, bool radial, const QskGradient& gradient )
: StrokerBase( rect, metrics, radial )
, m_gradient( gradient )
{
} }
inline int ArcStroker::setFillLines( int FillStroker::lineCount() const
const int length, QskVertex::ColoredLine* lines ) const {
return arcLineCount() + m_gradient.stepCount() - 1;
}
inline int FillStroker::setLines(
const qreal length, QskVertex::ColoredLine* lines ) const
{ {
if ( m_radial ) if ( m_radial )
{ {
const RadialStroker lineStroker( m_rect, length ); const RadialStroker lineStroker( m_rect, length );
return renderFillLines( lineStroker, lines ); return renderLines( lineStroker, lines );
} }
else else
{ {
const OrthogonalStroker lineStroker( m_rect, length ); const OrthogonalStroker lineStroker( m_rect, length );
return renderFillLines( lineStroker, lines ); return renderLines( lineStroker, lines );
} }
} }
template< class LineStroker > template< class LineStroker >
inline int ArcStroker::renderFillLines( inline int FillStroker::renderLines(
const LineStroker& stroker, QskVertex::ColoredLine* lines ) const const LineStroker& stroker, QskVertex::ColoredLine* lines ) const
{ {
auto l = lines; auto l = lines;
@ -259,10 +281,86 @@ namespace
return l - lines; return l - lines;
} }
}
inline qreal ArcStroker::radiansAt( qreal progress ) const namespace
{
class BorderStroker : StrokerBase
{ {
return m_radians1 + progress * ( m_radians2 - m_radians1 ); public:
BorderStroker( const QRectF&, const QskArcMetrics&,
bool radial, const QColor& color );
int lineCount() const;
int setLines( qreal length, qreal border, QskVertex::ColoredLine* ) const;
private:
void setClosingLines( const QSGGeometry::ColoredPoint2D&,
QskVertex::ColoredLine* ) const;
const QskVertex::Color m_color;
};
BorderStroker::BorderStroker( const QRectF& rect,
const QskArcMetrics& metrics, bool radial, const QColor& color )
: StrokerBase( rect, metrics, radial )
, m_color( color )
{
}
int BorderStroker::lineCount() const
{
auto count = 2 * arcLineCount();
if ( !m_closed )
count += 2 * 3;
return count;
}
int BorderStroker::setLines( qreal length, qreal border,
QskVertex::ColoredLine* lines ) const
{
const auto count = arcLineCount();
const qreal stepMax = count - 1;
const auto stepSize = ( m_radians2 - m_radians1 ) / stepMax;
auto outer = lines;
if ( !m_closed )
outer += 3;
auto inner = outer + count;
if ( !m_closed )
inner += 3;
OrthogonalStroker stroker( m_rect, length );
for ( int i = 0; i < count; i++ )
{
stroker.setBorderLines( m_radians1 + i * stepSize,
border, m_color, outer[i], inner[count - 1 - i] );
}
if ( !m_closed )
{
setClosingLines( inner[count - 1].p1, outer );
setClosingLines( outer[count - 1].p1, inner );
}
return 2 * ( count + ( m_closed ? 0 : 3 ) );
}
void BorderStroker::setClosingLines( const QSGGeometry::ColoredPoint2D& pos,
QskVertex::ColoredLine* lines ) const
{
const auto& l0 = lines[0];
const auto sign = ( m_radians1 <= m_radians2 ) ? 1.0 : -1.0;
const auto dx = sign * l0.dy();
const auto dy = sign * l0.dx();
lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, m_color );
lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, m_color );
lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), m_color );
} }
} }
@ -283,15 +381,15 @@ void QskArcRenderer::renderFillGeometry( const QRectF& rect,
const auto b2 = 0.5 * borderWidth; const auto b2 = 0.5 * borderWidth;
const auto r = rect.adjusted( b2, b2, -b2, -b2 ); const auto r = rect.adjusted( b2, b2, -b2, -b2 );
ArcStroker stroker( r, metrics, radial, gradient ); FillStroker stroker( r, metrics, radial, gradient );
const auto lineCount = stroker.fillCount(); const auto lineCount = stroker.lineCount();
const auto lines = qskAllocateColoredLines( geometry, lineCount ); const auto lines = qskAllocateColoredLines( geometry, lineCount );
if ( lines == nullptr ) if ( lines == nullptr )
return; return;
const auto effectiveCount = stroker.setFillLines( const auto effectiveCount = stroker.setLines(
metrics.thickness() - borderWidth, lines ); metrics.thickness() - borderWidth, lines );
if ( effectiveCount > lineCount ) if ( effectiveCount > lineCount )
@ -315,22 +413,24 @@ void QskArcRenderer::renderFillGeometry( const QRectF& rect,
void QskArcRenderer::renderBorder( const QRectF& rect, const QskArcMetrics& metrics, void QskArcRenderer::renderBorder( const QRectF& rect, const QskArcMetrics& metrics,
bool radial, qreal borderWidth, const QColor& borderColor, QSGGeometry& geometry ) bool radial, qreal borderWidth, const QColor& borderColor, QSGGeometry& geometry )
{ {
Q_UNUSED( borderWidth ); Q_ASSERT( borderWidth > 0.0 );
Q_UNUSED( radial );
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
ArcStroker stroker( rect, metrics, radial, QskGradient() ); BorderStroker stroker( rect, metrics, radial, borderColor );
const auto lineCount = stroker.borderCount(); const auto lineCount = stroker.lineCount();
const auto lines = qskAllocateColoredLines( geometry, lineCount ); const auto lines = qskAllocateColoredLines( geometry, lineCount );
if ( lines ) if ( lines )
{ {
const auto effectiveCount = stroker.setBorderLines( lines, borderColor ); const auto effectiveCount = stroker.setLines(
metrics.thickness(), borderWidth, lines );
if ( lineCount != effectiveCount ) if ( lineCount != effectiveCount )
{ {
qWarning() << lineCount << effectiveCount; qFatal( "geometry: allocated memory does not match: %d vs. %d",
effectiveCount, lineCount );
} }
} }
} }

View File

@ -133,6 +133,15 @@ namespace QskVertex
setLine( x1, y1, x2, y2 ); setLine( x1, y1, x2, y2 );
} }
inline float x1() const noexcept { return p1.x; }
inline float y1() const noexcept { return p1.y; }
inline float x2() const noexcept { return p2.x; }
inline float y2() const noexcept { return p2.y; }
inline float dx() const noexcept { return p2.x - p1.x; }
inline float dy() const noexcept { return p2.y - p1.y; }
QSGGeometry::Point2D p1; QSGGeometry::Point2D p1;
QSGGeometry::Point2D p2; QSGGeometry::Point2D p2;
}; };
@ -172,6 +181,15 @@ namespace QskVertex
setLine( x, y1, color, x, y2, color ); setLine( x, y1, color, x, y2, color );
} }
inline float x1() const noexcept { return p1.x; }
inline float y1() const noexcept { return p1.y; }
inline float x2() const noexcept { return p2.x; }
inline float y2() const noexcept { return p2.y; }
inline float dx() const noexcept { return p2.x - p1.x; }
inline float dy() const noexcept { return p2.y - p1.y; }
QSGGeometry::ColoredPoint2D p1; QSGGeometry::ColoredPoint2D p1;
QSGGeometry::ColoredPoint2D p2; QSGGeometry::ColoredPoint2D p2;
}; };