Clock : Distortion
My clock concept underwent some serious changes as it progressed. At first, my plan was to have a city which built itself and then destroyed itself every 24 hours, as shown in the schematics below:
Using toxiclibs, I managed to create a class which would create a flat plane in 3D space. As time progressed, boxes would emerge from the plane and grow upwards to a predetermined stop height. The problem was that if the plane was slanted in any direction, the boxes would still go straight up. I spent roughly 12 hours trying to fix this, but without any specific plan or debugging methodology, I only managed to waste loads of time and create more errors with the box generation. I ditched the idea of boxes for lines, which were much less time consuming to deal with. In the end, I managed to create a class which would accept any mesh, and generate lines which grow out of the given mesh in a specific direction. Thus, when a mesh that looks like this:
is fed into the class, this is the result:
captured in motion:
I inserted a shape into a 3D environment:
Originally, I planned to make the lines simply grow in different directions based on the time of day, but that didn’t seem to involve the surrounding terrain at all. Instead, I raised the shape high above the land, and altered the topography:
The beam of light moves to predetermined points (the intersections of the crevices between hills) around the terrain once per minute. A sped up section of random movement is shown here:
While at these points, the light will distort the surface of the terrain in one of five ways:
![terraform-1](./../../../../wp-content/uploads/2014/10/terraform-11.gif)
![terraform-2](./../../../../wp-content/uploads/2014/10/terraform-21.gif)
![terraform-3](./../../../../wp-content/uploads/2014/10/terraform-31.gif)
![terraform-4](./../../../../wp-content/uploads/2014/10/terraform-41.gif)
![terraform-5](./../../../../wp-content/uploads/2014/10/terraform-51.gif)
Every hour, the mesh is reset. One can look at the mesh and, judging its degree of distortion, tell how far into the current hour one is. Implementing a way to tell which hour is the next step, but currently this is only a one hour clock.
Here is a timelapse of the clock in action:
Code:
import toxi.color.*;
import toxi.color.theory.*;
import toxi.physics2d.constraints.*;
import toxi.physics.*;
import toxi.physics.constraints.*;
import toxi.physics.behaviors.*;
import toxi.math.conversion.*;
import toxi.geom.*;
import toxi.math.*;
import toxi.util.datatypes.*;
import toxi.util.events.*;
import toxi.geom.mesh.subdiv.*;
import toxi.geom.mesh.*;
import toxi.math.waves.*;
import toxi.util.*;
import toxi.math.noise.*;
import java.util.*;
import toxi.processing.*;
import java.util.*;
import processing.opengl.*;
int timeChange;
Viewer v;
Mesh3D shape;
ToxiclibsSupport gfx;
SuperStructure struc;
VerletPhysics world;
Land land;
Agent3D zapPos;
int imginc, curMin, curHour;
void setup(){
size(1700,1000,OPENGL);
v = new Viewer(-1280,-776,-1109, new Vec3D());
struc = new SuperStructure(0,-1000,0,(TriangleMesh)new AABB(20).toMesh(),new Vec3D(0,1,0),100,83,2000,1);
gfx = new ToxiclibsSupport(this);
world = new VerletPhysics();
land = new Land(0,50,0,new Vec3D());
imginc = 0;
timeChange = 0;
zapPos = new Agent3D(0,0,0,new Vec3D());
zapPos.maxSpeed = 15.0;
zapPos.maxForce = 5.0;
curMin = minute();
curHour = hour();
zapPos.setSeekPt(land.marks.get(int(random(land.marks.size()))));
struc.zapPoint(zapPos);
}
void draw(){
noCursor();
background(30,40,90);
if(curMin != minute()){
zapPos.setSeekPt(land.marks.get(int(random(land.marks.size()))));
struc.zapPoint(zapPos);
curMin = minute();
land.changeRule(int(random(5)));
imginc++;
}
if(curHour != hour()){
land.reconfiguring = true;
curHour = hour();
}
world.update();
zapPos.run(gfx);
Vec3D lightPos = struc.interpolateTo(zapPos,0.8);
pointLight(220,180,200,lightPos.x,lightPos.y,lightPos.z);
noStroke();
struc.run(gfx);
land.run(gfx);
v.run();
if(key == 'c'){
println(v.campos, v.direction);
}
}
class Agent3D extends VerletParticle{
Vec3D targ, direction;
TriangleMesh shape;
float maxSpeed, maxForce;
Agent3D(float x, float y, float z){
super(x,y,z);
direction = null;
shape = new TriangleMesh();
targ = null;
maxSpeed = 5.0;
maxForce = 1.2;
}
Agent3D(float x, float y, float z, Vec3D rot){
this(x,y,z);
direction = rot;
}
Agent3D(float x, float y, float z, Vec3D rot, TriangleMesh shape){
this(x,y,z,rot);
this.shape = shape;
}
void setSeekPt(Vec3D target){
targ = target;
}
void setDirectionToFace(Vec3D pt){
direction.set(this.sub(pt).normalize());
}
void seek(){
if(targ != null){
Vec3D c = this.sub(targ);
float m = map(c.magnitude(),0,100,0,maxSpeed);
c = c.normalizeTo(m);
Vec3D steer = this.getVelocity().sub(c);
steer = steer.limit(maxForce);
this.addVelocity(steer);
}
}
void display(ToxiclibsSupport tls){
tls.translate(this);
shape = shape.pointTowards(direction);
tls.mesh((Mesh3D)shape, false);
tls.translate(new Vec3D().sub(this));
}
void run(ToxiclibsSupport tls){
seek();
update();
display(tls);
clearVelocity();
}
}
class SuperStructure extends Agent3D{
int timeOfBirth, age, maxAge, maxBlocks;
Structure[] structs;
SuperStructure(float x, float y, float z, Mesh3D m, Vec3D dir, int lifespan, int blockNum, int blockH, int blockW){
super(x,y,x,dir);
maxBlocks = blockNum;
maxAge = lifespan;
shape = (TriangleMesh)new STLReader().loadBinary(sketchPath("god.stl"),STLReader.TRIANGLEMESH);
structs = new Structure[m.getFaces().size()];
for(int i = 0; i < structs.length; i++){
Face f = (Face)m.getFaces().toArray()[i];
structs[i] = new Structure(this.x,this.y,this.z,this.direction, blockH, maxAge, blockW, blockH, maxBlocks, f);
}
}
void zapPoint(Vec3D pt){
setDirectionToFace(pt);
for(Structure s: structs){
s.setDirectionToFace(pt);
}
}
void run(ToxiclibsSupport tls){
zapPoint(zapPos);
for(Structure s: structs){
if(s != null){
s.grow();
}
}
fill(255);
this.display(tls);
}
}
class Structure extends Agent3D{
float baseRadius, maxBlockWidth, maxBlockHeight, blockWidth, blockHeight;
int timeOfBirth, age, maxAge, maxBlockNum, blockSlot, lifeStage;
Block[] blocks;
Triangle3D base;
Structure(float x, float y, float z, Vec3D dir, float top, int lifespan, float wid, float hei, int blockNum, Face baseMake){
super(x,y,z,dir);
timeOfBirth = frameCount;
maxBlockWidth = wid;
blockSlot = 0;
maxBlockHeight = hei;
maxBlockNum = blockNum;
maxAge = lifespan;
blocks = new Block[maxBlockNum];
base = new Triangle3D((Vec3D)baseMake.a, (Vec3D)baseMake.b, (Vec3D)baseMake.c);
for(int i = 0; i < maxBlockNum; i++){
Vec3D newPoint = base.a.interpolateTo(base.b.interpolateTo(base.c,random(1)),random(1));
blocks[i] = new Block(newPoint.x,newPoint.y,newPoint.z,direction,maxBlockWidth,maxBlockHeight,(int)random(maxAge));
}
}
void setDirectionToFace(Vec3D pt){
direction = pt.sub(this).normalize();
}
Triangle3D buildBase(){
Vec3D[] corners = new Vec3D[3];
for(int i = 0; i < 3; i++){
//creates corners by making flat random vector and then rotating to match structures rotation
Vec3D newCorner = new Vec3D(random(-1,1)*baseRadius,0,random(-1,1)*baseRadius).rotateX(direction.x).rotateY(direction.y);
corners[i] = newCorner;
}
return new Triangle3D(corners[0],corners[1],corners[2]);
}
void grow(){
gfx.translate(this);
for(int i = 0; i < maxBlockNum; i++){
Block b = blocks[i];
b.grow();
b.display(gfx);
if(frameCount-b.birth > b.maxAge){
Vec3D newPoint = base.a.interpolateTo(base.b.interpolateTo(base.c,random(1)),random(1));
blocks[i] = new Block(newPoint.x,newPoint.y,newPoint.z,direction,maxBlockWidth,maxBlockHeight,maxAge);
}
}
gfx.translate(new Vec3D().sub(this));
}
}
class Block extends Agent3D{
float wdth, hght;
int tone, birth, maxAge;
TColor col;
Vec3D up;
Vec3D box;
Block(float x,float y,float z,Vec3D rot, float wt, float ht,int maxAge){
super(x,y,z,rot);
birth = frameCount;
this.maxAge = maxAge;
wdth = wt;
hght = ht;
box = blockFace();
tone = int(noise(frameCount*0.03)*30);
}
Vec3D blockFace(){
return this.add(direction.normalizeTo((frameCount+1-birth)*1.0/maxAge*hght));
}
void grow(){
box = blockFace();
}
void display(ToxiclibsSupport tls){
strokeWeight(wdth);
stroke(180,180,200,tone);
tls.line(this,box);
}
}
class Viewer{
Vec3D direction, campos;
Viewer(float x, float y, float z, Vec3D dir) {
direction = dir;
campos = new Vec3D(x,y,z);
}
void run() {
direction.set(sin(map(mouseX,0,width,2*PI,0)),map(mouseY,0,height,-1.5,1.5),cos(map(mouseX,0,width,2*PI,0)));
direction.addSelf(campos);
// Change height of the camera with mouseY
camera(campos.x, campos.y, campos.z, // eyeX, eyeY, eyeZ
direction.x, direction.y, direction.z, // centerX, centerY, centerZ
0.0, 1.0, 0.0); // upX, upY, upZ
if(keyPressed){
Vec3D dir = direction.copy();
dir.subSelf(campos);
switch(key){
case('a'):
dir.set(sin(map(mouseX,0,width,2.5*PI,PI/2)),0,cos(map(mouseX,0,width,2.5*PI,PI/2)));
campos.addSelf(dir.normalizeTo(3));
break;
case('w'):
campos.addSelf(dir.normalizeTo(3));
break;
case('d'):
dir.set(sin(map(mouseX,0,width,2.5*PI,PI/2)),0,cos(map(mouseX,0,width,2.5*PI,PI/2)));
campos.subSelf(dir.normalizeTo(3));
break;
case('s'):
campos.subSelf(dir.normalizeTo(2));
break;
}
}
}
}
class Land extends Agent3D{
TriangleMesh originalShape;
Terrain terrain;
ArrayList<Agent3D> marks;
int ruleNum;
boolean reconfiguring;
float reconfigRatio;
float xCenter = 5.2;
float zCenter = 5.2;
Land(float xx,float yy,float zz, Vec3D dir){
super(xx,yy,zz,dir);
marks = new ArrayList();
int DIMS = 40;
reconfiguring = false;
reconfigRatio = 0.0;
terrain = new Terrain(DIMS,DIMS, 50);
float[] el = new float[DIMS*DIMS];
noiseSeed((long)random(500));
ruleNum = int(random(5));
for (int z = 0, i = 0; z < DIMS; z++) {
for (int x = 0; x < DIMS; x++) {
el[i++] = noise(x * 0.12, z * 0.12) * 400/PApplet.max((PApplet.abs(xCenter - (x%10))*PApplet.abs(zCenter-(z%10))),4);
if((x+5)%10 == 0 && (z+5)%10 == 0){
IsectData3D sec = new IsectData3D();
sec = terrain.intersectAtPoint(x,z);
marks.add(new Agent3D(sec.pos.x()*50-1000,150,sec.pos.z()*50-1000,new Vec3D()));
}
}
}
terrain.setElevation(el);
// create mesh
shape = (TriangleMesh)terrain.toMesh();
originalShape = shape.copy();
}
void display(ToxiclibsSupport tls){
noStroke();
super.display(tls);
stroke(255,0,0);
}
void run(ToxiclibsSupport tls){
if(reconfiguring) {
reconfigure();
}
super.run(tls);
for(Agent3D mark: marks){
mark.run(tls);
}
alterTerrain(zapPos);
}
ArrayList<Vec3D> getVertexPoints(TriangleMesh s){
ArrayList<Vec3D> start = new ArrayList();
Iterator<Vertex> getter = shape.getVertices().iterator();
while(getter.hasNext()){
start.add((Vec3D)getter.next());
}
return start;
}
ArrayList<Vec3D> getCloseVertices(Vec3D pt){
ArrayList<Vec3D> result = new ArrayList();
Vec3D ptFlat = new Vec3D(pt.x,0,pt.z);
ListIterator<Vec3D> remover = getVertexPoints(shape).listIterator();
while(remover.hasNext()){
Vec3D f = (Vec3D)remover.next();
Vec3D flat = new Vec3D(f.x,0,f.z);
if(flat.distanceToSquared(ptFlat) < 10000){
result.add(f);
}
}
return result;
}
Vec3D average(ArrayList<Vec3D> vecs){
Vec3D result = new Vec3D();
for(Vec3D v: vecs){
result = result.add(v);
}
return result.scale(1.0/vecs.size());
}
void reconfigure(){
shape = originalShape.copy();
reconfiguring = false;
}
void changeRule(int r){
ruleNum = r;
}
void alterTerrain(Agent3D cursor){
for(Agent3D mark: marks){
if(cursor.distanceToSquared(mark) < 4){
ArrayList<Vec3D> nearPts = getCloseVertices(cursor);
for(Vec3D pt: nearPts){
Vec3D rule = new Vec3D();
switch(ruleNum){
case(0):
rule = pt.sub(average(nearPts));
break;
case(1):
rule = average(nearPts).sub(pt);
break;
case(2):
rule = new Vec3D(cos(second()*PI/30),sin(second()*PI/15),sin(second()*PI/30));
break;
case(3):
rule = new Vec3D(pt.sub(struc));
break;
case(4):
rule = new Vec3D(struc.sub(pt));
break;
}
shape.updateVertex(pt, pt.add(rule.normalizeTo(2)));
}
mark.setSeekPt(average(getCloseVertices(cursor)));
}
}
}
}