Commit | Line | Data |
---|---|---|
4861d6c4 LW |
1 | class L8onLife extends SCPattern { |
2 | // Controls the rate of life algorithm ticks, in milliseconds | |
7782bf8e | 3 | private BasicParameter rateParameter = new BasicParameter("DELAY", 112.5, 0.0, 1000.0); |
7234b0b8 | 4 | // Controls the probability of a mutation in the cycleOfLife |
ea4abd09 | 5 | private BasicParameter mutationParameter = new BasicParameter("MUT", 0.000000011, 0.0, 0.1); |
4861d6c4 LW |
6 | // Controls the saturation. |
7 | private BasicParameter saturationParameter = new BasicParameter("SAT", 90.0, 0.0, 100.0); | |
8 | ||
64ed63c7 LW |
9 | public final double MIN_ALIVE_PROBABILITY = 0.2; |
10 | public final double MAX_ALIVE_PROBABILITY = 0.9; | |
4861d6c4 | 11 | |
ea4abd09 LW |
12 | public final float MAX_ALIVE_BRIGHTNESS = 90.0; |
13 | ||
14 | private final SawLFO cubePos = new SawLFO(0, model.cubes.size(), 4000); | |
2815b690 LW |
15 | |
16 | class CubeState { | |
17 | // Index of cube in glucose.model.cubes | |
18 | public Integer index; | |
19 | // Boolean which describes if cube is alive. | |
20 | public boolean alive; | |
ea4abd09 LW |
21 | // Boolean which describes if strip was just changed; |
22 | public boolean just_changed; | |
23 | // Current brightness | |
24 | public float current_brightness; | |
2815b690 LW |
25 | // List of this cubes neighbors |
26 | public List<Integer> neighbors; | |
27 | ||
ea4abd09 | 28 | public CubeState(Integer index, boolean alive, float current_brightness, List<Integer> neighbors) { |
2815b690 LW |
29 | this.index = index; |
30 | this.alive = alive; | |
ea4abd09 | 31 | this.current_brightness = current_brightness; |
2815b690 LW |
32 | this.neighbors = neighbors; |
33 | } | |
34 | } | |
35 | ||
4861d6c4 LW |
36 | // Contains the state of all cubes by index. |
37 | private List<CubeState> cube_states; | |
38 | // Contains the amount of time since the last cycle of life. | |
39 | private int time_since_last_run; | |
40 | // Boolean describing if life changes were made during the current run. | |
41 | private boolean any_changes_this_run; | |
64ed63c7 LW |
42 | // Hold the new lives |
43 | private List<Boolean> new_lives; | |
f7f0bfe5 | 44 | |
4861d6c4 | 45 | public L8onLife(GLucose glucose) { |
f7f0bfe5 | 46 | super(glucose); |
4861d6c4 LW |
47 | |
48 | //Print debug info about the cubes. | |
49 | //outputCubeInfo(); | |
50 | ||
f7f0bfe5 LW |
51 | initCubeStates(); |
52 | time_since_last_run = 0; | |
53 | any_changes_this_run = false; | |
64ed63c7 | 54 | new_lives = new ArrayList<Boolean>(); |
4861d6c4 LW |
55 | |
56 | addParameter(rateParameter); | |
ea4abd09 | 57 | addParameter(mutationParameter); |
61f677b8 | 58 | addParameter(saturationParameter); |
7234b0b8 | 59 | addModulator(cubePos).trigger(); |
f7f0bfe5 LW |
60 | } |
61 | ||
62 | public void run(double deltaMs) { | |
63 | Integer i = 0; | |
4861d6c4 | 64 | CubeState cube_state; |
f7f0bfe5 | 65 | |
4861d6c4 | 66 | any_changes_this_run = false; |
64ed63c7 | 67 | new_lives.clear(); |
f7f0bfe5 LW |
68 | time_since_last_run += deltaMs; |
69 | ||
4861d6c4 | 70 | for (Cube cube : model.cubes) { |
2815b690 | 71 | cube_state = this.cube_states.get(i); |
64ed63c7 | 72 | |
4861d6c4 | 73 | if(shouldLightCube(cube_state)) { |
ea4abd09 | 74 | lightLiveCube(cube, cube_state, deltaMs); |
f7f0bfe5 | 75 | } else { |
ea4abd09 | 76 | lightDeadCube(cube, cube_state, deltaMs); |
4861d6c4 LW |
77 | } |
78 | ||
79 | i++; | |
f7f0bfe5 LW |
80 | } |
81 | ||
7234b0b8 | 82 | if(!any_changes_this_run) { |
f7f0bfe5 | 83 | randomizeCubeStates(); |
64ed63c7 LW |
84 | } else { |
85 | applyNewLives(); | |
f7f0bfe5 LW |
86 | } |
87 | ||
4861d6c4 LW |
88 | if(time_since_last_run >= rateParameter.getValuef()) { |
89 | time_since_last_run = 0; | |
90 | } | |
f7f0bfe5 LW |
91 | } |
92 | ||
ea4abd09 LW |
93 | public void lightLiveCube(Cube cube, CubeState cube_state, double deltaMs) { |
94 | float cube_dist = LXUtils.wrapdistf((float) cube_state.index, cubePos.getValuef(), model.cubes.size()); | |
7234b0b8 | 95 | float hv = (cube_dist / model.cubes.size()) * 360; |
ea4abd09 LW |
96 | float bv = cube_state.current_brightness; |
97 | ||
98 | if(!cube_state.just_changed || deltaMs >= rateParameter.getValuef()) { | |
99 | float bright_prop = min(((float) time_since_last_run / rateParameter.getValuef()), 1.0); | |
100 | bv = min(MAX_ALIVE_BRIGHTNESS, bright_prop * MAX_ALIVE_BRIGHTNESS); | |
101 | ||
102 | if(cube_state.current_brightness < bv) { | |
103 | cube_state.current_brightness = bv; | |
104 | } else { | |
105 | bv = cube_state.current_brightness; | |
106 | } | |
107 | } | |
7234b0b8 | 108 | |
4861d6c4 | 109 | for (LXPoint p : cube.points) { |
4861d6c4 LW |
110 | colors[p.index] = lx.hsb( |
111 | hv, | |
112 | saturationParameter.getValuef(), | |
ea4abd09 | 113 | bv |
4861d6c4 LW |
114 | ); |
115 | } | |
116 | } | |
117 | ||
ea4abd09 LW |
118 | public void lightDeadCube(Cube cube, CubeState cube_state, double deltaMs) { |
119 | float cube_dist = LXUtils.wrapdistf((float) cube_state.index, cubePos.getValuef(), model.cubes.size()); | |
120 | float hv = (cube_dist / (float) model.cubes.size()) * 360; | |
121 | float bv = cube_state.current_brightness; | |
122 | ||
123 | if(!cube_state.just_changed || deltaMs >= rateParameter.getValuef()) { | |
124 | float bright_prop = 1.0 - min(((float) time_since_last_run / rateParameter.getValuef()), 1.0); | |
125 | bv = max(0.0, bright_prop * MAX_ALIVE_BRIGHTNESS); | |
126 | ||
127 | if(cube_state.current_brightness > bv) { | |
128 | cube_state.current_brightness = bv; | |
129 | } else { | |
130 | bv = cube_state.current_brightness; | |
131 | } | |
132 | } | |
7234b0b8 | 133 | |
4861d6c4 | 134 | for (LXPoint p : cube.points) { |
4861d6c4 LW |
135 | colors[p.index] = lx.hsb( |
136 | hv, | |
137 | saturationParameter.getValuef(), | |
ea4abd09 | 138 | bv |
4861d6c4 LW |
139 | ); |
140 | } | |
141 | } | |
142 | ||
f7f0bfe5 LW |
143 | public void outputCubeInfo() { |
144 | int i = 0; | |
145 | for (Cube c : model.cubes) { | |
146 | print("Cube " + i + ": " + c.x + "," + c.y + "," + c.z + "\n"); | |
147 | ++i; | |
148 | } | |
149 | print("Edgeheight: " + Cube.EDGE_HEIGHT + "\n"); | |
150 | print("Edgewidth: " + Cube.EDGE_WIDTH + "\n"); | |
151 | print("Channelwidth: " + Cube.CHANNEL_WIDTH + "\n"); | |
152 | } | |
153 | ||
154 | private void initCubeStates() { | |
155 | List<Integer> neighbors; | |
156 | boolean alive = false; | |
157 | CubeState cube_state; | |
2815b690 | 158 | this.cube_states = new ArrayList<CubeState>(); |
ea4abd09 | 159 | float current_brightness = 0.0; |
f7f0bfe5 LW |
160 | Integer i = 0; |
161 | ||
ea4abd09 | 162 | for (Cube c : model.cubes) { |
f7f0bfe5 | 163 | neighbors = findCubeNeighbors(c, i); |
1c56d2f1 | 164 | alive = true; |
ea4abd09 | 165 | cube_state = new CubeState(i, alive, current_brightness, neighbors); |
f7f0bfe5 LW |
166 | this.cube_states.add(cube_state); |
167 | ++i; | |
168 | } | |
169 | } | |
170 | ||
4861d6c4 LW |
171 | private void randomizeCubeStates() { |
172 | double prob_range = (1.0 - MIN_ALIVE_PROBABILITY) - (1.0 - MAX_ALIVE_PROBABILITY); | |
173 | double prob = MIN_ALIVE_PROBABILITY + (prob_range * Math.random()); | |
1c56d2f1 | 174 | |
4861d6c4 LW |
175 | //print("Randomizing cubes! p = " + prob + "\n"); |
176 | ||
177 | for (CubeState cube_state: this.cube_states) { | |
178 | cube_state.alive = (Math.random() <= prob); | |
f7f0bfe5 LW |
179 | } |
180 | } | |
181 | ||
182 | public List<Integer> findCubeNeighbors(Cube cube, Integer index) { | |
183 | List<Integer> neighbors = new LinkedList<Integer>(); | |
184 | Integer i = 0; | |
185 | ||
186 | for (Cube c : model.cubes) { | |
4861d6c4 LW |
187 | if(index != i) { |
188 | if(abs(c.x - cube.x) < (Cube.EDGE_WIDTH * 2) && abs(c.y - cube.y) < (Cube.EDGE_HEIGHT * 2)) { | |
189 | //print("Cube " + i + " is a neighbor of " + index + "\n"); | |
190 | neighbors.add(i); | |
191 | } | |
f7f0bfe5 LW |
192 | } |
193 | ||
194 | i++; | |
195 | } | |
196 | ||
197 | return neighbors; | |
198 | } | |
199 | ||
4861d6c4 LW |
200 | public boolean shouldLightCube(CubeState cube_state) { |
201 | // Respect rate parameter. | |
202 | if(time_since_last_run < rateParameter.getValuef()) { | |
203 | any_changes_this_run = true; | |
ea4abd09 | 204 | cube_state.just_changed = false; |
64ed63c7 | 205 | return cube_state.alive; |
4861d6c4 | 206 | } else { |
64ed63c7 LW |
207 | boolean new_life = cycleOfLife(cube_state); |
208 | new_lives.add(new_life); | |
209 | return new_life; | |
210 | } | |
211 | } | |
212 | ||
213 | public void applyNewLives() { | |
214 | int index = 0; | |
215 | for(boolean liveliness: new_lives) { | |
216 | CubeState cube_state = this.cube_states.get(index); | |
217 | cube_state.alive = new_lives.get(index); | |
218 | index++; | |
219 | } | |
4861d6c4 LW |
220 | } |
221 | ||
222 | public boolean cycleOfLife(CubeState cube_state) { | |
223 | Integer index = cube_state.index; | |
224 | Integer alive_neighbor_count = countLiveNeighbors(cube_state); | |
f7f0bfe5 | 225 | boolean before_alive = cube_state.alive; |
64ed63c7 | 226 | boolean after_alive = before_alive; |
7234b0b8 | 227 | double mutation = Math.random(); |
4861d6c4 | 228 | |
f7f0bfe5 LW |
229 | if(cube_state.alive) { |
230 | if(alive_neighbor_count < 2 || alive_neighbor_count > 3) { | |
64ed63c7 | 231 | after_alive = false; |
f7f0bfe5 | 232 | } else { |
64ed63c7 | 233 | after_alive = true; |
f7f0bfe5 LW |
234 | } |
235 | ||
236 | } else { | |
2815b690 | 237 | if(alive_neighbor_count == 2) { |
64ed63c7 | 238 | after_alive = true; |
f7f0bfe5 | 239 | } else { |
64ed63c7 | 240 | after_alive = false; |
f7f0bfe5 | 241 | } |
64ed63c7 LW |
242 | } |
243 | ||
ea4abd09 | 244 | if(mutation <= mutationParameter.getValuef()) { |
7234b0b8 LW |
245 | after_alive = !after_alive; |
246 | } | |
247 | ||
64ed63c7 | 248 | if(before_alive != after_alive) { |
ea4abd09 | 249 | cube_state.just_changed = true; |
4861d6c4 | 250 | any_changes_this_run = true; |
64ed63c7 LW |
251 | } |
252 | ||
253 | return after_alive; | |
f7f0bfe5 LW |
254 | } |
255 | ||
256 | public Integer countLiveNeighbors(CubeState cube_state) { | |
257 | Integer count = 0; | |
258 | CubeState neighbor_cube_state; | |
259 | ||
260 | for(Integer neighbor_index: cube_state.neighbors) { | |
261 | neighbor_cube_state = this.cube_states.get(neighbor_index); | |
262 | if(neighbor_cube_state.alive) { | |
263 | count++; | |
264 | } | |
265 | } | |
266 | ||
267 | return count; | |
4861d6c4 | 268 | } |
f7f0bfe5 | 269 | } |
2815b690 LW |
270 | |
271 | class L8onAutomata extends SCPattern { | |
2815b690 | 272 | // Controls the rate of life algorithm ticks, in milliseconds |
7782bf8e | 273 | private BasicParameter rateParameter = new BasicParameter("DELAY", 75.0, 0.0, 1000.0); |
ea4abd09 LW |
274 | // Controls the probability of a mutation in the cycleOfStripperLife |
275 | private BasicParameter mutationParameter = new BasicParameter("MUT", 0.000000011, 0.0, 0.1); | |
276 | // Controls the rate of life algorithm ticks, in milliseconds | |
277 | private BasicParameter saturationParameter = new BasicParameter("SAT", 90.0, 0.0, 100.0); | |
2815b690 | 278 | |
ea4abd09 | 279 | private final SawLFO pointPos = new SawLFO(0, model.points.size(), 8000); |
2815b690 LW |
280 | |
281 | public final double MIN_ALIVE_PROBABILITY = 0.2; | |
282 | public final double MAX_ALIVE_PROBABILITY = 0.9; | |
283 | ||
ea4abd09 LW |
284 | public final float MAX_ALIVE_BRIGHTNESS = 90.0; |
285 | ||
2815b690 LW |
286 | class PointState { |
287 | // Index of cube in glucose.model.cubes | |
288 | public Integer index; | |
289 | // Boolean which describes if cube is alive. | |
290 | public boolean alive; | |
ea4abd09 LW |
291 | // Boolean which describes if strip was just changed; |
292 | public boolean just_changed; | |
293 | // Current brightness | |
294 | public float current_brightness; | |
2815b690 | 295 | |
ea4abd09 | 296 | public PointState(Integer index, boolean alive, float current_brightness) { |
2815b690 LW |
297 | this.index = index; |
298 | this.alive = alive; | |
ea4abd09 LW |
299 | this.current_brightness = current_brightness; |
300 | this.just_changed = false; | |
2815b690 LW |
301 | } |
302 | } | |
303 | ||
304 | // Contains the state of all cubes by index. | |
305 | private List<PointState> point_states; | |
306 | // Contains the amount of time since the last cycle of life. | |
307 | private int time_since_last_run; | |
308 | // Boolean describing if life changes were made during the current run. | |
309 | private boolean any_changes_this_run; | |
310 | // Hold the new lives | |
311 | private List<Boolean> new_states; | |
312 | ||
313 | public L8onAutomata(GLucose glucose) { | |
314 | super(glucose); | |
315 | ||
316 | //Print debug info about the cubes. | |
317 | //outputCubeInfo(); | |
318 | ||
319 | initPointStates(); | |
320 | randomizePointStates(); | |
321 | time_since_last_run = 0; | |
322 | any_changes_this_run = false; | |
323 | new_states = new ArrayList<Boolean>(); | |
324 | ||
ea4abd09 | 325 | addParameter(mutationParameter); |
2815b690 | 326 | addParameter(rateParameter); |
ea4abd09 | 327 | addParameter(saturationParameter); |
7234b0b8 | 328 | addModulator(pointPos).trigger(); |
2815b690 LW |
329 | } |
330 | ||
331 | private void initPointStates() { | |
ea4abd09 | 332 | boolean alive = true; |
2815b690 LW |
333 | PointState point_state; |
334 | this.point_states = new ArrayList<PointState>(); | |
335 | Integer i = 0; | |
ea4abd09 | 336 | float current_brightness = 0.0; |
2815b690 LW |
337 | |
338 | for (LXPoint p : model.points) { | |
ea4abd09 | 339 | point_state = new PointState(i, alive, current_brightness); |
2815b690 LW |
340 | this.point_states.add(point_state); |
341 | ++i; | |
342 | } | |
343 | } | |
344 | ||
345 | public void run(double deltaMs) { | |
346 | Integer i = 0; | |
347 | PointState point_state; | |
348 | ||
349 | any_changes_this_run = false; | |
350 | new_states.clear(); | |
351 | time_since_last_run += deltaMs; | |
352 | ||
353 | for (LXPoint p : model.points) { | |
354 | point_state = this.point_states.get(i); | |
355 | ||
356 | if(shouldLightPoint(point_state)) { | |
ea4abd09 | 357 | lightLivePoint(p, point_state, deltaMs); |
2815b690 | 358 | } else { |
ea4abd09 | 359 | lightDeadPoint(p, point_state, deltaMs); |
2815b690 | 360 | } |
2815b690 LW |
361 | i++; |
362 | } | |
363 | ||
7234b0b8 | 364 | if(!any_changes_this_run) { |
2815b690 LW |
365 | randomizePointStates(); |
366 | } else { | |
367 | applyNewStates(); | |
368 | } | |
369 | ||
370 | if(time_since_last_run >= rateParameter.getValuef()) { | |
371 | time_since_last_run = 0; | |
372 | } | |
373 | } | |
374 | ||
ea4abd09 LW |
375 | public void lightLivePoint(LXPoint p, PointState point_state, double deltaMs) { |
376 | float point_dist = LXUtils.wrapdistf((float) point_state.index, pointPos.getValuef(), model.points.size()); | |
7234b0b8 | 377 | float hv = (point_dist / model.points.size()) * 360; |
ea4abd09 LW |
378 | float bv = point_state.current_brightness; |
379 | ||
380 | if(deltaMs >= rateParameter.getValuef() || !point_state.just_changed) { | |
381 | float bright_prop = min(((float) time_since_last_run / rateParameter.getValuef()), 1.0); | |
382 | bv = min(MAX_ALIVE_BRIGHTNESS, bright_prop * MAX_ALIVE_BRIGHTNESS); | |
383 | ||
384 | if(point_state.current_brightness < bv) { | |
385 | point_state.current_brightness = bv; | |
386 | } else { | |
387 | bv = point_state.current_brightness; | |
388 | } | |
389 | } | |
7234b0b8 | 390 | |
2815b690 LW |
391 | colors[p.index] = lx.hsb( |
392 | hv, | |
ea4abd09 LW |
393 | saturationParameter.getValuef(), |
394 | bv | |
2815b690 LW |
395 | ); |
396 | } | |
397 | ||
ea4abd09 LW |
398 | public void lightDeadPoint(LXPoint p, PointState point_state, double deltaMs) { |
399 | float point_dist = LXUtils.wrapdistf((float) point_state.index, pointPos.getValuef(), model.points.size()); | |
400 | float hv = (point_dist / model.points.size()) * 360; | |
401 | float bv = point_state.current_brightness; | |
402 | ||
403 | if(!point_state.just_changed || deltaMs >= rateParameter.getValuef()) { | |
404 | float bright_prop = 1.0 - min(((float) time_since_last_run / rateParameter.getValuef()), 1.0); | |
405 | bv = max(0.0, bright_prop * MAX_ALIVE_BRIGHTNESS); | |
406 | ||
407 | if(point_state.current_brightness > bv) { | |
408 | point_state.current_brightness = bv; | |
409 | } else { | |
410 | bv = point_state.current_brightness; | |
411 | } | |
412 | } | |
413 | ||
2815b690 | 414 | colors[p.index] = lx.hsb( |
ea4abd09 LW |
415 | hv, |
416 | saturationParameter.getValuef(), | |
417 | bv | |
2815b690 LW |
418 | ); |
419 | } | |
420 | ||
421 | public boolean shouldLightPoint(PointState point_state) { | |
422 | // Respect rate parameter. | |
423 | if(time_since_last_run < rateParameter.getValuef()) { | |
424 | any_changes_this_run = true; | |
ea4abd09 | 425 | point_state.just_changed = false; |
2815b690 LW |
426 | return point_state.alive; |
427 | } else { | |
428 | boolean new_state = cycleOfAutomata(point_state); | |
429 | new_states.add(new_state); | |
430 | return new_state; | |
431 | } | |
432 | } | |
433 | ||
434 | public boolean cycleOfAutomata(PointState point_state) { | |
435 | Integer index = point_state.index; | |
436 | Integer alive_neighbor_count = countLiveNeighbors(point_state); | |
437 | boolean before_alive = point_state.alive; | |
438 | boolean after_alive = before_alive; | |
7234b0b8 | 439 | double mutation = Math.random(); |
2815b690 LW |
440 | |
441 | if(point_state.alive) { | |
442 | if(alive_neighbor_count == 1) { | |
443 | after_alive = true; | |
444 | } else { | |
445 | after_alive = false; | |
446 | } | |
447 | ||
448 | } else { | |
449 | if(alive_neighbor_count == 1) { | |
450 | after_alive = true; | |
451 | } else { | |
452 | after_alive = false; | |
453 | } | |
454 | } | |
455 | ||
ea4abd09 | 456 | if(mutation < mutationParameter.getValuef()) { |
7234b0b8 LW |
457 | after_alive = !after_alive; |
458 | } | |
459 | ||
2815b690 LW |
460 | if(before_alive != after_alive) { |
461 | any_changes_this_run = true; | |
ea4abd09 | 462 | point_state.just_changed = true; |
2815b690 LW |
463 | } |
464 | ||
465 | return after_alive; | |
466 | } | |
467 | ||
468 | public int countLiveNeighbors(PointState point_state) { | |
2815b690 | 469 | int count = 0; |
ea4abd09 LW |
470 | |
471 | if (point_state.index > 0) { | |
472 | PointState before_neighbor = point_states.get(point_state.index - 1); | |
2815b690 LW |
473 | if(before_neighbor.alive) { |
474 | count++; | |
475 | } | |
476 | } | |
477 | ||
ea4abd09 LW |
478 | if (point_state.index < (point_states.size() - 1)) { |
479 | PointState after_neighbor = point_states.get(point_state.index + 1); | |
2815b690 LW |
480 | if(after_neighbor.alive) { |
481 | count++; | |
482 | } | |
483 | } | |
484 | ||
485 | return count; | |
486 | } | |
487 | ||
488 | private void applyNewStates() { | |
489 | int index = 0; | |
490 | for(boolean new_state: new_states) { | |
491 | PointState point_state = this.point_states.get(index); | |
492 | point_state.alive = new_states.get(index); | |
493 | index++; | |
494 | } | |
495 | } | |
496 | ||
497 | private void randomizePointStates() { | |
498 | double prob_range = (1.0 - MIN_ALIVE_PROBABILITY) - (1.0 - MAX_ALIVE_PROBABILITY); | |
499 | double prob = MIN_ALIVE_PROBABILITY + (prob_range * Math.random()); | |
500 | ||
7234b0b8 | 501 | //print("Randomizing points! p = " + prob + "\n"); |
2815b690 LW |
502 | |
503 | for (PointState point_state: this.point_states) { | |
504 | point_state.alive = (Math.random() <= prob); | |
ea4abd09 | 505 | point_state.just_changed = true; |
2815b690 LW |
506 | } |
507 | } | |
7782bf8e LW |
508 | } |
509 | ||
ea4abd09 | 510 | class L8onStripLife extends SCPattern { |
7782bf8e | 511 | // Controls the rate of life algorithm ticks, in milliseconds |
8004bace | 512 | private BasicParameter rateParameter = new BasicParameter("DELAY", 112.5, 1.0, 1000.0); |
7234b0b8 | 513 | // Controls the probability of a mutation in the cycleOfStripperLife |
ea4abd09 | 514 | private BasicParameter mutationParameter = new BasicParameter("MUT", 0.000000011, 0.0, 0.1); |
7782bf8e LW |
515 | // Controls the saturation. |
516 | private BasicParameter saturationParameter = new BasicParameter("SAT", 90.0, 0.0, 100.0); | |
517 | ||
f01b5ada | 518 | public final double MIN_ALIVE_PROBABILITY = 0.4; |
7782bf8e LW |
519 | public final double MAX_ALIVE_PROBABILITY = 0.9; |
520 | ||
ea4abd09 | 521 | public final float MAX_ALIVE_BRIGHTNESS = 90.0; |
8004bace | 522 | |
ea4abd09 | 523 | private final SawLFO stripPos = new SawLFO(0, model.strips.size(), 8000); |
7782bf8e LW |
524 | |
525 | class StripState { | |
526 | // Index of strip in glucose.model.strips | |
527 | public Integer index; | |
528 | // Boolean which describes if strip is alive. | |
529 | public boolean alive; | |
8004bace LW |
530 | // Boolean which describes if strip was just changed; |
531 | public boolean just_changed; | |
532 | // Current brightness | |
533 | public float current_brightness; | |
7782bf8e LW |
534 | // List of this cubes neighbors |
535 | public List<Integer> neighbors; | |
536 | ||
8004bace | 537 | public StripState(Integer index, boolean alive, float current_brightness, List<Integer> neighbors) { |
7782bf8e LW |
538 | this.index = index; |
539 | this.alive = alive; | |
ea4abd09 | 540 | this.current_brightness = current_brightness; |
7782bf8e | 541 | this.neighbors = neighbors; |
ea4abd09 | 542 | this.just_changed = false; |
7782bf8e LW |
543 | } |
544 | } | |
545 | ||
546 | // Contains the state of all cubes by index. | |
547 | private List<StripState> strip_states; | |
548 | // Contains the amount of time since the last cycle of life. | |
549 | private int time_since_last_run; | |
550 | // Boolean describing if life changes were made during the current run. | |
551 | private boolean any_changes_this_run; | |
552 | // Hold the new lives | |
553 | private List<Boolean> new_lives; | |
554 | ||
ea4abd09 | 555 | public L8onStripLife(GLucose glucose) { |
7782bf8e LW |
556 | super(glucose); |
557 | ||
558 | //Print debug info about the strips. | |
559 | //outputStripInfo(); | |
560 | ||
561 | initStripStates(); | |
562 | randomizeStripStates(); | |
563 | time_since_last_run = 0; | |
564 | any_changes_this_run = false; | |
565 | new_lives = new ArrayList<Boolean>(); | |
566 | ||
567 | addParameter(rateParameter); | |
ea4abd09 | 568 | addParameter(mutationParameter); |
7782bf8e LW |
569 | addParameter(saturationParameter); |
570 | ||
7234b0b8 | 571 | addModulator(stripPos).trigger(); |
7782bf8e LW |
572 | } |
573 | ||
574 | public void run(double deltaMs) { | |
575 | Integer i = 0; | |
576 | StripState strip_state; | |
577 | ||
578 | any_changes_this_run = false; | |
579 | new_lives.clear(); | |
580 | time_since_last_run += deltaMs; | |
581 | ||
582 | for (Strip strip : model.strips) { | |
583 | strip_state = this.strip_states.get(i); | |
584 | ||
585 | if(shouldLightStrip(strip_state)) { | |
8004bace | 586 | lightLiveStrip(strip, strip_state, deltaMs); |
7782bf8e | 587 | } else { |
8004bace | 588 | lightDeadStrip(strip, strip_state, deltaMs); |
7782bf8e | 589 | } |
7782bf8e LW |
590 | i++; |
591 | } | |
592 | ||
7234b0b8 | 593 | if(!any_changes_this_run) { |
7782bf8e LW |
594 | randomizeStripStates(); |
595 | } else { | |
596 | applyNewLives(); | |
597 | } | |
598 | ||
599 | if(time_since_last_run >= rateParameter.getValuef()) { | |
600 | time_since_last_run = 0; | |
601 | } | |
602 | } | |
603 | ||
8004bace | 604 | public void lightLiveStrip(Strip strip, StripState strip_state, double deltaMs) { |
ea4abd09 | 605 | float strip_dist = LXUtils.wrapdistf((float) strip_state.index, stripPos.getValuef(), model.strips.size()); |
7234b0b8 | 606 | float hv = (strip_dist / model.strips.size()) * 360; |
8004bace LW |
607 | float bv = strip_state.current_brightness; |
608 | ||
ea4abd09 | 609 | if(deltaMs >= rateParameter.getValuef() || !strip_state.just_changed) { |
8004bace LW |
610 | float bright_prop = min(((float) time_since_last_run / rateParameter.getValuef()), 1.0); |
611 | bv = min(MAX_ALIVE_BRIGHTNESS, bright_prop * MAX_ALIVE_BRIGHTNESS); | |
612 | ||
8004bace LW |
613 | if(strip_state.current_brightness < bv) { |
614 | strip_state.current_brightness = bv; | |
615 | } else { | |
616 | bv = strip_state.current_brightness; | |
617 | } | |
8004bace | 618 | } |
7234b0b8 | 619 | |
7782bf8e | 620 | for (LXPoint p : strip.points) { |
7782bf8e LW |
621 | colors[p.index] = lx.hsb( |
622 | hv, | |
623 | saturationParameter.getValuef(), | |
8004bace | 624 | bv |
7782bf8e LW |
625 | ); |
626 | } | |
627 | } | |
628 | ||
8004bace | 629 | public void lightDeadStrip(Strip strip, StripState strip_state, double deltaMs) { |
ea4abd09 LW |
630 | float strip_dist = LXUtils.wrapdistf((float) strip_state.index, stripPos.getValuef(), model.strips.size()); |
631 | float hv = (strip_dist / model.strips.size()) * 360; | |
632 | float bv = strip_state.current_brightness; | |
8004bace LW |
633 | |
634 | if(!strip_state.just_changed || deltaMs >= rateParameter.getValuef()) { | |
635 | float bright_prop = 1.0 - min(((float) time_since_last_run / rateParameter.getValuef()), 1.0); | |
636 | bv = max(0.0, bright_prop * MAX_ALIVE_BRIGHTNESS); | |
637 | ||
8004bace LW |
638 | if(strip_state.current_brightness > bv) { |
639 | strip_state.current_brightness = bv; | |
640 | } else { | |
641 | bv = strip_state.current_brightness; | |
642 | } | |
8004bace | 643 | } |
2815b690 | 644 | |
7234b0b8 | 645 | for (LXPoint p : strip.points) { |
7782bf8e LW |
646 | colors[p.index] = lx.hsb( |
647 | hv, | |
648 | saturationParameter.getValuef(), | |
8004bace | 649 | bv |
7782bf8e LW |
650 | ); |
651 | } | |
652 | } | |
653 | ||
654 | public void outputStripInfo() { | |
655 | int i = 0; | |
656 | for (Strip strip : model.strips) { | |
657 | print("Strip " + i + ": " + strip.cx + "," + strip.cy + "," + strip.cz + "\n"); | |
658 | ++i; | |
659 | } | |
660 | } | |
661 | ||
662 | private void initStripStates() { | |
663 | List<Integer> neighbors; | |
664 | boolean alive = false; | |
ea4abd09 | 665 | float current_brightness = 0.0; |
7782bf8e LW |
666 | StripState strip_state; |
667 | this.strip_states = new ArrayList<StripState>(); | |
668 | Integer i = 0; | |
669 | ||
670 | int total_neighbors = 0; | |
671 | ||
672 | for (Strip strip : model.strips) { | |
673 | neighbors = findStripNeighbors(strip, i); | |
674 | alive = true; | |
8004bace | 675 | strip_state = new StripState(i, alive, current_brightness, neighbors); |
7782bf8e LW |
676 | this.strip_states.add(strip_state); |
677 | ||
678 | total_neighbors += neighbors.size(); | |
679 | ++i; | |
680 | } | |
681 | ||
682 | float average_neighbor_count = (float) total_neighbors / (float) model.strips.size(); | |
683 | //print("Average neighbor count: " + average_neighbor_count + "\n"); | |
684 | } | |
685 | ||
686 | private void randomizeStripStates() { | |
687 | double prob_range = (1.0 - MIN_ALIVE_PROBABILITY) - (1.0 - MAX_ALIVE_PROBABILITY); | |
688 | double prob = MIN_ALIVE_PROBABILITY + (prob_range * Math.random()); | |
689 | ||
690 | //print("Randomizing strips! p = " + prob + "\n"); | |
691 | ||
692 | for (StripState strip_state : this.strip_states) { | |
693 | strip_state.alive = (Math.random() <= prob); | |
ea4abd09 | 694 | strip_state.just_changed = true; |
7782bf8e LW |
695 | } |
696 | } | |
697 | ||
698 | public List<Integer> findStripNeighbors(Strip strip, Integer index) { | |
699 | List<Integer> neighbors = new LinkedList<Integer>(); | |
700 | Integer i = 0; | |
701 | int neighbor_count = 0; | |
702 | double distance = 0.0; | |
703 | ||
704 | for (Strip s : model.strips) { | |
705 | if( (int)index != (int)i ) { | |
706 | distance = Math.sqrt( Math.pow((s.cx - strip.cx), 2) + Math.pow((s.cy - strip.cy), 2) + Math.pow((s.cz - strip.cz), 2) ); | |
707 | ||
708 | if(distance < ( (double) Cube.EDGE_WIDTH) ) { | |
709 | //print("Strip " + i + " is a neighbor of " + index + "\n"); | |
710 | neighbors.add(i); | |
711 | } | |
712 | } | |
713 | i++; | |
714 | } | |
715 | ||
716 | return neighbors; | |
717 | } | |
718 | ||
719 | public boolean shouldLightStrip(StripState strip_state) { | |
720 | // Respect rate parameter. | |
721 | if(time_since_last_run < rateParameter.getValuef()) { | |
722 | any_changes_this_run = true; | |
8004bace | 723 | strip_state.just_changed = false; |
7782bf8e LW |
724 | return strip_state.alive; |
725 | } else { | |
726 | boolean new_life = cycleOfStripperLife(strip_state); | |
727 | new_lives.add(new_life); | |
728 | return new_life; | |
729 | } | |
730 | } | |
731 | ||
732 | public void applyNewLives() { | |
733 | int index = 0; | |
734 | for(boolean liveliness: new_lives) { | |
735 | StripState strip_state = this.strip_states.get(index); | |
736 | strip_state.alive = new_lives.get(index); | |
737 | index++; | |
738 | } | |
739 | } | |
740 | ||
741 | public boolean cycleOfStripperLife(StripState strip_state) { | |
7782bf8e LW |
742 | Integer alive_neighbor_count = countLiveNeighbors(strip_state); |
743 | boolean before_alive = strip_state.alive; | |
744 | boolean after_alive = before_alive; | |
7234b0b8 | 745 | double mutation = Math.random(); |
7782bf8e LW |
746 | |
747 | if(strip_state.alive) { | |
748 | if(alive_neighbor_count < 2 || alive_neighbor_count > 6) { | |
749 | after_alive = false; | |
750 | } else { | |
751 | after_alive = true; | |
752 | } | |
753 | ||
754 | } else { | |
755 | if(alive_neighbor_count == 5) { | |
756 | after_alive = true; | |
757 | } else { | |
758 | after_alive = false; | |
759 | } | |
760 | } | |
761 | ||
ea4abd09 | 762 | if(mutation < mutationParameter.getValuef()) { |
7234b0b8 LW |
763 | after_alive = !after_alive; |
764 | } | |
765 | ||
7782bf8e LW |
766 | if(before_alive != after_alive) { |
767 | any_changes_this_run = true; | |
8004bace | 768 | strip_state.just_changed = true; |
7782bf8e LW |
769 | } |
770 | ||
771 | return after_alive; | |
772 | } | |
773 | ||
774 | public Integer countLiveNeighbors(StripState strip_state) { | |
775 | Integer count = 0; | |
776 | StripState neighbor_strip_state; | |
777 | ||
778 | for(Integer neighbor_index: strip_state.neighbors) { | |
779 | neighbor_strip_state = this.strip_states.get(neighbor_index); | |
780 | if(neighbor_strip_state.alive) { | |
781 | count++; | |
782 | } | |
783 | } | |
784 | ||
785 | return count; | |
786 | } | |
2815b690 | 787 | } |