console
class Canvas {
constructor(container) {
this.elem = document.getElementById(container);
this.width = 0;
this.height = 0;
}
enableFullscreen(style) {
if (
document.fullscreenEnabled ||
document.webkitFullscreenEnabled ||
document.mozFullScreenEnabled ||
document.msFullscreenEnabled
) {
this.bfs = document.createElement("button");
this.bfs.appendChild(document.createTextNode("Fullscreen"));
this.elem.parentElement.appendChild(this.bfs);
for (let s in style) this.bfs.style[s] = style[s];
this.bfs.addEventListener('click', e => {
e.preventDefault();
this.requestFullscreen();
});
}
}
requestFullscreen() {
if (this.elem.requestFullscreen) {
this.elem.requestFullscreen();
} else if (this.elem.webkitRequestFullscreen) {
this.elem.webkitRequestFullscreen();
} else if (this.elem.mozRequestFullScreen) {
this.elem.mozRequestFullScreen();
} else if (this.elem.msRequestFullscreen) {
this.elem.msRequestFullscreen();
}
}
}
class Pointer {
constructor(canvas) {
this.x = 0;
this.y = 0;
this.z = 0;
this.xold = 0;
this.yold = 0;
this.zold = 0;
this.isDown = false;
this.canvas = canvas;
window.addEventListener('mousemove', e => this.move(e), false);
canvas.elem.addEventListener('touchmove', e => this.move(e), false);
window.addEventListener('mousedown', e => this.down(e), false);
window.addEventListener('touchstart', e => this.down(e), false);
window.addEventListener('mouseup', e => this.up(e), false);
window.addEventListener('touchend', e => this.up(e), false);
window.addEventListener('wheel', e => this.wheel(e), false);
}
down(e) {
if (e.target !== this.canvas.elem) return;
this.move(e);
this.xold = this.x;
this.yold = this.y;
this.isDown = true;
}
up(e) {
this.isDown = false;
}
move(e) {
const touchMode = e.targetTouches;
let pointer = null;
if (touchMode) {
e.preventDefault();
if (touchMode.length > 1) {
const dx = touchMode[0].clientX - touchMode[1].clientX;
const dy = touchMode[0].clientY - touchMode[1].clientY;
const d = dx * dx + dy * dy;
this.z += (d > this.zold) ? -0.2 : 0.2;
this.zold = d;
return;
}
pointer = touchMode[0];
} else pointer = e;
this.x = pointer.clientX;
this.y = pointer.clientY;
}
wheel(e) {
e.preventDefault();
this.z += e.deltaY > 0 ? -1 : 1;
}
}
class WebGL {
constructor(canvas, options) {
this.canvas = canvas;
this.gl = this.canvas.elem.getContext("webgl", options);
if (!this.gl) this.gl = this.canvas.elem.getContext("experimental-webgl", options);
if (!this.gl) throw new Error('This browser does not support WebGL');
this.width = 0;
this.height = 0;
this.aspect = 0;
this.textureUnits = [];
for (let i = 0; i < 16; ++i) {
this.textureUnits.push(null);
}
this.vertexUnits = [];
for (let i = 0; i < 16; ++i) {
this.vertexUnits.push({
enabled: false,
drawable: null,
idx: null
});
}
this.currentShader = null;
}
getExtension(name) {
const ext = this.gl.getExtension(name);
if (!ext) {
throw new Error('WebGL Extension not supported: ' + name);
}
return ext;
}
adjustSize() {
const canvasWidth = (this.canvas.elem.offsetWidth * 1) || 2;
const canvasHeight = (this.canvas.elem.offsetHeight * 1) || 2;
if (this.width !== canvasWidth || this.height !== canvasHeight) {
this.width = this.canvas.width = this.canvas.elem.width = canvasWidth;
this.height = this.canvas.height = this.canvas.elem.height = canvasHeight;
this.aspect = this.width / this.height;
}
return this;
}
viewport(left = 0, top = 0, width = this.width, height = this.height) {
this.gl.viewport(left, top, width, height);
return this;
}
cullFace(value = true) {
if (value) {
this.gl.enable(this.gl.CULL_FACE);
} else {
this.gl.disable(this.gl.CULL_FACE);
}
return this;
}
clearColor(r = 0, g = 0, b = 0, a = 1) {
this.gl.clearColor(r, g, b, a);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
return this;
}
clearDepth(depth = 1) {
this.gl.clearDepth(depth);
this.gl.clear(this.gl.DEPTH_BUFFER_BIT);
return this;
}
depthTest(value = true) {
if (value) {
this.gl.enable(this.gl.DEPTH_TEST);
} else {
this.gl.disable(this.gl.DEPTH_TEST);
}
return this;
}
texture(params) {
return new Texture(this, params);
}
framebuffer() {
return new Framebuffer(this);
}
depthbuffer() {
return new Depthbuffer(this);
}
shader(params) {
return new Shader(this, params);
}
drawable(params) {
return new Drawable(this, params);
}
filter(size, filter) {
return new Filter(this, size, filter);
}
vec3(x = 0, y = 0, z = 0) {
return new Vec3(x, y, z);
}
mat3(data) {
return new Mat3(data);
}
mat4(data) {
return new Mat4(data);
}
meshesPointers() {
return [
{
name: 'position',
size: 3,
offset: 0,
stride: 6
}, {
name: 'normal',
size: 3,
offset: 3,
stride: 6
}
];
}
quad() {
return {
pointers: [{
name: 'position',
size: 2,
offset: 0,
stride: 2
}],
vertexSize: 2,
vertices: [
-1, -1, 1, -1, 1, 1,
-1, 1, -1, -1, 1, 1
]
};
}
plane(s) {
return {
pointers: this.meshesPointers(),
vertexSize: 6,
vertices: [
-s, 0, -s, 0, 1, 0,
-s, 0, s, 0, 1, 0,
s, 0, s, 0, 1, 0,
s, 0, -s, 0, 1, 0,
-s, 0, -s, 0, 1, 0,
s, 0, s, 0, 1, 0
]
};
}
cube(x = 1, y = 1, z = 1) {
return {
pointers: this.meshesPointers(),
vertexSize: 6,
vertices: [
-x, -y, -z, 0, 0, -1,
-x, y, -z, 0, 0, -1,
x, y, -z, 0, 0, -1,
x, -y, -z, 0, 0, -1,
-x, -y, -z, 0, 0, -1,
x, y, -z, 0, 0, -1,
x, y, z, 0, 0, 1,
-x, y, z, 0, 0, 1,
-x, -y, z, 0, 0, 1,
x, y, z, 0, 0, 1,
-x, -y, z, 0, 0, 1,
x, -y, z, 0, 0, 1,
-x, y, -z, 0, 1, 0,
-x, y, z, 0, 1, 0,
x, y, z, 0, 1, 0,
x, y, -z, 0, 1, 0,
-x, y, -z, 0, 1, 0,
x, y, z, 0, 1, 0,
x, -y, z, 0, -1, 0,
-x, -y, z, 0, -1, 0,
-x, -y, -z, 0, -1, 0,
x, -y, z, 0, -1, 0,
-x, -y, -z, 0, -1, 0,
x, -y, -z, 0, -1, 0,
-x, -y, -z, -1, 0, 0,
-x, -y, z, -1, 0, 0,
-x, y, z, -1, 0, 0,
-x, y, -z, -1, 0, 0,
-x, -y, -z, -1, 0, 0,
-x, y, z, -1, 0, 0,
x, y, z, 1, 0, 0,
x, -y, z, 1, 0, 0,
x, -y, -z, 1, 0, 0,
x, y, z, 1, 0, 0,
x, -y, -z, 1, 0, 0,
x, y, -z, 1, 0, 0
]
};
}
sphere(radius = 1, res = 36) {
const nx = [];
const ny = [];
const nz = [];
const vertices = [];
for (let i = 0; i <= res; i++) {
const theta = i * Math.PI / res;
const sinTheta = Math.sin(theta);
const cosTheta = Math.cos(theta);
for (let j = 0; j <= res; j++) {
const phi = -j * 2 * Math.PI / res;
nx.push(Math.cos(phi) * sinTheta)
ny.push(cosTheta);
nz.push(Math.sin(phi) * sinTheta);
}
}
for (let i = 0; i < res; i++) {
for (let j = 0; j < res; j++) {
const first = (i * (res + 1)) + j;
const second = first + res + 1;
vertices.push(
nx[first] * radius,
ny[first] * radius,
nz[first] * radius,
nx[first],
ny[first],
nz[first],
nx[second] * radius,
ny[second] * radius,
nz[second] * radius,
nx[second],
ny[second],
nz[second],
nx[first + 1] * radius,
ny[first + 1] * radius,
nz[first + 1] * radius,
nx[first + 1],
ny[first + 1],
nz[first + 1],
nx[second] * radius,
ny[second] * radius,
nz[second] * radius,
nx[second],
ny[second],
nz[second],
nx[second + 1] * radius,
ny[second + 1] * radius,
nz[second + 1] * radius,
nx[second + 1],
ny[second + 1],
nz[second + 1],
nx[first + 1] * radius,
ny[first + 1] * radius,
nz[first + 1] * radius,
nx[first + 1],
ny[first + 1],
nz[first + 1]
);
}
}
return {
pointers: this.meshesPointers(),
vertexSize: 6,
vertices: vertices
};
}
cylinder ( radius = 1, res = 36) {
let angle = 0;
const alpha = 2 * Math.PI / res;
const vertices = [];
for ( let i = 0; i < res; i++) {
const c0 = Math.cos(angle);
const s0 = Math.sin(angle);
const c1 = Math.cos(angle + alpha);
const s1 = Math.sin(angle + alpha);
vertices.push(
c1 * radius, s1 * radius, -1, c1, s1, -1,
c0 * radius, s0 * radius, 1, c0, s0, 1,
c0 * radius, s0 * radius, -1, c0, s0, -1,
c1 * radius, s1 * radius, -1, c1, s1, -1,
c1 * radius, s1 * radius, 1, c1, s1, 1,
c0 * radius, s0 * radius, 1, c0, s0, 1,
c0 * radius, s0 * radius, -1, c0, s0, -1,
0, 0, -1, 0, 0, -1,
c1 * radius, s1 * radius, -1, c1, s1, -1,
c1 * radius, s1 * radius, 1, c1, s1, 1,
0, 0, -1, 0, 0, 1,
c0 * radius, s0 * radius, 1, c0, s0, 1
);
angle += alpha;
}
return {
pointers: this.meshesPointers(),
vertexSize: 6,
vertices: vertices
};
}
}
class Shader {
constructor(webGL, shaders) {
this.webGL = webGL;
this.gl = webGL.gl;
this.program = this.gl.createProgram();
this.vs = this.gl.createShader(this.gl.VERTEX_SHADER);
this.fs = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.attachShader(this.program, this.vs);
this.gl.attachShader(this.program, this.fs);
this.compileShader(this.vs, shaders.vertex);
this.compileShader(this.fs, shaders.fragment);
this.link();
this.uniformCache = {};
this.attributeCache = {};
this.samplers = {};
this.unitCounter = 0;
}
compileShader(shader, source) {
const boilerplate = `
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp int;
precision highp float;
#else
precision mediump int;
precision mediump float;
#endif
#define PI 3.141592653589793
`;
this.gl.shaderSource(shader, boilerplate + '\n' + source);
this.gl.compileShader(shader);
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
throw new Error(this.gl.getShaderInfoLog(shader));
}
}
attributeLocation(name) {
let location = this.attributeCache[name];
if (location === void 0) {
location = this.attributeCache[name] = this.gl.getAttribLocation(this.program, name);
}
return location;
}
uniformLocation(name) {
let location = this.uniformCache[name];
if (location === void 0) {
location = this.uniformCache[name] = this.gl.getUniformLocation(this.program, name);
}
return location;
}
link() {
this.gl.linkProgram(this.program);
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
throw new Error(this.gl.getProgramInfoLog(this.program));
}
}
use() {
if (this.webGL.currentShader !== this) {
this.webGL.currentShader = this;
this.gl.useProgram(this.program);
}
return this;
}
draw(drawable) {
drawable.setPointersForShader(this)
.draw();
return this;
}
int(name, value) {
const loc = this.uniformLocation(name);
if (loc) {
this.gl.uniform1i(loc, value);
}
return this;
}
sampler(name, texture) {
let unit = this.samplers[name];
if (unit === void 0) {
unit = this.samplers[name] = this.unitCounter++;
}
texture.bind(unit);
this.int(name, unit);
return this;
}
vec2(name, a, b) {
const loc = this.uniformLocation(name);
if (loc) {
this.gl.uniform2f(loc, a, b);
}
return this;
}
vec3(name, a, b, c) {
const loc = this.uniformLocation(name);
if (loc) {
this.gl.uniform3f(loc, a, b, c);
}
return this;
}
mat4(name, value) {
const loc = this.uniformLocation(name);
if (loc) {
if (value instanceof Mat4) {
this.gl.uniformMatrix4fv(loc, this.gl.FALSE, value.data);
} else {
this.gl.uniformMatrix4fv(loc, this.gl.FALSE, value);
}
}
return this;
}
mat3(name, value) {
const loc = this.uniformLocation(name);
if (loc) {
if (value instanceof Mat3) {
this.gl.uniformMatrix3fv(loc, this.gl.FALSE, value.data);
} else {
this.gl.uniformMatrix3fv(loc, this.gl.FALSE, value);
}
}
return this;
}
float(name, value) {
const loc = this.uniformLocation(name);
if (loc) {
this.gl.uniform1f(loc, value);
}
return this;
}
}
class Drawable {
constructor(webGL, obj) {
this.pointers = obj.pointers;
this.webGL = webGL;
this.gl = webGL.gl;
this.buffer = this.gl.createBuffer();
this.mode = this.gl.TRIANGLES;
this.vertexSize = obj.vertexSize;
this.upload(new Float32Array(obj.vertices));
}
upload(vertices) {
this.size = vertices.length / this.vertexSize;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
return this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
}
setPointersForShader(shader) {
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer);
for (let i = 0, len = this.pointers.length; i < len; ++i) {
const pointer = this.pointers[i];
this.setPointer(shader, pointer, i);
}
return this;
}
setPointer(shader, pointer, idx) {
const location = shader.attributeLocation(pointer.name);
if (location >= 0) {
const unit = this.webGL.vertexUnits[location];
if (!unit.enabled) {
unit.enabled = true;
this.gl.enableVertexAttribArray(location);
}
if (unit.drawable !== this || unit.idx !== idx) {
const float_size = Float32Array.BYTES_PER_ELEMENT;
unit.idx = idx;
unit.drawable = this;
this.gl.vertexAttribPointer(
location,
pointer.size,
this.gl.FLOAT,
false,
pointer.stride * float_size,
pointer.offset * float_size
);
}
}
return this;
}
draw(first = 0, size = this.size, mode = this.mode) {
this.gl.drawArrays(mode, first, size);
return this;
}
}
class Vec3 {
constructor(x = 0.0, y = 0.0, z = 0.0) {
this.x = x;
this.y = y;
this.z = z;
}
set(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
copy(v) {
this.x = v.x;
this.y = v.y;
this.z = v.z;
return this;
}
get() {
return [this.x, this.y, this.z];
}
distance(b) {
const dx = b.x - this.x;
const dy = b.y - this.y;
const dz = b.z - this.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
transformMat4(v, mat) {
const m = mat.data;
const x = v.x;
const y = v.y;
const z = v.z;
const w = (m[3] * x + m[7] * y + m[11] * z + m[15]) || 1.0;
this.x = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w;
this.y = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w;
this.z = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w;
return this;
}
}
class Mat3 {
constructor() {
this.data = new Float32Array(9);
this.ident();
}
ident() {
const d = this.data;
d[0] = 1;
d[1] = 0;
d[2] = 0;
d[3] = 0;
d[4] = 1;
d[5] = 0;
d[6] = 0;
d[7] = 0;
d[8] = 1;
return this;
}
fromMat4Rot(source) {
return source.toMat3Rot(this);
}
}
class Mat4 {
constructor() {
this.data = new Float32Array(16);
this.ident();
}
ident() {
const d = this.data;
d[0] = 1;
d[1] = 0;
d[2] = 0;
d[3] = 0;
d[4] = 0;
d[5] = 1;
d[6] = 0;
d[7] = 0;
d[8] = 0;
d[9] = 0;
d[10] = 1;
d[11] = 0;
d[12] = 0;
d[13] = 0;
d[14] = 0;
d[15] = 1;
return this;
}
zero() {
const d = this.data;
d[0] = 0;
d[1] = 0;
d[2] = 0;
d[3] = 0;
d[4] = 0;
d[5] = 0
d[6] = 0;
d[7] = 0;
d[8] = 0;
d[9] = 0;
d[10] = 0;
d[11] = 0;
d[12] = 0;
d[13] = 0;
d[14] = 0;
d[15] = 0;
return this;
}
set (a00, a10, a20, a30, a01, a11, a21, a31, a02, a12, a22, a32, a03, a13, a23, a33) {
const d = this.data;
d[0] = a00;
d[4] = a10;
d[8] = a20;
d[12] = a30;
d[1] = a01;
d[5] = a11;
d[9] = a21;
d[13] = a31;
d[2] = a02;
d[6] = a12;
d[10] = a22;
d[14] = a32;
d[3] = a03;
d[7] = a13;
d[11] = a23;
d[15] = a33;
return this;
}
perspective(data) {
const fov = data.fov || 60;
const aspect = data.aspect || 1;
const near = data.near || 0.01;
const far = data.far || 100;
this.zero();
const d = this.data;
const top = near * Math.tan(fov * Math.PI / 360);
const right = top * aspect;
const left = -right;
const bottom = -top;
d[0] = (2 * near) / (right - left);
d[5] = (2 * near) / (top - bottom);
d[8] = (right + left) / (right - left);
d[9] = (top + bottom) / (top - bottom);
d[10] = -(far + near) / (far - near);
d[11] = -1;
d[14] = -(2 * far * near) / (far - near);
return this;
}
ortho (near = -1, far = 1, top = -1, bottom = 1, left = -1, right = 1) {
const rl = right - left;
const tb = top - bottom;
const fn = far - near;
return this.set(
2 / rl,
0,
0,
-(left + right) / rl,
0,
2 / tb,
0,
-(top + bottom) / tb,
0,
0,
-2 / fn,
-(far + near) / fn,
0,
0,
0,
1
);
}
trans(x, y, z) {
const d = this.data;
d[12] = d[0] * x + d[4] * y + d[8] * z + d[12];
d[13] = d[1] * x + d[5] * y + d[9] * z + d[13];
d[14] = d[2] * x + d[6] * y + d[10] * z + d[14];
d[15] = d[3] * x + d[7] * y + d[11] * z + d[15];
return this;
}
rotatex(angle) {
const d = this.data;
const rad = Math.PI * (angle / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a10 = d[4];
const a11 = d[5];
const a12 = d[6];
const a13 = d[7];
const a20 = d[8];
const a21 = d[9];
const a22 = d[10];
const a23 = d[11];
d[4] = a10 * c + a20 * s;
d[5] = a11 * c + a21 * s;
d[6] = a12 * c + a22 * s;
d[7] = a13 * c + a23 * s;
d[8] = a10 * -s + a20 * c;
d[9] = a11 * -s + a21 * c;
d[10] = a12 * -s + a22 * c;
d[11] = a13 * -s + a23 * c;
return this;
}
rotatey(angle) {
const d = this.data;
const rad = Math.PI * (angle / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a00 = d[0];
const a01 = d[1];
const a02 = d[2];
const a03 = d[3];
const a20 = d[8];
const a21 = d[9];
const a22 = d[10];
const a23 = d[11];
d[0] = a00 * c + a20 * -s;
d[1] = a01 * c + a21 * -s;
d[2] = a02 * c + a22 * -s;
d[3] = a03 * c + a23 * -s;
d[8] = a00 * s + a20 * c;
d[9] = a01 * s + a21 * c;
d[10] = a02 * s + a22 * c;
d[11] = a03 * s + a23 * c;
return this;
}
rotatez(angle) {
const d = this.data;
const rad = Math.PI * (angle / 180);
const s = Math.sin(rad);
const c = Math.cos(rad);
const a00 = d[0];
const a01 = d[1];
const a02 = d[2];
const a03 = d[3];
const a10 = d[4];
const a11 = d[5];
const a12 = d[6];
const a13 = d[7];
d[0] = a00 * c + a10 * s;
d[1] = a01 * c + a11 * s;
d[2] = a02 * c + a12 * s;
d[3] = a03 * c + a13 * s;
d[4] = a00 * -s + a10 * c;
d[5] = a01 * -s + a11 * c;
d[6] = a02 * -s + a12 * c;
d[7] = a03 * -s + a13 * c;
return this;
}
scale(x, y, z) {
const d = this.data;
d[0] *= x;
d[1] *= x;
d[2] *= x;
d[3] *= x;
d[4] *= y;
d[5] *= y;
d[6] *= y;
d[7] *= y;
d[8] *= z;
d[9] *= z;
d[10] *= z;
d[11] *= z;
return this;
}
toMat3Rot(dest) {
const dst = dest.data;
const src = this.data;
const a00 = src[0];
const a01 = src[1];
const a02 = src[2];
const a10 = src[4];
const a11 = src[5];
const a12 = src[6];
const a20 = src[8];
const a21 = src[9];
const a22 = src[10];
const b01 = a22 * a11 - a12 * a21;
const b11 = -a22 * a10 + a12 * a20;
const b21 = a21 * a10 - a11 * a20;
const d = a00 * b01 + a01 * b11 + a02 * b21;
const id = 1 / d;
dst[0] = b01 * id;
dst[3] = (-a22 * a01 + a02 * a21) * id;
dst[6] = ( a12 * a01 - a02 * a11) * id;
dst[1] = b11 * id;
dst[4] = ( a22 * a00 - a02 * a20) * id;
dst[7] = (-a12 * a00 + a02 * a10) * id;
dst[2] = b21 * id;
dst[5] = (-a21 * a00 + a01 * a20) * id;
dst[8] = ( a11 * a00 - a01 * a10) * id;
return dest;
}
multiply(m1, m2) {
let b0, b1, b2, b3;
const mat = this.data;
const mat1 = m1.data;
const mat2 = m2.data;
const a00 = mat1[0];
const a01 = mat1[1];
const a02 = mat1[2];
const a03 = mat1[3];
const a10 = mat1[4];
const a11 = mat1[5];
const a12 = mat1[6];
const a13 = mat1[7];
const a20 = mat1[8];
const a21 = mat1[9];
const a22 = mat1[10];
const a23 = mat1[11];
const a30 = mat1[12];
const a31 = mat1[13];
const a32 = mat1[14];
const a33 = mat1[15];
b0 = mat2[0];
b1 = mat2[1];
b2 = mat2[2];
b3 = mat2[3];
mat[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
mat[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
mat[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
mat[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = mat2[4];
b1 = mat2[5];
b2 = mat2[6];
b3 = mat2[7];
mat[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
mat[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
mat[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
mat[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = mat2[8];
b1 = mat2[9];
b2 = mat2[10];
b3 = mat2[11];
mat[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
mat[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
mat[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
mat[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
b0 = mat2[12];
b1 = mat2[13];
b2 = mat2[14];
b3 = mat2[15];
mat[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30;
mat[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31;
mat[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32;
mat[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33;
return this;
}
inverse() {
let a00, a01, a02, a03, a10, a11, a12, a13, a20, a21, a22, a23, a30, a31, a32, a33, b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11, d, invDet;
const mat = this.data;
a00 = mat[0];
a01 = mat[1];
a02 = mat[2];
a03 = mat[3];
a10 = mat[4];
a11 = mat[5];
a12 = mat[6];
a13 = mat[7];
a20 = mat[8];
a21 = mat[9];
a22 = mat[10];
a23 = mat[11];
a30 = mat[12];
a31 = mat[13];
a32 = mat[14];
a33 = mat[15];
b00 = a00 * a11 - a01 * a10;
b01 = a00 * a12 - a02 * a10;
b02 = a00 * a13 - a03 * a10;
b03 = a01 * a12 - a02 * a11;
b04 = a01 * a13 - a03 * a11;
b05 = a02 * a13 - a03 * a12;
b06 = a20 * a31 - a21 * a30;
b07 = a20 * a32 - a22 * a30;
b08 = a20 * a33 - a23 * a30;
b09 = a21 * a32 - a22 * a31;
b10 = a21 * a33 - a23 * a31;
b11 = a22 * a33 - a23 * a32;
d = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
if (d === 0) return;
invDet = 1 / d;
mat[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
mat[1] = (-a01 * b11 + a02 * b10 - a03 * b09) * invDet;
mat[2] = (a31 * b05 - a32 * b04 + a33 * b03) * invDet;
mat[3] = (-a21 * b05 + a22 * b04 - a23 * b03) * invDet;
mat[4] = (-a10 * b11 + a12 * b08 - a13 * b07) * invDet;
mat[5] = (a00 * b11 - a02 * b08 + a03 * b07) * invDet;
mat[6] = (-a30 * b05 + a32 * b02 - a33 * b01) * invDet;
mat[7] = (a20 * b05 - a22 * b02 + a23 * b01) * invDet;
mat[8] = (a10 * b10 - a11 * b08 + a13 * b06) * invDet;
mat[9] = (-a00 * b10 + a01 * b08 - a03 * b06) * invDet;
mat[10] = (a30 * b04 - a31 * b02 + a33 * b00) * invDet;
mat[11] = (-a20 * b04 + a21 * b02 - a23 * b00) * invDet;
mat[12] = (-a10 * b09 + a11 * b07 - a12 * b06) * invDet;
mat[13] = (a00 * b09 - a01 * b07 + a02 * b06) * invDet;
mat[14] = (-a30 * b03 + a31 * b01 - a32 * b00) * invDet;
mat[15] = (a20 * b03 - a21 * b01 + a22 * b00) * invDet;
return this;
}
}
'use strict';
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
(function (struct) {
var Node = function () {
function Node(node) {
_classCallCheck(this, Node);
this.pos = gl.vec3(node.x, node.y, node.z);
this.old = gl.vec3(node.x, node.y, node.z);
this.radius = node.w;
this.mass = node.mass || 1.0;
this.rgb = gl.vec3(node.color[0], node.color[1], node.color[2]);
}
Node.prototype.integrate = function integrate() {
var _pos$get = this.pos.get();
var x = _pos$get[0];
var y = _pos$get[1];
var z = _pos$get[2];
this.pos.x += this.pos.x - this.old.x;
this.pos.y += this.pos.y - this.old.y - 0.001 * this.mass;
this.pos.z += this.pos.z - this.old.z;
this.old.set(x, y, z);
};
Node.prototype.draw = function draw(shader) {
shader.vec3('modelColor', this.rgb.x, this.rgb.y, this.rgb.z).mat4('model', gl.mat4().trans(this.pos.x, this.pos.y, this.pos.z).scale(this.radius, this.radius, this.radius)).draw(sphereGeom);
};
Node.prototype.checkScreenLimits = function checkScreenLimits() {
if (this.pos.y <= -1 + this.radius * 0.2) {
this.pos.x -= (this.pos.x - this.old.x) * 0.02;
this.pos.z -= (this.pos.z - this.old.z) * 0.02;
var d = Math.abs(-1 - this.pos.y + this.radius * 0.2);
this.pos.y += d / 10;
this.old.y = this.pos.y;
}
};
return Node;
}();
var Constraint = function () {
function Constraint(n0, n1) {
_classCallCheck(this, Constraint);
this.n0 = n0;
this.n1 = n1;
this.dist = n0.pos.distance(n1.pos);
}
Constraint.prototype.solve = function solve() {
var n0 = this.n0.pos;
var n1 = this.n1.pos;
var dx = n0.x - n1.x;
var dy = n0.y - n1.y;
var dz = n0.z - n1.z;
var currentDist = Math.sqrt(dx * dx + dy * dy + dz * dz);
var delta = 0.5 * (currentDist - this.dist) / currentDist;
dx *= delta;
dy *= delta;
dz *= delta;
var m1 = this.n0.mass + this.n1.mass;
var m2 = this.n0.mass / m1;
m1 = this.n1.mass / m1;
n1.x += dx * m2;
n1.y += dy * m2;
n1.z += dz * m2;
n0.x -= dx * m1;
n0.y -= dy * m1;
n0.z -= dz * m1;
};
return Constraint;
}();
var run = function run() {
requestAnimationFrame(run);
for (var _iterator = nodes, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var n = _ref;
n.integrate();
n.checkScreenLimits();
}
for (var i = 0; i < 5; i++) {
for (var _iterator2 = constraints, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
var _ref2;
if (_isArray2) {
if (_i2 >= _iterator2.length) break;
_ref2 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) break;
_ref2 = _i2.value;
}
var n = _ref2;
n.solve();
}
}
gl.adjustSize().viewport().cullFace().clearColor(0, 0, 0, 0).clearDepth(1);
camProj.perspective({
fov: 60,
aspect: gl.aspect,
near: 0.01,
far: 100
});
pointer.z = Math.min(Math.max(pointer.z, 3), 21);
camDist += 0.1 * (pointer.z / 3 - camDist);
camView.ident().trans(0, 0, -camDist);
var light = nodes[4].pos;
displayShader.use().vec3('uPointLightingLocation', light.x, light.y + 0.06, light.z).vec3('uPointLightingColor', 1.0, 1.0, 1.0).mat4('camProj', camProj).mat4('camView', camView);
displayShader.vec3('modelColor', 0.6, 1.0, 1.2).mat4('model', gl.mat4().trans(0.0, -1.0, 0.0)).draw(planeGeom);
for (var _iterator3 = nodes, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
var _ref3;
if (_isArray3) {
if (_i3 >= _iterator3.length) break;
_ref3 = _iterator3[_i3++];
} else {
_i3 = _iterator3.next();
if (_i3.done) break;
_ref3 = _i3.value;
}
var n = _ref3;
n.draw(displayShader);
}
if (pointer.isDown) {
if (!drag) {
var dmax = 10000,
over = null;
viewProj.multiply(camProj, camView);
for (var _iterator4 = nodes, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) {
var _ref4;
if (_isArray4) {
if (_i4 >= _iterator4.length) break;
_ref4 = _iterator4[_i4++];
} else {
_i4 = _iterator4.next();
if (_i4.done) break;
_ref4 = _i4.value;
}
var n = _ref4;
point.transformMat4(n.pos, viewProj);
var _x = Math.round((point.x + 1) / 2.0 * canvas.width);
var _y = Math.round((1 - point.y) / 2.0 * canvas.height);
var dx = Math.abs(pointer.x - _x);
var dy = Math.abs(pointer.y - _y);
var d = Math.sqrt(dx * dx + dy * dy);
if (d < dmax) {
dmax = d;
over = n;
}
}
canvas.elem.style.cursor = 'move';
drag = over;
rgb.copy(drag.rgb);
drag.rgb.set(2, 1, 0);
}
var x = (2.0 * pointer.x / canvas.width - 1) * 0.55 * camDist * gl.aspect;
var y = (-2.0 * pointer.y / canvas.height + 1) * 0.55 * camDist;
drag.pos.x += (x - drag.pos.x) * 0.2;
drag.pos.y += (y - drag.pos.y) * 0.2;
drag.pos.z *= 0.99;
drag.old.copy(drag.pos);
} else {
if (drag) {
canvas.elem.style.cursor = 'pointer';
drag.rgb.copy(rgb);
drag = null;
}
}
};
var canvas = new Canvas("canvas");
var gl = new WebGL(canvas).depthTest();
var pointer = new Pointer(canvas);
var point = gl.vec3();
var camProj = gl.mat4();
var camView = gl.mat4();
var viewProj = gl.mat4();
var planeGeom = gl.drawable(gl.plane(60));
var sphereGeom = gl.drawable(gl.sphere(1, 36));
var nodes = [];
var constraints = [];
var drag = null;
var rgb = gl.vec3();
var camDist = 3;
pointer.z = camDist * 3;
canvas.enableFullscreen({
position: 'absolute',
right: '7px',
bottom: '7px',
cursor: 'pointer',
background: '#1e1e1e',
fontFamily: 'Lato, Lucida Grande, Lucida Sans Unicode, Tahoma, Sans-Serif',
fontSize: '0.8rem',
padding: '2px 7px',
borderRadius: '3px',
border: '3px solid transparent',
color: 'white',
whiteSpace: 'nowrap',
textAlign: 'center',
userSelect: 'none'
});
var displayShader = gl.shader({
vertex: '\n\t\t\tuniform mat4 camProj, camView;\n\t\t\tuniform mat4 model;\n\t\t\tattribute vec3 position, normal;\n\t\t\tvarying vec3 vLigthPosition;\n\t\t\tvarying vec3 vNormal;\n\t\t\tvoid main(){\n\t\t\t\tvec4 vWorldPosition = model * vec4(position, 1.0);\n\t\t\t\tgl_Position = camProj * camView * vWorldPosition;\n\t\t\t\tvLigthPosition = vWorldPosition.xyz;\n\t\t\t\tvNormal = normal;\n\t\t\t}\n ',
fragment: '\n\t\t\tuniform vec3 modelColor;\n\t\t\tuniform vec3 uPointLightingLocation;\n\t\t\tuniform vec3 uPointLightingColor;\n\t\t\tvarying vec3 vLigthPosition;\n\t\t\tvarying vec3 vNormal;\n\t\t\tvoid main(){\n\t\t\t\tvec3 lightDirection = normalize(uPointLightingLocation - vLigthPosition);\n\t\t\t\tfloat angle = max(dot(lightDirection, vNormal), 0.001);\n\t\t\t\tvec3 diffuse = pow(uPointLightingColor * angle * modelColor, vec3(1.0));\n\t\t\t\tgl_FragColor = vec4(diffuse, 1.0);\n\t\t\t}\n '
});
for (var n in struct.nodes) {
var node = new Node(struct.nodes[n]);
struct.nodes[n].id = node;
nodes.push(node);
}
for (var i = 0; i < struct.constraints.length; i++) {
constraints.push(new Constraint(struct.nodes[struct.constraints[i][0]].id, struct.nodes[struct.constraints[i][1]].id));
}
nodes[3].pos.x += 0.01;
nodes[3].pos.z += 0.01;
run();
})({
nodes: {
n0: {
x: 0,
y: 1,
z: -0.57735,
w: 0.2,
mass: 1.0,
color: [1.5, 1.2, 0.8]
},
n1: {
x: 0.5,
y: 1,
z: 0.288675,
w: 0.2,
mass: 1.0,
color: [1.5, 1.2, 0.8]
},
n2: {
x: -0.5,
y: 1,
z: 0.288675,
w: 0.2,
mass: 1.0,
color: [1.5, 1.2, 0.8]
},
n3: {
x: 0,
y: 1.7,
z: 0,
w: 0.2,
mass: 1.0,
color: [0.8, 1.2, 1.5]
},
n4: {
x: 0,
y: 1.2,
z: 0,
w: 0.1,
mass: 0.6,
color: [1000.0, 1000.0, 1000.0]
}
},
constraints: [["n0", "n1"], ["n1", "n2"], ["n2", "n0"], ["n0", "n3"], ["n1", "n3"], ["n2", "n3"], ["n3", "n4"]]
});
<canvas id="canvas"></canvas>
html {
overflow: hidden;
-ms-touch-action: none;
-ms-content-zooming: none;
}
body {
position: absolute;
margin: 0;
padding: 0;
background: #222;
width: 100%;
height: 100%;
}
#canvas {
position: absolute;
width: 100%;
height: 100%;
background: #000;
cursor: pointer;
}