[FEATURE] rendering of 3d wide lines, rendering of polygon edges

This commit is contained in:
Martin Dobias 2019-04-04 16:59:36 +02:00
parent d15cae9ae2
commit 28b349f04a
13 changed files with 839 additions and 81 deletions

View File

@ -35,6 +35,8 @@ SET(QGIS_3D_SRCS
symbols/qgsabstract3dsymbol.cpp
symbols/qgsline3dsymbol.cpp
symbols/qgsline3dsymbol_p.cpp
symbols/qgslinematerial_p.cpp
symbols/qgslinevertexdata_p.cpp
symbols/qgsmesh3dsymbol.cpp
symbols/qgsmesh3dsymbol_p.cpp
symbols/qgspoint3dsymbol.cpp
@ -75,6 +77,8 @@ SET(QGIS_3D_MOC_HDRS
processing/qgs3dalgorithms.h
symbols/qgslinematerial_p.h
terrain/qgsdemterraintileloader_p.h
terrain/qgsflatterraingenerator.h
terrain/qgsterrainentity_p.h
@ -122,6 +126,8 @@ SET(QGIS_3D_HDRS
symbols/qgsabstract3dsymbol.h
symbols/qgsline3dsymbol.h
symbols/qgsline3dsymbol_p.h
symbols/qgslinematerial_p.h
symbols/qgslinevertexdata_p.h
symbols/qgsmesh3dsymbol.h
symbols/qgsmesh3dsymbol_p.h
symbols/qgspoint3dsymbol.h

View File

@ -50,6 +50,8 @@
#include "qgsvectorlayer.h"
#include "qgsvectorlayer3drenderer.h"
#include "qgslinematerial_p.h"
Qgs3DMapScene::Qgs3DMapScene( const Qgs3DMapSettings &map, QgsAbstract3DEngine *engine )
: mMap( map )
, mEngine( engine )
@ -570,6 +572,25 @@ void Qgs3DMapScene::addLayerEntity( QgsMapLayer *layer )
newEntity->addComponent( picker );
connect( picker, &Qt3DRender::QObjectPicker::pressed, this, &Qgs3DMapScene::onLayerEntityPickEvent );
}
// this is probably not the best place for material-specific configuration,
// maybe this could be more generalized when other materials need some specific treatment
QgsLineMaterial *lm = newEntity->findChild<QgsLineMaterial *>();
if ( lm )
{
connect( mCameraController, &QgsCameraController::cameraChanged, lm, [lm, this]
{
Qt3DRender::QCamera *cam = mCameraController->camera();
lm->setCameraParameters( cam->position(), cam->viewVector(), cam->nearPlane() );
} );
connect( mCameraController, &QgsCameraController::viewportChanged, lm, [lm, this]
{
lm->setViewportSize( mCameraController->viewport().size() );
} );
lm->setCameraParameters( cameraController()->camera()->position(), cameraController()->camera()->viewVector(), cameraController()->camera()->nearPlane() );
lm->setViewportSize( cameraController()->viewport().size() );
}
}
}

View File

@ -3,5 +3,8 @@
<file>shaders/instanced.frag</file>
<file>shaders/instanced.vert</file>
<file>shaders/light.inc.frag</file>
<file>shaders/lines.vert</file>
<file>shaders/lines.geom</file>
<file>shaders/lines.frag</file>
</qresource>
</RCC>

30
src/3d/shaders/lines.frag Normal file
View File

@ -0,0 +1,30 @@
#version 150
uniform vec4 lineColor;
uniform bool useTex;
uniform sampler2D tex0;
in VertexData{
vec2 mTexCoord;
// vec3 mColor;
} VertexIn;
out vec4 oColor;
void main(void)
{
if (!useTex)
{
// option 1: plain color
oColor = lineColor;
}
else
{
// option 2: textured color
oColor = texture(tex0, VertexIn.mTexCoord.xy );
}
//vec4 clr = texture( tex0, VertexIn.mTexCoord.xy );
//oColor.rgb = VertexIn.mColor * clr.rgb;
//oColor.a = clr.a;
}

174
src/3d/shaders/lines.geom Normal file
View File

