Revision control
Copy as Markdown
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Tests that every valid combination of PhysicsWorld.staticQueryStructure
// (StaticTree/DynamicTree) and PhysicsWorld.dynamicQueryStructure
// (NoStructure/StaticTree/DynamicTree) doesn't break CharacterController's ground
// detection, which PhysX implements internally via a scene overlap query (against
// both static and dynamic geometry) and a scene raycast (against dynamic geometry only)
// -- the actual code paths these properties affect -- while static actors are being
// added and removed elsewhere in the scene at runtime (simulating chunk streaming) and
// dynamic actors are present for the ground-raycast to consider.
//
// PhysicsWorld.NoStructure is not a valid value for staticQueryStructure, so the full
// matrix has 2 x 3 = 6 combinations; all six are exercised below.
import QtCore
import QtQuick
import QtTest
import QtQuick3D
import QtQuick3D.Physics
import QtQuick3D.Physics.TestUtils
Item {
property real sceneWidth: 300
property real sceneHeight: 300
width: sceneWidth * 3
height: sceneHeight * 2
visible: true
CharacterGroundScene {
id: sceneStaticNone
width: parent.sceneWidth
height: parent.sceneHeight
x: parent.sceneWidth * 0
y: parent.sceneHeight * 0
staticQueryStructure: PhysicsWorld.StaticTree
dynamicQueryStructure: PhysicsWorld.NoStructure
}
PhysicsTestCase {
name: "static=StaticTree, dynamic=NoStructure: CharacterController ground detection still works"
goalReached: sceneStaticNone.collisions === CharacterController.Down
}
CharacterGroundScene {
id: sceneStaticStatic
width: parent.sceneWidth
height: parent.sceneHeight
x: parent.sceneWidth * 1
y: parent.sceneHeight * 0
staticQueryStructure: PhysicsWorld.StaticTree
dynamicQueryStructure: PhysicsWorld.StaticTree
}
PhysicsTestCase {
name: "static=StaticTree, dynamic=StaticTree: CharacterController ground detection still works"
goalReached: sceneStaticStatic.collisions === CharacterController.Down
}
CharacterGroundScene {
id: sceneStaticDynamic
width: parent.sceneWidth
height: parent.sceneHeight
x: parent.sceneWidth * 2
y: parent.sceneHeight * 0
staticQueryStructure: PhysicsWorld.StaticTree
dynamicQueryStructure: PhysicsWorld.DynamicTree
}
PhysicsTestCase {
name: "static=StaticTree, dynamic=DynamicTree: CharacterController ground detection still works"
goalReached: sceneStaticDynamic.collisions === CharacterController.Down
}
CharacterGroundScene {
id: sceneNone
width: parent.sceneWidth
height: parent.sceneHeight
x: parent.sceneWidth * 0
y: parent.sceneHeight * 1
staticQueryStructure: PhysicsWorld.DynamicTree
dynamicQueryStructure: PhysicsWorld.NoStructure
}
PhysicsTestCase {
name: "static=DynamicTree, dynamic=NoStructure: CharacterController ground detection still works"
goalReached: sceneNone.collisions === CharacterController.Down
}
CharacterGroundScene {
id: sceneDynamicStatic
width: parent.sceneWidth
height: parent.sceneHeight
x: parent.sceneWidth * 1
y: parent.sceneHeight * 1
staticQueryStructure: PhysicsWorld.DynamicTree
dynamicQueryStructure: PhysicsWorld.StaticTree
}
PhysicsTestCase {
name: "static=DynamicTree, dynamic=StaticTree: CharacterController ground detection still works"
goalReached: sceneDynamicStatic.collisions === CharacterController.Down
}
CharacterGroundScene {
id: sceneDynamic
width: parent.sceneWidth
height: parent.sceneHeight
x: parent.sceneWidth * 2
y: parent.sceneHeight * 1
staticQueryStructure: PhysicsWorld.DynamicTree
dynamicQueryStructure: PhysicsWorld.DynamicTree
}
PhysicsTestCase {
name: "static=DynamicTree, dynamic=DynamicTree: CharacterController ground detection still works"
goalReached: sceneDynamic.collisions === CharacterController.Down
}
}