1use glam::Vec2;
22use std::collections::{HashMap, VecDeque};
23
24#[derive(Debug, Clone)]
36pub struct FlowField {
37 pub width: usize,
38 pub height: usize,
39 pub cell_size: f32,
40 pub costs: Vec<f32>,
42 pub integration: Vec<f32>,
44 pub directions: Vec<Vec2>,
46 pub origin: Vec2,
47}
48
49impl FlowField {
50 pub fn new(width: usize, height: usize, cell_size: f32) -> Self {
52 let n = width * height;
53 FlowField {
54 width,
55 height,
56 cell_size,
57 costs: vec![1.0; n],
58 integration: vec![f32::INFINITY; n],
59 directions: vec![Vec2::ZERO; n],
60 origin: Vec2::ZERO,
61 }
62 }
63
64 pub fn with_origin(mut self, origin: Vec2) -> Self {
65 self.origin = origin;
66 self
67 }
68
69 pub fn set_cost(&mut self, x: usize, y: usize, cost: f32) {
71 if let Some(i) = self.idx(x, y) {
72 self.costs[i] = cost;
73 }
74 }
75
76 pub fn reset_costs(&mut self) {
78 self.costs.fill(1.0);
79 }
80
81 pub fn world_to_grid(&self, pos: Vec2) -> (usize, usize) {
83 let local = pos - self.origin;
84 let x = ((local.x / self.cell_size).floor() as isize)
85 .clamp(0, self.width as isize - 1) as usize;
86 let y = ((local.y / self.cell_size).floor() as isize)
87 .clamp(0, self.height as isize - 1) as usize;
88 (x, y)
89 }
90
91 pub fn grid_to_world(&self, x: usize, y: usize) -> Vec2 {
93 self.origin
94 + Vec2::new(
95 x as f32 * self.cell_size + self.cell_size * 0.5,
96 y as f32 * self.cell_size + self.cell_size * 0.5,
97 )
98 }
99
100 pub fn build_integration_field(&mut self, goals: &[(usize, usize)]) {
103 self.integration.fill(f32::INFINITY);
104 use std::collections::BinaryHeap;
106 use std::cmp::Ordering;
107
108 #[derive(Clone)]
109 struct Node(f32, usize); impl PartialEq for Node { fn eq(&self, o: &Self) -> bool { self.0 == o.0 } }
111 impl Eq for Node {}
112 impl PartialOrd for Node {
113 fn partial_cmp(&self, o: &Self) -> Option<Ordering> { Some(self.cmp(o)) }
114 }
115 impl Ord for Node {
116 fn cmp(&self, o: &Self) -> Ordering {
117 o.0.partial_cmp(&self.0).unwrap_or(Ordering::Equal)
118 }
119 }
120
121 let mut heap: BinaryHeap<Node> = BinaryHeap::new();
122 for &(gx, gy) in goals {
123 if let Some(i) = self.idx(gx, gy) {
124 if self.costs[i].is_finite() {
125 self.integration[i] = 0.0;
126 heap.push(Node(0.0, i));
127 }
128 }
129 }
130
131 while let Some(Node(cost, idx)) = heap.pop() {
132 if cost > self.integration[idx] { continue; }
133 let cx = idx % self.width;
134 let cy = idx / self.width;
135 for (nx, ny, dc) in self.cardinal_neighbors(cx, cy) {
136 let ni = ny * self.width + nx;
137 let new_cost = cost + self.costs[ni] * dc;
138 if new_cost < self.integration[ni] {
139 self.integration[ni] = new_cost;
140 heap.push(Node(new_cost, ni));
141 }
142 }
143 }
144 }
145
146 pub fn build_direction_field(&mut self) {
148 for y in 0..self.height {
149 for x in 0..self.width {
150 let idx = y * self.width + x;
151 if !self.costs[idx].is_finite() {
152 self.directions[idx] = Vec2::ZERO;
153 continue;
154 }
155 let mut best_val = self.integration[idx];
157 let mut best_dir = Vec2::ZERO;
158 for (nx, ny, _) in self.all_neighbors(x, y) {
159 let ni = ny * self.width + nx;
160 if self.integration[ni] < best_val {
161 best_val = self.integration[ni];
162 let dx = nx as f32 - x as f32;
163 let dy = ny as f32 - y as f32;
164 let d = Vec2::new(dx, dy);
165 best_dir = if d.length() > 0.0 { d.normalize() } else { Vec2::ZERO };
166 }
167 }
168 self.directions[idx] = best_dir;
169 }
170 }
171 }
172
173 pub fn get_direction(&self, world_pos: Vec2) -> Vec2 {
175 let local = world_pos - self.origin;
176 let fx = (local.x / self.cell_size - 0.5).max(0.0);
177 let fy = (local.y / self.cell_size - 0.5).max(0.0);
178 let x0 = (fx.floor() as usize).min(self.width - 1);
179 let y0 = (fy.floor() as usize).min(self.height - 1);
180 let x1 = (x0 + 1).min(self.width - 1);
181 let y1 = (y0 + 1).min(self.height - 1);
182 let tx = fx.fract();
183 let ty = fy.fract();
184
185 let d00 = self.directions[y0 * self.width + x0];
186 let d10 = self.directions[y0 * self.width + x1];
187 let d01 = self.directions[y1 * self.width + x0];
188 let d11 = self.directions[y1 * self.width + x1];
189
190 let top = d00.lerp(d10, tx);
191 let bottom = d01.lerp(d11, tx);
192 let result = top.lerp(bottom, ty);
193 if result.length_squared() > 0.0 { result.normalize() } else { Vec2::ZERO }
194 }
195
196 pub fn get_integration(&self, world_pos: Vec2) -> f32 {
198 let (x, y) = self.world_to_grid(world_pos);
199 self.integration[y * self.width + x]
200 }
201
202 pub fn is_passable(&self, x: usize, y: usize) -> bool {
204 self.idx(x, y).map(|i| self.costs[i].is_finite()).unwrap_or(false)
205 }
206
207 fn idx(&self, x: usize, y: usize) -> Option<usize> {
208 if x < self.width && y < self.height { Some(y * self.width + x) } else { None }
209 }
210
211 fn cardinal_neighbors(&self, x: usize, y: usize) -> Vec<(usize, usize, f32)> {
212 let mut out = Vec::with_capacity(4);
213 let dirs = [(-1i32, 0i32, 1.0f32), (1, 0, 1.0), (0, -1, 1.0), (0, 1, 1.0)];
214 for (dx, dy, dc) in dirs {
215 let nx = x as i32 + dx;
216 let ny = y as i32 + dy;
217 if nx >= 0 && ny >= 0 && nx < self.width as i32 && ny < self.height as i32 {
218 let nx = nx as usize; let ny = ny as usize;
219 if self.costs[ny * self.width + nx].is_finite() {
220 out.push((nx, ny, dc));
221 }
222 }
223 }
224 out
225 }
226
227 fn all_neighbors(&self, x: usize, y: usize) -> Vec<(usize, usize, f32)> {
228 let mut out = Vec::with_capacity(8);
229 for dx in -1i32..=1 {
230 for dy in -1i32..=1 {
231 if dx == 0 && dy == 0 { continue; }
232 let nx = x as i32 + dx;
233 let ny = y as i32 + dy;
234 if nx >= 0 && ny >= 0 && nx < self.width as i32 && ny < self.height as i32 {
235 let nx = nx as usize; let ny = ny as usize;
236 let dc = if dx.abs() + dy.abs() == 2 {
237 std::f32::consts::SQRT_2
238 } else {
239 1.0
240 };
241 if self.costs[ny * self.width + nx].is_finite() {
242 out.push((nx, ny, dc));
243 }
244 }
245 }
246 }
247 out
248 }
249
250 pub fn len(&self) -> usize { self.width * self.height }
252}
253
254#[derive(Debug)]
261pub struct FlowFieldCache {
262 pub capacity: usize,
263 fields: HashMap<(usize, usize), FlowField>,
264 access_order: VecDeque<(usize, usize)>,
266}
267
268impl FlowFieldCache {
269 pub fn new(capacity: usize) -> Self {
270 FlowFieldCache {
271 capacity,
272 fields: HashMap::new(),
273 access_order: VecDeque::new(),
274 }
275 }
276
277 pub fn get_or_build(
279 &mut self,
280 goal: (usize, usize),
281 width: usize,
282 height: usize,
283 cell_size: f32,
284 costs: &[f32],
285 ) -> &FlowField {
286 if !self.fields.contains_key(&goal) {
287 if self.fields.len() >= self.capacity {
289 if let Some(evict_key) = self.access_order.pop_front() {
290 self.fields.remove(&evict_key);
291 }
292 }
293 let mut field = FlowField::new(width, height, cell_size);
294 field.costs.copy_from_slice(costs);
295 field.build_integration_field(&[goal]);
296 field.build_direction_field();
297 self.fields.insert(goal, field);
298 self.access_order.push_back(goal);
299 } else {
300 self.access_order.retain(|k| k != &goal);
302 self.access_order.push_back(goal);
303 }
304 &self.fields[&goal]
305 }
306
307 pub fn invalidate(&mut self, goal: (usize, usize)) {
309 self.fields.remove(&goal);
310 self.access_order.retain(|k| k != &goal);
311 }
312
313 pub fn clear(&mut self) {
315 self.fields.clear();
316 self.access_order.clear();
317 }
318
319 pub fn len(&self) -> usize { self.fields.len() }
320 pub fn is_empty(&self) -> bool { self.fields.is_empty() }
321}
322
323#[derive(Debug, Clone)]
329pub struct FlowFieldAgent {
330 pub id: u64,
331 pub position: Vec2,
332 pub velocity: Vec2,
333 pub speed: f32,
334 pub radius: f32,
335}
336
337impl FlowFieldAgent {
338 pub fn new(id: u64, position: Vec2, speed: f32, radius: f32) -> Self {
339 FlowFieldAgent { id, position, velocity: Vec2::ZERO, speed, radius }
340 }
341}
342
343#[derive(Debug, Clone)]
345pub struct FlowFieldGroup {
346 pub agents: Vec<FlowFieldAgent>,
347 pub field: Option<FlowField>,
348 pub destination: Option<Vec2>,
349 pub separation_weight: f32,
350}
351
352impl FlowFieldGroup {
353 pub fn new() -> Self {
354 FlowFieldGroup {
355 agents: Vec::new(),
356 field: None,
357 destination: None,
358 separation_weight: 1.5,
359 }
360 }
361
362 pub fn add_agent(&mut self, agent: FlowFieldAgent) {
363 self.agents.push(agent);
364 }
365
366 pub fn set_destination(&mut self, dest: Vec2, field: FlowField) {
368 self.destination = Some(dest);
369 self.field = Some(field);
370 }
371
372 pub fn update(&mut self, dt: f32) {
374 let Some(ref field) = self.field else { return; };
375 let n = self.agents.len();
376
377 let mut desired: Vec<Vec2> = self.agents.iter().map(|a| {
379 let dir = field.get_direction(a.position);
380 dir * a.speed
381 }).collect();
382
383 for i in 0..n {
385 let mut sep = Vec2::ZERO;
386 for j in 0..n {
387 if i == j { continue; }
388 let diff = self.agents[i].position - self.agents[j].position;
389 let dist = diff.length();
390 let min_dist = self.agents[i].radius + self.agents[j].radius + 0.5;
391 if dist < min_dist && dist > 0.0 {
392 sep += (diff / dist) * (min_dist - dist);
393 }
394 }
395 desired[i] += sep * self.separation_weight;
396 }
397
398 for (agent, vel) in self.agents.iter_mut().zip(desired.iter()) {
400 agent.velocity = *vel;
401 agent.position += agent.velocity * dt;
402 }
403 }
404
405 pub fn arrived_count(&self, threshold: f32) -> usize {
407 let Some(dest) = self.destination else { return 0; };
408 self.agents.iter().filter(|a| a.position.distance(dest) <= threshold).count()
409 }
410
411 pub fn all_arrived(&self, threshold: f32) -> bool {
412 self.arrived_count(threshold) == self.agents.len()
413 }
414}
415
416impl Default for FlowFieldGroup {
417 fn default() -> Self { Self::new() }
418}
419
420#[derive(Debug, Clone)]
426pub struct Boid {
427 pub position: Vec2,
428 pub velocity: Vec2,
429 pub max_speed: f32,
430 pub max_force: f32,
431}
432
433impl Boid {
434 pub fn new(position: Vec2, velocity: Vec2, max_speed: f32, max_force: f32) -> Self {
435 Boid { position, velocity, max_speed, max_force }
436 }
437
438 fn limit_force(&self, force: Vec2) -> Vec2 {
439 let len = force.length();
440 if len > self.max_force { force / len * self.max_force } else { force }
441 }
442
443 fn limit_speed(&self, vel: Vec2) -> Vec2 {
444 let len = vel.length();
445 if len > self.max_speed { vel / len * self.max_speed } else { vel }
446 }
447}
448
449#[derive(Debug, Clone)]
451pub struct Flock {
452 pub boids: Vec<Boid>,
453 pub separation_weight: f32,
454 pub alignment_weight: f32,
455 pub cohesion_weight: f32,
456 pub neighbor_radius: f32,
458 pub separation_radius: f32,
460 pub bounds: Option<(Vec2, Vec2)>,
462}
463
464impl Flock {
465 pub fn new() -> Self {
466 Flock {
467 boids: Vec::new(),
468 separation_weight: 1.5,
469 alignment_weight: 1.0,
470 cohesion_weight: 1.0,
471 neighbor_radius: 5.0,
472 separation_radius: 2.0,
473 bounds: None,
474 }
475 }
476
477 pub fn add_agent(&mut self, pos: Vec2, vel: Vec2) {
479 self.boids.push(Boid::new(pos, vel, 5.0, 0.5));
480 }
481
482 pub fn add_boid(&mut self, boid: Boid) {
483 self.boids.push(boid);
484 }
485
486 pub fn update(&mut self, dt: f32) {
488 let n = self.boids.len();
489 let mut steering = vec![Vec2::ZERO; n];
490
491 for i in 0..n {
492 let sep = self.separation_force(i);
493 let ali = self.alignment_force(i);
494 let coh = self.cohesion_force(i);
495 steering[i] = sep * self.separation_weight
496 + ali * self.alignment_weight
497 + coh * self.cohesion_weight;
498 steering[i] = self.boids[i].limit_force(steering[i]);
499 }
500
501 for (boid, steer) in self.boids.iter_mut().zip(steering.iter()) {
502 boid.velocity = boid.limit_speed(boid.velocity + *steer * dt);
503 boid.position += boid.velocity * dt;
504 }
505
506 if let Some((bmin, bmax)) = self.bounds {
508 for boid in self.boids.iter_mut() {
509 if boid.position.x < bmin.x { boid.position.x = bmax.x; }
511 if boid.position.x > bmax.x { boid.position.x = bmin.x; }
512 if boid.position.y < bmin.y { boid.position.y = bmax.y; }
513 if boid.position.y > bmax.y { boid.position.y = bmin.y; }
514 }
515 }
516 }
517
518 pub fn separation_force(&self, idx: usize) -> Vec2 {
520 let boid = &self.boids[idx];
521 let mut force = Vec2::ZERO;
522 let mut count = 0;
523 for (i, other) in self.boids.iter().enumerate() {
524 if i == idx { continue; }
525 let diff = boid.position - other.position;
526 let dist = diff.length();
527 if dist < self.separation_radius && dist > 0.0 {
528 force += diff.normalize() / dist; count += 1;
530 }
531 }
532 if count > 0 {
533 force /= count as f32;
534 if force.length_squared() > 0.0 {
535 force = force.normalize() * boid.max_speed - boid.velocity;
536 force = boid.limit_force(force);
537 }
538 }
539 force
540 }
541
542 pub fn alignment_force(&self, idx: usize) -> Vec2 {
544 let boid = &self.boids[idx];
545 let mut avg_vel = Vec2::ZERO;
546 let mut count = 0;
547 for (i, other) in self.boids.iter().enumerate() {
548 if i == idx { continue; }
549 if boid.position.distance(other.position) < self.neighbor_radius {
550 avg_vel += other.velocity;
551 count += 1;
552 }
553 }
554 if count == 0 { return Vec2::ZERO; }
555 avg_vel /= count as f32;
556 if avg_vel.length_squared() > 0.0 {
557 avg_vel = avg_vel.normalize() * boid.max_speed;
558 }
559 let force = avg_vel - boid.velocity;
560 boid.limit_force(force)
561 }
562
563 pub fn cohesion_force(&self, idx: usize) -> Vec2 {
565 let boid = &self.boids[idx];
566 let mut center = Vec2::ZERO;
567 let mut count = 0;
568 for (i, other) in self.boids.iter().enumerate() {
569 if i == idx { continue; }
570 if boid.position.distance(other.position) < self.neighbor_radius {
571 center += other.position;
572 count += 1;
573 }
574 }
575 if count == 0 { return Vec2::ZERO; }
576 center /= count as f32;
577 let desired = center - boid.position;
578 if desired.length_squared() == 0.0 { return Vec2::ZERO; }
579 let desired = desired.normalize() * boid.max_speed;
580 let force = desired - boid.velocity;
581 boid.limit_force(force)
582 }
583
584 pub fn seek_target(&mut self, target: Vec2, weight: f32, dt: f32) {
586 for boid in self.boids.iter_mut() {
587 let diff = target - boid.position;
588 if diff.length_squared() < 0.0001 { continue; }
589 let desired = diff.normalize() * boid.max_speed;
590 let steer = boid.limit_force(desired - boid.velocity) * weight;
591 boid.velocity = boid.limit_speed(boid.velocity + steer * dt);
592 }
593 }
594
595 pub fn centroid(&self) -> Vec2 {
597 if self.boids.is_empty() { return Vec2::ZERO; }
598 let sum: Vec2 = self.boids.iter().map(|b| b.position).sum();
599 sum / self.boids.len() as f32
600 }
601
602 pub fn average_speed(&self) -> f32 {
604 if self.boids.is_empty() { return 0.0; }
605 self.boids.iter().map(|b| b.velocity.length()).sum::<f32>() / self.boids.len() as f32
606 }
607}
608
609impl Default for Flock {
610 fn default() -> Self { Self::new() }
611}
612
613#[derive(Debug, Clone)]
619pub struct DynamicObstacleField {
620 obstacles: Vec<(Vec2, f32, f32)>,
622}
623
624impl DynamicObstacleField {
625 pub fn new() -> Self { DynamicObstacleField { obstacles: Vec::new() } }
626
627 pub fn add_obstacle(&mut self, center: Vec2, radius: f32, cost_multiplier: f32) {
628 self.obstacles.push((center, radius, cost_multiplier));
629 }
630
631 pub fn clear(&mut self) { self.obstacles.clear(); }
632
633 pub fn apply_and_rebuild(&self, field: &mut FlowField, goals: &[(usize, usize)]) {
635 for &(center, radius, mult) in &self.obstacles {
636 let (cx, cy) = field.world_to_grid(center);
637 let cell_r = (radius / field.cell_size).ceil() as i32 + 1;
638 for dy in -cell_r..=cell_r {
639 for dx in -cell_r..=cell_r {
640 let nx = cx as i32 + dx;
641 let ny = cy as i32 + dy;
642 if nx < 0 || ny < 0 || nx >= field.width as i32 || ny >= field.height as i32 {
643 continue;
644 }
645 let world_pos = field.grid_to_world(nx as usize, ny as usize);
646 if world_pos.distance(center) <= radius {
647 let i = ny as usize * field.width + nx as usize;
648 field.costs[i] = (field.costs[i] * mult).min(f32::INFINITY);
649 }
650 }
651 }
652 }
653 field.build_integration_field(goals);
654 field.build_direction_field();
655 }
656}
657
658impl Default for DynamicObstacleField {
659 fn default() -> Self { Self::new() }
660}
661
662#[cfg(test)]
667mod tests {
668 use super::*;
669 use glam::Vec2;
670
671 fn make_field(w: usize, h: usize) -> FlowField {
672 FlowField::new(w, h, 1.0)
673 }
674
675 #[test]
676 fn test_build_integration_field() {
677 let mut field = make_field(10, 10);
678 field.build_integration_field(&[(9, 9)]);
679 assert_eq!(field.integration[9 * 10 + 9], 0.0);
681 assert!(field.integration[0].is_finite());
683 assert!(field.integration[0] > 0.0);
684 }
685
686 #[test]
687 fn test_build_direction_field() {
688 let mut field = make_field(10, 10);
689 field.build_integration_field(&[(9, 9)]);
690 field.build_direction_field();
691 let dir = field.directions[0];
693 assert!(dir.x > 0.0 || dir.y > 0.0);
694 }
695
696 #[test]
697 fn test_get_direction_normalized() {
698 let mut field = make_field(10, 10);
699 field.build_integration_field(&[(9, 9)]);
700 field.build_direction_field();
701 let dir = field.get_direction(Vec2::new(0.5, 0.5));
702 let len = dir.length();
704 assert!(len <= 1.01);
705 }
706
707 #[test]
708 fn test_impassable_cell() {
709 let mut field = make_field(10, 10);
710 field.set_cost(5, 5, f32::INFINITY);
711 field.build_integration_field(&[(9, 9)]);
712 field.build_direction_field();
713 assert_eq!(field.directions[5 * 10 + 5], Vec2::ZERO);
715 }
716
717 #[test]
718 fn test_multi_goal() {
719 let mut field = make_field(10, 10);
720 field.build_integration_field(&[(0, 0), (9, 9)]);
721 field.build_direction_field();
722 let val = field.integration[5 * 10 + 5];
724 assert!(val.is_finite());
725 }
726
727 #[test]
728 fn test_flow_field_cache_lru() {
729 let mut cache = FlowFieldCache::new(2);
730 let costs = vec![1.0f32; 100];
731 let _ = cache.get_or_build((9, 9), 10, 10, 1.0, &costs);
732 let _ = cache.get_or_build((0, 0), 10, 10, 1.0, &costs);
733 assert_eq!(cache.len(), 2);
734 let _ = cache.get_or_build((5, 5), 10, 10, 1.0, &costs);
736 assert_eq!(cache.len(), 2);
737 }
738
739 #[test]
740 fn test_flock_separation() {
741 let mut flock = Flock::new();
742 flock.add_agent(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0));
744 flock.add_agent(Vec2::new(0.1, 0.0), Vec2::new(-1.0, 0.0));
745 let sep0 = flock.separation_force(0);
746 assert!(sep0.length() > 0.0);
747 }
748
749 #[test]
750 fn test_flock_alignment() {
751 let mut flock = Flock::new();
752 flock.add_agent(Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0));
753 flock.add_agent(Vec2::new(1.0, 0.0), Vec2::new(1.0, 0.0));
754 let ali = flock.alignment_force(0);
755 assert!(ali.length() < 0.1);
757 }
758
759 #[test]
760 fn test_flock_cohesion() {
761 let mut flock = Flock::new();
762 flock.add_agent(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0));
763 flock.add_agent(Vec2::new(3.0, 0.0), Vec2::new(0.0, 0.0));
764 let coh = flock.cohesion_force(0);
765 assert!(coh.x > 0.0);
767 }
768
769 #[test]
770 fn test_flock_update() {
771 let mut flock = Flock::new();
772 for i in 0..10 {
773 flock.add_agent(Vec2::new(i as f32, 0.0), Vec2::new(1.0, 0.0));
774 }
775 let centroid_before = flock.centroid();
776 flock.update(0.1);
777 let centroid_after = flock.centroid();
778 assert!((centroid_after - centroid_before).length() > 0.0);
780 }
781
782 #[test]
783 fn test_flow_field_group_update() {
784 let mut field = make_field(20, 20);
785 field.build_integration_field(&[(19, 19)]);
786 field.build_direction_field();
787
788 let mut group = FlowFieldGroup::new();
789 for i in 0..5 {
790 group.add_agent(FlowFieldAgent::new(i as u64, Vec2::new(i as f32, 0.0), 2.0, 0.3));
791 }
792 group.set_destination(Vec2::new(19.5, 19.5), field);
793 group.update(0.1);
794 for agent in &group.agents {
796 assert!(agent.position.x >= 0.0);
797 }
798 }
799
800 #[test]
801 fn test_dynamic_obstacle_field() {
802 let mut field = make_field(10, 10);
803 let obs = DynamicObstacleField::new();
804 obs.apply_and_rebuild(&mut field, &[(9, 9)]);
805 assert!(field.integration[0].is_finite());
806 }
807
808 #[test]
809 fn test_world_to_grid_round_trip() {
810 let field = make_field(10, 10);
811 let pos = Vec2::new(3.7, 6.2);
812 let (gx, gy) = field.world_to_grid(pos);
813 let world = field.grid_to_world(gx, gy);
814 assert!((world.x - 3.5).abs() < 0.5);
815 assert!((world.y - 6.5).abs() < 0.5);
816 }
817
818 #[test]
819 fn test_flock_seek_target() {
820 let mut flock = Flock::new();
821 flock.add_agent(Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0));
822 flock.seek_target(Vec2::new(10.0, 0.0), 1.0, 0.1);
823 let speed = flock.boids[0].velocity.length();
824 assert!(speed > 0.0);
825 }
826}