I’ve just uploaded a customizable clock for 2D setups to the pattern site.
I’m putting the code here too, since it relates to several recent discussions and some of the components may be useful to folks making their own clocks.
/*
Draw a clock in 2D using RGB.
The second hand is drawn in red, minutes are green, and hours are blue.
Sub-seconds can be displayed as white.
There are several controls that can adjust the way the clock is drawn.
The clock face drawing is broken up into 2 mode groups, one controls
drawing the hands as they relate to the angle (rotational), and the
other controls the way they are drawn relative to the center (radially).
*/
//these variables are set by controls
export var sharpness = 14
export var sharpnessVariation = 0
export var sharpnessSpeed = 1
export var distance = 1
export var strength = 1.03
export var secondsBrightness = .1
//time is broken up into variables. These include factional components.
//e.g at 1:59 hours will be about 1.98 instead of 1.0.
export var secondFraction, seconds, minutes, hours, sharpnessP
//there are 4 clock hand drawing modes and 4 radius modes.
var rmodeFns = array(4) //array of functions that can be used
export var rmode = 1
var cmodeFns = array(4) //array of functions that can be used
export var cmode = 0
//a slider to set which radius mode ot use, from 0-3
export function sliderRadiusMode(v) {
rmode = floor(.5 + v*3)
}
//a slider to set which hand drawing mode to use, from 0-3
export function sliderHandMode(v) {
cmode = floor(.5 + v*3)
}
//clock hands can be more defined or smoother
export function sliderSharpness(v) {
sharpness = 1 + v*v*40
}
//sets the stength/intensity of the hands combined with sharpness
export function sliderStrength(v) {
strength = 1+v*v
}
//the shaprness level can breathe, varying definition.
//slide to the left to disable (set to 0)
export function sliderBreathe(v) {
sharpnessVariation = v*10
}
//The speed of animations, from once a second to once a minute
//this affects breathing speed and animated hand modes
export function sliderSpeed(v) {
sharpnessSpeed = 1 - v*0.9847412109375
}
//zoom in or out on the clock face
export function sliderDistance(v) {
distance = 1 + v*5
}
//sub-second hand intensity can be adjusted from completely off to bright white
export function sliderSecondsBrightness(v) {
secondsBrightness = v*v
}
var lastSeconds = clockSecond() //used to detect when a second threshold is crossed
export function beforeRender(delta) {
//calculate secondFraction, resetting when a second has elapsed
if (lastSeconds != clockSecond()) {
//the second just changed, so subseconds should be zero, however it probably transitioned somewhere between frames
secondFraction = delta/2000 //assume half a frame of jitter, could be better
lastSeconds = clockSecond()
} else {
secondFraction += delta/1000 //delta is in milliseconds
}
//calculate the time as fractional components that will smoothly move
//around a clockface
seconds = (lastSeconds + secondFraction)
//if you prefer your seconds to jump, use this instead
// seconds = clockSecond()
minutes = (clockMinute() + seconds/60)
hours = (clockHour() % 12 + minutes/60)
t1 = time(sharpnessSpeed)
sharpnessP = sharpness + ((wave(t1) - .5) * sharpnessVariation)
}
/*
Globals used in animations:
a = the pixel angle on the clock face, in which 0 and 1 meet at the top of the circle.
r = radius, the distance from center
*/
var a, r
/*
Globals used between radial modes and hand drawing modes.
These are set by the radial mode and used when drawing the hands.
rf = intensity for the given pixel for the sub-seconds hand
rs = intensity for the given pixel for the seconds hand
rm = intensity for the given pixel for the minutes hand
rh = intensity for the given pixel for the hours hand
*/
var rf, rs, rm, rh
/*
Several radial modes are defined.
0 = repeating equidistant gradient arcs are given to each hand.
1 = similar to above, but the arcs are closer together and blend more.
2 = solid arc bands (no radial blending). Combine with hand mode=1 for pixelation
3 = No radial component is used, all hands are drawn as rays to infinity
*/
//equidistant
rmodeFns[0] = () => {
rf = min(triangle(0.4 + .2 + r * distance) * 1.05, 1)
rs = min(triangle(0.4 + 0.333 + r * distance) * 1.05, 1)
rm = min(triangle(0.4 + 0.666 + r * distance) * 1.2, 1)
rh = min(triangle(0.4 + 0 + r * distance) * 1.2, 1)
}
//clusterred (intended for distance=1)
rmodeFns[1] = () => {
rf = min(triangle(r*1.8 * distance) * 1.05, 1)
rs = min(triangle(r*1.1 * distance) * 1.05, 1)
rm = min(triangle(r*1.4 * distance) * 1.2, 1)
rh = min(r < .6 && triangle(r*2.3 * distance) * 1.6, 1)
rf = abs(r - .1) < .15
rh = clamp(1.22-r*distance,0,1)
}
//bands
rmodeFns[2] = () => {
rf = abs(r - .1) < .15
rs = abs(r - .4) < .2
rm = abs(r - .3) < .15
rh = abs(r - .1) < .2
}
//rays to infinity
rmodeFns[3] = () => {
rf = 1
rs = 1
rm = 1
rh = 1
}
/*
Several hand drawing modes are defined.
0 = gradients centered around the clock hand.
1 = a threshold is applied to above making pixels on or off.
2 = gradients that pulse outward.
3 = beams of light shoot from the clock hand outward.
*/
/*
this helper function does most of the work for several hand drawing modes.
Given a 't' fraction from 0-1, and 'rf' a radial filter, draw a gradient with
strenth and sharpness settings applied.
*/
function angleGradient(t, rf) {
return pow((strength-triangle(a + t)) * rf, sharpnessP)
}
//gradient
cmodeFns[0] = () => {
white = min(1, angleGradient(secondFraction, rf)) * secondsBrightness
red = angleGradient(seconds/60, rs) + white
green = angleGradient(minutes/60, rm) + white
blue = angleGradient(hours/12, rh) + white
rgb(red,green,blue)
}
//threshold
cmodeFns[1] = () => {
white = (angleGradient(secondFraction, rf) > .5) * secondsBrightness
red = (angleGradient(seconds/60, rs) > .5) + white
green = (angleGradient(minutes/60, rm) > .5) + white
blue = (angleGradient(hours/12, rh) > .5) + white
rgb(red,green,blue)
}
//pusle animation
cmodeFns[2] = () => {
var tf = triangle(a + secondFraction)
var ts = triangle(a + seconds/60)
var tm = triangle(a + minutes/60)
var th = triangle(a + hours/12)
white = min(1,pow(((strength-tf)+triangle(tf - t1)*strength*.2) * rs, sharpnessP)) * secondsBrightness
red = pow(((strength-ts)+triangle(ts - t1)*strength*.2) * rs, sharpnessP) + white
green = pow(((strength-tm)+triangle(tm - t1)*strength*.2) * rm, sharpnessP) + white
blue = pow(((strength-th)+triangle(th - t1)*strength*.2) * rh, sharpnessP) + white
rgb(red,green,blue)
}
//beamshot animation
cmodeFns[3] = () => {
white = (angleGradient(secondFraction, rf) > .5) * secondsBrightness
var ts = triangle(a + seconds/60)
var tm = triangle(a + minutes/60)
var th = triangle(a + hours/12)
red = pow(((strength-ts)*triangle(triangle(ts - t1))*strength) * rs, sharpnessP)+ white
green = pow(((strength-tm)*triangle(triangle(tm - t1))*strength) * rm, sharpnessP)+ white
blue = pow(((strength-th)*triangle(triangle(th - t1))*strength) * rh, sharpnessP)+ white
rgb(red,green,blue)
}
var HALF_PI = PI/2
//NOTE: atan2 has a bug in V2.23, when fixed this can be replaced with atan2 directly
function arctan2(y, x) {
if (x > 0) return atan(y/x)
if (y > 0) return HALF_PI - atan(x/y)
if (y < 0) return -HALF_PI - atan(x/y)
if (x < 0) return PI + atan(y/x)
return 1.0
}
export function render2D(index, x,y) {
//center the coordinates and calculate angle (from top) and radius
x -= 0.5
y -= 0.5
a = (PI + arctan2(x, y))/PI2
r = sqrt(x*x + y*y)
rmodeFns[rmode]()
cmodeFns[cmode]()
}
Some example settings: