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 | |
8 | QT_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 | |
180 | QQuick3DRandomInstancing::QQuick3DRandomInstancing(QQuick3DObject *parent) |
181 | : QQuick3DInstancing(parent) |
182 | { |
183 | |
184 | } |
185 | |
186 | QQuick3DRandomInstancing::~QQuick3DRandomInstancing() |
187 | { |
188 | } |
189 | |
190 | void 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 | |
200 | void 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 | |
211 | void 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 | |
228 | void 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 | |
245 | void 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 | |
262 | void 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 | |
280 | void 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 | |
297 | void 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 | |
307 | void QQuick3DRandomInstancing::handleChange() |
308 | { |
309 | m_dirty = true; |
310 | markDirty(); |
311 | } |
312 | |
313 | static 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 | |
319 | static 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 | |
328 | static 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 | |
337 | static 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 | |
370 | QByteArray QQuick3DRandomInstancing::getInstanceBuffer(int *instanceCount) |
371 | { |
372 | if (m_dirty) |
373 | generateInstanceTable(); |
374 | if (instanceCount) |
375 | *instanceCount = m_randomCount; |
376 | return m_instanceData; |
377 | } |
378 | |
379 | void 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 | |
414 | QQuick3DInstanceRange::QQuick3DInstanceRange(QQuick3DObject *parent) |
415 | : QQuick3DObject(parent) |
416 | { |
417 | |
418 | } |
419 | |
420 | void 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 | |
430 | void 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 | |
440 | void 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 | |
450 | QT_END_NAMESPACE |
451 | |