feat: yay rust rewrite its faster
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
rust/target
|
||||
|
@ -1,26 +0,0 @@
|
||||
extends Node2D
|
||||
class_name Boid2D
|
||||
|
||||
## controls the properties of this boid, deciding how it will behave.
|
||||
@export var properties: BoidProperties
|
||||
|
||||
# position is .position since this is base Node2D
|
||||
var velocity := Vector2.ZERO
|
||||
|
||||
# this is assigned by the flock, if this boid is a child of it
|
||||
var flock: Flock
|
||||
|
||||
var last_processed_in: int = 0
|
||||
|
||||
## applies some force to this boid.
|
||||
func apply_force(spatial_force: Vector3) -> void:
|
||||
var force := Vector2(spatial_force.x, spatial_force.y)
|
||||
velocity += force
|
||||
velocity = velocity.limit_length(properties.max_speed)
|
||||
position += velocity
|
||||
|
||||
func _get_boid_position() -> Vector3:
|
||||
return Vector3(position.x, position.y, 0.0)
|
||||
|
||||
func _get_boid_velocity() -> Vector3:
|
||||
return Vector3(velocity.x, velocity.y, 0.0)
|
@ -1,25 +0,0 @@
|
||||
extends Node3D
|
||||
class_name Boid3D
|
||||
|
||||
## controls the properties of this boid, deciding how it will behave.
|
||||
@export var properties: BoidProperties
|
||||
|
||||
# position is .position since this is base Node2D
|
||||
var velocity := Vector3.ZERO
|
||||
|
||||
# this is assigned by the flock, if this boid is a child of it
|
||||
var flock: Flock
|
||||
|
||||
var last_processed_in: int = 0
|
||||
|
||||
## applies some force to this boid.
|
||||
func apply_force(force: Vector3) -> void:
|
||||
velocity += force
|
||||
velocity = velocity.limit_length(properties.max_speed)
|
||||
position += velocity
|
||||
|
||||
func _get_boid_position() -> Vector3:
|
||||
return position
|
||||
|
||||
func _get_boid_velocity() -> Vector3:
|
||||
return velocity
|
@ -1,179 +0,0 @@
|
||||
extends Node
|
||||
|
||||
# parallelize the work into a new task per n boids
|
||||
# this seems to help with 1000 boids in a single flock from 400ms to 180ms (before quadtrees)
|
||||
const PARALLELIZATION_RATE: int = 50 # 50 seems to be the best value?
|
||||
const EPSILON: float = 0.00001
|
||||
|
||||
var flocks: Dictionary = {}
|
||||
var total_boid_count: int = 0:
|
||||
set(new_count):
|
||||
total_boid_count = new_count
|
||||
args_array.resize(total_boid_count)
|
||||
forces_array.resize(total_boid_count)
|
||||
|
||||
# create our arrays for parallel processing
|
||||
var args_array: Array[Dictionary] = []
|
||||
var forces_array: PackedVector3Array = []
|
||||
#var grids: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
get_tree().node_added.connect(_register_flock)
|
||||
get_tree().node_removed.connect(_unregister_flock)
|
||||
|
||||
_init_register_flock()
|
||||
|
||||
args_array.resize(total_boid_count)
|
||||
forces_array.resize(total_boid_count)
|
||||
|
||||
func _init_register_flock(node: Node = get_tree().root) -> void:
|
||||
_register_flock(node)
|
||||
for child: Node in node.get_children():
|
||||
_init_register_flock(child)
|
||||
|
||||
func _register_flock(maybe_flock: Node) -> void:
|
||||
if maybe_flock is not Flock: return
|
||||
var flock_id := maybe_flock.get_instance_id()
|
||||
flocks[flock_id] = maybe_flock
|
||||
#grids[flock_id] = Grid.new()
|
||||
print_verbose("[BoidManager] flock ", maybe_flock, " registered")
|
||||
|
||||
func _unregister_flock(maybe_flock: Node) -> void:
|
||||
if maybe_flock is not Flock: return
|
||||
var flock_id := maybe_flock.get_instance_id()
|
||||
flocks.erase(flock_id)
|
||||
#grids.erase(flock_id)
|
||||
print_verbose("[BoidManager] flock ", maybe_flock, " unregistered")
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
_process_boids()
|
||||
|
||||
func _process_boids() -> void:
|
||||
var total_parallel_tasks := total_boid_count / PARALLELIZATION_RATE
|
||||
if total_boid_count % PARALLELIZATION_RATE > 0: total_parallel_tasks += 1
|
||||
|
||||
var boid_count := 0
|
||||
# organize the work into tasks
|
||||
for flock: Flock in flocks.values():
|
||||
var flock_args := _pack_calc_args_flock(flock)
|
||||
var boids := flock.boids.values()
|
||||
#grids.get(flock.get_instance_id()).build(Vector3.ONE * 1000.0, 30.0, boids)
|
||||
for boid in boids:
|
||||
var args := _pack_calc_args_boid(flock, boid, flock_args.duplicate())
|
||||
args_array[boid_count] = args
|
||||
forces_array[boid_count] = Vector3.ZERO
|
||||
boid_count += 1
|
||||
|
||||
# distribute tasks to threads
|
||||
# TODO: calculate on main thread if there arent enough boids to warrant doing this
|
||||
var calc_task := WorkerThreadPool.add_group_task(
|
||||
_calculate_boid_parallel,
|
||||
total_parallel_tasks,
|
||||
total_parallel_tasks,
|
||||
true,
|
||||
)
|
||||
WorkerThreadPool.wait_for_group_task_completion(calc_task)
|
||||
|
||||
# apply the forces
|
||||
var idx := 0
|
||||
for force in forces_array:
|
||||
args_array[idx].boid.apply_force(force)
|
||||
idx += 1
|
||||
|
||||
func _pack_calc_args_flock(flock: Flock) -> Dictionary:
|
||||
var num_of_boids := flock.boids.size()
|
||||
var others_pos := PackedVector3Array([]); others_pos.resize(num_of_boids)
|
||||
var others_vel := PackedVector3Array([]); others_vel.resize(num_of_boids)
|
||||
var idx := 0
|
||||
for aboid in flock.boids.values():
|
||||
others_pos.set(idx, aboid._get_boid_position())
|
||||
others_vel.set(idx, aboid._get_boid_velocity())
|
||||
idx += 1
|
||||
var flock_args := {
|
||||
'others_pos': others_pos,
|
||||
'others_vel': others_vel,
|
||||
'goal_seperation': flock.goal_seperation,
|
||||
'goal_alignment': flock.goal_alignment,
|
||||
'goal_cohesion': flock.goal_cohesion,
|
||||
}
|
||||
if flock.target != null:
|
||||
flock_args['target_position'] = flock.target.global_position
|
||||
return flock_args
|
||||
|
||||
func _pack_calc_args_boid(flock: Flock, boid, args: Dictionary) -> Dictionary:
|
||||
#var nearby_boids: Array[Node] = grids.get(flock.get_instance_id()).get_nearby_boids(boid)
|
||||
#var others_pos := PackedVector3Array([]); others_pos.resize(nearby_boids.size())
|
||||
#var others_vel := PackedVector3Array([]); others_vel.resize(nearby_boids.size())
|
||||
#var idx := 0
|
||||
#for aboid in nearby_boids:
|
||||
#others_pos.set(idx, aboid._get_boid_position())
|
||||
#others_vel.set(idx, aboid._get_boid_velocity())
|
||||
#idx += 1
|
||||
#args['others_pos'] = others_pos
|
||||
#args['others_vel'] = others_vel
|
||||
args['boid'] = boid
|
||||
args['self_props'] = boid.properties
|
||||
args['self_vel'] = boid._get_boid_velocity()
|
||||
args['self_pos'] = boid._get_boid_position()
|
||||
return args
|
||||
|
||||
func _calculate_boid_parallel(idx: int) -> void:
|
||||
var start_from := PARALLELIZATION_RATE * idx
|
||||
var end_at := mini(start_from + PARALLELIZATION_RATE, total_boid_count)
|
||||
var arg_idx := start_from
|
||||
while arg_idx < end_at:
|
||||
var force := _calculate_boid(args_array[arg_idx])
|
||||
forces_array[arg_idx] = force
|
||||
arg_idx += 1
|
||||
|
||||
func _calculate_boid(args: Dictionary) -> Vector3:
|
||||
var boid_properties: BoidProperties = args.self_props
|
||||
var boid_pos: Vector3 = args.self_pos
|
||||
var boid_vel: Vector3 = args.self_vel
|
||||
|
||||
var steer := Vector3.ZERO
|
||||
var align := Vector3.ZERO
|
||||
var cohere := Vector3.ZERO
|
||||
|
||||
var steer_count := 0
|
||||
var align_count := 0
|
||||
var cohere_count := 0
|
||||
|
||||
var goal_seperation: float = args.goal_seperation
|
||||
var goal_alignment: float = args.goal_alignment
|
||||
var goal_cohesion: float = args.goal_cohesion
|
||||
var others_pos: PackedVector3Array = args.others_pos
|
||||
var others_vel: PackedVector3Array = args.others_vel
|
||||
var aboid_idx := 0
|
||||
# iterating over the packed array for pos is faster, we use pos always, vel only in one case
|
||||
for aboid_pos in others_pos:
|
||||
# faster for when checking, we can just sqrt later for calculating steering
|
||||
var dist = boid_pos.distance_squared_to(aboid_pos)
|
||||
if dist > EPSILON:
|
||||
if dist < goal_seperation:
|
||||
var diff = (boid_pos - aboid_pos).normalized() / sqrt(dist)
|
||||
steer += diff; steer_count += 1
|
||||
if dist < goal_alignment: align += others_vel[aboid_idx]; align_count += 1
|
||||
if dist < goal_cohesion: cohere += aboid_pos; cohere_count += 1
|
||||
aboid_idx += 1
|
||||
|
||||
if steer_count > 0: steer /= steer_count
|
||||
if align_count > 0: align /= align_count
|
||||
if cohere_count > 0: cohere /= cohere_count; cohere -= boid_pos
|
||||
|
||||
if align.length_squared() > 0.0: align = (align.normalized() * boid_properties.max_speed - boid_vel).limit_length(boid_properties.max_force)
|
||||
if steer.length_squared() > 0.0: steer = (steer.normalized() * boid_properties.max_speed - boid_vel).limit_length(boid_properties.max_force)
|
||||
if cohere.length_squared() > 0.0: cohere = (cohere.normalized() * boid_properties.max_speed - boid_vel).limit_length(boid_properties.max_force)
|
||||
|
||||
var target := Vector3.ZERO
|
||||
var target_position := args.get('target_position')
|
||||
if target_position != null:
|
||||
target = ((target_position - boid_pos) - boid_vel).limit_length(boid_properties.max_force)
|
||||
|
||||
var steer_force := steer * boid_properties.seperation
|
||||
var align_force := align * boid_properties.alignment
|
||||
var cohere_force := cohere * boid_properties.cohesion
|
||||
var target_force := target * boid_properties.targeting
|
||||
var force := steer_force + align_force + cohere_force + target_force
|
||||
|
||||
return force
|
@ -1,17 +0,0 @@
|
||||
extends Resource
|
||||
class_name BoidProperties
|
||||
|
||||
## controls the maximum speed.
|
||||
@export var max_speed := 4.0
|
||||
## controls the maximum force.
|
||||
@export var max_force := 1.0
|
||||
|
||||
@export_group("weights")
|
||||
## controls how inclined the boid will be to align with the rest of it's flock.
|
||||
@export var alignment := 1.5
|
||||
## controls how inclined the boid will be to cohere together with the rest of it's flock.
|
||||
@export var cohesion := 1.0
|
||||
## controls how inclined the boid will be to separate from the rest of it's flock.
|
||||
@export var seperation := 1.2
|
||||
## controls how inclined the boid will be to go to a target (defined by a flock).
|
||||
@export var targeting := 0.8
|
@ -3,16 +3,8 @@ extends EditorPlugin
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
add_custom_type("BoidProperties", "Resource", preload("boid_properties/boid_properties.gd"), preload("boid_properties/boid_properties.svg"))
|
||||
add_custom_type("Flock", "Node", preload("flock/flock.gd"), preload("flock/flock.svg"))
|
||||
add_custom_type("Boid2D", "Node2D", preload("boid_2d/boid_2d.gd"), preload("boid_2d/boid_2d.svg"))
|
||||
add_custom_type("Boid3D", "Node3D", preload("boid_3d/boid_3d.gd"), preload("boid_3d/boid_3d.svg"))
|
||||
add_autoload_singleton("BoidManager", "res://addons/boids/boid_manager.gd")
|
||||
add_autoload_singleton("BoidsProcess_2D", "res://addons/boids/boids_process_2d.tscn")
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_custom_type("Flock")
|
||||
remove_custom_type("Boid2D")
|
||||
remove_custom_type("Boid3D")
|
||||
remove_custom_type("BoidProperties")
|
||||
remove_autoload_singleton("BoidManager")
|
||||
remove_autoload_singleton("BoidsProcess_2D")
|
||||
|
20
addons/boids/boids.gdextension
Normal file
@ -0,0 +1,20 @@
|
||||
[configuration]
|
||||
entry_symbol = "gdext_rust_init"
|
||||
compatibility_minimum = 4.2
|
||||
reloadable = true
|
||||
|
||||
[libraries]
|
||||
linux.debug.x86_64 = "res://rust/target/debug/libboids.so"
|
||||
linux.release.x86_64 = "res://lib/libboids.x86.so"
|
||||
windows.debug.x86_64 = "res://rust/target/debug/boids.dll"
|
||||
windows.release.x86_64 = "res://lib/boids.x86.dll"
|
||||
macos.release = "res://lib/libboids.x86.dylib"
|
||||
macos.release.arm64 = "res://lib/libboids.arm64.dylib"
|
||||
|
||||
[icons]
|
||||
BoidProperties = "res://addons/boids/resources/boid_properties.svg"
|
||||
FlockProperties = "res://addons/boids/resources/flock_properties.svg"
|
||||
Flock2D = "res://addons/boids/resources/flock_2d.svg"
|
||||
Boid2D = "res://addons/boids/resources/boid_2d.svg"
|
||||
Flock3D = "res://addons/boids/resources/flock_3d.svg"
|
||||
Boid3D = "res://addons/boids/resources/boid_3d.svg"
|
@ -1,36 +0,0 @@
|
||||
extends Node
|
||||
class_name Flock
|
||||
|
||||
@export var goal_seperation: float = 25.0 ** 2
|
||||
@export var goal_alignment: float = 50.0 ** 2
|
||||
@export var goal_cohesion: float = 50.0 ** 2
|
||||
|
||||
var boids: Dictionary = {}
|
||||
|
||||
## a node that the flock will try to follow.
|
||||
## target should be either a Node2D or a Node3D (or any inheritors of these two).
|
||||
@export var target: Node
|
||||
|
||||
func _ready() -> void:
|
||||
self.child_entered_tree.connect(_register_boid)
|
||||
self.child_exiting_tree.connect(_unregister_boid)
|
||||
|
||||
_init_register_boid()
|
||||
|
||||
func _init_register_boid(node: Node = self) -> void:
|
||||
_register_boid(node)
|
||||
for child: Node in node.get_children():
|
||||
_init_register_boid(child)
|
||||
|
||||
func _register_boid(maybe_boid: Node) -> void:
|
||||
if maybe_boid is not Boid2D and maybe_boid is not Boid3D: return
|
||||
maybe_boid.flock = self
|
||||
boids[maybe_boid.get_instance_id()] = maybe_boid
|
||||
BoidManager.total_boid_count += 1
|
||||
print_verbose("[", self, "]", " boid ", maybe_boid, " registered")
|
||||
|
||||
func _unregister_boid(maybe_boid: Node) -> void:
|
||||
if maybe_boid is not Boid2D and maybe_boid is not Boid3D: return
|
||||
boids.erase(maybe_boid.get_instance_id())
|
||||
BoidManager.total_boid_count -= 1
|
||||
print_verbose("[", self, "]", " boid ", maybe_boid, " unregistered")
|
@ -1,122 +0,0 @@
|
||||
extends RefCounted
|
||||
class_name Grid
|
||||
|
||||
var _cells: Dictionary
|
||||
var _scale: float
|
||||
var size: Vector3
|
||||
var scaled_points: Dictionary
|
||||
|
||||
func build(unscaled_size: Vector3, scale: float, boids: Array):
|
||||
_scale = scale
|
||||
size = Vector3(_scale_axis(unscaled_size.x), _scale_axis(unscaled_size.y), _scale_axis(unscaled_size.z))
|
||||
_cells.clear()
|
||||
scaled_points.clear()
|
||||
|
||||
var idx := 0
|
||||
for boid in boids:
|
||||
var scaled_point := _scale_point(boid._get_boid_position())
|
||||
_add_body(boid, scaled_point)
|
||||
scaled_points[boid.get_instance_id()] = scaled_point
|
||||
idx += 1
|
||||
|
||||
|
||||
func _scale_axis(point: float) -> float:
|
||||
return floorf(point / _scale)
|
||||
|
||||
|
||||
func _scale_point(vector: Vector3) -> Vector3:
|
||||
var scaled_point = (vector / _scale).floor()
|
||||
scaled_point.x = minf(maxf(scaled_point.x, 0), size.x)
|
||||
scaled_point.y = minf(maxf(scaled_point.y, 0), size.y)
|
||||
scaled_point.z = minf(maxf(scaled_point.z, 0), size.z)
|
||||
return scaled_point
|
||||
|
||||
|
||||
func _add_body(body: Node, scaled_point: Vector3) -> void:
|
||||
var boids := _cells.get(scaled_point, [])
|
||||
boids.append(body)
|
||||
_cells[scaled_point] = boids
|
||||
|
||||
func _get_cell(x: float, y: float, z: float, write_to: Array[Node]) -> void:
|
||||
write_to.append_array(_cells.get(Vector3(x, y, z), []))
|
||||
|
||||
func get_nearby_boids(boid: Node) -> Array[Node]:
|
||||
var scaled_point: Vector3 = scaled_points[boid.get_instance_id()]
|
||||
|
||||
# keep the points in bounds
|
||||
var x := minf(maxf(scaled_point.x, 0), size.x)
|
||||
var y := minf(maxf(scaled_point.y, 0), size.y)
|
||||
var z := minf(maxf(scaled_point.z, 0), size.z)
|
||||
|
||||
var results: Array[Node] = []
|
||||
var gb := func(x, y, z): _get_cell(x, y, z, results)
|
||||
gb.call(x, y, z)
|
||||
|
||||
var up := y - 1
|
||||
var down := y + 1
|
||||
var left := x - 1
|
||||
var right := x + 1
|
||||
var forwards := z - 1
|
||||
var backwards := z + 1
|
||||
|
||||
# up
|
||||
if up > 0:
|
||||
gb.call(x, up, z)
|
||||
if left > 0:
|
||||
gb.call(left, up, z)
|
||||
if right <= size.x:
|
||||
gb.call(right, up, z)
|
||||
if forwards > 0:
|
||||
gb.call(x, up, forwards)
|
||||
if left > 0:
|
||||
gb.call(left, up, forwards)
|
||||
if right <= size.x:
|
||||
gb.call(right, up, forwards)
|
||||
if backwards <= size.z:
|
||||
gb.call(x, up, backwards)
|
||||
if left > 0:
|
||||
gb.call(left, up, backwards)
|
||||
if right <= size.x:
|
||||
gb.call(right, up, backwards)
|
||||
# down
|
||||
if down <= size.y:
|
||||
gb.call(x, down, z)
|
||||
if left > 0:
|
||||
gb.call(left, down, z)
|
||||
if right <= size.x:
|
||||
gb.call(right, down, z)
|
||||
if forwards > 0:
|
||||
gb.call(x, down, forwards)
|
||||
if left > 0:
|
||||
gb.call(left, down, forwards)
|
||||
if right <= size.x:
|
||||
gb.call(right, down, forwards)
|
||||
if backwards <= size.z:
|
||||
gb.call(x, down, backwards)
|
||||
if left > 0:
|
||||
gb.call(left, down, backwards)
|
||||
if right <= size.x:
|
||||
gb.call(right, down, backwards)
|
||||
|
||||
# forwards
|
||||
if forwards > 0:
|
||||
gb.call(x, y, forwards)
|
||||
if left > 0:
|
||||
gb.call(left, y, forwards)
|
||||
if right <= size.x:
|
||||
gb.call(right, y, forwards)
|
||||
|
||||
if backwards <= size.z:
|
||||
gb.call(x, y, backwards)
|
||||
if left > 0:
|
||||
gb.call(left, y, backwards)
|
||||
if right <= size.x:
|
||||
gb.call(right, y, backwards)
|
||||
|
||||
# left and right
|
||||
if left > 0:
|
||||
gb.call(left, y, z)
|
||||
if right <= size.x:
|
||||
gb.call(right, y, z)
|
||||
|
||||
return results
|
4
addons/boids/process_boids.tscn
Normal file
@ -0,0 +1,4 @@
|
||||
[gd_scene format=3 uid="uid://c84c62urmhxbm"]
|
||||
|
||||
[node name="BoidsProcess" type="BoidsProcess"]
|
||||
process_2d = true
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c32akbxu1rkj8"
|
||||
path="res://.godot/imported/boid_2d.svg-4548c85817be0f2e607fa4357493b734.ctex"
|
||||
path="res://.godot/imported/boid_2d.svg-6eb9c1bb2a917467b7bca481a26670cd.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/boids/boid_2d/boid_2d.svg"
|
||||
dest_files=["res://.godot/imported/boid_2d.svg-4548c85817be0f2e607fa4357493b734.ctex"]
|
||||
source_file="res://addons/boids/resources/boid_2d.svg"
|
||||
dest_files=["res://.godot/imported/boid_2d.svg-6eb9c1bb2a917467b7bca481a26670cd.ctex"]
|
||||
|
||||
[params]
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://drjkd138np2bs"
|
||||
path="res://.godot/imported/boid_3d.svg-88c9926717ca573fea33e015b5e5eabe.ctex"
|
||||
path="res://.godot/imported/boid_3d.svg-7eb8b2fd8a0459a17418f05b04812bab.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/boids/boid_3d/boid_3d.svg"
|
||||
dest_files=["res://.godot/imported/boid_3d.svg-88c9926717ca573fea33e015b5e5eabe.ctex"]
|
||||
source_file="res://addons/boids/resources/boid_3d.svg"
|
||||
dest_files=["res://.godot/imported/boid_3d.svg-7eb8b2fd8a0459a17418f05b04812bab.ctex"]
|
||||
|
||||
[params]
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bjix5s2wjg1te"
|
||||
path="res://.godot/imported/boid_properties.svg-65ee4ea68118f8a485d6b21ab00db988.ctex"
|
||||
path="res://.godot/imported/boid_properties.svg-a84e56cd5111635c4baa23dc685bed70.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/boids/boid_properties/boid_properties.svg"
|
||||
dest_files=["res://.godot/imported/boid_properties.svg-65ee4ea68118f8a485d6b21ab00db988.ctex"]
|
||||
source_file="res://addons/boids/resources/boid_properties.svg"
|
||||
dest_files=["res://.godot/imported/boid_properties.svg-a84e56cd5111635c4baa23dc685bed70.ctex"]
|
||||
|
||||
[params]
|
||||
|
78
addons/boids/resources/flock_2d.svg
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16.000001"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="flock_2d..svg"
|
||||
inkscape:export-filename="..\..\resources\boid_2d.png"
|
||||
inkscape:export-xdpi="768"
|
||||
inkscape:export-ydpi="768"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="24.544781"
|
||||
inkscape:cx="9.513224"
|
||||
inkscape:cy="7.3131636"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="1"
|
||||
spacingy="1.0000001"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 1,0.47939285 c 0,0 7,2.33333335 7,2.97323935 0,0.526761 -7,2.860094 -7,2.860094 2.333333,-2.333333 2.333333,-3.5 0,-5.83333335 z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 8.5092732,5.0267612 c 0,0 6.9999998,2.3333333 6.9999998,2.9732393 0,0.526761 -6.9999998,2.8600945 -6.9999998,2.8600945 2.3333328,-2.3333335 2.3333328,-3.5000005 0,-5.8333338 z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
<path
|
||||
style="fill:#8da5f3;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 2.4897504,9.4619873 c 0,0 7,2.3333327 7,2.9732387 0,0.526761 -7,2.860095 -7,2.860095 2.333333,-2.333334 2.333333,-3.500001 0,-5.8333337 z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
38
addons/boids/resources/flock_2d.svg.import
Normal file
@ -0,0 +1,38 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://vomei5qtdnve"
|
||||
path="res://.godot/imported/flock_2d.svg-47deab08c18f15991172d7e7bc5b0fba.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/boids/resources/flock_2d.svg"
|
||||
dest_files=["res://.godot/imported/flock_2d.svg-47deab08c18f15991172d7e7bc5b0fba.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
78
addons/boids/resources/flock_3d.svg
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16.000001"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
sodipodi:docname="flock_3d.svg"
|
||||
inkscape:export-filename="..\..\resources\boid_2d.png"
|
||||
inkscape:export-xdpi="768"
|
||||
inkscape:export-ydpi="768"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="true"
|
||||
inkscape:zoom="24.544781"
|
||||
inkscape:cx="9.513224"
|
||||
inkscape:cy="7.3131636"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1">
|
||||
<inkscape:grid
|
||||
id="grid1"
|
||||
units="px"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingx="1"
|
||||
spacingy="1.0000001"
|
||||
empcolor="#0099e5"
|
||||
empopacity="0.30196078"
|
||||
color="#0099e5"
|
||||
opacity="0.14901961"
|
||||
empspacing="5"
|
||||
dotted="false"
|
||||
gridanglex="30"
|
||||
gridanglez="30"
|
||||
visible="true" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#fc7f7f;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 1,0.47939285 c 0,0 7,2.33333335 7,2.97323935 0,0.526761 -7,2.860094 -7,2.860094 2.333333,-2.333333 2.333333,-3.5 0,-5.83333335 z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
<path
|
||||
style="fill:#fc7f7f;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 8.5092732,5.0267612 c 0,0 6.9999998,2.3333333 6.9999998,2.9732393 0,0.526761 -6.9999998,2.8600945 -6.9999998,2.8600945 2.3333328,-2.3333335 2.3333328,-3.5000005 0,-5.8333338 z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
<path
|
||||
style="fill:#fc7f7f;fill-opacity:1;stroke-width:26.4565;stroke-linejoin:bevel"
|
||||
d="m 2.4897504,9.4619873 c 0,0 7,2.3333327 7,2.9732387 0,0.526761 -7,2.860095 -7,2.860095 2.333333,-2.333334 2.333333,-3.500001 0,-5.8333337 z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="cscc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
38
addons/boids/resources/flock_3d.svg.import
Normal file
@ -0,0 +1,38 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c2uweiyvhkth2"
|
||||
path="res://.godot/imported/flock_3d.svg-acc5687a4886765157480151046c93a8.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/boids/resources/flock_3d.svg"
|
||||
dest_files=["res://.godot/imported/flock_3d.svg-acc5687a4886765157480151046c93a8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@ -3,7 +3,7 @@
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b8kh6tdumqytn"
|
||||
path="res://.godot/imported/flock.svg-e863ec0929f57a6863c3b7914e0d4cd3.ctex"
|
||||
path="res://.godot/imported/flock_properties.svg-0c28b950b4cd1e6d443e4480692c1ed5.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
@ -11,8 +11,8 @@ metadata={
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/boids/flock/flock.svg"
|
||||
dest_files=["res://.godot/imported/flock.svg-e863ec0929f57a6863c3b7914e0d4cd3.ctex"]
|
||||
source_file="res://addons/boids/resources/flock_properties.svg"
|
||||
dest_files=["res://.godot/imported/flock_properties.svg-0c28b950b4cd1e6d443e4480692c1ed5.ctex"]
|
||||
|
||||
[params]
|
||||
|
@ -1,17 +1,8 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://b2sg3n42rkbx8"]
|
||||
[gd_scene load_steps=4 format=3 uid="uid://b2sg3n42rkbx8"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/boids/boid_2d/boid_2d.gd" id="1_vh1uc"]
|
||||
[ext_resource type="Texture2D" uid="uid://rk5u1wthr0n0" path="res://examples/boids/2d/example_boid.svg" id="2_jx2vb"]
|
||||
[ext_resource type="Script" path="res://addons/boids/boid_properties/boid_properties.gd" id="2_up2nk"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_m74bv"]
|
||||
script = ExtResource("2_up2nk")
|
||||
max_speed = 4.0
|
||||
max_force = 1.0
|
||||
alignment = 1.5
|
||||
cohesion = 1.0
|
||||
seperation = 1.2
|
||||
targeting = 0.8
|
||||
[sub_resource type="BoidProperties" id="BoidProperties_a6wou"]
|
||||
|
||||
[sub_resource type="GDScript" id="GDScript_ldfpo"]
|
||||
resource_name = "example_boid_sprite"
|
||||
@ -20,14 +11,13 @@ script/source = "extends Sprite2D
|
||||
@onready var boid: Boid2D = get_parent()
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
var dir := boid.velocity.normalized()
|
||||
var dir := boid.get_velocity().normalized()
|
||||
var target_rot := atan2(dir.y, dir.x)
|
||||
rotation = move_toward(rotation, target_rot, delta * PI * 2.0 * absf(target_rot - rotation))
|
||||
"
|
||||
|
||||
[node name="ExampleBoid" type="Node2D"]
|
||||
script = ExtResource("1_vh1uc")
|
||||
properties = SubResource("Resource_m74bv")
|
||||
[node name="ExampleBoid" type="Boid2D"]
|
||||
properties = SubResource("BoidProperties_a6wou")
|
||||
|
||||
[node name="Sprite2D" type="Sprite2D" parent="."]
|
||||
texture = ExtResource("2_jx2vb")
|
||||
|
@ -2,9 +2,9 @@ extends Node2D
|
||||
|
||||
func _ready() -> void:
|
||||
for flock in get_children():
|
||||
for i in 100: spawnBoid(flock)
|
||||
for i in 1000: spawnBoid(flock)
|
||||
|
||||
func spawnBoid(flock: Flock) -> void:
|
||||
func spawnBoid(flock: Flock2D) -> void:
|
||||
var boid: Boid2D = preload("../example_boid.tscn").instantiate()
|
||||
var screensize := get_viewport_rect().size
|
||||
boid.modulate = Color(randf(), randf(), randf(), 1)
|
||||
|
@ -1,10 +1,11 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://op0qicvpbjt6"]
|
||||
|
||||
[ext_resource type="Script" path="res://examples/boids/2d/simple/example.gd" id="1_3gcrf"]
|
||||
[ext_resource type="Script" path="res://addons/boids/flock/flock.gd" id="2_1xeeb"]
|
||||
|
||||
[sub_resource type="FlockProperties" id="FlockProperties_cvyp0"]
|
||||
|
||||
[node name="Example" type="Node2D"]
|
||||
script = ExtResource("1_3gcrf")
|
||||
|
||||
[node name="Flock" type="Node" parent="."]
|
||||
script = ExtResource("2_1xeeb")
|
||||
[node name="Flock" type="Flock2D" parent="."]
|
||||
properties = SubResource("FlockProperties_cvyp0")
|
||||
|
64
export_presets.cfg
Normal file
@ -0,0 +1,64 @@
|
||||
[preset.0]
|
||||
|
||||
name="Windows Desktop"
|
||||
platform="Windows Desktop"
|
||||
runnable=true
|
||||
advanced_options=false
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
exclude_filter=""
|
||||
export_path="../boid-addon-out/win.exe"
|
||||
encryption_include_filters=""
|
||||
encryption_exclude_filters=""
|
||||
encrypt_pck=false
|
||||
encrypt_directory=false
|
||||
script_export_mode=2
|
||||
|
||||
[preset.0.options]
|
||||
|
||||
custom_template/debug=""
|
||||
custom_template/release=""
|
||||
debug/export_console_wrapper=1
|
||||
binary_format/embed_pck=false
|
||||
texture_format/s3tc_bptc=true
|
||||
texture_format/etc2_astc=false
|
||||
binary_format/architecture="x86_64"
|
||||
codesign/enable=false
|
||||
codesign/timestamp=true
|
||||
codesign/timestamp_server_url=""
|
||||
codesign/digest_algorithm=1
|
||||
codesign/description=""
|
||||
codesign/custom_options=PackedStringArray()
|
||||
application/modify_resources=true
|
||||
application/icon=""
|
||||
application/console_wrapper_icon=""
|
||||
application/icon_interpolation=4
|
||||
application/file_version=""
|
||||
application/product_version=""
|
||||
application/company_name=""
|
||||
application/product_name=""
|
||||
application/file_description=""
|
||||
application/copyright=""
|
||||
application/trademarks=""
|
||||
application/export_angle=0
|
||||
application/export_d3d12=0
|
||||
application/d3d12_agility_sdk_multiarch=true
|
||||
ssh_remote_deploy/enabled=false
|
||||
ssh_remote_deploy/host="user@host_ip"
|
||||
ssh_remote_deploy/port="22"
|
||||
ssh_remote_deploy/extra_args_ssh=""
|
||||
ssh_remote_deploy/extra_args_scp=""
|
||||
ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}'
|
||||
$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}'
|
||||
$trigger = New-ScheduledTaskTrigger -Once -At 00:00
|
||||
$settings = New-ScheduledTaskSettingsSet
|
||||
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings
|
||||
Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true
|
||||
Start-ScheduledTask -TaskName godot_remote_debug
|
||||
while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 }
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue"
|
||||
ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue
|
||||
Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue
|
||||
Remove-Item -Recurse -Force '{temp_dir}'"
|
@ -16,11 +16,7 @@ config/features=PackedStringArray("4.3", "GL Compatibility")
|
||||
|
||||
[autoload]
|
||||
|
||||
BoidManager="*res://addons/boids/boid_manager.gd"
|
||||
|
||||
[debug]
|
||||
|
||||
settings/stdout/verbose_stdout=true
|
||||
ProcessBoids="*res://addons/boids/process_boids.tscn"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
@ -30,7 +26,3 @@ enabled=PackedStringArray("res://addons/boids/plugin.cfg")
|
||||
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
renderer/rendering_method.mobile="gl_compatibility"
|
||||
|
||||
[threading]
|
||||
|
||||
worker_pool/max_threads=20
|
||||
|
1
rust/.gdignore
Normal file
@ -0,0 +1 @@
|
||||
rust/
|
340
rust/Cargo.lock
generated
Normal file
@ -0,0 +1,340 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boids"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"godot",
|
||||
"indexmap",
|
||||
"rayon",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "gdextension-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=releases#6d902e8a6060007f4ab94cd78882247ae2558d96"
|
||||
|
||||
[[package]]
|
||||
name = "gensym"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94"
|
||||
|
||||
[[package]]
|
||||
name = "godot"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#7634fe769d1fcb66209586f0b6c06aac40978253"
|
||||
dependencies = [
|
||||
"godot-core",
|
||||
"godot-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-bindings"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#7634fe769d1fcb66209586f0b6c06aac40978253"
|
||||
dependencies = [
|
||||
"gdextension-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-cell"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#7634fe769d1fcb66209586f0b6c06aac40978253"
|
||||
|
||||
[[package]]
|
||||
name = "godot-codegen"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#7634fe769d1fcb66209586f0b6c06aac40978253"
|
||||
dependencies = [
|
||||
"godot-bindings",
|
||||
"heck",
|
||||
"nanoserde",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-core"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#7634fe769d1fcb66209586f0b6c06aac40978253"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"godot-bindings",
|
||||
"godot-cell",
|
||||
"godot-codegen",
|
||||
"godot-ffi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-ffi"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#7634fe769d1fcb66209586f0b6c06aac40978253"
|
||||
dependencies = [
|
||||
"gensym",
|
||||
"godot-bindings",
|
||||
"godot-codegen",
|
||||
"libc",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "godot-macros"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/godot-rust/gdext?branch=master#7634fe769d1fcb66209586f0b6c06aac40978253"
|
||||
dependencies = [
|
||||
"godot-bindings",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"venial",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.158"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "nanoserde"
|
||||
version = "0.1.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de9cf844ab1e25a0353525bd74cb889843a6215fa4a0d156fd446f4857a1b99"
|
||||
dependencies = [
|
||||
"nanoserde-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanoserde-derive"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e943b2c21337b7e3ec6678500687cdc741b7639ad457f234693352075c082204"
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "venial"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6816bc32f30bf8dd1b3adb04de8406c7bf187d2f923bd9e4c0b99365d012613f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
20
rust/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "boids"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
stats = []
|
||||
|
||||
[dependencies]
|
||||
godot = { git = "https://github.com/godot-rust/gdext", branch = "master", features = ["api-4-3", "experimental-threads"] }
|
||||
glam = { version = "0.28", features = ["fast-math"] }
|
||||
rayon = { version = "1.10" }
|
||||
rustc-hash = "2"
|
||||
indexmap = "2.4.0"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
96
rust/src/boid/boid_2d.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use super::*;
|
||||
use godot::prelude::*;
|
||||
|
||||
use crate::{BoidProperties, Flock2D};
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(init, base=Node2D)]
|
||||
pub struct Boid2D {
|
||||
#[export]
|
||||
properties: Gd<BoidProperties>,
|
||||
props: BoidProperties,
|
||||
vel: Vec2,
|
||||
flock_id: i64,
|
||||
base: Base<Node2D>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl Boid2D {
|
||||
#[func]
|
||||
#[inline(always)]
|
||||
fn get_velocity(&self) -> Vector2 {
|
||||
Vector2::new(self.vel.x, self.vel.y)
|
||||
}
|
||||
|
||||
#[func]
|
||||
#[inline(always)]
|
||||
pub fn get_id(&self) -> i64 {
|
||||
self.base().instance_id().to_i64()
|
||||
}
|
||||
|
||||
#[func]
|
||||
#[inline(always)]
|
||||
pub fn get_flock_id(&self) -> i64 {
|
||||
self.flock_id
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl INode2D for Boid2D {
|
||||
fn enter_tree(&mut self) {
|
||||
let Some(mut flock) = self
|
||||
.to_gd()
|
||||
.get_parent()
|
||||
.and_then(|gd| gd.try_cast::<Flock2D>().ok())
|
||||
else {
|
||||
let boid_id = self.get_id();
|
||||
godot_error!("[Boid2D:{boid_id}] boids parent isn't a Flock2D, or has no parent");
|
||||
return;
|
||||
};
|
||||
let mut flock = flock.bind_mut();
|
||||
flock.register_boid(self.get_id());
|
||||
self.flock_id = flock.get_id();
|
||||
}
|
||||
|
||||
fn ready(&mut self) {
|
||||
self.props = self.properties.bind().clone();
|
||||
}
|
||||
|
||||
fn exit_tree(&mut self) {
|
||||
let mut flock = godot::global::instance_from_id(self.get_flock_id())
|
||||
.unwrap()
|
||||
.cast::<Flock2D>();
|
||||
flock.bind_mut().unregister_boid(self.get_id());
|
||||
}
|
||||
}
|
||||
|
||||
impl Boid for Boid2D {
|
||||
#[inline(always)]
|
||||
fn apply_force(&mut self, force: Vec3) {
|
||||
self.vel += force.xy();
|
||||
let new_vel = self.vel.clamp_length_max(self.props.max_speed);
|
||||
self.vel = new_vel;
|
||||
self.base_mut().translate(Vector2::new(new_vel.x, new_vel.y));
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_boid_position(&self) -> Vec3 {
|
||||
let pos = self.base().get_position();
|
||||
vec3(pos.x, pos.y, 0.0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_boid_velocity(&self) -> Vec3 {
|
||||
vec3(self.vel.x, self.vel.y, 0.0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_boid_properties(&self) -> &BoidProperties {
|
||||
&self.props
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_flock_id(&self) -> i64 {
|
||||
self.get_flock_id()
|
||||
}
|
||||
}
|
0
rust/src/boid/boid_3d.rs
Normal file
122
rust/src/boid/mod.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use std::ops::Sub;
|
||||
use std::sync::Arc;
|
||||
|
||||
use glam::*;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::{BoidProperties, FlockProperties};
|
||||
|
||||
pub mod boid_2d;
|
||||
pub mod boid_3d;
|
||||
|
||||
pub trait Boid {
|
||||
fn apply_force(&mut self, force: Vec3);
|
||||
fn get_boid_position(&self) -> Vec3;
|
||||
fn get_boid_velocity(&self) -> Vec3;
|
||||
fn get_boid_properties(&self) -> &BoidProperties;
|
||||
|
||||
fn get_flock_id(&self) -> i64;
|
||||
}
|
||||
|
||||
struct CalcArgs {
|
||||
steer: Vec3,
|
||||
align: Vec3,
|
||||
cohere: Vec3,
|
||||
|
||||
steer_count: i32,
|
||||
align_count: i32,
|
||||
cohere_count: i32,
|
||||
}
|
||||
|
||||
impl CalcArgs {
|
||||
const fn identity() -> Self {
|
||||
Self {
|
||||
steer: Vec3::ZERO,
|
||||
align: Vec3::ZERO,
|
||||
cohere: Vec3::ZERO,
|
||||
steer_count: 0,
|
||||
align_count: 0,
|
||||
cohere_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_boid(
|
||||
boid_pos: Vec3,
|
||||
boid_vel: Vec3,
|
||||
boid_props: BoidProperties,
|
||||
flock_props: FlockProperties,
|
||||
boids: Arc<Vec<(Vec3, Vec3)>>,
|
||||
target_position: Option<Vec3>,
|
||||
) -> Vec3 {
|
||||
//godot::godot_print!("[Boids] executing from thread {:?}", rayon::current_thread_index());
|
||||
|
||||
let mut calced = boids
|
||||
.par_iter()
|
||||
.fold(CalcArgs::identity, |mut acc, (aboid_pos, aboid_vel)| {
|
||||
let dist = boid_pos.distance_squared(*aboid_pos);
|
||||
if dist > f32::EPSILON {
|
||||
if dist < flock_props.goal_seperation {
|
||||
let diff = (boid_pos.sub(*aboid_pos)).normalize() / f32::sqrt(dist);
|
||||
acc.steer += diff;
|
||||
acc.steer_count += 1;
|
||||
}
|
||||
if dist < flock_props.goal_alignment {
|
||||
acc.align += *aboid_vel;
|
||||
acc.align_count += 1;
|
||||
}
|
||||
if dist < flock_props.goal_cohesion {
|
||||
acc.cohere += *aboid_pos;
|
||||
acc.cohere_count += 1;
|
||||
}
|
||||
}
|
||||
acc
|
||||
})
|
||||
.reduce(CalcArgs::identity, |mut left, right| {
|
||||
left.steer += right.steer;
|
||||
left.align += right.align;
|
||||
left.cohere += right.cohere;
|
||||
left.steer_count += right.steer_count;
|
||||
left.align_count += right.align_count;
|
||||
left.cohere_count += right.cohere_count;
|
||||
left
|
||||
});
|
||||
|
||||
if calced.steer_count > 0 {
|
||||
calced.steer /= calced.steer_count as f32;
|
||||
}
|
||||
if calced.align_count > 0 {
|
||||
calced.align /= calced.align_count as f32;
|
||||
}
|
||||
if calced.cohere_count > 0 {
|
||||
calced.cohere /= calced.cohere_count as f32;
|
||||
calced.cohere -= boid_pos;
|
||||
}
|
||||
|
||||
let max_speed = boid_props.max_speed;
|
||||
let max_force = boid_props.max_force;
|
||||
if calced.align.length_squared() > 0.0 {
|
||||
calced.align =
|
||||
(calced.align.normalize() * max_speed - boid_vel).clamp_length_max(max_force);
|
||||
}
|
||||
if calced.steer.length_squared() > 0.0 {
|
||||
calced.steer =
|
||||
(calced.steer.normalize() * max_speed - boid_vel).clamp_length_max(max_force);
|
||||
}
|
||||
if calced.cohere.length_squared() > 0.0 {
|
||||
calced.cohere =
|
||||
(calced.cohere.normalize() * max_speed - boid_vel).clamp_length_max(max_force);
|
||||
}
|
||||
|
||||
let target = target_position.map_or(Vec3::ZERO, |target_position| {
|
||||
((target_position - boid_pos) - boid_vel).clamp_length_max(max_force)
|
||||
});
|
||||
|
||||
let steer_force = calced.steer * boid_props.seperation;
|
||||
let align_force = calced.align * boid_props.alignment;
|
||||
let cohere_force = calced.cohere * boid_props.cohesion;
|
||||
let target_force = target * boid_props.targeting;
|
||||
let force = steer_force + align_force + cohere_force + target_force;
|
||||
|
||||
force
|
||||
}
|
24
rust/src/boid_properties.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use godot::prelude::*;
|
||||
|
||||
#[derive(Default, Clone, Debug, GodotClass)]
|
||||
#[class(tool, init, base=Resource)]
|
||||
pub struct BoidProperties {
|
||||
#[export]
|
||||
#[init(val = 4.0)]
|
||||
pub max_speed: f32,
|
||||
#[export]
|
||||
#[init(val = 1.0)]
|
||||
pub max_force: f32,
|
||||
#[export]
|
||||
#[init(val = 1.5)]
|
||||
pub alignment: f32,
|
||||
#[export]
|
||||
#[init(val = 1.0)]
|
||||
pub cohesion: f32,
|
||||
#[export]
|
||||
#[init(val = 1.2)]
|
||||
pub seperation: f32,
|
||||
#[export]
|
||||
#[init(val = 0.8)]
|
||||
pub targeting: f32,
|
||||
}
|
102
rust/src/flock/flock_2d.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use glam::*;
|
||||
use godot::prelude::*;
|
||||
|
||||
use crate::{get_singleton, Boid, Boid2D, BoidProperties, FlockProperties, FxIndexMap};
|
||||
|
||||
use super::Flock;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(init, base=Node2D)]
|
||||
pub struct Flock2D {
|
||||
#[export]
|
||||
properties: Gd<FlockProperties>,
|
||||
props: FlockProperties,
|
||||
#[export]
|
||||
target: Option<Gd<Node2D>>,
|
||||
pub boids: FxIndexMap<i64, Gd<Boid2D>>,
|
||||
base: Base<Node2D>,
|
||||
}
|
||||
|
||||
impl Flock2D {
|
||||
pub fn register_boid(&mut self, boid_id: i64) {
|
||||
let boid: Gd<Boid2D> = godot::global::instance_from_id(boid_id).unwrap().cast();
|
||||
self.boids.insert(boid_id, boid.clone());
|
||||
get_singleton().bind_mut().register_boid_2d(boid_id, boid);
|
||||
let flock_id = self.get_id();
|
||||
godot_print!("[Flock2D:{flock_id}] boid {boid_id} registered");
|
||||
}
|
||||
|
||||
pub fn unregister_boid(&mut self, boid_id: i64) {
|
||||
self.boids.shift_remove(&boid_id);
|
||||
get_singleton().bind_mut().unregister_boid_2d(boid_id);
|
||||
let flock_id = self.get_id();
|
||||
godot_print!("[Flock2D:{flock_id}] boid {boid_id} unregistered");
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl INode2D for Flock2D {
|
||||
fn enter_tree(&mut self) {
|
||||
get_singleton().bind_mut().register_flock_2d(self.get_id())
|
||||
}
|
||||
|
||||
fn ready(&mut self) {
|
||||
self.props = self.properties.bind().clone();
|
||||
}
|
||||
|
||||
fn exit_tree(&mut self) {
|
||||
get_singleton()
|
||||
.bind_mut()
|
||||
.unregister_flock_2d(self.get_id())
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl Flock2D {
|
||||
#[func]
|
||||
#[inline(always)]
|
||||
pub fn get_id(&self) -> i64 {
|
||||
self.base().instance_id().to_i64()
|
||||
}
|
||||
}
|
||||
|
||||
impl Flock for Flock2D {
|
||||
#[inline(always)]
|
||||
fn get_flock_properties(&self) -> &FlockProperties {
|
||||
&self.props
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_target_position(&self) -> Option<Vec3> {
|
||||
self.target.as_ref().map(|t| {
|
||||
let pos = t.get_position();
|
||||
vec3(pos.x, pos.y, 0.0)
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_boids_posvel(&self) -> Vec<(Vec3, Vec3)> {
|
||||
let boid_count = self.boids.len();
|
||||
let mut result = Vec::with_capacity(boid_count);
|
||||
result.extend(self.boids.values().map(|b| {
|
||||
let b = b.bind();
|
||||
(b.get_boid_position(), b.get_boid_velocity())
|
||||
}));
|
||||
result
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_boids(&self) -> impl Iterator<Item = (&i64, (Vec3, Vec3, BoidProperties))> {
|
||||
self.boids.iter().map(|(id, boid)| {
|
||||
let boid = boid.bind();
|
||||
(
|
||||
id,
|
||||
(
|
||||
boid.get_boid_position(),
|
||||
boid.get_boid_velocity(),
|
||||
boid.get_boid_properties().clone(),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
12
rust/src/flock/mod.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use crate::{BoidProperties, FlockProperties};
|
||||
|
||||
use glam::*;
|
||||
|
||||
pub mod flock_2d;
|
||||
|
||||
pub trait Flock {
|
||||
fn get_flock_properties(&self) -> &FlockProperties;
|
||||
fn get_target_position(&self) -> Option<Vec3>;
|
||||
fn get_boids(&self) -> impl Iterator<Item = (&i64, (Vec3, Vec3, BoidProperties))>;
|
||||
fn get_boids_posvel(&self) -> Vec<(Vec3, Vec3)>;
|
||||
}
|
18
rust/src/flock_properties.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use godot::prelude::*;
|
||||
|
||||
#[derive(Default, Clone, Debug, GodotClass)]
|
||||
#[class(tool, init, base=Resource)]
|
||||
pub struct FlockProperties {
|
||||
#[export]
|
||||
#[init(val = 625.0)]
|
||||
/// squared
|
||||
pub goal_seperation: f32,
|
||||
#[export]
|
||||
#[init(val = 2500.0)]
|
||||
/// squared
|
||||
pub goal_alignment: f32,
|
||||
#[export]
|
||||
#[init(val = 2500.0)]
|
||||
/// squared
|
||||
pub goal_cohesion: f32,
|
||||
}
|
245
rust/src/lib.rs
Normal file
@ -0,0 +1,245 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use glam::*;
|
||||
use godot::{
|
||||
classes::Engine,
|
||||
obj::{bounds::DeclUser, Bounds},
|
||||
prelude::*,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use rayon::prelude::*;
|
||||
|
||||
mod boid;
|
||||
mod boid_properties;
|
||||
mod flock;
|
||||
mod flock_properties;
|
||||
|
||||
pub use boid::{boid_2d::*, boid_3d::*, Boid};
|
||||
pub use boid_properties::BoidProperties;
|
||||
pub use flock::{flock_2d::*, Flock};
|
||||
pub use flock_properties::FlockProperties;
|
||||
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
type FxIndexMap<K, V> = IndexMap<K, V, FxBuildHasher>;
|
||||
|
||||
const SINGLETON_NAME: &str = "Boids";
|
||||
|
||||
fn get_singleton_name() -> StringName {
|
||||
StringName::from(SINGLETON_NAME)
|
||||
}
|
||||
|
||||
fn get_singleton() -> Gd<Boids> {
|
||||
Engine::singleton()
|
||||
.get_singleton(get_singleton_name())
|
||||
.unwrap()
|
||||
.cast()
|
||||
}
|
||||
|
||||
struct BoidsExtension;
|
||||
|
||||
#[gdextension]
|
||||
unsafe impl ExtensionLibrary for BoidsExtension {
|
||||
fn on_level_init(level: InitLevel) {
|
||||
match level {
|
||||
InitLevel::Scene => {
|
||||
let singleton = Boids::new_alloc().upcast::<Object>();
|
||||
Engine::singleton().register_singleton(get_singleton_name(), singleton);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_level_deinit(level: InitLevel) {
|
||||
if level == InitLevel::Scene {
|
||||
// Get the `Engine` instance and `StringName` for your singleton.
|
||||
let mut engine = Engine::singleton();
|
||||
let singleton_name = get_singleton_name();
|
||||
|
||||
// We need to retrieve the pointer to the singleton object,
|
||||
// as it has to be freed manually - unregistering singleton
|
||||
// doesn't do it automatically.
|
||||
let singleton = engine
|
||||
.get_singleton(singleton_name.clone())
|
||||
.expect("cannot retrieve the singleton");
|
||||
|
||||
// Unregistering singleton and freeing the object itself is needed
|
||||
// to avoid memory leaks and warnings, especially for hot reloading.
|
||||
engine.unregister_singleton(singleton_name);
|
||||
singleton.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(init, base=Node)]
|
||||
pub struct BoidsProcess {
|
||||
#[export]
|
||||
process_2d: bool,
|
||||
#[export]
|
||||
process_3d: bool,
|
||||
#[export]
|
||||
#[init(val = 1)]
|
||||
process_per_tick: i64,
|
||||
boids: Option<Gd<Boids>>,
|
||||
engine: Option<Gd<Engine>>,
|
||||
}
|
||||
|
||||
impl BoidsProcess {
|
||||
#[inline(always)]
|
||||
fn get_boids_singleton(&mut self) -> &mut Gd<Boids> {
|
||||
unsafe { self.boids.as_mut().unwrap_unchecked() }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_engine_singleton(&self) -> &Gd<Engine> {
|
||||
unsafe { self.engine.as_ref().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl INode for BoidsProcess {
|
||||
#[inline(always)]
|
||||
fn ready(&mut self) {
|
||||
self.boids = Some(get_singleton());
|
||||
self.engine = Some(Engine::singleton());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn physics_process(&mut self, _: f64) {
|
||||
if self.get_engine_singleton().get_physics_frames() % (self.process_per_tick as u64) == 0 {
|
||||
if self.process_2d {
|
||||
self.get_boids_singleton().bind_mut().process_boids_2d();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(init, base=Object)]
|
||||
struct Boids {
|
||||
flocks2d: FxIndexMap<i64, Gd<Flock2D>>,
|
||||
boids2d: FxIndexMap<i64, Gd<Boid2D>>,
|
||||
base: Base<Object>,
|
||||
}
|
||||
|
||||
impl Boids {
|
||||
fn register_flock_2d(&mut self, flock_id: i64) {
|
||||
let flock = godot::global::instance_from_id(flock_id).unwrap().cast();
|
||||
self.flocks2d.insert(flock_id, flock);
|
||||
godot_print!("[Boids] flock {flock_id} registered");
|
||||
}
|
||||
|
||||
fn unregister_flock_2d(&mut self, flock_id: i64) {
|
||||
self.flocks2d.shift_remove(&flock_id);
|
||||
godot_print!("[Boids] flock {flock_id} unregistered");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn register_boid_2d(&mut self, boid_id: i64, boid: Gd<Boid2D>) {
|
||||
self.boids2d.insert(boid_id, boid);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unregister_boid_2d(&mut self, boid_id: i64) {
|
||||
self.boids2d.shift_remove(&boid_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl Boids {
|
||||
#[func]
|
||||
#[inline(always)]
|
||||
fn process_boids_2d(&mut self) {
|
||||
process_boids(&mut self.boids2d, &self.flocks2d)
|
||||
}
|
||||
|
||||
#[func]
|
||||
#[inline(always)]
|
||||
fn get_total_boid_count(&self) -> i64 {
|
||||
self.boids2d.len() as i64
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
const fn to_glam_vec(godot_vec: Vector3) -> Vec3 {
|
||||
vec3(godot_vec.x, godot_vec.y, godot_vec.z)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn process_boids<F, B>(boids: &mut FxIndexMap<i64, Gd<B>>, flocks: &FxIndexMap<i64, Gd<F>>)
|
||||
where
|
||||
F: Flock + GodotClass,
|
||||
F: Bounds<Declarer = DeclUser>,
|
||||
B: Boid + GodotClass,
|
||||
B: Bounds<Declarer = DeclUser>,
|
||||
{
|
||||
#[cfg(feature = "stats")]
|
||||
let time = std::time::Instant::now();
|
||||
let total_boid_count = boids.len();
|
||||
let mut calc_funcs = Vec::with_capacity(total_boid_count);
|
||||
for (_, flock) in flocks.iter() {
|
||||
let flock = flock.bind();
|
||||
let flock_props = flock.get_flock_properties();
|
||||
let target_position = flock.get_target_position();
|
||||
let boids = Arc::new(flock.get_boids_posvel());
|
||||
for (boid_id, (boid_pos, boid_vel, boid_props)) in flock.get_boids() {
|
||||
let boid_id = *boid_id;
|
||||
let flock_props = flock_props.clone();
|
||||
let target_position = target_position.clone();
|
||||
let boids = boids.clone();
|
||||
calc_funcs.push((boid_id, move || {
|
||||
boid::calculate_boid(
|
||||
boid_pos,
|
||||
boid_vel,
|
||||
boid_props,
|
||||
flock_props,
|
||||
boids,
|
||||
target_position,
|
||||
)
|
||||
}));
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "stats")]
|
||||
godot_print!(
|
||||
"[Boids] preparing all calculations took {} ms",
|
||||
time.elapsed().as_millis()
|
||||
);
|
||||
|
||||
#[cfg(feature = "stats")]
|
||||
let time = std::time::Instant::now();
|
||||
let forces: Vec<(i64, Vec3)> = calc_funcs
|
||||
.into_par_iter()
|
||||
.fold(
|
||||
|| Vec::<(i64, Vec3)>::with_capacity(total_boid_count / 10),
|
||||
|mut acc, (boid_id, calc_fn)| {
|
||||
let force = calc_fn();
|
||||
acc.push((boid_id, force));
|
||||
acc
|
||||
},
|
||||
)
|
||||
.reduce(
|
||||
|| Vec::<(i64, Vec3)>::with_capacity(total_boid_count / 10),
|
||||
|mut left, mut right| {
|
||||
left.append(&mut right);
|
||||
left
|
||||
},
|
||||
);
|
||||
#[cfg(feature = "stats")]
|
||||
godot_print!(
|
||||
"[Boids] calculating all boids took {} ms",
|
||||
time.elapsed().as_millis()
|
||||
);
|
||||
|
||||
#[cfg(feature = "stats")]
|
||||
let time = std::time::Instant::now();
|
||||
for (boid_id, force) in forces {
|
||||
let boid = boids.get_mut(&boid_id).unwrap();
|
||||
boid.bind_mut().apply_force(force);
|
||||
}
|
||||
#[cfg(feature = "stats")]
|
||||
godot_print!(
|
||||
"[Boids] applying forces took {} ms",
|
||||
time.elapsed().as_millis()
|
||||
);
|
||||
}
|