diff options
Diffstat (limited to 'src/threed/geometry')
26 files changed, 8952 insertions, 0 deletions
diff --git a/src/threed/geometry/geometry.pri b/src/threed/geometry/geometry.pri new file mode 100644 index 000000000..5623ac054 --- /dev/null +++ b/src/threed/geometry/geometry.pri @@ -0,0 +1,28 @@ +INCLUDEPATH += $$PWD +VPATH += $$PWD +HEADERS += qglcube.h \ + qglsphere.h \ + qgeometrydata.h \ + qlogicalvertex.h \ + qglbuilder.h \ + qglbezierpatches.h \ + qglmaterialcollection.h \ + qglteapot.h \ + qglcylinder.h \ + qgldome.h +SOURCES += qglcube.cpp \ + qglsphere.cpp \ + qgeometrydata.cpp \ + qglbuilder.cpp \ + qglsection.cpp \ + qglbezierpatches.cpp \ + qglmaterialcollection.cpp \ + qglteapot.cpp \ + qlogicalvertex.cpp \ + qglcylinder.cpp \ + qgldome.cpp +PRIVATE_HEADERS += qglteapot_data_p.h \ + qglbuilder_p.h \ + qglsection_p.h \ + qglteapot_data_p.h \ + qvector_utils_p.h diff --git a/src/threed/geometry/qgeometrydata.cpp b/src/threed/geometry/qgeometrydata.cpp new file mode 100644 index 000000000..f5cedc960 --- /dev/null +++ b/src/threed/geometry/qgeometrydata.cpp @@ -0,0 +1,2025 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgeometrydata.h" +#include "qlogicalvertex.h" +#include "qglpainter.h" + +#include <QtOpenGL/qgl.h> +#include <QtCore/qdebug.h> + +/*! + \class QGeometryData + \brief The QGeometryData class encapsulates sets of geometry data. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + The QGeometryData class encloses a number of data arrays + that model most typical vertex data needs. The class provides a + store for all of the data types in the QGL::VertexAttribute enumeration. + + \table + \header + \o QGL::VertexAttribute + \o QGeometryData functions + \row + \o QGL::Position + \o appendVertex(), vertex(), vertices() + \row + \o QGL::Normal + \o appendNormal(), normal(), normals() + \row + \o QGL::Color + \o appendColor(), colorRef(), colors() + \row + \o QGL::TextureCoord0 - QGL::TextureCoord3 + \o appendTexCoord(), texCoordRef(), texCoords() + \row + \o QGL::CustomVertex0 - QGL::CustomVertex1, QGL::UserVertex + \o appendAttribute(), vector3DAttribute(), attributes() + \endtable + + Additionally the class provides the following features: + \list + \o appendVertex() for adding a QLogicalVertex() + \o logicalVertexAt() for return the data at an index as a QLogicalVertex() + \o hasField() to find if a particular data type is present + \o normalizeNormals() to reduce all normal vectors to unit length + \o boundingBox() to find the bounds of the geometry + \endlist + + It is up to the user of a QGeometryData instance to ensure that the + data has an equal number of items in each field. For example, if five + vertices are added and only two normals are added, the logical vertex at + position 3 will be corrupt, since it does not have a normal. + + While data is being accumulated the counts of different fields will vary, + since it may be convenient to add several vertices, then several normals, + colors or attributes at a time. However when a logical vertex is + constructed or when the data is sent to the GPU, counts of all fields + must be equal. + + QGeometryData uses explicit sharing with lazy creation of internal + data so that code like: + \code + QGeometryData myData; + if (processed) + myData = processedData(); + \endcode + is very inexpensive, since the first declaration and initialization + does not cause internal data to be created (only to be overwritten by the + assignment operation). + + Since QGeometryData is explicitly shared, variables of type + QGeometryData behave like references, and the underlying data is modified + by calling a non-const function on any variable which shares that data. + + To force an explicit copy call the detach() function. +*/ + +/*! + \typedef QGL::IndexArray + + This is a convenience for either QArray<ushort> (OpenGL/ES) or + QArray<int> (desktop OpenGL). +*/ + +class QGeometryDataPrivate +{ +public: + QGeometryDataPrivate(); + ~QGeometryDataPrivate(); + QGeometryDataPrivate *clone() const; + + QBasicAtomicInt ref; + + QVector3DArray vertices; + QVector3DArray normals; + QArray<QColor4ub> colors; + QList<QCustomDataArray> attributes; + QList<QVector2DArray> textures; + QGL::IndexArray indices; + QGLVertexBundle vertexBundle; + QGLIndexBuffer indexBuffer; + bool uploadsViable; + bool modified; + QBox3D bb; + static const int ATTR_CNT = 32; + quint32 fields; + qint8 key[ATTR_CNT]; + quint8 size[ATTR_CNT]; + int count; + int reserved; + bool boxValid; + QGeometryData::BufferStrategy bufferStrategy; +}; + +QGeometryDataPrivate::QGeometryDataPrivate() + : uploadsViable(true) + , modified(false) + , fields(0) + , count(0) + , reserved(-1) + , boxValid(true) + , bufferStrategy(QGeometryData::BufferIfPossible | QGeometryData::KeepClientData) +{ + ref = 0; + qMemSet(key, -1, ATTR_CNT); + qMemSet(size, 0, ATTR_CNT); +} + +QGeometryDataPrivate::~QGeometryDataPrivate() +{ +} + +QGeometryDataPrivate *QGeometryDataPrivate::clone() const +{ + QGeometryDataPrivate *temp = new QGeometryDataPrivate; + temp->vertices = vertices; + temp->normals = normals; + temp->colors = colors; + temp->attributes = attributes; + temp->textures = textures; + temp->indices = indices; + temp->vertexBundle = vertexBundle; + temp->indexBuffer = indexBuffer; + temp->uploadsViable = uploadsViable; + temp->modified = modified; + temp->bb = bb; + temp->fields = fields; + qMemCopy(temp->key, key, ATTR_CNT); + qMemCopy(temp->size, size, ATTR_CNT); + temp->count = count; + temp->reserved = reserved; + temp->boxValid = boxValid; + temp->bufferStrategy = bufferStrategy; + return temp; +} + +/*! + \fn quint32 QGL::fieldMask(QGL::VertexAttribute attribute) + \relates QGeometryData + Returns an unsigned integer mask from the \a attribute. + + \sa QGeometryData::fields() +*/ + +/*! + \enum QGeometryData::BufferStrategyFlags + + This enum serves to describe how management of the data is handled + with respect to vertex buffer objects. The strategies are essentially a + combination of whether the client data is kept around after it has been + successfully uploaded to the GPU; and whether an upload is attempted at + all. + + If the data set is very small it may be pointless to use up a VBO, hence + in this case KeepClientData may be used resulting in no attempt to upload + the data and client side arrays used instead. + + \value InvalidStrategy No valid strategy has been specified. + \value KeepClientData Keep the client data, even after successful upload to the GPU. + \value BufferIfPossible Try to upload the data to the GPU. +*/ + +/*! + Construct an empty QGeometryData +*/ +QGeometryData::QGeometryData() + : d(0) +{ +} + +/*! + Construct QGeometryData as a copy of \a other +*/ +QGeometryData::QGeometryData(const QGeometryData &other) + : d(other.d) +{ + if (d) + d->ref.ref(); +} + +/*! + Construct an empty QGeometryData with the \a fields enabled. +*/ +QGeometryData::QGeometryData(quint32 fields) + : d(new QGeometryDataPrivate) +{ + d->ref.ref(); + const quint32 mask = 0x01; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (!(mask & fields)) continue; + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + enableField(attr); + } +} + +/*! + Destroys this QGeometryData recovering any resources. +*/ +QGeometryData::~QGeometryData() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Assigns this QGeometryData to be a copy of \a other. +*/ +QGeometryData &QGeometryData::operator=(const QGeometryData &other) +{ + if (d != other.d) + { + if (d && !d->ref.deref()) + delete d; + d = other.d; + if (d) + d->ref.ref(); + } + return *this; +} + +/*! + Appends the geometry in \a data to this. If this is empty, then all + fields of \a data are appended; otherwise (when this has existing fields) + only those fields that exist in both are appended. + + This does not change the indices - to reference the new geometry add + indices via the appendIndices() functions. +*/ +void QGeometryData::appendGeometry(const QGeometryData &data) +{ + if (data.d && data.count()) + { + detach(); + d->modified = true; + d->boxValid = false; + int cnt = data.d->count; + const quint32 mask = 0x01; + quint32 fields = d->fields | data.fields(); + d->fields = fields; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + enableField(attr); // might not be enabled if we had NO fields + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + d->vertices.append(data.d->vertices); + else if (attr == QGL::Normal) + d->normals.append(data.d->normals); + else // colors + d->colors.append(data.d->colors); + } + else if (attr < QGL::CustomVertex0) + { + d->textures[d->key[attr]].append(data.texCoords(attr)); + } + else + { + d->attributes[d->key[attr]].append(data.attributes(attr)); + } + } + } + d->count += cnt; + } +} + +/*! + Appends all the data fields in QLogicalVertex \a v to this + QGeometryData object. +*/ +int QGeometryData::appendVertex(const QLogicalVertex &v) +{ + create(); + d->modified = true; + if (d->boxValid) + d->bb.unite(v.vertex()); + quint32 fields = v.fields(); + const quint32 mask = 0x01; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + appendVertex(v.vertex()); + else if (attr == QGL::Normal) + appendNormal(v.normal()); + else + appendColor(v.color()); + } + else if (attr < QGL::CustomVertex0) + { + appendTexCoord(v.texCoord(attr), attr); + } + else + { + appendAttribute(v.attribute(attr), attr); + } + } + } + return d->count - 1; +} + +/*! + Returns a QLogicalVertex that references the \a{i}'th logical vertex + of this geometry. +*/ +QLogicalVertex QGeometryData::logicalVertexAt(int i) const +{ + return QLogicalVertex(*this, i); +} + +/*! + Normalize all the normal vectors in this geometry to unit length. +*/ +void QGeometryData::normalizeNormals() +{ + check(); + if (d) // nothng to do if its null + { + create(); + d->modified = true; + if (hasField(QGL::Normal)) + { + for (int i = 0; i < d->normals.count(); ++i) + d->normals[i].normalize(); + } + } +} + +/*! + Calculate and return a bounding box for the vertex data in this geometry. +*/ +QBox3D QGeometryData::boundingBox() const +{ + QBox3D box; + if (d) + { + if (d->boxValid) + { + box = d->bb; + } + else + { + for (int i = 0; i < d->count; ++i) + box.unite(d->vertices.at(i)); + d->bb = box; + } + } + return box; +} + +/*! + Returns the coordinates of the center of the geometry. + + The center is calculated as the centroid or geometric barycenter + of the vertices (the average of the vertices). For a convex hull this + is guaranteed to be inside the figure. +*/ +QVector3D QGeometryData::center() const +{ + QVector3D center; + for (int i = 0; i < d->vertices.count(); ++i) + center += d->vertices.at(i); + return center / (float)d->vertices.count(); +} + +/*! + Returns a copy of this geometry data with elements in reverse order. +*/ +QGeometryData QGeometryData::reversed() const +{ + QGeometryData r; + for (int i = count() - 1; i >= 0; --i) + r.appendVertex(logicalVertexAt(i)); + return r; +} + +/*! + Returns a copy of this geometry data with QGL::Position data translated by + the vector \a t. The other fields are unchanged. +*/ +QGeometryData QGeometryData::translated(const QVector3D &t) const +{ + QGeometryData r(*this); + r.detach(); + for (int i = 0; i < count(); ++i) + { + r.vertex(i) = r.vertexAt(i) + t; + } + return r; +} + +/*! + Modifies this geometry data by generating texture data based on QGL::Position + values. If \a orientation is Qt::Horizontal (the default) then x-coordinate + values are generated, and y-coordinate values are set to 0.0; otherwise + y-coordinate values are generated and x-coordinate values are set to 0.0. + The values are appended to the texture coordinate \a field. + + The method of calculation is based on the assumption that the vertex data + is a list of extents which span across the texture space horizontally, from + x = 0.0 to x = 1.0, in the case of Qt::Horizontal; or vertically in the + case of Qt::Vertical. The texture space of 1.0 is divided up proportionately + by the length of each extent. + + \image texture-coords-gen.png + + In this diagram the large blue numbers are the lengths of each extent, and + the texture coordinates generated are shown as \c{t(7/16, 1)} and so on. + + Thus the texture coordinate t0 for vertex v0, is 0.0; t1 for vertex v1 is + \c{(v1 - v0).length() / totalLength} and so on. + + The code to produce the texture coordinates for the quads in the image is: + \code + QGeometryData top; + + // add data to the primitive + top.appendVertex(QVector3D(0.0, 0.0, 0.0)); + top.appendVertex(QVector3D(6.0, 3.6, 0.0)); // (v1 - v0).length() = 7.0 + top.appendVertex(QVector3D(10.0, 0.6, 0.0)); // (v2 - v1).length() = 5.0 + top.appendVertex(QVector3D(13.0, 3.24, 0.0)); // (v3 - v2).length() = 4.0 + + // generate x (Qt::Horizontal) texture coordinates over the primitive + top.generateTextureCoordinates(); // spread over 7 + 5 + 4 = 16 + + // make a copy translated down, the copy has y texture coordinates all 0 + QGeometryData bottom = top.translated(QVector3D(0, 0, -1)); + + // now modify the top so its y texture coordinates are all 1 + for (int i = 0; i < top.count(); ++i) + top.texCoordRef(QGL::TextureCoord0).setY(1.0); + + displayList->addQuadsZipped(top, bottom); + \endcode +*/ +void QGeometryData::generateTextureCoordinates(Qt::Orientation orientation, QGL::VertexAttribute field) +{ + QArray<qreal> extents; + extents.append(0.0); + qreal totalExtents = 0.0; + QArray<QVector3D> v = vertices(); + for (int i = 0; i < v.count() - 1; ++i) + { + int n = (i + 1) % v.count(); + QVector3D e = v[n] - v[i]; + qreal extent = e.length(); + totalExtents += extent; + extents.append(totalExtents); + } + if (hasField(field)) + clear(field); + if (orientation == Qt::Horizontal) + { + for (int i = 0; i < v.count(); ++i) + appendTexCoord(QVector2D(extents[i] / totalExtents, 0.0), field); + } + else + { + for (int i = 0; i < v.count(); ++i) + appendTexCoord(QVector2D(0.0, extents[i] / totalExtents), field); + } +} + +/*! + Returns a QGeometryData instance containing alternating vertices from + this geometry and \a other. The resulting geometry contains N vertices + where \c{N == qMin(count(), other.count())}, and has only the fields + that are in both geometries. +*/ +QGeometryData QGeometryData::interleavedWith(const QGeometryData &other) const +{ + QGeometryData res; + check(); + other.check(); + if (d && other.d) + { + int cnt = qMax(d->count, other.d->count); + const quint32 mask = 0x01; + quint32 fields = d->fields & other.d->fields; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + res.enableField(attr); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + { + QArray<QVector3D> tmp; + for (int i = 0; i < cnt; ++i) + { + tmp.append(d->vertices.at(i)); + tmp.append(other.d->vertices.at(i)); + } + res.d->vertices = tmp; + } + else if (attr == QGL::Normal) + { + QArray<QVector3D> tmp; + for (int i = 0; i < cnt; ++i) + { + tmp.append(d->normals.at(i)); + tmp.append(other.d->normals.at(i)); + } + res.d->normals = tmp; + } + else // colors + { + QArray<QColor4ub> tmp; + for (int i = 0; i < cnt; ++i) + { + tmp.append(d->colors.at(i)); + tmp.append(other.d->colors.at(i)); + } + res.d->colors = tmp; + } + } + else if (attr < QGL::CustomVertex0) + { + QArray<QVector2D> tmp; + const QArray<QVector2D> txa = d->textures.at(d->key[attr]); + const QArray<QVector2D> txb = other.d->textures.at(other.d->key[attr]); + for (int i = 0; i < cnt; ++i) + { + tmp.append(txa.at(i)); + tmp.append(txb.at(i)); + } + res.d->textures[d->key[attr]] = tmp; + } + else + { + QCustomDataArray tmp; + const QCustomDataArray ata = d->attributes.at(d->key[attr]); + const QCustomDataArray atb = other.d->attributes.at(other.d->key[attr]); + for (int i = 0; i < cnt; ++i) + { + tmp.append(ata.at(i)); + tmp.append(atb.at(i)); + } + res.d->attributes[d->key[attr]] = tmp; + } + } + } + res.d->count = cnt * 2; + } + return res; +} + +/*! + Sets this QGeometryData to contain alternating vertices from + this geometry and \a other. The resulting geometry contains \c{N * 2} vertices + where \c{N == qMin(count(), other.count())}, and has only the fields + that are in both geometries. +*/ +void QGeometryData::interleaveWith(const QGeometryData &other) +{ + check(); + other.check(); + if (d && other.d) + { + create(); + d->modified = true; + d->boxValid = false; + int cnt = qMin(d->count, other.d->count); + const quint32 mask = 0x01; + quint32 fields = d->fields & other.d->fields; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + { + QArray<QVector3D> tmp; + for (int i = 0; i < cnt; ++i) + { + tmp.append(d->vertices.at(i)); + tmp.append(other.d->vertices.at(i)); + } + d->vertices = tmp; + } + else if (attr == QGL::Normal) + { + QArray<QVector3D> tmp; + for (int i = 0; i < cnt; ++i) + { + tmp.append(d->normals.at(i)); + tmp.append(other.d->normals.at(i)); + } + d->normals = tmp; + } + else // colors + { + QArray<QColor4ub> tmp; + for (int i = 0; i < cnt; ++i) + { + tmp.append(d->colors.at(i)); + tmp.append(other.d->colors.at(i)); + } + d->colors = tmp; + } + } + else if (attr < QGL::CustomVertex0) + { + QArray<QVector2D> tmp; + const QArray<QVector2D> txa = d->textures.at(d->key[attr]); + const QArray<QVector2D> txb = other.d->textures.at(other.d->key[attr]); + for (int i = 0; i < cnt; ++i) + { + tmp.append(txa.at(i)); + tmp.append(txb.at(i)); + } + d->textures[d->key[attr]] = tmp; + } + else + { + QCustomDataArray tmp; + const QCustomDataArray ata = d->attributes.at(d->key[attr]); + const QCustomDataArray atb = other.d->attributes.at(other.d->key[attr]); + for (int i = 0; i < cnt; ++i) + { + tmp.append(ata.at(i)); + tmp.append(atb.at(i)); + } + d->attributes[d->key[attr]] = tmp; + } + } + } + d->count = cnt * 2; + } +} + +/*! + Clear all data structures. The actual fields are retained, but they + have no contents. + \code + QGeometryData data; + data.appendVertex(a); + data.appendTexCoord(t); + + // prints "1" + qDebug() << data.count(); + + // x == a + QVector3D x = data.vertexAt(0); + + quint32 flds = QGL::fieldMask(QGL::Position) | QGL::fieldMask(QGL::TextureCoord0); + qDebug() << (flds == data.fields()); // prints "true" + + data.clear(); + qDebug() << data.count(); // prints "0" + QVector3D x = data.vertexAt(0); // asserts - no data in vertices + qDebug() << (flds == data.fields()); // still prints "true" + \endcode + + To clear a specific field and its data use \c{data.clear(field)} below. + + To clear all fields and data, simply set this to an empty geometry: + \code + data = QGeometryData(); + \endcode + */ +void QGeometryData::clear() +{ + if (d) + { + create(); + d->modified = true; + d->bb = QBox3D(); + d->boxValid = true; + const quint32 mask = 0x01; + quint32 fields = d->fields; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + d->vertices.clear(); + else if (attr == QGL::Normal) + d->normals.clear(); + else + d->colors.clear(); + } + else if (attr < QGL::CustomVertex0) + { + d->textures[d->key[field]].clear(); + } + else + { + d->attributes[d->key[field]].clear(); + } + } + } + d->count = 0; + } +} + +/*! + Clears the data from \a field, and removes the field. After this call + hasField() will return false for this field. +*/ +void QGeometryData::clear(QGL::VertexAttribute field) +{ + if (d && (QGL::fieldMask(field) & d->fields)) + { + create(); + d->modified = true; + if (field == QGL::Position) + { + d->bb = QBox3D(); + d->boxValid = true; + } + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + d->vertices.clear(); + else if (attr == QGL::Normal) + d->normals.clear(); + else + d->colors.clear(); + } + else if (attr < QGL::CustomVertex0) + { + d->textures[d->key[field]].clear(); + } + else + { + d->attributes[d->key[field]].clear(); + } + d->key[field] = -1; + d->fields = d->fields & ~QGL::fieldMask(field); + } +} + +/*! + Sets the geometry data to handle an \a amount of data. This is generally + not required unless its anticipated that a large amount of data will be + appended and realloc overhead is desired to be avoided. If \a amount is + less than the amount already reserved, or if this object has + more than the \a amount of data items, then this function exits without + doing anything. This function will never delete data. +*/ +void QGeometryData::reserve(int amount) +{ + if (d && (d->reserved > amount || d->reserved < d->count)) + return; + create(); + d->reserved = amount; + const quint32 mask = 0x01; + quint32 fields = d->fields; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + d->vertices.reserve(amount); + else if (attr == QGL::Normal) + d->normals.reserve(amount); + else + d->colors.reserve(amount); + } + else if (attr < QGL::CustomVertex0) + { + d->textures[d->key[field]].reserve(amount); + } + else + { + d->attributes[d->key[field]].reserve(amount); + } + } + } +} + +/*! + Draws this geometry on the \a painter, from \a start for \a count elements + in \a mode. The drawing \a mode is by default QGL::Triangles. This function + Also calls the upload() method to ensure that the geometry is resident on + the graphics hardware if appropriate. + + If the geometry is a point or line, then the \a drawWidth value specified the + width/size of the line/point. +*/ +void QGeometryData::draw(QGLPainter *painter, int start, int count, GLenum mode, qreal drawWidth) +{ + if (d && d->indices.size() && d->count) + { + upload(); + painter->clearAttributes(); + if (mode==QGL::Points) { +#if !defined(QT_OPENGL_ES_2) + ::glPointSize(drawWidth); +#endif + } else if (mode==QGL::LineStrip || mode == QGL::Lines) { + ::glLineWidth(drawWidth); + } + painter->setVertexBundle(d->vertexBundle); + if (count == 0) + count = d->indexBuffer.indexCount(); + painter->draw(QGL::DrawingMode(mode), d->indexBuffer, start, count); + } +} + +/*! + Uploads this geometry data to the graphics hardware if appropriate. If the + data is already uploaded and has not been modified since it was last + uploaded, then this function does nothing. + + If the bufferStrategy() does not specify QGL::BufferIfPossible then this + function does nothing. + + If the data was successfully uploaded, and the bufferStrategy() does not + specify QGL::KeepClientData then the data will be removed with a call to + the clear() function. + + If the data was successfully uploaded, on this call or previously, then this + function will return true. Otherwise it returns false. +*/ +bool QGeometryData::upload() +{ + bool vboUploaded = false; + bool iboUploaded = false; + + if (!d) + return false; + if (!d->modified) + return d->vertexBundle.isUploaded() && d->indexBuffer.isUploaded(); + + check(); + + // Need to recreate the buffers from the modified data. + d->vertexBundle = QGLVertexBundle(); + d->indexBuffer = QGLIndexBuffer(); + + // Copy the geometry data to the vertex buffer. + const quint32 mask = 0x01; + quint32 fields = d->fields; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (!(mask & fields)) + continue; + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr == QGL::Position) + d->vertexBundle.addAttribute(attr, d->vertices); + else if (attr == QGL::Normal) + d->vertexBundle.addAttribute(attr, d->normals); + else if (attr == QGL::Color) + d->vertexBundle.addAttribute(attr, d->colors); + else if (attr < QGL::CustomVertex0) + d->vertexBundle.addAttribute(attr, d->textures.at(d->key[field])); + else + d->vertexBundle.addAttribute(attr, d->attributes.at(d->key[field])); + } + + // Upload the buffer if requested, otherwise keep it client-side. + // Note: QGLVertexBundle will act as a client-side buffer if not uploaded. + if ((d->bufferStrategy & BufferIfPossible) != 0) + { + if (d->vertexBundle.upload()) + vboUploaded = true; + } + + // Copy the geometry data to the index buffer and upload if requested. + d->indexBuffer.setIndexes(d->indices); + if ((d->bufferStrategy & BufferIfPossible) != 0) + { + if (d->indexBuffer.upload()) + iboUploaded = true; + } + + d->modified = false; + + if (!(d->bufferStrategy & KeepClientData) && vboUploaded && iboUploaded) + clear(); + + return vboUploaded && iboUploaded; +} + +/*! + Sets the buffer \a strategy for this geometry. + + \sa bufferStrategy() +*/ +void QGeometryData::setBufferStrategy(QGeometryData::BufferStrategy strategy) +{ + if (!d || d->bufferStrategy != strategy) + { + create(); + d->modified = true; + d->bufferStrategy = strategy; + } +} + +/*! + Returns the buffer strategy for this geometry. The default is + \c{QGL::BufferIfPossible | QGL::KeepClientData}. + + \sa setBufferStrategy() +*/ +QGeometryData::BufferStrategy QGeometryData::bufferStrategy() const +{ + if (d) + return d->bufferStrategy; + return InvalidStrategy; +} + +/*! + Returns a reference to the vertex buffer for this geometry. + + \sa indexBuffer() +*/ +QGLVertexBundle QGeometryData::vertexBundle() const +{ + return d->vertexBundle; +} + +/*! + Returns a reference to the index buffer for this geometry. + + \sa vertexBundle() +*/ +QGLIndexBuffer QGeometryData::indexBuffer() const +{ + return d->indexBuffer; +} + +/*! + Appends \a index to the vertex index array. + + \sa appendIndices(), indices() +*/ +void QGeometryData::appendIndex(int index) +{ + create(); + d->modified = true; + d->indices.append(index); +} + +/*! + Appends \a index1, \a index2, and \a index3 to the geometry's + index array. + + \sa appendIndex(), indices() +*/ +void QGeometryData::appendIndices(int index1, int index2, int index3) +{ + create(); + d->modified = true; + d->indices.append(index1, index2, index3); +} + +/*! + Returns the index array that was created by appendIndex(). + + \sa appendIndex(), appendIndices() +*/ +QGL::IndexArray QGeometryData::indices() const +{ + if (d) + return d->indices; + else + return QGL::IndexArray(); +} + +/*! + Appends the \a indices to the geometry's index array. +*/ +void QGeometryData::appendIndices(const QGL::IndexArray &indices) +{ + create(); + d->modified = true; + d->indices.append(indices); +} + +/*! + Append the point \a v0 to this geometry data as a position field. +*/ +void QGeometryData::appendVertex(const QVector3D &v0) +{ + create(); + d->modified = true; + enableField(QGL::Position); + d->vertices.append(v0); + if (d->boxValid) + d->bb.unite(v0); + d->count = qMax(d->count, d->vertices.count()); +} + +/*! + Append the points \a v0 and \a v1 to this geometry data as position fields. +*/ +void QGeometryData::appendVertex(const QVector3D &v0, const QVector3D &v1) +{ + create(); + d->modified = true; + enableField(QGL::Position); + d->vertices.append(v0, v1); + if (d->boxValid) + { + d->bb.unite(v0); + d->bb.unite(v1); + } + d->count = qMax(d->count, d->vertices.count()); +} + +/*! + Append the points \a v0, \a v1 and \a v2 to this geometry data as position fields. +*/ +void QGeometryData::appendVertex(const QVector3D &v0, const QVector3D &v1, const QVector3D &v2) +{ + create(); + d->modified = true; + enableField(QGL::Position); + d->vertices.append(v0, v1, v2); + if (d->boxValid) + { + d->bb.unite(v0); + d->bb.unite(v1); + d->bb.unite(v2); + } + d->count = qMax(d->count, d->vertices.count()); +} + +/*! + Append the points \a v0, \a v1, \a v2 and \a v3 to this geometry data as position fields. +*/ +void QGeometryData::appendVertex(const QVector3D &v0, const QVector3D &v1, const QVector3D &v2, const QVector3D &v3) +{ + create(); + d->modified = true; + enableField(QGL::Position); + d->vertices.append(v0, v1, v2, v3); + if (d->boxValid) + { + d->bb.unite(v0); + d->bb.unite(v1); + d->bb.unite(v2); + d->bb.unite(v3); + } + d->count = qMax(d->count, d->vertices.count()); +} + +/*! + Append the float \a a0 to this geometry data, as an attribute \a field. +*/ +void QGeometryData::appendAttribute(float a0, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + d->attributes[d->key[field]].append(a0); + d->count = qMax(d->count, d->attributes[d->key[field]].count()); +} + +/*! + Append the float \a a0 and \a a1 to this geometry data, as an attribute \a field. +*/ +void QGeometryData::appendAttribute(float a0, float a1, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + d->attributes[d->key[field]].append(a0, a1); + d->count = qMax(d->count, d->attributes[d->key[field]].count()); +} + +/*! + Append the floats \a a0, \a a1 and \a a2 to this geometry data, as attribute \a field. +*/ +void QGeometryData::appendAttribute(float a0, float a1, float a2, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + d->attributes[d->key[field]].append(a0, a1, a2); + d->count = qMax(d->count, d->attributes[d->key[field]].count()); +} + +/*! + Append the floats \a a0, \a a1, \a a2 and \a a3 to this geometry data, as attribute \a field. +*/ +void QGeometryData::appendAttribute(float a0, float a1, float a2, float a3, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + d->attributes[d->key[field]].append(a0, a1, a2, a3); + d->count = qMax(d->count, d->attributes[d->key[field]].count()); +} + +/*! + Append the 2D point \a a to this geometry data, as an attribute \a field. +*/ +void QGeometryData::appendAttribute(const QVector2D &a, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + if (d->attributes.at(d->key[field]).isEmpty()) + d->attributes[d->key[field]].setElementType(QCustomDataArray::Vector2D); + d->attributes[d->key[field]].append(a); + d->count = qMax(d->count, d->attributes[d->key[field]].count()); +} + +/*! + Append the 3D point \a v to this geometry data, as an attribute \a field. +*/ +void QGeometryData::appendAttribute(const QVector3D &v, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + if (d->attributes.at(d->key[field]).isEmpty()) + d->attributes[d->key[field]].setElementType(QCustomDataArray::Vector3D); + d->attributes[d->key[field]].append(v); + d->count = qMax(d->count, d->attributes[d->key[field]].count()); +} + +/*! + Append the variant value \a a to this geometry data, as an attribute \a field. +*/ +void QGeometryData::appendAttribute(const QVariant &a, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + if (d->attributes.at(d->key[field]).isEmpty()) + { + // floats and doubles get handled "automatically" - float is default + if (a.type() == QVariant::Vector2D) + d->attributes[d->key[field]].setElementType(QCustomDataArray::Vector2D); + else if (a.type() == QVariant::Vector3D) + d->attributes[d->key[field]].setElementType(QCustomDataArray::Vector3D); + else if (a.type() == QVariant::Vector4D) + d->attributes[d->key[field]].setElementType(QCustomDataArray::Vector4D); + else if (a.type() == QVariant::Color) + d->attributes[d->key[field]].setElementType(QCustomDataArray::Color); + else + Q_ASSERT_X(false, "QGeometryData::appendAttribute", "bad type"); + } + d->attributes[d->key[field]].append(a); + d->count = qMax(d->count, d->attributes[d->key[field]].count()); +} + +/*! + Append the vector \a n0 to this geometry data, as a lighting normal. +*/ +void QGeometryData::appendNormal(const QVector3D &n0) +{ + create(); + d->modified = true; + enableField(QGL::Normal); + d->normals.append(n0); + d->count = qMax(d->count, d->normals.count()); +} + +/*! + Append the vectors \a n0 and \a n1 to this geometry data, as lighting normals. +*/ +void QGeometryData::appendNormal(const QVector3D &n0, const QVector3D &n1) +{ + create(); + d->modified = true; + enableField(QGL::Normal); + d->normals.append(n0, n1); + d->count = qMax(d->count, d->normals.count()); +} + +/*! + Append the vectors \a n0, \a n1 and \a n2 to this geometry data, as lighting normals. +*/ +void QGeometryData::appendNormal(const QVector3D &n0, const QVector3D &n1, const QVector3D &n2) +{ + create(); + d->modified = true; + enableField(QGL::Normal); + d->normals.append(n0, n1, n2); + d->count = qMax(d->count, d->normals.count()); +} + +/*! + Append the vectors \a n0, \a n1, \a n2 and \a n3 to this geometry data, as lighting normals. +*/ +void QGeometryData::appendNormal(const QVector3D &n0, const QVector3D &n1, const QVector3D &n2, const QVector3D &n3) +{ + create(); + d->modified = true; + enableField(QGL::Normal); + d->normals.append(n0, n1, n2, n3); + d->count = qMax(d->count, d->normals.count()); +} + +/*! + Append the point \a t0 to this geometry data, as an texture \a field. +*/ +void QGeometryData::appendTexCoord(const QVector2D &t0, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + d->textures[d->key[field]].append(t0); + d->count = qMax(d->count, d->textures[d->key[field]].count()); +} + +/*! + Append the points \a t0 and \a t1 to this geometry data, as texture \a{field}s. +*/ +void QGeometryData::appendTexCoord(const QVector2D &t0, const QVector2D &t1, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + d->textures[d->key[field]].append(t0, t1); + d->count = qMax(d->count, d->textures[d->key[field]].count()); +} + +/*! + Append the points \a t0, \a t1 and \a t2 to this geometry data, as texture \a{field}s. +*/ +void QGeometryData::appendTexCoord(const QVector2D &t0, const QVector2D &t1, const QVector2D &t2, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + d->textures[d->key[field]].append(t0, t1, t2); + d->count = qMax(d->count, d->textures[d->key[field]].count()); +} + +/*! + Append the points \a t0, \a t1, \a t2 and \a t3 to this geometry data, as texture \a{field}s. +*/ +void QGeometryData::appendTexCoord(const QVector2D &t0, const QVector2D &t1, const QVector2D &t2, const QVector2D &t3, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + enableField(field); + d->textures[d->key[field]].append(t0, t1, t2, t3); + d->count = qMax(d->count, d->textures[d->key[field]].count()); +} + +/*! + Append the color \a c0 to this geometry data, as an color field. +*/ +void QGeometryData::appendColor(const QColor4ub &c0) +{ + create(); + d->modified = true; + enableField(QGL::Color); + d->colors.append(c0); + d->count = qMax(d->count, d->colors.count()); +} + +/*! + Append the color \a c0 and \a c1 to this geometry data, as color fields. +*/ +void QGeometryData::appendColor(const QColor4ub &c0, const QColor4ub &c1) +{ + create(); + d->modified = true; + enableField(QGL::Color); + d->colors.append(c0, c1); + d->count = qMax(d->count, d->colors.count()); +} + +/*! + Append the color \a c0, \a c1 and \a c2 to this geometry data, as color fields. +*/ +void QGeometryData::appendColor(const QColor4ub &c0, const QColor4ub &c1, const QColor4ub &c2) +{ + create(); + d->modified = true; + enableField(QGL::Color); + d->colors.append(c0, c1, c2); + d->count = qMax(d->count, d->colors.count()); +} + +/*! + Append the color \a c0, \a c1, \a c2 and \a c3 to this geometry data, as color fields. +*/ +void QGeometryData::appendColor(const QColor4ub &c0, const QColor4ub &c1, const QColor4ub &c2, const QColor4ub &c3) +{ + create(); + d->modified = true; + enableField(QGL::Color); + d->colors.append(c0, c1, c2, c3); + d->count = qMax(d->count, d->colors.count()); +} + +/*! + Append the points in \a ary to this geometry data as position fields. +*/ +void QGeometryData::appendVertexArray(const QVector3DArray &ary) +{ + if (ary.count()) + { + create(); + d->modified = true; + d->boxValid = false; + enableField(QGL::Position); + d->vertices.append(ary); + d->count = qMax(d->count, d->vertices.count()); + } +} + +/*! + Append the points in \a ary to this geometry data, as an attribute \a field entries. +*/ +void QGeometryData::appendAttributeArray(const QCustomDataArray &ary, QGL::VertexAttribute field) +{ + if (ary.count()) + { + create(); + d->modified = true; + enableField(field); + d->attributes[d->key[field]].append(ary); + d->count = qMax(d->count, d->attributes[d->key[field]].count()); + } +} + +/*! + Append the vectors in \a ary to this geometry data, as lighting normals. +*/ +void QGeometryData::appendNormalArray(const QVector3DArray &ary) +{ + if (ary.count()) + { + create(); + d->modified = true; + enableField(QGL::Normal); + d->normals.append(ary); + d->count = qMax(d->count, d->normals.count()); + } +} + +/*! + Append the 2D points in \a ary to this geometry data, as texture \a field entries. +*/ +void QGeometryData::appendTexCoordArray(const QVector2DArray &ary, QGL::VertexAttribute field) +{ + if (ary.count()) + { + create(); + d->modified = true; + enableField(field); + d->textures[d->key[field]].append(ary); + d->count = qMax(d->count, d->textures[d->key[field]].count()); + } +} + +/*! + Append the colors in \a ary to this geometry data, as color fields. +*/ +void QGeometryData::appendColorArray(const QArray<QColor4ub> &ary) +{ + if (ary.count()) + { + create(); + d->modified = true; + enableField(QGL::Color); + d->colors.append(ary); + d->count = qMax(d->count, d->colors.count()); + } +} + +/*! + Returns a modifiable reference to the vertex data at index \a i. +*/ +QVector3D &QGeometryData::vertex(int i) +{ + create(); + d->modified = true; + d->boxValid = false; + return d->vertices[i]; +} + +/*! + Returns a copy of the vertex position data. +*/ +QVector3DArray QGeometryData::vertices() const +{ + if (d) + return d->vertices; + return QArray<QVector3D>(); +} + +/*! + \internal + Returns a pointer to the vertex data. +*/ +const QVector3DArray *QGeometryData::vertexData() const +{ + if (d) + return &d->vertices; + return 0; +} + + +/*! + Returns a non-modifiable reference to the vertex position data at index \a i. +*/ +const QVector3D &QGeometryData::vertexAt(int i) const +{ + Q_ASSERT(hasField(QGL::Position)); + return d->vertices.at(i); +} + +/*! + Returns a modifiable reference to the normal data at index \a i. +*/ +QVector3D &QGeometryData::normal(int i) +{ + create(); + d->modified = true; + return d->normals[i]; +} + +/*! + Returns a non-modifiable reference to the normal data at index \a i. +*/ +const QVector3D &QGeometryData::normalAt(int i) const +{ + Q_ASSERT(hasField(QGL::Normal)); + return d->normals.at(i); +} + +/*! + Returns a copy of the lighting normal data. +*/ +QVector3DArray QGeometryData::normals() const +{ + if (d) + return d->normals; + return QArray<QVector3D>(); +} + +/*! + Returns a modifiable reference to the color data at index \a i. +*/ +QColor4ub &QGeometryData::color(int i) +{ + create(); + d->modified = true; + return d->colors[i]; +} + +/*! + Returns a non-modifiable reference to the color data at index \a i. +*/ +const QColor4ub &QGeometryData::colorAt(int i) const +{ + Q_ASSERT(hasField(QGL::Color)); + return d->colors.at(i); +} + +/*! + Returns a copy of the color data. +*/ +QArray<QColor4ub> QGeometryData::colors() const +{ + if (d) + return d->colors; + return QArray<QColor4ub>(); +} + +/*! + Returns a modifiable reference to the \a field texture coordinate data at index \a i. +*/ +QVector2D &QGeometryData::texCoord(int i, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + return d->textures[d->key[field]][i]; +} + +/*! + Returns a copy of the \a field texture coordinate data. +*/ +QVector2DArray QGeometryData::texCoords(QGL::VertexAttribute field) const +{ + return hasField(field) ? d->textures.at(d->key[field]) : QVector2DArray(); +} + +/*! + Returns a non-modifiable reference to the texture coordinate data at index \a i for \a field. +*/ +const QVector2D &QGeometryData::texCoordAt(int i, QGL::VertexAttribute field) const +{ + Q_ASSERT(hasField(field)); + return d->textures.at(d->key[field]).at(i); +} + +/*! + Returns a modifiable reference to the float \a field attribute data at index \a i. +*/ +float &QGeometryData::floatAttribute(int i, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + QCustomDataArray &ary = d->attributes[d->key[field]]; + Q_ASSERT(ary.elementType() == QCustomDataArray::Float); + return ary.m_array[i]; +} + +/*! + Returns a modifiable reference to the 2D vector \a field attribute data at index \a i. +*/ +QVector2D &QGeometryData::vector2DAttribute(int i, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + QCustomDataArray &ary = d->attributes[d->key[field]]; + Q_ASSERT(ary.elementType() == QCustomDataArray::Vector2D); + float *data = ary.m_array.data(); + QVector2D *v = reinterpret_cast<QVector2D*>(data + i*2); + return *v; +} + +/*! + Returns a modifiable reference to the 3D vector \a field attribute data at index \a i. +*/ +QVector3D &QGeometryData::vector3DAttribute(int i, QGL::VertexAttribute field) +{ + create(); + d->modified = true; + QCustomDataArray &ary = d->attributes[d->key[field]]; + Q_ASSERT(ary.elementType() == QCustomDataArray::Vector3D); + float *data = ary.m_array.data(); + QVector3D *v = reinterpret_cast<QVector3D*>(data + i*2); + return *v; +} + +/*! + Returns a copy of the \a field attribute data. +*/ +QCustomDataArray QGeometryData::attributes(QGL::VertexAttribute field) const +{ + return hasField(field) ? d->attributes.at(d->key[field]) : QCustomDataArray(); +} + +/*! + Returns a copy of the float \a field attribute data at index \a i. +*/ +float QGeometryData::floatAttributeAt(int i, QGL::VertexAttribute field) const +{ + Q_ASSERT(hasField(field)); + return d->attributes.at(d->key[field]).floatAt(i); +} + +/*! + Returns a copy of the 2D vector \a field attribute data at index \a i. +*/ +QVector2D QGeometryData::vector2DAttributeAt(int i, QGL::VertexAttribute field) const +{ + Q_ASSERT(hasField(field)); + return d->attributes.at(d->key[field]).vector2DAt(i); +} + +/*! + Returns a copy of the 3D vector \a field attribute data at index \a i. +*/ +QVector3D QGeometryData::vector3DAttributeAt(int i, QGL::VertexAttribute field) const +{ + Q_ASSERT(hasField(field)); + return d->attributes.at(d->key[field]).vector3DAt(i); +} + +/*! + Returns the attribute value for the \a field, suitable for passing + to QGLPainter. + + \sa QGLPainter::setVertexAttribute() +*/ +QGLAttributeValue QGeometryData::attributeValue(QGL::VertexAttribute field) const +{ + if (hasField(field)) + { + if (field < QGL::TextureCoord0) + { + if (field == QGL::Position) + return QGLAttributeValue(d->vertices); + else if (field == QGL::Normal) + return QGLAttributeValue(d->normals); + else if (field == QGL::Color) + return QGLAttributeValue(d->colors); + } + else + { + if (field < QGL::CustomVertex0) + return QGLAttributeValue(d->textures.at(d->key[field])); + else + return QGLAttributeValue(d->attributes.at(d->key[field])); + } + } + return QGLAttributeValue(); +} + +/*! + Returns true if this geometry has the field corresponding to \a attr. Note + that it is still possible for no data to have been added for that field. +*/ +bool QGeometryData::hasField(QGL::VertexAttribute attr) const +{ + if (d) + return d->key[attr] != -1; + return false; +} + +/*! + Enables this geometry to contain data of type \a field. Generally it is + not necessary to call this function since it is called by all the append + functions. +*/ +void QGeometryData::enableField(QGL::VertexAttribute field) +{ + if (d && d->key[field] != -1) + return; + create(); + d->modified = true; + Q_ASSERT(field < d->ATTR_CNT); // don't expand that enum too much + d->fields |= (1 << field); + switch (field) + { + case QGL::Position: + d->key[QGL::Position] = 0; + d->size[QGL::Position] = 3; + if (d->reserved > 0) + d->vertices.reserve(d->reserved); + break; + case QGL::Normal: + d->key[QGL::Normal] = 1; + d->size[QGL::Normal] = 3; + if (d->reserved > 0) + d->normals.reserve(d->reserved); + break; + case QGL::Color: + d->key[QGL::Color] = 2; + d->size[QGL::Color] = 1; + if (d->reserved > 0) + d->colors.reserve(d->reserved); + break; + case QGL::TextureCoord0: + case QGL::TextureCoord1: + case QGL::TextureCoord2: + d->textures.append(QVector2DArray()); + d->key[field] = d->textures.count() - 1; + d->size[field] = 2; + if (d->reserved > 0) + d->textures[d->key[field]].reserve(d->reserved); + break; + default: + // Custom and User vertex attributes. + d->attributes.append(QCustomDataArray()); + d->key[field] = d->attributes.count() - 1; + d->size[field] = d->attributes.at(d->key[field]).elementSize(); + if (d->reserved > 0) + d->attributes[d->key[field]].reserve(d->reserved); + break; + } +} + +/*! + Return a bit-mask of the supported fields in this geometry. The + QGL::VertexAttribute enum can be recovered from this bit-mask by + \code + quint32 fields = fields(); + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(fields); + \endcode +*/ +quint32 QGeometryData::fields() const +{ + if (d) + return d->fields; + return 0; +} + +/*! + Returns the count of logical vertices stored. This is effectively + the max() of QArray::count() over all of the enabled data types. +*/ +int QGeometryData::count() const +{ + if (d) + return d->count; + return 0; +} + +/*! + Returns the count of data stored in \a field. This will always be at + most count(), but could be less. +*/ +int QGeometryData::count(QGL::VertexAttribute field) const +{ + int result = 0; + if (d && (QGL::fieldMask(field) & d->fields)) + { + if (field < QGL::TextureCoord0) + { + if (field == QGL::Position) + result = d->vertices.count(); + else if (field == QGL::Normal) + result = d->normals.count(); + else + result = d->colors.count(); + } + else if (field < QGL::CustomVertex0) + { + result = d->textures[d->key[field]].count(); + } + else + { + result = d->attributes[d->key[field]].count(); + } + } + return result; +} + +/*! + Returns true if this geometry is identical to the \a other; and false otherwise. +*/ +bool QGeometryData::operator==(const QGeometryData &other) const +{ + bool isEqual = false; + if (d) + { + if (d == other.d) + { + isEqual = true; + } + else + { + if (other.d && d->fields == other.d->fields && d->count == other.d->count) + { + const quint32 mask = 0x01; + quint32 fields = d->fields; + isEqual = true; + for (int field = 0; fields && isEqual; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + isEqual = (d->vertices == other.d->vertices); + else if (attr == QGL::Normal) + isEqual = (d->normals == other.d->normals); + else // colors + isEqual = (d->colors == other.d->colors); + } + else if (attr < QGL::CustomVertex0) + { + isEqual = (d->textures.at(d->key[attr]) == other.d->textures.at(d->key[attr])); + } + else + { + QArray<float> me = d->attributes.at(d->key[attr]).toFloatArray(); + QArray<float> him = other.d->attributes.at(d->key[attr]).toFloatArray(); + isEqual = (me == him); + } + } + } + } + } + } + else + { + isEqual = other.isNull(); + } + return isEqual; +} + +/*! + Returns true if this geometry is empty - that is it contains no vertices + or other data - and returns false otherwise. If an existing geometry has + been made empty by a call to clear() then this will be true (but isNull() + will be false). + + \sa isNull() +*/ +bool QGeometryData::isEmpty() const +{ + bool empty = true; + if (d) + empty = d->count == 0; + return empty; +} + +/*! + Returns true if this geometry is uninitialized - that is it contains no + internal data structures. A newly constructed QGeometryData object is + null until some data is added or changed. + + \sa isEmpty() +*/ +bool QGeometryData::isNull() const +{ + return d == NULL; +} + +/*! + Returns the number of index values stored in this geometry data. + + This value is exactly the same as indices().size() (but does not + incur the copy). +*/ +int QGeometryData::indexCount() const +{ + if (d) + return d->indices.size(); + return 0; +} + +void QGeometryData::create() +{ + if (!d) // lazy creation of data block + { + d = new QGeometryDataPrivate; + d->ref.ref(); + } +} + +/*! + Force this geometry to ensure it has its own unshared internal data + block, making a copy in the case that it is currently shared. +*/ +void QGeometryData::detach() +{ + create(); + if (d->ref > 1) // being shared, must detach + { + QGeometryDataPrivate *temp = d->clone(); + d->ref.deref(); + d = temp; + d->ref.ref(); + } +} + +/*! + \fn quint64 QGeometryData::id() const + Return an opaque value that can be used to identify which data block is + being used by this QGeometryData instance. See the class documentation + relating to explicit sharing. +*/ + +#ifndef QT_NO_DEBUG +void QGeometryData::check() const +{ + if (!d) + return; + const quint32 mask = 0x01; + quint32 fields = d->fields; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + { + if (d->vertices.count() < d->count) + qWarning("QGeometryData - expected %d vertices, only %d found!", + d->count, d->vertices.count()); + } + else if (attr == QGL::Normal) + { + if (d->normals.count() < d->count) + qWarning("QGeometryData - expected %d normals, only %d found!", + d->count, d->normals.count()); + } + else + { + if (d->colors.count() < d->count) + qWarning("QGeometryData - expected %d colors, only %d found!", + d->count, d->colors.count()); + } + } + else if (attr < QGL::CustomVertex0) + { + if (d->textures.at(d->key[field]).count() < d->count) + qWarning("QGeometryData - expected %d texture coordinates for" + "QGL::TextureCoord%d, only %d found!", + d->count, field - QGL::TextureCoord0, d->textures.at(d->key[field]).count()); + } + else + { + if (d->attributes.at(d->key[field]).count() < d->count) + qWarning("QGeometryData - expected %d attributes for" + "QGL::CustomVertex%d, only %d found!", + d->count, field - QGL::CustomVertex0, d->attributes.at(d->key[field]).count()); + } + } + } +} +#endif + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QGeometryData &vertices) +{ + dbg << "QGeometryData" << &vertices << " size:" << vertices.count() +#ifndef QT_NO_DEBUG + << "data block id:" << vertices.id() +#endif + ; + quint32 fields = vertices.fields(); + const quint32 mask = 0x01; + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + { + dbg << " vertices:" << vertices.count(attr); + dbg << vertices.vertices(); + } + else if (attr == QGL::Normal) + { + dbg << " normals:" << vertices.count(attr); + dbg << vertices.normals(); + } + else + { + dbg << " colors:" << vertices.count(attr); + dbg << vertices.colors(); + } + } + else if (attr < QGL::CustomVertex0) + { + dbg << " textures:" << (attr - QGL::TextureCoord0) << vertices.count(attr); + dbg << vertices.texCoords(attr); + } + else + { + dbg << " custom:" << (attr - QGL::CustomVertex0) << vertices.count(attr); + dbg << vertices.texCoords(attr); + } + } + } + if (vertices.indexCount() > 0) + { + dbg << " indices:" << vertices.indices(); + } + return dbg; +} +#endif diff --git a/src/threed/geometry/qgeometrydata.h b/src/threed/geometry/qgeometrydata.h new file mode 100644 index 000000000..051915e32 --- /dev/null +++ b/src/threed/geometry/qgeometrydata.h @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGEOMETRYDATA_H +#define QGEOMETRYDATA_H + +#include "qcolor4ub.h" +#include "qglnamespace.h" +#include "qglindexbuffer.h" +#include "qglvertexbundle.h" +#include "qglattributevalue.h" +#include "qcustomdataarray.h" +#include "qbox3d.h" +#include "qarray.h" +#include "qvector2darray.h" +#include "qvector3darray.h" + +QT_BEGIN_NAMESPACE + +class QGeometryDataPrivate; +class QLogicalVertex; +class QGLPainter; + +namespace QGL +{ + inline quint32 fieldMask(QGL::VertexAttribute f) { return (quint32)0x01 << f; } + +#if defined(QT_OPENGL_ES) + typedef QArray<ushort> IndexArray; +#else + typedef QArray<uint> IndexArray; +#endif +}; + +class Q_QT3D_EXPORT QGeometryData +{ +public: + QGeometryData(); + QGeometryData(const QGeometryData &); + QGeometryData(quint32 fields); + ~QGeometryData(); + + QGeometryData &operator=(const QGeometryData &); + + void appendGeometry(const QGeometryData &data); + int appendVertex(const QLogicalVertex &v); + void normalizeNormals(); + QBox3D boundingBox() const; + QVector3D center() const; + + QGeometryData reversed() const; + QGeometryData translated(const QVector3D &) const; + void generateTextureCoordinates(Qt::Orientation orientation = Qt::Horizontal, + QGL::VertexAttribute attribute = QGL::TextureCoord0); + QGeometryData interleavedWith(const QGeometryData &other) const; + void interleaveWith(const QGeometryData &other); + void clear(); + void clear(QGL::VertexAttribute); + void reserve(int amount); + void draw(QGLPainter *painter, int start, int count, GLenum mode = QGL::Triangles, qreal drawWidth=1.0); + bool upload(); + enum BufferStrategyFlags + { + InvalidStrategy = 0x00, + KeepClientData = 0x01, + BufferIfPossible = 0x02, + }; + Q_DECLARE_FLAGS(BufferStrategy, BufferStrategyFlags) + void setBufferStrategy(BufferStrategy strategy); + BufferStrategy bufferStrategy() const; + QGLVertexBundle vertexBundle() const; + QGLIndexBuffer indexBuffer() const; + + void appendIndex(int index); + void appendIndices(int index1, int index2, int index3); + void appendIndices(const QGL::IndexArray &indices); + QGL::IndexArray indices() const; + + void appendVertex(const QVector3D &v0); + void appendVertex(const QVector3D &v0, const QVector3D &v1); + void appendVertex(const QVector3D &v0, const QVector3D &v1, const QVector3D &v2); + void appendVertex(const QVector3D &v0, const QVector3D &v1, const QVector3D &v2, const QVector3D &v3); + + void appendAttribute(float a, QGL::VertexAttribute field = QGL::CustomVertex0); + void appendAttribute(float a, float b, QGL::VertexAttribute field = QGL::CustomVertex0); + void appendAttribute(float a, float b, float c, QGL::VertexAttribute field = QGL::CustomVertex0); + void appendAttribute(float a, float b, float c, float d, QGL::VertexAttribute field = QGL::CustomVertex0); + void appendAttribute(const QVector2D &a, QGL::VertexAttribute field = QGL::CustomVertex0); + void appendAttribute(const QVector3D &a, QGL::VertexAttribute field = QGL::CustomVertex0); + void appendAttribute(const QVariant &a, QGL::VertexAttribute field = QGL::CustomVertex0); + + void appendNormal(const QVector3D &n0); + void appendNormal(const QVector3D &n0, const QVector3D &n1); + void appendNormal(const QVector3D &n0, const QVector3D &n1, const QVector3D &n2); + void appendNormal(const QVector3D &n0, const QVector3D &n1, const QVector3D &n2, const QVector3D &n3); + + void appendTexCoord(const QVector2D &t0, QGL::VertexAttribute field = QGL::TextureCoord0); + void appendTexCoord(const QVector2D &t0, const QVector2D &t1, QGL::VertexAttribute field = QGL::TextureCoord0); + void appendTexCoord(const QVector2D &t0, const QVector2D &t1, const QVector2D &t2, QGL::VertexAttribute field = QGL::TextureCoord0); + void appendTexCoord(const QVector2D &t0, const QVector2D &t1, const QVector2D &t2, const QVector2D &t3, QGL::VertexAttribute field = QGL::TextureCoord0); + + void appendColor(const QColor4ub &c0); + void appendColor(const QColor4ub &c0, const QColor4ub &c1); + void appendColor(const QColor4ub &c0, const QColor4ub &c1, const QColor4ub &c2); + void appendColor(const QColor4ub &c0, const QColor4ub &c1, const QColor4ub &c2, const QColor4ub &c3); + + void appendVertexArray(const QVector3DArray &ary); + void appendAttributeArray(const QCustomDataArray &ary, QGL::VertexAttribute field = QGL::CustomVertex0); + void appendNormalArray(const QVector3DArray &ary); + void appendTexCoordArray(const QVector2DArray &ary, QGL::VertexAttribute field = QGL::TextureCoord0); + void appendColorArray(const QArray<QColor4ub> &ary); + + QLogicalVertex logicalVertexAt(int i) const; + + QVector3DArray vertices() const; + QVector3D &vertex(int i); + const QVector3D &vertexAt(int i) const; + + QVector3DArray normals() const; + QVector3D &normal(int i); + const QVector3D &normalAt(int i) const; + + QArray<QColor4ub> colors() const; + QColor4ub &color(int i); + const QColor4ub &colorAt(int i) const; + + QVector2DArray texCoords(QGL::VertexAttribute field = QGL::TextureCoord0) const; + QVector2D &texCoord(int i, QGL::VertexAttribute field = QGL::TextureCoord0); + const QVector2D &texCoordAt(int i, QGL::VertexAttribute field = QGL::TextureCoord0) const; + + float &floatAttribute(int i, QGL::VertexAttribute field = QGL::CustomVertex0); + QVector2D &vector2DAttribute(int i, QGL::VertexAttribute field = QGL::CustomVertex0); + QVector3D &vector3DAttribute(int i, QGL::VertexAttribute field = QGL::CustomVertex0); + QCustomDataArray attributes(QGL::VertexAttribute field = QGL::CustomVertex0) const; + float floatAttributeAt(int i, QGL::VertexAttribute field = QGL::CustomVertex0) const; + QVector2D vector2DAttributeAt(int i, QGL::VertexAttribute field = QGL::CustomVertex0) const; + QVector3D vector3DAttributeAt(int i, QGL::VertexAttribute field = QGL::CustomVertex0) const; + + QGLAttributeValue attributeValue(QGL::VertexAttribute field) const; + bool hasField(QGL::VertexAttribute field) const; + void enableField(QGL::VertexAttribute field); + quint32 fields() const; + int count() const; + int count(QGL::VertexAttribute field) const; + int indexCount() const; + bool operator==(const QGeometryData &other) const; + bool isEmpty() const; + bool isNull() const; + void detach(); +#ifndef QT_NO_DEBUG + quint64 id() const { return (quint64)d; } +#endif +protected: + const QVector3DArray *vertexData() const; +private: + void create(); +#ifndef QT_NO_DEBUG + void check() const; +#else + void check() const {} +#endif + friend class QLogicalVertex; + + QGeometryDataPrivate *d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeometryData::BufferStrategy); + +#ifndef QT_NO_DEBUG_STREAM +Q_QT3D_EXPORT QDebug operator<<(QDebug dbg, const QGeometryData &vertices); +#endif + +QT_END_NAMESPACE + +#endif // QGEOMETRYDATA_H diff --git a/src/threed/geometry/qglbezierpatches.cpp b/src/threed/geometry/qglbezierpatches.cpp new file mode 100644 index 000000000..49842c653 --- /dev/null +++ b/src/threed/geometry/qglbezierpatches.cpp @@ -0,0 +1,815 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglbezierpatches.h" +#include "qglbuilder.h" +#include "qray3d.h" +#include "qtriangle3d.h" +#include <QtCore/qnumeric.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QGLBezierPatches + \brief The QGLBezierPatches class represents 3D geometry as a set of Bezier bicubic patches. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + Bezier bicubic patches represent a curved 3D surface by four fixed + control points at indices 0, 3, 12, and 15, together with twelve + additional floating control points that define the surface + curvature. Bezier geometry objects are made up of one or more + such patches to define the surface of an object. + + The application specifies the vertex position data to the + constructor, and can optionally provide an index array. + The class interprets groups of 16 vertices as the control + points for successive patches. + + A mesh defined by QGLBezierPatches is subdivided into flat + triangles for rendering when the \c{<<} operator is used + to add the patches to a QGLBuilder. + + Many curved 3D objects can be defined as being made up of Bezier + bicubic patches, stitched together into a mesh. The most famous + Bezier bicubic object is probably the classic 3D "Utah Teapot", + first rendered in 1975. The QGLTeapot class provides a built-in + implementation of this object for testing purposes. + + If texture co-ordinates are supplied via setTextureCoords(), + then patch texture co-ordinates will be derived from the + specified values as the patches are subdivided. Otherwise, + QGLBezierPatches will generate texture co-ordinates for each + patch based on the default square from (0, 0) to (1, 1). + The first vertex in the patch corresponds to (0, 0), + and the opposite vertex in the patch corresponds to (1, 1). + + \sa QGLBuilder, QGLTeapot +*/ + +class QGLBezierPatchesPrivate +{ +public: + QGLBezierPatchesPrivate() + : subdivisionDepth(4) {} + QGLBezierPatchesPrivate(const QGLBezierPatchesPrivate *other) + : positions(other->positions) + , textureCoords(other->textureCoords) + , subdivisionDepth(other->subdivisionDepth) {} + + void copy(const QGLBezierPatchesPrivate *other) + { + positions = other->positions; + textureCoords = other->textureCoords; + subdivisionDepth = other->subdivisionDepth; + } + + void subdivide(QGLBuilder *list) const; + qreal intersection + (const QRay3D &ray, bool anyIntersection, QVector2D *texCoord, int *patch) const; + + QVector3DArray positions; + QVector2DArray textureCoords; + int subdivisionDepth; +}; + +// Temporary patch data for performing sub-divisions. +class QGLBezierPatch +{ +public: + // Control points for this mesh. + QVector3D points[16]; + + // Triangle mesh indices of the control points at each corner. + int indices[4]; + + QVector3D normal(qreal s, qreal t) const; + void convertToTriangles + (QGeometryData *prim, + qreal xtex, qreal ytex, qreal wtex, qreal htex); + void subDivide(QGLBezierPatch &patch1, QGLBezierPatch &patch2, + QGLBezierPatch &patch3, QGLBezierPatch &patch4); + void createNewCorners(QGLBezierPatch &patch1, QGLBezierPatch &patch2, + QGLBezierPatch &patch3, QGLBezierPatch &patch4, + QGeometryData *prim, + qreal xtex, qreal ytex, qreal wtex, qreal htex); + void recursiveSubDivide + (QGeometryData *prim, + int depth, qreal xtex, qreal ytex, qreal wtex, qreal htex); + qreal intersection + (qreal result, int depth, const QRay3D &ray, bool anyIntersection, + qreal xtex, qreal ytex, qreal wtex, qreal htex, QVector2D *tc); +}; + +static int const cornerOffsets[] = {0, 3, 12, 15}; +static qreal const cornerS[] = {0.0f, 1.0f, 0.0f, 1.0f}; +static qreal const cornerT[] = {0.0f, 0.0f, 1.0f, 1.0f}; + +// Helper functions for calculating the components of the Bernstein +// polynomial and its derivative that make up the surface. +static inline qreal b0(qreal v) +{ + return (1.0f - v) * (1.0f - v) * (1.0f - v); +} +static inline qreal b1(qreal v) +{ + return 3.0f * v * (1.0f - v) * (1.0f - v); +} +static inline qreal b2(qreal v) +{ + return 2.0f * v * v * (1.0f - v); +} +static inline qreal b3(qreal v) +{ + return v * v * v; +} +static inline qreal db0(qreal v) +{ + return -3.0f * (1.0f - v) * (1.0f - v); +} +static inline qreal db1(qreal v) +{ + return -6.0f * v * (1.0f - v) + 3.0f * (1.0f - v) * (1.0f - v); +} +static inline qreal db2(qreal v) +{ + return -3.0f * v * v + 6.0f * v * (1.0f - v); +} +static inline qreal db3(qreal v) +{ + return 3.0f * v * v; +} + +// Compute the normal at a specific point in the patch. +// The s and t values vary between 0 and 1. +QVector3D QGLBezierPatch::normal(qreal s, qreal t) const +{ + qreal a[4]; + qreal b[4]; + qreal tx, ty, tz; + qreal sx, sy, sz; + + // Compute the derivative of the surface in t. + a[0] = b0(s); + a[1] = b1(s); + a[2] = b2(s); + a[3] = b3(s); + b[0] = db0(t); + b[1] = db1(t); + b[2] = db2(t); + b[3] = db3(t); + tx = 0.0f; + ty = 0.0f; + tz = 0.0f; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + tx += a[i] * points[j * 4 + i].x() * b[j]; + ty += a[i] * points[j * 4 + i].y() * b[j]; + tz += a[i] * points[j * 4 + i].z() * b[j]; + } + } + + // Compute the derivative of the surface in s. + a[0] = db0(s); + a[1] = db1(s); + a[2] = db2(s); + a[3] = db3(s); + b[0] = b0(t); + b[1] = b1(t); + b[2] = b2(t); + b[3] = b3(t); + sx = 0.0f; + sy = 0.0f; + sz = 0.0f; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + sx += a[i] * points[j * 4 + i].x() * b[j]; + sy += a[i] * points[j * 4 + i].y() * b[j]; + sz += a[i] * points[j * 4 + i].z() * b[j]; + } + } + + // The normal is the cross-product of the two derivatives, + // normalized to a unit vector. + QVector3D n = QVector3D::normal(QVector3D(sx, sy, sz), QVector3D(tx, ty, tz)); + if (n.isNull()) { + // A zero normal may occur if one of the patch edges is zero-length. + // We correct for this by substituting an overall patch normal that + // we compute from two of the sides that are not zero in length. + QVector3D sides[4]; + QVector3D vectors[2]; + sides[0] = points[3] - points[0]; + sides[1] = points[15] - points[3]; + sides[2] = points[12] - points[15]; + sides[3] = points[0] - points[12]; + int i = 0; + int j = 0; + vectors[0] = QVector3D(1.0f, 0.0f, 0.0f); + vectors[1] = QVector3D(0.0f, 1.0f, 0.0f); + while (i < 2 && j < 4) { + if (sides[j].isNull()) + ++j; + else + vectors[i++] = sides[j++]; + } + n = QVector3D::normal(vectors[0], vectors[1]); + } + return n; +} + +// Convert this patch into flat triangles. +void QGLBezierPatch::convertToTriangles + (QGeometryData *prim, + qreal xtex, qreal ytex, qreal wtex, qreal htex) +{ + // The edges are considered ok if they have a non-zero length. + // Zero-length edges can occur in triangular-shaped patches. + // There is no point generating triangles along such edges. + bool edge1ok = (points[0] != points[3]); + bool edge2ok = (points[0] != points[12]); + bool edge3ok = (points[12] != points[15]); + bool edge4ok = (points[15] != points[3]); + + // Find the mid-point on the patch by averaging the corners. + QVector3D mid = (points[0] + points[3] + points[12] + points[15]) / 4.0f; + + // Allocate a triangle mesh vertex for the mid-point. + int midIndex = prim->count(); + prim->appendVertex(mid); + prim->appendNormal(normal(0.5f, 0.5f)); + prim->appendTexCoord + (QVector2D(xtex + wtex / 2.0f, ytex + htex / 2.0f)); + + // Divide the patch into 4 triangles pointing at the center. + if (edge1ok) + prim->appendIndices(indices[0], indices[1], midIndex); + if (edge2ok) + prim->appendIndices(indices[2], indices[0], midIndex); + if (edge3ok) + prim->appendIndices(indices[3], indices[2], midIndex); + if (edge4ok) + prim->appendIndices(indices[1], indices[3], midIndex); +} + +// Sub-divide a Bezier curve (p1, p2, p3, p4) into two new +// Bezier curves (l1, l2, l3, l4) and (r1, r2, r3, r4). +static void subDivideBezierCurve + (const QVector3D &p1, const QVector3D &p2, + const QVector3D &p3, const QVector3D &p4, + QVector3D &l1, QVector3D &l2, QVector3D &l3, QVector3D &l4, + QVector3D &r1, QVector3D &r2, QVector3D &r3, QVector3D &r4) +{ + l1 = p1; + l2 = (p1 + p2) / 2.0f; + QVector3D h = (p2 + p3) / 2.0f; + l3 = (l2 + h) / 2.0f; + r3 = (p3 + p4) / 2.0f; + r2 = (h + r3) / 2.0f; + l4 = r1 = (l3 + r2) / 2.0f; + r4 = p4; +} + +// Sub-divide this patch into four new patches. The triangle mesh +// is used to allocate vertices for the corners of the new patches. +void QGLBezierPatch::subDivide + (QGLBezierPatch &patch1, QGLBezierPatch &patch2, + QGLBezierPatch &patch3, QGLBezierPatch &patch4) +{ + // Sub-divide the Bezier curves for the control rows to create + // four rows of 8 control points. These define the left and + // right halves of the patch. + QVector3D row1[8]; + QVector3D row2[8]; + QVector3D row3[8]; + QVector3D row4[8]; + subDivideBezierCurve + (points[0], points[1], points[2], points[3], + row1[0], row1[1], row1[2], row1[3], row1[4], row1[5], row1[6], row1[7]); + subDivideBezierCurve + (points[4], points[5], points[6], points[7], + row2[0], row2[1], row2[2], row2[3], row2[4], row2[5], row2[6], row2[7]); + subDivideBezierCurve + (points[8], points[9], points[10], points[11], + row3[0], row3[1], row3[2], row3[3], row3[4], row3[5], row3[6], row3[7]); + subDivideBezierCurve + (points[12], points[13], points[14], points[15], + row4[0], row4[1], row4[2], row4[3], row4[4], row4[5], row4[6], row4[7]); + + // Now sub-divide the 8 columns to create the four new patches. + subDivideBezierCurve + (row1[0], row2[0], row3[0], row4[0], + patch1.points[0], patch1.points[4], patch1.points[8], patch1.points[12], + patch3.points[0], patch3.points[4], patch3.points[8], patch3.points[12]); + subDivideBezierCurve + (row1[1], row2[1], row3[1], row4[1], + patch1.points[1], patch1.points[5], patch1.points[9], patch1.points[13], + patch3.points[1], patch3.points[5], patch3.points[9], patch3.points[13]); + subDivideBezierCurve + (row1[2], row2[2], row3[2], row4[2], + patch1.points[2], patch1.points[6], patch1.points[10], patch1.points[14], + patch3.points[2], patch3.points[6], patch3.points[10], patch3.points[14]); + subDivideBezierCurve + (row1[3], row2[3], row3[3], row4[3], + patch1.points[3], patch1.points[7], patch1.points[11], patch1.points[15], + patch3.points[3], patch3.points[7], patch3.points[11], patch3.points[15]); + subDivideBezierCurve + (row1[4], row2[4], row3[4], row4[4], + patch2.points[0], patch2.points[4], patch2.points[8], patch2.points[12], + patch4.points[0], patch4.points[4], patch4.points[8], patch4.points[12]); + subDivideBezierCurve + (row1[5], row2[5], row3[5], row4[5], + patch2.points[1], patch2.points[5], patch2.points[9], patch2.points[13], + patch4.points[1], patch4.points[5], patch4.points[9], patch4.points[13]); + subDivideBezierCurve + (row1[6], row2[6], row3[6], row4[6], + patch2.points[2], patch2.points[6], patch2.points[10], patch2.points[14], + patch4.points[2], patch4.points[6], patch4.points[10], patch4.points[14]); + subDivideBezierCurve + (row1[7], row2[7], row3[7], row4[7], + patch2.points[3], patch2.points[7], patch2.points[11], patch2.points[15], + patch4.points[3], patch4.points[7], patch4.points[11], patch4.points[15]); +} + +void QGLBezierPatch::createNewCorners + (QGLBezierPatch &patch1, QGLBezierPatch &patch2, + QGLBezierPatch &patch3, QGLBezierPatch &patch4, + QGeometryData *prim, + qreal xtex, qreal ytex, qreal wtex, qreal htex) +{ + // Add vertices for the new patch corners we have created. + qreal hwtex = wtex / 2.0f; + qreal hhtex = htex / 2.0f; + int topPointIndex = prim->count(); + int leftPointIndex = topPointIndex + 1; + int midPointIndex = topPointIndex + 2; + int rightPointIndex = topPointIndex + 3; + int bottomPointIndex = topPointIndex + 4; + + prim->appendVertex(patch1.points[3]); + prim->appendNormal(normal(0.5f, 0.0f)); + prim->appendTexCoord(QVector2D(xtex + hwtex, ytex)); + + prim->appendVertex(patch1.points[12]); + prim->appendNormal(normal(0.0f, 0.5f)); + prim->appendTexCoord(QVector2D(xtex, ytex + hhtex)); + + prim->appendVertex(patch1.points[15]); + prim->appendNormal(normal(0.5f, 0.5f)); + prim->appendTexCoord(QVector2D(xtex + hwtex, ytex + hhtex)); + + prim->appendVertex(patch2.points[15]); + prim->appendNormal(normal(1.0f, 0.5f)); + prim->appendTexCoord(QVector2D(xtex + wtex, ytex + hhtex)); + + prim->appendVertex(patch3.points[15]); + prim->appendNormal(normal(0.5f, 1.0f)); + prim->appendTexCoord(QVector2D(xtex + hwtex, ytex + htex)); + + // Copy the indices for the corners of the new patches. + patch1.indices[0] = indices[0]; + patch1.indices[1] = topPointIndex; + patch1.indices[2] = leftPointIndex; + patch1.indices[3] = midPointIndex; + patch2.indices[0] = topPointIndex; + patch2.indices[1] = indices[1]; + patch2.indices[2] = midPointIndex; + patch2.indices[3] = rightPointIndex; + patch3.indices[0] = leftPointIndex; + patch3.indices[1] = midPointIndex; + patch3.indices[2] = indices[2]; + patch3.indices[3] = bottomPointIndex; + patch4.indices[0] = midPointIndex; + patch4.indices[1] = rightPointIndex; + patch4.indices[2] = bottomPointIndex; + patch4.indices[3] = indices[3]; +} + +// Recursively sub-divide a patch into triangles. +void QGLBezierPatch::recursiveSubDivide + (QGeometryData *prim, + int depth, qreal xtex, qreal ytex, qreal wtex, qreal htex) +{ + if (depth <= 1) { + convertToTriangles(prim, xtex, ytex, wtex, htex); + } else { + QGLBezierPatch patch1, patch2, patch3, patch4; + subDivide(patch1, patch2, patch3, patch4); + createNewCorners(patch1, patch2, patch3, patch4, prim, xtex, ytex, wtex, htex); + --depth; + qreal hwtex = wtex / 2.0f; + qreal hhtex = htex / 2.0f; + patch1.recursiveSubDivide(prim, depth, xtex, ytex, hwtex, hhtex); + patch2.recursiveSubDivide(prim, depth, xtex + hwtex, ytex, hwtex, hhtex); + patch3.recursiveSubDivide(prim, depth, xtex, ytex + hhtex, hwtex, hhtex); + patch4.recursiveSubDivide(prim, depth, xtex + hwtex, ytex + hhtex, hwtex, hhtex); + } +} + +void QGLBezierPatchesPrivate::subdivide(QGLBuilder *list) const +{ + QGeometryData prim; + int count = positions.size(); + for (int posn = 0; (posn + 15) < count; posn += 16) { + // Construct a QGLBezierPatch object from the next high-level patch. + QGLBezierPatch patch; + int vertex; + for (int vertex = 0; vertex < 16; ++vertex) + patch.points[vertex] = positions[posn + vertex]; + QVector2D tex1, tex2; + if (!textureCoords.isEmpty()) { + tex1 = textureCoords[(posn / 16) * 2]; + tex2 = textureCoords[(posn / 16) * 2 + 1]; + } else { + tex1 = QVector2D(0.0f, 0.0f); + tex2 = QVector2D(1.0f, 1.0f); + } + qreal xtex = tex1.x(); + qreal ytex = tex1.y(); + qreal wtex = tex2.x() - xtex; + qreal htex = tex2.y() - ytex; + for (int corner = 0; corner < 4; ++corner) { + vertex = posn + cornerOffsets[corner]; + QVector3D n = patch.normal(cornerS[corner], cornerT[corner]); + patch.indices[corner] = prim.count(); + prim.appendVertex(patch.points[cornerOffsets[corner]]); + prim.appendNormal(n); + prim.appendTexCoord + (QVector2D(xtex + wtex * cornerS[corner], + ytex + htex * cornerT[corner])); + } + + // Subdivide the patch and generate the final triangles. + patch.recursiveSubDivide(&prim, subdivisionDepth, + xtex, ytex, wtex, htex); + } + list->addTriangles(prim); +} + +static inline qreal combineResults(qreal result, qreal t) +{ + if (qIsNaN(result)) + return t; + if (t >= 0.0f) + return result < 0.0f ? t : qMin(result, t); + else + return result >= 0.0f ? result : qMax(result, t); +} + +qreal QGLBezierPatch::intersection + (qreal result, int depth, const QRay3D& ray, bool anyIntersection, + qreal xtex, qreal ytex, qreal wtex, qreal htex, QVector2D *tc) +{ + // Check the convex hull of the patch for an intersection. + // If no intersection with the convex hull, then there is + // no point subdividing this patch further. + QBox3D box; + for (int point = 0; point < 16; ++point) + box.unite(points[point]); + if (!box.intersects(ray)) + return result; + + // Are we at the lowest point of subdivision yet? + if (depth <= 1) { + // Divide the patch into two triangles and intersect with those. + QTriangle3D triangle1(points[0], points[3], points[12]); + qreal t = triangle1.intersection(ray); + if (!qIsNaN(t)) { + result = combineResults(result, t); + if (result == t) { + QVector2D uv = triangle1.uv(ray.point(t)); + QVector2D tp(xtex, ytex); + QVector2D tq(xtex + wtex, ytex); + QVector2D tr(xtex, ytex + htex); + *tc = uv.x() * tp + uv.y() * tq + (1 - uv.x() - uv.y()) * tr; + } + } else { + QTriangle3D triangle2(points[3], points[15], points[12]); + qreal t = triangle2.intersection(ray); + if (!qIsNaN(t)) { + result = combineResults(result, t); + if (result == t) { + QVector2D uv = triangle2.uv(ray.point(t)); + QVector2D tp(xtex + wtex, ytex); + QVector2D tq(xtex + wtex, ytex + htex); + QVector2D tr(xtex, ytex + htex); + *tc = uv.x() * tp + uv.y() * tq + (1 - uv.x() - uv.y()) * tr; + } + } + } + } else { + // Subdivide the patch to find the point of intersection. + QGLBezierPatch patch1, patch2, patch3, patch4; + subDivide(patch1, patch2, patch3, patch4); + --depth; + qreal hwtex = wtex / 2.0f; + qreal hhtex = htex / 2.0f; + result = patch1.intersection + (result, depth, ray, anyIntersection, + xtex, ytex, hwtex, hhtex, tc); + if (anyIntersection && !qIsNaN(result)) + return result; + result = patch2.intersection + (result, depth, ray, anyIntersection, + xtex + hwtex, ytex, hwtex, hhtex, tc); + if (anyIntersection && !qIsNaN(result)) + return result; + result = patch3.intersection + (result, depth, ray, anyIntersection, + xtex, ytex + hhtex, hwtex, hhtex, tc); + if (anyIntersection && !qIsNaN(result)) + return result; + result = patch4.intersection + (result, depth, ray, anyIntersection, + xtex + hwtex, ytex + hhtex, hwtex, hhtex, tc); + } + return result; +} + +qreal QGLBezierPatchesPrivate::intersection + (const QRay3D &ray, bool anyIntersection, QVector2D *texCoord, int *bestPatch) const +{ + int count = positions.size(); + qreal result = qSNaN(); + QVector2D tc; + if (bestPatch) + *bestPatch = -1; + for (int posn = 0; (posn + 15) < count; posn += 16) { + QGLBezierPatch patch; + for (int vertex = 0; vertex < 16; ++vertex) + patch.points[vertex] = positions[posn + vertex]; + QVector2D tex1, tex2; + if (!textureCoords.isEmpty()) { + tex1 = textureCoords[(posn / 16) * 2]; + tex2 = textureCoords[(posn / 16) * 2 + 1]; + } else { + tex1 = QVector2D(0.0f, 0.0f); + tex2 = QVector2D(1.0f, 1.0f); + } + qreal xtex = tex1.x(); + qreal ytex = tex1.y(); + qreal wtex = tex2.x() - xtex; + qreal htex = tex2.y() - ytex; + qreal prev = result; + result = patch.intersection + (result, subdivisionDepth, ray, anyIntersection, + xtex, ytex, wtex, htex, &tc); + if (bestPatch && result != prev) + *bestPatch = posn / 16; + if (anyIntersection && !qIsNaN(result)) + break; + } + if (texCoord && !qIsNaN(result)) + *texCoord = tc; + return result; +} + +/*! + Constructs an empty Bezier patch list. + + \sa setPositions() +*/ +QGLBezierPatches::QGLBezierPatches() + : d_ptr(new QGLBezierPatchesPrivate()) +{ +} + +/*! + Constructs a copy of \a other. + + \sa operator=() +*/ +QGLBezierPatches::QGLBezierPatches(const QGLBezierPatches &other) + : d_ptr(new QGLBezierPatchesPrivate(other.d_ptr.data())) +{ +} + +/*! + Destroys this Bezier patch list. +*/ +QGLBezierPatches::~QGLBezierPatches() +{ +} + +/*! + Assigns \a other to this Bezier patch list. +*/ +QGLBezierPatches &QGLBezierPatches::operator= + (const QGLBezierPatches &other) +{ + if (this != &other) + d_ptr->copy(other.d_ptr.data()); + return *this; +} + +/*! + Returns the positions of the vertices in the Bezier patches. + + \sa setPositions(), textureCoords() +*/ +QVector3DArray QGLBezierPatches::positions() const +{ + Q_D(const QGLBezierPatches); + return d->positions; +} + +/*! + Sets the \a positions of the vertices in the Bezier patches. + + \sa positions(), setTextureCoords() +*/ +void QGLBezierPatches::setPositions(const QVector3DArray &positions) +{ + Q_D(QGLBezierPatches); + d->positions = positions; +} + +/*! + Returns the texture co-ordinates for the Bezier patches. + Each patch consumes two elements from the texture + co-ordinate array, defining the opposite corners. + + The default is an empty array, which indicates that each + patch will generate texture co-ordinates in the range + (0, 0) to (1, 1). + + \sa setTextureCoords(), positions() +*/ +QVector2DArray QGLBezierPatches::textureCoords() const +{ + Q_D(const QGLBezierPatches); + return d->textureCoords; +} + +/*! + Sets the texture co-ordinates for the Bezier patches to + the elements of \a textureCoords. Each patch consumes + two elements from \a textureCoords, defining the opposite + corners. + + If \a textureCoords is empty, then each patch will generate + texture co-ordinates in the range (0, 0) to (1, 1). + + \sa textureCoords(), setPositions() +*/ +void QGLBezierPatches::setTextureCoords(const QVector2DArray &textureCoords) +{ + Q_D(QGLBezierPatches); + d->textureCoords = textureCoords; +} + +/*! + Returns the depth of subdivision to use when converting the + Bezier geometry into triangles. The default value is 4. + + \sa setSubdivisionDepth() +*/ +int QGLBezierPatches::subdivisionDepth() const +{ + Q_D(const QGLBezierPatches); + return d->subdivisionDepth; +} + +/*! + Sets the depth of subdivision to use when converting the + Bezier geometry into triangles to \a value. + + \sa subdivisionDepth() +*/ +void QGLBezierPatches::setSubdivisionDepth(int value) +{ + Q_D(QGLBezierPatches); + d->subdivisionDepth = value; +} + +/*! + Transforms the positions() in this Bezier geometry object + according to \a matrix. + + \sa transformed() +*/ +void QGLBezierPatches::transform(const QMatrix4x4 &matrix) +{ + Q_D(QGLBezierPatches); + d->positions.transform(matrix); +} + +/*! + Returns a new Bezier geometry object that results from transforming + this object's positions() according to \a matrix. + + \sa transform() +*/ +QGLBezierPatches QGLBezierPatches::transformed(const QMatrix4x4 &matrix) const +{ + QGLBezierPatches result(*this); + result.d_ptr->positions.transform(matrix); + return result; +} + +/*! + Returns true if \a ray intersects this Bezier geometry object; + false otherwise. + + \sa intersection() +*/ +bool QGLBezierPatches::intersects(const QRay3D &ray) const +{ + Q_D(const QGLBezierPatches); + return !qIsNaN(d->intersection(ray, true, 0, 0)); +} + +/*! + Returns the t value at which \a ray intersects this Bezier + geometry object, or not-a-number if there is no intersection. + + When the \a ray intersects this object, the return value is a + parametric value that can be passed to QRay3D::point() to determine + the actual intersection point, as shown in the following example: + + \code + qreal t = patches.intersection(ray); + QVector3D pt; + if (qIsNaN(t)) { + qWarning("no intersection occurred"); + else + pt = ray.point(t); + \endcode + + If \a ray intersects the object multiple times, the returned + t will be the smallest t value, corresponding to the first + intersection of the \a ray with the object. The t value may + be negative if the first intersection occurs in the reverse + direction of \a ray. + + The intersection is determined by subdividing the patches into + triangles and intersecting with those triangles. A pruning + algorithm is used to discard patches whose convex hull do not + intersect with \a ray. + + If \a texCoord is not null, then it will return the texture + co-ordinate of the intersection point. + + If \a patch is not null, then it will return the index of the + patch that contains the intersection, or -1 if there is no + intersection. + + \sa intersects() +*/ +qreal QGLBezierPatches::intersection(const QRay3D &ray, QVector2D *texCoord, int *patch) const +{ + Q_D(const QGLBezierPatches); + return d->intersection(ray, false, texCoord, patch); +} + +/*! + \relates QGLBezierPatches + + Subdivides the Bezier patch data in \a patches into triangles + and adds them to the specified display \a list. +*/ +QGLBuilder &operator<<(QGLBuilder &list, const QGLBezierPatches &patches) +{ + patches.d_ptr->subdivide(&list); + return list; +} + +QT_END_NAMESPACE diff --git a/src/threed/geometry/qglbezierpatches.h b/src/threed/geometry/qglbezierpatches.h new file mode 100644 index 000000000..3bab8f5ff --- /dev/null +++ b/src/threed/geometry/qglbezierpatches.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLBEZIERPATCHES_H +#define QGLBEZIERPATCHES_H + +#include "qvector2darray.h" +#include "qvector3darray.h" +#include <QtCore/qscopedpointer.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLBezierPatchesPrivate; +class QGLBuilder; +class QRay3D; + +class Q_QT3D_EXPORT QGLBezierPatches +{ +public: + QGLBezierPatches(); + QGLBezierPatches(const QGLBezierPatches &other); + virtual ~QGLBezierPatches(); + + QGLBezierPatches &operator=(const QGLBezierPatches &other); + + QVector3DArray positions() const; + void setPositions(const QVector3DArray &positions); + + QVector2DArray textureCoords() const; + void setTextureCoords(const QVector2DArray &textureCoords); + + int subdivisionDepth() const; + void setSubdivisionDepth(int value); + + void transform(const QMatrix4x4 &matrix); + QGLBezierPatches transformed(const QMatrix4x4 &matrix) const; + + bool intersects(const QRay3D &ray) const; + qreal intersection(const QRay3D &ray, QVector2D *texCoord = 0, int *patch = 0) const; + +private: + QScopedPointer<QGLBezierPatchesPrivate> d_ptr; + + Q_DECLARE_PRIVATE(QGLBezierPatches) + + friend Q_QT3D_EXPORT QGLBuilder &operator<<(QGLBuilder &list, const QGLBezierPatches &patches); +}; + +Q_QT3D_EXPORT QGLBuilder &operator<<(QGLBuilder &list, const QGLBezierPatches &patches); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/geometry/qglbuilder.cpp b/src/threed/geometry/qglbuilder.cpp new file mode 100644 index 000000000..d3be13899 --- /dev/null +++ b/src/threed/geometry/qglbuilder.cpp @@ -0,0 +1,1378 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglbuilder.h" +#include "qglbuilder_p.h" +#include "qglsection_p.h" +#include "qglmaterialcollection.h" +#include "qglpainter.h" +#include "qgeometrydata.h" +#include "qvector_utils_p.h" + +#include <QtGui/qvector2d.h> + +#include <QtCore/qdebug.h> + +/*! + \class QGLBuilder + \brief The QGLBuilder class constructs geometry for efficient display. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + \tableofcontents + + Use a QGLBuilder to build up vertex, index, texture and other data + during application initialization. The finalizedSceneNode() function + returns an optimized scene which can be efficiently and flexibly + displayed during frames of rendering. It is suited to writing loaders + for 3D models, and for programatically creating geometry. + + \section1 Geometry Building + + QGLBuilder makes the job of getting triangles on the GPU simple. It + calculates indices and normals for you, then uploads the data. While + it has addQuads() and other functions to deal with quads, all data is + represented as triangles for portability. + + The simplest way to use QGLBuilder is to send a set of geometry + values to it using QGeometryData in the constructor: + + \code + MyView::MyView() : QGLView() + { + // in the constructor construct a builder on the stack + QGLBuilder builder; + QGeometryData triangle; + QVector3D a(2, 2, 0); + QVector3D b(-2, 2, 0); + QVector3D c(0, -2, 0); + triangle.appendVertex(a, b, c); + + // When adding geometry, QGLBuilder automatically creates lighting normals + builder << triangle; + + // obtain the scene from the builder + m_scene = builder.finalizedSceneNode(); + + // apply effects at app initialization time + QGLMaterial *mat = new QGLMaterial; + mat->setDiffuseColor(Qt::red); + m_scene->setMaterial(mat); + } + \endcode + + Then during rendering the scene is used to display the results: + \code + MyView::paintGL(QGLPainter *painter) + { + m_scene->draw(painter); + } + \endcode + + QGLBuilder automatically generates index values and normals + on-the-fly during geometry building. During building, simply send + primitives to the builder as a sequence of vertices, and + vertices that are the same will be referenced by a single index + automatically. + + Primitives will have standard normals generated automatically + based on vertex winding. + + Consider the following code for OpenGL to draw a quad with corner + points A, B, C and D : + + \code + float vertices[12] = + { + -1.0, -1.0, -1.0, // A + 1.0, -1.0, -1.0, // B + 1.0, 1.0, 1.0, // C + -1.0, 1.0, 1.0 // D + }; + float normals[12] = { 0.0f }; + for (int i = 0; i < 12; i += 3) + { + normals[i] = 0.0; + normals[i+1] = -sqrt(2.0); + normals[i+2] = sqrt(2.0); + } + GLuint indices[6] = { + 0, 1, 2, // triangle A-B-C + 0, 2, 3 // triangle A-C-D + }; + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, vertices); + glNormalPointer(3, GL_FLOAT, 0, normals); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, indices); + \endcode + + With QGLBuilder this code becomes: + + \code + float vertices[12] = + { + -1.0, -1.0, -1.0, // A + 1.0, -1.0, -1.0, // B + 1.0, 1.0, 1.0, // C + -1.0, 1.0, 1.0 // D + }; + QGLBuilder quad; + QGeometryData data; + data.appendVertexArray(QArray<QVector3D>::fromRawData( + reinterpret_cast<const QVector3D*>(vertices), 4)); + quad.addQuads(data); + \endcode + + The data primitive is added to the list, as two triangles, indexed to + removed the redundant double storage of B & C - just the same as the + OpenGL code. + + QGLBuilder will also calculate a normal for the quad and apply it + to the vertices. + + In this trivial example the indices are easily calculated, however + in more complex geometry it is easy to introduce bugs by trying + to manually control indices. Extra work is required to generate, + track and store the index values correctly. + + Bugs such as trying to index two vertices with different data - + one with texture data and one without - into one triangle can + easily result. The picture becomes more difficult when smoothing + groups are introduced - see below. + + Using indices is always preferred since it saves space on the GPU, + and makes the geometry perform faster during application run time. + + \section2 Removing Epsilon Errors + + Where vertices are generated by modelling packages or tools, or + during computation in code, very frequently rounding errors will + result in several vertices being generated that are actually + the same vertex but are separated by tiny amounts. At best these + duplications waste space on the GPU but at worst can introduce + visual artifacts that mar the image displayed. + + Closing paths, generating solids of rotation, or moving model + sections out and back can all introduce these types of epsilon + errors, resulting in "cracks" or artifacts on display. + + QGLBuilder's index generation process uses a fuzzy match that + coalesces all vertex values at a point - even if they are out by + a tiny amount - and references them with a single index. + + \section2 Lighting Normals and Null Triangles + + QGLBuilder functions calculate lighting normals, when building + geometry. This saves the application programmer from having to write + code to calculate them. Normals for each triangle (a, b, c) are + calculated as the QVector3D::normal(a, b, c). + + If lighting normals are explicitly supplied when using QGLBuilder, + then this calculation is not done. This may save on build time. + + As an optimization, QGLBuilder skips null triangles, that is ones + with zero area, where it can. Such triangles generate no fragments on + the GPU, and thus do not display but nonetheless can take up space + and processing power. + + Null triangles can easily occur when calculating vertices results + in two vertices coinciding, or three vertices lying on the same line. + + This skipping is done using the lighting normals cross-product. If the + cross-product is a null vector then the triangle is null. + + When lighting normals are specified explicitly the skipping + optimization is suppressed, so if for some reason null triangles are + required to be retained, then specify normals for each logical vertex. + + See the documentation below of the individual addTriangle() and other + functions for more details. + + \section2 Raw Triangle Mode + + Where generation of indices and normals is not needed - for example if + porting an existing application, it is possible to do a raw import of + triangle data, without using any of QGLBuilder's processing. + + To do this ensure that indices are placed in the QGeometryData passed to + the addTriangles() function, and this will trigger \bold{raw triangle} mode. + + When adding triangles in this way ensure that all appropriate values + have been correctly set, and that the normals, indices and other data + are correctly calculated, since no checking is done. + + When writing new applications, simply leave construction of normals and + indices to the QGLBuilder + + \section1 Rendering and QGLSceneNode items. + + QGLSceneNodes are used to manage application of local transformations, + materials and effects. + + QGLBuilder generates a root level QGLSceneNode, which can be accessed + with the sceneNode() function. Under this a new node is created for + each section of geometry, and also by using pushNode() and popNode(). + + To organize geometry for painting with different materials and effects + call the newNode() function: + + \code + QGLSceneNode *box = builder.newNode(); + box->setMaterial(wood); + \endcode + + Many nodes may be created this way, but they will be optimized into + a small number of buffers under the one scene when the + finalizedSceneNode() function is called. + + \image soup.png + + Here the front can is a set of built geometry and the other two are + scene nodes that reference it, without copying any geometry. + + \snippet builder/builder.cpp 0 + + QGLSceneNodes can be used after the builder is created to cheaply + copy and redisplay the whole scene. Or to reference parts of the geometry + use the functions newNode() or pushNode() and popNode() to manage + QGLSceneNode generation while building geometry. + + To draw the resulting built geometry simply call the draw method of the + build geometry. + + \snippet builder/builder.cpp 1 + + Call the \l{QGLSceneNode::palette()}{palette()} function on the sceneNode() + to get the QGLMaterialCollection for the node, and place textures + and materials into it. + + Built geometry will typically share the one palette. Either create a + palette, and pass it to the \l{QGLBuilder::QGLBuilder()}{constructor}; + or pass no arguments to the constructor and the QGLBuilder + will create a palette: + + \snippet builder/builder.cpp 2 + + These may then be applied as needed throughout the building of the + geometry using the integer reference, \c{canMat} in the above code. + + See the QGLSceneNode documentation for more. + + \section1 Using Sections + + During initialization of the QGLBuilder, while accumulating + geometry, the geometry data in a QGLBuilder is placed into + sections - there must be at least one section. + + Call the newSection() function to create a new section: + + \snippet builder/builder.cpp 3 + + Here separate sections for the rounded outside cylinder and flat top and + bottom of the soup can model makes for the appearance of a sharp edge + between them. If the sides and top and bottom were in the same section + QGLBuilder would attempt to average the normals around the edge resulting + in an unrealistic effect. + + In 3D applications this concept is referred to as + \l{http://www.google.com/search?smoothing+groups}{smoothing groups}. Within + a section (smoothing group) all normals are averaged making it appear + as one smoothly shaded surface. + + The can has 3 smoothing groups - bottom, top and sides. + + This mesh of a Q is a faceted model - it has 0 smoothing groups: + + \image faceted-q.png + + To create geometry with a faceted appearance call newSection() with + an argument of QGL::Faceted thus \c{newSection(QGL::Faceted)}. + + Faceted geometry is suitable for small models, where hard edges are + desired between every face - a dice, gem or geometric solid for example. + + If no section has been created when geometry is added a new section is + created automatically. This section will have its smoothing set + to QGL::Smooth. + + To create a faceted appearance rather than accepting the automatically + created section the << operator can also be used: + + \code + QGLBuilder builder; + QGeometryData triangles; + triangles.appendVertices(a, b, c); + builder << QGL::Faceted << triangles; + \endcode + + \section2 Geometry Data in a Section + + Management of normals and vertices for smoothing, and other data is + handled automatically by the QGLBuilder instance. + + Within a section, incoming geometry data will be coalesced and + indices created to reference the fewest possible copies of the vertex + data. For example, in smooth geometry all copies of a vertex are + coalesced into one, and referenced by indices. + + One of the few exceptions to this is the case where texture data forms + a \i seam and a copy of a vertex must be created to carry the two + texture coordinates either side of the seam. + + \image texture-seam.png + + Coalescing has the effect of packing geometry data into the + smallest space possible thus improving cache coherence and performance. + + Again all this is managed automatically by QGLBuilder and all + that is required is to create smooth or faceted sections, and add + geometry to them. + + Each QGLSection references a contiguous range of vertices in a + QGLBuilder. + + \section1 Finalizing and Retrieving the Scene + + Once the geometry has been accumulated in the QGLBuilder instance, the + finalizedSceneNode() method must be called to retrieve the optimized + scene. This function serves to normalize the geometry and optimize + it for display. + + While it may be convenient to get pointers to sub nodes in the scene + during construction, it is important to retrieve the root of the scene + so that the memory consumed by the scene can be recovered. The builder + will create a QGLMaterialCollection; and there may be geometry, materials + and other resources: these are all parented onto the root scene node. + These can easily be recovered by deleting the root scene node: + + \code + MyView::MyView() : QGLView() + { + // in the constructor construct a builder on the stack + QGLBuilder builder; + + // add geometry as shown above + builder << triangles; + + // obtain the scene from the builder & take ownership + m_scene = builder.finalizedSceneNode(); + } + + MyView::~MyView() + { + // recover all scene resources + delete m_scene; + } + \endcode + + Alternatively set the scene's parent to ensure resource recovery + \c{m_scene->setParent(this)}. + + +*/ + +QGLBuilderPrivate::QGLBuilderPrivate(QGLBuilder *parent) + : currentSection(0) + , currentNode(0) + , rootNode(0) + , defThreshold(5) + , q(parent) +{ +} + +QGLBuilderPrivate::~QGLBuilderPrivate() +{ + qDeleteAll(sections); + if (rootNode) + { + qWarning("Destroying QGLBuilder but finalizedSceneNode() not called"); + delete rootNode; + } +} + +/*! + Construct a new QGLBuilder using \a materials for the palette. If the + \a materials argument is null, then a new palette is created. +*/ +QGLBuilder::QGLBuilder(QGLMaterialCollection *materials) + : dptr(new QGLBuilderPrivate(this)) +{ + dptr->rootNode = new QGLSceneNode; + if (!materials) + materials = new QGLMaterialCollection(dptr->rootNode); + dptr->rootNode->setPalette(materials); +} + +/*! + Destroys this QGLBuilder recovering any resources. +*/ +QGLBuilder::~QGLBuilder() +{ + delete dptr; +} + +/*! + Helper function to calculate the normal for and set it on vertices + in \a i, \a j and \a k in triangle data \a p. If the triangle in + data \a p is a null triangle (area == 0) then the function returns + false, otherwise it returns true. +*/ +static inline void setNormals(int i, int j, int k, QGeometryData &p, const QVector3D &n) +{ + p.normal(i) = n; + p.normal(j) = n; + p.normal(k) = n; +} + +static bool qCalculateNormal(int i, int j, int k, QGeometryData &p, QVector3D *vec = 0) +{ + QVector3D norm; + QVector3D *n = &norm; + if (vec) + n = vec; + bool nullTriangle = false; + *n = QVector3D::crossProduct(p.vertexAt(j) - p.vertexAt(i), + p.vertexAt(k) - p.vertexAt(j)); + if (qFskIsNull(n->x())) + n->setX(0.0f); + if (qFskIsNull(n->y())) + n->setY(0.0f); + if (qFskIsNull(n->z())) + n->setZ(0.0f); + if (n->isNull()) + { + nullTriangle = true; + } + else + { + setNormals(i, j, k, p, *n); + } + return nullTriangle; +} + +/*! + \internal + Helper function to actually add the vertices to geometry. +*/ +void QGLBuilderPrivate::addTriangle(int i, int j, int k, + const QGeometryData &p, int &count) +{ + if (currentSection == 0) + q->newSection(); + QLogicalVertex a(p, i); + QLogicalVertex b(p, j); + QLogicalVertex c(p, k); + currentSection->append(a, b, c); + count += 3; +} + +/*! + Add \a triangles - a series of one or more triangles - to this builder. + + The data is broken into groups of 3 vertices, each processed as a triangle. + + If \a triangles has less than 3 vertices this function exits without + doing anything. Any vertices at the end of the list under a multiple + of 3 are ignored. + + If no normals are supplied in \a triangles, a normal is calculated; as + the cross-product \c{(b - a) x (c - a)}, for each group of 3 + logical vertices \c{a(triangle, i), b(triangle, i+1), c(triangle, i+2)}. + + In the case of a degenerate triangle, where the cross-product is null, + that triangle is skipped. Supplying normals suppresses this behaviour + (and means any degenerate triangles will be added to the geometry). + + \bold{Raw Triangle Mode} + + If \a triangles has indices specified then no processing of any kind is + done and all the geometry is simply dumped in to the builder. + + This \bold{raw triangle} mode is for advanced use, and it is assumed that + the user knows what they are doing, in particular that the indices + supplied are correct, and normals are supplied and correct. + + Normals are not calculated in raw triangle mode, and skipping of null + triangles is likewise not performed. See the section on + \l{raw-triangle-mode}{raw triangle mode} + in the class documentation above. + + \sa addQuads(), operator>>() +*/ +void QGLBuilder::addTriangles(const QGeometryData &triangles) +{ + if (triangles.count() < 3) + return; + if (triangles.indexCount() > 0) + { + // raw triangle mode + if (dptr->currentSection == 0) + newSection(); + dptr->currentSection->appendGeometry(triangles); + dptr->currentSection->appendIndices(triangles.indices()); + dptr->currentNode->setCount(dptr->currentNode->count() + triangles.indexCount()); + } + else + { + QGeometryData t = triangles; + bool calcNormal = !t.hasField(QGL::Normal); + if (calcNormal) + { + QVector3DArray nm(t.count()); + t.appendNormalArray(nm); + } + bool skip = false; + int k = 0; + for (int i = 0; i < t.count() - 2; i += 3) + { + if (calcNormal) + skip = qCalculateNormal(i, i+1, i+2, t); + if (!skip) + dptr->addTriangle(i, i+1, i+2, t, k); + } + dptr->currentNode->setCount(dptr->currentNode->count() + k); + } +} + +/*! + Add \a quads - a series of one or more quads - to this builder. + + If \a quads has less than four vertices this function exits without + doing anything. + + One normal per quad is calculated if \a quads does not have normals. + For this reason quads should have all four vertices in the same plane. + If the vertices do not lie in the same plane, use addTriangleStrip() + to add two adjacent triangles instead. + + Since internally \l{geometry-building}{quads are stored as two triangles}, + each quad is actually divided in half into two triangles. + + Degenerate triangles are skipped in the same way as addTriangles(). + + \sa addTriangles(), addTriangleStrip() +*/ +void QGLBuilder::addQuads(const QGeometryData &quads) +{ + if (quads.count() < 4) + return; + QGeometryData q = quads; + bool calcNormal = !q.hasField(QGL::Normal); + if (calcNormal) + { + QVector3DArray nm(q.count()); + q.appendNormalArray(nm); + } + bool skip = false; + int k = 0; + QVector3D norm; + for (int i = 0; i < q.count(); i += 4) + { + if (calcNormal) + skip = qCalculateNormal(i, i+1, i+2, q, &norm); + if (!skip) + dptr->addTriangle(i, i+1, i+2, q, k); + if (skip) + skip = qCalculateNormal(i, i+2, i+3, q, &norm); + if (!skip) + { + if (calcNormal) + setNormals(i, i+2, i+3, q, norm); + dptr->addTriangle(i, i+2, i+3, q, k); + } + } + dptr->currentNode->setCount(dptr->currentNode->count() + k); +} + +/*! + Adds to this section a set of connected triangles defined by \a fan. + + N triangular faces are generated, where \c{N == fan.count() - 2}. Each + face contains the 0th vertex in \a fan, followed by the i'th and i+1'th + vertex - where i takes on the values from 1 to \c{fan.count() - 1}. + + If \a fan has less than three vertices this function exits without + doing anything. + + This function is similar to the OpenGL mode GL_TRIANGLE_FAN. It + generates a number of triangles all sharing one common vertex, which + is the 0'th vertex of the \a fan. + + Normals are calculated as for addTriangle(), given the above ordering. + There is no requirement or assumption that all triangles lie in the + same plane. Degenerate triangles are skipped in the same way as + addTriangles(). + + \sa addTriangulatedFace() +*/ +void QGLBuilder::addTriangleFan(const QGeometryData &fan) +{ + if (fan.count() < 3) + return; + QGeometryData f = fan; + bool calcNormal = !f.hasField(QGL::Normal); + if (calcNormal) + { + QVector3DArray nm(f.count()); + f.appendNormalArray(nm); + } + int k = 0; + bool skip = false; + for (int i = 1; i < f.count() - 1; ++i) + { + if (calcNormal) + skip = qCalculateNormal(0, i, i+1, f); + if (!skip) + dptr->addTriangle(0, i, i+1, f, k); + } + dptr->currentNode->setCount(dptr->currentNode->count() + k); +} + +/*! + Adds to this section a set of connected triangles defined by \a strip. + + N triangular faces are generated, where \c{N == strip.count() - 2}. + The triangles are generated from vertices 0, 1, & 2, then 2, 1 & 3, + then 2, 3 & 4, and so on. In other words every second triangle has + the first and second vertices switched, as a new triangle is generated + from each successive set of three vertices. + + If \a strip has less than three vertices this function exits without + doing anything. + + Normals are calculated as for addTriangle(), given the above ordering. + + This function is very similar to the OpenGL mode GL_TRIANGLE_STRIP. It + generates triangles along a strip whose two sides are the even and odd + vertices. + + \sa addTriangulatedFace() +*/ +void QGLBuilder::addTriangleStrip(const QGeometryData &strip) +{ + if (strip.count() < 3) + return; + QGeometryData s = strip; + bool calcNormal = !s.hasField(QGL::Normal); + if (calcNormal) + { + QVector3DArray nm(s.count()); + s.appendNormalArray(nm); + } + bool skip = false; + int k = 0; + for (int i = 0; i < s.count() - 2; ++i) + { + if (i % 2) + { + if (calcNormal) + skip = qCalculateNormal(i+1, i, i+2, s); + if (!skip) + dptr->addTriangle(i+1, i, i+2, s, k); + } + else + { + if (calcNormal) + skip = qCalculateNormal(i, i+1, i+2, s); + if (!skip) + dptr->addTriangle(i, i+1, i+2, s, k); + } + } + dptr->currentNode->setCount(dptr->currentNode->count() + k); +} + +/*! + Adds to this section a set of quads defined by \a strip. + + If \a strip has less than four vertices this function exits without + doing anything. + + The first quad is formed from the 0'th, 2'nd, 3'rd and 1'st vertices. + The second quad is formed from the 2'nd, 4'th, 5'th and 3'rd vertices, + and so on, as shown in this diagram: + + \image quads.png + + One normal per quad is calculated if \a strip does not have normals. + For this reason quads should have all four vertices in the same plane. + If the vertices do not lie in the same plane, use addTriangles() instead. + + Since internally \l{geometry-building}{quads are stored as two triangles}, + each quad is actually divided in half into two triangles. + + Degenerate triangles are skipped in the same way as addTriangles(). + + \sa addQuads(), addTriangleStrip() +*/ +void QGLBuilder::addQuadStrip(const QGeometryData &strip) +{ + if (strip.count() < 4) + return; + QGeometryData s = strip; + bool calcNormal = !s.hasField(QGL::Normal); + if (calcNormal) + { + QVector3DArray nm(s.count()); + s.appendNormalArray(nm); + } + bool skip = false; + QVector3D norm; + int k = 0; + for (int i = 0; i < s.count() - 3; i += 2) + { + if (calcNormal) + skip = qCalculateNormal(i, i+2, i+3, s, &norm); + if (!skip) + dptr->addTriangle(i, i+2, i+3, s, k); + if (skip) + skip = qCalculateNormal(i, i+3, i+1, s, &norm); + if (!skip) + { + if (calcNormal) + setNormals(i, i+3, i+1, s, norm); + dptr->addTriangle(i, i+3, i+1, s, k); + } + } + dptr->currentNode->setCount(dptr->currentNode->count() + k); +} + +/*! + Adds to this section a polygonal face made of triangular sub-faces, + defined by \a face. The 0'th vertex is used for the center, while + the subsequent vertices form the perimeter of the face, which must + at minimum be a triangle. + + If \a face has less than four vertices this function exits without + doing anything. + + This function provides functionality similar to the OpenGL mode GL_POLYGON, + except it divides the face into sub-faces around a \bold{central point}. + The center and perimeter vertices must lie in the same plane (unlike + triangle fan). If they do not normals will be incorrectly calculated. + + \image triangulated-face.png + + Here the sub-faces are shown divided by green lines. Note how this + function handles some re-entrant (non-convex) polygons, whereas + addTriangleFan will not support such polygons. + + If required, the center point can be calculated using the center() function + of QGeometryData: + + \code + QGeometryData face; + face.appendVertex(perimeter.center()); // perimeter is a QGeometryData + face.appendVertices(perimeter); + builder.addTriangulatedFace(face); + \endcode + + N sub-faces are generated where \c{N == face.count() - 2}. + + Each triangular sub-face consists of the center; followed by the \c{i'th} + and \c{((i + 1) % N)'th} vertex. The last face generated then is + \c{(center, face[N - 1], face[0]}, the closing face. Note that the closing + face is automatically created, unlike addTriangleFan(). + + If no normals are supplied in the vertices of \a face, normals are + calculated as per addTriangle(). One normal is calculated, since a + faces vertices lie in the same plane. + + Degenerate triangles are skipped in the same way as addTriangles(). + + \sa addTriangleFan(), addTriangles() +*/ +void QGLBuilder::addTriangulatedFace(const QGeometryData &face) +{ + if (face.count() < 4) + return; + QGeometryData f; + f.appendGeometry(face); + int cnt = f.count(); + bool calcNormal = !f.hasField(QGL::Normal); + if (calcNormal) + { + QVector3DArray nm(cnt); + f.appendNormalArray(nm); + } + bool skip = false; + QVector3D norm; + int k = 0; + for (int i = 1; i < cnt; ++i) + { + int n = i + 1; + if (n == cnt) + n = 1; + if (calcNormal) + { + skip = qCalculateNormal(0, i, n, f); + if (norm.isNull() && !skip) + { + norm = f.normalAt(0); + for (int i = 0; i < cnt; ++i) + f.normal(i) = norm; + } + } + if (!skip) + dptr->addTriangle(0, i, n, f, k); + } + dptr->currentNode->setCount(dptr->currentNode->count() + k); +} + +/*! + Add a series of quads by 'interleaving' \a top and \a bottom. + + This function behaves like quadStrip(), where the odd-numbered vertices in + the input primitive are from \a top and the even-numbered vertices from + \a bottom. + + It is trivial to do extrusions using this function: + + \code + // create a series of quads for an extruded edge along -Y + addQuadsInterleaved(topEdge, topEdge.translated(QVector3D(0, -1, 0)); + \endcode + + N quad faces are generated where \c{N == min(top.count(), bottom.count() - 1}. + If \a top or \a bottom has less than 2 elements, this functions does + nothing. + + Each face is formed by the \c{i'th} and \c{(i + 1)'th} + vertices of \a bottom, followed by the \c{(i + 1)'th} and \c{i'th} + vertices of \a top. + + If the vertices in \a top and \a bottom are the perimeter vertices of + two polygons then this function can be used to generate quads which form + the sides of a \l{http://en.wikipedia.org/wiki/Prism_(geometry)}{prism} + with the polygons as the prisms top and bottom end-faces. + + \image quad-extrude.png + + In the diagram above, the \a top is shown in orange, and the \a bottom in + dark yellow. The first generated quad, (a, b, c, d) is generated in + the order shown by the blue arrow. + + To create such a extruded prismatic solid, complete with top and bottom cap + polygons, given just the top edge do this: + \code + QGeometryData top = buildTopEdge(); + QGeometryData bottom = top.translated(QVector3D(0, 0, -1)); + builder.addQuadsInterleaved(top, bottom); + builder.addTriangulatedFace(top); + builder.addTriangulatedFace(bottom.reversed()); + \endcode + The \a bottom QGeometryData must be \bold{reversed} so that the correct + winding for an outward facing polygon is obtained. +*/ +void QGLBuilder::addQuadsInterleaved(const QGeometryData &top, + const QGeometryData &bottom) +{ + if (top.count() < 2 || bottom.count() < 2) + return; + QGeometryData zipped = bottom.interleavedWith(top); + bool calcNormal = !zipped.hasField(QGL::Normal); + if (calcNormal) + { + QVector3DArray nm(zipped.count()); + zipped.appendNormalArray(nm); + } + bool skip = false; + QVector3D norm; + int k = 0; + for (int i = 0; i < zipped.count() - 2; i += 2) + { + if (calcNormal) + skip = qCalculateNormal(i, i+2, i+3, zipped, &norm); + if (!skip) + dptr->addTriangle(i, i+2, i+3, zipped, k); + if (skip) + skip = qCalculateNormal(i, i+3, i+1, zipped, &norm); + if (!skip) + { + if (calcNormal) + setNormals(i, i+3, i+1, zipped, norm); + dptr->addTriangle(i, i+3, i+1, zipped, k); + } + } + dptr->currentNode->setCount(dptr->currentNode->count() + k); +} + +/*! + \fn void QGLBuilder::addPane(QSizeF size) + Convenience function to create a quad centered on the origin, + lying in the Z=0 plane, with width (x dimension) and height + (y dimension) specified by \a size. +*/ + +/*! + \fn void QGLBuilder::addPane(qreal size) + Convenience method to add a single quad of dimensions \a size wide by + \a size high in the z = 0 plane, centered on the origin. The quad has + texture coordinates of (0, 0) at the bottom left and (1, 1) at the top + right. The default value for \a size is 1.0, resulting in a quad + from QVector3D(-0.5, -0.5, 0.0) to QVector3D(0.5, 0.5, 0.0). +*/ + +/*! + \internal +*/ +void QGLBuilderPrivate::adjustSectionNodes(QGLSection *sec, + int offset, const QGeometryData &geom) +{ + QList<QGLSceneNode*> children = sec->nodes(); + QList<QGLSceneNode*>::iterator it = children.begin(); + QList<QGLSceneNode*> deleted; + for ( ; it != children.end(); ++it) + adjustNodeTree(*it, offset, geom, deleted); +} + +/*! + \internal + Adjust \a top by incrementing its start by \a offset, and setting its + geometry to \a geom. Find the cumulative total of indexes - + QGLSceneNode::count() - for \a top and all its children. If this total is + equal to zero, then delete that node. +*/ +int QGLBuilderPrivate::adjustNodeTree(QGLSceneNode *top, + int offset, const QGeometryData &geom, + QList<QGLSceneNode*> &deleted) +{ + int totalItems = 0; + if (top && !deleted.contains(top)) + { + top->setStart(top->start() + offset); + top->setGeometry(geom); + totalItems = top->count(); + QList<QGLSceneNode*> children = top->children(); + QList<QGLSceneNode*>::iterator it = children.begin(); + for ( ; it != children.end(); ++it) + { + totalItems += adjustNodeTree(*it, offset, geom, deleted); + } + if (totalItems == 0 && top->objectName().isEmpty()) + { + delete top; + deleted.append(top); + } + } + return totalItems; +} + +/*! + \internal + Returns a count of all the items referenced by this node + and all its children. +*/ +static int recursiveCount(QGLSceneNode *top) +{ + int totalItems = 0; + if (top) + { + totalItems = top->count(); + QList<QGLSceneNode*> children = top->children(); + QList<QGLSceneNode*>::const_iterator it = children.constBegin(); + for ( ; it != children.constEnd(); ++it) + totalItems += recursiveCount(*it); + } + return totalItems; +} + +static int nodeCount(const QList<QGLSceneNode*> &list) +{ + int total = 0; + QList<QGLSceneNode*>::const_iterator it = list.constBegin(); + for ( ; it != list.constEnd(); ++it) + total += recursiveCount(*it); + return total; +} + +static inline void warnIgnore(int secCount, QGLSection *s, int vertCount, int nodeCount, + const char *msg) +{ + qWarning("Ignoring section %d (%p) with %d vertices and" + " %d indexes - %s", secCount, s, vertCount, nodeCount, msg); +} + +/*! + Finish the building of this geometry, optimize it for rendering, and return a + pointer to the detached top-level scene node (root node). + + Since the scene is detached from the builder object, the builder itself + may be deleted or go out of scope while the scene lives on: + + \code + void MyView::MyView() + { + QGLBuilder builder; + // construct geometry + m_thing = builder.finalizedSceneNode(); + } + + void MyView::~MyView() + { + delete m_thing; + } + + void MyView::paintGL() + { + m_thing->draw(painter); + } + \endcode + + The root node will have a child node for each section that was created + during geometry building. + + This method must be called exactly once after building the scene. + + \bold{Calling code takes ownership of the scene.} In particular take care + to either explicitly destroy the scene when it is no longer needed - as shown + above. + + For more complex applications parent each finalized scene node onto a QObject + so it will be implictly cleaned up by Qt. If you use QGLSceneNode::setParent() + to do this, you can save an explicit call to addNode() since if setParent() + detects that the new parent is a QGLSceneNode it will call addNode() for you: + + \code + // here a top level node for the app is created, and parented to the view + QGLSceneNode *topNode = new QGLSceneNode(this); + + QGLBuilder b1; + // build geometry + + QGLSceneNode *thing = b1.finalizedSceneNode(); + + // does a QObject::setParent() to manage memory, and also adds to the scene + // graph, so no need to call topNode->addNode(thing) + thing->setParent(topNode); + + QGLBuilder b2; + // build more geometry + QGLSceneNode *anotherThing = b2.finalizedSceneNode(); + + // again parent on get addNode for free + anotherThing->setParent(topNode); + \endcode + + If this builder is destroyed without calling this method to take + ownership of the scene, a warning will be printed on the console and the + scene will be deleted. If this method is called more than once, on the + second and subsequent calls a warning is printed and NULL is returned. + + This function does the following: + \list + \o packs all geometry data from sections into QGLSceneNode instances + \o recalculates QGLSceneNode start() and count() for the scene + \o deletes all QGLBuilder's internal data structures + \o returns the top level scene node that references the geometry + \o sets the internal pointer to the top level scene node to NULL + \endlist + + \sa sceneNode() +*/ +QGLSceneNode *QGLBuilder::finalizedSceneNode() +{ + if (dptr->rootNode == 0) + { + qWarning("QGLBuilder::finalizedSceneNode() called twice"); + return 0; + } + QGeometryData g; + QMap<quint32, QGeometryData> geos; + QMap<QGLSection*, int> offsets; + for (int i = 0; i < dptr->sections.count(); ++i) + { + // pack sections that have the same fields into one geometry + QGLSection *s = dptr->sections.at(i); + QGL::IndexArray indices = s->indices(); + int icnt = indices.size(); + int ncnt = nodeCount(s->nodes()); + int scnt = s->count(); + if (scnt == 0 || icnt == 0 || ncnt == 0) + { + if (!qgetenv("Q_WARN_EMPTY_MESH").isEmpty()) + { + if (ncnt == 0) + warnIgnore(scnt, s, icnt, ncnt, "nodes empty"); + else if (scnt == 0) + warnIgnore(scnt, s, icnt, ncnt, "geometry count zero"); + else + warnIgnore(scnt, s, icnt, ncnt, "index count zero"); + } + continue; + } + s->normalizeNormals(); + int sectionOffset = 0; + int sectionIndexOffset = 0; + if (geos.contains(s->fields())) + { + QGeometryData &gd = geos[s->fields()]; + sectionOffset = gd.count(); + sectionIndexOffset = gd.indexCount(); + offsets.insert(s, sectionIndexOffset); + gd.appendGeometry(*s); + for (int i = 0; i < icnt; ++i) + indices[i] += sectionOffset; + gd.appendIndices(indices); + } + else + { + g = QGeometryData(*s); + geos.insert(s->fields(), g); + } + } + while (dptr->sections.count() > 0) + { + QGLSection *s = dptr->sections.takeFirst(); + dptr->adjustSectionNodes(s, offsets[s], geos[s->fields()]); + delete s; + } + QGLSceneNode *tmp = dptr->rootNode; + dptr->rootNode = 0; // indicates root node detached + return tmp; +} + +/*! + Creates a new section with smoothing mode set to \a smooth. By default + \a smooth is QGL::Smooth. + + A section must be created before any geometry or new nodes can be added + to the builder. However one is created automatically by addTriangle() + and the other add functions; and also by newNode(), pushNode() or popNode() + if needed. + + The internal node stack - see pushNode() and popNode() - is cleared, + and a new top-level QGLSceneNode is created for this section by calling + newNode(). + + \sa newNode(), pushNode() +*/ +void QGLBuilder::newSection(QGL::Smoothing smooth) +{ + new QGLSection(this, smooth); // calls addSection +} + +void QGLBuilder::addSection(QGLSection *sec) +{ + dptr->currentSection = sec; + sec->setMapThreshold(dptr->defThreshold); + dptr->sections.append(sec); + dptr->nodeStack.clear(); + newNode(); +} + +/*! + \internal + Returns the current section, in which new geometry is being added. +*/ +QGLSection *QGLBuilder::currentSection() const +{ + return dptr->currentSection; +} + +/*! + \internal + Returns a list of the sections of the geometry in this builder. +*/ +QList<QGLSection*> QGLBuilder::sections() const +{ + return dptr->sections; +} + +/*! + \internal + Test function only. +*/ +void QGLBuilder::setDefaultThreshold(int t) +{ + dptr->defThreshold = t; +} + +/*! + Returns the root scene node of the geometry created by this builder. + + \sa newNode(), newSection() +*/ +QGLSceneNode *QGLBuilder::sceneNode() +{ + return dptr->rootNode; +} + +/*! + Creates a new QGLSceneNode and makes it current. A pointer to the new + node is returned. The node is added into the scene at the same level + as the currentNode(). + + The node is set to reference the geometry starting from the next + vertex created, such that currentNode()->start() will return the + index of this next vertex. + + \sa newSection() +*/ +QGLSceneNode *QGLBuilder::newNode() +{ + if (dptr->currentSection == 0) + { + newSection(); // calls newNode() + return dptr->currentNode; + } + QGLSceneNode *parentNode = dptr->rootNode; + if (dptr->nodeStack.count() > 0) + parentNode = dptr->nodeStack.last(); + dptr->currentNode = new QGLSceneNode(parentNode); + dptr->currentNode->setPalette(parentNode->palette()); + dptr->currentNode->setStart(dptr->currentSection->indexCount()); + if (dptr->nodeStack.count() == 0) + dptr->currentSection->addNode(dptr->currentNode); + return dptr->currentNode; +} + +/*! + Returns a pointer to the current scene node, within the current section. + + If there is no current section then newSection() will be called to + create one. + + \sa newNode(), newSection() +*/ +QGLSceneNode *QGLBuilder::currentNode() +{ + if (dptr->currentSection == 0) + newSection(); // calls newNode() + return dptr->currentNode; +} + +/*! + Creates a new scene node that is a child of the current node and, + makes it the current node. A pointer to the new node is returned. + The previous current node is saved on a stack and it may + be made current again by calling popNode(). + + \sa popNode(), newNode() +*/ +QGLSceneNode *QGLBuilder::pushNode() +{ + if (dptr->currentSection == 0) + newSection(); // calls newNode() + QGLSceneNode *parentNode = dptr->currentNode; + dptr->nodeStack.append(parentNode); + dptr->currentNode = new QGLSceneNode(parentNode); + dptr->currentNode->setStart(dptr->currentSection->indexCount()); + dptr->currentNode->setPalette(parentNode->palette()); + return dptr->currentNode; +} + +/*! + Removes the node from the top of the stack, makes a copy of it, and + makes the copy current. + + If the stack is empty, behaviour is undefined. In debug mode, calling + this function when the stack is empty will cause an assert. + + A pointer to the new current node is returned. + + The node is set to reference the geometry starting from the next + vertex created, such that QGLSceneNode::start() will return the + index of this next vertex. + + \sa pushNode(), newNode() +*/ +QGLSceneNode *QGLBuilder::popNode() +{ + if (dptr->currentSection == 0) + newSection(); // calls newNode() + int cnt = dptr->currentSection->indexCount(); + QGLSceneNode *s = dptr->nodeStack.takeLast(); // assert here + QGLSceneNode *parentNode = dptr->rootNode; + if (dptr->nodeStack.count() > 0) + parentNode = dptr->nodeStack.last(); + dptr->currentNode = s->cloneNoChildren(parentNode); + dptr->currentNode->setStart(cnt); + dptr->currentNode->setCount(0); + dptr->currentNode->setPalette(parentNode->palette()); + if (dptr->nodeStack.count() == 0) + dptr->currentSection->addNode(dptr->currentNode); + return dptr->currentNode; +} + +/*! + Returns the palette for this builder. This is the QGLMaterialCollection + pointer that was passed to the constructor; or if that was null a new + QGLMaterialCollection. This function returns the same result as + \c{sceneNode()->palette()}. + + \sa sceneNode() +*/ +QGLMaterialCollection *QGLBuilder::palette() +{ + return dptr->rootNode->palette(); +} + +/*! + \relates QGLBuilder + Convenience operator for creating a new section in \a builder with \a smoothing. + + \code + // equivalent to builder.newSection(QGL::Faceted) + builder << QGL::Faceted; + \endcode +*/ +QGLBuilder& operator<<(QGLBuilder& builder, const QGL::Smoothing& smoothing) +{ + builder.newSection(smoothing); + return builder; +} + +/*! + \relates QGLBuilder + Convenience operator for adding \a triangles to the \a builder. + + \code + // equivalent to builder.addTriangles(triangles); + builder << triangles; + \endcode +*/ +QGLBuilder& operator<<(QGLBuilder& builder, const QGeometryData& triangles) +{ + builder.addTriangles(triangles); + return builder; +} diff --git a/src/threed/geometry/qglbuilder.h b/src/threed/geometry/qglbuilder.h new file mode 100644 index 000000000..8c278e192 --- /dev/null +++ b/src/threed/geometry/qglbuilder.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLBuilder_H +#define QGLBuilder_H + +#include <QtCore/qvector.h> +#include <QtCore/qlist.h> +#include <QtGui/qvector3d.h> +#include <QtOpenGL/qgl.h> + +#include "qglnamespace.h" +#include "qglscenenode.h" +#include "qglattributevalue.h" +#include "qgeometrydata.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLSection; +class QGLMaterialCollection; +class QGLBuilderPrivate; +class QGLPainter; + +class Q_QT3D_EXPORT QGLBuilder +{ +public: + explicit QGLBuilder(QGLMaterialCollection *materials = 0); + virtual ~QGLBuilder(); + + // section management + void newSection(QGL::Smoothing sm = QGL::Smooth); + + // scene management + QGLSceneNode *sceneNode(); + QGLSceneNode *currentNode(); + QGLSceneNode *newNode(); + QGLSceneNode *pushNode(); + QGLSceneNode *popNode(); + QGLMaterialCollection *palette(); + QGLSceneNode *finalizedSceneNode(); + + // geometry building by primitive + void addTriangles(const QGeometryData &triangle); + void addQuads(const QGeometryData &quad); + void addTriangleFan(const QGeometryData &fan); + void addTriangleStrip(const QGeometryData &strip); + void addTriangulatedFace(const QGeometryData &face); + void addQuadStrip(const QGeometryData &strip); + void addQuadsInterleaved(const QGeometryData &top, + const QGeometryData &bottom); + inline void addPane(qreal size = 1.0f); + inline void addPane(QSizeF size); + +protected: + // internal and test functions + QGLSection *currentSection() const; + QList<QGLSection*> sections() const; + void setDefaultThreshold(int); + +private: + Q_DISABLE_COPY(QGLBuilder); + void addSection(QGLSection *section); + + friend class QGLSection; + + QGLBuilderPrivate *dptr; +}; + +inline void QGLBuilder::addPane(qreal size) +{ + addPane(QSizeF(size, size)); +} + +inline void QGLBuilder::addPane(QSizeF size) +{ + QSizeF f = size / 2.0f; + QVector2D a(-f.width(), -f.height()); + QVector2D b(f.width(), -f.height()); + QVector2D c(f.width(), f.height()); + QVector2D d(-f.width(), f.height()); + QVector2D ta(0.0f, 0.0f); + QVector2D tb(1.0f, 0.0f); + QVector2D tc(1.0f, 1.0f); + QVector2D td(0.0f, 1.0f); + QGeometryData quad; + quad.appendVertex(a, b, c, d); + quad.appendTexCoord(ta, tb, tc, td); + addQuads(quad); +} + +Q_QT3D_EXPORT QGLBuilder& operator<<(QGLBuilder& builder, const QGL::Smoothing& smoothing); +Q_QT3D_EXPORT QGLBuilder& operator<<(QGLBuilder& builder, const QGeometryData& triangles); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGLBuilder_H diff --git a/src/threed/geometry/qglbuilder_p.h b/src/threed/geometry/qglbuilder_p.h new file mode 100644 index 000000000..bcbd16883 --- /dev/null +++ b/src/threed/geometry/qglbuilder_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLBuilder_P_H +#define QGLBuilder_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qglbuilder.h" + +#include <QtCore/qmap.h> +#include <QPointer> + +QT_BEGIN_NAMESPACE + +class QGLBuilder; +class QGLSection; +class QGeometryData; + +class QGLBuilderPrivate +{ +public: + QGLBuilderPrivate(QGLBuilder *parent); + ~QGLBuilderPrivate(); + inline void setDirty(bool dirty = true); + void addTriangle(int a, int b, int c, const QGeometryData &p, int &count); + void adjustSectionNodes(QGLSection *sec, int offset, const QGeometryData &geom); + int adjustNodeTree(QGLSceneNode *top, int offset, const QGeometryData &geom, + QList<QGLSceneNode*> &deleted); + + QList<QGLSection*> sections; + QGLSection *currentSection; + QList<QGLSceneNode*> nodeStack; + QGLSceneNode *currentNode; + QGLSceneNode *rootNode; + int defThreshold; + QGLBuilder *q; +}; + +QT_END_NAMESPACE + +#endif // QGLBuilder_P_H diff --git a/src/threed/geometry/qglcube.cpp b/src/threed/geometry/qglcube.cpp new file mode 100644 index 000000000..5f5bda118 --- /dev/null +++ b/src/threed/geometry/qglcube.cpp @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglcube.h" +#include "qglbuilder.h" +#include "qvector3darray.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGLCube + \brief The QGLCube class represents the geometry of simple six-sided cube in 3D space. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + The following example adds a cube of 2 units on a side to a + geometry builder, and draws it at (10, 25, 0) in a QGLPainter: + + \code + QGLBuilder list; + list.newSection(QGL::Faceted); + list << QGLCube(2); + painter->translate(10, 25, 0); + list.draw(painter); + \endcode + + QGLCube will create a default set of texture coordinates that shows + the same texture of all six faces. +*/ + +/*! + \fn QGLCube::QGLCube(qreal size) + + Constructs the geometry for a regular cube of \a size + units on a side. +*/ + +/*! + \fn qreal QGLCube::size() const + + Returns the size of this cube. + + \sa setSize() +*/ + +/*! + \fn void QGLCube::setSize(qreal size) + + Sets the \a size of this cube. + + \sa size() +*/ + +static const int vertexDataLen = 6 * 4 * 3; + +static const float vertexData[vertexDataLen] = { + -0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, -0.5f, + + -0.5f, 0.5f, -0.5f, + -0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, -0.5f, + + 0.5f, 0.5f, -0.5f, + 0.5f, 0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, -0.5f, + + 0.5f, -0.5f, -0.5f, + 0.5f, -0.5f, 0.5f, + -0.5f, -0.5f, 0.5f, + -0.5f, -0.5f, -0.5f, + + 0.5f, -0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + -0.5f, -0.5f, 0.5f, + + 0.5f, 0.5f, -0.5f, + 0.5f, -0.5f, -0.5f, + -0.5f, -0.5f, -0.5f, + -0.5f, 0.5f, -0.5f +}; + +static const int texCoordDataLen = 4 * 2; + +static const float texCoordData[texCoordDataLen] = { + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f +}; + +/*! + \relates QGLCube + + Builds the geometry for \a cube within the specified + geometry \a builder. + + This operator specifies the positions, and 2D texture + co-ordinates for all of the vertices that make up the cube. + Normals will be calculated by the \a builder, depending on its + current section's smoothing setting. +*/ +QGLBuilder& operator<<(QGLBuilder& builder, const QGLCube& cube) +{ + QGeometryData op; + + QVector3DArray vrts = QVector3DArray::fromRawData( + reinterpret_cast<const QVector3D *>(vertexData), vertexDataLen / 3); + if (cube.size() != 1.0f) + vrts.scale(cube.size()); + + op.appendVertexArray(vrts); + + QVector2DArray texx = QVector2DArray::fromRawData( + reinterpret_cast<const QVector2D *>(texCoordData), texCoordDataLen / 2); + + for (int i = 0; i < 6; ++i) + op.appendTexCoordArray(texx); + + builder.addQuads(op); + return builder; +} + +QT_END_NAMESPACE diff --git a/src/threed/geometry/qglcube.h b/src/threed/geometry/qglcube.h new file mode 100644 index 000000000..19eebc26c --- /dev/null +++ b/src/threed/geometry/qglcube.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLCUBE_H +#define QGLCUBE_H + +#include "qt3dglobal.h" + +#include <QtGui/qvector2d.h> +#include "qvector2darray.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLBuilder; + +class Q_QT3D_EXPORT QGLCube +{ +public: + explicit QGLCube(qreal size = 1.0f) : m_size(size) {} + + qreal size() const { return m_size; } + void setSize(qreal size) { m_size = size; } + +private: + qreal m_size; +}; + +Q_QT3D_EXPORT QGLBuilder& operator<<(QGLBuilder& builder, const QGLCube& cube); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/geometry/qglcylinder.cpp b/src/threed/geometry/qglcylinder.cpp new file mode 100644 index 000000000..30b4f34cc --- /dev/null +++ b/src/threed/geometry/qglcylinder.cpp @@ -0,0 +1,384 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglcylinder.h" +#include "qglbuilder.h" +#include "qvector2darray.h" +#include "qvector3darray.h" +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QGLCylinder + \brief The QGLCylinder class represents the geometry of a simple cylinder/cone in 3D space. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + The following example creates a cone with a top diameter of 1 unit, + a bottom diameter of of 2 units in diameter and height of 3 units. + + It then draws it at (10, 25, 0) in a QGLPainter: + + \code + QGLBuilder builder; + builder << QGLCylinder(1.0,2.0,3.0); + QGLSceneNode *node = builder.finalizedSceneNode(); + + painter.translate(10, 25, 0); + node->draw(&painter); + \endcode + + Note that the bottom circle of the cylinder will always be centred at (0,0,0) + unless otherwise transformed after cylinder creation. + + The QGLCylinder class specifies positions, normals and 2D texture + co-ordinates for all of the vertices that make up the cylinder. + + The texture co-ordinates are fixed at construction time. This + is because constructing the cylinder can involve generating additional + vertices which need to interpolate the texture co-ordinates of their + neighboring vertices. + + The QGLCylinder is divided into slices and layers. The slices value + indicate number of triangular sections into which the top and bottom + circles of the cylinder are broken into. Consequently it also sets the + number of facets which run the length of the cylinder. More slices + results in a smoother circumference. + + The layers value indicates the number of longitudinal sections the + cylinder is broken into. Fewer layers means that the side facets of the + cylinder will be made up of fewer, very long, triangles, while a higher + number of layers will produce many and smaller triangles. Often it is + desirable to avoid large triangles as they may cause inefficiencies in + texturing/lighting on certain platforms. + + The end-caps and sides of the cylinder are independent sections of the + scene-graph, and so may be textured separately. + + Textures are wrapped around the sides of thecylinder in such a way that + the texture may distort across the x axis if the top and bottom diameters + of the cylinder differ (ie. the cylinder forms a truncated cone). Textures + begin and end at the centre points of the top and bottom end-caps of the + cylinder. This wrapping means that textures on either end-cap may be + distorted. + + Texture coordinates are assigned as shown below. + + \image cylinder-texture-coords.png + + It is worth noting that the cylinder class can, in fact, be used to generate + any regular solid polygonal prism. A rectangular prism can be created, for + example, by creating a 4 sided cylinder. Likewise a hexagonal prism is + simply a 6 sided cylinder. + + With this knowledge, and an understanding of the texture coordinate mapping, + it is possible to make custom textures which will be usable with these + three dimensional objects. + + \sa QGLBuilder +*/ + + +/*! + \fn QGLCylinder::QGLCylinder(qreal diameterTop, qreal diameterBase , qreal height, int slices, int layers, bool top, bool base) + + Constructs the geometry for a cylinder with top of diameter \a diameterTop, + a base of diameter \a diameterBase, and a height of \a height. + + The resultant mesh will be divided around the vertical axis of the cylinder + into \a slices individual wedges, and shall be formed of \a layers stacked + to form the cylinder. + + If the values for \a top or \a base are true, then the cylinder will be + created with solid endcaps. Otherwise, it shall form a hollow pipe. + + units on a side. +*/ + + +/*! + \fn qreal QGLCylinder::diameterTop() const + + Returns the diameter of the top of the cylinder. + + The default value is 1. + + \sa setDiameterTop() +*/ + +/*! + \fn void QGLCylinder::setDiameterTop(qreal diameter) + + Sets the diameter of the top of this cylinder to \a diameter. + + \sa diameterTop() +*/ + +/*! + \fn qreal QGLCylinder::diameterBottom() const + + Returns the diameter of the bottom of the cylinder. + + The default value is 1. + + \sa setDiameterBottom() +*/ + +/*! + \fn void QGLCylinder::setDiameterBottom(qreal diameter) + + Sets the diameter of the bottom of this cylinder to \a diameter. + + \sa diameterBottom() +*/ + +/*! + \fn qreal QGLCylinder::height() const + + Returns the height of the cylinder. + + The default value is 1.0 + + \sa setDiameterBottom() +*/ + +/*! + \fn void QGLCylinder::setHeight(qreal height) + + Sets the height of this cylinder to \a height. + + \sa diameterBottom() +*/ + + +/*! + \fn int QGLCylinder::slices() const + + Returns the number of triangular slices the cylinder is divided into + around its polar axis. + + The default is 6. + + \sa setSlices() +*/ + +/*! + \fn int QGLCylinder::setSlices(int slices) + + Sets the number of triangular \a slices the cylinder is divided into + around its polar axis. + + \sa slices() +*/ + +/*! + \fn int QGLCylinder::layers() const + + Returns the number of cylindrical layers the cylinder is divided into + along its height. + + The default is 3. + + \sa setLayers() +*/ + +/*! + \fn int QGLCylinder::setLayers(int layers) + + Sets the number of stacked \a layers the cylinder is divided into + along its height. + + \sa layers() +*/ + +/*! + \fn bool QGLCylinder::topEnabled() const + + Returns true if the top of the cyclinder will be created when + building the mesh. + + The default is true. + + \sa setTopEnabled() +*/ + +/*! + \fn void QGLCylinder::setTopEnabled(bool top) + + Set whether the top end-cap of the cylinder will be created when + building the mesh. If \a top is true, the end-cap will be created. + + \sa topEnabled() +*/ + +/*! + \fn bool QGLCylinder::baseEnabled() const + + Returns true if the base of the cyclinder will be created when + building the mesh. + + The default is true. + + \sa setBaseEnabled() +*/ + +/*! + \fn void QGLCylinder::setBaseEnabled(bool base) + + Set whether the base end-cap of the cylinder will be created when + building the mesh. If \a base is true, the end-cap will be created. + + \sa baseEnabled() +*/ + +/*! + \relates QGLCylinder + + Builds the geometry for \a cylinder within the specified + geometry \a builder. +*/ + +QGLBuilder& operator<<(QGLBuilder& builder, const QGLCylinder& cylinder) +{ + /* ASSERT(cylinder.diameterBottom()>=0 && + cylinder.diameterTop()>=0 && + cylinder.height()>0);*/ + + qreal numSlices = qreal(cylinder.slices()); + qreal numLayers = qreal(cylinder.layers()); + qreal topRadius = cylinder.diameterTop()/2.0; + qreal bottomRadius = cylinder.diameterBottom()/2.0; + + qreal angle = 0; + qreal angleIncrement = (2.0 * M_PI) / numSlices; + qreal radius = topRadius; + qreal radiusIncrement = qreal(bottomRadius-topRadius)/ numLayers; + qreal height = qreal(cylinder.height()); + qreal heightDecrement = height/numLayers; + + qreal textureHeight = 1.0; + qreal textureDecrement = 1.0/numLayers; + + QGeometryData oldLayer; + + //Generate vertices for the next layer of cylinder + for (int layerCount=0; layerCount<=cylinder.layers(); layerCount++) { + QGeometryData newLayer; + + //Generate a circle of vertices for this layer. + for (int i=0; i<cylinder.slices(); i++) + { + newLayer.appendVertex(QVector3D(radius * qCos(angle), + radius * qSin(angle), + height)); + angle+=angleIncrement; + } + angle = 0; + // Generate texture coordinates (including an extra seam vertex for textures). + newLayer.appendVertex(newLayer.vertex(0)); + newLayer.generateTextureCoordinates(); + for (int i = 0; i < newLayer.count(); i++) newLayer.texCoord(i).setY(textureHeight); + + //Special cases for top end-cap + if (layerCount==0 && cylinder.topEnabled()) { + //Draw end-cap at top + QGeometryData top; + builder.newSection(); + builder.currentNode()->setObjectName("Cylinder Top"); + top.appendVertex(newLayer.center()); + top.appendVertexArray(newLayer.vertices()); + //Generate a circle of texture vertices for this layer. + top.appendTexCoord(QVector2D(0.5,0.5)); + + for (int i=1; i<top.count(); i++) + { + top.appendTexCoord(QVector2D(0.5*qCos(angle)+0.5, 0.5*qSin(angle)+0.5)); + angle+=angleIncrement; + } + angle = 0; + builder.addTriangulatedFace(top); + } + + + //Add a new cylinder layer to the mesh + if (layerCount>0) + { + //If it's the first section, create a cylinder sides section + if (layerCount==1) { + builder.newSection(); + builder.currentNode()->setObjectName("Cylinder Sides"); + } + builder.addQuadsInterleaved(oldLayer, newLayer); + } + + //Special cases for bottom end-cap + if (layerCount==cylinder.layers() && cylinder.baseEnabled()) { + //Draw end-cap at bottom + QGeometryData base; + builder.newSection(); + builder.currentNode()->setObjectName("Cylinder Base"); + base.appendVertexArray(newLayer.vertices()); + base.appendVertex(newLayer.center()); + //Generate a circle of texture vertices for this layer. + for (int i=1; i<base.count(); i++) + { + base.appendTexCoord(QVector2D(0.5*qCos(angle)+0.5, 0.5*qSin(angle)+0.5)); + angle+=angleIncrement; + } + base.appendTexCoord(QVector2D(0.5,0.5)); + angle = 0; + + //we need to reverse the above to draw it properly - windings! + builder.addTriangulatedFace(base.reversed()); + } + + //Keep the current layer for drawing the next segment of the cylinder + oldLayer.clear(); + oldLayer.appendGeometry(newLayer); + radius+=radiusIncrement; + height-=heightDecrement; + textureHeight-=textureDecrement; + } + + return builder; +} diff --git a/src/threed/geometry/qglcylinder.h b/src/threed/geometry/qglcylinder.h new file mode 100644 index 000000000..51d2cfba6 --- /dev/null +++ b/src/threed/geometry/qglcylinder.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLCYLINDER_H +#define QGLCYLINDER_H + +#include "qt3dglobal.h" +#include "qglmaterialcollection.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLBuilder; +class QVector2D; + +class Q_QT3D_EXPORT QGLCylinder +{ +public: + explicit QGLCylinder(qreal diameterTop = 1.0f, qreal diameterBase = 1.0f, qreal height = 1.0f, int slices = 6, int layers = 3, bool top = true, bool base = true) + : m_diameterTop(diameterTop), m_diameterBottom(diameterBase), m_height(height), m_slices(slices), m_layers(layers), m_top(top), m_base(base) {} + + //Cylinder dimensions + qreal diameterTop() const {return m_diameterTop;} + void setDiameterTop(qreal diameter) {m_diameterTop=diameter;} + + qreal diameterBottom() const {return m_diameterBottom;} + void setDiameterBottom(qreal diameter) {m_diameterBottom=diameter;} + + qreal height() const {return m_height;} + void setHeight(qreal height) {m_height = height;} + + //Cylinder geometrical subdivisions + int slices() const {return m_slices;} + void setSlices(int slices) {m_slices = slices;} + + int layers() const {return m_layers;} + void setLayers(int layers) {m_layers = layers;} + + //End-caps attached? + bool topEnabled() const {return m_top;} + void setTopEnabled(bool top) {m_top = top;} + + bool baseEnabled() const {return m_base;} + void setBaseEnabled(bool base) {m_base = base;} + +protected: + qreal m_diameterTop; + qreal m_diameterBottom; + qreal m_height; + + int m_slices; + int m_layers; + + bool m_top; + bool m_base; +}; + +Q_QT3D_EXPORT QGLBuilder& operator<<(QGLBuilder& builder, const QGLCylinder& cylinder); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGLCYLINDER_H diff --git a/src/threed/geometry/qgldome.cpp b/src/threed/geometry/qgldome.cpp new file mode 100644 index 000000000..5cd18da01 --- /dev/null +++ b/src/threed/geometry/qgldome.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgldome.h" +#include "qglbuilder.h" +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QGLDome + \brief The QGLDome class represents the geometry of a simple hemisphere in 3D space. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + The following example creates a dome of 2 units in diameter and + draws it at (10, 25, 0) in a QGLPainter: + + \code + QGLBuilder builder; + builder << QGLDome(2); + QGLSceneNode *node = builder.finalizedSceneNode(); + + painter.translate(10, 25, 0); + node->draw(&painter); + \endcode + + The QGLDome class specifies positions, normals and 2D texture + co-ordinates for all of the vertices that make up the sphere. + + The texture co-ordinates are fixed at construction time. This + is because constructing the sphere can involve generating additional + vertices which need to interpolate the texture co-ordinates of their + neighboring vertices. + + The default mode of QGLDome is half of a "UV sphere", which divides + the object up into longitudinal and latitudinal sections. The longitudinal + slices meet at the pole, which in a single unit dome is defined to + be at (0, 0, +0.5) and (0, 0, -0.5). This choice is the simplest to + texture map as the texture will only distort along the x-axis of the + 2D texture. However the density of vertices is significantly higher at + the poles than it is elsewhere on the sphere and is a poor choice if a + uniform density of pixels from the texture map is required. + + \sa QGLBuilder +*/ + +/*! + \fn QGLDome::QGLDome(qreal diameter, int depth, bool base) + + Creates a dome of \a diameter across (default is 1). When the dome + is recursively subdivided into triangles, it will be subdivided no more + than \a depth times (between 1 and 5, default is 3). + + If \a base is true, the dome will be drawn with a bottom circle, creating + an enclosed solid. +*/ + +/*! + Destroys this dome object. +*/ +QGLDome::~QGLDome() +{ +} + +/*! + \fn qreal QGLDome::diameter() const + + Returns the diameter of this dome. The default is 1. + + \sa setDiameter() +*/ + +/*! + \fn void QGLDome::setDiameter(qreal diameter) + + Sets the diameter of this dome to \a diameter. + + \sa diameter() +*/ + +/*! + \fn int QGLDome::subdivisionDepth() const + + Returns the maximum depth when this hemisphere is subdivided into + triangles. The default is 3. The following picture shows the effect + of depth values between 1 and 5 for a UV sphere (hemisphere subdivision + depth shares this scheme). + + \image sphere-detail.png + + \sa setSubdivisionDepth(), QGLSphere::subdivisionDepth() +*/ + +/*! + \fn void QGLDome::setSubdivisionDepth(int depth) + + Sets the maximum \a depth when this hemisphere is subdivided into triangles. + + \sa subdivisionDepth() +*/ + +/*! + \fn bool QGLDome::baseEnabled() const + + Returns true if the base of the dome will be created when + building the mesh. + + The default is true. + + \sa setBaseEnabled() +*/ + +/*! + \fn void QGLDome::setBaseEnabled(bool base) + + Set whether the bottom of the dome will be created when + building the mesh. If \a base is true, the end-cap will be + created. + + \sa baseEnabled() +*/ + +/*! + \relates QGLDome + + Builds the geometry for \a dome within the specified + geometry \a builder. +*/ +QGLBuilder& operator<<(QGLBuilder& builder, const QGLDome& dome) +{ + qreal radius = dome.diameter() / 2.0f; + + // Determine the number of slices and stacks to generate. + int divisions = dome.subdivisionDepth(); + if (divisions < 1) + divisions = 1; + else if (divisions > 5) + divisions = 5; + int stacks = 2 * (1 << divisions); + int slices = 2 * stacks; + stacks = stacks>>1; + + // Precompute sin/cos values for the slices and stacks. + const int maxSlices = 4 * (1 << 5) + 1; + const int maxStacks = 2 * (1 << 5) + 1; + qreal sliceSin[maxSlices]; + qreal sliceCos[maxSlices]; + qreal stackSin[maxStacks]; + qreal stackCos[maxStacks]; + for (int slice = 0; slice < slices; ++slice) { + qreal angle = 2 * M_PI * slice / slices; + sliceSin[slice] = qFastSin(angle); + sliceCos[slice] = qFastCos(angle); + } + sliceSin[slices] = sliceSin[0]; // Join first and last slice. + sliceCos[slices] = sliceCos[0]; + + const qreal halfPi=M_PI/2.0; + + for (int stack = 0; stack <= stacks; ++stack) { + qreal angle = halfPi * stack / stacks; + stackSin[stack] = qFastSin(angle); + stackCos[stack] = qFastCos(angle); + } + stackSin[0] = 0.0f; // Come to a point at the poles. + stackSin[stacks] = 1.0f; + + builder.newSection(); + builder.currentNode()->setObjectName("Dome"); + // Create the stacks for the dome part of the dome + for (int stack = 0; stack < stacks; ++stack) { + QGeometryData prim; + qreal z = radius * stackCos[stack]; + qreal nextz = radius * stackCos[stack + 1]; + qreal s = stackSin[stack]; + qreal nexts = stackSin[stack + 1]; + qreal c = stackCos[stack]; + qreal nextc = stackCos[stack + 1]; + qreal r = radius * s; + qreal nextr = radius * nexts; + for (int slice = 0; slice <= slices; ++slice) { + prim.appendVertex(QVector3D(nextr * sliceSin[slice], nextr * sliceCos[slice], nextz)); + prim.appendNormal(QVector3D(sliceSin[slice] * nexts, sliceCos[slice] * nexts, nextc)); + prim.appendTexCoord(QVector2D(1.0f - qreal(slice) / slices, 1.0f - qreal(stack + 1) / stacks)); + + prim.appendVertex(QVector3D(r * sliceSin[slice], r * sliceCos[slice], z)); + prim.appendNormal(QVector3D(sliceSin[slice] * s, sliceCos[slice] * s, c)); + prim.appendTexCoord(QVector2D(1.0f - qreal(slice) / slices, 1.0f - qreal(stack) / stacks)); + } + builder.addQuadStrip(prim); + } + + if (dome.baseEnabled()) { + //Draw end-cap at bottom + builder.newSection(); + builder.currentNode()->setObjectName("Base"); + + //Generate a circle of vertices for this layer. + QGeometryData tempBase; + + tempBase.appendVertex(QVector3D(0,0,0)); + tempBase.appendTexCoord(QVector2D(0.5,0.5)); + for (int slice=0; slice<=slices+1; slice++) + { + tempBase.appendVertex(QVector3D(radius * sliceCos[slice], radius * sliceSin[slice], 0)); + tempBase.appendTexCoord(QVector2D(0.5*sliceCos[slice]+0.5, 0.5*sliceSin[slice]+0.5)); + } + + //we need to reverse the above to draw it properly - windings! + builder.addTriangulatedFace(tempBase.reversed()); + } + return builder; +} + +QT_END_NAMESPACE + diff --git a/src/threed/geometry/qgldome.h b/src/threed/geometry/qgldome.h new file mode 100644 index 000000000..23d86f508 --- /dev/null +++ b/src/threed/geometry/qgldome.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLDOME_H +#define QGLDOME_H + +#include "qt3dglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLBuilder; + +class Q_QT3D_EXPORT QGLDome +{ +public: + explicit QGLDome(qreal diameter = 1.0f, int depth = 3, bool baseEnabled = true) + : m_diameter(diameter), m_subdivisionDepth(depth), m_baseEnabled(baseEnabled) {} + virtual ~QGLDome(); + + qreal diameter() const { return m_diameter; } + void setDiameter(qreal diameter) { m_diameter = diameter; } + + int subdivisionDepth() const { return m_subdivisionDepth; } + void setSubdivisionDepth(int depth) { m_subdivisionDepth = depth; } + + bool baseEnabled() const {return m_baseEnabled; } + void setBaseEnabled(bool baseEnabled) {m_baseEnabled = baseEnabled;} + +private: + qreal m_diameter; + int m_subdivisionDepth; + bool m_baseEnabled; +}; + +Q_QT3D_EXPORT QGLBuilder& operator<<(QGLBuilder& builder, const QGLDome& dome); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/geometry/qglmaterialcollection.cpp b/src/threed/geometry/qglmaterialcollection.cpp new file mode 100644 index 000000000..674dac70f --- /dev/null +++ b/src/threed/geometry/qglmaterialcollection.cpp @@ -0,0 +1,415 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglmaterialcollection.h" +#include "qglmaterial_p.h" +#include <QtCore/qlist.h> +#include <QtCore/qhash.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QGLMaterialCollection + \brief The QGLMaterialCollection class manages groups of materials. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::enablers + + Managing more complex 3d graphics with several materials is easier when the + materials can be referred to as a collection. This is the role of the + QGLMaterialCollection class. + + Plug-ins implementing 3D formats may make the materials defined in + the format available to the application via a QGLMaterialCollection. + + The collection is also optimised for the case where many small objects + must refer to materials - such as faces in a mesh, or particles. In + this case the materials can be specified as a short data type using an + offset into the collection, rather than the material name. + + When building up a collection, meshes that refer to the various materials + can check off which ones are used by calling markMaterialAsUsed(), and then + remove spurious unused materials by calling removeUnusedMaterials(). This + technique is suitable for models loaded from a model file where a large + number of materials may be specified but only a few of those materials + are used by the particular mesh selected from the scene. + + To make a material available from a collection, call addMaterial(). To + retrieve a material from the collection call removeMaterial(). + + The collection takes ownership of the QGLMaterial + objects passed to it by the addMaterial() function. These + objects will be destroyed when the collection is destroyed. +*/ + +class QGLMaterialCollectionPrivate +{ +public: + QGLMaterialCollectionPrivate() + { + } + + QList<QGLMaterial *> materials; + QHash<QString, int> materialNames; +}; + +/*! + Construct a new empty QGLMaterialCollection object. The \a parent + is set as the parent of this object. +*/ +QGLMaterialCollection::QGLMaterialCollection(QObject *parent) + : QObject(parent) + , d_ptr(new QGLMaterialCollectionPrivate) +{ +} + +/*! + Destroy this collection. All material objects referred to by this + collection will be destroyed. +*/ +QGLMaterialCollection::~QGLMaterialCollection() +{ + // The QGLMaterial QObject's are reparented to the collection + // when addMaterial() is called, so the QObject destructor + // will take care of cleaning them up for us. +} + +/*! + Returns a pointer to the material corresponding to \a index; or null + if \a index is out of range or the material has been removed. + + Here's an example of searching for a material with a given ambient + \c{color} in the collection \c{materials}: + + \code + for (int colorIndex; colorIndex < materials->size(); ++colorIndex) { + if (material(colorIndex) && + material(colorIndex)->ambientColor() == color) + break; + } + if (colorIndex < materials->size()) + myObject->setMaterial(colorIndex); + \endcode +*/ +QGLMaterial *QGLMaterialCollection::material(int index) const +{ + Q_D(const QGLMaterialCollection); + return d->materials.value(index, 0); +} + +/*! + \overload + + Returns the material associated with \a name in this collection; + null if \a name is not present or the material has been removed. +*/ +QGLMaterial *QGLMaterialCollection::material(const QString &name) const +{ + Q_D(const QGLMaterialCollection); + int index = d->materialNames.value(name, -1); + if (index >= 0) + return d->materials[index]; + else + return 0; +} + +/*! + Returns true if this collection contains \a material; false otherwise. + + \sa indexOf() +*/ +bool QGLMaterialCollection::contains(QGLMaterial *material) const +{ + return material && material->d_func()->collection == this; +} + +/*! + \overload + + Returns true if this collection contains a material called \a name; + false otherwise. + + \sa indexOf() +*/ +bool QGLMaterialCollection::contains(const QString &name) const +{ + Q_D(const QGLMaterialCollection); + return d->materialNames.contains(name); +} + +/*! + Returns the index of \a material in this collection; -1 if + \a material is not present in this collection. + + \sa contains() +*/ +int QGLMaterialCollection::indexOf(QGLMaterial *material) const +{ + if (material && material->d_func()->collection == this) + return material->d_func()->index; + else + return -1; +} + +/*! + \overload + + Returns the index of the material called \a name in this collection; + -1 if \a name is not present in this collection. + + \sa contains() +*/ +int QGLMaterialCollection::indexOf(const QString &name) const +{ + Q_D(const QGLMaterialCollection); + return d->materialNames.value(name, -1); +} + +/*! + Returns the name of the material at \a index in this material collection; + a null QString if \a index is out of range. +*/ +QString QGLMaterialCollection::materialName(int index) const +{ + Q_D(const QGLMaterialCollection); + if (index >= 0 && index < d->materials.count()) { + QGLMaterial *material = d->materials[index]; + if (material) { + // Use the name in the private data block just in case the + // application has modified objectName() since adding. + return material->d_func()->name; + } + } + return QString(); +} + +/*! + Returns true if the material at \a index in this collection has been + marked as used by markMaterialAsUsed(). + + \sa markMaterialAsUsed() +*/ +bool QGLMaterialCollection::isMaterialUsed(int index) const +{ + QGLMaterial *mat = material(index); + if (mat) + return mat->d_func()->used; + else + return false; +} + +/*! + Flags the material corresponding to the \a index as used. Some model files + may contain a range of materials, applying to various objects in the scene. + + When a particular object is loaded from the file, many of those + materials may not be used in that object. This wastes space, + with many spurious materials being stored. + + Use this method during model loading or construction to mark off + materials that have been used. Materials so marked will not + be removed by removeUnusedMaterials(). + + \sa removeUnusedMaterials(), isMaterialUsed() +*/ +void QGLMaterialCollection::markMaterialAsUsed(int index) +{ + QGLMaterial *mat = material(index); + if (mat) + mat->d_func()->used = true; +} + +/*! + Removes and deletes materials which have not been marked as used. + + \sa markMaterialAsUsed(), isMaterialUsed() +*/ +void QGLMaterialCollection::removeUnusedMaterials() +{ + Q_D(QGLMaterialCollection); + for (int index = 0; index < d->materials.size(); ++index) { + QGLMaterial *material = d->materials[index]; + if (material && !material->d_func()->used) + delete removeMaterial(index); + } +} + +/*! + Adds \a material to this collection and returns its new index. The + collection takes ownership of the material and will delete it when the + collection is destroyed. Initially the \a material is marked as unused. + + The QObject::objectName() of \a material at the time addMaterial() + is called will be used as the material's name within this collection. + Changes to the object name after the material is added are ignored. + + If \a material is already present in this collection, then this + function will return the index that was previously assigned. + + Returns -1 if \a material has been added to another collection. + + \sa removeMaterial(), markMaterialAsUsed() +*/ +int QGLMaterialCollection::addMaterial(QGLMaterial *material) +{ + Q_D(QGLMaterialCollection); + Q_ASSERT(material); + + // Allocate a new index for the material. + int index = d->materials.count(); + + // Record the index in the private data attached to the material. + // This allows us to find the material's index quickly later. + QGLMaterialPrivate *dm = material->d_func(); + if (dm->collection) { + if (dm->collection == this) + return dm->index; + return -1; + } + dm->collection = this; + dm->index = index; + dm->name = material->objectName(); + dm->used = false; + + // Add the material to this collection. + material->setParent(this); + d->materials.append(material); + if (!dm->name.isEmpty()) + d->materialNames[dm->name] = index; + connect(material, SIGNAL(destroyed()), this, SLOT(materialDeleted())); + return index; +} + +/*! + Removes all instances of \a material from this collection. + The \a material object is not deleted and can be reused. + + Does nothing if \a material is null or not a member of + this collection. + + \sa addMaterial() +*/ +void QGLMaterialCollection::removeMaterial(QGLMaterial *material) +{ + Q_D(QGLMaterialCollection); + if (!material) + return; + + // Check the material's owning collection. + QGLMaterialPrivate *dm = material->d_func(); + if (dm->collection != this) + return; + + // Remove the material from the collection. + d->materials[dm->index] = 0; + if (!dm->name.isEmpty()) + d->materialNames.remove(dm->name); + material->setParent(0); + + // Detach from the owning collection. + dm->collection = 0; + dm->index = -1; +} + +/*! + Removes the material at \a index from this collection, and returns + a pointer to the material. + + Since the collection is designed for fast lookup by index, the + the stored material pointer is set to null but the \a index + otherwise remains valid. +*/ +QGLMaterial *QGLMaterialCollection::removeMaterial(int index) +{ + Q_D(QGLMaterialCollection); + + // Bail out if the material is invalid. + if (index < 0 || index >= d->materials.count()) + return 0; + QGLMaterial *material = d->materials[index]; + if (!material) + return 0; + + // Remove the material from the collection. + QGLMaterialPrivate *dm = material->d_func(); + d->materials[index] = 0; + if (!dm->name.isEmpty()) + d->materialNames.remove(dm->name); + material->setParent(0); + + // Detach from the owning collection. + dm->collection = 0; + dm->index = -1; + return material; +} + +/*! + Returns true if this collection is empty, false otherwise. + + \sa size() +*/ +bool QGLMaterialCollection::isEmpty() const +{ + Q_D(const QGLMaterialCollection); + return d->materials.isEmpty(); +} + +/*! + Returns the number of (possibly null) materials in this collection. + Null materials result from calling removeMaterial(). + + \sa isEmpty() +*/ +int QGLMaterialCollection::size() const +{ + Q_D(const QGLMaterialCollection); + return d->materials.size(); +} + +/*! + \internal + Responds to the destroyed() signal by calling removeMaterial() on the + material about to be deleted; +*/ +void QGLMaterialCollection::materialDeleted() +{ + removeMaterial(qobject_cast<QGLMaterial *>(sender())); +} diff --git a/src/threed/geometry/qglmaterialcollection.h b/src/threed/geometry/qglmaterialcollection.h new file mode 100644 index 000000000..52ec79bec --- /dev/null +++ b/src/threed/geometry/qglmaterialcollection.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLMATERIALCOLLECTION_H +#define QGLMATERIALCOLLECTION_H + +#include <QtCore/qobject.h> + +#include "qt3dglobal.h" +#include "qglmaterial.h" +#include "qgltexture2d.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLMaterialCollectionPrivate; + +class Q_QT3D_EXPORT QGLMaterialCollection : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QGLMaterialCollection) + Q_DISABLE_COPY(QGLMaterialCollection) +public: + QGLMaterialCollection(QObject *parent = 0); + virtual ~QGLMaterialCollection(); + + QGLMaterial *material(int index) const; + QGLMaterial *material(const QString &name) const; + + bool contains(QGLMaterial *material) const; + bool contains(const QString &name) const; + + int indexOf(QGLMaterial *material) const; + int indexOf(const QString &name) const; + + QString materialName(int index) const; + + bool isMaterialUsed(int index) const; + void markMaterialAsUsed(int index); + void removeUnusedMaterials(); + + int addMaterial(QGLMaterial *material); + void removeMaterial(QGLMaterial *material); + QGLMaterial *removeMaterial(int index); + + bool isEmpty() const; + int size() const; + +private Q_SLOTS: + void materialDeleted(); + +private: + QScopedPointer<QGLMaterialCollectionPrivate> d_ptr; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QGLMATERIALCOLLECTION_H diff --git a/src/threed/geometry/qglsection.cpp b/src/threed/geometry/qglsection.cpp new file mode 100644 index 000000000..3775af4ee --- /dev/null +++ b/src/threed/geometry/qglsection.cpp @@ -0,0 +1,696 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglsection_p.h" +#include "qglbuilder_p.h" +#include "qarray.h" +#include "qvector_utils_p.h" + +#include <QtGui/qvector3d.h> +#include <QtCore/qdebug.h> +#include <QtCore/qpointer.h> +#include <QtCore/qmap.h> +#include <QtCore/qbitarray.h> + +#include <limits.h> + +/*! + \internal + \class QGLSection + \brief The QGLSection class clusters like geometry in a QGLBuilder. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + QGLSection instances partition a QGLBuilder into related sections, + while the builder is being initialized with geometry data. + + Once the builder is initialized, and geometry building is complete + the QGLSection instances are destroyed and the data is uploaded to the + graphics hardware. + + The QGLSection class is a work horse for the QGLBuilder, and it + takes care of automatically managing vertex data. As such + for usual use cases, its functionality will not need to be referenced + directly. For low-level access to geometry, QGLSection provides a + range of accessors to reference geometry data during scene building. + + Within a section, incoming geometry data will be coalesced and + indexes created to reference the fewest possible copies of the vertex + data. For example, in smooth geometry all copies of a vertex are + coalesced into one, and referenced by indices - except in the case + where texture data forms a \i seam and a copy must be created to carry + the two texture coordinates of the seam. + + This is handled automatically by QGLSection, to pack data into the + smallest space possible thus improving cache coherence and performance. + + All the vertices in a QGLSection are treated with the same + \l{QGL::Smoothing}{smoothing}, and have the same + \l{QLogicalVertex::Type}{data types}. + + Each QGLSection references a contiguous range of vertices in a + QGLBuilder. + + A QGLBuilder instance has the \l{QGLBuilder::newSection()}{newSection()} + function which creates a new QGLSection to reference its data. Use this + to construct new QGLSection instances, or alternatively construct + a new QGLSection() and pass a non-null QGLBuilder pointer. + + These functions all return QVector values. QVector instances are + implicitly shared, thus the copies are inexpensive unless a + non-const function is called on them, triggering a copy-on-write. + + Generally for adding geometry, use append(). This function simply + calls virtual protected functions appendSmooth() (for smoothed vertices) + and appendFaceted() (for faceted vertices). See QGLBuilder for a + discussion of smoothing. +*/ + +// allow QVector3D's to be stored in a QMap +inline bool operator<(const QVector3D &a, const QVector3D &b) +{ + if (qFskCompare(a.x(), b.x())) + { + if (qFskCompare(a.y(), b.y())) + { + if (qFskCompare(a.z(), b.z())) + { + return false; // equal so not less-than + } + else + { + return a.z() < b.z(); + } + } + else + { + return a.y() < b.y(); + } + } + else + { + return a.x() < b.x(); + } +} + +static inline bool qSameDirection(const QVector3D &a , const QVector3D &b) +{ + bool res = false; + if (!a.isNull() && !b.isNull()) + { + float dot = QVector3D::dotProduct(a, b); + res = qFskCompare((qreal)dot, a.length() * b.length()); + } + return res; +} + +class QGLSectionPrivate +{ +public: + QGLSectionPrivate(const QVector3DArray *ary) + : vec_data(ary) + , it(vec_map.end()) + , map_threshold(5) + , number_mapped(0) + , start_ptr(-1) + , end_ptr(-1) + { + normIndices.fill(-1, 32); + } + + ~QGLSectionPrivate() {} + + bool normalAccumulated(int index, const QVector3D &norm) const + { + if (index >= normIndices.size()) + return false; + int ptr = normIndices.at(index); + while (ptr != -1) + { + int val_ptr = normPtrs.at(ptr); + //if (normValues.at(val_ptr) == norm) + if (qSameDirection(normValues.at(val_ptr), norm)) + return true; + ptr = normPtrs.at(ptr+1); + } + return false; + } + + void accumulateNormal(int index, const QVector3D &norm) + { + int new_norm_index = normValues.size(); + normValues.append(norm); + if (normIndices.size() <= index) + { + int old_size = normIndices.size(); + normIndices.extend(32); + for (int i = old_size; i < normIndices.size(); ++i) + normIndices[i] = -1; + } + int new_norm_ptr = normPtrs.size(); + normPtrs.append(new_norm_index); // even ptrs point to vector value + normPtrs.append(-1); // odd ptrs point to next in normPtr linked list + if (normIndices.at(index) == -1) + { + normIndices[index] = new_norm_ptr; + } + else + { + int norm_ptr = normIndices.at(index); + while (normPtrs.at(norm_ptr + 1) != -1) + { + norm_ptr = normPtrs.at(norm_ptr + 1); + } + normPtrs[norm_ptr+1] = new_norm_ptr; + } + } + + void mapVertex(const QVector3D &v, int ix) + { + Q_UNUSED(ix); + Q_UNUSED(v); + static bool seeded = false; + if (!seeded) + qsrand(31415); + Q_ASSERT(vec_data->at(ix) == v); + if ((vec_data->size() - number_mapped) > map_threshold) + { + int to_map = vec_data->size() - number_mapped; + QArray<int, 100> shuffle(to_map, -1); + for (int i = number_mapped, k = 0; i < vec_data->size(); ++i, ++k) + shuffle[k] = i; + for (int n = to_map; n > 1; --n) + { + int k = qrand() % n; + int tmp = shuffle[k]; + shuffle[k] = shuffle[n - 1]; + shuffle[n - 1] = tmp; + } + for (int i = 0; i < to_map; ++i) + vec_map.insertMulti(vec_data->at(shuffle.at(i)), shuffle.at(i)); + number_mapped += to_map; + } + } + + int nextIndex() + { + int result = -1; + if (end_ptr != -1) + { + // first look through the unmapped items + while (start_ptr <= end_ptr && result == -1) + { + // search from the end and beginning, favouring the end - most often + // its in the last few we added, sometimes in the first ones + if (qFskCompare(vec_data->at(end_ptr--), target)) + result = end_ptr+1; + else if (start_ptr <= end_ptr && qFskCompare(vec_data->at(end_ptr--), target)) + result = end_ptr+1; + else if (start_ptr <= end_ptr && qFskCompare(vec_data->at(start_ptr++), target)) + result = start_ptr-1; + } + // if that found nothing, have a look at the map + if (result == -1) + { + start_ptr = -1; + end_ptr = -1; + it = vec_map.constEnd(); + if (vec_map.size() > 0) + it = vec_map.find(target); + } + } + if (it != vec_map.constEnd()) + { + // if there was something in the map see if its still on target + if (qFskCompare(it.key(), target)) + { + result = it.value(); + ++it; // increment to find more insertMulti instances + } + else + { + // not on target - flag that we're done here + it = vec_map.constEnd(); + } + } + return result; + } + + int findVertex(const QVector3D &v) + { + end_ptr = vec_data->size() - 1; // last one not in QMap + start_ptr = number_mapped; // first one not in QMap + target = v; + return nextIndex(); + } + + // mapper + int index; + QVector3D target; + const QVector3DArray *vec_data; + QMap<QVector3D, int> vec_map; + QMap<int, int> index_map; + QMap<QVector3D,int>::const_iterator it; + int map_threshold; // if more than this is unmapped, do a mapping run + int number_mapped; // how many vertices have been mapped + int start_ptr; + int end_ptr; + + QArray<int, 32> normIndices; + QArray<int, 32> normPtrs; + QArray<QVector3D, 32> normValues; + + QList<QGLSceneNode*> nodes; +}; + +/*! + \internal + Construct a new QGLSection on \a builder, and with smoothing \a s. + By default the smoothing is QGL::Smooth. + + See QGLBuilder for a discussion of smoothing. + + The pointer \a list must be non-null, and in debug mode, unless QT_NO_DEBUG is + defined, this function will assert if \a list is null. + + The following lines of code have identical effect: + \code + QGLSection *s = myDisplayList->newSection(QGL::Faceted); + QGLSection *s2 = new QGLSection(myDisplayList, QGL::Faceted); + \endcode +*/ +QGLSection::QGLSection(QGLBuilder *builder, QGL::Smoothing s) + : m_smoothing(s) + , d(0) +{ + Q_ASSERT(builder); + enableField(QGL::Position); + Q_ASSERT(vertexData()); + d = new QGLSectionPrivate(vertexData()); + builder->addSection(this); +} + +/*! + \internal + Destroy this QGLSection, recovering any resources. +*/ +QGLSection::~QGLSection() +{ + delete d; +} + +/*! + \internal + Reserve capacity for \a amount items. This may avoid realloc + overhead when a large number of items will be appended. +*/ +void QGLSection::reserve(int amount) +{ + QGeometryData::reserve(amount); + d->normIndices.reserve(amount); + d->normPtrs.reserve(amount * 2); + d->normValues.reserve(amount); +} + +/*! + \internal + Adds the logical vertices \a a, \a b and \c to this section. All + should have the same fields. This function is exactly equivalent to + \code + append(a); append(b); append(c); + \endcode + + \sa appendSmooth(), appendFaceted() +*/ +void QGLSection::append(const QLogicalVertex &a, const QLogicalVertex &b, const QLogicalVertex &c) +{ + Q_ASSERT(a.fields() == b.fields() && b.fields() == c.fields()); + if (!a.hasField(QGL::Normal)) + { + appendFaceted(a, b, c); + } + else + { + if (m_smoothing == QGL::Smooth) + appendSmooth(a, b, c); + else + appendFaceted(a, b, c); + } +} + +/*! + \internal + Adds the logical vertex \a lv to this section. + + Otherwise, if the \a lv does have a lighting normal; then the + vertex processing depends on the smoothing property of this section. + If this section has smoothing QGL::Smooth, then the append will be done + by calling appendSmooth(); or if this section has smoothing QGL::Faceted, + then the append will be done by calling appendFaceted(). + + \sa appendSmooth(), appendFaceted() +*/ +void QGLSection::append(const QLogicalVertex &lv) +{ + if (!lv.hasField(QGL::Normal)) + { + appendFaceted(lv); + } + else + { + if (m_smoothing == QGL::Smooth) + appendSmooth(lv); + else + appendFaceted(lv); + } +} + +static bool qCompareByAttributes(const QLogicalVertex &a, const QLogicalVertex &b) +{ + static const quint32 ATTRS_AND_TEXTURES = (0xFFFFFFFF << QGL::TextureCoord0); + quint32 af = a.fields() & ATTRS_AND_TEXTURES; + quint32 bf = b.fields() & ATTRS_AND_TEXTURES; + if (af != bf) + return false; + quint32 flds = af | bf; + const quint32 mask = 0x01; + flds >>= QGL::TextureCoord0; + for (int i = QGL::TextureCoord0; flds; ++i, flds >>= 1) + { + if (flds & mask) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(i); + if (attr < QGL::CustomVertex0) + { + if (!qFskCompare(a.texCoord(attr), b.texCoord(attr))) + return false; + } + else + { + QVariant v1 = a.attribute(attr); + QVariant v2 = b.attribute(attr); + if (v1.type() == (QVariant::Type)QMetaType::Float) + return qFskCompare(v1.toFloat(), v2.toFloat()); + else if (v1.type() == QVariant::Vector2D) + return qFskCompare(qVariantValue<QVector2D>(v1), qVariantValue<QVector2D>(v2)); + else if (v1.type() == QVariant::Vector3D) + return qFskCompare(qVariantValue<QVector3D>(v1), qVariantValue<QVector3D>(v2)); + else + return v1 == v2; + } + } + } + return true; +} + +int QGLSection::appendOne(const QLogicalVertex &lv) +{ +#ifndef QT_NO_DEBUG_STREAM + if (count() && lv.fields() != fields()) + { + qDebug() << "Warning: adding" << lv << "fields:" << lv.fields() + << "fields do not match existing:" << fields() + << "create new section first?"; + } +#endif + int index = appendVertex(lv); + d->mapVertex(lv.vertex(), index); + appendIndex(index); + return index; +} + +/*! + \internal + Adds the logical vertex \a lv to this section of a builder. + + Two QLogicalVertex instances a and b are treated as being duplicates for + the purpose of smoothing, if \c{qFuzzyCompare(a.vertex(), b.vertex())} is + true + + All duplicate occurrences of a vertex are coalesced, that is replaced + by a GL index referencing the one copy. + + In order to draw \a lv as part of a smooth continuous surface, with + no distinct edge, duplicates of vertex \a lv are coalesced into one + (within this section) and the normal for that one set to the average of + the incoming unique normals. + + The incoming vertex \a lv is not treated as a duplicate if \a lv has + different texture coordinates or attributes. This occurs for example + in the case of a texture seam, where two different texture coordinates + are required at the same point on the geometry. + + In that case a new duplicate vertex is added to carry the unique + texture coordinates or attributes. When new vertex copies are added in + this way all copies receive the averaged normals. + + Call this function to add the vertices of a smooth face to the section + of a builder, or use: + + \code + myDisplayList->newSection(QGLBuilder::Smooth); + myDisplayList->addTriangle(a, b, c); + \endcode + + In smooth surfaces, the vertex and its normal is only sent to the + graphics hardware once (not once per face), thus smooth geometry may + consume fewer resources. + + \sa appendFaceted(), updateTexCoord(), QGLBuilder::newSection() +*/ +void QGLSection::appendSmooth(const QLogicalVertex &lv) +{ + Q_ASSERT(lv.hasField(QGL::Position)); + Q_ASSERT(lv.hasField(QGL::Normal)); + + int found_index = d->findVertex(lv.vertex()); + bool coalesce = false; + if (found_index == -1) + { + int newIndex = appendOne(lv); + d->accumulateNormal(newIndex, lv.normal()); + } + else + { + while (!coalesce && found_index != -1) + { + if (qCompareByAttributes(lv, logicalVertexAt(found_index))) + coalesce = true; + else + found_index = d->nextIndex(); + } + if (!coalesce) // texture or attributes prevented coalesce + { + // new vert to carry tex/attrib data + d->accumulateNormal(appendOne(lv), lv.normal()); + } + else + { + appendIndex(found_index); + while (found_index != -1) + { + if (!d->normalAccumulated(found_index, lv.normal())) + { + normal(found_index) += lv.normal(); + d->accumulateNormal(found_index, lv.normal()); + } + found_index = d->nextIndex(); + } + } + } +} + + +void QGLSection::appendSmooth(const QLogicalVertex &lv, int index) +{ + Q_ASSERT(lv.hasField(QGL::Position)); + Q_ASSERT(lv.hasField(QGL::Normal)); + + int found_index = -1; + QMap<int, int>::const_iterator it = d->index_map.constFind(index); + if (it != d->index_map.constEnd()) + found_index = it.value(); + if (found_index == -1) + { + int newIndex = appendVertex(lv); + d->index_map.insert(index, newIndex); + appendIndex(newIndex); + d->accumulateNormal(newIndex, lv.normal()); + } + else + { + appendIndex(found_index); + if (!d->normalAccumulated(found_index, lv.normal())) + { + normal(found_index) += lv.normal(); + d->accumulateNormal(found_index, lv.normal()); + } + } +} + +/*! + \internal + Add the logical vertex \a lv to this section of a builder. + + The vertex will be drawn as a distinct edge, instead of just part of a + continuous smooth surface. To acheive this the vertex value of \a lv + is duplicated for each unique normal in the current section. + + Note that duplication is only for unique normals: if a vertex is already + present with a given normal it is coalesced and simply referenced by index. + As for appendSmooth() vertices are not coalesced in this way if \a lv + has a different texture coordinate or attribute than its duplicate. + + In faceted surfaces, the vertex is sent to the graphics hardware once for + each normal it has, and thus may consume more resources. + + \sa appendSmooth(), updateTexCoord(), QGLBuilder::newSection() +*/ +void QGLSection::appendFaceted(const QLogicalVertex &lv) +{ + Q_ASSERT(lv.hasField(QGL::Position)); + int found_index = d->findVertex(lv.vertex()); + bool coalesce = false; + while (!coalesce && found_index != -1) + { + if (logicalVertexAt(found_index) == lv) + coalesce = true; + else + found_index = d->nextIndex(); + } + if (coalesce) // found + { + appendIndex(found_index); + } + else + { + appendOne(lv); + } +} + +/*! + \internal + Returns the current map threshold for this section. The threshold is the + point at which the section switches from using a plain QArray - with + linear performance ie O(n) - to using a QMap - with approx O(log n). These + structures are used for looking up vertices during the index generation and + normals calculation. + + The default value is 50. + + \sa setMapThreshold() +*/ +int QGLSection::mapThreshold() const +{ + return d->map_threshold; +} + +/*! + \internal + Sets the current map threshold to \a t for this section. + + \sa mapThreshold() +*/ +void QGLSection::setMapThreshold(int t) +{ + d->map_threshold = t; +} + +/*! + \internal + Returns a list of the QGLSceneNode instances associated with this section. +*/ +QList<QGLSceneNode*> QGLSection::nodes() const +{ + return d->nodes; +} + +/*! + \internal + Adds the \a node to the list of QGLSceneNode instances associated with + this section. +*/ +void QGLSection::addNode(QGLSceneNode *node) +{ + d->nodes.append(node); +} + +/*! + \internal + Deletes the \a node from the list of QGLSceneNode instances associated + with this section. Returns true if the \a node was found, false + otherwise. +*/ +bool QGLSection::deleteNode(QGLSceneNode *node) +{ + int ix = d->nodes.indexOf(node); + if (ix != -1) + { + d->nodes.removeAt(ix); + return true; + } + return false; +} + +#ifndef QT_NO_DEBUG_STREAM +/*! + \internal + Output a string representation of \a section to a debug stream \a dbg. + \relates QGLSection +*/ +QDebug operator<<(QDebug dbg, const QGLSection §ion) +{ + dbg.space() + << "QGLSection(" << §ion + << "- count:" << section.count() + << "- smoothing mode:" << (section.smoothing() == QGL::Smooth ? + "QGL::Smooth" : "QGL::Faceted") << "\n"; + QGL::IndexArray indices = section.indices(); + for (int i = 0; i < section.count(); ++i) + { + int ix = indices[i]; + dbg << section.logicalVertexAt(ix) << "\n"; + } + dbg << ")\n"; + return dbg.space(); +} +#endif diff --git a/src/threed/geometry/qglsection_p.h b/src/threed/geometry/qglsection_p.h new file mode 100644 index 000000000..ea48ff1b1 --- /dev/null +++ b/src/threed/geometry/qglsection_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLSECTION_H +#define QGLSECTION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qglpainter.h" +#include "qlogicalvertex.h" +#include "qbox3d.h" +#include "qglnamespace.h" + +#include <QtOpenGL/qgl.h> +#include <QtGui/qmatrix4x4.h> + +QT_BEGIN_NAMESPACE + +class QGLPainter; +class QGLBuilder; +class QGLSectionPrivate; +class QGeometryData; +class QGLSceneNode; + +class Q_QT3D_EXPORT QGLSection : public QGeometryData +{ +public: + QGLSection(QGLBuilder *d, QGL::Smoothing sm = QGL::Smooth); + ~QGLSection(); + + void reserve(int amount); + + void append(const QLogicalVertex &lv); + void append(const QLogicalVertex &a, const QLogicalVertex &b, const QLogicalVertex &c); + void appendSmooth(const QLogicalVertex &lv); + void appendSmooth(const QLogicalVertex &lv, int index); + void appendSmooth(const QLogicalVertex &a, const QLogicalVertex &b, const QLogicalVertex &c) + { + appendSmooth(a); + appendSmooth(b); + appendSmooth(c); + } + void appendFaceted(const QLogicalVertex &lv); + void appendFaceted(const QLogicalVertex &a, const QLogicalVertex &b, const QLogicalVertex &c) + { + appendFaceted(a); + appendFaceted(b); + appendFaceted(c); + } + + inline QGL::Smoothing smoothing() const; + inline void setSmoothing(QGL::Smoothing s); + int mapThreshold() const; + void setMapThreshold(int); + QList<QGLSceneNode*> nodes() const; + void addNode(QGLSceneNode *node); + bool deleteNode(QGLSceneNode *node); +private: + Q_DISABLE_COPY(QGLSection); + friend class QGLBuilder; + + int appendOne(const QLogicalVertex &vertex); + + QGL::Smoothing m_smoothing; + QGLSectionPrivate *d; +}; + +inline QGL::Smoothing QGLSection::smoothing() const +{ + return m_smoothing; +} + +inline void QGLSection::setSmoothing(QGL::Smoothing s) +{ + m_smoothing = s; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QGLSection §ion); +#endif + +QT_END_NAMESPACE + +#endif // QGLSECTION_H diff --git a/src/threed/geometry/qglsphere.cpp b/src/threed/geometry/qglsphere.cpp new file mode 100644 index 000000000..9a8976ac3 --- /dev/null +++ b/src/threed/geometry/qglsphere.cpp @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglsphere.h" +#include "qglbuilder.h" +#include <QtCore/qmath.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QGLSphere + \brief The QGLSphere class represents the geometry of a simple sphere in 3D space. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + The following example creates a sphere of 2 units in diameter and + draws it at (10, 25, 0) in a QGLPainter: + + \code + QGLBuilder builder; + builder << QGLSphere(2); + QGLSceneNode *node = builder.finalizedSceneNode(); + + painter.translate(10, 25, 0); + node->draw(&painter); + \endcode + + The QGLSphere class specifies positions, normals and 2D texture + co-ordinates for all of the vertices that make up the sphere. + + The texture co-ordinates are fixed at construction time. This + is because constructing the sphere can involve generating additional + vertices which need to interpolate the texture co-ordinates of their + neighboring vertices. + + The default mode of QGLSphere is a "UV sphere", which divides the + sphere up into longitudinal and latitudinal sections. The longitudinal + slices meet at the poles, which in a single unit sphere are defined to + be at (0, 0, +0.5) and (0, 0, -0.5). This choice is the simplest to + texture map as the texture will only distort along the x-axis of the + 2D texture. However the density of vertices is significantly higher at + the poles than it is elsewhere on the sphere and is a poor choice if a + uniform density of pixels from the texture map is required. + + \sa QGLBuilder +*/ + +/*! + \fn QGLSphere::QGLSphere(qreal diameter, int depth) + + Creates a sphere of \a diameter across (default is 1). When the sphere + is recursively subdivided into triangles, it will be subdivided no more + than \a depth times (between 1 and 10, default is 5). +*/ + +/*! + Destroys this sphere object. +*/ +QGLSphere::~QGLSphere() +{ +} + +/*! + \fn qreal QGLSphere::diameter() const + + Returns the diameter of this sphere. The default is 1. + + \sa setDiameter() +*/ + +/*! + \fn void QGLSphere::setDiameter(qreal diameter) + + Sets the diameter of this sphere to \a diameter. + + \sa diameter() +*/ + +/*! + \fn int QGLSphere::subdivisionDepth() const + + Returns the maximum depth when this sphere is subdivided into triangles. + The default is 5. The following picture shows the effect of depth + values between 1 and 10 for a UV sphere: + + \image sphere-detail.png + + \table + \header \o Level of Detail \o Number of Triangles + \row \o 1 \o 64 + \row \o 2 \o 128 + \row \o 3 \o 256 + \row \o 4 \o 512 + \row \o 5 \o 1024 + \row \o 6 \o 2048 + \row \o 7 \o 4096 + \row \o 8 \o 8192 + \row \o 9 \o 16384 + \row \o 10 \o 32768 + \endtable + + \sa setSubdivisionDepth() +*/ + +/*! + \fn void QGLSphere::setSubdivisionDepth(int depth) + + Sets the maximum \a depth when this sphere is subdivided into triangles. + + \sa subdivisionDepth() +*/ + +/*! + \relates QGLSphere + + Builds the geometry for \a sphere within the specified + geometry \a builder. +*/ +QGLBuilder& operator<<(QGLBuilder& builder, const QGLSphere& sphere) +{ + qreal radius = sphere.diameter() / 2.0f; + + // Determine the number of slices and stacks to generate. + static int const slicesAndStacks[] = { + 8, 4, + 8, 8, + 16, 8, + 16, 16, + 32, 16, + 32, 32, + 64, 32, + 64, 64, + 128, 64, + 128, 128 + }; + int divisions = sphere.subdivisionDepth(); + if (divisions < 1) + divisions = 1; + else if (divisions > 10) + divisions = 10; + int stacks = slicesAndStacks[divisions * 2 - 1]; + int slices = slicesAndStacks[divisions * 2 - 2]; + + // Precompute sin/cos values for the slices and stacks. + const int maxSlices = 128 + 1; + const int maxStacks = 128 + 1; + qreal sliceSin[maxSlices]; + qreal sliceCos[maxSlices]; + qreal stackSin[maxStacks]; + qreal stackCos[maxStacks]; + for (int slice = 0; slice < slices; ++slice) { + qreal angle = 2 * M_PI * slice / slices; + sliceSin[slice] = qFastSin(angle); + sliceCos[slice] = qFastCos(angle); + } + sliceSin[slices] = sliceSin[0]; // Join first and last slice. + sliceCos[slices] = sliceCos[0]; + for (int stack = 0; stack <= stacks; ++stack) { + qreal angle = M_PI * stack / stacks; + stackSin[stack] = qFastSin(angle); + stackCos[stack] = qFastCos(angle); + } + stackSin[0] = 0.0f; // Come to a point at the poles. + stackSin[stacks] = 0.0f; + + // Create the stacks. + for (int stack = 0; stack < stacks; ++stack) { + QGeometryData prim; + qreal z = radius * stackCos[stack]; + qreal nextz = radius * stackCos[stack + 1]; + qreal s = stackSin[stack]; + qreal nexts = stackSin[stack + 1]; + qreal c = stackCos[stack]; + qreal nextc = stackCos[stack + 1]; + qreal r = radius * s; + qreal nextr = radius * nexts; + for (int slice = 0; slice <= slices; ++slice) { + prim.appendVertex + (QVector3D(nextr * sliceSin[slice], + nextr * sliceCos[slice], nextz)); + prim.appendNormal + (QVector3D(sliceSin[slice] * nexts, + sliceCos[slice] * nexts, nextc)); + prim.appendTexCoord + (QVector2D(1.0f - qreal(slice) / slices, + 1.0f - qreal(stack + 1) / stacks)); + + prim.appendVertex + (QVector3D(r * sliceSin[slice], + r * sliceCos[slice], z)); + prim.appendNormal + (QVector3D(sliceSin[slice] * s, + sliceCos[slice] * s, c)); + prim.appendTexCoord + (QVector2D(1.0f - qreal(slice) / slices, + 1.0f - qreal(stack) / stacks)); + } + builder.addQuadStrip(prim); + } + + return builder; +} + +QT_END_NAMESPACE diff --git a/src/threed/geometry/qglsphere.h b/src/threed/geometry/qglsphere.h new file mode 100644 index 000000000..784f9db4c --- /dev/null +++ b/src/threed/geometry/qglsphere.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLSPHERE_H +#define QGLSPHERE_H + +#include "qt3dglobal.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class QGLBuilder; + +class Q_QT3D_EXPORT QGLSphere +{ +public: + explicit QGLSphere(qreal diameter = 1.0f, int depth = 5) + : m_diameter(diameter), m_subdivisionDepth(depth) {} + virtual ~QGLSphere(); + + qreal diameter() const { return m_diameter; } + void setDiameter(qreal diameter) { m_diameter = diameter; } + + int subdivisionDepth() const { return m_subdivisionDepth; } + void setSubdivisionDepth(int depth) { m_subdivisionDepth = depth; } + +private: + qreal m_diameter; + int m_subdivisionDepth; +}; + +Q_QT3D_EXPORT QGLBuilder& operator<<(QGLBuilder& builder, const QGLSphere& sphere); + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/geometry/qglteapot.cpp b/src/threed/geometry/qglteapot.cpp new file mode 100644 index 000000000..fb2419dce --- /dev/null +++ b/src/threed/geometry/qglteapot.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qglteapot.h" +#include "qglteapot_data_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QGLTeapot + \brief The QGLTeapot class represents a 3D teapot object. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + The classic 3D "Utah Teapot" was originally drawn by Martin Newell + in 1975. The vertex data was made publicly available by him and + it has been a standard 3D test object ever since. + + For more information on the history of the "Utah Teapot", see + Wikipedia, http://en.wikipedia.org/wiki/Utah_teapot. + + The following example code uses QGLTeapot to draw a teapot of size + 0.5 at the origin: + + \code + QGLBuilder builder; + builder << QGLTeapot(); + teapot = builder.finalizedSceneNode(); + + painter.modelViewMatrix().scale(0.5f); + teapot->draw(painter); + \endcode + + The QGLTeapot object contains a lot of vertex data once it has + been subdivided into triangles. It is recommended that instances + of this class be created at startup, added to a QGLBuilder, + and then the finalized scene node can be reused over and over. + + \sa QGLBezierPatches +*/ + +/*! + Constructs a new 3D teapot geometry object. +*/ +QGLTeapot::QGLTeapot() +{ + QVector3DArray positions; + for (int pindex = 0; pindex < teapotPatchCount * 16; ++pindex) { + int vindex = teapotPatchData[pindex]; + positions.append(teapotBezierVertexData[vindex * 3], + teapotBezierVertexData[vindex * 3 + 1], + teapotBezierVertexData[vindex * 3 + 2]); + } + setPositions(positions); + setSubdivisionDepth(teapotDepth); +} + +/*! + Destroys this teapot geometry object. +*/ +QGLTeapot::~QGLTeapot() +{ +} + +QT_END_NAMESPACE diff --git a/src/threed/geometry/qglteapot.h b/src/threed/geometry/qglteapot.h new file mode 100644 index 000000000..290ff23d9 --- /dev/null +++ b/src/threed/geometry/qglteapot.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLTEAPOT_H +#define QGLTEAPOT_H + +#include "qglbezierpatches.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Qt3D) + +class Q_QT3D_EXPORT QGLTeapot : public QGLBezierPatches +{ +public: + QGLTeapot(); + ~QGLTeapot(); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif diff --git a/src/threed/geometry/qglteapot_data_p.h b/src/threed/geometry/qglteapot_data_p.h new file mode 100644 index 000000000..28dbfa7ac --- /dev/null +++ b/src/threed/geometry/qglteapot_data_p.h @@ -0,0 +1,408 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGLTEAPOT_DATA_P_H +#define QGLTEAPOT_DATA_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qglbezierpatches.h" + +// Generated from teapot.txt by meshcvt, depth = 4 + +#define teapotBezierVertexCount 306 +#define teapotBezierVertexStride 3 +#define teapotPatchCount 32 +#define teapotDepth 4 +static float const teapotBezierVertexData[] = { + 0.700000f, 0.450000f, -0.000000f, + 0.700000f, 0.450000f, 0.392000f, + 0.392000f, 0.450000f, 0.700000f, + 0.000000f, 0.450000f, 0.700000f, + 0.668750f, 0.515625f, -0.000000f, + 0.668750f, 0.515625f, 0.374500f, + 0.374500f, 0.515625f, 0.668750f, + 0.000000f, 0.515625f, 0.668750f, + 0.718750f, 0.515625f, -0.000000f, + 0.718750f, 0.515625f, 0.402500f, + 0.402500f, 0.515625f, 0.718750f, + 0.000000f, 0.515625f, 0.718750f, + 0.750000f, 0.450000f, -0.000000f, + 0.750000f, 0.450000f, 0.420000f, + 0.420000f, 0.450000f, 0.750000f, + 0.000000f, 0.450000f, 0.750000f, + -0.392000f, 0.450000f, 0.700000f, + -0.700000f, 0.450000f, 0.392000f, + -0.700000f, 0.450000f, -0.000000f, + -0.374500f, 0.515625f, 0.668750f, + -0.668750f, 0.515625f, 0.374500f, + -0.668750f, 0.515625f, -0.000000f, + -0.402500f, 0.515625f, 0.718750f, + -0.718750f, 0.515625f, 0.402500f, + -0.718750f, 0.515625f, -0.000000f, + -0.420000f, 0.450000f, 0.750000f, + -0.750000f, 0.450000f, 0.420000f, + -0.750000f, 0.450000f, -0.000000f, + -0.700000f, 0.450000f, -0.392000f, + -0.392000f, 0.450000f, -0.700000f, + 0.000000f, 0.450000f, -0.700000f, + -0.668750f, 0.515625f, -0.374500f, + -0.374500f, 0.515625f, -0.668750f, + 0.000000f, 0.515625f, -0.668750f, + -0.718750f, 0.515625f, -0.402500f, + -0.402500f, 0.515625f, -0.718750f, + 0.000000f, 0.515625f, -0.718750f, + -0.750000f, 0.450000f, -0.420000f, + -0.420000f, 0.450000f, -0.750000f, + 0.000000f, 0.450000f, -0.750000f, + 0.392000f, 0.450000f, -0.700000f, + 0.700000f, 0.450000f, -0.392000f, + 0.374500f, 0.515625f, -0.668750f, + 0.668750f, 0.515625f, -0.374500f, + 0.402500f, 0.515625f, -0.718750f, + 0.718750f, 0.515625f, -0.402500f, + 0.420000f, 0.450000f, -0.750000f, + 0.750000f, 0.450000f, -0.420000f, + 0.875000f, 0.187500f, -0.000000f, + 0.875000f, 0.187500f, 0.490000f, + 0.490000f, 0.187500f, 0.875000f, + 0.000000f, 0.187500f, 0.875000f, + 1.000000f, -0.075000f, -0.000000f, + 1.000000f, -0.075000f, 0.560000f, + 0.560000f, -0.075000f, 1.000000f, + 0.000000f, -0.075000f, 1.000000f, + 1.000000f, -0.300000f, -0.000000f, + 1.000000f, -0.300000f, 0.560000f, + 0.560000f, -0.300000f, 1.000000f, + 0.000000f, -0.300000f, 1.000000f, + -0.490000f, 0.187500f, 0.875000f, + -0.875000f, 0.187500f, 0.490000f, + -0.875000f, 0.187500f, -0.000000f, + -0.560000f, -0.075000f, 1.000000f, + -1.000000f, -0.075000f, 0.560000f, + -1.000000f, -0.075000f, -0.000000f, + -0.560000f, -0.300000f, 1.000000f, + -1.000000f, -0.300000f, 0.560000f, + -1.000000f, -0.300000f, -0.000000f, + -0.875000f, 0.187500f, -0.490000f, + -0.490000f, 0.187500f, -0.875000f, + 0.000000f, 0.187500f, -0.875000f, + -1.000000f, -0.075000f, -0.560000f, + -0.560000f, -0.075000f, -1.000000f, + 0.000000f, -0.075000f, -1.000000f, + -1.000000f, -0.300000f, -0.560000f, + -0.560000f, -0.300000f, -1.000000f, + 0.000000f, -0.300000f, -1.000000f, + 0.490000f, 0.187500f, -0.875000f, + 0.875000f, 0.187500f, -0.490000f, + 0.560000f, -0.075000f, -1.000000f, + 1.000000f, -0.075000f, -0.560000f, + 0.560000f, -0.300000f, -1.000000f, + 1.000000f, -0.300000f, -0.560000f, + 1.000000f, -0.525000f, -0.000000f, + 1.000000f, -0.525000f, 0.560000f, + 0.560000f, -0.525000f, 1.000000f, + 0.000000f, -0.525000f, 1.000000f, + 0.750000f, -0.637500f, -0.000000f, + 0.750000f, -0.637500f, 0.420000f, + 0.420000f, -0.637500f, 0.750000f, + 0.000000f, -0.637500f, 0.750000f, + 0.750000f, -0.675000f, -0.000000f, + 0.750000f, -0.675000f, 0.420000f, + 0.420000f, -0.675000f, 0.750000f, + 0.000000f, -0.675000f, 0.750000f, + -0.560000f, -0.525000f, 1.000000f, + -1.000000f, -0.525000f, 0.560000f, + -1.000000f, -0.525000f, -0.000000f, + -0.420000f, -0.637500f, 0.750000f, + -0.750000f, -0.637500f, 0.420000f, + -0.750000f, -0.637500f, -0.000000f, + -0.420000f, -0.675000f, 0.750000f, + -0.750000f, -0.675000f, 0.420000f, + -0.750000f, -0.675000f, -0.000000f, + -1.000000f, -0.525000f, -0.560000f, + -0.560000f, -0.525000f, -1.000000f, + 0.000000f, -0.525000f, -1.000000f, + -0.750000f, -0.637500f, -0.420000f, + -0.420000f, -0.637500f, -0.750000f, + 0.000000f, -0.637500f, -0.750000f, + -0.750000f, -0.675000f, -0.420000f, + -0.420000f, -0.675000f, -0.750000f, + 0.000000f, -0.675000f, -0.750000f, + 0.560000f, -0.525000f, -1.000000f, + 1.000000f, -0.525000f, -0.560000f, + 0.420000f, -0.637500f, -0.750000f, + 0.750000f, -0.637500f, -0.420000f, + 0.420000f, -0.675000f, -0.750000f, + 0.750000f, -0.675000f, -0.420000f, + -0.800000f, 0.262500f, -0.000000f, + -0.800000f, 0.262500f, 0.150000f, + -0.750000f, 0.375000f, 0.150000f, + -0.750000f, 0.375000f, -0.000000f, + -1.150000f, 0.262500f, -0.000000f, + -1.150000f, 0.262500f, 0.150000f, + -1.250000f, 0.375000f, 0.150000f, + -1.250000f, 0.375000f, -0.000000f, + -1.350000f, 0.262500f, -0.000000f, + -1.350000f, 0.262500f, 0.150000f, + -1.500000f, 0.375000f, 0.150000f, + -1.500000f, 0.375000f, -0.000000f, + -1.350000f, 0.150000f, -0.000000f, + -1.350000f, 0.150000f, 0.150000f, + -1.500000f, 0.150000f, 0.150000f, + -1.500000f, 0.150000f, -0.000000f, + -0.750000f, 0.375000f, -0.150000f, + -0.800000f, 0.262500f, -0.150000f, + -1.250000f, 0.375000f, -0.150000f, + -1.150000f, 0.262500f, -0.150000f, + -1.500000f, 0.375000f, -0.150000f, + -1.350000f, 0.262500f, -0.150000f, + -1.500000f, 0.150000f, -0.150000f, + -1.350000f, 0.150000f, -0.150000f, + -1.350000f, 0.037500f, -0.000000f, + -1.350000f, 0.037500f, 0.150000f, + -1.500000f, -0.075000f, 0.150000f, + -1.500000f, -0.075000f, -0.000000f, + -1.250000f, -0.187500f, -0.000000f, + -1.250000f, -0.187500f, 0.150000f, + -1.325000f, -0.281250f, 0.150000f, + -1.325000f, -0.281250f, -0.000000f, + -1.000000f, -0.300000f, 0.150000f, + -0.950000f, -0.450000f, 0.150000f, + -0.950000f, -0.450000f, -0.000000f, + -1.500000f, -0.075000f, -0.150000f, + -1.350000f, 0.037500f, -0.150000f, + -1.325000f, -0.281250f, -0.150000f, + -1.250000f, -0.187500f, -0.150000f, + -0.950000f, -0.450000f, -0.150000f, + -1.000000f, -0.300000f, -0.150000f, + 0.850000f, -0.037500f, -0.000000f, + 0.850000f, -0.037500f, 0.330000f, + 0.850000f, -0.450000f, 0.330000f, + 0.850000f, -0.450000f, -0.000000f, + 1.300000f, -0.037500f, -0.000000f, + 1.300000f, -0.037500f, 0.330000f, + 1.550000f, -0.337500f, 0.330000f, + 1.550000f, -0.337500f, -0.000000f, + 1.150000f, 0.300000f, -0.000000f, + 1.150000f, 0.300000f, 0.125000f, + 1.200000f, 0.262500f, 0.125000f, + 1.200000f, 0.262500f, -0.000000f, + 1.350000f, 0.450000f, -0.000000f, + 1.350000f, 0.450000f, 0.125000f, + 1.650000f, 0.450000f, 0.125000f, + 1.650000f, 0.450000f, -0.000000f, + 0.850000f, -0.450000f, -0.330000f, + 0.850000f, -0.037500f, -0.330000f, + 1.550000f, -0.337500f, -0.330000f, + 1.300000f, -0.037500f, -0.330000f, + 1.200000f, 0.262500f, -0.125000f, + 1.150000f, 0.300000f, -0.125000f, + 1.650000f, 0.450000f, -0.125000f, + 1.350000f, 0.450000f, -0.125000f, + 1.400000f, 0.487500f, -0.000000f, + 1.400000f, 0.487500f, 0.125000f, + 1.762500f, 0.496875f, 0.125000f, + 1.762500f, 0.496875f, -0.000000f, + 1.450000f, 0.487500f, -0.000000f, + 1.450000f, 0.487500f, 0.075000f, + 1.725000f, 0.506250f, 0.075000f, + 1.725000f, 0.506250f, -0.000000f, + 1.400000f, 0.450000f, -0.000000f, + 1.400000f, 0.450000f, 0.075000f, + 1.600000f, 0.450000f, 0.075000f, + 1.600000f, 0.450000f, -0.000000f, + 1.762500f, 0.496875f, -0.125000f, + 1.400000f, 0.487500f, -0.125000f, + 1.725000f, 0.506250f, -0.075000f, + 1.450000f, 0.487500f, -0.075000f, + 1.600000f, 0.450000f, -0.075000f, + 1.400000f, 0.450000f, -0.075000f, + 0.000000f, 0.825000f, -0.000000f, + 0.000000f, 0.825000f, 0.001000f, + 0.001000f, 0.825000f, -0.000000f, + 0.400000f, 0.825000f, -0.000000f, + 0.400000f, 0.825000f, 0.225000f, + 0.225000f, 0.825000f, 0.400000f, + 0.000000f, 0.825000f, 0.400000f, + 0.000000f, 0.675000f, -0.000000f, + 0.100000f, 0.600000f, -0.000000f, + 0.100000f, 0.600000f, 0.056000f, + 0.056000f, 0.600000f, 0.100000f, + 0.000000f, 0.600000f, 0.100000f, + -0.001000f, 0.825000f, -0.000000f, + -0.225000f, 0.825000f, 0.400000f, + -0.400000f, 0.825000f, 0.225000f, + -0.400000f, 0.825000f, -0.000000f, + -0.056000f, 0.600000f, 0.100000f, + -0.100000f, 0.600000f, 0.056000f, + -0.100000f, 0.600000f, -0.000000f, + 0.000000f, 0.825000f, -0.001000f, + -0.400000f, 0.825000f, -0.225000f, + -0.225000f, 0.825000f, -0.400000f, + 0.000000f, 0.825000f, -0.400000f, + -0.100000f, 0.600000f, -0.056000f, + -0.056000f, 0.600000f, -0.100000f, + 0.000000f, 0.600000f, -0.100000f, + 0.225000f, 0.825000f, -0.400000f, + 0.400000f, 0.825000f, -0.225000f, + 0.056000f, 0.600000f, -0.100000f, + 0.100000f, 0.600000f, -0.056000f, + 0.200000f, 0.525000f, -0.000000f, + 0.200000f, 0.525000f, 0.112000f, + 0.112000f, 0.525000f, 0.200000f, + 0.000000f, 0.525000f, 0.200000f, + 0.650000f, 0.525000f, -0.000000f, + 0.650000f, 0.525000f, 0.364000f, + 0.364000f, 0.525000f, 0.650000f, + 0.000000f, 0.525000f, 0.650000f, + 0.650000f, 0.450000f, -0.000000f, + 0.650000f, 0.450000f, 0.364000f, + 0.364000f, 0.450000f, 0.650000f, + 0.000000f, 0.450000f, 0.650000f, + -0.112000f, 0.525000f, 0.200000f, + -0.200000f, 0.525000f, 0.112000f, + -0.200000f, 0.525000f, -0.000000f, + -0.364000f, 0.525000f, 0.650000f, + -0.650000f, 0.525000f, 0.364000f, + -0.650000f, 0.525000f, -0.000000f, + -0.364000f, 0.450000f, 0.650000f, + -0.650000f, 0.450000f, 0.364000f, + -0.650000f, 0.450000f, -0.000000f, + -0.200000f, 0.525000f, -0.112000f, + -0.112000f, 0.525000f, -0.200000f, + 0.000000f, 0.525000f, -0.200000f, + -0.650000f, 0.525000f, -0.364000f, + -0.364000f, 0.525000f, -0.650000f, + 0.000000f, 0.525000f, -0.650000f, + -0.650000f, 0.450000f, -0.364000f, + -0.364000f, 0.450000f, -0.650000f, + 0.000000f, 0.450000f, -0.650000f, + 0.112000f, 0.525000f, -0.200000f, + 0.200000f, 0.525000f, -0.112000f, + 0.364000f, 0.525000f, -0.650000f, + 0.650000f, 0.525000f, -0.364000f, + 0.364000f, 0.450000f, -0.650000f, + 0.650000f, 0.450000f, -0.364000f, + 0.000000f, -0.750000f, -0.000000f, + 0.750000f, -0.675000f, -0.000000f, + 0.750000f, -0.675000f, -0.420000f, + 0.420000f, -0.675000f, -0.750000f, + 0.000000f, -0.675000f, -0.750000f, + 0.750000f, -0.712500f, -0.000000f, + 0.750000f, -0.712500f, -0.420000f, + 0.420000f, -0.712500f, -0.750000f, + 0.000000f, -0.712500f, -0.750000f, + 0.712500f, -0.750000f, -0.000000f, + 0.712500f, -0.750000f, -0.399000f, + 0.399000f, -0.750000f, -0.712500f, + 0.000000f, -0.750000f, -0.712500f, + -0.420000f, -0.675000f, -0.750000f, + -0.750000f, -0.675000f, -0.420000f, + -0.750000f, -0.675000f, -0.000000f, + -0.420000f, -0.712500f, -0.750000f, + -0.750000f, -0.712500f, -0.420000f, + -0.750000f, -0.712500f, -0.000000f, + -0.399000f, -0.750000f, -0.712500f, + -0.712500f, -0.750000f, -0.399000f, + -0.712500f, -0.750000f, -0.000000f, + -0.750000f, -0.675000f, 0.420000f, + -0.420000f, -0.675000f, 0.750000f, + 0.000000f, -0.675000f, 0.750000f, + -0.750000f, -0.712500f, 0.420000f, + -0.420000f, -0.712500f, 0.750000f, + 0.000000f, -0.712500f, 0.750000f, + -0.712500f, -0.750000f, 0.399000f, + -0.399000f, -0.750000f, 0.712500f, + 0.000000f, -0.750000f, 0.712500f, + 0.420000f, -0.675000f, 0.750000f, + 0.750000f, -0.675000f, 0.420000f, + 0.420000f, -0.712500f, 0.750000f, + 0.750000f, -0.712500f, 0.420000f, + 0.399000f, -0.750000f, 0.712500f, + 0.712500f, -0.750000f, 0.399000f +}; + +static ushort const teapotPatchData[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 3, 16, 17, 18, 7, 19, 20, 21, 11, 22, 23, 24, 15, 25, 26, 27, + 18, 28, 29, 30, 21, 31, 32, 33, 24, 34, 35, 36, 27, 37, 38, 39, + 30, 40, 41, 0, 33, 42, 43, 4, 36, 44, 45, 8, 39, 46, 47, 12, + 12, 13, 14, 15, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 15, 25, 26, 27, 51, 60, 61, 62, 55, 63, 64, 65, 59, 66, 67, 68, + 27, 37, 38, 39, 62, 69, 70, 71, 65, 72, 73, 74, 68, 75, 76, 77, + 39, 46, 47, 12, 71, 78, 79, 48, 74, 80, 81, 52, 77, 82, 83, 56, + 56, 57, 58, 59, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 59, 66, 67, 68, 87, 96, 97, 98, 91, 99, 100, 101, 95, 102, 103, 104, + 68, 75, 76, 77, 98, 105, 106, 107, 101, 108, 109, 110, 104, 111, 112, 113, + 77, 82, 83, 56, 107, 114, 115, 84, 110, 116, 117, 88, 113, 118, 119, 92, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, + 123, 136, 137, 120, 127, 138, 139, 124, 131, 140, 141, 128, 135, 142, 143, 132, + 132, 133, 134, 135, 144, 145, 146, 147, 148, 149, 150, 151, 68, 152, 153, 154, + 135, 142, 143, 132, 147, 155, 156, 144, 151, 157, 158, 148, 154, 159, 160, 68, + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, + 164, 177, 178, 161, 168, 179, 180, 165, 172, 181, 182, 169, 176, 183, 184, 173, + 173, 174, 175, 176, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, + 176, 183, 184, 173, 188, 197, 198, 185, 192, 199, 200, 189, 196, 201, 202, 193, + 203, 203, 203, 203, 206, 207, 208, 209, 210, 210, 210, 210, 211, 212, 213, 214, + 203, 203, 203, 203, 209, 216, 217, 218, 210, 210, 210, 210, 214, 219, 220, 221, + 203, 203, 203, 203, 218, 223, 224, 225, 210, 210, 210, 210, 221, 226, 227, 228, + 203, 203, 203, 203, 225, 229, 230, 206, 210, 210, 210, 210, 228, 231, 232, 211, + 211, 212, 213, 214, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 214, 219, 220, 221, 236, 245, 246, 247, 240, 248, 249, 250, 244, 251, 252, 253, + 221, 226, 227, 228, 247, 254, 255, 256, 250, 257, 258, 259, 253, 260, 261, 262, + 228, 231, 232, 211, 256, 263, 264, 233, 259, 265, 266, 237, 262, 267, 268, 241, + 269, 269, 269, 269, 278, 279, 280, 281, 274, 275, 276, 277, 270, 271, 272, 273, + 269, 269, 269, 269, 281, 288, 289, 290, 277, 285, 286, 287, 273, 282, 283, 284, + 269, 269, 269, 269, 290, 297, 298, 299, 287, 294, 295, 296, 284, 291, 292, 293, + 269, 269, 269, 269, 299, 304, 305, 278, 296, 302, 303, 274, 293, 300, 301, 270 +}; + +#endif diff --git a/src/threed/geometry/qlogicalvertex.cpp b/src/threed/geometry/qlogicalvertex.cpp new file mode 100644 index 000000000..e1f892c3d --- /dev/null +++ b/src/threed/geometry/qlogicalvertex.cpp @@ -0,0 +1,416 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlogicalvertex.h" +#include "qvector_utils_p.h" + +#include <QtCore/qdebug.h> + +/*! + \class QLogicalVertex + \brief The QLogicalVertex class references QGeometryData at a single vertex. + \since 4.8 + \ingroup qt3d + \ingroup qt3d::geometry + + QLogicalVertex instances are a convenience class for use with + QGeometryData. A QLogicalVertex simply references through to the data + in a QGeometryData for a particular vertex, providing accessors to fetch + position, texture coordinates, and other values. + + Create a QLogicalVertex referring to a particular QGeometryData instance: + \code + QGeometryData data; + data.appendVertex(QVector3D(1, 2, 3)); + + // construct a QLogicalVertex referring to the first vertex in data + // the QGeometryData is implicitly shared with lv + QLogicalVertex lv(data, 0); + // lv.vertex() == QVector3D(1, 2, 3) + \endcode + This is inexpensive and no new storage is allocated for the actual data, + just the reference and index. + + With logical vertices instances referencing large QGeometryData instances, + avoid modifying the instance: + \code + // careful - assigning to a QLogicalVertex which refers to an external + // QGeometryData will result in a possibly expensive copy-on-write + lv.setVertex(3, 2, 1); + \endcode + + Create a QLogicalVertex with its own QGeometryData internally: + \code + QLogicalVertex lv; + // no copy on write here - internal QGeometryData is not shared + lv.setVertex(1, 2, 3); + \endcode + + Assign an instance of QLogicalVertex: + \code + QLogicalVertex lv2; + lv2 = data.logicalVertexAt(0); + \endcode + Although lv2 gets its own internal QGeometryData which is then immediately + thrown away by the assignment, because of lazy initialization in + QGeometryData the cost is negligible. + + Use the fields() and hasField() functions to determine if a particular + field is present in the vertex. Accessing non-existent data will cause + an assert in debug mode (from the underlying QArray), and give + undefined behaviour in release mode. + + \sa QGeometryData, QGLBuilder +*/ + +/*! + \fn QLogicalVertex::QLogicalVertex() + Constructs a new invalid QLogicalVertex which has no data. +*/ + +/*! + \fn QLogicalVertex::QLogicalVertex(QGeometryData data, int index) + Constructs a new QLogicalVertex referencing the \a data at \a index. + Note that if this QLogicalVertex is modified, by calling vertex() or + setNormal() for example, then a copy-on-write for \a data will be + triggered. +*/ + +/*! + \fn QLogicalVertex::QLogicalVertex(const QVector3D &a) + Constructs a new QLogicalVertex with a vertex set to \a a. +*/ + +/*! + \fn QLogicalVertex::QLogicalVertex(const QVector3D &a, const QVector3D &n); + Constructs a new QLogicalVertex with a vertex set to \a a, and normal set to \a n. +*/ + +/*! + \fn QLogicalVertex::QLogicalVertex(const QVector3D &a, const QVector3D &n, const QVector2D &t) + Constructs a new QLogicalVertex with its vertex value set to \a a, normal set + to \a n, and texture set to \a t. By default \a n is the null QVector3D, + and \a t is the InvalidTexCoord. If \a n is null then hasType(QLogicalVertex::Normal) + will return false. Likewise if \a t is the InvalidTexCoord then + hasType(QLogicalVertex::Texture) will return false. +*/ + +/*! + \fn QLogicalVertex::QLogicalVertex(const QVector3D &a, QColor4ub color) + Constructs a new QLogicalVertex with its vertex value set to \a a, + color value set to \a color. +*/ + +/*! + \fn QLogicalVertex::~QLogicalVertex() + Destroys this QLogicalVertex reclaiming any resources. +*/ + +/*! + \fn const QVector3D &QLogicalVertex::vertex() const + Returns a const reference to the vertex value for this vertex. +*/ + +/*! + \fn void QLogicalVertex::setVertex(const QVector3D &v) + Sets the vertex value for this vertex to \a v. +*/ + +/*! + \fn QVector3D &QLogicalVertex::vertex() + Returns a modifiable reference to the vertex value. +*/ + +/*! + \fn QLogicalVertex::operator QVector3D () + Returns a copy of the vertex value, by casting as a QVector3D. This + allows passing of a QLogicalVertex to functions that expect a QVector3D. +*/ + +/*! + \fn QVariant QLogicalVertex::attribute(QGL::VertexAttribute field) const + Returns the attribute value for \a field. The \a field defaults + to QGL::CustomVertex0. +*/ + +/*! + \fn void QLogicalVertex::setAttribute(float value, QGL::VertexAttribute field) + Sets the float attribute \a value at \a field. The \a field + defaults to QGL::CustomVertex0. +*/ + +/*! + \fn void QLogicalVertex::setAttribute(const QVector2D &v, QGL::VertexAttribute field) + Sets the QVector2D attribute \a v at \a field. The \a field + defaults to QGL::CustomVertex0. +*/ + +/*! + \fn void QLogicalVertex::setAttribute(const QVector3D &v, QGL::VertexAttribute field) + Sets the QVector3D attribute \a v at \a field. The \a field + defaults to QGL::CustomVertex0. +*/ + +/*! + \fn float &QLogicalVertex::floatAttribute(QGL::VertexAttribute field) + Returns a modifiable reference to the attribute at \a field, which + must be a float. The \a field defaults to QGL::CustomVertex0. +*/ + +/*! + \fn QVector2D &QLogicalVertex::vector2DAttribute(QGL::VertexAttribute field) + Returns a modifiable reference to the attribute at \a field, which + must be a QVector2D. The \a field defaults to QGL::CustomVertex0. +*/ + +/*! + \fn QVector3D &QLogicalVertex::vector3DAttribute(QGL::VertexAttribute field = QGL::CustomVertex0); + Returns a modifiable reference to the attribute at \a field, which + must be a QVector3D. The \a field defaults to QGL::CustomVertex0. +*/ + +/*! + \fn float QLogicalVertex::floatAttribute(QGL::VertexAttribute field) const + Returns the attribute at \a field. The \a field defaults to QGL::CustomVertex0. + The attribute must be a float value. +*/ + +/*! + \fn QVector2D QLogicalVertex::vector2DAttribute(QGL::VertexAttribute field) const + Returns the attribute at \a field. The \a field defaults to QGL::CustomVertex0. + The attribute must be a QVector2D value. +*/ + +/*! + \fn QVector3D QLogicalVertex::vector3DAttribute(QGL::VertexAttribute field) const + Returns the attribute at \a field. The \a field defaults to QGL::CustomVertex0. + The attribute must be a QVector3D value. +*/ + +/*! + \fn QCustomDataArray::ElementType QLogicalVertex::attributeType(QGL::VertexAttribute field) + Returns the element type for the attribute \a field. +*/ + +/*! + \fn const QVector3D &QLogicalVertex::normal() const + Returns a const reference to the normal value for this vertex. +*/ + +/*! + \fn void QLogicalVertex::setNormal(const QVector3D &n) + Sets the normal value for this vertex to \a n. +*/ + +/*! + \fn QVector3D &QLogicalVertex::normal() + Returns a modifiable reference to the normal value for this vertex. +*/ + +/*! + \fn QVector2D QLogicalVertex::texCoord(QGL::VertexAttribute field) const + Returns a copy of the texture coordinate value at \a field for this vertex. + The \a field defaults to QGL::TextureCoord0. +*/ + +/*! + \fn void QLogicalVertex::setTexCoord(const QVector2D &t, QGL::VertexAttribute field) + Sets the texture coordinate at \a field for this vertex to \a t. + The \a field defaults to QGL::TextureCoord0. +*/ + +/*! + \fn QVector2D &QLogicalVertex::texCoordRef(QGL::VertexAttribute field) + Returns a modifiable reference to the texture coordinate for this vertex. + The \a field defaults to QGL::TextureCoord0. +*/ + +/*! + \fn QColor4ub QLogicalVertex::color() const + Returns a const reference to the color value for this vertex. +*/ + +/*! + \fn void QLogicalVertex::setColor(const QColor4ub &c) + Sets the color value for this vertex to \a c. +*/ + +/*! + \fn QColor4ub &QLogicalVertex::colorRef() + Returns a modifiable reference to the color value for this vertex. +*/ + +/*! + \fn bool QLogicalVertex::hasField(QGL::VertexAttribute type) const + Returns true if this vertex has data field \a type, and false otherwise. + + In general check to see if a logical vertex has a particular field + type before attempting to access it. In debug mode accessing a + non-existent field will cause an assert; but in release mode the + behaviour is undefined. +*/ + +/*! + \fn quint32 QLogicalVertex::fields() const + Returns a bit-mask of the fields in this logical vertex. Test the + fields like this: + \code + if (vertex.fields() & QGL::fieldMask(QGL::TextureCoord0)) + tex = vertex.texCoord(); + \endcode + + \sa QGeometryData::fields() +*/ + +/*! + \fn int QLogicalVertex::index() const + Returns the index at which this logical vertex's data is located in + its associated QGeometryData; or -1 if this vertex is null. +*/ + +/*! + \fn QGeometryData QLogicalVertex::data() const + Returns a copy of the QGeometryData underlying this vertex. Note that + the copy is not expensive in terms of performance due to implicit sharing + unless the copy is modified (causing a copy-on-write). + + \sa QLogicalVertex::index() +*/ + +/*! + \fn QGeometryData data() const + Returns a copy of the QGeometryData associated with this vertex. Note + that as long as the copy is not modified, this method is not expensive. +*/ + +/*! + \fn bool QLogicalVertex::isNull() const + Returns true if this vertex is null. + + \sa QLogicalVertex() +*/ + +/*! + Returns true if \a rhs has exactly the same fields as this logical + vertex, and each of those are equal to the corresponding field of the \a rhs. + + If either are null, then false is returned. +*/ +bool QLogicalVertex::operator==(const QLogicalVertex &rhs) const +{ + if (isNull() || rhs.isNull()) + return false; + if (this == &rhs) + return true; + if (m_data.fields() != rhs.fields()) + return false; + const quint32 mask = 0x01; + quint32 fields = m_data.fields(); + for (int field = 0; fields; ++field, fields >>= 1) + { + if (mask & fields) + { + QGL::VertexAttribute attr = static_cast<QGL::VertexAttribute>(field); + if (attr < QGL::TextureCoord0) + { + if (attr == QGL::Position) + { + if (!qFskCompare(vertex(), rhs.vertex())) + return false; + } + else if (attr == QGL::Normal) + { + if (!qFskCompare(normal(), rhs.normal())) + return false; + } + else + { + if (color() != rhs.color()) + return false; + } + } + else if (attr < QGL::CustomVertex0) + { + if (!qFskCompare(texCoord(attr), rhs.texCoord(attr))) + return false; + } + else + { + if (attribute(attr) != rhs.attribute(attr)) + return false; + } + } + } + return true; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QLogicalVertex &lv) +{ + dbg.nospace(); + dbg << "QLogicalVertex("; + if (lv.isNull()) + { + dbg << " NULL"; + } + else + { + if (lv.hasField(QGL::Position)) + dbg << "V:" << QVector3D(lv.vertex()); + else + dbg << " (No Vertex)"; + if (lv.hasField(QGL::Normal)) + dbg << "N:" << QVector3D(lv.normal()); + else + dbg << " (No Normal)"; + if (lv.hasField(QGL::TextureCoord0)) + dbg << "T:" << QVector2D(lv.texCoord()); + else + dbg << " (No Texture)"; + if (lv.hasField(QGL::Color)) + dbg << "C:" << QColor4ub(lv.color()); + else + dbg << " (No Color)"; + } + dbg << " )"; + return dbg.space(); +} +#endif diff --git a/src/threed/geometry/qlogicalvertex.h b/src/threed/geometry/qlogicalvertex.h new file mode 100644 index 000000000..304aa9b05 --- /dev/null +++ b/src/threed/geometry/qlogicalvertex.h @@ -0,0 +1,328 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOGICALVERTEX_H +#define QLOGICALVERTEX_H + +#include "qgeometrydata.h" +#include "qcustomdataarray.h" + +QT_BEGIN_NAMESPACE + +// uncomment this to perform heavy checking of QLogicalVertex +// #define QT3D_DEBUG_QLOGICALVERTEX 1 + +class QLogicalVertex +{ +public: + inline QLogicalVertex(); + inline QLogicalVertex(QGeometryData data, int index); + inline QLogicalVertex(const QVector3D &a); + inline QLogicalVertex(const QVector3D &a, const QVector3D &n); + inline QLogicalVertex(const QVector3D &a, const QVector3D &n, const QVector2D &t); + inline QLogicalVertex(const QVector3D &a, QColor4ub color); + ~QLogicalVertex() {} + + inline const QVector3D &vertex() const; + inline void setVertex(const QVector3D &v); + inline QVector3D &vertex(); + + operator QVector3D () { return vertex(); } + + inline QVariant attribute(QGL::VertexAttribute field = QGL::CustomVertex0) const; + inline void setAttribute(float value, QGL::VertexAttribute attr); + inline void setAttribute(const QVector2D &v, QGL::VertexAttribute field = QGL::CustomVertex0); + inline void setAttribute(const QVector3D &v, QGL::VertexAttribute field = QGL::CustomVertex0); + inline float &floatAttribute(QGL::VertexAttribute field = QGL::CustomVertex0); + inline QVector2D &vector2DAttribute(QGL::VertexAttribute field = QGL::CustomVertex0); + inline QVector3D &vector3DAttribute(QGL::VertexAttribute field = QGL::CustomVertex0); + inline float floatAttribute(QGL::VertexAttribute field = QGL::CustomVertex0) const; + inline QVector2D vector2DAttribute(QGL::VertexAttribute field = QGL::CustomVertex0) const; + inline QVector3D vector3DAttribute(QGL::VertexAttribute field = QGL::CustomVertex0) const; + inline QCustomDataArray::ElementType attributeType(QGL::VertexAttribute field = QGL::CustomVertex0); + + inline const QVector3D &normal() const; + inline void setNormal(const QVector3D &n); + inline QVector3D &normal(); + + inline const QColor4ub &color() const; + inline void setColor(const QColor4ub &c); + inline QColor4ub &colorRef(); + + inline const QVector2D &texCoord(QGL::VertexAttribute attr = QGL::TextureCoord0) const; + inline void setTexCoord(const QVector2D &t, QGL::VertexAttribute attr = QGL::TextureCoord0); + inline QVector2D &texCoordRef(QGL::VertexAttribute attr = QGL::TextureCoord0); + + inline bool hasField(QGL::VertexAttribute type) const; + inline quint32 fields() const; + inline int index() const; + inline QGeometryData data() const; + inline bool isNull() const; + + bool operator==(const QLogicalVertex &rhs) const; + +private: + QGeometryData m_data; + int m_index; +}; + +inline QLogicalVertex::QLogicalVertex() + : m_index(-1) +{ +} + +inline QLogicalVertex::QLogicalVertex(QGeometryData data, int index) + : m_data(data) + , m_index(index) +{ + Q_ASSERT(index < data.count()); +#ifdef QT3D_DEBUG_QLOGICALVERTEX + data.check(); +#endif +} + +inline QLogicalVertex::QLogicalVertex(const QVector3D &a) + : m_index(0) +{ + m_data.appendVertex(a); +} + +inline QLogicalVertex::QLogicalVertex(const QVector3D &a, const QVector3D &n) + : m_index(0) +{ + m_data.appendVertex(a); + m_data.appendNormal(n); +} + +inline QLogicalVertex::QLogicalVertex(const QVector3D &a, const QVector3D &n, const QVector2D &t) + : m_index(0) +{ + m_data.appendVertex(a); + m_data.appendNormal(n); + m_data.appendTexCoord(t); +} + +inline QLogicalVertex::QLogicalVertex(const QVector3D &a, QColor4ub color) + : m_index(0) +{ + m_data.appendVertex(a); + m_data.appendColor(color); +} + +inline const QVector3D &QLogicalVertex::vertex() const +{ + return m_data.vertexAt(m_index); +} + +inline void QLogicalVertex::setVertex(const QVector3D &v) +{ + if (m_index == -1) + m_index = 0; + if (m_index == m_data.count(QGL::Position)) + m_data.appendVertex(v); + else + m_data.vertex(m_index) = v; +} + +inline QVector3D &QLogicalVertex::vertex() +{ + return m_data.vertex(m_index); +} + +inline QVariant QLogicalVertex::attribute(QGL::VertexAttribute attr) const +{ + return m_data.attributes(attr).at(m_index); +} + +inline void QLogicalVertex::setAttribute(float v, QGL::VertexAttribute attr) +{ + if (m_index == -1) + m_index = 0; + if (m_index == m_data.count(attr)) + m_data.appendAttribute(v, attr); + else + m_data.floatAttribute(m_index, attr) = v; +} + +inline void QLogicalVertex::setAttribute(const QVector2D &v, QGL::VertexAttribute attr) +{ + if (m_index == -1) + m_index = 0; + if (m_index == m_data.count(attr)) + m_data.appendAttribute(v, attr); + else + m_data.vector2DAttribute(m_index, attr) = v; +} + +inline void QLogicalVertex::setAttribute(const QVector3D &v, QGL::VertexAttribute attr) +{ + if (m_index == -1) + m_index = 0; + if (m_index == m_data.count(attr)) + m_data.appendAttribute(v, attr); + else + m_data.vector3DAttribute(m_index, attr) = v; +} + +inline float &QLogicalVertex::floatAttribute(QGL::VertexAttribute field) +{ + return m_data.floatAttribute(m_index, field); +} + +inline QVector2D &QLogicalVertex::vector2DAttribute(QGL::VertexAttribute field) +{ + return m_data.vector2DAttribute(m_index, field); +} + +inline QVector3D &QLogicalVertex::vector3DAttribute(QGL::VertexAttribute field) +{ + + return m_data.vector3DAttribute(m_index, field); +} + +inline float QLogicalVertex::floatAttribute(QGL::VertexAttribute field) const +{ + return m_data.floatAttributeAt(m_index, field); +} + +inline QVector2D QLogicalVertex::vector2DAttribute(QGL::VertexAttribute field) const +{ + return m_data.vector2DAttributeAt(m_index, field); +} + +inline QVector3D QLogicalVertex::vector3DAttribute(QGL::VertexAttribute field) const +{ + return m_data.vector3DAttributeAt(m_index, field); +} + +inline QCustomDataArray::ElementType QLogicalVertex::attributeType(QGL::VertexAttribute field) +{ + return m_data.attributes(field).elementType(); +} + +inline const QVector3D &QLogicalVertex::normal() const +{ + return m_data.normalAt(m_index); +} + +inline void QLogicalVertex::setNormal(const QVector3D &n) +{ + if (m_index == -1) + m_index = 0; + if (m_index == m_data.count(QGL::Normal)) + m_data.appendNormal(n); + else + m_data.normal(m_index) = n; +} + +inline QVector3D &QLogicalVertex::normal() +{ + return m_data.normal(m_index); +} + +inline const QVector2D &QLogicalVertex::texCoord(QGL::VertexAttribute attr) const +{ + return m_data.texCoordAt(m_index, attr); +} + +inline void QLogicalVertex::setTexCoord(const QVector2D &t, QGL::VertexAttribute attr) +{ + Q_ASSERT(attr >= QGL::TextureCoord0 && attr <= QGL::TextureCoord2); + if (m_index == -1) + m_index = 0; + if (m_index == m_data.count(attr)) + m_data.appendTexCoord(t, attr); + else + m_data.texCoord(m_index, attr) = t; +} + +inline QVector2D &QLogicalVertex::texCoordRef(QGL::VertexAttribute attr) +{ + return m_data.texCoord(m_index, attr); +} + +inline const QColor4ub &QLogicalVertex::color() const +{ + return m_data.colorAt(m_index); +} + +inline void QLogicalVertex::setColor(const QColor4ub &c) +{ + if (m_index == -1) + m_index = 0; + if (m_index == m_data.count(QGL::Color)) + m_data.appendColor(c); + else + m_data.color(m_index) = c; +} + +inline QColor4ub &QLogicalVertex::colorRef() +{ + return m_data.color(m_index); +} + +inline bool QLogicalVertex::hasField(QGL::VertexAttribute attr) const +{ + return m_data.hasField(attr); +} + +inline quint32 QLogicalVertex::fields() const +{ + return m_data.fields(); +} + +inline int QLogicalVertex::index() const +{ + return m_index; +} + +inline bool QLogicalVertex::isNull() const +{ + return (m_index == -1); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QLogicalVertex §ion); +#endif + +QT_END_NAMESPACE + +#endif // QLOGICALVERTEX_H diff --git a/src/threed/geometry/qvector_utils_p.h b/src/threed/geometry/qvector_utils_p.h new file mode 100644 index 000000000..64b436226 --- /dev/null +++ b/src/threed/geometry/qvector_utils_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtQuick3D module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QVECTOR_UTILS_P_H +#define QVECTOR_UTILS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtGui/qvector3d.h> +#include <QtGui/qvector2d.h> + +// Replacement for qFuzzyCompare(QVector3D, QVector3D) and friends, +// for a specific case where the results are going to be rendered +// by the GPU onto a display. +// +// The accuracy of this comparison should not change. Especially it +// should not change when you go from doubles to floats or when +// you get close to zero. If two verts or lighting normals are +// the same to within 5 places of floating point accuracy then they +// will dislay as being on top of each other. +// +// Also this avoids doing 3 floating point multiplications every time +// since the normal qFuzzyIsNull does a mul to scale the epsilon when +// close to zero. + +inline bool qFskIsNull(double d) +{ + return qAbs(d) <= 0.00001; +} + +inline bool qFskIsNull(float f) +{ + return qAbs(f) <= 0.00001f; +} + +inline bool qFskCompare(float a, float b) +{ + return qFskIsNull(a - b); +} + +inline bool qFskCompare(double a, double b) +{ + return qFskIsNull(a - b); +} + +inline bool qFskCompare(const QVector3D &a, const QVector3D &b) +{ + return ( + qFskIsNull(a.x() - b.x()) && + qFskIsNull(a.y() - b.y()) && + qFskIsNull(a.z() - b.z()) + ); +} + +inline bool qFskCompare(const QVector2D &a, const QVector2D &b) +{ + return ( + qFskIsNull(a.x() - b.x()) && + qFskIsNull(a.y() - b.y()) + ); +} + +#endif // QVECTOR_UTILS_P_H |