├── .gdignore ├── bin ├── linux │ └── .gitkeep ├── macos │ ├── .gitkeep │ └── macos.framework │ │ └── Resources │ │ └── Info.plist ├── android │ └── .gitkeep ├── windows │ └── .gitkeep └── ios │ └── ios.framework │ └── Info.plist ├── demo ├── bin │ ├── linux │ │ └── .gitkeep │ ├── macos │ │ └── .gitkeep │ ├── android │ │ └── .gitkeep │ ├── windows │ │ └── .gitkeep │ └── godot-box2c.gdextension ├── sprites │ ├── circle.png │ ├── L_Tetromino.webp │ ├── circle.png.import │ ├── L_Tetromino.webp.import │ ├── icon.svg.import │ ├── robot.svg.import │ ├── icon.svg │ └── robot.svg ├── scripts │ ├── move_down_area.gd │ ├── move_down_static_body.gd │ ├── override_timescale.gd │ ├── settings_panel_finder.gd │ ├── character_test_motion.gd │ ├── contact_drawer.gd │ ├── body_destroyer.gd │ ├── grid_spawner.gd │ ├── default_character_2d.gd │ ├── camera_navigation.gd │ ├── rain_spawner.gd │ ├── body_animator.gd │ ├── scene_switcher.gd │ ├── area_print_events.gd │ ├── rain_spawner_optimized.gd │ └── query_tester.gd ├── addons │ └── debug_menu │ │ ├── plugin.cfg │ │ ├── plugin.gd │ │ └── LICENSE.md ├── scenes │ ├── NavigationCamera.tscn │ ├── BoxBody.tscn │ ├── robot.tscn │ ├── tetromino.tscn │ └── Menu.tscn ├── Area Overrides.tscn ├── Contact Monitor.tscn ├── Churn (Optimized).tscn ├── ChuAC36.tmp ├── Churn (Nodes).tscn ├── project.godot ├── Joints.tscn ├── Body Properties.tscn ├── Character.tscn ├── Areas.tscn └── Queries.tscn ├── logo.png ├── .gitattributes ├── src ├── register_types.h ├── joints │ ├── box2d_groove_joint_2d.h │ ├── box2d_damped_spring_joint_2d.h │ ├── box2d_joint_2d.cpp │ ├── box2d_pin_joint_2d.h │ ├── box2d_joint_2d.h │ ├── box2d_groove_joint_2d.cpp │ ├── box2d_pin_joint_2d.cpp │ └── box2d_damped_spring_joint_2d.cpp ├── shapes │ ├── box2d_concave_polygon_shape_2d.h │ ├── box2d_circle_shape_2d.h │ ├── box2d_capsule_shape_2d.h │ ├── box2d_rectangle_shape_2d.h │ ├── box2d_convex_polygon_shape_2d.h │ ├── box2d_separation_ray_shape_2d.h │ ├── box2d_separation_ray_shape_2d.cpp │ ├── box2d_segment_shape_2d.h │ ├── box2d_shape_2d.cpp │ ├── box2d_shape_2d.h │ ├── box2d_circle_shape_2d.cpp │ ├── box2d_segment_shape_2d.cpp │ ├── box2d_rectangle_shape_2d.cpp │ ├── box2d_capsule_shape_2d.cpp │ ├── box2d_concave_polygon_shape_2d.cpp │ ├── box2d_shape_instance.cpp │ ├── box2d_shape_instance.h │ └── box2d_convex_polygon_shape_2d.cpp ├── box2d_project_settings.h ├── register_types.cpp ├── spaces │ ├── box2d_physics_direct_space_state_2d.h │ ├── box2d_query.cpp │ ├── box2d_space_2d.h │ └── box2d_query.h ├── bodies │ ├── box2d_direct_body_state_2d.h │ ├── box2d_collision_object_2d.h │ ├── box2d_area_2d.h │ └── box2d_direct_body_state_2d.cpp ├── box2d_globals.cpp └── box2d_project_settings.cpp ├── .vscode └── extensions.json ├── .gitmodules ├── .editorconfig ├── .gitignore ├── LICENSE.md ├── README.md ├── methods.py ├── SConstruct ├── .github ├── workflows │ └── builds.yml └── actions │ ├── build │ └── action.yml │ └── sign │ └── action.yml ├── docs └── settings.md └── .clang-format /.gdignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/linux/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/macos/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/android/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bin/windows/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/bin/linux/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/bin/macos/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/bin/android/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/bin/windows/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pizzaandy/godot-box2d-v3/HEAD/logo.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /demo/sprites/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pizzaandy/godot-box2d-v3/HEAD/demo/sprites/circle.png -------------------------------------------------------------------------------- /demo/sprites/L_Tetromino.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pizzaandy/godot-box2d-v3/HEAD/demo/sprites/L_Tetromino.webp -------------------------------------------------------------------------------- /src/register_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void initialize_gdextension_types(); 4 | void uninitialize_gdextension_types(); -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.cpptools-extension-pack", 4 | "ms-python.python" 5 | ] 6 | } -------------------------------------------------------------------------------- /demo/scripts/move_down_area.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | 4 | func _physics_process(delta: float) -> void: 5 | global_position += Vector2.DOWN * 250 * delta 6 | -------------------------------------------------------------------------------- /demo/scripts/move_down_static_body.gd: -------------------------------------------------------------------------------- 1 | extends StaticBody2D 2 | 3 | 4 | func _physics_process(delta: float) -> void: 5 | global_position += Vector2.DOWN * 250 * delta 6 | -------------------------------------------------------------------------------- /demo/addons/debug_menu/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Debug Menu" 4 | description="In-game debug menu displaying performance metrics and hardware information" 5 | author="Calinou" 6 | version="1.2.0" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /demo/scripts/override_timescale.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var time_scale: float = 1.0 4 | 5 | 6 | func _enter_tree() -> void: 7 | Engine.time_scale = time_scale 8 | 9 | 10 | func _exit_tree() -> void: 11 | Engine.time_scale = 1.0 12 | -------------------------------------------------------------------------------- /demo/scenes/NavigationCamera.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://c36kg7gwvknh4"] 2 | 3 | [ext_resource type="Script" path="res://scripts/camera_navigation.gd" id="1_g3o8p"] 4 | 5 | [node name="Camera2D" type="Camera2D"] 6 | position = Vector2(1503, 923) 7 | zoom = Vector2(0.2, 0.2) 8 | script = ExtResource("1_g3o8p") 9 | -------------------------------------------------------------------------------- /demo/scripts/settings_panel_finder.gd: -------------------------------------------------------------------------------- 1 | extends MarginContainer 2 | 3 | class_name SettingsPanelFinder 4 | 5 | func find_settings() -> void: 6 | for child in get_children(): 7 | child.free() 8 | var settings = get_tree().root.find_child("_Settings", true, false) 9 | if settings: 10 | settings.reparent(self, false) 11 | -------------------------------------------------------------------------------- /demo/scenes/BoxBody.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://dcxotvc8jebfx"] 2 | 3 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_dv7kh"] 4 | size = Vector2(52, 52) 5 | 6 | [node name="BoxBody" type="RigidBody2D"] 7 | 8 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 9 | shape = SubResource("RectangleShape2D_dv7kh") 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "godot-cpp"] 2 | path = godot-cpp 3 | url = https://github.com/godotengine/godot-cpp.git 4 | branch = 4.3 5 | [submodule "box2d"] 6 | path = thirdparty/box2d 7 | url = https://github.com/erincatto/box2d 8 | ignore = dirty 9 | [submodule "tracy"] 10 | path = thirdparty/tracy 11 | url = https://github.com/wolfpld/tracy 12 | ignore = dirty 13 | -------------------------------------------------------------------------------- /src/joints/box2d_groove_joint_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "box2d_joint_2d.h" 4 | 5 | class Box2DGrooveJoint2D : public Box2DJoint2D { 6 | public: 7 | Box2DGrooveJoint2D(const Vector2 &p_a_groove1, const Vector2 &p_a_groove2, const Vector2 &p_b_anchor, Box2DBody2D *p_body_a, Box2DBody2D *p_body_b); 8 | 9 | private: 10 | b2WheelJointDef wheel_def = b2DefaultWheelJointDef(); 11 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.gradle,AndroidManifest.xml}] 11 | indent_style = space 12 | indent_size = 4 13 | 14 | [{*.py,SConstruct,SCsub}] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | # YAML requires indentation with spaces instead of tabs. 19 | [*.{yml,yaml}] 20 | indent_style = space 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /demo/scenes/robot.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://cunci2twkihny"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://bbnlau37ph2py" path="res://sprites/robot.svg" id="1_w75v5"] 4 | 5 | [node name="Robot" type="RigidBody2D"] 6 | 7 | [node name="Sprite2D" type="Sprite2D" parent="."] 8 | scale = Vector2(0.123058, 0.123058) 9 | texture = ExtResource("1_w75v5") 10 | 11 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="."] 12 | polygon = PackedVector2Array(-50, -10, -50, 35, -27, 52, 23, 52, 49, 37, 49, -9, 57, -21, 48, -34, 37, -28, 26, -35, 26, -49, 12, -54, 6, -43, -6, -43, -12, -54, -27, -48, -26, -37, -37, -28, -47, -34, -57, -21) 13 | -------------------------------------------------------------------------------- /demo/scripts/character_test_motion.gd: -------------------------------------------------------------------------------- 1 | extends RigidBody2D 2 | 3 | func _physics_process(delta: float) -> void: 4 | var input_vec = Input.get_vector("move_left", "move_right", "move_up", "move_down") 5 | 6 | global_position += 300 * input_vec * delta 7 | 8 | if Input.is_action_just_pressed("ui_accept"): 9 | var params = PhysicsTestMotionParameters2D.new() 10 | params.margin = 0 11 | params.from = global_transform 12 | params.recovery_as_collision = true 13 | #params.motion = input_vec * 500 * delta 14 | var result := PhysicsTestMotionResult2D.new() 15 | PhysicsServer2D.body_test_motion(get_rid(), params, result) 16 | global_position += result.get_travel() 17 | -------------------------------------------------------------------------------- /demo/scripts/contact_drawer.gd: -------------------------------------------------------------------------------- 1 | extends RigidBody2D 2 | 3 | var contacts: Array[Vector2] = [] 4 | 5 | func _ready() -> void: 6 | max_contacts_reported = 8 7 | 8 | 9 | func _integrate_forces(state: PhysicsDirectBodyState2D) -> void: 10 | contacts.clear() 11 | 12 | for i in state.get_contact_count(): 13 | contacts.push_back(state.get_contact_local_position(i)) 14 | 15 | 16 | func _process(delta: float) -> void: 17 | queue_redraw() 18 | 19 | 20 | func _draw() -> void: 21 | draw_set_transform_matrix(global_transform.affine_inverse()) 22 | var zoom_scale = get_viewport_transform().affine_inverse().get_scale().x 23 | 24 | for contact in contacts: 25 | draw_circle(contact, zoom_scale * 4.0, Color.RED) 26 | -------------------------------------------------------------------------------- /src/shapes/box2d_concave_polygon_shape_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "box2d_shape_2d.h" 5 | 6 | class Box2DConcavePolygonShape2D : public Box2DShape2D { 7 | public: 8 | void add_to_body(Box2DShapeInstance *p_instance) const override; 9 | 10 | // Shape-casting segments is not supported 11 | int cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override { return 0; } 12 | int overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 13 | 14 | PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::ShapeType::SHAPE_CONCAVE_POLYGON; } 15 | }; -------------------------------------------------------------------------------- /src/shapes/box2d_circle_shape_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "box2d_shape_2d.h" 5 | 6 | class Box2DCircleShape2D : public Box2DShape2D { 7 | public: 8 | void add_to_body(Box2DShapeInstance *p_instance) const override; 9 | 10 | int cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 11 | int overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 12 | 13 | PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::ShapeType::SHAPE_CIRCLE; } 14 | 15 | static bool make_circle(const Transform2D &p_transform, const Variant &p_data, b2Circle &p_circle); 16 | }; -------------------------------------------------------------------------------- /src/shapes/box2d_capsule_shape_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "box2d_shape_2d.h" 5 | 6 | class Box2DCapsuleShape2D : public Box2DShape2D { 7 | public: 8 | void add_to_body(Box2DShapeInstance *p_instance) const override; 9 | 10 | int cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 11 | int overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 12 | 13 | PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::ShapeType::SHAPE_CAPSULE; } 14 | 15 | static bool make_capsule(const Transform2D &p_transform, const Variant &p_data, b2Capsule &p_capsule); 16 | }; -------------------------------------------------------------------------------- /src/shapes/box2d_rectangle_shape_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "box2d_shape_2d.h" 5 | 6 | class Box2DRectangleShape2D : public Box2DShape2D { 7 | public: 8 | void add_to_body(Box2DShapeInstance *p_instance) const override; 9 | 10 | int cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 11 | int overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 12 | 13 | PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::ShapeType::SHAPE_RECTANGLE; } 14 | 15 | static bool make_rectangle(const Transform2D &p_transform, const Variant &p_data, b2Polygon &p_box); 16 | }; -------------------------------------------------------------------------------- /src/shapes/box2d_convex_polygon_shape_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "box2d_shape_2d.h" 5 | 6 | class Box2DConvexPolygonShape2D : public Box2DShape2D { 7 | public: 8 | void add_to_body(Box2DShapeInstance *p_instance) const override; 9 | 10 | int cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 11 | int overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 12 | 13 | PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::ShapeType::SHAPE_CONVEX_POLYGON; } 14 | 15 | static bool make_polygon(const Transform2D &p_transform, const Variant &p_data, b2Polygon &p_polygon); 16 | }; -------------------------------------------------------------------------------- /demo/scripts/body_destroyer.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | 4 | func _input(event): 5 | var ev = event as InputEventMouseButton 6 | if ev and ev.pressed and ev.button_index == MOUSE_BUTTON_LEFT: 7 | var space_state = get_world_2d().direct_space_state 8 | var query = PhysicsPointQueryParameters2D.new() 9 | query.position = get_global_mouse_position() 10 | query.collide_with_bodies = true 11 | query.collide_with_areas = true 12 | 13 | var results = space_state.intersect_point(query) 14 | if results.size() == 0: 15 | return 16 | 17 | var rigidbodies = results.filter(func(result): return result.collider is RigidBody2D) 18 | if rigidbodies.size() > 0: 19 | var rid = rigidbodies[0].collider.get_rid() 20 | PhysicsServer2D.body_remove_shape(rid, rigidbodies[0].shape) 21 | -------------------------------------------------------------------------------- /src/shapes/box2d_separation_ray_shape_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "box2d_shape_2d.h" 5 | 6 | class Box2DSeparationRayShape2D : public Box2DShape2D { 7 | public: 8 | void add_to_body(Box2DShapeInstance *p_instance) const override; 9 | 10 | int cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override { return 0; } 11 | int overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override { return 0; } 12 | 13 | PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::ShapeType::SHAPE_SEGMENT; } 14 | 15 | static bool make_separation_ray(const Transform2D &p_transform, const Variant &p_data, b2Segment &p_segment); 16 | }; -------------------------------------------------------------------------------- /src/joints/box2d_damped_spring_joint_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "box2d_joint_2d.h" 4 | 5 | class Box2DDampedSpringJoint2D : public Box2DJoint2D { 6 | public: 7 | Box2DDampedSpringJoint2D(const Vector2 &p_anchor_a, const Vector2 &p_anchor_b, Box2DBody2D *p_body_a, Box2DBody2D *p_body_b); 8 | 9 | void set_rest_length(float p_length); 10 | float get_rest_length() const { return distance_def.length; } 11 | 12 | void set_damping_ratio(float p_damping); 13 | float get_damping_ratio() const { return distance_def.dampingRatio; } 14 | 15 | void set_stiffness(float p_stiffness); 16 | float get_stiffness() const { return stiffness; } 17 | 18 | void update_stiffness(); 19 | 20 | private: 21 | b2DistanceJointDef distance_def = b2DefaultDistanceJointDef(); 22 | float stiffness = 20.0; 23 | }; -------------------------------------------------------------------------------- /src/joints/box2d_joint_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_joint_2d.h" 2 | 3 | Box2DJoint2D::~Box2DJoint2D() { 4 | destroy_joint(); 5 | } 6 | 7 | void Box2DJoint2D::copy_settings_from(Box2DJoint2D *p_joint) { 8 | set_rid(p_joint->get_rid()); 9 | set_max_force(p_joint->get_max_force()); 10 | set_bias(p_joint->get_bias()); 11 | set_max_bias(p_joint->get_max_bias()); 12 | } 13 | 14 | void Box2DJoint2D::destroy_joint() { 15 | if (b2Joint_IsValid(joint_id)) { 16 | b2DestroyJoint(joint_id); 17 | } 18 | joint_id = b2_nullJointId; 19 | } 20 | 21 | void Box2DJoint2D::disable_collisions_between_bodies(bool p_disabled) { 22 | disabled_collisions_between_bodies = p_disabled; 23 | 24 | if (b2Joint_IsValid(joint_id)) { 25 | b2Joint_SetCollideConnected(joint_id, !disabled_collisions_between_bodies); 26 | } 27 | } -------------------------------------------------------------------------------- /src/box2d_project_settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using namespace godot; 6 | 7 | enum Box2DMixingRule : int32_t { 8 | MIXING_RULE_GODOT, 9 | MIXING_RULE_BOX2D, 10 | MIXING_RULE_MAX, 11 | }; 12 | 13 | class Box2DProjectSettings { 14 | public: 15 | static void register_settings(); 16 | 17 | static int32_t get_max_threads(); 18 | 19 | static int get_pixels_per_meter(); 20 | 21 | static int get_substeps(); 22 | 23 | static float get_contact_hertz(); 24 | 25 | static float get_contact_damping_ratio(); 26 | 27 | static float get_joint_hertz(); 28 | 29 | static float get_joint_damping_ratio(); 30 | 31 | static Box2DMixingRule get_friction_mixing_rule(); 32 | 33 | static Box2DMixingRule get_restitution_mixing_rule(); 34 | 35 | static bool get_presolve_enabled(); 36 | }; -------------------------------------------------------------------------------- /src/shapes/box2d_separation_ray_shape_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_separation_ray_shape_2d.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | 4 | void Box2DSeparationRayShape2D::add_to_body(Box2DShapeInstance *p_instance) const { 5 | // This shape only exists in character movement queries 6 | } 7 | 8 | bool Box2DSeparationRayShape2D::make_separation_ray(const Transform2D &p_transform, const Variant &p_data, b2Segment &p_segment) { 9 | Variant::Type type = p_data.get_type(); 10 | ERR_FAIL_COND_V(type != Variant::DICTIONARY, false); 11 | 12 | Dictionary dict = p_data; 13 | 14 | Rect2 rect; 15 | rect.position = Vector2(); 16 | rect.size = Vector2(0, 1) * static_cast(dict["length"]); 17 | 18 | p_segment.point1 = to_box2d(p_transform.xform(rect.position)); 19 | p_segment.point2 = to_box2d(p_transform.xform(rect.size)); 20 | 21 | return true; 22 | } -------------------------------------------------------------------------------- /demo/scripts/grid_spawner.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node2D 3 | 4 | @export var rows: int = 10 5 | @export var columns: int = 10 6 | @export var cell_length: float = 20 7 | @export var body: PackedScene 8 | 9 | func _ready() -> void: 10 | if Engine.is_editor_hint(): 11 | return 12 | 13 | if body == null: 14 | return 15 | 16 | for i in rows: 17 | for j in columns: 18 | var inst = body.instantiate() 19 | get_parent().add_child.call_deferred(inst) 20 | inst.global_position = global_position + Vector2(i * cell_length, j * cell_length) 21 | 22 | 23 | func _process(delta: float) -> void: 24 | queue_redraw() 25 | 26 | 27 | func _draw() -> void: 28 | if not Engine.is_editor_hint(): 29 | return 30 | for i in rows: 31 | for j in columns: 32 | var pos = global_position + Vector2(i * cell_length, j * cell_length) 33 | draw_circle(to_local(pos), 5, Color.BLACK) 34 | -------------------------------------------------------------------------------- /demo/scenes/tetromino.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://bte10iydevrpw"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://bxcx763jjxxox" path="res://sprites/L_Tetromino.webp" id="1_o27cr"] 4 | 5 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_w6kpp"] 6 | size = Vector2(97.5, 287.5) 7 | 8 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_0n21b"] 9 | size = Vector2(190.5, 97) 10 | 11 | [node name="Tetromino" type="RigidBody2D"] 12 | 13 | [node name="Sprite2D" type="Sprite2D" parent="."] 14 | texture = ExtResource("1_o27cr") 15 | 16 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 17 | position = Vector2(-48.75, 1.25) 18 | shape = SubResource("RectangleShape2D_w6kpp") 19 | 20 | [node name="CollisionShape2D2" type="CollisionShape2D" parent="."] 21 | position = Vector2(-2.25, 96.5) 22 | shape = SubResource("RectangleShape2D_0n21b") 23 | -------------------------------------------------------------------------------- /src/shapes/box2d_segment_shape_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "box2d_shape_2d.h" 5 | 6 | class Box2DSegmentShape2D : public Box2DShape2D { 7 | public: 8 | void add_to_body(Box2DShapeInstance *p_instance) const override; 9 | 10 | // Shape-casting segments is not supported 11 | int cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override { return 0; } 12 | int overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const override; 13 | 14 | PhysicsServer2D::ShapeType get_type() const override { return PhysicsServer2D::ShapeType::SHAPE_SEGMENT; } 15 | 16 | static bool make_segment(const Transform2D &p_transform, const Variant &p_data, b2Segment &p_segment); 17 | static bool make_capsule_segment(const Transform2D &p_transform, const Variant &p_data, b2Capsule &p_capsule); 18 | }; -------------------------------------------------------------------------------- /demo/sprites/circle.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bkyhaupky0h52" 6 | path="res://.godot/imported/circle.png-44b524053929f94dd1566f087585a590.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://sprites/circle.png" 14 | dest_files=["res://.godot/imported/circle.png-44b524053929f94dd1566f087585a590.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /src/joints/box2d_pin_joint_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "box2d_joint_2d.h" 4 | 5 | class Box2DPinJoint2D : public Box2DJoint2D { 6 | public: 7 | Box2DPinJoint2D(const Vector2 &p_pos, Box2DBody2D *p_body_a, Box2DBody2D *p_body_b); 8 | 9 | void set_upper_limit(float p_limit); 10 | float get_upper_limit() const { return revolute_def.upperAngle; } 11 | 12 | void set_lower_limit(float p_limit); 13 | float get_lower_limit() const { return revolute_def.lowerAngle; } 14 | 15 | void set_motor_speed(float p_speed); 16 | float get_motor_speed() const { return revolute_def.motorSpeed; } 17 | 18 | void set_motor_enabled(bool p_enabled); 19 | bool get_motor_enabled() const { return revolute_def.enableMotor; } 20 | 21 | void set_limit_enabled(bool p_enabled); 22 | bool get_limit_enabled() const { return revolute_def.enableLimit; } 23 | 24 | private: 25 | b2RevoluteJointDef revolute_def = b2DefaultRevoluteJointDef(); 26 | }; 27 | -------------------------------------------------------------------------------- /demo/sprites/L_Tetromino.webp.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bxcx763jjxxox" 6 | path="res://.godot/imported/L_Tetromino.webp-7d275268c752c167294028057ff464fa.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://sprites/L_Tetromino.webp" 14 | dest_files=["res://.godot/imported/L_Tetromino.webp-7d275268c752c167294028057ff464fa.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot auto generated files 2 | *.gen.* 3 | .godot/ 4 | 5 | # Ignore library files but not the gdextension file 6 | demo/bin/* 7 | !demo/bin/android 8 | demo/bin/android/* 9 | !demo/bin/android/.gitkeep 10 | !demo/bin/linux 11 | demo/bin/linux/* 12 | !demo/bin/linux/.gitkeep 13 | !demo/bin/macos 14 | demo/bin/macos/* 15 | !demo/bin/macos/.gitkeep 16 | !demo/bin/windows 17 | demo/bin/windows/* 18 | !demo/bin/windows/.gitkeep 19 | !demo/bin/*.gdextension 20 | .sconsign*.dblite 21 | 22 | # Ignore custom.py 23 | custom.py 24 | 25 | # Ignore generated compile_commands.json 26 | compile_commands.json 27 | 28 | # Ignore files generated for documentation 29 | /src/gen 30 | 31 | # Binaries 32 | *.o 33 | *.os 34 | *.so 35 | *.obj 36 | *.bc 37 | *.pyc 38 | *.dblite 39 | *.pdb 40 | *.lib 41 | *.config 42 | *.creator 43 | *.creator.user 44 | *.files 45 | *.includes 46 | *.idb 47 | *.exp 48 | *.dll 49 | 50 | # Other stuff 51 | *.log 52 | 53 | # VSCode 54 | .vscode/* 55 | !.vscode/extensions.json 56 | .DS_Store 57 | 58 | .vs/* 59 | -------------------------------------------------------------------------------- /demo/sprites/icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dbx66sovxd1" 6 | path="res://.godot/imported/icon.svg-964d8c66fbd3bda40c44ca195472a8e1.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://sprites/icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-964d8c66fbd3bda40c44ca195472a8e1.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /demo/sprites/robot.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bbnlau37ph2py" 6 | path="res://.godot/imported/robot.svg-4633733700ea1e158a911d6f87a766ef.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://sprites/robot.svg" 14 | dest_files=["res://.godot/imported/robot.svg-4633733700ea1e158a911d6f87a766ef.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /demo/addons/debug_menu/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | func _enter_tree() -> void: 5 | add_autoload_singleton("DebugMenu", "res://addons/debug_menu/debug_menu.tscn") 6 | 7 | # FIXME: This appears to do nothing. 8 | # if not ProjectSettings.has_setting("application/config/version"): 9 | # ProjectSettings.set_setting("application/config/version", "1.0.0") 10 | # 11 | # ProjectSettings.set_initial_value("application/config/version", "1.0.0") 12 | # ProjectSettings.add_property_info({ 13 | # name = "application/config/version", 14 | # type = TYPE_STRING, 15 | # }) 16 | # 17 | # if not InputMap.has_action("cycle_debug_menu"): 18 | # InputMap.add_action("cycle_debug_menu") 19 | # var event := InputEventKey.new() 20 | # event.keycode = KEY_F3 21 | # InputMap.action_add_event("cycle_debug_menu", event) 22 | # 23 | # ProjectSettings.save() 24 | 25 | 26 | func _exit_tree() -> void: 27 | remove_autoload_singleton("DebugMenu") 28 | # Don't remove the project setting's value and input map action, 29 | # as the plugin may be re-enabled in the future. 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024-present Andrew Song 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /bin/ios/ios.framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleInfoDictionaryVersion 6 | 6.0 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | libEXTENSION-NAME.macos.template_release 11 | CFBundleName 12 | Godot Template Cpp 13 | CFBundleDisplayName 14 | Godot Template Cpp 15 | CFBundleIdentifier 16 | org.godot.godot-template-cpp 17 | NSHumanReadableCopyright 18 | Unlicensed 19 | CFBundleVersion 20 | 1.0.0 21 | CFBundleShortVersionString 22 | 1.0.0 23 | CFBundlePackageType 24 | FMWK 25 | CSResourcesFileMapped 26 | 27 | DTPlatformName 28 | iphoneos 29 | MinimumOSVersion 30 | 12.0 31 | 32 | 33 | -------------------------------------------------------------------------------- /bin/macos/macos.framework/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleInfoDictionaryVersion 6 | 6.0 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | libEXTENSION-NAME.macos.template_release 11 | CFBundleName 12 | Godot Cpp Template 13 | CFBundleDisplayName 14 | Godot Cpp Template 15 | CFBundleIdentifier 16 | org.godot.godot-template-cpp 17 | NSHumanReadableCopyright 18 | Unlicensed 19 | CFBundleVersion 20 | 1.0.0 21 | CFBundleShortVersionString 22 | 1.0.0 23 | CFBundlePackageType 24 | FMWK 25 | CSResourcesFileMapped 26 | 27 | DTPlatformName 28 | macosx 29 | LSMinimumSystemVersion 30 | 10.12 31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/scripts/default_character_2d.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | 3 | 4 | const SPEED = 300.0 5 | const JUMP_VELOCITY = -400.0 6 | 7 | 8 | func _physics_process(delta: float) -> void: 9 | # Add the gravity. 10 | if not is_on_floor(): 11 | velocity += get_gravity() * delta 12 | 13 | # Handle jump. 14 | if Input.is_action_just_pressed("ui_accept") and is_on_floor(): 15 | velocity.y = JUMP_VELOCITY 16 | 17 | # Get the input direction and handle the movement/deceleration. 18 | # As good practice, you should replace UI actions with custom gameplay actions. 19 | var direction := Input.get_axis("ui_left", "ui_right") 20 | if direction: 21 | velocity.x = direction * SPEED 22 | else: 23 | velocity.x = move_toward(velocity.x, 0, SPEED) 24 | 25 | move_and_slide() 26 | queue_redraw() 27 | 28 | func _draw() -> void: 29 | draw_set_transform_matrix(global_transform.affine_inverse()) 30 | 31 | var last_collision = get_last_slide_collision() 32 | if not last_collision: 33 | return 34 | var point := last_collision.get_position() 35 | var normal := last_collision.get_normal() 36 | draw_circle(point, 3.0, Color.BLACK) 37 | draw_line(point, point + normal * 25, Color.BLACK, 3) 38 | -------------------------------------------------------------------------------- /demo/addons/debug_menu/LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright © 2023-present Hugo Locurcio and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/scripts/camera_navigation.gd: -------------------------------------------------------------------------------- 1 | extends Camera2D 2 | 3 | @export_range(1, 20, 0.01) var maxZoom : float = 10.0 4 | @export_range(0.01, 1, 0.01) var minZoom : float = 0.1 5 | @export_range(0.01, 0.2, 0.01) var zoomStepRatio : float = 0.1 6 | 7 | @onready var zoom_scale: float = clamp(zoom.x, minZoom, maxZoom) 8 | 9 | func _input(event: InputEvent) -> void: 10 | if Input.is_mouse_button_pressed(MOUSE_BUTTON_MIDDLE) or Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT): 11 | var motion = event as InputEventMouseMotion 12 | if motion != null: 13 | global_position -= motion.relative / zoom_scale 14 | 15 | var prev_zoom = zoom_scale 16 | var scroll = event as InputEventMouseButton 17 | if scroll != null: 18 | if scroll.button_index == MOUSE_BUTTON_WHEEL_UP: 19 | zoom_scale += zoom_scale * zoomStepRatio 20 | elif scroll.button_index == MOUSE_BUTTON_WHEEL_DOWN: 21 | zoom_scale -= zoom_scale * zoomStepRatio 22 | else: 23 | return 24 | else: 25 | return 26 | 27 | zoom_scale = clamp(zoom_scale, minZoom, maxZoom) 28 | 29 | var zoom_ratio = prev_zoom / zoom_scale 30 | global_position -= (get_global_mouse_position() - global_position) * (zoom_ratio - 1) 31 | 32 | zoom = Vector2.ONE * zoom_scale 33 | -------------------------------------------------------------------------------- /src/shapes/box2d_shape_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_shape_2d.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | 4 | Box2DShape2D::~Box2DShape2D() { 5 | remove_from_all_objects(); 6 | } 7 | 8 | void Box2DShape2D::remove_from_body(Box2DShapeInstance *p_instance) const { 9 | for (b2ShapeId shape_id : p_instance->get_shape_ids()) { 10 | if (b2Shape_IsValid(shape_id)) { 11 | b2DestroyShape(shape_id, false); 12 | } 13 | } 14 | p_instance->clear_shape_ids(); 15 | } 16 | 17 | void Box2DShape2D::set_data(const Variant &p_data) { 18 | data = p_data; 19 | update_instances(); 20 | } 21 | 22 | void Box2DShape2D::update_instances() { 23 | for (const auto &[owner, ref_count] : ref_counts_by_object) { 24 | owner->shape_updated(this); 25 | } 26 | } 27 | 28 | void Box2DShape2D::add_object_reference(Box2DCollisionObject2D *p_owner) { 29 | ref_counts_by_object[p_owner]++; 30 | } 31 | 32 | void Box2DShape2D::remove_object_reference(Box2DCollisionObject2D *p_owner) { 33 | if (--ref_counts_by_object[p_owner] <= 0) { 34 | ref_counts_by_object.erase(p_owner); 35 | } 36 | } 37 | 38 | void Box2DShape2D::remove_from_all_objects() { 39 | const auto ref_counts_by_owner_copy = ref_counts_by_object; 40 | for (const auto &[owner, ref_count] : ref_counts_by_owner_copy) { 41 | owner->remove_shape(this); 42 | } 43 | } -------------------------------------------------------------------------------- /demo/scripts/rain_spawner.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node2D 3 | 4 | @export var enabled = true 5 | @export var max_bodies = 5000 6 | @export var spawns_per_second = 1 7 | @export var body: PackedScene 8 | 9 | @export var spawn_range: Vector2 = Vector2(-400, 400) 10 | @export var scale_range: Vector2 = Vector2(0.5, 1.5) 11 | 12 | var count = 0 13 | var timer = 0 14 | 15 | func _physics_process(delta: float) -> void: 16 | if Engine.is_editor_hint(): 17 | return 18 | 19 | if count >= max_bodies or not enabled: 20 | return 21 | 22 | timer += delta 23 | var interval = 1.0 / spawns_per_second 24 | while timer >= interval: 25 | timer -= interval 26 | var pos = global_position + Vector2.RIGHT * randf_range(spawn_range.x, spawn_range.y) 27 | spawn_object(pos) 28 | count += 1 29 | 30 | 31 | func spawn_object(pos: Vector2): 32 | var inst: Node2D = body.instantiate() 33 | get_parent().add_child(inst) 34 | inst.global_position = pos 35 | inst.scale = randf_range(scale_range.x, scale_range.y) * Vector2.ONE 36 | inst.reset_physics_interpolation() 37 | 38 | 39 | func _process(delta: float) -> void: 40 | queue_redraw() 41 | 42 | 43 | func _draw() -> void: 44 | draw_set_transform_matrix(global_transform.affine_inverse()) 45 | draw_line(global_position + Vector2.RIGHT * spawn_range.x, global_position + Vector2.RIGHT * spawn_range.y, Color.BLACK) 46 | -------------------------------------------------------------------------------- /demo/scripts/body_animator.gd: -------------------------------------------------------------------------------- 1 | extends AnimatableBody2D 2 | 3 | var elapsed_time = 0 4 | @export var period_seconds: float = 2 5 | @export var motion_type: MotionType = MotionType.ROTATE 6 | var original_position: Vector2 7 | var original_scale: Vector2 8 | 9 | enum MotionType { 10 | ROTATE, 11 | TRANSLATE, 12 | SCALE, 13 | } 14 | 15 | 16 | func _ready() -> void: 17 | original_position = global_position 18 | original_scale = global_scale 19 | 20 | 21 | func _physics_process(delta: float) -> void: 22 | match motion_type: 23 | MotionType.ROTATE: 24 | spin(delta) 25 | MotionType.TRANSLATE: 26 | oscillate(delta) 27 | MotionType.SCALE: 28 | pulse(delta) 29 | 30 | 31 | func oscillate(delta: float): 32 | elapsed_time += delta 33 | var t : float = elapsed_time / period_seconds 34 | var sine_value : float = sin(t * PI * 2.0) 35 | global_position = original_position.lerp( 36 | original_position + Vector2(0, 100), 37 | (sine_value + 1.0) / 2.0 38 | ) 39 | 40 | 41 | func pulse(delta: float): 42 | elapsed_time += delta 43 | var t : float = elapsed_time / period_seconds 44 | var sine_value : float = sin(t * PI * 2.0) 45 | global_scale = original_scale.lerp( 46 | original_scale + Vector2(1, 1), 47 | (sine_value + 1.0) / 2.0 48 | ) 49 | 50 | 51 | func spin(delta: float): 52 | global_rotation += (delta * PI * 2.0) / period_seconds 53 | -------------------------------------------------------------------------------- /demo/scripts/scene_switcher.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends ItemList 3 | 4 | @export var get_scene_list: bool: 5 | set(value): 6 | get_scenes() 7 | 8 | @export var scenes: PackedStringArray 9 | 10 | @onready var settings_container: SettingsPanelFinder = $"../../../Panel/SettingsContainer" 11 | 12 | var current_scene = null 13 | 14 | func _ready() -> void: 15 | if not Engine.is_editor_hint(): 16 | for scene in scenes: 17 | add_item(scene) 18 | 19 | 20 | func get_scenes() -> void: 21 | scenes.clear() 22 | var root_files = DirAccess.get_files_at("res://") 23 | for filename in root_files: 24 | if not filename.ends_with(".tscn"): 25 | continue 26 | var name = filename.get_file().trim_suffix(".tscn") 27 | scenes.push_back(name) 28 | 29 | 30 | func _on_item_selected(index: int) -> void: 31 | var scene_name := get_item_text(index) 32 | goto_scene(scene_name) 33 | 34 | 35 | func goto_scene(name: String): 36 | var path: String = "res://%s.tscn" % name.strip_edges() 37 | if not ResourceLoader.exists(path): 38 | push_error("Scene %s does not exist" % name) 39 | _deferred_goto_scene.call_deferred(path) 40 | 41 | 42 | func _deferred_goto_scene(path): 43 | if current_scene: 44 | current_scene.free() 45 | var s = ResourceLoader.load(path) 46 | current_scene = s.instantiate() 47 | get_tree().root.add_child(current_scene) 48 | get_tree().current_scene = current_scene 49 | settings_container.find_settings() 50 | -------------------------------------------------------------------------------- /demo/scenes/Menu.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://dom8tup8nlvq"] 2 | 3 | [ext_resource type="Script" path="res://scripts/scene_switcher.gd" id="1_45eq1"] 4 | [ext_resource type="Script" path="res://scripts/settings_panel_finder.gd" id="2_bwc4w"] 5 | 6 | [node name="Menu" type="CanvasLayer"] 7 | 8 | [node name="ScenePanel" type="PanelContainer" parent="."] 9 | offset_right = 200.0 10 | offset_bottom = 1080.0 11 | 12 | [node name="MarginContainer" type="MarginContainer" parent="ScenePanel"] 13 | layout_mode = 2 14 | 15 | [node name="ItemList" type="ItemList" parent="ScenePanel/MarginContainer"] 16 | custom_minimum_size = Vector2(200, 0) 17 | layout_mode = 2 18 | focus_mode = 0 19 | script = ExtResource("1_45eq1") 20 | scenes = PackedStringArray("Area Overrides", "Areas", "Body Properties", "Character", "Churn (Nodes)", "Churn (Optimized)", "Contact Monitor", "Joints", "Queries") 21 | 22 | [node name="Panel" type="PanelContainer" parent="."] 23 | anchors_preset = 1 24 | anchor_left = 1.0 25 | anchor_right = 1.0 26 | offset_left = -200.0 27 | offset_bottom = 300.0 28 | grow_horizontal = 0 29 | 30 | [node name="SettingsContainer" type="MarginContainer" parent="Panel"] 31 | layout_mode = 2 32 | script = ExtResource("2_bwc4w") 33 | 34 | [connection signal="item_clicked" from="ScenePanel/MarginContainer/ItemList" to="ScenePanel/MarginContainer/ItemList" method="_on_item_clicked"] 35 | [connection signal="item_selected" from="ScenePanel/MarginContainer/ItemList" to="ScenePanel/MarginContainer/ItemList" method="_on_item_selected"] 36 | -------------------------------------------------------------------------------- /src/shapes/box2d_shape_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "../spaces/box2d_query.h" 5 | #include "box2d_shape_instance.h" 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace godot; 11 | 12 | class Box2DShapeInstance; 13 | class Box2DCollisionObject2D; 14 | 15 | class Box2DShape2D { 16 | public: 17 | virtual ~Box2DShape2D(); 18 | 19 | /// Note: This does not validate data 20 | virtual void add_to_body(Box2DShapeInstance *p_instance) const = 0; 21 | virtual void remove_from_body(Box2DShapeInstance *p_instance) const; 22 | 23 | virtual int cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const = 0; 24 | virtual int overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const = 0; 25 | 26 | virtual PhysicsServer2D::ShapeType get_type() const = 0; 27 | 28 | void set_rid(const RID &p_rid) { rid = p_rid; } 29 | RID get_rid() const { return rid; } 30 | 31 | void set_data(const Variant &p_data); 32 | Variant get_data() const { return data; } 33 | 34 | void add_object_reference(Box2DCollisionObject2D *p_owner); 35 | void remove_object_reference(Box2DCollisionObject2D *p_owner); 36 | void remove_from_all_objects(); 37 | 38 | protected: 39 | void update_instances(); 40 | 41 | RID rid; 42 | Variant data; 43 | HashMap ref_counts_by_object; 44 | }; -------------------------------------------------------------------------------- /src/joints/box2d_joint_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../bodies/box2d_body_2d.h" 4 | 5 | class Box2DJoint2D { 6 | public: 7 | Box2DJoint2D() = default; 8 | explicit Box2DJoint2D(PhysicsServer2D::JointType p_type, Box2DBody2D *p_body_a, Box2DBody2D *p_body_b) : 9 | type(p_type), body_a(p_body_a), body_b(p_body_b) {}; 10 | 11 | virtual ~Box2DJoint2D(); 12 | 13 | PhysicsServer2D::JointType get_type() const { return type; } 14 | 15 | void copy_settings_from(Box2DJoint2D *p_joint); 16 | 17 | void destroy_joint(); 18 | 19 | void set_rid(const RID &p_rid) { rid = p_rid; } 20 | RID get_rid() const { return rid; } 21 | 22 | void disable_collisions_between_bodies(bool p_disabled); 23 | bool is_disabled_collisions_between_bodies() const { return disabled_collisions_between_bodies; } 24 | 25 | void set_max_force(float p_force) { max_force = p_force; } 26 | float get_max_force() const { return max_force; } 27 | 28 | void set_bias(float p_bias) { bias = p_bias; } 29 | float get_bias() const { return bias; } 30 | 31 | void set_max_bias(float p_bias) { max_bias = p_bias; } 32 | float get_max_bias() const { return max_bias; } 33 | 34 | protected: 35 | Box2DBody2D *body_a = nullptr; 36 | Box2DBody2D *body_b = nullptr; 37 | PhysicsServer2D::JointType type = PhysicsServer2D::JOINT_TYPE_MAX; 38 | 39 | Box2DSpace2D *space = nullptr; 40 | b2JointId joint_id = b2_nullJointId; 41 | 42 | float bias = 0; 43 | float max_bias = 3.40282e+38; 44 | float max_force = 3.40282e+38; 45 | 46 | bool disabled_collisions_between_bodies = true; 47 | RID rid; 48 | }; -------------------------------------------------------------------------------- /src/joints/box2d_groove_joint_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_groove_joint_2d.h" 2 | #include "../spaces/box2d_space_2d.h" 3 | 4 | Box2DGrooveJoint2D::Box2DGrooveJoint2D(const Vector2 &p_a_groove1, const Vector2 &p_a_groove2, const Vector2 &p_b_anchor, Box2DBody2D *p_body_a, Box2DBody2D *p_body_b) : 5 | Box2DJoint2D(PhysicsServer2D::JOINT_TYPE_GROOVE, p_body_a, p_body_b) { 6 | if (!p_body_a || !p_body_b) { 7 | return; 8 | } 9 | 10 | ERR_FAIL_COND(!p_body_a->in_space()); 11 | ERR_FAIL_COND(p_body_a->get_space() != p_body_b->get_space()); 12 | space = p_body_a->get_space(); 13 | 14 | Vector2 point_a_1 = p_body_a->get_transform().affine_inverse().xform(p_a_groove1); 15 | Vector2 point_a_2 = p_body_a->get_transform().affine_inverse().xform(p_a_groove2); 16 | 17 | Vector2 anchor_a = point_a_1; 18 | Vector2 anchor_b = p_body_b->get_transform().affine_inverse().xform(p_b_anchor); 19 | Vector2 axis = (point_a_2 - point_a_1).normalized(); 20 | 21 | wheel_def.bodyIdA = p_body_a->get_body_id(); 22 | wheel_def.bodyIdB = p_body_b->get_body_id(); 23 | wheel_def.localAnchorA = to_box2d(anchor_a); 24 | wheel_def.localAnchorB = to_box2d(anchor_b); 25 | 26 | wheel_def.enableLimit = true; 27 | wheel_def.enableSpring = false; 28 | wheel_def.upperTranslation = to_box2d(p_a_groove1.distance_to(p_a_groove2)); 29 | wheel_def.lowerTranslation = 0; 30 | wheel_def.localAxisA = b2Vec2{ (float)axis.x, (float)axis.y }; 31 | 32 | wheel_def.collideConnected = !disabled_collisions_between_bodies; 33 | wheel_def.userData = this; 34 | 35 | joint_id = b2CreateWheelJoint(space->get_world_id(), &wheel_def); 36 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | Godot Box2D v3 is a high-performance physics extension for Godot 4.3+ that implements Box2D v3. It can be considered a successor to the [Godot Box2D](https://github.com/appsinacup/godot-box2d) extension. 6 | 7 | ## Features 8 | 9 | - Improved stability and performance, especially for large piles of bodies 10 | - Faster, more accurate queries (e.g. `cast_motion`, `intersect_ray`) 11 | - Continuous collision detection (CCD) enabled by default for dynamic vs static collisions 12 | - Additional APIs to make the physics server easier and faster to use (docs coming soon) 13 | 14 | ## Unsupported Features / Limitations 15 | Most of the Physics API has been implemented, but the following features do not have parity with Godot Physics: 16 | - `WorldBoundaryShape2D` unsupported (planned for a future version of Box2D) 17 | - `SeparationRayShape2D` unsupported 18 | - Space parameters (`space_set_param`) unsupported because Box2D v3 uses a different solver 19 | - "Cast Ray" CCD mode (`CCDMode::CCD_MODE_CAST_RAY`) unsupported 20 | - Contacts are not visible when `Visible Collision Shapes` is enabled 21 | - Convex polygons cannot have more than 8 vertices (does not affect `CollisionPolygon2D`) 22 | - `constant_linear_velocity` and `constant_angular_velocity` unsupported on static bodies 23 | - `CharacterBody2D`: unstable movement on moving platforms 24 | 25 | ## Planned Features 26 | - [Cross-platform determinism](https://box2d.org/posts/2024/08/determinism/) 27 | - Serialization + Rollback 28 | -------------------------------------------------------------------------------- /src/shapes/box2d_circle_shape_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_circle_shape_2d.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | 4 | void Box2DCircleShape2D::add_to_body(Box2DShapeInstance *p_instance) const { 5 | b2Circle shape; 6 | if (!make_circle(p_instance->get_global_transform(), data, shape)) { 7 | return; 8 | } 9 | b2ShapeDef shape_def = p_instance->get_shape_def(); 10 | b2ShapeId id = b2CreateCircleShape(p_instance->get_collision_object()->get_body_id(), &shape_def, &shape); 11 | p_instance->add_shape_id(id); 12 | } 13 | 14 | int Box2DCircleShape2D::cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 15 | b2Circle shape; 16 | if (!make_circle(p_transform, data, shape)) { 17 | return 0; 18 | } 19 | return box2d_cast_shape(shape, p_query, p_results); 20 | } 21 | 22 | int Box2DCircleShape2D::overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 23 | b2Circle shape; 24 | if (!make_circle(p_transform, data, shape)) { 25 | return 0; 26 | } 27 | return box2d_overlap_shape(shape, p_query, p_results); 28 | } 29 | 30 | bool Box2DCircleShape2D::make_circle(const Transform2D &p_transform, const Variant &p_data, b2Circle &p_circle) { 31 | Variant::Type type = p_data.get_type(); 32 | ERR_FAIL_COND_V(type != Variant::FLOAT && type != Variant::INT, false); 33 | 34 | float radius = p_data; 35 | Vector2 origin = p_transform.get_origin(); 36 | radius *= p_transform.get_scale().x; 37 | 38 | p_circle.center = to_box2d(origin); 39 | p_circle.radius = to_box2d(radius); 40 | 41 | return true; 42 | } -------------------------------------------------------------------------------- /methods.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from enum import Enum 4 | 5 | # Colors are disabled in non-TTY environments such as pipes. This means 6 | # that if output is redirected to a file, it won't contain color codes. 7 | # Colors are always enabled on continuous integration. 8 | _colorize = bool(sys.stdout.isatty() or os.environ.get("CI")) 9 | 10 | 11 | class ANSI(Enum): 12 | """ 13 | Enum class for adding ansi colorcodes directly into strings. 14 | Automatically converts values to strings representing their 15 | internal value, or an empty string in a non-colorized scope. 16 | """ 17 | 18 | RESET = "\x1b[0m" 19 | 20 | BOLD = "\x1b[1m" 21 | ITALIC = "\x1b[3m" 22 | UNDERLINE = "\x1b[4m" 23 | STRIKETHROUGH = "\x1b[9m" 24 | REGULAR = "\x1b[22;23;24;29m" 25 | 26 | BLACK = "\x1b[30m" 27 | RED = "\x1b[31m" 28 | GREEN = "\x1b[32m" 29 | YELLOW = "\x1b[33m" 30 | BLUE = "\x1b[34m" 31 | MAGENTA = "\x1b[35m" 32 | CYAN = "\x1b[36m" 33 | WHITE = "\x1b[37m" 34 | 35 | PURPLE = "\x1b[38;5;93m" 36 | PINK = "\x1b[38;5;206m" 37 | ORANGE = "\x1b[38;5;214m" 38 | GRAY = "\x1b[38;5;244m" 39 | 40 | def __str__(self) -> str: 41 | global _colorize 42 | return str(self.value) if _colorize else "" 43 | 44 | 45 | def print_warning(*values: object) -> None: 46 | """Prints a warning message with formatting.""" 47 | print(f"{ANSI.YELLOW}{ANSI.BOLD}WARNING:{ANSI.REGULAR}", *values, ANSI.RESET, file=sys.stderr) 48 | 49 | 50 | def print_error(*values: object) -> None: 51 | """Prints an error message with formatting.""" 52 | print(f"{ANSI.RED}{ANSI.BOLD}ERROR:{ANSI.REGULAR}", *values, ANSI.RESET, file=sys.stderr) 53 | -------------------------------------------------------------------------------- /src/shapes/box2d_segment_shape_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_segment_shape_2d.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | 4 | void Box2DSegmentShape2D::add_to_body(Box2DShapeInstance *p_instance) const { 5 | b2Segment shape; 6 | if (!make_segment(p_instance->get_global_transform(), data, shape)) { 7 | return; 8 | } 9 | b2ShapeDef shape_def = p_instance->get_shape_def(); 10 | b2ShapeId id = b2CreateSegmentShape(p_instance->get_collision_object()->get_body_id(), &shape_def, &shape); 11 | p_instance->add_shape_id(id); 12 | } 13 | 14 | int Box2DSegmentShape2D::overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 15 | b2Capsule shape; 16 | if (!make_capsule_segment(p_transform, data, shape)) { 17 | return 0; 18 | } 19 | return box2d_overlap_shape(shape, p_query, p_results); 20 | } 21 | 22 | bool Box2DSegmentShape2D::make_segment(const Transform2D &p_transform, const Variant &p_data, b2Segment &p_segment) { 23 | Variant::Type type = p_data.get_type(); 24 | ERR_FAIL_COND_V(type != Variant::RECT2, false); 25 | 26 | Rect2 rect = p_data; 27 | 28 | p_segment.point1 = to_box2d(p_transform.xform(rect.position)); 29 | p_segment.point2 = to_box2d(p_transform.xform(rect.size)); 30 | 31 | return true; 32 | } 33 | 34 | bool Box2DSegmentShape2D::make_capsule_segment(const Transform2D &p_transform, const Variant &p_data, b2Capsule &p_capsule) { 35 | Variant::Type type = p_data.get_type(); 36 | ERR_FAIL_COND_V(type != Variant::RECT2, false); 37 | 38 | Rect2 rect = p_data; 39 | 40 | p_capsule.radius = 0.0; 41 | p_capsule.center1 = to_box2d(p_transform.xform(rect.position)); 42 | p_capsule.center2 = to_box2d(p_transform.xform(rect.size)); 43 | 44 | return true; 45 | } -------------------------------------------------------------------------------- /demo/bin/godot-box2c.gdextension: -------------------------------------------------------------------------------- 1 | [configuration] 2 | 3 | entry_symbol = "godot_box2d_init" 4 | compatibility_minimum = "4.3" 5 | 6 | [libraries] 7 | macos.debug = "./macos/libgodot-box2c.macos.template_debug.dylib" 8 | macos.release = "./macos/libgodot-box2c.macos.template_release.dylib" 9 | 10 | ios.arm64.debug = "./ios/libgodot-box2c.ios.template_debug.arm64.dylib" 11 | ios.arm64.release = "./ios/libgodot-box2c.ios.template_release.arm64.dylib" 12 | 13 | windows.x86_32.debug = "./windows/godot-box2c.windows.template_debug.x86_32.dll" 14 | windows.x86_32.release = "./windows/godot-box2c.windows.template_release.x86_32.dll" 15 | 16 | windows.x86_64.debug = "./windows/godot-box2c.windows.template_debug.x86_64.dll" 17 | windows.x86_64.release = "./windows/godot-box2c.windows.template_release.x86_64.dll" 18 | 19 | linux.x86_64.debug = "./linux/libgodot-box2c.linux.template_debug.x86_64.so" 20 | linux.x86_64.release = "./linux/libgodot-box2c.linux.template_release.x86_64.so" 21 | 22 | linux.arm64.debug = "./linux/libgodot-box2c.linux.template_debug.arm64.so" 23 | linux.arm64.release = "./linux/libgodot-box2c.linux.template_release.arm64.so" 24 | 25 | linux.rv64.debug = "./linux/libgodot-box2c.linux.template_debug.rv64.so" 26 | linux.rv64.release = "./linux/libgodot-box2c.linux.template_release.rv64.so" 27 | 28 | android.x86_64.debug = "./android/libgodot-box2c.android.template_debug.x86_64.so" 29 | android.x86_64.release = "./android/libgodot-box2c.android.template_release.x86_64.so" 30 | 31 | android.arm64.debug = "./android/libgodot-box2c.android.template_debug.arm64.so" 32 | android.arm64.release = "./android/libgodot-box2c.android.template_release.arm64.so" 33 | 34 | web.wasm32.debug = "./web/libgodot-box2c.web.template_debug.wasm32.nothreads.wasm" 35 | web.wasm32.release = "./web/libgodot-box2c.web.template_release.wasm32.nothreads.wasm" -------------------------------------------------------------------------------- /src/shapes/box2d_rectangle_shape_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_rectangle_shape_2d.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | 4 | void Box2DRectangleShape2D::add_to_body(Box2DShapeInstance *p_instance) const { 5 | b2Polygon shape; 6 | if (!make_rectangle(p_instance->get_global_transform(), data, shape)) { 7 | return; 8 | } 9 | b2ShapeDef shape_def = p_instance->get_shape_def(); 10 | b2ShapeId id = b2CreatePolygonShape(p_instance->get_collision_object()->get_body_id(), &shape_def, &shape); 11 | p_instance->add_shape_id(id); 12 | } 13 | 14 | int Box2DRectangleShape2D::cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 15 | b2Polygon shape; 16 | if (!make_rectangle(p_transform, data, shape)) { 17 | return 0; 18 | } 19 | return box2d_cast_shape(shape, p_query, p_results); 20 | } 21 | 22 | int Box2DRectangleShape2D::overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 23 | b2Polygon shape; 24 | if (!make_rectangle(p_transform, data, shape)) { 25 | return 0; 26 | } 27 | return box2d_overlap_shape(shape, p_query, p_results); 28 | } 29 | 30 | bool Box2DRectangleShape2D::make_rectangle(const Transform2D &p_transform, const Variant &p_data, b2Polygon &p_box) { 31 | Variant::Type type = p_data.get_type(); 32 | ERR_FAIL_COND_V(type != Variant::VECTOR2, false); 33 | 34 | Vector2 half_extents = p_data; 35 | 36 | b2Vec2 points[4] = { 37 | to_box2d(p_transform.xform(Vector2(-half_extents.x, half_extents.y))), 38 | to_box2d(p_transform.xform(Vector2(half_extents.x, half_extents.y))), 39 | to_box2d(p_transform.xform(Vector2(half_extents.x, -half_extents.y))), 40 | to_box2d(p_transform.xform(Vector2(-half_extents.x, -half_extents.y))) 41 | }; 42 | 43 | b2Hull hull = b2ComputeHull(points, 4); 44 | 45 | p_box = b2MakePolygon(&hull, 0.0); 46 | 47 | return true; 48 | } -------------------------------------------------------------------------------- /demo/scripts/area_print_events.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | func _ready() -> void: 4 | body_entered.connect(on_body_entered) 5 | body_exited.connect(on_body_exited) 6 | body_shape_entered.connect(on_body_shape_entered) 7 | body_shape_exited.connect(on_body_shape_exited) 8 | area_shape_entered.connect(on_area_shape_entered) 9 | area_shape_exited.connect(on_area_shape_exited) 10 | area_entered.connect(on_area_entered) 11 | area_exited.connect(on_area_exited) 12 | 13 | 14 | func on_body_shape_entered(body_rid: RID, body: Node2D, body_shape_index: int, local_shape_index: int) -> void: 15 | print("body shape entered, node: %s, shape: %s" % [body.name, body_shape_index]) 16 | 17 | 18 | func on_body_shape_exited(body_rid: RID, body: Node2D, body_shape_index: int, local_shape_index: int) -> void: 19 | print("body shape exited, node: %s, shape: %s" % [body.name if body else "null", body_shape_index]) 20 | 21 | 22 | func on_area_shape_entered(area_rid: RID, area: Node2D, area_shape_index: int, local_shape_index: int) -> void: 23 | print("area shape entered, node: %s, shape: %s" % [area.name, area_shape_index]) 24 | 25 | 26 | func on_area_shape_exited(area_rid: RID, area: Node2D, area_shape_index: int, local_shape_index: int) -> void: 27 | print("area shape exited, node: %s, shape: %s" % [area.name if area else "null", area_shape_index]) 28 | 29 | 30 | func on_body_entered(body: Node2D) -> void: 31 | change_shape_colors(body, Color.RED) 32 | 33 | 34 | func on_body_exited(body: Node2D) -> void: 35 | change_shape_colors(body, Color.BLUE) 36 | 37 | 38 | func on_area_entered(area: Area2D) -> void: 39 | change_shape_colors(area, Color.RED) 40 | 41 | 42 | func on_area_exited(area: Area2D) -> void: 43 | change_shape_colors(area, Color.BLUE) 44 | 45 | 46 | func change_shape_colors(body: Node2D, color: Color) -> void: 47 | for shape in body.find_children("*", "CollisionShape2D", false): 48 | shape = shape as CollisionShape2D 49 | color.a = shape.debug_color.a 50 | shape.debug_color = color 51 | -------------------------------------------------------------------------------- /src/register_types.cpp: -------------------------------------------------------------------------------- 1 | #include "register_types.h" 2 | #include "bodies/box2d_direct_body_state_2d.h" 3 | #include "box2d_physics_server_2d.h" 4 | #include "box2d_project_settings.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace godot; 12 | 13 | Box2DPhysicsServer2D *create_box2d_physics_server() { 14 | return memnew(Box2DPhysicsServer2D); 15 | } 16 | 17 | void initialize_gdextension_types(ModuleInitializationLevel p_level) { 18 | switch (p_level) { 19 | case MODULE_INITIALIZATION_LEVEL_SERVERS: { 20 | #if defined(TRACY_DELAYED_INIT) && defined(TRACY_MANUAL_LIFETIME) 21 | tracy::StartupProfiler(); 22 | #endif 23 | GDREGISTER_VIRTUAL_CLASS(Box2DDirectBodyState2D); 24 | GDREGISTER_VIRTUAL_CLASS(Box2DDirectSpaceState2D); 25 | GDREGISTER_CLASS(Box2DPhysicsServer2D); 26 | 27 | PhysicsServer2DManager::get_singleton()->register_server( 28 | "Box2D", 29 | callable_mp_static(&create_box2d_physics_server)); 30 | } break; 31 | case MODULE_INITIALIZATION_LEVEL_SCENE: { 32 | Box2DProjectSettings::register_settings(); 33 | } break; 34 | default: 35 | break; 36 | } 37 | } 38 | 39 | void uninitialize_gdextension_types(ModuleInitializationLevel p_level) { 40 | if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { 41 | return; 42 | } 43 | } 44 | 45 | extern "C" { 46 | // Initialization 47 | GDExtensionBool GDE_EXPORT godot_box2d_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { 48 | GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); 49 | init_obj.register_initializer(initialize_gdextension_types); 50 | init_obj.register_terminator(uninitialize_gdextension_types); 51 | init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); 52 | 53 | return init_obj.init(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /demo/Area Overrides.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=3 uid="uid://cjlent5d8gvla"] 2 | 3 | [ext_resource type="Script" path="res://scripts/override_timescale.gd" id="1_fsffx"] 4 | [ext_resource type="Script" path="res://scripts/body_destroyer.gd" id="2_gxlct"] 5 | [ext_resource type="Script" path="res://scripts/area_print_events.gd" id="2_k3v6f"] 6 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="4_e0duv"] 7 | [ext_resource type="Script" path="res://scripts/rain_spawner.gd" id="5_i5jff"] 8 | [ext_resource type="PackedScene" uid="uid://cunci2twkihny" path="res://scenes/Robot.tscn" id="6_j7a84"] 9 | 10 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_h7rvq"] 11 | size = Vector2(1825, 105) 12 | 13 | [sub_resource type="CircleShape2D" id="CircleShape2D_rq1le"] 14 | radius = 430.029 15 | 16 | [node name="Areas" type="Node2D"] 17 | script = ExtResource("1_fsffx") 18 | 19 | [node name="StaticBody2D" type="AnimatableBody2D" parent="."] 20 | position = Vector2(846, 836) 21 | 22 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] 23 | position = Vector2(113, 71) 24 | shape = SubResource("RectangleShape2D_h7rvq") 25 | 26 | [node name="Area2D" type="Area2D" parent="."] 27 | position = Vector2(942, 535) 28 | gravity_space_override = 3 29 | gravity_point = true 30 | gravity_point_center = Vector2(0, 0) 31 | gravity_direction = Vector2(0, 0) 32 | gravity = 1000.0 33 | linear_damp = 100.0 34 | script = ExtResource("2_k3v6f") 35 | 36 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] 37 | shape = SubResource("CircleShape2D_rq1le") 38 | 39 | [node name="Node2D" type="Node2D" parent="."] 40 | script = ExtResource("2_gxlct") 41 | 42 | [node name="Camera2D" parent="." instance=ExtResource("4_e0duv")] 43 | position = Vector2(967, 500) 44 | zoom = Vector2(1, 1) 45 | 46 | [node name="Spawner" type="Node2D" parent="."] 47 | position = Vector2(945, 0) 48 | script = ExtResource("5_i5jff") 49 | max_bodies = 10 50 | spawns_per_second = 2 51 | body = ExtResource("6_j7a84") 52 | spawn_range = Vector2(-200, 200) 53 | -------------------------------------------------------------------------------- /demo/Contact Monitor.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://bad2fchsv0e8g"] 2 | 3 | [ext_resource type="Script" path="res://scripts/override_timescale.gd" id="1_texhd"] 4 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="1_ubnx8"] 5 | [ext_resource type="Script" path="res://scripts/contact_drawer.gd" id="2_yl0l3"] 6 | 7 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_d05bq"] 8 | radius = 33.0 9 | height = 126.0 10 | 11 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_2uxip"] 12 | size = Vector2(69, 72) 13 | 14 | [sub_resource type="CircleShape2D" id="CircleShape2D_ndawr"] 15 | radius = 43.5 16 | 17 | [node name="ContactMonitor" type="Node2D"] 18 | script = ExtResource("1_texhd") 19 | time_scale = 0.5 20 | 21 | [node name="StaticBody2D" type="StaticBody2D" parent="."] 22 | position = Vector2(-20, 95) 23 | 24 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBody2D"] 25 | polygon = PackedVector2Array(-258, -114, -144, -3, 163, 0, 295, -130, 404, -130, 398, 132, -366, 138, -379, -123) 26 | 27 | [node name="Camera2D" parent="." instance=ExtResource("1_ubnx8")] 28 | position = Vector2(-5, -129) 29 | zoom = Vector2(1, 1) 30 | 31 | [node name="RigidBody2D" type="RigidBody2D" parent="."] 32 | position = Vector2(-205, -125) 33 | script = ExtResource("2_yl0l3") 34 | 35 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D"] 36 | position = Vector2(-89, 44) 37 | shape = SubResource("CapsuleShape2D_d05bq") 38 | 39 | [node name="RigidBody2D2" type="RigidBody2D" parent="."] 40 | position = Vector2(-25, 30) 41 | skew = -0.670206 42 | script = ExtResource("2_yl0l3") 43 | 44 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D2"] 45 | position = Vector2(-0.5, 0) 46 | shape = SubResource("RectangleShape2D_2uxip") 47 | 48 | [node name="RigidBody2D3" type="RigidBody2D" parent="."] 49 | position = Vector2(251, -69) 50 | script = ExtResource("2_yl0l3") 51 | 52 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D3"] 53 | position = Vector2(-0.5, 0) 54 | shape = SubResource("CircleShape2D_ndawr") 55 | -------------------------------------------------------------------------------- /src/shapes/box2d_capsule_shape_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_capsule_shape_2d.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | 4 | void Box2DCapsuleShape2D::add_to_body(Box2DShapeInstance *p_instance) const { 5 | b2Capsule shape; 6 | if (!make_capsule(p_instance->get_global_transform(), data, shape)) { 7 | return; 8 | } 9 | b2ShapeDef shape_def = p_instance->get_shape_def(); 10 | b2ShapeId id = b2CreateCapsuleShape(p_instance->get_collision_object()->get_body_id(), &shape_def, &shape); 11 | p_instance->add_shape_id(id); 12 | } 13 | 14 | int Box2DCapsuleShape2D::cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 15 | b2Capsule shape; 16 | if (!make_capsule(p_transform, data, shape)) { 17 | return 0; 18 | } 19 | return box2d_cast_shape(shape, p_query, p_results); 20 | } 21 | 22 | int Box2DCapsuleShape2D::overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 23 | b2Capsule shape; 24 | if (!make_capsule(p_transform, data, shape)) { 25 | return 0; 26 | } 27 | return box2d_overlap_shape(shape, p_query, p_results); 28 | } 29 | 30 | bool Box2DCapsuleShape2D::make_capsule(const Transform2D &p_transform, const Variant &p_data, b2Capsule &p_capsule) { 31 | Variant::Type type = p_data.get_type(); 32 | ERR_FAIL_COND_V(type != Variant::VECTOR2 && type != Variant::ARRAY, false); 33 | 34 | float radius, height; 35 | 36 | if (type == Variant::ARRAY) { 37 | Array arr = p_data; 38 | ERR_FAIL_COND_V(arr.size() != 2, false); 39 | height = arr[0]; 40 | radius = arr[1]; 41 | } else { 42 | Vector2 p = p_data; 43 | // Order for Vector2 is flipped for some reason 44 | radius = p.x; 45 | height = p.y; 46 | } 47 | 48 | Vector2 origin = p_transform.get_origin(); 49 | Vector2 capsule_up = p_transform.columns[1].normalized(); 50 | float scale = p_transform.get_scale().x; 51 | 52 | radius *= scale; 53 | height *= scale; 54 | 55 | Vector2 center1 = origin + capsule_up * (0.5 * height - radius); 56 | Vector2 center2 = origin - capsule_up * (0.5 * height - radius); 57 | 58 | p_capsule.center1 = to_box2d(center1); 59 | p_capsule.center2 = to_box2d(center2); 60 | p_capsule.radius = to_box2d(radius); 61 | 62 | return true; 63 | } -------------------------------------------------------------------------------- /demo/Churn (Optimized).tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://dopfimfro3isn"] 2 | 3 | [ext_resource type="Script" path="res://scripts/body_animator.gd" id="2_rhjev"] 4 | [ext_resource type="Script" path="res://scripts/rain_spawner_optimized.gd" id="3_vse6i"] 5 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="3_w8qnv"] 6 | 7 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_hfuel"] 8 | size = Vector2(101.108, 2782.56) 9 | 10 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_ndips"] 11 | radius = 83.3262 12 | height = 1090.01 13 | 14 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_op26f"] 15 | size = Vector2(3474, 243) 16 | 17 | [node name="Node2D" type="Node2D"] 18 | 19 | [node name="StaticBody2D" type="StaticBody2D" parent="."] 20 | position = Vector2(1163, 964) 21 | scale = Vector2(3.605, 3.605) 22 | 23 | [node name="CollisionShape2D3" type="CollisionShape2D" parent="StaticBody2D"] 24 | position = Vector2(-614.702, -629.404) 25 | rotation = -0.261799 26 | scale = Vector2(0.675, 0.675) 27 | shape = SubResource("RectangleShape2D_hfuel") 28 | 29 | [node name="CollisionShape2D4" type="CollisionShape2D" parent="StaticBody2D"] 30 | position = Vector2(773.925, -631.345) 31 | rotation = 0.261799 32 | scale = Vector2(0.675, 0.675) 33 | shape = SubResource("RectangleShape2D_hfuel") 34 | 35 | [node name="AnimatableBody2D" type="AnimatableBody2D" parent="."] 36 | position = Vector2(1503, 597) 37 | script = ExtResource("2_rhjev") 38 | 39 | [node name="CollisionShape2D" type="CollisionShape2D" parent="AnimatableBody2D"] 40 | position = Vector2(-45, 166) 41 | rotation = 1.72299 42 | shape = SubResource("CapsuleShape2D_ndips") 43 | 44 | [node name="Spawner" type="Node2D" parent="."] 45 | position = Vector2(1346, -4326) 46 | script = ExtResource("3_vse6i") 47 | spawn_range = Vector2(-2000, 2000) 48 | 49 | [node name="StaticBody2D2" type="StaticBody2D" parent="."] 50 | position = Vector2(-71, 138) 51 | 52 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D2"] 53 | position = Vector2(1522, 1766) 54 | shape = SubResource("RectangleShape2D_op26f") 55 | 56 | [node name="Camera2D" parent="." instance=ExtResource("3_w8qnv")] 57 | position = Vector2(1548, -480) 58 | -------------------------------------------------------------------------------- /src/shapes/box2d_concave_polygon_shape_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_concave_polygon_shape_2d.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | 4 | void Box2DConcavePolygonShape2D::add_to_body(Box2DShapeInstance *p_instance) const { 5 | Variant::Type type = data.get_type(); 6 | ERR_FAIL_COND(type != Variant::PACKED_VECTOR2_ARRAY); 7 | PackedVector2Array arr = data; 8 | ERR_FAIL_COND(arr.size() % 2); 9 | 10 | Transform2D shape_transform = p_instance->get_global_transform(); 11 | b2ShapeDef shape_def = p_instance->get_shape_def(); 12 | 13 | for (int i = 0; i < arr.size() - 1; i++) { 14 | b2Vec2 point_a = to_box2d(shape_transform.xform(arr[i])); 15 | b2Vec2 point_b = to_box2d(shape_transform.xform(arr[i + 1])); 16 | b2Segment segment; 17 | segment.point1 = point_a; 18 | segment.point2 = point_b; 19 | b2ShapeId id = b2CreateSegmentShape(p_instance->get_collision_object()->get_body_id(), &shape_def, &segment); 20 | p_instance->add_shape_id(id); 21 | } 22 | } 23 | 24 | int Box2DConcavePolygonShape2D::overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 25 | Variant::Type type = data.get_type(); 26 | ERR_FAIL_COND_V(type != Variant::PACKED_VECTOR2_ARRAY, 0); 27 | PackedVector2Array arr = data; 28 | ERR_FAIL_COND_V(arr.size() % 2, 0); 29 | 30 | OverlapQuery query = p_query; 31 | LocalVector results; 32 | 33 | Transform2D shape_transform = p_transform; 34 | b2Capsule capsule; 35 | capsule.radius = 0.0; 36 | 37 | for (int i = 0; i < arr.size() - 1; i++) { 38 | capsule.center1 = to_box2d(shape_transform.xform(arr[i])); 39 | capsule.center2 = to_box2d(shape_transform.xform(arr[i + 1])); 40 | 41 | int count = box2d_overlap_shape(capsule, query, results); 42 | query.max_results -= count; 43 | if (query.max_results <= 0) { 44 | break; 45 | } 46 | } 47 | 48 | // De-duplicate overlaps 49 | int i = 0; 50 | while (i < results.size()) { 51 | int j = i + 1; 52 | while (j < results.size()) { 53 | if (results[j] == results[i]) { 54 | results.remove_at(j); 55 | } else { 56 | ++j; 57 | } 58 | } 59 | ++i; 60 | } 61 | 62 | for (ShapeOverlap &overlap : results) { 63 | p_results.push_back(overlap); 64 | } 65 | 66 | return results.size(); 67 | } -------------------------------------------------------------------------------- /src/joints/box2d_pin_joint_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_pin_joint_2d.h" 2 | #include "../spaces/box2d_space_2d.h" 3 | 4 | Box2DPinJoint2D::Box2DPinJoint2D(const Vector2 &p_pos, Box2DBody2D *p_body_a, Box2DBody2D *p_body_b) : 5 | Box2DJoint2D(PhysicsServer2D::JOINT_TYPE_PIN, p_body_a, p_body_b) { 6 | if (!p_body_a || !p_body_b) { 7 | return; 8 | } 9 | 10 | ERR_FAIL_COND(!p_body_a->in_space()); 11 | ERR_FAIL_COND(p_body_a->get_space() != p_body_b->get_space()); 12 | space = p_body_a->get_space(); 13 | 14 | Vector2 anchor_a = p_body_a->get_transform().affine_inverse().xform(p_pos); 15 | Vector2 anchor_b = p_body_b->get_transform().affine_inverse().xform(p_pos); 16 | 17 | revolute_def.bodyIdA = p_body_a->get_body_id(); 18 | revolute_def.bodyIdB = p_body_b->get_body_id(); 19 | revolute_def.localAnchorA = to_box2d(anchor_a); 20 | revolute_def.localAnchorB = to_box2d(anchor_b); 21 | 22 | // TODO: account for mass? 23 | revolute_def.maxMotorTorque = 100000.0; 24 | 25 | revolute_def.collideConnected = !disabled_collisions_between_bodies; 26 | revolute_def.userData = this; 27 | 28 | joint_id = b2CreateRevoluteJoint(space->get_world_id(), &revolute_def); 29 | } 30 | 31 | void Box2DPinJoint2D::set_upper_limit(float p_limit) { 32 | revolute_def.upperAngle = p_limit; 33 | if (b2Joint_IsValid(joint_id)) { 34 | b2RevoluteJoint_SetLimits(joint_id, revolute_def.lowerAngle, revolute_def.upperAngle); 35 | } 36 | } 37 | 38 | void Box2DPinJoint2D::set_lower_limit(float p_limit) { 39 | revolute_def.lowerAngle = p_limit; 40 | if (b2Joint_IsValid(joint_id)) { 41 | b2RevoluteJoint_SetLimits(joint_id, revolute_def.lowerAngle, revolute_def.upperAngle); 42 | } 43 | } 44 | 45 | void Box2DPinJoint2D::set_motor_speed(float p_speed) { 46 | revolute_def.motorSpeed = to_box2d(p_speed); 47 | if (b2Joint_IsValid(joint_id)) { 48 | b2RevoluteJoint_SetMotorSpeed(joint_id, revolute_def.motorSpeed); 49 | } 50 | } 51 | 52 | void Box2DPinJoint2D::set_motor_enabled(bool p_enabled) { 53 | revolute_def.enableMotor = p_enabled; 54 | if (b2Joint_IsValid(joint_id)) { 55 | b2RevoluteJoint_EnableMotor(joint_id, revolute_def.enableMotor); 56 | } 57 | } 58 | 59 | void Box2DPinJoint2D::set_limit_enabled(bool p_enabled) { 60 | revolute_def.enableLimit = p_enabled; 61 | if (b2Joint_IsValid(joint_id)) { 62 | b2RevoluteJoint_EnableLimit(joint_id, revolute_def.enableLimit); 63 | } 64 | } -------------------------------------------------------------------------------- /demo/ChuAC36.tmp: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://f2dle7n6bc7o"] 2 | 3 | [ext_resource type="Script" path="res://scripts/spinner.gd" id="1_o1u2r"] 4 | [ext_resource type="Script" path="res://camera_navigation.gd" id="1_rnyq5"] 5 | [ext_resource type="Script" path="res://scripts/rain_spawner.gd" id="2_whm8o"] 6 | [ext_resource type="PackedScene" uid="uid://dcxotvc8jebfx" path="res://circle_body.tscn" id="3_ip53t"] 7 | 8 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_hfuel"] 9 | size = Vector2(101.108, 2782.56) 10 | 11 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_ndips"] 12 | radius = 83.3262 13 | height = 1090.01 14 | 15 | [node name="Node2D" type="Node2D"] 16 | 17 | [node name="StaticBody2D" type="StaticBody2D" parent="."] 18 | position = Vector2(1163, 964) 19 | rotation = 0.162104 20 | scale = Vector2(3.605, 3.605) 21 | 22 | [node name="CollisionShape2D3" type="CollisionShape2D" parent="StaticBody2D"] 23 | position = Vector2(-681.649, -548.494) 24 | rotation = -0.392699 25 | scale = Vector2(0.675, 0.675) 26 | shape = SubResource("RectangleShape2D_hfuel") 27 | 28 | [node name="CollisionShape2D4" type="CollisionShape2D" parent="StaticBody2D"] 29 | position = Vector2(695.435, -743.906) 30 | rotation = 0.137409 31 | scale = Vector2(0.675, 0.675) 32 | shape = SubResource("RectangleShape2D_hfuel") 33 | 34 | [node name="Camera2D" type="Camera2D" parent="."] 35 | position = Vector2(1563, -287) 36 | zoom = Vector2(0.17, 0.17) 37 | script = ExtResource("1_rnyq5") 38 | 39 | [node name="AnimatableBody2D" type="AnimatableBody2D" parent="."] 40 | position = Vector2(-2601, -77) 41 | script = ExtResource("1_o1u2r") 42 | 43 | [node name="CollisionShape2D" type="CollisionShape2D" parent="AnimatableBody2D"] 44 | position = Vector2(-45, 166) 45 | rotation = 1.72299 46 | shape = SubResource("CapsuleShape2D_ndips") 47 | 48 | [node name="Spawner" type="Node2D" parent="."] 49 | position = Vector2(1520, -4820) 50 | script = ExtResource("2_whm8o") 51 | max_bodies = 6000 52 | body = ExtResource("3_ip53t") 53 | spawn_range = Vector2(-2000, 2000) 54 | 55 | [node name="StaticBody2D2" type="StaticBody2D" parent="."] 56 | position = Vector2(-71, 138) 57 | 58 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBody2D2"] 59 | build_mode = 1 60 | polygon = PackedVector2Array(345, 1051, 697, 911, 1697, 1351, 2846, 1123, 3363, 1360, 2303, 1487, 957, 1585, 892, 1275, 641, 1363, -167, 1190) 61 | -------------------------------------------------------------------------------- /demo/Churn (Nodes).tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=3 uid="uid://f2dle7n6bc7o"] 2 | 3 | [ext_resource type="Script" path="res://scripts/body_animator.gd" id="1_o1u2r"] 4 | [ext_resource type="Script" path="res://scripts/rain_spawner.gd" id="2_whm8o"] 5 | [ext_resource type="PackedScene" uid="uid://dcxotvc8jebfx" path="res://scenes/BoxBody.tscn" id="4_6buib"] 6 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="4_kk17e"] 7 | 8 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_hfuel"] 9 | size = Vector2(101.108, 2782.56) 10 | 11 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_ndips"] 12 | radius = 83.3262 13 | height = 1090.01 14 | 15 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_op26f"] 16 | size = Vector2(3474, 243) 17 | 18 | [node name="Node2D" type="Node2D"] 19 | 20 | [node name="StaticBody2D" type="StaticBody2D" parent="."] 21 | position = Vector2(1163, 964) 22 | scale = Vector2(3.605, 3.605) 23 | 24 | [node name="CollisionShape2D3" type="CollisionShape2D" parent="StaticBody2D"] 25 | position = Vector2(-614.702, -629.404) 26 | rotation = -0.261799 27 | scale = Vector2(0.675, 0.675) 28 | shape = SubResource("RectangleShape2D_hfuel") 29 | 30 | [node name="CollisionShape2D4" type="CollisionShape2D" parent="StaticBody2D"] 31 | position = Vector2(773.925, -631.345) 32 | rotation = 0.261799 33 | scale = Vector2(0.675, 0.675) 34 | shape = SubResource("RectangleShape2D_hfuel") 35 | 36 | [node name="AnimatableBody2D" type="AnimatableBody2D" parent="."] 37 | position = Vector2(1503, 597) 38 | script = ExtResource("1_o1u2r") 39 | 40 | [node name="CollisionShape2D" type="CollisionShape2D" parent="AnimatableBody2D"] 41 | position = Vector2(-45, 166) 42 | rotation = 1.72299 43 | shape = SubResource("CapsuleShape2D_ndips") 44 | 45 | [node name="Spawner" type="Node2D" parent="."] 46 | position = Vector2(1346, -4326) 47 | script = ExtResource("2_whm8o") 48 | max_bodies = 4000 49 | spawns_per_second = 500 50 | body = ExtResource("4_6buib") 51 | spawn_range = Vector2(-2000, 2000) 52 | 53 | [node name="StaticBody2D2" type="StaticBody2D" parent="."] 54 | position = Vector2(-71, 138) 55 | 56 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D2"] 57 | position = Vector2(1522, 1766) 58 | shape = SubResource("RectangleShape2D_op26f") 59 | 60 | [node name="Camera2D" parent="." instance=ExtResource("4_kk17e")] 61 | position = Vector2(1516, -537) 62 | -------------------------------------------------------------------------------- /demo/project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Box2D Demo Project" 14 | run/main_scene="res://scenes/Menu.tscn" 15 | config/features=PackedStringArray("4.3", "Forward Plus") 16 | 17 | [autoload] 18 | 19 | DebugMenu="*res://addons/debug_menu/debug_menu.tscn" 20 | 21 | [display] 22 | 23 | window/size/viewport_width=1920 24 | window/size/viewport_height=1080 25 | 26 | [dotnet] 27 | 28 | project/assembly_name="Box2D Demo Project" 29 | 30 | [editor_plugins] 31 | 32 | enabled=PackedStringArray("res://addons/debug_menu/plugin.cfg") 33 | 34 | [input] 35 | 36 | move_up={ 37 | "deadzone": 0.5, 38 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) 39 | ] 40 | } 41 | move_down={ 42 | "deadzone": 0.5, 43 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) 44 | ] 45 | } 46 | move_left={ 47 | "deadzone": 0.5, 48 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) 49 | ] 50 | } 51 | move_right={ 52 | "deadzone": 0.5, 53 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) 54 | ] 55 | } 56 | 57 | [physics] 58 | 59 | 2d/physics_engine="Box2D" 60 | box_2d/solver/contact_hertz=30.0 61 | box_2d/solver/contact_damping_ratio=10.0 62 | -------------------------------------------------------------------------------- /demo/scripts/rain_spawner_optimized.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node2D 3 | 4 | @export var enabled = true 5 | @export var max_bodies = 10_000 6 | @export var spawn_range: Vector2 = Vector2(-400, 400) 7 | 8 | var count = 0 9 | var multimesh: MultiMesh 10 | var ball_texture = preload("res://sprites/circle.png") 11 | var shape_rid: RID 12 | var bodies: Array[RID] 13 | 14 | func _ready() -> void: 15 | shape_rid = Box2DPhysicsServer2D.circle_shape_create() 16 | Box2DPhysicsServer2D.shape_set_data(shape_rid, 25) 17 | multimesh = MultiMesh.new() 18 | multimesh.transform_format = MultiMesh.TRANSFORM_2D 19 | multimesh.instance_count = max_bodies 20 | multimesh.visible_instance_count = 0 21 | var quadmesh = QuadMesh.new() 22 | quadmesh.size = Vector2(50, 50) 23 | multimesh.mesh = quadmesh 24 | 25 | 26 | func _physics_process(delta: float) -> void: 27 | if Engine.is_editor_hint(): 28 | return 29 | 30 | var event_data := Box2DPhysicsServer2D.space_get_body_move_events(get_world_2d().space) 31 | 32 | for i in range(0, event_data.size(), 2): 33 | var index = event_data[i] 34 | if not index: 35 | continue 36 | var xform = event_data[i + 1] 37 | multimesh.set_instance_transform_2d(index, xform) 38 | 39 | if count >= max_bodies or not enabled: 40 | return 41 | 42 | for i in 20: 43 | var pos = global_position + Vector2.RIGHT * randf_range(spawn_range.x, spawn_range.y) 44 | spawn_ball(pos) 45 | count += 1 46 | multimesh.visible_instance_count += 1 47 | 48 | 49 | func spawn_ball(pos: Vector2): 50 | var rid = Box2DPhysicsServer2D.body_create() 51 | 52 | Box2DPhysicsServer2D.body_set_user_data(rid, count) 53 | Box2DPhysicsServer2D.body_set_mode(rid, PhysicsServer2D.BodyMode.BODY_MODE_RIGID) 54 | Box2DPhysicsServer2D.body_add_shape(rid, shape_rid) 55 | Box2DPhysicsServer2D.body_set_state(rid, PhysicsServer2D.BodyState.BODY_STATE_TRANSFORM, Transform2D(0, pos)) 56 | Box2DPhysicsServer2D.body_set_space(rid, get_world_2d().space) 57 | 58 | bodies.push_back(rid) 59 | 60 | 61 | func _process(delta: float) -> void: 62 | queue_redraw() 63 | 64 | 65 | func _draw() -> void: 66 | draw_set_transform_matrix(global_transform.affine_inverse()) 67 | draw_line(global_position + Vector2.RIGHT * spawn_range.x, global_position + Vector2.RIGHT * spawn_range.y, Color.BLACK) 68 | if multimesh: 69 | draw_multimesh(multimesh, ball_texture) 70 | 71 | 72 | func _exit_tree() -> void: 73 | for rid in bodies: 74 | Box2DPhysicsServer2D.free_rid(rid) 75 | -------------------------------------------------------------------------------- /src/shapes/box2d_shape_instance.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_shape_instance.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | 4 | Box2DShapeInstance::Box2DShapeInstance( 5 | Box2DCollisionObject2D *p_object, 6 | Box2DShape2D *p_shape, 7 | const Transform2D &p_transform, 8 | bool p_disabled) : 9 | object(p_object), 10 | shape(p_shape), 11 | transform(p_transform), 12 | disabled(p_disabled) { 13 | ERR_FAIL_NULL(shape); 14 | shape->add_object_reference(object); 15 | shape_type = shape->get_type(); 16 | } 17 | 18 | Box2DShapeInstance::~Box2DShapeInstance() { 19 | if (!shape || !object) { 20 | return; 21 | } 22 | shape->remove_from_body(this); 23 | shape->remove_object_reference(object); 24 | } 25 | 26 | void Box2DShapeInstance::set_shape(Box2DShape2D *p_shape) { 27 | shape = p_shape; 28 | shape_type = shape->get_type(); 29 | } 30 | 31 | void Box2DShapeInstance::build() { 32 | ERR_FAIL_COND(!shape); 33 | ERR_FAIL_COND(!object); 34 | 35 | shape->remove_from_body(this); 36 | 37 | if (disabled) { 38 | return; 39 | } 40 | 41 | shape->add_to_body(this); 42 | } 43 | 44 | Transform2D Box2DShapeInstance::get_global_transform() const { 45 | Transform2D parent_transform = object->get_transform(); 46 | return get_global_transform_with_parent_transform(parent_transform); 47 | } 48 | 49 | Transform2D Box2DShapeInstance::get_global_transform_with_parent_transform(const Transform2D &p_parent_transform) const { 50 | Transform2D parent_scale_and_skew = Transform2D(0.0, p_parent_transform.get_scale(), p_parent_transform.get_skew(), Vector2()); 51 | return parent_scale_and_skew * transform; 52 | } 53 | 54 | b2ShapeDef Box2DShapeInstance::get_shape_def() { 55 | b2ShapeDef shape_def = object->get_shape_def(); 56 | shape_def.userData = this; 57 | return shape_def; 58 | } 59 | 60 | bool Box2DShapeInstance::should_filter_one_way_collision(const Vector2 &p_motion, const Vector2 &p_normal, float p_depth) const { 61 | ERR_FAIL_COND_V(!shape, false); 62 | ERR_FAIL_COND_V(!object, false); 63 | 64 | if (!has_one_way_collision()) { 65 | return false; 66 | } 67 | 68 | Vector2 one_way_normal = -(get_transform() * get_collision_object()->get_transform()).columns[1].normalized(); 69 | float max_allowed_depth = p_motion.length() * Math::max(p_motion.normalized().dot(one_way_normal), real_t(0.0f)) + get_one_way_collision_margin(); 70 | if (p_normal.dot(one_way_normal) <= 0.0f || p_depth > max_allowed_depth) { 71 | return true; 72 | } 73 | 74 | return false; 75 | } -------------------------------------------------------------------------------- /src/shapes/box2d_shape_instance.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "box2d_shape_2d.h" 4 | #include 5 | #include 6 | 7 | class Box2DShape2D; 8 | class Box2DCollisionObject2D; 9 | 10 | class Box2DShapeInstance { 11 | public: 12 | explicit Box2DShapeInstance(Box2DCollisionObject2D *p_object, 13 | Box2DShape2D *p_shape, 14 | const Transform2D &p_transform, 15 | bool p_disabled); 16 | 17 | Box2DShapeInstance() = default; 18 | 19 | ~Box2DShapeInstance(); 20 | 21 | void set_shape(Box2DShape2D *p_shape); 22 | Box2DShape2D *get_shape() const { return shape; } 23 | 24 | void set_index(int p_index) { index = p_index; } 25 | int get_index() const { return index; } 26 | 27 | void set_transform(const Transform2D &p_transform) { transform = p_transform; } 28 | Transform2D get_transform() const { return transform; } 29 | 30 | void set_disabled(bool p_disabled) { disabled = p_disabled; } 31 | bool get_disabled() const { return disabled; } 32 | 33 | void set_one_way_collision(bool p_one_way) { one_way_collision = p_one_way; } 34 | _FORCE_INLINE_ bool has_one_way_collision() const { return one_way_collision; } 35 | 36 | void set_one_way_collision_margin(float p_margin) { one_way_collision_margin = p_margin; } 37 | float get_one_way_collision_margin() const { return one_way_collision_margin; } 38 | 39 | bool should_filter_one_way_collision(const Vector2 &p_motion, const Vector2 &p_normal, float p_depth) const; 40 | 41 | Transform2D get_global_transform() const; 42 | Transform2D get_global_transform_with_parent_transform(const Transform2D &p_parent_transform) const; 43 | b2ShapeDef get_shape_def(); 44 | 45 | void build(); 46 | 47 | void add_shape_id(b2ShapeId p_id) { shape_ids.push_back(p_id); } 48 | const LocalVector &get_shape_ids() { return shape_ids; } 49 | void clear_shape_ids() { shape_ids.clear(); } 50 | 51 | bool is_separation_ray() const { return shape_type == PhysicsServer2D::SHAPE_SEPARATION_RAY; } 52 | 53 | Box2DCollisionObject2D *get_collision_object() const { return object; } 54 | 55 | private: 56 | Box2DCollisionObject2D *object = nullptr; 57 | Box2DShape2D *shape = nullptr; 58 | PhysicsServer2D::ShapeType shape_type = PhysicsServer2D::SHAPE_CUSTOM; 59 | 60 | LocalVector shape_ids; 61 | 62 | int index = -1; 63 | Transform2D transform; 64 | bool disabled = false; 65 | bool one_way_collision = false; 66 | float one_way_collision_margin = 0.0; 67 | }; -------------------------------------------------------------------------------- /src/spaces/box2d_physics_direct_space_state_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | 5 | #include 6 | #include 7 | 8 | using namespace godot; 9 | 10 | class Box2DSpace2D; 11 | class CastHit; 12 | class ShapeOverlap; 13 | 14 | class Box2DDirectSpaceState2D : public PhysicsDirectSpaceState2DExtension { 15 | GDCLASS(Box2DDirectSpaceState2D, PhysicsDirectSpaceState2DExtension); 16 | 17 | public: 18 | Box2DDirectSpaceState2D() = default; 19 | Box2DDirectSpaceState2D(Box2DSpace2D *p_space) : 20 | space(p_space) {}; 21 | 22 | bool _intersect_ray(const Vector2 &p_from, const Vector2 &p_to, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas, bool p_hit_from_inside, PhysicsServer2DExtensionRayResult *p_result) override; 23 | int32_t _intersect_point(const Vector2 &p_position, uint64_t p_canvas_instance_id, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas, PhysicsServer2DExtensionShapeResult *p_results, int32_t p_max_results) override; 24 | int32_t _intersect_shape(const RID &p_shape_rid, const Transform2D &p_transform, const Vector2 &p_motion, float p_margin, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas, PhysicsServer2DExtensionShapeResult *p_result, int32_t p_max_results) override; 25 | bool _cast_motion(const RID &p_shape_rid, const Transform2D &p_transform, const Vector2 &p_motion, float p_margin, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas, float *p_closest_safe, float *p_closest_unsafe) override; 26 | bool _collide_shape(const RID &p_shape_rid, const Transform2D &p_transform, const Vector2 &p_motion, float p_margin, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas, void *p_results, int32_t p_max_results, int32_t *p_result_count) override; 27 | bool _rest_info(const RID &p_shape_rid, const Transform2D &p_transform, const Vector2 &p_motion, float p_margin, uint32_t p_collision_mask, bool p_collide_with_bodies, bool p_collide_with_areas, PhysicsServer2DExtensionShapeRestInfo *p_rest_info) override; 28 | 29 | Dictionary cast_shape(const Ref &p_parameters); 30 | TypedArray cast_shape_all(const Ref &p_parameters, int32_t p_max_results = 32); 31 | 32 | static Object *get_instance_hack(uint64_t p_object_id) { 33 | // ???? 34 | return reinterpret_cast((GodotObject *)(internal::gdextension_interface_object_get_instance_from_id(p_object_id))); 35 | } 36 | 37 | b2QueryFilter make_filter(uint64_t p_collision_mask, bool p_collide_bodies, bool p_collide_areas); 38 | 39 | private: 40 | static void _bind_methods(); 41 | 42 | Box2DSpace2D *space = nullptr; 43 | }; -------------------------------------------------------------------------------- /src/joints/box2d_damped_spring_joint_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_damped_spring_joint_2d.h" 2 | #include "../spaces/box2d_space_2d.h" 3 | 4 | Box2DDampedSpringJoint2D::Box2DDampedSpringJoint2D(const Vector2 &p_anchor_a, const Vector2 &p_anchor_b, Box2DBody2D *p_body_a, Box2DBody2D *p_body_b) : 5 | Box2DJoint2D(PhysicsServer2D::JOINT_TYPE_DAMPED_SPRING, p_body_a, p_body_b) { 6 | if (!p_body_a || !p_body_b) { 7 | return; 8 | } 9 | 10 | ERR_FAIL_COND(!p_body_a->in_space()); 11 | ERR_FAIL_COND(p_body_a->get_space() != p_body_b->get_space()); 12 | space = p_body_a->get_space(); 13 | 14 | Vector2 anchor_a = p_body_a->get_transform().affine_inverse().xform(p_anchor_a); 15 | Vector2 anchor_b = p_body_b->get_transform().affine_inverse().xform(p_anchor_b); 16 | 17 | distance_def.bodyIdA = p_body_a->get_body_id(); 18 | distance_def.bodyIdB = p_body_b->get_body_id(); 19 | distance_def.localAnchorA = to_box2d(anchor_a); 20 | distance_def.localAnchorB = to_box2d(anchor_b); 21 | 22 | distance_def.enableSpring = true; 23 | distance_def.length = to_box2d(50.0); 24 | distance_def.dampingRatio = 1.0; 25 | set_stiffness(20.0); 26 | 27 | distance_def.collideConnected = !disabled_collisions_between_bodies; 28 | distance_def.userData = this; 29 | 30 | joint_id = b2CreateDistanceJoint(space->get_world_id(), &distance_def); 31 | } 32 | 33 | void Box2DDampedSpringJoint2D::set_rest_length(float p_length) { 34 | distance_def.length = to_box2d(p_length); 35 | if (b2Joint_IsValid(joint_id)) { 36 | b2DistanceJoint_SetLength(joint_id, distance_def.length); 37 | } 38 | } 39 | 40 | void Box2DDampedSpringJoint2D::set_damping_ratio(float p_damping) { 41 | distance_def.dampingRatio = p_damping; 42 | if (b2Joint_IsValid(joint_id)) { 43 | b2DistanceJoint_SetSpringDampingRatio(joint_id, distance_def.dampingRatio); 44 | } 45 | } 46 | 47 | void Box2DDampedSpringJoint2D::set_stiffness(float p_stiffness) { 48 | stiffness = p_stiffness; 49 | update_stiffness(); 50 | } 51 | 52 | /// Box2D uses natural frequency for spring stiffness instead of a spring constant. 53 | /// This method converts the spring constant to a natural frequency, accounting for mass. 54 | void Box2DDampedSpringJoint2D::update_stiffness() { 55 | float mass_a = body_a->get_mass(); 56 | float mass_b = body_b->get_mass(); 57 | 58 | bool is_a_dynamic = body_a->is_dynamic() && mass_a > 0.0; 59 | bool is_b_dynamic = body_b->is_dynamic() && mass_b > 0.0; 60 | 61 | float mass; 62 | 63 | if (!is_a_dynamic && !is_b_dynamic) { 64 | return; 65 | } else if (is_a_dynamic && is_b_dynamic) { 66 | mass = (mass_a * mass_b) / (mass_a + mass_b); 67 | } else { 68 | mass = is_a_dynamic ? mass_a : mass_b; 69 | } 70 | 71 | distance_def.hertz = Math::sqrt(stiffness / mass) / (2.0 * Math_PI); 72 | 73 | if (b2Joint_IsValid(joint_id)) { 74 | b2DistanceJoint_SetSpringHertz(joint_id, distance_def.hertz); 75 | } 76 | } -------------------------------------------------------------------------------- /src/shapes/box2d_convex_polygon_shape_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_convex_polygon_shape_2d.h" 2 | #include "../bodies/box2d_collision_object_2d.h" 3 | #include 4 | 5 | void Box2DConvexPolygonShape2D::add_to_body(Box2DShapeInstance *p_instance) const { 6 | b2Polygon shape; 7 | if (!make_polygon(p_instance->get_global_transform(), data, shape)) { 8 | return; 9 | } 10 | b2ShapeDef shape_def = p_instance->get_shape_def(); 11 | b2ShapeId id = b2CreatePolygonShape(p_instance->get_collision_object()->get_body_id(), &shape_def, &shape); 12 | p_instance->add_shape_id(id); 13 | } 14 | 15 | int Box2DConvexPolygonShape2D::cast(const CastQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 16 | b2Polygon shape; 17 | if (!make_polygon(p_transform, data, shape)) { 18 | return 0; 19 | } 20 | return box2d_cast_shape(shape, p_query, p_results); 21 | } 22 | 23 | int Box2DConvexPolygonShape2D::overlap(const OverlapQuery &p_query, const Transform2D &p_transform, LocalVector &p_results) const { 24 | b2Polygon shape; 25 | if (!make_polygon(p_transform, data, shape)) { 26 | return 0; 27 | } 28 | return box2d_overlap_shape(shape, p_query, p_results); 29 | } 30 | 31 | bool Box2DConvexPolygonShape2D::make_polygon(const Transform2D &p_transform, const Variant &p_data, b2Polygon &p_polygon) { 32 | Variant::Type type = p_data.get_type(); 33 | #ifdef REAL_T_IS_DOUBLE 34 | ERR_FAIL_COND_V(type != Variant::PACKED_VECTOR2_ARRAY && type != Variant::PACKED_FLOAT64_ARRAY, false); 35 | #else 36 | ERR_FAIL_COND_V(type != Variant::PACKED_VECTOR2_ARRAY && type != Variant::PACKED_FLOAT32_ARRAY, false); 37 | #endif 38 | 39 | int point_count; 40 | b2Vec2 *points = nullptr; 41 | 42 | if (type == Variant::PACKED_VECTOR2_ARRAY) { 43 | PackedVector2Array arr = p_data; 44 | ERR_FAIL_COND_V(arr.is_empty(), false); 45 | 46 | point_count = arr.size(); 47 | points = memnew_arr(b2Vec2, point_count); 48 | for (int i = 0; i < point_count; i++) { 49 | points[i] = to_box2d(p_transform.xform(arr[i])); 50 | } 51 | } else { 52 | #ifdef REAL_T_IS_DOUBLE 53 | PackedFloat64Array arr = p_data; 54 | #else 55 | PackedFloat32Array arr = p_data; 56 | #endif 57 | ERR_FAIL_COND_V(arr.is_empty(), false); 58 | ERR_FAIL_COND_V(arr.size() % 4 == 0, false); 59 | 60 | point_count = arr.size() / 4; 61 | points = memnew_arr(b2Vec2, point_count); 62 | for (int i = 0; i < point_count; i += 2) { 63 | points[i] = to_box2d(p_transform.xform(Vector2(arr[i], arr[i + 1]))); 64 | } 65 | } 66 | 67 | ERR_FAIL_COND_V(point_count < 3, false); 68 | 69 | ERR_FAIL_COND_V_MSG( 70 | point_count > B2_MAX_POLYGON_VERTICES, 71 | false, 72 | "Box2D: Convex polygons cannot have more than " + itos(B2_MAX_POLYGON_VERTICES) + " vertices"); 73 | 74 | b2Hull hull = b2ComputeHull(points, point_count); 75 | 76 | memdelete_arr(points); 77 | 78 | ERR_FAIL_COND_V_MSG(hull.count == 0, false, "Box2D: Failed to compute convex hull for polygon"); 79 | 80 | p_polygon = b2MakePolygon(&hull, 0.0); 81 | 82 | return true; 83 | } -------------------------------------------------------------------------------- /src/bodies/box2d_direct_body_state_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "box2d_body_2d.h" 4 | #include 5 | 6 | using namespace godot; 7 | 8 | class Box2DBody2D; 9 | 10 | class Box2DDirectBodyState2D : public PhysicsDirectBodyState2DExtension { 11 | GDCLASS(Box2DDirectBodyState2D, PhysicsDirectBodyState2DExtension); 12 | 13 | Box2DBody2D *body = nullptr; 14 | 15 | protected: 16 | static void _bind_methods() {} 17 | 18 | public: 19 | Box2DDirectBodyState2D() = default; 20 | explicit Box2DDirectBodyState2D(Box2DBody2D *p_body) : 21 | body(p_body) {} 22 | 23 | Vector2 _get_total_gravity() const override; 24 | float _get_total_angular_damp() const override; 25 | float _get_total_linear_damp() const override; 26 | 27 | Vector2 _get_center_of_mass() const override; 28 | Vector2 _get_center_of_mass_local() const override; 29 | float _get_inverse_mass() const override; 30 | float _get_inverse_inertia() const override; 31 | 32 | void _set_linear_velocity(const Vector2 &p_velocity) override; 33 | Vector2 _get_linear_velocity() const override; 34 | 35 | void _set_angular_velocity(float p_velocity) override; 36 | float _get_angular_velocity() const override; 37 | 38 | void _set_transform(const Transform2D &p_transform) override; 39 | Transform2D _get_transform() const override; 40 | 41 | Vector2 _get_velocity_at_local_position(const Vector2 &p_position) const override; 42 | 43 | void _apply_central_impulse(const Vector2 &p_impulse) override; 44 | void _apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position = Vector2()) override; 45 | void _apply_torque_impulse(float p_torque) override; 46 | 47 | void _apply_central_force(const Vector2 &p_force) override; 48 | void _apply_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()) override; 49 | void _apply_torque(float p_torque) override; 50 | 51 | void _add_constant_central_force(const Vector2 &p_force) override; 52 | void _add_constant_force(const Vector2 &p_force, const Vector2 &p_position = Vector2()) override; 53 | void _add_constant_torque(float p_torque) override; 54 | void _set_constant_force(const Vector2 &p_force) override; 55 | Vector2 _get_constant_force() const override; 56 | void _set_constant_torque(float p_torque) override; 57 | float _get_constant_torque() const override; 58 | 59 | void _set_sleep_state(bool p_enable) override; 60 | bool _is_sleeping() const override; 61 | 62 | int _get_contact_count() const override; 63 | Vector2 _get_contact_local_position(int p_contact_idx) const override; 64 | Vector2 _get_contact_local_normal(int p_contact_idx) const override; 65 | int _get_contact_local_shape(int p_contact_idx) const override; 66 | RID _get_contact_collider(int p_contact_idx) const override; 67 | Vector2 _get_contact_collider_position(int p_contact_idx) const override; 68 | uint64_t _get_contact_collider_id(int p_contact_idx) const override; 69 | int _get_contact_collider_shape(int p_contact_idx) const override; 70 | Vector2 _get_contact_impulse(int p_contact_idx) const override; 71 | Vector2 _get_contact_collider_velocity_at_position(int p_contact_idx) const override; 72 | 73 | PhysicsDirectSpaceState2D *_get_space_state() override; 74 | 75 | float _get_step() const override; 76 | }; -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | from methods import print_error 6 | 7 | 8 | libname = "godot-box2c" 9 | projectdir = "demo" 10 | 11 | localEnv = Environment(tools=["default"], PLATFORM="") 12 | 13 | # Build profiles can be used to decrease compile times. 14 | # You can either specify "disabled_classes", OR 15 | # explicitly specify "enabled_classes" which disables all other classes. 16 | # Modify the example file as needed and uncomment the line below or 17 | # manually specify the build_profile parameter when running SCons. 18 | 19 | # localEnv["build_profile"] = "build_profile.json" 20 | 21 | customs = ["custom.py"] 22 | customs = [os.path.abspath(path) for path in customs] 23 | 24 | opts = Variables(customs, ARGUMENTS) 25 | opts.Add(BoolVariable("tracy_enabled", "Enable tracy profiler", False)) 26 | opts.Update(localEnv) 27 | 28 | Help(opts.GenerateHelpText(localEnv)) 29 | 30 | env = localEnv.Clone() 31 | 32 | if not (os.path.isdir("godot-cpp") and os.listdir("godot-cpp")): 33 | print_error("""godot-cpp is not available within this folder, as Git submodules haven't been initialized. 34 | Run the following command to download godot-cpp: 35 | 36 | git submodule update --init --recursive""") 37 | sys.exit(1) 38 | 39 | if not (os.path.isdir("thirdparty/box2d") and os.listdir("thirdparty/box2d")): 40 | print_error("""thirdparty/box2d is not available within this folder, as Git submodules haven't been initialized. 41 | Run the following command to download box2d: 42 | 43 | git submodule update --init --recursive""") 44 | sys.exit(1) 45 | 46 | env = SConscript("godot-cpp/SConstruct", {"env": env, "customs": customs}) 47 | 48 | 49 | env.Append(CPPPATH=["src/"]) 50 | sources = [ 51 | Glob("src/*.cpp"), 52 | Glob("src/servers/*.cpp"), 53 | Glob("src/spaces/*.cpp"), 54 | Glob("src/shapes/*.cpp"), 55 | Glob("src/bodies/*.cpp"), 56 | Glob("src/joints/*.cpp") 57 | ] 58 | 59 | # Add box2d as static library 60 | if env["CC"] == "cl": 61 | env.Append(CFLAGS=["/std:c17"]) 62 | 63 | if env["CC"].endswith("gcc") or env["CC"].endswith("clang"): 64 | env.Append(CFLAGS=["-ffp-contract=off"]) 65 | 66 | if env["CC"] == "emcc": 67 | env.Append(CFLAGS=["-msimd128", "-msse2"]) 68 | 69 | env.Append(CPPPATH=["thirdparty/box2d/include/"]) 70 | box2d_lib = env.StaticLibrary("box2d", Glob("thirdparty/box2d/src/*.c")) 71 | env.Append(LIBS=[box2d_lib]) 72 | 73 | 74 | if env["target"] in ["editor", "template_debug"]: 75 | # Tracy 76 | if env["tracy_enabled"]: 77 | env.Append(CPPDEFINES=["TRACY_ENABLE", "TRACY_ON_DEMAND", "TRACY_DELAYED_INIT", "TRACY_MANUAL_LIFETIME"]) 78 | sources.append("thirdparty/tracy/public/TracyClient.cpp") 79 | 80 | try: 81 | doc_data = env.GodotCPPDocData("src/gen/doc_data.gen.cpp", source=Glob("doc_classes/*.xml")) 82 | sources.append(doc_data) 83 | except AttributeError: 84 | print("Not including class reference as we're targeting a pre-4.3 baseline.") 85 | 86 | suffix = env['suffix'].replace(".dev", "").replace(".universal", "") 87 | 88 | lib_filename = "{}{}{}{}".format(env.subst('$SHLIBPREFIX'), libname, suffix, env.subst('$SHLIBSUFFIX')) 89 | 90 | library = env.SharedLibrary( 91 | "bin/{}/{}".format(env['platform'], lib_filename), 92 | source=sources, 93 | ) 94 | 95 | copy = env.Install("{}/bin/{}/".format(projectdir, env["platform"]), library) 96 | 97 | default_args = [library, copy] 98 | Default(*default_args) -------------------------------------------------------------------------------- /src/spaces/box2d_query.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "box2d_query.h" 3 | #include "../bodies/box2d_collision_object_2d.h" 4 | 5 | bool BodyQueryFilter::is_excluded(const Box2DCollisionObject2D *p_object) const { 6 | return B2_ID_EQUALS(p_object->get_body_id(), body_id); 7 | } 8 | 9 | bool SpaceStateQueryFilter::is_excluded(const Box2DCollisionObject2D *p_object) const { 10 | return space_state->is_body_excluded_from_query(p_object->get_rid()); 11 | } 12 | 13 | bool ArrayQueryFilter::is_excluded(const Box2DCollisionObject2D *p_object) const { 14 | return exclude.has(p_object->get_rid()); 15 | } 16 | 17 | int box2d_cast_shape(const Box2DShapePrimitive &p_shape, const CastQuery p_query, LocalVector &p_results) { 18 | CastQueryCollector collector(p_query, p_results); 19 | b2ShapeProxy proxy = p_shape.inflated(p_query.margin).get_proxy(); 20 | b2World_CastShape(p_query.world, &proxy, to_box2d(p_query.translation), p_query.filter.box2d_filter, cast_callback, &collector); 21 | return collector.count; 22 | } 23 | 24 | int box2d_overlap_shape(const Box2DShapePrimitive &p_shape, const OverlapQuery p_query, LocalVector &p_results) { 25 | Box2DShapePrimitive shape = p_shape.inflated(p_query.margin); 26 | b2ShapeProxy proxy = shape.get_proxy(); 27 | OverlapQueryCollector collector(p_query, p_results, shape); 28 | b2World_OverlapShape(p_query.world, &proxy, p_query.filter.box2d_filter, overlap_callback, &collector); 29 | return collector.count; 30 | } 31 | 32 | /// context = OverlapQueryCollector 33 | bool overlap_callback(b2ShapeId shapeId, void *context) { 34 | OverlapQueryCollector *collector = static_cast(context); 35 | b2BodyId body_id = b2Shape_GetBody(shapeId); 36 | Box2DCollisionObject2D *object = static_cast(b2Body_GetUserData(body_id)); 37 | Box2DShapeInstance *shape = static_cast(b2Shape_GetUserData(shapeId)); 38 | 39 | if (!collector->filter.is_excluded(object)) { 40 | collector->results.push_back(ShapeOverlap{ object, shape, shapeId, collector->shape }); 41 | collector->count++; 42 | } 43 | 44 | return collector->results.size() < collector->max_results; 45 | } 46 | 47 | /// context = CastQueryCollector 48 | float cast_callback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void *context) { 49 | CastQueryCollector *collector = static_cast(context); 50 | 51 | b2BodyId body_id = b2Shape_GetBody(shapeId); 52 | Box2DCollisionObject2D *object = static_cast(b2Body_GetUserData(body_id)); 53 | Box2DShapeInstance *shape = static_cast(b2Shape_GetUserData(shapeId)); 54 | 55 | if (collector->filter.is_excluded(object)) { 56 | return -1; 57 | } 58 | 59 | if (fraction <= 0.0f && collector->ignore_initial_overlaps) { 60 | return -1; 61 | } 62 | 63 | collector->results.push_back(CastHit{ object, shape, shapeId, to_godot(point), to_godot(normal), fraction }); 64 | collector->count++; 65 | 66 | if (collector->find_nearest) { 67 | return fraction; 68 | } else if (collector->results.size() >= collector->max_results) { 69 | return 0; 70 | } 71 | 72 | return 1; 73 | } 74 | 75 | int find_nearest_cast_hit(LocalVector &p_results) { 76 | ERR_FAIL_COND_V(p_results.size() == 0, -1); 77 | int nearest_index = 0; 78 | for (int i = 0; i < p_results.size(); i++) { 79 | if (p_results[i].fraction < p_results[nearest_index].fraction) { 80 | nearest_index = i; 81 | } 82 | } 83 | return nearest_index; 84 | } 85 | -------------------------------------------------------------------------------- /demo/Joints.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://chjjbg4uqmayi"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://cunci2twkihny" path="res://scenes/Robot.tscn" id="2_dm0gm"] 4 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="2_nwtlb"] 5 | 6 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_7hk3i"] 7 | size = Vector2(634, 132) 8 | 9 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_tnpra"] 10 | radius = 38.0 11 | height = 270.0 12 | 13 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_fs63m"] 14 | size = Vector2(113, 158) 15 | 16 | [sub_resource type="CircleShape2D" id="CircleShape2D_xbkjp"] 17 | radius = 49.0408 18 | 19 | [node name="Joints" type="Node2D"] 20 | 21 | [node name="StaticBody2D" type="StaticBody2D" parent="."] 22 | position = Vector2(634, 155) 23 | 24 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] 25 | position = Vector2(-4, -1) 26 | shape = SubResource("RectangleShape2D_7hk3i") 27 | 28 | [node name="RigidBody2D" type="RigidBody2D" parent="."] 29 | position = Vector2(364, 311) 30 | rotation = 0.789654 31 | 32 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D"] 33 | position = Vector2(-21.2853, -16.8801) 34 | rotation = 0.142366 35 | shape = SubResource("CapsuleShape2D_tnpra") 36 | 37 | [node name="PinJoint2D" type="PinJoint2D" parent="."] 38 | position = Vector2(455, 215) 39 | node_a = NodePath("../StaticBody2D") 40 | node_b = NodePath("../RigidBody2D") 41 | angular_limit_lower = -0.872665 42 | angular_limit_upper = 0.872665 43 | motor_target_velocity = 1.74533 44 | 45 | [node name="GrooveJoint2D" type="GrooveJoint2D" parent="."] 46 | position = Vector2(433, 454) 47 | rotation = -1.78473 48 | node_a = NodePath("../StaticBody2D") 49 | node_b = NodePath("../RigidBody2D2") 50 | length = 370.0 51 | initial_offset = 236.0 52 | 53 | [node name="RigidBody2D2" type="RigidBody2D" parent="."] 54 | position = Vector2(663, 371) 55 | mass = 0.1 56 | 57 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D2"] 58 | position = Vector2(-2, 36) 59 | rotation = -0.225776 60 | shape = SubResource("RectangleShape2D_fs63m") 61 | 62 | [node name="DampedSpringJoint2D" type="DampedSpringJoint2D" parent="."] 63 | position = Vector2(674, 469) 64 | node_a = NodePath("../RigidBody2D2") 65 | node_b = NodePath("../Robot") 66 | rest_length = 50.0 67 | 68 | [node name="Robot" parent="." instance=ExtResource("2_dm0gm")] 69 | position = Vector2(673, 646) 70 | mass = 0.05 71 | 72 | [node name="Camera2D" parent="." instance=ExtResource("2_nwtlb")] 73 | position = Vector2(665, 466) 74 | zoom = Vector2(1, 1) 75 | 76 | [node name="DampedSpringJoint2D2" type="DampedSpringJoint2D" parent="."] 77 | position = Vector2(1259, 267) 78 | node_a = NodePath("../RigidBody2D3") 79 | node_b = NodePath("../StaticBody2D") 80 | stiffness = 50.0 81 | 82 | [node name="DampedSpringJoint2D3" type="DampedSpringJoint2D" parent="."] 83 | position = Vector2(1260, 391) 84 | node_a = NodePath("../RigidBody2D3") 85 | node_b = NodePath("../RigidBody2D4") 86 | rest_length = 20.0 87 | stiffness = 10.0 88 | 89 | [node name="RigidBody2D3" type="RigidBody2D" parent="."] 90 | position = Vector2(1262, 331) 91 | 92 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D3"] 93 | shape = SubResource("CircleShape2D_xbkjp") 94 | 95 | [node name="RigidBody2D4" type="RigidBody2D" parent="."] 96 | position = Vector2(1261, 453) 97 | 98 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D4"] 99 | shape = SubResource("CircleShape2D_xbkjp") 100 | -------------------------------------------------------------------------------- /src/spaces/box2d_space_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../bodies/box2d_area_2d.h" 4 | #include "../bodies/box2d_body_2d.h" 5 | #include "../box2d_globals.h" 6 | #include "box2d_physics_direct_space_state_2d.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace godot; 14 | 15 | struct Box2DTaskData { 16 | WorkerThreadPool::GroupID group_id; 17 | void *task_context; 18 | b2TaskCallback *task; 19 | int32_t item_count; 20 | int32_t task_count; 21 | }; 22 | 23 | class Box2DDirectSpaceState2D; 24 | 25 | class Box2DSpace2D { 26 | public: 27 | Box2DSpace2D(); 28 | ~Box2DSpace2D(); 29 | 30 | void step(float p_step); 31 | 32 | bool is_locked() const { return locked; } 33 | 34 | int get_max_tasks() const { return max_tasks; } 35 | 36 | void sync_state(); 37 | 38 | RID get_rid() const { return rid; } 39 | 40 | void set_rid(const RID &p_rid) { rid = p_rid; } 41 | 42 | Box2DDirectSpaceState2D *get_direct_state(); 43 | 44 | b2WorldId get_world_id() const { return world_id; } 45 | 46 | float get_last_step() const { return last_step; } 47 | 48 | int get_debug_contact_count() { return debug_contact_count; }; 49 | 50 | PackedVector2Array &get_debug_contacts() { return debug_contacts; }; 51 | 52 | void set_max_debug_contacts(int p_count) { debug_contacts.resize(p_count); } 53 | 54 | void add_debug_contact(Vector2 p_contact) { 55 | if (debug_contact_count < debug_contacts.size()) { 56 | debug_contacts[debug_contact_count++] = p_contact; 57 | } 58 | } 59 | 60 | void set_default_area(Box2DArea2D *p_area) { default_area = p_area; } 61 | Box2DArea2D *get_default_area() const { return default_area; } 62 | 63 | void set_default_gravity(Vector2 p_gravity); 64 | Vector2 get_default_gravity() { return default_gravity; } 65 | 66 | void default_area_linear_damp_changed() { linear_damp_changed = true; } 67 | void default_area_angular_damp_changed() { angular_damp_changed = true; } 68 | 69 | void add_active_area(Box2DArea2D *p_area) { 70 | areas_to_step.ordered_insert(p_area); 71 | } 72 | void remove_active_area(Box2DArea2D *p_area) { 73 | areas_to_step.erase(p_area); 74 | } 75 | 76 | void add_constant_force_body(Box2DBody2D *p_body) { 77 | constant_force_list.push_back(p_body); 78 | } 79 | void remove_constant_force_body(Box2DBody2D *p_body) { 80 | constant_force_list.erase(p_body); 81 | } 82 | 83 | void add_force_integration_body(Box2DBody2D *p_body) { 84 | force_integration_list.push_back(p_body); 85 | } 86 | void remove_force_integration_body(Box2DBody2D *p_body) { 87 | force_integration_list.erase(p_body); 88 | } 89 | 90 | void add_body_with_overrides(Box2DBody2D *p_body) { 91 | bodies_with_overrides.insert(p_body); 92 | } 93 | 94 | private: 95 | b2WorldDef world_def = b2DefaultWorldDef(); 96 | LocalVector constant_force_list; 97 | LocalVector force_integration_list; 98 | LocalVector areas_to_step; 99 | HashSet bodies_with_overrides; 100 | 101 | Box2DArea2D *default_area = nullptr; 102 | Box2DDirectSpaceState2D *direct_state = nullptr; 103 | 104 | b2WorldId world_id = b2_nullWorldId; 105 | RID rid; 106 | float last_step = -1.0; 107 | b2ContactEvents contact_events; 108 | PackedVector2Array debug_contacts; 109 | int debug_contact_count = 0; 110 | int max_tasks = -1; 111 | int substeps = 4; 112 | Vector2 default_gravity = Vector2(); 113 | 114 | bool linear_damp_changed = false; 115 | bool angular_damp_changed = false; 116 | 117 | bool locked = false; 118 | }; -------------------------------------------------------------------------------- /demo/sprites/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /demo/Body Properties.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=3 uid="uid://dh5toih2pg8nh"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="1_ua41v"] 4 | 5 | [sub_resource type="PhysicsMaterial" id="PhysicsMaterial_vm3h3"] 6 | 7 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_fg4r7"] 8 | radius = 26.0 9 | height = 811.863 10 | 11 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_okqu6"] 12 | size = Vector2(1825, 105) 13 | 14 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_6o1xy"] 15 | size = Vector2(451.025, 24.4965) 16 | 17 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_xnqn8"] 18 | radius = 81.0 19 | height = 304.0 20 | 21 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_vkp8a"] 22 | size = Vector2(172.015, 167.265) 23 | 24 | [sub_resource type="PhysicsMaterial" id="PhysicsMaterial_xe5ho"] 25 | friction = 0.5 26 | 27 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_1k8ba"] 28 | size = Vector2(93.8495, 98.3126) 29 | 30 | [node name="BodyProperties" type="Node2D"] 31 | 32 | [node name="StaticBody2D2" type="StaticBody2D" parent="."] 33 | position = Vector2(869, 450) 34 | rotation = -6.18717 35 | physics_material_override = SubResource("PhysicsMaterial_vm3h3") 36 | constant_linear_velocity = Vector2(100, 0) 37 | 38 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D2"] 39 | rotation = 1.27814 40 | shape = SubResource("CapsuleShape2D_fg4r7") 41 | 42 | [node name="StaticBody2D" type="AnimatableBody2D" parent="."] 43 | position = Vector2(821, 1000) 44 | 45 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] 46 | position = Vector2(60, 19) 47 | shape = SubResource("RectangleShape2D_okqu6") 48 | 49 | [node name="CollisionShape2D2" type="CollisionShape2D" parent="StaticBody2D"] 50 | position = Vector2(17, -251) 51 | rotation = -3.05649 52 | shape = SubResource("RectangleShape2D_6o1xy") 53 | one_way_collision = true 54 | 55 | [node name="Camera2D" parent="." instance=ExtResource("1_ua41v")] 56 | position = Vector2(861, 634) 57 | zoom = Vector2(1, 1) 58 | 59 | [node name="RigidBody2D" type="RigidBody2D" parent="."] 60 | position = Vector2(973, 548) 61 | rotation = 1.5107 62 | center_of_mass_mode = 1 63 | center_of_mass = Vector2(0, 150) 64 | 65 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D"] 66 | position = Vector2(58.1499, 32.5513) 67 | shape = SubResource("CapsuleShape2D_xnqn8") 68 | 69 | [node name="RigidBody2D2" type="RigidBody2D" parent="."] 70 | position = Vector2(245, 607) 71 | rotation = -0.530987 72 | constant_torque = 650.0 73 | 74 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D2"] 75 | position = Vector2(3.8147e-06, 9.53674e-07) 76 | shape = SubResource("RectangleShape2D_vkp8a") 77 | 78 | [node name="RigidBody2D3" type="RigidBody2D" parent="."] 79 | position = Vector2(1188, 268) 80 | rotation = -1.06197 81 | physics_material_override = SubResource("PhysicsMaterial_xe5ho") 82 | 83 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D3"] 84 | position = Vector2(44.9023, -264.365) 85 | shape = SubResource("RectangleShape2D_1k8ba") 86 | 87 | [node name="RigidBody2D4" type="RigidBody2D" parent="."] 88 | position = Vector2(740, 241) 89 | rotation = -2.12394 90 | 91 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D4"] 92 | position = Vector2(63.0164, 48.3108) 93 | rotation = 0.504093 94 | shape = SubResource("RectangleShape2D_1k8ba") 95 | 96 | [node name="RigidBody2D5" type="RigidBody2D" parent="."] 97 | position = Vector2(878, 298) 98 | rotation = -2.12394 99 | 100 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D5"] 101 | position = Vector2(63.0164, 48.3108) 102 | rotation = 0.504093 103 | shape = SubResource("RectangleShape2D_1k8ba") 104 | 105 | [node name="RigidBody2D6" type="RigidBody2D" parent="."] 106 | position = Vector2(606, 370) 107 | rotation = -2.12394 108 | 109 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D6"] 110 | position = Vector2(63.0164, 48.3108) 111 | rotation = 0.504093 112 | shape = SubResource("RectangleShape2D_1k8ba") 113 | -------------------------------------------------------------------------------- /.github/workflows/builds.yml: -------------------------------------------------------------------------------- 1 | name: Build GDExtension 2 | on: 3 | workflow_call: 4 | push: 5 | pull_request: 6 | merge_group: 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | # A build is made for every possible combination of parameters 14 | # You can add or remove entries from the arrays of each parameter to custimize which builds you want to run 15 | # See https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow 16 | target: 17 | [ 18 | { platform: linux, arch: x86_64, os: ubuntu-22.04 }, 19 | { platform: windows, arch: x86_64, os: windows-latest }, 20 | { platform: windows, arch: x86_32, os: windows-latest }, 21 | { platform: macos, arch: universal, os: macos-latest }, 22 | { platform: android, arch: arm64, os: ubuntu-22.04 }, 23 | { platform: android, arch: arm32, os: ubuntu-22.04 }, 24 | { platform: android, arch: x86_64, os: ubuntu-22.04 }, 25 | { platform: android, arch: x86_32, os: ubuntu-22.04 }, 26 | { platform: ios, arch: arm64, os: macos-latest }, 27 | { platform: web, arch: wasm32, os: ubuntu-22.04 }, 28 | ] 29 | target-type: [template_debug, template_release] 30 | float-precision: [single] 31 | 32 | runs-on: ${{ matrix.target.os }} 33 | steps: 34 | # Clone this repository 35 | - name: Checkout 36 | uses: actions/checkout@v4 37 | with: 38 | submodules: true 39 | 40 | # Lint 41 | #- name: Setup clang-format 42 | # shell: bash 43 | # run: | 44 | # python -m pip install clang-format 45 | #- name: Run clang-format 46 | # shell: bash 47 | # run: | 48 | # clang-format src/** --dry-run --Werror 49 | 50 | # Setup dependencies 51 | - name: Setup godot-cpp 52 | uses: ./godot-cpp/.github/actions/setup-godot-cpp 53 | with: 54 | platform: ${{ matrix.target.platform }} 55 | em-version: 3.1.62 56 | 57 | # Build GDExtension (with caches) 58 | 59 | - name: Restore .scons_cache 60 | uses: ./godot-cpp/.github/actions/godot-cache-restore 61 | with: 62 | scons-cache: ${{ github.workspace }}/.scons-cache/ 63 | cache-name: ${{ matrix.target.platform }}_${{ matrix.target.arch }}_${{ matrix.float-precision }}_${{ matrix.target-type }} 64 | 65 | - name: Build GDExtension Debug Build 66 | shell: sh 67 | env: 68 | SCONS_CACHE: ${{ github.workspace }}/.scons-cache/ 69 | run: | 70 | scons target=${{ matrix.target-type }} platform=${{ matrix.target.platform }} arch=${{ matrix.target.arch }} precision=${{ matrix.float-precision }} 71 | 72 | - name: Save .scons_cache 73 | uses: ./godot-cpp/.github/actions/godot-cache-save 74 | with: 75 | scons-cache: ${{ github.workspace }}/.scons-cache/ 76 | cache-name: ${{ matrix.target.platform }}_${{ matrix.target.arch }}_${{ matrix.float-precision }}_${{ matrix.target-type }} 77 | 78 | # Clean up compilation files 79 | - name: Windows - Delete compilation files 80 | if: ${{ matrix.target.platform == 'windows' }} 81 | shell: pwsh 82 | run: | 83 | Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force 84 | 85 | # Upload the build 86 | - name: Upload Artifact 87 | uses: actions/upload-artifact@v4 88 | with: 89 | name: godot-box2d-v3-${{ matrix.target.platform }}-${{ matrix.target.arch }}-${{ matrix.float-precision }}-${{ matrix.target-type }} 90 | path: | 91 | ${{ github.workspace }}/bin/** 92 | 93 | # Merges all the build artifacts together into a single godot-cpp-template artifact. 94 | # If you comment out this step, all the builds will be uploaded individually. 95 | merge: 96 | runs-on: ubuntu-22.04 97 | needs: build 98 | steps: 99 | - name: Merge Artifacts 100 | uses: actions/upload-artifact/merge@v4 101 | with: 102 | name: godot-box2d-v3 103 | pattern: godot-box2d-v3-* 104 | delete-merged: true 105 | -------------------------------------------------------------------------------- /docs/settings.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | This document lists the project settings exposed by this extension as well as relevant built-in project settings. 4 | 5 | ⚠️ Enable "Advanced Settings" in the top-right corner of the project settings window to see the settings listed here. 6 | 7 | ## Table of Contents 8 | 9 | - [Common](#common) 10 | - [2D](#2d) 11 | - [Godot Box2D 3D](#box2d) 12 | 13 | ## Common 14 | 15 | These settings are part of Godot's default project settings and can be found under `Physics -> Common`. 16 | 17 | | Category | Name | Supported | Notes | 18 | |----------|--------------------------|-----------|-------| 19 | | - | Physics Ticks per Second | ✅ | - | 20 | | - | Enable Object Picking | ❌ | - | 21 | 22 | ## 2D 23 | 24 | These settings are part of Godot's default project settings and can be found under `Physics -> 2D`. 25 | 26 | | Category | Name | Supported | Notes | 27 | |----------|-----------------------------|-----------|------------------------------------| 28 | | - | Run on Separate Thread | ❌ | - | 29 | | - | Physics Engine | ✅ | - | 30 | | - | Default Gravity | ✅ | - | 31 | | - | Default Gravity Vector | ✅ | - | 32 | | - | Default Linear Damp | ✅ | - | 33 | | - | Default Angular Damp | ✅ | - | 34 | | - | Sleep Threshold Linear | ❌ | - | 35 | | - | Sleep Threshold Angular | ❌ | - | 36 | | - | Time Before Sleep | ❌ | - | 37 | | Solver | Solver Iterations | ❌ | See extension settings [below](#box2d). | 38 | | Solver | Contact Recycle Radius | ❌ | - | 39 | | Solver | Contact Max Separation | ❌ | - | 40 | | Solver | Contact Max Allowed Penetration | ❌ | - | 41 | | Solver | Default Contact Bias | ❌ | - | 42 | 43 | ## Box2D 44 | 45 | These settings are exposed by Godot Jolt and can be found under `Physics -> Box 2D`. 46 | 47 | | Category | Name | Description | Notes | 48 | |----------|---------------------------|-------------|-------| 49 | | - | Substeps | Number of substeps to perform per physics update. Increasing this value will increase accuracy. | - | 50 | | - | Pixels per Meter | Adjusts the scale of the simulation. The default scale is `100 px / m` to match Godot's default gravity of `980`. | - | 51 | | Solver | Contact Hertz | Contact stiffness in cycles per second. Increasing this value increases the speed of overlap recovery, but can introduce jitter. | - | 52 | | Solver | Contact Damping Ratio | Contact bounciness. You can speed up overlap recovery by decreasing this with the trade-off that overlap resolution becomes more energetic. | - | 53 | | Solver | Joint Hertz | Joint stiffness in cycles per second. | - | 54 | | Solver | Joint Damping Ratio | Joint bounciness. | - | 55 | | Solver | Friction Mixing Rule | Godot calculates friction by taking the minimum of two friction values e.g. `min(frictionA, frictionB)`.
Box2D calculates friction by taking the geometric mean e.g. `sqrt(frictionA * frictionB)`.
Use `Minimum (Godot)` for parity with Godot Physics or `Geometric Mean (Box2D)` to account for both friction values. | - | 56 | | Solver | Restitution Mixing Rule | Godot calculates bounce by adding two bounce values and limiting the result to 1 e.g. `min(bounceA, bounceB, 1)`.
Box2D calculates bounce by taking the max of two bounce values e.g. `max(bounceA, bounceB)`.
Use `Additive (Godot)` for parity with Godot Physics or `Maximum (Box2D)` for bounce values higher than one. | - | 57 | | Advanced | Presolve Enabled | Godot Box2D v3 implements a number of Godot Physics features by calling a pre-solve function for every contact. This has a small cost and can be disabled.
**Collision Exceptions** and **One-way Collisions** for rigidbodies will not work without presolve enabled. | - | 58 | -------------------------------------------------------------------------------- /demo/Character.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=12 format=3 uid="uid://d4lt8s00uk8e7"] 2 | 3 | [ext_resource type="Script" path="res://scripts/override_timescale.gd" id="1_x1558"] 4 | [ext_resource type="Script" path="res://scripts/body_destroyer.gd" id="3_bghif"] 5 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="4_3gho3"] 6 | [ext_resource type="Script" path="res://scripts/default_character_2d.gd" id="4_5ouet"] 7 | [ext_resource type="Script" path="res://scripts/body_animator.gd" id="5_o5wcv"] 8 | 9 | [sub_resource type="CircleShape2D" id="CircleShape2D_k0kwk"] 10 | radius = 99.5038 11 | 12 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_q40ig"] 13 | size = Vector2(379, 94) 14 | 15 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_lj7go"] 16 | radius = 30.992 17 | height = 96.0049 18 | 19 | [sub_resource type="SegmentShape2D" id="SegmentShape2D_ttn6k"] 20 | a = Vector2(-100.012, -1.24411) 21 | b = Vector2(155.988, -2.17911) 22 | 23 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_jl1cf"] 24 | size = Vector2(103, 67) 25 | 26 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_hdxr1"] 27 | radius = 12.0 28 | height = 218.1 29 | 30 | [node name="Areas" type="Node2D"] 31 | script = ExtResource("1_x1558") 32 | 33 | [node name="StaticBody2D" type="AnimatableBody2D" parent="."] 34 | position = Vector2(1208, 482) 35 | 36 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] 37 | position = Vector2(-3, -29) 38 | shape = SubResource("CircleShape2D_k0kwk") 39 | 40 | [node name="CollisionShape2D2" type="CollisionShape2D" parent="StaticBody2D"] 41 | position = Vector2(-230.5, -12) 42 | rotation = -0.199228 43 | shape = SubResource("RectangleShape2D_q40ig") 44 | 45 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBody2D"] 46 | position = Vector2(-84, -66) 47 | rotation = 0.00033644 48 | polygon = PackedVector2Array(-881.028, -81.7036, -824.034, -99.7228, -753.027, -79.7467, -711.006, -18.7608, -550.007, -20.8149, -490.99, 29.1652, -377.995, 13.1272, -256.972, 84.0865, -561.955, 134.189, -924.982, 52.3112, -936.045, -132.685) 49 | 50 | [node name="Node2D" type="Node2D" parent="."] 51 | script = ExtResource("3_bghif") 52 | 53 | [node name="Camera2D" parent="." instance=ExtResource("4_3gho3")] 54 | position = Vector2(967, 500) 55 | zoom = Vector2(1, 1) 56 | 57 | [node name="CharacterBody2D" type="CharacterBody2D" parent="."] 58 | position = Vector2(1051, 262) 59 | rotation = 0.00260896 60 | floor_constant_speed = true 61 | floor_max_angle = 0.872665 62 | floor_snap_length = 30.0 63 | script = ExtResource("4_5ouet") 64 | 65 | [node name="CollisionShape2D2" type="CollisionShape2D" parent="CharacterBody2D"] 66 | shape = SubResource("CapsuleShape2D_lj7go") 67 | 68 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="CharacterBody2D"] 69 | position = Vector2(-0.0182624, -6.99998) 70 | polygon = PackedVector2Array(-102.06, -22.7338, -62.039, -14.8382, -54.9476, 20.1434, -87.9423, 22.2295, -112.974, 10.2948) 71 | 72 | [node name="StaticBody2D2" type="StaticBody2D" parent="."] 73 | position = Vector2(1433, 425) 74 | rotation = 3.0949 75 | 76 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D2"] 77 | rotation = -9.37053 78 | shape = SubResource("SegmentShape2D_ttn6k") 79 | one_way_collision = true 80 | 81 | [node name="CollisionShape2D2" type="CollisionShape2D" parent="StaticBody2D2"] 82 | position = Vector2(-101.609, 96.3622) 83 | rotation = -9.37053 84 | shape = SubResource("SegmentShape2D_ttn6k") 85 | one_way_collision = true 86 | 87 | [node name="CollisionShape2D3" type="CollisionShape2D" parent="StaticBody2D2"] 88 | position = Vector2(-198.942, 186.918) 89 | rotation = -9.37053 90 | shape = SubResource("SegmentShape2D_ttn6k") 91 | one_way_collision = true 92 | 93 | [node name="RigidBody2D" type="RigidBody2D" parent="."] 94 | position = Vector2(688, 111) 95 | 96 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D"] 97 | position = Vector2(-42.5, 14.5) 98 | shape = SubResource("RectangleShape2D_jl1cf") 99 | 100 | [node name="AnimatableBody2D" type="AnimatableBody2D" parent="."] 101 | position = Vector2(518, 238) 102 | rotation = 1.57989 103 | script = ExtResource("5_o5wcv") 104 | motion_type = 1 105 | 106 | [node name="CollisionShape2D" type="CollisionShape2D" parent="AnimatableBody2D"] 107 | rotation = 0.00240106 108 | shape = SubResource("CapsuleShape2D_hdxr1") 109 | -------------------------------------------------------------------------------- /demo/Areas.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=15 format=3 uid="uid://ced48acgmpi0n"] 2 | 3 | [ext_resource type="Script" path="res://scripts/override_timescale.gd" id="1_a6y3g"] 4 | [ext_resource type="Script" path="res://scripts/area_print_events.gd" id="1_m33uo"] 5 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="2_0dh5b"] 6 | [ext_resource type="Script" path="res://scripts/body_destroyer.gd" id="3_k3b74"] 7 | [ext_resource type="Script" path="res://scripts/move_down_static_body.gd" id="5_2dv5o"] 8 | [ext_resource type="Script" path="res://scripts/move_down_area.gd" id="6_ykdix"] 9 | 10 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_h7rvq"] 11 | size = Vector2(1825, 105) 12 | 13 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_dm7sd"] 14 | size = Vector2(336, 26.25) 15 | 16 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_qlqpe"] 17 | radius = 24.0 18 | height = 79.6821 19 | 20 | [sub_resource type="CircleShape2D" id="CircleShape2D_vey6j"] 21 | radius = 28.7924 22 | 23 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_lq7m4"] 24 | size = Vector2(66.9442, 71.9972) 25 | 26 | [sub_resource type="SegmentShape2D" id="SegmentShape2D_4vqp8"] 27 | a = Vector2(-57.7581, -116.194) 28 | b = Vector2(10.8633, 11.916) 29 | 30 | [sub_resource type="CircleShape2D" id="CircleShape2D_l5cai"] 31 | radius = 38.1182 32 | 33 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_xba0a"] 34 | size = Vector2(88, 77) 35 | 36 | [node name="Areas" type="Node2D"] 37 | script = ExtResource("1_a6y3g") 38 | time_scale = 0.25 39 | 40 | [node name="StaticBody2D" type="AnimatableBody2D" parent="."] 41 | position = Vector2(846, 836) 42 | 43 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] 44 | position = Vector2(60, 19) 45 | shape = SubResource("RectangleShape2D_h7rvq") 46 | 47 | [node name="Area2D" type="Area2D" parent="."] 48 | position = Vector2(851, 531) 49 | script = ExtResource("1_m33uo") 50 | 51 | [node name="CollisionShape2D2" type="CollisionShape2D" parent="Area2D"] 52 | position = Vector2(8, -151) 53 | shape = SubResource("RectangleShape2D_dm7sd") 54 | 55 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="Area2D"] 56 | position = Vector2(15, 57) 57 | polygon = PackedVector2Array(-149, -97, -28, -82, 120, -113, 28, -41, 142, 24, 29, -3, -36, -1, -146, 16, -21, -43) 58 | 59 | [node name="CollisionShape2D3" type="CollisionShape2D" parent="Area2D"] 60 | position = Vector2(11, 243) 61 | shape = SubResource("RectangleShape2D_dm7sd") 62 | 63 | [node name="RigidBody2D" type="RigidBody2D" parent="."] 64 | position = Vector2(845, 293) 65 | rotation = -2.97817 66 | 67 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D"] 68 | position = Vector2(39.7925, -4.53443) 69 | shape = SubResource("CapsuleShape2D_qlqpe") 70 | 71 | [node name="RigidBody2D2" type="RigidBody2D" parent="."] 72 | position = Vector2(961, 213) 73 | rotation = -1.54029 74 | 75 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D2"] 76 | position = Vector2(-8.44682, 18.2661) 77 | shape = SubResource("CapsuleShape2D_qlqpe") 78 | 79 | [node name="RigidBody2D3" type="RigidBody2D" parent="."] 80 | position = Vector2(840, 129) 81 | rotation = 1.79087 82 | 83 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D3"] 84 | position = Vector2(-22.2787, -30.8814) 85 | shape = SubResource("CapsuleShape2D_qlqpe") 86 | 87 | [node name="RigidBody2D4" type="RigidBody2D" parent="."] 88 | position = Vector2(846, -52) 89 | rotation = 1.79087 90 | 91 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D4"] 92 | position = Vector2(28.6733, 27.4015) 93 | shape = SubResource("CircleShape2D_vey6j") 94 | 95 | [node name="CollisionShape2D2" type="CollisionShape2D" parent="RigidBody2D4"] 96 | position = Vector2(-44.7248, -103.739) 97 | shape = SubResource("RectangleShape2D_lq7m4") 98 | 99 | [node name="CollisionShape2D3" type="CollisionShape2D" parent="RigidBody2D4"] 100 | position = Vector2(15.64, 14.9463) 101 | shape = SubResource("SegmentShape2D_4vqp8") 102 | 103 | [node name="Camera2D" parent="." instance=ExtResource("2_0dh5b")] 104 | position = Vector2(901, 499) 105 | zoom = Vector2(1, 1) 106 | 107 | [node name="Node2D" type="Node2D" parent="."] 108 | script = ExtResource("3_k3b74") 109 | 110 | [node name="StaticBody2D2" type="StaticBody2D" parent="."] 111 | position = Vector2(874, -208) 112 | script = ExtResource("5_2dv5o") 113 | 114 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D2"] 115 | shape = SubResource("CircleShape2D_l5cai") 116 | 117 | [node name="Area2D2" type="Area2D" parent="."] 118 | position = Vector2(834, -345) 119 | script = ExtResource("6_ykdix") 120 | 121 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D2"] 122 | position = Vector2(34, 28.5) 123 | shape = SubResource("RectangleShape2D_xba0a") 124 | -------------------------------------------------------------------------------- /.github/actions/build/action.yml: -------------------------------------------------------------------------------- 1 | name: GDExtension Build 2 | description: Build GDExtension 3 | 4 | inputs: 5 | platform: 6 | required: true 7 | description: Target platform. 8 | arch: 9 | required: true 10 | description: Target architecture. 11 | float-precision: 12 | default: 'single' 13 | description: Float precision (single or double). 14 | build-target-type: 15 | default: 'template_debug' 16 | description: Build type (template_debug or template_release). 17 | scons-cache: 18 | default: .scons-cache/ 19 | description: Scons cache location. 20 | em_version: 21 | default: 3.1.39 22 | description: Emscripten version. 23 | em_cache_folder: 24 | default: emsdk-cache 25 | description: Emscripten cache folder. 26 | 27 | runs: 28 | using: composite 29 | steps: 30 | # Android only 31 | - name: Android - Set up Java 17 32 | uses: actions/setup-java@v4 33 | if: ${{ inputs.platform == 'android' }} 34 | with: 35 | distribution: temurin 36 | java-version: 17 37 | 38 | - name: Android - Remove existing Android SDK, and set up ENV vars 39 | if: ${{ inputs.platform == 'android' }} 40 | shell: sh 41 | run: | 42 | sudo rm -r /usr/local/lib/android/sdk/** 43 | export ANDROID_HOME=/usr/local/lib/android/sdk 44 | export ANDROID_SDK_ROOT=$ANDROID_HOME 45 | export ANDROID_NDK_VERSION=23.2.8568313 46 | export ANDROID_NDK_ROOT=${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION} 47 | echo "ANDROID_HOME=$ANDROID_HOME" >> "$GITHUB_ENV" 48 | echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" >> "$GITHUB_ENV" 49 | echo "ANDROID_NDK_VERSION=$ANDROID_NDK_VERSION" >> "$GITHUB_ENV" 50 | echo "ANDROID_NDK_ROOT=$ANDROID_NDK_ROOT" >> "$GITHUB_ENV" 51 | 52 | - name: Android - Set up Android SDK 53 | if: ${{ inputs.platform == 'android' }} 54 | uses: android-actions/setup-android@v3 55 | with: 56 | packages: "ndk;${{ env.ANDROID_NDK_VERSION }} cmdline-tools;latest build-tools;34.0.0 platforms;android-34 cmake;3.22.1" 57 | # Linux only 58 | - name: Linux - dependencies 59 | if: ${{ inputs.platform == 'linux' }} 60 | shell: sh 61 | run: | 62 | sudo apt-get update -qq 63 | sudo apt-get install -qqq build-essential pkg-config 64 | # Web only 65 | - name: Web - Set up Emscripten latest 66 | if: ${{ inputs.platform == 'web' }} 67 | uses: mymindstorm/setup-emsdk@v13 68 | with: 69 | version: ${{ inputs.em_version }} 70 | actions-cache-folder: ${{ inputs.em_cache_folder }}.${{ inputs.float-precision }}.${{ inputs.build-target-type }} 71 | - name: Web - Verify Emscripten setup 72 | if: ${{ inputs.platform == 'web' }} 73 | shell: sh 74 | run: | 75 | emcc -v 76 | # Windows only 77 | - name: Windows - Setup MinGW for Windows/MinGW build 78 | uses: egor-tensin/setup-mingw@v2 79 | if: ${{ inputs.platform == 'windows' }} 80 | with: 81 | version: 12.2.0 82 | # Dependencies of godot 83 | # Use python 3.x release (works cross platform) 84 | - name: Set up Python 3.x 85 | uses: actions/setup-python@v5 86 | with: 87 | # Semantic version range syntax or exact version of a Python version 88 | python-version: "3.x" 89 | # Optional - x64 or x86 architecture, defaults to x64 90 | architecture: "x64" 91 | - name: Setup scons 92 | shell: bash 93 | run: | 94 | python -c "import sys; print(sys.version)" 95 | python -m pip install scons==4.4.0 96 | scons --version 97 | # Build 98 | - name: Cache .scons_cache 99 | uses: actions/cache@v4 100 | with: 101 | path: | 102 | ${{ github.workspace }}/${{ inputs.gdextension-location }}/${{ inputs.scons-cache }}/ 103 | ${{ github.workspace }}/${{ inputs.godot-cpp }}/${{ inputs.scons-cache }}/ 104 | key: ${{ inputs.platform }}_${{ inputs.arch }}_${{ inputs.float-precision }}_${{ inputs.build-target-type }}_cache 105 | # Build godot-cpp 106 | - name: Build godot-cpp Debug Build 107 | shell: sh 108 | env: 109 | SCONS_CACHE: ${{ github.workspace }}/${{ inputs.godot-cpp }}/${{ inputs.scons-cache }}/ 110 | run: | 111 | scons target=${{ inputs.build-target-type }} platform=${{ inputs.platform }} arch=${{ inputs.arch }} generate_bindings=yes precision=${{ inputs.float-precision }} 112 | working-directory: ${{ inputs.godot-cpp }} 113 | # Build gdextension 114 | - name: Build GDExtension Debug Build 115 | shell: sh 116 | env: 117 | SCONS_CACHE: ${{ github.workspace }}/${{ inputs.gdextension-location }}/${{ inputs.scons-cache }}/ 118 | run: | 119 | scons target=${{ inputs.build-target-type }} platform=${{ inputs.platform }} arch=${{ inputs.arch }} precision=${{ inputs.float-precision }} 120 | working-directory: ${{ inputs.gdextension-location }} 121 | -------------------------------------------------------------------------------- /src/bodies/box2d_collision_object_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "../box2d_project_settings.h" 5 | #include "../shapes/box2d_shape_2d.h" 6 | #include "../shapes/box2d_shape_instance.h" 7 | #include "../spaces/box2d_query.h" 8 | #include 9 | #include 10 | 11 | class Box2DSpace2D; 12 | class Box2DArea2D; 13 | class Box2DBody2D; 14 | 15 | class Box2DCollisionObject2D { 16 | public: 17 | enum Type { 18 | RIGIDBODY, 19 | AREA, 20 | }; 21 | 22 | explicit Box2DCollisionObject2D(Type p_type); 23 | 24 | Type get_type() const { return type; } 25 | 26 | bool is_area() const { return type == Type::AREA; } 27 | bool is_body() const { return type == Type::RIGIDBODY; } 28 | 29 | Box2DArea2D *as_area() { return is_area() ? reinterpret_cast(this) : nullptr; } 30 | Box2DBody2D *as_body() { return is_body() ? reinterpret_cast(this) : nullptr; } 31 | 32 | const Box2DArea2D *as_area() const { return is_area() ? reinterpret_cast(this) : nullptr; } 33 | const Box2DBody2D *as_body() const { return is_body() ? reinterpret_cast(this) : nullptr; } 34 | 35 | void free(); 36 | 37 | RID get_rid() const { return rid; } 38 | void set_rid(const RID &p_rid) { rid = p_rid; } 39 | 40 | void set_space(Box2DSpace2D *p_space); 41 | Box2DSpace2D *get_space() const { return space; } 42 | _FORCE_INLINE_ bool in_space() const { return space; } 43 | 44 | void set_mode(PhysicsServer2D::BodyMode p_mode); 45 | PhysicsServer2D::BodyMode get_mode() const { return mode; } 46 | bool is_dynamic() const { return mode > PhysicsServer2D::BODY_MODE_KINEMATIC; } 47 | bool is_static() const { return mode == PhysicsServer2D::BODY_MODE_STATIC; } 48 | 49 | void set_collision_layer(uint32_t p_layer); 50 | uint32_t get_collision_layer() { return shape_def.filter.categoryBits; } 51 | void set_collision_mask(uint32_t p_mask); 52 | uint32_t get_collision_mask() { return shape_def.filter.maskBits; } 53 | 54 | void set_transform(const Transform2D &p_transform, bool p_move_kinematic = false); 55 | Transform2D get_transform() const { return current_transform; } 56 | 57 | b2BodyId get_body_id() const { return body_id; } 58 | b2ShapeDef get_shape_def() const { return shape_def; } 59 | 60 | void add_shape(Box2DShape2D *p_shape, const Transform2D &p_transform, bool p_disabled); 61 | void set_shape(int p_index, Box2DShape2D *p_shape); 62 | void shape_updated(Box2DShape2D *p_shape); 63 | void remove_shape(int p_index); 64 | void remove_shape(Box2DShape2D *p_shape); 65 | void clear_shapes(); 66 | void reindex_all_shapes(); 67 | 68 | int32_t get_shape_count() const { return shapes.size(); } 69 | void set_shape_transform(int p_index, const Transform2D &p_transform); 70 | Transform2D get_shape_transform(int p_index) const; 71 | RID get_shape_rid(int p_index) const; 72 | void set_shape_disabled(int p_index, bool p_disabled); 73 | 74 | void set_instance_id(const ObjectID &p_instance_id) { instance_id = p_instance_id; } 75 | ObjectID get_instance_id() const { return instance_id; } 76 | 77 | void set_canvas_instance_id(const ObjectID &p_canvas_instance_id) { canvas_instance_id = p_canvas_instance_id; } 78 | ObjectID get_canvas_instance_id() const { return canvas_instance_id; } 79 | 80 | void set_user_data(const Variant &p_data) { user_data = p_data; } 81 | Variant get_user_data() const { return user_data; } 82 | 83 | virtual void shapes_changed() {}; 84 | 85 | struct CharacterCollideContext { 86 | b2ShapeId shape_id; 87 | b2Transform transform; 88 | Box2DShapePrimitive shape; 89 | LocalVector &results; 90 | }; 91 | 92 | struct CharacterCastContext { 93 | b2ShapeId shape_id; 94 | b2Transform transform; 95 | Box2DShapePrimitive shape; 96 | CharacterCastResult &result; 97 | Vector2 motion; 98 | float margin; 99 | }; 100 | 101 | int character_collide(const Transform2D &p_from, float p_margin, LocalVector &p_results); 102 | CharacterCastResult character_cast(const Transform2D &p_from, float p_margin, Vector2 p_motion); 103 | 104 | protected: 105 | void build_shape(Box2DShapeInstance &p_shape, bool p_shapes_changed = true); 106 | void rebuild_all_shapes(); 107 | 108 | virtual uint64_t modify_mask_bits(uint32_t p_mask) { return p_mask; } 109 | virtual uint64_t modify_layer_bits(uint32_t p_layer) { return p_layer; } 110 | 111 | virtual void on_added_to_space() {}; 112 | virtual void on_remove_from_space() {}; 113 | 114 | Type type = Type::RIGIDBODY; 115 | Variant user_data = Variant(); 116 | bool is_animatable_body = false; 117 | 118 | Box2DSpace2D *space = nullptr; 119 | RID rid; 120 | ObjectID instance_id; 121 | ObjectID canvas_instance_id; 122 | Transform2D current_transform; 123 | LocalVector shapes; 124 | PhysicsServer2D::BodyMode mode = PhysicsServer2D::BodyMode::BODY_MODE_STATIC; 125 | 126 | b2BodyDef body_def = b2DefaultBodyDef(); 127 | b2ShapeDef shape_def = b2DefaultShapeDef(); 128 | b2BodyId body_id = b2_nullBodyId; 129 | 130 | uint16_t generation = 0; 131 | 132 | bool _is_freed = false; 133 | }; 134 | -------------------------------------------------------------------------------- /src/spaces/box2d_query.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../box2d_globals.h" 4 | #include "box2d_physics_direct_space_state_2d.h" 5 | #include 6 | #include 7 | 8 | using namespace godot; 9 | 10 | class Box2DShapeInstance; 11 | class Box2DCollisionObject2D; 12 | 13 | class QueryFilter { 14 | public: 15 | b2QueryFilter box2d_filter = b2DefaultQueryFilter(); 16 | virtual bool is_excluded(const Box2DCollisionObject2D *p_object) const { return false; } 17 | }; 18 | 19 | class BodyQueryFilter : public QueryFilter { 20 | public: 21 | explicit BodyQueryFilter(b2BodyId p_body_id) : 22 | body_id(p_body_id) {} 23 | 24 | bool is_excluded(const Box2DCollisionObject2D *p_object) const override; 25 | 26 | b2BodyId body_id; 27 | }; 28 | 29 | class SpaceStateQueryFilter : public QueryFilter { 30 | public: 31 | explicit SpaceStateQueryFilter(Box2DDirectSpaceState2D *p_space_state, b2QueryFilter p_filter) { 32 | space_state = p_space_state; 33 | box2d_filter = p_filter; 34 | } 35 | 36 | bool is_excluded(const Box2DCollisionObject2D *p_object) const override; 37 | 38 | Box2DDirectSpaceState2D *space_state = nullptr; 39 | }; 40 | 41 | class ArrayQueryFilter : public QueryFilter { 42 | public: 43 | TypedArray exclude; 44 | 45 | explicit ArrayQueryFilter(TypedArray p_exclude, b2QueryFilter p_filter) { 46 | box2d_filter = p_filter; 47 | exclude = p_exclude; 48 | } 49 | 50 | bool is_excluded(const Box2DCollisionObject2D *p_object) const override; 51 | }; 52 | 53 | struct OverlapQuery { 54 | b2WorldId world; 55 | QueryFilter filter; 56 | int max_results = 0; 57 | float margin = 0.0f; 58 | }; 59 | 60 | struct CastQuery { 61 | b2WorldId world; 62 | QueryFilter filter; 63 | Vector2 translation; 64 | int max_results; 65 | bool find_nearest; 66 | bool ignore_intial_overlaps = false; 67 | float margin = 0.0f; 68 | }; 69 | 70 | /// Overlap query result 71 | struct ShapeOverlap { 72 | Box2DCollisionObject2D *object = nullptr; 73 | Box2DShapeInstance *shape = nullptr; 74 | b2ShapeId shape_id = b2_nullShapeId; 75 | Box2DShapePrimitive source_shape = {}; 76 | 77 | bool operator==(const ShapeOverlap &p_other) const { 78 | return B2_ID_EQUALS(p_other.shape_id, shape_id); 79 | } 80 | }; 81 | 82 | struct CharacterCollideResult { 83 | Vector2 point = Vector2(); 84 | Vector2 normal = Vector2(); 85 | float depth = 0.0f; 86 | b2ShapeId shape_id = b2_nullShapeId; 87 | Box2DShapeInstance *shape = nullptr; 88 | b2ShapeId other_shape_id = b2_nullShapeId; 89 | Box2DShapeInstance *other_shape = nullptr; 90 | 91 | bool operator<(const CharacterCollideResult &p_other) const { 92 | return p_other.depth < p_other.depth; 93 | } 94 | }; 95 | 96 | struct CharacterCastResult { 97 | bool hit = false; 98 | Vector2 point = Vector2(); 99 | Vector2 normal = Vector2(); 100 | float unsafe_fraction = 1.0f; 101 | b2ShapeId shape_id = b2_nullShapeId; 102 | Box2DShapeInstance *shape = nullptr; 103 | b2ShapeId other_shape_id = b2_nullShapeId; 104 | Box2DShapeInstance *other_shape = nullptr; 105 | }; 106 | 107 | /// Cast query result 108 | struct CastHit { 109 | Box2DCollisionObject2D *object = nullptr; 110 | Box2DShapeInstance *shape = nullptr; 111 | b2ShapeId shape_id = b2_nullShapeId; 112 | Vector2 point = Vector2(); 113 | Vector2 normal = Vector2(); 114 | float fraction = 1.0f; 115 | 116 | bool operator<(const CastHit &p_other) const { 117 | return fraction < p_other.fraction; 118 | } 119 | 120 | bool operator==(const CastHit &p_other) const { 121 | return B2_ID_EQUALS(p_other.shape_id, shape_id); 122 | } 123 | }; 124 | 125 | struct CastQueryCollector { 126 | int max_results = 0; 127 | int count = 0; 128 | bool find_nearest = false; 129 | bool ignore_initial_overlaps = false; 130 | const QueryFilter filter; 131 | LocalVector &results; 132 | 133 | explicit CastQueryCollector(const CastQuery &p_query, LocalVector &p_results) : 134 | results(p_results), 135 | max_results(p_query.max_results), 136 | filter(p_query.filter), 137 | find_nearest(p_query.find_nearest), 138 | ignore_initial_overlaps(p_query.ignore_intial_overlaps) {} 139 | 140 | explicit CastQueryCollector(int p_max_results, const QueryFilter p_filter, bool p_find_nearest, LocalVector &p_results) : 141 | results(p_results), max_results(p_max_results), filter(p_filter), find_nearest(p_find_nearest) {} 142 | }; 143 | 144 | struct OverlapQueryCollector { 145 | int max_results = 0; 146 | int count = 0; 147 | const QueryFilter filter; 148 | LocalVector &results; 149 | Box2DShapePrimitive shape = {}; 150 | 151 | explicit OverlapQueryCollector(const OverlapQuery &p_query, LocalVector &p_results, Box2DShapePrimitive p_shape) : 152 | results(p_results), max_results(p_query.max_results), filter(p_query.filter), shape(p_shape) {} 153 | 154 | explicit OverlapQueryCollector(int p_max_results, const QueryFilter p_filter, LocalVector &p_results) : 155 | results(p_results), max_results(p_max_results), filter(p_filter) {} 156 | }; 157 | 158 | bool overlap_callback(b2ShapeId shapeId, void *context); 159 | 160 | float cast_callback(b2ShapeId shapeId, b2Vec2 point, b2Vec2 normal, float fraction, void *context); 161 | 162 | int find_nearest_cast_hit(LocalVector &p_results); 163 | 164 | int box2d_cast_shape(const Box2DShapePrimitive &p_shape, const CastQuery p_query, LocalVector &p_results); 165 | 166 | int box2d_overlap_shape(const Box2DShapePrimitive &p_shape, const OverlapQuery p_query, LocalVector &p_results); 167 | -------------------------------------------------------------------------------- /src/box2d_globals.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_globals.h" 2 | 3 | float BOX2D_PIXELS_PER_METER = 1; 4 | float BOX2D_LINEAR_SLOP = 0.005f; 5 | 6 | // TODO: revisit, consider implementing Godot-style cast function 7 | float box2d_compute_safe_fraction(float p_unsafe_fraction, float p_total_distance, float p_amount) { 8 | if (p_amount <= 0.0f) { 9 | p_amount = 2.0f * to_godot(BOX2D_LINEAR_SLOP); 10 | } 11 | 12 | if (p_total_distance <= 0.0f) { 13 | return 0.0f; 14 | } 15 | 16 | if (p_unsafe_fraction >= 1.0f) { 17 | return 1.0f; 18 | } 19 | 20 | float distance = p_unsafe_fraction * p_total_distance; 21 | float adjusted_distance = Math::max(0.0f, distance - p_amount); 22 | 23 | return adjusted_distance / p_total_distance; 24 | } 25 | 26 | ShapeCollideResult box2d_collide_shapes( 27 | const Box2DShapePrimitive &p_shape_a, 28 | const b2Transform &xfa, 29 | const Box2DShapePrimitive &p_shape_b, 30 | const b2Transform &xfb, 31 | bool p_swapped) { 32 | b2ShapeType type_a = p_shape_a.type; 33 | b2ShapeType type_b = p_shape_b.type; 34 | 35 | b2Manifold manifold = { 0 }; 36 | 37 | switch (type_a) { 38 | case b2ShapeType::b2_capsuleShape: { 39 | b2Capsule a = p_shape_a.capsule; 40 | switch (type_b) { 41 | case b2ShapeType::b2_capsuleShape: { 42 | manifold = b2CollideCapsules(&a, xfa, &p_shape_b.capsule, xfb); 43 | break; 44 | } 45 | case b2ShapeType::b2_circleShape: { 46 | manifold = b2CollideCapsuleAndCircle(&a, xfa, &p_shape_b.circle, xfb); 47 | break; 48 | } 49 | case b2ShapeType::b2_polygonShape: 50 | case b2ShapeType::b2_segmentShape: 51 | case b2ShapeType::b2_chainSegmentShape: { 52 | return box2d_collide_shapes(p_shape_b, xfb, p_shape_a, xfa, true); 53 | } 54 | default: { 55 | ERR_FAIL_V({}); 56 | } 57 | } 58 | break; 59 | } 60 | case b2ShapeType::b2_circleShape: { 61 | b2Circle a = p_shape_a.circle; 62 | switch (type_b) { 63 | case b2ShapeType::b2_capsuleShape: 64 | case b2ShapeType::b2_polygonShape: 65 | case b2ShapeType::b2_segmentShape: 66 | case b2ShapeType::b2_chainSegmentShape: { 67 | return box2d_collide_shapes(p_shape_b, xfb, p_shape_a, xfa, true); 68 | } 69 | case b2ShapeType::b2_circleShape: { 70 | manifold = b2CollideCircles(&a, xfa, &p_shape_b.circle, xfb); 71 | break; 72 | } 73 | default: { 74 | ERR_FAIL_V({}); 75 | } 76 | } 77 | break; 78 | } 79 | case b2ShapeType::b2_polygonShape: { 80 | b2Polygon a = p_shape_a.polygon; 81 | switch (type_b) { 82 | case b2ShapeType::b2_capsuleShape: { 83 | manifold = b2CollidePolygonAndCapsule(&a, xfa, &p_shape_b.capsule, xfb); 84 | break; 85 | } 86 | case b2ShapeType::b2_circleShape: { 87 | manifold = b2CollidePolygonAndCircle(&a, xfa, &p_shape_b.circle, xfb); 88 | break; 89 | } 90 | case b2ShapeType::b2_polygonShape: { 91 | manifold = b2CollidePolygons(&a, xfa, &p_shape_b.polygon, xfb); 92 | break; 93 | } 94 | case b2ShapeType::b2_segmentShape: 95 | case b2ShapeType::b2_chainSegmentShape: { 96 | return box2d_collide_shapes(p_shape_b, xfb, p_shape_a, xfa, true); 97 | } 98 | default: { 99 | ERR_FAIL_V({}); 100 | } 101 | } 102 | break; 103 | } 104 | case b2ShapeType::b2_segmentShape: { 105 | b2Segment a = p_shape_a.segment; 106 | switch (type_b) { 107 | case b2ShapeType::b2_capsuleShape: { 108 | manifold = b2CollideSegmentAndCapsule(&a, xfa, &p_shape_b.capsule, xfb); 109 | break; 110 | } 111 | case b2ShapeType::b2_circleShape: { 112 | manifold = b2CollideSegmentAndCircle(&a, xfa, &p_shape_b.circle, xfb); 113 | break; 114 | } 115 | case b2ShapeType::b2_polygonShape: { 116 | manifold = b2CollideSegmentAndPolygon(&a, xfa, &p_shape_b.polygon, xfb); 117 | break; 118 | } 119 | case b2ShapeType::b2_segmentShape: 120 | case b2ShapeType::b2_chainSegmentShape: { 121 | return {}; 122 | } 123 | default: { 124 | ERR_FAIL_V({}); 125 | } 126 | } 127 | break; 128 | } 129 | case b2ShapeType::b2_chainSegmentShape: { 130 | b2ChainSegment a = p_shape_a.chain_segment; 131 | switch (type_b) { 132 | case b2ShapeType::b2_capsuleShape: { 133 | b2SimplexCache cache{ 0 }; 134 | manifold = b2CollideChainSegmentAndCapsule(&a, xfa, &p_shape_b.capsule, xfb, &cache); 135 | break; 136 | } 137 | case b2ShapeType::b2_circleShape: { 138 | manifold = b2CollideChainSegmentAndCircle(&a, xfa, &p_shape_b.circle, xfb); 139 | break; 140 | } 141 | case b2ShapeType::b2_polygonShape: { 142 | b2SimplexCache cache{ 0 }; 143 | manifold = b2CollideChainSegmentAndPolygon(&a, xfa, &p_shape_b.polygon, xfb, &cache); 144 | break; 145 | } 146 | case b2ShapeType::b2_segmentShape: 147 | case b2ShapeType::b2_chainSegmentShape: { 148 | return {}; 149 | } 150 | default: { 151 | ERR_FAIL_V({}); 152 | } 153 | } 154 | break; 155 | } 156 | default: { 157 | ERR_FAIL_V({}); 158 | } 159 | } 160 | 161 | ShapeCollideResult result; 162 | 163 | result.point_count = manifold.pointCount; 164 | 165 | if (result.point_count == 0) { 166 | return result; 167 | } 168 | 169 | result.normal = -to_godot_normalized(manifold.normal); 170 | 171 | if (p_swapped) { 172 | result.normal *= -1.0f; 173 | } 174 | 175 | for (int i = 0; i < manifold.pointCount; i++) { 176 | result.points[i].depth = -to_godot(manifold.points[i].separation); 177 | result.points[i].point = to_godot(manifold.points[i].point) + (0.5f * result.points[i].depth * result.normal); 178 | } 179 | 180 | return result; 181 | } -------------------------------------------------------------------------------- /src/bodies/box2d_area_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "box2d_collision_object_2d.h" 4 | #include 5 | 6 | using namespace godot; 7 | 8 | class Box2DBody2D; 9 | 10 | template 11 | bool integrate(T &p_value, const T &p_override_value, PhysicsServer2D::AreaSpaceOverrideMode p_mode) { 12 | switch (p_mode) { 13 | case PhysicsServer2D::AREA_SPACE_OVERRIDE_DISABLED: { 14 | return false; 15 | } 16 | case PhysicsServer2D::AREA_SPACE_OVERRIDE_COMBINE: { 17 | p_value += p_override_value; 18 | return false; 19 | } 20 | case PhysicsServer2D::AREA_SPACE_OVERRIDE_COMBINE_REPLACE: { 21 | p_value += p_override_value; 22 | return true; 23 | } 24 | case PhysicsServer2D::AREA_SPACE_OVERRIDE_REPLACE: { 25 | p_value = p_override_value; 26 | return true; 27 | } 28 | case PhysicsServer2D::AREA_SPACE_OVERRIDE_REPLACE_COMBINE: { 29 | p_value = p_override_value; 30 | return false; 31 | } 32 | default: 33 | ERR_FAIL_V(false); 34 | } 35 | } 36 | 37 | class Box2DArea2D final : public Box2DCollisionObject2D { 38 | public: 39 | struct ShapePair { 40 | Box2DShapeInstance *other_shape = nullptr; 41 | Box2DShapeInstance *self_shape = nullptr; 42 | 43 | static uint32_t hash(const ShapePair &p_pair) { 44 | uint32_t hash = hash_murmur3_one_64((uint64_t)p_pair.other_shape); 45 | hash = hash_murmur3_one_64((uint64_t)p_pair.self_shape, hash); 46 | return hash_fmix32(hash); 47 | } 48 | 49 | bool operator==(const ShapePair &other) const { 50 | return other_shape == other.other_shape && self_shape == other.self_shape; 51 | } 52 | }; 53 | 54 | struct ObjectAndOverlapCount { 55 | Box2DCollisionObject2D *object = nullptr; 56 | int count = 0; 57 | }; 58 | 59 | Box2DArea2D(); 60 | ~Box2DArea2D(); 61 | 62 | void apply_overrides(); 63 | 64 | void update_area_step_list(); 65 | 66 | void add_overlap(Box2DShapeInstance *p_other_shape, Box2DShapeInstance *p_self_shape); 67 | 68 | void remove_overlap(Box2DShapeInstance *p_other_shape, Box2DShapeInstance *p_self_shape); 69 | 70 | void update_overlaps(); 71 | 72 | void report_event( 73 | Type p_type, 74 | PhysicsServer2D::AreaBodyStatus p_status, 75 | Box2DCollisionObject2D *p_other_object, 76 | int32_t p_other_shape_index, 77 | int32_t p_self_shape_index); 78 | 79 | bool is_default_area() const; 80 | 81 | void set_gravity_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_mode); 82 | PhysicsServer2D::AreaSpaceOverrideMode get_gravity_override_mode() const { return override_gravity_mode; } 83 | 84 | void set_linear_damp_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_mode); 85 | PhysicsServer2D::AreaSpaceOverrideMode get_linear_damp_override_mode() const { return override_linear_damp_mode; } 86 | 87 | void set_angular_damp_override_mode(PhysicsServer2D::AreaSpaceOverrideMode p_mode); 88 | PhysicsServer2D::AreaSpaceOverrideMode get_angular_damp_override_mode() const { return override_angular_damp_mode; } 89 | 90 | void set_linear_damp(float p_damp); 91 | float get_linear_damp() const { return linear_damp; } 92 | 93 | void set_angular_damp(float p_damp); 94 | float get_angular_damp() const { return angular_damp; } 95 | 96 | void set_gravity_strength(float p_strength); 97 | float get_gravity_strength() const { return gravity_strength; } 98 | 99 | void set_gravity_direction(Vector2 p_direction); 100 | Vector2 get_gravity_direction() const { return gravity_direction; } 101 | 102 | void set_gravity_point_enabled(bool p_enabled); 103 | bool get_gravity_point_enabled() const { return gravity_point_enabled; } 104 | 105 | void set_gravity_point_unit_distance(float p_distance) { gravity_point_unit_distance = p_distance; } 106 | float get_gravity_point_unit_distance() const { return gravity_point_unit_distance; } 107 | 108 | Vector2 compute_gravity(Vector2 p_center_of_mass) const; 109 | 110 | void gravity_changed(); 111 | 112 | void set_priority(float p_priority); 113 | int get_priority() const { return priority; } 114 | 115 | void set_monitorable(bool p_monitorable); 116 | bool get_monitorable() const { return monitorable; } 117 | 118 | void set_body_monitor_callback(const Callable &p_callback) { body_monitor_callback = p_callback; } 119 | void set_area_monitor_callback(const Callable &p_callback) { area_monitor_callback = p_callback; } 120 | 121 | bool operator<(const Box2DArea2D &other) const { 122 | return get_priority() < other.get_priority(); 123 | } 124 | 125 | void shapes_changed() override; 126 | 127 | protected: 128 | void on_added_to_space() override; 129 | void on_remove_from_space() override; 130 | 131 | // `overlaps` is a HashMap to account for redundant sensor events from compound shapes. 132 | // value = number of simultaneous overlap events between two shape instances 133 | HashMap overlaps; 134 | HashMap object_overlap_count; 135 | 136 | uint64_t modify_mask_bits(uint32_t p_mask) override; 137 | 138 | int priority = 0; 139 | bool monitorable = false; 140 | Callable body_monitor_callback; 141 | Callable area_monitor_callback; 142 | 143 | float gravity_strength = 9.8; 144 | Vector2 gravity_direction = Vector2(0, -1); 145 | float linear_damp = 0.1; 146 | float angular_damp = 0.1; 147 | 148 | bool gravity_point_enabled = false; 149 | float gravity_point_unit_distance = 0.0; 150 | 151 | bool in_area_step_list = false; 152 | 153 | PhysicsServer2D::AreaSpaceOverrideMode override_gravity_mode = PhysicsServer2D::AREA_SPACE_OVERRIDE_DISABLED; 154 | PhysicsServer2D::AreaSpaceOverrideMode override_linear_damp_mode = PhysicsServer2D::AREA_SPACE_OVERRIDE_DISABLED; 155 | PhysicsServer2D::AreaSpaceOverrideMode override_angular_damp_mode = PhysicsServer2D::AREA_SPACE_OVERRIDE_DISABLED; 156 | }; -------------------------------------------------------------------------------- /src/bodies/box2d_direct_body_state_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_direct_body_state_2d.h" 2 | #include "../box2d_physics_server_2d.h" 3 | #include "../spaces/box2d_physics_direct_space_state_2d.h" 4 | #include "../spaces/box2d_query.h" 5 | 6 | Vector2 Box2DDirectBodyState2D::_get_total_gravity() const { 7 | ERR_FAIL_COND_V(!body->in_space(), Vector2()); 8 | Vector2 total = body->area_overrides.total_gravity; 9 | if (!body->area_overrides.skip_world_gravity) { 10 | total += body->get_space()->get_default_gravity(); 11 | } 12 | return total; 13 | } 14 | 15 | float Box2DDirectBodyState2D::_get_total_angular_damp() const { 16 | ERR_FAIL_COND_V(!body->in_space(), 0.0); 17 | return body->area_overrides.total_angular_damp; 18 | } 19 | 20 | float Box2DDirectBodyState2D::_get_total_linear_damp() const { 21 | ERR_FAIL_COND_V(!body->in_space(), 0.0); 22 | return body->area_overrides.total_linear_damp; 23 | } 24 | 25 | Vector2 Box2DDirectBodyState2D::_get_center_of_mass() const { 26 | return body->get_center_of_mass_global(); 27 | } 28 | 29 | Vector2 Box2DDirectBodyState2D::_get_center_of_mass_local() const { 30 | return body->get_center_of_mass(); 31 | } 32 | 33 | float Box2DDirectBodyState2D::_get_inverse_mass() const { 34 | return body->get_inverse_mass(); 35 | } 36 | 37 | float Box2DDirectBodyState2D::_get_inverse_inertia() const { 38 | return body->get_inverse_inertia(); 39 | } 40 | 41 | void Box2DDirectBodyState2D::_set_linear_velocity(const Vector2 &p_velocity) { 42 | body->set_linear_velocity(p_velocity); 43 | } 44 | 45 | Vector2 Box2DDirectBodyState2D::_get_linear_velocity() const { 46 | return body->get_linear_velocity(); 47 | } 48 | 49 | void Box2DDirectBodyState2D::_set_angular_velocity(float p_velocity) { 50 | body->set_angular_velocity(p_velocity); 51 | } 52 | 53 | float Box2DDirectBodyState2D::_get_angular_velocity() const { 54 | return body->get_angular_velocity(); 55 | } 56 | 57 | void Box2DDirectBodyState2D::_set_transform(const Transform2D &p_transform) { 58 | body->set_transform(p_transform); 59 | } 60 | 61 | Transform2D Box2DDirectBodyState2D::_get_transform() const { 62 | return body->get_transform(); 63 | } 64 | 65 | Vector2 Box2DDirectBodyState2D::_get_velocity_at_local_position(const Vector2 &p_position) const { 66 | return body->get_velocity_at_local_point(p_position); 67 | } 68 | 69 | void Box2DDirectBodyState2D::_apply_central_impulse(const Vector2 &p_impulse) { 70 | body->apply_impulse_center(p_impulse); 71 | } 72 | 73 | void Box2DDirectBodyState2D::_apply_impulse(const Vector2 &p_impulse, const Vector2 &p_position) { 74 | body->apply_impulse(p_impulse, p_position); 75 | } 76 | 77 | void Box2DDirectBodyState2D::_apply_torque_impulse(float p_torque) { 78 | body->apply_torque_impulse(p_torque); 79 | } 80 | 81 | void Box2DDirectBodyState2D::_apply_central_force(const Vector2 &p_force) { 82 | body->apply_central_force(p_force); 83 | } 84 | 85 | void Box2DDirectBodyState2D::_apply_force(const Vector2 &p_force, const Vector2 &p_position) { 86 | body->apply_force(p_force, p_position); 87 | } 88 | 89 | void Box2DDirectBodyState2D::_apply_torque(float p_torque) { 90 | body->apply_torque(p_torque); 91 | } 92 | 93 | void Box2DDirectBodyState2D::_add_constant_central_force(const Vector2 &p_force) { 94 | body->add_constant_central_force(p_force); 95 | } 96 | 97 | void Box2DDirectBodyState2D::_add_constant_force(const Vector2 &p_force, const Vector2 &p_position) { 98 | body->add_constant_force(p_force, p_position); 99 | } 100 | 101 | void Box2DDirectBodyState2D::_add_constant_torque(float p_torque) { 102 | body->add_constant_torque(p_torque); 103 | } 104 | 105 | void Box2DDirectBodyState2D::_set_constant_force(const Vector2 &p_force) { 106 | body->set_constant_force(p_force); 107 | } 108 | 109 | Vector2 Box2DDirectBodyState2D::_get_constant_force() const { 110 | return body->get_constant_force(); 111 | } 112 | 113 | void Box2DDirectBodyState2D::_set_constant_torque(float p_torque) { 114 | body->set_constant_torque(p_torque); 115 | } 116 | 117 | float Box2DDirectBodyState2D::_get_constant_torque() const { 118 | return body->get_constant_torque(); 119 | } 120 | 121 | void Box2DDirectBodyState2D::_set_sleep_state(bool p_enable) { 122 | body->set_sleep_state(p_enable); 123 | } 124 | 125 | bool Box2DDirectBodyState2D::_is_sleeping() const { 126 | return body->is_sleeping(); 127 | } 128 | 129 | int Box2DDirectBodyState2D::_get_contact_count() const { 130 | return body->get_contact_count(); 131 | } 132 | 133 | Vector2 Box2DDirectBodyState2D::_get_contact_local_position(int p_contact_idx) const { 134 | return body->get_contact_local_position(p_contact_idx); 135 | } 136 | 137 | Vector2 Box2DDirectBodyState2D::_get_contact_local_normal(int p_contact_idx) const { 138 | return body->get_contact_local_normal(p_contact_idx); 139 | } 140 | 141 | int Box2DDirectBodyState2D::_get_contact_local_shape(int p_contact_idx) const { 142 | return body->get_contact_local_shape(p_contact_idx); 143 | } 144 | 145 | RID Box2DDirectBodyState2D::_get_contact_collider(int p_contact_idx) const { 146 | return body->get_contact_collider(p_contact_idx); 147 | } 148 | 149 | Vector2 Box2DDirectBodyState2D::_get_contact_collider_position(int p_contact_idx) const { 150 | return body->get_contact_collider_position(p_contact_idx); 151 | } 152 | 153 | uint64_t Box2DDirectBodyState2D::_get_contact_collider_id(int p_contact_idx) const { 154 | return body->get_contact_collider_id(p_contact_idx); 155 | } 156 | 157 | int Box2DDirectBodyState2D::_get_contact_collider_shape(int p_contact_idx) const { 158 | return body->get_contact_collider_shape(p_contact_idx); 159 | } 160 | 161 | Vector2 Box2DDirectBodyState2D::_get_contact_impulse(int p_contact_idx) const { 162 | return body->get_contact_impulse(p_contact_idx); 163 | } 164 | 165 | Vector2 Box2DDirectBodyState2D::_get_contact_collider_velocity_at_position(int p_contact_idx) const { 166 | return body->get_contact_collider_velocity_at_position(p_contact_idx); 167 | } 168 | 169 | PhysicsDirectSpaceState2D *Box2DDirectBodyState2D::_get_space_state() { 170 | return body->get_space()->get_direct_state(); 171 | } 172 | 173 | float Box2DDirectBodyState2D::_get_step() const { 174 | return body->get_space()->get_last_step(); 175 | } -------------------------------------------------------------------------------- /src/box2d_project_settings.cpp: -------------------------------------------------------------------------------- 1 | #include "box2d_project_settings.h" 2 | 3 | constexpr char SUBSTEPS[] = "physics/box_2d/substeps"; 4 | constexpr char PIXELS_PER_METER[] = "physics/box_2d/pixels_per_meter"; 5 | 6 | constexpr char CONTACT_HERTZ[] = "physics/box_2d/solver/contact_hertz"; 7 | constexpr char CONTACT_DAMPING_RATIO[] = "physics/box_2d/solver/contact_damping_ratio"; 8 | constexpr char JOINT_HERTZ[] = "physics/box_2d/solver/joint_hertz"; 9 | constexpr char JOINT_DAMPING_RATIO[] = "physics/box_2d/solver/joint_damping_ratio"; 10 | constexpr char FRICTION_MIXING_RULE[] = "physics/box_2d/solver/friction_mixing_rule"; 11 | constexpr char RESTITUTION_MIXING_RULE[] = "physics/box_2d/solver/restitution_mixing_rule"; 12 | 13 | constexpr char PRESOLVE_ENABLED[] = "physics/box_2d/advanced/presolve_enabled"; 14 | 15 | constexpr char MAX_THREADS[] = "threading/worker_pool/max_threads"; 16 | 17 | template 18 | TType get_setting(const char *p_setting) { 19 | const ProjectSettings *project_settings = ProjectSettings::get_singleton(); 20 | const Variant setting_value = project_settings->get_setting_with_override(p_setting); 21 | const Variant::Type setting_type = setting_value.get_type(); 22 | const Variant::Type expected_type = Variant(TType()).get_type(); 23 | 24 | ERR_FAIL_COND_V_MSG( 25 | setting_type != expected_type, 26 | {}, 27 | vformat("Unexpected type for setting '%s'. Expected type '%s' but found '%s'.", p_setting, Variant::get_type_name(expected_type), Variant::get_type_name(setting_type))); 28 | 29 | return setting_value; 30 | } 31 | 32 | void register_setting( 33 | const String &p_name, 34 | const Variant &p_value, 35 | bool p_needs_restart, 36 | PropertyHint p_hint, 37 | const String &p_hint_string) { 38 | ProjectSettings *project_settings = ProjectSettings::get_singleton(); 39 | 40 | if (!project_settings->has_setting(p_name)) { 41 | project_settings->set(p_name, p_value); 42 | } 43 | 44 | Dictionary property_info; 45 | property_info["name"] = p_name; 46 | property_info["type"] = p_value.get_type(); 47 | property_info["hint"] = p_hint; 48 | property_info["hint_string"] = p_hint_string; 49 | 50 | project_settings->add_property_info(property_info); 51 | project_settings->set_initial_value(p_name, p_value); 52 | project_settings->set_restart_if_changed(p_name, p_needs_restart); 53 | 54 | // HACK(mihe): We want our settings to appear in the order we register them in, but if we start 55 | // the order at 0 we end up moving the entire `physics/` group to the top of the tree view, so 56 | // instead we give it a hefty starting order and increment from there, which seems to give us 57 | // the desired effect. 58 | static int32_t order = 1000000; 59 | 60 | project_settings->set_order(p_name, order++); 61 | } 62 | 63 | void register_setting_plain( 64 | const String &p_name, 65 | const Variant &p_value, 66 | bool p_needs_restart = false) { 67 | register_setting(p_name, p_value, p_needs_restart, PROPERTY_HINT_NONE, {}); 68 | } 69 | 70 | void register_setting_hinted( 71 | const String &p_name, 72 | const Variant &p_value, 73 | const String &p_hint_string, 74 | bool p_needs_restart = false) { 75 | register_setting(p_name, p_value, p_needs_restart, PROPERTY_HINT_NONE, p_hint_string); 76 | } 77 | 78 | void register_setting_ranged( 79 | const String &p_name, 80 | const Variant &p_value, 81 | const String &p_hint_string, 82 | bool p_needs_restart = false) { 83 | register_setting(p_name, p_value, p_needs_restart, PROPERTY_HINT_RANGE, p_hint_string); 84 | } 85 | 86 | void register_setting_enum( 87 | const String &p_name, 88 | const Variant &p_value, 89 | const String &p_hint_string, 90 | bool p_needs_restart = false) { 91 | register_setting(p_name, p_value, p_needs_restart, PROPERTY_HINT_ENUM, p_hint_string); 92 | } 93 | 94 | void Box2DProjectSettings::register_settings() { 95 | register_setting_ranged(SUBSTEPS, 4, U"1,8,or_greater"); 96 | register_setting_ranged(PIXELS_PER_METER, 100, U"1,500,or_greater,suffix:px / m", true); 97 | 98 | // Slightly different from Box2D defaults to match Godot Physics 99 | register_setting_plain(CONTACT_HERTZ, 30.0); 100 | register_setting_plain(CONTACT_DAMPING_RATIO, 10.0); 101 | 102 | register_setting_plain(JOINT_HERTZ, 60.0); 103 | register_setting_plain(JOINT_DAMPING_RATIO, 2.0); 104 | 105 | register_setting_enum(FRICTION_MIXING_RULE, MIXING_RULE_GODOT, "Minimum (Godot),Geometric Mean (Box2D)"); 106 | register_setting_enum(RESTITUTION_MIXING_RULE, MIXING_RULE_GODOT, "Additive (Godot),Maximum (Box2D)"); 107 | 108 | register_setting_plain(PRESOLVE_ENABLED, true, true); 109 | } 110 | 111 | int Box2DProjectSettings::get_pixels_per_meter() { 112 | static const auto value = get_setting(PIXELS_PER_METER); 113 | return value; 114 | } 115 | 116 | int Box2DProjectSettings::get_substeps() { 117 | static const auto value = get_setting(SUBSTEPS); 118 | return value; 119 | } 120 | 121 | float Box2DProjectSettings::get_contact_hertz() { 122 | static const auto value = get_setting(CONTACT_HERTZ); 123 | return value; 124 | } 125 | 126 | float Box2DProjectSettings::get_contact_damping_ratio() { 127 | static const auto value = get_setting(CONTACT_DAMPING_RATIO); 128 | return value; 129 | } 130 | 131 | float Box2DProjectSettings::get_joint_hertz() { 132 | static const auto value = get_setting(JOINT_HERTZ); 133 | return value; 134 | } 135 | 136 | float Box2DProjectSettings::get_joint_damping_ratio() { 137 | static const auto value = get_setting(JOINT_DAMPING_RATIO); 138 | return value; 139 | } 140 | 141 | bool Box2DProjectSettings::get_presolve_enabled() { 142 | static const auto value = get_setting(PRESOLVE_ENABLED); 143 | return value; 144 | } 145 | 146 | Box2DMixingRule Box2DProjectSettings::get_friction_mixing_rule() { 147 | static const auto value = get_setting(FRICTION_MIXING_RULE); 148 | ERR_FAIL_COND_V(value >= MIXING_RULE_MAX, MIXING_RULE_GODOT); 149 | return static_cast(value); 150 | } 151 | 152 | Box2DMixingRule Box2DProjectSettings::get_restitution_mixing_rule() { 153 | static const auto value = get_setting(RESTITUTION_MIXING_RULE); 154 | ERR_FAIL_COND_V(value >= MIXING_RULE_MAX, MIXING_RULE_GODOT); 155 | return static_cast(value); 156 | } 157 | 158 | int32_t Box2DProjectSettings::get_max_threads() { 159 | static const auto value = get_setting(MAX_THREADS); 160 | return value; 161 | } -------------------------------------------------------------------------------- /demo/scripts/query_tester.gd: -------------------------------------------------------------------------------- 1 | # @tool 2 | extends Node2D 3 | 4 | var selected_shape_rid: RID 5 | var circle_rid: RID 6 | var capsule_rid: RID 7 | var rectangle_rid: RID 8 | var polygon_rid: RID 9 | 10 | var query: QueryType = QueryType.RAYCAST 11 | 12 | var shape_color: Color 13 | var shape_type: PhysicsServer2D.ShapeType = PhysicsServer2D.ShapeType.SHAPE_CIRCLE 14 | 15 | var collide_areas = true 16 | var collide_bodies = true 17 | 18 | var polygon_points = [ 19 | Vector2(0, -40), 20 | Vector2(30, -30), 21 | Vector2(40, 0), 22 | Vector2(30, 30), 23 | Vector2(0, 40), 24 | Vector2(-25, 30), 25 | Vector2(-40, 0), 26 | Vector2(-25, -30) 27 | ] 28 | 29 | enum QueryType { 30 | RAYCAST, 31 | CAST_MOTION, 32 | COLLIDE_SHAPE, 33 | REST_INFO, 34 | } 35 | 36 | func _ready() -> void: 37 | circle_rid = PhysicsServer2D.circle_shape_create() 38 | PhysicsServer2D.shape_set_data(circle_rid, 30) 39 | 40 | selected_shape_rid = circle_rid 41 | 42 | capsule_rid = PhysicsServer2D.capsule_shape_create() 43 | PhysicsServer2D.shape_set_data(capsule_rid, [80, 30]) 44 | 45 | rectangle_rid = PhysicsServer2D.rectangle_shape_create() 46 | PhysicsServer2D.shape_set_data(rectangle_rid, Vector2(30, 30)) 47 | 48 | polygon_rid = PhysicsServer2D.convex_polygon_shape_create() 49 | PhysicsServer2D.shape_set_data(polygon_rid, PackedVector2Array(polygon_points)) 50 | 51 | shape_color = Color.DIM_GRAY 52 | shape_color.a = 0.6 53 | 54 | 55 | func _physics_process(delta: float) -> void: 56 | global_position = get_global_mouse_position() 57 | queue_redraw() 58 | 59 | 60 | func _draw() -> void: 61 | draw_set_transform_matrix(global_transform.affine_inverse()) 62 | 63 | match query: 64 | QueryType.RAYCAST: 65 | var ray_count = 1000 66 | for i in range(ray_count): 67 | var angle = i * (2 * PI / ray_count) 68 | var ray = Vector2.from_angle(angle) * 2000 69 | cast_ray(ray) 70 | QueryType.CAST_MOTION: 71 | var cast_count = 32 72 | for i in range(cast_count): 73 | var angle = i * (2 * PI / cast_count) 74 | var ray = Vector2.from_angle(angle) * 2000 75 | cast_motion(ray, selected_shape_rid) 76 | QueryType.COLLIDE_SHAPE: 77 | var cast_count = 1 78 | for i in range(cast_count): 79 | var angle = i * (2 * PI / cast_count) 80 | var ray = Vector2.from_angle(angle) * 300 81 | collide_shape(ray, selected_shape_rid) 82 | QueryType.REST_INFO: 83 | rest_info(selected_shape_rid) 84 | 85 | 86 | func cast_ray(ray: Vector2): 87 | var parameters = PhysicsRayQueryParameters2D.new() 88 | parameters.from = global_position 89 | parameters.to = global_position + ray 90 | parameters.hit_from_inside = true 91 | var result = get_world_2d().direct_space_state.intersect_ray(parameters) 92 | if result: 93 | #draw_line(result["position"], result["position"] + result["normal"] * 25, Color.RED) 94 | draw_line(global_position, result["position"], Color.BLACK) 95 | else: 96 | draw_line(global_position, global_position + ray, Color.BLACK) 97 | 98 | 99 | func cast_motion(ray: Vector2, shape_rid: RID) -> void: 100 | var parameters = PhysicsShapeQueryParameters2D.new() 101 | parameters.transform = global_transform 102 | parameters.motion = ray 103 | parameters.shape_rid = shape_rid 104 | parameters.collide_with_bodies = collide_bodies 105 | parameters.collide_with_areas = collide_areas 106 | var fractions = get_world_2d().direct_space_state.cast_motion(parameters) 107 | if not fractions: 108 | return 109 | var end_point = global_position + (ray * fractions[0]) 110 | draw_shape(selected_shape_rid, end_point) 111 | draw_line(global_position, end_point, Color.BLACK) 112 | 113 | 114 | func collide_shape(ray: Vector2, shape_rid: RID) -> void: 115 | var parameters = PhysicsShapeQueryParameters2D.new() 116 | parameters.transform = global_transform 117 | parameters.motion = ray 118 | parameters.shape_rid = shape_rid 119 | parameters.collide_with_bodies = collide_bodies 120 | parameters.collide_with_areas = collide_areas 121 | var end_point = global_position + ray 122 | draw_shape(selected_shape_rid, end_point) 123 | draw_line(global_position, end_point, Color.BLACK) 124 | var points = get_world_2d().direct_space_state.collide_shape(parameters) 125 | var zoom_scale = get_viewport_transform().affine_inverse().get_scale().x 126 | for i in range(0, points.size(), 2): 127 | draw_circle(points[i], zoom_scale * 6.0, Color.ORANGE) 128 | draw_circle(points[i + 1], zoom_scale * 3.0, Color.ROYAL_BLUE) 129 | 130 | 131 | func rest_info(shape_rid: RID) -> void: 132 | var parameters = PhysicsShapeQueryParameters2D.new() 133 | parameters.transform = global_transform 134 | parameters.shape_rid = shape_rid 135 | parameters.collide_with_bodies = collide_bodies 136 | parameters.collide_with_areas = collide_areas 137 | var result := get_world_2d().direct_space_state.get_rest_info(parameters) 138 | 139 | draw_shape(selected_shape_rid, global_position) 140 | if not result: 141 | return 142 | var zoom_scale = get_viewport_transform().affine_inverse().get_scale().x 143 | draw_circle(result["point"], zoom_scale * 6.0, Color.ORANGE) 144 | 145 | 146 | func test_point(): 147 | var parameters = PhysicsPointQueryParameters2D.new() 148 | parameters.position = global_position 149 | var results = get_world_2d().direct_space_state.intersect_point(parameters) 150 | 151 | 152 | func _on_circle_pressed() -> void: 153 | # shape_type = ShapeType.CIRCLE 154 | selected_shape_rid = circle_rid 155 | 156 | func _on_capsule_pressed() -> void: 157 | # shape_type = ShapeType.CAPSULE 158 | selected_shape_rid = capsule_rid 159 | 160 | func _on_rectangle_pressed() -> void: 161 | # shape_type = ShapeType.RECTANGLE 162 | selected_shape_rid = rectangle_rid 163 | 164 | func _on_polygon_pressed() -> void: 165 | # shape_type = ShapeType.POLYGON 166 | selected_shape_rid = polygon_rid 167 | 168 | 169 | func draw_shape(rid: RID, pos: Vector2) -> void: 170 | var type := PhysicsServer2D.shape_get_type(rid) 171 | var data = PhysicsServer2D.shape_get_data(rid) 172 | match type: 173 | PhysicsServer2D.ShapeType.SHAPE_CIRCLE: 174 | draw_circle(pos, 30, shape_color) 175 | PhysicsServer2D.ShapeType.SHAPE_RECTANGLE: 176 | draw_rect(Rect2(pos - Vector2(30, 30), Vector2(60, 60)), shape_color) 177 | PhysicsServer2D.ShapeType.SHAPE_CONVEX_POLYGON: 178 | for j in polygon_points.size(): 179 | var point_a = pos + polygon_points[j] 180 | var point_b = pos + polygon_points[(j + 1) % polygon_points.size()] 181 | draw_line(point_a, point_b, shape_color, 2.0) 182 | 183 | 184 | func _on_intersect_ray_pressed() -> void: 185 | query = QueryType.RAYCAST 186 | 187 | 188 | func _on_cast_motion_pressed() -> void: 189 | query = QueryType.CAST_MOTION 190 | 191 | 192 | func _on_collide_shape_pressed() -> void: 193 | query = QueryType.COLLIDE_SHAPE 194 | 195 | 196 | func _on_rest_info_pressed() -> void: 197 | query = QueryType.REST_INFO 198 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Commented out parameters are those with the same value as base LLVM style. 2 | # We can uncomment them if we want to change their value, or enforce the 3 | # chosen value in case the base style changes (last sync: Clang 14.0). 4 | --- 5 | ### General config, applies to all languages ### 6 | BasedOnStyle: LLVM 7 | AccessModifierOffset: -4 8 | AlignAfterOpenBracket: DontAlign 9 | # AlignArrayOfStructures: None 10 | # AlignConsecutiveMacros: None 11 | # AlignConsecutiveAssignments: None 12 | # AlignConsecutiveBitFields: None 13 | # AlignConsecutiveDeclarations: None 14 | # AlignEscapedNewlines: Right 15 | AlignOperands: DontAlign 16 | AlignTrailingComments: false 17 | # AllowAllArgumentsOnNextLine: true 18 | AllowAllParametersOfDeclarationOnNextLine: false 19 | # AllowShortEnumsOnASingleLine: true 20 | # AllowShortBlocksOnASingleLine: Never 21 | # AllowShortCaseLabelsOnASingleLine: false 22 | # AllowShortFunctionsOnASingleLine: All 23 | # AllowShortLambdasOnASingleLine: All 24 | # AllowShortIfStatementsOnASingleLine: Never 25 | # AllowShortLoopsOnASingleLine: false 26 | # AlwaysBreakAfterDefinitionReturnType: None 27 | # AlwaysBreakAfterReturnType: None 28 | # AlwaysBreakBeforeMultilineStrings: false 29 | # AlwaysBreakTemplateDeclarations: MultiLine 30 | # AttributeMacros: 31 | # - __capability 32 | # BinPackArguments: true 33 | # BinPackParameters: true 34 | # BraceWrapping: 35 | # AfterCaseLabel: false 36 | # AfterClass: false 37 | # AfterControlStatement: Never 38 | # AfterEnum: false 39 | # AfterFunction: false 40 | # AfterNamespace: false 41 | # AfterObjCDeclaration: false 42 | # AfterStruct: false 43 | # AfterUnion: false 44 | # AfterExternBlock: false 45 | # BeforeCatch: false 46 | # BeforeElse: false 47 | # BeforeLambdaBody: false 48 | # BeforeWhile: false 49 | # IndentBraces: false 50 | # SplitEmptyFunction: true 51 | # SplitEmptyRecord: true 52 | # SplitEmptyNamespace: true 53 | # BreakBeforeBinaryOperators: None 54 | # BreakBeforeConceptDeclarations: true 55 | # BreakBeforeBraces: Attach 56 | # BreakBeforeInheritanceComma: false 57 | # BreakInheritanceList: BeforeColon 58 | # BreakBeforeTernaryOperators: true 59 | # BreakConstructorInitializersBeforeComma: false 60 | BreakConstructorInitializers: AfterColon 61 | # BreakStringLiterals: true 62 | ColumnLimit: 0 63 | # CommentPragmas: '^ IWYU pragma:' 64 | # QualifierAlignment: Leave 65 | # CompactNamespaces: false 66 | ConstructorInitializerIndentWidth: 8 67 | ContinuationIndentWidth: 8 68 | Cpp11BracedListStyle: false 69 | # DeriveLineEnding: true 70 | # DerivePointerAlignment: false 71 | # DisableFormat: false 72 | # EmptyLineAfterAccessModifier: Never 73 | # EmptyLineBeforeAccessModifier: LogicalBlock 74 | # ExperimentalAutoDetectBinPacking: false 75 | # PackConstructorInitializers: BinPack 76 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 77 | # AllowAllConstructorInitializersOnNextLine: true 78 | # FixNamespaceComments: true 79 | # ForEachMacros: 80 | # - foreach 81 | # - Q_FOREACH 82 | # - BOOST_FOREACH 83 | # IfMacros: 84 | # - KJ_IF_MAYBE 85 | # IncludeBlocks: Preserve 86 | IncludeCategories: 87 | - Regex: '".*"' 88 | Priority: 1 89 | - Regex: '^<.*\.h>' 90 | Priority: 2 91 | - Regex: '^<.*' 92 | Priority: 3 93 | # IncludeIsMainRegex: '(Test)?$' 94 | # IncludeIsMainSourceRegex: '' 95 | # IndentAccessModifiers: false 96 | IndentCaseLabels: true 97 | # IndentCaseBlocks: false 98 | # IndentGotoLabels: true 99 | # IndentPPDirectives: None 100 | # IndentExternBlock: AfterExternBlock 101 | # IndentRequires: false 102 | IndentWidth: 4 103 | # IndentWrappedFunctionNames: false 104 | # InsertTrailingCommas: None 105 | # JavaScriptQuotes: Leave 106 | # JavaScriptWrapImports: true 107 | KeepEmptyLinesAtTheStartOfBlocks: false 108 | # LambdaBodyIndentation: Signature 109 | # MacroBlockBegin: '' 110 | # MacroBlockEnd: '' 111 | # MaxEmptyLinesToKeep: 1 112 | # NamespaceIndentation: None 113 | # PenaltyBreakAssignment: 2 114 | # PenaltyBreakBeforeFirstCallParameter: 19 115 | # PenaltyBreakComment: 300 116 | # PenaltyBreakFirstLessLess: 120 117 | # PenaltyBreakOpenParenthesis: 0 118 | # PenaltyBreakString: 1000 119 | # PenaltyBreakTemplateDeclaration: 10 120 | # PenaltyExcessCharacter: 1000000 121 | # PenaltyReturnTypeOnItsOwnLine: 60 122 | # PenaltyIndentedWhitespace: 0 123 | # PointerAlignment: Right 124 | # PPIndentWidth: -1 125 | # ReferenceAlignment: Pointer 126 | # ReflowComments: true 127 | # RemoveBracesLLVM: false 128 | # SeparateDefinitionBlocks: Leave 129 | # ShortNamespaceLines: 1 130 | # SortIncludes: CaseSensitive 131 | # SortJavaStaticImport: Before 132 | # SortUsingDeclarations: true 133 | # SpaceAfterCStyleCast: false 134 | # SpaceAfterLogicalNot: false 135 | # SpaceAfterTemplateKeyword: true 136 | # SpaceBeforeAssignmentOperators: true 137 | # SpaceBeforeCaseColon: false 138 | # SpaceBeforeCpp11BracedList: false 139 | # SpaceBeforeCtorInitializerColon: true 140 | # SpaceBeforeInheritanceColon: true 141 | # SpaceBeforeParens: ControlStatements 142 | # SpaceBeforeParensOptions: 143 | # AfterControlStatements: true 144 | # AfterForeachMacros: true 145 | # AfterFunctionDefinitionName: false 146 | # AfterFunctionDeclarationName: false 147 | # AfterIfMacros: true 148 | # AfterOverloadedOperator: false 149 | # BeforeNonEmptyParentheses: false 150 | # SpaceAroundPointerQualifiers: Default 151 | # SpaceBeforeRangeBasedForLoopColon: true 152 | # SpaceInEmptyBlock: false 153 | # SpaceInEmptyParentheses: false 154 | # SpacesBeforeTrailingComments: 1 155 | # SpacesInAngles: Never 156 | # SpacesInConditionalStatement: false 157 | # SpacesInContainerLiterals: true 158 | # SpacesInCStyleCastParentheses: false 159 | ## Godot TODO: We'll want to use a min of 1, but we need to see how to fix 160 | ## our comment capitalization at the same time. 161 | SpacesInLineCommentPrefix: 162 | Minimum: 0 163 | Maximum: -1 164 | # SpacesInParentheses: false 165 | # SpacesInSquareBrackets: false 166 | # SpaceBeforeSquareBrackets: false 167 | # BitFieldColonSpacing: Both 168 | # StatementAttributeLikeMacros: 169 | # - Q_EMIT 170 | # StatementMacros: 171 | # - Q_UNUSED 172 | # - QT_REQUIRE_VERSION 173 | TabWidth: 4 174 | # UseCRLF: false 175 | UseTab: Always 176 | # WhitespaceSensitiveMacros: 177 | # - STRINGIZE 178 | # - PP_STRINGIZE 179 | # - BOOST_PP_STRINGIZE 180 | # - NS_SWIFT_NAME 181 | # - CF_SWIFT_NAME 182 | --- 183 | ### C++ specific config ### 184 | Language: Cpp 185 | Standard: c++17 186 | --- 187 | ### ObjC specific config ### 188 | Language: ObjC 189 | # ObjCBinPackProtocolList: Auto 190 | ObjCBlockIndentWidth: 4 191 | # ObjCBreakBeforeNestedBlockParam: true 192 | # ObjCSpaceAfterProperty: false 193 | # ObjCSpaceBeforeProtocolList: true 194 | --- 195 | ### Java specific config ### 196 | Language: Java 197 | # BreakAfterJavaFieldAnnotations: false 198 | JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax'] 199 | ... 200 | -------------------------------------------------------------------------------- /.github/actions/sign/action.yml: -------------------------------------------------------------------------------- 1 | # This file incorporates work covered by the following copyright and permission notice: 2 | # 3 | # Copyright (c) Mikael Hermansson and Godot Jolt contributors. 4 | # Copyright (c) Dragos Daian. 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | # this software and associated documentation files (the "Software"), to deal in 8 | # the Software without restriction, including without limitation the rights to 9 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | # the Software, and to permit persons to whom the Software is furnished to do so, 11 | # subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | name: GDExtension Sign 24 | description: Sign Mac GDExtension 25 | 26 | inputs: 27 | FRAMEWORK_PATH: 28 | description: The path of the artifact. Eg. bin/addons/my_addon/bin/libmy_addon.macos.template_release.universal.framework 29 | required: true 30 | SIGN_FLAGS: 31 | description: The extra flags to use. Eg. --deep 32 | required: false 33 | APPLE_CERT_BASE64: 34 | required: true 35 | description: Base64 file from p12 certificate. 36 | APPLE_CERT_PASSWORD: 37 | required: true 38 | description: Password set when creating p12 certificate from .cer certificate. 39 | APPLE_DEV_PASSWORD: 40 | required: true 41 | description: Apple App-Specific Password. Eg. abcd-abcd-abcd-abcd 42 | APPLE_DEV_ID: 43 | required: true 44 | description: Email used for Apple Id. Eg. email@provider.com 45 | APPLE_DEV_TEAM_ID: 46 | required: true 47 | description: Apple Team Id. Eg. 1ABCD23EFG 48 | APPLE_DEV_APP_ID: 49 | required: true 50 | description: | 51 | Certificate name from get info -> Common name . Eg. Developer ID Application: Common Name (1ABCD23EFG) 52 | outputs: 53 | zip_path: 54 | value: ${{ steps.sign.outputs.path }} 55 | 56 | 57 | runs: 58 | using: composite 59 | steps: 60 | - name: Sign 61 | id: sign 62 | shell: pwsh 63 | run: | 64 | #!/usr/bin/env pwsh 65 | 66 | # Copyright (c) Mikael Hermansson and Godot Jolt contributors. 67 | 68 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 69 | # this software and associated documentation files (the "Software"), to deal in 70 | # the Software without restriction, including without limitation the rights to 71 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 72 | # the Software, and to permit persons to whom the Software is furnished to do so, 73 | # subject to the following conditions: 74 | 75 | # The above copyright notice and this permission notice shall be included in all 76 | # copies or substantial portions of the Software. 77 | 78 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 79 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 80 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 81 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 82 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 83 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 84 | 85 | # Taken from https://github.com/godot-jolt/godot-jolt/blob/master/scripts/ci_sign_macos.ps1 86 | 87 | Set-StrictMode -Version Latest 88 | $ErrorActionPreference = "Stop" 89 | 90 | $CodesignPath = Get-Command codesign | Resolve-Path 91 | 92 | $CertificateBase64 = "${{inputs.APPLE_CERT_BASE64}}" 93 | $CertificatePassword = "${{inputs.APPLE_CERT_PASSWORD}}" 94 | $CertificatePath = [IO.Path]::ChangeExtension((New-TemporaryFile), "p12") 95 | 96 | $Keychain = "ephemeral.keychain" 97 | $KeychainPassword = (New-Guid).ToString().Replace("-", "") 98 | 99 | $DevId = "${{ inputs.APPLE_DEV_ID }}" 100 | $DevTeamId = "${{ inputs.APPLE_DEV_TEAM_ID }}" 101 | $DevPassword = "${{ inputs.APPLE_DEV_PASSWORD }}" 102 | $DeveloperIdApplication = "${{ inputs.APPLE_DEV_APP_ID }}" 103 | 104 | if (!$CertificateBase64) { throw "No certificate provided" } 105 | if (!$CertificatePassword) { throw "No certificate password provided" } 106 | if (!$DevId) { throw "No Apple Developer ID provided" } 107 | if (!$DeveloperIdApplication) { throw "No Apple Developer ID Application provided" } 108 | if (!$DevTeamId) { throw "No Apple Team ID provided" } 109 | if (!$DevPassword) { throw "No Apple Developer password provided" } 110 | 111 | Write-Output "Decoding certificate..." 112 | 113 | $Certificate = [Convert]::FromBase64String($CertificateBase64) 114 | 115 | Write-Output "Writing certificate to disk..." 116 | 117 | [IO.File]::WriteAllBytes($CertificatePath, $Certificate) 118 | 119 | Write-Output "Creating keychain..." 120 | 121 | security create-keychain -p $KeychainPassword $Keychain 122 | 123 | Write-Output "Setting keychain as default..." 124 | 125 | security default-keychain -s $Keychain 126 | 127 | Write-Output "Importing certificate into keychain..." 128 | security import $CertificatePath ` 129 | -k ~/Library/Keychains/$Keychain ` 130 | -P $CertificatePassword ` 131 | -T $CodesignPath 132 | Write-Output "Check identities..." 133 | 134 | security find-identity 135 | 136 | Write-Output "Granting access to keychain..." 137 | 138 | security set-key-partition-list -S "apple-tool:,apple:" -s -k $KeychainPassword $Keychain 139 | 140 | $Framework = "${{ inputs.FRAMEWORK_PATH }}" 141 | $SignFlags = "${{ inputs.SIGN_FLAGS }}" 142 | $Archive = [IO.Path]::ChangeExtension((New-TemporaryFile), "zip") 143 | 144 | Write-Output "Signing '$Framework'..." 145 | 146 | & $CodesignPath --verify --timestamp --verbose "$SignFlags" --sign $DeveloperIdApplication "$Framework" 147 | 148 | Write-Output "Verifying signing..." 149 | 150 | & $CodesignPath --verify -dvvv "$Framework" 151 | 152 | Get-ChildItem -Force -Recurse -Path "$Framework" 153 | 154 | Write-Output "Archiving framework to '$Archive'..." 155 | 156 | ditto -ck -rsrc --sequesterRsrc --keepParent "$Framework" "$Archive" 157 | 158 | Write-Output "Submitting archive for notarization..." 159 | 160 | $output = xcrun notarytool submit "$Archive" ` 161 | --apple-id $DevId ` 162 | --team-id $DevTeamId ` 163 | --password $DevPassword ` 164 | --wait 165 | echo $output 166 | $matches = $output -match '((\d|[a-z])+-(\d|[a-z])+-(\d|[a-z])+-(\d|[a-z])+-(\d|[a-z])+)' 167 | if ($output) { 168 | $id_res = $matches[0].Substring(6) 169 | } 170 | xcrun notarytool log $id_res ` 171 | --apple-id $DevId ` 172 | --team-id $DevTeamId ` 173 | --password $DevPassword ` 174 | developer_log.json 175 | get-content developer_log.json 176 | 177 | echo "path=$Archive" >> $env:GITHUB_OUTPUT 178 | 179 | 180 | -------------------------------------------------------------------------------- /demo/Queries.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=11 format=3 uid="uid://cmhr2esr7b2ty"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://c36kg7gwvknh4" path="res://scenes/NavigationCamera.tscn" id="1_c0irw"] 4 | [ext_resource type="Script" path="res://scripts/body_animator.gd" id="2_vflxn"] 5 | [ext_resource type="Script" path="res://scripts/query_tester.gd" id="3_scqh4"] 6 | 7 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_lf6xy"] 8 | size = Vector2(107, 1105) 9 | 10 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_e88so"] 11 | size = Vector2(107, 1950.03) 12 | 13 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_r2w26"] 14 | radius = 44.0 15 | height = 174.0 16 | 17 | [sub_resource type="SegmentShape2D" id="SegmentShape2D_e5b1l"] 18 | a = Vector2(-40, -11) 19 | b = Vector2(185, 58) 20 | 21 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_o0sn6"] 22 | size = Vector2(152, 145) 23 | 24 | [sub_resource type="CircleShape2D" id="CircleShape2D_yx4hw"] 25 | radius = 96.0208 26 | 27 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_72i8i"] 28 | size = Vector2(191.713, 115.41) 29 | 30 | [node name="Queries" type="Node2D"] 31 | 32 | [node name="_Settings" type="VBoxContainer" parent="."] 33 | offset_left = 2038.0 34 | offset_right = 2180.0 35 | offset_bottom = 136.0 36 | 37 | [node name="HBoxContainer" type="HBoxContainer" parent="_Settings"] 38 | layout_mode = 2 39 | 40 | [node name="Shape" type="VBoxContainer" parent="_Settings/HBoxContainer"] 41 | layout_mode = 2 42 | 43 | [node name="Circle" type="Button" parent="_Settings/HBoxContainer/Shape"] 44 | layout_mode = 2 45 | text = "Circle" 46 | 47 | [node name="Capsule" type="Button" parent="_Settings/HBoxContainer/Shape"] 48 | layout_mode = 2 49 | text = "Capsule" 50 | 51 | [node name="Rectangle" type="Button" parent="_Settings/HBoxContainer/Shape"] 52 | layout_mode = 2 53 | text = "Rectangle" 54 | 55 | [node name="Polygon" type="Button" parent="_Settings/HBoxContainer/Shape"] 56 | layout_mode = 2 57 | text = "Polygon" 58 | 59 | [node name="Query" type="VBoxContainer" parent="_Settings/HBoxContainer"] 60 | layout_mode = 2 61 | 62 | [node name="IntersectRay" type="Button" parent="_Settings/HBoxContainer/Query"] 63 | layout_mode = 2 64 | text = "intersect_ray 65 | " 66 | 67 | [node name="CastMotion" type="Button" parent="_Settings/HBoxContainer/Query"] 68 | layout_mode = 2 69 | text = "cast_motion 70 | " 71 | 72 | [node name="CollideShape" type="Button" parent="_Settings/HBoxContainer/Query"] 73 | layout_mode = 2 74 | text = "collide_shape" 75 | 76 | [node name="RestInfo" type="Button" parent="_Settings/HBoxContainer/Query"] 77 | layout_mode = 2 78 | text = "get_rest_info 79 | " 80 | 81 | [node name="QueryTester" type="Node2D" parent="."] 82 | position = Vector2(172.175, -179.065) 83 | script = ExtResource("3_scqh4") 84 | 85 | [node name="StaticBody2D" type="StaticBody2D" parent="."] 86 | 87 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] 88 | position = Vector2(44, 527) 89 | shape = SubResource("RectangleShape2D_lf6xy") 90 | 91 | [node name="CollisionShape2D2" type="CollisionShape2D" parent="StaticBody2D"] 92 | position = Vector2(1936, 537) 93 | shape = SubResource("RectangleShape2D_lf6xy") 94 | 95 | [node name="CollisionShape2D3" type="CollisionShape2D" parent="StaticBody2D"] 96 | position = Vector2(989.966, 1038.94) 97 | rotation = -1.57397 98 | shape = SubResource("RectangleShape2D_e88so") 99 | 100 | [node name="CollisionShape2D4" type="CollisionShape2D" parent="StaticBody2D"] 101 | position = Vector2(984, 32.9999) 102 | rotation = -1.57397 103 | shape = SubResource("RectangleShape2D_e88so") 104 | 105 | [node name="Camera2D" parent="." instance=ExtResource("1_c0irw")] 106 | position = Vector2(963, 535) 107 | zoom = Vector2(1, 1) 108 | 109 | [node name="RigidBody2D" type="AnimatableBody2D" parent="."] 110 | position = Vector2(404, 677) 111 | script = ExtResource("2_vflxn") 112 | period_seconds = 4.0 113 | 114 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="RigidBody2D"] 115 | position = Vector2(12, -18) 116 | polygon = PackedVector2Array(112, -126, -72, -68, -100, 95, 64, 164, 97, 62, 15, 44, -2, -9) 117 | 118 | [node name="RigidBody2D2" type="AnimatableBody2D" parent="."] 119 | position = Vector2(808, 368) 120 | script = ExtResource("2_vflxn") 121 | period_seconds = 4.0 122 | 123 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D2"] 124 | position = Vector2(-28, -2) 125 | shape = SubResource("CapsuleShape2D_r2w26") 126 | 127 | [node name="RigidBody2D3" type="AnimatableBody2D" parent="."] 128 | position = Vector2(964, 823) 129 | script = ExtResource("2_vflxn") 130 | period_seconds = 4.0 131 | 132 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D3"] 133 | position = Vector2(-66, -24) 134 | shape = SubResource("SegmentShape2D_e5b1l") 135 | 136 | [node name="RigidBody2D4" type="AnimatableBody2D" parent="."] 137 | position = Vector2(1273, 577) 138 | script = ExtResource("2_vflxn") 139 | period_seconds = 4.0 140 | 141 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D4"] 142 | position = Vector2(-67, -24.5) 143 | shape = SubResource("RectangleShape2D_o0sn6") 144 | 145 | [node name="RigidBody2D5" type="AnimatableBody2D" parent="."] 146 | position = Vector2(1628, 614) 147 | script = ExtResource("2_vflxn") 148 | period_seconds = 4.0 149 | 150 | [node name="CollisionPolygon2D2" type="CollisionPolygon2D" parent="RigidBody2D5"] 151 | position = Vector2(11, 11) 152 | build_mode = 1 153 | polygon = PackedVector2Array(-97, -11, -26, -85, 59, -6, -16, 67) 154 | 155 | [node name="RigidBody2D6" type="AnimatableBody2D" parent="."] 156 | position = Vector2(1635, 264) 157 | script = ExtResource("2_vflxn") 158 | period_seconds = 4.0 159 | 160 | [node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="RigidBody2D6"] 161 | position = Vector2(24, -20) 162 | build_mode = 1 163 | polygon = PackedVector2Array(-86, 25, -55.7708, 52.9733, -19, 87, 57, 27, -19, -47) 164 | 165 | [node name="CCW" type="Label" parent="."] 166 | offset_left = 1621.0 167 | offset_top = 253.0 168 | offset_right = 1661.0 169 | offset_bottom = 276.0 170 | text = "CCW" 171 | 172 | [node name="CW" type="Label" parent="."] 173 | offset_left = 1600.0 174 | offset_top = 606.0 175 | offset_right = 1640.0 176 | offset_bottom = 629.0 177 | text = "CW 178 | " 179 | 180 | [node name="Area2D" type="StaticBody2D" parent="."] 181 | 182 | [node name="CollisionShape2D5" type="CollisionShape2D" parent="Area2D"] 183 | position = Vector2(343, 301) 184 | shape = SubResource("CircleShape2D_yx4hw") 185 | 186 | [node name="RigidBody2D7" type="RigidBody2D" parent="."] 187 | position = Vector2(743, 138) 188 | 189 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RigidBody2D7"] 190 | position = Vector2(45.1434, 53) 191 | shape = SubResource("RectangleShape2D_72i8i") 192 | 193 | [connection signal="pressed" from="_Settings/HBoxContainer/Shape/Circle" to="QueryTester" method="_on_circle_pressed"] 194 | [connection signal="pressed" from="_Settings/HBoxContainer/Shape/Capsule" to="QueryTester" method="_on_capsule_pressed"] 195 | [connection signal="pressed" from="_Settings/HBoxContainer/Shape/Rectangle" to="QueryTester" method="_on_rectangle_pressed"] 196 | [connection signal="pressed" from="_Settings/HBoxContainer/Shape/Polygon" to="QueryTester" method="_on_polygon_pressed"] 197 | [connection signal="pressed" from="_Settings/HBoxContainer/Query/IntersectRay" to="QueryTester" method="_on_intersect_ray_pressed"] 198 | [connection signal="pressed" from="_Settings/HBoxContainer/Query/CastMotion" to="QueryTester" method="_on_cast_motion_pressed"] 199 | [connection signal="pressed" from="_Settings/HBoxContainer/Query/CollideShape" to="QueryTester" method="_on_collide_shape_pressed"] 200 | [connection signal="pressed" from="_Settings/HBoxContainer/Query/RestInfo" to="QueryTester" method="_on_rest_info_pressed"] 201 | -------------------------------------------------------------------------------- /demo/sprites/robot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 58 | 62 | 67 | 68 | 72 | 77 | 78 | 82 | 87 | 88 | 92 | 97 | 98 | 102 | 107 | 108 | 112 | 117 | 118 | 122 | 127 | 128 | 132 | 137 | 138 | 139 | 140 | --------------------------------------------------------------------------------