7#include <QtGui/private/qtriangulator_p.h>
10#if QT_CONFIG(concurrent)
11#include <QtConcurrentRun>
40 path.setFillRule(Qt::WindingFill);
41 path.addText(0, 0, font, text);
42 QList<QPolygonF> polygons = path.toSubpathPolygons(QTransform().scale(1., -1.));
48 const size_t prevNumIndices = result.indices.size();
51 path = QPainterPath();
52 path.setFillRule(Qt::WindingFill);
53 for (QPolygonF &p : polygons)
57 QPolylineSet polylines = qPolyline(path);
58 std::vector<ExtrudedTextGeometry::IndexType> tmpIndices;
59 tmpIndices.resize(size_t(polylines.indices.size()));
60 memcpy(tmpIndices.data(), polylines.indices.data(), size_t(polylines.indices.size()) *
sizeof(ExtrudedTextGeometry::IndexType));
63 for (
const ExtrudedTextGeometry::IndexType idx : tmpIndices) {
64 if (idx == std::numeric_limits<ExtrudedTextGeometry::IndexType>::max()) {
65 const int endOutline = lastIndex;
66 result.outlines.push_back({beginOutline, endOutline});
67 beginOutline = endOutline;
69 result.outlineIndices.push_back(idx);
76 transform.scale(scale, scale);
77 const QTriangleSet triangles = qTriangulate(path, transform);
80 result.indices.resize(result.indices.size() + size_t(triangles.indices.size()));
81 memcpy(&result.indices[prevNumIndices], triangles.indices.data(), size_t(triangles.indices.size()) *
sizeof(ExtrudedTextGeometry::IndexType));
82 for (size_t i = prevNumIndices, m = result.indices.size(); i < m; ++i)
83 result.indices[i] += ExtrudedTextGeometry::IndexType(result.vertices.size());
86 result.vertices.reserve(size_t(triangles.vertices.size()) / 2);
87 for (qsizetype i = 0, m = triangles.vertices.size(); i < m; i += 2)
88 result.vertices.push_back(QVector3D(triangles.vertices[i] / font.pointSizeF(), triangles.vertices[i + 1] / font.pointSizeF(), 0.0f));
93inline QVector3D mix(
const QVector3D &a,
const QVector3D &b,
float ratio)
95 return a + (b - a) * ratio;
102
103
104
105
106
107
108
109
110
111
112
113
114
117
118
119
120
123
124
125
126
127
128
129
130
133
134
135
136
139
140
141
142
143
146
147
148
149
152
153
154
155
156
157
158
159
160
161
164ExtrudedTextGeometry::ExtrudedTextGeometry(QQuick3DObject *parent)
165 : QQuick3DGeometry(parent)
167#if QT_CONFIG(concurrent)
168 connect(&m_geometryDataWatcher, &QFutureWatcher<GeometryData>::finished,
this, &ExtrudedTextGeometry::requestFinished);
170 scheduleGeometryUpdate();
173ExtrudedTextGeometry::~ExtrudedTextGeometry()
178QString ExtrudedTextGeometry::text()
const
183void ExtrudedTextGeometry::setText(
const QString &newText)
185 if (m_text == newText)
189 scheduleGeometryUpdate();
192QFont ExtrudedTextGeometry::font()
const
197void ExtrudedTextGeometry::setFont(
const QFont &newFont)
199 if (m_font == newFont)
203 scheduleGeometryUpdate();
206float ExtrudedTextGeometry::depth()
const
211void ExtrudedTextGeometry::setDepth(
float newDepth)
213 if (qFuzzyCompare(m_depth, newDepth))
217 scheduleGeometryUpdate();
220float ExtrudedTextGeometry::scale()
const
225void ExtrudedTextGeometry::setScale(
float newScale)
227 if (qFuzzyCompare(m_scale, newScale))
231 scheduleGeometryUpdate();
234bool ExtrudedTextGeometry::asynchronous()
const
236 return m_asynchronous;
239void ExtrudedTextGeometry::setAsynchronous(
bool newAsynchronous)
241 if (m_asynchronous == newAsynchronous)
243 m_asynchronous = newAsynchronous;
244 emit asynchronousChanged();
247ExtrudedTextGeometry::Status ExtrudedTextGeometry::status()
const
252void ExtrudedTextGeometry::doUpdateGeometry()
255 m_geometryUpdateRequested =
false;
257#if QT_CONFIG(concurrent)
258 if (m_geometryDataFuture.isRunning()) {
259 m_pendingAsyncUpdate =
true;
267 if (m_text.isEmpty()) {
273#if QT_CONFIG(concurrent)
275 if (m_asynchronous) {
276 m_geometryDataFuture = QtConcurrent::run(generateExtrudedTextGeometryAsync,
281 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
282 m_status = Status::Loading;
283 Q_EMIT statusChanged();
289 updateGeometry(generateExtrudedTextGeometry(m_text, m_font, m_depth, m_scale));
293void ExtrudedTextGeometry::requestFinished()
295#if QT_CONFIG(concurrent)
296 const auto output = m_geometryDataFuture.takeResult();
297 updateGeometry(output);
301void ExtrudedTextGeometry::scheduleGeometryUpdate()
303 if (!m_geometryUpdateRequested) {
304 QMetaObject::invokeMethod(
this,
"doUpdateGeometry", Qt::QueuedConnection);
305 m_geometryUpdateRequested =
true;
309void ExtrudedTextGeometry::updateGeometry(
const GeometryData &geometryData)
312 setStride(
sizeof(
float) * 6);
313 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
314 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
316 QQuick3DGeometry::Attribute::F32Type);
317 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
319 QQuick3DGeometry::Attribute::F32Type);
320 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
322 QQuick3DGeometry::Attribute::U32Type);
324 setBounds(geometryData.boundsMin, geometryData.boundsMax);
325 setVertexData(geometryData.vertexData);
326 setIndexData(geometryData.indexData);
330 if (m_pendingAsyncUpdate) {
331 m_pendingAsyncUpdate =
false;
332 scheduleGeometryUpdate();
334 m_status = Status::Ready;
335 Q_EMIT statusChanged();
340ExtrudedTextGeometry::GeometryData ExtrudedTextGeometry::generateExtrudedTextGeometry(
const QString &text,
352 std::vector<IndexType> indices;
353 std::vector<Vertex> vertices;
355 TriangulationData data = triangulate(text, font, scale);
357 const IndexType numVertices = IndexType(data.vertices.size());
358 const size_t numIndices = data.indices.size();
360 vertices.reserve(data.vertices.size() * 2);
361 for (QVector3D &v : data.vertices)
362 vertices.push_back({ v, QVector3D(0.0f, 0.0f, -1.0f) });
363 for (QVector3D &v : data.vertices)
364 vertices.push_back({ QVector3D(v.x(), v.y(), depth), QVector3D(0.0f, 0.0f, 1.0f) });
366 int verticesIndex =
int(vertices.size());
367 for (size_t i = 0; i < data.outlines.size(); ++i) {
368 const int begin = data.outlines[i].begin;
369 const int end = data.outlines[i].end;
370 const int verticesIndexBegin = verticesIndex;
375 QVector3D prevNormal = QVector3D::crossProduct(
376 vertices[data.outlineIndices[end - 1] + numVertices].position - vertices[data.outlineIndices[end - 1]].position,
377 vertices[data.outlineIndices[begin]].position - vertices[data.outlineIndices[end - 1]].position).normalized();
379 for (
int j = begin; j < end; ++j) {
380 const bool isLastIndex = (j == end - 1);
381 const IndexType cur = data.outlineIndices[j];
382 const IndexType next = data.outlineIndices[((j - begin + 1) % (end - begin)) + begin];
383 const QVector3D normal = QVector3D::crossProduct(vertices[cur + numVertices].position - vertices[cur].position, vertices[next].position - vertices[cur].position).normalized();
386 const bool smooth = QVector3D::dotProduct(prevNormal, normal) > (90.0f - edgeSplitAngle) / 90.0f;
387 const QVector3D resultNormal = smooth ? mix(prevNormal, normal, 0.5f) : normal;
389 vertices.push_back({vertices[cur].position, prevNormal});
390 vertices.push_back({vertices[cur + numVertices].position, prevNormal});
394 vertices.push_back({vertices[cur].position, resultNormal});
395 vertices.push_back({vertices[cur + numVertices].position, resultNormal});
397 const int v0 = verticesIndex;
398 const int v1 = verticesIndex + 1;
399 const int v2 = isLastIndex ? verticesIndexBegin : verticesIndex + 2;
400 const int v3 = isLastIndex ? verticesIndexBegin + 1 : verticesIndex + 3;
402 indices.push_back(v0);
403 indices.push_back(v1);
404 indices.push_back(v2);
405 indices.push_back(v2);
406 indices.push_back(v1);
407 indices.push_back(v3);
415 const int indicesOffset =
int(indices.size());
416 indices.resize(indices.size() + numIndices * 2);
419 IndexType *indicesFaces = indices.data() + indicesOffset;
420 memcpy(indicesFaces, data.indices.data(), numIndices *
sizeof(IndexType));
423 for (size_t j = 0; j < numIndices; j += 3) {
424 indicesFaces[numIndices + j ] = indicesFaces[j ] + numVertices;
425 indicesFaces[numIndices + j + 1] = indicesFaces[j + 2] + numVertices;
426 indicesFaces[numIndices + j + 2] = indicesFaces[j + 1] + numVertices;
429 for (
const auto &vertex : vertices) {
430 const auto &p = vertex.position;
431 output.boundsMin = QVector3D(qMin(output.boundsMin.x(), p.x()), qMin(output.boundsMin.y(), p.y()), qMin(output.boundsMin.z(), p.z()));
432 output.boundsMax = QVector3D(qMax(output.boundsMax.x(), p.x()), qMax(output.boundsMax.y(), p.y()), qMax(output.boundsMax.z(), p.z()));
436 output.vertexData.resize(vertices.size() *
sizeof(Vertex));
437 memcpy(output.vertexData.data(), vertices.data(), vertices.size() *
sizeof(Vertex));
440 output.indexData.resize(indices.size() *
sizeof(IndexType));
441 memcpy(output.indexData.data(), indices.data(), indices.size() *
sizeof(IndexType));
446#if QT_CONFIG(concurrent)
447void ExtrudedTextGeometry::generateExtrudedTextGeometryAsync(QPromise<GeometryData> &promise,
453 GeometryData output = generateExtrudedTextGeometry(text, font, depth, scale);
454 promise.addResult(output);
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