QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
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#include "qgspainting.h"
20#include "qgsfillsymbol.h"
21#include "qgssymbollayerutils.h"
22
27
29
31{
32 return QStringLiteral( "texture" );
33}
34
36{
37 std::unique_ptr< QgsTiledSceneTextureRenderer > res = std::make_unique< QgsTiledSceneTextureRenderer >();
38 res->setFillSymbol( mFillSymbol->clone() );
39
40 copyCommonProperties( res.get() );
41
42 return res.release();
43}
44
46{
47 std::unique_ptr< QgsTiledSceneTextureRenderer > r = std::make_unique< QgsTiledSceneTextureRenderer >();
48 {
49 const QDomElement fillSymbolElem = element.firstChildElement( QStringLiteral( "fillSymbol" ) );
50 if ( !fillSymbolElem.isNull() )
51 {
52 const QDomElement symbolElem = fillSymbolElem.firstChildElement( QStringLiteral( "symbol" ) );
53 std::unique_ptr< QgsFillSymbol > fillSymbol( QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context ) );
54 if ( fillSymbol )
55 r->mFillSymbol = std::move( fillSymbol );
56 }
57 }
58
59 r->restoreCommonProperties( element, context );
60
61 return r.release();
62}
63
65{
66 QVariantMap properties;
67 properties.insert( QStringLiteral( "color" ), QStringLiteral( "224,224,224" ) );
68 properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
69 properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
70 properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "124,124,124" ) );
71 properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.1" ) );
72 properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "round" ) );
73
74 return QgsFillSymbol::createSimple( properties );
75}
76
78{
79 return mFillSymbol.get();
80}
81
83{
84 mFillSymbol.reset( symbol );
85}
86
87QDomElement QgsTiledSceneTextureRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
88{
89 QDomElement rendererElem = doc.createElement( QStringLiteral( "renderer" ) );
90
91 rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "texture" ) );
92
93 {
94 QDomElement fillSymbolElem = doc.createElement( QStringLiteral( "fillSymbol" ) );
95 const QDomElement symbolElement = QgsSymbolLayerUtils::saveSymbol( QString(),
96 mFillSymbol.get(),
97 doc,
98 context );
99 fillSymbolElem.appendChild( symbolElement );
100 rendererElem.appendChild( fillSymbolElem );
101 }
102
103 saveCommonProperties( rendererElem, context );
104
105 return rendererElem;
106}
107
109{
110 // force raster rendering for this renderer type -- there's no benefit in exporting these layers as a bunch
111 // of triangular images which are pieced together, that adds a lot of extra content to the exports and results
112 // in files which can be extremely slow to open and render in other viewers.
116}
117
119{
120 if ( context.textureImage().isNull() )
121 {
122 mFillSymbol->renderPolygon( triangle, nullptr, nullptr, context.renderContext() );
123 return;
124 }
125
126 float textureX1;
127 float textureY1;
128 float textureX2;
129 float textureY2;
130 float textureX3;
131 float textureY3;
132 context.textureCoordinates( textureX1, textureY1, textureX2, textureY2, textureX3, textureY3 );
133
134 QPainter *painter = context.renderContext().painter();
135 painter->setPen( Qt::NoPen );
136
137 auto unitNormal = []( const QPointF p1, const QPointF p2 )
138 {
139 const float dx = p2.x() - p1.x();
140 const float dy = p2.y() - p1.y();
141 QPointF n( -dy, dx );
142 const double length = std::sqrt( n.x() * n.x() + n.y() * n.y() );
143 return QPointF( n.x() / length, n.y() / length );
144 };
145
146 auto intersect = []( const QPointF p1, const QPointF p2, const QPointF q1, const QPointF q2 )
147 {
148 const double a1 = p2.y() - p1.y();
149 const double b1 = p1.x() - p2.x();
150 const double c1 = a1 * p1.x() + b1 * p1.y();
151
152 const double a2 = q2.y() - q1.y();
153 const double b2 = q1.x() - q2.x();
154 const double c2 = a2 * q1.x() + b2 * q1.y();
155
156 const double det = a1 * b2 - a2 * b1;
157
158 if ( qgsDoubleNear( det, 0 ) )
159 {
160 return QPointF();
161 }
162 else
163 {
164 return QPointF( ( b2 * c1 - b1 * c2 ) / det,
165 ( a1 * c2 - a2 * c1 ) / det );
166 }
167 };
168
169 auto smallestAngleInTriangle = []( const QPolygonF & triangle )
170 {
171 const QPointF p1 = triangle.at( 0 );
172 const QPointF p2 = triangle.at( 1 );
173 const QPointF p3 = triangle.at( 2 );
174
175 const QPointF v1 = p2 - p1;
176 const QPointF v2 = p3 - p2;
177 const QPointF v3 = p1 - p3;
178
179 const double a = std::sqrt( v1.x() * v1.x() + v1.y() * v1.y() );
180 const double b = std::sqrt( v2.x() * v2.x() + v2.y() * v2.y() );
181 const double c = std::sqrt( v3.x() * v3.x() + v3.y() * v3.y() );
182
183 return std::min(
184 std::min(
185 std::acos( ( b * b + c * c - a * a ) / ( 2 * b * c ) ),
186 std::acos( ( a * a + c * c - b * b ) / ( 2 * a * c ) ) ),
187 std::acos( ( a * a + b * b - c * c ) / ( 2 * a * b ) )
188 );
189 };
190
191 auto growTriangle = [&unitNormal, &intersect]( const QPolygonF & triangle, float pixels )
192 {
193 QPair< QPointF, QPointF > offsetEdges[3];
194 for ( int i = 0; i < 3; ++i )
195 {
196 const QPointF p1 = triangle.at( i );
197 const QPointF p2 = triangle.at( i + 1 );
198 const QPointF n = unitNormal( p1, p2 );
199
200 const QPointF offsetP1( p1.x() + n.x() * pixels, p1.y() + n.y() * pixels );
201 const QPointF offsetP2( p2.x() + n.x() * pixels, p2.y() + n.y() * pixels );
202
203 offsetEdges[i] = { offsetP1, offsetP2 };
204 }
205
206 QPolygonF result;
207 result.reserve( 4 );
208 // limit triangle vertices to shifting AT MOST 2 pixels from their original locations, to avoid narrow triangles
209 // getting buffered too large
210 static double constexpr MAX_TRIANGLE_GROW_PIXELS_SQUARED = 3 * 3;
211 for ( int i = 0; i < 3; ++i )
212 {
213 const auto &edge1 = offsetEdges[i];
214 const auto &edge2 = offsetEdges[i == 0 ? 2 : ( i - 1 )];
215
216 const QPointF vertex = intersect( edge1.first, edge1.second, edge2.first, edge2.second );
217 if ( vertex.isNull() )
218 return triangle;
219
220 const QPointF originalPoint = triangle.at( i );
221
222 // don't allow triangle to grow too large
223 double delta = std::pow( vertex.x() - originalPoint.x(), 2 ) + std::pow( vertex.y() - originalPoint.y(), 2 );
224 if ( delta > MAX_TRIANGLE_GROW_PIXELS_SQUARED )
225 {
226 double dx = ( vertex.x() - originalPoint.x() ) * MAX_TRIANGLE_GROW_PIXELS_SQUARED / delta;
227 double dy = ( vertex.y() - originalPoint.y() ) * MAX_TRIANGLE_GROW_PIXELS_SQUARED / delta;
228 result << triangle.at( i ) + QPointF( dx, dy );
229 }
230 else
231 result << vertex;
232 }
233 result << result.at( 0 );
234 return result;
235 };
236
237 // buffer the triangles out slightly to reduce artifacts caused by antialiasing,
238 // but try to avoid new artifacts caused by buffering narrow triangles
239 const double minAngle = smallestAngleInTriangle( triangle ) * 180 / M_PI;
240 if ( std::isnan( minAngle ) || minAngle < 0.1 )
241 {
242 // don't try to draw slivers
243 return;
244 }
245
247 painter,
248 growTriangle( triangle, 1 ),
249 context.textureImage(),
250 textureX1, textureY1,
251 textureX2, textureY2,
252 textureX3, textureY3
253 );
254}
255
260
262{
264 mFillSymbol->startRender( context.renderContext() );
265}
266
268{
269 mFillSymbol->stopRender( context.renderContext() );
270
272}
@ RequiresTextures
Renderer requires textures.
@ ForceRasterRender
Layer should always be rendered as a raster image.
@ RendersTriangles
Renderer can render triangle primitives.
QFlags< TiledSceneRendererFlag > TiledSceneRendererFlags
Flags which control how tiled scene 2D renderers behave.
Definition qgis.h:5303
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static 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.
The class is used as a container of context for various read/write operations on other objects.
QPainter * painter()
Returns the destination QPainter for the render operation.
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.
Abstract base class for 2d tiled scene renderers.
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.
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.
static QgsFillSymbol * createDefaultFillSymbol()
Returns a copy of the default fill symbol used to render triangles without textures.
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 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:5917