Нет описания

trixl.html 17KB

    <!doctype html> <html> <head> <title>.trixl</title> <meta charset="utf-8" /> <style> body { overflow: hidden; } #stage { position: absolute; top: 0; left: 0; } #debug { position: absolute; bottom: 0; right: 0; } </style> </head> <body> <canvas id="stage"> sorry. your browser is from the dark ages. please initiate temporal leap to a minimally brighter future where browsers can investigate the third dimension. </canvas> <span id="debug"></span> <script type="gl/vertex-shader"> attribute vec3 pos; attribute vec3 normal; varying vec3 world_pos; varying vec3 lighting; uniform mat4 transform; void main() { world_pos = pos; gl_Position = vec4(transform * vec4(pos, 1)); highp vec3 ambientLight = vec3(0.6, 0.6, 0.6); highp vec3 directionalLightColor = vec3(0.5, 0.5, 0.75); highp vec3 directionalVector = vec3(0.85, 0.8, 0.75); // FIXME: transform should only be transform w/o view & perspective highp vec4 transformedNormal = transform * vec4(normal, 1.0); highp float directional = max(dot(normal, directionalVector), 0.0); lighting = ambientLight + (directionalLightColor * directional); } </script> <script type="gl/fragment-shader"> precision mediump float; uniform vec4 color; varying vec3 world_pos; varying vec3 lighting; void main() { if (color.x >= 0.0 && color.x <= 1.0) { gl_FragColor = vec4(color.rgb * lighting, color.a); } else { gl_FragColor = vec4(world_pos.x, world_pos.y, world_pos.z, 1); } } </script> <script> var polyfill = {}; polyfill.pointerLockElement = function() { return document.pointerLockElement || document.webkitPointerLockElement || document.mozPointerLockElement; } Element.prototype.requestPointerLock = Element.prototype.mozRequestPointerLock || Element.prototype.webkitRequestPointerLock || Element.prototype.requestPointerLock; polyfill.mouseEvent = function(ev) { ev.movementX = ev.movementX || ev.mozMovementX; ev.movementY = ev.movementY || ev.mozMovementY; return ev; } </script> <script src="gl.js"></script> <script src="geometry.js"></script> <script src="matrix.js"></script> <script src="glmatrix.js"></script> <script> mat4.multiplyMany = function() { var m = arguments[0]; for(var i = 1; i < arguments.length; i++) { m = mat4.multiply([], arguments[i], m); } return m; } var range = function(lo, hi) { var both = hi !== undefined, l = both ? lo : 0, h = both ? hi : lo; return Array.apply(null, Array(h - l)).map(function(_, i) { return l + i; }); } var rand = function(lo, hi) { switch (arguments.length) { case 0: return Math.random(); case 1: return Math.round(Math.random() * lo); default: return lo + Math.round(Math.random() * (hi - lo)); } } var rgbFromCss = function(color) { var el = document.createElement('div'); el.style.color = color; var style = getComputedStyle(el); var rgb = style.getPropertyCSSValue('color').getRGBColorValue(); var f = function(val) { return val.getFloatValue(CSSPrimitiveValue.CSS_NUMBER); } return [f(rgb.red), f(rgb.green), f(rgb.blue), f(rgb.alpha)]; } </script> <script> window.trixl = {}; trixl.stage = document.querySelector("#stage"); trixl.window = {w: window.innerWidth, h: window.innerHeight}; trixl.stage.width = trixl.window.w; trixl.stage.height = trixl.window.h; trixl.debug = document.querySelector("#debug"); trixl.debug_pos = function(pos) { var coord_html = function(coord) { var s = (coord.toString() + " ").slice(0, 6); if (coord < -1.0 || coord > 1.0) { return '<span style="color: red">' + s + '</span>'; } else { return '<span>' + s + '</span>'; } return s; } return coord_html(pos[0]) + ", " + coord_html(pos[1]) + ", " + coord_html(pos[2]); } var gl = trixl.gl = trixl.stage.getContext("webgl"); //gl.enable(gl.CULL_FACE); gl.enable(gl.DEPTH_TEST); trixl.camera = { pos: [0, 0, 5], focus: [0, 0, 10], up: [0, 1, 0] }; trixl.camera.front = function() { var front = vec3.subtract([], trixl.camera.focus, trixl.camera.pos); return vec3.normalize(front, front); } trixl.camera.strafe = function(front) { var strafe = vec3.cross([], front || trixl.camera.front(), trixl.camera.up); return vec3.normalize(strafe, strafe); } trixl.camera.forward = function(scale) { var front = trixl.camera.front(); vec3.scale(front, front, scale); vec3.add(trixl.camera.pos, trixl.camera.pos, front); vec3.add(trixl.camera.focus, trixl.camera.focus, front); } trixl.camera.sideways = function(scale) { var front = trixl.camera.front(); var strafe = trixl.camera.strafe(front); vec3.scale(strafe, strafe, scale); vec3.add(trixl.camera.pos, trixl.camera.pos, strafe); vec3.add(trixl.camera.focus, trixl.camera.focus, strafe); } trixl.camera.rotate = function(dx, dy) { var front = trixl.camera.front(); var strafe = trixl.camera.strafe(front); trixl.camera.rotateAround(-dx, trixl.camera.up); trixl.camera.rotateAround(-dy, strafe); } trixl.camera.rotateAround = function(angle, axis) { var front = trixl.camera.front(); var q = quat.setAxisAngle([], axis, angle); vec3.transformQuat(front, front, q); vec3.add(trixl.camera.focus, trixl.camera.pos, front); } var vs = document.querySelector("script[type='gl/vertex-shader']").textContent; var fs = document.querySelector("script[type='gl/fragment-shader']").textContent; var program = createProgram(vs, fs); gl.useProgram(program); var vertexPosBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(geometry.cube()), gl.STATIC_DRAW); program.vertexPosAttrib = gl.getAttribLocation(program, 'pos'); gl.enableVertexAttribArray(program.vertexPosAttrib); gl.vertexAttribPointer(program.vertexPosAttrib, 3, gl.FLOAT, false, 0, 0); var vertexNormalBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexNormalBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(geometry.cube.normals()), gl.STATIC_DRAW); program.vertexNormalAttrib = gl.getAttribLocation(program, 'normal'); gl.enableVertexAttribArray(program.vertexNormalAttrib); gl.vertexAttribPointer(program.vertexNormalAttrib, 3, gl.FLOAT, false, 0, 0); program.color = gl.getUniformLocation(program, 'color'); gl.uniform4f(program.color, -1, -1, -1, -1); trixl.color = [0, 0, 0, 1]; trixl.world = new Map([ [[0, 0, 2], {color: [0.7, 0, 0, 1]}], [[-3.0, +0.0, 2], {color: [0.5, 0.5, 0.5, 1]}], [[+3.0, +0.0, 2], {color: [0.5, 0.5, 0.5, 1]}], [[+0.0, -3.0, 2], {color: [0.5, 0.5, 0.5, 1]}], [[+0.0, +3.0, 2], {color: [0.5, 0.5, 0.5, 1]}] ]); trixl.world.find = function(pos) { var key = null; for(var k of trixl.world.keys()) { if (k[0] === pos[0] && k[1] === pos[1] && k[2] === pos[2]) { key = k; break; } } return key; } trixl.world.remove = function(pos) { return trixl.world.delete(trixl.world.find(pos)); } trixl.world.dynamic = []; trixl.dynamic = {}; trixl.dynamic.orbit = function(pos, radius, color) { var color = color || trixl.color; return function(t) { var x = radius * Math.sin(t * 0.01), y = radius * Math.cos(t * 0.01); return {x: pos[0] + x, y: pos[1] + y, z: pos[2], color: color}; } } trixl.dynamic.sin = function(pos, width, height, color) { var color = color || trixl.color; return function(t) { var x = (t * 0.01) % width, y = height * (Math.sin(t * 0.01) + 1.0) * 0.5; return {x: pos[0] + x, y: pos[1] + y, z: pos[2], color: color}; } } trixl.dynamic.circleDance = function(pos, radius, height, color) { var color = color || trixl.color; var scale = 1 / (10 * radius) return function(t) { var x = radius * Math.sin(t * scale), y = height * (Math.sin(t * scale / height) + 1.0) * 0.5, z = radius * Math.cos(t * scale); return {x: pos[0] + x, y: pos[1] + y, z: pos[2] + z, color: color}; } } trixl.dynamic.sinWave = function(pos, length, width, height, color) { var wavies = []; for (var i = 0; i < length; i++) { var sin = trixl.dynamic.sin(pos, width, height, color); var wavie = function(i) { return function(t) { return sin(t - i * 250); } }(i); wavies.push(wavie); } return wavies; } trixl.dynamic.jumping = function(lo, hi, interval, color) { var pos = [rand(lo, hi), rand(lo, hi), rand(lo, hi)]; var last = 0; var interval = interval || 1000; var color = color || trixl.color; return function(t) { if (Math.abs(last - t) > interval) { pos = [rand(lo, hi), rand(lo, hi), rand(lo, hi)]; last = t; } return {x: pos[0], y: pos[1], z: pos[2], color: color}; } } trixl.dynamic.blinking = function(pos, interval, color) { var interval = interval || 5000; var color = color || trixl.color; return function(t) { var c= color; c[3] = Math.sin((t % interval) / interval * Math.PI); return {x: pos[0], y: pos[1], z: pos[2], color: c}; } } trixl.generate = {}; trixl.generate.random = function(lo, hi, color) { var x = lo + Math.round(Math.random() * (hi - lo)), y = lo + Math.round(Math.random() * (hi - lo)), z = lo + Math.round(Math.random() * (hi - lo)), color = color || [Math.random(), Math.random(), Math.random(), 1]; trixl.world.set([x, y, z], {color: color || trixl.color}); } trixl.generate.many = function(n, lo, hi, color) { Array.apply(null, Array(n)).map(function() { trixl.generate.random(lo, hi, color) }); } trixl.generate.fun = function() { trixl.generate.many(10, -3, 3); trixl.generate.many(100, -25, 25); trixl.generate.many(1000, -100, 100); } trixl.generate.tower = function(pos, width, height, color) { range(height).map(function() { trixl.world.set([ rand(pos[0], pos[0] + width), rand(pos[1], pos[1] + height), rand(pos[2], pos[2] + width) ], {color: color || [Math.random(), Math.random(), Math.random(), 1]}); }); } trixl.generate.city = function(n, width, height) { range(n).map(function() { var pos = [rand(width), 0, rand(width)]; var w = rand(5, 20); var h = rand(height); trixl.generate.tower(pos, w, h); }); } trixl.generate.jumpers = function(n, lo, hi, loI, hiI) { range(n).map(function() { var color = [Math.random(), Math.random(), Math.random(), 1]; var interval = rand(loI || 10000, hiI || 100000); trixl.world.dynamic.push(trixl.dynamic.jumping(lo, hi, interval, color)); }); } trixl.geometry = {}; trixl.geometry.circle = function(pos, radius, color) { range(0, 2 * radius).map(function(i) { trixl.world.set([ pos[0] + radius * Math.sin(i * Math.PI / radius), pos[1] + radius * Math.cos(i * Math.PI / radius), pos[2] ], {color: color || trixl.color}); }); } trixl.geometry.sphere = function(pos, radius, color) { for (var i = 0; i < 2 * Math.PI; i += Math.PI / radius) { for (var j = 0; j < Math.PI; j += Math.PI / radius) { var x = pos[0] + radius * Math.cos(i) * Math.sin(j), y = pos[1] + radius * Math.sin(i) * Math.sin(j), z = pos[2] + radius * Math.cos(j); trixl.world.set([x, y, z], {color: color}); } } } trixl.geometry.plane = function(pos, v, w, width, height, color) { var v = vec3.normalize([], v), w = vec3.normalize([], w); for (var i = 0; i < width; i++) { for (var j = 0; j < height; j++) { var p = vec3.clone(pos); vec3.scaleAndAdd(p, p, v, i); vec3.scaleAndAdd(p, p, w, j); trixl.world.set([ Math.round(p[0]), Math.round(p[1]), Math.round(p[2]) ], {color: color || trixl.color}); } } } program.transform = gl.getUniformLocation(program, 'transform'); trixl.redraw = function(t) { var view = mat4.lookAt([], trixl.camera.pos, trixl.camera.focus, trixl.camera.up); var aspect = trixl.window.w / trixl.window.h; var transform = function(pos) { var m = mat4.create(); mat4.translate(m, m, [-0.5 + pos[0], -0.5 + pos[1], -0.5 + pos[2]]); mat4.multiply(m, view, m); mat4.multiply(m, matrix.perspective(Math.PI / 3, aspect), m); return m; } gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); for (var pair of trixl.world) { var pos = pair[0], data = pair[1]; var color = data ? (data.color.apply ? data.color(pos, t) : data.color) : trixl.color; gl.uniform4fv(program.color, color); gl.uniformMatrix4fv(program.transform, false, transform(pos)); gl.drawArrays(gl.TRIANGLES, 0, 6 * 6); } trixl.world.dynamic.forEach(function(tr) { var tr = tr(t); gl.uniform4fv(program.color, tr.color); gl.uniformMatrix4fv(program.transform, false, transform([tr.x, tr.y, tr.z])); gl.drawArrays(gl.TRIANGLES, 0, 6 * 6); }); gl.uniform4f(program.color, -1, -1, -1, 0); } trixl.step = function(t) { trixl.step.reqId = requestAnimationFrame(trixl.step); trixl.redraw(t); } trixl.start = function() { trixl.step.reqId = requestAnimationFrame(trixl.step); } trixl.stop = function() { cancelAnimationFrame(trixl.step.reqId); trixl.step.reqId = null; } trixl.start(); trixl.speed = { move: 0.1, turn: 0.01 } trixl.input = {}; trixl.input.active = true; trixl.input.keys = new Set(); document.addEventListener("keydown", function(ev) { trixl.input.keys.add(ev.keyCode); var isPressed = function(keyCode) { return trixl.input.keys.has(keyCode); } if (!trixl.input.active) { return; } if (isPressed(32)) { // space if (trixl.step.reqId == null) { trixl.start(); } else { trixl.stop(); } } if (isPressed(37) || isPressed(65)) { // left || a trixl.camera.sideways(-trixl.speed.move); } if (isPressed(38) || isPressed(87)) { // up || w trixl.camera.forward(-trixl.speed.move); } if (isPressed(39) || isPressed(68)) { // right || d trixl.camera.sideways(trixl.speed.move); } if (isPressed(40) || isPressed(83)) { // down || s trixl.camera.forward(trixl.speed.move); } }); document.addEventListener("keyup", function(ev) { trixl.input.keys.delete(ev.keyCode); }); trixl.input.mouse = {last: null}; trixl.stage.addEventListener("mousemove", function(ev) { var mouse = trixl.input.mouse; if (mouse.last === null) { mouse.last = {x: ev.clientX, y: ev.clientY}; } ev = polyfill.mouseEvent(ev); var diff = { x: -ev.movementX || mouse.last.x - ev.clientX, y: -ev.movementY || mouse.last.y - ev.clientY }; trixl.camera.rotate(diff.x * trixl.speed.turn, diff.y * trixl.speed.turn); mouse.last = {x: ev.clientX, y: ev.clientY}; }); trixl.stage.addEventListener("mousedown", function(ev) { if (polyfill.pointerLockElement() === null) { trixl.stage.requestPointerLock(); } }); window.onresize = function() { trixl.window.w = window.innerWidth; trixl.window.h = window.innerHeight; trixl.stage.width = trixl.window.w; trixl.stage.height = trixl.window.h; gl.viewport(0, 0, trixl.window.w, trixl.window.h); trixl.redraw(); } trixl.importPixls = function(url) { var xhr = new XMLHttpRequest(); xhr.open('GET', url || 'http://pixl.papill0n.org/world'); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { var world = JSON.parse(xhr.responseText); var keys = Object.keys(world); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var coords = key.split(',').map(function(n) { return parseInt(n); }); var color = rgbFromCss(world[key].color) trixl.world.set([-coords[0], -coords[1], 0], {color: [color[0] / 255.0, color[1] / 255, color[2] / 255, color[3]]}); } } } xhr.send(); } </script> <script> trixl.ui = {} var editor = trixl.ui.editor = document.createElement("textarea"); trixl.ui.editor.style = "position: absolute; top: 0; right: 0; height: 100%; width: 30%; overflow: hidden; background-color: transparent"; trixl.ui.editor.style.display = "none"; document.body.appendChild(trixl.ui.editor); editor.onfocus = function() { trixl.input.active = false; } editor.onblur = function() { trixl.input.active = true; } editor.value = localStorage["org.papill0n.trixl.script"] || ""; editor.onchange = function() { localStorage["org.papill0n.trixl.script"] = editor.value; } document.addEventListener("keydown", function(ev) { if (ev.keyCode == 69) { // e if (editor != document.activeElement) { if (editor.style.display == "none") { editor.style.display = "inherit"; editor.focus(); } else { editor.style.display = "none"; } ev.preventDefault(); } } if (ev.ctrlKey && ev.keyCode == 13) { // ctrl+enter if (editor == document.activeElement) { var sel = editor.value.substring(editor.selectionStart, editor.selectionEnd); eval(sel); } } if (ev.keyCode == 27) { // escape if (editor == document.activeElement) { editor.style.display = "none"; editor.blur(); } } }); </script> <script> trixl.script = {}; trixl.script.load = function(name) { var xhr = new XMLHttpRequest(); xhr.open('GET', '/script/' + name); xhr.onreadystatechange = function(ev) { if (xhr.readyState == 4) { var scriptEl = document.createElement('script'); scriptEl.textContent = xhr.responseText; document.body.appendChild(scriptEl); } } xhr.send(); }; trixl.script.save = function(name, script) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/script/' + name); xhr.send(script); } </script> </body> </html>