Skip to content

Instantly share code, notes, and snippets.

@strictlymomo
Created June 11, 2021 20:29
Show Gist options
  • Save strictlymomo/5d3fa58cd5b14584506c98cea2336335 to your computer and use it in GitHub Desktop.
Save strictlymomo/5d3fa58cd5b14584506c98cea2336335 to your computer and use it in GitHub Desktop.
Quasicrystals Graphic Synthesizer
<!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