@ -0,0 +1,174 @@
#version 150
uniform float THICKNESS; // the thickness of the line in pixels
uniform float MITER_LIMIT; // 1.0: always miter, -1.0: never miter, 0.75: default
uniform vec2 WIN_SCALE; // the size of the viewport in pixels
uniform mat4 modelViewProjection;
uniform vec3 camNearPlanePoint;
uniform vec3 camNearPlaneNormal;
layout( lines_adjacency ) in;
layout( triangle_strip, max_vertices = 7 ) out;
in VertexData{
vec3 worldPosition;
// vec3 mColor;
} VertexIn[4];
out VertexData{
vec2 mTexCoord;
// vec3 mColor;
} VertexOut;
vec2 toScreenSpace( vec4 vertex )
{
return vec2( vertex.xy / vertex.w ) * WIN_SCALE;
}
vec4 clip_line_point(vec3 pt0, vec3 pt1, vec4 projected)
{
// we have line segment given by pt0 and pt1 (in world coordinates) and 'projected' point
// (in clip coordinates) that is one of the endpoints. If the projected point's w >= 1
// then everything is fine because the point is in front of the camera's near plane and
// it is projected correctly. If not, the projected point is wrong and needs to be adjusted.
// we place it at the intersection of the line and near plane to fix its position.
if (projected.w < 1)
{
vec3 lineDir = pt1 - pt0;
float d = dot(camNearPlaneNormal, camNearPlanePoint - pt0) / dot(lineDir, camNearPlaneNormal);
if (d > 0 && d < 1)
{
// figure out the intersection point of line and near plane
vec3 wpIntersect = pt0 + lineDir * d;
vec4 wpIntersectProj = modelViewProjection * vec4( wpIntersect, 1.0 );
return wpIntersectProj;
}
}
return projected;
}
void main( void )
{
// these are original positions in world coordinates
vec3 wp0 = VertexIn[0].worldPosition;
vec3 wp1 = VertexIn[1].worldPosition;
vec3 wp2 = VertexIn[2].worldPosition;
vec3 wp3 = VertexIn[3].worldPosition;
// Perform line clipping first. we search for intersection between the line and the near plane.
// In case the near plane intersects line between segment's endpoints, we need to adjust the line
// otherwise we would use completely non-sense points when points get 'behind' the camera.
// We do this also for the 'previous' and 'next' segments to get the miters right.
vec4 projp0 = clip_line_point(wp0, wp1, gl_in[0].gl_Position);
vec4 projp1 = clip_line_point(wp1, wp2, gl_in[1].gl_Position);
vec4 projp2 = clip_line_point(wp1, wp2, gl_in[2].gl_Position);
vec4 projp3 = clip_line_point(wp2, wp3, gl_in[3].gl_Position);
// get the four vertices passed to the shader:
vec2 p0 = toScreenSpace( projp0 ); // start of previous segment
vec2 p1 = toScreenSpace( projp1 ); // end of previous segment, start of current segment
vec2 p2 = toScreenSpace( projp2 ); // end of current segment, start of next segment
vec2 p3 = toScreenSpace( projp3 ); // end of next segment
// these are already 'final' depths in range [0,1] so we don't need to further transform them
float p1z = projp1.z / projp1.w;
float p2z = projp2.z / projp2.w;
// determine the direction of each of the 3 segments (previous, current, next)
vec2 v0 = normalize( p1 - p0 );
vec2 v1 = normalize( p2 - p1 );
vec2 v2 = normalize( p3 - p2 );
// Martin's addition to fix flicker on starting ending point of lines
if (p1 == p0) v0 = v1;
if (p3 == p2) v2 = v1;
// determine the normal of each of the 3 segments (previous, current, next)
vec2 n0 = vec2( -v0.y, v0.x );
vec2 n1 = vec2( -v1.y, v1.x );
vec2 n2 = vec2( -v2.y, v2.x );
// determine miter lines by averaging the normals of the 2 segments
vec2 miter_a = normalize( n0 + n1 ); // miter at start of current segment
vec2 miter_b = normalize( n1 + n2 ); // miter at end of current segment
// determine the length of the miter by projecting it onto normal and then inverse it
float length_a = THICKNESS / dot( miter_a, n1 );
float length_b = THICKNESS / dot( miter_b, n1 );
// prevent excessively long miters at sharp corners
if( dot( v0, v1 ) < -MITER_LIMIT ) {
miter_a = n1;
length_a = THICKNESS;
// close the gap
if( dot( v0, n1 ) > 0 ) {
VertexOut.mTexCoord = vec2( 0, 0 );
//VertexOut.mColor = VertexIn[1].mColor;
gl_Position = vec4( ( p1 + THICKNESS * n1 ) / WIN_SCALE, p1z, 1.0 );
EmitVertex();
VertexOut.mTexCoord = vec2( 0, 0 );
//VertexOut.mColor = VertexIn[1].mColor;
gl_Position = vec4( ( p1 + THICKNESS * n0 ) / WIN_SCALE, p1z, 1.0 );
EmitVertex();
VertexOut.mTexCoord = vec2( 0, 0.5 );
//VertexOut.mColor = VertexIn[1].mColor;
gl_Position = vec4( p1 / WIN_SCALE, p1z, 1.0 );
EmitVertex();
EndPrimitive();
}
else {
VertexOut.mTexCoord = vec2( 0, 1 );
//VertexOut.mColor = VertexIn[1].mColor;
gl_Position = vec4( ( p1 - THICKNESS * n0 ) / WIN_SCALE, p1z, 1.0 );
EmitVertex();
VertexOut.mTexCoord = vec2( 0, 1 );
//VertexOut.mColor = VertexIn[1].mColor;
gl_Position = vec4( ( p1 - THICKNESS * n1 ) / WIN_SCALE, p1z, 1.0 );
EmitVertex();
VertexOut.mTexCoord = vec2( 0, 0.5 );
//VertexOut.mColor = VertexIn[1].mColor;
gl_Position = vec4( p1 / WIN_SCALE, p1z, 1.0 );
EmitVertex();
EndPrimitive();
}
}
if( dot( v1, v2 ) < -MITER_LIMIT ) {
miter_b = n1;
length_b = THICKNESS;
}
// generate the triangle strip
VertexOut.mTexCoord = vec2( 0, 0 );
//VertexOut.mColor = VertexIn[1].mColor;
gl_Position = vec4( ( p1 + length_a * miter_a ) / WIN_SCALE, p1z, 1.0 );
EmitVertex();
VertexOut.mTexCoord = vec2( 0, 1 );
//VertexOut.mColor = VertexIn[1].mColor;
gl_Position = vec4( ( p1 - length_a * miter_a ) / WIN_SCALE, p1z, 1.0 );
EmitVertex();
VertexOut.mTexCoord = vec2( 0, 0 );
//VertexOut.mColor = VertexIn[2].mColor;
gl_Position = vec4( ( p2 + length_b * miter_b ) / WIN_SCALE, p2z, 1.0 );
EmitVertex();
VertexOut.mTexCoord = vec2( 0, 1 );
//VertexOut.mColor = VertexIn[2].mColor;
gl_Position = vec4( ( p2 - length_b * miter_b ) / WIN_SCALE, p2z, 1.0 );
EmitVertex();
EndPrimitive();
}

