1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 Paul Lemire |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the Qt3D module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/QTest> |
30 | #include <Qt3DCore/qentity.h> |
31 | #include <Qt3DCore/qtransform.h> |
32 | #include <Qt3DRender/qgeometry.h> |
33 | #include <Qt3DRender/qgeometryrenderer.h> |
34 | #include <Qt3DRender/qattribute.h> |
35 | #include <Qt3DRender/qbuffer.h> |
36 | #include <Qt3DRender/private/nodemanagers_p.h> |
37 | #include <Qt3DRender/private/managers_p.h> |
38 | #include <Qt3DRender/private/entity_p.h> |
39 | #include <Qt3DRender/private/filterproximitydistancejob_p.h> |
40 | #include <Qt3DRender/private/updatetreeenabledjob_p.h> |
41 | #include <Qt3DRender/private/updateworldtransformjob_p.h> |
42 | #include <Qt3DRender/private/updateworldboundingvolumejob_p.h> |
43 | #include <Qt3DRender/private/calcboundingvolumejob_p.h> |
44 | #include <Qt3DRender/private/expandboundingvolumejob_p.h> |
45 | #include <Qt3DRender/qproximityfilter.h> |
46 | |
47 | #include "testaspect.h" |
48 | |
49 | namespace { |
50 | |
51 | Qt3DCore::QEntity *buildEntityAtDistance(float distance, Qt3DCore::QEntity *parent) |
52 | { |
53 | Qt3DCore::QEntity *entity = new Qt3DCore::QEntity(parent); |
54 | |
55 | // create geometry with a valid bounding volume - a single point is sufficient |
56 | auto geometry = new Qt3DRender::QGeometry; |
57 | auto vertexBuffer = new Qt3DRender::QBuffer(geometry); |
58 | |
59 | auto positionAttribute = new Qt3DRender::QAttribute; |
60 | positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName()); |
61 | positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute); |
62 | positionAttribute->setVertexBaseType(Qt3DRender::QAttribute::Float); |
63 | positionAttribute->setVertexSize(3); |
64 | positionAttribute->setByteStride(3 * sizeof(float)); |
65 | positionAttribute->setBuffer(vertexBuffer); |
66 | |
67 | QByteArray vertexBufferData; |
68 | vertexBufferData.resize(size: static_cast<int>(3 * sizeof(float))); |
69 | |
70 | auto vertexArray = reinterpret_cast<float*>(vertexBufferData.data()); |
71 | |
72 | int i = 0; |
73 | vertexArray[i++] = 0.0f; |
74 | vertexArray[i++] = 0.0f; |
75 | vertexArray[i++] = 0.0f; |
76 | |
77 | vertexBuffer->setData(vertexBufferData); |
78 | positionAttribute->setCount(1); |
79 | |
80 | geometry->addAttribute(attribute: positionAttribute); |
81 | |
82 | auto geometryRenderer = new Qt3DRender::QGeometryRenderer; |
83 | geometryRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Points); |
84 | geometryRenderer->setGeometry(geometry); |
85 | |
86 | entity->addComponent(comp: geometryRenderer); |
87 | |
88 | Qt3DCore::QTransform *transform = new Qt3DCore::QTransform(parent); |
89 | const QVector3D t = QVector3D(1.0f, 0.0f, 0.0f) * distance; |
90 | |
91 | transform->setTranslation(t); |
92 | entity->addComponent(comp: transform); |
93 | |
94 | return entity; |
95 | } |
96 | |
97 | } // anonymous |
98 | |
99 | class tst_ProximityFiltering : public QObject |
100 | { |
101 | Q_OBJECT |
102 | private Q_SLOTS: |
103 | |
104 | void checkInitialState() |
105 | { |
106 | // GIVEN |
107 | Qt3DRender::Render::FilterProximityDistanceJob filterJob; |
108 | |
109 | // THEN |
110 | QCOMPARE(filterJob.hasProximityFilter(), false); |
111 | QCOMPARE(filterJob.filteredEntities().size(), 0); |
112 | QCOMPARE(filterJob.proximityFilterIds().size(), 0); |
113 | QVERIFY(filterJob.manager() == nullptr); |
114 | } |
115 | |
116 | void filterEntities_data() |
117 | { |
118 | QTest::addColumn<Qt3DCore::QEntity *>(name: "entitySubtree" ); |
119 | QTest::addColumn<Qt3DCore::QNodeIdVector>(name: "proximityFilterIds" ); |
120 | QTest::addColumn<Qt3DCore::QNodeIdVector>(name: "expectedSelectedEntities" ); |
121 | |
122 | { |
123 | Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); |
124 | Qt3DCore::QEntity *targetEntity = new Qt3DCore::QEntity(rootEntity); |
125 | Qt3DCore::QEntity *childEntity1 = buildEntityAtDistance(distance: 50.0f, parent: rootEntity); |
126 | Qt3DCore::QEntity *childEntity2 = buildEntityAtDistance(distance: 25.0f, parent: rootEntity); |
127 | Qt3DCore::QEntity *childEntity3 = buildEntityAtDistance(distance: 75.0f, parent: rootEntity); |
128 | |
129 | Qt3DRender::QProximityFilter *proximityFilter = new Qt3DRender::QProximityFilter(rootEntity); |
130 | proximityFilter->setDistanceThreshold(200.0f); |
131 | proximityFilter->setEntity(targetEntity); |
132 | |
133 | QTest::newRow(dataTag: "ShouldSelectAll" ) << rootEntity |
134 | << (Qt3DCore::QNodeIdVector() << proximityFilter->id()) |
135 | << (Qt3DCore::QNodeIdVector() |
136 | << rootEntity->id() |
137 | << targetEntity->id() |
138 | << childEntity1->id() |
139 | << childEntity2->id() |
140 | << childEntity3->id() |
141 | ); |
142 | } |
143 | |
144 | { |
145 | Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); |
146 | Qt3DCore::QEntity *childEntity1 = new Qt3DCore::QEntity(rootEntity); |
147 | Qt3DCore::QEntity *childEntity2 = new Qt3DCore::QEntity(rootEntity); |
148 | Qt3DCore::QEntity *childEntity3 = new Qt3DCore::QEntity(rootEntity); |
149 | Q_UNUSED(childEntity1); |
150 | Q_UNUSED(childEntity2); |
151 | Q_UNUSED(childEntity3); |
152 | |
153 | Qt3DRender::QProximityFilter *proximityFilter = new Qt3DRender::QProximityFilter(rootEntity); |
154 | |
155 | QTest::newRow(dataTag: "ShouldSelectNone" ) << rootEntity |
156 | << (Qt3DCore::QNodeIdVector() << proximityFilter->id()) |
157 | << (Qt3DCore::QNodeIdVector()); |
158 | } |
159 | |
160 | { |
161 | Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); |
162 | Qt3DCore::QEntity *targetEntity = new Qt3DCore::QEntity(rootEntity); |
163 | Qt3DCore::QEntity *childEntity1 = buildEntityAtDistance(distance: 50.0f, parent: rootEntity); |
164 | Qt3DCore::QEntity *childEntity2 = buildEntityAtDistance(distance: 25.0f, parent: rootEntity); |
165 | Qt3DCore::QEntity *childEntity3 = buildEntityAtDistance(distance: 75.0f, parent: rootEntity); |
166 | Qt3DRender::QProximityFilter *proximityFilter = new Qt3DRender::QProximityFilter(rootEntity); |
167 | proximityFilter->setDistanceThreshold(30.0f); |
168 | |
169 | // Note: rootEntity BoundingSphere will be centered in vec3(75.0f / 2.0, 0.0f 0.0f); |
170 | |
171 | // Note: we cannot set rootEntity here as that would mean |
172 | // that the parent of the root would then be the proximity filter |
173 | // (since rootEntity has no parent) but this isn't valid in the way |
174 | // we have build the test |
175 | // Also rootEntity is centered based on the size of the child it contains |
176 | proximityFilter->setEntity(targetEntity); |
177 | |
178 | Q_UNUSED(childEntity1); |
179 | Q_UNUSED(childEntity3); |
180 | |
181 | QTest::newRow(dataTag: "ShouldSelectChild2" ) << rootEntity |
182 | << (Qt3DCore::QNodeIdVector() << proximityFilter->id()) |
183 | << (Qt3DCore::QNodeIdVector() |
184 | << targetEntity->id() |
185 | << childEntity2->id() |
186 | ); |
187 | } |
188 | |
189 | { |
190 | Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); |
191 | Qt3DCore::QEntity *targetEntity = new Qt3DCore::QEntity(rootEntity); |
192 | Qt3DCore::QEntity *childEntity1 = buildEntityAtDistance(distance: 50.0f, parent: rootEntity); |
193 | Qt3DCore::QEntity *childEntity2 = buildEntityAtDistance(distance: 25.0f, parent: rootEntity); |
194 | Qt3DCore::QEntity *childEntity3 = buildEntityAtDistance(distance: 49.9f, parent: rootEntity); |
195 | Qt3DRender::QProximityFilter *proximityFilter = new Qt3DRender::QProximityFilter(rootEntity); |
196 | proximityFilter->setDistanceThreshold(50.0f); |
197 | |
198 | // Note: rootEntity BoundingSphere will be centered in vec3(50.0f / 2.0, 0.0f 0.0f); |
199 | |
200 | // Note: we cannot set rootEntity here as that would mean |
201 | // that the parent of the root would then be the proximity filter |
202 | // (since rootEntity has no parent) but this isn't valid in the way |
203 | // we have build the test |
204 | // Also rootEntity is centered based on the size of the child it contains |
205 | proximityFilter->setEntity(targetEntity); |
206 | |
207 | QTest::newRow(dataTag: "ShouldSelectRootChild123" ) << rootEntity |
208 | << (Qt3DCore::QNodeIdVector() << proximityFilter->id()) |
209 | << (Qt3DCore::QNodeIdVector() |
210 | << rootEntity->id() |
211 | << targetEntity->id() |
212 | << childEntity1->id() |
213 | << childEntity2->id() |
214 | << childEntity3->id() |
215 | ); |
216 | } |
217 | |
218 | { |
219 | Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); |
220 | Qt3DCore::QEntity *targetEntity = new Qt3DCore::QEntity(rootEntity); |
221 | Qt3DCore::QEntity *childEntity1 = buildEntityAtDistance(distance: 51.0f, parent: rootEntity); |
222 | Qt3DCore::QEntity *childEntity2 = buildEntityAtDistance(distance: 75.0f, parent: rootEntity); |
223 | Qt3DCore::QEntity *childEntity3 = buildEntityAtDistance(distance: 883.0f, parent: rootEntity); |
224 | Qt3DRender::QProximityFilter *proximityFilter = new Qt3DRender::QProximityFilter(rootEntity); |
225 | proximityFilter->setDistanceThreshold(50.0f); |
226 | |
227 | // Note: rootEntity BoundingSphere will be centered in vec3(883.0f / 2.0, 0.0f 0.0f); |
228 | |
229 | // Note: we cannot set rootEntity here as that would mean |
230 | // that the parent of the root would then be the proximity filter |
231 | // (since rootEntity has no parent) but this isn't valid in the way |
232 | // we have build the test |
233 | // Also rootEntity is centered based on the size of the child it contains |
234 | proximityFilter->setEntity(targetEntity); |
235 | |
236 | Q_UNUSED(childEntity1); |
237 | Q_UNUSED(childEntity2); |
238 | Q_UNUSED(childEntity3); |
239 | |
240 | QTest::newRow(dataTag: "ShouldSelectNoneButTarget" ) << rootEntity |
241 | << (Qt3DCore::QNodeIdVector() << proximityFilter->id()) |
242 | << (Qt3DCore::QNodeIdVector() << targetEntity->id()); |
243 | } |
244 | |
245 | { |
246 | Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity(); |
247 | Qt3DCore::QEntity *targetEntity = new Qt3DCore::QEntity(rootEntity); |
248 | Qt3DCore::QEntity *childEntity1 = buildEntityAtDistance(distance: 50.0f, parent: rootEntity); |
249 | Qt3DCore::QEntity *childEntity2 = buildEntityAtDistance(distance: 150.0f, parent: rootEntity); |
250 | Qt3DCore::QEntity *childEntity3 = buildEntityAtDistance(distance: 25.0f, parent: rootEntity); |
251 | |
252 | Qt3DRender::QProximityFilter *proximityFilter = new Qt3DRender::QProximityFilter(rootEntity); |
253 | proximityFilter->setDistanceThreshold(50.0f); |
254 | |
255 | Qt3DRender::QProximityFilter *proximityFilter2 = new Qt3DRender::QProximityFilter(rootEntity); |
256 | proximityFilter2->setDistanceThreshold(30.0f); |
257 | |
258 | // Note: rootEntity BoundingSphere will be centered in vec3(150.0f / 2.0, 0.0f 0.0f); |
259 | |
260 | // Note: we cannot set rootEntity here as that would mean |
261 | // that the parent of the root would then be the proximity filter |
262 | // (since rootEntity has no parent) but this isn't valid in the way |
263 | // we have build the test |
264 | // Also rootEntity is centered based on the size of the child it contains |
265 | proximityFilter->setEntity(targetEntity); |
266 | proximityFilter2->setEntity(targetEntity); |
267 | |
268 | Q_UNUSED(childEntity2); |
269 | |
270 | QTest::newRow(dataTag: "Nested-Step1" ) << rootEntity |
271 | << (Qt3DCore::QNodeIdVector() << proximityFilter->id()) |
272 | << (Qt3DCore::QNodeIdVector() |
273 | << targetEntity->id() |
274 | << childEntity1->id() |
275 | << childEntity3->id() |
276 | ); |
277 | QTest::newRow(dataTag: "Nested-Step2" ) << rootEntity |
278 | << (Qt3DCore::QNodeIdVector() << proximityFilter->id() << proximityFilter2->id()) |
279 | << (Qt3DCore::QNodeIdVector() |
280 | << targetEntity->id() |
281 | << childEntity3->id() |
282 | ); |
283 | } |
284 | } |
285 | |
286 | void filterEntities() |
287 | { |
288 | QFETCH(Qt3DCore::QEntity *, entitySubtree); |
289 | QFETCH(Qt3DCore::QNodeIdVector, proximityFilterIds); |
290 | QFETCH(Qt3DCore::QNodeIdVector, expectedSelectedEntities); |
291 | |
292 | // GIVEN |
293 | QScopedPointer<Qt3DRender::TestAspect> aspect(new Qt3DRender::TestAspect(entitySubtree)); |
294 | |
295 | // WHEN |
296 | Qt3DRender::Render::Entity *backendRoot = aspect->nodeManagers()->renderNodesManager()->getOrCreateResource(id: entitySubtree->id()); |
297 | |
298 | Qt3DRender::Render::UpdateTreeEnabledJob updateTreeEnabledJob; |
299 | updateTreeEnabledJob.setRoot(backendRoot); |
300 | updateTreeEnabledJob.setManagers(aspect->nodeManagers()); |
301 | updateTreeEnabledJob.run(); |
302 | |
303 | Qt3DRender::Render::UpdateWorldTransformJob updateWorldTransform; |
304 | updateWorldTransform.setRoot(backendRoot); |
305 | updateWorldTransform.setManagers(aspect->nodeManagers()); |
306 | updateWorldTransform.run(); |
307 | |
308 | Qt3DRender::Render::CalculateBoundingVolumeJob calcBVolume; |
309 | calcBVolume.setManagers(aspect->nodeManagers()); |
310 | calcBVolume.setRoot(backendRoot); |
311 | calcBVolume.run(); |
312 | |
313 | Qt3DRender::Render::UpdateWorldBoundingVolumeJob updateWorldBVolume; |
314 | updateWorldBVolume.setManager(aspect->nodeManagers()->renderNodesManager()); |
315 | updateWorldBVolume.run(); |
316 | |
317 | Qt3DRender::Render::ExpandBoundingVolumeJob expandBVolume; |
318 | expandBVolume.setRoot(backendRoot); |
319 | expandBVolume.setManagers(aspect->nodeManagers()); |
320 | expandBVolume.run(); |
321 | |
322 | // WHEN |
323 | Qt3DRender::Render::FilterProximityDistanceJob filterJob; |
324 | filterJob.setProximityFilterIds(proximityFilterIds); |
325 | filterJob.setManager(aspect->nodeManagers()); |
326 | filterJob.run(); |
327 | |
328 | // THEN |
329 | const QVector<Qt3DRender::Render::Entity *> filterEntities = filterJob.filteredEntities(); |
330 | QCOMPARE(filterEntities.size(), expectedSelectedEntities.size()); |
331 | |
332 | for (auto i = 0, m = expectedSelectedEntities.size(); i < m; ++i) |
333 | QCOMPARE(filterEntities.at(i)->peerId(), expectedSelectedEntities.at(i)); |
334 | } |
335 | }; |
336 | |
337 | QTEST_MAIN(tst_ProximityFiltering) |
338 | |
339 | #include "tst_proximityfiltering.moc" |
340 | |