Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6c14525
Base classes for driving rapier via tick loop
albertok Mar 26, 2025
b47b710
init physics
albertok Mar 26, 2025
681a728
support stepping PR
albertok Mar 28, 2025
92f82b7
remove breaking types
elementbound Mar 31, 2025
eca1cc3
physics driver toggles
elementbound Mar 31, 2025
f1a7554
fxs
elementbound Mar 31, 2025
b2ff965
Merge branch 'foxssake:main' into step-physics-rapier
albertok Apr 10, 2025
a224fc6
Merge branch 'foxssake:main' into step-physics-rapier
albertok Apr 14, 2025
fd60ae0
state and rollback synchronizer compatible RigidBody classes
Apr 14, 2025
f2670d3
Overidable method for applying forces
Apr 14, 2025
ec0ec3d
warning fix
Apr 14, 2025
7ddf146
Merge branch 'foxssake:main' into step-physics-rapier
albertok Apr 25, 2025
22258fe
Set state only if in rollback prepare phase
albertok Apr 25, 2025
e5fdfb0
flush queries before stepping in Godot PR
albertok Apr 25, 2025
237d36e
docs
albertok Apr 25, 2025
b8accae
use groups to call network_rigid_bodies while stepping
albertok Apr 25, 2025
9093b07
2D Godot physics driver
albertok Apr 25, 2025
8f37a9c
uid files
albertok Apr 25, 2025
4c9772b
touched docs up
albertok Apr 25, 2025
bd575d7
doc edits
albertok Apr 26, 2025
093ed4c
Fix global space rollback
albertok Apr 27, 2025
cf72568
Merge branch 'foxssake:main' into step-physics-rapier
albertok Apr 27, 2025
4e4e3c0
update docs regarding state-synchronizer
albertok Apr 27, 2025
a2f170e
snapshot only physics bodies in 3D
albertok Apr 28, 2025
4ba030c
Update addons/netfox.extras/physics/godot_driver_2d.gd.off
albertok May 6, 2025
da57924
Update addons/netfox.extras/physics/network-rigid-body-2d.gd
albertok May 6, 2025
3a6e9e5
Update addons/netfox.extras/physics/network-rigid-body-2d.gd
albertok May 6, 2025
33481c3
Update addons/netfox.extras/physics/network-rigid-body-2d.gd
albertok May 6, 2025
0c1b31e
performance tweaks
albertok May 6, 2025
d5a8ee4
Update addons/netfox.extras/physics/godot_driver_3d.gd.off
albertok May 8, 2025
f929fed
Update addons/netfox.extras/physics/godot_driver_2d.gd.off
albertok May 8, 2025
29e86ac
Update addons/netfox.extras/physics/godot_driver_2d.gd.off
albertok May 8, 2025
d20499b
Merge branch 'main' into step-physics-rapier
albertok May 15, 2025
748a345
Update docs/netfox.extras/guides/physics.md
albertok May 27, 2025
5763610
Update addons/netfox.extras/physics/godot_driver_3d.gd.off
albertok May 27, 2025
0570a52
Update addons/netfox.extras/physics/godot_driver_2d.gd.off
albertok May 27, 2025
8df9035
fxs + ci
elementbound Jun 3, 2025
5202049
bv
elementbound Jun 3, 2025
d73e04b
docs
elementbound Jun 3, 2025
ed6d9d9
Merge branch 'main' into step-physics-rapier
elementbound Jun 3, 2025
c1b869d
icons
elementbound Jun 3, 2025
8123c03
disconnect signal handlers on exit tree
elementbound Jun 16, 2025
11efc4c
Merge branch 'main' into step-physics-rapier
elementbound Jun 16, 2025
6b5b541
docs
elementbound Jun 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions addons/netfox.extras/netfox-extras.gd
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@ const AUTOLOADS = [
}
]

const PhysicsDriverToggles := preload("res://addons/netfox.extras/physics/physics-driver-toggles.gd")

var _tool_menu_items := [] as Array[String]

func _enter_tree():
for setting in SETTINGS:
add_setting(setting)

for autoload in AUTOLOADS:
add_autoload_singleton(autoload.name, autoload.path)

_render_tool_menu()

func _exit_tree():
if ProjectSettings.get_setting("netfox/general/clear_settings", false):
Expand All @@ -47,6 +52,8 @@ func _exit_tree():
for autoload in AUTOLOADS:
remove_autoload_singleton(autoload.name)

