Intersecting/Colliding with an area in PhysicsServer3D: A Mystery

Can anyone help me with a PhysicsServer3D thing?

I am trying to make an area via PhysicsServer3D and then test for intersection with a shape in that area.

I can’t get anything to work :frowning:

Here’s a very small project to see what I mean. It’s Godot v4.3.dev2.official (The latest from the website)

Here’s my basic gdscript:

@tool
extends Node3D

## Area intersection isue
##
## Trying to make an area by PhysicsServer3D and
## test for intersection with a box shape
##
## Open the scene and press the bool Tog in the inspector
## Observe the printed output.

var A:RID
var state:PhysicsDirectSpaceState3D

@export var tog:bool:
	set(b):
		tog = false
		create()
		go()

func create():
	print()
	print("*****************************")

	# Not sure which "space state" to use. Neither seem to work
	#state = owner.get_world_3d().direct_space_state
	state = self.get_world_3d().direct_space_state

	# Make the area
	PhysicsServer3D.free_rid(A)
	A = PhysicsServer3D.area_create()

	PhysicsServer3D.area_set_space(A, state)
	PhysicsServer3D.area_set_transform(A, self.transform)

	# Debug prints
	var areas_transform = PhysicsServer3D.area_get_transform(A)
	print("area's transform:", areas_transform)
	print("mine            :", self.transform)

	#mystery command. No clue. It feels like add_child, but who knows?
	PhysicsServer3D.area_attach_object_instance_id(A,self.get_instance_id())

	#Seems to make no difference
	PhysicsServer3D.area_set_monitorable(A, true)

	# Also makes no diff
	PhysicsServer3D.area_set_ray_pickable(A, true)

func go():
	await probe()

func probe():
	PhysicsServer3D.area_clear_shapes(A)

	# Loop through those mesh instances.
	# Test for an intersection with an area.
	# If no hit, add that shape to the area.
	# This should test, add, test, add etc.
	# Until the last mesh called 'overlapper' which is
	# plonk on top of the first three. It should report
	# an intersection with the area.
	# It does not :(
	for b:MeshInstance3D in get_children():
		# make a shape, the same size as b
		var shape_rid = PhysicsServer3D.box_shape_create()
		PhysicsServer3D.shape_set_data(shape_rid, b.mesh.size)

		#As per docs
		var params:PhysicsShapeQueryParameters3D = PhysicsShapeQueryParameters3D.new()
		params.shape_rid = shape_rid
		params.transform = b.transform
		params.collide_with_bodies = false
		params.collide_with_areas = true # <-- surely?

		# Execute physics queries here...
		var surrounds = state.intersect_shape(params,16)

		# Debug output
		print()
		print("Made shape on:", b.name)
		print(" orig pos, size:", b.position, " ", b.mesh.size)
		print(" shape pos, size:",
				params.transform.origin,
				PhysicsServer3D.shape_get_data(shape_rid)
		)
		print("--hits?--")
		print(str(surrounds).replace("}, {", "\n"))

		# If we did not hit anything, place a shape into the area
		# for the next round
		if surrounds.is_empty():
			var newt:Transform3D = params.transform
			PhysicsServer3D.area_add_shape(A, shape_rid, newt)
			print(" set as:", newt)
		#actually hoping I don't need this await at all.
		await get_tree().process_frame

	print()
	print("Number of shapes:", PhysicsServer3D.area_get_shape_count(A))
	for s in range(0,PhysicsServer3D.area_get_shape_count(A)):
		var st = PhysicsServer3D.area_get_shape_transform(A,s)
		print("  :", st)

In my scene, I placed a few MeshInstance3D nodes (with a box shape resource in each) and the last one overlaps the first three.

In the code, when you press the “Tog” boolean in the inspector, it:

  1. Makes an area using PhysicsServer3D
  2. Loops the MeshInstances and creates a shape of the same size and position. (I hope)
  3. Performs an intersect_shape query with that shape
  4. If there is no hit/result, then add that shape to the area so that it can be potentially hit the next time round.

I expect the last test to report a collision/intersection because it’s placed to hit three other shapes (previously placed in the loop).

However, I can’t get any results and I am losing hope!

Never tried something like that, but isn’t needed to set the layer and mask collision? where is this set?

Hi, yes in further hacks I have set the collision mask and stuff. I feel I am missing something fundamental; or it’s a Godot bug.

Did you check if the collision shape is being created at the right posiiton and size enabling the collision shapes visible in Debug menu?

1 Like

I am fairly sure my test code does that. Remember this is not an Area3D Node, it’s using the PhysicsServer to make whatever weird thing actually underlies that node.

I can get the system to work with nodes, but I wanted to see how it would work with the Server too. Am also hoping to avoid having to wait for get_tree().process_frame

I see, sorry for asking, but I’m not used to this kind of implementation. I was hoping the editor would show the shapes even if it was created directly from the server.

See if this tutorial helps you:

1 Like

Thank you for your assist. No need to apologize! I will watch that video and hack on. I’ll also update this thread should I find something that works.

1 Like

I’m trying again as I have not had any success. I made a simpler test script. Please fetch the project from the gitlab link in the OP.

Turns out I can’t seem to get this to work even with Nodes. I am now making an Area3D node, putting a few CollisionShape3D nodes (+ BoxShape3D) into it. Then I am making a new Shape3D and trying to use intersect_shape … to no avail.

Can anyone spot my mistake? I’d like to know because this may be worth posting as a bug to the Godot devs.

Here’s a screeny of my scene:
image

Here’s the new gdscript:

@tool
extends Node3D

## Area intersection isue
##
## Trying to make an Aread3D by using nodes and
## test for intersection with a box shape.
##
## Open the scene and click the bool Tog in the inspector
## Observe the printed output.
##
## I expect to see:
## *****************************
## one
## two
## three
## TEST HIT
## Bingo!

var A:Area3D
var state:PhysicsDirectSpaceState3D

@export var WTF:bool:
	set(b):
		WTF = false
		await set_state()
		await wtf()

func set_state():
	state = null
	await get_tree().physics_frame
	if not state:
		state = get_world_3d().direct_space_state

func wtf():
	await create_area3d_node()
	await node_probe()

func create_area3d_node():
	print()
	print("*****************************")

	var _a = find_child("AREANODE")
	if _a:
		remove_child(_a)
		_a.queue_free()
		A = null

	A = Area3D.new()
	A.name = "AREANODE"
	add_child(A)
	A.set_owner(self.owner)

	await get_tree().physics_frame


func node_probe():
	var box:BoxShape3D
	# make a shape, the same size as b
	var poke = [$one, $two, $three]
	for b in poke:
		print(b.name)
		box = BoxShape3D.new()
		box.size = b.mesh.size
		var newt:Transform3D = b.transform
		var coll:CollisionShape3D
		coll = CollisionShape3D.new()
		coll.name = b.name
		coll.transform = b.transform
		coll.shape = box
		A.add_child(coll)
		coll.set_owner(A.owner)
		await get_tree().physics_frame

	#As per docs
	var params:PhysicsShapeQueryParameters3D = PhysicsShapeQueryParameters3D.new()
	var shape_rid = PhysicsServer3D.box_shape_create()
	PhysicsServer3D.shape_set_data(shape_rid, $overlapper.mesh.size)
	params.shape_rid = shape_rid
	params.transform = $overlapper.transform
	params.collide_with_areas = true
	params.collide_with_bodies = false

	print("TEST HIT")
	var surrounds = state.intersect_shape(params, 6)

	PhysicsServer3D.free_rid(shape_rid)

	if surrounds:
		print("Bingo!")

I looked at your code today again, and it seems totally fine - couldn’t spot any issues. So I figured, it might be a bug in the core physics code. Not an unheard-of thing :wink:
So I added this right after #As per docs:

PhysicsServer3D.set_active(true)

Aaaand guess what? :sweat_smile:

1 Like

It didn’t work! :face_with_hand_over_mouth:

I totally understand your skepticism :sweat_smile:
image

1 Like

On the first try it doesn’t print “Bingo” BTW. Not sure why, would have to dig into physics code to figure it out. Anyway, somehow this kicks some gears into motion, and it starts to work on the second and subsequent toggles.

Thank you. I really appreciate your help. :blush:

