Physics2D: Begin fixing Top-Level CollisionShapes

In the physics server, shapes are simple data structure without reference to the canvas, just a transform. This transform was always expected to be relative to the parent object's transform.

By adding a new field to these shapes (indpdt_xform) representing whether it is top_level, the final tranform computations can be adjusted to be relative or not
This commit is contained in:
tgautot 2024-11-10 12:16:39 +01:00
parent e65a23762b
commit 172401188c
17 changed files with 69 additions and 38 deletions

View File

@ -134,8 +134,9 @@
<return type="void" />
<param index="0" name="owner_id" type="int" />
<param index="1" name="shape" type="Shape2D" />
<param index="2" name="top_level" type="bool" default="false" />
<description>
Adds a [Shape2D] to the shape owner.
Adds a [Shape2D] to the shape owner. The [param top_level] parameter is only used if the [CollisionObject2D] owning the shape owner is an [Area2D]. If it is [code]true[/code], it makes the shape owner's transform independent from the collision object's transform.
</description>
</method>
<method name="shape_owner_clear_shapes">
@ -218,8 +219,10 @@
<return type="void" />
<param index="0" name="owner_id" type="int" />
<param index="1" name="transform" type="Transform2D" />
<param index="2" name="top_level" type="bool" default="false" />
<description>
Sets the [Transform2D] of the given shape owner.
Sets the [Transform2D] of the given shape owner. The [param top_level] parameter is only used if the [CollisionObject2D] owning the shape owner is an [Area2D]. If it is [code]true[/code], it makes the shape owner's transform independent from the collision object's transform.
[b]Note:[/b] [param top_level] has a default value of [code]false[/code], you must pass it [code]true[/code] every time you call this function otherwise your shape owner will revert to being NOT top-level.
</description>
</method>
</methods>

View File

@ -22,8 +22,9 @@
<param index="1" name="shape" type="RID" />
<param index="2" name="transform" type="Transform2D" default="Transform2D(1, 0, 0, 1, 0, 0)" />
<param index="3" name="disabled" type="bool" default="false" />
<param index="4" name="top_level" type="bool" default="false" />
<description>
Adds a shape to the area, with the given local transform. The shape (together with its [param transform] and [param disabled] properties) is added to an array of shapes, and the shapes of an area are usually referenced by their index in this array.
Adds a shape to the area, with the given local transform (or [b]global[/b] transform if [param top_level] is set to [code]true[/code]). The shape (together with its [param transform], [param disabled] and [param top_level] properties) is added to an array of shapes, and the shapes of an area are usually referenced by their index in this array.
</description>
</method>
<method name="area_attach_canvas_instance_id">
@ -221,8 +222,9 @@
<param index="0" name="area" type="RID" />
<param index="1" name="shape_idx" type="int" />
<param index="2" name="transform" type="Transform2D" />
<param index="3" name="top_level" type="bool" default="false" />
<description>
Sets the local transform matrix of the area's shape with the given index.
Sets the local transform (or [b]global[/b] if [param top_level] is set to [code]true[/code]) matrix of the area's shape with the given index.
</description>
</method>
<method name="area_set_space">

View File

@ -16,6 +16,7 @@
<param index="1" name="shape" type="RID" />
<param index="2" name="transform" type="Transform2D" />
<param index="3" name="disabled" type="bool" />
<param index="4" name="top_level" type="bool" />
<description>
Overridable version of [method PhysicsServer2D.area_add_shape].
</description>
@ -211,6 +212,7 @@
<param index="0" name="area" type="RID" />
<param index="1" name="shape_idx" type="int" />
<param index="2" name="transform" type="Transform2D" />
<param index="3" name="top_level" type="bool" />
<description>
Overridable version of [method PhysicsServer2D.area_set_shape_transform].
</description>

View File