_free_tool_menu()

func add_setting(setting: Dictionary):
if ProjectSettings.has_setting(setting.name):
return
Expand All @@ -65,3 +72,41 @@ func remove_setting(setting: Dictionary):
return

ProjectSettings.clear(setting.name)

func _render_tool_menu():
_free_tool_menu()
for driver_toggle in PhysicsDriverToggles.all():
var prefix := "Enable" if not driver_toggle.is_enabled() else "Disable"
var item := "%s %s physics driver" % [prefix, driver_toggle.get_name()]

_tool_menu_items.append(item)

add_tool_menu_item(item, func():
# Toggle physics driver, then re-render menu to show changes
_call_physics_driver_toggle(driver_toggle)
_render_tool_menu()
)

func _free_tool_menu():
for item in _tool_menu_items:
remove_tool_menu_item(item)
_tool_menu_items.clear()

func _call_physics_driver_toggle(driver_toggle: PhysicsDriverToggles.PhysicsDriverToggle):
var error_messages := driver_toggle.toggle()
if not error_messages.is_empty():
var error_text := "\n".join(error_messages)

var dialog := AcceptDialog.new()
dialog.title = "Physics driver toggle failed!"
dialog.dialog_text = error_text

get_editor_interface().popup_dialog_centered(dialog)
else:
var dialog := AcceptDialog.new()
dialog.title = "Physics driver toggle success!"
dialog.dialog_text = ("%s physics driver was successfully toggled! " +
"You might need to refresh your script or reload project.") %\
[driver_toggle.get_name()]

get_editor_interface().popup_dialog_centered(dialog)
66 changes: 66 additions & 0 deletions addons/netfox.extras/physics/godot_driver_2d.gd.off
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
extends PhysicsDriver

class_name PhysicsDriver2D

# Physics driver based on netfox ticks
# Requires a custom build of Godot with https://github.com/godotengine/godot/pull/76462

var scene_collision_objects: Array = []
var collision_objects_snapshots: Dictionary[int, Dictionary] = {}

func _init_physics_space() -> void:
physics_space = get_viewport().world_2D.space
PhysicsServer2D.space_set_active(physics_space, false)

get_tree().node_added.connect(node_added)
get_tree().node_removed.connect(node_removed)
scan_tree()

func _physics_step(delta) -> void:
PhysicsServer2D.space_flush_queries(physics_space)
PhysicsServer2D.space_step(physics_space, delta)

func _snapshot_space(tick: int) -> void:
var rid_states: Dictionary[RID, Array] = {}
for element in scene_collision_objects:
var rid = element.get_rid()
rid_states[rid] = get_body_states(rid)

snapshots[tick] = rid_states

func _rollback_space(tick) -> void:
if snapshots.has(tick):
var rid_states = snapshots[tick]
for rid in rid_states.keys():
set_body_states(rid, rid_states[rid])

func get_body_states(rid: RID) -> Array:
var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO]
body_state[0] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_TRANSFORM)
body_state[1] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_LINEAR_VELOCITY)
body_state[2] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_ANGULAR_VELOCITY)
body_state[3] = PhysicsServer2D.body_get_state(rid, PhysicsServer2D.BODY_STATE_SLEEPING)
return body_state

func set_body_states(rid: RID, body_state: Array) -> void:
PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_TRANSFORM, body_state[0])
PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_LINEAR_VELOCITY, body_state[1])
PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_ANGULAR_VELOCITY, body_state[2])
PhysicsServer2D.body_set_state(rid, PhysicsServer2D.BODY_STATE_SLEEPING, body_state[3])

func scan_tree():
scene_collision_objects.clear()
scene_collision_objects = get_all_children(get_node('/root'))

func get_all_children(in_node: Node) -> Array:
var nodes = []
nodes = in_node.find_children("*", "PhysicsBody2D", true, false)
return nodes

func node_added(node: Node) -> void:
if node is PhysicsBody2D:
scene_collision_objects.append(node)

func node_removed(node: Node) -> void:
if node is PhysicsBody2D:
scene_collision_objects.erase(node)
1 change: 1 addition & 0 deletions addons/netfox.extras/physics/godot_driver_2d.gd.uid.off
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://c8p8gymii2y5v
67 changes: 67 additions & 0 deletions addons/netfox.extras/physics/godot_driver_3d.gd.off
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
extends PhysicsDriver
class_name PhysicsDriver3D

