|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://rawcdn.githack.com/monfera/ndarray-bundle/cbbafcb5/ndarray.min.js"></script> |
|
<script src="https://rawcdn.githack.com/regl-project/regl/v1.3.13/dist/regl.min.js"></script> |
|
<style> |
|
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<script> |
|
|
|
// 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({extensions: ['OES_standard_derivatives']}) |
|
|
|
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}) => ({ |
|
|
|
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] |
|
}, |
|
|
|
depth: { |
|
enable: true, |
|
mask: true, |
|
func: 'less', |
|
range: [0, 1] |
|
}, |
|
|
|
vert: ` |
|
precision mediump float; |
|
|
|
attribute vec3 positionFrom; |
|
attribute vec3 positionTo; |
|
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() { |
|
vec3 tweened = mix(positionFrom, positionTo, smootherstep(0.0, 1.0, tween)); |
|
gl_Position = vec4(tweened, 1.0); |
|
c = color; |
|
gl_PointSize = 60.0; |
|
}`, |
|
|
|
frag: ` |
|
|
|
#ifdef GL_OES_standard_derivatives |
|
#extension GL_OES_standard_derivatives : enable |
|
#endif |
|
|
|
precision mediump float; |
|
|
|
uniform mat3 S1; |
|
uniform mat3 S2; |
|
uniform float tween; |
|
|
|
varying vec4 c; |
|
|
|
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 s = S[2][1]; |
|
|
|
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 s * r; |
|
} |
|
|
|
void main() { |
|
|
|
float alpha = 1.0, delta = 0.0; |
|
|
|
vec2 pxy = 2.0 * gl_PointCoord.xy - 1.0; |
|
float dist = length(pxy); |
|
float fi = atan(pxy.y, pxy.x); |
|
|
|
float r1 = superFormula(S1, fi); |
|
float r2 = superFormula(S2, fi); |
|
float r = mix(r1, r2, smoothstep(0.0, 1.0, tween)); |
|
|
|
float R = dist - r + 1.0; |
|
|
|
#ifdef GL_OES_standard_derivatives |
|
delta = fwidth(dist); |
|
if(R > 1.0 + delta ) discard; // avoid further calc, blending if possible |
|
alpha = 1.0 - smoothstep(1.0 - delta, 1.0 + delta, R); |
|
#else |
|
if(R > 1.0) discard; |
|
#endif |
|
|
|
gl_FragColor = vec4(c.xyz, c.a * alpha); |
|
}`, |
|
|
|
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 = 3 |
|
const colorLength = 4 |
|
|
|
const positionFrom = nd(Float32Array, [lineCount, pointCount, positionLength]) |
|
const positionTo = nd(Float32Array, [lineCount, pointCount, positionLength]) |
|
const color = nd(Float32Array, [lineCount, pointCount, colorLength]) |
|
let i, j, x1, x2, y1, y2, z |
|
|
|
for(i = 0; i < lineCount; i++) { |
|
|
|
for(j = 0; j < pointCount; j++) { |
|
x1 = Math.random() |
|
x2 = Math.random() |
|
y1 = Math.random() |
|
y2 = Math.random() |
|
z = 1 - (j + i * pointCount) / (lineCount * pointCount) |
|
positionFrom.set(i, j, 0, xScale(x1)) |
|
positionFrom.set(i, j, 1, yScale(y1)) |
|
positionFrom.set(i, j, 2, z) |
|
positionTo.set(i, j, 0, xScale(x2)) |
|
positionTo.set(i, j, 1, yScale(y2)) |
|
positionTo.set(i, j, 2, z) |
|
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, 0.8) |
|
} |
|
} |
|
|
|
return { |
|
lineCount, |
|
pointCount, |
|
attributes: { |
|
positionFrom: positionFrom.data, |
|
positionTo: positionTo.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, |
|
/* s= */ 0.99, // leave room for antialiasing band |
|
/* */ 0, |
|
], |
|
|
|
// hexagon |
|
S2 : [ |
|
/* a= */ 1, |
|
/* b= */ 1, |
|
/* m1= */ 6, |
|
/* m2= */ 6, |
|
/* n1= */ 100, |
|
/* n2= */ 40, |
|
/* n3= */ 40, |
|
/* s= */ 0.8, // make hexagon smaller for similar visual weight |
|
/* */ 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) + 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() |
|
|
|
</script> |
|
</body> |