Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
831d75d
Added file and directory stubs for boids simulation
kro-ma Apr 29, 2025
582c5d2
Added boid scene and started structuring boids_simulation demo scene
versjon May 8, 2025
8dc91c6
Merge branch 'Ivorforce:main' into main
kro-ma May 8, 2025
6ec259f
Set solver in boids_model.gd
kro-ma May 10, 2025
f2bf2c8
workaround empty solver in boids_model.gd
kro-ma May 10, 2025
76290e1
spawning first boid
kro-ma May 10, 2025
e6dd5fc
boid facing right direction and moving
kro-ma May 11, 2025
0f42051
boid moving with noise in circle
kro-ma May 11, 2025
7ee180c
smoother angle movement of boid
kro-ma May 11, 2025
742d4ca
further smoother angle movement of boid
kro-ma May 11, 2025
1c51a42
dynamic number of boids (hard-coded) moving into different directions
kro-ma May 11, 2025
21373ff
Now with smoother noise turns: dynamic number of boids (hard-coded) m…
kro-ma May 12, 2025
7efb972
Slider for number of boids implemented
kro-ma May 12, 2025
45e6c8f
Merge branch 'Ivorforce:main' into main
kro-ma May 12, 2025
a468cb1
Separation implemented
kro-ma May 14, 2025
822bf02
Cohesion implemented
kro-ma May 14, 2025
791e1f7
GUI sliders implemented
kro-ma May 14, 2025
b47ce3e
FPS Label activated
kro-ma May 14, 2025
9d81074
cleanup and TODOs
kro-ma May 16, 2025
90b4252
signals for scene nodes and sliders
kro-ma May 16, 2025
76567b9
Added ND-Solver updating Boids and handling params, rules yet to be i…
versjon May 18, 2025
21235ba
Numdot solver working, removed noise from model
versjon May 20, 2025
e8353ad
Godot Solver working
versjon May 20, 2025
39d088b
Fixed ND Solver crashing n range 0
versjon May 20, 2025
bcdab21
Quickfix to avoid crashing
versjon May 21, 2025
f94ef60
Merge branch 'Ivorforce:main' into main
kro-ma May 31, 2025
786c26f
doc strings for boids_model functions
kro-ma Jun 1, 2025
787431e
doc strings for gd_solver
kro-ma Jun 1, 2025
5e3f040
doc strings for nd_solver
kro-ma Jun 1, 2025
ce61025
Merge branch 'Ivorforce:main' into main
kro-ma Jun 1, 2025
52810c9
revert whitespace changes in demo/demos/game_of_life/gd_solver.gd
kro-ma Jun 1, 2025
14608c3
revert whitespace changes in demo/demos/game_of_life/
kro-ma Jun 1, 2025
fe13483
revert whitespace changes in demo/demos/sirs_model/
kro-ma Jun 1, 2025
2a2e92f
revert whitespace changes in demo/demos/game_of_life/nd_solver.gd
kro-ma Jun 1, 2025
4eef504
revert whitespace changes in demo/demos/kuramoto_model/
kro-ma Jun 1, 2025
60a8355
revert whitespace changes in demo/demos/wave_equation/ and launcher
kro-ma Jun 1, 2025
0fdbcbd
revert changes () in nd_solvers
kro-ma Jun 1, 2025
c2a3d3f
revert changes in xtensor
kro-ma Jun 1, 2025
6967335
revert changes in godot-cpp
kro-ma Jun 1, 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ __pycache__
# Custom config
/custom.py
/numdot_config.py

