Last active
May 18, 2017 03:38
-
-
Save rflow/39692bd181fb1eb0b077a4caf886b077 to your computer and use it in GitHub Desktop.
ReGL circle animation example
This file contains hidden or 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
<html> | |
<body> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/regl/1.3.0/regl.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.3/chroma.min.js"></script> | |
<script> | |
const regl = createREGL(); | |
const doc = document.body; | |
const docWidth = window.innerWidth; | |
const docHeight = window.innerHeight; | |
const aspectRatio = docHeight / docWidth; | |
const colCount = 150; | |
const colWidth = docWidth / colCount; | |
const rowCount = parseInt(colCount * aspectRatio); | |
const rowHeight = docHeight / rowCount; | |
const circleCount = rowCount * colCount; | |
const maxRadius = Math.min(colWidth, rowHeight) / 2; | |
const minScale = 1e-6; // use small number to avoid reaching zero | |
const maxScale = 1.0; | |
const randomScale = () => minScale + ((maxScale - minScale) * Math.random()); | |
const palette = chroma.brewer.YlGnBu; | |
const paletteGL = palette.map(c => chroma(c).gl()); // gl mode uses [r,g,b] vector w normalised values | |
const randomColor = () => paletteGL[Math.floor(palette.length * Math.random())]; | |
const getGridPoints = (cols, rows, colWidth, rowHeight, gridWidth, gridHeight) => { | |
let count = cols * rows; | |
let points = new Float32Array(count * 2); | |
let col, row; | |
let xPos, yPos, xIndex, yIndex; | |
for (let i = 0; i < count; i++) { | |
col = i % cols; | |
row = parseInt(i / cols); | |
xPos = (col * colWidth) + (colWidth / 2); | |
yPos = (row * rowHeight) + (rowHeight / 2); | |
xIndex = (2 * i); | |
yIndex = xIndex + 1; | |
points[xIndex] = (2 * xPos / gridWidth) - 1; // convert to (-1, 1) GL coord space | |
points[yIndex] = (2 * yPos / gridHeight) - 1; // convert to (-1, 1) GL coord space | |
} | |
return points; | |
} | |
const getAnimStates = (count) => { | |
const colors = new Float32Array(count * 3); | |
const scales = new Float32Array(count); | |
let r, g, b; | |
let rIndex, gIndex, bIndex; | |
for (let i = 0; i < count; i++) { | |
[r, g, b] = randomColor(); | |
rIndex = (3 * i); | |
gIndex = rIndex + 1; | |
bIndex = rIndex + 2; | |
colors[rIndex] = r; | |
colors[gIndex] = g; | |
colors[bIndex] = b; | |
scales[i] = randomScale(); | |
}; | |
return { colors, scales }; | |
} | |
// build inputs for our animation: | |
const centroids = getGridPoints(colCount, rowCount, colWidth, rowHeight, docWidth, docHeight); | |
// create a series of animation states for each circle | |
const numStates = 50; | |
const allStates = []; | |
while (allStates.length < numStates) { | |
allStates.push(getAnimStates(circleCount)); | |
} | |
// regl command featuring shaders that will draw supplied points | |
const drawPoints = regl({ | |
vert:` | |
precision highp float; | |
uniform float progress; // interpolation progress (from 0.0 to 1.0) | |
uniform float maxRadius; // max radius to draw (n.b. point is square of 2*r x 2*r) | |
attribute vec2 point; // position at which to draw | |
attribute float scaleA; // scale factor A | |
attribute float scaleB; // scale factor B | |
attribute vec3 colorA; // color A | |
attribute vec3 colorB; // color B | |
varying vec3 rgb; // interpolated color | |
varying float scale; // interpolated scale | |
void main () { | |
rgb = mix(colorA, colorB, progress); | |
scale = mix(scaleA, scaleB, progress); | |
gl_PointSize = maxRadius * 2. * scale; | |
gl_Position = vec4(point, 0, 1); | |
} | |
`, | |
frag:` | |
precision highp float; | |
varying vec3 rgb; | |
varying float scale; | |
void main () { | |
// determine normalized distance from center of point | |
float point_dist = length(gl_PointCoord * 2. - 1.); | |
// calc scale at which to start fading out the circle | |
float min_dist = scale * 0.70; | |
// calc scale at which we find the edge of the circle | |
float max_dist = scale; | |
// https://thebookofshaders.com/glossary/?search=smoothstep | |
float alpha = 1. - smoothstep(min_dist, max_dist, point_dist); | |
gl_FragColor = vec4(rgb, alpha); | |
} | |
`, | |
// using textbook example from http://regl.party/api#blending | |
blend: { | |
enable: true, | |
func: { | |
srcRGB: 'src alpha', | |
srcAlpha: 1, | |
dstRGB: 'one minus src alpha', | |
dstAlpha: 1 | |
}, | |
equation: { | |
rgb: 'add', | |
alpha: 'add' | |
}, | |
color: [0, 0, 0, 0] | |
}, | |
attributes: { | |
point: centroids, | |
colorA: regl.prop('currColors'), | |
colorB: regl.prop('nextColors'), | |
scaleA: regl.prop('currScales'), | |
scaleB: regl.prop('nextScales'), | |
}, | |
uniforms: { | |
maxRadius: maxRadius, | |
progress: regl.prop('progress') | |
}, | |
count: circleCount, | |
primitive: 'points' | |
}); | |
// render loop | |
const fps = 60; | |
const tweenTime = 2; | |
const tweenFrames = fps * tweenTime; | |
let state = 0; | |
regl.frame(({ tick }) => { | |
regl.clear({ | |
color: [0, 0, 0, 1], | |
depth: 1 | |
}) | |
// increment frame counter until we reach the desired loop point | |
let frame = tick % tweenFrames; | |
// increment state counter once we've looped back around | |
if (frame === 0) { | |
state = ++state % numStates; | |
}; | |
// track progress as proportion of frames completed | |
let progress = frame / tweenFrames; | |
// determine current and next state | |
let currState = allStates[state]; | |
let nextState = allStates[(state + 1) % numStates]; | |
drawPoints({ | |
currColors: currState.colors, | |
currScales: currState.scales, | |
nextColors: nextState.colors, | |
nextScales: nextState.scales, | |
progress: progress | |
}); | |
}) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment