/**
*
Layers_by_spxl
*
*
* Last modified: 2009-01-09 10:47
*
* Experiments in graphics using PGraphics layers to construct an image.
*
* I like the idea of having artificial "construction lines" optionally
* appear in an image, similar to showing a "wireframe" on a 3D model. This
* example uses two layers of construction lines: one behind the objects, and
* one in front of the objects. The level of detail can also be adjusted; in
* this case, the crosshairs to the edge of the window are at a higher
* level (not to be confused with higher layer!) than the small "+" to mark
* a circle's centre and a circle's outline.
*
* The base Shape class includes details for presentation (fill colour,
* edge colour, edge weight, etc) as well as position and animation (in this
* case simple velocity with acceleration), including drawing a "trace" of
* the shape's location. The motion calculations aren't "reality-accurate",
* but they are time based, not frame based. To demonstrate this, you can
* increase and decrease the sketch's frame rate and see that the objects
* seem to move in similar trajectories whether the updates are regular
* (eg at 32 fps) or infrequent (eg at 1 fps). Time is "stopped" (or more
* like skipped) when the sketch is paused.
*
* Another neat trick implemented is seamless wrapping of objects at the
* edges of the window. This is achieved by drawing an object multiple times,
* if required, offset by the window width and/or height. A similar trick is
* used to wrap the trace line generated for the shape.
*
* Keys:
*
* - [1][2][3][4][5] show/hide layers
* - [SPACE] pause/unpause
* - (r)estart
* - (c)onstruction lines toggle, (d)ecrease level, [e] increase level
* - (z)ero gravity, [q] gravity up, [a] gravity down
* - (t)race on/off, [x] erase trace
* - (f)ramerate drop, (g)o faster
*
*/
// CONSTANTS
static final int MAX_CIRCLES = 30;
static final int NUM_LAYERS = 5;
static final int LAYER_TRACE = 0;
static final int LAYER_CNSTR_UNDER = 1;
static final int LAYER_BACKGROUND = 2;
static final int LAYER_DRAW = 3;
static final int LAYER_CNSTR_OVER = 4;
// GLOBAL VARIABLES
boolean paused = false;
int frameRate = 32;
boolean showConstructionLines = true;
int showConstructionLinesLevel = 2;
color constructionLinesColor = color(127, 127, 127, 127);
int constructionLinesWeight = 1;
boolean showTrace = true;
color traceColor = color(255, 255, 0, 63);
int traceLinesWeight = 2;
color shapeBgColor = color(255, 0, 0, 80);
color cWhite = color(255, 255, 255);
color cWhite50 = color(255, 255, 255, 127);
color cRed = color(255, 0, 0);
Vector2D gravity = new Vector2D(0, 0);
Circle[] c;
PGraphics[] layer;
boolean[] showLayer;
int clockMillis;
int prevMillis;
void setup()
{
size(600, 400, P2D);
frameRate(this.frameRate);
smooth();
background(0);
layer = new PGraphics[NUM_LAYERS];
showLayer = new boolean[NUM_LAYERS];
for (int i = 0; i < NUM_LAYERS; i++)
{
layer[i] = createGraphics(width, height, P2D);
showLayer[i] = true;
}
c = new Circle[MAX_CIRCLES];
for (int i = 0; i < MAX_CIRCLES; i++)
{
c[i] = new Circle(random(width), random(height), 5 + random(50));
c[i].v = new Vector2D((random(50) + 20) * (random(1) < 0.5 ? -1 : 1), - random(50));
c[i].a = gravity;
c[i].fillColor = color(rnd(256), rnd(256), rnd(256));
c[i].bgColor = shapeBgColor;
c[i].showEdge = false;
c[i].showFill = true;
c[i].edgeWeight = 2;
println("c[" + i + "]: " + c[i].str());
}
}
void draw()
{
prevMillis = clockMillis;
clockMillis = millis();
int frameMillis = clockMillis - prevMillis;
background(0);
// Clear the layers (except the trace layer)
for (int i = 0; i < NUM_LAYERS; i++)
{
if (i != LAYER_TRACE)
layer[i].background(0, 0); // Note: layers are transparent!
layer[i].beginDraw();
}
for (int i = 0; i < MAX_CIRCLES; i++)
{
c[i].animTick(frameMillis);
c[i].draw();
}
for (int i = 0; i < NUM_LAYERS; i++)
{
layer[i].endDraw();
if (showLayer[i]) image(layer[i], 0, 0);
}
}
void keyPressed()
{
// SHOW/HIDE LAYERS
if (key >= '1' && (key - '0' <= NUM_LAYERS))
{
int n = key - '0' - 1;
showLayer[n] = !showLayer[n];
}
else switch (key)
{
// RESET
case 'r': setup();
break;
// FRAMERATE
case ' ': togglePaused();
break;
case 'f': if (frameRate > 1) frameRate(frameRate /= 2); // Slow down
break;
case 'g': frameRate(frameRate *= 2); // Go faster
break;
// MOTION TRACE
case 't': showTrace = !showTrace;
break;
case 'x': layer[LAYER_TRACE].background(0, 0); // Erase trace layer
break;
// CONSTRUCTION LINES
case 'c': showConstructionLines = !showConstructionLines;
break;
case 'e': showConstructionLinesLevel++;
break;
case 'd': if (showConstructionLinesLevel > 0) showConstructionLinesLevel--;
break;
// GRAVITY
case 'z': gravity.y = 0;
break;
case 'q': gravity.y -= 1; // Gravity "upwards"
break;
case 'a': gravity.y += 1; // gravity "downwards"
break;
}
}
public void pause()
{
paused = true;
noLoop();
}
public void togglePaused()
{
paused = ! paused;
if (paused)
{
noLoop();
}
else
{
loop();
clockMillis = millis();
}
}
// ------------------------------------------------------------
// Return a random colour
// ------------------------------------------------------------
color rndColor()
{
return color(rnd(256), rnd(256), rnd(256));
}
// ------------------------------------------------------------
// Return a random integer from 0 to n (not including n)
// that is also not equal to x
// ------------------------------------------------------------
int rndNotX(int n, int x)
{
int val = int(random(n - 1));
return (val < x) ? val : val + 1;
}
// ------------------------------------------------------------
// Return a random integer from 0 to n (not including n)
// ------------------------------------------------------------
int rnd(int n)
{
return int(random(n));
}
// ------------------------------------------------------------
// "Wrap" a value within the limits of minValue and maxValue (inclusive).
// Similar to performing modulo arithmetic.
// ------------------------------------------------------------
int wrapConstrain(int value, int minValue, int maxValue)
{
if (value < minValue || value > maxValue)
{
int rangeSize = maxValue - minValue + 1;
for (;value < minValue; value += rangeSize);
for (;value > maxValue; value -= rangeSize);
}
return value;
}