Belatedly – hah! that was a cut-paste typo.
I made it for integration into the previous demo sketch. Again, all that complexity isn’t usually necessary – but different forms for different needs.
Here is something that tests each distribution:
/**
* RandomRanges2
* 2020-01-09 Jeremy Douglass Processing 3.4
* useful approaches for generating distributions of random ranges
* in linear or circular spaces, e.g. hue colorspaces.
*
* In this example wrap and overflow are converted inline into the wrap
* and split format in order to avoid ArrayIndexOutOfBoundsException.
* In actual code you need only return the needed format.
*
* - split (and wrap, when split) trend towards flat distributions.
* - crop (and overflow, when cropped) fall off on the left-hand side.
* - fitCenter and fitwidth both have curved distributions.
* - fitwidth returns shorter ranges on average, as it ignores minWide
* when center is near edge.
*
* see https://discourse.processing.org/t/minimum-and-maximum-random-numbers/16888/4
*/
String[] modes;
float[][] ranges;
int[][] graphs;
int wmax = 360;
boolean reset;
void settings() {
size(wmax, 480);
}
void setup() {
colorMode(HSB);
strokeWeight(3);
// list different ways to generate a random range
modes = new String[]{"split", "wrap", "overflow", "crop", "fitWidth", "fitCenter"};
// each frame store a new range for each mode,
// and add the cumulative ranges to a graph for each mode
ranges = new float[modes.length][];
graphs = new int[modes.length][width];
// when this flag is set the sketch will screenshot then rerun setup
reset = false;
}
void draw() {
background(0);
// loop over modes
for (int m=0; m<modes.length; m++) {
// get range for this mode
ranges[m] = randomRange(0, 360, 0, 360, modes[m]); // ! changing 4th argument to more than 2nd breaks fit modes
// fix bad return ranges by reformatting
if (modes[m].equals("overflow") && ranges[m][1]>wmax) ranges[m] = new float[]{ranges[m][0], min(ranges[m][1], wmax)}; // crop the overflow
if (modes[m].equals("wrap") && ranges[m][1]<ranges[m][0]) ranges[m] = new float[]{0, ranges[m][1], ranges[m][0], wmax-1}; // split the wrap
// add range(s) to graph
if(ranges[m].length>6) print(ranges[m].length, " ");
for (int r=0; r<ranges[m].length; r+=2) {
for (int x=(int)ranges[m][r]; x<(int)ranges[m][r+1]; x++) {
graphs[m][x]++;
// if a graph height has filled the page, reset sketch at end of this frame
if (graphs[m][x] > height-50) reset = true;
}
}
}
for (int m=0; m<modes.length; m++) {
// draw range bars and graphs
int h = m * (360-120)/modes.length;
stroke(h, 255, 192); // set mode line color
int lb = (m+1)*5;
line(ranges[m][0], lb, ranges[m][1], lb);
if (ranges[m].length==4) line(ranges[m][2], lb, ranges[m][3], lb);
for (int x=0; x<width; x++) {
point(x, height-graphs[m][x]);
}
}
for (int m=0; m<modes.length; m++) {
// draw labels
int h = m * (360-120)/modes.length;
int tab = m * (wmax-20)/(modes.length);
fill(0);
textSize(16);
text(modes[m], tab, height-graphs[m][tab]);
fill(h, 128, 255);
text(modes[m], tab+1, height-graphs[m][tab]+1);
}
// if resetting, save frame first;
if (reset) {
saveFrame("RandomRanges2--screenshot#####.png");
frameCount = -1; // rerun setup;
}
}
float[] randomRange(float minVal, float maxVal, float minWide, float maxWide, String mode) {
//if (maxWide > maxVal-minVal) throw new IllegalArgumentException("maxWide cannot be greater than value range.");
// if the mode is to "fit" the random range inside the min-max values then
// fitting can happen width-first (fit center) or center-first (fit width).
switch(mode) {
case "fitCenter": // random width, fit center
float wide = random(minWide, maxWide);
float center = random(minVal + wide/2.0, maxVal - wide/2.0);
return new float[]{center - wide/2.0, center + wide/2.0};
case "fitWidth": // random center, fit width -- ignores minWide and limits range if center near edge
float center2 = random(minVal, maxVal);
float wideLimit = 2 * min(center2, maxVal-center2);
float wide2 = random(min(wideLimit, minWide), min(wideLimit, maxWide));
return new float[]{center2 - wide2/2.0, center2 + wide2/2.0};
}
// otherwise, just pick a start and end, which might overflow
float start = random(minVal, maxVal);
float end = start + random(minWide, maxWide);
// did end overflow? if so, choose a format to represent the result
if (end>maxVal) {
switch(mode) {
case "crop":
// crop range, throw away invalid part
return new float[] {start, maxVal};
case "split":
// crop range, add overflow as wrapped (valid) ranges
int wraps = (int)((end - end%maxVal)/maxVal);
float[] result = new float[2*wraps + 2];
for (int i=0; i<result.length; i+=2) {
result[i] = minVal;
result[i+1] = maxVal;
}
result[0] = start;
result[result.length-1] = end%maxVal;
return result;
//return new float[]{minVal, end%maxVal, start, maxVal};
case "overflow":
// return invalid part of the range anyway
// using these values to iterate or interpolate may cause errors
return new float[] {start, end};
case "wrap":
// wrapping around last-first, for example: [350, 370] -> [350, 10]
// always returns a range with 2 endpoint, e.g. [100, 3800] -> [100, 200];
// using these values to iterate or interpolate may cause errors
return new float[] {start, end%maxVal};
default:
throw new IllegalArgumentException("Invalid mode on overflow: " + mode);
}
}
// no overflow: range already fits
return new float[]{start, end};
}
Result:
