Me

Portfolio

Orbit Clock

Tags
Academic 13 Animations 8
Language(s)
Java 7 Processing 7
Date

The task was to create some kind of abstract time visualization.

My first thought was that the time of the day repeats itself over and over again, every day. So I choose a lemniscate as base shape to display the hours of the day.

Then every other unit of time would be shown as an orbit (thus the name orbit clock), centered at the position of the next higher unit.

/* @pjs pauseOnBlur="true"; */
/**
 * Currently running instance
 */
public static OrbitClock $;

private static final int STEPS      = 240;

private CurveFunction    curveFunction;

private float            centerX;

private float            centerY;

private TimeShape        minutesShape;

private TimeShape        secondsShape;

private TimeShape        millisShape;

public void setup() {
    if (typeof preSetup == 'function') { preSetup(); }
    size(displayWidth, displayHeight);
    smooth();
    noSmooth();

    $ = this;

    centerX = width / 2;
    centerY = height / 2;

    curveFunction = new LemniscateOfBernoulli(width * .30);

    strokeCap(SQUARE);

    minutesShape = new TimeShape.Minutes();
    minutesShape.radius = width * .05;
    minutesShape.steps = 60;
    minutesShape.width = 10;
    minutesShape.mode = OrbMode.ORBIT;

    secondsShape = new TimeShape.Seconds();
    secondsShape.radius = minutesShape.radius * .5;
    secondsShape.steps = 60;
    secondsShape.width = minutesShape.width * .2;
    secondsShape.mode = OrbMode.ORBIT;

    millisShape = new TimeShape.Millis();
    millisShape.radius = secondsShape.radius * .4;
    millisShape.steps = 100;
    millisShape.width = secondsShape.width * .5;
    millisShape.mode = OrbMode.ORBIT;
}

public void draw() {
    drawBackground();
    drawLemniscate();
    drawFPS();
}

private void drawBackground() {
            background(0, 0);
}

private void drawLemniscate() {
    float angle;
            angle = hour2angle(hour(), minute());

    Point startPoint = curveFunction.calculate(angle);
    float startX = centerX + startPoint.x;
    float startY = centerY + startPoint.y;
    float lastX = startX;
    float lastY = startY;
    float lastX1 = -1;
    float lastY1 = -1;
    float lastX2 = -1;
    float lastY2 = -1;
    for (int currentStep = 0; currentStep <= STEPS; currentStep++) {
        angle += TWO_PI / STEPS;
        Point point = curveFunction.calculate(angle);
        float x = centerX + point.x;
        float y = centerY + point.y;

        float angleBetween =
                TwoDimensional.angleBetween(lastX, lastY, x, y) + HALF_PI;
        noStroke();

        setFill(currentStep, STEPS);

        int lineWidth = 30;
        float distX = cos(angleBetween) * lineWidth;
        float distY = sin(angleBetween) * lineWidth;
        float x1 = x - distX;
        float y1 = y - distY;
        float x2 = x + distX;
        float y2 = y + distY;
        if (lastX1 > -1) {
            quad(lastX1, lastY1, x1, y1, x2, y2, lastX2, lastY2);
        }
        lastX1 = x1;
        lastY1 = y1;
        lastX2 = x2;
        lastY2 = y2;

        lastX = x;
        lastY = y;
    }
    drawMinutes(startX, startY);
}

private void drawFPS() {
    if (mousePressed) {
        fill(0);
        text(sprintf("%.1f", frameRate), width - 100, height - 100);
    }
}

void setStroke(float currentStep, float steps) {
            stroke(0, 255 * (1 - currentStep / steps));
}

void setFill(float currentStep, float steps) {
            fill(0, 255 * (1 - currentStep / steps));
}

private void drawMinutes(float x, float y) {
    minutesShape.draw(x, y);
    drawSeconds(x + cos(minutesShape.getStartAngle()) * minutesShape.radius, y
            + sin(minutesShape.getStartAngle()) * minutesShape.radius);
}

private void drawSeconds(float x, float y) {
    secondsShape.draw(x, y);
    drawMillis(x + cos(secondsShape.getStartAngle()) * secondsShape.radius, y
            + sin(secondsShape.getStartAngle()) * secondsShape.radius);
}

