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 | |
62 | QT_BEGIN_NAMESPACE |
63 | |
64 | namespace Qt3DExtras { |
65 | |
66 | namespace { |
67 | |
68 | static float = 90.f * 0.1f; |
69 | |
70 | using = unsigned int; |
71 | |
72 | struct { |
73 | struct { |
74 | int ; |
75 | int ; |
76 | }; |
77 | |
78 | QVector<QVector3D> ; |
79 | QVector<IndexType> ; |
80 | QVector<Outline> ; |
81 | QVector<IndexType> ; |
82 | bool ; |
83 | }; |
84 | |
85 | TriangulationData (const QString &text, const QFont &font) |
86 | { |
87 | TriangulationData result; |
88 | int beginOutline = 0; |
89 | |
90 | // Initialize path with text and extract polygons |
91 | QPainterPath path; |
92 | path.setFillRule(Qt::WindingFill); |
93 | path.addText(0, 0, font, text); |
94 | QList<QPolygonF> polygons = path.toSubpathPolygons(QTransform().scale(1.f, -1.f)); |
95 | |
96 | // maybe glyph has no geometry |
97 | if (polygons.size() == 0) |
98 | return result; |
99 | |
100 | const int prevNumIndices = result.indices.size(); |
101 | |
102 | // Reset path and add previously extracted polygons (which where spatially transformed) |
103 | path = QPainterPath(); |
104 | path.setFillRule(Qt::WindingFill); |
105 | for (QPolygonF &p : polygons) |
106 | path.addPolygon(p); |
107 | |
108 | // Extract polylines out of the path, this allows us to retrieve indices for each glyph outline |
109 | QPolylineSet polylines = qPolyline(path); |
110 | QVector<IndexType> tmpIndices; |
111 | tmpIndices.resize(polylines.indices.size()); |
112 | memcpy(tmpIndices.data(), polylines.indices.data(), polylines.indices.size() * sizeof(IndexType)); |
113 | |
114 | int lastIndex = 0; |
115 | for (const IndexType idx : tmpIndices) { |
116 | if (idx == std::numeric_limits<IndexType>::max()) { |
117 | const int endOutline = lastIndex; |
118 | result.outlines.push_back({beginOutline, endOutline}); |
119 | beginOutline = endOutline; |
120 | } else { |
121 | result.outlineIndices.push_back(idx); |
122 | ++lastIndex; |
123 | } |
124 | } |
125 | |
126 | // Triangulate path |
127 | const QTriangleSet triangles = qTriangulate(path); |
128 | |
129 | // Append new indices to result.indices buffer |
130 | result.indices.resize(result.indices.size() + triangles.indices.size()); |
131 | memcpy(&result.indices[prevNumIndices], triangles.indices.data(), triangles.indices.size() * sizeof(IndexType)); |
132 | for (int i = prevNumIndices, m = result.indices.size(); i < m; ++i) |
133 | result.indices[i] += result.vertices.size(); |
134 | |
135 | // Append new triangles to result.vertices |
136 | result.vertices.reserve(triangles.vertices.size() / 2); |
137 | for (int i = 0, m = triangles.vertices.size(); i < m; i += 2) |
138 | result.vertices.push_back(QVector3D(triangles.vertices[i] / font.pointSizeF(), triangles.vertices[i + 1] / font.pointSizeF(), 0.0f)); |
139 | |
140 | return result; |
141 | } |
142 | |
143 | inline QVector3D (const QVector3D &a, const QVector3D &b, float ratio) |
144 | { |
145 | return a + (b - a) * ratio; |
146 | } |
147 | |
148 | } // anonymous namespace |
149 | |
150 | QExtrudedTextGeometryPrivate::() |
151 | : QGeometryPrivate() |
152 | , m_font(QFont(QStringLiteral("Arial" ))) |
153 | , m_depth(1.f) |
154 | , m_positionAttribute(nullptr) |
155 | , m_normalAttribute(nullptr) |
156 | , m_indexAttribute(nullptr) |
157 | , m_vertexBuffer(nullptr) |
158 | , m_indexBuffer(nullptr) |
159 | { |
160 | m_font.setPointSize(4); |
161 | } |
162 | |
163 | void QExtrudedTextGeometryPrivate::() |
164 | { |
165 | Q_Q(QExtrudedTextGeometry); |
166 | m_positionAttribute = new Qt3DRender::QAttribute(q); |
167 | m_normalAttribute = new Qt3DRender::QAttribute(q); |
168 | m_indexAttribute = new Qt3DRender::QAttribute(q); |
169 | m_vertexBuffer = new Qt3DRender::QBuffer(q); |
170 | m_indexBuffer = new Qt3DRender::QBuffer(q); |
171 | |
172 | const quint32 elementSize = 3 + 3; |
173 | const quint32 stride = elementSize * sizeof(float); |
174 | |
175 | m_positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName()); |
176 | m_positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); |
177 | m_positionAttribute->setVertexSize(3); |
178 | m_positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); |
179 | m_positionAttribute->setBuffer(m_vertexBuffer); |
180 | m_positionAttribute->setByteStride(stride); |
181 | m_positionAttribute->setByteOffset(0); |
182 | m_positionAttribute->setCount(0); |
183 | |
184 | m_normalAttribute->setName(Qt3DRender::QAttribute::defaultNormalAttributeName()); |
185 | m_normalAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); |
186 | m_normalAttribute->setVertexSize(3); |
187 | m_normalAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); |
188 | m_normalAttribute->setBuffer(m_vertexBuffer); |
189 | m_normalAttribute->setByteStride(stride); |
190 | m_normalAttribute->setByteOffset(3 * sizeof(float)); |
191 | m_normalAttribute->setCount(0); |
192 | |
193 | m_indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute); |
194 | m_indexAttribute->setVertexBaseType(Qt3DRender::QAttribute::UnsignedInt); |
195 | m_indexAttribute->setBuffer(m_indexBuffer); |
196 | m_indexAttribute->setCount(0); |
197 | |
198 | q->addAttribute(m_positionAttribute); |
199 | q->addAttribute(m_normalAttribute); |
200 | q->addAttribute(m_indexAttribute); |
201 | |
202 | update(); |
203 | } |
204 | |
205 | /*! |
206 | * \qmltype ExtrudedTextGeometry |
207 | * \instantiates Qt3DExtras::QExtrudedTextGeometry |
208 | * \inqmlmodule Qt3D.Extras |
209 | * \brief ExtrudedTextGeometry allows creation of a 3D text in 3D space. |
210 | * |
211 | * The ExtrudedTextGeometry type is most commonly used internally by the |
212 | * ExtrudedTextMesh type but can also be used in custom GeometryRenderer types. |
213 | */ |
214 | |
215 | /*! |
216 | * \qmlproperty QString ExtrudedTextGeometry::text |
217 | * |
218 | * Holds the text used for the mesh. |
219 | */ |
220 | |
221 | /*! |
222 | * \qmlproperty QFont ExtrudedTextGeometry::font |
223 | * |
224 | * Holds the font of the text. |
225 | */ |
226 | |
227 | /*! |
228 | * \qmlproperty float ExtrudedTextGeometry::depth |
229 | * |
230 | * Holds the extrusion depth of the text. |
231 | */ |
232 | |
233 | /*! |
234 | * \qmlproperty Attribute ExtrudedTextGeometry::positionAttribute |
235 | * |
236 | * Holds the geometry position attribute. |
237 | */ |
238 | |
239 | /*! |
240 | * \qmlproperty Attribute ExtrudedTextGeometry::normalAttribute |
241 | * |
242 | * Holds the geometry normal attribute. |
243 | */ |
244 | |
245 | /*! |
246 | * \qmlproperty Attribute ExtrudedTextGeometry::indexAttribute |
247 | * |
248 | * Holds the geometry index attribute. |
249 | */ |
250 | |
251 | /*! |
252 | * \class Qt3DExtras::QExtrudedTextGeometry |
253 | * \inheaderfile Qt3DExtras/QExtrudedTextGeometry |
254 | * \inmodule Qt3DExtras |
255 | * \brief The QExtrudedTextGeometry class allows creation of a 3D extruded text |
256 | * in 3D space. |
257 | * \since 5.9 |
258 | * \ingroup geometries |
259 | * \inherits Qt3DRender::QGeometry |
260 | * |
261 | * The QExtrudedTextGeometry class is most commonly used internally by the QText3DMesh |
262 | * but can also be used in custom Qt3DRender::QGeometryRenderer subclasses. |
263 | */ |
264 | |
265 | /*! |
266 | * Constructs a new QExtrudedTextGeometry with \a parent. |
267 | */ |
268 | QExtrudedTextGeometry::(Qt3DCore::QNode *parent) |
269 | : QGeometry(*new QExtrudedTextGeometryPrivate(), parent) |
270 | { |
271 | Q_D(QExtrudedTextGeometry); |
272 | d->init(); |
273 | } |
274 | |
275 | /*! |
276 | * \internal |
277 | */ |
278 | QExtrudedTextGeometry::(QExtrudedTextGeometryPrivate &dd, Qt3DCore::QNode *parent) |
279 | : QGeometry(dd, parent) |
280 | { |
281 | Q_D(QExtrudedTextGeometry); |
282 | d->init(); |
283 | } |
284 | |
285 | /*! |
286 | * \internal |
287 | */ |
288 | QExtrudedTextGeometry::() |
289 | {} |
290 | |
291 | /*! |
292 | * \internal |
293 | * Updates vertices based on text, font, extrusionLength and smoothAngle properties. |
294 | */ |
295 | void QExtrudedTextGeometryPrivate::() |
296 | { |
297 | if (m_text.trimmed().isEmpty()) // save enough? |
298 | return; |
299 | |
300 | TriangulationData data = triangulate(m_text, m_font); |
301 | |
302 | const int numVertices = data.vertices.size(); |
303 | const int numIndices = data.indices.size(); |
304 | |
305 | struct Vertex { |
306 | QVector3D position; |
307 | QVector3D normal; |
308 | }; |
309 | |
310 | QVector<IndexType> indices; |
311 | QVector<Vertex> vertices; |
312 | |
313 | // TODO: keep 'vertices.size()' small when extruding |
314 | vertices.reserve(data.vertices.size() * 2); |
315 | for (QVector3D &v : data.vertices) // front face |
316 | vertices.push_back({ v, // vertex |
317 | QVector3D(0.0f, 0.0f, -1.0f) }); // normal |
318 | for (QVector3D &v : data.vertices) // front face |
319 | vertices.push_back({ QVector3D(v.x(), v.y(), m_depth), // vertex |
320 | QVector3D(0.0f, 0.0f, 1.0f) }); // normal |
321 | |
322 | for (int i = 0, verticesIndex = vertices.size(); i < data.outlines.size(); ++i) { |
323 | const int begin = data.outlines[i].begin; |
324 | const int end = data.outlines[i].end; |
325 | const int verticesIndexBegin = verticesIndex; |
326 | |
327 | if (begin == end) |
328 | continue; |
329 | |
330 | QVector3D prevNormal = QVector3D::crossProduct( |
331 | vertices[data.outlineIndices[end - 1] + numVertices].position - vertices[data.outlineIndices[end - 1]].position, |
332 | vertices[data.outlineIndices[begin]].position - vertices[data.outlineIndices[end - 1]].position).normalized(); |
333 | |
334 | for (int j = begin; j < end; ++j) { |
335 | const bool isLastIndex = (j == end - 1); |
336 | const IndexType cur = data.outlineIndices[j]; |
337 | const IndexType next = data.outlineIndices[((j - begin + 1) % (end - begin)) + begin]; // normalize, bring in range and adjust |
338 | const QVector3D normal = QVector3D::crossProduct(vertices[cur + numVertices].position - vertices[cur].position, vertices[next].position - vertices[cur].position).normalized(); |
339 | |
340 | // use smooth normals in case of a short angle |
341 | const bool smooth = QVector3D::dotProduct(prevNormal, normal) > (90.0f - edgeSplitAngle) / 90.0f; |
342 | const QVector3D resultNormal = smooth ? mix(prevNormal, normal, 0.5f) : normal; |
343 | if (!smooth) { |
344 | vertices.push_back({vertices[cur].position, prevNormal}); |
345 | vertices.push_back({vertices[cur + numVertices].position, prevNormal}); |
346 | verticesIndex += 2; |
347 | } |
348 | |
349 | vertices.push_back({vertices[cur].position, resultNormal}); |
350 | vertices.push_back({vertices[cur + numVertices].position, resultNormal}); |
351 | |
352 | const int v0 = verticesIndex; |
353 | const int v1 = verticesIndex + 1; |
354 | const int v2 = isLastIndex ? verticesIndexBegin : verticesIndex + 2; |
355 | const int v3 = isLastIndex ? verticesIndexBegin + 1 : verticesIndex + 3; |
356 | |
357 | indices.push_back(v0); |
358 | indices.push_back(v1); |
359 | indices.push_back(v2); |
360 | indices.push_back(v2); |
361 | indices.push_back(v1); |
362 | indices.push_back(v3); |
363 | |
364 | verticesIndex += 2; |
365 | prevNormal = normal; |
366 | } |
367 | } |
368 | |
369 | { // upload vertices |
370 | QByteArray data; |
371 | data.resize(vertices.size() * sizeof(Vertex)); |
372 | memcpy(data.data(), vertices.data(), vertices.size() * sizeof(Vertex)); |
373 | |
374 | m_vertexBuffer->setData(data); |
375 | m_positionAttribute->setCount(vertices.size()); |
376 | m_normalAttribute->setCount(vertices.size()); |
377 | } |
378 | |
379 | // resize for following insertions |
380 | const int indicesOffset = indices.size(); |
381 | indices.resize(indices.size() + numIndices * 2); |
382 | |
383 | // copy values for back faces |
384 | IndexType *indicesFaces = indices.data() + indicesOffset; |
385 | memcpy(indicesFaces, data.indices.data(), numIndices * sizeof(IndexType)); |
386 | |
387 | // insert values for front face and flip triangles |
388 | for (int j = 0; j < numIndices; j += 3) |
389 | { |
390 | indicesFaces[numIndices + j ] = indicesFaces[j ] + numVertices; |
391 | indicesFaces[numIndices + j + 1] = indicesFaces[j + 2] + numVertices; |
392 | indicesFaces[numIndices + j + 2] = indicesFaces[j + 1] + numVertices; |
393 | } |
394 | |
395 | { // upload indices |
396 | QByteArray data; |
397 | data.resize(indices.size() * sizeof(IndexType)); |
398 | memcpy(data.data(), indices.data(), indices.size() * sizeof(IndexType)); |
399 | |
400 | m_indexBuffer->setData(data); |
401 | m_indexAttribute->setCount(indices.size()); |
402 | } |
403 | } |
404 | |
405 | void QExtrudedTextGeometry::(const QString &text) |
406 | { |
407 | Q_D(QExtrudedTextGeometry); |
408 | if (d->m_text != text) { |
409 | d->m_text = text; |
410 | d->update(); |
411 | emit textChanged(text); |
412 | } |
413 | } |
414 | |
415 | void QExtrudedTextGeometry::(const QFont &font) |
416 | { |
417 | Q_D(QExtrudedTextGeometry); |
418 | if (d->m_font != font) { |
419 | d->m_font = font; |
420 | d->update(); |
421 | emit fontChanged(font); |
422 | } |
423 | } |
424 | |
425 | void QExtrudedTextGeometry::(float depth) |
426 | { |
427 | Q_D(QExtrudedTextGeometry); |
428 | if (d->m_depth != depth) { |
429 | d->m_depth = depth; |
430 | d->update(); |
431 | emit depthChanged(depth); |
432 | } |
433 | } |
434 | |
435 | /*! |
436 | * \property QExtrudedTextGeometry::text |
437 | * |
438 | * Holds the text used for the mesh. |
439 | */ |
440 | QString QExtrudedTextGeometry::() const |
441 | { |
442 | Q_D(const QExtrudedTextGeometry); |
443 | return d->m_text; |
444 | } |
445 | |
446 | /*! |
447 | * \property QExtrudedTextGeometry::font |
448 | * |
449 | * Holds the font of the text. |
450 | */ |
451 | QFont QExtrudedTextGeometry::() const |
452 | { |
453 | Q_D(const QExtrudedTextGeometry); |
454 | return d->m_font; |
455 | } |
456 | |
457 | /*! |
458 | * \property QExtrudedTextGeometry::extrusionLength |
459 | * |
460 | * Holds the extrusion length of the text. |
461 | */ |
462 | float QExtrudedTextGeometry::() const |
463 | { |
464 | Q_D(const QExtrudedTextGeometry); |
465 | return d->m_depth; |
466 | } |
467 | |
468 | /*! |
469 | * \property QExtrudedTextGeometry::positionAttribute |
470 | * |
471 | * Holds the geometry position attribute. |
472 | */ |
473 | Qt3DRender::QAttribute *QExtrudedTextGeometry::() const |
474 | { |
475 | Q_D(const QExtrudedTextGeometry); |
476 | return d->m_positionAttribute; |
477 | } |
478 | |
479 | /*! |
480 | * \property QExtrudedTextGeometry::normalAttribute |
481 | * |
482 | * Holds the geometry normal attribute. |
483 | */ |
484 | Qt3DRender::QAttribute *QExtrudedTextGeometry::() const |
485 | { |
486 | Q_D(const QExtrudedTextGeometry); |
487 | return d->m_normalAttribute; |
488 | } |
489 | |
490 | /*! |
491 | * \property QExtrudedTextGeometry::indexAttribute |
492 | * |
493 | * Holds the geometry index attribute. |
494 | */ |
495 | Qt3DRender::QAttribute *QExtrudedTextGeometry::() const |
496 | { |
497 | Q_D(const QExtrudedTextGeometry); |
498 | return d->m_indexAttribute; |
499 | } |
500 | |
501 | } // Qt3DExtras |
502 | |
503 | QT_END_NAMESPACE |
504 | |