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 emit instanceCountChanged();
196 m_dirty = true;
197 markDirty();
198}
199
200void QQuick3DRandomInstancing::setRandomSeed(int randomSeed)
201{
202 if (m_randomSeed == randomSeed)
203 return;
204
205 m_randomSeed = randomSeed;
206 emit randomSeedChanged();
207 m_dirty = true;
208 markDirty();
209}
210
211void QQuick3DRandomInstancing::setPosition(QQuick3DInstanceRange *position)
212{
213 if (m_position == position)
214 return;
215
216 if (m_position)
217 disconnect(sender: m_position, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
218 m_position = position;
219 emit positionChanged();
220 m_dirty = true;
221 markDirty();
222 if (m_position) {
223 connect(sender: m_position, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
224 connect(sender: m_position, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_position) m_position = nullptr; });
225 }
226}
227
228void QQuick3DRandomInstancing::setScale(QQuick3DInstanceRange *scale)
229{
230 if (m_scale == scale)
231 return;
232
233 if (m_scale)
234 disconnect(sender: m_scale, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
235 m_scale = scale;
236 emit scaleChanged();
237 m_dirty = true;
238 markDirty();
239 if (m_scale) {
240 connect(sender: m_scale, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
241 connect(sender: m_scale, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_scale) m_scale = nullptr; });
242 }
243}
244
245void QQuick3DRandomInstancing::setRotation(QQuick3DInstanceRange *rotation)
246{
247 if (m_rotation == rotation)
248 return;
249
250 if (m_rotation)
251 disconnect(sender: m_rotation, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
252 m_rotation = rotation;
253 emit rotationChanged();
254 m_dirty = true;
255 markDirty();
256 if (m_rotation) {
257 connect(sender: m_rotation, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
258 connect(sender: m_rotation, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_rotation) m_rotation = nullptr; });
259 }
260}
261
262void QQuick3DRandomInstancing::setColor(QQuick3DInstanceRange *color)
263{
264 if (m_color == color)
265 return;
266
267 if (m_color)
268 disconnect(sender: m_color, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
269 m_color = color;
270 emit colorChanged();
271 m_dirty = true;
272 markDirty();
273 if (m_color) {
274 connect(sender: m_color, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
275 connect(sender: m_color, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_color) m_color = nullptr; });
276 }
277
278}
279
280void QQuick3DRandomInstancing::setCustomData(QQuick3DInstanceRange *customData)
281{
282 if (m_customData == customData)
283 return;
284
285 if (m_customData)
286 disconnect(sender: m_customData, signal: &QQuick3DInstanceRange::changed, receiver: this, slot: &QQuick3DRandomInstancing::handleChange);
287 m_customData = customData;
288 emit customDataChanged();
289 m_dirty = true;
290 markDirty();
291 if (m_customData) {
292 connect(sender: m_customData, signal: &QQuick3DInstanceRange::changed, context: this, slot: &QQuick3DRandomInstancing::handleChange);
293 connect(sender: m_customData, signal: &QObject::destroyed, context: this, slot: [this](QObject *obj){ if (obj == m_customData) m_customData = nullptr; });
294 }
295}
296
297void QQuick3DRandomInstancing::setColorModel(QQuick3DRandomInstancing::ColorModel colorModel)
298{
299 if (m_colorModel == colorModel)
300 return;
301 m_colorModel = colorModel;
302 emit colorModelChanged();
303 m_dirty = true;
304 markDirty();
305}
306
307void QQuick3DRandomInstancing::handleChange()
308{
309 m_dirty = true;
310 markDirty();
311}
312
313static inline float genRandom(float from, float to, QRandomGenerator *rgen)
314{
315 float c = rgen->bounded(highest: 1.0);
316 return from + c * (to - from);
317}
318
319static QVector3D genRandom(const QVector3D &from, const QVector3D &to, bool proportional, QRandomGenerator *rgen)
320{
321 if (proportional) {
322 float c = rgen->bounded(highest: 1.0);
323 return from + c * (to - from);
324 }
325 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) };
326}
327
328static QVector4D genRandom(const QVector4D &from, const QVector4D &to, bool proportional, QRandomGenerator *rgen)
329{
330 if (proportional) {
331 float c = rgen->bounded(highest: 1.0);
332 return from + c * (to - from);
333 }
334 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) };
335}
336
337static QColor genRandom(const QColor &from, const QColor &to, bool proportional, QQuick3DRandomInstancing::ColorModel colorModel, QRandomGenerator *rgen)
338{
339 QVector4D v1, v2;
340 switch (colorModel) {
341 case QQuick3DRandomInstancing::ColorModel::HSL:
342 from.getHslF(h: &v1[0], s: &v1[1], l: &v1[2], a: &v1[3]);
343 to.getHslF(h: &v2[0], s: &v2[1], l: &v2[2], a: &v2[3]);
344 break;
345 case QQuick3DRandomInstancing::ColorModel::HSV:
346 from.getHsvF(h: &v1[0], s: &v1[1], v: &v1[2], a: &v1[3]);
347 to.getHsvF(h: &v2[0], s: &v2[1], v: &v2[2], a: &v2[3]);
348 break;
349 case QQuick3DRandomInstancing::ColorModel::RGB:
350 default:
351 from.getRgbF(r: &v1[0], g: &v1[1], b: &v1[2], a: &v1[3]);
352 to.getRgbF(r: &v2[0], g: &v2[1], b: &v2[2], a: &v2[3]);
353 break;
354 }
355 QVector4D r = genRandom(from: v1, to: v2, proportional, rgen);
356
357 switch (colorModel) {
358 case QQuick3DRandomInstancing::ColorModel::HSL:
359 return QColor::fromHslF(h: r[0], s: r[1], l: r[2], a: r[3]);
360 break;
361 case QQuick3DRandomInstancing::ColorModel::HSV:
362 return QColor::fromHsvF(h: r[0], s: r[1], v: r[2], a: r[3]);
363 break;
364 case QQuick3DRandomInstancing::ColorModel::RGB:
365 default:
366 return QColor::fromRgbF(r: r[0], g: r[1], b: r[2], a: r[3]);
367 }
368}
369
370QByteArray QQuick3DRandomInstancing::getInstanceBuffer(int *instanceCount)
371{
372 if (m_dirty)
373 generateInstanceTable();
374 if (instanceCount)
375 *instanceCount = m_randomCount;
376 return m_instanceData;
377}
378
379void QQuick3DRandomInstancing::generateInstanceTable()
380{
381 m_dirty = false;
382 const int count = m_randomCount;
383
384 QRandomGenerator rgen(m_randomSeed);
385 if (m_randomSeed == -1)
386 rgen.seed(s: QRandomGenerator::global()->generate());
387
388 qsizetype tableSize = count * sizeof(InstanceTableEntry);
389 m_instanceData.resize(size: tableSize);
390
391 //qDebug() << "generating" << count << "instances, for total size" << tableSize;
392 auto *array = reinterpret_cast<InstanceTableEntry*>(m_instanceData.data());
393 for (int i = 0; i < count; ++i) {
394 QVector3D pos;
395 QVector3D scale{1, 1, 1};
396 QVector3D eulerRotation;
397 QColor color(Qt::white);
398 QVector4D customData;
399 if (m_position)
400 pos = genRandom(from: m_position->from().value<QVector3D>(), to: m_position->to().value<QVector3D>(), proportional: m_position->proportional(), rgen: &rgen);
401 if (m_scale)
402 scale = genRandom(from: m_scale->from().value<QVector3D>(), to: m_scale->to().value<QVector3D>(), proportional: m_scale->proportional(), rgen: &rgen);
403 if (m_rotation) //TODO: quaternion rotation???
404 eulerRotation = genRandom(from: m_rotation->from().value<QVector3D>(), to: m_rotation->to().value<QVector3D>(), proportional: m_rotation->proportional(), rgen: &rgen);
405 if (m_color)
406 color = genRandom(from: m_color->from().value<QColor>(), to: m_color->to().value<QColor>(), proportional: m_color->proportional(), colorModel: m_colorModel, rgen: &rgen);
407 if (m_customData)
408 customData = genRandom(from: m_customData->from().value<QVector4D>(), to: m_customData->to().value<QVector4D>(), proportional: m_customData->proportional(), rgen: &rgen);
409
410 array[i] = calculateTableEntry(position: pos, scale, eulerRotation, color, customData);
411 }
412}
413
414QQuick3DInstanceRange::QQuick3DInstanceRange(QQuick3DObject *parent)
415 : QQuick3DObject(parent)
416{
417
418}
419
420void QQuick3DInstanceRange::setFrom(QVariant from)
421{
422 if (m_from == from)
423 return;
424
425 m_from = from;
426 emit fromChanged();
427 emit changed();
428}
429
430void QQuick3DInstanceRange::setTo(QVariant to)
431{
432 if (m_to == to)
433 return;
434
435 m_to = to;
436 emit toChanged();
437 emit changed();
438}
439
440void QQuick3DInstanceRange::setProportional(bool proportional)
441{
442 if (m_proportional == proportional)
443 return;
444
445 m_proportional = proportional;
446 emit proportionalChanged();
447 emit changed();
448}
449
450QT_END_NAMESPACE
451

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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