Revision control

Copy as Markdown

// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
// Walks the world's bodies while one of them has been deleted. A deleted node
// leaves its backend behind until a frame takes it out, so the world is stopped
// first, since nothing else clears it, and then each of the three things that
// walk every body is asked for: a new default density, debug drawing off, and a
// move to another scene. Each of them crashed.
//
// Reaching the end is the assertion. The walks are named across the top and lit
// as they are survived, so the scene can be judged by eye too.
import QtQuick
import QtTest
import QtQuick3D
import QtQuick3D.Physics
import QtQuick3D.Physics.TestUtils
Item {
width: 640
height: 480
visible: true
PhysicsWorld {
id: world
gravity: Qt.vector3d(0, -981, 0)
running: true
forceDebugDraw: true
typicalLength: 100
typicalSpeed: 1000
minimumTimestep: 15
maximumTimestep: 15
scene: viewport.scene
}
QtObject {
id: walked
property bool density: false
property bool debugDraw: false
property bool scene: false
}
// A step apart, so the delete the first one asks for, which is deferred,
// has happened by the time the next one walks the bodies.
Timer {
interval: 300
repeat: true
running: true
property int step: 0
onTriggered: {
switch (++step) {
case 1:
// Stopped, so no frame takes the removed backend out from
// under the walks below.
world.running = false;
boxes.model = 2;
break;
case 2:
world.defaultDensity = 0.002;
walked.density = true;
break;
case 3:
world.forceDebugDraw = false;
walked.debugDraw = true;
break;
case 4:
world.scene = elsewhere;
walked.scene = true;
break;
}
}
}
View3D {
id: viewport
anchors.fill: parent
environment: SceneEnvironment {
clearColor: "#151a3f"
backgroundMode: SceneEnvironment.Color
}
PerspectiveCamera {
position: Qt.vector3d(0, 100, 600)
clipFar: 4000
}
DirectionalLight {
eulerRotation: Qt.vector3d(-35, 25, 0)
brightness: 1.5
ambientColor: "#3a3a44"
}
// What the world is moved to, so that moving it means every body
// leaves the scene it is in.
Node {
id: elsewhere
}
StaticRigidBody {
position: Qt.vector3d(0, -150, 0)
collisionShapes: BoxShape {
extents: Qt.vector3d(800, 40, 400)
}
Model {
source: "#Cube"
scale: Qt.vector3d(8, 0.4, 4)
materials: PrincipledMaterial {
baseColor: "#3f8f4f"
roughness: 0.85
}
}
}
// The last of these is the one that gets deleted.
Repeater3D {
id: boxes
model: 3
DynamicRigidBody {
required property int index
position: Qt.vector3d(index * 120 - 120, 200, 0)
collisionShapes: BoxShape {
extents: Qt.vector3d(60, 60, 60)
}
Model {
source: "#Cube"
scale: Qt.vector3d(0.6, 0.6, 0.6)
materials: PrincipledMaterial {
baseColor: index === 2 ? "#c0504d" : "#4a86e8"
roughness: 0.5
}
}
}
}
}
Row {
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: 12
spacing: 24
component Marker: Text {
required property bool done
color: done ? "#7ee08a" : "#9aa0b4"
font.pixelSize: 16
}
Marker { text: "density"; done: walked.density }
Marker { text: "debug draw"; done: walked.debugDraw }
Marker { text: "scene"; done: walked.scene }
}
PhysicsTestCase {
name: "giving every body a new default density"
goalReached: walked.density
}
PhysicsTestCase {
name: "taking every body's debug drawing away"
goalReached: walked.debugDraw
}
PhysicsTestCase {
name: "moving every body to another scene"
goalReached: walked.scene
}
}