Fixes and adjustments in 3D physics tests

Add Functional Test / Stack & Pyramid
For testing stack stability.

Add Functional Test / Raycasts
Visually test raycast on different shapes.

Add Performance Test / Broadphase
Add/move/remove lots of non-colliding objects and measure time.

Fix leaks on exit
Some Nodes are copied and removed from the scene to be used as templates,
they need to be freed manually on exit.

Fix Performance Test / Contacts
Positions adjusted, some shape types were not created at the center.
This commit is contained in:
PouleyKetchoupp
2020-12-09 20:06:37 -07:00
parent 3147c6c5bd
commit 6dd09308fa
15 changed files with 542 additions and 21 deletions

View File

@@ -26,7 +26,7 @@ _global_script_class_icons={
[application]
config/name="Physics Tests"
config/name="3D Physics Tests"
run/main_scene="res://main.tscn"
config/icon="res://icon.png"
@@ -71,5 +71,5 @@ restart_test={
[rendering]
quality/driver/driver_name="GLES2"
environment/default_clear_color=Color( 0, 0, 0, 1 )
environment/default_clear_color=Color( 0.184314, 0.184314, 0.184314, 1 )
quality/filters/msaa=2

View File

@@ -1,10 +1,33 @@
class_name Test
extends Node
signal wait_done()
var _timer
var _timer_started = false
var _wait_physics_ticks_counter = 0
func _physics_process(_delta):
if (_wait_physics_ticks_counter > 0):
_wait_physics_ticks_counter -= 1
if (_wait_physics_ticks_counter == 0):
emit_signal("wait_done")
func create_rigidbody_box(size):
var template_shape = BoxShape.new()
template_shape.extents = 0.5 * size
var template_collision = CollisionShape.new()
template_collision.shape = template_shape
var template_body = RigidBody.new()
template_body.add_child(template_collision)
return template_body
func start_timer(timeout):
if _timer == null:
@@ -32,5 +55,10 @@ func is_timer_canceled():
return _timer.paused
func wait_for_physics_ticks(tick_count):
_wait_physics_ticks_counter = tick_count
return self
func _on_timer_done():
_timer_started = false

View File

@@ -14,6 +14,22 @@ var _tests = [
"id": "Functional Tests/Friction",
"path": "res://tests/functional/test_friction.tscn",
},
{
"id": "Functional Tests/Box Stack",
"path": "res://tests/functional/test_stack.tscn",
},
{
"id": "Functional Tests/Box Pyramid",
"path": "res://tests/functional/test_pyramid.tscn",
},
{
"id": "Functional Tests/Raycasting",
"path": "res://tests/functional/test_raycasting.tscn",
},
{
"id": "Performance Tests/Broadphase",
"path": "res://tests/performance/test_perf_broadphase.tscn",
},
{
"id": "Performance Tests/Contacts",
"path": "res://tests/performance/test_perf_contacts.tscn",

View File

@@ -0,0 +1,58 @@
extends Test
export(int, 1, 100) var height = 10
export(int, 1, 100) var width_max = 100
export(int, 1, 100) var depth_max = 1
export(Vector3) var box_size = Vector3(1.0, 1.0, 1.0)
export(Vector3) var box_spacing = Vector3(0.0, 0.0, 0.0)
func _ready():
_create_pyramid()
func _create_pyramid():
var root_node = $Pyramid
var template_shape = BoxShape.new()
template_shape.extents = 0.5 * box_size
var template_collision = CollisionShape.new()
template_collision.shape = template_shape
var template_body = RigidBody.new()
template_body.add_child(template_collision)
var pos_y = 0.5 * box_size.y + box_spacing.y
for level in height:
var level_index = height - level - 1
var num_boxes = 2 * level_index + 1
var num_boxes_width = min(num_boxes, width_max)
var num_boxes_depth = min(num_boxes, depth_max)
var row_node = Spatial.new()
row_node.transform.origin = Vector3(0.0, pos_y, 0.0)
row_node.name = "Row%02d" % (level + 1)
root_node.add_child(row_node)
var pos_x = -0.5 * (num_boxes_width - 1) * (box_size.x + box_spacing.x)
for box_index_x in num_boxes_width:
var pos_z = -0.5 * (num_boxes_depth - 1) * (box_size.z + box_spacing.z)
for box_index_z in num_boxes_depth:
var box_index = box_index_x * box_index_z
var box = template_body.duplicate()
box.transform.origin = Vector3(pos_x, 0.0, pos_z)
box.name = "Box%02d" % (box_index + 1)
row_node.add_child(box)
pos_z += box_size.z + box_spacing.z
pos_x += box_size.x + box_spacing.x
pos_y += box_size.y + box_spacing.y
template_body.queue_free()

View File

@@ -0,0 +1,16 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://tests/functional/test_pyramid.gd" type="Script" id=1]
[ext_resource path="res://tests/static_scene_plane.tscn" type="PackedScene" id=2]
[ext_resource path="res://utils/camera_orbit.gd" type="Script" id=4]
[node name="Test" type="Spatial"]
script = ExtResource( 1 )
[node name="Pyramid" type="Spatial" parent="."]
[node name="StaticBodyPlane" parent="." instance=ExtResource( 2 )]
[node name="Camera" type="Camera" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.62348, 22.9474 )
script = ExtResource( 4 )

View File

@@ -0,0 +1,78 @@
extends Test
var _do_raycasts = false
onready var _raycast_visuals = ImmediateGeometry.new()
func _ready():
var material = SpatialMaterial.new()
material.flags_unshaded = true
material.vertex_color_use_as_albedo = true
_raycast_visuals.material_override = material
add_child(_raycast_visuals)
move_child(_raycast_visuals, get_child_count())
yield(start_timer(0.5), "timeout")
if is_timer_canceled():
return
_do_raycasts = true
func _physics_process(_delta):
if !_do_raycasts:
return
_do_raycasts = false
Log.print_log("* Start Raycasting...")
_raycast_visuals.clear()
_raycast_visuals.begin(Mesh.PRIMITIVE_LINES)
for shape in $Shapes.get_children():
var body = shape as PhysicsBody
var space_state = body.get_world().direct_space_state
Log.print_log("* Testing: %s" % body.name)
var center = body.global_transform.origin
# Raycast entering from the top.
var res = _add_raycast(space_state, center + Vector3(0.0, 2.0, 0.0), center)
Log.print_log("Raycast in: %s" % ("HIT" if res else "NO HIT"))
# Raycast exiting from inside.
center.x -= 0.2
res = _add_raycast(space_state, center, center - Vector3(0.0, 3.0, 0.0))
Log.print_log("Raycast out: %s" % ("HIT" if res else "NO HIT"))
# Raycast all inside.
center.x += 0.4
res = _add_raycast(space_state, center, center - Vector3(0.0, 0.8, 0.0))
Log.print_log("Raycast inside: %s" % ("HIT" if res else "NO HIT"))
_raycast_visuals.end()
func _add_raycast(space_state, pos_start, pos_end):
var result = space_state.intersect_ray(pos_start, pos_end)
if result:
_raycast_visuals.set_color(Color.green)
else:
_raycast_visuals.set_color(Color.red.darkened(0.5))
# Draw raycast line.
_raycast_visuals.add_vertex(pos_start)
_raycast_visuals.add_vertex(pos_end)
# Draw raycast arrow.
_raycast_visuals.add_vertex(pos_end)
_raycast_visuals.add_vertex(pos_end + Vector3(-0.05, 0.1, 0.0))
_raycast_visuals.add_vertex(pos_end)
_raycast_visuals.add_vertex(pos_end + Vector3(0.05, 0.1, 0.0))
return result

View File

@@ -0,0 +1,74 @@
[gd_scene load_steps=10 format=2]
[ext_resource path="res://assets/robot_head/godot3_robot_head_collision.tres" type="Shape" id=1]
[ext_resource path="res://tests/functional/test_raycasting.gd" type="Script" id=2]
[ext_resource path="res://utils/exception_cylinder.gd" type="Script" id=3]
[ext_resource path="res://utils/camera_orbit.gd" type="Script" id=4]
[sub_resource type="BoxShape" id=1]
[sub_resource type="CapsuleShape" id=2]
[sub_resource type="CylinderShape" id=3]
[sub_resource type="ConvexPolygonShape" id=4]
points = PoolVector3Array( -0.7, 0, -0.7, -0.3, 0, 0.8, 0.8, 0, -0.3, 0, -1, 0 )
[sub_resource type="SphereShape" id=5]
[node name="Test" type="Spatial"]
script = ExtResource( 2 )
[node name="Shapes" type="Spatial" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 9.35591, 0 )
[node name="RigidBodyBox" type="RigidBody" parent="Shapes"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, 0 )
mode = 3
[node name="CollisionShape" type="CollisionShape" parent="Shapes/RigidBodyBox"]
transform = Transform( 0.579556, 0.0885213, 0.145926, 0, 0.939693, -0.205212, -0.155291, 0.330366, 0.544604, 0, 0, 0 )
shape = SubResource( 1 )
[node name="RigidBodyCapsule" type="RigidBody" parent="Shapes"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 0 )
mode = 3
[node name="CollisionShape" type="CollisionShape" parent="Shapes/RigidBodyCapsule"]
transform = Transform( 0.8, 0, 0, 0, -1.30337e-07, -0.8, 0, 0.8, -1.30337e-07, 0, 0, 0 )
shape = SubResource( 2 )
[node name="RigidBodyCylinder" type="RigidBody" parent="Shapes"]
mode = 3
script = ExtResource( 3 )
[node name="CollisionShape" type="CollisionShape" parent="Shapes/RigidBodyCylinder"]
transform = Transform( 0.772741, -0.258819, 2.59821e-08, 0.2, 0.933013, -0.207055, 0.0535898, 0.25, 0.772741, 0, 0, 0 )
shape = SubResource( 3 )
[node name="RigidBodyConvex" type="RigidBody" parent="Shapes"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 3, -0.210678, 0 )
mode = 3
[node name="CollisionShape" type="CollisionShape" parent="Shapes/RigidBodyConvex"]
transform = Transform( 2, 0, 0, 0, 2.89766, -0.517939, 0, 0.776908, 1.93177, 0, 0.3533, 0 )
shape = SubResource( 4 )
[node name="RigidBodySphere" type="RigidBody" parent="Shapes"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 6, 0, 0 )
mode = 3
[node name="CollisionShape" type="CollisionShape" parent="Shapes/RigidBodySphere"]
transform = Transform( 1.2, 0, 0, 0, 1.2, 0, 0, 0, 1.2, 0, 0, 0 )
shape = SubResource( 5 )
[node name="StaticBodyHead" type="StaticBody" parent="Shapes"]
transform = Transform( 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, -6, 3.93357 )
[node name="CollisionShape" type="CollisionShape" parent="Shapes/StaticBodyHead"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0 )
shape = ExtResource( 1 )
[node name="Camera" type="Camera" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.8667, 11.8164 )
script = ExtResource( 4 )

View File

@@ -0,0 +1,53 @@
extends Test
export(int, 1, 100) var height = 10
export(int, 1, 100) var width = 1
export(int, 1, 100) var depth = 1
export(Vector3) var box_size = Vector3(1.0, 1.0, 1.0)
export(Vector3) var box_spacing = Vector3(0.0, 0.0, 0.0)
func _ready():
_create_stack()
func _create_stack():
var root_node = $Stack
var template_shape = BoxShape.new()
template_shape.extents = 0.5 * box_size
var template_collision = CollisionShape.new()
template_collision.shape = template_shape
var template_body = RigidBody.new()
template_body.add_child(template_collision)
var pos_y = 0.5 * box_size.y + box_spacing.y
for level in height:
var row_node = Spatial.new()
row_node.transform.origin = Vector3(0.0, pos_y, 0.0)
row_node.name = "Row%02d" % (level + 1)
root_node.add_child(row_node)
var pos_x = -0.5 * (width - 1) * (box_size.x + box_spacing.x)
for box_index_x in width:
var pos_z = -0.5 * (depth - 1) * (box_size.z + box_spacing.z)
for box_index_z in depth:
var box_index = box_index_x * box_index_z
var box = template_body.duplicate()
box.transform.origin = Vector3(pos_x, 0.0, pos_z)
box.name = "Box%02d" % (box_index + 1)
row_node.add_child(box)
pos_z += box_size.z + box_spacing.z
pos_x += box_size.x + box_spacing.x
pos_y += box_size.y + box_spacing.y
template_body.queue_free()

View File

@@ -0,0 +1,16 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://tests/functional/test_stack.gd" type="Script" id=1]
[ext_resource path="res://tests/static_scene_plane.tscn" type="PackedScene" id=2]
[ext_resource path="res://utils/camera_orbit.gd" type="Script" id=4]
[node name="Test" type="Spatial"]
script = ExtResource( 1 )
[node name="Stack" type="Spatial" parent="."]
[node name="StaticBodyPlane" parent="." instance=ExtResource( 2 )]
[node name="Camera" type="Camera" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4.53602, 12.2684 )
script = ExtResource( 4 )

View File

@@ -0,0 +1,155 @@
extends Test
const BOX_SIZE = Vector3(0.8, 0.8, 0.8)
const BOX_SPACE = Vector3(1.0, 1.0, 1.0)
export(int, 1, 1000) var row_size = 20
export(int, 1, 1000) var column_size = 20
export(int, 1, 1000) var depth_size = 20
var _objects = []
var _log_physics = false
var _log_physics_time = 0
var _log_physics_time_start = 0
func _ready():
_create_objects()
_log_physics_start()
yield(wait_for_physics_ticks(5), "wait_done")
_log_physics_stop()
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_add_objects()
_log_physics_start()
yield(wait_for_physics_ticks(5), "wait_done")
_log_physics_stop()
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_move_objects()
_log_physics_start()
yield(wait_for_physics_ticks(5), "wait_done")
_log_physics_stop()
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
_remove_objects()
_log_physics_start()
yield(wait_for_physics_ticks(5), "wait_done")
_log_physics_stop()
yield(start_timer(1.0), "timeout")
if is_timer_canceled():
return
Log.print_log("* Done.")
func _exit_tree():
for object in _objects:
object.free()
func _physics_process(_delta):
if _log_physics:
var time = OS.get_ticks_usec()
var time_delta = time - _log_physics_time
var time_total = time - _log_physics_time_start
_log_physics_time = time
Log.print_log(" Physics Tick: %.3f ms (total = %.3f ms)" % [0.001 * time_delta, 0.001 * time_total])
func _log_physics_start():
_log_physics = true
_log_physics_time_start = OS.get_ticks_usec()
_log_physics_time = _log_physics_time_start
func _log_physics_stop():
_log_physics = false
func _create_objects():
_objects.clear()
var template_body = create_rigidbody_box(BOX_SIZE)
template_body.gravity_scale = 0.0
Log.print_log("* Creating objects...")
var timer = OS.get_ticks_usec()
var pos_x = -0.5 * (row_size - 1) * BOX_SPACE.x
for row in row_size:
var pos_y = -0.5 * (column_size - 1) * BOX_SPACE.y
for column in column_size:
var pos_z = -0.5 * (depth_size - 1) * BOX_SPACE.z
for depth in depth_size:
var box = template_body.duplicate()
box.transform.origin = Vector3(pos_x, pos_y, pos_z)
box.name = "Box%03d" % (row * column + 1)
_objects.push_back(box)
pos_z += BOX_SPACE.z
pos_y += BOX_SPACE.y
pos_x += BOX_SPACE.x
timer = OS.get_ticks_usec() - timer
Log.print_log(" Create Time: %.3f ms" % (0.001 * timer))
template_body.queue_free()
func _add_objects():
var root_node = $Objects
Log.print_log("* Adding objects...")
var timer = OS.get_ticks_usec()
for object in _objects:
root_node.add_child(object)
timer = OS.get_ticks_usec() - timer
Log.print_log(" Add Time: %.3f ms" % (0.001 * timer))
func _move_objects():
Log.print_log("* Moving objects...")
var timer = OS.get_ticks_usec()
for object in _objects:
object.transform.origin += BOX_SPACE
timer = OS.get_ticks_usec() - timer
Log.print_log(" Move Time: %.3f ms" % (0.001 * timer))
func _remove_objects():
var root_node = $Objects
Log.print_log("* Removing objects...")
var timer = OS.get_ticks_usec()
for object in _objects:
root_node.remove_child(object)
timer = OS.get_ticks_usec() - timer
Log.print_log(" Remove Time: %.3f ms" % (0.001 * timer))

View File

@@ -0,0 +1,13 @@
[gd_scene load_steps=3 format=2]
[ext_resource path="res://tests/performance/test_perf_broadphase.gd" type="Script" id=1]
[ext_resource path="res://utils/camera_orbit.gd" type="Script" id=5]
[node name="Test" type="Spatial"]
script = ExtResource( 1 )
[node name="Objects" type="Spatial" parent="."]
[node name="Camera" type="Camera" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 29.8407 )
script = ExtResource( 5 )

View File

@@ -10,7 +10,7 @@ const OPTION_TYPE_SPHERE = "Shape type/Sphere"
export(Array) var spawns = Array()
export(int) var spawn_count = 100
export(int, 1, 10) var spawn_multipiler = 5
export(int, 1, 10) var spawn_multiplier = 5
var _object_templates = []
@@ -36,6 +36,11 @@ func _ready():
_start_all_types()
func _exit_tree():
for object_template in _object_templates:
object_template.free()
func _on_option_selected(option):
cancel_timer()
@@ -123,9 +128,10 @@ func _spawn_objects(type_index):
Log.print_log("* Spawning: " + template_node.name)
for _index in range(spawn_multipiler):
for _node_index in spawn_count / spawn_multipiler:
for _index in range(spawn_multiplier):
for _node_index in spawn_count / spawn_multiplier:
var node = template_node.duplicate() as Spatial
node.transform.origin = Vector3.ZERO
spawn_parent.add_child(node)

View File

@@ -1,25 +1,12 @@
[gd_scene load_steps=5 format=2]
[gd_scene load_steps=4 format=2]
[ext_resource path="res://assets/robot_head/godot3_robot_head_collision.tres" type="Shape" id=1]
[ext_resource path="res://assets/robot_head/godot3_robot_head.mesh" type="ArrayMesh" id=2]
[sub_resource type="PlaneMesh" id=1]
[sub_resource type="ConcavePolygonShape" id=2]
data = PoolVector3Array( -1, 0, 1, 1, 0, -1, 1, 0, 1, -1, 0, 1, -1, 0, -1, 1, 0, -1 )
[ext_resource path="res://tests/static_scene_plane.tscn" type="PackedScene" id=3]
[node name="StaticScene" type="Spatial"]
[node name="StaticBodyPlane" type="StaticBody" parent="."]
[node name="MeshInstance" type="MeshInstance" parent="StaticBodyPlane"]
transform = Transform( 50, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 0 )
mesh = SubResource( 1 )
material/0 = null
[node name="CollisionShape" type="CollisionShape" parent="StaticBodyPlane"]
transform = Transform( 50, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 0 )
shape = SubResource( 2 )
[node name="StaticBodyPlane" parent="." instance=ExtResource( 3 )]
[node name="StaticBodyHead" type="StaticBody" parent="."]
transform = Transform( 10, 0, 0, 0, 8.66025, 5, 0, -5, 8.66025, 0, -11.1389, 2.29332 )

View File

@@ -0,0 +1,17 @@
[gd_scene load_steps=3 format=2]
[sub_resource type="PlaneMesh" id=1]
[sub_resource type="ConcavePolygonShape" id=2]
data = PoolVector3Array( -1, 0, 1, 1, 0, -1, 1, 0, 1, -1, 0, 1, -1, 0, -1, 1, 0, -1 )
[node name="StaticBodyPlane" type="StaticBody"]
[node name="MeshInstance" type="MeshInstance" parent="."]
transform = Transform( 50, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 0 )
mesh = SubResource( 1 )
material/0 = null
[node name="CollisionShape" type="CollisionShape" parent="."]
transform = Transform( 50, 0, 0, 0, 1, 0, 0, 0, 50, 0, 0, 0 )
shape = SubResource( 2 )

View File

@@ -13,6 +13,10 @@ func _enter_tree():
remove_child(_entry_template)
func _exit_tree():
_entry_template.free()
func clear():
while get_child_count():
var entry = get_child(get_child_count() - 1)