9#include <QtGui/private/qtriangulator_p.h>
12#if QT_CONFIG(concurrent)
13#include <QtConcurrentRun>
42 path.setFillRule(Qt::WindingFill);
43 path.addText(0, 0, font, text);
44 QList<QPolygonF> polygons = path.toSubpathPolygons(QTransform().scale(1., -1.));
50 const size_t prevNumIndices = result.indices.size();
53 path = QPainterPath();
54 path.setFillRule(Qt::WindingFill);
55 for (QPolygonF &p : polygons)
59 QPolylineSet polylines = qPolyline(path);
60 std::vector<ExtrudedTextGeometry::IndexType> tmpIndices;
61 tmpIndices.resize(size_t(polylines.indices.size()));
62 memcpy(tmpIndices.data(), polylines.indices.data(), size_t(polylines.indices.size()) *
sizeof(ExtrudedTextGeometry::IndexType));
65 for (
const ExtrudedTextGeometry::IndexType idx : tmpIndices) {
66 if (idx == std::numeric_limits<ExtrudedTextGeometry::IndexType>::max()) {
67 const int endOutline = lastIndex;
68 result.outlines.push_back({beginOutline, endOutline});
69 beginOutline = endOutline;
71 result.outlineIndices.push_back(idx);
78 transform.scale(scale, scale);
79 const QTriangleSet triangles = qTriangulate(path, transform);
82 result.indices.resize(result.indices.size() + size_t(triangles.indices.size()));
83 memcpy(&result.indices[prevNumIndices], triangles.indices.data(), size_t(triangles.indices.size()) *
sizeof(ExtrudedTextGeometry::IndexType));
84 for (size_t i = prevNumIndices, m = result.indices.size(); i < m; ++i)
85 result.indices[i] += ExtrudedTextGeometry::IndexType(result.vertices.size());
88 result.vertices.reserve(size_t(triangles.vertices.size()) / 2);
89 for (qsizetype i = 0, m = triangles.vertices.size(); i < m; i += 2)
90 result.vertices.push_back(QVector3D(triangles.vertices[i] / font.pointSizeF(), triangles.vertices[i + 1] / font.pointSizeF(), 0.0f));
95inline QVector3D mix(
const QVector3D &a,
const QVector3D &b,
float ratio)
97 return a + (b - a) * ratio;
104
105
106
107
108
109
110
111
112
113
114
115
116
119
120
121
122
125
126
127
128
129
130
131
132
135
136
137
138
141
142
143
144
145
148
149
150
151
154
155
156
157
158
159
160
161
162
163
166ExtrudedTextGeometry::ExtrudedTextGeometry(QQuick3DObject *parent)
167 : QQuick3DGeometry(parent)
169#if QT_CONFIG(concurrent)
170 connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished,
this, &ExtrudedTextGeometry::requestFinished);
172 scheduleGeometryUpdate();
175ExtrudedTextGeometry::~ExtrudedTextGeometry()
180QString ExtrudedTextGeometry::text()
const
185void ExtrudedTextGeometry::setText(
const QString &newText)
187 if (m_text == newText)
191 scheduleGeometryUpdate();
194QFont ExtrudedTextGeometry::font()
const
199void ExtrudedTextGeometry::setFont(
const QFont &newFont)
201 if (m_font == newFont)
205 scheduleGeometryUpdate();
208float ExtrudedTextGeometry::depth()
const
213void ExtrudedTextGeometry::setDepth(
float newDepth)
215 if (qFuzzyCompare(m_depth, newDepth))
219 scheduleGeometryUpdate();
222float ExtrudedTextGeometry::scale()
const
227void ExtrudedTextGeometry::setScale(
float newScale)
229 if (qFuzzyCompare(m_scale, newScale))
233 scheduleGeometryUpdate();
236bool ExtrudedTextGeometry::asynchronous()
const
238 return m_asynchronous;
241void ExtrudedTextGeometry::setAsynchronous(
bool newAsynchronous)
243 if (m_asynchronous == newAsynchronous)
245 m_asynchronous = newAsynchronous;
246 emit asynchronousChanged();
249ExtrudedTextGeometry::Status ExtrudedTextGeometry::status()
const
254void ExtrudedTextGeometry::doUpdateGeometry()
257 m_geometryUpdateRequested =
false;
259#if QT_CONFIG(concurrent)
260 if (m_geometryDataFuture.isRunning()) {
261 m_pendingAsyncUpdate =
true;
269 if (m_text.isEmpty()) {
275#if QT_CONFIG(concurrent)
277 if (m_asynchronous) {
278 m_geometryDataFuture = QtConcurrent::run(generateExtrudedTextGeometryAsync,
283 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
284 m_status = Status::Loading;
285 Q_EMIT statusChanged();
291 updateGeometry(generateExtrudedTextGeometry(m_text, m_font, m_depth, m_scale));
295void ExtrudedTextGeometry::requestFinished()
297#if QT_CONFIG(concurrent)
298 const auto output = m_geometryDataFuture.takeResult();
299 updateGeometry(output);
303void ExtrudedTextGeometry::scheduleGeometryUpdate()
305 if (!m_geometryUpdateRequested) {
306 QMetaObject::invokeMethod(
this,
"doUpdateGeometry", Qt::QueuedConnection);
307 m_geometryUpdateRequested =
true;
311void ExtrudedTextGeometry::updateGeometry(
const GeometryData &geometryData)
314 setStride(
sizeof(
float) * 6);
315 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
316 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
318 QQuick3DGeometry::Attribute::F32Type);
319 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
321 QQuick3DGeometry::Attribute::F32Type);
322 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
324 QQuick3DGeometry::Attribute::U32Type);
326 setBounds(geometryData.boundsMin, geometryData.boundsMax);
327 setVertexData(geometryData.vertexData);
328 setIndexData(geometryData.indexData);
332 if (m_pendingAsyncUpdate) {
333 m_pendingAsyncUpdate =
false;
334 scheduleGeometryUpdate();
336 m_status = Status::Ready;
337 Q_EMIT statusChanged();
340 emit geometryChanged();
343ExtrudedTextGeometry::GeometryData ExtrudedTextGeometry::generateExtrudedTextGeometry(
const QString &text,
355 std::vector<IndexType> indices;
356 std::vector<Vertex> vertices;
358 TriangulationData data = triangulate(text, font, scale);
360 const IndexType numVertices = IndexType(data.vertices.size());
361 const size_t numIndices = data.indices.size();
363 vertices.reserve(data.vertices.size() * 2);
364 for (QVector3D &v : data.vertices)
365 vertices.push_back({ v, QVector3D(0.0f, 0.0f, -1.0f) });
366 for (QVector3D &v : data.vertices)
367 vertices.push_back({ QVector3D(v.x(), v.y(), depth), QVector3D(0.0f, 0.0f, 1.0f) });
369 int verticesIndex =
int(vertices.size());
370 for (size_t i = 0; i < data.outlines.size(); ++i) {
371 const int begin = data.outlines[i].begin;
372 const int end = data.outlines[i].end;
373 const int verticesIndexBegin = verticesIndex;
378 QVector3D prevNormal = QVector3D::crossProduct(
379 vertices[data.outlineIndices[end - 1] + numVertices].position - vertices[data.outlineIndices[end - 1]].position,
380 vertices[data.outlineIndices[begin]].position - vertices[data.outlineIndices[end - 1]].position).normalized();
382 for (
int j = begin; j < end; ++j) {
383 const bool isLastIndex = (j == end - 1);
384 const IndexType cur = data.outlineIndices[j];
385 const IndexType next = data.outlineIndices[((j - begin + 1) % (end - begin)) + begin];
386 const QVector3D normal = QVector3D::crossProduct(vertices[cur + numVertices].position - vertices[cur].position, vertices[next].position - vertices[cur].position).normalized();
389 const bool smooth = QVector3D::dotProduct(prevNormal, normal) > (90.0f - edgeSplitAngle) / 90.0f;
390 const QVector3D resultNormal = smooth ? mix(prevNormal, normal, 0.5f) : normal;
392 vertices.push_back({vertices[cur].position, prevNormal});
393 vertices.push_back({vertices[cur + numVertices].position, prevNormal});
397 vertices.push_back({vertices[cur].position, resultNormal});
398 vertices.push_back({vertices[cur + numVertices].position, resultNormal});
400 const int v0 = verticesIndex;
401 const int v1 = verticesIndex + 1;
402 const int v2 = isLastIndex ? verticesIndexBegin : verticesIndex + 2;
403 const int v3 = isLastIndex ? verticesIndexBegin + 1 : verticesIndex + 3;
405 indices.push_back(v0);
406 indices.push_back(v1);
407 indices.push_back(v2);
408 indices.push_back(v2);
409 indices.push_back(v1);
410 indices.push_back(v3);
418 const int indicesOffset =
int(indices.size());
419 indices.resize(indices.size() + numIndices * 2);
422 IndexType *indicesFaces = indices.data() + indicesOffset;
423 memcpy(indicesFaces, data.indices.data(), numIndices *
sizeof(IndexType));
426 for (size_t j = 0; j < numIndices; j += 3) {
427 indicesFaces[numIndices + j ] = indicesFaces[j ] + numVertices;
428 indicesFaces[numIndices + j + 1] = indicesFaces[j + 2] + numVertices;
429 indicesFaces[numIndices + j + 2] = indicesFaces[j + 1] + numVertices;
432 for (
const auto &vertex : vertices) {
433 const auto &p = vertex.position;
434 output.boundsMin = QVector3D(qMin(output.boundsMin.x(), p.x()), qMin(output.boundsMin.y(), p.y()), qMin(output.boundsMin.z(), p.z()));
435 output.boundsMax = QVector3D(qMax(output.boundsMax.x(), p.x()), qMax(output.boundsMax.y(), p.y()), qMax(output.boundsMax.z(), p.z()));
439 output.vertexData.resize(vertices.size() *
sizeof(Vertex));
440 memcpy(output.vertexData.data(), vertices.data(), vertices.size() *
sizeof(Vertex));
443 output.indexData.resize(indices.size() *
sizeof(IndexType));
444 memcpy(output.indexData.data(), indices.data(), indices.size() *
sizeof(IndexType));
449#if QT_CONFIG(concurrent)
450void ExtrudedTextGeometry::generateExtrudedTextGeometryAsync(QPromise<GeometryData> &promise,
456 GeometryData output = generateExtrudedTextGeometry(text, font, depth, scale);
457 promise.addResult(output);
Combined button and popup list for selecting options.
QVector3D mix(const QVector3D &a, const QVector3D &b, float ratio)
static float edgeSplitAngle
TriangulationData triangulate(const QString &text, const QFont &font, float scale)
std::vector< QVector3D > vertices
std::vector< Outline > outlines
std::vector< ExtrudedTextGeometry::IndexType > indices
std::vector< ExtrudedTextGeometry::IndexType > outlineIndices