Mini Shell
//============================================================
//
// Copyright below.
//
// CoAuthor: Patrick Geyer
//
// Twitter: https://twitter.com/PatrickGeyer_
//
//============================================================
//============================================================
//
// 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;
},
floor: function (target) {
target[0] = Math.floor(target[0]);
target[1] = Math.floor(target[1]);
target[2] = Math.floor(target[2]);
return target;
}
};
/**
* @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 (color, opacity) {
this.rgba = [];
this.color = color || '#000000';
this.opacity = FSS.Utils.isNumber(opacity) ? opacity : 1;
this.set(this.color, this.opacity);
};
FSS.Color.prototype = {
set: function (color, opacity) {
if (color.indexOf("#") === -1) {
if (color.indexOf('rgb(') === 0) {
var pars = color.indexOf(',');
this.rgba[0] = parseInt(color.substr(4, pars));
this.rgba[1] = parseInt(color.substr(pars + 1, color.indexOf(',', pars)));
this.rgba[2] = parseInt(color.substr(color.indexOf(',', pars + 1) + 1, color.indexOf(')')));
this.rgba[3] = 1;
} else if (color.indexOf('rgba(') === 0) {
var pars = color.indexOf(',');
var repars = color.indexOf(',', pars + 1);
this.rgba[0] = parseInt(color.substr(5, pars));
this.rgba[1] = parseInt(color.substr(pars + 1, repars));
this.rgba[2] = parseInt(color.substr(color.indexOf(',', pars + 1) + 1, color.indexOf(',', repars)));
this.rgba[3] = parseFloat(color.substr(color.indexOf(',', repars + 1) + 1, color.indexOf(')')));
}
} else {
color = color.replace('#', '');
var size = color.length / 3;
this.rgba[0] = parseInt(color.substring(size * 0, size * 1), 16) / 255;
this.rgba[1] = parseInt(color.substring(size * 1, size * 2), 16) / 255;
this.rgba[2] = parseInt(color.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 () {
return "rgba(" + this.rgba[0] + "," + this.rgba[1] + "," + this.rgba[2] + "," + this.rgba[3] + ")"; //this.hex
// 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 || 'rgba(68,68,68, 1)');
this.diffuse = new FSS.Color(diffuse || 'rgba(255,255,255, 1)');
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, light_count;
light_count = lights.length;
// 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
for (var i = 0; i < 3; i++) {
this.material.slave.rgba[i] = (((1 / light_count) * this.material.ambient.rgba[i]) * ((1 / light_count) * light.ambient.rgba[i])) / 128;
if (i !== 3) {
this.material.slave.rgba[i] = Math.round(this.material.slave.rgba[i]);
}
}
/* Add the resultant values to the triangle color vector. Not required to factor illuminance because it is ambient light. */
FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba);
// Calculate Diffuse Light
for (var i = 0; i < 3; i++) {
this.material.slave.rgba[i] = ((1 / light_count) * this.material.diffuse.rgba[i] * (1 / light_count) * light.diffuse.rgba[i]) / 128;
if (i !== 3) {
this.material.slave.rgba[i] = Math.round(this.material.slave.rgba[i]);
}
}
// FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.diffuse.rgba, light.diffuse.rgba);
// FSS.Vector4.multiplyScalar(this.material.slave.rgba, illuminance);
for (var i = 0; i < 3; i++) {
this.material.slave.rgba[i] = Math.round(this.material.slave.rgba[i] * illuminance);
}
FSS.Vector4.add(triangle.color.rgba, this.material.slave.rgba);
}
// Clamp & Format Color
FSS.Vector4.clamp(triangle.color.rgba, 0, 255);
triangle.color.rgba[3] = this.material.diffuse.rgba[3]; //Math.min(triangle.color.rgba[3], 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.element.style.zIndex = "-100";
this.element.style.pointerEvents = "none";
this.context = this.element.getContext('2d');
this.setSize(this.element.width, this.element.height);
};
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, 0, 0);
return this;
};
FSS.CanvasRenderer.prototype.clear = function () {
FSS.Renderer.prototype.clear.call(this);
this.context.clearRect(0, 0, this.width, this.height);
return this;
};
var opacity = [];
FSS.CanvasRenderer.prototype.render = function (scene) {
FSS.Renderer.prototype.render.call(this, scene);
var m, mesh, t, triangle, color;
var pi2 = 2 * Math.PI;
// Clear Context
this.clear();
// Configure Context
this.context.lineJoin = 'round';
this.context.lineWidth = 0;
// Update Meshes
for (m = scene.meshes.length - 1; m >= 0; m--) {
mesh = scene.meshes[m];
if (typeof opacity[m] == "undefined") {
opacity[m] = [];
}
if (mesh.visible) {
mesh.update(scene.lights, true);
// Render Triangles
for (t = mesh.geometry.triangles.length - 1; t >= 0; t--) {
var now = Date.now();
if (typeof opacity[m][t] === "undefined") {
opacity[m][t] = {};
opacity[m][t].step = FSS.Vector3.create(
Math.randomInRange(0.2, 1.0),
Math.randomInRange(0.2, 1.0),
Math.randomInRange(0.2, 1.0)
);
opacity[m][t].time = Math.randomInRange(0, Math.PIM2);
opacity[m][t].line = 0;
} else {
opacity[m][t].line = Math.sin(opacity[m][t].time + opacity[m][t].step[0] * now * (scene.LINE.fluctuationSpeed / 100)) * scene.LINE.fluctuationIntensity;
opacity[m][t].vertex = Math.sin(opacity[m][t].time + opacity[m][t].step[0] * now * (scene.VERTEX.fluctuationSpeed / 100)) * scene.VERTEX.fluctuationIntensity;
opacity[m][t].mesh = Math.sin(opacity[m][t].time + opacity[m][t].step[0] * now * (scene.MESH.fluctuationSpeed / 100)) * scene.MESH.fluctuationIntensity;
}
triangle = mesh.geometry.triangles[t];
if (scene.MESH.draw !== false) {
c = triangle.color.rgba;
color = "rgba(" + c[0] + "," + c[1] + ", " + c[2] + "," + c[3] + ")";
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.fillStyle = color; //Color of triangle
this.context.fill();
}
if (scene.LINE.draw !== false) {
var c = new FSS.Color(scene.LINE.fill);
c = c.rgba;
c[3] = c[3] * (1 - opacity[m][t].line);
c = "rgba(" + c[0] + "," + c[1] + ", " + c[2] + "," + c[3] + ")";
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.lineWidth = scene.LINE.thickness;
this.context.fillStyle = c;
this.context.fill();
this.context.strokeStyle = c;
this.context.stroke();
}
if (scene.VERTEX.draw !== false) {
// var grd = this.context.createRadialGradient(triangle.a.position[0], triangle.a.position[1], scene.vertex.radius + 100, triangle.a.position[0], triangle.a.position[1], scene.vertex.radius + 105);
// light blue
// grd.addColorStop(0, '#8ED6FF');
// dark blue
// grd.addColorStop(1, '#004CB3');
var c = new FSS.Color(scene.VERTEX.fill);
c = c.rgba;
c[3] = c[3] * (1 - opacity[m][t].vertex);
c = "rgba(" + c[0] + "," + c[1] + ", " + c[2] + "," + c[3] + ")";
var c1 = new FSS.Color(scene.VERTEX.strokeColor);
c1 = c1.rgba;
c1[3] = c1[3] * (1 - opacity[m][t].vertex);
c1 = "rgba(" + c1[0] + "," + c1[1] + ", " + c1[2] + "," + c1[3] + ")";
this.context.beginPath();
this.context.arc(triangle.a.position[0], triangle.a.position[1], scene.VERTEX.radius, 0, pi2, false);
this.context.fillStyle = c; //scene.VERTEX.fill;
this.context.fill();
this.context.lineWidth = scene.VERTEX.strokeWidth;
this.context.strokeStyle = c1;
this.context.stroke();
}
}
}
}
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 (vertex.position[0]) + ',' + (vertex.position[1]);
};
FSS.SVGRenderer.prototype.formatStyle = function (color) {
var style = 'fill:' + color + ';';
style += 'stroke:' + color + ';';
return style;
};
(function () {
$.fn.Geometryangle = function (opt) {
var fss = [];
var element = $(this);
var len = element.length, k;
for (var j = 0; j < len; j++) {
k = element[j];
fss[fss.length] = FSS_Worker(opt, k);
}
return (fss.length == 1 ? fss[0] : fss);
};
var FSS_Worker = function (opt, k) {
opt = opt || {};
var MESH = {}, LIGHT = [{}],
VERTEX = {}, LINE = {};
var getDataAttr = function (element) {
var data_option = [
[],
[]
];
// var keys = Object.keys(option);
var d = [MESH, LIGHT];
$.each(d, function (u) {
$.each(d[u], function (i, val) {
if (typeof element.getAttribute('data-fss-' [i]) !== "undefined" && typeof element.getAttribute('data-fss-' + [i]) !== false) {
try {
data_option[u][i] = $.parseJSON(element.getAttribute('data-fss-' + [i]));
return true; //continue;
} catch (e) {
}
if (typeof data_option[u][i] !== "object") {
data_option[u][i] = element.getAttribute('data-fss-' + [i]);
}
}
});
var data_json = (typeof element.getAttribute('data-fss') !== "undefined" && typeof element.getAttribute('data-fss') !== false ? $.parseJSON(element.getAttribute('data-fss')) : []);
$.extend(true, data_option[u], data_json);
});
return data_option;
};
var rgbaToRgb = function (rgba) {
try {
var bits = rgba.split("(");
} catch (e) {
return;
}
if (typeof bits[1] !== "undefined") {
bits = bits[1].split(")")[0].split(",");
return "rgb(" + bits[0] + "," + bits[1] + "," + bits[2] + ")";
}
return;
};
//------------------------------
// Mesh Properties
//------------------------------
var mesh_default = {
width: 1.2,
height: 1.2,
depth: 10,
columns: undefined,
columns_auto: true,
rows: undefined,
rows_auto: true,
zoom: 1,
xRange: 0.8,
yRange: 0.1,
zRange: 1.0,
ambient: 'rgba(85, 85, 85, 1)',
diffuse: 'rgba(255, 255, 255, 1)',
background: 'rgb(255, 255, 255)',
speed: 0.0002,
fluctuationSpeed: 0.5,
fluctuationIntensity: 0,
onRender: function () {
},
floorPosition: false,
draw: true
};
var vertex_default = {
radius: 0,
fill: "rgba(0, 0, 0, 0)",
fluctuationSpeed: 0.5,
fluctuationIntensity: 0,
strokeWidth: 0,
strokeColor: "rgba(0, 0, 0, 0)",
draw: false
};
var line_default = {
fill: "rgba(0, 0, 0, 0)",
thickness: 1,
fluctuationIntensity: 0,
fluctuationSpeed: 0.5,
draw: false
};
//------------------------------
// Light Properties
//------------------------------
var light_default = {
count: 1,
xyScalar: 1,
zOffset: 100,
ambient: 'rgba(255,0,102, 1)',
diffuse: 'rgba(255,136,0, 1)',
speed: 0.010,
gravity: 1200,
dampening: 0.95,
minLimit: 10,
maxLimit: null,
minDistance: 20,
maxDistance: 400,
autopilot: false,
draw: false, //show circle
bounds: FSS.Vector3.create(),
step: FSS.Vector3.create(
Math.randomInRange(0.2, 1.0),
Math.randomInRange(0.2, 1.0),
Math.randomInRange(0.2, 1.0)
)
};
var self = k;
var createValues = function (opt) {
opt.mesh = opt.mesh || MESH;
opt.lights = opt.lights || LIGHT;
opt.vertex = opt.vertex || VERTEX;
opt.line = opt.line || LINE;
MESH = $.extend(true, mesh_default, MESH, opt.mesh);
VERTEX = $.extend(true, vertex_default, VERTEX, opt.vertex);
LINE = $.extend(true, line_default, LINE, opt.line);
for (var i = 0; i < LIGHT.length; i++) {
LIGHT[i] = $.extend(true, light_default, LIGHT[i], opt.lights[i]);
}
var box_data_option = getDataAttr(self);
MESH = $.extend(true, box_data_option[0], MESH);
MESH.columns_auto = (typeof opt.mesh.columns === "undefined");
MESH.rows_auto = (typeof opt.mesh.rows === "undefined");
};
createValues({
mesh: mesh_default,
line: line_default,
vertex: vertex_default,
lights: [light_default]
});
createValues(opt);
var container = document.createElement("div");
container.style.position = "absolute";
container.style.left = "0";
container.style.right = "0";
container.style.top = "0";
container.style.bottom = "0";
container.style.background = MESH.background;
container.style.zIndex = "-100";
container.setAttribute('class', 'fss-output');
self.insertBefore(container, null);
//------------------------------
// Render Properties
//------------------------------
var WEBGL = 'webgl';
var CANVAS = 'canvas';
var SVG = 'svg';
var RENDER = {
renderer: CANVAS
};
//------------------------------
// UI Properties
//------------------------------
var UI = {
show: true
};
//------------------------------
// Global Properties
//------------------------------
var now, start = Date.now();
var center = FSS.Vector3.create();
var attractor = FSS.Vector3.create();
//var container = document.getElementById('container'); -- taken from JQuery element
/* var output = document.getElementById('output'); */
var ui = document.getElementById('ui');
var renderer, scene, mesh, geometry, material;
var webglRenderer, canvasRenderer, svgRenderer;
var gui, autopilotController;
//------------------------------
// Methods
//------------------------------
function initialise() {
createRenderer();
createScene();
createMesh();
createLights();
addEventListeners();
callbacks.resize(container.offsetWidth, container.offsetHeight);
animate();
}
function createRenderer() {
webglRenderer = new FSS.WebGLRenderer();
canvasRenderer = new FSS.CanvasRenderer();
svgRenderer = new FSS.SVGRenderer();
setRenderer(RENDER.renderer);
}
function setRenderer(index) {
if (renderer) {
/* output.removeChild(renderer.element); */
}
switch (index) {
case WEBGL:
renderer = webglRenderer;
break;
case CANVAS:
renderer = canvasRenderer;
break;
case SVG:
renderer = svgRenderer;
break;
}
renderer.setSize(container.offsetWidth, container.offsetHeight);
container.insertBefore(renderer.element, null);
var style = window.getComputedStyle(self);
if (style.getPropertyValue('position') == 'static' || style.getPropertyValue('position').length == 0) {
self.style.position = 'relative';
}
}
function createScene() {
scene = new FSS.Scene();
scene.VERTEX = VERTEX;
scene.LINE = LINE;
scene.MESH = MESH;
}
function createMesh() {
scene.remove(mesh);
renderer.clear();
geometry = new FSS.Plane(MESH.width * renderer.width, MESH.height * renderer.height, MESH.columns, MESH.rows);
material = new FSS.Material(MESH.ambient, MESH.diffuse);
mesh = new FSS.Mesh(geometry, material);
scene.add(mesh);
// Augment vertices for animation
var v, vertex;
for (v = geometry.vertices.length - 1; v >= 0; v--) {
vertex = geometry.vertices[v];
vertex.anchor = FSS.Vector3.floor(FSS.Vector3.clone(vertex.position));
vertex.step = FSS.Vector3.create(
Math.randomInRange(0.2, 1.0),
Math.randomInRange(0.2, 1.0),
Math.randomInRange(0.2, 1.0)
);
vertex.time = Math.randomInRange(0, Math.PIM2);
}
}
function createLights() {
var l, light;
for (l = scene.lights.length - 1; l >= 0; l--) {
light = scene.lights[l];
scene.remove(light);
}
renderer.clear();
for (l = 0; l < LIGHT.length; l++) {
for (var u = 0; u < LIGHT[l].count; u++) {
light = new FSS.Light(LIGHT[l].ambient, LIGHT[l].diffuse);
scene.add(light);
// Augment light for animation
light.mass = Math.randomInRange(0.5, 1);
light.velocity = FSS.Vector3.create();
light.acceleration = FSS.Vector3.create();
light.force = FSS.Vector3.create();
// Ring SVG Circle
light.ring = document.createElementNS(FSS.SVGNS, 'circle');
light.ring.setAttributeNS(null, 'stroke', light.ambient);
light.ring.setAttributeNS(null, 'stroke-width', '0.5');
light.ring.setAttributeNS(null, 'fill', 'none');
light.ring.setAttributeNS(null, 'r', '10');
// Core SVG Circle
light.core = document.createElementNS(FSS.SVGNS, 'circle');
light.core.setAttributeNS(null, 'fill', light.diffuseHex);
light.core.setAttributeNS(null, 'r', '4');
}
}
}
var callbacks = {
resize: function (width, height) {
if (typeof width == "undefined" || typeof width === undefined) {
width = self.width();
}
if (typeof height == "undefined" || typeof height === undefined) {
height = self.height();
}
var ratio_x = width / 1000;
var ratio_y = height / 1000;
var x_tiles = Math.round(ratio_x * 10) * MESH.zoom;
var y_tiles = Math.round(ratio_y * 10) * MESH.zoom;
MESH.columns = (MESH.columns_auto === true ? x_tiles : MESH.columns);
MESH.rows = (MESH.rows_auto === true ? y_tiles : MESH.rows);
renderer.setSize(width, height);
FSS.Vector3.set(center, renderer.halfWidth, renderer.halfHeight);
createMesh();
},
update: function (opt) {
createValues(opt);
scene.vertex = VERTEX;
scene.line = LINE;
//Ambient
for (i = 0, l = scene.meshes.length; i < l; i++) {
scene.meshes[i].material.ambient.set(MESH.ambient);
scene.meshes[i].material.diffuse.set(MESH.diffuse);
}
//width
if (geometry.width !== MESH.width * renderer.width) {
createMesh();
}
if (geometry.height !== MESH.height * renderer.height) {
createMesh();
}
if (geometry.segments !== MESH.columns) {
createMesh();
}
if (geometry.slices !== MESH.rows) {
createMesh();
}
var light_index = 0;
for (l = 0; l < LIGHT.length; l++) {
for (var i = 0; i < LIGHT[l].count; i++) {
light = scene.lights[light_index];
light.ambient.set(LIGHT[l].ambient);
light = scene.lights[light_index];
light.diffuse.set(LIGHT[l].diffuse);
light_index++;
}
}
if (scene.lights.length !== light_index) {
createLights();
}
},
animateValues: function (colors) {
var body = document.body,
html = document.documentElement, scrollTop = ((window.pageYOffset || html.scrollTop) - (html.clientTop || 0));
var height = Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);
var length = colors.length;
var height = Math.round(height / length); // Height of the segment between two colors
var i = Math.floor(scrollTop / height); // Start color index
var d = scrollTop % height / height; // Which part of the segment between start color and end color is passed
var c1 = colors[i]; // Start color
var c2 = colors[(i + 1) % length]; // End color
var result = [];
for (var i = 0; i < c1.length; i++) {
result[i] = c1[i] + ((c2[i] - c1[i]) * d);
if (i !== 3) {
result[i] = Math.round(result[i]);
}
}
return result;
},
formatRGBA: function (a) {
var string = "rgba(" + a[0] + "," + a[1] + "," + a[2] + "," + a[3] + ")";
return string;
}
};
function animate() {
now = Date.now() - start;
update();
render();
requestAnimationFrame(animate);
}
function update() {
var ox, oy, oz, l, light, v, vertex, offset = MESH.depth / 2;
var light_index = 0;
var render_vector = FSS.Vector3.floor(FSS.Vector3.create(renderer.halfWidth, renderer.halfHeight, 0));
// Animate Lights
for (l = 0; l < LIGHT.length; l++) {
for (var i = 0; i < LIGHT[l].count; i++) {
light = scene.lights[light_index];
// Update Bounds
FSS.Vector3.copy(LIGHT[l].bounds, center);
FSS.Vector3.multiplyScalar(LIGHT[l].bounds, LIGHT[l].xyScalar);
// Update Attractor
FSS.Vector3.setZ(attractor, LIGHT[l].zOffset);
// Overwrite the Attractor position
if (LIGHT[l].autopilot && typeof LIGHT[l].position === "undefined") {
ox = Math.sin(LIGHT[l].step[0] * now * LIGHT[l].speed);
oy = Math.cos(LIGHT[l].step[1] * now * LIGHT[l].speed);
FSS.Vector3.set(attractor,
LIGHT[l].bounds[0] * ox,
LIGHT[l].bounds[1] * oy,
LIGHT[l].zOffset);
}
// Reset the z position of the light
FSS.Vector3.setZ(light.position, LIGHT[l].zOffset);
if (typeof LIGHT[l].position !== "undefined") {
FSS.Vector3.set(light.position);
FSS.Vector3.add(light.position, FSS.Vector3.create(LIGHT[l].position[0], LIGHT[l].position[1], LIGHT[l].zOffset));
} else {
// Calculate the force Luke!
var D = Math.clamp(FSS.Vector3.distanceSquared(light.position, attractor), LIGHT[l].minDistance, LIGHT[l].maxDistance);
var F = LIGHT[l].gravity * light.mass / D;
FSS.Vector3.subtractVectors(light.force, attractor, light.position);
FSS.Vector3.normalise(light.force);
FSS.Vector3.multiplyScalar(light.force, F);
// Update the light position
FSS.Vector3.set(light.acceleration);
FSS.Vector3.add(light.acceleration, light.force);
FSS.Vector3.add(light.velocity, light.acceleration);
FSS.Vector3.multiplyScalar(light.velocity, LIGHT[l].dampening);
FSS.Vector3.limit(light.velocity, LIGHT[l].minLimit, LIGHT[l].maxLimit);
FSS.Vector3.add(light.position, light.velocity);
}
light_index++;
}
}
// Animate Vertices
for (v = geometry.vertices.length - 1; v >= 0; v--) {
vertex = geometry.vertices[v];
ox = Math.sin(vertex.time + vertex.step[0] * now * MESH.speed);
oy = Math.cos(vertex.time + vertex.step[1] * now * MESH.speed);
oz = Math.sin(vertex.time + vertex.step[2] * now * MESH.speed);
vertex.position = FSS.Vector3.create(
MESH.xRange * geometry.segmentWidth * ox,
MESH.yRange * geometry.sliceHeight * oy,
MESH.zRange * offset * oz - offset);
if (MESH.positionFloor === true) {
vertex.position = FSS.Vector3.floor(vertex.position);
}
FSS.Vector3.add(vertex.position, vertex.anchor);
FSS.Vector3.add(vertex.position, render_vector);
}
// Set the Geometry to dirty
geometry.dirty = true;
}
function render() {
renderer.render(scene);
// Draw Lights
if (LIGHT.draw) {
var l, lx, ly, light;
for (l = scene.lights.length - 1; l >= 0; l--) {
light = scene.lights[l];
lx = light.position[0];
ly = light.position[1];
switch (RENDER.renderer) {
case CANVAS:
renderer.context.lineWidth = 0.5;
renderer.context.beginPath();
renderer.context.arc(lx, ly, 10, 0, Math.PIM2);
renderer.context.strokeStyle = light.ambient;
renderer.context.stroke();
renderer.context.beginPath();
renderer.context.arc(lx, ly, 4, 0, Math.PIM2);
renderer.context.fillStyle = light.diffuse;
renderer.context.fill();
break;
case SVG:
/* lx += renderer.halfWidth; */
/* ly = renderer.halfHeight - ly; */
light.core.setAttributeNS(null, 'fill', light.diffuse);
light.core.setAttributeNS(null, 'cx', lx);
light.core.setAttributeNS(null, 'cy', ly);
renderer.element.appendChild(light.core);
light.ring.setAttributeNS(null, 'stroke', light.ambient);
light.ring.setAttributeNS(null, 'cx', lx);
light.ring.setAttributeNS(null, 'cy', ly);
renderer.element.appendChild(light.ring);
break;
}
}
}
MESH.onRender(scene, renderer.context);
}
function addEventListeners() {
if(window.attachEvent) {
window.addEventHandler = window.attachEvent;
}
window.addEventListener('resize', onWindowResize, false);
self.addEventListener('click', onMouseClick, false);
self.addEventListener('mousemove', onMouseMove, true);
}
//------------------------------
// Callbacks
//------------------------------
function onMouseClick(event) {
FSS.Vector3.set(attractor, event.x, event.y);
/* FSS.Vector3.subtract(attractor, center); */
LIGHT.autopilot = !LIGHT.autopilot;
}
function onMouseMove(event) {
console.log(event);
FSS.Vector3.set(attractor, event.x, event.y);
/* FSS.Vector3.subtract(attractor, center); */
}
function onWindowResize(event) {
callbacks.resize(self.offsetWidth, self.offsetHeight);
render();
}
// Let there be light!
initialise();
return callbacks;
};
})();
$(document).ready(function(){
$('.background').Geometryangle({
// handle transparent colors
mesh:{
width: 1.2,
height: 1.2,
// How far should the mesh vary into z-space.
depth: 10,
// Number of columns for the mesh.
columns: 12,
columns_auto: false,
// Number of rows for the mesh.
rows: 7,
rows_auto: true,
zoom: 1,
xRange: 0.8,
yRange: 0.1,
zRange: 3.0,
ambient: 'rgba(14, 9, 131, 1)',
diffuse: 'rgba(136, 12, 131, 1)',
background: 'rgb(241, 173, 27)',
speed: 0.0002,
fluctuationSpeed: .2,
fluctuationIntensity: 0,
onRender: function () {
},
floorPosition: false,
draw: true
},
lights: {
// How many light sources belong to this light.
count: 1,
xyScalar: 1,
// Position of light source.
zOffset: 100,
ambient: 'rgba(97,0,94, 1)',
diffuse: 'rgba(97,18,94, 1)',
speed: 0.001,
gravity: 100,
// Dampening of light's movements.
dampening: 0.95,
minLimit: 10,
maxLimit: null,
minDistance: 20,
maxDistance: 400,
autopilot: false,
draw: false, //show circle
bounds: FSS.Vector3.create(),
step: FSS.Vector3.create(
Math.randomInRange(0.2, 1.0),
Math.randomInRange(0.2, 1.0),
Math.randomInRange(0.2, 1.0)
)
},
// specify the thickness, color, stroke, etc.
line: {
fill: "rgba(0, 0, 0, 0)",
thickness: 1,
fluctuationIntensity: 0,
fluctuationSpeed: 0.5,
draw: false
},
// Set the point attributes for the vertex.
vertex: {
// Radius of vertice circle.
radius: 0,
fill: "rgba(0, 0, 0, 0)",
// Fluctuates opacity of vertex.
fluctuationSpeed: 0.5,
fluctuationIntensity: 0,
strokeWidth: 0,
strokeColor: "rgba(0, 0, 0, 0)",
// Instead of setting alpha channel to zero
// Set draw to false to avoid computing.
draw: false
}
});
});