first implementation of arc borders
This commit is contained in:
parent
9086c94715
commit
e937e3941f
|
@ -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 =
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue