QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgstiledscenetexturerenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgstiledscenetexturerenderer.h
3 --------------------
4 begin : August 2023
5 copyright : (C) 2023 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19
20#include "qgsfillsymbol.h"
21#include "qgspainting.h"
22#include "qgssymbollayerutils.h"
23
28
30
32{
33 return QStringLiteral( "texture" );
34}
35
37{
38 auto res = std::make_unique< QgsTiledSceneTextureRenderer >();
39 res->setFillSymbol( mFillSymbol->clone() );
40
41 copyCommonProperties( res.get() );
42
43 return res.release();
44}
45
47{
48 auto r = std::make_unique< QgsTiledSceneTextureRenderer >();
49 {
50 const QDomElement fillSymbolElem = element.firstChildElement( QStringLiteral( "fillSymbol" ) );
51 if ( !fillSymbolElem.isNull() )
52 {
53 const QDomElement symbolElem = fillSymbolElem.firstChildElement( QStringLiteral( "symbol" ) );
54 std::unique_ptr< QgsFillSymbol > fillSymbol( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context ) );
55 if ( fillSymbol )
56 r->mFillSymbol = std::move( fillSymbol );
57 }
58 }
59
60 r->restoreCommonProperties( element, context );
61
62 return r.release();
63}
64
66{
67 QVariantMap properties;
68 properties.insert( QStringLiteral( "color" ), QStringLiteral( "224,224,224" ) );
69 properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
70 properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
71 properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "124,124,124" ) );
72 properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.1" ) );
73 properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "round" ) );
74
75 return QgsFillSymbol::createSimple( properties );
76}
77
79{
80 return mFillSymbol.get();
81}
82
84{
85 mFillSymbol.reset( symbol );
86}
87
88QDomElement QgsTiledSceneTextureRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
89{
90 QDomElement rendererElem = doc.createElement( QStringLiteral( "renderer" ) );
91
92 rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "texture" ) );
93
94 {
95 QDomElement fillSymbolElem = doc.createElement( QStringLiteral( "fillSymbol" ) );
96 const QDomElement symbolElement = QgsSymbolLayerUtils::saveSymbol( QString(),
97 mFillSymbol.get(),
98 doc,
99 context );
100 fillSymbolElem.appendChild( symbolElement );
101 rendererElem.appendChild( fillSymbolElem );
102 }
103
104 saveCommonProperties( rendererElem, context );
105
106 return rendererElem;
107}
108
110{
111 // force raster rendering for this renderer type -- there's no benefit in exporting these layers as a bunch
112 // of triangular images which are pieced together, that adds a lot of extra content to the exports and results
113 // in files which can be extremely slow to open and render in other viewers.
117}
118
120{
121 if ( context.textureImage().isNull() )
122 {
123 mFillSymbol->renderPolygon( triangle, nullptr, nullptr, context.renderContext() );
124 return;
125 }
126
127 float textureX1;
128 float textureY1;
129 float textureX2;
130 float textureY2;
131 float textureX3;
132 float textureY3;
133 context.textureCoordinates( textureX1, textureY1, textureX2, textureY2, textureX3, textureY3 );
134
135 QPainter *painter = context.renderContext().painter();
136 painter->setPen( Qt::NoPen );
137
138 auto unitNormal = []( const QPointF p1, const QPointF p2 )
139 {
140 const float dx = p2.x() - p1.x();
141 const float dy = p2.y() - p1.y();
142 QPointF n( -dy, dx );
143 const double length = std::sqrt( n.x() * n.x() + n.y() * n.y() );
144 return QPointF( n.x() / length, n.y() / length );
145 };
146
147 auto intersect = []( const QPointF p1, const QPointF p2, const QPointF q1, const QPointF q2 )
148 {
149 const double a1 = p2.y() - p1.y();
150 const double b1 = p1.x() - p2.x();
151 const double c1 = a1 * p1.x() + b1 * p1.y();
152
153 const double a2 = q2.y() - q1.y();
154 const double b2 = q1.x() - q2.x();
155 const double c2 = a2 * q1.x() + b2 * q1.y();
156
157 const double det = a1 * b2 - a2 * b1;
158
159 if ( qgsDoubleNear( det, 0 ) )
160 {
161 return QPointF();
162 }
163 else
164 {
165 return QPointF( ( b2 * c1 - b1 * c2 ) / det,
166 ( a1 * c2 - a2 * c1 ) / det );
167 }
168 };
169
170 auto smallestAngleInTriangle = []( const QPolygonF & triangle )
171 {
172 const QPointF p1 = triangle.at( 0 );
173 const QPointF p2 = triangle.at( 1 );
174 const QPointF p3 = triangle.at( 2 );
175
176 const QPointF v1 = p2 - p1;
177 const QPointF v2 = p3 - p2;
178 const QPointF v3 = p1 - p3;
179
180 const double a = std::sqrt( v1.x() * v1.x() + v1.y() * v1.y() );
181 const double b = std::sqrt( v2.x() * v2.x() + v2.y() * v2.y() );
182 const double c = std::sqrt( v3.x() * v3.x() + v3.y() * v3.y() );
183
184 return std::min(
185 std::min(
186 std::acos( ( b * b + c * c - a * a ) / ( 2 * b * c ) ),
187 std::acos( ( a * a + c * c - b * b ) / ( 2 * a * c ) ) ),
188 std::acos( ( a * a + b * b - c * c ) / ( 2 * a * b ) )
189 );
190 };
191
192 auto growTriangle = [&unitNormal, &intersect]( const QPolygonF & triangle, float pixels )
193 {
194 QPair< QPointF, QPointF > offsetEdges[3];
195 for ( int i = 0; i < 3; ++i )
196 {
197 const QPointF p1 = triangle.at( i );
198 const QPointF p2 = triangle.at( i + 1 );
199 const QPointF n = unitNormal( p1, p2 );
200
201 const QPointF offsetP1( p1.x() + n.x() * pixels, p1.y() + n.y() * pixels );
202 const QPointF offsetP2( p2.x() + n.x() * pixels, p2.y() + n.y() * pixels );
203
204 offsetEdges[i] = { offsetP1, offsetP2 };
205 }
206
207 QPolygonF result;
208 result.reserve( 4 );
209 // limit triangle vertices to shifting AT MOST 2 pixels from their original locations, to avoid narrow triangles
210 // getting buffered too large
211 static double constexpr MAX_TRIANGLE_GROW_PIXELS_SQUARED = 3 * 3;
212 for ( int i = 0; i < 3; ++i )
213 {
214 const auto &edge1 = offsetEdges[i];
215 const auto &edge2 = offsetEdges[i == 0 ? 2 : ( i - 1 )];
216
217 const QPointF vertex = intersect( edge1.first, edge1.second, edge2.first, edge2.second );
218 if ( vertex.isNull() )
219 return triangle;
220
221 const QPointF originalPoint = triangle.at( i );
222
223 // don't allow triangle to grow too large
224 double delta = std::pow( vertex.x() - originalPoint.x(), 2 ) + std::pow( vertex.y() - originalPoint.y(), 2 );
225 if ( delta > MAX_TRIANGLE_GROW_PIXELS_SQUARED )
226 {
227 double dx = ( vertex.x() - originalPoint.x() ) * MAX_TRIANGLE_GROW_PIXELS_SQUARED / delta;
228 double dy = ( vertex.y() - originalPoint.y() ) * MAX_TRIANGLE_GROW_PIXELS_SQUARED / delta;
229 result << triangle.at( i ) + QPointF( dx, dy );
230 }
231 else
232 result << vertex;
233 }
234 result << result.at( 0 );
235 return result;
236 };
237
238 // buffer the triangles out slightly to reduce artifacts caused by antialiasing,
239 // but try to avoid new artifacts caused by buffering narrow triangles
240 const double minAngle = smallestAngleInTriangle( triangle ) * 180 / M_PI;
241 if ( std::isnan( minAngle ) || minAngle < 0.1 )
242 {
243 // don't try to draw slivers
244 return;
245 }
246
248 painter,
249 growTriangle( triangle, 1 ),
250 context.textureImage(),
251 textureX1, textureY1,
252 textureX2, textureY2,
253 textureX3, textureY3
254 );
255}
256
261
263{
265 mFillSymbol->startRender( context.renderContext() );
266}
267
269{
270 mFillSymbol->stopRender( context.renderContext() );
271
273}
@ RequiresTextures
Renderer requires textures.
Definition qgis.h:5701
@ ForceRasterRender
Layer should always be rendered as a raster image.
Definition qgis.h:5702
@ RendersTriangles
Renderer can render triangle primitives.
Definition qgis.h:5703
QFlags< TiledSceneRendererFlag > TiledSceneRendererFlags
Flags which control how tiled scene 2D renderers behave.
Definition qgis.h:5713
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static std::unique_ptr< QgsFillSymbol > createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
static bool drawTriangleUsingTexture(QPainter *painter, const QPolygonF &triangle, const QImage &textureImage, float textureX1, float textureY1, float textureX2, float textureY2, float textureX3, float textureY3)
Draws a triangle onto a painter using a mapped texture image.
A container for the context for various read/write operations on objects.
QPainter * painter()
Returns the destination QPainter for the render operation.
static std::unique_ptr< QgsSymbol > loadSymbol(const QDomElement &element, const QgsReadWriteContext &context)
Attempts to load a symbol from a DOM element.
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
Encapsulates the render context for a 2D tiled scene rendering operation.
void textureCoordinates(float &textureX1, float &textureY1, float &textureX2, float &textureY2, float &textureX3, float &textureY3) const
Returns the current texture coordinates.
QgsRenderContext & renderContext()
Returns a reference to the context's render context.
QImage textureImage() const
Returns the current texture image.
void saveCommonProperties(QDomElement &element, const QgsReadWriteContext &context) const
Saves common renderer properties (such as point size and screen error) to the specified DOM element.
virtual void stopRender(QgsTiledSceneRenderContext &context)
Must be called when a render cycle has finished, to allow the renderer to clean up.
QgsTiledSceneRenderer()=default
virtual void startRender(QgsTiledSceneRenderContext &context)
Must be called when a new render cycle is started.
void copyCommonProperties(QgsTiledSceneRenderer *destination) const
Copies common tiled scene renderer properties (such as screen error) to the destination renderer.
void startRender(QgsTiledSceneRenderContext &context) override
Must be called when a new render cycle is started.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const override
Saves the renderer configuration to an XML element.
~QgsTiledSceneTextureRenderer() override
QgsTiledSceneTextureRenderer()
Constructor for QgsTiledSceneTextureRenderer.
QgsFillSymbol * fillSymbol() const
Returns the fill symbol used to render triangles without textures.
void renderLine(QgsTiledSceneRenderContext &context, const QPolygonF &line) override
Renders a line.
QgsTiledSceneRenderer * clone() const override
Create a deep copy of this renderer.
void setFillSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used to render triangles without textures.
QString type() const override
Returns the identifier of the renderer type.
static std::unique_ptr< QgsFillSymbol > createDefaultFillSymbol()
Returns a copy of the default fill symbol used to render triangles without textures.
static QgsTiledSceneRenderer * create(QDomElement &element, const QgsReadWriteContext &context)
Creates a textured renderer from an XML element.
void stopRender(QgsTiledSceneRenderContext &context) override
Must be called when a render cycle has finished, to allow the renderer to clean up.
void renderTriangle(QgsTiledSceneRenderContext &context, const QPolygonF &triangle) override
Renders a triangle.
Qgis::TiledSceneRendererFlags flags() const override
Returns flags which control how the renderer behaves.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607