From d1ae2059f75519018b7410dd0f2d94925ca6bac5 Mon Sep 17 00:00:00 2001 From: VersusTune Date: Tue, 7 Apr 2020 21:44:46 +0200 Subject: [PATCH] WIP --- .gitignore | 1 + build/gulpfile.js | 20 ++ build/package.json | 36 +++ build/task/js.js | 37 +++ build/task/jsonMinifier.js | 26 ++ build/task/scss.js | 17 ++ build/task/spriteBuilder.js | 104 ++++++++ index.html | 71 +++-- js/handler.js | 9 + js/index.js | 63 +++-- js/old.js | 88 ------- js/sphere.js | 48 ++-- js/utils.js | 39 ++- js/{gui.js => visualGUI.js} | 119 +++++---- out/gui/.gitkeep | 0 out/gui/sphere.json | 1 + out/icon-sprite.svg | 1 + out/js/scripts.js | 431 +++++++++++++++++++++++++++++++ out/js/scripts.min.js | 1 + out/theme/style.css | 1 + raw/gui/sphere.json | 134 ++++++++++ raw/icons/.gitkeep | 0 raw/javascript/app.js | 24 ++ raw/javascript/audio.js | 5 + raw/javascript/gui.js | 5 + raw/javascript/handler.js | 95 +++++++ raw/javascript/player.js | 5 + raw/javascript/utils.js | 275 ++++++++++++++++++++ raw/javascript/visual.js | 15 ++ raw/javascript/visuals/sphere.js | 7 + raw/scss/_controls.scss | 24 ++ raw/scss/_gui.scss | 108 ++++++++ raw/scss/_input.scss | 79 ++++++ raw/scss/style.scss | 43 +++ shaders/sphere.frag | 8 +- shaders/sphere.vert | 8 +- shaders/{test.frag => wave.frag} | 0 shaders/{test.vert => wave.vert} | 0 style.css | 215 --------------- 39 files changed, 1735 insertions(+), 428 deletions(-) create mode 100644 build/gulpfile.js create mode 100644 build/package.json create mode 100644 build/task/js.js create mode 100644 build/task/jsonMinifier.js create mode 100644 build/task/scss.js create mode 100644 build/task/spriteBuilder.js delete mode 100644 js/old.js rename js/{gui.js => visualGUI.js} (72%) create mode 100644 out/gui/.gitkeep create mode 100644 out/gui/sphere.json create mode 100644 out/icon-sprite.svg create mode 100644 out/js/scripts.js create mode 100644 out/js/scripts.min.js create mode 100644 out/theme/style.css create mode 100644 raw/gui/sphere.json create mode 100644 raw/icons/.gitkeep create mode 100644 raw/javascript/app.js create mode 100644 raw/javascript/audio.js create mode 100644 raw/javascript/gui.js create mode 100644 raw/javascript/handler.js create mode 100644 raw/javascript/player.js create mode 100644 raw/javascript/utils.js create mode 100644 raw/javascript/visual.js create mode 100644 raw/javascript/visuals/sphere.js create mode 100644 raw/scss/_controls.scss create mode 100644 raw/scss/_gui.scss create mode 100644 raw/scss/_input.scss create mode 100644 raw/scss/style.scss rename shaders/{test.frag => wave.frag} (100%) rename shaders/{test.vert => wave.vert} (100%) delete mode 100644 style.css diff --git a/.gitignore b/.gitignore index 0f2e5aa..27f553a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /webGLTest.iml /.idea/ +/build/node_modules/ \ No newline at end of file diff --git a/build/gulpfile.js b/build/gulpfile.js new file mode 100644 index 0000000..c1d6674 --- /dev/null +++ b/build/gulpfile.js @@ -0,0 +1,20 @@ +const gulp = require('gulp'), + spriteBuild = require('./task/spriteBuilder').buildIconSprites, + scss = require('./task/scss').buildCSS, + js = require('./task/js').build, + gui = require('./task/jsonMinifier').build; + +gulp.task('scss', scss); +gulp.task('js', js); +gulp.task('sprite', spriteBuild); +gulp.task('gui', gui); + +gulp.task('watchMe', () => { + gulp.watch('./../raw/javascript/**/*.js', gulp.series('js')); + gulp.watch('./../raw/scss/**/*.scss', gulp.series('scss')); + gulp.watch('./../raw/gui/**/*.json', gulp.series('gui')); +}); + +gulp.task('default', gulp.parallel('js', 'scss', 'sprite', 'gui')); + +gulp.task('watch', gulp.parallel('js', 'scss', 'sprite', 'gui', 'watchMe')); diff --git a/build/package.json b/build/package.json new file mode 100644 index 0000000..b7d731d --- /dev/null +++ b/build/package.json @@ -0,0 +1,36 @@ +{ + "name": "vtwebbuild", + "version": "1.0.0", + "description": "Buildprocess for VTWeb like babble, sass and other things", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://gitlab.com/versustune/vtweb.git" + }, + "author": "Maurice Grönwoldt", + "license": "ISC", + "bugs": { + "url": "https://gitlab.com/versustune/vtweb/issues" + }, + "homepage": "https://gitlab.com/versustune/vtweb#readme", + "dependencies": { + "@fortawesome/fontawesome-pro": "^5.5.0", + "@fortawesome/pro-light-svg-icons": "^5.5.0", + "@fortawesome/pro-regular-svg-icons": "^5.5.0", + "@fortawesome/pro-solid-svg-icons": "^5.5.0", + "fontawesome-svg-sprite-generator": "^1.0.0", + "gulp": "^4.0.0", + "gulp-clean-css": "^4.0.0", + "gulp-concat": "^2.6.1", + "gulp-rename": "^1.4.0", + "gulp-sass": "^4.0.2", + "gulp-terser": "^1.1.7", + "merge-stream": "^1.0.1", + "node-fs-extra": "^0.8.2", + "require-dir": "^1.0.0", + "svg-sprite": "^1.4.0" + } +} diff --git a/build/task/js.js b/build/task/js.js new file mode 100644 index 0000000..7b7741b --- /dev/null +++ b/build/task/js.js @@ -0,0 +1,37 @@ +const terser = require('gulp-terser'), + concat = require('gulp-concat'), + rename = require('gulp-rename'), + gulp = require('gulp'); + +const basePath = __dirname + '/../../raw/javascript/'; +const visualPath = basePath + 'visuals/'; +const visuals = [ + visualPath + 'sphere.js', + //visualPath + 'wave.js', + //visualPath + 'water.js', + //visualPath + 'experimental.js', +] +const config = { + src: [ + basePath + 'utils.js', + basePath + 'handler.js', + basePath + 'audio.js', + basePath + 'player.js', + basePath + 'gui.js', + basePath + 'visual.js', + ...visuals, + basePath + 'app.js' + ], + dest: __dirname + '/../../out/js' +}; + +function build() { + return gulp.src(config.src) + .pipe(concat('scripts.js')) + .pipe(gulp.dest(config.dest)) + .pipe(rename('scripts.min.js')) + .pipe(terser()) + .pipe(gulp.dest(config.dest)); +} + +module.exports.build = build; diff --git a/build/task/jsonMinifier.js b/build/task/jsonMinifier.js new file mode 100644 index 0000000..ef6795c --- /dev/null +++ b/build/task/jsonMinifier.js @@ -0,0 +1,26 @@ +const gulp = require('gulp'), + fs = require('fs') + + +const basePath = __dirname + '/../../raw/gui/'; + +const config = { + src: basePath + '*.json', + dest: __dirname + '/../../out/gui/' +} + +function build() { + fs.readdirSync(basePath).forEach(file => { + try { + if (fs.lstatSync(basePath + file).isDirectory()) + return + let content = fs.readFileSync(basePath + file).toString() + fs.writeFileSync(config.dest + file, JSON.stringify(JSON.parse(content))) + } catch (e) { + console.error(e) + } + }) + return gulp.src(config.src) +} + +module.exports.build = build; \ No newline at end of file diff --git a/build/task/scss.js b/build/task/scss.js new file mode 100644 index 0000000..a52931b --- /dev/null +++ b/build/task/scss.js @@ -0,0 +1,17 @@ +const sass = require('gulp-sass'), + clean = require('gulp-clean-css'), + gulp = require('gulp'); + +const config = { + src: __dirname + '/../../raw/scss/**/*.scss', + dest: __dirname + '/../../out/theme' +}; + +function buildCSS() { + return gulp.src(config.src) + .pipe(sass().on('error', sass.logError)) + .pipe(clean()) + .pipe(gulp.dest(config.dest)); +} + +module.exports.buildCSS = buildCSS; diff --git a/build/task/spriteBuilder.js b/build/task/spriteBuilder.js new file mode 100644 index 0000000..29f5e33 --- /dev/null +++ b/build/task/spriteBuilder.js @@ -0,0 +1,104 @@ +const faSvgSprite = require('fontawesome-svg-sprite-generator'), + SVGSpriter = require('svg-sprite'), + path = require('path'), + fs = require('fs'), + fal = require('@fortawesome/pro-light-svg-icons'), + far = require('@fortawesome/pro-regular-svg-icons'), + fas = require('@fortawesome/pro-solid-svg-icons'), + fsextra = require('node-fs-extra'), + gulp = require('gulp'); + +let toMergeData = []; + +function buildIconSprites() { + let config = { + src: __dirname + '/../../raw/icons', + dest: __dirname + '/../../out/icon-sprite.svg', + spriter: { + shape: { + id: { + generator: function (name) { + return "vt-" + name.replace('.svg', ''); + } + } + }, + "svg": { + "xmlDeclaration": false, + "doctypeDeclaration": false + }, + "mode": { + "symbol": true + } + } + }; + + let icons = { + fa: [ + fal.faPlay, + fal.faPause, + fal.faCaretRight, + fal.faCaretLeft, + fal.faRandom, + fal.faCogs, + fal.faFolderUpload, + fal.faListMusic, + ], + vt: [] + }; + + builder.init(config, icons); + builder.generateFontAwesome(); + builder.generateFromIcons(); + return gulp.src(config.src); +} + +let builder = { + init: function (config, icons) { + this.config = config; + this.icons = icons; + }, + + generateFontAwesome: function () { + let sprite = faSvgSprite.generate(this.icons.fa, {license: '', xmlDeclaration: false}); + toMergeData.push(sprite.svg); + }, + + generateFromIcons: function () { + builder.merge(); + }, + + merge: function () { + if (toMergeData.length === 0) { + return; + } + let data = ''; + for (let item of toMergeData) { + data += item.replace('', '').replace(/(]*)/g, ''); + } + data += ''; + + let dirname = path.dirname(builder.config.dest); + if (!fs.existsSync(dirname)) { + fsextra.mkdirpSync(dirname); + } + fs.writeFileSync(builder.config.dest, data); + }, + + readFiles: function (dir, spriter, icons) { + let files = fs.readdirSync(dir); + for (let file of files) { + let split = file.split('.'); + let suffix = split[split.length - 1]; + if (suffix === 'svg' && icons.indexOf('vt-' + split[0]) !== -1) { + let filePath = path.resolve(dir, file); + spriter.add( + filePath, + file, + fs.readFileSync(filePath, {encoding: 'utf-8'}) + ); + } + } + } +}; + +module.exports.buildIconSprites = buildIconSprites; diff --git a/index.html b/index.html index 4e1c80a..eb50ef4 100644 --- a/index.html +++ b/index.html @@ -3,32 +3,55 @@ WEBGL Test - + - -
- - Rotation - - - Transform - - - Color - - - World - - - Draw - +
+ + + Loading
-
- - - - - +
+ + + +
+
+ + + +
+ + diff --git a/js/handler.js b/js/handler.js index b68f701..884a547 100644 --- a/js/handler.js +++ b/js/handler.js @@ -80,4 +80,13 @@ class Shaders { getProgram(name) { return this.programs[name]; } + + async loadArray(list, path) { + let self = this; + for (const e of list) { + await self.loadShader(e, path) + } + await self.createProgramForEach(list) + return true; + } } \ No newline at end of file diff --git a/js/index.js b/js/index.js index 2be60e4..95b4392 100644 --- a/js/index.js +++ b/js/index.js @@ -1,22 +1,35 @@ -let shaderHandler, gl, c, actx, analyser, peak; -let positionData = []; -let positionSize = 8192 * 2 * 2; +let shaderHandler, gl, c, actx, analyser, peak, isInit = false; -function createAudioContextStream(stream) { +function createAudioContextStream(audio) { let AudioContext = window.AudioContext || window.webkitAudioContext; if (actx) { actx.close(); } actx = new AudioContext(); analyser = actx.createAnalyser(); + let MEDIA_ELEMENT_NODES = new WeakMap(); let Source; analyser.fftSize = 4096; analyser.maxDecibels = 0; analyser.smoothingTimeConstant = .4; - Source = actx.createMediaStreamSource(stream); + if (audio) { + if (MEDIA_ELEMENT_NODES.has(audio)) { + Source = MEDIA_ELEMENT_NODES.get(audio); + } else { + Source = actx.createMediaElementSource(audio); + MEDIA_ELEMENT_NODES.set(audio, Source); + } - Source.connect(analyser); + Source.connect(analyser); + analyser.connect(actx.destination); + audio.oncanplay = () => { + actx.resume(); + }; + audio.onended = function () { + actx.pause(); + }; + } return null; } @@ -28,26 +41,38 @@ async function init() { return false; } shaderHandler = new Shaders(gl); - await shaderHandler.loadShader("test", "shaders/"); + await shaderHandler.loadShader("wave", "shaders/"); //sphere shader await shaderHandler.loadShader("sphere", "shaders/", gl.VERTEX_SHADER); - return shaderHandler.createProgramForEach(["test", "sphere"]); + return shaderHandler.createProgramForEach(["wave", "sphere"]); } -(function () { - navigator.mediaDevices.getUserMedia({audio: true, video: false}) - .then(createAudioContextStream).then(e => { - generateRotationSliders(); - generateColorSliders(); - generateWorldSliders(); - generateDrawSliders(); - generateTranslateSliders(); +function createView() { + if (!isInit) { + createAudioContextStream(audioFile); init().then(b => { if (b) { - sphereObject.drawMode = gl.POINTS; + loadConfig(); draw(); } }) - }); -})(); \ No newline at end of file + isInit = true; + } +} + +function initGUI() { + generateRotationSliders(); + generateColorSliders(); + generateWorldSliders(); + generateDrawSliders(); + generateTranslateSliders(); +} + +(function () { + if (document.readyState === "complete" || document.readyState === "interactive") { + initGUI() + } else { + document.addEventListener('DOMContentLoaded', initGUI); + } +})() \ No newline at end of file diff --git a/js/old.js b/js/old.js deleted file mode 100644 index ac3b912..0000000 --- a/js/old.js +++ /dev/null @@ -1,88 +0,0 @@ -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 index b83985e..a27d272 100644 --- a/js/sphere.js +++ b/js/sphere.js @@ -7,20 +7,21 @@ let sphereObject = { rotation: [0, 0, 0], //radians rotateInc: [0.0, 0.0, 0.0], //degreesInc rotateByBeat: true, - translate: [1, 1, 1], + translate: [0, 0, 0], total: 50, radius: 500, color: {r: 0, g: 0, b: 0}, colorByBeat: true, - drawMode: 0, + drawMode: 5, sphereMode: 0, lightPos: [0, 0, 0], pointSize: 2, steps: 512, - dirtyMode: false + dirtyMode: false, + light: 0.3 } -function readData() { +function readDataBar() { let items = sphereObject.steps; let dataArray = new Uint8Array(items); analyser.getByteFrequencyData(dataArray); @@ -36,6 +37,7 @@ function readData() { let sphereDataVectors = [], lastTotal = 0; +// avoid garbage collection each run function prepareData() { let total = sphereObject.total; if (lastTotal !== total) { @@ -52,7 +54,7 @@ function prepareData() { function setupSphere() { sphereData = []; - let data = readData(), + let data = readDataBar(), radData = data[0], map = VTUtils.map, total = sphereObject.total, @@ -94,14 +96,21 @@ function setupSphere() { function getAddRad(counter, data, total) { let mapping, rAdd, map = VTUtils.map; + let h = total / 2; 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 if (sphereObject.sphereMode === 4) { + if (counter > h) { + mapping = map(counter, h, total, 0, data.length - 1); + } else { + mapping = map(counter, 0, h, data.length - 1, 0); + } + rAdd = data[Math.round(mapping)] || 0; } else { mapping = map(counter, 0, total, 0, data.length - 1); rAdd = data[Math.round(mapping)] || 0; @@ -109,18 +118,16 @@ function getAddRad(counter, data, total) { return rAdd; } -let position, world, color, rotate, light, lightPos, lightAngle = 90; +let position, color, rotate, light, lightPos; 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(light, [sphereObject.light, 0, 0]); gl.uniform3fv(lightPos, sphereObject.lightPos); gl.uniform1f(pointSize, sphereObject.pointSize); } @@ -135,16 +142,15 @@ function draw() { 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 + 1 / aspect, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + sphereObject.translate[0], sphereObject.translate[1], sphereObject.translate[2], 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); @@ -152,7 +158,6 @@ function draw() { 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); @@ -186,12 +191,6 @@ function sphereMode(lat, lon, i, counter, rx, ry) { 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); @@ -202,6 +201,11 @@ function sphereMode(lat, lon, i, counter, rx, ry) { y = ry * sin(lat) * sin(lat); z = ry * cos(lon); break; + default: + x = rx * sin(lat) * cos(lon); + y = ry * sin(lat) * sin(lon); + z = ry * cos(lat); + break; } return {x: x, y: y, z: z}; } \ No newline at end of file diff --git a/js/utils.js b/js/utils.js index 68e1886..1b56266 100644 --- a/js/utils.js +++ b/js/utils.js @@ -131,7 +131,7 @@ class VTVector { this.z += z; } - setXYZ(x,y,z) { + setXYZ(x, y, z) { this.x = x || 0; this.y = y || 0; this.z = z || 0; @@ -236,3 +236,40 @@ class TDUtils { return d * Math.PI / 180; } } + +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]); + } +} \ No newline at end of file diff --git a/js/gui.js b/js/visualGUI.js similarity index 72% rename from js/gui.js rename to js/visualGUI.js index a16a30e..a98be8b 100644 --- a/js/gui.js +++ b/js/visualGUI.js @@ -1,40 +1,3 @@ -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"); @@ -43,7 +6,7 @@ function generateRotationSliders() { function generateTranslateSliders() { let group = $('#translate'); - generateSlider(["X", "Y", "Z"], -1, 1, 1, group, "translate"); + generateSlider(["X", "Y", "Z"], -1, 1, 0, group, "translate"); } function generateColorSliders() { @@ -54,20 +17,20 @@ function generateColorSliders() { function generateWorldSliders() { let group = $('#world'); - generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -1, 1, 1, group, "light"); - generateSlider(["LightAngle"], 0, 360, 90, group, "light"); + generateSlider(["LightPos-X", "LightPos-Y", "LightPos-Z"], -100, 100, 0, group, "light"); + generateSlider(["Light"], 0, 1, 0.3, group, "light"); } function generateDrawSliders() { let group = $('#draw'), g = "drawMode" generateSlider(["DrawMode"], 0, 6, 0, group, g); - generateSlider(["Form"], 0, 3, 0, group, g); + generateSlider(["Form"], 0, 4, 0, group, g); generateSlider(["Radius"], 20, 1500, 500, group, g); - generateSlider(["Total"], 0, 200, 50, group, g); + generateSlider(["Total"], 20, 250, 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); + generateSlider(["Smoothing"], 0, 100, 80, group, g, 1); + generateSlider(["Steps"], 256, 2048, 512, group, g, 16); generateCheckBox(["Dirty"], false, group, g) } @@ -102,11 +65,10 @@ function setColors() { 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)) + sphereObject.lightPos[0] = getValue($('#LightPos-X', group)); + sphereObject.lightPos[1] = getValue($('#LightPos-Y', group)); + sphereObject.lightPos[2] = getValue($('#LightPos-Z', group)); + sphereObject.light = getValue($('#Light', group)); } function setDraw() { @@ -118,7 +80,7 @@ function setDraw() { sphereObject.pointSize = getValue($('#PointSize', group)); sphereObject.steps = getValue($('#Steps', group)); sphereObject.dirtyMode = $('#drawModeDirty', group).checked; - analyser.smoothingTimeConstant = getValue($('#Smoothing', group)); + analyser.smoothingTimeConstant = getValue($('#Smoothing', group)) / 100; } function setTranslate() { @@ -183,6 +145,7 @@ function changeHandler(el) { } else if (d === "drawMode") { setDraw(); } + saveConfig(); } document.body.addDelegatedEventListener('input', 'group-input input', (ev, el) => { @@ -205,4 +168,60 @@ function getValue(slider) { $('.settings-icon').addEventListener('click', function () { $('.off-can').classList.toggle("closed"); + $('.settings-icon').classList.toggle("open"); +}) + +window.addEventListener('keyup', e => { + if (e.key === 'F11') { + c.requestFullscreen(); + return; + } + if (e.key === 'Escape') { + if (document.fullscreenElement) { + document.exitFullscreen().then(console.log); + } + } + if (e.key === 'p') { + audioFile.play(); + } + if (e.key === 's') { + audioFile.pause(); + } +}); + +function saveConfig() { + localStorage.setItem('config-sphere', JSON.stringify(sphereObject)); +} + +function loadConfig() { + let item = localStorage.getItem('config-sphere'); + if (item && item !== "") { + sphereObject = JSON.parse(item); + } +} + +let uploadField = $('#upload'); +let audioFile = new Audio(); +let lastSong = null; +uploadField.addEventListener('change', e => { + let file = uploadField.files[0]; + if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null && file.name.match(".wma") === null) { + if (lastSong) { + URL.revokeObjectURL(lastSong); + } + audioFile.src = URL.createObjectURL(file); + document.title = file.name; + lastSong = audioFile.src; + createView(); + } +}) + +$('#play').addEventListener('click', e => { + if (audioFile.src !== "") { + c.requestFullscreen().then(r => { + setTimeout(x => { + audioFile.play(); + }, 1000) + }); + } }) \ No newline at end of file diff --git a/out/gui/.gitkeep b/out/gui/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/out/gui/sphere.json b/out/gui/sphere.json new file mode 100644 index 0000000..6e39aa1 --- /dev/null +++ b/out/gui/sphere.json @@ -0,0 +1 @@ +[{"group":"rotation","name":["X","Y","Z"],"props":[0,1,2],"type":"slider","max":360,"min":0},{"group":"rotation","name":["X-Inc","Y-Inc","Z-Inc"],"props":[0,1,2],"type":"slider","max":1,"min":0,"stepSize":0.01},{"group":"rotation","name":"by-Beat","type":"checkbox","value":true,"tooltip":"Rotate the Sphere based by Beat
Low-Freq = Y, Mid-Freq = Z, High-Freq = X"},{"group":"color","name":["R","G","B"],"props":["r","g","b"],"type":"slider","max":255,"min":0},{"group":"light","name":["X","Y","Z"],"props":[0,1,2],"type":"slider","max":1,"min":-1,"value":0,"stepSize":0.1},{"group":"light","name":"Light","type":"slider","max":1,"min":0,"stepSize":0.05},{"group":"color","name":"by-Beat","type":"checkbox","value":true,"tooltip":"Draw The Color Strength based on the Low-Freq"},{"group":"draw","name":"DrawMode","type":"slider","max":6,"min":0},{"group":"draw","name":"Form","type":"slider","max":4,"min":0},{"group":"draw","name":"Radius","type":"slider","max":1500,"min":20},{"group":"draw","name":"Total","type":"slider","max":200,"min":20},{"group":"draw","name":"PointSize","type":"slider","max":10,"min":1,"stepSize":0.2},{"group":"draw","name":"Dirty","type":"checkbox","value":false,"tooltip":"Full Draw or with less Points"}] \ No newline at end of file diff --git a/out/icon-sprite.svg b/out/icon-sprite.svg new file mode 100644 index 0000000..f13e5ca --- /dev/null +++ b/out/icon-sprite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/out/js/scripts.js b/out/js/scripts.js new file mode 100644 index 0000000..dac8955 --- /dev/null +++ b/out/js/scripts.js @@ -0,0 +1,431 @@ +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; + } +} + +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]); + } +} +class ShaderHandler { + constructor(gl) { + this.gl = gl; + this.shaderNames = []; + this.shaders = {}; + this.programs = {}; + } + + setGL(gl) { + this.gl = gl; + } + + 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]; + } + + async loadArray(list, path) { + let self = this; + for (const e of list) { + await self.loadShader(e, path) + } + await self.createProgramForEach(list) + } +} +class AudioHandler { + async init() { + this.audioFile = new Audio(); + } +} +class Player { + async init() { + + } +} +class GUI { + init() { + + } +} +class Visual { + constructor() { + this.data = []; //for drawing + this.dataArray = []; + } + draw() { + } + + setup() { + } +} + +class VisualDrawer { + +} +class Sphere extends Visual { + draw() { + } + + setup() { + } +} +const shaderHandler = new ShaderHandler(null), + audioHandler = new AudioHandler(), + gui = new GUI(), + player = new Player(); + +async function startUP() { + let c = document.body.querySelector('#c'), + gl = c.getContext("webgl2"); + if (!gl) { + alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"); + return false; + } + shaderHandler.setGL(gl) + await shaderHandler.loadArray(["wave", "sphere"], 'shaders/'); + await audioHandler.init(); + await player.init(); + gui.init(); +} + +startUP().then(r => { + setTimeout(e => { + $('.loading-screen').remove(); + }, 100) +}); \ No newline at end of file diff --git a/out/js/scripts.min.js b/out/js/scripts.min.js new file mode 100644 index 0000000..ae2c780 --- /dev/null +++ b/out/js/scripts.min.js @@ -0,0 +1 @@ +class VTUtils{static random(t,e){let a=Math.random();if(void 0===t)return a;if(void 0===e)return t instanceof Array?t[Math.floor(a*t.length)]:a*t;if(t>e){let a=t;t=e,e=a}return a*(e-t)+t}static randomInt(t,e){return Math.floor(VTUtils.random(t,e))}static normalize(t,e,a){return(t-a)/(e-a)}static distance(t,e,a,r){let s=t-a,i=e-r;return Math.sqrt(s*s+i*i)}static map(t,e,a,r,s,i){let n=(t-e)/(a-e)*(s-r)+r;return i?r{let r=t.target;if(r.matches(e))a(t,r);else{let s=r.closest(e);s&&a(t,s)}})};class ShaderHandler{constructor(t){this.gl=t,this.shaderNames=[],this.shaders={},this.programs={}}setGL(t){this.gl=t}async loadShader(t,e){this.shaderNames.push(t),await this.load(t,e+t+".vert",this.gl.VERTEX_SHADER),await this.load(t,e+t+".frag",this.gl.FRAGMENT_SHADER)}async load(t,e,a){let r=t+"_"+a;if(!this.shaders[r]){let t=await fetch(e),s=this.createShader(await t.text(),a);s&&(this.shaders[r]=s)}return!!this.shaders[r]}getShader(t,e){let a=t+"_"+e;return this.shaders[a]}getAllShaders(){return this.shaderNames}async createProgramForEach(t){t=t||this.shaderNames;for(let e=0;e{setTimeout(t=>{$(".loading-screen").remove()},100)}); \ No newline at end of file diff --git a/out/theme/style.css b/out/theme/style.css new file mode 100644 index 0000000..118e5e6 --- /dev/null +++ b/out/theme/style.css @@ -0,0 +1 @@ +*{box-sizing:border-box}:focus{outline:0}body,html{padding:0;margin:0;overflow:hidden;font-size:16px;font-family:sans-serif;background-color:#000}div{position:fixed;color:#fff;padding:1em}.hide{display:none!important}.icon{width:1em;height:1em;vertical-align:middle;font-size:1em;shape-rendering:geometricPrecision;transition:transform .5s cubic-bezier(.22,.61,.36,1);stroke-width:5px;text-align:center;display:block}#c{width:100%;height:100%}group{display:block;padding-bottom:10px;user-select:none}group-label{display:block;border-bottom:1px solid #fff;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}.top-menu-left{display:flex}.top-menu-left div{position:relative}.loading-screen{z-index:100;background-color:#000;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;flex-direction:column}.loading-screen span{font-family:monospace;font-size:4vw;z-index:2}.loading-screen loader{position:absolute;top:calc(6vw + 10px);left:0;right:0;bottom:0;margin:auto;display:block;width:30vw;height:6px;transform:scaleX(0);transform-origin:left;background-color:#006ea8;animation:loadingBar 2s infinite}.loading-screen loader.delay{background-color:rgba(0,110,168,.24);filter:blur(1px);animation-delay:.05s}@keyframes loadingBar{0%,100%{transform:scaleX(0) scaleY(0)}50%{transform:scaleX(1) scaleY(1);transform-origin:left}100%,51%{transform-origin:right}}input[type=range]{width:100%}input[type=range]:focus{outline:0}input[type=range]:focus::-webkit-slider-runnable-track{background:#545454}input[type=range]:focus::-ms-fill-lower{background:#424242}input[type=range]:focus::-ms-fill-upper{background:#545454}input[type=range]::-webkit-slider-runnable-track{width:100%;height:25.6px;cursor:pointer;box-shadow:1px 1px 1px #000,0 0 1px #0d0d0d;background:#424242;border-radius:0;border:0 solid #010101}input[type=range]::-webkit-slider-thumb{height:25px;width:15px;border-radius:0;cursor:pointer;margin-top:.3px}switch input{position:absolute;appearance:none;opacity:0}switch input:checked+label:after{transform:translateX(20px)}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}input[type=file]{position:fixed;left:-100000vw;height:1px;width:1px}.controls{right:0;display:flex}.controls button,.menu-icon{background-color:rgba(33,33,33,.6);border:none;font-size:1.4em;border-top:4px solid #089bec;padding:1.5rem;cursor:pointer;color:#fff;transition:.5s}.controls button.active,.menu-icon.active{border-color:#ff066a}.controls button:hover,.menu-icon:hover{background-color:rgba(21,21,21,.7);border-color:#aaef22} \ No newline at end of file diff --git a/raw/gui/sphere.json b/raw/gui/sphere.json new file mode 100644 index 0000000..2f8936b --- /dev/null +++ b/raw/gui/sphere.json @@ -0,0 +1,134 @@ +[ + { + "group": "rotation", + "name": [ + "X", + "Y", + "Z" + ], + "props": [ + 0, + 1, + 2 + ], + "type": "slider", + "max": 360, + "min": 0 + }, + { + "group": "rotation", + "name": [ + "X-Inc", + "Y-Inc", + "Z-Inc" + ], + "props": [ + 0, + 1, + 2 + ], + "type": "slider", + "max": 1, + "min": 0, + "stepSize": 0.01 + }, + { + "group": "rotation", + "name": "by-Beat", + "type": "checkbox", + "value": true, + "tooltip": "Rotate the Sphere based by Beat
Low-Freq = Y, Mid-Freq = Z, High-Freq = X" + }, + { + "group": "color", + "name": [ + "R", + "G", + "B" + ], + "props": [ + "r", + "g", + "b" + ], + "type": "slider", + "max": 255, + "min": 0 + }, + { + "group": "light", + "name": [ + "X", + "Y", + "Z" + ], + "props": [ + 0, + 1, + 2 + ], + "type": "slider", + "max": 1, + "min": -1, + "value": 0, + "stepSize": 0.1 + }, + { + "group": "light", + "name": "Light", + "type": "slider", + "max": 1, + "min": 0, + "stepSize": 0.05 + }, + { + "group": "color", + "name": "by-Beat", + "type": "checkbox", + "value": true, + "tooltip": "Draw The Color Strength based on the Low-Freq" + }, + { + "group": "draw", + "name": "DrawMode", + "type": "slider", + "max": 6, + "min": 0 + }, + { + "group": "draw", + "name": "Form", + "type": "slider", + "max": 4, + "min": 0 + }, + { + "group": "draw", + "name": "Radius", + "type": "slider", + "max": 1500, + "min": 20 + }, + { + "group": "draw", + "name": "Total", + "type": "slider", + "max": 200, + "min": 20 + }, + { + "group": "draw", + "name": "PointSize", + "type": "slider", + "max": 10, + "min": 1, + "stepSize": 0.2 + }, + { + "group": "draw", + "name": "Dirty", + "type": "checkbox", + "value": false, + "tooltip": "Full Draw or with less Points" + } +] \ No newline at end of file diff --git a/raw/icons/.gitkeep b/raw/icons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/raw/javascript/app.js b/raw/javascript/app.js new file mode 100644 index 0000000..de5e54f --- /dev/null +++ b/raw/javascript/app.js @@ -0,0 +1,24 @@ +const shaderHandler = new ShaderHandler(null), + audioHandler = new AudioHandler(), + gui = new GUI(), + player = new Player(); + +async function startUP() { + let c = document.body.querySelector('#c'), + gl = c.getContext("webgl2"); + if (!gl) { + alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2"); + return false; + } + shaderHandler.setGL(gl) + await shaderHandler.loadArray(["wave", "sphere"], 'shaders/'); + await audioHandler.init(); + await player.init(); + gui.init(); +} + +startUP().then(r => { + setTimeout(e => { + $('.loading-screen').remove(); + }, 100) +}); \ No newline at end of file diff --git a/raw/javascript/audio.js b/raw/javascript/audio.js new file mode 100644 index 0000000..f8aa9dc --- /dev/null +++ b/raw/javascript/audio.js @@ -0,0 +1,5 @@ +class AudioHandler { + async init() { + this.audioFile = new Audio(); + } +} \ No newline at end of file diff --git a/raw/javascript/gui.js b/raw/javascript/gui.js new file mode 100644 index 0000000..7228c33 --- /dev/null +++ b/raw/javascript/gui.js @@ -0,0 +1,5 @@ +class GUI { + init() { + + } +} \ No newline at end of file diff --git a/raw/javascript/handler.js b/raw/javascript/handler.js new file mode 100644 index 0000000..a676610 --- /dev/null +++ b/raw/javascript/handler.js @@ -0,0 +1,95 @@ +class ShaderHandler { + constructor(gl) { + this.gl = gl; + this.shaderNames = []; + this.shaders = {}; + this.programs = {}; + } + + setGL(gl) { + this.gl = gl; + } + + 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]; + } + + async loadArray(list, path) { + let self = this; + for (const e of list) { + await self.loadShader(e, path) + } + await self.createProgramForEach(list) + } +} \ No newline at end of file diff --git a/raw/javascript/player.js b/raw/javascript/player.js new file mode 100644 index 0000000..ebe312f --- /dev/null +++ b/raw/javascript/player.js @@ -0,0 +1,5 @@ +class Player { + async init() { + + } +} \ No newline at end of file diff --git a/raw/javascript/utils.js b/raw/javascript/utils.js new file mode 100644 index 0000000..1b56266 --- /dev/null +++ b/raw/javascript/utils.js @@ -0,0 +1,275 @@ +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; + } +} + +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]); + } +} \ No newline at end of file diff --git a/raw/javascript/visual.js b/raw/javascript/visual.js new file mode 100644 index 0000000..5f8dbd7 --- /dev/null +++ b/raw/javascript/visual.js @@ -0,0 +1,15 @@ +class Visual { + constructor() { + this.data = []; //for drawing + this.dataArray = []; + } + draw() { + } + + setup() { + } +} + +class VisualDrawer { + +} \ No newline at end of file diff --git a/raw/javascript/visuals/sphere.js b/raw/javascript/visuals/sphere.js new file mode 100644 index 0000000..73c8b2c --- /dev/null +++ b/raw/javascript/visuals/sphere.js @@ -0,0 +1,7 @@ +class Sphere extends Visual { + draw() { + } + + setup() { + } +} \ No newline at end of file diff --git a/raw/scss/_controls.scss b/raw/scss/_controls.scss new file mode 100644 index 0000000..b1f7dc2 --- /dev/null +++ b/raw/scss/_controls.scss @@ -0,0 +1,24 @@ +.controls { + right: 0; + display: flex; +} + +.controls button, .menu-icon { + background-color: rgba(33, 33, 33, .6); + border: none; + font-size: 1.4em; + border-top: 4px solid #089bec; + padding: 1.5rem; + cursor: pointer; + color: #fff; + transition: .5s; + + &.active { + border-color: #ff066a; + } + + &:hover { + background-color: rgba(21, 21, 21, .7); + border-color: #aaef22; + } +} \ No newline at end of file diff --git a/raw/scss/_gui.scss b/raw/scss/_gui.scss new file mode 100644 index 0000000..01338cf --- /dev/null +++ b/raw/scss/_gui.scss @@ -0,0 +1,108 @@ +#c { + width: 100%; + height: 100%; +} + +group { + display: block; + padding-bottom: 10px; + user-select: none; +} + +group-label { + display: block; + border-bottom: 1px solid #fff; + font-size: 21px; + font-weight: 500; + user-select: none; +} + +group-input { + display: flex; + align-items: center; + margin-top: 5px; + user-select: none; + + label { + padding-right: 10px; + user-select: none; + width: 150px; + } + + input { + flex-grow: 1; + user-select: none; + max-width: 150px; + } + + button { + border: 1px solid #dcdcdc; + background-color: transparent; + color: #fff; + margin-left: 5px; + } +} + +.closed { + transform: translateX(-350px); + transition: all .5s; +} + +.top-menu-left { + display: flex; + div { + position: relative; + } +} + +.loading-screen { + z-index: 100; + background-color: #000; + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + + span { + font-family: monospace; + font-size: 4vw; + z-index: 2; + } + + loader { + position: absolute; + top: calc(6vw + 10px); + left: 0; + right: 0; + bottom: 0; + margin: auto; + display: block; + width: 30vw; + height: 6px; + transform: scaleX(0); + transform-origin: left; + background-color: #006ea8; + animation: loadingBar 2s infinite; + + &.delay { + background-color: rgba(0, 110, 168, 0.24); + filter: blur(1px); + animation-delay: .05s; + } + } +} + +@keyframes loadingBar { + 0%, 100% { + transform: scaleX(0) scaleY(0); + } + 50% { + transform: scaleX(1) scaleY(1); + transform-origin: left; + } + 51%, 100% { + transform-origin: right; + } +} \ No newline at end of file diff --git a/raw/scss/_input.scss b/raw/scss/_input.scss new file mode 100644 index 0000000..5bfe3bb --- /dev/null +++ b/raw/scss/_input.scss @@ -0,0 +1,79 @@ +input[type=range] { + width: 100%; + + &:focus { + outline: none; + } + + &:focus::-webkit-slider-runnable-track { + background: #545454; + } + + &:focus::-ms-fill-lower { + background: #424242; + } + + &:focus::-ms-fill-upper { + background: #545454; + } +} + +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 25.6px; + cursor: pointer; + box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d; + background: #424242; + border-radius: 0; + border: 0 solid #010101; +} + +input[type=range]::-webkit-slider-thumb { + height: 25px; + width: 15px; + border-radius: 0; + cursor: pointer; + margin-top: 0.3px; +} + +switch { + input { + position: absolute; + appearance: none; + opacity: 0; + + &:checked + label:after { + transform: translateX(20px); + } + } + + label { + display: block; + border-radius: 10px; + width: 40px; + height: 20px; + background-color: #dcdcdc; + position: relative; + cursor: pointer; + padding: 0; + + &:after { + content: ''; + background-color: #ff3232; + position: absolute; + top: 2px; + left: 2px; + height: 16px; + width: 16px; + border-radius: 10px; + transition: .5s; + } + } +} + +input[type="file"] { + position: fixed; + left: -100000vw; + height: 1px; + width: 1px; +} \ No newline at end of file diff --git a/raw/scss/style.scss b/raw/scss/style.scss new file mode 100644 index 0000000..5f04f9f --- /dev/null +++ b/raw/scss/style.scss @@ -0,0 +1,43 @@ +* { + box-sizing: border-box; +} + +*:focus { + outline: none; +} + +html, body { + padding: 0; + margin: 0; + overflow: hidden; + font-size: 16px; + font-family: sans-serif; + background-color: #000000; +} + +div { + position: fixed; + color: #fff; + padding: 1em; +} + +.hide { + display: none !important; +} + +.icon { + width: 1em; + height: 1em; + vertical-align: middle; + font-size: 1em; + shape-rendering: geometricPrecision; + transition: transform .5s cubic-bezier(.22, .61, .36, 1); + stroke-width: 5px; + text-align: center; + display: block; +} + + +@import "gui"; +@import "input"; +@import "controls"; \ No newline at end of file diff --git a/shaders/sphere.frag b/shaders/sphere.frag index 57589f2..987b45c 100644 --- a/shaders/sphere.frag +++ b/shaders/sphere.frag @@ -3,17 +3,17 @@ // 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 vec4 fragNormal; in vec3 v_surfaceToLight; uniform vec4 u_color; +uniform vec3 u_light; uniform vec3 u_lightPos; out vec4 outColor; void main() { - vec3 surfaceToLightDirection = normalize(v_surfaceToLight); + float light = max(dot(fragNormal.xyz, normalize(v_surfaceToLight).xyz), u_light.x); outColor = u_color; - float light = 1.0; - //outColor.rgb *= surfaceToLightDirection; + outColor.rgb *= light; } \ No newline at end of file diff --git a/shaders/sphere.vert b/shaders/sphere.vert index 45fc06f..2ed16f7 100644 --- a/shaders/sphere.vert +++ b/shaders/sphere.vert @@ -1,12 +1,11 @@ #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 vec4 fragNormal; out vec3 v_surfaceToLight; void main() { @@ -14,8 +13,7 @@ void main() { vec4 pos = a_position * u_matrix; gl_Position = pos; gl_PointSize = u_pointSize; - - vec3 surfaceWorldPosition = (u_world * pos).xyz; - + fragNormal = normalize(pos); + vec3 surfaceWorldPosition = (u_matrix * pos).xyz; v_surfaceToLight = u_lightPos - surfaceWorldPosition; } \ No newline at end of file diff --git a/shaders/test.frag b/shaders/wave.frag similarity index 100% rename from shaders/test.frag rename to shaders/wave.frag diff --git a/shaders/test.vert b/shaders/wave.vert similarity index 100% rename from shaders/test.vert rename to shaders/wave.vert diff --git a/style.css b/style.css deleted file mode 100644 index ccea8c6..0000000 --- a/style.css +++ /dev/null @@ -1,215 +0,0 @@ -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); -} -