private void drawMillis(float x, float y) {
    millisShape.draw(x, y);
}

/**
 * 12:00 = PI
 * 24:00 = 0 = TWO_PI
 * 6:00 = -PI/2 = 1.5 * PI
 * 18:00 = PI/2
 * 
 * @param hours
 * @param minutes
 * @return
 */
private static float hour2angle(int hours, int minutes) {
    return map(hours + minutes / 60, 0, 24, TWO_PI, 0);
}

/**
 * Enum
 */
class OrbMode {

    public static final int
        ORB = 0,
        ORBIT = 1;
}
public class LemniscateOfBernoulli extends CurveFunction {

    public static final float SQRT_2 = (float) sqrt(2);

    public LemniscateOfBernoulli(float resizeFactor) {
        super(resizeFactor);
    }

    public Point calculate(float angle) {
        float sin1 = sin(angle);
        float divisor = sq(sin1) + 1;
        float dividend = resizeFactor * SQRT_2 * cos(angle);
        float x = dividend / divisor;
        float y = (dividend * sin1) / divisor;
        return new Point(x, y);
    }

}
/**
 * Base for all possible curve functions.
 * 
 * @author rza
 */
public abstract class CurveFunction {

    float resizeFactor;

    public CurveFunction(float resizeFactor) {
        this.resizeFactor = resizeFactor;
    }

    public abstract Point calculate(float angle);
}
public class Point {

    public float x;

    public float y;

    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    public String toString() {
        return sprintf("%.2f / %.2f", x, y);
    }

    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + Float.floatToIntBits(x);
        result = prime * result + Float.floatToIntBits(y);
        return result;
    }

    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Point other = (Point) obj;
        if (Float.floatToIntBits(x) != Float.floatToIntBits(other.x))
            return false;
        if (Float.floatToIntBits(y) != Float.floatToIntBits(other.y))
            return false;
        return true;
    }

    public Float dist(Point point) {
        return PApplet.dist(x, y, point.x, point.y);
    }

    public Point intermediate(Point point) {
        return new Point((x + point.x) / 2, (y + point.y) / 2);
    }

}
public class TwoDimensional {

    private TwoDimensional() {}

    public static float angleBetween(float x1, float y1, float x2, float y2) {
        double atan2 = Math.atan2(y1 - y2, x1 - x2);
        return (float) (atan2 < 0 ? Math.PI * 2 + atan2 : atan2);
    }

}
abstract class TimeShape {

    public static class Minutes extends TimeShape {

        protected float getStartAngle() {
            return map(minute() + second() / 60, 0, 60, PI * -.5f, PI * 1.5);
        }
    }

    public static class Seconds extends TimeShape {

        protected float getStartAngle() {
            return map(second(), 0, 60, PI * -.5f, PI * 1.5);
        }
    }

    public static class Millis extends TimeShape {

        protected float getStartAngle() {
            return map(Date.now() % 1000, 0, 1000, PI * -.5f, PI * 1.5);
        }
    }

    float radius;

    int   steps;

    float width;

    int   mode;

    public void draw(float x, float y) {
        float startAngle = getStartAngle();
        float angleSteps = TWO_PI / steps;
        switch (mode) {
            case OrbMode.ORB:
                $.setFill(steps, steps);
                $.noStroke();
                $.ellipse(x + cos(startAngle) * radius, y + sin(startAngle) * radius, 10 * width, 10 * width);
                break;
            case OrbMode.ORBIT:
                $.noFill();
                $.strokeWeight(width);
                for (int i = 0; i < steps; i++) {
                    float angle = startAngle - angleSteps * i;
                    $.setStroke(i, steps);
                    $.arc(x, y, radius * 2, radius * 2, angle, angle + angleSteps);
                }
        }
    }

    protected abstract float getStartAngle();
}
Left/Right Arrow Key Switch File
Portrait

Raoul Zander

UI Lead

hello

Raoul Zander

UI Lead

I'm a 36 year-old from Berlin and into coding since I was 14.

I enjoy creating both, interactive visuals and abstract software architecture.

It's my goal to become an amazing developer and for that I try new things all the time.

Curriculum Vitae

hello