Last active
April 3, 2018 21:05
-
-
Save riptl/22caf6aed22cc2816930706c70f553e1 to your computer and use it in GitHub Desktop.
Grid Engine for Processing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Grid grid; | |
void setup() { | |
size(400, 400, FX2D); | |
grid = new Grid(); | |
} | |
float radius = 0f; | |
float phase = 0f; | |
void draw() { | |
background(0); | |
grid.draw(); | |
if (frameCount % 2 == 0) { | |
radius += 0.5f; | |
phase += PI / radius; | |
int x = (int)(radius * cos(phase)); | |
int y = (int)(radius * sin(phase)); | |
grid.set(x, y, new DefaultPart()); | |
} | |
} | |
void mouseWheel(MouseEvent event) { | |
float e = event.getCount(); | |
grid.zoom(e); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Stack; | |
// v1 | |
boolean gboxDebug; | |
class Grid { | |
// x,y: middle (1 = part), z: distance | |
final PVector pos; | |
final int screenFit; | |
float partScale; | |
private HashMap<ChunkPos, Chunk> chunks; | |
float zoomSpeed = 0.025f; | |
float minZoom = 0.25f; | |
Grid() { | |
pos = new PVector(-50, -50, 100); | |
chunks = new HashMap<ChunkPos, Chunk>(); | |
screenFit = min(width, height); | |
} | |
void draw() { | |
if (gboxDebug) | |
background(0xFF, 0, 0); | |
// z-Distance of 1: length of chunk = screenFit | |
// scale times 1 | |
partScale = (1 / pos.z) * screenFit; | |
// Move around | |
if (mousePressed) { | |
PVector move = PVectors.alloc(); | |
move.set(pmouseX - mouseX, pmouseY - mouseY); | |
// Convert screen space to part space | |
move.div(partScale); | |
pos.x += move.x; | |
pos.y += move.y; | |
PVectors.free(move); | |
} | |
// Remove one chunk per frame | |
ChunkPos toClear = null; | |
// TODO: No offscreen chunk drawing!! | |
for (Chunk c : chunks.values()) { | |
c.draw(); | |
if (c.emptyLastFrame) toClear = c.pos; | |
} | |
if (toClear != null) | |
chunks.remove(toClear); | |
if (gboxDebug) { | |
noStroke(); fill(0, 0, 0, 128); | |
rect(0, 0, 200, 65); | |
fill(0xFF); | |
text(String.format("Center: Part [%.2f, %.2f]", pos.x, pos.y), 20, 20); | |
text(String.format("On-screen part size: %.2f", partScale), 20, 40); | |
} | |
} | |
// Zoom after cursor | |
void zoom(float steps) { | |
// TODO Use multiple calculations if steps is too big | |
float dist = sqrt(pos.z) - zoomSpeed * steps; | |
// new Z pos | |
float nPosZ = dist * dist; | |
nPosZ = max(minZoom, nPosZ); | |
float dZ = pos.z - nPosZ; | |
// New upper left corner | |
pos.x += mouseX * dZ / width; | |
pos.y += mouseY * dZ / height; | |
pos.z = nPosZ; | |
partScale = ((1 / pos.z) * screenFit); | |
} | |
ChunkPos chunkOfPart(int x, int y) { | |
ChunkPos p = ChunkPoses.alloc(); | |
p.x = floor((float) x / Chunk.CHUNK_SIZE); | |
p.y = floor((float) y / Chunk.CHUNK_SIZE); | |
return p; | |
} | |
void set(int x, int y, Part p) { | |
ChunkPos cp = chunkOfPart(x, y); | |
Chunk c = chunks.get(cp); | |
// Create chunk if necessary | |
if (c == null) chunks.put(cp, c = new Chunk(this, cp)); | |
c.set(x, y, p); | |
} | |
Part get(int x, int y) { | |
ChunkPos cp = chunkOfPart(x, y); | |
Chunk c = chunks.get(cp); | |
if (c == null) return null; | |
return c.get(x, y); | |
} | |
} | |
class Chunk { | |
// 2^n for best performance | |
static final int CHUNK_SIZE = 64; | |
final ChunkPos pos; | |
final Grid grid; | |
private Part[] parts; | |
boolean emptyLastFrame; | |
Chunk(Grid grid, ChunkPos pos) { | |
this.grid = grid; | |
this.pos = pos; | |
this.parts = new Part[CHUNK_SIZE * CHUNK_SIZE]; | |
} | |
void draw() { | |
PVector scrPos = getScreenPos(); | |
// Clear area | |
if (gboxDebug) { | |
fill(0); | |
noStroke(); | |
rect(scrPos.x, scrPos.y, scrPos.z, scrPos.z); | |
} | |
// Keep track of draw positions float xp = scrPos.x; float yp = scrPos.y; | |
// ^ probably less efficient than multiplication if not enough elements | |
boolean notEmpty = false; | |
// Iterate all parts, keeping track of index i | |
for (int y = 0, i = 0; y < CHUNK_SIZE; y++) { | |
for (int x = 0; x < CHUNK_SIZE; x++, i++) { | |
Part p = parts[i]; | |
if (p == null) continue; | |
float xp = scrPos.x + x * grid.partScale; | |
float yp = scrPos.y + y * grid.partScale; | |
p.draw(xp, yp, grid.partScale); | |
notEmpty = true; | |
} | |
} | |
emptyLastFrame = !notEmpty; | |
if (gboxDebug) { | |
fill(0, 0xFF, 0, 23); | |
stroke(0, 0, 0, 23); | |
rect(scrPos.x, scrPos.y, scrPos.z, scrPos.z); | |
} | |
PVectors.free(scrPos); | |
} | |
// x, y: top left; z: length of side | |
PVector getScreenPos() { | |
PVector v = PVectors.alloc(); | |
// v = distance to middle | |
v.set(pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE, 0); | |
v.sub(grid.pos.x, grid.pos.y, 0); | |
// scale part distance to real distance | |
v.mult(grid.partScale); | |
// set Z to length of side | |
v.z = CHUNK_SIZE * grid.partScale; | |
return v; | |
} | |
int absModulo(int base, int modulo) { | |
if (base >= 0) | |
return base % modulo; | |
else | |
return modulo - ((-base-1) % modulo) - 1; | |
} | |
void set(int x, int y, Part p) { | |
int rx = absModulo(x, CHUNK_SIZE); | |
int ry = absModulo(y, CHUNK_SIZE); | |
try { | |
parts[ry * CHUNK_SIZE + rx] = p; | |
} catch(Exception e) { | |
println(x); | |
println(y); | |
println(rx); | |
println(ry); | |
throw e; | |
} | |
} | |
Part get(int x, int y) { | |
x = absModulo(x, CHUNK_SIZE); | |
y = absModulo(y, CHUNK_SIZE); | |
return parts[y * CHUNK_SIZE + x]; | |
} | |
void clear() { | |
java.util.Arrays.fill(parts, null); | |
} | |
} | |
static class ChunkPos { | |
int x, y; | |
ChunkPos() { x = 0; y = 0; } | |
ChunkPos(int x, int y) { this.x = x; this.y = y; } | |
@Override | |
public int hashCode() { | |
return java.util.Objects.hash(x, y); | |
} | |
@Override | |
public boolean equals(Object other) { | |
if (other == this) { return true; } | |
if (!(other instanceof ChunkPos)) { return false; } | |
ChunkPos rhs = ((ChunkPos) other); | |
return x == rhs.x && y == rhs.y; | |
} | |
} | |
// Cache PVectors for performance :P | |
static class SimpleFactory<T> { | |
// Wish I had lambdas | |
static interface Allocator<T> { | |
public T allocate(); | |
} | |
public SimpleFactory(Allocator<T> allocator) { this.allocator = allocator; } | |
private Allocator<T> allocator; | |
private Stack<T> cache = new Stack<T>(); | |
T alloc() { | |
if (cache.empty()) | |
return allocator.allocate(); | |
else | |
return cache.pop(); | |
} | |
void free(T t) { | |
cache.push(t); | |
} | |
} | |
// Make it look like a static class | |
static SimpleFactory<PVector> PVectors = new SimpleFactory(new SimpleFactory.Allocator() { | |
@Override public PVector allocate() { return new PVector(); } | |
}); | |
static SimpleFactory<ChunkPos> ChunkPoses = new SimpleFactory(new SimpleFactory.Allocator() { | |
@Override public ChunkPos allocate() { return new ChunkPos(); } | |
}); | |
abstract class Part { | |
abstract void draw(float x, float y, float s); | |
// Returns true if part is offscreen | |
boolean cull(float x, float y, float s) { | |
return x > width || y > height || x < -s || y < -s; | |
} | |
} | |
class DefaultPart extends Part { | |
void draw(float x, float y, float s) { | |
if (cull(x, y, s)) return; | |
strokeWeight(2); stroke(0xCC); fill(0xFF); | |
rect(x, y, s, s); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment