1232 lines
33 KiB
JavaScript
1232 lines
33 KiB
JavaScript
|
|
//============================================================
|
||
|
|
//
|
||
|
|
// 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;
|
||
|
|
};
|