<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width" />
<title>Minecraft model editor, maybe</title>
<script type="text/javascript" src="boundarray.js"></script>
<style type="text/css">
* {
margin: 0; padding: 0; border: 0;
height: 100%; overflow: hidden;
/*
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
*/
}
div {
background: #000 url("panorama_0.png") no-repeat;
background-size: 100% 100%;
-webkit-filter: blur(.75vh);
-moz-filter: blur(.75vh);
-ms-filter: blur(.75vh);
-o-filter: blur(.75vh);
filter: blur(.75vh);
transform: scale(1.03);
}
canvas { position: absolute; top: 0; left: 0; }
</style>
</head>
<body>
<div></div>
<canvas></canvas>
<script id="fragment" type="x-shader/fragment">
precision mediump float;
varying vec2 Texel;
uniform sampler2D sampler;
/* texture mapping happens here */
void main () { gl_FragColor = texture2D(sampler, Texel); }
</script><script id="vertex" type="x-shader/vertex">
uniform mat4 projection;
attribute vec3 coord;
attribute vec2 texel;
varying vec2 Texel;
uniform vec3 mRotate, mTranslate;
uniform vec3 vRotate, vTranslate;
// model = scale * rotate * translate
uniform float mScale;
uniform float vScale;
mat4 rotation_matrix (vec3 rotate) {
return mat4( // around X axis
1.0, 0.0, 0.0, 0.0,
0.0, cos(rotate.x), -sin(rotate.x), 0.0,
0.0, sin(rotate.x), cos(rotate.x), 0.0,
0.0, 0.0, 0.0, 1.0
) * mat4( // around Y axis
cos(rotate.y), 0.0, sin(rotate.y), 0.0,
0.0, 1.0, 0.0, 0.0,
-sin(rotate.y), 0.0, cos(rotate.y), 0.0,
0.0, 0.0, 0.0, 1.0
) * mat4( // around Z axis
cos(rotate.z), sin(rotate.z), 0.0, 0.0,
-sin(rotate.z), cos(rotate.z), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
mat4 scale_matrix (float scale) {
return mat4(
scale, 0.0, 0.0, 0.0,
0.0, scale, 0.0, 0.0,
0.0, 0.0, scale, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
/* OpenGL matrices appear to be flipped along the x-w diagonal.
1.0, 0.0, 0.0, 0.0, <-.
0.0, 1.0, 0.0, 0.0, <-+- projection
0.0, 0.0, 1.0, 0.0, <-'
0.0, 0.0, 0.0, 1.0
^ ^ ^
`----+----'
|
translate
*/
mat4 translation_matrix (vec3 translate) {
return mat4(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
translate.x, translate.y, translate.z, 1.0
);
}
/* coordinate transformations happen here */
void main () {
Texel = texel;
mat4 view
= rotation_matrix(vRotate)
* translation_matrix(vTranslate)
* scale_matrix(vScale);
mat4 model
= translation_matrix(mTranslate)
* scale_matrix(mScale)
* rotation_matrix(mRotate);
gl_Position = projection * view * model * vec4(coord, 1.0);
}
</script><script type="text/javascript">
"use strict";
var canvas = document.getElementsByTagName("canvas")[0];
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!gl) {
alert("sorry, you need webgl for this.");
//history.back();
}
gl.enable(gl.DEPTH_TEST);
/*
gl.depthFunc(gl.LESS);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
*/
gl.lineWidth(2);
function compile_shader (type, id) {
var shader = gl.createShader(type);
gl.shaderSource(shader, document.getElementById(id).innerHTML);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
console.log(gl.getShaderInfoLog(shader));
return shader;
}
var shader = gl.createProgram();
gl.attachShader(shader, compile_shader(gl.FRAGMENT_SHADER, "fragment"));
gl.attachShader(shader, compile_shader(gl.VERTEX_SHADER, "vertex"));
gl.linkProgram(shader);
if (!gl.getProgramParameter(shader, gl.LINK_STATUS))
console.log(gl.getProgramInfoLog(shader));
gl.useProgram(shader);
function load_buffer (data, type) {
var vbo = gl.createBuffer();
gl.bindBuffer(type, vbo);
gl.bufferData(type, data, gl.STATIC_DRAW);
return vbo;
}
function cached_uniform1f (pgm, dest) {
var u = gl.getUniformLocation(pgm, dest);
return function (v) { return gl.uniform1f(u, v); }
}
var cube = {
attributes: {
coord: gl.getAttribLocation(shader, "coord"),
texel: gl.getAttribLocation(shader, "texel")
},
vertices: load_buffer(new Float32Array([
// define the cube vertices
-1,-1, 1, 0/4, 3/4, // NWD - down face start
1,-1, 1, 1/4, 3/4, // NED
-1,-1,-1, 0/4, 2/4, // SWD
1,-1,-1, 1/4, 2/4, // SED - down face end, east face start
1, 1,-1, 1/4, 1/4, // SEU
1,-1, 1, 2/4, 2/4, // NED - north face start
1, 1, 1, 2/4, 1/4, // NEU - east face end
-1,-1, 1, 3/4, 2/4, // NWD - west face start
-1, 1, 1, 3/4, 1/4, // NWU - north face end
-1,-1,-1, 4/4, 2/4, // SWD
-1, 1,-1, 4/4, 1/4, // SWU - up face start, west face end
// GL_TEXTURE_WRAP_S/GL_REPEAT magic
1, 1,-1, 5/4, 1/4, // SEU
-1, 1, 1, 4/4, 0/4, // NWU
1, 1, 1, 5/4, 0/4 // NEU - up face end
]), gl.ARRAY_BUFFER),
elements: {
length: 14,
buffer: load_buffer(new Uint16Array([
// define the cube vertex draw order
0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13
]), gl.ELEMENT_ARRAY_BUFFER)
}
};
// bind buffer vertices as attribute coord
gl.bindBuffer(gl.ARRAY_BUFFER, cube.vertices);
gl.enableVertexAttribArray(cube.attributes.coord);
gl.vertexAttribPointer(
cube.attributes.coord, 3, gl.FLOAT, gl.FALSE, 20, 0
);
gl.enableVertexAttribArray(cube.attributes.texel);
gl.vertexAttribPointer(
cube.attributes.texel, 2, gl.FLOAT, gl.FALSE, 20, 12
);
gl.uniform = {};
function bind_uniform3fv (name, pgm, dest) {
var u = gl.getUniformLocation(pgm, dest);
var a = [ 0, 0, 0 ];
var s = function (d) {
gl.uniform3fv(u, d);
requestAnimationFrame(doFrame);
return d;
}
var q = function (a, s) {
var f = [].fill.bind(a);
return Object.defineProperties(a, {
"length": { writable: false },
"fill": {
get: function () {
return function (d) { f(d); return s(a); }
}
}
});
}
a = q(a, s);
Object.defineProperty(gl.uniform, name, {
get: function () { return a; },
set: function (d) { a = q(d, s); return s(a); }
});
return gl.uniform[name];
}
function cached_static_uniform3fv (pgm, dest) {
var u = gl.getUniformLocation(pgm, dest);
function upload () {
return gl.uniform3fv(u, this);
}
function reinit (d) {
var c = new Float32Array(d);
c.upload = upload.bind(c);
c.reinit = reinit.bind(c);
if (c.length) c.upload();
return c;
}
return reinit([]);
}
function cached_static_uniform_mat4fv (pgm, dest) {
var u = gl.getUniformLocation(pgm, dest);
function upload () {
return gl.uniformMatrix4fv(u, false, this);
}
function reinit (d) {
var c = new Float32Array(d);
c.upload = upload.bind(c);
c.reinit = reinit.bind(c);
if (c.length) c.upload();
return c;
}
return reinit([]);
}
var matrix = {
/* identity matrix does nothing
identity: function () { return new Float32Array([
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
])},
*/
// view matrix moves the camera.
view: {
scale: cached_uniform1f(shader, "vScale"),
translate: cached_static_uniform3fv(shader, "vTranslate").reinit([
0, 0, 3
]),
rotate: cached_static_uniform3fv(shader, "vRotate").reinit([
0, 0, 0
])
},
// model matrix moves the object.
// model matrix = scale * translate * rotate
model: {
scale: cached_uniform1f(shader, "mScale"),
translate: cached_static_uniform3fv(shader, "mTranslate").reinit([
0, 0, 1
]),
rotate: cached_static_uniform3fv(shader, "mRotate").reinit([
0, 0, 0
])
},
// projection matrix defines the view frustum
projection: cached_static_uniform_mat4fv(shader, "projection")
};
matrix.view.scale(1);
gl.clearColor(0, 0, 0, 0);
Math.TAU = 2 * Math.PI;
var theta = 0;
var lastframe;
var spinspeed = 1 / 20000;
function doFrame (time) {
if (time - lastframe < 1 / 60) return;
if (fullscreen.active()) {
requestAnimationFrame(doFrame);
if (lastframe) {
theta = theta + (time - lastframe) * spinspeed;
if (Math.TAU <= theta) theta = 0;
}
lastframe = time;
matrix.model.rotate[0] = 3 * theta;
matrix.model.rotate[1] = 5 * theta;
matrix.model.rotate[2] = 7 * theta;
matrix.model.rotate.upload();
}
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
matrix.model.scale(1.0);
gl.drawElements(gl.TRIANGLE_STRIP, cube.elements.length, gl.UNSIGNED_SHORT, 0);
}
var scrollspeed = 1 / 16;
document.onwheel = function (e) {
// this function shamelessly stolen from
// https://stackoverflow.com/users/3225372/martijn
function sign(x) {
return typeof x === 'number' ? x ? x < 0 ? -1 : 1 : x === x ? 0 : NaN : NaN;
}
matrix.view.translate[2] += sign(e.deltaY) * scrollspeed;
matrix.view.translate.upload();
if (!fullscreen.active()) requestAnimationFrame(doFrame);
}
var fullscreen = (function () {
var el = document.documentElement,
fs_off = document.exitFullscreen
|| document.webkitExitFullscreen
|| document.mozCancelFullScreen
|| document.msExitFullscreen,
fs_on = el.requestFullScreen
|| el.webkitRequestFullScreen
|| el.mozRequestFullScreen
|| el.msRequestFullscreen;
return {
off: fs_off.bind(document),
on: fs_on.bind(el),
active: function () {
return (
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
};
})()
onresize = function () {
gl.viewport(0, 0, innerWidth, innerHeight);
canvas.height = innerHeight;
canvas.width = innerWidth;
// for the projection matrix, we must first normalize
// the screen dimensions to somewhere near 1.
var hyp
= Math.sqrt(canvas.width * canvas.width
+ canvas.height * canvas.height);
/* for matrix
a, b, c, d,
e, f, g, h,
i, j, k, l,
m, n, o, p
x = a*x + e*y + i*z + m
y = b*x + f*y + j*z + n
z = c*x + g*y + k*z + o
*/
// 50mm lens = 48.8° diagonal FOV.
// var fov = Math.tan(Math.PI * 45 / 180 / 2);
var fov = Math.tan(Math.PI * 0.125);
/* orthographic
var zf = -100;
var zn = -0;
var xx = canvas.height / hyp;
var yy = canvas.width / hyp;
var zz = -1 / (zf - zn);
// if zz is 0, no z-ordering can be done.
matrix.projection = matrix.projection.reinit([
xx, 0, 0, 0,
0,yy, 0, 0,
0, 0,zz, 0,
0, 0, 0, 1
]);
//*/
// projection
var zf = -1.1;
var zn = -1;
var xx = canvas.height / hyp;
var yy = canvas.width / hyp;
var zt = zf - zn;
var zz = zt / zf;
var zw = fov;
matrix.projection = matrix.projection.reinit([
xx, 0, 0, 0,
0,yy, 0, 0,
0, 0,zz,zw,
0, 0,zt, 0
]);
//*/
/* ??
var fov = 1 / Math.tan(Math.PI * 45 / 180 / 2);
var ar = canvas.width / canvas.height;
matrix.projection = matrix.projection.reinit([
fov / ar, 0, 0, 0,
0, fov, 0, 0,
0, 0, 1, 2, // still have no idea why this isn't zero
0, 0,-1, 0
]);
//*/
if (!fullscreen.active()) requestAnimationFrame(doFrame);
}
document.onmousemove = function (e) {
if (fullscreen.active()) return;
matrix.model.rotate[0] = 2 * Math.PI * (e.clientY / (canvas.height - 1) - .5);
matrix.model.rotate[1] = 2 * Math.PI * (e.clientX / (canvas.width - 1) - .5);
matrix.model.rotate[2] = 0;
matrix.model.rotate.upload();
requestAnimationFrame(doFrame);
};
document.onclick = function () {
if (fullscreen.active()) {
fullscreen.off();
lastframe = undefined;
} else {
fullscreen.on();
requestAnimationFrame(doFrame);
}
};
// bind crap testing texture as attribute sampler
var texture = gl.createTexture();
gl.activeTexture(gl.TEXTURE0);
var image = new Image();
image.onload = function () {
if (
Number.isInteger(Math.log2(image.width)) &&
Number.isInteger(Math.log2(image.height))
) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
//gl.bindTexture(gl.TEXTURE_2D, null);
onresize();
} else {
alert(
"Only images with dimensions equal to powers of two can be " +
"used as textures.\nFor example, 16x16, 32x32, 64x128, etc."
);
}
}
gl.uniform1i(gl.getUniformLocation(shader, "sampler"), 0);
//image.src = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=";
image.src = "med4x4.png";
var input = document.createElement("input");
input.type = "file";
document.onclick = function () { input.click(); }
input.onchange = function () {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) { image.src = e.target.result; }
reader.readAsDataURL(input.files[0]);
}
}
</script>
</body>
</html>