Created
June 11, 2021 20:29
-
-
Save strictlymomo/5d3fa58cd5b14584506c98cea2336335 to your computer and use it in GitHub Desktop.
Quasicrystals Graphic Synthesizer
This file contains 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
<!DOCTYPE html> | |
<meta charset="utf-8" /> | |
<title>Quasicrystals</title> | |
<body> | |
<div class="ui"> | |
<!-- RED --> | |
<div class="group"> | |
<div class="vec"> | |
<header>RED</header> | |
<div> | |
<header class="subheading">CONTRAST (ar)</header> | |
<div> | |
<div><span id="ar" class="label"></span> <input class="slider" id="ar_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_ar(this.value)" /></div> | |
</div> | |
</div> | |
<div> | |
<header class="subheading">BRIGHTNESS (br)</header> | |
<div><span id="br" class="label"></span> <input class="slider" id="br_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_br(this.value)" /></div> | |
</div> | |
<div> | |
<header class="subheading">FREQ (cr)</header> | |
<div><span id="cr" class="label"></span> <input class="slider" id="cr_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_cr(this.value)" /></div> | |
</div> | |
<div> | |
<header class="subheading">PHASE (dr)</header> | |
<div><span id="dr" class="label"></span> <input class="slider" id="dr_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_dr(this.value)" /></div> | |
</div> | |
</div> | |
<!-- GREEN --> | |
<div class="vec"> | |
<header>GREEN</header> | |
<div> | |
<header class="subheading">CONTRAST (ag)</header> | |
<div></div> | |
<span id="ag" class="label"></span> <input class="slider" id="ag_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_ag(this.value)" /> | |
</div> | |
<div> | |
<header class="subheading">BRIGHTNESS (bg)</header> | |
<div></div> | |
<span id="bg" class="label"></span> <input class="slider" id="bg_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_bg(this.value)" /> | |
</div> | |
<div> | |
<header class="subheading">FREQ (cg)</header> | |
<div></div> | |
<span id="cg" class="label"></span> <input class="slider" id="cg_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_cg(this.value)" /> | |
</div> | |
<div> | |
<header class="subheading">PHASE (dg)</header> | |
<div></div> | |
<span id="dg" class="label"></span> <input class="slider" id="dg_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_dg(this.value)" /> | |
</div> | |
</div> | |
<!-- BLUE --> | |
<div class="vec"> | |
<header>BLUE</header> | |
<div> | |
<header class="subheading">CONTRAST (ab)</header> | |
<div><span id="ab" class="label"></span><input class="slider" id="ab_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_ab(this.value)" /></div> | |
</div> | |
<div> | |
<header class="subheading">BRIGHTNESS (bb)</header> | |
<div><span id="bb" class="label"></span> <input class="slider" id="bb_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_bb(this.value)" /></div> | |
</div> | |
<div> | |
<header class="subheading">FREQ (cb)</header> | |
<div><span id="cb" class="label"></span> <input class="slider" id="cb_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_cb(this.value)" /></div> | |
</div> | |
<div> | |
<header class="subheading">PHASE (db)</header> | |
<div><span id="db" class="label"></span><input class="slider" id="db_input" type="range" min="0.01" max="2.99" step="0.001" class="slider" onchange="change_db(this.value)" /></div> | |
</div> | |
</div> | |
</div> | |
<!-- PARAMS --> | |
<div class="group"> | |
<div class="vec"> | |
<header>alpha</header> | |
<div><span id="alpha" class="label"></span> <input class="slider" id="alpha_input" type="range" min="0.01" max="0.99" step="0.01" class="slider" onchange="change_alpha(this.value)" /></div> | |
</div> | |
</div> | |
<div class="group vertical"> | |
<div class="vec"> | |
<header>alphaFactor</header> | |
<div> | |
<span id="alphaFactor" class="label"></span> <input class="slider long" id="alphaFactor_input" type="range" min="0.01" max="10000.00" step="0.001" class="slider" onchange="change_alphaFactor(this.value)" /> | |
</div> | |
</div> | |
<div class="vec"> | |
<header>N</header> | |
<div><span id="N" class="label"></span><input class="slider long" id="N_input" type="range" min="1" max="1000" step="1" class="slider" onchange="change_N(this.value)" /></div> | |
</div> | |
</div> | |
<div class="group"> | |
<div class="vec"> | |
<header>Divider</header> | |
<div><span id="divider" class="label"></span><input class="slider" id="divider_input" type="range" min="1" max="10" step=".4" class="slider" onchange="change_Divider(this.value)" /></div> | |
</div> | |
</div> | |
</div> | |
<canvas id="mycanvas"></canvas> | |
</body> | |
<script> | |
const WIDTH = window.innerWidth; | |
const HEIGHT = window.innerHeight; | |
const devicePixelRatio = window.devicePixelRatio; | |
const canvas = document.getElementById('mycanvas'); | |
const FPS_60 = 1000 / 60; | |
const loop = false; | |
let now = new Date().getTime(); | |
// FRAMES | |
const record = false; | |
// var frames = 10; | |
/* | |
https://www.shadertoy.com/view/tlSBDw | |
https://iquilezles.org/www/articles/palettes/palettes.htm | |
color(t) = a + b⋅cos[2π(c⋅t+d)] | |
As t runs from 0 to 1 (our normalized palette index or domain), | |
the cosine oscilates c times with a phase of d. | |
The result is scaled and biased by a and b | |
to meet the desired constrast and brightness. | |
*/ | |
let jackmo = [[2.91, 2.99, 0.533, 1.116], [1.66, 1.16, 0.368, -0.075], [1.159, 0.431, 0.131, -0.075], [1.0], [0.01, 8, 1]]; | |
let yellow = [[0.907, 0.527, 0.99, 1.116], [0.901, 0.431, 0.99, -0.075], [0.607, 0.407, 0.99, -0.458], [0.9], [0.01, 51, 1]]; | |
let rainbow = [[0.635, 0.762, 2.985, 0.81], [0.635, 0.761, 2.437, 1.475], [0.652, 0.333, 2.315, 0.84], [1.0], [0.01, 58, 1]]; | |
let rainbow2 = [[0.725, 0.779, 1.931, 0.943], [0.725, 0.779, 2.992, 1.835], [0.826, 0.396, 1.858, 0.87], [1.0], [0.01, 58, 1]]; | |
// RED | |
let ar = 2.91; // contrast | |
let br = 2.99; // brightness | |
let cr = 0.533; // freq (oscillations) | |
let dr = 1.116; // phase | |
// BLUE | |
let ag = 1.66; // contrast | |
let bg = 1.16; // brightness | |
let cg = 0.368; // freq (oscillations) | |
let dg = -0.075; // phase | |
// GREEN | |
let ab = 1.159; // contrast | |
let bb = 0.431; // brightness | |
let cb = 0.131; // freq (oscillations) | |
let db = -0.075; // phase | |
let alphaFactor = 0.01; | |
let alphaFactorStep = 0.05; | |
let alphaFactorLimit = 100; | |
let N = 8; | |
let divider = 1; | |
let alpha = 1; | |
[[ar, br, cr, dr], [ag, bg, cg, dg], [ab, bb, cb, db], [alpha], [alphaFactor, N, divider]] = rainbow2; | |
function set() { | |
[$0, GL] = gl(WIDTH, HEIGHT); | |
// console.log('$0 ', $0); | |
// console.log('GL ', GL); | |
FRAGMENTSHADER = fragmentShader(GL, N); | |
VERTEXSHADER = vertexShader(GL); | |
VERTEXBUFFER = vertexBuffer(GL); | |
PROGRAM = program(GL, VERTEXSHADER, FRAGMENTSHADER); | |
A_VERTEX = a_vertex(GL, PROGRAM); | |
U_TRANSLATE = u_translate(GL, PROGRAM); | |
U_SCALE = u_scale(GL, PROGRAM); | |
U_T = u_t(GL, PROGRAM); | |
U_N = u_n(GL, PROGRAM); | |
} | |
setInnerHTML(); | |
setInterval(() => { | |
// console.log('alphaFactor ', alphaFactor); | |
now = new Date().getTime(); | |
now = now / 16; | |
set(); | |
draw(init, GL, U_SCALE, now, U_T, $0); | |
if (record) { | |
capture(canvas); | |
} | |
}, FPS_60); | |
setInterval(() => { | |
alphaFactor = +alphaFactor + alphaFactorStep; | |
// reset on a loop | |
if (loop) { | |
if (alphaFactor > alphaFactorLimit) { | |
alphaFactor = 0.01; | |
change_alphaFactor(alphaFactor); | |
} else { | |
change_alphaFactor(parseFloat(alphaFactor)); | |
} | |
} else { | |
change_alphaFactor(parseFloat(alphaFactor)); | |
} | |
}, FPS_60); | |
// returns [HTMLCanvasElement, WebGLRenderingContext] | |
function gl(width, height) { | |
canvas.width = width * devicePixelRatio; | |
canvas.height = height * devicePixelRatio; | |
canvas.style = `width: ${width}px; height: auto;`; | |
canvas.value = canvas.getContext('webgl'); | |
return [canvas, canvas.value]; | |
} | |
function init(gl, program, a_vertex, u_translate, $0) { | |
gl.useProgram(program); | |
gl.enableVertexAttribArray(a_vertex); | |
gl.vertexAttribPointer(a_vertex, 2, gl.FLOAT, false, 0, 0); // gl.FLOAT = 5126 | |
gl.uniform2f(u_translate, $0.width / 2, $0.height / 2); | |
gl.viewport(0, 0, $0.width, $0.height); | |
} | |
function draw(init, gl, u_scale, now, u_t) { | |
init(GL, PROGRAM, A_VERTEX, U_TRANSLATE, $0); | |
gl.uniform1f(u_scale, Math.sin(now / 4000) * 2 + 10); | |
gl.uniform1f(u_t, (now / 400) % (2 * Math.PI)); | |
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4); | |
} | |
// return WebGLShader | |
function fragmentShader(gl, N) { | |
const shader = gl.createShader(gl.FRAGMENT_SHADER); | |
gl.shaderSource( | |
shader, | |
` | |
precision highp float; | |
uniform sampler2D u_image; | |
uniform vec2 u_translate; | |
uniform float u_scale; | |
uniform float u_t; | |
const float c_pi = 3.14159265358979323846264; | |
vec4 color(float t) { | |
const float ar = ${ar}, ag = ${ag}, ab = ${ab}; | |
const float br = ${br}, bg = ${bg}, bb = ${bb}; | |
const float cr = ${cr}, cg = ${cg}, cb = ${cb}; | |
const float dr = ${dr}, dg = ${dg}, db = ${db}; | |
return vec4( | |
ar + br * cos(2.0 * c_pi * (cr * t + dr)), | |
ag + bg * cos(2.0 * c_pi * (cg * t + dg)), | |
ab + bb * cos(2.0 * c_pi * (cb * t + db)), | |
${alpha} | |
); | |
} | |
void main(void) { | |
const int n = ${N}; | |
const float step = c_pi / float(n); | |
vec2 p = (gl_FragCoord.xy - u_translate) / u_scale; | |
float alpha; | |
for (int i = 0; i < n; i++) { | |
float theta = float(i) * step / float(${divider}); | |
alpha += cos(cos(theta) * p.x + sin(theta) * p.y + u_t); | |
} | |
gl_FragColor = color(alpha * ${alphaFactor} / float(n)); | |
} | |
` | |
); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(shader)); | |
return shader; | |
} | |
// return WebGLShader | |
function vertexShader(gl) { | |
const shader = gl.createShader(gl.VERTEX_SHADER); | |
gl.shaderSource( | |
shader, | |
` | |
attribute vec2 a_vertex; | |
void main(void) { | |
gl_Position = vec4(a_vertex, 0.0, 1.0); | |
} | |
` | |
); | |
gl.compileShader(shader); | |
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(shader)); | |
return shader; | |
} | |
// return WebGLBuffer - opaque buffer object storing data such as vertices or colors. | |
function vertexBuffer(gl) { | |
const buffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, Float32Array.of(-1, -1, +1, -1, +1, +1, -1, +1), gl.STATIC_DRAW); | |
return buffer; | |
} | |
// return WebGLProgram | |
// WebGLProgram object is a combination of two compiled WebGLShaders consisting of a vertex shader and a fragment shader (both written in GLSL). | |
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/createProgram | |
function program(gl, vertexShader, fragmentShader) { | |
const program = gl.createProgram(); | |
gl.attachShader(program, vertexShader); | |
gl.attachShader(program, fragmentShader); | |
gl.linkProgram(program); | |
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program)); | |
return program; | |
} | |
// return 0 | |
// A GLint number indicating the location of the variable name if found. Returns -1 otherwise. | |
function a_vertex(gl, program) { | |
return gl.getAttribLocation(program, 'a_vertex'); | |
} | |
// return WebGLUniformLocation (null) | |
function u_n(gl, program) { | |
return gl.getUniformLocation(program, 'u_n'); | |
} | |
// return WebGLUniformLocation | |
function u_translate(gl, program) { | |
return gl.getUniformLocation(program, 'u_translate'); | |
} | |
// return WebGLUniformLocation | |
function u_scale(gl, program) { | |
return gl.getUniformLocation(program, 'u_scale'); | |
} | |
// return WebGLUniformLocation | |
function u_t(gl, program) { | |
return gl.getUniformLocation(program, 'u_t'); | |
} | |
function change_ar(value) { | |
ar = parseFloat(value).toFixed(3); | |
document.getElementById('ar').innerHTML = ar; | |
} | |
function change_ag(value) { | |
ag = parseFloat(value).toFixed(3); | |
document.getElementById('ag').innerHTML = ag; | |
} | |
function change_ab(value) { | |
ab = parseFloat(value).toFixed(3); | |
document.getElementById('ab').innerHTML = ab; | |
} | |
function change_br(value) { | |
br = parseFloat(value).toFixed(3); | |
document.getElementById('br').innerHTML = br; | |
} | |
function change_bg(value) { | |
bg = parseFloat(value).toFixed(3); | |
document.getElementById('bg').innerHTML = bg; | |
} | |
function change_bb(value) { | |
bb = parseFloat(value).toFixed(3); | |
document.getElementById('bb').innerHTML = bb; | |
} | |
function change_cr(value) { | |
cr = parseFloat(value).toFixed(3); | |
document.getElementById('cr').innerHTML = cr; | |
} | |
function change_cg(value) { | |
cg = parseFloat(value).toFixed(3); | |
document.getElementById('cg').innerHTML = cg; | |
} | |
function change_cb(value) { | |
cb = parseFloat(value).toFixed(3); | |
document.getElementById('cb').innerHTML = cb; | |
} | |
function change_dr(value) { | |
dr = parseFloat(value).toFixed(3); | |
document.getElementById('dr').innerHTML = dr; | |
} | |
function change_dg(value) { | |
dg = parseFloat(value).toFixed(3); | |
document.getElementById('dg').innerHTML = dg; | |
} | |
function change_db(value) { | |
db = parseFloat(value).toFixed(3); | |
document.getElementById('db').innerHTML = db; | |
} | |
function change_alpha(value) { | |
alpha = parseFloat(value).toFixed(2); | |
document.getElementById('alpha').innerHTML = alpha; | |
} | |
function change_alphaFactor(value) { | |
alphaFactor = parseFloat(value).toFixed(2); | |
document.getElementById('alphaFactor').innerHTML = alphaFactor; | |
} | |
function change_N(value) { | |
N = parseInt(value); | |
document.getElementById('N').innerHTML = N; | |
} | |
function change_Divider(value) { | |
divider = parseInt(value); | |
document.getElementById('divider').innerHTML = divider; | |
} | |
function setInnerHTML() { | |
change_ar(ar); | |
change_ab(ab); | |
change_ag(ag); | |
document.getElementById('ar_input').value = ar; | |
document.getElementById('ab_input').value = ab; | |
document.getElementById('ag_input').value = ag; | |
change_br(br); | |
change_bb(bb); | |
change_bg(bg); | |
document.getElementById('br_input').value = br; | |
document.getElementById('bb_input').value = bb; | |
document.getElementById('bg_input').value = bg; | |
change_cr(cr); | |
change_cb(cb); | |
change_cg(cg); | |
document.getElementById('cr_input').value = cr; | |
document.getElementById('cb_input').value = cb; | |
document.getElementById('cg_input').value = cg; | |
change_dr(dr); | |
change_db(db); | |
change_dg(dg); | |
document.getElementById('dr_input').value = dr; | |
document.getElementById('db_input').value = db; | |
document.getElementById('dg_input').value = dg; | |
change_alpha(alpha); | |
change_alphaFactor(alphaFactor); | |
change_N(N); | |
change_Divider(divider); | |
document.getElementById('alpha_input').value = alpha; | |
document.getElementById('alphaFactor_input').value = alphaFactor; | |
document.getElementById('N_input').value = N; | |
document.getElementById('divider_input').value = divider; | |
} | |
////////////////////////////////////////// | |
// VIDEO SHIT | |
// https://gist.github.com/unconed/4370822 | |
let storageInfo = navigator.webkitTemporaryStorage || window.webkitStorageInfo; | |
var requestedBytes = 1024 * 1024 * 1024 * 40; // Request 40 GB (should be enough for ~5 minutes of 1080p) | |
storageInfo.queryUsageAndQuota( | |
(usedBytes, grantedBytes) => console.log('we are using ', usedBytes, ' of ', grantedBytes, 'bytes'), | |
(e) => console.log('Error', e) | |
); | |
storageInfo.requestQuota( | |
requestedBytes, | |
function (grantedBytes) { | |
window.webkitRequestFileSystem(PERSISTENT, grantedBytes, function (fs) { | |
window.fs = fs; | |
console.log('Got filesystem'); | |
}); | |
}, | |
function (e) { | |
console.log('Storage error', e); | |
} | |
); | |
// file system is here: https://stackoverflow.com/questions/11676584/where-does-persistent-file-system-storage-store-with-chrome | |
function capture(canvas) { | |
if (!window.fs) return; | |
var name = Math.random(); // File name doesn't matter | |
var image = canvas.toDataURL('image/png').slice(22); | |
fs.root.getFile( | |
name, | |
{ create: true }, | |
function (FileSystemFileEntry) { | |
FileSystemFileEntry.createWriter(function (writer) { | |
// Convert base64 to binary without UTF-8 mangling. | |
var data = atob(image); | |
var buf = new Uint8Array(data.length); | |
for (var i = 0; i < data.length; ++i) { | |
buf[i] = data.charCodeAt(i); | |
} | |
// Write data | |
var blob = new Blob([buf], {}); | |
writer.seek(0); | |
writer.write(blob); | |
console.log('Writing file', frames, blob.size); | |
}); | |
}, | |
function () { | |
console.log('File error', arguments); | |
} | |
); | |
} | |
</script> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
} | |
canvas { | |
position: fixed; | |
/* top: 300px; */ | |
/* left: 300px; */ | |
width: 100%; | |
height: 100%; | |
} | |
.ui { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
padding: 16px; | |
background-color: transparent; | |
z-index: 10; | |
display: flex; | |
flex-direction: row; | |
justify-content: flex-start; | |
} | |
.group:hover { | |
opacity: 1; | |
background-color: rgba(255, 255, 255, 0.5); | |
} | |
.group { | |
opacity: 0; | |
display: flex; | |
flex-direction: row; | |
padding: 16px; | |
border: 1px solid black; | |
} | |
.vertical { | |
flex-direction: column; | |
} | |
.vec { | |
margin-right: 20px; | |
margin-bottom: 10px; | |
} | |
.vec > div { | |
margin-bottom: 10px; | |
} | |
.subheading { | |
font-size: 10px; | |
font-weight: 700; | |
} | |
.label { | |
display: inline-block; | |
width: 50px; | |
font-size: 10px; | |
text-align: right; | |
font-weight: 700; | |
} | |
.slider { | |
-webkit-appearance: none; | |
width: 50%; | |
height: 15px; | |
background: #000; | |
outline: none; | |
border: 5px solid mediumvioletred; | |
border-radius: 8px; | |
} | |
.long { | |
width: 400px; | |
} | |
/* for chrome/safari */ | |
.slider::-webkit-slider-thumb { | |
-webkit-appearance: none; | |
appearance: none; | |
width: 10px; | |
height: 30px; | |
background: #000; | |
cursor: pointer; | |
border: 5px solid lawngreen; | |
border-radius: 4px; | |
} | |
/* for firefox */ | |
.slider::-moz-range-thumb { | |
width: 10px; | |
height: 30px; | |
background: #000; | |
cursor: pointer; | |
border: 5px solid lawngreen; | |
border-radius: 4px; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment