PhysicsServer3D body in BodyMode Rigid not detecting collisions with StaticBody3D's

Godot Version

4.2

Question

Hello there!

This question is one of a series so far where I am trying to handle collision resolution myself, while using Godot’s built-in collision detection. (This is as a learning exercise, not to include in a game that I am making.)

I am currently trying to add a body to the physics server that is independent of the node which will use it (so that I can decide how to use the physics body to update the node). I’m not sure if I have made a mistake somewhere, or if there is a bug. I have added a spherical body in rigid mode, and set many of the parameters that the body and shape need. It will collide with RigidBody3Ds, but does not detect any collision with StaticBody3Ds.

I am wondering if I should submit a bug report or if it is something I can fix.

Thank you in advance for your time. :slight_smile:


The following code snippets are part of a custom class in GDExtension called PoolBall. Let me know if you need any more context.

code for _ready function:

void PoolBall::_ready(void){
	if(DEBUG) godot::UtilityFunctions::print("Ready - PoolBall."); 

    once = true; // sometimes I like to check whether things are working in looping functions, but only once.

    // increment the counter
    num_balls = (num_balls + 1) % MAX_BALLS; // by capping it at a sensible MAX_BALLS, we won't have any problems getting a texture for it.
    
    // assign a mesh and the correct resource
    SphereMesh* sphere_mesh = memnew(SphereMesh);
    set_mesh(sphere_mesh);
    StandardMaterial3D* standard_material = memnew(StandardMaterial3D); // let's use the standard material, since shaders aren't the focus here. 
    sphere_mesh->surface_set_material(0, standard_material);

    // load the corresponding texture and assign it to the material's albedo_texture
    char filepath[30];
    sprintf(filepath, "res://textures/ball_%i.png", num_balls);
    standard_material->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, ResourceLoader::get_singleton()->load(filepath));

    // grab the singleton for the physics server
    server = PhysicsServer3D::get_singleton();

    // we are going to use a synced body with a sphere shape to detect the collisions; this way we can handle how the collisions work ourselves
    physics_body_resource = server->body_create();
    RID shape_rid = server->sphere_shape_create(); // our pool balls have sphere shapes
    Dictionary sphere_data; sphere_data["radius"] = 0.5;
    server->shape_set_data(shape_rid, sphere_data);

    // float shape_scaling_factor = 2.0;
    // Transform3D shape_transform = Transform3D(Vector3(shape_scaling_factor, 0.0, 0.0), Vector3(0.0, shape_scaling_factor, 0.0), Vector3(0.0, 0.0, shape_scaling_factor), Vector3(0.0, 0.0, 0.0));

    server->body_add_shape(physics_body_resource, shape_rid); // I believe that the Transform passed to this function is a local transform (as if the shape is a child of the body)
    
    // if we didn't have this, we would need to make a new space... this means that the collisions could only be between bodies in that same space
    // that would be fine, but I want to be able to use this class WITH built-in classes added in the editor, if desired.
    // the "default" space that bodies are added to in built-in classes is the space which belongs to the World3D that those nodes belong to. 
    RID space_rid = get_world_3d()->get_space();

    // set up the body's parameters
    server->body_set_space(physics_body_resource, space_rid); // MUST set the space explicitly
    server->body_set_max_contacts_reported(physics_body_resource, 10);
    server->body_set_mode(physics_body_resource, PhysicsServer3D::BODY_MODE_RIGID);
    server->body_set_state(physics_body_resource, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform());
    server->body_set_state(physics_body_resource, PhysicsServer3D::BODY_STATE_LINEAR_VELOCITY, Vector3(0.0, 0.0, 0.0));
    server->body_set_state(physics_body_resource, PhysicsServer3D::BODY_STATE_ANGULAR_VELOCITY, Vector3(0.0, 0.0, 0.0));
    server->body_set_state(physics_body_resource, PhysicsServer3D::BODY_STATE_SLEEPING, false);
    server->body_set_state(physics_body_resource, PhysicsServer3D::BODY_STATE_CAN_SLEEP, false);
    server->body_set_enable_continuous_collision_detection(physics_body_resource, true);
    // server->body_set_force_integration_callback(physics_body_resource, Callable(this, "_integrate_forces"));
    // server->body_set_omit_force_integration(physics_body_resource, true);
    
    // set_process(true); // not sure if this is necessary; consider uncommenting it if your process function(s) aren't running.
    godot::UtilityFunctions::print(server->body_get_shape_transform(physics_body_resource, 0));
    godot::UtilityFunctions::print(server->body_get_collision_mask(physics_body_resource));
    godot::UtilityFunctions::print(server->body_get_collision_layer(physics_body_resource));
    godot::UtilityFunctions::print(server->body_get_collision_priority(physics_body_resource));
}

code for _physics_process function:

void PoolBall::_physics_process(float delta){
	if (Engine::get_singleton()->is_editor_hint()) return; // Early return if we are in editor

    if(once){	if(DEBUG) godot::UtilityFunctions::print("Physics Process - PoolBall.");  once = false; }

    godot::UtilityFunctions::print(server->body_get_space(physics_body_resource));
    PhysicsDirectBodyState3D* state = server->body_get_direct_state(physics_body_resource);

    int num = state->get_contact_count();
    godot::UtilityFunctions::print(num);
    // godot::UtilityFunctions::print(state->get_transform());

    // physics body follows the pool ball
    // server->body_set_state(physics_body_resource, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform());

    // pool ball follows the physics body
    set_global_transform(server->body_get_state(physics_body_resource, PhysicsServer3D::BODY_STATE_TRANSFORM));
}

edit: typo

UPDATE

If you add the following code to _physics_process, it can detect the collision with static bodies. I’m not entirely sure if this is the “solution”, but it is certainly a relevant step in the right direction.

    Ref<PhysicsTestMotionParameters3D> test_move = memnew(PhysicsTestMotionParameters3D); 
    test_move->set_motion(Vector3(0, -delta, 0)); // this was just to test whether it would intersect with the static "floor" object I have in the scene
    test_move->set_from(server->body_get_state(physics_body_resource, PhysicsServer3D::BODY_STATE_TRANSFORM));
    test_move->set_max_collisions(10);
    test_move->set_collide_separation_ray_enabled(true);
    godot::UtilityFunctions::print(server->body_test_motion(physics_body_resource, test_move)?"collision":"no collision");

UPDATE 2

The body_test_motion function has an optional result parameter that gave me all of the information that I needed! :slight_smile:

UPDATE 3

Okay… I feel very stupid but hopefully this can help someone else in a similar position.

I thought that shape_set_data(RID, Variant) would take a Dictionary such as dictionary["radius"] = value. That is not the case. For a sphere, it just takes a float for the radius value. It doesn’t seem to have any default radius when you create a sphere (I thought the default would be 0.5).

Once you properly set the radius, it will detect static AND rigid bodies.

// grab the singleton for the physics server
server = PhysicsServer3D::get_singleton();

// create the shape
RID shape_rid = server->sphere_shape_create();

// now properly initialize the shape
float radius = 0.5f;
server->shape_set_data(shape_rid, radius);

It is worth noting that the body_test_motion results give some more/different information, so it is still valuable.