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