OSM Buildings
A 3d building viewer of OSM buildings map data.

The OSMBuildings application demonstrates of using 3d building data from OSM servers or from locally limited data set when the server isn't available, to create 3d building geometry from the data to show on the map.
The application uses a queue to handle concurrent requests to boost up the loading process of the maps and the building data.
OSMRequest::OSMRequest(QObject *parent) : QObject{parent} { connect( &m_queuesTimer, &QTimer::timeout, this, [this](){ if ( m_buildingsQueue.isEmpty() && m_mapsQueue.isEmpty() ) { m_queuesTimer.stop(); } else { #ifdef QT_DEBUG const int numConcurrentRequests = 1; #else const int numConcurrentRequests = 6; #endif if ( !m_buildingsQueue.isEmpty() && m_buildingsNumberOfRequestsInFlight < numConcurrentRequests ) { getBuildingsDataRequest(m_buildingsQueue.dequeue()); ++m_buildingsNumberOfRequestsInFlight; } if ( !m_mapsQueue.isEmpty() && m_mapsNumberOfRequestsInFlight < numConcurrentRequests ) { getMapsDataRequest(m_mapsQueue.dequeue()); ++m_mapsNumberOfRequestsInFlight; } } }); m_queuesTimer.setInterval(0);
It implements a custom request handler class for fetching the data from the OSM building and map servers.
void OSMRequest::getBuildingsData(const QQueue<OSMTileData> &buildingsQueue) { if ( buildingsQueue.isEmpty() ) return; m_buildingsQueue = buildingsQueue; if ( !m_queuesTimer.isActive() ) m_queuesTimer.start(); } void OSMRequest::getBuildingsDataRequest(const OSMTileData &tile) { QString fileName = "data/" + QString::number(tile.ZoomLevel) + "," + QString::number(tile.TileX) + "," + QString::number(tile.TileY) + ".json"; QFileInfo file(fileName); if ( file.size() ) { QFile file(fileName); if (file.open(QFile::ReadOnly)){ QByteArray data = file.readAll(); file.close(); emit buildingsDataReady( importGeoJson(QJsonDocument::fromJson( data )), tile.TileX, tile.TileY, tile.ZoomLevel ); --m_buildingsNumberOfRequestsInFlight; return; } } QUrl url = QUrl(tr(m_uRL_OSMB_JSON).arg(QString::number(tile.ZoomLevel),QString::number(tile.TileX),QString::number(tile.TileY)) ); QNetworkReply * reply = m_networkAccessManager.get( QNetworkRequest(url)); connect( reply, &QNetworkReply::finished, this, [this, reply, tile, url](){ reply->deleteLater(); if ( reply->error() == QNetworkReply::NoError ) { QByteArray data = reply->readAll(); emit buildingsDataReady( importGeoJson(QJsonDocument::fromJson( data )), tile.TileX, tile.TileY, tile.ZoomLevel ); }else { qWarning() << "OSMRequest::getBuildingsData" << reply->error() << url; } --m_buildingsNumberOfRequestsInFlight; } ); void OSMRequest::getMapsData(const QQueue<OSMTileData> &mapsQueue) { if ( mapsQueue.isEmpty() ) return; m_mapsQueue = mapsQueue; if ( !m_queuesTimer.isActive() ) m_queuesTimer.start(); } void OSMRequest::getMapsDataRequest(const OSMTileData &tile) { QString fileName = "data/" + QString::number(tile.ZoomLevel) + "," + QString::number(tile.TileX) + "," + QString::number(tile.TileY) + ".png"; QFileInfo file(fileName); if ( file.size() ) { QFile file(fileName); if (file.open(QFile::ReadOnly)){ QByteArray data = file.readAll(); file.close(); emit mapsDataReady( data, tile.TileX, tile.TileY, tile.ZoomLevel ); --m_mapsNumberOfRequestsInFlight; return; } } QUrl url = QUrl(tr(m_uRL_OSMB_MAP).arg(QString::number(tile.ZoomLevel),QString::number(tile.TileX),QString::number(tile.TileY)) ); QNetworkReply * reply = m_networkAccessManager.get( QNetworkRequest(url)); connect( reply, &QNetworkReply::finished, this, [this, reply, tile, url](){ reply->deleteLater(); if ( reply->error() == QNetworkReply::NoError ) { QByteArray data = reply->readAll(); emit mapsDataReady( data, tile.TileX, tile.TileY, tile.ZoomLevel ); }else { qWarning() << "OSMRequest::getMapsDataRequest" << reply->error() << url; } --m_mapsNumberOfRequestsInFlight; } );
Also it's parsing the online data to convert it to a QVarianList of keys and values of Geo formats e.g. QGeoPolygon.
emit buildingsDataReady( importGeoJson(QJsonDocument::fromJson( data )), tile.TileX, tile.TileY, tile.ZoomLevel );
--m_buildingsNumberOfRequestsInFlight;
The building parsed data send to a custom geometry item to convert the geo coordinate to proper 3d coordinates.
constexpr auto convertGeoCoordToVertexPosition = [](const float lat, const float lon) -> QVector3D {
const double scale = 1.212;
const double geoToPositionScale = 1000000 * scale;
const double XOffsetFromCenter = 537277 * scale;
const double YOffsetFromCenter = 327957 * scale;
double x = (lon/360.0 + 0.5) * geoToPositionScale;
double y = (1.0-log(qTan(qDegreesToRadians(lat)) + 1.0 / qCos(qDegreesToRadians(lat))) / M_PI) * 0.5 * geoToPositionScale;
return QVector3D( x - XOffsetFromCenter, YOffsetFromCenter - y, 0.0 );
};
It generates necessary data for the index buffer and vertex buffer e.g. Position, Normal, Tangent, UV, ...
for ( const QVariant &baseData : geoVariantsList ) {
for ( const QVariant &dataValue : baseData.toMap()["data"].toList() ) {
auto featureMap = dataValue.toMap();
auto properties = featureMap["properties"].toMap();
auto buildingCoords = featureMap["data"].value<QGeoPolygon>().perimeter();
float height = 0.15 * properties["height"].toLongLong();
float levels = static_cast<float>(properties["levels"].toLongLong());
QColor color = QColor::fromString( properties["color"].toString());
if ( !color.isValid() || color == QColor::fromString("black") )
color = QColor("white");
QColor roofColor = QColor::fromString( properties["roofColor"].toString());
if ( !roofColor.isValid() || roofColor == QColor::fromString("black") )
roofColor = color;
QVector3D subsetMinBound = QVector3D(maxFloat, maxFloat, maxFloat);
QVector3D subsetMaxBound = QVector3D(minFloat, minFloat, minFloat);
qsizetype numSubsetVertices = buildingCoords.size() * 2;
qsizetype lastVertexDataCount = vertexData.size();
qsizetype lastIndexDataCount = indexData.size();
vertexData.resize( lastVertexDataCount + numSubsetVertices * strideVertex );
indexData.resize( lastIndexDataCount + ( numSubsetVertices - 2 ) * stridePermitive );
float *vbPtr = &reinterpret_cast<float *>(vertexData.data())[globalVertexCounter * striedVertexLen];
uint32_t *ibPtr = &reinterpret_cast<uint32_t *>(indexData.data())[globalPermitiveCounter * 3];
qsizetype subsetVertexCounter = 0;
QVector3D lastBaseVertexPos;
QVector3D lastExtrudedVertexPos;
QVector3D currentBaseVertexPos;
QVector3D currentExtrudedVertexPos;
QVector3D subsetPolygonCenter;
using PolygonVertex = std::array<double, 2>;
using PolygonVertices = std::vector<PolygonVertex>;
PolygonVertices roofPolygonVertices;
for ( const QGeoCoordinate &buildingPoint : buildingCoords ) {
lastBaseVertexPos = currentBaseVertexPos;
lastExtrudedVertexPos = currentExtrudedVertexPos;
currentBaseVertexPos = convertGeoCoordToVertexPosition( buildingPoint.latitude(), buildingPoint.longitude() );
currentExtrudedVertexPos = QVector3D(currentBaseVertexPos.x(), currentBaseVertexPos.y(), height);
roofPolygonVertices.push_back( {currentBaseVertexPos.x(),currentBaseVertexPos.y()} );
subsetPolygonCenter.setX( subsetPolygonCenter.x() + currentBaseVertexPos.x() );
subsetPolygonCenter.setY( subsetPolygonCenter.y() + currentBaseVertexPos.y() );
meshMinBound.setX( qMin( meshMinBound.x(), currentBaseVertexPos.x() ) );
meshMinBound.setY( qMin( meshMinBound.y(), currentBaseVertexPos.y() ) );
meshMinBound.setZ( qMin( meshMinBound.z(), currentBaseVertexPos.z() ) );
meshMaxBound.setX( qMax( meshMaxBound.x(), currentExtrudedVertexPos.x() ) );
meshMaxBound.setY( qMax( meshMaxBound.y(), currentExtrudedVertexPos.y() ) );
meshMaxBound.setZ( qMax( meshMaxBound.z(), currentExtrudedVertexPos.z() ) );
subsetMinBound.setX( qMin( subsetMinBound.x(), currentBaseVertexPos.x() ) );
subsetMinBound.setY( qMin( subsetMinBound.y(), currentBaseVertexPos.y() ) );
subsetMinBound.setZ( qMin( subsetMinBound.z(), currentBaseVertexPos.z() ) );
subsetMaxBound.setX( qMax( subsetMaxBound.x(), currentExtrudedVertexPos.x() ) );
subsetMaxBound.setY( qMax( subsetMaxBound.y(), currentExtrudedVertexPos.y() ) );
subsetMaxBound.setZ( qMax( subsetMaxBound.z(), currentExtrudedVertexPos.z() ) );
if ( subsetVertexCounter < numSubsetVertices - 2 ) {
*ibPtr++ = globalVertexCounter + 3;
*ibPtr++ = globalVertexCounter + 2;
*ibPtr++ = globalVertexCounter + 0;
*ibPtr++ = globalVertexCounter + 1;
*ibPtr++ = globalVertexCounter + 3;
*ibPtr++ = globalVertexCounter + 0;
globalPermitiveCounter += 2;
}
if ( subsetVertexCounter == 2 ) {
QVector3D tangent = (currentExtrudedVertexPos - currentBaseVertexPos).normalized();
QVector3D binormal = (lastBaseVertexPos - currentBaseVertexPos).normalized();
QVector3D normal = QVector3D::crossProduct( binormal, tangent).normalized();
//position
*vbPtr++ = lastBaseVertexPos.x();
*vbPtr++ = lastBaseVertexPos.y();
*vbPtr++ = lastBaseVertexPos.z();
*vbPtr++ = normal.x();
*vbPtr++ = normal.y();
*vbPtr++ = normal.z();
//tangent
*vbPtr++ = tangent.x();
*vbPtr++ = tangent.y();
*vbPtr++ = tangent.z();
//binormal
*vbPtr++ = binormal.x();
*vbPtr++ = binormal.y();
*vbPtr++ = binormal.z();
*vbPtr++ = color.redF();
*vbPtr++ = color.greenF();
*vbPtr++ = color.blueF();
*vbPtr++ = 1.0;
//texcoord
*vbPtr++ = 0.0;
*vbPtr++ = 0.0;
*vbPtr++ = levels;
*vbPtr++ = 0.0;
//position
*vbPtr++ = lastExtrudedVertexPos.x();
*vbPtr++ = lastExtrudedVertexPos.y();
*vbPtr++ = lastExtrudedVertexPos.z();
*vbPtr++ = normal.x();
*vbPtr++ = normal.y();
*vbPtr++ = normal.z();
//tangent
*vbPtr++ = tangent.x();
*vbPtr++ = tangent.y();
*vbPtr++ = tangent.z();
//binormal
*vbPtr++ = binormal.x();
*vbPtr++ = binormal.y();
*vbPtr++ = binormal.z();
*vbPtr++ = color.redF();
*vbPtr++ = color.greenF();
*vbPtr++ = color.blueF();
*vbPtr++ = 1.0;
//texcoord
*vbPtr++ = 0.0;
*vbPtr++ = 1.0;
*vbPtr++ = levels;
*vbPtr++ = 0.0;
}
if ( subsetVertexCounter >= 2 ) {
QVector3D tangent = (currentExtrudedVertexPos - currentBaseVertexPos).normalized();
QVector3D binormal = (lastBaseVertexPos - currentBaseVertexPos).normalized();
QVector3D normal = QVector3D::crossProduct( binormal, tangent).normalized();
*vbPtr++ = currentBaseVertexPos.x();
*vbPtr++ = currentBaseVertexPos.y();
*vbPtr++ = currentBaseVertexPos.z();
*vbPtr++ = normal.x();
*vbPtr++ = normal.y();
*vbPtr++ = normal.z();
//tangent
*vbPtr++ = tangent.x();
*vbPtr++ = tangent.y();
*vbPtr++ = tangent.z();
//binormal
*vbPtr++ = binormal.x();
*vbPtr++ = binormal.y();
*vbPtr++ = binormal.z();
*vbPtr++ = color.redF();
*vbPtr++ = color.greenF();
*vbPtr++ = color.blueF();
*vbPtr++ = 1.0;
float xCoord = ( subsetVertexCounter % 4 ) ? 1.0f : 0.0f;
//texcoord
*vbPtr++ = xCoord;
*vbPtr++ = 0.0;
*vbPtr++ = levels;
*vbPtr++ = 0.0;
//position
*vbPtr++ = currentExtrudedVertexPos.x();
*vbPtr++ = currentExtrudedVertexPos.y();
*vbPtr++ = currentExtrudedVertexPos.z();
*vbPtr++ = normal.x();
*vbPtr++ = normal.y();
*vbPtr++ = normal.z();
//tangent
*vbPtr++ = tangent.x();
*vbPtr++ = tangent.y();
*vbPtr++ = tangent.z();
//binormal
*vbPtr++ = binormal.x();
*vbPtr++ = binormal.y();
*vbPtr++ = binormal.z();
*vbPtr++ = color.redF();
*vbPtr++ = color.greenF();
*vbPtr++ = color.blueF();
*vbPtr++ = 1.0;
//texcoord
*vbPtr++ = xCoord;
*vbPtr++ = 1.0;
*vbPtr++ = levels;
*vbPtr++ = 0.0;
}
subsetVertexCounter += 2;
globalVertexCounter += 2;
}
QString shape = properties["shape"].toString();
{
if ( shape == "sphere" )
{
subsetPolygonCenter = QVector3D(subsetPolygonCenter.x() / roofPolygonVertices.size(),
subsetPolygonCenter.y() / roofPolygonVertices.size(), height );
float sphereRadius = qAbs(roofPolygonVertices[0][0] - subsetPolygonCenter.x());
if ( shape == "sphere" )
sphereRadius *= 2.0;
sphereRadius = qMax(sphereRadius, 1.0);
float sphereRadiuslengthInv = 1.0f / sphereRadius;
const uint32_t sphereSectorCount = 10;
const uint32_t sphereStackCount = 10;
constexpr double sphereSectorStep = 2.0 * M_PI / sphereSectorCount;
constexpr double sphereStackStep = M_PI / sphereStackCount;
float sphereSectorAngle;
float sphereStackAngle;
lastVertexDataCount = vertexData.size();
lastIndexDataCount = indexData.size();
uint32_t sphereVetexCount = sphereStackCount * (sphereSectorCount + 1);
vertexData.resize( lastVertexDataCount + sphereVetexCount * strideVertex );
indexData.resize( lastIndexDataCount + sphereVetexCount * 2 * 3 * sizeof(uint32_t) );
vbPtr = &reinterpret_cast<float *>(vertexData.data())[globalVertexCounter * striedVertexLen];
ibPtr = &reinterpret_cast<uint32_t *>(indexData.data())[globalPermitiveCounter * 3];
for (uint32_t stackIndex = 0; stackIndex <= sphereStackCount; ++stackIndex)
{
float k1 = stackIndex * (sphereSectorCount + 1);
float k2 = k1 + sphereSectorCount + 1;
sphereStackAngle = M_PI / 2.0 - stackIndex * sphereStackStep;
float xy = sphereRadius * qCos(sphereStackAngle);
float z = sphereRadius * qSin(sphereStackAngle);
for (uint32_t sectorIndex = 0; sectorIndex <= sphereSectorCount; ++sectorIndex, ++k1, ++k2)
{
if (stackIndex != 0)
{
*ibPtr++ = k1 + globalVertexCounter;
*ibPtr++ = k2 + globalVertexCounter;
*ibPtr++ = k1 + 1 + globalVertexCounter;
++globalPermitiveCounter;
}
if (stackIndex != (sphereStackCount-1))
{
*ibPtr++ = k1 + 1 + globalVertexCounter;
*ibPtr++ = k2 + globalVertexCounter;
*ibPtr++ = k2 + 1 + globalVertexCounter;
++globalPermitiveCounter;
}
sphereSectorAngle = sectorIndex * sphereSectorStep;
float x = xy * qCos(sphereSectorAngle);
float y = xy * qSin(sphereSectorAngle);
//position
*vbPtr++ = x + subsetPolygonCenter.x();
*vbPtr++ = y + subsetPolygonCenter.y();
*vbPtr++ = z + subsetPolygonCenter.z();
//normal
*vbPtr++ = x * sphereRadiuslengthInv;
*vbPtr++ = y * sphereRadiuslengthInv;
*vbPtr++ = z * sphereRadiuslengthInv;
//tangent
*vbPtr++ = 0.0;
*vbPtr++ = 0.0;
*vbPtr++ = 0.0;
//binormal
*vbPtr++ = 0.0;
*vbPtr++ = 0.0;
*vbPtr++ = 0.0;
//color
*vbPtr++ = roofColor.redF();
*vbPtr++ = roofColor.greenF();
*vbPtr++ = roofColor.blueF();
*vbPtr++ = 1.0;
//texcoord
*vbPtr++ = 1.0;
*vbPtr++ = 1.0;
*vbPtr++ = 0.0;
*vbPtr++ = 1.0;
}
}
subsetVertexCounter += sphereVetexCount;
globalVertexCounter += sphereVetexCount;
}
{
std::vector<PolygonVertices> roofPolygonsVerices;
roofPolygonsVerices.push_back( roofPolygonVertices );
std::vector<uint32_t> roofIndices = mapbox::earcut<uint32_t>(roofPolygonsVerices);
lastVertexDataCount = vertexData.size();
lastIndexDataCount = indexData.size();
vertexData.resize( lastVertexDataCount + roofPolygonVertices.size() * strideVertex );
indexData.resize( lastIndexDataCount + roofIndices.size() * sizeof(uint32_t) );
vbPtr = &reinterpret_cast<float *>(vertexData.data())[globalVertexCounter * striedVertexLen];
ibPtr = &reinterpret_cast<uint32_t *>(indexData.data())[globalPermitiveCounter * 3];
for ( const uint32_t &roofIndex : roofIndices ) {
*ibPtr++ = roofIndex + globalVertexCounter;
}
qsizetype roofPermitiveCount = roofIndices.size() / 3;
globalPermitiveCounter += roofPermitiveCount;
for ( const PolygonVertex &polygonVertex : roofPolygonVertices ) {
//position
*vbPtr++ = polygonVertex.at(0);
*vbPtr++ = polygonVertex.at(1);
*vbPtr++ = height;
//normal
*vbPtr++ = 0.0;
*vbPtr++ = 0.0;
*vbPtr++ = 1.0;
//tangent
*vbPtr++ = 1.0;
*vbPtr++ = 0.0;
*vbPtr++ = 0.0;
//binormal
*vbPtr++ = 0.0;
*vbPtr++ = 1.0;
*vbPtr++ = 0.0;
//color/
*vbPtr++ = roofColor.redF();
*vbPtr++ = roofColor.greenF();
*vbPtr++ = roofColor.blueF();
*vbPtr++ = 1.0;
//texcoord
*vbPtr++ = 1.0;
*vbPtr++ = 1.0;
*vbPtr++ = 0.0;
*vbPtr++ = 1.0;
++subsetVertexCounter;
++globalVertexCounter;
}
}
}
}
}
clear();
The map downloaded PNG data send to a custom QQuick3DTextureData item to convert the PNG format data to a texture for use as a texture for map tiles.
void CustomTextureData::setImageData(const QByteArray &data) { QImage image = QImage::fromData(data).convertToFormat(QImage::Format_RGBA8888); setTextureData( QByteArray(reinterpret_cast<const char*>(image.constBits()), image.sizeInBytes()) ); setSize( image.size() ); setHasTransparency(false); setFormat(Format::RGBA8); }
It uses camera position, orientation, zoom level, and tilt to find the nearest tiles in the view.
void OSMManager::setCameraProperties(const QVector3D &position, const QVector3D &right, float cameraZoom, float minimunZoom, float maximumZoom, float cameraTilt, float minimumTilt, float maxmumTilt) { float tiltFactor = (cameraTilt - minimumTilt) / qMax(maxmumTilt - minimumTilt, 1.0); float zoomFactor = (cameraZoom - minimunZoom) / qMax(maximumZoom - minimunZoom, 1.0); QVector3D forwardVector = QVector3D::crossProduct(right, QVector3D(0.0, 0.0, -1.0)).normalized(); //Forward vector align to the XY plane QVector3D projectionOfForwardOnXY = position + forwardVector * tiltFactor * zoomFactor * 50.0; QQueue<OSMTileData> queue; for ( int fowardIndex = -20; fowardIndex <= 20; ++fowardIndex ){ for ( int sidewardIndex = -20; sidewardIndex <= 20; ++sidewardIndex ){ QVector3D transferedPosition = projectionOfForwardOnXY + QVector3D(float(m_tileSizeX * sidewardIndex) , float(m_tileSizeY * fowardIndex), 0.0); addBuildingRequestToQueue(queue, m_startBuildingTileX + int(transferedPosition.x() / m_tileSizeX), m_startBuildingTileY - int(transferedPosition.y() / m_tileSizeY)); } } int projectedTileX = m_startBuildingTileX + int(projectionOfForwardOnXY.x() / m_tileSizeX); int projectedTileY = m_startBuildingTileY - int(projectionOfForwardOnXY.y() / m_tileSizeY); std::sort(queue.begin(), queue.end(), [projectedTileX, projectedTileY](const OSMTileData &v1, const OSMTileData &v2)->bool{ return qSqrt(qPow(v1.TileX - projectedTileX, 2) + qPow(v1.TileY - projectedTileY, 2)) < qSqrt(qPow(v2.TileX - projectedTileX, 2) + qPow(v2.TileY - projectedTileY, 2)); }); m_request->getBuildingsData( queue ); m_request->getMapsData( queue );
Also generates the tiles request queue to submit as a request.
void OSMManager::addBuildingRequestToQueue(QQueue<OSMTileData> &queue, int tileX, int tileY, int zoomLevel) { QString key = QString::number(tileX) + "," + QString::number(tileY) + "," + QString::number(zoomLevel); if ( m_buildingsHash.contains( key ) ) return; OSMTileData tile; tile.ZoomLevel = zoomLevel; tile.TileX = tileX; tile.TileY = tileY; queue.append( tile ); }
The app uses a custom camera controller to navigate through the map, using the left mouse button to pan the map and right mouse button to rotate, and mouse wheel to zoom the camera.
OSMCameraController {
id: cameraController
origin: originNode
camera: cameraNode
}
Every chunk of the map tile consists of two types of the QML model and custom material. one using a rectangle as a base to render tile map texture.
...
id: chunkModelMap
Node {
property variant mapData: null
property int tileX: 0
property int tileY: 0
property int zoomLevel: 0
Model {
id: basePlane
position: Qt.vector3d( osmManager.tileSizeX * tileX, osmManager.tileSizeY * -tileY, 0.0 )
scale: Qt.vector3d( osmManager.tileSizeX / 100., osmManager.tileSizeY / 100., 0.5)
source: "#Rectangle"
materials: [
CustomMaterial {
property TextureInput tileTexture: TextureInput {
enabled: true
texture: Texture {
textureData: CustomTextureData {
Component.onCompleted: setImageData( mapData )
} }
}
shadingMode: CustomMaterial.Shaded
cullMode: Material.BackFaceCulling
fragmentShader: "customshadertiles.frag"
}
]
}
It's using a custom geometry to render tile buildings.
...
id: chunkModelBuilding
Node {
property variant geoVariantsList: null
property int tileX: 0
property int tileY: 0
property int zoomLevel: 0
Model {
id: model
scale: Qt.vector3d(1, 1, 1)
OSMGeometry {
id: osmGeometry
Component.onCompleted: updateData( geoVariantsList )
onGeometryReady:{
model.geometry = osmGeometry
}
}
materials: [
CustomMaterial {
shadingMode: CustomMaterial.Shaded
cullMode: Material.BackFaceCulling
vertexShader: "customshaderbuildings.vert"
fragmentShader: "customshaderbuildings.frag"
}
]
}
To render buildings parts e.g rooftop and body of the building with one draw call, it uses a custom shader to render building chunks.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
VARYING vec4 color;
float rectangle(vec2 samplePosition, vec2 halfSize) {
vec2 componentWiseEdgeDistance = abs(samplePosition) - halfSize;
float outsideDistance = length(max(componentWiseEdgeDistance, 0.0));
float insideDistance = min(max(componentWiseEdgeDistance.x, componentWiseEdgeDistance.y), 0.0);
return outsideDistance + insideDistance;
}
void MAIN() {
vec2 tc = UV0;
vec2 uv = fract(tc * UV1.x); //UV1.x number of levels
uv = uv * 2.0 - 1.0;
uv.x = 0.0;
uv.y = smoothstep(0.0, 0.2, rectangle( vec2(uv.x, uv.y + 0.5), vec2(0.2)) );
BASE_COLOR = vec4(color.xyz * mix( clamp( ( vec3( 0.4, 0.4, 0.4 ) + tc.y)
* ( vec3( 0.6, 0.6, 0.6 ) + uv.y)
, 0.0, 1.0), vec3(1.0), UV1.y ), 1.0); // UV1.y as is roofTop
ROUGHNESS = 0.3;
METALNESS = 0.0;
FRESNEL_POWER = 1.0;
}
To understand the application better, browse through its code using Qt Creator.
Running the Example
To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.
See also QML Applications.