1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "randominstancing_p.h"
5#include <QRandomGenerator>
6#include <QObject>
7
8QT_BEGIN_NAMESPACE
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 InstanceRange RandomInstancing::scale
110
111 The scale property defines the scaling limits for the generated instances. The type is
112 \l vector3d.
113 Set \l {InstanceRange::proportional}{InstanceRange.proportional} to \c true for uniform scaling.
114 The default value is empty, causing no scaling to be applied.
115
116 \sa position, color, rotation, scale, customData
117*/
118
119/*!
120 \qmlproperty InstanceRange RandomInstancing::rotation
121
122 The rotation property defines the rotation range for the generated instances. The type is
123 \l vector3d, corresponding to a Euler rotation vector \c{[xRotation, yRotation, zRotation]}.
124 The default value is empty, causing no rotation to be applied.
125
126 \sa position, color, scale, customData
127*/
128
129/*!
130 \qmlproperty InstanceRange RandomInstancing::color
131
132
133 The color property defines the color variation range for the generated instances. The type is \l color.
134 The default value is empty, causing the color to be white.
135
136 Setting the colorModel property makes it possible to select only saturated colors, for example.
137
138 \sa position, rotation, scale, customData
139*/
140
141/*!
142 \qmlproperty InstanceRange RandomInstancing::customData
143
144 The customData property defines the custom data variation range for the generated instances.
145 The type is \l vector4d.
146 The default value is empty, causing causing the generated data to be \c{[0, 0, 0, 0]}.
147
148 \sa position, color, rotation, scale, customData
149*/
150/*!
151 \qmlproperty enumeration RandomInstancing::colorModel
152
153 This property controls how the color range is interpreted.
154
155 The instance colors are generated component by component within the range determined by the
156 \e from and \e to colors. The color model determines how those components are defined.
157
158 \value RandomInstancing.RGB
159 The components are red, green, blue, and alpha, according to the RGB color model.
160 \value RandomInstancing.HSV
161 The components are hue, saturation, value, and alpha, according to the \l{QColor#The HSV Color Model}{HSV Color Model}.
162 \value RandomInstancing.HSL
163 The components are hue, saturation, lightness, and alpha,, according to the \l{QColor#The HSL Color Model}{HSL Color Model}.
164
165 As an example, the following color range
166 \qml
167 color: InstanceRange {
168 from: Qt.hsva(0, 0.1, 0.8, 1)
169 to: Qt.hsva(1, 0.3, 1, 1)
170 }
171 \endqml
172 will generate a full range of pastel colors when using the \c HSV color model, but only shades of pink
173 when using the \c RGB color model.
174
175 The default value is \c RandomInstancing.RGB
176
177 \sa RandomInstancing::color
178*/
179
180QQuick3DRandomInstancing::QQuick3DRandomInstancing(QQuick3DObject *parent)
181 : QQuick3DInstancing(parent)
182{
183
184}
185
186QQuick3DRandomInstancing::~QQuick3DRandomInstancing()
187{
188}
189
190void QQuick3DRandomInstancing::setInstanceCount(int instanceCount)
191{
192 if (instanceCount == m_randomCount)
193 return;
194 m_randomCount = instanceCount;
195 m_dirty = true;
196 markDirty();
197}
198
199void QQuick3DRandomInstancing::setRandomSeed(int randomSeed)
200{
201 if (m_randomSeed == randomSeed)
202 return;
203
204 m_randomSeed = randomSeed;
205 emit randomSeedChanged();
206 m_dirty = true;
207 markDirty();
208}
209
210void QQuick3DRandomInstancing::setPosition(QQuick3DInstanceRange *position)
211{
212 if (m_position == position)
213 return;
214
215 if (m_position)
216 disconnect(sender: m_position, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
217 m_position = position;
218 emit positionChanged();
219 m_dirty = true;
220 markDirty();
221 if (m_position) {
222 connect(sender: m_position, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
223 connect(sender: m_position, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_position) m_position = nullptr; });
224 }
225}
226
227void QQuick3DRandomInstancing::setScale(QQuick3DInstanceRange *scale)
228{
229 if (m_scale == scale)
230 return;
231
232 if (m_scale)
233 disconnect(sender: m_scale, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
234 m_scale = scale;
235 emit scaleChanged();
236 m_dirty = true;
237 markDirty();
238 if (m_scale) {
239 connect(sender: m_scale, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
240 connect(sender: m_scale, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_scale) m_scale = nullptr; });
241 }
242}
243
244void QQuick3DRandomInstancing::setRotation(QQuick3DInstanceRange *rotation)
245{
246 if (m_rotation == rotation)
247 return;
248
249 if (m_rotation)
250 disconnect(sender: m_rotation, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
251 m_rotation = rotation;
252 emit rotationChanged();
253 m_dirty = true;
254 markDirty();
255 if (m_rotation) {
256 connect(sender: m_rotation, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
257 connect(sender: m_rotation, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_rotation) m_rotation = nullptr; });
258 }
259}
260
261void QQuick3DRandomInstancing::setColor(QQuick3DInstanceRange *color)
262{
263 if (m_color == color)
264 return;
265
266 if (m_color)
267 disconnect(sender: m_color, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
268 m_color = color;
269 emit colorChanged();
270 m_dirty = true;
271 markDirty();
272 if (m_color) {
273 connect(sender: m_color, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
274 connect(sender: m_color, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_color) m_color = nullptr; });
275 }
276
277}
278
279void QQuick3DRandomInstancing::setCustomData(QQuick3DInstanceRange *customData)
280{
281 if (m_customData == customData)
282 return;
283
284 if (m_customData)
285 disconnect(sender: m_customData, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
286 m_customData = customData;
287 emit customDataChanged();
288 m_dirty = true;
289 markDirty();
290 if (m_customData) {
291 connect(sender: m_customData, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
292 connect(sender: m_customData, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_customData) m_customData = nullptr; });
293 }
294}
295
296void QQuick3DRandomInstancing::setColorModel(QQuick3DRandomInstancing::ColorModel colorModel)
297{
298 if (m_colorModel == colorModel)
299 return;
300 m_colorModel = colorModel;
301 emit colorModelChanged();
302 m_dirty = true;
303 markDirty();
304}
305
306void QQuick3DRandomInstancing::handleChange()
307{
308 m_dirty = true;
309 markDirty();
310}
311
312static inline float genRandom(float from, float to, QRandomGenerator *rgen)
313{
314 float c = rgen->bounded(highest: 1.0);
315 return from + c * (to - from);
316}
317
318static QVector3D genRandom(const QVector3D &from, const QVector3D &to, bool proportional, QRandomGenerator *rgen)
319{
320 if (proportional) {
321 float c = rgen->bounded(highest: 1.0);
322 return from + c * (to - from);
323 }
324 return { genRandom(from: from.x(), to: to.x(), rgen), genRandom(from: from.y(), to: to.y(), rgen), genRandom(from: from.z(), to: to.z(), rgen) };
325}
326
327static QVector4D genRandom(const QVector4D &from, const QVector4D &to, bool proportional, QRandomGenerator *rgen)
328{
329 if (proportional) {
330 float c = rgen->bounded(highest: 1.0);
331 return from + c * (to - from);
332 }
333 return { genRandom(from: from.x(), to: to.x(), rgen), genRandom(from: from.y(), to: to.y(), rgen), genRandom(from: from.z(), to: to.z(), rgen), genRandom(from: from.w(), to: to.w(), rgen) };
334}
335
336static QColor genRandom(const QColor &from, const QColor &to, bool proportional, QQuick3DRandomInstancing::ColorModel colorModel, QRandomGenerator *rgen)
337{
338 QVector4D v1, v2;
339 switch (colorModel) {
340 case QQuick3DRandomInstancing::ColorModel::HSL:
341 from.getHslF(h: &v1[0], s: &v1[1], l: &v1[2], a: &v1[3]);
342 to.getHslF(h: &v2[0], s: &v2[1], l: &v2[2], a: &v2[3]);
343 break;
344 case QQuick3DRandomInstancing::ColorModel::HSV:
345 from.getHsvF(h: &v1[0], s: &v1[1], v: &v1[2], a: &v1[3]);
346 to.getHsvF(h: &v2[0], s: &v2[1], v: &v2[2], a: &v2[3]);
347 break;
348 case QQuick3DRandomInstancing::ColorModel::RGB:
349 default:
350 from.getRgbF(r: &v1[0], g: &v1[1], b: &v1[2], a: &v1[3]);
351 to.getRgbF(r: &v2[0], g: &v2[1], b: &v2[2], a: &v2[3]);
352 break;
353 }
354 QVector4D r = genRandom(from: v1, to: v2, proportional, rgen);
355
356 switch (colorModel) {
357 case QQuick3DRandomInstancing::ColorModel::HSL:
358 return QColor::fromHslF(h: r[0], s: r[1], l: r[2], a: r[3]);
359 break;
360 case QQuick3DRandomInstancing::ColorModel::HSV:
361 return QColor::fromHsvF(h: r[0], s: r[1], v: r[2], a: r[3]);
362 break;
363 case QQuick3DRandomInstancing::ColorModel::RGB:
364 default:
365 return QColor::fromRgbF(r: r[0], g: r[1], b: r[2], a: r[3]);
366 }
367}
368
369QByteArray QQuick3DRandomInstancing::getInstanceBuffer(int *instanceCount)
370{
371 if (m_dirty)
372 generateInstanceTable();
373 if (instanceCount)
374 *instanceCount = m_randomCount;
375 return m_instanceData;
376}
377
378void QQuick3DRandomInstancing::generateInstanceTable()
379{
380 m_dirty = false;
381 const int count = m_randomCount;
382
383 QRandomGenerator rgen(m_randomSeed);
384 if (m_randomSeed == -1)
385 rgen.seed(s: QRandomGenerator::global()->generate());
386
387 qsizetype tableSize = count * sizeof(InstanceTableEntry);
388 m_instanceData.resize(size: tableSize);
389
390 //qDebug() << "generating" << count << "instances, for total size" << tableSize;
391 auto *array = reinterpret_cast<InstanceTableEntry*>(m_instanceData.data());
392 for (int i = 0; i < count; ++i) {
393 QVector3D pos;
394 QVector3D scale{1, 1, 1};
395 QVector3D eulerRotation;
396 QColor color(Qt::white);
397 QVector4D customData;
398 if (m_position)
399 pos = genRandom(from: m_position->from().value<QVector3D>(), to: m_position->to().value<QVector3D>(), proportional: m_position->proportional(), rgen: &rgen);
400 if (m_scale)
401 scale = genRandom(from: m_scale->from().value<QVector3D>(), to: m_scale->to().value<QVector3D>(), proportional: m_scale->proportional(), rgen: &rgen);
402 if (m_rotation) //TODO: quaternion rotation???
403 eulerRotation = genRandom(from: m_rotation->from().value<QVector3D>(), to: m_rotation->to().value<QVector3D>(), proportional: m_rotation->proportional(), rgen: &rgen);
404 if (m_color)
405 color = genRandom(from: m_color->from().value<QColor>(), to: m_color->to().value<QColor>(), proportional: m_color->proportional(), colorModel: m_colorModel, rgen: &rgen);
406 if (m_customData)
407 customData = genRandom(from: m_customData->from().value<QVector4D>(), to: m_customData->to().value<QVector4D>(), proportional: m_customData->proportional(), rgen: &rgen);
408
409 array[i] = calculateTableEntry(position: pos, scale, eulerRotation, color, customData);
410 }
411}
412
413QQuick3DInstanceRange::QQuick3DInstanceRange(QQuick3DObject *parent)
414 : QQuick3DObject(parent)
415{
416
417}
418
419void QQuick3DInstanceRange::setFrom(QVariant from)
420{
421 if (m_from == from)
422 return;
423
424 m_from = from;
425 emit fromChanged();
426 emit changed();
427}
428
429void QQuick3DInstanceRange::setTo(QVariant to)
430{
431 if (m_to == to)
432 return;
433
434 m_to = to;
435 emit toChanged();
436 emit changed();
437}
438
439void QQuick3DInstanceRange::setProportional(bool proportional)
440{
441 if (m_proportional == proportional)
442 return;
443
444 m_proportional = proportional;
445 emit proportionalChanged();
446 emit changed();
447}
448
449QT_END_NAMESPACE
450

source code of qtquick3d/src/helpers/randominstancing.cpp