diff --git a/frontend/www/index.html b/frontend/www/index.html
index fef4ea8..2ea1b5c 100644
--- a/frontend/www/index.html
+++ b/frontend/www/index.html
@@ -3,9 +3,12 @@
Hello wasm-pack!
+
-
+
+
+
diff --git a/frontend/www/index.js b/frontend/www/index.js
index a4ce02d..60f4cb7 100644
--- a/frontend/www/index.js
+++ b/frontend/www/index.js
@@ -1,7 +1,7 @@
import { Game } from "planetwars";
import { Shader } from "./webgl/shader"
-import { main } from './index.ts'
+import { set_instance } from './index.ts'
const URL = window.location.origin+window.location.pathname;
const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
@@ -11,5 +11,5 @@ const game_location = LOCATION + "static/game.json";
fetch(game_location)
.then((r) => r.text())
.then((response) => {
- main(Game.new(response));
+ set_instance(Game.new(response));
}).catch(console.error);
diff --git a/frontend/www/index.ts b/frontend/www/index.ts
index dec7c89..57562af 100644
--- a/frontend/www/index.ts
+++ b/frontend/www/index.ts
@@ -1,15 +1,122 @@
import { Game } from "planetwars";
import { memory } from "planetwars/plantwars_bg";
+import { Resizer, resizeCanvasToDisplaySize, FPSCounter } from "./webgl/util";
+import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Uniform2f, ShaderFactory } from './webgl/shader';
+import { Renderer } from "./webgl/renderer";
+import { VertexBuffer, IndexBuffer } from "./webgl/buffer";
+import { VertexBufferLayout, VertexArray } from "./webgl/vertexBufferLayout";
+import { callbackify } from "util";
+const COUNTER = new FPSCounter();
+const LOADER = document.getElementById("loader");
+
+function set_loading(loading: boolean) {
+ if (loading) {
+ if (!LOADER.classList.contains("loading")) {
+ LOADER.classList.add("loading");
+ }
+ } else {
+ LOADER.classList.remove("loading");
+ }
+}
+
+const URL = window.location.origin+window.location.pathname;
+const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
const CANVAS = document.getElementById("c");
+const RESOLUTION = [CANVAS.width, CANVAS.height];
+
+const GL = CANVAS.getContext("webgl");
+resizeCanvasToDisplaySize(GL.canvas);
+GL.viewport(0, 0, GL.canvas.width, GL.canvas.height);
+
+GL.clearColor(0, 0, 0, 0);
+GL.clear(GL.COLOR_BUFFER_BIT);
+
+GL.enable(GL.BLEND);
+GL.blendFunc(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA);
+
+const positionBuffer = new VertexBuffer(GL, [
+ -1, -1,
+ -1, 1,
+ 1, -1,
+ 1, 1,
+]);
+
+const layout = new VertexBufferLayout();
+layout.push(GL.FLOAT, 2, 4, "a_position");
+const vao = new VertexArray();
+vao.addBuffer(positionBuffer, layout);
+
+const indexBuffer = new IndexBuffer(GL, [
+ 0, 1, 2,
+ 1, 2, 3,
+]);
+
+var SHADERFACOTRY: ShaderFactory;
+ShaderFactory.create_factory(
+ LOCATION + "static/shaders/frag/simple.glsl", LOCATION + "static/shaders/vert/simple.glsl"
+).then((e) => SHADERFACOTRY = e);
function create_array(ptr: number, size: number): Float64Array {
return new Float64Array(memory.buffer, ptr, size);
}
-export function main(game: Game) {
+class GameInstance {
+ resizer: Resizer;
+ game: Game;
+ shader: Shader;
+ renderer: Renderer;
+
+ constructor(game: Game) {
+ this.game = game;
+ this.shader = SHADERFACOTRY.create_shader(GL, {"MAX_CIRCLES": "50"});
+ this.resizer = new Resizer(CANVAS, [...create_array(game.get_viewbox(), 4)], true);
+ this.renderer = new Renderer();
+ this.renderer.addToDraw(indexBuffer, vao, this.shader);
+ }
+
+ render(time: number) {
+ this.shader.uniform(GL, "u_circle_count", new Uniform1i(3));
+
+ this.shader.uniform(GL, "u_time", new Uniform1f(time * 0.001));
+ this.shader.uniform(GL, "u_mouse", new Uniform2f(this.resizer.get_mouse_pos()));
+ this.shader.uniform(GL, "u_viewbox", new Uniform4f(this.resizer.get_viewbox()));
+ this.shader.uniform(GL, "u_resolution", new Uniform2f(RESOLUTION));
+
+ this.shader.uniform(GL, "u_circles", new Uniform3fv([
+ 0, 0, 3.5,
+ -2, -2, 2,
+ 5, 2, 4,
+ ]));
+ this.shader.uniform(GL, "u_color", new Uniform4f([1, 1, 0, 1]));
+
+ this.renderer.render(GL);
+ COUNTER.frame(time);
+ }
+}
+
+var game_instance: GameInstance;
+
+export function set_instance(game: Game) {
+ game_instance = new GameInstance(game);
+
console.log(game.turn_count());
console.log(create_array(game.get_viewbox(), 4));
}
+
+
+function step(time: number) {
+ if (game_instance) {
+ game_instance.render(time);
+ set_loading(false);
+ } else {
+ set_loading(true);
+ }
+
+ requestAnimationFrame(step);
+}
+set_loading(true);
+
+requestAnimationFrame(step);
diff --git a/frontend/www/static/res/style.css b/frontend/www/static/res/style.css
new file mode 100644
index 0000000..f5bc236
--- /dev/null
+++ b/frontend/www/static/res/style.css
@@ -0,0 +1,57 @@
+
+.loading {
+ position: relative;
+ display: inline-block;
+}
+
+.loading::before{
+ content: "";
+ position: absolute;
+ width: 100px;
+ height: 100px;
+ background-color: #1c5ba2;
+ border-radius: 100%;
+ z-index: 5;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ -webkit-animation: slide-top 0.5s ease-out infinite alternate ;
+ animation: slide-top 0.5s ease-out infinite alternate ;
+ }
+
+ #c {
+ position: relative;
+ }
+
+/* ----------------------------------------------
+ * Generated by Animista on 2019-9-17 14:35:13
+ * Licensed under FreeBSD License.
+ * See http://animista.net/license for more info.
+ * w: http://animista.net, t: @cssanimista
+ * ---------------------------------------------- */
+
+/**
+ * ----------------------------------------
+ * animation slide-top
+ * ----------------------------------------
+ */
+ @-webkit-keyframes slide-top {
+ 0% {
+ -webkit-transform: translate(-50%, 50%);
+ transform: translate(-50%, 50%);
+ }
+ 100% {
+ -webkit-transform: translate(-50%, -150%);
+ transform: translate(-50%, -150%);
+ }
+ }
+ @keyframes slide-top {
+ 0% {
+ -webkit-transform: translate(-50%, 50%);
+ transform: translate(-50%, 50%);
+ }
+ 100% {
+ -webkit-transform: translate(-50%, -150%);
+ transform: translate(-50%, -150%);
+ }
+ }
diff --git a/frontend/www/static/shaders/frag/simple.glsl b/frontend/www/static/shaders/frag/simple.glsl
new file mode 100644
index 0000000..9f6fb42
--- /dev/null
+++ b/frontend/www/static/shaders/frag/simple.glsl
@@ -0,0 +1,27 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform int u_circle_count;
+uniform float u_time;
+uniform vec2 u_mouse;
+uniform vec4 u_viewbox; // [x, y, width, height]
+uniform vec2 u_resolution;
+uniform vec3 u_circles[$MAX_CIRCLES];
+uniform vec4 u_color;
+
+varying vec2 v_pos;
+
+void main() {
+ vec2 uv = v_pos;
+
+ float alpha = 0.0;
+ for (int i = 0; i < $MAX_CIRCLES; i++ ){
+ if (i >= u_circle_count) { break; }
+ float d = distance(uv.xy, u_circles[i].xy);
+ alpha = max(1.0 - d/u_circles[i].z, alpha);
+ }
+
+ gl_FragColor = u_color;
+ gl_FragColor.w *= alpha;
+}
diff --git a/frontend/www/static/shaders/vert/simple.glsl b/frontend/www/static/shaders/vert/simple.glsl
new file mode 100644
index 0000000..1d74e16
--- /dev/null
+++ b/frontend/www/static/shaders/vert/simple.glsl
@@ -0,0 +1,22 @@
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+attribute vec2 a_position;
+
+uniform vec4 u_viewbox; // [x, y, width, height]
+uniform vec2 u_resolution;
+
+varying vec2 v_pos;
+
+void main() {
+
+ vec2 uv = ( a_position.xy + 1.0 ) * 0.5;
+
+ uv *= u_viewbox.zw;
+ uv += u_viewbox.xy;
+
+ v_pos = uv.xy;
+
+ gl_Position = vec4(a_position.xy, 0.0, 1.0);
+}
diff --git a/frontend/www/webgl/index.ts b/frontend/www/webgl/index.ts
new file mode 100644
index 0000000..f3e8616
--- /dev/null
+++ b/frontend/www/webgl/index.ts
@@ -0,0 +1,115 @@
+
+import { Shader, Uniform4f, Uniform2fv, Uniform3fv, Uniform1i, Uniform1f, Uniform2f, ShaderFactory } from './shader';
+import { resizeCanvasToDisplaySize, FPSCounter, onload2promise, Resizer } from "./util";
+import { VertexBuffer, IndexBuffer } from './buffer';
+import { VertexArray, VertexBufferLayout } from './vertexBufferLayout';
+import { Renderer } from './renderer';
+import { Texture } from './texture';
+
+
+async function main() {
+
+ const URL = window.location.origin+window.location.pathname;
+ const LOCATION = URL.substring(0, URL.lastIndexOf("/") + 1);
+
+
+ // Get A WebGL context
+ var canvas = document.getElementById("c");
+ const resolution = [canvas.width, canvas.height];
+
+ const resizer = new Resizer(canvas, [0, 0, 900, 900], true);
+
+ var gl = canvas.getContext("webgl");
+ if (!gl) {
+ return;
+ }
+
+ const renderer = new Renderer();
+
+ const factory = await ShaderFactory.create_factory(LOCATION + "static/shaders/frag/simple.glsl", LOCATION + "static/shaders/vert/simple.glsl");
+ const program = factory.create_shader(gl, {"MAX_CIRCLES": "50"});
+
+ var positions = [
+ -1, -1, 0, 1,
+ -1, 1, 0, 0,
+ 1, -1, 1, 1,
+ 1, 1, 1, 0,
+ ];
+
+ var positionBuffer = new VertexBuffer(gl, positions);
+ var layout = new VertexBufferLayout();
+ layout.push(gl.FLOAT, 2, 4, "a_position");
+ layout.push(gl.FLOAT, 2, 4, "a_tex");
+
+ const vao = new VertexArray();
+ vao.addBuffer(positionBuffer, layout);
+
+ resizeCanvasToDisplaySize(gl.canvas);
+
+ // Tell WebGL how to convert from clip space to pixels
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+ // Clear the canvas
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.enable(gl.BLEND);
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
+
+ program.bind(gl);
+ vao.bind(gl, program);
+
+ var indices = [
+ 0, 1, 2,
+ 1, 2, 3,
+ ];
+
+ var indexBuffer = new IndexBuffer(gl, indices);
+ indexBuffer.bind(gl);
+
+ renderer.addToDraw(indexBuffer, vao, program);
+
+ var blue = 1.0;
+ var inc = 0.05;
+
+ const counter = new FPSCounter();
+
+ const step = function (time: number) {
+ blue += inc;
+ // if (blue > 1.0 || blue < 0.0) {
+ // inc = -1 * inc;
+ // blue += inc;
+ // }
+
+ program.uniform(gl, "u_circle_count", new Uniform1i(3));
+
+ program.uniform(gl, "u_time", new Uniform1f(time * 0.001));
+ program.uniform(gl, "u_mouse", new Uniform2f(resizer.get_mouse_pos()));
+ program.uniform(gl, "u_viewbox", new Uniform4f(resizer.get_viewbox()));
+ program.uniform(gl, "u_resolution", new Uniform2f(resolution));
+ program.uniform(gl, "u_circles", new Uniform3fv([
+ 450, 450, 100,
+ 200, 200, 200,
+ 900, 0, 300,
+ ]));
+ program.uniform(gl, "u_color", new Uniform4f([1, blue, 0, 1]));
+
+ renderer.render(gl);
+
+ counter.frame(time);
+ requestAnimationFrame(step);
+ }
+
+ requestAnimationFrame(step);
+}
+
+main();
+
+const loader = document.getElementById("loader");
+setInterval(() => {
+ if (loader.classList.contains("loading")) {
+ loader.classList.remove("loading")
+ } else {
+ loader.classList.add("loading");
+ }
+}, 2000);
diff --git a/frontend/www/webgl/shader.ts b/frontend/www/webgl/shader.ts
index 409d81a..aba96fd 100644
--- a/frontend/www/webgl/shader.ts
+++ b/frontend/www/webgl/shader.ts
@@ -1,7 +1,7 @@
import { Dictionary } from './util';
function error(msg: string) {
- console.log(msg);
+ console.error(msg);
}
const defaultShaderType = [
@@ -9,6 +9,7 @@ const defaultShaderType = [
"FRAGMENT_SHADER"
];
+/// Create Shader from Source string
function loadShader(
gl: WebGLRenderingContext,
shaderSource: string,
@@ -41,6 +42,7 @@ function loadShader(
return shader;
}
+/// Actually Create Program with Shader's
function createProgram(
gl: WebGLRenderingContext,
shaders: WebGLShader[],
@@ -76,39 +78,45 @@ function createProgram(
return program;
}
-function createShaderFromScript(
- gl: WebGLRenderingContext,
- scriptId: string,
- context: Dictionary,
- opt_shaderType: number,
- opt_errorCallback: any,
-): WebGLShader {
- var shaderSource = "";
- var shaderType;
- var shaderScript = document.getElementById(scriptId) as HTMLScriptElement;
- if (!shaderScript) {
- console.log("*** Error: unknown script element" + scriptId);
- }
- shaderSource = shaderScript.text;
+export class ShaderFactory {
+ frag_source: string;
+ vert_source: string;
- for (let key in context) {
- console.log("substitute " + key);
- shaderSource = shaderSource.replace(new RegExp("\\$" + key, 'g'), context[key]);
+ static async create_factory(frag_url: string, vert_url: string): Promise {
+ const sources = await Promise.all([
+ fetch(frag_url).then((r) => r.text()),
+ fetch(vert_url).then((r) => r.text()),
+ ]);
+
+ return new ShaderFactory(sources[0], sources[1]);
}
- if (!opt_shaderType) {
- if (shaderScript.type === "x-shader/x-vertex") {
- shaderType = 35633;
- } else if (shaderScript.type === "x-shader/x-fragment") {
- shaderType = 35632;
- } else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
- console.log("*** Error: unknown shader type");
+ constructor(frag_source: string, vert_source: string ) {
+ this.frag_source = frag_source;
+ this.vert_source = vert_source;
+ }
+
+ create_shader(
+ gl: WebGLRenderingContext,
+ context?: Dictionary,
+ opt_attribs?: string[],
+ opt_locations?: number[],
+ opt_errorCallback?: any,
+ ): Shader {
+ let vert = this.vert_source.slice();
+ let frag = this.frag_source.slice();
+ for (let key in context) {
+ vert = vert.replace(new RegExp("\\$" + key, 'g'), context[key]);
+ frag = frag.replace(new RegExp("\\$" + key, 'g'), context[key]);
}
- }
- return loadShader(
- gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
- opt_errorCallback);
+ const shaders = [
+ loadShader(gl, vert, gl.VERTEX_SHADER, opt_errorCallback),
+ loadShader(gl, frag, gl.FRAGMENT_SHADER, opt_errorCallback),
+ ];
+
+ return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback));
+ }
}
export class Shader {
@@ -116,22 +124,6 @@ export class Shader {
uniformCache: Dictionary;
attribCache: Dictionary;
- static createProgramFromScripts(
- gl: WebGLRenderingContext,
- shaderScriptIds: string[],
- context = {},
- opt_attribs?: string[],
- opt_locations?: number[],
- opt_errorCallback?: any,
- ): Shader {
- var shaders = [];
- for (var ii = 0; ii < shaderScriptIds.length; ++ii) {
- shaders.push(createShaderFromScript(
- gl, shaderScriptIds[ii], context, (gl as any)[defaultShaderType[ii % 2]] as number, opt_errorCallback));
- }
- return new Shader(createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback));
- }
-
static async createProgramFromUrls(
gl: WebGLRenderingContext,
vert_url: string,
@@ -198,6 +190,10 @@ export class Shader {
uniform.setUniform(gl, location);
}
+
+ clear(gl: WebGLRenderingContext) {
+ gl.deleteProgram(this.shader);
+ }
}
export interface Uniform {
@@ -226,6 +222,22 @@ export class Uniform3fv implements Uniform {
}
}
+export class Uniform3f implements Uniform {
+ x: number;
+ y: number;
+ z: number;
+
+ constructor(x: number, y: number, z: number) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
+ gl.uniform3f(location, this.x ,this.y, this.z);
+ }
+}
+
export class Uniform1iv implements Uniform {
data: number[];
constructor(data: number[]) {
@@ -265,9 +277,9 @@ export class Uniform2f implements Uniform {
x: number;
y: number;
- constructor(x: number, y: number) {
- this.x = x;
- this.y = y;
+ constructor(xy: number[]) {
+ this.x = xy[0];
+ this.y = xy[1];
}
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
@@ -281,25 +293,14 @@ export class Uniform4f implements Uniform {
v2: number;
v3: number;
- constructor(vec: number[]) {
- this.v0 = vec[0];
- this.v1 = vec[1];
- this.v2 = vec[2];
- this.v3 = vec[3];
+ constructor(xyzw: number[]) {
+ this.v0 = xyzw[0];
+ this.v1 = xyzw[1];
+ this.v2 = xyzw[2];
+ this.v3 = xyzw[3];
}
setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
gl.uniform4f(location, this.v0, this.v1, this.v2, this.v3);
}
}
-
-export class UniformMatrix3fv implements Uniform {
- data: number[];
- constructor(data: number[]) {
- this.data = data;
- }
-
- setUniform(gl: WebGLRenderingContext, location: WebGLUniformLocation) {
- gl.uniformMatrix3fv(location, false, this.data);
- }
-}
diff --git a/frontend/www/webgl/util.ts b/frontend/www/webgl/util.ts
index 7aa4a6c..007cb53 100644
--- a/frontend/www/webgl/util.ts
+++ b/frontend/www/webgl/util.ts
@@ -39,7 +39,7 @@ export class FPSCounter {
frame(now: number) {
this.count += 1;
- if (now - this.last > 1) {
+ if (now - this.last > 1000) {
this.last = now;
console.log(this.count + " fps");
this.count = 0;
@@ -47,83 +47,122 @@ export class FPSCounter {
}
}
-export class M3 {
- _data: any;
+export class Resizer {
+ hoovering: boolean;
+ dragging: boolean;
- constructor(data: any) {
- this._data = data;
- }
+ mouse_pos: number[];
+ last_drag: number[];
- static ident(): M3 {
- return new M3([
- 1, 0, 0,
- 0, 1, 0,
- 0, 0, 1
- ]);
- }
+ viewbox: number[];
+ orig_viewbox: number[];
- multiply(other: M3): M3 {
- const a = this._data;
- const b = other._data;
+ el_width: number;
- var a00 = a[0 * 3 + 0];
- var a01 = a[0 * 3 + 1];
- var a02 = a[0 * 3 + 2];
- var a10 = a[1 * 3 + 0];
- var a11 = a[1 * 3 + 1];
- var a12 = a[1 * 3 + 2];
- var a20 = a[2 * 3 + 0];
- var a21 = a[2 * 3 + 1];
- var a22 = a[2 * 3 + 2];
- var b00 = b[0 * 3 + 0];
- var b01 = b[0 * 3 + 1];
- var b02 = b[0 * 3 + 2];
- var b10 = b[1 * 3 + 0];
- var b11 = b[1 * 3 + 1];
- var b12 = b[1 * 3 + 2];
- var b20 = b[2 * 3 + 0];
- var b21 = b[2 * 3 + 1];
- var b22 = b[2 * 3 + 2];
+ scaleX = 1;
+ scaleY = 1;
- return new M3([
- b00 * a00 + b01 * a10 + b02 * a20,
- b00 * a01 + b01 * a11 + b02 * a21,
- b00 * a02 + b01 * a12 + b02 * a22,
- b10 * a00 + b11 * a10 + b12 * a20,
- b10 * a01 + b11 * a11 + b12 * a21,
- b10 * a02 + b11 * a12 + b12 * a22,
- b20 * a00 + b21 * a10 + b22 * a20,
- b20 * a01 + b21 * a11 + b22 * a21,
- b20 * a02 + b21 * a12 + b22 * a22,
- ]);
- }
+ constructor(el: HTMLCanvasElement, viewbox: number[], keep_aspect_ratio=false) {
+ console.log("viewbox:" + viewbox);
+ this.hoovering = false;
+ this.dragging = false;
- translation(x: number, y: number): M3 {
- const out = [...this._data];
- out[6] += x;
- out[7] += y;
- return new M3(out);
- }
+ this.mouse_pos = [0, 0];
+ this.last_drag = [0, 0];
- rotate(rad: number): M3 {
- var c = Math.cos(rad);
- var s = Math.sin(rad);
+ this.viewbox = [...viewbox];
- const out = new M3([...this._data]);
+ if (keep_aspect_ratio) {
+ const or_width = this.viewbox[2];
+ const or_height = this.viewbox[3];
+ const scaleX = el.height / el.width;
+ if (scaleX < 1) {
+ this.scaleX= 1 / scaleX;
- return out.multiply(new M3([
- c, -s, 0,
- s, c, 0,
- 0, 0, 1
- ]));
- }
+ this.viewbox[2] *= this.scaleX;
+ } else {
+ this.scaleY = scaleX;
+ this.viewbox[3] *= scaleX;
+ }
- scale(s_x: number, s_y = s_x, s_z = 1): M3 {
- const out = new M3([...this._data]);
- return out.multiply(new M3([
- s_x, 0, 0,
- 0, s_y, 0,
- 0, 0, s_z,
- ]));
- }
+ this.viewbox[0] -= (this.viewbox[2] - or_width) / 2;
+ this.viewbox[1] -= (this.viewbox[3] - or_height) / 2;
+ }
+
+ this.orig_viewbox = [...this.viewbox];
+
+ this.el_width = el.width;
+
+ el.addEventListener("mouseenter", this.mouseenter.bind(this), { capture: false, passive: true});
+ el.addEventListener("mouseleave", this.mouseleave.bind(this), { capture: false, passive: true});
+ el.addEventListener("mousemove", this.mousemove.bind(this), { capture: false, passive: true});
+ el.addEventListener("mousedown", this.mousedown.bind(this), { capture: false, passive: true});
+ el.addEventListener("mouseup", this.mouseup.bind(this), { capture: false, passive: true});
+
+ window.addEventListener('wheel', this.wheel.bind(this), { capture: false, passive: true});
+ }
+
+ clip_viewbox() {
+ this.viewbox[0] = Math.max(this.viewbox[0], this.orig_viewbox[0]);
+ this.viewbox[1] = Math.max(this.viewbox[1], this.orig_viewbox[1]);
+
+ this.viewbox[0] = Math.min(this.viewbox[0] + this.viewbox[2], this.orig_viewbox[0] + this.orig_viewbox[2]) - this.viewbox[2];
+ this.viewbox[1] = Math.min(this.viewbox[1] + this.viewbox[3], this.orig_viewbox[1] + this.orig_viewbox[3]) - this.viewbox[3];
+ }
+
+ mouseenter() {
+ this.hoovering = true;
+ }
+
+ mouseleave() {
+ this.hoovering = false;
+ this.dragging = false;
+ }
+
+ mousemove(e: MouseEvent) {
+ this.mouse_pos = [e.offsetX, this.el_width - e.offsetY];
+
+ if (this.dragging) {
+ const scale = this.viewbox[3] / this.orig_viewbox[3];
+ this.viewbox[0] += (this.last_drag[0] - this.mouse_pos[0]) * scale;
+ this.viewbox[1] += (this.last_drag[1] - this.mouse_pos[1]) * scale;
+
+ this.last_drag = [...this.mouse_pos];
+
+ this.clip_viewbox();
+ }
+ }
+
+ mousedown() {
+ this.dragging = true;
+ this.last_drag = [...this.mouse_pos];
+ }
+
+ mouseup() {
+ this.dragging = false;
+ }
+
+ wheel(e: WheelEvent) {
+ if (this.hoovering) {
+ const dx = e.deltaY * this.scaleX;
+ this.viewbox[2] += dx;
+ this.viewbox[0] -= dx / 2;
+ this.viewbox[2] = Math.min(this.viewbox[2], this.orig_viewbox[2]);
+
+ const dy = e.deltaY * this.scaleY;
+ this.viewbox[3] += dy;
+ this.viewbox[1] -= dy / 2;
+ this.viewbox[3] = Math.min(this.viewbox[3], this.orig_viewbox[3]);
+
+ this.clip_viewbox();
+ }
+ }
+
+ get_viewbox(): number[] {
+ return this.viewbox;
+ }
+
+ get_mouse_pos(): number[] {
+ return this.mouse_pos;
+ }
}
diff --git a/frontend/www/webgl/webgl-utils.d.ts b/frontend/www/webgl/webgl-utils.d.ts
new file mode 100644
index 0000000..bd6bab2
--- /dev/null
+++ b/frontend/www/webgl/webgl-utils.d.ts
@@ -0,0 +1,20 @@
+// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
+// Project: [~THE PROJECT NAME~]
+// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
+
+/*~ This is the module template file. You should rename it to index.d.ts
+ *~ and place it in a folder with the same name as the module.
+ *~ For example, if you were writing a file for "super-greeter", this
+ *~ file should be 'super-greeter/index.d.ts'
+ */
+
+// /*~ If this module is a UMD module that exposes a global variable 'myLib' when
+// *~ loaded outside a module loader environment, declare that global here.
+// *~ Otherwise, delete this declaration.
+// */
+// export as namespace myLib;
+
+/*~ If this module has methods, declare them as functions like so.
+ */
+export function createProgramFromScripts(gl: WebGLRenderingContext, shaderScriptIds: string[], opt_attribs?: string[], opt_locations?: number[], opt_errorCallback?: any): any;
+export function resizeCanvasToDisplaySize(canvas: HTMLCanvasElement, multiplier?: number): boolean;
diff --git a/frontend/www/webgl/webgl-utils.js b/frontend/www/webgl/webgl-utils.js
new file mode 100644
index 0000000..da64e73
--- /dev/null
+++ b/frontend/www/webgl/webgl-utils.js
@@ -0,0 +1,1293 @@
+/*
+ * Copyright 2012, Gregg Tavares.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Gregg Tavares. nor the names of his
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+(function(root, factory) { // eslint-disable-line
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define([], function() {
+ return factory.call(root);
+ });
+ } else {
+ // Browser globals
+ root.webglUtils = factory.call(root);
+ }
+}(this, function() {
+ "use strict";
+
+ var topWindow = this;
+
+ /** @module webgl-utils */
+
+ function isInIFrame(w) {
+ w = w || topWindow;
+ return w !== w.top;
+ }
+
+ if (!isInIFrame()) {
+ console.log("%c%s", 'color:blue;font-weight:bold;', 'for more about webgl-utils.js see:'); // eslint-disable-line
+ console.log("%c%s", 'color:blue;font-weight:bold;', 'http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html'); // eslint-disable-line
+ }
+
+ /**
+ * Wrapped logging function.
+ * @param {string} msg The message to log.
+ */
+ function error(msg) {
+ if (topWindow.console) {
+ if (topWindow.console.error) {
+ topWindow.console.error(msg);
+ } else if (topWindow.console.log) {
+ topWindow.console.log(msg);
+ }
+ }
+ }
+
+
+ /**
+ * Error Callback
+ * @callback ErrorCallback
+ * @param {string} msg error message.
+ * @memberOf module:webgl-utils
+ */
+
+
+ /**
+ * Loads a shader.
+ * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
+ * @param {string} shaderSource The shader source.
+ * @param {number} shaderType The type of shader.
+ * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors.
+ * @return {WebGLShader} The created shader.
+ */
+ function loadShader(gl, shaderSource, shaderType, opt_errorCallback) {
+ var errFn = opt_errorCallback || error;
+ // Create the shader object
+ var shader = gl.createShader(shaderType);
+
+ // Load the shader source
+ gl.shaderSource(shader, shaderSource);
+
+ // Compile the shader
+ gl.compileShader(shader);
+
+ // Check the compile status
+ var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+ if (!compiled) {
+ // Something went wrong during compilation; get the error
+ var lastError = gl.getShaderInfoLog(shader);
+ errFn("*** Error compiling shader '" + shader + "':" + lastError);
+ gl.deleteShader(shader);
+ return null;
+ }
+
+ return shader;
+ }
+
+ /**
+ * Creates a program, attaches shaders, binds attrib locations, links the
+ * program and calls useProgram.
+ * @param {WebGLShader[]} shaders The shaders to attach
+ * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in
+ * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
+ * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console
+ * on error. If you want something else pass an callback. It's passed an error message.
+ * @memberOf module:webgl-utils
+ */
+ function createProgram(
+ gl, shaders, opt_attribs, opt_locations, opt_errorCallback) {
+ var errFn = opt_errorCallback || error;
+ var program = gl.createProgram();
+ shaders.forEach(function(shader) {
+ gl.attachShader(program, shader);
+ });
+ if (opt_attribs) {
+ opt_attribs.forEach(function(attrib, ndx) {
+ gl.bindAttribLocation(
+ program,
+ opt_locations ? opt_locations[ndx] : ndx,
+ attrib);
+ });
+ }
+ gl.linkProgram(program);
+
+ // Check the link status
+ var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
+ if (!linked) {
+ // something went wrong with the link
+ var lastError = gl.getProgramInfoLog(program);
+ errFn("Error in program linking:" + lastError);
+
+ gl.deleteProgram(program);
+ return null;
+ }
+ return program;
+ }
+
+ /**
+ * Loads a shader from a script tag.
+ * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use.
+ * @param {string} scriptId The id of the script tag.
+ * @param {number} opt_shaderType The type of shader. If not passed in it will
+ * be derived from the type of the script tag.
+ * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors.
+ * @return {WebGLShader} The created shader.
+ */
+ function createShaderFromScript(
+ gl, scriptId, opt_shaderType, opt_errorCallback) {
+ var shaderSource = "";
+ var shaderType;
+ var shaderScript = document.getElementById(scriptId);
+ if (!shaderScript) {
+ console.log ("*** Error: unknown script element" + scriptId);
+ }
+ shaderSource = shaderScript.text;
+
+ if (!opt_shaderType) {
+ if (shaderScript.type === "x-shader/x-vertex") {
+ shaderType = 35633;
+ } else if (shaderScript.type === "x-shader/x-fragment") {
+ shaderType = 35632;
+ } else if (shaderType !== gl.VERTEX_SHADER && shaderType !== gl.FRAGMENT_SHADER) {
+ console.log ("*** Error: unknown shader type");
+ }
+ }
+
+ return loadShader(
+ gl, shaderSource, opt_shaderType ? opt_shaderType : shaderType,
+ opt_errorCallback);
+ }
+
+ var defaultShaderType = [
+ "VERTEX_SHADER",
+ "FRAGMENT_SHADER",
+ ];
+
+ /**
+ * Creates a program from 2 script tags.
+ *
+ * @param {WebGLRenderingContext} gl The WebGLRenderingContext
+ * to use.
+ * @param {string[]} shaderScriptIds Array of ids of the script
+ * tags for the shaders. The first is assumed to be the
+ * vertex shader, the second the fragment shader.
+ * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in
+ * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
+ * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console
+ * on error. If you want something else pass an callback. It's passed an error message.
+ * @return {WebGLProgram} The created program.
+ * @memberOf module:webgl-utils
+ */
+ function createProgramFromScripts(
+ gl, shaderScriptIds, opt_attribs, opt_locations, opt_errorCallback) {
+ var shaders = [];
+ for (var ii = 0; ii < shaderScriptIds.length; ++ii) {
+ shaders.push(createShaderFromScript(
+ gl, shaderScriptIds[ii], gl[defaultShaderType[ii]], opt_errorCallback));
+ }
+ return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
+ }
+
+ /**
+ * Creates a program from 2 sources.
+ *
+ * @param {WebGLRenderingContext} gl The WebGLRenderingContext
+ * to use.
+ * @param {string[]} shaderSourcess Array of sources for the
+ * shaders. The first is assumed to be the vertex shader,
+ * the second the fragment shader.
+ * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in
+ * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
+ * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console
+ * on error. If you want something else pass an callback. It's passed an error message.
+ * @return {WebGLProgram} The created program.
+ * @memberOf module:webgl-utils
+ */
+ function createProgramFromSources(
+ gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
+ var shaders = [];
+ for (var ii = 0; ii < shaderSources.length; ++ii) {
+ shaders.push(loadShader(
+ gl, shaderSources[ii], gl[defaultShaderType[ii]], opt_errorCallback));
+ }
+ return createProgram(gl, shaders, opt_attribs, opt_locations, opt_errorCallback);
+ }
+
+ /**
+ * Returns the corresponding bind point for a given sampler type
+ */
+ function getBindPointForSamplerType(gl, type) {
+ if (type === gl.SAMPLER_2D) return gl.TEXTURE_2D; // eslint-disable-line
+ if (type === gl.SAMPLER_CUBE) return gl.TEXTURE_CUBE_MAP; // eslint-disable-line
+ return undefined;
+ }
+
+ /**
+ * @typedef {Object.} Setters
+ */
+
+ /**
+ * Creates setter functions for all uniforms of a shader
+ * program.
+ *
+ * @see {@link module:webgl-utils.setUniforms}
+ *
+ * @param {WebGLProgram} program the program to create setters for.
+ * @returns {Object.} an object with a setter by name for each uniform
+ * @memberOf module:webgl-utils
+ */
+ function createUniformSetters(gl, program) {
+ var textureUnit = 0;
+
+ /**
+ * Creates a setter for a uniform of the given program with it's
+ * location embedded in the setter.
+ * @param {WebGLProgram} program
+ * @param {WebGLUniformInfo} uniformInfo
+ * @returns {function} the created setter.
+ */
+ function createUniformSetter(program, uniformInfo) {
+ var location = gl.getUniformLocation(program, uniformInfo.name);
+ var type = uniformInfo.type;
+ // Check if this uniform is an array
+ var isArray = (uniformInfo.size > 1 && uniformInfo.name.substr(-3) === "[0]");
+ if (type === gl.FLOAT && isArray) {
+ return function(v) {
+ gl.uniform1fv(location, v);
+ };
+ }
+ if (type === gl.FLOAT) {
+ return function(v) {
+ gl.uniform1f(location, v);
+ };
+ }
+ if (type === gl.FLOAT_VEC2) {
+ return function(v) {
+ gl.uniform2fv(location, v);
+ };
+ }
+ if (type === gl.FLOAT_VEC3) {
+ return function(v) {
+ gl.uniform3fv(location, v);
+ };
+ }
+ if (type === gl.FLOAT_VEC4) {
+ return function(v) {
+ gl.uniform4fv(location, v);
+ };
+ }
+ if (type === gl.INT && isArray) {
+ return function(v) {
+ gl.uniform1iv(location, v);
+ };
+ }
+ if (type === gl.INT) {
+ return function(v) {
+ gl.uniform1i(location, v);
+ };
+ }
+ if (type === gl.INT_VEC2) {
+ return function(v) {
+ gl.uniform2iv(location, v);
+ };
+ }
+ if (type === gl.INT_VEC3) {
+ return function(v) {
+ gl.uniform3iv(location, v);
+ };
+ }
+ if (type === gl.INT_VEC4) {
+ return function(v) {
+ gl.uniform4iv(location, v);
+ };
+ }
+ if (type === gl.BOOL) {
+ return function(v) {
+ gl.uniform1iv(location, v);
+ };
+ }
+ if (type === gl.BOOL_VEC2) {
+ return function(v) {
+ gl.uniform2iv(location, v);
+ };
+ }
+ if (type === gl.BOOL_VEC3) {
+ return function(v) {
+ gl.uniform3iv(location, v);
+ };
+ }
+ if (type === gl.BOOL_VEC4) {
+ return function(v) {
+ gl.uniform4iv(location, v);
+ };
+ }
+ if (type === gl.FLOAT_MAT2) {
+ return function(v) {
+ gl.uniformMatrix2fv(location, false, v);
+ };
+ }
+ if (type === gl.FLOAT_MAT3) {
+ return function(v) {
+ gl.uniformMatrix3fv(location, false, v);
+ };
+ }
+ if (type === gl.FLOAT_MAT4) {
+ return function(v) {
+ gl.uniformMatrix4fv(location, false, v);
+ };
+ }
+ if ((type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) && isArray) {
+ var units = [];
+ for (var ii = 0; ii < info.size; ++ii) {
+ units.push(textureUnit++);
+ }
+ return function(bindPoint, units) {
+ return function(textures) {
+ gl.uniform1iv(location, units);
+ textures.forEach(function(texture, index) {
+ gl.activeTexture(gl.TEXTURE0 + units[index]);
+ gl.bindTexture(bindPoint, texture);
+ });
+ };
+ }(getBindPointForSamplerType(gl, type), units);
+ }
+ if (type === gl.SAMPLER_2D || type === gl.SAMPLER_CUBE) {
+ return function(bindPoint, unit) {
+ return function(texture) {
+ gl.uniform1i(location, unit);
+ gl.activeTexture(gl.TEXTURE0 + unit);
+ gl.bindTexture(bindPoint, texture);
+ };
+ }(getBindPointForSamplerType(gl, type), textureUnit++);
+ }
+ throw ("unknown type: 0x" + type.toString(16)); // we should never get here.
+ }
+
+ var uniformSetters = { };
+ var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
+
+ for (var ii = 0; ii < numUniforms; ++ii) {
+ var uniformInfo = gl.getActiveUniform(program, ii);
+ if (!uniformInfo) {
+ break;
+ }
+ var name = uniformInfo.name;
+ // remove the array suffix.
+ if (name.substr(-3) === "[0]") {
+ name = name.substr(0, name.length - 3);
+ }
+ var setter = createUniformSetter(program, uniformInfo);
+ uniformSetters[name] = setter;
+ }
+ return uniformSetters;
+ }
+
+ /**
+ * Set uniforms and binds related textures.
+ *
+ * example:
+ *
+ * var programInfo = createProgramInfo(
+ * gl, ["some-vs", "some-fs");
+ *
+ * var tex1 = gl.createTexture();
+ * var tex2 = gl.createTexture();
+ *
+ * ... assume we setup the textures with data ...
+ *
+ * var uniforms = {
+ * u_someSampler: tex1,
+ * u_someOtherSampler: tex2,
+ * u_someColor: [1,0,0,1],
+ * u_somePosition: [0,1,1],
+ * u_someMatrix: [
+ * 1,0,0,0,
+ * 0,1,0,0,
+ * 0,0,1,0,
+ * 0,0,0,0,
+ * ],
+ * };
+ *
+ * gl.useProgram(program);
+ *
+ * This will automatically bind the textures AND set the
+ * uniforms.
+ *
+ * setUniforms(programInfo.uniformSetters, uniforms);
+ *
+ * For the example above it is equivalent to
+ *
+ * var texUnit = 0;
+ * gl.activeTexture(gl.TEXTURE0 + texUnit);
+ * gl.bindTexture(gl.TEXTURE_2D, tex1);
+ * gl.uniform1i(u_someSamplerLocation, texUnit++);
+ * gl.activeTexture(gl.TEXTURE0 + texUnit);
+ * gl.bindTexture(gl.TEXTURE_2D, tex2);
+ * gl.uniform1i(u_someSamplerLocation, texUnit++);
+ * gl.uniform4fv(u_someColorLocation, [1, 0, 0, 1]);
+ * gl.uniform3fv(u_somePositionLocation, [0, 1, 1]);
+ * gl.uniformMatrix4fv(u_someMatrix, false, [
+ * 1,0,0,0,
+ * 0,1,0,0,
+ * 0,0,1,0,
+ * 0,0,0,0,
+ * ]);
+ *
+ * Note it is perfectly reasonable to call `setUniforms` multiple times. For example
+ *
+ * var uniforms = {
+ * u_someSampler: tex1,
+ * u_someOtherSampler: tex2,
+ * };
+ *
+ * var moreUniforms {
+ * u_someColor: [1,0,0,1],
+ * u_somePosition: [0,1,1],
+ * u_someMatrix: [
+ * 1,0,0,0,
+ * 0,1,0,0,
+ * 0,0,1,0,
+ * 0,0,0,0,
+ * ],
+ * };
+ *
+ * setUniforms(programInfo.uniformSetters, uniforms);
+ * setUniforms(programInfo.uniformSetters, moreUniforms);
+ *
+ * @param {Object.|module:webgl-utils.ProgramInfo} setters the setters returned from
+ * `createUniformSetters` or a ProgramInfo from {@link module:webgl-utils.createProgramInfo}.
+ * @param {Object.} an object with values for the
+ * uniforms.
+ * @memberOf module:webgl-utils
+ */
+ function setUniforms(setters, values) {
+ setters = setters.uniformSetters || setters;
+ Object.keys(values).forEach(function(name) {
+ var setter = setters[name];
+ if (setter) {
+ setter(values[name]);
+ }
+ });
+ }
+
+ /**
+ * Creates setter functions for all attributes of a shader
+ * program. You can pass this to {@link module:webgl-utils.setBuffersAndAttributes} to set all your buffers and attributes.
+ *
+ * @see {@link module:webgl-utils.setAttributes} for example
+ * @param {WebGLProgram} program the program to create setters for.
+ * @return {Object.} an object with a setter for each attribute by name.
+ * @memberOf module:webgl-utils
+ */
+ function createAttributeSetters(gl, program) {
+ var attribSetters = {
+ };
+
+ function createAttribSetter(index) {
+ return function(b) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, b.buffer);
+ gl.enableVertexAttribArray(index);
+ gl.vertexAttribPointer(
+ index, b.numComponents || b.size, b.type || gl.FLOAT, b.normalize || false, b.stride || 0, b.offset || 0);
+ };
+ }
+
+ var numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
+ for (var ii = 0; ii < numAttribs; ++ii) {
+ var attribInfo = gl.getActiveAttrib(program, ii);
+ if (!attribInfo) {
+ break;
+ }
+ var index = gl.getAttribLocation(program, attribInfo.name);
+ attribSetters[attribInfo.name] = createAttribSetter(index);
+ }
+
+ return attribSetters;
+ }
+
+ /**
+ * Sets attributes and binds buffers (deprecated... use {@link module:webgl-utils.setBuffersAndAttributes})
+ *
+ * Example:
+ *
+ * var program = createProgramFromScripts(
+ * gl, ["some-vs", "some-fs");
+ *
+ * var attribSetters = createAttributeSetters(program);
+ *
+ * var positionBuffer = gl.createBuffer();
+ * var texcoordBuffer = gl.createBuffer();
+ *
+ * var attribs = {
+ * a_position: {buffer: positionBuffer, numComponents: 3},
+ * a_texcoord: {buffer: texcoordBuffer, numComponents: 2},
+ * };
+ *
+ * gl.useProgram(program);
+ *
+ * This will automatically bind the buffers AND set the
+ * attributes.
+ *
+ * setAttributes(attribSetters, attribs);
+ *
+ * Properties of attribs. For each attrib you can add
+ * properties:
+ *
+ * * type: the type of data in the buffer. Default = gl.FLOAT
+ * * normalize: whether or not to normalize the data. Default = false
+ * * stride: the stride. Default = 0
+ * * offset: offset into the buffer. Default = 0
+ *
+ * For example if you had 3 value float positions, 2 value
+ * float texcoord and 4 value uint8 colors you'd setup your
+ * attribs like this
+ *
+ * var attribs = {
+ * a_position: {buffer: positionBuffer, numComponents: 3},
+ * a_texcoord: {buffer: texcoordBuffer, numComponents: 2},
+ * a_color: {
+ * buffer: colorBuffer,
+ * numComponents: 4,
+ * type: gl.UNSIGNED_BYTE,
+ * normalize: true,
+ * },
+ * };
+ *
+ * @param {Object.|model:webgl-utils.ProgramInfo} setters Attribute setters as returned from createAttributeSetters or a ProgramInfo as returned {@link module:webgl-utils.createProgramInfo}
+ * @param {Object.} attribs AttribInfos mapped by attribute name.
+ * @memberOf module:webgl-utils
+ * @deprecated use {@link module:webgl-utils.setBuffersAndAttributes}
+ */
+ function setAttributes(setters, attribs) {
+ setters = setters.attribSetters || setters;
+ Object.keys(attribs).forEach(function(name) {
+ var setter = setters[name];
+ if (setter) {
+ setter(attribs[name]);
+ }
+ });
+ }
+
+ /**
+ * Creates a vertex array object and then sets the attributes
+ * on it
+ *
+ * @param {WebGLRenderingContext} gl The WebGLRenderingContext
+ * to use.
+ * @param {Object.} setters Attribute setters as returned from createAttributeSetters
+ * @param {Object.} attribs AttribInfos mapped by attribute name.
+ * @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices
+ */
+ function createVAOAndSetAttributes(gl, setters, attribs, indices) {
+ var vao = gl.createVertexArray();
+ gl.bindVertexArray(vao);
+ setAttributes(setters, attribs);
+ if (indices) {
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
+ }
+ // We unbind this because otherwise any change to ELEMENT_ARRAY_BUFFER
+ // like when creating buffers for other stuff will mess up this VAO's binding
+ gl.bindVertexArray(null);
+ return vao;
+ }
+
+ /**
+ * Creates a vertex array object and then sets the attributes
+ * on it
+ *
+ * @param {WebGLRenderingContext} gl The WebGLRenderingContext
+ * to use.
+ * @param {Object.| module:webgl-utils.ProgramInfo} programInfo as returned from createProgramInfo or Attribute setters as returned from createAttributeSetters
+ * @param {module:webgl-utils:BufferInfo} bufferInfo BufferInfo as returned from createBufferInfoFromArrays etc...
+ * @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices
+ */
+ function createVAOFromBufferInfo(gl, programInfo, bufferInfo) {
+ return createVAOAndSetAttributes(gl, programInfo.attribSetters || programInfo, bufferInfo.attribs, bufferInfo.indices);
+ }
+
+ /**
+ * @typedef {Object} ProgramInfo
+ * @property {WebGLProgram} program A shader program
+ * @property {Object} uniformSetters: object of setters as returned from createUniformSetters,
+ * @property {Object} attribSetters: object of setters as returned from createAttribSetters,
+ * @memberOf module:webgl-utils
+ */
+
+ /**
+ * Creates a ProgramInfo from 2 sources.
+ *
+ * A ProgramInfo contains
+ *
+ * programInfo = {
+ * program: WebGLProgram,
+ * uniformSetters: object of setters as returned from createUniformSetters,
+ * attribSetters: object of setters as returned from createAttribSetters,
+ * }
+ *
+ * @param {WebGLRenderingContext} gl The WebGLRenderingContext
+ * to use.
+ * @param {string[]} shaderSourcess Array of sources for the
+ * shaders or ids. The first is assumed to be the vertex shader,
+ * the second the fragment shader.
+ * @param {string[]} [opt_attribs] An array of attribs names. Locations will be assigned by index if not passed in
+ * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations.
+ * @param {module:webgl-utils.ErrorCallback} opt_errorCallback callback for errors. By default it just prints an error to the console
+ * on error. If you want something else pass an callback. It's passed an error message.
+ * @return {module:webgl-utils.ProgramInfo} The created program.
+ * @memberOf module:webgl-utils
+ */
+ function createProgramInfo(
+ gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) {
+ shaderSources = shaderSources.map(function(source) {
+ var script = document.getElementById(source);
+ return script ? script.text : source;
+ });
+ var program = webglUtils.createProgramFromSources(gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback);
+ if (!program) {
+ return null;
+ }
+ var uniformSetters = createUniformSetters(gl, program);
+ var attribSetters = createAttributeSetters(gl, program);
+ return {
+ program: program,
+ uniformSetters: uniformSetters,
+ attribSetters: attribSetters,
+ };
+ }
+
+ /**
+ * Sets attributes and buffers including the `ELEMENT_ARRAY_BUFFER` if appropriate
+ *
+ * Example:
+ *
+ * var programInfo = createProgramInfo(
+ * gl, ["some-vs", "some-fs");
+ *
+ * var arrays = {
+ * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], },
+ * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], },
+ * };
+ *
+ * var bufferInfo = createBufferInfoFromArrays(gl, arrays);
+ *
+ * gl.useProgram(programInfo.program);
+ *
+ * This will automatically bind the buffers AND set the
+ * attributes.
+ *
+ * setBuffersAndAttributes(programInfo.attribSetters, bufferInfo);
+ *
+ * For the example above it is equivilent to
+ *
+ * gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+ * gl.enableVertexAttribArray(a_positionLocation);
+ * gl.vertexAttribPointer(a_positionLocation, 3, gl.FLOAT, false, 0, 0);
+ * gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
+ * gl.enableVertexAttribArray(a_texcoordLocation);
+ * gl.vertexAttribPointer(a_texcoordLocation, 4, gl.FLOAT, false, 0, 0);
+ *
+ * @param {WebGLRenderingContext} gl A WebGLRenderingContext.
+ * @param {Object.} setters Attribute setters as returned from `createAttributeSetters`
+ * @param {module:webgl-utils.BufferInfo} buffers a BufferInfo as returned from `createBufferInfoFromArrays`.
+ * @memberOf module:webgl-utils
+ */
+ function setBuffersAndAttributes(gl, setters, buffers) {
+ setAttributes(setters, buffers.attribs);
+ if (buffers.indices) {
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices);
+ }
+ }
+
+ // Add your prefix here.
+ var browserPrefixes = [
+ "",
+ "MOZ_",
+ "OP_",
+ "WEBKIT_",
+ ];
+
+ /**
+ * Given an extension name like WEBGL_compressed_texture_s3tc
+ * returns the supported version extension, like
+ * WEBKIT_WEBGL_compressed_teture_s3tc
+ * @param {string} name Name of extension to look for
+ * @return {WebGLExtension} The extension or undefined if not
+ * found.
+ * @memberOf module:webgl-utils
+ */
+ function getExtensionWithKnownPrefixes(gl, name) {
+ for (var ii = 0; ii < browserPrefixes.length; ++ii) {
+ var prefixedName = browserPrefixes[ii] + name;
+ var ext = gl.getExtension(prefixedName);
+ if (ext) {
+ return ext;
+ }
+ }
+ return undefined;
+ }
+
+ /**
+ * Resize a canvas to match the size its displayed.
+ * @param {HTMLCanvasElement} canvas The canvas to resize.
+ * @param {number} [multiplier] amount to multiply by.
+ * Pass in window.devicePixelRatio for native pixels.
+ * @return {boolean} true if the canvas was resized.
+ * @memberOf module:webgl-utils
+ */
+ function resizeCanvasToDisplaySize(canvas, multiplier) {
+ multiplier = multiplier || 1;
+ var width = canvas.clientWidth * multiplier | 0;
+ var height = canvas.clientHeight * multiplier | 0;
+ if (canvas.width !== width || canvas.height !== height) {
+ canvas.width = width;
+ canvas.height = height;
+ return true;
+ }
+ return false;
+ }
+
+ // Add `push` to a typed array. It just keeps a 'cursor'
+ // and allows use to `push` values into the array so we
+ // don't have to manually compute offsets
+ function augmentTypedArray(typedArray, numComponents) {
+ var cursor = 0;
+ typedArray.push = function() {
+ for (var ii = 0; ii < arguments.length; ++ii) {
+ var value = arguments[ii];
+ if (value instanceof Array || (value.buffer && value.buffer instanceof ArrayBuffer)) {
+ for (var jj = 0; jj < value.length; ++jj) {
+ typedArray[cursor++] = value[jj];
+ }
+ } else {
+ typedArray[cursor++] = value;
+ }
+ }
+ };
+ typedArray.reset = function(opt_index) {
+ cursor = opt_index || 0;
+ };
+ typedArray.numComponents = numComponents;
+ Object.defineProperty(typedArray, 'numElements', {
+ get: function() {
+ return this.length / this.numComponents | 0;
+ },
+ });
+ return typedArray;
+ }
+
+ /**
+ * creates a typed array with a `push` function attached
+ * so that you can easily *push* values.
+ *
+ * `push` can take multiple arguments. If an argument is an array each element
+ * of the array will be added to the typed array.
+ *
+ * Example:
+ *
+ * var array = createAugmentedTypedArray(3, 2); // creates a Float32Array with 6 values
+ * array.push(1, 2, 3);
+ * array.push([4, 5, 6]);
+ * // array now contains [1, 2, 3, 4, 5, 6]
+ *
+ * Also has `numComponents` and `numElements` properties.
+ *
+ * @param {number} numComponents number of components
+ * @param {number} numElements number of elements. The total size of the array will be `numComponents * numElements`.
+ * @param {constructor} opt_type A constructor for the type. Default = `Float32Array`.
+ * @return {ArrayBuffer} A typed array.
+ * @memberOf module:webgl-utils
+ */
+ function createAugmentedTypedArray(numComponents, numElements, opt_type) {
+ var Type = opt_type || Float32Array;
+ return augmentTypedArray(new Type(numComponents * numElements), numComponents);
+ }
+
+ function createBufferFromTypedArray(gl, array, type, drawType) {
+ type = type || gl.ARRAY_BUFFER;
+ var buffer = gl.createBuffer();
+ gl.bindBuffer(type, buffer);
+ gl.bufferData(type, array, drawType || gl.STATIC_DRAW);
+ return buffer;
+ }
+
+ function allButIndices(name) {
+ return name !== "indices";
+ }
+
+ function createMapping(obj) {
+ var mapping = {};
+ Object.keys(obj).filter(allButIndices).forEach(function(key) {
+ mapping["a_" + key] = key;
+ });
+ return mapping;
+ }
+
+ function getGLTypeForTypedArray(gl, typedArray) {
+ if (typedArray instanceof Int8Array) { return gl.BYTE; } // eslint-disable-line
+ if (typedArray instanceof Uint8Array) { return gl.UNSIGNED_BYTE; } // eslint-disable-line
+ if (typedArray instanceof Int16Array) { return gl.SHORT; } // eslint-disable-line
+ if (typedArray instanceof Uint16Array) { return gl.UNSIGNED_SHORT; } // eslint-disable-line
+ if (typedArray instanceof Int32Array) { return gl.INT; } // eslint-disable-line
+ if (typedArray instanceof Uint32Array) { return gl.UNSIGNED_INT; } // eslint-disable-line
+ if (typedArray instanceof Float32Array) { return gl.FLOAT; } // eslint-disable-line
+ throw "unsupported typed array type";
+ }
+
+ // This is really just a guess. Though I can't really imagine using
+ // anything else? Maybe for some compression?
+ function getNormalizationForTypedArray(typedArray) {
+ if (typedArray instanceof Int8Array) { return true; } // eslint-disable-line
+ if (typedArray instanceof Uint8Array) { return true; } // eslint-disable-line
+ return false;
+ }
+
+ function isArrayBuffer(a) {
+ return a.buffer && a.buffer instanceof ArrayBuffer;
+ }
+
+ function guessNumComponentsFromName(name, length) {
+ var numComponents;
+ if (name.indexOf("coord") >= 0) {
+ numComponents = 2;
+ } else if (name.indexOf("color") >= 0) {
+ numComponents = 4;
+ } else {
+ numComponents = 3; // position, normals, indices ...
+ }
+
+ if (length % numComponents > 0) {
+ throw "can not guess numComponents. You should specify it.";
+ }
+
+ return numComponents;
+ }
+
+ function makeTypedArray(array, name) {
+ if (isArrayBuffer(array)) {
+ return array;
+ }
+
+ if (array.data && isArrayBuffer(array.data)) {
+ return array.data;
+ }
+
+ if (Array.isArray(array)) {
+ array = {
+ data: array,
+ };
+ }
+
+ if (!array.numComponents) {
+ array.numComponents = guessNumComponentsFromName(name, array.length);
+ }
+
+ var type = array.type;
+ if (!type) {
+ if (name === "indices") {
+ type = Uint16Array;
+ }
+ }
+ var typedArray = createAugmentedTypedArray(array.numComponents, array.data.length / array.numComponents | 0, type);
+ typedArray.push(array.data);
+ return typedArray;
+ }
+
+ /**
+ * @typedef {Object} AttribInfo
+ * @property {number} [numComponents] the number of components for this attribute.
+ * @property {number} [size] the number of components for this attribute.
+ * @property {number} [type] the type of the attribute (eg. `gl.FLOAT`, `gl.UNSIGNED_BYTE`, etc...) Default = `gl.FLOAT`
+ * @property {boolean} [normalized] whether or not to normalize the data. Default = false
+ * @property {number} [offset] offset into buffer in bytes. Default = 0
+ * @property {number} [stride] the stride in bytes per element. Default = 0
+ * @property {WebGLBuffer} buffer the buffer that contains the data for this attribute
+ * @memberOf module:webgl-utils
+ */
+
+
+ /**
+ * Creates a set of attribute data and WebGLBuffers from set of arrays
+ *
+ * Given
+ *
+ * var arrays = {
+ * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], },
+ * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], },
+ * normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], },
+ * color: { numComponents: 4, data: [255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 255], type: Uint8Array, },
+ * indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], },
+ * };
+ *
+ * returns something like
+ *
+ * var attribs = {
+ * a_position: { numComponents: 3, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, },
+ * a_texcoord: { numComponents: 2, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, },
+ * a_normal: { numComponents: 3, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, },
+ * a_color: { numComponents: 4, type: gl.UNSIGNED_BYTE, normalize: true, buffer: WebGLBuffer, },
+ * };
+ *
+ * @param {WebGLRenderingContext} gl The webgl rendering context.
+ * @param {Object.} arrays The arrays
+ * @param {Object.} [opt_mapping] mapping from attribute name to array name.
+ * if not specified defaults to "a_name" -> "name".
+ * @return {Object.} the attribs
+ * @memberOf module:webgl-utils
+ */
+ function createAttribsFromArrays(gl, arrays, opt_mapping) {
+ var mapping = opt_mapping || createMapping(arrays);
+ var attribs = {};
+ Object.keys(mapping).forEach(function(attribName) {
+ var bufferName = mapping[attribName];
+ var origArray = arrays[bufferName];
+ var array = makeTypedArray(origArray, bufferName);
+ attribs[attribName] = {
+ buffer: createBufferFromTypedArray(gl, array),
+ numComponents: origArray.numComponents || array.numComponents || guessNumComponentsFromName(bufferName),
+ type: getGLTypeForTypedArray(gl, array),
+ normalize: getNormalizationForTypedArray(array),
+ };
+ });
+ return attribs;
+ }
+
+ /**
+ * tries to get the number of elements from a set of arrays.
+ */
+ function getNumElementsFromNonIndexedArrays(arrays) {
+ var key = Object.keys(arrays)[0];
+ var array = arrays[key];
+ if (isArrayBuffer(array)) {
+ return array.numElements;
+ } else {
+ return array.data.length / array.numComponents;
+ }
+ }
+
+ /**
+ * @typedef {Object} BufferInfo
+ * @property {number} numElements The number of elements to pass to `gl.drawArrays` or `gl.drawElements`.
+ * @property {WebGLBuffer} [indices] The indices `ELEMENT_ARRAY_BUFFER` if any indices exist.
+ * @property {Object.} attribs The attribs approriate to call `setAttributes`
+ * @memberOf module:webgl-utils
+ */
+
+
+ /**
+ * Creates a BufferInfo from an object of arrays.
+ *
+ * This can be passed to {@link module:webgl-utils.setBuffersAndAttributes} and to
+ * {@link module:webgl-utils:drawBufferInfo}.
+ *
+ * Given an object like
+ *
+ * var arrays = {
+ * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], },
+ * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], },
+ * normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], },
+ * indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], },
+ * };
+ *
+ * Creates an BufferInfo like this
+ *
+ * bufferInfo = {
+ * numElements: 4, // or whatever the number of elements is
+ * indices: WebGLBuffer, // this property will not exist if there are no indices
+ * attribs: {
+ * a_position: { buffer: WebGLBuffer, numComponents: 3, },
+ * a_normal: { buffer: WebGLBuffer, numComponents: 3, },
+ * a_texcoord: { buffer: WebGLBuffer, numComponents: 2, },
+ * },
+ * };
+ *
+ * The properties of arrays can be JavaScript arrays in which case the number of components
+ * will be guessed.
+ *
+ * var arrays = {
+ * position: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0],
+ * texcoord: [0, 0, 0, 1, 1, 0, 1, 1],
+ * normal: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1],
+ * indices: [0, 1, 2, 1, 2, 3],
+ * };
+ *
+ * They can also by TypedArrays
+ *
+ * var arrays = {
+ * position: new Float32Array([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]),
+ * texcoord: new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]),
+ * normal: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]),
+ * indices: new Uint16Array([0, 1, 2, 1, 2, 3]),
+ * };
+ *
+ * Or augmentedTypedArrays
+ *
+ * var positions = createAugmentedTypedArray(3, 4);
+ * var texcoords = createAugmentedTypedArray(2, 4);
+ * var normals = createAugmentedTypedArray(3, 4);
+ * var indices = createAugmentedTypedArray(3, 2, Uint16Array);
+ *
+ * positions.push([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]);
+ * texcoords.push([0, 0, 0, 1, 1, 0, 1, 1]);
+ * normals.push([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
+ * indices.push([0, 1, 2, 1, 2, 3]);
+ *
+ * var arrays = {
+ * position: positions,
+ * texcoord: texcoords,
+ * normal: normals,
+ * indices: indices,
+ * };
+ *
+ * For the last example it is equivalent to
+ *
+ * var bufferInfo = {
+ * attribs: {
+ * a_position: { numComponents: 3, buffer: gl.createBuffer(), },
+ * a_texcoods: { numComponents: 2, buffer: gl.createBuffer(), },
+ * a_normals: { numComponents: 3, buffer: gl.createBuffer(), },
+ * },
+ * indices: gl.createBuffer(),
+ * numElements: 6,
+ * };
+ *
+ * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_position.buffer);
+ * gl.bufferData(gl.ARRAY_BUFFER, arrays.position, gl.STATIC_DRAW);
+ * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_texcoord.buffer);
+ * gl.bufferData(gl.ARRAY_BUFFER, arrays.texcoord, gl.STATIC_DRAW);
+ * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.a_normal.buffer);
+ * gl.bufferData(gl.ARRAY_BUFFER, arrays.normal, gl.STATIC_DRAW);
+ * gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferInfo.indices);
+ * gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, arrays.indices, gl.STATIC_DRAW);
+ *
+ * @param {WebGLRenderingContext} gl A WebGLRenderingContext
+ * @param {Object.} arrays Your data
+ * @param {Object.} [opt_mapping] an optional mapping of attribute to array name.
+ * If not passed in it's assumed the array names will be mapped to an attribute
+ * of the same name with "a_" prefixed to it. An other words.
+ *
+ * var arrays = {
+ * position: ...,
+ * texcoord: ...,
+ * normal: ...,
+ * indices: ...,
+ * };
+ *
+ * bufferInfo = createBufferInfoFromArrays(gl, arrays);
+ *
+ * Is the same as
+ *
+ * var arrays = {
+ * position: ...,
+ * texcoord: ...,
+ * normal: ...,
+ * indices: ...,
+ * };
+ *
+ * var mapping = {
+ * a_position: "position",
+ * a_texcoord: "texcoord",
+ * a_normal: "normal",
+ * };
+ *
+ * bufferInfo = createBufferInfoFromArrays(gl, arrays, mapping);
+ *
+ * @return {module:webgl-utils.BufferInfo} A BufferInfo
+ * @memberOf module:webgl-utils
+ */
+ function createBufferInfoFromArrays(gl, arrays, opt_mapping) {
+ var bufferInfo = {
+ attribs: createAttribsFromArrays(gl, arrays, opt_mapping),
+ };
+ var indices = arrays.indices;
+ if (indices) {
+ indices = makeTypedArray(indices, "indices");
+ bufferInfo.indices = createBufferFromTypedArray(gl, indices, gl.ELEMENT_ARRAY_BUFFER);
+ bufferInfo.numElements = indices.length;
+ } else {
+ bufferInfo.numElements = getNumElementsFromNonIndexedArrays(arrays);
+ }
+
+ return bufferInfo;
+ }
+
+ /**
+ * Creates buffers from typed arrays
+ *
+ * Given something like this
+ *
+ * var arrays = {
+ * positions: [1, 2, 3],
+ * normals: [0, 0, 1],
+ * }
+ *
+ * returns something like
+ *
+ * buffers = {
+ * positions: WebGLBuffer,
+ * normals: WebGLBuffer,
+ * }
+ *
+ * If the buffer is named 'indices' it will be made an ELEMENT_ARRAY_BUFFER.
+ *
+ * @param {WebGLRenderingContext} gl A WebGLRenderingContext.
+ * @param {Object} arrays
+ * @return {Object} returns an object with one WebGLBuffer per array
+ * @memberOf module:webgl-utils
+ */
+ function createBuffersFromArrays(gl, arrays) {
+ var buffers = { };
+ Object.keys(arrays).forEach(function(key) {
+ var type = key === "indices" ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
+ var array = makeTypedArray(arrays[key], name);
+ buffers[key] = createBufferFromTypedArray(gl, array, type);
+ });
+
+ // hrm
+ if (arrays.indices) {
+ buffers.numElements = arrays.indices.length;
+ } else if (arrays.position) {
+ buffers.numElements = arrays.position.length / 3;
+ }
+
+ return buffers;
+ }
+
+ /**
+ * Calls `gl.drawElements` or `gl.drawArrays`, whichever is appropriate
+ *
+ * normally you'd call `gl.drawElements` or `gl.drawArrays` yourself
+ * but calling this means if you switch from indexed data to non-indexed
+ * data you don't have to remember to update your draw call.
+ *
+ * @param {WebGLRenderingContext} gl A WebGLRenderingContext
+ * @param {module:webgl-utils.BufferInfo} bufferInfo as returned from createBufferInfoFromArrays
+ * @param {enum} [primitiveType] eg (gl.TRIANGLES, gl.LINES, gl.POINTS, gl.TRIANGLE_STRIP, ...)
+ * @param {number} [count] An optional count. Defaults to bufferInfo.numElements
+ * @param {number} [offset] An optional offset. Defaults to 0.
+ * @memberOf module:webgl-utils
+ */
+ function drawBufferInfo(gl, bufferInfo, primitiveType, count, offset) {
+ var indices = bufferInfo.indices;
+ primitiveType = primitiveType === undefined ? gl.TRIANGLES : primitiveType;
+ var numElements = count === undefined ? bufferInfo.numElements : count;
+ offset = offset === undefined ? offset : 0;
+ if (indices) {
+ gl.drawElements(primitiveType, numElements, gl.UNSIGNED_SHORT, offset);
+ } else {
+ gl.drawArrays(primitiveType, offset, numElements);
+ }
+ }
+
+ /**
+ * @typedef {Object} DrawObject
+ * @property {module:webgl-utils.ProgramInfo} programInfo A ProgramInfo as returned from createProgramInfo
+ * @property {module:webgl-utils.BufferInfo} bufferInfo A BufferInfo as returned from createBufferInfoFromArrays
+ * @property {Object} uniforms The values for the uniforms
+ * @memberOf module:webgl-utils
+ */
+
+ /**
+ * Draws a list of objects
+ * @param {WebGLRenderingContext} gl A WebGLRenderingContext
+ * @param {DrawObject[]} objectsToDraw an array of objects to draw.
+ * @memberOf module:webgl-utils
+ */
+ function drawObjectList(gl, objectsToDraw) {
+ var lastUsedProgramInfo = null;
+ var lastUsedBufferInfo = null;
+
+ objectsToDraw.forEach(function(object) {
+ var programInfo = object.programInfo;
+ var bufferInfo = object.bufferInfo;
+ var bindBuffers = false;
+
+ if (programInfo !== lastUsedProgramInfo) {
+ lastUsedProgramInfo = programInfo;
+ gl.useProgram(programInfo.program);
+ bindBuffers = true;
+ }
+
+ // Setup all the needed attributes.
+ if (bindBuffers || bufferInfo !== lastUsedBufferInfo) {
+ lastUsedBufferInfo = bufferInfo;
+ setBuffersAndAttributes(gl, programInfo.attribSetters, bufferInfo);
+ }
+
+ // Set the uniforms.
+ setUniforms(programInfo.uniformSetters, object.uniforms);
+
+ // Draw
+ drawBufferInfo(gl, bufferInfo);
+ });
+ }
+
+ var isIE = /*@cc_on!@*/false || !!document.documentMode;
+ // Edge 20+
+ var isEdge = !isIE && !!window.StyleMedia;
+ if (isEdge) {
+ // Hack for Edge. Edge's WebGL implmentation is crap still and so they
+ // only respond to "experimental-webgl". I don't want to clutter the
+ // examples with that so his hack works around it
+ HTMLCanvasElement.prototype.getContext = function(origFn) {
+ return function() {
+ var args = arguments;
+ var type = args[0];
+ if (type === "webgl") {
+ args = [].slice.call(arguments);
+ args[0] = "experimental-webgl";
+ }
+ return origFn.apply(this, args);
+ };
+ }(HTMLCanvasElement.prototype.getContext);
+ }
+
+ return {
+ createAugmentedTypedArray: createAugmentedTypedArray,
+ createAttribsFromArrays: createAttribsFromArrays,
+ createBuffersFromArrays: createBuffersFromArrays,
+ createBufferInfoFromArrays: createBufferInfoFromArrays,
+ createAttributeSetters: createAttributeSetters,
+ createProgram: createProgram,
+ createProgramFromScripts: createProgramFromScripts,
+ createProgramFromSources: createProgramFromSources,
+ createProgramInfo: createProgramInfo,
+ createUniformSetters: createUniformSetters,
+ createVAOAndSetAttributes: createVAOAndSetAttributes,
+ createVAOFromBufferInfo: createVAOFromBufferInfo,
+ drawBufferInfo: drawBufferInfo,
+ drawObjectList: drawObjectList,
+ getExtensionWithKnownPrefixes: getExtensionWithKnownPrefixes,
+ resizeCanvasToDisplaySize: resizeCanvasToDisplaySize,
+ setAttributes: setAttributes,
+ setBuffersAndAttributes: setBuffersAndAttributes,
+ setUniforms: setUniforms,
+ };
+
+}));