Skip to content

Conversation

tancop
Copy link

@tancop tancop commented Dec 22, 2024

Closes godotengine/godot-proposals#7401.

This PR adds:

  • Methods shape_set_friction, shape_get_friction, shape_set_bounce, shape_get_bounce to PhysicsServer3D
  • Property physics_material to Shape3D

This is implemented for both Godot and Jolt Physics (using a custom Jolt physics material like one of the official samples).

Example project that uses two different materials on one body: per-shape-materials.zip

@tancop tancop requested review from a team as code owners December 22, 2024 18:57
@tancop tancop force-pushed the add-per-shape-materials branch 3 times, most recently from 6079b03 to 198281e Compare December 23, 2024 15:27
@AThousandShips AThousandShips added this to the 4.x milestone Dec 23, 2024
@tancop tancop force-pushed the add-per-shape-materials branch 2 times, most recently from 3afbf17 to 0713122 Compare December 24, 2024 09:11
@tancop
Copy link
Author

tancop commented Dec 24, 2024

Fixed the xml method order and added support for concave shapes.

@tancop tancop force-pushed the add-per-shape-materials branch from 0713122 to 0589886 Compare December 25, 2024 10:10
@tancop
Copy link
Author

tancop commented Dec 25, 2024

Added support for changing materials at runtime. I had to add a new custom shape type JoltCustomMaterialOverrideShape, add a new virtual method on JoltShape3D and change some others to return a mutable JPH::Ref<JPH::Shape> instead of a JPH::ShapeRefC.

@tancop tancop force-pushed the add-per-shape-materials branch 2 times, most recently from 08080d6 to e72672f Compare December 27, 2024 21:24
@tancop
Copy link
Author

tancop commented Dec 28, 2024

@AThousandShips I guess this is ready for review. If you see something wrong or confusing let me know

jrouwe added a commit to jrouwe/JoltPhysics that referenced this pull request Dec 29, 2024
@tancop tancop force-pushed the add-per-shape-materials branch from e72672f to 65418b3 Compare December 30, 2024 09:40
@tancop
Copy link
Author

tancop commented Dec 30, 2024

@jrouwe thanks! I saw RestoreMaterialState in the docs but thought it was just for deserializing from binary state. You saved 200 lines and a custom shape slot

@tancop tancop force-pushed the add-per-shape-materials branch 2 times, most recently from 49b2190 to 07de536 Compare December 31, 2024 08:26
@tancop tancop force-pushed the add-per-shape-materials branch from 07de536 to 870c7db Compare December 31, 2024 17:46
@tancop
Copy link
Author

tancop commented Dec 31, 2024

Renamed JoltShape3D::_set_material to _update_material, it only changes the generated Jolt material not the shape itself

@tancop tancop force-pushed the add-per-shape-materials branch from 870c7db to 8967fc0 Compare January 3, 2025 13:06
@tancop
Copy link
Author

tancop commented Jan 3, 2025

Rebased on latest master

@tancop tancop force-pushed the add-per-shape-materials branch 3 times, most recently from 7a3ac0c to 0456e35 Compare May 17, 2025 09:58
@tancop
Copy link
Author

tancop commented May 17, 2025

@mihe thanks for the review just merged all the changes. I see you removed some casts to uint32_t in new JoltBody3D methods, had to add them back because -Wsign-compare is turned on for official builds

@tancop tancop force-pushed the add-per-shape-materials branch from 0456e35 to c50f336 Compare May 19, 2025 06:41
@tancop tancop requested a review from mihe May 19, 2025 06:42
@tancop
Copy link
Author

tancop commented May 22, 2025

@mihe this good to go now or you still got questions? just wanna make sure everything clean before it gets merged

Copy link
Contributor

@mihe mihe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a fairly critical issue with the Godot Physics implementation, and some very minor cosmetic remarks in the Jolt implementation.

Outside of these review comments, I must admit that I'm becoming more and more uncomfortable with the API of these two PhysicsServer*D methods:

void body_set_shape_friction_override(RID p_body, int p_shape_idx, bool p_enable, real_t p_friction = 0.0);
void body_set_shape_bounce_override(RID p_body, int p_shape_idx, bool p_enable, real_t p_bounce = 0.0);

I realize it's a bit late to be having this conversation, and that the current design stems from feedback that have been given here already, so I won't push too hard on this, but the whole thing with having p_enable there, and then defaulting the values to 0.0, just so you can pass false without needing to give it a value. It makes for an odd-looking API in my opinion.

Unfortunately I struggle to think of an alternative that wouldn't ideally require a bool to be added for each material state as well, which seems like a hefty price to pay for what is only (subjectively) a better API.

Anyway, I just figured I'd voice my concern at least, in case anyone had any other ideas, or know of other Godot APIs that do something similar already.

