203 lines
6.2 KiB
GDScript
203 lines
6.2 KiB
GDScript
extends Node3D
|
|
|
|
@export var resources : ResourcePreloader
|
|
|
|
@export_subgroup("Initialisation")
|
|
|
|
@export var start_count : int = 10
|
|
@export var spawn_volume : Vector3 = Vector3.ONE
|
|
|
|
@export_subgroup("Boid settings")
|
|
|
|
@export var protected_range : float = 1.0
|
|
@export var visual_range : float = 2.0
|
|
@export var centering_factor : float = 0.0005
|
|
@export var avoid_factor : float = 0.05
|
|
@export var matching_factor : float = 0.05
|
|
@export var min_speed : float = 2.0
|
|
@export var max_speed : float = 3.0
|
|
@export var turn_factor : float = 0.2
|
|
@export var margin : float = 0.3
|
|
|
|
@export_subgroup("Edges")
|
|
|
|
@export var shape : Shape3D
|
|
|
|
@export_subgroup("Gravity")
|
|
|
|
@export var use_gravity : bool = true
|
|
@export var gravity_vector : Vector3 = Vector3(0, -9.807, 0)
|
|
@export var gravity_factor : float = 0.1
|
|
|
|
var boids : Array[Boid]
|
|
var boid_scene : PackedScene
|
|
|
|
var protected_range_squared : float
|
|
var visual_range_squared : float
|
|
|
|
# Called when the node enters the scene tree for the first time.
|
|
func _ready():
|
|
protected_range_squared = pow(protected_range, 2)
|
|
visual_range_squared = pow(visual_range, 2)
|
|
|
|
boid_scene = resources.get_resource("Boid")
|
|
for i in range(start_count):
|
|
var boid : Boid = boid_scene.instantiate()
|
|
boid.position = random_pos()
|
|
boid.velocity = random_vel()
|
|
add_child(boid)
|
|
boids.push_back(boid)
|
|
|
|
|
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
|
func _process(delta):
|
|
for boid in boids:
|
|
|
|
var neighboring_boids : int = 0
|
|
|
|
var close : Vector3 = Vector3.ZERO
|
|
var position_avg : Vector3 = Vector3.ZERO
|
|
var velocity_avg : Vector3 = Vector3.ZERO
|
|
|
|
for other_boid in boids.filter(func(cur): return cur != boid):
|
|
# Micro optimization, don't compute distance yet, just check if it's in the range by checking
|
|
# individual dimension distances
|
|
var diff : Vector3 = other_boid.position - boid.position
|
|
|
|
if abs(diff.x) < visual_range and abs(diff.y) < visual_range and abs(diff.z) < visual_range:
|
|
# Using the squared distance so it performs better
|
|
var squared_distance : float = boid.position.distance_squared_to(other_boid.position)
|
|
|
|
# If other boid too close, add the difference (if multiple boids too close, it should average naturally)
|
|
if squared_distance < protected_range_squared:
|
|
close -= diff
|
|
|
|
# If other boid in visual range, we add its position and velocity
|
|
elif squared_distance < visual_range_squared:
|
|
position_avg += other_boid.position
|
|
velocity_avg += other_boid.velocity
|
|
|
|
neighboring_boids += 1
|
|
|
|
# We collected all data related to neighboring boids
|
|
# Let's change our boid behavior
|
|
|
|
if neighboring_boids > 0:
|
|
position_avg /= neighboring_boids
|
|
velocity_avg /= neighboring_boids
|
|
|
|
boid.velocity = (boid.velocity +
|
|
(position_avg - boid.position) * centering_factor +
|
|
(velocity_avg - boid.velocity) * matching_factor
|
|
)
|
|
|
|
# Add avoidance contribution to velocity
|
|
boid.velocity = boid.velocity + (close * avoid_factor)
|
|
|
|
# Edge avoidance
|
|
boid.velocity = apply_edge_avoidance(boid.position, boid.velocity)
|
|
|
|
boid.velocity = apply_gravity(boid.velocity, delta)
|
|
boid.velocity = apply_speed_limits(boid.velocity)
|
|
|
|
# Update position, finally!
|
|
boid.position += boid.velocity * delta
|
|
boid.look_at(boid.global_position + boid.velocity.normalized())
|
|
|
|
|
|
func apply_edge_avoidance(position : Vector3, velocity : Vector3) -> Vector3:
|
|
if shape is BoxShape3D:
|
|
velocity = apply_cube_edge_avoidance(position, velocity, shape as BoxShape3D)
|
|
elif shape is CylinderShape3D:
|
|
velocity = apply_cylinder_edge_avoidance(position, velocity, shape as CylinderShape3D)
|
|
elif shape is SphereShape3D:
|
|
velocity = apply_sphere_edge_avoidance(position, velocity, shape as SphereShape3D)
|
|
else:
|
|
print("Shape not implemented!")
|
|
return velocity
|
|
|
|
|
|
func apply_cube_edge_avoidance(position : Vector3, velocity : Vector3, box : BoxShape3D) -> Vector3:
|
|
if position.x < (-box.size.x / 2.0) + margin:
|
|
velocity.x += turn_factor
|
|
if position.x > (box.size.x / 2.0) - margin:
|
|
velocity.x -= turn_factor
|
|
if position.y < (-box.size.y / 2.0) + margin:
|
|
velocity.y += turn_factor
|
|
if position.y > (box.size.y / 2.0) - margin:
|
|
velocity.y -= turn_factor
|
|
if position.z < (-box.size.z / 2.0) + margin:
|
|
velocity.z += turn_factor
|
|
if position.z > (box.size.z / 2.0) - margin:
|
|
velocity.z -= turn_factor
|
|
|
|
return velocity
|
|
|
|
|
|
func apply_cylinder_edge_avoidance(position : Vector3, velocity : Vector3, cylinder : CylinderShape3D) -> Vector3:
|
|
if position.y < (-cylinder.height / 2.0) + margin:
|
|
velocity.y += turn_factor
|
|
if position.y > (cylinder.height / 2.0) - margin:
|
|
velocity.y -= turn_factor
|
|
|
|
# We're centering around zero so let's take the position as direction
|
|
var direction : Vector2 = Vector2(position.x, position.z)
|
|
|
|
if direction.length() > cylinder.radius - margin:
|
|
var turn : Vector2 = -direction.normalized() * turn_factor
|
|
velocity.x += turn.x
|
|
velocity.z += turn.y
|
|
|
|
return velocity
|
|
|
|
|
|
func apply_sphere_edge_avoidance(position : Vector3, velocity : Vector3, sphere : SphereShape3D) -> Vector3:
|
|
var direction : Vector3 = position
|
|
|
|
if direction.length() > sphere.radius - margin:
|
|
velocity = -direction.normalized() * turn_factor
|
|
|
|
return velocity
|
|
|
|
|
|
func apply_gravity(velocity : Vector3, delta : float) -> Vector3:
|
|
if use_gravity:
|
|
velocity += gravity_vector * gravity_factor * delta
|
|
|
|
return velocity
|
|
|
|
|
|
func apply_speed_limits(velocity : Vector3) -> Vector3:
|
|
# Boid's speed
|
|
var speed : float = velocity.length()
|
|
|
|
# Enforce max and min speed
|
|
if speed < min_speed:
|
|
velocity = (velocity / speed) * min_speed
|
|
if speed > max_speed:
|
|
velocity = (velocity / speed) * max_speed
|
|
|
|
return velocity
|
|
|
|
|
|
func random_pos() -> Vector3:
|
|
var x_range : Vector2 = Vector2(-spawn_volume.x / 2.0, spawn_volume.x / 2.0)
|
|
var y_range : Vector2 = Vector2(-spawn_volume.y / 2.0, spawn_volume.y / 2.0)
|
|
var z_range : Vector2 = Vector2(-spawn_volume.z / 2.0, spawn_volume.z / 2.0)
|
|
|
|
return Vector3(
|
|
randf_range(x_range.x, x_range.y),
|
|
randf_range(y_range.x, y_range.y),
|
|
randf_range(z_range.x, z_range.y)
|
|
)
|
|
|
|
|
|
func random_vel() -> Vector3:
|
|
var speed : float = pow(randf_range(min_speed, max_speed), 2)
|
|
var x : float = randf_range(0, speed)
|
|
speed -= x
|
|
var y : float = randf_range(0, speed)
|
|
speed -= y
|
|
var z : float = randf_range(0, speed)
|
|
return Vector3(sqrt(x), sqrt(y), sqrt(z))
|