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 );
}
{
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 );
addItem( slider, 1, 1);
@ -59,7 +59,7 @@ namespace
setThickness( 10.0 );
setBorderWidth( 2.0 );
setBorderColor( Qt::black );
setBorderColor( QColor( 0, 0, 0, 150 ) );
#if 0
const QskGradientStops stops =

View File

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

View File

@ -47,15 +47,29 @@ namespace
const auto cos = qFastCos( 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
ellipse tangent:
m = w / h * tan( angle )
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
also be found ( first quadrant ) by:
@ -63,26 +77,32 @@ namespace
atan2( tan( angle ), h / w );
*/
qreal dx, dy;
if ( qFuzzyIsNull( cos ) )
{
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 );
return { 0.0, ( sin > 0.0 ) ? 1.0 : -1.0 };
dx = ( cos >= 0.0 ) ? t : -t;
dy = m * dx;
const qreal m = m_aspectRatio * ( sin / cos );
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 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
@ -136,52 +156,23 @@ namespace
const qreal m_cx;
const qreal m_cy;
};
}
class ArcStroker
namespace
{
public:
ArcStroker( const QRectF&, const QskArcMetrics&, bool radial, const QskGradient& );
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 )
class StrokerBase
{
protected:
StrokerBase( const QRectF& rect, const QskArcMetrics& metrics, bool radial )
: m_rect( rect )
, m_radians1( qDegreesToRadians( metrics.startAngle() ) )
, m_radians2( qDegreesToRadians( metrics.endAngle() ) )
, m_radial( qFuzzyCompare( rect.width(), rect.height() ) ? true : radial )
, m_gradient( gradient )
, m_closed( metrics.isClosed() )
{
}
int ArcStroker::fillCount() const
{
return arcLineCount() + m_gradient.stepCount() - 1;
}
int ArcStroker::arcLineCount() const
int arcLineCount() const
{
// not very sophisticated - TODO ...
@ -192,37 +183,68 @@ namespace
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 QskVertex::Color color ) const
{
Q_UNUSED( lines );
Q_UNUSED( color );
const QRectF& m_rect;
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;
};
}
inline int ArcStroker::setFillLines(
const int length, QskVertex::ColoredLine* lines ) const
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 )
{
}
int FillStroker::lineCount() const
{
return arcLineCount() + m_gradient.stepCount() - 1;
}
inline int FillStroker::setLines(
const qreal length, QskVertex::ColoredLine* lines ) const
{
if ( m_radial )
{
const RadialStroker lineStroker( m_rect, length );
return renderFillLines( lineStroker, lines );
return renderLines( lineStroker, lines );
}
else
{
const OrthogonalStroker lineStroker( m_rect, length );
return renderFillLines( lineStroker, lines );
return renderLines( lineStroker, lines );
}
}
template< class LineStroker >
inline int ArcStroker::renderFillLines(
inline int FillStroker::renderLines(
const LineStroker& stroker, QskVertex::ColoredLine* lines ) const
{
auto l = lines;
@ -259,10 +281,86 @@ namespace
return l - lines;
}
}
inline qreal ArcStroker::radiansAt( qreal progress ) const
namespace
{
return m_radians1 + progress * ( m_radians2 - m_radians1 );
class BorderStroker : StrokerBase
{
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 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 );
if ( lines == nullptr )
return;
const auto effectiveCount = stroker.setFillLines(
const auto effectiveCount = stroker.setLines(
metrics.thickness() - borderWidth, lines );
if ( effectiveCount > lineCount )
@ -315,22 +413,24 @@ void QskArcRenderer::renderFillGeometry( const QRectF& rect,
void QskArcRenderer::renderBorder( const QRectF& rect, const QskArcMetrics& metrics,
bool radial, qreal borderWidth, const QColor& borderColor, QSGGeometry& geometry )
{
Q_UNUSED( borderWidth );
Q_UNUSED( radial );
Q_ASSERT( borderWidth > 0.0 );
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 );
if ( lines )
{
const auto effectiveCount = stroker.setBorderLines( lines, borderColor );
const auto effectiveCount = stroker.setLines(
metrics.thickness(), borderWidth, lines );
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 );
}
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 p2;
};
@ -172,6 +181,15 @@ namespace QskVertex
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 p2;
};