I have read this discussion with interest and it sounds and challenging project to implement. The ideas I present here are just that ultimately you must follow your own programming path but I hope you find my ideas interesting if not useful.
I have decided to implement something very similar but using Javascript. I created the sketch below to try some ideas out and share them with you. The each cell contains a path and that path is described by a pair of parametric equations x=f(t) and y=g(t). The variable t can have any value in the range \ge0 and \le1 but 0 defines the path start and 1 the path end, values between represent some point on the path.
Having a single Cell
class that can be used for any path is not efficient because you are forever having to check the path type and direction and the code becomes messy and unmanageable . The solution I used is a simple class hierarchy which can easily be extended to include other paths.
Anyway this video shows the output from the sketch. If you have questions about the code then just ask away
ArrayList<Cell> cells = new ArrayList<Cell>();
ArrayList<String> types = new ArrayList<String>();
float t = 0f, dt = 0.005f;
public void settings() {
size(500, 320);
}
public void setup() {
float s = 80, x = 20, y = 100, dx = 120, dy = 120;
textAlign(CENTER, CENTER);
textSize(20);
cells.add(getCell("NS", x, y, s));
cells.add(getCell("EW", x + dx, y, s));
cells.add(getCell("NE", x, y + dy, s));
cells.add(getCell("ES", x + dx, y + dy, s));
cells.add(getCell("SW", x + 2 * dx, y + dy, s));
cells.add(getCell("WN", x + 3 * dx, y + dy, s));
}
public void draw() {
background(192);
t += dt;
if (t < 0) {
t=0;
dt *= -1;
} else if (t > 1) {
t = 1;
dt *= -1;
}
if (t > 1) t = 0;
noStroke();
fill(255, 255, 0);
for (Cell c : cells) {
c.draw();
float[] p = c.getPos(t);
ellipse(c.x + p[0], c.y + p[1], 10, 10);
}
// Travel direction and parametric details
rect(240, 30, t*200, 30);
noFill();
stroke(0);
strokeWeight(2);
rect(240, 30, 200, 30);
fill(0);
text(nf(t, 0, 3), 240, 30, 200, 24);
text("Direction:", 20, 0, 200, 24);
text(dt >= 0 ? "Forward" : "Reverse", 220, 0, 200, 24);
text("Parametric value (t):", 20, 30, 200, 24);
// Display NS, EW , NE etc
fill(0);
for (int i = 0; i < types.size(); i++) {
Cell c = cells.get(i);
text(types.get(i), c.x, c.y - 32, 36, 30);
}
}
public void exit() {
super.exit();
}
/**
* This is the key method for creating the tiles with the correct path.
*
* @param type two letter code indicating positive travel direction
* @param x x coordinate for top-left corner for the tile
* @param y y coordinate for top-left corner for the tile
* @param size cell size
* @return the tile or null if the type cannot be found
*/
public Cell getCell(String type, float x, float y, float size) {
switch(type.toUpperCase()) {
case "NS":
types.add("NS");
return new CellLine(x, y, size, new float[] {0.5f, 0, 0.5f, 1});
case "EW":
types.add("EW");
return new CellLine(x, y, size, new float[] {1, 0.5f, 0, 0.5f});
case "NE":
types.add("NE");
return new CellArc(x, y, size, new float[] {1, 0, PI, 0.5f * PI});
case "ES":
types.add("ES");
return new CellArc(x, y, size, new float[] {1, 1, 1.5f * PI, PI});
case "SW":
types.add("SW");
return new CellArc(x, y, size, new float[] {0, 1, 2 * PI, 1.5f * PI});
case "WN":
types.add("WN");
return new CellArc(x, y, size, new float[] {0, 0, 0.5f * PI, 0});
}
return null;
}
/**
* The base class for all cells. All useful cells are created
* from sub-classes.
*
*/
public class Cell {
public float x, y, size, t;
public float pathLength;
public Cell(float x, float y, float size) {
this.x = x;
this.y = y;
this.size = size;
}
public void drawCellBorder() {
rectMode(CORNER);
noFill();
strokeWeight(1);
stroke(255, 255, 0);
rect(0, 0, size, size);
}
public void draw() {
}
public float[] getPos(float t) {
return new float[] {0, 0};
}
public float getPathLength() {
return pathLength;
}
}
/** Straight path cell */
public class CellLine extends Cell {
public float tx0, tx1, ty0, ty1;
public CellLine(float x, float y, float size, float[] path_0_1) {
super(x, y, size);
this.tx0 = path_0_1[0];
this.ty0 = path_0_1[1];
this.tx1 = path_0_1[2];
this.ty1 = path_0_1[3];
this.pathLength = this.size;
}
public float[] getPos(float t) {
t = constrain(t, 0, 1);
float x = map(t, 0, 1, tx0, tx1) * size;
float y = map(t, 0, 1, ty0, ty1) * size;
return new float[] {x, y};
}
public void draw() {
push();
translate(x, y);
drawCellBorder();
stroke(0);
line(tx0 * size, ty0 * size, tx1 * size, ty1 * size);
pop();
}
}
/** Arc path cell */
public class CellArc extends Cell {
public float ta0, ta1;
public float orgX, orgY;
public CellArc(float x, float y, float size, float[] path_0_1) {
super(x, y, size);
this.orgX = path_0_1[0];
this.orgY = path_0_1[1];
this.ta0 = path_0_1[2];
this.ta1 = path_0_1[3];
this.pathLength = this.size * PI /4;
}
public float[] getPos(float t) {
t = constrain(t, 0.0f, 1.0f);
float ang = map(t, 0.0f, 1f, ta0, ta1);
float x = (orgX + 0.5f * cos(ang)) * size;
float y = (orgY + 0.5f * sin(ang)) * size;
return new float[] {x, y};
}
public void draw() {
push();
translate(x, y);
drawCellBorder();
ellipseMode(CENTER);
stroke(0);
arc(orgX * size, orgY * size, size, size,
min(ta0, ta1), max(ta0, ta1));
pop();
}
}