1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt3D module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "qextrudedtextgeometry.h" |
52 | #include "qextrudedtextgeometry_p.h" |
53 | #include <Qt3DRender/qbuffer.h> |
54 | #include <Qt3DRender/qbufferdatagenerator.h> |
55 | #include <Qt3DRender/qattribute.h> |
56 | #include <private/qtriangulator_p.h> |
57 | #include <qmath.h> |
58 | #include <QVector3D> |
59 | #include <QTextLayout> |
60 | #include <QTime> |
61 | #include <QPainterPath> |
62 | |
63 | QT_BEGIN_NAMESPACE |
64 | |
65 | namespace Qt3DExtras { |
66 | |
67 | namespace { |
68 | |
69 | static float = 90.f * 0.1f; |
70 | |
71 | using = unsigned int; |
72 | |
73 | struct { |
74 | struct { |
75 | int ; |
76 | int ; |
77 | }; |
78 | |
79 | QVector<QVector3D> ; |
80 | QVector<IndexType> ; |
81 | QVector<Outline> ; |
82 | QVector<IndexType> ; |
83 | bool ; |
84 | }; |
85 | |
86 | TriangulationData (const QString &text, const QFont &font) |
87 | { |
88 | TriangulationData result; |
89 | int beginOutline = 0; |
90 | |
91 | // Initialize path with text and extract polygons |
92 | QPainterPath path; |
93 | path.setFillRule(Qt::WindingFill); |
94 | path.addText(x: 0, y: 0, f: font, text); |
95 | QList<QPolygonF> polygons = path.toSubpathPolygons(matrix: QTransform().scale(sx: 1.f, sy: -1.f)); |
96 | |
97 | // maybe glyph has no geometry |
98 | if (polygons.size() == 0) |
99 | return result; |
100 | |
101 | const int prevNumIndices = result.indices.size(); |
102 | |
103 | // Reset path and add previously extracted polygons (which where spatially transformed) |
104 | path = QPainterPath(); |
105 | path.setFillRule(Qt::WindingFill); |
106 | for (QPolygonF &p : polygons) |
107 | path.addPolygon(polygon: p); |
108 | |
109 | // Extract polylines out of the path, this allows us to retrieve indices for each glyph outline |
110 | QPolylineSet polylines = qPolyline(path); |
111 | QVector<IndexType> tmpIndices; |
112 | tmpIndices.resize(asize: polylines.indices.size()); |
113 | memcpy(dest: tmpIndices.data(), src: polylines.indices.data(), n: polylines.indices.size() * sizeof(IndexType)); |
114 | |
115 | int lastIndex = 0; |
116 | for (const IndexType idx : tmpIndices) { |
117 | if (idx == std::numeric_limits<IndexType>::max()) { |
118 | const int endOutline = lastIndex; |
119 | result.outlines.push_back(t: {.begin: beginOutline, .end: endOutline}); |
120 | beginOutline = endOutline; |
121 | } else { |
122 | result.outlineIndices.push_back(t: idx); |
123 | ++lastIndex; |
124 | } |
125 | } |
126 | |
127 | // Triangulate path |
128 | const QTriangleSet triangles = qTriangulate(path); |
129 | |
130 | // Append new indices to result.indices buffer |
131 | result.indices.resize(asize: result.indices.size() + triangles.indices.size()); |
132 | memcpy(dest: &result.indices[prevNumIndices], src: triangles.indices.data(), n: triangles.indices.size() * sizeof(IndexType)); |
133 | for (int i = prevNumIndices, m = result.indices.size(); i < m; ++i) |
134 | result.indices[i] += result.vertices.size(); |
135 | |
136 | // Append new triangles to result.vertices |
137 | result.vertices.reserve(asize: triangles.vertices.size() / 2); |
138 | for (int i = 0, m = triangles.vertices.size(); i < m; i += 2) |
139 | result.vertices.push_back(t: QVector3D(triangles.vertices[i] / font.pointSizeF(), triangles.vertices[i + 1] / font.pointSizeF(), 0.0f)); |
140 | |
141 | return result; |
142 | } |
143 | |
144 | inline QVector3D (const QVector3D &a, const QVector3D &b, float ratio) |
145 | { |
146 | return a + (b - a) * ratio; |
147 | } |
148 | |
149 | } // anonymous namespace |
150 | |
151 | QExtrudedTextGeometryPrivate::() |
152 | : QGeometryPrivate() |
153 | , m_font(QFont(QStringLiteral("Arial" ))) |
154 | , m_depth(1.f) |
155 | , m_positionAttribute(nullptr) |
156 | , m_normalAttribute(nullptr) |
157 | , m_indexAttribute(nullptr) |
158 | , m_vertexBuffer(nullptr) |
159 | , m_indexBuffer(nullptr) |
160 | { |
161 | m_font.setPointSize(4); |
162 | } |
163 | |
164 | void QExtrudedTextGeometryPrivate::() |
165 | { |
166 | Q_Q(QExtrudedTextGeometry); |
167 | m_positionAttribute = new Qt3DRender::QAttribute(q); |
168 | m_normalAttribute = new Qt3DRender::QAttribute(q); |
169 | m_indexAttribute = new Qt3DRender::QAttribute(q); |
170 | m_vertexBuffer = new Qt3DRender::QBuffer(q); |
171 | m_indexBuffer = new Qt3DRender::QBuffer(q); |
172 | |
173 | const quint32 elementSize = 3 + 3; |
174 | const quint32 stride = elementSize * sizeof(float); |
175 | |
176 | m_positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName()); |
177 | m_positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); |
178 | m_positionAttribute->setVertexSize(3); |
179 | m_positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); |
180 | m_positionAttribute->setBuffer(m_vertexBuffer); |
181 | m_positionAttribute->setByteStride(stride); |
182 | m_positionAttribute->setByteOffset(0); |
183 | m_positionAttribute->setCount(0); |
184 | |
185 | m_normalAttribute->setName(Qt3DRender::QAttribute::defaultNormalAttributeName()); |
186 | m_normalAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); |
187 | m_normalAttribute->setVertexSize(3); |
188 | m_normalAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); |
189 | m_normalAttribute->setBuffer(m_vertexBuffer); |
190 | m_normalAttribute->setByteStride(stride); |
191 | m_normalAttribute->setByteOffset(3 * sizeof(float)); |
192 | m_normalAttribute->setCount(0); |
193 | |
194 | m_indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute); |
195 | m_indexAttribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt); |
196 | m_indexAttribute->setBuffer(m_indexBuffer); |
197 | m_indexAttribute->setCount(0); |
198 | |
199 | q->addAttribute(attribute: m_positionAttribute); |
200 | q->addAttribute(attribute: m_normalAttribute); |
201 | q->addAttribute(attribute: m_indexAttribute); |
202 | |
203 | update(); |
204 | } |
205 | |
206 | /*! |
207 | * \qmltype ExtrudedTextGeometry |
208 | * \instantiates Qt3DExtras::QExtrudedTextGeometry |
209 | * \inqmlmodule Qt3D.Extras |
210 | * \brief ExtrudedTextGeometry allows creation of a 3D text in 3D space. |
211 | * |
212 | * The ExtrudedTextGeometry type is most commonly used internally by the |
213 | * ExtrudedTextMesh type but can also be used in custom GeometryRenderer types. |
214 | * |
215 | * The origin of the geometry is the rear left end of the text's baseline. |
216 | */ |
217 | |
218 | /*! |
219 | * \qmlproperty QString ExtrudedTextGeometry::text |
220 | * |
221 | * Holds the text used for the mesh. |
222 | */ |
223 | |
224 | /*! |
225 | * \qmlproperty QFont ExtrudedTextGeometry::font |
226 | * |
227 | * Holds the font of the text. |
228 | * |
229 | * The geometry is normalized by the font's pointSize, so a larger pointSize |
230 | * will result in smoother, rather than larger, text. pixelSize should not |
231 | * be used. |
232 | */ |
233 | |
234 | /*! |
235 | * \qmlproperty float ExtrudedTextGeometry::depth |
236 | * |
237 | * Holds the extrusion depth of the text. |
238 | */ |
239 | |
240 | /*! |
241 | * \qmlproperty Attribute ExtrudedTextGeometry::positionAttribute |
242 | * |
243 | * Holds the geometry position attribute. |
244 | */ |
245 | |
246 | /*! |
247 | * \qmlproperty Attribute ExtrudedTextGeometry::normalAttribute |
248 | * |
249 | * Holds the geometry normal attribute. |
250 | */ |
251 | |
252 | /*! |
253 | * \qmlproperty Attribute ExtrudedTextGeometry::indexAttribute |
254 | * |
255 | * Holds the geometry index attribute. |
256 | */ |
257 | |
258 | /*! |
259 | * \class Qt3DExtras::QExtrudedTextGeometry |
260 | * \inheaderfile Qt3DExtras/QExtrudedTextGeometry |
261 | * \inmodule Qt3DExtras |
262 | * \brief The QExtrudedTextGeometry class allows creation of a 3D extruded text |
263 | * in 3D space. |
264 | * \since 5.9 |
265 | * \ingroup geometries |
266 | * \inherits Qt3DRender::QGeometry |
267 | * |
268 | * The QExtrudedTextGeometry class is most commonly used internally by the QText3DMesh |
269 | * but can also be used in custom Qt3DRender::QGeometryRenderer subclasses. |
270 | * |
271 | * The origin of the geometry is the rear left end of the text's baseline. |
272 | */ |
273 | |
274 | /*! |
275 | * Constructs a new QExtrudedTextGeometry with \a parent. |
276 | */ |
277 | QExtrudedTextGeometry::(Qt3DCore::QNode *parent) |
278 | : QGeometry(*new QExtrudedTextGeometryPrivate(), parent) |
279 | { |
280 | Q_D(QExtrudedTextGeometry); |
281 | d->init(); |
282 | } |
283 | |
284 | /*! |
285 | * \internal |
286 | */ |
287 | QExtrudedTextGeometry::(QExtrudedTextGeometryPrivate &dd, Qt3DCore::QNode *parent) |
288 | : QGeometry(dd, parent) |
289 | { |
290 | Q_D(QExtrudedTextGeometry); |
291 | d->init(); |
292 | } |
293 | |
294 | /*! |
295 | * \internal |
296 | */ |
297 | QExtrudedTextGeometry::() |
298 | {} |
299 | |
300 | /*! |
301 | * \internal |
302 | * Updates vertices based on text, font, extrusionLength and smoothAngle properties. |
303 | */ |
304 | void QExtrudedTextGeometryPrivate::() |
305 | { |
306 | if (m_text.trimmed().isEmpty()) // save enough? |
307 | return; |
308 | |
309 | TriangulationData data = triangulate(text: m_text, font: m_font); |
310 | |
311 | const int numVertices = data.vertices.size(); |
312 | const int numIndices = data.indices.size(); |
313 | |
314 | struct Vertex { |
315 | QVector3D position; |
316 | QVector3D normal; |
317 | }; |
318 | |
319 | QVector<IndexType> indices; |
320 | QVector<Vertex> vertices; |
321 | |
322 | // TODO: keep 'vertices.size()' small when extruding |
323 | vertices.reserve(asize: data.vertices.size() * 2); |
324 | for (QVector3D &v : data.vertices) // front face |
325 | vertices.push_back(t: { .position: v, // vertex |
326 | .normal: QVector3D(0.0f, 0.0f, -1.0f) }); // normal |
327 | for (QVector3D &v : data.vertices) // front face |
328 | vertices.push_back(t: { .position: QVector3D(v.x(), v.y(), m_depth), // vertex |
329 | .normal: QVector3D(0.0f, 0.0f, 1.0f) }); // normal |
330 | |
331 | for (int i = 0, verticesIndex = vertices.size(); i < data.outlines.size(); ++i) { |
332 | const int begin = data.outlines[i].begin; |
333 | const int end = data.outlines[i].end; |
334 | const int verticesIndexBegin = verticesIndex; |
335 | |
336 | if (begin == end) |
337 | continue; |
338 | |
339 | QVector3D prevNormal = QVector3D::crossProduct( |
340 | v1: vertices[data.outlineIndices[end - 1] + numVertices].position - vertices[data.outlineIndices[end - 1]].position, |
341 | v2: vertices[data.outlineIndices[begin]].position - vertices[data.outlineIndices[end - 1]].position).normalized(); |
342 | |
343 | for (int j = begin; j < end; ++j) { |
344 | const bool isLastIndex = (j == end - 1); |
345 | const IndexType cur = data.outlineIndices[j]; |
346 | const IndexType next = data.outlineIndices[((j - begin + 1) % (end - begin)) + begin]; // normalize, bring in range and adjust |
347 | const QVector3D normal = QVector3D::crossProduct(v1: vertices[cur + numVertices].position - vertices[cur].position, v2: vertices[next].position - vertices[cur].position).normalized(); |
348 | |
349 | // use smooth normals in case of a short angle |
350 | const bool smooth = QVector3D::dotProduct(v1: prevNormal, v2: normal) > (90.0f - edgeSplitAngle) / 90.0f; |
351 | const QVector3D resultNormal = smooth ? mix(a: prevNormal, b: normal, ratio: 0.5f) : normal; |
352 | if (!smooth) { |
353 | vertices.push_back(t: {.position: vertices[cur].position, .normal: prevNormal}); |
354 | vertices.push_back(t: {.position: vertices[cur + numVertices].position, .normal: prevNormal}); |
355 | verticesIndex += 2; |
356 | } |
357 | |
358 | vertices.push_back(t: {.position: vertices[cur].position, .normal: resultNormal}); |
359 | vertices.push_back(t: {.position: vertices[cur + numVertices].position, .normal: resultNormal}); |
360 | |
361 | const int v0 = verticesIndex; |
362 | const int v1 = verticesIndex + 1; |
363 | const int v2 = isLastIndex ? verticesIndexBegin : verticesIndex + 2; |
364 | const int v3 = isLastIndex ? verticesIndexBegin + 1 : verticesIndex + 3; |
365 | |
366 | indices.push_back(t: v0); |
367 | indices.push_back(t: v1); |
368 | indices.push_back(t: v2); |
369 | indices.push_back(t: v2); |
370 | indices.push_back(t: v1); |
371 | indices.push_back(t: v3); |
372 | |
373 | verticesIndex += 2; |
374 | prevNormal = normal; |
375 | } |
376 | } |
377 | |
378 | { // upload vertices |
379 | QByteArray data; |
380 | data.resize(size: vertices.size() * sizeof(Vertex)); |
381 | memcpy(dest: data.data(), src: vertices.data(), n: vertices.size() * sizeof(Vertex)); |
382 | |
383 | m_vertexBuffer->setData(data); |
384 | m_positionAttribute->setCount(vertices.size()); |
385 | m_normalAttribute->setCount(vertices.size()); |
386 | } |
387 | |
388 | // resize for following insertions |
389 | const int indicesOffset = indices.size(); |
390 | indices.resize(asize: indices.size() + numIndices * 2); |
391 | |
392 | // copy values for back faces |
393 | IndexType *indicesFaces = indices.data() + indicesOffset; |
394 | memcpy(dest: indicesFaces, src: data.indices.data(), n: numIndices * sizeof(IndexType)); |
395 | |
396 | // insert values for front face and flip triangles |
397 | for (int j = 0; j < numIndices; j += 3) |
398 | { |
399 | indicesFaces[numIndices + j ] = indicesFaces[j ] + numVertices; |
400 | indicesFaces[numIndices + j + 1] = indicesFaces[j + 2] + numVertices; |
401 | indicesFaces[numIndices + j + 2] = indicesFaces[j + 1] + numVertices; |
402 | } |
403 | |
404 | { // upload indices |
405 | QByteArray data; |
406 | data.resize(size: indices.size() * sizeof(IndexType)); |
407 | memcpy(dest: data.data(), src: indices.data(), n: indices.size() * sizeof(IndexType)); |
408 | |
409 | m_indexBuffer->setData(data); |
410 | m_indexAttribute->setCount(indices.size()); |
411 | } |
412 | } |
413 | |
414 | void QExtrudedTextGeometry::(const QString &text) |
415 | { |
416 | Q_D(QExtrudedTextGeometry); |
417 | if (d->m_text != text) { |
418 | d->m_text = text; |
419 | d->update(); |
420 | emit textChanged(text); |
421 | } |
422 | } |
423 | |
424 | void QExtrudedTextGeometry::(const QFont &font) |
425 | { |
426 | Q_D(QExtrudedTextGeometry); |
427 | if (d->m_font != font) { |
428 | d->m_font = font; |
429 | d->update(); |
430 | emit fontChanged(font); |
431 | } |
432 | } |
433 | |
434 | void QExtrudedTextGeometry::(float depth) |
435 | { |
436 | Q_D(QExtrudedTextGeometry); |
437 | if (d->m_depth != depth) { |
438 | d->m_depth = depth; |
439 | d->update(); |
440 | emit depthChanged(extrusionLength: depth); |
441 | } |
442 | } |
443 | |
444 | /*! |
445 | * \property QExtrudedTextGeometry::text |
446 | * |
447 | * Holds the text used for the mesh. |
448 | */ |
449 | QString QExtrudedTextGeometry::() const |
450 | { |
451 | Q_D(const QExtrudedTextGeometry); |
452 | return d->m_text; |
453 | } |
454 | |
455 | /*! |
456 | * \property QExtrudedTextGeometry::font |
457 | * |
458 | * Holds the font of the text. |
459 | * |
460 | * The geometry is normalized by the font's pointSize, so a larger pointSize |
461 | * will result in smoother, rather than larger, text. pixelSize should not |
462 | * be used. |
463 | */ |
464 | QFont QExtrudedTextGeometry::() const |
465 | { |
466 | Q_D(const QExtrudedTextGeometry); |
467 | return d->m_font; |
468 | } |
469 | |
470 | /*! |
471 | * \property QExtrudedTextGeometry::extrusionLength |
472 | * |
473 | * Holds the extrusion length of the text. |
474 | */ |
475 | float QExtrudedTextGeometry::() const |
476 | { |
477 | Q_D(const QExtrudedTextGeometry); |
478 | return d->m_depth; |
479 | } |
480 | |
481 | /*! |
482 | * \property QExtrudedTextGeometry::positionAttribute |
483 | * |
484 | * Holds the geometry position attribute. |
485 | */ |
486 | Qt3DRender::QAttribute *QExtrudedTextGeometry::() const |
487 | { |
488 | Q_D(const QExtrudedTextGeometry); |
489 | return d->m_positionAttribute; |
490 | } |
491 | |
492 | /*! |
493 | * \property QExtrudedTextGeometry::normalAttribute |
494 | * |
495 | * Holds the geometry normal attribute. |
496 | */ |
497 | Qt3DRender::QAttribute *QExtrudedTextGeometry::() const |
498 | { |
499 | Q_D(const QExtrudedTextGeometry); |
500 | return d->m_normalAttribute; |
501 | } |
502 | |
503 | /*! |
504 | * \property QExtrudedTextGeometry::indexAttribute |
505 | * |
506 | * Holds the geometry index attribute. |
507 | */ |
508 | Qt3DRender::QAttribute *QExtrudedTextGeometry::() const |
509 | { |
510 | Q_D(const QExtrudedTextGeometry); |
511 | return d->m_indexAttribute; |
512 | } |
513 | |
514 | } // Qt3DExtras |
515 | |
516 | QT_END_NAMESPACE |
517 | |