Revision control
Copy as Markdown
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qdebugdrawhelper_p.h"
#include "qcollisiondebugmeshbuilder_p.h"
#include "qphysicsutils_p.h"
#include <foundation/PxBounds3.h>
#include <foundation/PxVec3.h>
#include <geometry/PxConvexMesh.h>
#include <geometry/PxTriangleMesh.h>
#include <geometry/PxHeightField.h>
#include <QQuick3DGeometry>
QQuick3DGeometry *QDebugDrawHelper::generateBoxGeometry(const QVector3D &halfExtents)
{
auto boxGeometry = new QQuick3DGeometry();
boxGeometry->clear();
boxGeometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
boxGeometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 16,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
boxGeometry->setStride(32);
boxGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
boxGeometry->setBounds(-halfExtents, halfExtents);
const float x = halfExtents.x();
const float y = halfExtents.y();
const float z = halfExtents.z();
QCollisionDebugMeshBuilder builder;
// top
builder.addLine(QVector3D(-x, -y, z), QVector3D(-x, y, z));
builder.addLine(QVector3D(-x, y, z), QVector3D(x, y, z));
builder.addLine(QVector3D(x, y, z), QVector3D(x, -y, z));
builder.addLine(QVector3D(x, -y, z), QVector3D(-x, -y, z));
// bottom
builder.addLine(QVector3D(-x, -y, -z), QVector3D(-x, y, -z));
builder.addLine(QVector3D(-x, y, -z), QVector3D(x, y, -z));
builder.addLine(QVector3D(x, y, -z), QVector3D(x, -y, -z));
builder.addLine(QVector3D(x, -y, -z), QVector3D(-x, -y, -z));
// front
builder.addLine(QVector3D(x, -y, z), QVector3D(x, -y, -z));
builder.addLine(QVector3D(-x, -y, -z), QVector3D(-x, -y, z));
// back
builder.addLine(QVector3D(x, y, z), QVector3D(x, y, -z));
builder.addLine(QVector3D(-x, y, -z), QVector3D(-x, y, z));
boxGeometry->setVertexData(builder.generateVertexArray());
return boxGeometry;
}
QQuick3DGeometry *QDebugDrawHelper::generateSphereGeometry(const float radius)
{
auto sphereGeometry = new QQuick3DGeometry();
sphereGeometry->clear();
sphereGeometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
sphereGeometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 16,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
sphereGeometry->setStride(32);
sphereGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
sphereGeometry->setBounds(QVector3D(-radius, -radius, -radius),
QVector3D(radius, radius, radius));
// One circle around each axis
// So create a 2D circle first from points
const int circleSegments = 24;
constexpr double TAU = 2 * M_PI;
const float step = float(TAU / circleSegments);
QVector<QVector2D> circlePoints;
for (float theta = 0; theta < TAU; theta += step) {
const float x = radius * qCos(theta);
const float y = radius * qSin(theta);
circlePoints.append(QVector2D(x, y));
}
QCollisionDebugMeshBuilder builder;
// X
for (int i = 0; i < circlePoints.count(); ++i) {
const auto refPoint1 = circlePoints[i];
int index2 = i + 1;
if (index2 == circlePoints.count())
index2 = 0;
const auto refPoint2 = circlePoints[index2];
const auto vertex1 = QVector3D(0.0f, refPoint1.x(), refPoint1.y());
const auto vertex2 = QVector3D(0.0f, refPoint2.x(), refPoint2.y());
builder.addLine(vertex1, vertex2, QVector3D(1, 0, 0));
}
// Y
for (int i = 0; i < circlePoints.count(); ++i) {
const auto refPoint1 = circlePoints[i];
int index2 = i + 1;
if (index2 == circlePoints.count())
index2 = 0;
const auto refPoint2 = circlePoints[index2];
const auto vertex1 = QVector3D(refPoint1.x(), 0.0f, refPoint1.y());
const auto vertex2 = QVector3D(refPoint2.x(), 0.0f, refPoint2.y());
builder.addLine(vertex1, vertex2, QVector3D(0, 1, 0));
}
// Z
for (int i = 0; i < circlePoints.count(); ++i) {
const auto refPoint1 = circlePoints[i];
int index2 = i + 1;
if (index2 == circlePoints.count())
index2 = 0;
const auto refPoint2 = circlePoints[index2];
const auto vertex1 = QVector3D(refPoint1.x(), refPoint1.y(), 0.0f);
const auto vertex2 = QVector3D(refPoint2.x(), refPoint2.y(), 0.0f);
builder.addLine(vertex1, vertex2, QVector3D(0, 0, 1));
}
sphereGeometry->setVertexData(builder.generateVertexArray());
return sphereGeometry;
}
QQuick3DGeometry *QDebugDrawHelper::generateCapsuleGeometry(const float radius,
const float halfHeight)
{
auto capsuleGeometry = new QQuick3DGeometry();
capsuleGeometry->clear();
capsuleGeometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
capsuleGeometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 16,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
capsuleGeometry->setStride(32);
capsuleGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
capsuleGeometry->setBounds(QVector3D(-(halfHeight + radius), -radius, -radius),
QVector3D(halfHeight + radius, radius, radius));
// The total height is height+2*radius, so the height is just the height
// between the center of each 'sphere' of the capsule caps.
// Create a 2D circle first for points
const int circleSegments = 32;
constexpr double TAU = 2 * M_PI;
Q_ASSERT(circleSegments % 4 == 0);
const float step = float(TAU / circleSegments);
QVector<QVector2D> circlePoints;
for (float theta = 0; theta < TAU; theta += step) {
const float x = radius * qCos(theta);
const float y = radius * qSin(theta);
circlePoints.append(QVector2D(x, y));
}
QCollisionDebugMeshBuilder builder;
// Top Y Cirlce (y = height * 0.5)
for (int i = 0; i < circlePoints.count(); ++i) {
const auto refPoint1 = circlePoints[i];
int index2 = i + 1;
if (index2 == circlePoints.count())
index2 = 0;
const auto refPoint2 = circlePoints[index2];
const auto vertex1 = QVector3D(halfHeight, refPoint1.x(), refPoint1.y());
const auto vertex2 = QVector3D(halfHeight, refPoint2.x(), refPoint2.y());
const auto normal = QVector3D(1, 0, 0);
builder.addLine(vertex1, vertex2, normal);
}
// Bottom Y Circle (y = -height * 0.5)
for (int i = 0; i < circlePoints.count(); ++i) {
const auto refPoint1 = circlePoints[i];
int index2 = i + 1;
if (index2 == circlePoints.count())
index2 = 0;
const auto refPoint2 = circlePoints[index2];
const auto vertex1 = QVector3D(-halfHeight, refPoint1.x(), refPoint1.y());
const auto vertex2 = QVector3D(-halfHeight, refPoint2.x(), refPoint2.y());
const auto normal = QVector3D(1, 0, 0);
builder.addLine(vertex1, vertex2, normal);
}
// Front Cylinder Line (z = radius, y = length , x = 0)
{
const auto vertex1 = QVector3D(halfHeight, 0, radius);
const auto vertex2 = QVector3D(-halfHeight, 0, radius);
const auto normal = QVector3D(0, 0, 1);
builder.addLine(vertex1, vertex2, normal);
}
// Back Cylinder Line (z = -radius, y = length, x = 0)
{
const auto vertex1 = QVector3D(halfHeight, 0, -radius);
const auto vertex2 = QVector3D(-halfHeight, 0, -radius);
const auto normal = QVector3D(0, 0, -1);
builder.addLine(vertex1, vertex2, normal);
}
// Left Cylinder Line (x = -radius, y = length, z = 0)
{
const auto vertex1 = QVector3D(halfHeight, -radius, 0);
const auto vertex2 = QVector3D(-halfHeight, -radius, 0);
const auto normal = QVector3D(0, -1, 0);
builder.addLine(vertex1, vertex2, normal);
}
// Right Cylinder Line (x = radius, y = length, z = 0)
{
const auto vertex1 = QVector3D(halfHeight, radius, 0);
const auto vertex2 = QVector3D(-halfHeight, radius, 0);
const auto normal = QVector3D(0, 1, 0);
builder.addLine(vertex1, vertex2, normal);
}
// Get half circle values
QVector<int> topIndexes;
QVector<int> bottomIndexes;
{
const int half = circlePoints.count() / 2;
for (int i = 0; i < half + 1; ++i)
topIndexes.append(i);
for (int i = half; i <= circlePoints.count(); ++i) {
int index = i;
if (i >= circlePoints.count())
index = index - circlePoints.count();
bottomIndexes.append(index);
}
}
// Z Top Half Circle
for (int i = 0; i < topIndexes.count(); ++i) {
const auto refPoint1 = circlePoints[topIndexes[i]];
int index2 = i + 1;
if (index2 == topIndexes.count())
break;
const auto refPoint2 = circlePoints[topIndexes[index2]];
const auto vertex1 = QVector3D(refPoint1.y() + halfHeight, refPoint1.x(), 0.0f);
const auto vertex2 = QVector3D(refPoint2.y() + halfHeight, refPoint2.x(), 0.0f);
const auto normal = QVector3D(0, 0, 1);
builder.addLine(vertex1, vertex2, normal);
}
// Z Bottom Half Circle
for (int i = 0; i < bottomIndexes.count(); ++i) {
const auto refPoint1 = circlePoints[bottomIndexes[i]];
int index2 = i + 1;
if (index2 == bottomIndexes.count())
break;
const auto refPoint2 = circlePoints[bottomIndexes[index2]];
const auto vertex1 = QVector3D(refPoint1.y() - halfHeight, refPoint1.x(), 0.0f);
const auto vertex2 = QVector3D(refPoint2.y() - halfHeight, refPoint2.x(), 0.0f);
const auto normal = QVector3D(0, 0, 1);
builder.addLine(vertex1, vertex2, normal);
}
// X Top Half Circle
for (int i = 0; i < topIndexes.count(); ++i) {
const auto refPoint1 = circlePoints[topIndexes[i]];
int index2 = i + 1;
if (index2 == topIndexes.count())
break;
const auto refPoint2 = circlePoints[topIndexes[index2]];
const auto vertex1 = QVector3D(refPoint1.y() + halfHeight, 0.0f, refPoint1.x());
const auto vertex2 = QVector3D(refPoint2.y() + halfHeight, 0.0f, refPoint2.x());
const auto normal = QVector3D(0, 1, 0);
builder.addLine(vertex1, vertex2, normal);
}
// X Bottom Half Circle
for (int i = 0; i < bottomIndexes.count(); ++i) {
const auto refPoint1 = circlePoints[bottomIndexes[i]];
int index2 = i + 1;
if (index2 == bottomIndexes.count())
break;
const auto refPoint2 = circlePoints[bottomIndexes[index2]];
const auto vertex1 = QVector3D(refPoint1.y() - halfHeight, 0.0f, refPoint1.x());
const auto vertex2 = QVector3D(refPoint2.y() - halfHeight, 0.0f, refPoint2.x());
const auto normal = QVector3D(0, 1, 0);
builder.addLine(vertex1, vertex2, normal);
}
capsuleGeometry->setVertexData(builder.generateVertexArray());
return capsuleGeometry;
}
QQuick3DGeometry *QDebugDrawHelper::generatePlaneGeometry()
{
auto planeGeometry = new QQuick3DGeometry();
planeGeometry->clear();
planeGeometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
planeGeometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 16,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
planeGeometry->setStride(32);
planeGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
// TODO: Some sort of debug scale? Or a level-of-detail grid thing? => QtQuick3DHelpers
float s = 50;
float h = 5; // Set to avoid flat bounding box
planeGeometry->setBounds({ -s, -s, -h }, { s, s, h });
QCollisionDebugMeshBuilder builder;
builder.addLine({ -s, -s, 0 }, { s, -s, 0 });
builder.addLine({ -s, -s, 0 }, { 0, 0, 0 });
builder.addLine({ s, -s, 0 }, { s, s, 0 });
builder.addLine({ s, -s, 0 }, { 0, 0, 0 });
builder.addLine({ s, s, 0 }, { -s, s, 0 });
builder.addLine({ s, s, 0 }, { 0, 0, 0 });
builder.addLine({ -s, s, 0 }, { -s, -s, 0 });
builder.addLine({ -s, s, 0 }, { 0, 0, 0 });
planeGeometry->setVertexData(builder.generateVertexArray());
return planeGeometry;
}
QQuick3DGeometry *QDebugDrawHelper::generateHeightFieldGeometry(physx::PxHeightField *heightField,
float heightScale, float rowScale,
float columnScale)
{
if (!heightField || heightField->getNbRows() < 2 || heightField->getNbColumns() < 2)
return nullptr;
auto geometry = new QQuick3DGeometry();
geometry->clear();
geometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
geometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 16,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
geometry->setStride(32);
geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
QCollisionDebugMeshBuilder builder;
const int numRows = heightField->getNbRows();
const int numCols = heightField->getNbColumns();
const float sizeX = rowScale * (numRows - 1);
const float sizeZ = columnScale * (numCols - 1);
const float heightF = heightScale;
float minHeight = 0.f;
float maxHeight = 0.f;
auto sample = [&](int row, int col) -> QVector3D {
const float height = heightField->getSample(row, col).height * heightF;
maxHeight = qMax(maxHeight, height);
minHeight = qMin(minHeight, height);
return QVector3D(row * rowScale, height, col * columnScale);
};
for (int row = 0; row < numRows; row++) {
for (int col = 0; col < numCols; col++) {
if (row < numRows - 1)
builder.addLine(sample(row, col), sample(row + 1, col));
if (col < numCols - 1)
builder.addLine(sample(row, col), sample(row, col + 1));
}
}
geometry->setBounds(QVector3D(0, minHeight, 0), QVector3D(sizeX, maxHeight, sizeZ));
geometry->setVertexData(builder.generateVertexArray());
return geometry;
}
QQuick3DGeometry *QDebugDrawHelper::generateConvexMeshGeometry(physx::PxConvexMesh *convexMesh)
{
if (!convexMesh)
return nullptr;
auto geometry = new QQuick3DGeometry();
geometry->clear();
geometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
geometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 16,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
geometry->setStride(32);
geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
QCollisionDebugMeshBuilder builder;
const physx::PxU32 nbPolys = convexMesh->getNbPolygons();
const physx::PxU8 *polygons = convexMesh->getIndexBuffer();
const physx::PxVec3 *verts = convexMesh->getVertices();
const physx::PxU32 nbVerts = convexMesh->getNbVertices();
physx::PxHullPolygon data;
for (physx::PxU32 i = 0; i < nbPolys; i++) {
convexMesh->getPolygonData(i, data);
Q_ASSERT(data.mNbVerts > 2);
const physx::PxU32 nbTris = physx::PxU32(data.mNbVerts - 2);
const physx::PxU8 vref0 = polygons[data.mIndexBase + 0];
Q_ASSERT(vref0 < nbVerts);
for (physx::PxU32 j = 0; j < nbTris; j++) {
const physx::PxU32 vref1 = polygons[data.mIndexBase + 0 + j + 1];
const physx::PxU32 vref2 = polygons[data.mIndexBase + 0 + j + 2];
Q_ASSERT(vref1 < nbVerts);
Q_ASSERT(vref2 < nbVerts);
const QVector3D p0 = QPhysicsUtils::toQtType(verts[vref0]);
const QVector3D p1 = QPhysicsUtils::toQtType(verts[vref1]);
const QVector3D p2 = QPhysicsUtils::toQtType(verts[vref2]);
builder.addLine(p0, p1);
builder.addLine(p1, p2);
builder.addLine(p2, p0);
}
}
auto bounds = convexMesh->getLocalBounds();
geometry->setBounds(QPhysicsUtils::toQtType(bounds.minimum),
QPhysicsUtils::toQtType(bounds.maximum));
geometry->setVertexData(builder.generateVertexArray());
return geometry;
}
QQuick3DGeometry *
QDebugDrawHelper::generateTriangleMeshGeometry(physx::PxTriangleMesh *triangleMesh)
{
if (!triangleMesh)
return nullptr;
auto geometry = new QQuick3DGeometry();
geometry->clear();
geometry->addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
geometry->addAttribute(QQuick3DGeometry::Attribute::NormalSemantic, 16,
QQuick3DGeometry::Attribute::ComponentType::F32Type);
geometry->setStride(32);
geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
QCollisionDebugMeshBuilder builder;
const physx::PxU32 triangleCount = triangleMesh->getNbTriangles();
const physx::PxU32 has16BitIndices =
triangleMesh->getTriangleMeshFlags() & physx::PxTriangleMeshFlag::e16_BIT_INDICES;
const void *indexBuffer = triangleMesh->getTriangles();
const physx::PxVec3 *vertexBuffer = triangleMesh->getVertices();
const physx::PxU32 *intIndices = reinterpret_cast<const physx::PxU32 *>(indexBuffer);
const physx::PxU16 *shortIndices = reinterpret_cast<const physx::PxU16 *>(indexBuffer);
for (physx::PxU32 i = 0; i < triangleCount; ++i) {
physx::PxVec3 triVert[3];
if (has16BitIndices) {
triVert[0] = vertexBuffer[*shortIndices++];
triVert[1] = vertexBuffer[*shortIndices++];
triVert[2] = vertexBuffer[*shortIndices++];
} else {
triVert[0] = vertexBuffer[*intIndices++];
triVert[1] = vertexBuffer[*intIndices++];
triVert[2] = vertexBuffer[*intIndices++];
}
const QVector3D p0 = QPhysicsUtils::toQtType(triVert[0]);
const QVector3D p1 = QPhysicsUtils::toQtType(triVert[1]);
const QVector3D p2 = QPhysicsUtils::toQtType(triVert[2]);
builder.addLine(p0, p1);
builder.addLine(p1, p2);
builder.addLine(p2, p0);
}
auto bounds = triangleMesh->getLocalBounds();
geometry->setBounds(QPhysicsUtils::toQtType(bounds.minimum),
QPhysicsUtils::toQtType(bounds.maximum));
geometry->setVertexData(builder.generateVertexArray());
return geometry;
}