1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "goose.h" 6 7 namespace { 8 // The maximum speed of a goose. Measured in meters/second. 9 const double kMaxSpeed = 2.0; 10 11 // The maximum force that can be applied to turn a goose when computing the 12 // aligment. Measured in meters/second/second. 13 const double kMaxTurningForce = 0.05; 14 15 // The neighbour radius of a goose. Only geese within this radius will affect 16 // the flocking computations of this goose. Measured in pixels. 17 const double kNeighbourRadius = 64.0; 18 19 // The minimum distance that a goose can be from this goose. If another goose 20 // comes within this distance of this goose, the flocking algorithm tries to 21 // move the geese apart. Measured in pixels. 22 const double kPersonalSpace = 32.0; 23 24 // The distance at which attractors have effect on a goose's direction. 25 const double kAttractorRadius = 320.0; 26 27 // The goose will try to turn towards geese within this distance (computed 28 // during the cohesion phase). Measured in pixels. 29 const double kMaxTurningDistance = 100.0; 30 31 // The weights used when computing the weighted sum the three flocking 32 // components. 33 const double kSeparationWeight = 2.0; 34 const double kAlignmentWeight = 1.0; 35 const double kCohesionWeight = 1.0; 36 37 } // namespace 38 39 40 Goose::Goose() : location_(0, 0), velocity_(0, 0) { 41 } 42 43 Goose::Goose(const Vector2& location, const Vector2& velocity) 44 : location_(location), 45 velocity_(velocity) { 46 } 47 48 void Goose::SimulationTick(const std::vector<Goose>& geese, 49 const std::vector<Vector2>& attractors, 50 const pp::Rect& flock_box) { 51 52 Vector2 acceleration = DesiredVector(geese, attractors); 53 velocity_.Add(acceleration); 54 55 // Limit the velocity to a maximum speed. 56 velocity_.Clamp(kMaxSpeed); 57 58 location_.Add(velocity_); 59 60 // Wrap the goose location to the flock box. 61 if (!flock_box.IsEmpty()) { 62 while (location_.x() < flock_box.x()) 63 location_.set_x(location_.x() + flock_box.width()); 64 65 while (location_.x() >= flock_box.right()) 66 location_.set_x(location_.x() - flock_box.width()); 67 68 while (location_.y() < flock_box.y()) 69 location_.set_y(location_.y() + flock_box.height()); 70 71 while (location_.y() >= flock_box.bottom()) 72 location_.set_y(location_.y() - flock_box.height()); 73 } 74 } 75 76 Vector2 Goose::DesiredVector(const std::vector<Goose>& geese, 77 const std::vector<Vector2>& attractors) { 78 // Loop over all the neighbouring geese in the flock, accumulating 79 // the separation mean, the alignment mean and the cohesion mean. 80 int32_t separation_count = 0; 81 Vector2 separation; 82 int32_t align_count = 0; 83 Vector2 alignment; 84 int32_t cohesion_count = 0; 85 Vector2 cohesion; 86 87 for (std::vector<Goose>::const_iterator goose_it = geese.begin(); 88 goose_it < geese.end(); 89 ++goose_it) { 90 const Goose& goose = *goose_it; 91 92 // Compute the distance from this goose to its neighbour. 93 Vector2 goose_delta = Vector2::Difference( 94 location_, goose.location()); 95 double distance = goose_delta.Magnitude(); 96 97 separation_count = AccumulateSeparation( 98 distance, goose_delta, &separation, separation_count); 99 100 align_count = AccumulateAlignment( 101 distance, goose, &alignment, align_count); 102 cohesion_count = AccumulateCohesion( 103 distance, goose, &cohesion, cohesion_count); 104 } 105 106 // Compute the means and create a weighted sum. This becomes the goose's new 107 // acceleration. 108 if (separation_count > 0) { 109 separation.Scale(1.0 / static_cast<double>(separation_count)); 110 } 111 if (align_count > 0) { 112 alignment.Scale(1.0 / static_cast<double>(align_count)); 113 // Limit the effect that alignment has on the final acceleration. The 114 // alignment component can overpower the others if there is a big 115 // difference between this goose's velocity and its neighbours'. 116 alignment.Clamp(kMaxTurningForce); 117 } 118 119 // Compute the effect of the attractors and blend this in with the flock 120 // cohesion component. An attractor has to be within kAttractorRadius to 121 // effect the heading of a goose. 122 for (size_t i = 0; i < attractors.size(); ++i) { 123 Vector2 attractor_direction = Vector2::Difference( 124 attractors[i], location_); 125 double distance = attractor_direction.Magnitude(); 126 if (distance < kAttractorRadius) { 127 attractor_direction.Scale(1000); // Each attractor acts like 1000 geese. 128 cohesion.Add(attractor_direction); 129 cohesion_count++; 130 } 131 } 132 133 // If there is a non-0 cohesion component, steer the goose so that it tries 134 // to follow the flock. 135 if (cohesion_count > 0) { 136 cohesion.Scale(1.0 / static_cast<double>(cohesion_count)); 137 cohesion = TurnTowardsTarget(cohesion); 138 } 139 // Compute the weighted sum. 140 separation.Scale(kSeparationWeight); 141 alignment.Scale(kAlignmentWeight); 142 cohesion.Scale(kCohesionWeight); 143 Vector2 weighted_sum = cohesion; 144 weighted_sum.Add(alignment); 145 weighted_sum.Add(separation); 146 return weighted_sum; 147 } 148 149 Vector2 Goose::TurnTowardsTarget(const Vector2& target) { 150 Vector2 desired_direction = Vector2::Difference(target, location_); 151 double distance = desired_direction.Magnitude(); 152 Vector2 new_direction; 153 if (distance > 0.0) { 154 desired_direction.Normalize(); 155 // If the target is within the turning affinity distance, then make the 156 // desired direction based on distance to the target. Otherwise, base 157 // the desired direction on MAX_SPEED. 158 if (distance < kMaxTurningDistance) { 159 // Some pretty arbitrary dampening. 160 desired_direction.Scale(kMaxSpeed * distance / 100.0); 161 } else { 162 desired_direction.Scale(kMaxSpeed); 163 } 164 new_direction = Vector2::Difference(desired_direction, velocity_); 165 new_direction.Clamp(kMaxTurningForce); 166 } 167 return new_direction; 168 } 169 170 int32_t Goose::AccumulateSeparation(double distance, 171 const Vector2& goose_delta, 172 Vector2* separation, /* inout */ 173 int32_t separation_count) { 174 if (distance > 0.0 && distance < kPersonalSpace) { 175 Vector2 weighted_direction = goose_delta; 176 weighted_direction.Normalize(); 177 weighted_direction.Scale(1.0 / distance); 178 separation->Add(weighted_direction); 179 separation_count++; 180 } 181 return separation_count; 182 } 183 184 int32_t Goose::AccumulateAlignment(double distance, 185 const Goose& goose, 186 Vector2* alignment, /* inout */ 187 int32_t align_count) { 188 if (distance > 0.0 && distance < kNeighbourRadius) { 189 alignment->Add(goose.velocity()); 190 align_count++; 191 } 192 return align_count; 193 } 194 195 int32_t Goose::AccumulateCohesion(double distance, 196 const Goose& goose, 197 Vector2* cohesion, /* inout */ 198 int32_t cohesion_count) { 199 if (distance > 0.0 && distance < kNeighbourRadius) { 200 cohesion->Add(goose.location()); 201 cohesion_count++; 202 } 203 return cohesion_count; 204 } 205