@ -122,7 +122,14 @@ GodotAreaPair2D::~GodotAreaPair2D() {
bool GodotArea2Pair2D::setup(real_t p_step) {
bool result_a = area_a->collides_with(area_b);
bool result_b = area_b->collides_with(area_a);
if ((result_a || result_b) && !GodotCollisionSolver2D::solve(area_a->get_shape(shape_a), area_a->get_transform() * area_a->get_shape_transform(shape_a), Vector2(), area_b->get_shape(shape_b), area_b->get_transform() * area_b->get_shape_transform(shape_b), Vector2(), nullptr, this)) {
bool a_indpdt_xform = area_a->get_shape_xform_independance(shape_a);
bool b_indpdt_xform = area_b->get_shape_xform_independance(shape_b);
Transform2D transform_a = a_indpdt_xform ? area_a->get_shape_transform(shape_a) : (area_a->get_transform() * area_a->get_shape_transform(shape_a));
Transform2D transform_b = b_indpdt_xform ? area_b->get_shape_transform(shape_b) : (area_b->get_transform() * area_b->get_shape_transform(shape_b));
if ((result_a || result_b) && !GodotCollisionSolver2D::solve(area_a->get_shape(shape_a), transform_a, Vector2(), area_b->get_shape(shape_b), transform_b, Vector2(), nullptr, this)) {
result_a = false;
result_b = false;
}

View File

@ -32,10 +32,11 @@
#include "godot_physics_server_2d.h"
#include "godot_space_2d.h"
void GodotCollisionObject2D::add_shape(GodotShape2D *p_shape, const Transform2D &p_transform, bool p_disabled) {
void GodotCollisionObject2D::add_shape(GodotShape2D *p_shape, const Transform2D &p_transform, bool p_disabled, bool p_indpdt_xform) {
Shape s;
s.shape = p_shape;
s.xform = p_transform;
s.indpdt_xform = p_indpdt_xform;
s.xform_inv = s.xform.affine_inverse();
s.bpid = 0; //needs update
s.disabled = p_disabled;
@ -61,11 +62,12 @@ void GodotCollisionObject2D::set_shape(int p_index, GodotShape2D *p_shape) {
}
}
void GodotCollisionObject2D::set_shape_transform(int p_index, const Transform2D &p_transform) {
void GodotCollisionObject2D::set_shape_transform(int p_index, const Transform2D &p_transform, bool p_indpdt_xform) {
ERR_FAIL_INDEX(p_index, shapes.size());
shapes.write[p_index].xform = p_transform;
shapes.write[p_index].xform_inv = p_transform.affine_inverse();
shapes.write[p_index].indpdt_xform = p_indpdt_xform;
if (!pending_shape_update_list.in_list()) {
GodotPhysicsServer2D::godot_singleton->pending_shape_update_list.add(&pending_shape_update_list);
@ -170,7 +172,7 @@ void GodotCollisionObject2D::_update_shapes() {
//not quite correct, should compute the next matrix..
Rect2 shape_aabb = s.shape->get_aabb();
Transform2D xform = transform * s.xform;
Transform2D xform = s.indpdt_xform ? s.xform : transform * s.xform;
shape_aabb = xform.xform(shape_aabb);
shape_aabb.grow_by((s.aabb_cache.size.x + s.aabb_cache.size.y) * 0.5 * 0.05);
s.aabb_cache = shape_aabb;

View File

@ -56,6 +56,7 @@ private:
struct Shape {
Transform2D xform;
Transform2D xform_inv;
bool indpdt_xform;
GodotBroadPhase2D::ID bpid = 0;
Rect2 aabb_cache; //for rayqueries
GodotShape2D *shape = nullptr;
@ -108,9 +109,13 @@ public:
void _shape_changed() override;
_FORCE_INLINE_ Type get_type() const { return type; }
void add_shape(GodotShape2D *p_shape, const Transform2D &p_transform = Transform2D(), bool p_disabled = false);
void add_shape(GodotShape2D *p_shape, const Transform2D &p_transform = Transform2D(), bool p_disabled = false, bool p_indpdt_xform = false);
void set_shape(int p_index, GodotShape2D *p_shape);
void set_shape_transform(int p_index, const Transform2D &p_transform);
void set_shape_transform(int p_index, const Transform2D &p_transform, bool p_indpdt_xform = false);
void set_shape_xform_independance(int p_index, bool p_indpt) {
ERR_FAIL_INDEX(p_index, shapes.size());
shapes.write[p_index].indpdt_xform = p_indpt;
}
_FORCE_INLINE_ int get_shape_count() const { return shapes.size(); }
_FORCE_INLINE_ GodotShape2D *get_shape(int p_index) const {
@ -129,6 +134,10 @@ public:
CRASH_BAD_INDEX(p_index, shapes.size());
return shapes[p_index].aabb_cache;
}
_FORCE_INLINE_ bool get_shape_xform_independance(int p_index) const {
CRASH_BAD_INDEX(p_index, shapes.size());
return shapes[p_index].indpdt_xform;
}
_FORCE_INLINE_ const Transform2D &get_transform() const { return transform; }
_FORCE_INLINE_ const Transform2D &get_inv_transform() const { return inv_transform; }

View File

@ -320,14 +320,14 @@ RID GodotPhysicsServer2D::area_get_space(RID p_area) const {
return space->get_self();
}
void GodotPhysicsServer2D::area_add_shape(RID p_area, RID p_shape, const Transform2D &p_transform, bool p_disabled) {
void GodotPhysicsServer2D::area_add_shape(RID p_area, RID p_shape, const Transform2D &p_transform, bool p_disabled, bool p_indpdt_xform) {
GodotArea2D *area = area_owner.get_or_null(p_area);
ERR_FAIL_NULL(area);
GodotShape2D *shape = shape_owner.get_or_null(p_shape);
ERR_FAIL_NULL(shape);
area->add_shape(shape, p_transform, p_disabled);
area->add_shape(shape, p_transform, p_disabled, p_indpdt_xform);
}
void GodotPhysicsServer2D::area_set_shape(RID p_area, int p_shape_idx, RID p_shape) {
@ -341,11 +341,11 @@ void GodotPhysicsServer2D::area_set_shape(RID p_area, int p_shape_idx, RID p_sha
area->set_shape(p_shape_idx, shape);
}
void GodotPhysicsServer2D::area_set_shape_transform(RID p_area, int p_shape_idx, const Transform2D &p_transform) {
void GodotPhysicsServer2D::area_set_shape_transform(RID p_area, int p_shape_idx, const Transform2D &p_transform, bool p_indpdt_xform) {
GodotArea2D *area = area_owner.get_or_null(p_area);
ERR_FAIL_NULL(area);
area->set_shape_transform(p_shape_idx, p_transform);
area->set_shape_transform(p_shape_idx, p_transform, p_indpdt_xform);
}
void GodotPhysicsServer2D::area_set_shape_disabled(RID p_area, int p_shape, bool p_disabled) {

View File

@ -126,9 +126,9 @@ public:
virtual void area_set_space(RID p_area, RID p_space) override;
virtual RID area_get_space(RID p_area) const override;
virtual void area_add_shape(RID p_area, RID p_shape, const Transform2D &p_transform = Transform2D(), bool p_disabled = false) override;
virtual void area_add_shape(RID p_area, RID p_shape, const Transform2D &p_transform = Transform2D(), bool p_disabled = false, bool p_indpdt_xform = false) override;
virtual void area_set_shape(RID p_area, int p_shape_idx, RID p_shape) override;
virtual void area_set_shape_transform(RID p_area, int p_shape_idx, const Transform2D &p_transform) override;
virtual void area_set_shape_transform(RID p_area, int p_shape_idx, const Transform2D &p_transform, bool p_indpdt_xform) override;
virtual int area_get_shape_count(RID p_area) const override;
virtual RID area_get_shape(RID p_area, int p_shape_idx) const override;

View File

@ -391,7 +391,7 @@ PackedInt32Array CollisionObject2D::_get_shape_owners() {
return ret;
}
void CollisionObject2D::shape_owner_set_transform(uint32_t p_owner, const Transform2D &p_transform) {
void CollisionObject2D::shape_owner_set_transform(uint32_t p_owner, const Transform2D &p_transform, bool p_indpdt_xform) {
ERR_FAIL_COND(!shapes.has(p_owner));
ShapeData &sd = shapes[p_owner];
@ -399,7 +399,7 @@ void CollisionObject2D::shape_owner_set_transform(uint32_t p_owner, const Transf
sd.xform = p_transform;
for (int i = 0; i < sd.shapes.size(); i++) {
if (area) {
PhysicsServer2D::get_singleton()->area_set_shape_transform(rid, sd.shapes[i].index, sd.xform);
PhysicsServer2D::get_singleton()->area_set_shape_transform(rid, sd.shapes[i].index, sd.xform, p_indpdt_xform);
} else {
PhysicsServer2D::get_singleton()->body_set_shape_transform(rid, sd.shapes[i].index, sd.xform);
}
@ -418,7 +418,7 @@ Object *CollisionObject2D::shape_owner_get_owner(uint32_t p_owner) const {
return ObjectDB::get_instance(shapes[p_owner].owner_id);
}
void CollisionObject2D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2D> &p_shape) {
void CollisionObject2D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2D> &p_shape, bool p_shape_indpdt_xform) {
ERR_FAIL_COND(!shapes.has(p_owner));
ERR_FAIL_COND(p_shape.is_null());
@ -427,7 +427,7 @@ void CollisionObject2D::shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2
s.index = total_subshapes;
s.shape = p_shape;
if (area) {
PhysicsServer2D::get_singleton()->area_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled);
PhysicsServer2D::get_singleton()->area_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled, p_shape_indpdt_xform);
} else {
PhysicsServer2D::get_singleton()->body_add_shape(rid, p_shape->get_rid(), sd.xform, sd.disabled);
}
@ -610,7 +610,7 @@ void CollisionObject2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_shape_owner", "owner"), &CollisionObject2D::create_shape_owner);
ClassDB::bind_method(D_METHOD("remove_shape_owner", "owner_id"), &CollisionObject2D::remove_shape_owner);
ClassDB::bind_method(D_METHOD("get_shape_owners"), &CollisionObject2D::_get_shape_owners);
ClassDB::bind_method(D_METHOD("shape_owner_set_transform", "owner_id", "transform"), &CollisionObject2D::shape_owner_set_transform);
ClassDB::bind_method(D_METHOD("shape_owner_set_transform", "owner_id", "transform", "top_level"), &CollisionObject2D::shape_owner_set_transform, DEFVAL(false));
ClassDB::bind_method(D_METHOD("shape_owner_get_transform", "owner_id"), &CollisionObject2D::shape_owner_get_transform);
ClassDB::bind_method(D_METHOD("shape_owner_get_owner", "owner_id"), &CollisionObject2D::shape_owner_get_owner);
ClassDB::bind_method(D_METHOD("shape_owner_set_disabled", "owner_id", "disabled"), &CollisionObject2D::shape_owner_set_disabled);
@ -619,7 +619,7 @@ void CollisionObject2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_shape_owner_one_way_collision_enabled", "owner_id"), &CollisionObject2D::is_shape_owner_one_way_collision_enabled);
ClassDB::bind_method(D_METHOD("shape_owner_set_one_way_collision_margin", "owner_id", "margin"), &CollisionObject2D::shape_owner_set_one_way_collision_margin);
ClassDB::bind_method(D_METHOD("get_shape_owner_one_way_collision_margin", "owner_id"), &CollisionObject2D::get_shape_owner_one_way_collision_margin);
ClassDB::bind_method(D_METHOD("shape_owner_add_shape", "owner_id", "shape"), &CollisionObject2D::shape_owner_add_shape);
ClassDB::bind_method(D_METHOD("shape_owner_add_shape", "owner_id", "shape", "top_level"), &CollisionObject2D::shape_owner_add_shape, DEFVAL(false));
ClassDB::bind_method(D_METHOD("shape_owner_get_shape_count", "owner_id"), &CollisionObject2D::shape_owner_get_shape_count);
ClassDB::bind_method(D_METHOD("shape_owner_get_shape", "owner_id", "shape_id"), &CollisionObject2D::shape_owner_get_shape);
ClassDB::bind_method(D_METHOD("shape_owner_get_shape_index", "owner_id", "shape_id"), &CollisionObject2D::shape_owner_get_shape_index);

View File

@ -140,7 +140,7 @@ public:
void get_shape_owners(List<uint32_t> *r_owners);
PackedInt32Array _get_shape_owners();
void shape_owner_set_transform(uint32_t p_owner, const Transform2D &p_transform);
void shape_owner_set_transform(uint32_t p_owner, const Transform2D &p_transform, bool p_indpdt_xform = false);
Transform2D shape_owner_get_transform(uint32_t p_owner) const;
Object *shape_owner_get_owner(uint32_t p_owner) const;
@ -153,7 +153,7 @@ public:
void shape_owner_set_one_way_collision_margin(uint32_t p_owner, real_t p_margin);
real_t get_shape_owner_one_way_collision_margin(uint32_t p_owner) const;
void shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2D> &p_shape);
void shape_owner_add_shape(uint32_t p_owner, const Ref<Shape2D> &p_shape, bool p_shape_indpdt_xform = false);
int shape_owner_get_shape_count(uint32_t p_owner) const;
Ref<Shape2D> shape_owner_get_shape(uint32_t p_owner, int p_shape) const;
int shape_owner_get_shape_index(uint32_t p_owner, int p_shape) const;

View File

@ -40,7 +40,7 @@ void CollisionShape2D::_shape_changed() {
}
void CollisionShape2D::_update_in_shape_owner(bool p_xform_only) {
collision_object->shape_owner_set_transform(owner_id, get_transform());
collision_object->shape_owner_set_transform(owner_id, get_transform(), is_set_as_top_level());
if (p_xform_only) {
return;
}
@ -61,7 +61,7 @@ void CollisionShape2D::_notification(int p_what) {
if (collision_object) {
owner_id = collision_object->create_shape_owner(this);
if (shape.is_valid()) {
collision_object->shape_owner_add_shape(owner_id, shape);
collision_object->shape_owner_add_shape(owner_id, shape, is_set_as_top_level());
}
_update_in_shape_owner();
}
@ -77,6 +77,7 @@ void CollisionShape2D::_notification(int p_what) {
if (collision_object) {
_update_in_shape_owner(true);
}
update_configuration_warnings(); // Only needed for the top level warning
} break;
case NOTIFICATION_UNPARENTED: {
@ -186,6 +187,10 @@ PackedStringArray CollisionShape2D::get_configuration_warnings() const {
if (one_way_collision && Object::cast_to<Area2D>(col_object)) {
warnings.push_back(RTR("The One Way Collision property will be ignored when the collision object is an Area2D."));
}
if (is_set_as_top_level()) {
// If removing this, also remove update_configuration_warnings in NOTIFICATION_LOCAL_TRANSFORM_CHANGED
warnings.push_back(RTR("Top-Level on CollisionShape2D is in development and might not work as expected"));
}
Ref<ConvexPolygonShape2D> convex = shape;
Ref<ConcavePolygonShape2D> concave = shape;

View File

@ -168,9 +168,9 @@ void PhysicsServer2DExtension::_bind_methods() {
GDVIRTUAL_BIND(_area_set_space, "area", "space");
GDVIRTUAL_BIND(_area_get_space, "area");
GDVIRTUAL_BIND(_area_add_shape, "area", "shape", "transform", "disabled");
GDVIRTUAL_BIND(_area_add_shape, "area", "shape", "transform", "disabled", "top_level");
GDVIRTUAL_BIND(_area_set_shape, "area", "shape_idx", "shape");
GDVIRTUAL_BIND(_area_set_shape_transform, "area", "shape_idx", "transform");
GDVIRTUAL_BIND(_area_set_shape_transform, "area", "shape_idx", "transform", "top_level");
GDVIRTUAL_BIND(_area_set_shape_disabled, "area", "shape_idx", "disabled");
GDVIRTUAL_BIND(_area_get_shape_count, "area");

View File

@ -245,9 +245,9 @@ public:
EXBIND2(area_set_space, RID, RID)
EXBIND1RC(RID, area_get_space, RID)
EXBIND4(area_add_shape, RID, RID, const Transform2D &, bool)
EXBIND5(area_add_shape, RID, RID, const Transform2D &, bool, bool)
EXBIND3(area_set_shape, RID, int, RID)
EXBIND3(area_set_shape_transform, RID, int, const Transform2D &)
EXBIND4(area_set_shape_transform, RID, int, const Transform2D &, bool)
EXBIND3(area_set_shape_disabled, RID, int, bool)
EXBIND1RC(int, area_get_shape_count, RID)

View File

@ -381,6 +381,7 @@ TypedArray<Dictionary> PhysicsDirectSpaceState2D::_intersect_shape(const Ref<Phy
Vector<ShapeResult> sr;
sr.resize(p_max_results);
int rc = intersect_shape(p_shape_query->get_parameters(), sr.ptrw(), sr.size());
print_line("Intersect shape was called");
TypedArray<Dictionary> ret;
ret.resize(rc);
for (int i = 0; i < rc; i++) {
@ -643,9 +644,9 @@ void PhysicsServer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("area_set_space", "area", "space"), &PhysicsServer2D::area_set_space);
ClassDB::bind_method(D_METHOD("area_get_space", "area"), &PhysicsServer2D::area_get_space);
ClassDB::bind_method(D_METHOD("area_add_shape", "area", "shape", "transform", "disabled"), &PhysicsServer2D::area_add_shape, DEFVAL(Transform2D()), DEFVAL(false));
ClassDB::bind_method(D_METHOD("area_add_shape", "area", "shape", "transform", "disabled", "top_level"), &PhysicsServer2D::area_add_shape, DEFVAL(Transform2D()), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("area_set_shape", "area", "shape_idx", "shape"), &PhysicsServer2D::area_set_shape);
ClassDB::bind_method(D_METHOD("area_set_shape_transform", "area", "shape_idx", "transform"), &PhysicsServer2D::area_set_shape_transform);
ClassDB::bind_method(D_METHOD("area_set_shape_transform", "area", "shape_idx", "transform", "top_level"), &PhysicsServer2D::area_set_shape_transform, DEFVAL(false));
ClassDB::bind_method(D_METHOD("area_set_shape_disabled", "area", "shape_idx", "disabled"), &PhysicsServer2D::area_set_shape_disabled);
ClassDB::bind_method(D_METHOD("area_get_shape_count", "area"), &PhysicsServer2D::area_get_shape_count);

View File

@ -308,9 +308,9 @@ public:
AREA_SPACE_OVERRIDE_REPLACE_COMBINE // Discards all previous calculations, then keeps combining
};
virtual void area_add_shape(RID p_area, RID p_shape, const Transform2D &p_transform = Transform2D(), bool p_disabled = false) = 0;
virtual void area_add_shape(RID p_area, RID p_shape, const Transform2D &p_transform = Transform2D(), bool p_disabled = false, bool p_indpdt_xform = false) = 0;
virtual void area_set_shape(RID p_area, int p_shape_idx, RID p_shape) = 0;
virtual void area_set_shape_transform(RID p_area, int p_shape_idx, const Transform2D &p_transform) = 0;
virtual void area_set_shape_transform(RID p_area, int p_shape_idx, const Transform2D &p_transform, bool p_indpdt_xform) = 0;
virtual int area_get_shape_count(RID p_area) const = 0;
virtual RID area_get_shape(RID p_area, int p_shape_idx) const = 0;

View File

@ -166,9 +166,9 @@ public:
virtual void area_set_space(RID p_area, RID p_space) override {}
virtual RID area_get_space(RID p_area) const override { return RID(); }
virtual void area_add_shape(RID p_area, RID p_shape, const Transform2D &p_transform = Transform2D(), bool p_disabled = false) override {}
virtual void area_add_shape(RID p_area, RID p_shape, const Transform2D &p_transform = Transform2D(), bool p_disabled = false, bool p_indpdt_xform = false) override {}
virtual void area_set_shape(RID p_area, int p_shape_idx, RID p_shape) override {}
virtual void area_set_shape_transform(RID p_area, int p_shape_idx, const Transform2D &p_transform) override {}
virtual void area_set_shape_transform(RID p_area, int p_shape_idx, const Transform2D &p_transform, bool p_indpdt_xform) override {}
virtual int area_get_shape_count(RID p_area) const override { return 0; }
virtual RID area_get_shape(RID p_area, int p_shape_idx) const override { return RID(); }

View File

@ -131,9 +131,9 @@ public:
FUNC2(area_set_space, RID, RID);
FUNC1RC(RID, area_get_space, RID);
FUNC4(area_add_shape, RID, RID, const Transform2D &, bool);
FUNC5(area_add_shape, RID, RID, const Transform2D &, bool, bool);
FUNC3(area_set_shape, RID, int, RID);
FUNC3(area_set_shape_transform, RID, int, const Transform2D &);
FUNC4(area_set_shape_transform, RID, int, const Transform2D &, bool);
FUNC3(area_set_shape_disabled, RID, int, bool);
FUNC1RC(int, area_get_shape_count, RID);