This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
nms-oam/gloam/WebRoot/js/fss.js

1232 lines
33 KiB
JavaScript
Raw Normal View History

2018-09-27 16:28:35 +08:00
//============================================================
//
// Copyright (C) 2013 Matthew Wagerfield
//
// Twitter: https://twitter.com/mwagerfield
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute,
// sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY
// OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
// EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
// OR OTHER DEALINGS IN THE SOFTWARE.
//
//============================================================
/**
* Defines the Flat Surface Shader namespace for all the awesomeness to exist upon.
* @author Matthew Wagerfield
*/
FSS = {
FRONT : 0,
BACK : 1,
DOUBLE : 2,
SVGNS : 'http://www.w3.org/2000/svg'
};
/**
* @class Array
* @author Matthew Wagerfield
*/
FSS.Array = typeof Float32Array === 'function' ? Float32Array : Array;
/**
* @class Utils
* @author Matthew Wagerfield
*/
FSS.Utils = {
isNumber: function(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
}
};
/**
* Request Animation Frame Polyfill.
* @author Paul Irish
* @see https://gist.github.com/paulirish/1579671
*/
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = function(callback, element) {
var currentTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currentTime - lastTime));
var id = window.setTimeout(function() {
callback(currentTime + timeToCall);
}, timeToCall);
lastTime = currentTime + timeToCall;
return id;
};
}
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
}());
/**
* @object Math Augmentation
* @author Matthew Wagerfield
*/
Math.PIM2 = Math.PI*2;
Math.PID2 = Math.PI/2;
Math.randomInRange = function(min, max) {
return min + (max - min) * Math.random();
};
Math.clamp = function(value, min, max) {
value = Math.max(value, min);
value = Math.min(value, max);
return value;
};
/**
* @object Vector3
* @author Matthew Wagerfield
*/
FSS.Vector3 = {
create: function(x, y, z) {
var vector = new FSS.Array(3);
this.set(vector, x, y, z);
return vector;
},
clone: function(a) {
var vector = this.create();
this.copy(vector, a);
return vector;
},
set: function(target, x, y, z) {
target[0] = x || 0;
target[1] = y || 0;
target[2] = z || 0;
return this;
},
setX: function(target, x) {
target[0] = x || 0;
return this;
},
setY: function(target, y) {
target[1] = y || 0;
return this;
},
setZ: function(target, z) {
target[2] = z || 0;
return this;
},
copy: function(target, a) {
target[0] = a[0];
target[1] = a[1];
target[2] = a[2];
return this;
},
add: function(target, a) {
target[0] += a[0];
target[1] += a[1];
target[2] += a[2];
return this;
},
addVectors: function(target, a, b) {
target[0] = a[0] + b[0];
target[1] = a[1] + b[1];
target[2] = a[2] + b[2];
return this;
},
addScalar: function(target, s) {
target[0] += s;
target[1] += s;
target[2] += s;
return this;
},
subtract: function(target, a) {
target[0] -= a[0];
target[1] -= a[1];
target[2] -= a[2];
return this;
},
subtractVectors: function(target, a, b) {
target[0] = a[0] - b[0];
target[1] = a[1] - b[1];
target[2] = a[2] - b[2];
return this;
},
subtractScalar: function(target, s) {
target[0] -= s;
target[1] -= s;
target[2] -= s;
return this;
},
multiply: function(target, a) {
target[0] *= a[0];
target[1] *= a[1];
target[2] *= a[2];
return this;
},
multiplyVectors: function(target, a, b) {
target[0] = a[0] * b[0];
target[1] = a[1] * b[1];
target[2] = a[2] * b[2];
return this;
},
multiplyScalar: function(target, s) {
target[0] *= s;
target[1] *= s;
target[2] *= s;
return this;
},
divide: function(target, a) {
target[0] /= a[0];
target[1] /= a[1];
target[2] /= a[2];
return this;
},
divideVectors: function(target, a, b) {
target[0] = a[0] / b[0];
target[1] = a[1] / b[1];
target[2] = a[2] / b[2];
return this;
},
divideScalar: function(target, s) {
if (s !== 0) {
target[0] /= s;
target[1] /= s;
target[2] /= s;
} else {
target[0] = 0;
target[1] = 0;
target[2] = 0;
}
return this;
},
cross: function(target, a) {
var x = target[0];
var y = target[1];
var z = target[2];
target[0] = y*a[2] - z*a[1];
target[1] = z*a[0] - x*a[2];
target[2] = x*a[1] - y*a[0];
return this;
},
crossVectors: function(target, a, b) {
target[0] = a[1]*b[2] - a[2]*b[1];
target[1] = a[2]*b[0] - a[0]*b[2];
target[2] = a[0]*b[1] - a[1]*b[0];
return this;
},
min: function(target, value) {
if (target[0] < value) { target[0] = value; }
if (target[1] < value) { target[1] = value; }
if (target[2] < value) { target[2] = value; }
return this;
},
max: function(target, value) {
if (target[0] > value) { target[0] = value; }
if (target[1] > value) { target[1] = value; }
if (target[2] > value) { target[2] = value; }
return this;
},
clamp: function(target, min, max) {
this.min(target, min);
this.max(target, max);
return this;
},
limit: function(target, min, max) {
var length = this.length(target);
if (min !== null && length < min) {
this.setLength(target, min);
} else if (max !== null && length > max) {
this.setLength(target, max);
}
return this;
},
dot: function(a, b) {
return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
},
normalise: function(target) {
return this.divideScalar(target, this.length(target));
},
negate: function(target) {
return this.multiplyScalar(target, -1);
},
distanceSquared: function(a, b) {
var dx = a[0] - b[0];
var dy = a[1] - b[1];
var dz = a[2] - b[2];
return dx*dx + dy*dy + dz*dz;
},
distance: function(a, b) {
return Math.sqrt(this.distanceSquared(a, b));
},
lengthSquared: function(a) {
return a[0]*a[0] + a[1]*a[1] + a[2]*a[2];
},
length: function(a) {
return Math.sqrt(this.lengthSquared(a));
},
setLength: function(target, l) {
var length = this.length(target);
if (length !== 0 && l !== length) {
this.multiplyScalar(target, l / length);
}
return this;
}
};
/**
* @object Vector4
* @author Matthew Wagerfield
*/
FSS.Vector4 = {
create: function(x, y, z, w) {
var vector = new FSS.Array(4);
this.set(vector, x, y, z);
return vector;
},
set: function(target, x, y, z, w) {
target[0] = x || 0;
target[1] = y || 0;
target[2] = z || 0;
target[3] = w || 0;
return this;
},
setX: function(target, x) {
target[0] = x || 0;
return this;
},
setY: function(target, y) {
target[1] = y || 0;
return this;
},
setZ: function(target, z) {
target[2] = z || 0;
return this;
},
setW: function(target, w) {
target[3] = w || 0;
return this;
},
add: function(target, a) {
target[0] += a[0];
target[1] += a[1];
target[2] += a[2];
target[3] += a[3];
return this;
},
multiplyVectors: function(target, a, b) {
target[0] = a[0] * b[0];
target[1] = a[1] * b[1];
target[2] = a[2] * b[2];
target[3] = a[3] * b[3];
return this;
},
multiplyScalar: function(target, s) {
target[0] *= s;
target[1] *= s;
target[2] *= s;
target[3] *= s;
return this;
},
min: function(target, value) {
if (target[0] < value) { target[0] = value; }
if (target[1] < value) { target[1] = value; }
if (target[2] < value) { target[2] = value; }
if (target[3] < value) { target[3] = value; }
return this;
},
max: function(target, value) {
if (target[0] > value) { target[0] = value; }
if (target[1] > value) { target[1] = value; }
if (target[2] > value) { target[2] = value; }
if (target[3] > value) { target[3] = value; }
return this;
},
clamp: function(target, min, max) {
this.min(target, min);
this.max(target, max);
return this;
}
};
/**
* @class Color
* @author Matthew Wagerfield
*/
FSS.Color = function(hex, opacity) {
this.rgba = FSS.Vector4.create();
this.hex = hex || '#000000';
this.opacity = FSS.Utils.isNumber(opacity) ? opacity : 1;
this.set(this.hex, this.opacity);
};
FSS.Color.prototype = {
set: function(hex, opacity) {
hex = hex.replace('#', '');
var size = hex.length / 3;
this.rgba[0] = parseInt(hex.substring(size*0, size*1), 16) / 255;
this.rgba[1] = parseInt(hex.substring(size*1, size*2), 16) / 255;
this.rgba[2] = parseInt(hex.substring(size*2, size*3), 16) / 255;
this.rgba[3] = FSS.Utils.isNumber(opacity) ? opacity : this.rgba[3];
return this;
},
hexify: function(channel) {
var hex = Math.ceil(channel*255).toString(16);
if (hex.length === 1) { hex = '0' + hex; }
return hex;
},
format: function() {
var r = this.hexify(this.rgba[0]);
var g = this.hexify(this.rgba[1]);
var b = this.hexify(this.rgba[2]);
this.hex = '#' + r + g + b;
return this.hex;
}
};
/**
* @class Object
* @author Matthew Wagerfield
*/
FSS.Object = function() {
this.position = FSS.Vector3.create();
};
FSS.Object.prototype = {
setPosition: function(x, y, z) {
FSS.Vector3.set(this.position, x, y, z);
return this;
}
};
/**
* @class Light
* @author Matthew Wagerfield
*/
FSS.Light = function(ambient, diffuse) {
FSS.Object.call(this);
this.ambient = new FSS.Color(ambient || '#FFFFFF');
this.diffuse = new FSS.Color(diffuse || '#FFFFFF');
this.ray = FSS.Vector3.create();
};
FSS.Light.prototype = Object.create(FSS.Object.prototype);
/**
* @class Vertex
* @author Matthew Wagerfield
*/
FSS.Vertex = function(x, y, z) {
this.position = FSS.Vector3.create(x, y, z);
};
FSS.Vertex.prototype = {
setPosition: function(x, y, z) {
FSS.Vector3.set(this.position, x, y, z);
return this;
}
};
/**
* @class Triangle
* @author Matthew Wagerfield
*/
FSS.Triangle = function(a, b, c) {
this.a = a || new FSS.Vertex();
this.b = b || new FSS.Vertex();
this.c = c || new FSS.Vertex();
this.vertices = [this.a, this.b, this.c];
this.u = FSS.Vector3.create();
this.v = FSS.Vector3.create();
this.centroid = FSS.Vector3.create();
this.normal = FSS.Vector3.create();
this.color = new FSS.Color();
this.polygon = document.createElementNS(FSS.SVGNS, 'polygon');
this.polygon.setAttributeNS(null, 'stroke-linejoin', 'round');
this.polygon.setAttributeNS(null, 'stroke-miterlimit', '1');
this.polygon.setAttributeNS(null, 'stroke-width', '1');
this.computeCentroid();
this.computeNormal();
};
FSS.Triangle.prototype = {
computeCentroid: function() {
this.centroid[0] = this.a.position[0] + this.b.position[0] + this.c.position[0];
this.centroid[1] = this.a.position[1] + this.b.position[1] + this.c.position[1];
this.centroid[2] = this.a.position[2] + this.b.position[2] + this.c.position[2];
FSS.Vector3.divideScalar(this.centroid, 3);
return this;
},
computeNormal: function() {
FSS.Vector3.subtractVectors(this.u, this.b.position, this.a.position);
FSS.Vector3.subtractVectors(this.v, this.c.position, this.a.position);
FSS.Vector3.crossVectors(this.normal, this.u, this.v);
FSS.Vector3.normalise(this.normal);
return this;
}
};
/**
* @class Geometry
* @author Matthew Wagerfield
*/
FSS.Geometry = function() {
this.vertices = [];
this.triangles = [];
this.dirty = false;
};
FSS.Geometry.prototype = {
update: function() {
if (this.dirty) {
var t,triangle;
for (t = this.triangles.length - 1; t >= 0; t--) {
triangle = this.triangles[t];
triangle.computeCentroid();
triangle.computeNormal();
}
this.dirty = false;
}
return this;
}
};
/**
* @class Plane
* @author Matthew Wagerfield
*/
FSS.Plane = function(width, height, segments, slices) {
FSS.Geometry.call(this);
this.width = width || 100;
this.height = height || 100;
this.segments = segments || 4;
this.slices = slices || 4;
this.segmentWidth = this.width / this.segments;
this.sliceHeight = this.height / this.slices;
// Cache Variables
var x, y, v0, v1, v2, v3,
vertex, triangle, vertices = [],
offsetX = this.width * -0.5,
offsetY = this.height * 0.5;
// Add Vertices
for (x = 0; x <= this.segments; x++) {
vertices.push([]);
for (y = 0; y <= this.slices; y++) {
vertex = new FSS.Vertex(offsetX + x*this.segmentWidth, offsetY - y*this.sliceHeight);
vertices[x].push(vertex);
this.vertices.push(vertex);
}
}
// Add Triangles
for (x = 0; x < this.segments; x++) {
for (y = 0; y < this.slices; y++) {
v0 = vertices[x+0][y+0];
v1 = vertices[x+0][y+1];
v2 = vertices[x+1][y+0];
v3 = vertices[x+1][y+1];
t0 = new FSS.Triangle(v0, v1, v2);
t1 = new FSS.Triangle(v2, v1, v3);
this.triangles.push(t0, t1);
}
}
};
FSS.Plane.prototype = Object.create(FSS.Geometry.prototype);
/**
* @class Material
* @author Matthew Wagerfield
*/
FSS.Material = function(ambient, diffuse) {
this.ambient = new FSS.Color(ambient || '#444444');
this.diffuse = new FSS.Color(diffuse || '#FFFFFF');
this.slave = new FSS.Color();
};
/**
* @class Mesh
* @author Matthew Wagerfield
*/
FSS.Mesh = function(geometry, material) {
FSS.Object.call(this);
this.geometry = geometry || new FSS.Geometry();
this.material = material || new FSS.Material();
this.side = FSS.FRONT;
this.visible = true;
};
FSS.Mesh.prototype = Object.create(FSS.Object.prototype);
FSS.Mesh.prototype.update = function(lights, calculate) {
var t,triangle, l,light, illuminance;
// Update Geometry
this.geometry.update();
// Calculate the triangle colors
if (calculate) {
// Iterate through Triangles
for (t = this.geometry.triangles.length - 1; t >= 0; t--) {
triangle = this.geometry.triangles[t];
// Reset Triangle Color
FSS.Vector4.set(triangle.color.rgba);
// Iterate through Lights
for (l = lights.length - 1; l >= 0; l--) {
light = lights[l];
// Calculate Illuminance
FSS.Vector3.subtractVectors(light.ray, light.position, triangle.centroid);
FSS.Vector3.normalise(light.ray);
illuminance = FSS.Vector3.dot(triangle.normal, light.ray);
if (this.side === FSS.FRONT) {
illuminance = Math.max(illuminance, 0);
} else if (this.side === FSS.BACK) {
illuminance = Math.abs(Math.min(illuminance, 0));
} else if (this.side === FSS.DOUBLE) {
illuminance = Math.max(Math.abs(illuminance), 0);
}
// Calculate Ambient Light
FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.ambient.rgba, light.ambient.rgba);
FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba);
// Calculate Diffuse Light
FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.diffuse.rgba, light.diffuse.rgba);
FSS.Vector4.multiplyScalar(this.material.slave.rgba, illuminance);
FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba);
}
// Clamp & Format Color
FSS.Vector4.clamp(triangle.color.rgba, 0, 1);
}
}
return this;
};
/**
* @class Scene
* @author Matthew Wagerfield
*/
FSS.Scene = function() {
this.meshes = [];
this.lights = [];
};
FSS.Scene.prototype = {
add: function(object) {
if (object instanceof FSS.Mesh && !~this.meshes.indexOf(object)) {
this.meshes.push(object);
} else if (object instanceof FSS.Light && !~this.lights.indexOf(object)) {
this.lights.push(object);
}
return this;
},
remove: function(object) {
if (object instanceof FSS.Mesh && ~this.meshes.indexOf(object)) {
this.meshes.splice(this.meshes.indexOf(object), 1);
} else if (object instanceof FSS.Light && ~this.lights.indexOf(object)) {
this.lights.splice(this.lights.indexOf(object), 1);
}
return this;
}
};
/**
* @class Renderer
* @author Matthew Wagerfield
*/
FSS.Renderer = function() {
this.width = 0;
this.height = 0;
this.halfWidth = 0;
this.halfHeight = 0;
};
FSS.Renderer.prototype = {
setSize: function(width, height) {
if (this.width === width && this.height === height) return;
this.width = width;
this.height = height;
this.halfWidth = this.width * 0.5;
this.halfHeight = this.height * 0.5;
return this;
},
clear: function() {
return this;
},
render: function(scene) {
return this;
}
};
/**
* @class Canvas Renderer
* @author Matthew Wagerfield
*/
FSS.CanvasRenderer = function() {
FSS.Renderer.call(this);
this.element = document.createElement('canvas');
this.element.style.display = 'block';
this.context = this.element.getContext('2d');
this.setSize(this.element.width, this.element.height);
this.element.style.position="absolute";
// this.element.style.z-index=-1;
/* var newspan=document.createElement("span");
newspan.setAttribute("id","showText");
this.element.appendChild(newspan);*/
};
FSS.CanvasRenderer.prototype = Object.create(FSS.Renderer.prototype);
FSS.CanvasRenderer.prototype.setSize = function(width, height) {
FSS.Renderer.prototype.setSize.call(this, width, height);
this.element.width = width;
this.element.height = height;
this.context.setTransform(1, 0, 0, -1, this.halfWidth, this.halfHeight);
return this;
};
FSS.CanvasRenderer.prototype.clear = function() {
FSS.Renderer.prototype.clear.call(this);
this.context.clearRect(-this.halfWidth, -this.halfHeight, this.width, this.height);
return this;
};
FSS.CanvasRenderer.prototype.render = function(scene) {
FSS.Renderer.prototype.render.call(this, scene);
var m,mesh, t,triangle, color;
// Clear Context
this.clear();
// Configure Context
this.context.lineJoin = 'round';
this.context.lineWidth = 1;
// Update Meshes
for (m = scene.meshes.length - 1; m >= 0; m--) {
mesh = scene.meshes[m];
if (mesh.visible) {
mesh.update(scene.lights, true);
// Render Triangles
for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) {
triangle = mesh.geometry.triangles[t];
color = triangle.color.format();
this.context.beginPath();
this.context.moveTo(triangle.a.position[0], triangle.a.position[1]);
this.context.lineTo(triangle.b.position[0], triangle.b.position[1]);
this.context.lineTo(triangle.c.position[0], triangle.c.position[1]);
this.context.closePath();
this.context.strokeStyle = color;
this.context.fillStyle = color;
this.context.stroke();
this.context.fill();
}
}
}
return this;
};
/**
* @class WebGL Renderer
* @author Matthew Wagerfield
*/
FSS.WebGLRenderer = function() {
FSS.Renderer.call(this);
this.element = document.createElement('canvas');
this.element.style.display = 'block';
// Set initial vertex and light count
this.vertices = null;
this.lights = null;
// Create parameters object
var parameters = {
preserveDrawingBuffer: false,
premultipliedAlpha: true,
antialias: true,
stencil: true,
alpha: true
};
// Create and configure the gl context
this.gl = this.getContext(this.element, parameters);
// Set the internal support flag
this.unsupported = !this.gl;
// Setup renderer
if (this.unsupported) {
return 'WebGL is not supported by your browser.';
} else {
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
this.gl.enable(this.gl.DEPTH_TEST);
this.setSize(this.element.width, this.element.height);
}
};
FSS.WebGLRenderer.prototype = Object.create(FSS.Renderer.prototype);
FSS.WebGLRenderer.prototype.getContext = function(canvas, parameters) {
var context = false;
try {
if (!(context = canvas.getContext('experimental-webgl', parameters))) {
throw 'Error creating WebGL context.';
}
} catch (error) {
console.error(error);
}
return context;
};
FSS.WebGLRenderer.prototype.setSize = function(width, height) {
FSS.Renderer.prototype.setSize.call(this, width, height);
if (this.unsupported) return;
// Set the size of the canvas element
this.element.width = width;
this.element.height = height;
// Set the size of the gl viewport
this.gl.viewport(0, 0, width, height);
return this;
};
FSS.WebGLRenderer.prototype.clear = function() {
FSS.Renderer.prototype.clear.call(this);
if (this.unsupported) return;
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
return this;
};
FSS.WebGLRenderer.prototype.render = function(scene) {
FSS.Renderer.prototype.render.call(this, scene);
if (this.unsupported) return;
var m,mesh, t,tl,triangle, l,light,
attribute, uniform, buffer, data, location,
update = false, lights = scene.lights.length,
index, v,vl,vetex,vertices = 0;
// Clear context
this.clear();
// Build the shader program
if (this.lights !== lights) {
this.lights = lights;
if (this.lights > 0) {
this.buildProgram(lights);
} else {
return;
}
}
// Update program
if (!!this.program) {
// Increment vertex counter
for (m = scene.meshes.length - 1; m >= 0; m--) {
mesh = scene.meshes[m];
if (mesh.geometry.dirty) update = true;
mesh.update(scene.lights, false);
vertices += mesh.geometry.triangles.length*3;
}
// Compare vertex counter
if (update || this.vertices !== vertices) {
this.vertices = vertices;
// Build buffers
for (attribute in this.program.attributes) {
buffer = this.program.attributes[attribute];
buffer.data = new FSS.Array(vertices*buffer.size);
// Reset vertex index
index = 0;
// Update attribute buffer data
for (m = scene.meshes.length - 1; m >= 0; m--) {
mesh = scene.meshes[m];
for (t = 0, tl = mesh.geometry.triangles.length; t < tl; t++) {
triangle = mesh.geometry.triangles[t];
for (v = 0, vl = triangle.vertices.length; v < vl; v++) {
vertex = triangle.vertices[v];
switch (attribute) {
case 'side':
this.setBufferData(index, buffer, mesh.side);
break;
case 'position':
this.setBufferData(index, buffer, vertex.position);
break;
case 'centroid':
this.setBufferData(index, buffer, triangle.centroid);
break;
case 'normal':
this.setBufferData(index, buffer, triangle.normal);
break;
case 'ambient':
this.setBufferData(index, buffer, mesh.material.ambient.rgba);
break;
case 'diffuse':
this.setBufferData(index, buffer, mesh.material.diffuse.rgba);
break;
}
index++;
}
}
}
// Upload attribute buffer data
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer.buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, buffer.data, this.gl.DYNAMIC_DRAW);
this.gl.enableVertexAttribArray(buffer.location);
this.gl.vertexAttribPointer(buffer.location, buffer.size, this.gl.FLOAT, false, 0, 0);
}
}
// Build uniform buffers
this.setBufferData(0, this.program.uniforms.resolution, [this.width, this.height, this.width]);
for (l = lights-1; l >= 0; l--) {
light = scene.lights[l];
this.setBufferData(l, this.program.uniforms.lightPosition, light.position);
this.setBufferData(l, this.program.uniforms.lightAmbient, light.ambient.rgba);
this.setBufferData(l, this.program.uniforms.lightDiffuse, light.diffuse.rgba);
}
// Update uniforms
for (uniform in this.program.uniforms) {
buffer = this.program.uniforms[uniform];
location = buffer.location;
data = buffer.data;
switch (buffer.structure) {
case '3f':
this.gl.uniform3f(location, data[0], data[1], data[2]);
break;
case '3fv':
this.gl.uniform3fv(location, data);
break;
case '4fv':
this.gl.uniform4fv(location, data);
break;
}
}
}
// Draw those lovely triangles
this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertices);
return this;
};
FSS.WebGLRenderer.prototype.setBufferData = function(index, buffer, value) {
if (FSS.Utils.isNumber(value)) {
buffer.data[index*buffer.size] = value;
} else {
for (var i = value.length - 1; i >= 0; i--) {
buffer.data[index*buffer.size+i] = value[i];
}
}
};
/**
* Concepts taken from three.js WebGLRenderer
* @see https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLRenderer.js
*/
FSS.WebGLRenderer.prototype.buildProgram = function(lights) {
if (this.unsupported) return;
// Create shader source
var vs = FSS.WebGLRenderer.VS(lights);
var fs = FSS.WebGLRenderer.FS(lights);
// Derive the shader fingerprint
var code = vs + fs;
// Check if the program has already been compiled
if (!!this.program && this.program.code === code) return;
// Create the program and shaders
var program = this.gl.createProgram();
var vertexShader = this.buildShader(this.gl.VERTEX_SHADER, vs);
var fragmentShader = this.buildShader(this.gl.FRAGMENT_SHADER, fs);
// Attach an link the shader
this.gl.attachShader(program, vertexShader);
this.gl.attachShader(program, fragmentShader);
this.gl.linkProgram(program);
// Add error handling
if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
var error = this.gl.getError();
var status = this.gl.getProgramParameter(program, this.gl.VALIDATE_STATUS);
console.error('Could not initialise shader.\nVALIDATE_STATUS: '+status+'\nERROR: '+error);
return null;
}
// Delete the shader
this.gl.deleteShader(fragmentShader);
this.gl.deleteShader(vertexShader);
// Set the program code
program.code = code;
// Add the program attributes
program.attributes = {
side: this.buildBuffer(program, 'attribute', 'aSide', 1, 'f' ),
position: this.buildBuffer(program, 'attribute', 'aPosition', 3, 'v3'),
centroid: this.buildBuffer(program, 'attribute', 'aCentroid', 3, 'v3'),
normal: this.buildBuffer(program, 'attribute', 'aNormal', 3, 'v3'),
ambient: this.buildBuffer(program, 'attribute', 'aAmbient', 4, 'v4'),
diffuse: this.buildBuffer(program, 'attribute', 'aDiffuse', 4, 'v4')
};
// Add the program uniforms
program.uniforms = {
resolution: this.buildBuffer(program, 'uniform', 'uResolution', 3, '3f', 1 ),
lightPosition: this.buildBuffer(program, 'uniform', 'uLightPosition', 3, '3fv', lights),
lightAmbient: this.buildBuffer(program, 'uniform', 'uLightAmbient', 4, '4fv', lights),
lightDiffuse: this.buildBuffer(program, 'uniform', 'uLightDiffuse', 4, '4fv', lights)
};
// Set the renderer program
this.program = program;
// Enable program
this.gl.useProgram(this.program);
// Return the program
return program;
};
FSS.WebGLRenderer.prototype.buildShader = function(type, source) {
if (this.unsupported) return;
// Create and compile shader
var shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
// Add error handling
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
console.error(this.gl.getShaderInfoLog(shader));
return null;
}
// Return the shader
return shader;
};
FSS.WebGLRenderer.prototype.buildBuffer = function(program, type, identifier, size, structure, count) {
var buffer = {buffer:this.gl.createBuffer(), size:size, structure:structure, data:null};
// Set the location
switch (type) {
case 'attribute':
buffer.location = this.gl.getAttribLocation(program, identifier);
break;
case 'uniform':
buffer.location = this.gl.getUniformLocation(program, identifier);
break;
}
// Create the buffer if count is provided
if (!!count) {
buffer.data = new FSS.Array(count*size);
}
// Return the buffer
return buffer;
};
FSS.WebGLRenderer.VS = function(lights) {
var shader = [
// Precision
'precision mediump float;',
// Lights
'#define LIGHTS ' + lights,
// Attributes
'attribute float aSide;',
'attribute vec3 aPosition;',
'attribute vec3 aCentroid;',
'attribute vec3 aNormal;',
'attribute vec4 aAmbient;',
'attribute vec4 aDiffuse;',
// Uniforms
'uniform vec3 uResolution;',
'uniform vec3 uLightPosition[LIGHTS];',
'uniform vec4 uLightAmbient[LIGHTS];',
'uniform vec4 uLightDiffuse[LIGHTS];',
// Varyings
'varying vec4 vColor;',
// Main
'void main() {',
// Create color
'vColor = vec4(0.0);',
// Calculate the vertex position
'vec3 position = aPosition / uResolution * 2.0;',
// Iterate through lights
'for (int i = 0; i < LIGHTS; i++) {',
'vec3 lightPosition = uLightPosition[i];',
'vec4 lightAmbient = uLightAmbient[i];',
'vec4 lightDiffuse = uLightDiffuse[i];',
// Calculate illuminance
'vec3 ray = normalize(lightPosition - aCentroid);',
'float illuminance = dot(aNormal, ray);',
'if (aSide == 0.0) {',
'illuminance = max(illuminance, 0.0);',
'} else if (aSide == 1.0) {',
'illuminance = abs(min(illuminance, 0.0));',
'} else if (aSide == 2.0) {',
'illuminance = max(abs(illuminance), 0.0);',
'}',
// Calculate ambient light
'vColor += aAmbient * lightAmbient;',
// Calculate diffuse light
'vColor += aDiffuse * lightDiffuse * illuminance;',
'}',
// Clamp color
'vColor = clamp(vColor, 0.0, 1.0);',
// Set gl_Position
'gl_Position = vec4(position, 1.0);',
'}'
// Return the shader
].join('\n');
return shader;
};
FSS.WebGLRenderer.FS = function(lights) {
var shader = [
// Precision
'precision mediump float;',
// Varyings
'varying vec4 vColor;',
// Main
'void main() {',
// Set gl_FragColor
'gl_FragColor = vColor;',
'}'
// Return the shader
].join('\n');
return shader;
};
/**
* @class SVG Renderer
* @author Matthew Wagerfield
*/
FSS.SVGRenderer = function() {
FSS.Renderer.call(this);
this.element = document.createElementNS(FSS.SVGNS, 'svg');
this.element.setAttribute('xmlns', FSS.SVGNS);
this.element.setAttribute('version', '1.1');
this.element.style.display = 'block';
this.setSize(300, 150);
};
FSS.SVGRenderer.prototype = Object.create(FSS.Renderer.prototype);
FSS.SVGRenderer.prototype.setSize = function(width, height) {
FSS.Renderer.prototype.setSize.call(this, width, height);
this.element.setAttribute('width', width);
this.element.setAttribute('height', height);
return this;
};
FSS.SVGRenderer.prototype.clear = function() {
FSS.Renderer.prototype.clear.call(this);
for (var i = this.element.childNodes.length - 1; i >= 0; i--) {
this.element.removeChild(this.element.childNodes[i]);
}
return this;
};
FSS.SVGRenderer.prototype.render = function(scene) {
FSS.Renderer.prototype.render.call(this, scene);
var m,mesh, t,triangle, points, style;
// Update Meshes
for (m = scene.meshes.length - 1; m >= 0; m--) {
mesh = scene.meshes[m];
if (mesh.visible) {
mesh.update(scene.lights, true);
// Render Triangles
for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) {
triangle = mesh.geometry.triangles[t];
if (triangle.polygon.parentNode !== this.element) {
this.element.appendChild(triangle.polygon);
}
points = this.formatPoint(triangle.a)+' ';
points += this.formatPoint(triangle.b)+' ';
points += this.formatPoint(triangle.c);
style = this.formatStyle(triangle.color.format());
triangle.polygon.setAttributeNS(null, 'points', points);
triangle.polygon.setAttributeNS(null, 'style', style);
}
}
}
return this;
};
FSS.SVGRenderer.prototype.formatPoint = function(vertex) {
return (this.halfWidth+vertex.position[0])+','+(this.halfHeight-vertex.position[1]);
};
FSS.SVGRenderer.prototype.formatStyle = function(color) {
var style = 'fill:'+color+';';
style += 'stroke:'+color+';';
return style;
};