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 | m_dirty = true; |
196 | markDirty(); |
197 | } |
198 | |
199 | void 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 | |
210 | void 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 | |
227 | void 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 | |
244 | void 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 | |
261 | void 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 | |
279 | void 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 | |
296 | void 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 | |
306 | void QQuick3DRandomInstancing::handleChange() |
307 | { |
308 | m_dirty = true; |
309 | markDirty(); |
310 | } |
311 | |
312 | static 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 | |
318 | static 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 | |
327 | static 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 | |
336 | static 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 | |
369 | QByteArray QQuick3DRandomInstancing::getInstanceBuffer(int *instanceCount) |
370 | { |
371 | if (m_dirty) |
372 | generateInstanceTable(); |
373 | if (instanceCount) |
374 | *instanceCount = m_randomCount; |
375 | return m_instanceData; |
376 | } |
377 | |
378 | void 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 | |
413 | QQuick3DInstanceRange::QQuick3DInstanceRange(QQuick3DObject *parent) |
414 | : QQuick3DObject(parent) |
415 | { |
416 | |
417 | } |
418 | |
419 | void 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 | |
429 | void 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 | |
439 | void 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 | |
449 | QT_END_NAMESPACE |
450 | |