|
<!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>
|