From 42778a9d465a2570e54410da4639be06f62a4c82 Mon Sep 17 00:00:00 2001 From: VersusTune Date: Sun, 5 Apr 2020 15:28:22 +0200 Subject: [PATCH] first commit --- .gitignore | 2 + index.html | 34 +++++++ js/gui.js | 208 ++++++++++++++++++++++++++++++++++++++ js/handler.js | 83 +++++++++++++++ js/index.js | 53 ++++++++++ js/old.js | 88 ++++++++++++++++ js/sphere.js | 207 ++++++++++++++++++++++++++++++++++++++ js/utils.js | 238 ++++++++++++++++++++++++++++++++++++++++++++ shaders/sphere.frag | 19 ++++ shaders/sphere.vert | 21 ++++ shaders/test.frag | 13 +++ shaders/test.vert | 11 ++ style.css | 215 +++++++++++++++++++++++++++++++++++++++ 13 files changed, 1192 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 js/gui.js create mode 100644 js/handler.js create mode 100644 js/index.js create mode 100644 js/old.js create mode 100644 js/sphere.js create mode 100644 js/utils.js create mode 100644 shaders/sphere.frag create mode 100644 shaders/sphere.vert create mode 100644 shaders/test.frag create mode 100644 shaders/test.vert create mode 100644 style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f2e5aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/webGLTest.iml +/.idea/ diff --git a/index.html b/index.html new file mode 100644 index 0000000..4e1c80a --- /dev/null +++ b/index.html @@ -0,0 +1,34 @@ + + + + + WEBGL Test + + + + +
+ + Rotation + + + Transform + + + Color + + + World + + + Draw + +
+
+ + + + + + + diff --git a/js/gui.js b/js/gui.js new file mode 100644 index 0000000..a16a30e --- /dev/null +++ b/js/gui.js @@ -0,0 +1,208 @@ +function $(sel, s) { + return $$(sel, s)[0]; +} + +function $$(sel, s) { + s = s || document; + return s.querySelectorAll(sel); +} + +Node.prototype.addDelegatedEventListener = function (type, aim, cb) { + this.addEventListener(type, (event) => { + let target = event.target; + if (target.matches(aim)) { + cb(event, target); + } else { + let parent = target.closest(aim); + if (parent) { + cb(event, parent); + } + } + }) +}; + +function create(name, content) { + let d = document.createElement(name); + if (content) { + d.innerHTML = content; + } + return d; +} + +function append(to, array) { + for (let item in array) { + to.appendChild(array[item]); + } +} + +function generateRotationSliders() { + let group = $('#rotate'); + generateSlider(["X", "Y", "Z", "X-Inc", "Y-Inc", "Z-Inc"], 0, 360, 0, group, "rotate"); + generateCheckBox(["by Beat"], true, group, "rotate"); +} + +function generateTranslateSliders() { + let group = $('#translate'); + generateSlider(["X", "Y", "Z"], -1, 1, 1, group, "translate"); +} + +function generateColorSliders() { + let group = $('#color'); + generateSlider(["R", "G", "B"], 0, 255, 125, group, "color"); + generateCheckBox(["by Beat"], true, group, "color"); +} + +function generateWorldSliders() { + let group = $('#world'); + generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -1, 1, 1, group, "light"); + generateSlider(["LightAngle"], 0, 360, 90, group, "light"); +} + +function generateDrawSliders() { + let group = $('#draw'), + g = "drawMode" + generateSlider(["DrawMode"], 0, 6, 0, group, g); + generateSlider(["Form"], 0, 3, 0, group, g); + generateSlider(["Radius"], 20, 1500, 500, group, g); + generateSlider(["Total"], 0, 200, 50, group, g); + generateSlider(["PointSize"], 1, 10, 2, group, g, .2); + generateSlider(["Smoothing"], 0, 1, 0.8, group, g, .05); + generateSlider(["Steps"], 512, analyser.fftSize / 2, 512, group, g, 256); + generateCheckBox(["Dirty"], false, group, g) +} + +function setRotations(changedEl) { + let group = $('#rotate'); + let items = ["#X", "#Y", "#Z"]; + for (let i in items) { + let el = $(items[i], group); + if (el === changedEl) { + sphereObject.rotationRaw[i] = getValue(el) + } + } + items = ["#X-Inc", "#Y-Inc", "#Z-Inc"]; + for (let i in items) { + let el = $(items[i], group); + if (el === changedEl) { + sphereObject.rotateInc[i] = getValue(el) / 360; + } + } + + sphereObject.rotateByBeat = $('#rotateby-Beat', group).checked; +} + +function setColors() { + let group = $('#color'); + sphereObject.color.r = getValue($('#R', group)) / 255; + sphereObject.color.g = getValue($('#G', group)) / 255; + sphereObject.color.b = getValue($('#B', group)) / 255; + + sphereObject.colorByBeat = $('#colorby-Beat', group).checked; +} + +function setWorld() { + let group = $('#world'); + sphereObject.lightPos[0] = getValue($('#LightPos-X', group)) * 0.2; + sphereObject.lightPos[1] = getValue($('#LightPos-Y', group)) * 0.2; + sphereObject.lightPos[2] = getValue($('#LightPos-Z', group)) * 0.2; + + lightAngle = getValue($('#LightAngle', group)) +} + +function setDraw() { + let group = $('#draw'); + sphereObject.drawMode = getValue($('#DrawMode', group)); + sphereObject.sphereMode = getValue($('#Form', group)); + sphereObject.radius = getValue($('#Radius', group)); + sphereObject.total = getValue($('#Total', group)); + sphereObject.pointSize = getValue($('#PointSize', group)); + sphereObject.steps = getValue($('#Steps', group)); + sphereObject.dirtyMode = $('#drawModeDirty', group).checked; + analyser.smoothingTimeConstant = getValue($('#Smoothing', group)); +} + +function setTranslate() { + let group = $('#translate'); + let items = ["#X", "#Y", "#Z"]; + for (let i in items) { + sphereObject.translate[i] = getValue($(items[i], group)) + } +} + +function generateSlider(fromList, min, max, value, appendTo, group, stepSize) { + for (let i = 0; i < fromList.length; i++) { + let label = create("label", fromList[i]); + let reset = create("button", "d"); + let range = create("input"); + range.type = "range"; + range.min = min; + range.max = max; + range.value = value; + range.id = fromList[i]; + range.step = max === 1 ? .01 : (stepSize || 1); + range.dataset.group = group; + range.dataset.value = value; + let item = create("group-input"); + append(item, [label, range, reset]) + appendTo.appendChild(item); + } +} + +function generateCheckBox(fromList, defaultValue, appendTo, group) { + for (let i = 0; i < fromList.length; i++) { + let label = create("label", fromList[i]) + let checkbox = create("input"); + checkbox.type = "checkbox"; + checkbox.checked = defaultValue === true; + checkbox.dataset.group = group; + checkbox.id = group + fromList[i].split(" ").join("-"); + let item = create("group-input"); + append(item, [label, createSwitch(checkbox)]) + appendTo.appendChild(item); + } +} + +function createSwitch(checkbox) { + let swi = create("switch"); + let label = create("label"); + label.setAttribute("for", checkbox.id); + append(swi, [checkbox, label]); + return swi; +} + +function changeHandler(el) { + let d = el.dataset.group; + if (d === "rotate") { + setRotations(el); + } else if (d === "translate") { + setTranslate(); + } else if (d === "color") { + setColors(); + } else if (d === "light") { + setWorld(); + } else if (d === "drawMode") { + setDraw(); + } +} + +document.body.addDelegatedEventListener('input', 'group-input input', (ev, el) => { + changeHandler(el); +}) + +document.body.addDelegatedEventListener('click', 'group-input button', (ev, el) => { + let input = $('input', el.parentNode); + input.value = input.dataset.value; + changeHandler(input); +}); + +function getValue(slider) { + try { + return parseFloat(slider.value); + } catch (e) { + return 0; + } +} + +$('.settings-icon').addEventListener('click', function () { + $('.off-can').classList.toggle("closed"); +}) \ No newline at end of file diff --git a/js/handler.js b/js/handler.js new file mode 100644 index 0000000..b68f701 --- /dev/null +++ b/js/handler.js @@ -0,0 +1,83 @@ +class Shaders { + constructor(gl) { + this.gl = gl; + this.shaderNames = []; + this.shaders = {}; + this.programs = {}; + } + + async loadShader(name, path) { + this.shaderNames.push(name); + await this.load(name, path + name + ".vert", this.gl.VERTEX_SHADER); + await this.load(name, path + name + ".frag", this.gl.FRAGMENT_SHADER); + } + + async load(name, url, type) { + let realName = name + "_" + type; + if (!this.shaders[realName]) { + let data = await fetch(url); + let shader = this.createShader(await data.text(), type); + if (shader) { + this.shaders[realName] = shader; + } + } + return !!this.shaders[realName]; + } + + getShader(name, type) { + let realName = name + "_" + type; + return this.shaders[realName]; + } + + getAllShaders() { + return this.shaderNames; + } + + async createProgramForEach(arr) { + arr = arr || this.shaderNames; + for (let i = 0; i < arr.length; i++) { + let shader = arr[i]; + let v = await shaderHandler.createProgram(shader, [shader]) + if (!v) { + return false; + } + } + return true; + } + + createShader(source, type) { + let gl = this.gl; + let shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + return shader; + } + console.error(gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); + return null; + } + + createProgram(name, shaders) { + let gl = this.gl; + let pro = gl.createProgram(); + for (let i = 0; i < shaders.length; i++) { + gl.attachShader(pro, this.getShader(shaders[i], gl.VERTEX_SHADER)); + gl.attachShader(pro, this.getShader(shaders[i], gl.FRAGMENT_SHADER)); + } + gl.linkProgram(pro); + var success = gl.getProgramParameter(pro, gl.LINK_STATUS); + if (success) { + this.programs[name] = pro; + return pro; + } + + console.log(gl.getProgramInfoLog(pro)); + gl.deleteProgram(pro); + return null; + } + + getProgram(name) { + return this.programs[name]; + } +} \ No newline at end of file diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..2be60e4 --- /dev/null +++ b/js/index.js @@ -0,0 +1,53 @@ +let shaderHandler, gl, c, actx, analyser, peak; +let positionData = []; +let positionSize = 8192 * 2 * 2; + +function createAudioContextStream(stream) { + let AudioContext = window.AudioContext || window.webkitAudioContext; + if (actx) { + actx.close(); + } + actx = new AudioContext(); + analyser = actx.createAnalyser(); + let Source; + + analyser.fftSize = 4096; + analyser.maxDecibels = 0; + analyser.smoothingTimeConstant = .4; + Source = actx.createMediaStreamSource(stream); + + Source.connect(analyser); + return null; +} + +async function init() { + c = document.body.querySelector('#c'); + gl = c.getContext("webgl2"); + if (!gl) { + console.error("GL not found"); + return false; + } + shaderHandler = new Shaders(gl); + await shaderHandler.loadShader("test", "shaders/"); + + //sphere shader + await shaderHandler.loadShader("sphere", "shaders/", gl.VERTEX_SHADER); + return shaderHandler.createProgramForEach(["test", "sphere"]); +} + +(function () { + navigator.mediaDevices.getUserMedia({audio: true, video: false}) + .then(createAudioContextStream).then(e => { + generateRotationSliders(); + generateColorSliders(); + generateWorldSliders(); + generateDrawSliders(); + generateTranslateSliders(); + init().then(b => { + if (b) { + sphereObject.drawMode = gl.POINTS; + draw(); + } + }) + }); +})(); \ No newline at end of file diff --git a/js/old.js b/js/old.js new file mode 100644 index 0000000..ac3b912 --- /dev/null +++ b/js/old.js @@ -0,0 +1,88 @@ +function readData() { + let items = analyser.fftSize; + let dataArray = new Float32Array(items); + analyser.getFloatTimeDomainData(dataArray); + let space = 255 / (items + 2); + let pos = [0, 127.5]; + let x = space; + peak = 0; + for (let i = 0; i < items; i++) { + let data = (dataArray[i] * 125) + 127.5 + if (Math.abs(data) > peak) { + peak = data; + } + pos.push(x); + pos.push(data); + x += space; + } + pos.push(255, 127.5); + positions = pos; +} + +let positions = [ + Math.random(), Math.random(), + Math.random(), Math.random(), + Math.random(), Math.random(), +]; + +/*function draw() { + readData(); + let program = shaderHandler.getProgram("test"); + let positionAttributeLocation = gl.getAttribLocation(program, "a_position"); + var colorLocation = gl.getUniformLocation(program, "u_color"); + let positionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW); + let vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + gl.enableVertexAttribArray(positionAttributeLocation); + gl.vertexAttribPointer( + positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); + + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.useProgram(program); + gl.uniform4f(colorLocation, peak / 255, (255 - peak) / 255, .2, 1); + gl.drawArrays(gl.LINE_STRIP, 0, positions.length / 2); + + let positionBuffer2 = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer2); + gl.uniform4f(colorLocation, 0, 0, 1, .3); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW); + gl.drawArrays(gl.POINTS, 0, positions.length / 2); + + requestAnimationFrame(draw); +}*/ + +function setupSphere() { + sphereData = []; + let data = readData(), + radData = data[0]; + let sin = Math.sin, + cos = Math.cos, + total = sphereObject.total || 50, + cTotal = (total + 1) * (total + 1), + r = sphereObject.radius || 1000, + rx = r / c.width, + ry = r / c.height, + counter = 0; + for (let i = 1; i <= total; i++) { + let lat = VTUtils.map(i, 0, total, 0, Math.PI, false); + for (let j = 1; j <= total; j++) { + let lon = VTUtils.map(j, 0, total, 0, Math.TWO_PI, false); + let map = VTUtils.map(counter, 0, cTotal, 0, radData.length - 1, false); + map = Math.floor(map); + let realRX = rx + radData[map]; + let realRY = ry + radData[map]; + let x = rx * sin(lon) * cos(lat); + let y = rx * sin(lon) * sin(lat); + let z = ry * cos(lon); + sphereData.push(x); + sphereData.push(y); + sphereData.push(z); + counter++; + } + } + return [VTUtils.hsvToRgb(data[1], 1, 1), data[1]]; +} diff --git a/js/sphere.js b/js/sphere.js new file mode 100644 index 0000000..b83985e --- /dev/null +++ b/js/sphere.js @@ -0,0 +1,207 @@ +let sphereData = []; +Math.HALF_PI = Math.PI / 2; +Math.TWO_PI = Math.PI * 2; + +let sphereObject = { + rotationRaw: [0, 0, 0], // degrees + rotation: [0, 0, 0], //radians + rotateInc: [0.0, 0.0, 0.0], //degreesInc + rotateByBeat: true, + translate: [1, 1, 1], + total: 50, + radius: 500, + color: {r: 0, g: 0, b: 0}, + colorByBeat: true, + drawMode: 0, + sphereMode: 0, + lightPos: [0, 0, 0], + pointSize: 2, + steps: 512, + dirtyMode: false +} + +function readData() { + let items = sphereObject.steps; + let dataArray = new Uint8Array(items); + analyser.getByteFrequencyData(dataArray); + let arr = []; + let sum = 0; + for (let i = 0; i < items; i++) { + let data = dataArray[i] / 255; + arr.push(data * .5); + sum += dataArray[i]; + } + return [arr, (sum / items) / 255]; +} + +let sphereDataVectors = [], lastTotal = 0; + +function prepareData() { + let total = sphereObject.total; + if (lastTotal !== total) { + sphereDataVectors = []; + for (let i = 0; i <= total; i++) { + sphereDataVectors[i] = []; + for (let j = 0; j <= total; j++) { + sphereDataVectors[i][j] = new VTVector(); + } + } + lastTotal = total; + } +} + +function setupSphere() { + sphereData = []; + let data = readData(), + radData = data[0], + map = VTUtils.map, + total = sphereObject.total, + cTotal = (total + 1) * (total + 1), + radius = sphereObject.radius, + rx = radius / c.width, + ry = radius / c.height, + counter = 0; + + prepareData(); + for (let i = 0; i <= total; i++) { + let lat = map(i, 0, total, 0, Math.PI, false); + for (let j = 0; j <= total; j++) { + let lon = map(j, 0, total, 0, Math.TWO_PI, false); + let rAdd = getAddRad(counter, radData, cTotal); + let realRX = rx + rAdd; + let realRY = ry + rAdd; + let {x, y, z} = sphereMode(lat, lon, i, counter, realRX, realRY); + if (sphereObject.drawMode < 2 || sphereObject.dirtyMode) { + sphereData.push(x, y, z); + } else { + sphereDataVectors[i][j].setXYZ(x, y, z); + } + counter++; + } + } + if (sphereObject.drawMode > 1 && !sphereObject.dirtyMode) { + for (let i = 0; i < total; i++) { + for (let j = 0; j <= total; j++) { + let cur = sphereDataVectors[i][j]; + sphereData.push(cur.x, cur.y, cur.z); + cur = sphereDataVectors[i + 1][j]; + sphereData.push(cur.x, cur.y, cur.z); + } + } + } + return [VTUtils.peakRGB(data[1] * 2), data[1]]; +} + +function getAddRad(counter, data, total) { + let mapping, rAdd, map = VTUtils.map; + if (sphereObject.sphereMode === 3) { + let h = total / 2; + if (counter > h) { + mapping = map(counter, h, total, data.length - 1, 0); + } else { + mapping = map(counter, 0, h, 0, data.length - 1); + } + rAdd = data[Math.round(mapping)] || 0; + } else { + mapping = map(counter, 0, total, 0, data.length - 1); + rAdd = data[Math.round(mapping)] || 0; + } + return rAdd; +} + +let position, world, color, rotate, light, lightPos, lightAngle = 90; + +function prepare(program) { + position = gl.getAttribLocation(program, "a_position"); + world = gl.getUniformLocation(program, 'u_world'); + color = gl.getUniformLocation(program, "u_color"); + light = gl.getUniformLocation(program, "u_light"); + rotate = gl.getUniformLocation(program, "u_matrix"); + lightPos = gl.getUniformLocation(program, "u_lightPos"); + let pointSize = gl.getUniformLocation(program, "u_pointSize"); + gl.uniformMatrix4fv(world, false, TDUtils.yRotation(TDUtils.degToRad(lightAngle))); + gl.uniform3fv(light, [0.95, 0.62, 0.094]); + gl.uniform3fv(lightPos, sphereObject.lightPos); + gl.uniform1f(pointSize, sphereObject.pointSize); +} + +function draw() { + c.width = window.innerWidth; + c.height = window.innerHeight; + let aspect = c.height / c.width; + let d = setupSphere(); + let newColor = d[0]; + let program = shaderHandler.getProgram("sphere"); + gl.useProgram(program); + prepare(program); + let matrix = [ + sphereObject.translate[0] / aspect, 0, 0, 0, + 0, sphereObject.translate[1], 0, 0, + 0, 0, sphereObject.translate[2], 0, + 0, 0, 0, 1 + ] + matrix = TDUtils.multiply(matrix, TDUtils.xRotation(sphereObject.rotation[0])); + matrix = TDUtils.multiply(matrix, TDUtils.yRotation(sphereObject.rotation[1])); + matrix = TDUtils.multiply(matrix, TDUtils.zRotation(sphereObject.rotation[2])); + gl.uniformMatrix4fv(rotate, false, matrix); + + let positionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphereData), gl.DYNAMIC_DRAW); + let vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + gl.enableVertexAttribArray(position); + gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0); + + gl.clearColor(0, 0, 0, 1); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + gl.clearDepth(2.0) + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); + if (sphereObject.colorByBeat) { + gl.uniform4f(color, newColor.r, newColor.g, 0, 1); + } else { + let cx = sphereObject.color; + gl.uniform4f(color, cx.r, cx.g, cx.b, 1); + } + gl.drawArrays(sphereObject.drawMode || gl.POINTS, 0, sphereData.length / 3); + if (sphereObject.rotateByBeat) { + sphereObject.rotationRaw[1] += d[1]; + sphereObject.rotationRaw[0] += newColor.r; + } else { + for (let i = 0; i < 3; i++) { + sphereObject.rotationRaw[i] += sphereObject.rotateInc[i]; + } + } + + for (let i = 0; i < 3; i++) { + sphereObject.rotation[i] = TDUtils.degToRad(sphereObject.rotationRaw[i]) + } + requestAnimationFrame(draw); +} + +function sphereMode(lat, lon, i, counter, rx, ry) { + let x, y, z, + sin = Math.sin, + cos = Math.cos; + switch (sphereObject.sphereMode) { + case 0: + case 3: + x = rx * sin(lat) * cos(lon); + y = ry * sin(lat) * sin(lon); + z = ry * cos(lat); + break; + case 1: + x = rx * sin(lon) * cos(lat); + y = ry * sin(lon) * sin(lat); + z = ry * cos(lat); + break; + case 2: + x = rx * sin(lat) * cos(lat); + y = ry * sin(lat) * sin(lat); + z = ry * cos(lon); + break; + } + return {x: x, y: y, z: z}; +} \ No newline at end of file diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 0000000..68e1886 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,238 @@ +class VTUtils { + static random(min, max) { + let rand = Math.random(); + if (typeof min === 'undefined') { + return rand; + } else if (typeof max === 'undefined') { + if (min instanceof Array) { + return min[Math.floor(rand * min.length)]; + } else { + return rand * min; + } + } else { + if (min > max) { + let tmp = min; + min = max; + max = tmp; + } + return rand * (max - min) + min; + } + }; + + static randomInt(min, max) { + return Math.floor(VTUtils.random(min, max)); + } + + static normalize(val, max, min) { + return (val - min) / (max - min); + }; + + static distance(x, y, x2, y2) { + let a = x - x2; + let b = y - y2; + + return Math.sqrt(a * a + b * b); + } + + static map(n, start1, stop1, start2, stop2, withinBounds) { + let newVal = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2; + if (!withinBounds) { + return newVal; + } + if (start2 < stop2) { + return this.constrain(newVal, start2, stop2); + } else { + return this.constrain(newVal, stop2, start2); + } + }; + + static constrain(n, low, high) { + return Math.max(Math.min(n, high), low); + } + + static hsvToRgb(h, s, v) { + var r, g, b; + + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + + switch (i % 6) { + case 0: + r = v, g = t, b = p; + break; + case 1: + r = q, g = v, b = p; + break; + case 2: + r = p, g = v, b = t; + break; + case 3: + r = p, g = q, b = v; + break; + case 4: + r = t, g = p, b = v; + break; + case 5: + r = v, g = p, b = q; + break; + } + + return {r: r, g: g, b: b}; + } + + static peakRGB(peak) { + return { + r: peak, + g: 1 - peak, + b: 0 + }; + } +} + +class VTVector { + constructor(x, y, z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + } + + //helper + static createRandom(x, y, z) { + x = x || 1; + y = y || 1; + z = z || 0; + return new VTVector(VTUtils.random(-x, x), VTUtils.random(-y, y), VTUtils.random(-z, z)); + } + + mult(times) { + this.x *= times; + this.y *= times; + this.z *= times; + } + + set(vector) { + this.x = vector.x; + this.y = vector.y; + this.z = vector.z; + } + + add(vector) { + this.x = this.x + vector.x; + this.y = this.y + vector.y; + this.z = this.z + vector.z; + } + + addXYZ(x, y, z) { + this.x += x; + this.y += y; + this.z += z; + } + + setXYZ(x,y,z) { + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + } + + clone() { + return new VTVector(this.x, this.y, this.z); + } +} + +class TDUtils { + static multiply(a, b) { + let b00 = b[0 * 4 + 0]; + let b01 = b[0 * 4 + 1]; + let b02 = b[0 * 4 + 2]; + let b03 = b[0 * 4 + 3]; + let b10 = b[1 * 4 + 0]; + let b11 = b[1 * 4 + 1]; + let b12 = b[1 * 4 + 2]; + let b13 = b[1 * 4 + 3]; + let b20 = b[2 * 4 + 0]; + let b21 = b[2 * 4 + 1]; + let b22 = b[2 * 4 + 2]; + let b23 = b[2 * 4 + 3]; + let b30 = b[3 * 4 + 0]; + let b31 = b[3 * 4 + 1]; + let b32 = b[3 * 4 + 2]; + let b33 = b[3 * 4 + 3]; + let a00 = a[0 * 4 + 0]; + let a01 = a[0 * 4 + 1]; + let a02 = a[0 * 4 + 2]; + let a03 = a[0 * 4 + 3]; + let a10 = a[1 * 4 + 0]; + let a11 = a[1 * 4 + 1]; + let a12 = a[1 * 4 + 2]; + let a13 = a[1 * 4 + 3]; + let a20 = a[2 * 4 + 0]; + let a21 = a[2 * 4 + 1]; + let a22 = a[2 * 4 + 2]; + let a23 = a[2 * 4 + 3]; + let a30 = a[3 * 4 + 0]; + let a31 = a[3 * 4 + 1]; + let a32 = a[3 * 4 + 2]; + let a33 = a[3 * 4 + 3]; + let dst = []; + dst[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30; + dst[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31; + dst[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32; + dst[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33; + dst[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30; + dst[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31; + dst[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32; + dst[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33; + dst[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30; + dst[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31; + dst[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32; + dst[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33; + dst[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30; + dst[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31; + dst[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32; + dst[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33; + return dst; + } + + static xRotation(angleInRadians) { + let c = Math.cos(angleInRadians); + let s = Math.sin(angleInRadians); + + return [ + 1, 0, 0, 0, + 0, c, s, 0, + 0, -s, c, 0, + 0, 0, 0, 1, + ]; + } + + static yRotation(angleInRadians) { + let c = Math.cos(angleInRadians); + let s = Math.sin(angleInRadians); + + return [ + c, 0, -s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1, + ]; + } + + static zRotation(angleInRadians) { + let c = Math.cos(angleInRadians); + let s = Math.sin(angleInRadians); + + return [ + c, s, 0, 0, + -s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ]; + } + + static degToRad(d) { + return d * Math.PI / 180; + } +} diff --git a/shaders/sphere.frag b/shaders/sphere.frag new file mode 100644 index 0000000..57589f2 --- /dev/null +++ b/shaders/sphere.frag @@ -0,0 +1,19 @@ +#version 300 es + +// fragment shaders don't have a default precision so we need +// to pick one. mediump is a good default. It means "medium precision" +precision highp float; + +in vec3 v_surfaceToLight; + +uniform vec4 u_color; +uniform vec3 u_lightPos; + +out vec4 outColor; + +void main() { + vec3 surfaceToLightDirection = normalize(v_surfaceToLight); + outColor = u_color; + float light = 1.0; + //outColor.rgb *= surfaceToLightDirection; +} \ No newline at end of file diff --git a/shaders/sphere.vert b/shaders/sphere.vert new file mode 100644 index 0000000..45fc06f --- /dev/null +++ b/shaders/sphere.vert @@ -0,0 +1,21 @@ +#version 300 es + +in vec4 a_position; +uniform mat4 u_world; +uniform mat4 u_matrix; +uniform vec3 u_lightPos; +uniform vec3 u_light; +uniform float u_pointSize; + +out vec3 v_surfaceToLight; + +void main() { + // convert the position from pixels to 0.0 to 1.0 + vec4 pos = a_position * u_matrix; + gl_Position = pos; + gl_PointSize = u_pointSize; + + vec3 surfaceWorldPosition = (u_world * pos).xyz; + + v_surfaceToLight = u_lightPos - surfaceWorldPosition; +} \ No newline at end of file diff --git a/shaders/test.frag b/shaders/test.frag new file mode 100644 index 0000000..088b9b9 --- /dev/null +++ b/shaders/test.frag @@ -0,0 +1,13 @@ +#version 300 es + +// fragment shaders don't have a default precision so we need +// to pick one. mediump is a good default. It means "medium precision" +precision mediump float; + +uniform vec4 u_color; + +out vec4 outColor; + +void main() { + outColor = u_color; +} \ No newline at end of file diff --git a/shaders/test.vert b/shaders/test.vert new file mode 100644 index 0000000..ddf3857 --- /dev/null +++ b/shaders/test.vert @@ -0,0 +1,11 @@ +#version 300 es + +in vec2 a_position; +void main() { + // convert the position from pixels to 0.0 to 1.0 + vec2 scale = a_position / vec2(255.0, 255.0); + vec2 remap = scale * 2.0; + vec2 space = remap - 1.0; + space.y = space.y * 0.85; + gl_Position = vec4(space, 0,1); +} \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..ccea8c6 --- /dev/null +++ b/style.css @@ -0,0 +1,215 @@ +html, body { + padding: 0; + margin: 0; + overflow: hidden; + font-size: 16px; + font-family: sans-serif; +} + +div { + position: fixed; + color: #fff; + padding: 1em; +} + +#c { + width: 100%; + height: 100%; +} + +.settings-icon { + right: 0; + bottom: 0; + font-size: 1.5em; + cursor: pointer; +} + +.settings-icon:hover { + color: #0199ff; +} + +.off-can { + top: 0; + left: 0; + width: 300px; + background-color: rgba(33, 33, 33, 0.6); + height: 100vh; + transition: all .5s; + user-select: none; +} + +group { + display: block; + padding-bottom: 10px; + user-select: none; +} + +group-label { + display: block; + border-bottom: 1px solid #ffffff; + font-size: 21px; + font-weight: 500; + user-select: none; +} + +group-input { + display: flex; + align-items: center; + margin-top: 5px; + user-select: none; +} + +group-input label { + padding-right: 10px; + user-select: none; + width: 150px; +} + + +group-input input { + flex-grow: 1; + user-select: none; + max-width: 150px; +} + +group-input button { + border: 1px solid #dcdcdc; + background-color: transparent; + color: #fff; + margin-left: 5px; +} + +.closed { + transform: translateX(-350px); + transition: all .5s; +} + +input[type=range] { + -webkit-appearance: none; + width: 100%; +} + +input[type=range]:focus { + outline: none; +} + +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 25.6px; + cursor: pointer; + box-shadow: 1px 1px 1px #000000, 0 0 1px #0d0d0d; + background: #424242; + border-radius: 0; + border: 0 solid #010101; +} + +input[type=range]::-webkit-slider-thumb { + box-shadow: 0 0 0 #470000, 0 0 0 #610000; + border: 0 solid #ff0000; + height: 25px; + width: 15px; + border-radius: 0; + background: #a8c64e; + cursor: pointer; + -webkit-appearance: none; + margin-top: 0.3px; +} + +input[type=range]:focus::-webkit-slider-runnable-track { + background: #545454; +} + +input[type=range]::-moz-range-track { + width: 100%; + height: 25.6px; + cursor: pointer; + box-shadow: 1px 1px 1px #000000, 0 0 1px #0d0d0d; + background: #424242; + border-radius: 0; + border: 0 solid #010101; +} + +input[type=range]::-moz-range-thumb { + box-shadow: 0 0 0 #470000, 0 0 0 #610000; + border: 0 solid #ff0000; + height: 25px; + width: 15px; + border-radius: 0; + background: #a8c64e; + cursor: pointer; +} + +input[type=range]::-ms-track { + width: 100%; + height: 25.6px; + cursor: pointer; + background: transparent; + border-color: transparent; + color: transparent; +} + +input[type=range]::-ms-fill-lower { + background: #303030; + border: 0 solid #010101; + border-radius: 0; + box-shadow: 1px 1px 1px #000000, 0 0 1px #0d0d0d; +} + +input[type=range]::-ms-fill-upper { + background: #424242; + border: 0 solid #010101; + border-radius: 0; + box-shadow: 1px 1px 1px #000000, 0 0 1px #0d0d0d; +} + +input[type=range]::-ms-thumb { + box-shadow: 0 0 0 #470000, 0 0 0 #610000; + border: 0 solid #ff0000; + width: 15px; + border-radius: 0; + background: #a8c64e; + cursor: pointer; + height: 25px; +} + +input[type=range]:focus::-ms-fill-lower { + background: #424242; +} + +input[type=range]:focus::-ms-fill-upper { + background: #545454; +} + +switch input { + position: absolute; + appearance: none; + opacity: 0; +} + +switch label { + display: block; + border-radius: 10px; + width: 40px; + height: 20px; + background-color: #dcdcdc; + position: relative; + cursor: pointer; + padding: 0; +} + +switch label:after { + content: ''; + background-color: #ff3232; + position: absolute; + top: 2px; + left: 2px; + height: 16px; + width: 16px; + border-radius: 10px; + transition: .5s; +} + +switch input:checked + label:after { + transform: translateX(20px); +} +