Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qheightfieldshape.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qcacheutils_p.h"
6
7#include <QFileInfo>
8#include <QImage>
9#include <QQmlContext>
10#include <QQmlFile>
11#include <QtQuick3D/QQuick3DGeometry>
12#include <extensions/PxExtensionsAPI.h>
13
14//########################################################################################
15// NOTE:
16// Triangle mesh, heightfield or plane geometry shapes configured as eSIMULATION_SHAPE are
17// not supported for non-kinematic PxRigidDynamic instances.
18//########################################################################################
19
20#include "foundation/PxVec3.h"
21//#include "cooking/PxTriangleMeshDesc.h"
22#include "extensions/PxDefaultStreams.h"
23#include "geometry/PxHeightField.h"
24#include "geometry/PxHeightFieldDesc.h"
25
26#include "qphysicsworld_p.h"
27
29
30// TODO: Unify with QQuick3DPhysicsMeshManager??? It's the same basic logic,
31// but we're using images instead of meshes.
32
34{
35public:
36 QQuick3DPhysicsHeightField(const QString &qmlSource);
39
40 void ref() { ++refCount; }
41 int deref() { return --refCount; }
42 void writeSamples(const QImage &heightMap);
43 physx::PxHeightField *heightField();
44
45 int rows() const;
46 int columns() const;
47
48private:
49 QString m_sourcePath;
50 // This raw pointer is safe to store since when the Image or
51 // HeightFieldShape is destroyed, this heightfield will be dereferenced
52 // from all shapes and deleted.
53 QQuickImage *m_image = nullptr;
54 physx::PxHeightFieldSample *m_samples = nullptr;
55 physx::PxHeightField *m_heightField = nullptr;
56 int m_rows = 0;
57 int m_columns = 0;
58 int refCount = 0;
59};
60
62{
63public:
65 const QObject *contextObject);
67 static void releaseHeightField(QQuick3DPhysicsHeightField *heightField);
68
69private:
70 static QHash<QString, QQuick3DPhysicsHeightField *> heightFieldHash;
71 static QHash<QQuickImage *, QQuick3DPhysicsHeightField *> heightFieldImageHash;
72};
73
74QHash<QString, QQuick3DPhysicsHeightField *> QQuick3DPhysicsHeightFieldManager::heightFieldHash;
75QHash<QQuickImage *, QQuick3DPhysicsHeightField *>
76 QQuick3DPhysicsHeightFieldManager::heightFieldImageHash;
77
80{
81 const QQmlContext *context = qmlContext(contextObject);
82
83 const auto resolvedUrl = context ? context->resolvedUrl(source) : source;
84 const auto qmlSource = QQmlFile::urlToLocalFileOrQrc(resolvedUrl);
85
86 auto *heightField = heightFieldHash.value(qmlSource);
87 if (!heightField) {
88 heightField = new QQuick3DPhysicsHeightField(qmlSource);
89 heightFieldHash[qmlSource] = heightField;
90 }
91 heightField->ref();
92 return heightField;
93}
94
96{
97 auto *heightField = heightFieldImageHash.value(source);
98 if (!heightField) {
99 heightField = new QQuick3DPhysicsHeightField(source);
100 heightFieldImageHash[source] = heightField;
101 }
102 heightField->ref();
103 return heightField;
104}
105
107{
108 if (heightField != nullptr && heightField->deref() == 0) {
109 qCDebug(lcQuick3dPhysics()) << "deleting height field" << heightField;
110 erase_if(heightFieldHash,
111 [heightField](std::pair<const QString &, QQuick3DPhysicsHeightField *&> h) {
112 return h.second == heightField;
113 });
114 erase_if(heightFieldImageHash,
115 [heightField](std::pair<QQuickImage *, QQuick3DPhysicsHeightField *&> h) {
116 return h.second == heightField;
117 });
118 delete heightField;
119 }
120}
121
123 : m_sourcePath(qmlSource)
124{
125}
126
128
133
135{
136 m_rows = heightMap.height();
137 m_columns = heightMap.width();
138 int numRows = m_rows;
139 int numCols = m_columns;
140
141 free(m_samples);
142 m_samples = reinterpret_cast<physx::PxHeightFieldSample *>(
143 malloc(sizeof(physx::PxHeightFieldSample) * (numRows * numCols)));
144 for (int i = 0; i < numCols; i++)
145 for (int j = 0; j < numRows; j++) {
146 float f = heightMap.pixelColor(i, j).valueF() - 0.5;
147 // qDebug() << i << j << f;
148 m_samples[i * numRows + j] = { qint16(0xffff * f), 0, 0 }; //{qint16(i%3*2 + j), 0, 0};
149 }
150}
151
153{
154 if (m_heightField)
155 return m_heightField;
156
157 physx::PxPhysics *thePhysics = QPhysicsWorld::getPhysics();
158 if (thePhysics == nullptr)
159 return nullptr;
160
161 // No source set
162 if (m_image == nullptr && m_sourcePath.isEmpty())
163 return nullptr;
164
165 // Reading from image property has precedence
166 const bool readFromFile = m_image == nullptr;
167
168 if (readFromFile) {
169 // Try read cached file
170 m_heightField = QCacheUtils::readCachedHeightField(m_sourcePath, *thePhysics);
171 if (m_heightField != nullptr) {
172 m_rows = m_heightField->getNbRows();
173 m_columns = m_heightField->getNbColumns();
174 return m_heightField;
175 }
176
177 // Try read cooked file
178 m_heightField = QCacheUtils::readCookedHeightField(m_sourcePath, *thePhysics);
179 if (m_heightField != nullptr) {
180 m_rows = m_heightField->getNbRows();
181 m_columns = m_heightField->getNbColumns();
182 return m_heightField;
183 }
184
185 // Try read image file
186 writeSamples(QImage(m_sourcePath));
187 } else {
188 writeSamples(m_image->image());
189 }
190
191 int numRows = m_rows;
192 int numCols = m_columns;
193 auto samples = m_samples;
194
195 physx::PxHeightFieldDesc hfDesc;
196 hfDesc.format = physx::PxHeightFieldFormat::eS16_TM;
197 hfDesc.nbColumns = numRows;
198 hfDesc.nbRows = numCols;
199 hfDesc.samples.data = samples;
200 hfDesc.samples.stride = sizeof(physx::PxHeightFieldSample);
201
202 physx::PxDefaultMemoryOutputStream buf;
203
204 const auto cooking = QPhysicsWorld::getCooking();
205 if (numRows && numCols && cooking && cooking->cookHeightField(hfDesc, buf)) {
206 auto size = buf.getSize();
207 auto *data = buf.getData();
208 physx::PxDefaultMemoryInputData input(data, size);
209 m_heightField = thePhysics->createHeightField(input);
210 qCDebug(lcQuick3dPhysics) << "created height field" << m_heightField << numCols << numRows
211 << "from"
212 << (readFromFile ? m_sourcePath : QString::fromUtf8("image"));
213 if (readFromFile)
215 } else {
216 qCWarning(lcQuick3dPhysics) << "Could not create height field from"
217 << (readFromFile ? m_sourcePath : QString::fromUtf8("image"));
218 }
219
220 return m_heightField;
221}
222
224{
225 return m_rows;
226}
227
229{
230 return m_columns;
231}
232
294
296{
297 delete m_heightFieldGeometry;
298 if (m_heightField)
300}
301
303{
304 if (m_dirtyPhysx || m_scaleDirty || !m_heightFieldGeometry) {
305 updatePhysXGeometry();
306 }
307 return m_heightFieldGeometry;
308}
309
310void QHeightFieldShape::updatePhysXGeometry()
311{
312 delete m_heightFieldGeometry;
313 m_heightFieldGeometry = nullptr;
314 if (!m_heightField)
315 return;
316
317 auto *hf = m_heightField->heightField();
318 float rows = m_heightField->rows();
319 float cols = m_heightField->columns();
320 updateExtents();
321 if (hf && cols > 1 && rows > 1) {
322 QVector3D scaledExtents = m_extents * sceneScale();
323 m_heightFieldGeometry = new physx::PxHeightFieldGeometry(
324 hf, physx::PxMeshGeometryFlags(), scaledExtents.y() / 0x10000,
325 scaledExtents.x() / (cols - 1), scaledExtents.z() / (rows - 1));
326 m_hfOffset = { -scaledExtents.x() / 2, 0, -scaledExtents.z() / 2 };
327
328 qCDebug(lcQuick3dPhysics) << "created height field geom" << m_heightFieldGeometry << "scale"
329 << scaledExtents << m_heightField->columns()
330 << m_heightField->rows();
331 }
332 m_dirtyPhysx = false;
333}
334
335void QHeightFieldShape::updateExtents()
336{
337 if (!m_heightField || m_extentsSetExplicitly)
338 return;
339 int numRows = m_heightField->rows();
340 int numCols = m_heightField->columns();
341 auto prevExt = m_extents;
342 if (numRows == numCols) {
343 m_extents = { 100, 100, 100 };
344 } else if (numRows < numCols) {
345 float f = float(numRows) / float(numCols);
346 m_extents = { 100.f, 100.f, 100.f * f };
347 } else {
348 float f = float(numCols) / float(numRows);
349 m_extents = { 100.f * f, 100.f, 100.f };
350 }
351 if (m_extents != prevExt) {
353 }
354}
355
356const QUrl &QHeightFieldShape::source() const
357{
358 return m_heightMapSource;
359}
360
361void QHeightFieldShape::setSource(const QUrl &newSource)
362{
363 if (m_heightMapSource == newSource)
364 return;
365 m_heightMapSource = newSource;
366
367 // If we get a new source and our heightfield was from the old source
368 // (meaning it was NOT from an image) we deref
369 if (m_image == nullptr) {
371 m_heightField = nullptr;
372 }
373
374 // Load new height field only if we don't have image as source
375 if (m_image == nullptr && !newSource.isEmpty()) {
376 m_heightField = QQuick3DPhysicsHeightFieldManager::getHeightField(m_heightMapSource, this);
377 emit needsRebuild(this);
378 }
379
380 m_dirtyPhysx = true;
381 emit sourceChanged();
382}
383
385{
386 return m_image;
387}
388
389void QHeightFieldShape::setImage(QQuickImage *newImage)
390{
391 if (m_image == newImage)
392 return;
393
394 if (m_image)
395 m_image->disconnect(this);
396
397 m_image = newImage;
398
399 if (m_image != nullptr) {
400 connect(m_image, &QObject::destroyed, this, &QHeightFieldShape::imageDestroyed);
402 &QHeightFieldShape::imageGeometryChanged);
403 }
404
405 // New image means we get a new heightfield so deref the old one
407 m_heightField = nullptr;
408
409 if (m_image != nullptr)
411 else if (!m_heightMapSource.isEmpty())
412 m_heightField = QQuick3DPhysicsHeightFieldManager::getHeightField(m_heightMapSource, this);
413
414 m_dirtyPhysx = true;
415 emit needsRebuild(this);
416 emit imageChanged();
417}
418
419void QHeightFieldShape::imageDestroyed(QObject *image)
420{
421 Q_ASSERT(m_image == image);
422 // Set image to null and the old one will be disconnected and dereferenced
423 setImage(nullptr);
424}
425
426void QHeightFieldShape::imageGeometryChanged()
427{
428 Q_ASSERT(m_image);
429 // Using image has precedence so it is safe to assume this is the current source
432 m_dirtyPhysx = true;
433 emit needsRebuild(this);
434}
435
437{
438 return m_extents;
439}
440
442{
443 m_extentsSetExplicitly = true;
444 if (m_extents == newExtents)
445 return;
446 m_extents = newExtents;
447
448 m_dirtyPhysx = true;
449
450 emit needsRebuild(this);
452}
453
void needsRebuild(QObject *)
float valueF() const noexcept
Returns the value color component of this color.
Definition qcolor.cpp:1818
void setExtents(const QVector3D &newExtents)
physx::PxGeometry * getPhysXGeometry() override
QHeightFieldShape()
\qmltype HeightFieldShape \inqmlmodule QtQuick3D.Physics \inherits CollisionShape
\inmodule QtGui
Definition qimage.h:37
int width() const
Returns the width of the image.
int height() const
Returns the height of the image.
QColor pixelColor(int x, int y) const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qimage.cpp:2709
\inmodule QtCore
Definition qobject.h:103
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
The QQmlContext class defines a context within a QML engine.
Definition qqmlcontext.h:25
static QString urlToLocalFileOrQrc(const QString &)
If url is a local file returns a path suitable for passing to \l{QFile}.
Definition qqmlfile.cpp:742
QVector3D sceneScale
static QQuick3DPhysicsHeightField * getHeightField(const QUrl &source, const QObject *contextObject)
static void releaseHeightField(QQuick3DPhysicsHeightField *heightField)
QQuick3DPhysicsHeightField(const QString &qmlSource)
void writeSamples(const QImage &heightMap)
physx::PxHeightField * heightField()
QImage image() const
void paintedGeometryChanged()
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
\inmodule QtCore
Definition qurl.h:94
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1896
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
void writeCachedHeightField(const QString &filePath, physx::PxDefaultMemoryOutputStream &buf)
physx::PxHeightField * readCookedHeightField(const QString &filePath, physx::PxPhysics &physics)
physx::PxHeightField * readCachedHeightField(const QString &filePath, physx::PxPhysics &physics)
Combined button and popup list for selecting options.
Definition image.cpp:4
static void * context
qsizetype erase_if(QByteArray &ba, Predicate pred)
Definition qbytearray.h:788
#define qCWarning(category,...)
#define qCDebug(category,...)
GLsizei samples
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLfloat GLfloat f
GLenum GLuint GLenum GLsizei const GLchar * buf
GLfloat GLfloat GLfloat GLfloat h
GLsizei GLsizei GLchar * source
GLenum GLenum GLenum input
QQmlContext * qmlContext(const QObject *obj)
Definition qqml.cpp:75
static QUrl resolvedUrl(const QUrl &url, const QQmlRefPointer< QQmlContextData > &context)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
short qint16
Definition qtypes.h:47
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)