# Physics driver based on netfox ticks
# Requires a custom build of Godot with https://github.com/godotengine/godot/pull/76462

# Maps ticks ( int ) to global snapshots ( Dictionary<RID, Array> )
var scene_collision_objects: Array = []

func _init_physics_space() -> void:
physics_space = get_viewport().world_3d.space
PhysicsServer3D.space_set_active(physics_space, false)

get_tree().node_added.connect(node_added)
get_tree().node_removed.connect(node_removed)
scan_tree()

func _physics_step(delta) -> void:
PhysicsServer3D.space_flush_queries(physics_space)
PhysicsServer3D.space_step(physics_space, delta)

func _snapshot_space(tick: int) -> void:
# Maps RIDs to physics state ( Array )
var rid_states := {}
for element in scene_collision_objects:
var rid = element.get_rid()
rid_states[rid] = get_body_states(rid)

snapshots[tick] = rid_states

func _rollback_space(tick) -> void:
if snapshots.has(tick):
var rid_states = snapshots[tick]
for rid in rid_states.keys():
set_body_states(rid, rid_states[rid])


func get_body_states(rid: RID) -> Array:
var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO]
body_state[0] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_TRANSFORM)
body_state[1] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_LINEAR_VELOCITY)
body_state[2] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_ANGULAR_VELOCITY)
body_state[3] = PhysicsServer3D.body_get_state(rid, PhysicsServer3D.BODY_STATE_SLEEPING)
return body_state

func set_body_states(rid: RID, body_state: Array) -> void:
PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_TRANSFORM, body_state[0])
PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_LINEAR_VELOCITY, body_state[1])
PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_ANGULAR_VELOCITY, body_state[2])
PhysicsServer3D.body_set_state(rid, PhysicsServer3D.BODY_STATE_SLEEPING, body_state[3])

func scan_tree():
scene_collision_objects.clear()
scene_collision_objects = get_all_children(get_node('/root'))

func get_all_children(in_node: Node) -> Array:
var nodes = []
nodes = in_node.find_children("*", "PhysicsBody3D", true, false)
return nodes

func node_added(node: Node) -> void:
if node is PhysicsBody3D:
scene_collision_objects.append(node)

func node_removed(node: Node) -> void:
if node is PhysicsBody3D:
scene_collision_objects.erase(node)
1 change: 1 addition & 0 deletions addons/netfox.extras/physics/godot_driver_3d.gd.uid.off
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://wr5ur5dqrkni
45 changes: 45 additions & 0 deletions addons/netfox.extras/physics/network-rigid-body-2d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
extends RigidBody2D

class_name NetworkRigidBody2D

## A rollback / state synchronizer class for RigidBody2D.
## Set state property path to physics_state to synchronize the state of this body.

@onready var direct_state = PhysicsServer2D.body_get_direct_state(get_rid())

var physics_state: Array:
get: return get_state()
set(v): set_state(v)

enum {
ORIGIN,
QUAT,
LIN_VEL,
ANG_VEL,
SLEEPING
}

func _notification(notification: int):
if notification == NOTIFICATION_READY:
add_to_group("network_rigid_body")

func get_state() -> Array:
var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO, false]
body_state[ORIGIN] = direct_state.transform.origin
body_state[QUAT] = direct_state.transform.basis.get_rotation_quaternion()
body_state[LIN_VEL] = direct_state.linear_velocity
body_state[ANG_VEL] = direct_state.angular_velocity
body_state[SLEEPING] = direct_state.sleeping
return body_state

func set_state(remote_state: Array) -> void:
direct_state.transform.origin = remote_state[ORIGIN]
direct_state.transform.basis = Basis(remote_state[QUAT])
direct_state.linear_velocity = remote_state[LIN_VEL]
direct_state.angular_velocity = remote_state[ANG_VEL]
direct_state.sleeping = remote_state[SLEEPING]

## Override and apply any logic, forces or impulses to the rigid body as you would in physics_process
## The physics engine will run its simulation during rollback_tick with other nodes
func _physics_rollback_tick(_delta, _tick):
pass
1 change: 1 addition & 0 deletions addons/netfox.extras/physics/network-rigid-body-2d.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://c8hw7ol53m55g
46 changes: 46 additions & 0 deletions addons/netfox.extras/physics/network-rigid-body-3d.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
extends RigidBody3D