@tancop tancop force-pushed the add-per-shape-materials branch 5 times, most recently from 6fb0b72 to 269885f Compare May 26, 2025 12:56
@tancop
Copy link
Author

tancop commented May 27, 2025

@mihe switched this to use shape owner methods can you take a look if i did it right

@Mickeon Mickeon requested a review from mihe May 30, 2025 15:41
@tancop
Copy link
Author

tancop commented Jun 1, 2025

@Calinou looks like mihe doesnt have time to review. is anyone else free right now?

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested again, it works as expected with GodotPhysics3D and Jolt Physics. Code and documentation look good to me.

I suggest having mihe take a final look at the API still, just in case. We can't change what's exposed once it lands in a stable release, so we need to make sure it's rock-solid.

@Mickeon Mickeon modified the milestones: 4.x, 4.5 Jun 1, 2025
@mihe

This comment was marked as outdated.

Copy link
Contributor

@mihe mihe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did another pass on this and found some bugs with the recent changes, which you'll find in the review comments. I also threw in some more nitpicks while I was at it.

I've also spent some more time thinking about the PhysicsServer*D API, and I think I've arrived at a better API for this:

func body_set_shape_friction_override(body: RID, shape_idx: int, friction: float) -> void
func body_get_shape_friction(body: RID, shape_idx: int) -> float
func body_is_shape_friction_overridden(body: RID, shape_idx: int) -> bool
func body_clear_shape_friction_override(body: RID, shape_idx: int) -> void

This gets rid of NAN from the API completely, by having body_get_shape_friction fall back on the body's friction when there is no override set, while still not burdening implementers with a separate enabled state.

To save us some time, I've gone ahead and made these changes already, and just like before you'll find the changes here (patch here), which also includes fixes for the new review comments.

Like before, if you have no objections to these changes, I can go ahead and push directly to your fork/branch as well. We're only a few days away from the 4.5 feature freeze, and I don't see anything else in this PR that should prevent this from being merged by then.

EDIT: Changes rebased on top of latest push (de20ef0).

Comment on lines +178 to +179
_FORCE_INLINE_ bool is_area() const { return area; }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed anymore.

Suggested change
_FORCE_INLINE_ bool is_area() const { return area; }

Comment on lines +180 to +181
_FORCE_INLINE_ bool is_area() const { return area; }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same feedback as with 2D.

Suggested change
_FORCE_INLINE_ bool is_area() const { return area; }