# Program Debug Database
*.pdb
Binary file added demo/demos/boids_simulation/boid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions demo/demos/boids_simulation/boid.png.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://wxlyngv1jx7x"
path="res://.godot/imported/boid.png-3c7feec7ec9b604bb681557bcbd1345a.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://demos/boids_simulation/boid.png"
dest_files=["res://.godot/imported/boid.png-3c7feec7ec9b604bb681557bcbd1345a.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
4 changes: 4 additions & 0 deletions demo/demos/boids_simulation/boid.tres
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[gd_resource type="CompressedTexture2D" format=3 uid="uid://ci2unstco8670"]

[resource]
load_path = "res://.godot/imported/boid.png-3c7feec7ec9b604bb681557bcbd1345a.ctex"
22 changes: 22 additions & 0 deletions demo/demos/boids_simulation/boid.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[gd_scene load_steps=2 format=3 uid="uid://b65qrclghjwb1"]

[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_nx014"]

[node name="Boid" type="Control"]
modulate = Color(0.1634, 0.76712, 0.86, 1)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

[node name="BoidTexture" type="TextureRect" parent="."]
z_index = -1
layout_mode = 0
offset_left = -12.8
offset_top = -25.6
offset_right = 243.2
offset_bottom = 358.4
scale = Vector2(0.1, 0.1)
texture = SubResource("CompressedTexture2D_nx014")
164 changes: 164 additions & 0 deletions demo/demos/boids_simulation/boids_model.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
extends Node2D

@export var boid_count: int = 20
@export var speed: float = 200.0
@export var range: float = 100.0
@export var separation_weight: float = 1.0
@export var alignment_weight: float = 0.5
@export var cohesion_weight: float = 0.5

@export var scale_factor: float = 0.1

@export_category("Simulation parameters")
@export var solver: BoidsSolver

var texture = preload("res://demos/boids_simulation/boid.tres")

var frame_time: float = 0.

func _ready() -> void:
"""
Synchronize initial values with GUI, instantiate boid nodes, and initialize solver.
"""
# Synchronize initial values with GUI
boid_count = %NumberOfBoidsSlider.value
speed = %SpeedSlider.value
range = %RangeSlider.value
separation_weight = %SeparationSlider.value
alignment_weight = %AlignmentSlider.value
cohesion_weight = %CohesionSlider.value

# Instantiate Boid nodes under $Boids
if not has_node("Boids"):
var boids_container = Node2D.new()
boids_container.name = "Boids"
add_child(boids_container)
initialize_boids(boid_count)

solver.initialize()


func _process(delta: float) -> void:
"""
Called every frame. Performs simulation step and updates GUI metrics.

Parameters:
delta (float): Time since last frame in seconds.
"""
frame_time = Time.get_ticks_usec()
solver.simulation_step(delta)
frame_time = Time.get_ticks_usec() - frame_time

%FPSLabel.text = "FPS: " + str(Engine.get_frames_per_second())
%FrameTimeLabel.text = "Delta (ms): " + str(snappedf(frame_time/1000, 1e-3))


func initialize_boids(target_count: int) -> void:
"""
Initializes boid nodes to match the specified target count.

Parameters:
target_count (int): Desired number of boids.
"""
var current_count = $Boids.get_child_count()
if current_count > target_count:
var boids = $Boids.get_children()
for boid in range(target_count, current_count).map(func(index): return boids[index]):
boid.queue_free()
else:
for i in range(target_count-current_count):
add_boid(current_count+i)


func add_boid(i: int):
"""
Adds a single boid sprite to the simulation.

Parameters:
i (int): Index identifier for naming the new boid.
"""
var boid = Sprite2D.new()
boid.texture = texture
boid.set_modulate(Color.DARK_SLATE_GRAY)
boid.z_index = -1
boid.scale *= scale_factor
boid.name = "Boid" + str(i)
$Boids.add_child(boid)


func _on_speed_slider_value_changed(value) -> void:
"""
Updates speed based on slider input and updates GUI label.

Parameters:
value (float): New speed value from the slider.
"""
speed = value
%SpeedLabel.text = "Speed: " + str(speed)

func _on_range_slider_value_changed(value) -> void:
"""
Updates range based on slider input and updates GUI label.

Parameters:
value (float): New range value from the slider.
"""
range = value
%RangeLabel.text = "Range: " + str(range)

func _on_separation_slider_value_changed(value) -> void:
"""
Updates separation weight based on slider input and updates GUI label.

Parameters:
value (float): New separation weight value.
"""
separation_weight = value
%SeparationLabel.text = "Separation: " + str(separation_weight)

func _on_alignment_slider_value_changed(value) -> void:
"""
Updates alignment weight based on slider input and updates GUI label.

Parameters:
value (float): New alignment weight value.
"""
alignment_weight = value
%AlignmentLabel.text = "Alignment: " + str(alignment_weight)

func _on_cohesion_slider_value_changed(value) -> void:
"""
Updates cohesion weight based on slider input and updates GUI label.

Parameters:
value (float): New cohesion weight value.
"""
cohesion_weight = value
%CohesionLabel.text = "Cohesion: " + str(cohesion_weight)

func _on_restart_button_pressed() -> void:
"""
Resets and reinitializes the solver upon pressing the restart button.
"""
solver.initialize()

func _on_solver_option_item_selected(index: int) -> void:
"""
Switches solver based on selection from options.

Parameters:
index (int): Index of the newly selected solver.
"""
solver = $Solvers.get_child(index)
solver.initialize()

func _on_number_of_boids_slider_value_changed(value) -> void:
"""
Adjusts the number of boids based on slider input and updates GUI label.

Parameters:
value (int): New desired count of boids.
"""
boid_count = value
%NumberOfBoids.text = "Boids: " + str(boid_count)
initialize_boids(boid_count)
134 changes: 134 additions & 0 deletions demo/demos/boids_simulation/gd_solver.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
extends BoidsSolver

var positions: Array[Vector2]
var directions: Array[Vector2]

var screen_size: Vector2

func initialize() -> void:
"""
Initializes the solver by setting the screen size
and generating initial position and direction arrays.
"""
screen_size = params.get_viewport_rect().size

# Initialize position and direction vector
positions = initialize_position_array(params.boid_count)
directions = initialize_direction_array(params.boid_count)


# Helper function to create position direction vector with length
func initialize_position_array(length: int) -> Array[Vector2]:
"""
Creates array of random positions within screen bounds.

Parameters:
length (int): Number of positions.

Returns:
Array[Vector2]: Random position vectors.
"""
# Initialize position Array with |length| Vector2s
# Values are random 2D-positions on screen
var positions_xy: Array[Vector2] = []
for i in range(length):
positions_xy.append(Vector2(randf()*screen_size.x, randf()*screen_size.y))
return positions_xy


# Helper function to create random direction vector with length
func initialize_direction_array(length: int) -> Array[Vector2]:
"""
Creates array of normalized random direction vectors.

Parameters:
length (int): Number of directions.

Returns:
Array[Vector2]: Random direction vectors.
"""
# Initialize direction Array with |length| Vector2s
# Values are normalized 2D-vectors with random angle
var directions_xy: Array[Vector2] = []
for i in range(length):
var angle = 2*PI*randf()
var direction = Vector2(cos(angle), sin(angle))
directions_xy.append(direction)
return directions_xy


func simulation_step(delta: float) -> void:
"""
Updates positions and directions based on separation,
alignment, and cohesion during simulation.

Parameters:
delta (float): Time since last frame.
"""
# Check if boid_count has been changed, update vector sizes accordingly
var boid_count_difference = params.boid_count-positions.size()
if boid_count_difference < 0:
positions.resize(params.boid_count)
directions.resize(params.boid_count)
elif boid_count_difference > 0:
var new_positions := initialize_position_array(boid_count_difference)
var new_directions := initialize_direction_array(boid_count_difference)
positions.append_array(new_positions)
directions.append_array(new_directions)

# Calculate separation, alignment and cohesion per boid
var separations: Array[Vector2] = []
var alignments: Array[Vector2] = []
var cohesions: Array[Vector2] = []
for i in range(params.boid_count):
var separation := Vector2(0, 0)
var alignment := Vector2(0, 0)
var cohesion := Vector2(0, 0)
for j in range(params.boid_count):
var distance = (positions[i] - positions[j]).length()
if distance < params.range * 0.5 and distance != 0:
separation += (positions[i]-positions[j])/(distance**2)
if distance < params.range:
alignment += directions[j]
cohesion += positions[j]-positions[i]
if separation != Vector2(0, 0):
separation /= separation.length()
if cohesion != Vector2(0, 0):
cohesion /= cohesion.length()
if alignment != Vector2(0, 0):
alignment /= alignment.length()
separations.append(separation)
cohesions.append(cohesion)
alignments.append(alignment)

for i in range(params.boid_count):
# Apply separation, alignment and cohesion to direction vector and normalize
directions[i] += separations[i] * params.separation_weight * delta * 2.0
directions[i] += cohesions[i] * params.cohesion_weight * delta
directions[i] += alignments[i] * params.alignment_weight * delta
directions[i] /= directions[i].length()

# Move positions in directions by delta*speed
positions[i] += directions[i]*delta*params.speed

# Make boid positions wrap around at borders of screen
positions[i] = Vector2(fposmod(positions[i].x, screen_size.x), fposmod(positions[i].y, screen_size.y))
update_boids()


func update_boids() -> void:
"""
Updates graphical positions and orientations of boids.
"""
var boids := params.get_node("Boids").get_children()
for i in range(params.boid_count):
var boid: Node2D = boids[i]

# Set position of boids by updating origin of transform
boid.transform.origin = positions[i]

# Set rotation of boids by aligning direction with up-vector of transform
var up := directions[i]
var right := Vector2(up.y, -up.x)
boid.transform.x = right*params.scale_factor
boid.transform.y = -up*params.scale_factor
Loading