class_name NetworkRigidBody3D

## A rollback / state synchronizer class for RigidBody3D.
## Set state property path to physics_state to synchronize the state of this body.

@onready var direct_state = PhysicsServer3D.body_get_direct_state(get_rid())

var physics_state: Array:
get: return get_state()
set(v): set_state(v)

enum {
ORIGIN,
QUAT,
LIN_VEL,
ANG_VEL,
SLEEPING
}

func _notification(notification: int):
if notification == NOTIFICATION_READY:
add_to_group("network_rigid_body")

func get_state() -> Array:
var body_state: Array = [Vector3.ZERO, Quaternion.IDENTITY, Vector3.ZERO, Vector3.ZERO, false]
body_state[ORIGIN] = direct_state.transform.origin
body_state[QUAT] = direct_state.transform.basis.get_rotation_quaternion()
body_state[LIN_VEL] = direct_state.linear_velocity
body_state[ANG_VEL] = direct_state.angular_velocity
body_state[SLEEPING] = direct_state.sleeping
return body_state

func set_state(remote_state: Array) -> void:
direct_state.transform.origin = remote_state[ORIGIN]
direct_state.transform.basis = Basis(remote_state[QUAT])
direct_state.linear_velocity = remote_state[LIN_VEL]
direct_state.angular_velocity = remote_state[ANG_VEL]
direct_state.sleeping = remote_state[SLEEPING]


## Override and apply any logic, forces or impulses to the rigid body as you would in physics_process
## The physics engine will run its simulation during rollback_tick with other nodes
func _physics_rollback_tick(_delta, _tick):
pass
1 change: 1 addition & 0 deletions addons/netfox.extras/physics/network-rigid-body-3d.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://bklxcdyxgbjg2
74 changes: 74 additions & 0 deletions addons/netfox.extras/physics/physics-driver-toggles.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
extends Object

class PhysicsDriverToggle:
const INACTIVE_SUFFIX := ".off"

func get_name() -> String:
return "???"

func get_files() -> Array[String]:
return []

func get_error_messages() -> Array[String]:
return []

func is_enabled() -> bool:
return get_files().any(func(it): return FileAccess.file_exists(it))

func toggle() -> Array[String]:
var errors := get_error_messages()
if not errors.is_empty():
return errors

var enable := not is_enabled()

var uid_files := get_files().map(func(it): return it + ".uid")
var renames = (get_files() + uid_files).map(func(it):
if enable: return [it + INACTIVE_SUFFIX, it]
else: return [it, it + INACTIVE_SUFFIX]
)

for rename in renames:
var result := DirAccess.rename_absolute(rename[0], rename[1])
if result != OK:
errors.append(
"Failed rename \"%s\" -> \"%s\"; reason: %s" %
[rename[0], rename[1], error_string(result)]
)
return errors

class RapierPhysicsDriverToggle extends PhysicsDriverToggle:
func get_name() -> String:
return "Rapier"

func get_files() -> Array[String]:
return [
"res://addons/netfox.extras/physics/rapier_driver_2d.gd",
"res://addons/netfox.extras/physics/rapier_driver_3d.gd",
]

func get_error_messages() -> Array[String]:
if not ClassDB.class_exists("RapierPhysicsServer2D") or not ClassDB.class_exists("RapierPhysicsServer3D"):
return ["Rapier physics is not available! Is the extension installed?"]
return []

class GodotPhysicsDriverToggle extends PhysicsDriverToggle:
func get_name() -> String:
return "Godot"

func get_files() -> Array[String]:
return [
"res://addons/netfox.extras/physics/godot_driver_3d.gd",
"res://addons/netfox.extras/physics/godot_driver_2d.gd"
]

func get_error_messages() -> Array[String]:
if not PhysicsServer3D.has_method("space_step") or not PhysicsServer2D.has_method("space_step"):
return ["Physics stepping is not available! Is this the right Godot build?"]
return []

static func all() -> Array[PhysicsDriverToggle]:
return [
RapierPhysicsDriverToggle.new(),
GodotPhysicsDriverToggle.new()
]
1 change: 1 addition & 0 deletions addons/netfox.extras/physics/physics-driver-toggles.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://bu4ppfj0ovkbr
Loading
Loading