Comment on lines +262 to +265
if (shape.is_null()) {
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check doesn't make sense anymore.

Suggested change
if (shape.is_null()) {
return;
}

Comment on lines +254 to +257
if (shape.is_null()) {
return;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same feedback as with 2D.

Suggested change
if (shape.is_null()) {
return;
}

return debug_color;
}

void CollisionShape2D::set_physics_material(const Ref<PhysicsMaterial> &p_material) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not disconnecting from the changed signal when the physics material is cleared. See CollisionShape2D::set_shape for reference.

Comment on lines +235 to +239
GDVIRTUAL_BIND(_body_set_shape_bounce_override, "body", "shape_idx", "enable", "bounce");
GDVIRTUAL_BIND(_body_set_shape_friction_override, "body", "shape_idx", "enable", "friction");

GDVIRTUAL_BIND(_body_get_shape_bounce_override, "body", "shape_idx");
GDVIRTUAL_BIND(_body_get_shape_friction_override, "body", "shape_idx");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nitpicky, but in most other places friction is listed before bounce.

Suggested change
GDVIRTUAL_BIND(_body_set_shape_bounce_override, "body", "shape_idx", "enable", "bounce");
GDVIRTUAL_BIND(_body_set_shape_friction_override, "body", "shape_idx", "enable", "friction");
GDVIRTUAL_BIND(_body_get_shape_bounce_override, "body", "shape_idx");
GDVIRTUAL_BIND(_body_get_shape_friction_override, "body", "shape_idx");
GDVIRTUAL_BIND(_body_set_shape_friction_override, "body", "shape_idx", "enable", "friction");
GDVIRTUAL_BIND(_body_set_shape_bounce_override, "body", "shape_idx", "enable", "bounce");
GDVIRTUAL_BIND(_body_get_shape_friction_override, "body", "shape_idx");
GDVIRTUAL_BIND(_body_get_shape_bounce_override, "body", "shape_idx");

Comment on lines +698 to +702
ClassDB::bind_method(D_METHOD("body_set_shape_bounce_override", "body", "shape_idx", "enable", "bounce"), &PhysicsServer2D::body_set_shape_bounce_override, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("body_set_shape_friction_override", "body", "shape_idx", "enable", "friction"), &PhysicsServer2D::body_set_shape_friction_override, DEFVAL(0.0));

ClassDB::bind_method(D_METHOD("body_get_shape_bounce_override", "body", "shape_idx"), &PhysicsServer2D::body_get_shape_bounce_override);
ClassDB::bind_method(D_METHOD("body_get_shape_friction_override", "body", "shape_idx"), &PhysicsServer2D::body_get_shape_friction_override);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nitpick about the ordering here.

Suggested change
ClassDB::bind_method(D_METHOD("body_set_shape_bounce_override", "body", "shape_idx", "enable", "bounce"), &PhysicsServer2D::body_set_shape_bounce_override, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("body_set_shape_friction_override", "body", "shape_idx", "enable", "friction"), &PhysicsServer2D::body_set_shape_friction_override, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("body_get_shape_bounce_override", "body", "shape_idx"), &PhysicsServer2D::body_get_shape_bounce_override);
ClassDB::bind_method(D_METHOD("body_get_shape_friction_override", "body", "shape_idx"), &PhysicsServer2D::body_get_shape_friction_override);
ClassDB::bind_method(D_METHOD("body_set_shape_friction_override", "body", "shape_idx", "enable", "friction"), &PhysicsServer2D::body_set_shape_friction_override, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("body_set_shape_bounce_override", "body", "shape_idx", "enable", "bounce"), &PhysicsServer2D::body_set_shape_bounce_override, DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("body_get_shape_friction_override", "body", "shape_idx"), &PhysicsServer2D::body_get_shape_friction_override);
ClassDB::bind_method(D_METHOD("body_get_shape_bounce_override", "body", "shape_idx"), &PhysicsServer2D::body_get_shape_bounce_override);

Comment on lines +347 to +349
if (sd.material == p_material) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, since it'll keep the same reference even when we change values within the material.

Suggested change
if (sd.material == p_material) {
return;
}

Comment on lines +579 to +581
if (sd.material == p_material) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same feedback as with 2D.

Suggested change
if (sd.material == p_material) {
return;
}

#include "godot_area_2d.h"
#include "godot_body_direct_state_2d.h"
#include "godot_constraint_2d.h"
#include "godot_physics_server_2d.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed.

@mihe
Copy link
Contributor

mihe commented Jun 9, 2025

As another last-minute decision, I've omitted the body_is_shape_(friction|bounce)_overridden physics server methods from the API that I suggested above, because I struggle to see a use-case for such a query, and it's not something that's needed by the scene nodes anyway.

If anyone needs this in the future it can trivially be added then instead.

So the PhysicsServer*D API would look like this instead, which I'm mostly happy with:

func body_set_shape_friction_override(body: RID, shape_idx: int, friction: float) -> void
func body_clear_shape_friction_override(body: RID, shape_idx: int) -> void
func body_get_shape_friction(body: RID, shape_idx: int) -> float

The branch/patch linked above should have updated accordingly, but here (patch here) it is again if you need it.

EDIT: Changes have rebased on top of latest push (de20ef0).

@Repiteo Repiteo modified the milestones: 4.5, 4.x Jun 23, 2025
@tancop tancop force-pushed the add-per-shape-materials branch from 269885f to de20ef0 Compare July 4, 2025 08:21
@Xtarsia
Copy link

Xtarsia commented Jul 10, 2025

Does this have any chance of working with Heightmap Shapes? (I belive these construct a grid of Quad shapes under the hood, that are tested for in a specific way due to the nature of heightmaps.)

In Terrain3D I would like to be able to send a physics material "ID" for each Quad. Since We have access to an index map that could determine the dominant texture for any given quad, and that texture has other properties associated with it already, linking each texture ID (upto 32) to a physics material, would essentially Let us have paintable physics materials.

@mihe
Copy link
Contributor

mihe commented Jul 11, 2025

Does this have any chance of working with Heightmap Shapes? [...] In Terrain3D I would like to be able to send a physics material "ID" for each Quad.

@Xtarsia Not as-is, no. The same goes for individual triangles in ConcavePolygonShape3D. It's technically doable with Jolt, which supports this for both shapes, but it's not immediately clear to me how you'd go about implementing it for Godot Physics.

Either way, we'd need some way of actually assigning material values to groups of faces, both from a UX perspective and from an API perspective, which is likely quite a bit of work, and better suited for a future PR. The texture approach might make some sense for HeightMapShape3D, but I'm not sure it suits ConcavePolygonShape3D as well.

This lets users override bounce and friction for individual collision shapes instead of using
the same properties for the whole body.
@tancop tancop force-pushed the add-per-shape-materials branch from f59f57b to 2dd8a5b Compare August 4, 2025 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add per-shape physics materials to the 3D physics system
9 participants