1 /*
2  *  cGradientCount.cc
3  *  Avida
4  *
5  *  Copyright 2010-2011 Michigan State University. All rights reserved.
6  *  http://avida.devosoft.org/
7  *
8  *
9  *  This file is part of Avida.
10  *
11  *  Avida is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
12  *  as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
13  *
14  *  Avida is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Lesser General Public License along with Avida.
18  *  If not, see <http://www.gnu.org/licenses/>.
19  *
20  *  Authors: Aaron P Wagner <apwagner@msu.edu>
21  *
22  */
23 
24 #include "cGradientCount.h"
25 
26 #include "avida/core/WorldDriver.h"
27 
28 #include "cAvidaContext.h"
29 #include "cPopulation.h"
30 #include "cStats.h"
31 #include "cWorld.h"
32 
33 #include <cmath>
34 #include <iostream>
35 
36 using namespace Avida;
37 
38 
39 /* cGradientCount is designed to give moving peaks of resources. Peaks are <optionally> capped with plateaus. The slope of the peaks
40 is height / distance. Consequently, when height = distance from center of peak, the value at that cell = 1. This was
41 designed this way because the organims used for this could only consume resources when the value is >= 1. Thus, height also
42 gives radius of 'edible' resources (aka the plateau). If plateaus are >1, you get sloped edges leading up to plateau
43 cylinders.
44   Spread gives the radius of the entire resource peak to the outside of the sloped edge. Organisms could detect resources
45 all along the spread, but only consume that portion on the plateau. Thus, spread - plateau = sense radius (smell) while
46 plateau = consumable radius (actual food).
47   Peaks move within the boundaries set by min/max x and y. If the plateau / edible portion of the peak hits the boundary, the peak
48 'bounces' (sign of direction of movement changes).
49   Smoothness of the movement is controlled by move_a_scaler which is the A in eq1 in Morrison & DeJong 1999. A-values
50 need to be between 1 and 4. Values of 1 to ~3 give smooth movements. Larger values should yield chaotic moves. However, beyond
51 establishing that peaks don't move when the value = 1 and do move when the value > 1, the effects of A-values have not really been
52 evaluated.
53   If depletable (via reaction) peaks stop moving when they are first bitten.
54   Depletable peaks will be refreshed when either all edible portions (>=1) are consumed or when the decay timestep (in
55 updates) is reached, whichever comes first.
56   Once bitten, depletable peaks will not move again until refreshed.
57   Peak values are refreshed to match initial height, spread, and plateau, but for non-halo peaks, the placement of the
58 refreshed peak is random within the min/max x and y area. For halo peaks, the peak is currently refreshed at the SE
59 corner of the orbit.
60 cGradientCount cannot access the random number generator at the very first update. Thus, it uses the DefaultContext initially.
61   We use movesign to determine direction of peak movement
62   First, to get smooth movements, for non-halo resources we only allow either the x or y direction change to be evaluated in
63  a single update. For halo resources, we only evaluate either the orbit or the direction in a given update.
64   Second, we then decide the change of direction based on the current direction, e.g. so that non-halo peak movesigns can't 'jump'
65 from -1 to 1, without first changing to 0
66   Finally, we only toy with movement direction when # updates since last change = updatestep.
67  */
68 
cGradientCount(cWorld * world,int peakx,int peaky,int height,int spread,double plateau,int decay,int max_x,int max_y,int min_x,int min_y,double move_a_scaler,int updatestep,int worldx,int worldy,int geometry,int halo,int halo_inner_radius,int halo_width,int halo_anchor_x,int halo_anchor_y,int move_speed,double plateau_inflow,double plateau_outflow,double cone_inflow,double cone_outflow,double gradient_inflow,int is_plateau_common,double floor,int habitat,int min_size,int max_size,int config,int count,double init_plat)69 cGradientCount::cGradientCount(cWorld* world, int peakx, int peaky, int height, int spread, double plateau, int decay,
70                                int max_x, int max_y, int min_x, int min_y, double move_a_scaler, int updatestep,
71                                int worldx, int worldy, int geometry, int halo, int halo_inner_radius, int halo_width,
72                                int halo_anchor_x, int halo_anchor_y, int move_speed,
73                                double plateau_inflow, double plateau_outflow, double cone_inflow, double cone_outflow,
74                                double gradient_inflow, int is_plateau_common, double floor, int habitat, int min_size,
75                                int max_size, int config, int count, double init_plat)
76   : m_world(world)
77   , m_peakx(peakx), m_peaky(peaky)
78   , m_height(height), m_spread(spread), m_plateau(plateau), m_decay(decay)
79   , m_max_x(max_x), m_max_y(max_y), m_min_x(min_x), m_min_y(min_y)
80   , m_move_a_scaler(move_a_scaler), m_updatestep(updatestep)
81   , m_halo(halo), m_halo_inner_radius(halo_inner_radius), m_halo_width(halo_width)
82   , m_halo_anchor_x(halo_anchor_x), m_halo_anchor_y(halo_anchor_y), m_move_speed(move_speed)
83   , m_plateau_inflow(plateau_inflow), m_plateau_outflow(plateau_outflow), m_cone_inflow(cone_inflow), m_cone_outflow(cone_outflow)
84   , m_gradient_inflow(gradient_inflow), m_is_plateau_common(is_plateau_common), m_floor(floor)
85   , m_habitat(habitat), m_min_size(min_size), m_max_size(max_size), m_config(config), m_count(count)
86   , m_initial_plat(init_plat)
87   , m_geometry(geometry)
88   , m_initial(false)
89   , m_move_y_scaler(0.5)
90   , m_counter(0)
91   , m_move_counter(1)
92   , m_topo_counter(updatestep)
93   , m_movesignx(0)
94   , m_movesigny(0)
95   , m_old_peakx(peakx)
96   , m_old_peaky(peaky)
97   , m_just_reset(true)
98   , m_past_height(0.0)
99   , m_current_height(0.0)
100   , m_ave_plat_cell_loss(0.0)
101   , m_common_plat_height(0.0)
102   , m_skip_moves(0)
103   , m_skip_counter(0)
104   , m_mean_plat_inflow(plateau_inflow)
105   , m_var_plat_inflow(0)
106   , m_predator(false)
107   , m_pred_odds(0.0)
108   , m_guarded_juvs_per_adult(0)
109   , m_probabilistic(false)
110   , m_min_usedx(-1)
111   , m_min_usedy(-1)
112   , m_max_usedx(-1)
113   , m_max_usedy(-1)
114 {
115   ResetGradRes(m_world->GetDefaultContext(), worldx, worldy);
116 }
117 
~cGradientCount()118 cGradientCount::~cGradientCount() { ; }
119 
StateAll()120 void cGradientCount::StateAll()
121 {
122   return;
123 }
124 
UpdateCount(cAvidaContext & ctx)125 void cGradientCount::UpdateCount(cAvidaContext& ctx)
126 {
127   m_old_peakx = m_peakx;
128   m_old_peaky = m_peaky;
129   if (m_habitat == 2) generateBarrier(m_world->GetDefaultContext());
130   else if (m_habitat == 1) generateHills(m_world->GetDefaultContext());
131   else if (m_probabilistic) UpdateProbabilisticRes();
132   else updatePeakRes(ctx);
133 }
134 
updatePeakRes(cAvidaContext & ctx)135 void cGradientCount::updatePeakRes(cAvidaContext& ctx)
136 {
137   bool has_edible = false;
138 
139   // determine if there is any edible food left in the peak (don't refresh the peak values until decay kicks in if there is edible food left)
140   // to speed things up, we only check cells within the possible spread of the peak
141   // and we only need to do this if decay > 1 (if decay == 1, we're going to reset everything regardless of the amount left)
142   // if decay = 1 and the resource IS depletable, that means we have a moving depleting resource! Odd, but useful.
143   if (m_decay > 1) {
144     int max_pos_x = min(m_peakx + m_spread + 1, GetX() - 1);
145     int min_pos_x = max(m_peakx - m_spread - 1, 0);
146     int max_pos_y = min(m_peaky + m_spread + 1, GetY() - 1);
147     int min_pos_y = max(m_peaky - m_spread - 1, 0);
148     for (int ii = min_pos_x; ii < max_pos_x + 1; ii++) {
149       for (int jj = min_pos_y; jj < max_pos_y + 1; jj++) {
150         if (Element(jj * GetX() + ii).GetAmount() >= 1) {
151           has_edible = true;
152           break;
153         }
154       }
155     }
156   }
157 
158   // once a resource cone has been 'bitten', start the clock that counts down to when the entire peak will be
159   // refreshed (carcass rots for only so long before disappearing)
160   if (has_edible && GetModified()) m_counter++;
161 
162   // only update resource values at declared update timesteps if there is resource left in the cone
163   if (has_edible && m_counter < m_decay && GetModified()) {
164     if (m_predator) UpdatePredatoryRes(ctx);
165     return;
166   }
167 
168   // before we move anything, if we have a depletable resource, we need to get the current plateau cell values
169   if (m_decay == 1) getCurrentPlatValues();
170 
171   // When the counter matches decay, regenerate resource peak
172   if (m_counter == m_decay) generatePeak(ctx);
173 
174   // if we are working with moving peaks, calculate the y-scaler
175   if (m_move_a_scaler > 1) m_move_y_scaler = m_move_a_scaler * m_move_y_scaler * (1 - m_move_y_scaler);
176 
177   // if working with moving resources, check if we are moving once per update or less frequently
178   if (m_skip_counter == m_skip_moves) moveRes(ctx);
179   else m_skip_counter++;
180 
181   // to speed things up, we only check cells within the possible spread of the peak
182   // and we only do this if the resource is set to actually move, has inflow/outflow to update, or
183   // we just reset a non-moving resource
184   if (m_move_a_scaler > 1 || m_plateau_inflow != 0 || m_plateau_outflow != 0 || m_cone_inflow != 0 || m_cone_outflow != 0
185   || m_gradient_inflow != 0 || (m_move_a_scaler == 1 && m_just_reset)) fillinResourceValues();
186 
187   m_counter = 0;  // reset decay counter after cone resources updated
188 
189   if (m_predator) UpdatePredatoryRes(ctx);
190 }
191 
generatePeak(cAvidaContext & ctx)192 void cGradientCount::generatePeak(cAvidaContext& ctx)
193 {
194   // Get initial peak cell x, y coordinates and movement directions.
195   cRandom& rng = ctx.GetRandom();
196   int temp_height = 0;
197   if (m_plateau < 0) temp_height = 1;
198   else temp_height = m_height;
199   // If we are not moving the resource we default to the config input m_peakx and m_peaky for 'normal' gradient resources
200   // for non-moving halo's we generate a random location on the orbit,
201   //   otherwise we get a random location and direction.
202   if (m_move_a_scaler > 1) {
203     if (!m_halo) {
204       m_peakx = rng.GetUInt(m_min_x + temp_height, m_max_x - temp_height + 1);
205       m_peaky = rng.GetUInt(m_min_y + temp_height, m_max_y - temp_height + 1);
206       // Get a random direction for movement on the x-axis
207       m_movesignx = rng.GetInt(-1,2);
208       // If x-axis movement is 0, we want to make sure y-axis movement is not also 0
209       if (m_movesignx == 0) {
210         m_movesigny = (rng.GetUInt(0,2) == 1) ? -1 : 1;
211       } else {
212         m_movesigny = rng.GetInt(-1,2);
213       }
214     } else if (m_halo) {
215       m_halo_dir = (rng.GetUInt(0,2) == 1) ? -1 : 1;
216       m_changling = (rng.GetUInt(0,2) == 1) ? -1 : 1;
217     }
218   }
219   if (m_halo) {
220     const int chooseUpDown = rng.GetUInt(0,2);
221     if (chooseUpDown == 0) {
222     int chooseEW = rng.GetUInt(0,2);
223       if (chooseEW == 0) {
224         m_peakx = rng.GetUInt(m_halo_anchor_x - m_halo_inner_radius - m_halo_width + temp_height,
225                               m_halo_anchor_x - m_halo_inner_radius - temp_height + 1);
226       } else {
227         m_peakx = rng.GetUInt(m_halo_anchor_x + m_halo_inner_radius + temp_height,
228                               m_halo_anchor_x + m_halo_inner_radius + m_halo_width - temp_height + 1);
229       }
230       m_peaky = rng.GetUInt(m_halo_anchor_y - m_halo_inner_radius - m_halo_width + temp_height,
231                             m_halo_anchor_y + m_halo_inner_radius + m_halo_width - temp_height + 1);
232     }
233     else {
234       int chooseNS = rng.GetUInt(0,2);
235       if (chooseNS == 0) {
236         m_peaky = rng.GetUInt(m_halo_anchor_y - m_halo_inner_radius - m_halo_width + temp_height,
237                               m_halo_anchor_y - m_halo_inner_radius - temp_height + 1);
238       } else {
239         m_peaky = rng.GetUInt(m_halo_anchor_y + m_halo_inner_radius + temp_height,
240                               m_halo_anchor_y + m_halo_inner_radius + m_halo_width - temp_height + 1);
241       }
242       m_peakx = rng.GetUInt(m_halo_anchor_x - m_halo_inner_radius - m_halo_width + temp_height,
243                             m_halo_anchor_x + m_halo_inner_radius + m_halo_width - temp_height + 1);
244     }
245   }
246 
247   SetModified(false);
248   m_counter = 0;
249   m_skip_counter = 0;
250   m_just_reset = true;
251   fillinResourceValues();
252 }
253 
fillinResourceValues()254 void cGradientCount::fillinResourceValues()
255 {
256   int max_pos_x;
257   int min_pos_x;
258   int max_pos_y;
259   int min_pos_y;
260   resetUsedBounds();
261 
262   // if we are resetting a resource, we need to calculate new values for the whole world so we can wipe away any residue
263   if (m_just_reset) {
264     max_pos_x = GetX() - 1;
265     min_pos_x = 0;
266     max_pos_y = GetY() - 1;
267     min_pos_y = 0;
268   } else {
269     // otherwise we only need to update values within the possible range of the peak
270     // we check all the way back to move_speed to make sure we're not leaving any old residue behind
271     max_pos_x = min(m_peakx + m_spread + m_move_speed + 1, GetX() - 1);
272     min_pos_x = max(m_peakx - m_spread - m_move_speed - 1, 0);
273     max_pos_y = min(m_peaky + m_spread + m_move_speed + 1, GetY() - 1);
274     min_pos_y = max(m_peaky - m_spread - m_move_speed - 1, 0);
275   }
276 
277   if (m_is_plateau_common == 1 && !m_just_reset && m_world->GetStats().GetUpdate() > 0) {
278     // with common depletion, new peak height is not the plateau heights, but the delta in plateau heights applied to
279     // peak height from the last time
280     m_current_height = m_current_height - m_ave_plat_cell_loss + m_plateau_inflow - (m_current_height * m_plateau_outflow);
281     m_common_plat_height = m_common_plat_height - m_ave_plat_cell_loss + m_plateau_inflow - (m_current_height * m_plateau_outflow);
282     if (m_common_plat_height > m_plateau && m_plateau >= 0) m_common_plat_height = m_plateau;
283     if (m_common_plat_height < 0 && m_plateau >=0) m_common_plat_height = 0;
284     if (m_current_height > m_height && m_plateau >= 0) m_current_height = m_height;
285     if (m_current_height < 0 && m_plateau >= 0) m_current_height = 0;
286   }
287   else {
288     m_current_height = m_height;
289   }
290 
291   int plateau_cell = 0;
292   for (int ii = min_pos_x; ii < max_pos_x + 1; ii++) {
293     for (int jj = min_pos_y; jj < max_pos_y + 1; jj++) {
294       double thisheight = 0.0;
295       double thisdist = sqrt((double) (m_peakx - ii) * (m_peakx - ii) + (m_peaky - jj) * (m_peaky - jj));
296       if (m_spread >= thisdist) {
297         // determine theoretical individual cells values and add one to distance from center
298         // (so that center point = radius 1, not 0)
299         // also used to distinguish plateau cells
300 
301         thisheight = m_current_height / (thisdist + 1);
302 
303         // set the floor values
304         // plateaus will override this so that plateaus can hit 0 when being eaten
305         if (thisheight < m_floor) thisheight = m_floor;
306 
307         // create cylindrical profiles of resources whereever thisheight would be >1 (area where thisdist + 1 <= m_height)
308         // and slopes outside of that range
309         // plateau = -1 turns off this option; if activated, causes 'peaks' to be flat plateaus = plateau value
310         bool is_plat_cell = ((m_height / (thisdist + 1)) >= 1);
311         // apply plateau inflow(s) and outflow
312         if ((is_plat_cell && m_plateau >= 0) || (m_plateau < 0 && thisdist == 0 && m_plateau_array.GetSize())) {
313           if (m_just_reset || m_world->GetStats().GetUpdate() <= 0) {
314             m_past_height = m_height;
315             if (m_plateau >= 0.0) {
316               thisheight = m_plateau;
317             }
318             else {
319               thisheight = m_height;
320             }
321           }
322           else {
323             if (m_is_plateau_common == 0) {
324               m_past_height = m_plateau_array[plateau_cell];
325               thisheight = m_past_height + m_plateau_inflow - (m_past_height * m_plateau_outflow);
326               thisheight += m_gradient_inflow / (thisdist + 1);
327               if (thisheight > m_plateau && m_plateau >= 0) {
328                 thisheight = m_plateau;
329               }
330               if (m_plateau < 0 && thisdist == 0 && thisheight > m_height) {
331                 thisheight = m_height;
332               }
333             }
334             else if (m_is_plateau_common == 1) {
335               thisheight = m_common_plat_height;
336             }
337           }
338           if (m_initial && m_initial_plat != -1) thisheight = m_initial_plat;
339           if (thisheight < 0) thisheight = 0;
340           m_plateau_array[plateau_cell] = thisheight;
341           m_plateau_cell_IDs[plateau_cell] = jj * GetX() + ii;
342           plateau_cell ++;
343          }
344         // now apply any off-plateau inflow(s) and outflow
345         else if (!is_plat_cell && (m_cone_inflow > 0 || m_cone_outflow > 0 || m_gradient_inflow > 0)) {
346           if (!m_just_reset && m_world->GetStats().GetUpdate() > 0) {
347             int offsetx = m_old_peakx - m_peakx;
348             int offsety = m_old_peaky - m_peaky;
349 
350             int old_cell_x = ii + offsetx;
351             int old_cell_y = jj + offsety;
352 
353             // cone cells that were previously off the world and moved onto world, start at 0
354             if ( old_cell_x < 0 || old_cell_y < 0 || (old_cell_y > (GetY() - 1)) || (old_cell_x > (GetX() - 1)) ) {
355               thisheight = 0;
356             }
357             else {
358               double past_height = Element(old_cell_y * GetX() + old_cell_x).GetAmount();
359               double newheight = past_height;
360               if (m_cone_inflow > 0 || m_cone_outflow > 0) newheight += m_cone_inflow - (past_height * m_cone_outflow);
361               if (m_gradient_inflow > 0) newheight += m_gradient_inflow / (thisdist + 1);
362               // don't exceed expected slope value
363               if (newheight < thisheight) thisheight = newheight;
364               if (thisheight < 0) thisheight = 0;
365             }
366           }
367         }
368       }
369       Element(jj * GetX() + ii).SetAmount(thisheight);
370       if (thisheight > 0) updateBounds(ii, jj);
371     }
372   }
373   SetCurrPeakX(m_peakx);
374   SetCurrPeakY(m_peaky);
375   m_just_reset = false;
376 }
377 
getCurrentPlatValues()378 void cGradientCount::getCurrentPlatValues()
379 {
380   int temp_height = 0;
381   if (m_plateau < 0) temp_height = 1;
382   else temp_height = m_height;
383   int plateau_box_min_x = m_peakx - temp_height - 1;
384   int plateau_box_max_x = m_peakx + temp_height + 1;
385   int plateau_box_min_y = m_peaky - temp_height - 1;
386   int plateau_box_max_y = m_peaky + temp_height + 1;
387   int plateau_cell = 0;
388   double amount_devoured = 0.0;
389   for (int ii = plateau_box_min_x; ii < plateau_box_max_x + 1; ii++) {
390     for (int jj = plateau_box_min_y; jj < plateau_box_max_y + 1; jj++) {
391       double thisdist = sqrt((double) (m_peakx - ii) * (double) (m_peakx - ii) + (double) (m_peaky - jj) * (double) (m_peaky - jj));
392       double find_plat_dist = temp_height / (thisdist + 1);
393       if ((find_plat_dist >= 1 && m_plateau >= 0) || (m_plateau < 0 && thisdist == 0 && m_plateau_array.GetSize() > 0)) {
394         double past_cell_height = m_plateau_array[plateau_cell];
395         double pre_move_height = Element(m_plateau_cell_IDs[plateau_cell]).GetAmount();
396         if (pre_move_height < past_cell_height) {
397           m_plateau_array[plateau_cell] = pre_move_height;
398           amount_devoured = amount_devoured + past_cell_height - pre_move_height;
399         }
400         plateau_cell ++;
401       }
402     }
403   }
404   m_ave_plat_cell_loss = amount_devoured / plateau_cell;
405 }
406 
moveRes(cAvidaContext & ctx)407 void cGradientCount::moveRes(cAvidaContext& ctx)
408 {
409   // for halo peaks, find current orbit. Add 1 to distance to account for the anchor grid cell
410   int current_orbit = max(abs(m_halo_anchor_x - m_peakx), abs(m_halo_anchor_y - m_peaky)) + 1;
411 
412   // if we are working with moving resources and it's time to update direction
413   if (m_move_counter == m_updatestep && m_move_a_scaler > 1) {
414     m_move_counter = 1;
415     if (m_halo == 1) current_orbit = setHaloPeakMovement(ctx, current_orbit);
416     else setPeakMoveMovement(ctx);
417   }
418   else m_move_counter++;
419 
420   if (m_move_a_scaler > 1) {
421     if (m_halo == 1 && m_move_a_scaler > 1) moveHaloPeak(current_orbit);
422     else movePeak();
423   }
424   m_skip_counter = 0;
425 }
426 
setPeakMoveMovement(cAvidaContext & ctx)427 void cGradientCount::setPeakMoveMovement(cAvidaContext& ctx)
428 {
429   int choosesign = ctx.GetRandom().GetInt(1,3);
430   if (choosesign == 1) {
431     if (m_movesignx == -1) m_movesignx = ctx.GetRandom().GetInt(-1,1);
432     else if (m_movesignx == 1) m_movesignx = ctx.GetRandom().GetInt(0,2);
433     else m_movesignx = ctx.GetRandom().GetInt(-1,2);
434   }
435   else if (choosesign == 2){
436     if (m_movesigny == -1) m_movesigny = ctx.GetRandom().GetInt(-1,1);
437     else if (m_movesigny == 1) m_movesigny = ctx.GetRandom().GetInt(0,2);
438     else m_movesigny = ctx.GetRandom().GetInt(-1,2);
439   }
440 }
441 
setHaloPeakMovement(cAvidaContext & ctx,int current_orbit)442 int cGradientCount::setHaloPeakMovement(cAvidaContext& ctx, int current_orbit)
443 {
444   // move cones by moving m_peakx & m_peaky
445   // halo resources orbit at a fixed org walking distance from an anchor point
446   // if halo width > the height of the halo resource, the resource will be bounded inside the halo but the orbit can vary within those bounds
447   // halo's are actually square in avida because, at a given orbit, this keeps a constant distance (in number of steps and org would have to take)
448   //    between the anchor point and any orbit
449 
450   //choose to change orbit (0) or direction (1)
451   int random_shift = ctx.GetRandom().GetUInt(0,2);
452   // if changing orbit, choose to go in or out one orbit
453   // then figure out if we need change the x or the y to shift orbit (based on what quadrant we're in)
454   int temp_height = 0;
455   if (m_plateau < 0) temp_height = 1;
456   else temp_height = m_height;
457   if (random_shift == 0) {
458     //do nothing unless there's room to change orbit
459     if (m_halo_width > (temp_height * 2)) {
460       int orbit_shift = ctx.GetRandom().GetUInt(0,2);
461       if (orbit_shift == 0) {
462         current_orbit = current_orbit - 1;
463         if (abs(m_halo_anchor_y - m_peaky) > abs(m_halo_anchor_x - m_peakx))
464           m_peaky = m_old_peaky - 1;
465         else
466           m_peakx = m_old_peakx - 1;
467       }
468       else if (orbit_shift == 1) {
469         current_orbit = current_orbit + 1;
470         if (abs(m_halo_anchor_y - m_peaky) > abs(m_halo_anchor_x - m_peakx))
471           m_peaky = m_old_peaky + 1;
472         else
473           m_peakx = m_old_peakx + 1;
474       }
475       // we have to check that we are still going to be within the halo after an orbit change
476       if (current_orbit > (m_halo_inner_radius + m_halo_width - temp_height + 2)) {
477         // if we go out of bounds, we need to go the other way instead (taking two steps back on orbit since we already took one in the wrong direction)
478         current_orbit = current_orbit - 2;
479         if (abs(m_halo_anchor_y - m_old_peaky) > abs(m_halo_anchor_x - m_old_peakx))
480           m_peaky = m_old_peaky + 1;
481         else
482           m_peakx = m_old_peakx + 1;
483       }
484       else if (current_orbit < (m_halo_inner_radius + temp_height + 2)) {
485         current_orbit = current_orbit + 2;
486         if (abs(m_halo_anchor_y - m_old_peaky) > abs(m_halo_anchor_x - m_old_peakx))
487           m_peaky = m_old_peaky - 1;
488         else
489           m_peakx = m_old_peakx - 1;
490       }
491     }
492     // if there was no room to change orbit, change the direction instead of the orbit
493     else random_shift = 1;
494   }
495   // if changing direction of rotation, we just switch sign of rotation
496   else if (random_shift == 1) {
497     m_halo_dir = m_halo_dir * -1;
498   }
499   return current_orbit;
500 }
501 
moveHaloPeak(int current_orbit)502 void cGradientCount::moveHaloPeak(int current_orbit)
503 {
504   // what quadrant we are in determines whether we are changing x's or y's (= changling)
505   // if we are on a corner, we just stick with the current changling
506   if (abs(m_halo_anchor_y - m_peaky) > abs(m_halo_anchor_x - m_peakx))
507     m_changling = 1;
508   else if (abs(m_halo_anchor_y - m_peaky) < abs(m_halo_anchor_x - m_peakx))
509     m_changling = -1;
510 
511   if (m_changling == 1) {
512     // check to make sure the move will not put peak beyond the bounds (at corner) of the orbit
513     // if it will go beyond the bounds of the orbit, turn the corner (e.g. if move = 5 & space to move on x =2, move 2 on x and 3 on y)
514     int next_posx = m_peakx + (m_halo_dir * m_move_speed);
515     int max_orbit_x = m_halo_anchor_x + current_orbit - 1;
516     int min_orbit_x = m_halo_anchor_x - current_orbit + 1;
517     int current_x = m_peakx;
518     if (next_posx > max_orbit_x) {
519       m_peakx = max_orbit_x;
520       if (m_peaky > m_halo_anchor_y) {
521         // turning this corner means changing the sign of the movement once we switch from moving along x to moving along y
522         m_halo_dir *= -1;
523         m_peaky = m_peaky + m_halo_dir * (m_move_speed - abs(m_peakx - current_x));
524       } else {
525         m_peaky = m_peaky + m_halo_dir * (m_move_speed - abs(m_peakx - current_x));
526       }
527       m_changling *= -1;
528     }
529     else if (next_posx < min_orbit_x) {
530       m_peakx = min_orbit_x;
531       if (m_peaky > m_halo_anchor_y) {
532         m_peaky = m_peaky + m_halo_dir * (m_move_speed - abs(m_peakx - current_x));
533       } else {
534         m_halo_dir *= -1;
535         m_peaky = m_peaky + m_halo_dir * (m_move_speed - abs(m_peakx - current_x));
536       }
537       m_changling *= -1;
538     }
539     else m_peakx = m_peakx + (m_halo_dir * m_move_speed);
540   }
541   else {
542     int next_posy = m_peaky + (m_halo_dir * m_move_speed);
543     int max_orbit_y = m_halo_anchor_y + current_orbit - 1;
544     int min_orbit_y = m_halo_anchor_y - current_orbit + 1;
545     int current_y = m_peaky;
546 
547     if (next_posy > max_orbit_y) {
548       m_peaky = max_orbit_y;
549       if (m_peakx < m_halo_anchor_x) {
550         m_peakx = m_peakx + m_halo_dir * (m_move_speed - abs(m_peaky - current_y));
551       } else {
552         m_halo_dir *= -1;
553         m_peakx = m_peakx + m_halo_dir * (m_move_speed - abs(m_peaky - current_y));
554       }
555       m_changling *= -1;
556     } else if (next_posy < min_orbit_y) {
557       m_peaky = min_orbit_y;
558       if (m_peakx < m_halo_anchor_x) {
559         m_halo_dir *= -1;
560         m_peakx = m_peakx + m_halo_dir * (m_move_speed - abs(m_peaky - current_y));
561       } else {
562         m_peakx = m_peakx + m_halo_dir * (m_move_speed - abs(m_peaky - current_y));
563       }
564       m_changling *= -1;
565     } else {
566       m_peaky = m_peaky + (m_halo_dir * m_move_speed);
567     }
568   }
569 }
570 
movePeak()571 void cGradientCount::movePeak()
572 {
573   // for non-halo peaks keep cones inside their bounding boxes, bouncing them if they hit the edge
574   int temp_height = 0;
575   if (m_plateau < 0) temp_height = 1;
576   else temp_height = m_height;
577 
578   int temp_peakx = m_peakx + (int)(m_move_y_scaler + 0.5) * m_movesignx;
579   int temp_peaky = m_peaky + (int)(m_move_y_scaler + 0.5) * m_movesigny;
580 
581   if (temp_peakx > (m_max_x - temp_height)) m_movesignx = -1;
582   if (temp_peakx < (m_min_x + temp_height + 1)) m_movesignx = 1;
583 
584   if (temp_peaky > (m_max_y - temp_height)) m_movesigny = -1;
585   if (temp_peaky < (m_min_y + temp_height + 1)) m_movesigny = 1;
586 
587   m_peakx = (int) (m_peakx + (m_movesignx * m_move_y_scaler) + .5);
588   m_peaky = (int) (m_peaky + (m_movesigny * m_move_y_scaler) + .5);
589 }
590 
generateBarrier(cAvidaContext & ctx)591 void cGradientCount::generateBarrier(cAvidaContext& ctx)
592 // If habitat == 2 we are creating barriers to movement (walls), not really gradient resources
593 {
594   // generate/regenerate walls when counter == config updatestep
595   if (m_topo_counter == m_updatestep) {
596     resetUsedBounds();
597     // reset counter
598     m_topo_counter = 1;
599     // clear any old resource
600     for (int ii = 0; ii < GetX(); ii++) {
601       for (int jj = 0; jj < GetY(); jj++) {
602         Element(jj * GetX() + ii).SetAmount(0);
603       }
604     }
605     m_wall_cells.Resize(0);
606     // generate number barriers equal to count
607     for (int i = 0; i < m_count; i++) {
608       // drop the anchor/first block for current barrier
609       int start_randx = 0;
610       int start_randy = 0;
611       if (m_config == 3 || m_config == 4) {
612         start_randx = m_peakx;
613         start_randy = m_peaky;
614       }
615       else {
616         start_randx = ctx.GetRandom().GetUInt(0, GetX());
617         start_randy = ctx.GetRandom().GetUInt(0, GetY());
618       }
619       Element(start_randy * GetX() + start_randx).SetAmount(m_plateau);
620       if (m_plateau > 0) updateBounds(start_randx, start_randy);
621       m_wall_cells.Push(start_randy * GetX() + start_randx);
622 
623       int randx = start_randx;
624       int randy = start_randy;
625       int prev_blockx = randx;
626       int prev_blocky = randy;
627       int cornerx = prev_blockx;
628       int cornery = prev_blocky;
629       bool place_corner = false;
630 
631       // decide the size of the current barrier
632       int rand_block_count = ctx.GetRandom().GetUInt(m_min_size, m_max_size + 1);
633       // for vertical or horizontal wall building, pick a random direction once for the whole wall
634       int direction = ctx.GetRandom().GetUInt(0,2);
635 
636       for (int num_blocks = 0; num_blocks < rand_block_count; num_blocks++) {
637         // if config == 0, build random shaped walls
638         if (m_config == 0) {
639           prev_blockx = randx;
640           prev_blocky = randy;
641           cornerx = prev_blockx;
642           cornery = prev_blocky;
643           place_corner = false;
644           // move one cell in chosen direction
645           // on diagonals, we need to place a block at chosen spot + 1 block in corner to prevent porous diagonal walls
646           if (direction == 0) {
647             randy = randy - 1;
648           }
649           else if (direction == 1) {
650             randy = randy - 1;
651             randx = randx + 1;
652             place_corner = true;
653             cornery -= 1;
654           }
655           else if (direction == 2) {
656             randx = randx + 1;
657           }
658           else if (direction == 3) {
659             randy = randy + 1;
660             randx = randx + 1;
661             place_corner = true;
662             cornerx += 1;
663           }
664           else if (direction == 4) {
665             randy = randy + 1;
666           }
667           else if (direction == 5) {
668             randy = randy + 1;
669             randx = randx - 1;
670             place_corner = true;
671             cornerx -= 1;
672           }
673           else if (direction == 6) {
674             randx = randx - 1;
675           }
676           else if (direction == 7) {
677             randy = randy - 1;
678             randx = randx - 1;
679             place_corner = true;
680             cornery += 1;
681           }
682           // choose a direction for next block with fixed 90% probability of not changing direction (~1 of 20 blocks will be in new direction)
683           if(ctx.GetRandom().GetUInt(0, 21) == 20) direction = ctx.GetRandom().GetUInt(0, 8);
684         }
685         // if config == 1, build randomly placed vertical walls
686         else if (m_config == 1) {
687           // choose up/down build direction
688           if (direction == 0) randy = randy - 1;
689           else randy = randy + 1;
690         }
691         // if config == 2, build randonly placed horizontal walls
692         else if (m_config == 2) {
693           // choose left/right build direction
694           if (direction == 0) randx = randx - 1;
695           else randx = randx + 1;
696         }
697         // if config == 3, build vertical walls from north to south
698         else if (m_config == 3) randy = randy + 1;
699         // if config == 4, build horizontal walls from west to east
700         else if (m_config == 4) randx = randx + 1;
701 
702         bool count_block = true;
703         // place the new block(s) if not off edge of world
704         if (randy < GetY() && randy >= 0 && randx < GetX() && randx >= 0) {
705           // if we are trying to build across an inner_radius
706           // or for random walls, if there is already a block here
707           // don't count or place this one (continue walking across inner_radius)
708           if ((randx < (m_halo_anchor_x + m_halo_inner_radius) &&
709                randy < (m_halo_anchor_y + m_halo_inner_radius) &&
710                randx > (m_halo_anchor_x - m_halo_inner_radius) &&
711                randy > (m_halo_anchor_y - m_halo_inner_radius)) ||
712               (m_config == 0 && Element(randy * GetX() + randx).GetAmount())) {
713             num_blocks --;
714             count_block = false;
715           }
716           if (count_block) {
717             Element(randy * GetX() + randx).SetAmount(m_plateau);
718             if (m_plateau > 0) updateBounds(randx, randy);
719             m_wall_cells.Push(randy * GetX() + randx);
720             if (place_corner) {
721               if (cornery < GetY() && cornery >= 0 && cornerx < GetX() && cornerx >= 0) {
722                 if ( ! ((cornerx < (m_halo_anchor_x + m_halo_inner_radius) &&
723                      cornery < (m_halo_anchor_y + m_halo_inner_radius) &&
724                      cornerx > (m_halo_anchor_x - m_halo_inner_radius) &&
725                      cornery > (m_halo_anchor_y - m_halo_inner_radius))) ){
726                   Element(cornery * GetX() + cornerx).SetAmount(m_plateau);
727                   if (m_plateau > 0) updateBounds(cornerx, cornery);
728                   m_wall_cells.Push(randy * GetX() + randx);
729                 }
730               }
731             }
732           }
733         }
734         // if the wall is horizontal or vertical build and we went off the world edge, build from the opposite direction
735         else if (m_config == 1 || m_config == 2) {
736           randx = start_randx;
737           randy = start_randy;
738           direction = abs(direction - 1);
739           num_blocks --;
740         }
741         // if a random build and we went off the world edge, backup a block and try again
742         else if (m_config == 0) {
743           randx = prev_blockx;
744           randy = prev_blocky;
745           num_blocks --;
746         }
747       }
748     }
749   }
750   else m_topo_counter++;
751 }
752 
generateHills(cAvidaContext & ctx)753 void cGradientCount::generateHills(cAvidaContext& ctx)
754 // If habitat == 1 we are creating hills which slow movement, not really gradient resources
755 {
756   // generate/regenerate hills when counter == config updatestep
757   if (m_topo_counter == m_updatestep) {
758     resetUsedBounds();
759     // reset counter
760     m_topo_counter = 1;
761     // since we are potentially plotting more than one hill per resource, we need to wipe the world before we start
762     for (int ii = 0; ii < GetX(); ii++) {
763       for (int jj = 0; jj < GetY(); jj++) {
764         Element(jj * GetX() + ii).SetAmount(0);
765       }
766     }
767 
768     cRandom& rng = ctx.GetRandom();
769     // generate number hills equal to count
770     for (int i = 0; i < m_count; i++) {
771       // decide the size of the current hill
772       int rand_hill_radius = ctx.GetRandom().GetUInt(m_min_size, m_max_size + 1);
773 
774       // generate random hills, if config == 0, otherwise generate 1 hill at peakx X peaky
775       if (m_config == 0) {
776         // choose the peak center for current hill, keeping the entire hill outside of any inner_radius
777         int chooseEW = rng.GetUInt(0,2);
778         if (chooseEW == 0) {
779           m_peakx = rng.GetUInt(rand_hill_radius, m_halo_anchor_x - m_halo_inner_radius - rand_hill_radius);
780         } else {
781           m_peakx = rng.GetUInt(m_halo_anchor_x + m_halo_inner_radius + rand_hill_radius, GetX() - 1 - rand_hill_radius);
782         }
783         int chooseNS = rng.GetUInt(0,2);
784         if (chooseNS == 0) {
785           m_peaky = rng.GetUInt(rand_hill_radius, m_halo_anchor_y - m_halo_inner_radius - rand_hill_radius);
786         } else {
787           m_peaky = rng.GetUInt(m_halo_anchor_y + m_halo_inner_radius + rand_hill_radius, GetY() - 1 - rand_hill_radius);
788         }
789       }
790 
791       // figure the coordinate extent of each hill (box)
792       int max_pos_x = min(m_peakx + rand_hill_radius + 1, GetX() - 1);
793       int min_pos_x = max(m_peakx - rand_hill_radius - 1, 0);
794       int max_pos_y = min(m_peaky + rand_hill_radius + 1, GetY() - 1);
795       int min_pos_y = max(m_peaky - rand_hill_radius - 1, 0);
796 
797       // look to place new cell values within a box around the hill center
798       for (int ii = min_pos_x; ii < max_pos_x + 1; ii++) {
799         for (int jj = min_pos_y; jj < max_pos_y + 1; jj++) {
800           double thisheight = 0.0;
801           double thisdist = sqrt((double) (m_peakx - ii) * (m_peakx - ii) + (m_peaky - jj) * (m_peaky - jj));
802           // only plot values when within set config radius & if no larger amount has already been plotted for another overlapping hill
803           if ((thisdist <= rand_hill_radius) && (Element(jj * GetX() + ii).GetAmount() <  m_plateau / (thisdist + 1))) {
804           thisheight = m_plateau / (thisdist + 1);
805           Element(jj * GetX() + ii).SetAmount(thisheight);
806           if (thisheight > 0) updateBounds(ii, jj);
807           }
808         }
809       }
810     }
811   }
812   else m_topo_counter++;
813 }
814 
ResetGradRes(cAvidaContext & ctx,int worldx,int worldy)815 void cGradientCount::ResetGradRes(cAvidaContext& ctx, int worldx, int worldy)
816 {
817   if ((m_move_speed >= (2 * (m_halo_inner_radius + m_halo_width))) && ((m_halo_inner_radius + m_halo_width) != 0)
818       && m_move_speed != 0) {
819     m_world->GetDriver().RaiseFatalException(-1, "Move speed greater or equal to 2*Radius");
820   }
821   if (m_halo == 1 && (m_halo_width < (2 * m_height) && m_plateau >= 0)) {
822     m_world->GetDriver().RaiseFatalException(-1, "Halo width < 2 * height (aka plateau radius)");
823   }
824   if (m_move_speed < 0) {
825     m_skip_moves = abs(m_move_speed);
826     m_move_speed = 1;
827   }
828   m_plateau_array.Resize(int(4 * m_height * m_height + 0.5));
829   m_plateau_array.SetAll(0);
830   m_plateau_cell_IDs.Resize(int(4 * m_height * m_height + 0.5));
831   m_plateau_cell_IDs.SetAll(0);
832   m_prob_res_cells.Resize(0);
833   m_wall_cells.Resize(0);
834   m_current_height = m_height;
835   m_common_plat_height = m_plateau;
836   m_mean_plat_inflow = m_plateau_inflow;
837   m_var_plat_inflow = 0;
838   resetUsedBounds();
839 
840   m_initial = true;
841   ResizeClear(worldx, worldy, m_geometry);
842   if (m_habitat == 2) {
843     generateBarrier(ctx);
844   }
845   else if (m_habitat == 1) {
846     generateHills(ctx);
847   }
848   else {
849     generatePeak(ctx);
850     UpdateCount(ctx);
851   }
852   // set m_initial to false now that we have reset the resource
853   m_initial = false;
854 }
855 
SetGradPlatVarInflow(double mean,double variance,int type)856 void cGradientCount::SetGradPlatVarInflow(double mean, double variance, int type)
857 {
858   if (variance > 0) {
859     m_mean_plat_inflow = mean;
860     m_var_plat_inflow = variance;
861     double the_inflow = 0;
862     if (type == 0) {
863       the_inflow = abs(m_world->GetRandom().GetRandNormal(mean, variance));
864       SetGradPlatInflow(the_inflow);
865     }
866     else if (type < 0) {
867       the_inflow = abs(m_world->GetRandom().GetRandNormal(0, variance));
868       if (mean - the_inflow < 0) the_inflow = mean;
869       SetGradPlatInflow(mean - the_inflow);
870     }
871     else if (type == 1) {
872       the_inflow = abs(m_world->GetRandom().GetRandNormal(0, variance));
873       SetGradPlatInflow(mean + the_inflow);
874     }
875     else if (type == 2) {
876       the_inflow = m_world->GetRandom().GetRandNormal(0, variance);
877       if (mean + the_inflow < 0) the_inflow = mean;
878       SetGradPlatInflow(mean + the_inflow);
879     }
880   }
881   else SetGradPlatInflow(mean);
882 }
883 
UpdatePredatoryRes(cAvidaContext & ctx)884 void cGradientCount::UpdatePredatoryRes(cAvidaContext& ctx)
885 {
886   // kill off up to 1 org per update within the predator radius (plateau area), with prob of death for selected prey = m_pred_odds
887   if (m_predator) {
888     for (int i = 0; i < m_plateau_cell_IDs.GetSize(); i ++) {
889       if (Element(m_plateau_cell_IDs[i]).GetAmount() >= 1) {
890         m_world->GetPopulation().ExecutePredatoryResource(ctx, m_plateau_cell_IDs[i], m_pred_odds, m_guarded_juvs_per_adult);
891       }
892     }
893   }
894 }
895 
SetPredatoryResource(double odds,int juvsper)896 void cGradientCount::SetPredatoryResource(double odds, int juvsper)
897 {
898   m_predator = true;
899   m_pred_odds = odds;
900   m_guarded_juvs_per_adult = juvsper;
901 }
902 
SetProbabilisticResource(cAvidaContext & ctx,double initial,double inflow,double outflow,double lambda,double theta,int x,int y,int num_cells)903 void cGradientCount::SetProbabilisticResource(cAvidaContext& ctx, double initial, double inflow, double outflow, double lambda, double theta, int x, int y, int num_cells)
904 {
905   m_probabilistic = true;
906   m_initial_plat = initial;
907   m_plateau_inflow = inflow;
908   m_plateau_outflow = outflow;
909 
910   BuildProbabilisticRes(ctx, lambda, theta, x , y, num_cells);
911 }
912 
BuildProbabilisticRes(cAvidaContext & ctx,double lambda,double theta,int x,int y,int num_cells)913 void cGradientCount::BuildProbabilisticRes(cAvidaContext& ctx, double lambda, double theta, int x, int y, int num_cells)
914 {
915   if (m_min_usedx != -1) clearExistingProbRes();
916   resetUsedBounds();
917   int cells_used = 0;
918   const int worldx = GetX();
919   const int worldy = GetY();
920   int world_size = worldx * worldy;
921   int max_idx = world_size - 1;
922   int max_tries = min(1000, world_size);
923 
924   tArray <int> cell_id_array;
925   cell_id_array.ResizeClear(world_size);
926   for (int i = 0; i < cell_id_array.GetSize(); i++) cell_id_array[i] = i;
927 
928   if (x == -1) m_peakx = ctx.GetRandom().GetUInt(0, worldx);
929   else m_peakx = x;
930   if (y == -1) m_peaky = ctx.GetRandom().GetUInt(0, worldy);
931   else m_peaky = y;
932 
933   if (num_cells != -1) m_prob_res_cells.ResizeClear(num_cells);
934 
935   // only if theta == 1 do want want a 'hill' with resource for certain in the center
936   if (theta == 0) {
937     Element(m_peaky * worldx + m_peakx).SetAmount(m_initial_plat);
938     if (m_initial_plat > 0) updateBounds(m_peakx, m_peaky);
939     if (m_plateau_outflow > 0 || m_plateau_inflow > 0) {
940       if (num_cells == -1) m_prob_res_cells.Push(m_peaky * worldx + m_peakx);
941       else m_prob_res_cells[cells_used] = m_peaky * worldx + m_peakx;
942     }
943     cells_used++;
944     // no need to pop this cell off the array, just move it and don't check that far anymore
945     cell_id_array.Swap(m_peaky * worldx + m_peakx, max_idx--);
946   }
947 
948   // prevent looping when num_cells not specified
949   bool loop_once = false;
950   if (num_cells == -1) {
951     loop_once = true;
952     num_cells = cells_used + 1;
953   }
954 
955   int max_unused_idx = max_idx;
956   while (max_tries) {   // emergency exit
957     // allow looping through multiple times until num_cells quota is filled
958     if (max_unused_idx == 0) {
959       max_tries--;
960       if (!loop_once) max_unused_idx = max_idx;
961     }
962 
963     int cell_idx = m_world->GetRandom().GetUInt(max_unused_idx + 1);
964     int cell_id = cell_id_array[cell_idx];
965     int this_x = cell_id % worldx;
966     int this_y = cell_id / worldx;
967     double cell_dist = sqrt((double) (m_peakx - this_x) * (m_peakx - this_x) + (m_peaky - this_y) * (m_peaky - this_y));
968     // use a half normal
969     double this_prob = (1/lambda) * (sqrt(2 / 3.14159)) * exp(-0.5 * pow(((cell_dist - theta) / lambda), 2));
970 
971     if (ctx.GetRandom().P(this_prob)) {
972       Element(cell_id).SetAmount(m_initial_plat);
973       if (m_initial_plat > 0) updateBounds(this_x, this_y);
974       if (m_plateau_outflow > 0 || m_plateau_inflow > 0) {
975         if (loop_once) m_prob_res_cells.Push(cell_id);
976         else m_prob_res_cells[cells_used] = cell_id;
977       }
978       cells_used++;
979       cell_id_array.Swap(cell_idx, max_idx--);
980     }
981     // just push this cell out of the way for this loop, but keep it around for next time
982     else {
983       Element(cell_id).SetAmount(0);
984       cell_id_array.Swap(cell_idx, max_unused_idx--);
985     }
986 
987     if (cells_used >= num_cells && !loop_once) break;
988     if (max_unused_idx <= 0 && loop_once) break;
989     if (max_idx <= 0) break;
990   }
991 }
992 
UpdateProbabilisticRes()993 void cGradientCount::UpdateProbabilisticRes()
994 {
995   if (m_plateau_outflow > 0 || m_plateau_inflow > 0) {
996     for (int i = 0; i < m_prob_res_cells.GetSize(); i++) {
997       double curr_val = Element(m_prob_res_cells[i]).GetAmount();
998       double amount = curr_val + m_plateau_inflow - (curr_val * m_plateau_outflow);
999       Element(m_prob_res_cells[i]).SetAmount(amount);
1000       if (amount > 0) updateBounds(m_prob_res_cells[i] % GetX(), m_prob_res_cells[i] / GetX());
1001     }
1002   }
1003 }
1004 
clearExistingProbRes()1005 void cGradientCount::clearExistingProbRes()
1006 {
1007   for (int x = m_min_usedx; x < m_max_usedx + 1; x ++) {
1008     for (int y = m_min_usedy; y < m_max_usedy + 1; y ++) {
1009       Element(y * GetX() + x).SetAmount(0);
1010     }
1011   }
1012 }
1013 
updateBounds(int x,int y)1014 void cGradientCount::updateBounds(int x, int y)
1015 {
1016   if (x < m_min_usedx || m_min_usedx == -1) m_min_usedx = x;
1017   if (y < m_min_usedy || m_min_usedy == -1) m_min_usedy = y;
1018   if (x > m_max_usedx || m_max_usedx == -1) m_max_usedx = x;
1019   if (y > m_max_usedy || m_max_usedy == -1) m_max_usedy = y;
1020 }
1021 
resetUsedBounds()1022 void cGradientCount::resetUsedBounds()
1023 {
1024   m_min_usedx = -1;
1025   m_min_usedy = -1;
1026   m_max_usedx = -1;
1027   m_max_usedy = -1;
1028 }
1029