19
src/3d/shaders/lines.vert Normal file
View File

@ -0,0 +1,19 @@
#version 150
uniform mat4 modelViewProjection;
in vec3 vertexPosition;
//in vec3 ciColor;
out VertexData{
// vec3 mColor;
vec3 worldPosition;
} VertexOut;
void main(void)
{
//VertexOut.mColor = ciColor;
gl_Position = modelViewProjection * vec4( vertexPosition, 1.0 );
VertexOut.worldPosition = vertexPosition;
}

View File

@ -16,6 +16,8 @@
#include "qgsline3dsymbol_p.h"
#include "qgsline3dsymbol.h"
#include "qgslinematerial_p.h"
#include "qgslinevertexdata_p.h"
#include "qgstessellatedpolygongeometry.h"
#include "qgs3dmapsettings.h"
//#include "qgsterraingenerator.h"
@ -186,9 +188,6 @@ class QgsSimpleLine3DSymbolHandler : public QgsFeature3DHandler
QgsSimpleLine3DSymbolHandler( const QgsLine3DSymbol &symbol, const QgsFeatureIds &selectedIds )
: mSymbol( symbol ), mSelectedIds( selectedIds )
{
// the first index is invalid, we use it for primitive restart
outNormal.vertices << QVector3D();
outSelected.vertices << QVector3D();
}
bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
@ -197,14 +196,7 @@ class QgsSimpleLine3DSymbolHandler : public QgsFeature3DHandler
private:
//! temporary data we will pass to the tessellator
struct LineData
{
QVector<QVector3D> vertices;
QVector<unsigned int> indexes;
};
void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected );
void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
// input specific for this class
@ -213,59 +205,44 @@ class QgsSimpleLine3DSymbolHandler : public QgsFeature3DHandler
QgsFeatureIds mSelectedIds;
// outputs
LineData outNormal; //!< Features that are not selected
LineData outSelected; //!< Features that are selected
QgsLineVertexData outNormal; //!< Features that are not selected
QgsLineVertexData outSelected; //!< Features that are selected
};
bool QgsSimpleLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
{
Q_UNUSED( context );
Q_UNUSED( attributeNames );
outNormal.init( mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), &context.map() );
outSelected.init( mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), &context.map() );
return true;
}
void QgsSimpleLine3DSymbolHandler::processFeature( QgsFeature &f, const Qgs3DRenderContext &context )
{
Q_UNUSED( context );
if ( f.geometry().isNull() )
return;
LineData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
QgsPoint centroid;
if ( mSymbol.altitudeBinding() == Qgs3DTypes::AltBindCentroid )
centroid = QgsPoint( f.geometry().centroid().asPoint() );
QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
QgsGeometry geom = f.geometry();
const QgsAbstractGeometry *g = geom.constGet();
if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
{
for ( int i = 0; i < ls->vertexCount(); ++i )
{
QgsPoint p = ls->pointN( i );
float z = Qgs3DUtils::clampAltitude( p, mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), centroid, context.map() );
out.vertices << QVector3D( p.x() - context.map().origin().x(), z, -( p.y() - context.map().origin().y() ) );
out.indexes << out.vertices.count() - 1;
}
out.addLineString( *ls );
}
else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
{
for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
{
const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( mls->geometryN( nGeom ) );
for ( int i = 0; i < ls->vertexCount(); ++i )
{
QgsPoint p = ls->pointN( i );
float z = Qgs3DUtils::clampAltitude( p, mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), centroid, context.map() );
out.vertices << QVector3D( p.x() - context.map().origin().x(), z, -( p.y() - context.map().origin().y() ) );
out.indexes << out.vertices.count() - 1;
}
out.indexes << 0; // add primitive restart
out.addLineString( *ls );
}
}
out.indexes << 0; // add primitive restart
}
void QgsSimpleLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
@ -276,7 +253,7 @@ void QgsSimpleLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qg
}
void QgsSimpleLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, LineData &out, bool selected )
void QgsSimpleLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
{
if ( out.indexes.isEmpty() )
return;
@ -292,54 +269,125 @@ void QgsSimpleLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const
// geometry renderer
QByteArray vertexBufferData;
vertexBufferData.resize( out.vertices.size() * 3 * sizeof( float ) );
float *rawVertexArray = reinterpret_cast<float *>( vertexBufferData.data() );
int idx = 0;
for ( const auto &v : qgis::as_const( out.vertices ) )
{
rawVertexArray[idx++] = v.x();
rawVertexArray[idx++] = v.y();
rawVertexArray[idx++] = v.z();
}
QByteArray indexBufferData;
indexBufferData.resize( out.indexes.size() * sizeof( int ) );
unsigned int *rawIndexArray = reinterpret_cast<unsigned int *>( indexBufferData.data() );
idx = 0;
for ( unsigned int indexVal : qgis::as_const( out.indexes ) )
{
rawIndexArray[idx++] = indexVal;
}
Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
Qt3DRender::QBuffer *vertexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::VertexBuffer, entity );
vertexBuffer->setData( vertexBufferData );
Qt3DRender::QBuffer *indexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::IndexBuffer, entity );
indexBuffer->setData( indexBufferData );
Qt3DRender::QAttribute *positionAttribute = new Qt3DRender::QAttribute( entity );
positionAttribute->setAttributeType( Qt3DRender::QAttribute::VertexAttribute );
positionAttribute->setBuffer( vertexBuffer );
positionAttribute->setVertexBaseType( Qt3DRender::QAttribute::Float );
positionAttribute->setVertexSize( 3 );
positionAttribute->setName( Qt3DRender::QAttribute::defaultPositionAttributeName() );
Qt3DRender::QAttribute *indexAttribute = new Qt3DRender::QAttribute( entity );
indexAttribute->setAttributeType( Qt3DRender::QAttribute::IndexAttribute );
indexAttribute->setBuffer( indexBuffer );
indexAttribute->setVertexBaseType( Qt3DRender::QAttribute::UnsignedInt );
Qt3DRender::QGeometry *geom = new Qt3DRender::QGeometry;
geom->addAttribute( positionAttribute );
geom->addAttribute( indexAttribute );
Qt3DRender::QGeometry *geom = out.createGeometry( entity );
Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStrip );
renderer->setGeometry( geom );
renderer->setVertexCount( out.vertices.count() );
renderer->setVertexCount( out.indexes.count() );
renderer->setPrimitiveRestartEnabled( true );
renderer->setRestartIndexValue( 0 );
// make entity
entity->addComponent( renderer );
entity->addComponent( mat );
entity->setParent( parent );
}
// --------------
class QgsThickLine3DSymbolHandler : public QgsFeature3DHandler
{
public:
QgsThickLine3DSymbolHandler( const QgsLine3DSymbol &symbol, const QgsFeatureIds &selectedIds )
: mSymbol( symbol ), mSelectedIds( selectedIds )
{
}
bool prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames ) override;
void processFeature( QgsFeature &feature, const Qgs3DRenderContext &context ) override;
void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override;
private:
void makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected );
Qt3DExtras::QPhongMaterial *material( const QgsLine3DSymbol &symbol ) const;
// input specific for this class
const QgsLine3DSymbol &mSymbol;
// inputs - generic
QgsFeatureIds mSelectedIds;
// outputs
QgsLineVertexData outNormal; //!< Features that are not selected
QgsLineVertexData outSelected; //!< Features that are selected
};
bool QgsThickLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
{
Q_UNUSED( attributeNames );
outNormal.withAdjacency = true;
outSelected.withAdjacency = true;
outNormal.init( mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), &context.map() );
outSelected.init( mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), &context.map() );
return true;
}
void QgsThickLine3DSymbolHandler::processFeature( QgsFeature &f, const Qgs3DRenderContext &context )
{
Q_UNUSED( context );
if ( f.geometry().isNull() )
return;
QgsLineVertexData &out = mSelectedIds.contains( f.id() ) ? outSelected : outNormal;
QgsGeometry geom = f.geometry();
const QgsAbstractGeometry *g = geom.constGet();
if ( const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( g ) )
{
out.addLineString( *ls );
}
else if ( const QgsMultiLineString *mls = qgsgeometry_cast<const QgsMultiLineString *>( g ) )
{
for ( int nGeom = 0; nGeom < mls->numGeometries(); ++nGeom )
{
const QgsLineString *ls = qgsgeometry_cast<const QgsLineString *>( mls->geometryN( nGeom ) );
out.addLineString( *ls );
}
}
}
void QgsThickLine3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context )
{
// create entity for selected and not selected
makeEntity( parent, context, outNormal, false );
makeEntity( parent, context, outSelected, true );
}
void QgsThickLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context, QgsLineVertexData &out, bool selected )
{
if ( out.indexes.isEmpty() )
return;
// material (only ambient color is used for the color)
QgsLineMaterial *mat = new QgsLineMaterial;
mat->setLineColor( mSymbol.material().ambient() );
mat->setLineWidth( mSymbol.width() );
if ( selected )
{
// update the material with selection colors
mat->setLineColor( context.map().selectionColor() );
}
Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
// geometry renderer
Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
renderer->setGeometry( out.createGeometry( entity ) );
renderer->setVertexCount( out.indexes.count() );
renderer->setPrimitiveRestartEnabled( true );
renderer->setRestartIndexValue( 0 );
@ -359,7 +407,8 @@ namespace Qgs3DSymbolImpl
QgsFeature3DHandler *handlerForLine3DSymbol( QgsVectorLayer *layer, const QgsLine3DSymbol &symbol )
{
if ( symbol.renderAsSimpleLines() )
return new QgsSimpleLine3DSymbolHandler( symbol, layer->selectedFeatureIds() );
return new QgsThickLine3DSymbolHandler( symbol, layer->selectedFeatureIds() );
//return new QgsSimpleLine3DSymbolHandler( symbol, layer->selectedFeatureIds() );
else
return new QgsBufferedLine3DSymbolHandler( symbol, layer->selectedFeatureIds() );
}

View File

@ -0,0 +1,120 @@
/***************************************************************************
qgslinematerial_p.cpp
--------------------------------------
Date : Apr 2019
Copyright : (C) 2019 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslinematerial_p.h"
#include <QColor>
#include <QSizeF>
#include <QUrl>
#include <QVector3D>
#include <Qt3DRender/QBlendEquation>
#include <Qt3DRender/QBlendEquationArguments>
#include <Qt3DRender/QCamera>
#include <Qt3DRender/QEffect>
#include <Qt3DRender/QGraphicsApiFilter>
#include <Qt3DRender/QParameter>
#include <Qt3DRender/QRenderPass>
#include <Qt3DRender/QTechnique>
/// @cond PRIVATE
QgsLineMaterial::QgsLineMaterial()
: mParameterThickness( new Qt3DRender::QParameter( "THICKNESS", 10, this ) )
, mParameterMiterLimit( new Qt3DRender::QParameter( "MITER_LIMIT", -1, this ) ) // 0.75
, mParameterLineColor( new Qt3DRender::QParameter( "lineColor", QColor( 0, 255, 0 ), this ) )
, mParameterWindowScale( new Qt3DRender::QParameter( "WIN_SCALE", QSizeF(), this ) )
, mParameterCameraNearPlanePoint( new Qt3DRender::QParameter( "camNearPlanePoint", QVector3D(), this ) )
, mParameterCameraNearPlaneNormal( new Qt3DRender::QParameter( "camNearPlaneNormal", QVector3D(), this ) )
{
addParameter( mParameterThickness );
addParameter( mParameterMiterLimit );
addParameter( mParameterLineColor );
addParameter( mParameterWindowScale );
addParameter( mParameterCameraNearPlanePoint );
addParameter( mParameterCameraNearPlaneNormal );
//Parameter { name: "tex0"; value: txt },
//Parameter { name: "useTex"; value: false },
Qt3DRender::QShaderProgram *shaderProgram = new Qt3DRender::QShaderProgram( this );
shaderProgram->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/lines.vert" ) ) ) );
shaderProgram->setFragmentShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/lines.frag" ) ) ) );
shaderProgram->setGeometryShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/lines.geom" ) ) ) );
Qt3DRender::QBlendEquation *blendEquation = new Qt3DRender::QBlendEquation( this );
blendEquation->setBlendFunction( Qt3DRender::QBlendEquation::Add );
Qt3DRender::QBlendEquationArguments *blendEquationArgs = new Qt3DRender::QBlendEquationArguments( this );
blendEquationArgs->setSourceRgb( Qt3DRender::QBlendEquationArguments::SourceAlpha );
blendEquationArgs->setDestinationRgb( Qt3DRender::QBlendEquationArguments::OneMinusSourceAlpha );
Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass( this );
renderPass->setShaderProgram( shaderProgram );
renderPass->addRenderState( blendEquation );
renderPass->addRenderState( blendEquationArgs );
// without this filter the default forward renderer would not render this
Qt3DRender::QFilterKey *filterKey = new Qt3DRender::QFilterKey;
filterKey->setName( QStringLiteral( "renderingStyle" ) );
filterKey->setValue( "forward" );
Qt3DRender::QTechnique *technique = new Qt3DRender::QTechnique;
technique->addFilterKey( filterKey );
technique->addRenderPass( renderPass );
technique->graphicsApiFilter()->setApi( Qt3DRender::QGraphicsApiFilter::OpenGL );
technique->graphicsApiFilter()->setProfile( Qt3DRender::QGraphicsApiFilter::CoreProfile );
technique->graphicsApiFilter()->setMajorVersion( 3 );
technique->graphicsApiFilter()->setMinorVersion( 1 );
Qt3DRender::QEffect *effect = new Qt3DRender::QEffect( this );
effect->addTechnique( technique );
setEffect( effect );
}
void QgsLineMaterial::setLineColor( const QColor &color )
{
mParameterLineColor->setValue( color );
}
QColor QgsLineMaterial::lineColor() const
{
return mParameterLineColor->value().value<QColor>();
}
void QgsLineMaterial::setLineWidth( float width )
{
mParameterThickness->setValue( width );
}
float QgsLineMaterial::lineWidth() const
{
return mParameterThickness->value().toFloat();
}
void QgsLineMaterial::setCameraParameters( const QVector3D &position, const QVector3D &viewVector, float nearPlane )
{
mParameterCameraNearPlanePoint->setValue( position + viewVector * nearPlane );
mParameterCameraNearPlaneNormal->setValue( viewVector );
}
void QgsLineMaterial::setViewportSize( const QSizeF &viewportSize )
{
mParameterWindowScale->setValue( viewportSize );
}
/// @endcond

View File

@ -0,0 +1,83 @@
/***************************************************************************
qgslinematerial_p.h
--------------------------------------
Date : Apr 2019
Copyright : (C) 2019 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLINEMATERIAL_P_H
#define QGSLINEMATERIAL_P_H
/// @cond PRIVATE
//
// W A R N I N G
// -------------
//
// This file is not part of the QGIS API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
#include <Qt3DRender/QMaterial>
namespace Qt3DRender
{
class QCamera;
}
/**
* \ingroup 3d
* Implementation of material that renders 3D linestrings.
*
* Supports:
* - arbitrary line width (in pixels)
* - bevel and miter line joins (including limit for miter join to avoid very long miters on sharp angles)
* - flat line caps
* - alpha blending
*
* The material needs information about viewport size (to correctly scale line widths) and to camera
* parameters (to correctly clip lines).
*
* It is implemented by using a geometry shader that accepts primitive type Line strip with adjacency
* (i.e. we have access to points p1-p2 which define line segment's endpoints, and in addition to that,
* we have access to previous (p0) and next (p3) points). Geometry shader generates two triangles
* for each segment and possibly another triangle for bevel join.
*/
class QgsLineMaterial : public Qt3DRender::QMaterial
{
Q_OBJECT
public:
QgsLineMaterial();
void setLineColor( const QColor &color );
QColor lineColor() const;
void setLineWidth( float width );
float lineWidth() const;
Q_INVOKABLE void setCameraParameters( const QVector3D &position, const QVector3D &viewVector, float nearPlane );
Q_INVOKABLE void setViewportSize( const QSizeF &viewportSize );
private:
Qt3DRender::QParameter *mParameterThickness = nullptr;
Qt3DRender::QParameter *mParameterMiterLimit = nullptr;
Qt3DRender::QParameter *mParameterLineColor = nullptr;
Qt3DRender::QParameter *mParameterWindowScale = nullptr;
Qt3DRender::QParameter *mParameterCameraNearPlanePoint = nullptr;
Qt3DRender::QParameter *mParameterCameraNearPlaneNormal = nullptr;
};
/// @endcond
#endif // QGSLINEMATERIAL_P_H

View File

@ -0,0 +1,123 @@
/***************************************************************************
qgslinevertexdata_p.cpp
--------------------------------------
Date : Apr 2019
Copyright : (C) 2019 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgslinevertexdata_p.h"
#include <Qt3DRender/QAttribute>
#include <Qt3DRender/QBuffer>
#include <Qt3DRender/QGeometry>
#include "qgslogger.h"
#include "qgs3dutils.h"
#include "qgslinestring.h"
/// @cond PRIVATE
QgsLineVertexData::QgsLineVertexData()
{
// the first index is invalid, we use it for primitive restart
vertices << QVector3D();
}
void QgsLineVertexData::init( Qgs3DTypes::AltitudeClamping clamping, Qgs3DTypes::AltitudeBinding binding, float height, const Qgs3DMapSettings *map )
{
altClamping = clamping;
altBinding = binding;
baseHeight = height;
mapSettings = map;
}
QByteArray QgsLineVertexData::createVertexBuffer()
{
QByteArray vertexBufferData;
vertexBufferData.resize( vertices.size() * 3 * sizeof( float ) );
float *rawVertexArray = reinterpret_cast<float *>( vertexBufferData.data() );
int idx = 0;
for ( const auto &v : qgis::as_const( vertices ) )
{
rawVertexArray[idx++] = v.x();
rawVertexArray[idx++] = v.y();
rawVertexArray[idx++] = v.z();
}
return vertexBufferData;
}
QByteArray QgsLineVertexData::createIndexBuffer()
{
QByteArray indexBufferData;
indexBufferData.resize( indexes.size() * sizeof( int ) );
unsigned int *rawIndexArray = reinterpret_cast<unsigned int *>( indexBufferData.data() );
int idx = 0;
for ( unsigned int indexVal : qgis::as_const( indexes ) )
{
rawIndexArray[idx++] = indexVal;
}
return indexBufferData;
}
Qt3DRender::QGeometry *QgsLineVertexData::createGeometry( Qt3DCore::QNode *parent )
{
Qt3DRender::QBuffer *vertexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::VertexBuffer, parent );
vertexBuffer->setData( createVertexBuffer() );
Qt3DRender::QBuffer *indexBuffer = new Qt3DRender::QBuffer( Qt3DRender::QBuffer::IndexBuffer, parent );
indexBuffer->setData( createIndexBuffer() );
QgsDebugMsg( QString( "vertex buffer %1 MB index buffer %2 MB " ).arg( vertexBuffer->data().count() / 1024. / 1024. ).arg( indexBuffer->data().count() / 1024. / 1024. ) );
Qt3DRender::QAttribute *positionAttribute = new Qt3DRender::QAttribute( parent );
positionAttribute->setAttributeType( Qt3DRender::QAttribute::VertexAttribute );
positionAttribute->setBuffer( vertexBuffer );
positionAttribute->setVertexBaseType( Qt3DRender::QAttribute::Float );
positionAttribute->setVertexSize( 3 );
positionAttribute->setName( Qt3DRender::QAttribute::defaultPositionAttributeName() );
Qt3DRender::QAttribute *indexAttribute = new Qt3DRender::QAttribute( parent );
indexAttribute->setAttributeType( Qt3DRender::QAttribute::IndexAttribute );
indexAttribute->setBuffer( indexBuffer );
indexAttribute->setVertexBaseType( Qt3DRender::QAttribute::UnsignedInt );
Qt3DRender::QGeometry *geom = new Qt3DRender::QGeometry;
geom->addAttribute( positionAttribute );
geom->addAttribute( indexAttribute );
return geom;
}
void QgsLineVertexData::addLineString( const QgsLineString &lineString )
{
if ( withAdjacency )
indexes << vertices.count(); // add the following vertex (for adjacency)
QgsPoint centroid;
if ( altBinding == Qgs3DTypes::AltBindCentroid )
centroid = lineString.centroid();
for ( int i = 0; i < lineString.vertexCount(); ++i )
{
QgsPoint p = lineString.pointN( i );
float z = Qgs3DUtils::clampAltitude( p, altClamping, altBinding, baseHeight, centroid, *mapSettings );
vertices << QVector3D( p.x() - mapSettings->origin().x(), z, -( p.y() - mapSettings->origin().y() ) );
indexes << vertices.count() - 1;
}
if ( withAdjacency )
indexes << vertices.count() - 1; // add the last vertex (for adjacency)
indexes << 0; // add primitive restart
}
/// @endcond

View File

@ -0,0 +1,86 @@
/***************************************************************************
qgslinevertexdata_p.h
--------------------------------------
Date : Apr 2019
Copyright : (C) 2019 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSLINEVERTEXDATA_P_H
#define QGSLINEVERTEXDATA_P_H
/// @cond PRIVATE
//
// W A R N I N G
// -------------
//
// This file is not part of the QGIS API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
#include <QVector>
#include <QVector3D>
#include "qgs3dtypes.h"
namespace Qt3DCore
{
class QNode;
}
namespace Qt3DRender
{
class QGeometry;
}
class QgsLineString;
class Qgs3DMapSettings;
/**
* \ingroup 3d
* Helper class to store vertex buffer and index buffer data that will be used to render
* lines (either using "line strip" or "line strip with adjacency" primitive.
*
* Index zero is used for primitive restart (to separate two linestrings).
*
* It is expected that client code:
* 1. calls init()
* 2. calls addLineString() many times
* 3. calls createGeometry()
*/
struct QgsLineVertexData
{
QVector<QVector3D> vertices;
QVector<unsigned int> indexes;
bool withAdjacency = false; //!< Whether line strip with adjacency primitive will be used
// extra info to calculate elevation
Qgs3DTypes::AltitudeClamping altClamping = Qgs3DTypes::AltClampRelative;
Qgs3DTypes::AltitudeBinding altBinding = Qgs3DTypes::AltBindVertex;
float baseHeight = 0;
const Qgs3DMapSettings *mapSettings = nullptr;
QgsLineVertexData();
void init( Qgs3DTypes::AltitudeClamping clamping, Qgs3DTypes::AltitudeBinding binding, float height, const Qgs3DMapSettings *map );
QByteArray createVertexBuffer();
QByteArray createIndexBuffer();
Qt3DRender::QGeometry *createGeometry( Qt3DCore::QNode *parent );
void addLineString( const QgsLineString &lineString );
};
/// @endcond
#endif // QGSLINEVERTEXDATA_P_H

View File

@ -28,8 +28,11 @@
#include <Qt3DRender/QGeometryRenderer>
#include "qgsvectorlayer.h"
#include "qgslinestring.h"
#include "qgsmultipolygon.h"
#include "qgslinevertexdata_p.h"
#include "qgslinematerial_p.h"
/// @cond PRIVATE
@ -66,11 +69,19 @@ class QgsPolygon3DSymbolHandler : public QgsFeature3DHandler
// outputs
PolygonData outNormal; //!< Features that are not selected
PolygonData outSelected; //!< Features that are selected
bool addEdges = true; //!< TODO: should go to polygon symbol
QColor edgeColor = QColor( 0, 0, 0 ); //!< TODO: go to polygon symbol
float edgeWidth = 2; //!< TODO: go to polygon symbol
QgsLineVertexData outEdges; //!< When highlighting edges, this holds data for vertex/index buffer
};
bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet<QString> &attributeNames )
{
outEdges.withAdjacency = true;
outEdges.init( mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), mSymbol.height(), &context.map() );
QSet<QString> attrs = mSymbol.dataDefinedProperties().referencedFields( context.expressionContext() );
attributeNames.unite( attrs );
return true;
@ -78,6 +89,16 @@ bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet
void QgsPolygon3DSymbolHandler::processPolygon( QgsPolygon *polyClone, QgsFeatureId fid, float height, bool hasDDExtrusion, float extrusionHeight, const Qgs3DRenderContext &context, PolygonData &out )
{
if ( addEdges )
{
// add edges before the polygon gets the Z values modified because addLineString() does its own altitude handling
outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->exteriorRing() ) );
for ( int i = 0; i < polyClone->numInteriorRings(); ++i )
outEdges.addLineString( *static_cast<const QgsLineString *>( polyClone->interiorRing( i ) ) );
// TODO: if has extrusion: also add vertical edges for each vertex
}
Qgs3DUtils::clampAltitudes( polyClone, mSymbol.altitudeClamping(), mSymbol.altitudeBinding(), height, context.map() );
out.polygons.append( polyClone );
out.fids.append( fid );
@ -148,6 +169,29 @@ void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3D
// create entity for selected and not selected
makeEntity( parent, context, outNormal, false );
makeEntity( parent, context, outSelected, true );
// add entity for edges
if ( addEdges && !outEdges.indexes.isEmpty() )
{
QgsLineMaterial *mat = new QgsLineMaterial;
mat->setLineColor( edgeColor );
mat->setLineWidth( edgeWidth );
Qt3DCore::QEntity *entity = new Qt3DCore::QEntity;
// geometry renderer
Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer;
renderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::LineStripAdjacency );
renderer->setGeometry( outEdges.createGeometry( entity ) );
renderer->setVertexCount( outEdges.indexes.count() );
renderer->setPrimitiveRestartEnabled( true );
renderer->setRestartIndexValue( 0 );
// make entity
entity->addComponent( renderer );
entity->addComponent( mat );
entity->setParent( parent );
}
}

View File

@ -67,6 +67,6 @@ QgsLine3DSymbol QgsLine3DSymbolWidget::symbol() const
void QgsLine3DSymbolWidget::updateGuiState()
{
bool simple = chkSimpleLines->isChecked();
spinWidth->setEnabled( !simple );
//spinWidth->setEnabled( !simple );
spinExtrusion->setEnabled( !simple );
}