diff --git a/addons/boids/boid_2d/boid_2d.gd b/addons/boids/boid_2d/boid_2d.gd index c786ae9..ca15842 100644 --- a/addons/boids/boid_2d/boid_2d.gd +++ b/addons/boids/boid_2d/boid_2d.gd @@ -10,12 +10,14 @@ 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 * BoidManager.SIMULATION_RATE + position += velocity func _get_boid_position() -> Vector3: return Vector3(position.x, position.y, 0.0) diff --git a/addons/boids/boid_3d/boid_3d.gd b/addons/boids/boid_3d/boid_3d.gd index f1c748c..29d7a8e 100644 --- a/addons/boids/boid_3d/boid_3d.gd +++ b/addons/boids/boid_3d/boid_3d.gd @@ -10,11 +10,13 @@ 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 * BoidManager.SIMULATION_RATE + position += velocity func _get_boid_position() -> Vector3: return position diff --git a/addons/boids/boid_manager.gd b/addons/boids/boid_manager.gd index 39dd4d4..34ae53c 100644 --- a/addons/boids/boid_manager.gd +++ b/addons/boids/boid_manager.gd @@ -4,8 +4,6 @@ extends Node # 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 -# simulate per n physics frame ticks -var SIMULATION_RATE: int = 1 var flocks: Dictionary = {} var total_boid_count: int = 0: @@ -17,6 +15,7 @@ var total_boid_count: int = 0: # 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) @@ -34,18 +33,20 @@ func _init_register_flock(node: Node = get_tree().root) -> void: func _register_flock(maybe_flock: Node) -> void: if maybe_flock is not Flock: return - flocks[maybe_flock.get_instance_id()] = maybe_flock + 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 - flocks.erase(maybe_flock.get_instance_id()) + 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: - # run the simulation at a given rate - if Engine.get_physics_frames() % SIMULATION_RATE == 0: - _process_boids() + _process_boids() func _process_boids() -> void: var total_parallel_tasks := total_boid_count / PARALLELIZATION_RATE @@ -55,8 +56,10 @@ func _process_boids() -> void: # organize the work into tasks for flock: Flock in flocks.values(): var flock_args := _pack_calc_args_flock(flock) - for boid in flock.boids.values(): - var args := _pack_calc_args_boid(boid, flock_args.duplicate()) + 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 @@ -97,7 +100,17 @@ func _pack_calc_args_flock(flock: Flock) -> Dictionary: flock_args['target_position'] = flock.target.global_position return flock_args -func _pack_calc_args_boid(boid, args: Dictionary) -> Dictionary: +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() @@ -109,7 +122,7 @@ func _calculate_boid_parallel(idx: int) -> void: 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]) + var force := _calculate_boid(args_array[arg_idx]) forces_array[arg_idx] = force arg_idx += 1 diff --git a/addons/boids/grid.gd b/addons/boids/grid.gd new file mode 100644 index 0000000..a7e8b89 --- /dev/null +++ b/addons/boids/grid.gd @@ -0,0 +1,122 @@ +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 diff --git a/examples/boids/2d/simple/example.gd b/examples/boids/2d/simple/example.gd index 0a0a621..cc82b19 100644 --- a/examples/boids/2d/simple/example.gd +++ b/examples/boids/2d/simple/example.gd @@ -2,7 +2,7 @@ extends Node2D func _ready() -> void: for flock in get_children(): - for i in 1000: spawnBoid(flock) + for i in 100: spawnBoid(flock) func spawnBoid(flock: Flock) -> void: var boid: Boid2D = preload("../example_boid.tscn").instantiate()