Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
randominstancing.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
5#include <QRandomGenerator>
6#include <QObject>
7
9
10/*!
11 \qmltype InstanceRange
12 \inherits Object3D
13 \inqmlmodule QtQuick3D.Helpers
14 \since 6.2
15 \brief Specifies a range for RandomInstancing.
16
17 The InstanceRange QML type is used to specify the range of variation for
18 RandomInstancing attributes.
19*/
20
21/*!
22 \qmlproperty Variant InstanceRange::from
23
24 This property specifies the lower bound of the range. The type needs to match the type of the attribute that this range is applied to.
25*/
26
27/*!
28 \qmlproperty Variant InstanceRange::to
29
30 This property specifies the upper bound of the range. The type needs to match the type of the attribute that this range is applied to.
31*/
32
33/*!
34 \qmlproperty bool InstanceRange::proportional
35
36 This property determines whether the components of the attribute vary proportionally or independently.
37 The default value is \c true, meaning that all components are independent.
38
39 For example, the following defines a scaling range that preserves the aspect ratio of the model:
40 \qml
41 InstanceRange {
42 from: Qt.vector3d(1, 1, 1)
43 to: Qt.vector3d(5, 5, 5)
44 proportional: true
45 }
46 \endqml
47
48 This defines a greyscale color range:
49 \qml
50 InstanceRange {
51 from: "black"
52 to: "white"
53 proportional: true
54 }
55 \endqml
56
57 While the following defines a range that covers all colors
58 \qml
59 InstanceRange {
60 from: "black"
61 to: "white"
62 }
63 \endqml
64*/
65
66/*!
67 \qmltype RandomInstancing
68 \inherits Instancing
69 \inqmlmodule QtQuick3D.Helpers
70 \since 6.2
71 \brief Generates a random instancing table.
72
73 The RandomInstancing type provides an easy way to generate a large number of
74 random instances within defined bounds. The number of instances is defined by the
75 \l instanceCount property. The bounds are defined by the properties
76 \l position, \l scale, \l rotation, \l color, and \l customData.
77
78 \sa InstanceList
79*/
80
81/*!
82 \qmlproperty int RandomInstancing::instanceCount
83
84 The instanceCount property specifies the number of instances to generate. Changing this value will regenerate the whole table.
85
86 \sa randomSeed
87*/
88
89/*!
90 \qmlproperty int RandomInstancing::randomSeed
91
92 This property defines the seed for the random number generator. Setting this to a value
93 different from -1 guarantees that the instance table will have the same content each time it is generated.
94 Note that adding or changing attributes may cause a completely different table to be generated.
95
96 The default value is -1, causing the table to get a new random value each time it is generated.
97*/
98
99/*!
100 \qmlproperty InstanceRange RandomInstancing::position
101
102 The position property defines the geometrical bounds of the generated instances.
103 The default value is empty, causing a generated position of \c{[0, 0, 0]}.
104
105 \sa color, rotation, scale, customData
106*/
107
108/*!
109 \qmlproperty vector3d RandomInstancing::gridSpacing
110 \since 6.9
111
112 The gridSpacing property defines the minimum spacing between instances, ensuring they do not overlap.
113 Each position will be separated by at least the value specified in \c gridSpacing.
114
115 If the specified \c gridSpacing cannot accommodate the requested number of instances, the \c instanceCount
116 property will be reduced to the number of instances that can be placed without overlap.
117
118 \note The gridSpacing property affects only the position of instances.
119 Rotation and scaling applied to instances are not considered in the spacing algorithm.
120
121 The default value is \c{[0, 0, 0]}, which imposes no restriction on overlapping instances.
122
123 \sa position
124*/
125
126/*!
127 \qmlproperty InstanceRange RandomInstancing::scale
128
129 The scale property defines the scaling limits for the generated instances. The type is
130 \l vector3d.
131 Set \l {InstanceRange::proportional}{InstanceRange.proportional} to \c true for uniform scaling.
132 The default value is empty, causing no scaling to be applied.
133
134 \sa position, color, rotation, scale, customData
135*/
136
137/*!
138 \qmlproperty InstanceRange RandomInstancing::rotation
139
140 The rotation property defines the rotation range for the generated instances. The type is
141 \l vector3d, corresponding to a Euler rotation vector \c{[xRotation, yRotation, zRotation]}.
142 The default value is empty, causing no rotation to be applied.
143
144 \sa position, color, scale, customData
145*/
146
147/*!
148 \qmlproperty InstanceRange RandomInstancing::color
149
150
151 The color property defines the color variation range for the generated instances. The type is \l color.
152 The default value is empty, causing the color to be white.
153
154 Setting the colorModel property makes it possible to select only saturated colors, for example.
155
156 \sa position, rotation, scale, customData
157*/
158
159/*!
160 \qmlproperty InstanceRange RandomInstancing::customData
161
162 The customData property defines the custom data variation range for the generated instances.
163 The type is \l vector4d.
164 The default value is empty, causing causing the generated data to be \c{[0, 0, 0, 0]}.
165
166 \sa position, color, rotation, scale, customData
167*/
168/*!
169 \qmlproperty enumeration RandomInstancing::colorModel
170
171 This property controls how the color range is interpreted.
172
173 The instance colors are generated component by component within the range determined by the
174 \e from and \e to colors. The color model determines how those components are defined.
175
176 \value RandomInstancing.RGB
177 The components are red, green, blue, and alpha, according to the RGB color model.
178 \value RandomInstancing.HSV
179 The components are hue, saturation, value, and alpha, according to the \l{QColor#The HSV Color Model}{HSV Color Model}.
180 \value RandomInstancing.HSL
181 The components are hue, saturation, lightness, and alpha,, according to the \l{QColor#The HSL Color Model}{HSL Color Model}.
182
183 As an example, the following color range
184 \qml
185 color: InstanceRange {
186 from: Qt.hsva(0, 0.1, 0.8, 1)
187 to: Qt.hsva(1, 0.3, 1, 1)
188 }
189 \endqml
190 will generate a full range of pastel colors when using the \c HSV color model, but only shades of pink
191 when using the \c RGB color model.
192
193 The default value is \c RandomInstancing.RGB
194
195 \sa RandomInstancing::color
196*/
197
203
204QQuick3DRandomInstancing::~QQuick3DRandomInstancing()
205{
206}
207
208void QQuick3DRandomInstancing::setInstanceCount(int instanceCount)
209{
210 if (instanceCount == m_randomCount)
211 return;
212 m_randomCount = instanceCount;
213 emit instanceCountChanged();
214 m_dirty = true;
215 markDirty();
216}
217
219{
220 if (m_randomSeed == randomSeed)
221 return;
222
223 m_randomSeed = randomSeed;
224 emit randomSeedChanged();
225 m_dirty = true;
226 markDirty();
227}
228
229void QQuick3DRandomInstancing::setPosition(QQuick3DInstanceRange *position)
230{
231 if (m_position == position)
232 return;
233
234 if (m_position)
235 disconnect(m_position, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
236 m_position = position;
237 emit positionChanged();
238 m_dirty = true;
239 markDirty();
240 if (m_position) {
241 connect(m_position, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
242 connect(m_position, &QObject::destroyed, this, [this](QObject *obj){ if (obj == m_position) m_position = nullptr; });
243 }
244}
245
246void QQuick3DRandomInstancing::setScale(QQuick3DInstanceRange *scale)
247{
248 if (m_scale == scale)
249 return;
250
251 if (m_scale)
252 disconnect(m_scale, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
253 m_scale = scale;
254 emit scaleChanged();
255 m_dirty = true;
256 markDirty();
257 if (m_scale) {
258 connect(m_scale, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
259 connect(m_scale, &QObject::destroyed, this, [this](QObject *obj){ if (obj == m_scale) m_scale = nullptr; });
260 }
261}
262
263void QQuick3DRandomInstancing::setRotation(QQuick3DInstanceRange *rotation)
264{
265 if (m_rotation == rotation)
266 return;
267
268 if (m_rotation)
269 disconnect(m_rotation, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
270 m_rotation = rotation;
271 emit rotationChanged();
272 m_dirty = true;
273 markDirty();
274 if (m_rotation) {
275 connect(m_rotation, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
276 connect(m_rotation, &QObject::destroyed, this, [this](QObject *obj){ if (obj == m_rotation) m_rotation = nullptr; });
277 }
278}
279
280void QQuick3DRandomInstancing::setColor(QQuick3DInstanceRange *color)
281{
282 if (m_color == color)
283 return;
284
285 if (m_color)
286 disconnect(m_color, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
287 m_color = color;
288 emit colorChanged();
289 m_dirty = true;
290 markDirty();
291 if (m_color) {
292 connect(m_color, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
293 connect(m_color, &QObject::destroyed, this, [this](QObject *obj){ if (obj == m_color) m_color = nullptr; });
294 }
295
296}
297
298void QQuick3DRandomInstancing::setCustomData(QQuick3DInstanceRange *customData)
299{
300 if (m_customData == customData)
301 return;
302
303 if (m_customData)
304 disconnect(m_customData, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
305 m_customData = customData;
306 emit customDataChanged();
307 m_dirty = true;
308 markDirty();
309 if (m_customData) {
310 connect(m_customData, &QQuick3DInstanceRange::changed, this, &QQuick3DRandomInstancing::handleChange);
311 connect(m_customData, &QObject::destroyed, this, [this](QObject *obj){ if (obj == m_customData) m_customData = nullptr; });
312 }
313}
314
315void QQuick3DRandomInstancing::setColorModel(QQuick3DRandomInstancing::ColorModel colorModel)
316{
317 if (m_colorModel == colorModel)
318 return;
319 m_colorModel = colorModel;
320 emit colorModelChanged();
321 m_dirty = true;
322 markDirty();
323}
324
325void QQuick3DRandomInstancing::handleChange()
326{
327 m_dirty = true;
328 markDirty();
329}
330
331static inline float genRandom(float from, float to, QRandomGenerator *rgen)
332{
333 float c = rgen->bounded(1.0);
334 return from + c * (to - from);
335}
336
337static QVector3D genRandom(const QVector3D &from, const QVector3D &to, bool proportional, QRandomGenerator *rgen)
338{
339 if (proportional) {
340 float c = rgen->bounded(1.0);
341 return from + c * (to - from);
342 }
343 return { genRandom(from.x(), to.x(), rgen), genRandom(from.y(), to.y(), rgen), genRandom(from.z(), to.z(), rgen) };
344}
345
346static QVector4D genRandom(const QVector4D &from, const QVector4D &to, bool proportional, QRandomGenerator *rgen)
347{
348 if (proportional) {
349 float c = rgen->bounded(1.0);
350 return from + c * (to - from);
351 }
352 return { genRandom(from.x(), to.x(), rgen), genRandom(from.y(), to.y(), rgen), genRandom(from.z(), to.z(), rgen), genRandom(from.w(), to.w(), rgen) };
353}
354
355static QColor genRandom(const QColor &from, const QColor &to, bool proportional, QQuick3DRandomInstancing::ColorModel colorModel, QRandomGenerator *rgen)
356{
357 QVector4D v1, v2;
358 switch (colorModel) {
359 case QQuick3DRandomInstancing::ColorModel::HSL:
360 from.getHslF(&v1[0], &v1[1], &v1[2], &v1[3]);
361 to.getHslF(&v2[0], &v2[1], &v2[2], &v2[3]);
362 break;
363 case QQuick3DRandomInstancing::ColorModel::HSV:
364 from.getHsvF(&v1[0], &v1[1], &v1[2], &v1[3]);
365 to.getHsvF(&v2[0], &v2[1], &v2[2], &v2[3]);
366 break;
367 case QQuick3DRandomInstancing::ColorModel::RGB:
368 default:
369 from.getRgbF(&v1[0], &v1[1], &v1[2], &v1[3]);
370 to.getRgbF(&v2[0], &v2[1], &v2[2], &v2[3]);
371 break;
372 }
373 QVector4D r = genRandom(v1, v2, proportional, rgen);
374
375 switch (colorModel) {
376 case QQuick3DRandomInstancing::ColorModel::HSL:
377 return QColor::fromHslF(r[0], r[1], r[2], r[3]);
378 break;
379 case QQuick3DRandomInstancing::ColorModel::HSV:
380 return QColor::fromHsvF(r[0], r[1], r[2], r[3]);
381 break;
382 case QQuick3DRandomInstancing::ColorModel::RGB:
383 default:
384 return QColor::fromRgbF(r[0], r[1], r[2], r[3]);
385 }
386}
387
389{
390 if (m_dirty)
391 generateInstanceTable();
392 if (instanceCount)
393 *instanceCount = m_randomCount;
394 return m_instanceData;
395}
396
397namespace {
398
399struct GridPosition {
400 int x, y, z;
401};
402
403inline bool operator==(const GridPosition &e1, const GridPosition &e2)
404{
405 return e1.x == e2.x && e1.y == e2.y && e1.z == e2.z;
406}
407
408inline size_t qHash(const GridPosition &key, size_t seed)
409{
410 return qHashMulti(seed, key.x, key.y, key.z);
411}
412
413class PositionGenerator {
414public:
415 void init(QVector3D fromPos, QVector3D toPos, bool proportional, bool gridMode, QVector3D gridSize) {
416 m_from = fromPos;
417 m_to = toPos;
418 m_proportional = proportional;
419 m_gridMode = gridMode;
420 if (gridMode) {
421 int nx, ny, nz;
422 float cellWidth;
423 float cellDepth;
424 float cellHeight;
425 float width = toPos.x() - fromPos.x();
426 float height = toPos.y() - fromPos.y();
427 float depth = toPos.z() - fromPos.z();
428 if (qFuzzyIsNull(width)) {
429 cellWidth = 0;
430 nx = 1;
431 } else {
432 cellWidth = gridSize.x() > 0 ? gridSize.x() : width;
433 nx = width / cellWidth;
434 }
435 if (qFuzzyIsNull(height)) {
436 cellHeight = 0;
437 ny = 1;
438 } else {
439 cellHeight = gridSize.y() > 0 ? gridSize.y() : height;
440 ny = height / cellHeight;
441 }
442 if (qFuzzyIsNull(depth)) {
443 cellDepth = 0;
444 nz = 1;
445 } else {
446 cellDepth = gridSize.z() > 0 ? gridSize.z() : depth;
447 nz = depth / cellDepth;
448 }
449 m_xGrid = nx > 1;
450 m_yGrid = ny > 1;
451 m_zGrid = nz > 1;
452 m_gridSize = {cellWidth, cellHeight, cellDepth};
453 }
454 m_remainingAttempts = 1000000;
455 }
456
457 inline GridPosition gridPos(QVector3D pos) {
458 int ix = m_xGrid ? int(pos.x() / m_gridSize.x()) : 0;
459 int iy = m_yGrid ? int(pos.y() / m_gridSize.y()) : 0;
460 int iz = m_zGrid ? int(pos.z() / m_gridSize.z()) : 0;
461 return {ix, iy, iz};
462 }
463
464 inline bool collision(const GridPosition &gp) {
465 for (int x = gp.x - m_xGrid; x <= gp.x + m_xGrid; ++x)
466 for (int y = gp.y - m_yGrid; y <= gp.y + m_yGrid; ++y)
467 for (int z = gp.z - m_zGrid; z <= gp.z + m_zGrid; ++z )
468 if (m_occupiedCells.contains({x,y,z}))
469 return true;
470 return false;
471 }
472
473 QVector3D generate(QRandomGenerator *rgen) {
474 if (m_gridMode) {
475 while (m_remainingAttempts > 0) {
476 auto pos = genRandom(m_from, m_to, m_proportional, rgen);
477 auto gPos = gridPos(pos);
478 if (!collision(gPos)) {
479 m_occupiedCells.insert(gPos);
480 return pos;
481 }
482 m_remainingAttempts--;
483 }
484 return {};
485 }
486 return genRandom(m_from, m_to, m_proportional, rgen);
487 }
488
489 bool isFull() const { return m_remainingAttempts <= 0; }
490
491private:
492 QVector3D m_from;
493 QVector3D m_to;
494 QVector3D m_gridSize;
495 QSet<GridPosition> m_occupiedCells; // TODO: We could use a Bloom filter (no, not the graphics effect;) to save memory
496 int m_remainingAttempts;
497 bool m_proportional = false;
498 bool m_gridMode = false;
499 bool m_xGrid = false;
500 bool m_yGrid = false;
501 bool m_zGrid = false;
502};
503}
504
505
506void QQuick3DRandomInstancing::generateInstanceTable()
507{
508 m_dirty = false;
509 const int count = m_randomCount;
510
511 QRandomGenerator rgen(m_randomSeed);
512 if (m_randomSeed == -1)
513 rgen.seed(QRandomGenerator::global()->generate());
514
515 qsizetype tableSize = count * sizeof(InstanceTableEntry);
516 m_instanceData.resize(tableSize);
517
518 auto *array = reinterpret_cast<InstanceTableEntry*>(m_instanceData.data());
519
520 PositionGenerator posGen;
521 if (m_position)
522 posGen.init(m_position->from().value<QVector3D>(), m_position->to().value<QVector3D>(), m_position->proportional(), m_gridMode, m_gridSpacing);
523
524 for (int i = 0; i < count; ++i) {
525 QVector3D pos;
526 QVector3D scale{1, 1, 1};
527 QVector3D eulerRotation;
528 QColor color(Qt::white);
529 QVector4D customData;
530
531 if (m_position)
532 pos = posGen.generate(&rgen);
533 if (m_scale)
534 scale = genRandom(m_scale->from().value<QVector3D>(), m_scale->to().value<QVector3D>(), m_scale->proportional(), &rgen);
535 if (m_rotation) //TODO: quaternion rotation???
536 eulerRotation = genRandom(m_rotation->from().value<QVector3D>(), m_rotation->to().value<QVector3D>(), m_rotation->proportional(), &rgen);
537 if (m_color)
538 color = genRandom(m_color->from().value<QColor>(), m_color->to().value<QColor>(), m_color->proportional(), m_colorModel, &rgen);
539 if (m_customData)
540 customData = genRandom(m_customData->from().value<QVector4D>(), m_customData->to().value<QVector4D>(), m_customData->proportional(), &rgen);
541
542 if (Q_UNLIKELY(posGen.isFull())) {
543 qWarning() << "RandomInstancing: Could not find free cell, truncating instance array" << i;
544 qsizetype newSize = i * sizeof(InstanceTableEntry);
545 m_instanceData.truncate(newSize);
546 m_randomCount = i;
547 emit instanceCountChanged();
548 break;
549 }
550
551 array[i] = calculateTableEntry(pos, scale, eulerRotation, color, customData);
552 }
553}
554
555QQuick3DInstanceRange::QQuick3DInstanceRange(QQuick3DObject *parent)
556 : QQuick3DObject(parent)
557{
558
559}
560
561void QQuick3DInstanceRange::setFrom(QVariant from)
562{
563 if (m_from == from)
564 return;
565
566 m_from = from;
567 emit fromChanged();
568 emit changed();
569}
570
571void QQuick3DInstanceRange::setTo(QVariant to)
572{
573 if (m_to == to)
574 return;
575
576 m_to = to;
577 emit toChanged();
578 emit changed();
579}
580
581void QQuick3DInstanceRange::setProportional(bool proportional)
582{
583 if (m_proportional == proportional)
584 return;
585
586 m_proportional = proportional;
587 emit proportionalChanged();
588 emit changed();
589}
590
592{
593 return m_gridSpacing;
594}
595
596void QQuick3DRandomInstancing::setGridSpacing(const QVector3D &newGridSpacing)
597{
598 if (m_gridSpacing == newGridSpacing)
599 return;
600 m_gridSpacing = newGridSpacing;
601 emit gridSpacingChanged();
602 m_gridMode = (newGridSpacing.x() > 0 || newGridSpacing.y() > 0 || newGridSpacing.z() > 0) && !(newGridSpacing.x() < 0 || newGridSpacing.y() < 0 || newGridSpacing.z() < 0);
603 m_dirty = true;
604 markDirty();
605}
606
607QT_END_NAMESPACE
QObject * parent
Definition qobject.h:73
\qmltype Object3D \inqmlmodule QtQuick3D \nativetype QQuick3DObject \inherits QtObject
void setRandomSeed(int randomSeed)
void setGridSpacing(const QVector3D &newGridSpacing)
void setColor(QQuick3DInstanceRange *color)
void setCustomData(QQuick3DInstanceRange *customData)
QByteArray getInstanceBuffer(int *instanceCount) override
Implement this function to return the contents of the instance table.
void setRotation(QQuick3DInstanceRange *rotation)
void setPosition(QQuick3DInstanceRange *position)
void setScale(QQuick3DInstanceRange *scale)
Combined button and popup list for selecting options.
static QVector3D genRandom(const QVector3D &from, const QVector3D &to, bool proportional, QRandomGenerator *rgen)
static float genRandom(float from, float to, QRandomGenerator *rgen)
static QColor genRandom(const QColor &from, const QColor &to, bool proportional, QQuick3DRandomInstancing::ColorModel colorModel, QRandomGenerator *rgen)