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)
278 if (m_asynchronous && QT_CONFIG(thread)) {
279 m_geometryDataFuture = QtConcurrent::run(generateExtrudedTextGeometryAsync,
284 m_geometryDataWatcher.setFuture(m_geometryDataFuture);
285 m_status = Status::Loading;
286 Q_EMIT statusChanged();
292 updateGeometry(generateExtrudedTextGeometry(m_text, m_font, m_depth, m_scale));
296void ExtrudedTextGeometry::requestFinished()
298#if QT_CONFIG(concurrent)
299 const auto output = m_geometryDataFuture.takeResult();
300 updateGeometry(output);
304void ExtrudedTextGeometry::scheduleGeometryUpdate()
306 if (!m_geometryUpdateRequested) {
307 QMetaObject::invokeMethod(
this,
"doUpdateGeometry", Qt::QueuedConnection);
308 m_geometryUpdateRequested =
true;
312void ExtrudedTextGeometry::updateGeometry(
const GeometryData &geometryData)
315 setStride(
sizeof(
float) * 6);
316 setPrimitiveType(QQuick3DGeometry::PrimitiveType::Triangles);
317 addAttribute(QQuick3DGeometry::Attribute::PositionSemantic,
319 QQuick3DGeometry::Attribute::F32Type);
320 addAttribute(QQuick3DGeometry::Attribute::NormalSemantic,
322 QQuick3DGeometry::Attribute::F32Type);
323 addAttribute(QQuick3DGeometry::Attribute::IndexSemantic,
325 QQuick3DGeometry::Attribute::U32Type);
327 setBounds(geometryData.boundsMin, geometryData.boundsMax);
328 setVertexData(geometryData.vertexData);
329 setIndexData(geometryData.indexData);
333 if (m_pendingAsyncUpdate) {
334 m_pendingAsyncUpdate =
false;
335 scheduleGeometryUpdate();
337 m_status = Status::Ready;
338 Q_EMIT statusChanged();
341 emit geometryChanged();
344ExtrudedTextGeometry::GeometryData ExtrudedTextGeometry::generateExtrudedTextGeometry(
const QString &text,
356 std::vector<IndexType> indices;
357 std::vector<Vertex> vertices;
359 TriangulationData data = triangulate(text, font, scale);
361 const IndexType numVertices = IndexType(data.vertices.size());
362 const size_t numIndices = data.indices.size();
364 vertices.reserve(data.vertices.size() * 2);
365 for (QVector3D &v : data.vertices)
366 vertices.push_back({ v, QVector3D(0.0f, 0.0f, -1.0f) });
367 for (QVector3D &v : data.vertices)
368 vertices.push_back({ QVector3D(v.x(), v.y(), depth), QVector3D(0.0f, 0.0f, 1.0f) });
370 int verticesIndex =
int(vertices.size());
371 for (size_t i = 0; i < data.outlines.size(); ++i) {
372 const int begin = data.outlines[i].begin;
373 const int end = data.outlines[i].end;
374 const int verticesIndexBegin = verticesIndex;
379 QVector3D prevNormal = QVector3D::crossProduct(
380 vertices[data.outlineIndices[end - 1] + numVertices].position - vertices[data.outlineIndices[end - 1]].position,
381 vertices[data.outlineIndices[begin]].position - vertices[data.outlineIndices[end - 1]].position).normalized();
383 for (
int j = begin; j < end; ++j) {
384 const bool isLastIndex = (j == end - 1);
385 const IndexType cur = data.outlineIndices[j];
386 const IndexType next = data.outlineIndices[((j - begin + 1) % (end - begin)) + begin];
387 const QVector3D normal = QVector3D::crossProduct(vertices[cur + numVertices].position - vertices[cur].position, vertices[next].position - vertices[cur].position).normalized();
390 const bool smooth = QVector3D::dotProduct(prevNormal, normal) > (90.0f - edgeSplitAngle) / 90.0f;
391 const QVector3D resultNormal = smooth ? mix(prevNormal, normal, 0.5f) : normal;
393 vertices.push_back({vertices[cur].position, prevNormal});
394 vertices.push_back({vertices[cur + numVertices].position, prevNormal});
398 vertices.push_back({vertices[cur].position, resultNormal});
399 vertices.push_back({vertices[cur + numVertices].position, resultNormal});
401 const int v0 = verticesIndex;
402 const int v1 = verticesIndex + 1;
403 const int v2 = isLastIndex ? verticesIndexBegin : verticesIndex + 2;
404 const int v3 = isLastIndex ? verticesIndexBegin + 1 : verticesIndex + 3;
406 indices.push_back(v0);
407 indices.push_back(v1);
408 indices.push_back(v2);
409 indices.push_back(v2);
410 indices.push_back(v1);
411 indices.push_back(v3);
419 const int indicesOffset =
int(indices.size());
420 indices.resize(indices.size() + numIndices * 2);
423 IndexType *indicesFaces = indices.data() + indicesOffset;
424 memcpy(indicesFaces, data.indices.data(), numIndices *
sizeof(IndexType));
427 for (size_t j = 0; j < numIndices; j += 3) {
428 indicesFaces[numIndices + j ] = indicesFaces[j ] + numVertices;
429 indicesFaces[numIndices + j + 1] = indicesFaces[j + 2] + numVertices;
430 indicesFaces[numIndices + j + 2] = indicesFaces[j + 1] + numVertices;
433 for (
const auto &vertex : vertices) {
434 const auto &p = vertex.position;
435 output.boundsMin = QVector3D(qMin(output.boundsMin.x(), p.x()), qMin(output.boundsMin.y(), p.y()), qMin(output.boundsMin.z(), p.z()));
436 output.boundsMax = QVector3D(qMax(output.boundsMax.x(), p.x()), qMax(output.boundsMax.y(), p.y()), qMax(output.boundsMax.z(), p.z()));
440 output.vertexData.resize(vertices.size() *
sizeof(Vertex));
441 memcpy(output.vertexData.data(), vertices.data(), vertices.size() *
sizeof(Vertex));
444 output.indexData.resize(indices.size() *
sizeof(IndexType));
445 memcpy(output.indexData.data(), indices.data(), indices.size() *
sizeof(IndexType));
450#if QT_CONFIG(concurrent)
451void ExtrudedTextGeometry::generateExtrudedTextGeometryAsync(QPromise<GeometryData> &promise,
457 GeometryData output = generateExtrudedTextGeometry(text, font, depth, scale);
458 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