232 lines
4.9 KiB
JavaScript
232 lines
4.9 KiB
JavaScript
// short writeup: https://bl.ocks.org/monfera/85aa9627de1ae521d3ac5b26c9cd1c49
|
|
|
|
// http://colorbrewer2.org/#type=qualitative&scheme=Paired&n=12
|
|
// except yellow
|
|
const palette = [
|
|
[166,206,227],
|
|
[ 31,120,180],
|
|
[178,223,138],
|
|
[ 51,160, 44],
|
|
[251,154,153],
|
|
[227, 26, 28],
|
|
[253,191,111],
|
|
[255,127, 0],
|
|
[202,178,214],
|
|
[106, 61,154],
|
|
[177, 89, 40]
|
|
]
|
|
|
|
const regl = window.createREGL()
|
|
|
|
const lineCount = 11
|
|
const pointCount = 5
|
|
|
|
const lineWidth = 2
|
|
|
|
// padding is interpreted as percentage of the screen width or height
|
|
const xPadding = 0.1
|
|
const yPadding = 0.25
|
|
const xScale = x => 2 * (1 - 2 * xPadding) * (x - 0.5)
|
|
const yScale = y => 2 * (1 - 2 * yPadding) * (y - 0.5)
|
|
|
|
// nd :: typedArrayClass -> array -> ndarray
|
|
const nd = (Array, dimensions) => ndarray(
|
|
new Array(dimensions.reduce((p, n) => p * n, 1)),
|
|
dimensions
|
|
)
|
|
|
|
const colorLineAesthetic = ({lineWidth}) => ({
|
|
|
|
vert: `
|
|
precision mediump float;
|
|
|
|
attribute vec4 position;
|
|
attribute vec4 color;
|
|
uniform float tween;
|
|
|
|
varying vec4 c;
|
|
|
|
float smootherstep(float edge0, float edge1, float x) {
|
|
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
return x*x*x*(x*(x*6.0 - 15.0) + 10.0);
|
|
}
|
|
|
|
void main() {
|
|
float tweenedX = mix(position[0], position[1], smootherstep(0.0, 1.0, tween));
|
|
float tweenedY = mix(position[2], position[3], smootherstep(0.0, 1.0, tween));
|
|
gl_Position = vec4(tweenedX, tweenedY, 0, 1);
|
|
c = color;
|
|
gl_PointSize = 40.0;
|
|
}`,
|
|
|
|
frag: `
|
|
precision mediump float;
|
|
|
|
uniform mat3 S1;
|
|
uniform mat3 S2;
|
|
uniform float tween;
|
|
|
|
varying vec4 c;
|
|
|
|
vec2 pos = vec2(0.5, 0.5);
|
|
|
|
float superFormula(mat3 S, float fi) {
|
|
|
|
float a = S[0][0];
|
|
float b = S[0][1];
|
|
float m1 = S[0][2];
|
|
float m2 = S[1][0];
|
|
float n1 = S[1][1];
|
|
float n2 = S[1][2];
|
|
float n3 = S[2][0];
|
|
|
|
float r = pow( pow(abs(cos(m1 * fi / 4.0) / a), n2)
|
|
+ pow(abs(sin(m2 * fi / 4.0) / b), n3),
|
|
-1.0 / n1
|
|
);
|
|
|
|
return r;
|
|
}
|
|
|
|
void main() {
|
|
|
|
float dist = distance(gl_PointCoord.xy, pos);
|
|
vec2 dist2 = gl_PointCoord - pos;
|
|
float fi = atan(dist2.y, dist2.x);
|
|
|
|
float r1 = superFormula(S1, fi);
|
|
float r2 = superFormula(S2, fi);
|
|
float r = mix(r1, r2, smoothstep(0.0, 1.0, tween));
|
|
|
|
if(dist > r / 2.5) discard;
|
|
gl_FragColor = c;
|
|
}`,
|
|
|
|
primitive: 'points',
|
|
|
|
lineWidth
|
|
|
|
})
|
|
|
|
/* For performance reasons, the model format is identical with the format
|
|
* that regl expects attribute arrays to be in.
|
|
*/
|
|
const model = (({lineCount, pointCount, xScale, yScale}) => {
|
|
|
|
const positionLength = 4
|
|
const colorLength = 4
|
|
|
|
const position = nd(Float32Array, [lineCount, pointCount, positionLength])
|
|
const color = nd(Float32Array, [lineCount, pointCount, colorLength])
|
|
let i, j, x1, x2, y1, y2
|
|
|
|
for(i = 0; i < lineCount; i++) {
|
|
|
|
for(j = 0; j < pointCount; j++) {
|
|
x1 = Math.random()
|
|
x2 = Math.random()
|
|
y1 = Math.random()
|
|
y2 = Math.random()
|
|
position.set(i, j, 0, xScale(x1))
|
|
position.set(i, j, 1, xScale(x2))
|
|
position.set(i, j, 2, yScale(y1))
|
|
position.set(i, j, 3, yScale(y2))
|
|
color.set(i, j, 0, palette[i][0] / 255)
|
|
color.set(i, j, 1, palette[i][1] / 255)
|
|
color.set(i, j, 2, palette[i][2] / 255)
|
|
color.set(i, j, 3, 1)
|
|
}
|
|
}
|
|
|
|
return {
|
|
lineCount,
|
|
pointCount,
|
|
attributes: {
|
|
position: position.data,
|
|
color: color.data
|
|
},
|
|
staticUniforms: {
|
|
|
|
// cross
|
|
S1 : [
|
|
/* a= */ 1,
|
|
/* b= */ 0.6875,
|
|
/* m1= */ 8,
|
|
/* m2= */ 8,
|
|
/* n1= */ 1.3,
|
|
/* n2= */ 0.01,
|
|
/* n3= */ 3.313,
|
|
/* */ 0,
|
|
/* */ 0,
|
|
],
|
|
|
|
// hexagon
|
|
S2 : [
|
|
/* a= */ 1,
|
|
/* b= */ 1,
|
|
/* m1= */ 6,
|
|
/* m2= */ 6,
|
|
/* n1= */ 100,
|
|
/* n2= */ 40,
|
|
/* n3= */ 40,
|
|
/* */ 0,
|
|
/* */ 0,
|
|
]
|
|
}
|
|
}
|
|
|
|
})({lineCount, pointCount, xScale, yScale})
|
|
|
|
const lineViewModelMaker = model => {
|
|
|
|
const elements = []
|
|
let i, j
|
|
let index = 0
|
|
|
|
const {lineCount, pointCount, attributes, staticUniforms} = model
|
|
|
|
const uniforms = Object.assign(
|
|
{},
|
|
staticUniforms,
|
|
{
|
|
tween: ({time}) => Math.cos(time / 2) + 1 / 2
|
|
})
|
|
|
|
for(i = 0; i < lineCount; i++) {
|
|
for(j = 0; j < pointCount; j++) {
|
|
elements.push(index)
|
|
index++
|
|
}
|
|
}
|
|
|
|
return {attributes, uniforms, elements}
|
|
}
|
|
|
|
const linesDrawerMaker = model => {
|
|
|
|
const aesthetic = colorLineAesthetic
|
|
|
|
const layer = Object.assign(
|
|
{},
|
|
lineViewModelMaker(model),
|
|
aesthetic({lineWidth: lineWidth})
|
|
)
|
|
|
|
return regl(layer)
|
|
}
|
|
|
|
const linesDrawer = linesDrawerMaker(model)
|
|
|
|
const render = () => {
|
|
|
|
regl.frame(() => {
|
|
|
|
regl.clear({
|
|
color: [1, 1, 1, 1]
|
|
})
|
|
|
|
linesDrawer()
|
|
})
|
|
}
|
|
|
|
render() |