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