I have noticed that the more it runs, and the more I mess with the code, stuff seems to come and go. It’s always tricky writing tool scripts.

I might open a bug on this thanks to your feedback. Probably tomorrow morning.

1 Like

I opened an issue here:

1 Like

I tested in godot 4.2 I think it’s worked

Update. It strange work when i run the scene and press toggle
image

Thank you! So it might be a regression. Very useful to know.

Although, the order of the loop is weird. one, then TEST, then two, three and TEST again? Very weird.

We need advanced tutorial to understand Godot in deep level

I have found some solutions. I made a short post on my gitlab wiki, which I’ll try to post here too:

Introduction

When writing a @tool (or plugin) in Godot 4.x that requires detection of what’s in the 3D world (the scene) around you, then you’ll find the docs (right now) a little confusing. I did!

It’s easy to find the PhysicsDirectSpaceState3D class (docs), however, what you need is the RenderingServer class (docs). (If you are using PhysicsDirectSpaceState3D then you may be on the wrong path.)

In a recent thread on Godot’s Github, I discovered that “physics” is (intended to be) disabled in the editor.

“PhysicsServers are disabled by the EditorNode inside the Editor for both performance reasons and to not have shape conflict with the ray picking done by the Editor Plugins for that node and the Editor Viewport.”— link here

The BVH (Bounding Volume Hierarchy) API

Godot uses the BVH algorithm. The API to use it, in the editor, is in the RenderingServer class. There are three methods you can play with:

  1. instances_cull_aabb
  2. instances_cull_convex
  3. instances_cull_ray

These methods are not as useful as the ones in PhysicsDirectSpaceState3D, but they are what we are intended to use.

One Exception

For reasons I am not clear on, one of the methods in PhysicsDirectSpaceState3D is supported in the editor. It may be because it has already been used by several plugins and would break them should it be turned off. Not sure. Anyway, you can use this one in the editor: intersect-ray

Demo Project

I made a little demo. Move the probe around and then press the Do Probe toggle in the inspector. Watch the output and it will print the names of the 3D objects that the probe is intersecting.

My demo project files can be fetched from here

Here’s the code in full:

@tool
extends MeshInstance3D

## How to use RenderingServer.instances_cull_aabb() in an editor tool
##
## There are three methods in the RenderingServer class that are
## intended for use (only) in the Editor.
## Here I explore one that returns an array of all VisualInstance nodes
## that intersect a given AABB.
##
## The way I found to get it to work is to use await. You may be able
## to use func _physics_process(delta: float) to get the same result.
## This depends on your plugin/tool and what it does.

## Run the probe and see a printout of hits.
@export var do_probe:bool:
	set(b):
		do_probe = false
		print()
		print("PROBING")
		var hits:Array = await overlapping(self.transform)
		for objid in hits:
			var i = instance_from_id(objid)
			print("Probe hit:", i.name)


# This makes use of Godot's built-in BVH volume system. It's a bit
# clunky, but it's better than anything I can make.
# Note: BVH only works on VisualInstance derived NODES. (Not geometry made
# with server methods)
func overlapping(t:Transform3D) -> Array:
	var local_aabb:AABB = self.mesh.get_aabb()

	# get_aabb is in a mesh-local-space
	# it must be moved to t (inside our space)
	var global_aabb:AABB = t * local_aabb

	# To avoid detecting ourselves, we go invisible
	self.visible = false

	# NB await BEFORE the cull_aabb.
	# IT WON'T WORK AT ALL ELSE.
	await get_tree().physics_frame

	# The "space" that instances_cull_aabb works in is a bit vague to me.
	# It seems to work when the space of the probe and the space of the
	# targets is the same. Keep playing with that until stuff happens.
	var pa:PackedInt64Array = RenderingServer.instances_cull_aabb(
			global_aabb, get_world_3d().scenario)

	self.visible = true

	if pa:
		# For some reason, there are many repeats within the
		# returned array. So I removed dups.
		var a:Array = dedup(Array(pa))
		return a
	return []

func dedup(array: Array) -> Array:
	var unique: Array = []
	for item in array:
		if not unique.has(item):
			unique.append(item)
	return unique
1 Like