968 lines
28 KiB
JavaScript
968 lines
28 KiB
JavaScript
function FSS(container, output){
|
|
FSS = {
|
|
FRONT: 0,
|
|
BACK: 1,
|
|
DOUBLE: 2,
|
|
SVGNS: "http://www.w3.org/2000/svg"
|
|
};
|
|
FSS.Array = "function" === typeof Float32Array ? Float32Array: Array;
|
|
FSS.Utils = {
|
|
isNumber: function(b) {
|
|
return ! isNaN(parseFloat(b)) && isFinite(b)
|
|
}
|
|
};
|
|
|
|
|
|
(function() {
|
|
for (var b = 0, d = ["ms", "moz", "webkit", "o"], h = 0; h < d.length && !window.requestAnimationFrame; ++h) window.requestAnimationFrame = window[d[h] + "RequestAnimationFrame"],
|
|
window.cancelAnimationFrame = window[d[h] + "CancelAnimationFrame"] || window[d[h] + "CancelRequestAnimationFrame"];
|
|
window.requestAnimationFrame || (window.requestAnimationFrame = function(d, h) {
|
|
var n = (new Date).getTime(),
|
|
s = Math.max(0, 16 - (n - b)),
|
|
k = window.setTimeout(function() {
|
|
d(n + s)
|
|
},
|
|
s);
|
|
b = n + s;
|
|
return k
|
|
});
|
|
window.cancelAnimationFrame || (window.cancelAnimationFrame = function(b) {
|
|
clearTimeout(b)
|
|
})
|
|
})();
|
|
Math.PIM2 = 2 * Math.PI;
|
|
Math.PID2 = Math.PI / 2;
|
|
Math.randomInRange = function(b, d) {
|
|
return b + (d - b) * Math.random()
|
|
};
|
|
Math.clamp = function(b, d, h) {
|
|
b = Math.max(b, d);
|
|
return b = Math.min(b, h)
|
|
};
|
|
FSS.Vector3 = {
|
|
create: function(b, d, h) {
|
|
var g = new FSS.Array(3);
|
|
this.set(g, b, d, h);
|
|
return g
|
|
},
|
|
clone: function(b) {
|
|
var d = this.create();
|
|
this.copy(d, b);
|
|
return d
|
|
},
|
|
set: function(b, d, h, g) {
|
|
b[0] = d || 0;
|
|
b[1] = h || 0;
|
|
b[2] = g || 0;
|
|
return this
|
|
},
|
|
setX: function(b, d) {
|
|
b[0] = d || 0;
|
|
return this
|
|
},
|
|
setY: function(b, d) {
|
|
b[1] = d || 0;
|
|
return this
|
|
},
|
|
setZ: function(b, d) {
|
|
b[2] = d || 0;
|
|
return this
|
|
},
|
|
copy: function(b, d) {
|
|
b[0] = d[0];
|
|
b[1] = d[1];
|
|
b[2] = d[2];
|
|
return this
|
|
},
|
|
add: function(b, d) {
|
|
b[0] += d[0];
|
|
b[1] += d[1];
|
|
b[2] += d[2];
|
|
return this
|
|
},
|
|
addVectors: function(b, d, h) {
|
|
b[0] = d[0] +
|
|
h[0];
|
|
b[1] = d[1] + h[1];
|
|
b[2] = d[2] + h[2];
|
|
return this
|
|
},
|
|
addScalar: function(b, d) {
|
|
b[0] += d;
|
|
b[1] += d;
|
|
b[2] += d;
|
|
return this
|
|
},
|
|
subtract: function(b, d) {
|
|
b[0] -= d[0];
|
|
b[1] -= d[1];
|
|
b[2] -= d[2];
|
|
return this
|
|
},
|
|
subtractVectors: function(b, d, h) {
|
|
b[0] = d[0] - h[0];
|
|
b[1] = d[1] - h[1];
|
|
b[2] = d[2] - h[2];
|
|
return this
|
|
},
|
|
subtractScalar: function(b, d) {
|
|
b[0] -= d;
|
|
b[1] -= d;
|
|
b[2] -= d;
|
|
return this
|
|
},
|
|
multiply: function(b, d) {
|
|
b[0] *= d[0];
|
|
b[1] *= d[1];
|
|
b[2] *= d[2];
|
|
return this
|
|
},
|
|
multiplyVectors: function(b, d, h) {
|
|
b[0] = d[0] * h[0];
|
|
b[1] = d[1] * h[1];
|
|
b[2] = d[2] * h[2];
|
|
return this
|
|
},
|
|
multiplyScalar: function(b, d) {
|
|
b[0] *= d;
|
|
b[1] *= d;
|
|
b[2] *= d;
|
|
return this
|
|
},
|
|
divide: function(b, d) {
|
|
b[0] /= d[0];
|
|
b[1] /= d[1];
|
|
b[2] /= d[2];
|
|
return this
|
|
},
|
|
divideVectors: function(b, d, h) {
|
|
b[0] = d[0] / h[0];
|
|
b[1] = d[1] / h[1];
|
|
b[2] = d[2] / h[2];
|
|
return this
|
|
},
|
|
divideScalar: function(b, d) {
|
|
0 !== d ? (b[0] /= d, b[1] /= d, b[2] /= d) : (b[0] = 0, b[1] = 0, b[2] = 0);
|
|
return this
|
|
},
|
|
cross: function(b, d) {
|
|
var h = b[0],
|
|
g = b[1],
|
|
l = b[2];
|
|
b[0] = g * d[2] - l * d[1];
|
|
b[1] = l * d[0] - h * d[2];
|
|
b[2] = h * d[1] - g * d[0];
|
|
return this
|
|
},
|
|
crossVectors: function(b, d, h) {
|
|
b[0] = d[1] * h[2] - d[2] * h[1];
|
|
b[1] = d[2] * h[0] - d[0] * h[2];
|
|
b[2] = d[0] * h[1] - d[1] * h[0];
|
|
return this
|
|
},
|
|
min: function(b, d) {
|
|
b[0] < d && (b[0] = d);
|
|
b[1] < d && (b[1] = d);
|
|
b[2] < d && (b[2] = d);
|
|
return this
|
|
},
|
|
max: function(b, d) {
|
|
b[0] > d && (b[0] = d);
|
|
b[1] > d && (b[1] = d);
|
|
b[2] > d && (b[2] = d);
|
|
return this
|
|
},
|
|
clamp: function(b, d, h) {
|
|
this.min(b, d);
|
|
this.max(b, h);
|
|
return this
|
|
},
|
|
limit: function(b, d, h) {
|
|
var g = this.length(b);
|
|
null !== d && g < d ? this.setLength(b, d) : null !== h && g > h && this.setLength(b, h);
|
|
return this
|
|
},
|
|
dot: function(b, d) {
|
|
return b[0] * d[0] + b[1] * d[1] + b[2] * d[2]
|
|
},
|
|
normalise: function(b) {
|
|
return this.divideScalar(b, this.length(b))
|
|
},
|
|
negate: function(b) {
|
|
return this.multiplyScalar(b, -1)
|
|
},
|
|
distanceSquared: function(b, d) {
|
|
var h = b[0] - d[0],
|
|
g = b[1] - d[1],
|
|
l = b[2] - d[2];
|
|
return h * h + g * g + l * l
|
|
},
|
|
distance: function(b, d) {
|
|
return Math.sqrt(this.distanceSquared(b, d))
|
|
},
|
|
lengthSquared: function(b) {
|
|
return b[0] * b[0] + b[1] * b[1] + b[2] * b[2]
|
|
},
|
|
length: function(b) {
|
|
return Math.sqrt(this.lengthSquared(b))
|
|
},
|
|
setLength: function(b, d) {
|
|
var h = this.length(b);
|
|
0 !== h && d !== h && this.multiplyScalar(b, d / h);
|
|
return this
|
|
}
|
|
};
|
|
FSS.Vector4 = {
|
|
create: function(b, d, h, g) {
|
|
g = new FSS.Array(4);
|
|
this.set(g, b, d, h);
|
|
return g
|
|
},
|
|
set: function(b, d, h, g, l) {
|
|
b[0] = d || 0;
|
|
b[1] = h || 0;
|
|
b[2] = g || 0;
|
|
b[3] = l || 0;
|
|
return this
|
|
},
|
|
setX: function(b, d) {
|
|
b[0] = d || 0;
|
|
return this
|
|
},
|
|
setY: function(b, d) {
|
|
b[1] = d || 0;
|
|
return this
|
|
},
|
|
setZ: function(b, d) {
|
|
b[2] = d || 0;
|
|
return this
|
|
},
|
|
setW: function(b, d) {
|
|
b[3] = d || 0;
|
|
return this
|
|
},
|
|
add: function(b, d) {
|
|
b[0] += d[0];
|
|
b[1] += d[1];
|
|
b[2] += d[2];
|
|
b[3] += d[3];
|
|
return this
|
|
},
|
|
multiplyVectors: function(b, d, h) {
|
|
b[0] = d[0] * h[0];
|
|
b[1] = d[1] * h[1];
|
|
b[2] = d[2] * h[2];
|
|
b[3] = d[3] * h[3];
|
|
return this
|
|
},
|
|
multiplyScalar: function(b, d) {
|
|
b[0] *= d;
|
|
b[1] *= d;
|
|
b[2] *= d;
|
|
b[3] *= d;
|
|
return this
|
|
},
|
|
min: function(b, d) {
|
|
b[0] < d && (b[0] = d);
|
|
b[1] < d && (b[1] = d);
|
|
b[2] < d && (b[2] = d);
|
|
b[3] < d && (b[3] = d);
|
|
return this
|
|
},
|
|
max: function(b, d) {
|
|
b[0] > d && (b[0] = d);
|
|
b[1] > d && (b[1] = d);
|
|
b[2] > d && (b[2] = d);
|
|
b[3] > d && (b[3] = d);
|
|
return this
|
|
},
|
|
clamp: function(b, d, h) {
|
|
this.min(b, d);
|
|
this.max(b, h);
|
|
return this
|
|
}
|
|
};
|
|
FSS.Color = function(b, d) {
|
|
this.rgba = FSS.Vector4.create();
|
|
this.hex = b || "#000000";
|
|
this.opacity = FSS.Utils.isNumber(d) ? d: 1;
|
|
this.set(this.hex, this.opacity)
|
|
};
|
|
FSS.Color.prototype = {
|
|
set: function(b, d) {
|
|
b = b.replace("#", "");
|
|
var h = b.length / 3;
|
|
this.rgba[0] = parseInt(b.substring(0 * h, 1 * h), 16) / 255;
|
|
this.rgba[1] = parseInt(b.substring(1 * h, 2 * h), 16) / 255;
|
|
this.rgba[2] = parseInt(b.substring(2 * h, 3 * h), 16) / 255;
|
|
this.rgba[3] = FSS.Utils.isNumber(d) ? d: this.rgba[3];
|
|
return this
|
|
},
|
|
hexify: function(b) {
|
|
b = Math.ceil(255 * b).toString(16);
|
|
1 === b.length && (b = "0" + b);
|
|
return b
|
|
},
|
|
format: function() {
|
|
var b = this.hexify(this.rgba[0]),
|
|
d = this.hexify(this.rgba[1]),
|
|
h = this.hexify(this.rgba[2]);
|
|
return this.hex = "#" +
|
|
b + d + h
|
|
}
|
|
};
|
|
FSS.Object = function() {
|
|
this.position = FSS.Vector3.create()
|
|
};
|
|
FSS.Object.prototype = {
|
|
setPosition: function(b, d, h) {
|
|
FSS.Vector3.set(this.position, b, d, h);
|
|
return this
|
|
}
|
|
};
|
|
FSS.Light = function(b, d) {
|
|
FSS.Object.call(this);
|
|
this.ambient = new FSS.Color(b || "#FFFFFF");
|
|
this.diffuse = new FSS.Color(d || "#FFFFFF");
|
|
this.ray = FSS.Vector3.create()
|
|
};
|
|
FSS.Light.prototype = Object.create(FSS.Object.prototype);
|
|
FSS.Vertex = function(b, d, h) {
|
|
this.position = FSS.Vector3.create(b, d, h)
|
|
};
|
|
FSS.Vertex.prototype = {
|
|
setPosition: function(b, d, h) {
|
|
FSS.Vector3.set(this.position, b, d, h);
|
|
return this
|
|
}
|
|
};
|
|
FSS.Triangle = function(b, d, h) {
|
|
this.a = b || new FSS.Vertex;
|
|
this.b = d || new FSS.Vertex;
|
|
this.c = h || 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
|
|
}
|
|
};
|
|
FSS.Geometry = function() {
|
|
this.vertices = [];
|
|
this.triangles = [];
|
|
this.dirty = !1
|
|
};
|
|
FSS.Geometry.prototype = {
|
|
update: function() {
|
|
if (this.dirty) {
|
|
var b,
|
|
d;
|
|
for (b = this.triangles.length - 1; 0 <= b; b--) d = this.triangles[b],
|
|
d.computeCentroid(),
|
|
d.computeNormal();
|
|
this.dirty = !1
|
|
}
|
|
return this
|
|
}
|
|
};
|
|
FSS.Plane = function(b, d, h, g) {
|
|
FSS.Geometry.call(this);
|
|
this.width = b || 100;
|
|
this.height = d || 100;
|
|
this.segments = h || 4;
|
|
this.slices = g || 4;
|
|
this.segmentWidth = this.width / this.segments;
|
|
this.sliceHeight = this.height / this.slices;
|
|
var l,
|
|
n,
|
|
s;
|
|
h = [];
|
|
l = -0.5 * this.width;
|
|
n = 0.5 * this.height;
|
|
for (b = 0; b <= this.segments; b++) for (h.push([]), d = 0; d <= this.slices; d++) g = new FSS.Vertex(l + b * this.segmentWidth, n - d * this.sliceHeight),
|
|
h[b].push(g),
|
|
this.vertices.push(g);
|
|
for (b = 0; b < this.segments; b++) for (d = 0; d < this.slices; d++) g = h[b + 0][d + 0],
|
|
l = h[b + 0][d +
|
|
1],
|
|
n = h[b + 1][d + 0],
|
|
s = h[b + 1][d + 1],
|
|
t0 = new FSS.Triangle(g, l, n),
|
|
t1 = new FSS.Triangle(n, l, s),
|
|
this.triangles.push(t0, t1)
|
|
};
|
|
FSS.Plane.prototype = Object.create(FSS.Geometry.prototype);
|
|
FSS.Material = function(b, d) {
|
|
this.ambient = new FSS.Color(b || "#444444");
|
|
this.diffuse = new FSS.Color(d || "#FFFFFF");
|
|
this.slave = new FSS.Color
|
|
};
|
|
FSS.Mesh = function(b, d) {
|
|
FSS.Object.call(this);
|
|
this.geometry = b || new FSS.Geometry;
|
|
this.material = d || new FSS.Material;
|
|
this.side = FSS.FRONT;
|
|
this.visible = !0
|
|
};
|
|
FSS.Mesh.prototype = Object.create(FSS.Object.prototype);
|
|
FSS.Mesh.prototype.update = function(b, d) {
|
|
var h,
|
|
g,
|
|
l,
|
|
n,
|
|
s;
|
|
this.geometry.update();
|
|
if (d) for (h = this.geometry.triangles.length - 1; 0 <= h; h--) {
|
|
g = this.geometry.triangles[h];
|
|
FSS.Vector4.set(g.color.rgba);
|
|
for (l = b.length - 1; 0 <= l; l--) n = b[l],
|
|
FSS.Vector3.subtractVectors(n.ray, n.position, g.centroid),
|
|
FSS.Vector3.normalise(n.ray),
|
|
s = FSS.Vector3.dot(g.normal, n.ray),
|
|
this.side === FSS.FRONT ? s = Math.max(s, 0) : this.side === FSS.BACK ? s = Math.abs(Math.min(s, 0)) : this.side === FSS.DOUBLE && (s = Math.max(Math.abs(s), 0)),
|
|
FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.ambient.rgba, n.ambient.rgba),
|
|
FSS.Vector4.add(g.color.rgba, this.material.slave.rgba),
|
|
FSS.Vector4.multiplyVectors(this.material.slave.rgba, this.material.diffuse.rgba, n.diffuse.rgba),
|
|
FSS.Vector4.multiplyScalar(this.material.slave.rgba, s),
|
|
FSS.Vector4.add(g.color.rgba, this.material.slave.rgba);
|
|
FSS.Vector4.clamp(g.color.rgba, 0, 1)
|
|
}
|
|
return this
|
|
};
|
|
FSS.Scene = function() {
|
|
this.meshes = [];
|
|
this.lights = []
|
|
};
|
|
FSS.Scene.prototype = {
|
|
add: function(b) {
|
|
b instanceof FSS.Mesh && !~this.meshes.indexOf(b) ? this.meshes.push(b) : b instanceof FSS.Light && !~this.lights.indexOf(b) && this.lights.push(b);
|
|
return this
|
|
},
|
|
remove: function(b) {
|
|
b instanceof FSS.Mesh && ~this.meshes.indexOf(b) ? this.meshes.splice(this.meshes.indexOf(b), 1) : b instanceof FSS.Light && ~this.lights.indexOf(b) && this.lights.splice(this.lights.indexOf(b), 1);
|
|
return this
|
|
}
|
|
};
|
|
FSS.Renderer = function() {
|
|
this.halfHeight = this.halfWidth = this.height = this.width = 0
|
|
};
|
|
FSS.Renderer.prototype = {
|
|
setSize: function(b, d) {
|
|
if (this.width !== b || this.height !== d) return this.width = b,
|
|
this.height = d,
|
|
this.halfWidth = 0.5 * this.width,
|
|
this.halfHeight = 0.5 * this.height,
|
|
this
|
|
},
|
|
clear: function() {
|
|
return this
|
|
},
|
|
render: function(b) {
|
|
return this
|
|
}
|
|
};
|
|
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)
|
|
};
|
|
FSS.CanvasRenderer.prototype = Object.create(FSS.Renderer.prototype);
|
|
FSS.CanvasRenderer.prototype.setSize = function(b, d) {
|
|
FSS.Renderer.prototype.setSize.call(this, b, d);
|
|
this.element.width = b;
|
|
this.element.height = d;
|
|
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(b) {
|
|
FSS.Renderer.prototype.render.call(this, b);
|
|
var d,
|
|
h,
|
|
g,
|
|
l,
|
|
n;
|
|
this.clear();
|
|
this.context.lineJoin = "round";
|
|
this.context.lineWidth = 1;
|
|
for (d = b.meshes.length - 1; 0 <= d; d--) if (h = b.meshes[d], h.visible) for (h.update(b.lights, !0), g = h.geometry.triangles.length - 1; 0 <= g; g--) l = h.geometry.triangles[g],
|
|
n = l.color.format(),
|
|
this.context.beginPath(),
|
|
this.context.moveTo(l.a.position[0], l.a.position[1]),
|
|
this.context.lineTo(l.b.position[0], l.b.position[1]),
|
|
this.context.lineTo(l.c.position[0], l.c.position[1]),
|
|
this.context.closePath(),
|
|
this.context.strokeStyle = n,
|
|
this.context.fillStyle = n,
|
|
this.context.stroke(),
|
|
this.context.fill();
|
|
return this
|
|
};
|
|
FSS.WebGLRenderer = function() {
|
|
FSS.Renderer.call(this);
|
|
this.element = document.createElement("canvas");
|
|
this.element.style.display = "block";
|
|
this.lights = this.vertices = null;
|
|
this.gl = this.getContext(this.element, {
|
|
preserveDrawingBuffer: !1,
|
|
premultipliedAlpha: !0,
|
|
antialias: !0,
|
|
stencil: !0,
|
|
alpha: !0
|
|
});
|
|
if (this.unsupported = !this.gl) return "WebGL is not supported by your browser.";
|
|
this.gl.clearColor(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(b, d) {
|
|
var h = !1;
|
|
try {
|
|
if (! (h = b.getContext("experimental-webgl", d))) throw "Error creating WebGL context.";
|
|
} catch(g) {
|
|
// console.error(g);
|
|
}
|
|
return h
|
|
};
|
|
FSS.WebGLRenderer.prototype.setSize = function(b, d) {
|
|
FSS.Renderer.prototype.setSize.call(this, b, d);
|
|
if (!this.unsupported) return this.element.width = b,
|
|
this.element.height = d,
|
|
this.gl.viewport(0, 0, b, d),
|
|
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),
|
|
this
|
|
};
|
|
FSS.WebGLRenderer.prototype.render = function(b) {
|
|
FSS.Renderer.prototype.render.call(this, b);
|
|
if (!this.unsupported) {
|
|
var d,
|
|
h,
|
|
g,
|
|
l,
|
|
n,
|
|
s,
|
|
k,
|
|
m;
|
|
s = !1;
|
|
var r = b.lights.length,
|
|
u,
|
|
E,
|
|
y,
|
|
z = 0;
|
|
this.clear();
|
|
if (this.lights !== r) if (this.lights = r, 0 < this.lights) this.buildProgram(r);
|
|
else return;
|
|
if (this.program) {
|
|
for (d = b.meshes.length - 1; 0 <= d; d--) h = b.meshes[d],
|
|
h.geometry.dirty && (s = !0),
|
|
h.update(b.lights, !1),
|
|
z += 3 * h.geometry.triangles.length;
|
|
if (s || this.vertices !== z) for (k in this.vertices = z, this.program.attributes) {
|
|
s = this.program.attributes[k];
|
|
s.data = new FSS.Array(z * s.size);
|
|
u = 0;
|
|
for (d = b.meshes.length - 1; 0 <= d; d--) for (h = b.meshes[d], g = 0, l = h.geometry.triangles.length; g < l; g++) for (n = h.geometry.triangles[g], E = 0, y = n.vertices.length; E < y; E++) {
|
|
vertex = n.vertices[E];
|
|
switch (k) {
|
|
case "side":
|
|
this.setBufferData(u, s, h.side);
|
|
break;
|
|
case "position":
|
|
this.setBufferData(u, s, vertex.position);
|
|
break;
|
|
case "centroid":
|
|
this.setBufferData(u, s, n.centroid);
|
|
break;
|
|
case "normal":
|
|
this.setBufferData(u, s, n.normal);
|
|
break;
|
|
case "ambient":
|
|
this.setBufferData(u, s, h.material.ambient.rgba);
|
|
break;
|
|
case "diffuse":
|
|
this.setBufferData(u, s, h.material.diffuse.rgba)
|
|
}
|
|
u++
|
|
}
|
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, s.buffer);
|
|
this.gl.bufferData(this.gl.ARRAY_BUFFER, s.data, this.gl.DYNAMIC_DRAW);
|
|
this.gl.enableVertexAttribArray(s.location);
|
|
this.gl.vertexAttribPointer(s.location, s.size, this.gl.FLOAT, !1, 0, 0)
|
|
}
|
|
this.setBufferData(0, this.program.uniforms.resolution, [this.width, this.height, this.width]);
|
|
for (s = r - 1; 0 <= s; s--) d = b.lights[s],
|
|
this.setBufferData(s, this.program.uniforms.lightPosition, d.position),
|
|
this.setBufferData(s, this.program.uniforms.lightAmbient, d.ambient.rgba),
|
|
this.setBufferData(s, this.program.uniforms.lightDiffuse, d.diffuse.rgba);
|
|
for (m in this.program.uniforms) switch (s = this.program.uniforms[m], d = s.location, b = s.data, s.structure) {
|
|
case "3f":
|
|
this.gl.uniform3f(d, b[0], b[1], b[2]);
|
|
break;
|
|
case "3fv":
|
|
this.gl.uniform3fv(d, b);
|
|
break;
|
|
case "4fv":
|
|
this.gl.uniform4fv(d, b)
|
|
}
|
|
}
|
|
this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertices);
|
|
return this
|
|
}
|
|
};
|
|
FSS.WebGLRenderer.prototype.setBufferData = function(b, d, h) {
|
|
if (FSS.Utils.isNumber(h)) d.data[b * d.size] = h;
|
|
else for (var g = h.length - 1; 0 <= g; g--) d.data[b * d.size + g] = h[g]
|
|
};
|
|
FSS.WebGLRenderer.prototype.buildProgram = function(b) {
|
|
if (!this.unsupported) {
|
|
var d = FSS.WebGLRenderer.VS(b),
|
|
h = FSS.WebGLRenderer.FS(b),
|
|
g = d + h;
|
|
if (!this.program || this.program.code !== g) {
|
|
var l = this.gl.createProgram(),
|
|
d = this.buildShader(this.gl.VERTEX_SHADER, d),
|
|
h = this.buildShader(this.gl.FRAGMENT_SHADER, h);
|
|
this.gl.attachShader(l, d);
|
|
this.gl.attachShader(l, h);
|
|
this.gl.linkProgram(l);
|
|
if (!this.gl.getProgramParameter(l, this.gl.LINK_STATUS)) return b = this.gl.getError(),
|
|
l = this.gl.getProgramParameter(l, this.gl.VALIDATE_STATUS),
|
|
console.error("Could not initialise shader.\nVALIDATE_STATUS: " + l + "\nERROR: " + b),
|
|
null;
|
|
this.gl.deleteShader(h);
|
|
this.gl.deleteShader(d);
|
|
l.code = g;
|
|
l.attributes = {
|
|
side: this.buildBuffer(l, "attribute", "aSide", 1, "f"),
|
|
position: this.buildBuffer(l, "attribute", "aPosition", 3, "v3"),
|
|
centroid: this.buildBuffer(l, "attribute", "aCentroid", 3, "v3"),
|
|
normal: this.buildBuffer(l, "attribute", "aNormal", 3, "v3"),
|
|
ambient: this.buildBuffer(l, "attribute", "aAmbient", 4, "v4"),
|
|
diffuse: this.buildBuffer(l, "attribute", "aDiffuse", 4, "v4")
|
|
};
|
|
l.uniforms = {
|
|
resolution: this.buildBuffer(l, "uniform", "uResolution", 3, "3f", 1),
|
|
lightPosition: this.buildBuffer(l, "uniform", "uLightPosition", 3, "3fv", b),
|
|
lightAmbient: this.buildBuffer(l, "uniform", "uLightAmbient", 4, "4fv", b),
|
|
lightDiffuse: this.buildBuffer(l, "uniform", "uLightDiffuse", 4, "4fv", b)
|
|
};
|
|
this.program = l;
|
|
this.gl.useProgram(this.program);
|
|
return l
|
|
}
|
|
}
|
|
};
|
|
FSS.WebGLRenderer.prototype.buildShader = function(b, d) {
|
|
if (!this.unsupported) {
|
|
var h = this.gl.createShader(b);
|
|
this.gl.shaderSource(h, d);
|
|
this.gl.compileShader(h);
|
|
return this.gl.getShaderParameter(h, this.gl.COMPILE_STATUS) ? h: (console.error(this.gl.getShaderInfoLog(h)), null)
|
|
}
|
|
};
|
|
FSS.WebGLRenderer.prototype.buildBuffer = function(b, d, h, g, l, n) {
|
|
l = {
|
|
buffer: this.gl.createBuffer(),
|
|
size: g,
|
|
structure: l,
|
|
data: null
|
|
};
|
|
switch (d) {
|
|
case "attribute":
|
|
l.location = this.gl.getAttribLocation(b, h);
|
|
break;
|
|
case "uniform":
|
|
l.location = this.gl.getUniformLocation(b, h)
|
|
}
|
|
n && (l.data = new FSS.Array(n * g));
|
|
return l
|
|
};
|
|
FSS.WebGLRenderer.VS = function(b) {
|
|
return ["precision mediump float;", "#define LIGHTS " + b, "attribute float aSide;\nattribute vec3 aPosition;\nattribute vec3 aCentroid;\nattribute vec3 aNormal;\nattribute vec4 aAmbient;\nattribute vec4 aDiffuse;\nuniform vec3 uResolution;\nuniform vec3 uLightPosition[LIGHTS];\nuniform vec4 uLightAmbient[LIGHTS];\nuniform vec4 uLightDiffuse[LIGHTS];\nvarying vec4 vColor;\nvoid main() {\nvColor = vec4(0.0);\nvec3 position = aPosition / uResolution * 2.0;\nfor (int i = 0; i < LIGHTS; i++) {\nvec3 lightPosition = uLightPosition[i];\nvec4 lightAmbient = uLightAmbient[i];\nvec4 lightDiffuse = uLightDiffuse[i];\nvec3 ray = normalize(lightPosition - aCentroid);\nfloat illuminance = dot(aNormal, ray);\nif (aSide == 0.0) {\nilluminance = max(illuminance, 0.0);\n} else if (aSide == 1.0) {\nilluminance = abs(min(illuminance, 0.0));\n} else if (aSide == 2.0) {\nilluminance = max(abs(illuminance), 0.0);\n}\nvColor += aAmbient * lightAmbient;\nvColor += aDiffuse * lightDiffuse * illuminance;\n}\nvColor = clamp(vColor, 0.0, 1.0);\ngl_Position = vec4(position, 1.0);\n}"].join("\n")
|
|
};
|
|
FSS.WebGLRenderer.FS = function(b) {
|
|
return "precision mediump float;\nvarying vec4 vColor;\nvoid main() {\ngl_FragColor = vColor;\n}"
|
|
};
|
|
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(b, d) {
|
|
FSS.Renderer.prototype.setSize.call(this, b, d);
|
|
this.element.setAttribute("width", b);
|
|
this.element.setAttribute("height", d);
|
|
return this
|
|
};
|
|
FSS.SVGRenderer.prototype.clear = function() {
|
|
FSS.Renderer.prototype.clear.call(this);
|
|
for (var b = this.element.childNodes.length - 1; 0 <= b; b--) this.element.removeChild(this.element.childNodes[b]);
|
|
return this
|
|
};
|
|
FSS.SVGRenderer.prototype.render = function(b) {
|
|
FSS.Renderer.prototype.render.call(this, b);
|
|
var d,
|
|
h,
|
|
g,
|
|
l,
|
|
n,
|
|
s;
|
|
for (d = b.meshes.length - 1; 0 <= d; d--) if (h = b.meshes[d], h.visible) for (h.update(b.lights, !0), g = h.geometry.triangles.length - 1; 0 <= g; g--) l = h.geometry.triangles[g],
|
|
l.polygon.parentNode !== this.element && this.element.appendChild(l.polygon),
|
|
n = this.formatPoint(l.a) + " ",
|
|
n += this.formatPoint(l.b) + " ",
|
|
n += this.formatPoint(l.c),
|
|
s = this.formatStyle(l.color.format()),
|
|
l.polygon.setAttributeNS(null, "points", n),
|
|
l.polygon.setAttributeNS(null, "style", s);
|
|
return this
|
|
};
|
|
FSS.SVGRenderer.prototype.formatPoint = function(b) {
|
|
return this.halfWidth + b.position[0] + "," + (this.halfHeight - b.position[1])
|
|
};
|
|
FSS.SVGRenderer.prototype.formatStyle = function(b) {
|
|
return b = "fill:" + b + ";" + ("stroke:" + b + ";")
|
|
};
|
|
|
|
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
|
|
function() {
|
|
function b() {
|
|
B.remove(G);
|
|
D.clear();
|
|
H = new FSS.Plane(l.width * D.width, l.height * D.height, l.segments, l.slices);
|
|
J = new FSS.Material(l.ambient, l.diffuse);
|
|
G = new FSS.Mesh(H, J);
|
|
B.add(G);
|
|
var b,
|
|
d;
|
|
for (b = H.vertices.length - 1; 0 <= b; b--) d = H.vertices[b],
|
|
d.anchor = FSS.Vector3.clone(d.position),
|
|
d.step = FSS.Vector3.create(Math.randomInRange(0.2, 1), Math.randomInRange(0.2, 1), Math.randomInRange(0.2, 1)),
|
|
d.time = Math.randomInRange(0, Math.PIM2)
|
|
}
|
|
function d(d, g) {
|
|
D.setSize(d, g);
|
|
FSS.Vector3.set(E, D.halfWidth, D.halfHeight);
|
|
b()
|
|
}
|
|
function h() {
|
|
r = Date.now() - u;
|
|
var b,
|
|
d,
|
|
k,
|
|
m,
|
|
s,
|
|
z = l.depth / 2;
|
|
FSS.Vector3.copy(n.bounds, E);
|
|
FSS.Vector3.multiplyScalar(n.bounds, n.xyScalar);
|
|
FSS.Vector3.setZ(y, n.zOffset);
|
|
n.autopilot && (b = Math.sin(n.step[0] * r * n.speed), d = Math.cos(n.step[1] * r * n.speed), FSS.Vector3.set(y, n.bounds[0] * b, n.bounds[1] * d, n.zOffset));
|
|
for (b = B.lights.length - 1; 0 <= b; b--) d = B.lights[b],
|
|
FSS.Vector3.setZ(d.position, n.zOffset),
|
|
k = Math.clamp(FSS.Vector3.distanceSquared(d.position, y), n.minDistance, n.maxDistance),
|
|
k = n.gravity * d.mass / k,
|
|
FSS.Vector3.subtractVectors(d.force, y, d.position),
|
|
FSS.Vector3.normalise(d.force),
|
|
FSS.Vector3.multiplyScalar(d.force, k),
|
|
FSS.Vector3.set(d.acceleration),
|
|
FSS.Vector3.add(d.acceleration, d.force),
|
|
FSS.Vector3.add(d.velocity, d.acceleration),
|
|
FSS.Vector3.multiplyScalar(d.velocity, n.dampening),
|
|
FSS.Vector3.limit(d.velocity, n.minLimit, n.maxLimit),
|
|
FSS.Vector3.add(d.position, d.velocity);
|
|
for (m = H.vertices.length - 1; 0 <= m; m--) s = H.vertices[m],
|
|
b = Math.sin(s.time + s.step[0] * r * l.speed),
|
|
d = Math.cos(s.time + s.step[1] * r * l.speed),
|
|
k = Math.sin(s.time + s.step[2] * r * l.speed),
|
|
FSS.Vector3.set(s.position, l.xRange * H.segmentWidth * b, l.yRange * H.sliceHeight * d, l.zRange * z * k - z),
|
|
FSS.Vector3.add(s.position, s.anchor);
|
|
H.dirty = !0;
|
|
g();
|
|
requestAnimationFrame(h)
|
|
}
|
|
function g() {
|
|
D.render(B);
|
|
if (n.draw) {
|
|
var b,
|
|
d,
|
|
g,
|
|
h;
|
|
for (b = B.lights.length - 1; 0 <= b; b--) switch (h = B.lights[b], d = h.position[0], g = h.position[1], m.renderer) {
|
|
case s:
|
|
D.context.lineWidth = 0.5;
|
|
D.context.beginPath();
|
|
D.context.arc(d, g, 10, 0, Math.PIM2);
|
|
D.context.strokeStyle = h.ambientHex;
|
|
D.context.stroke();
|
|
D.context.beginPath();
|
|
D.context.arc(d, g, 4, 0, Math.PIM2);
|
|
D.context.fillStyle = h.diffuseHex;
|
|
D.context.fill();
|
|
break;
|
|
case k:
|
|
d += D.halfWidth,
|
|
g = D.halfHeight - g,
|
|
h.core.setAttributeNS(null, "fill", h.diffuseHex),
|
|
h.core.setAttributeNS(null, "cx", d),
|
|
h.core.setAttributeNS(null, "cy", g),
|
|
D.element.appendChild(h.core),
|
|
h.ring.setAttributeNS(null, "stroke", h.ambientHex),
|
|
h.ring.setAttributeNS(null, "cx", d),
|
|
h.ring.setAttributeNS(null, "cy", g),
|
|
D.element.appendChild(h.ring)
|
|
}
|
|
}
|
|
}
|
|
var l = {
|
|
width: 1.8,
|
|
height: 1.8,
|
|
depth: 10,
|
|
segments: 16,
|
|
slices: 8,
|
|
xRange: 0.8,
|
|
yRange: 0.1,
|
|
zRange: 1,
|
|
ambient: "#010101",
|
|
diffuse: "#ffffff",
|
|
speed: 6E-4,
|
|
opacity: 0.5
|
|
},
|
|
n = {
|
|
count: 2,
|
|
xyScalar: 1,
|
|
zOffset: 100,
|
|
ambient: "#ffffff",
|
|
diffuse: "#2d2d2d",
|
|
speed: 0.001,
|
|
gravity: 800,
|
|
dampening: 0.95,
|
|
minLimit: 10,
|
|
maxLimit: null,
|
|
minDistance: 20,
|
|
maxDistance: 400,
|
|
autopilot: !1,
|
|
draw: !1,
|
|
bounds: FSS.Vector3.create(),
|
|
step: FSS.Vector3.create(Math.randomInRange(0.2, 1), Math.randomInRange(0.2, 1), Math.randomInRange(0.2, 1))
|
|
},
|
|
s = "canvas",
|
|
k = "svg",
|
|
m = {
|
|
renderer: s
|
|
},
|
|
r,
|
|
u = Date.now(),
|
|
E = FSS.Vector3.create(),
|
|
y = FSS.Vector3.create(),
|
|
z = document.getElementById(container || "container");
|
|
document.getElementById("controls");
|
|
var X = document.getElementById(output || "output");
|
|
document.getElementById("ui");
|
|
var D,
|
|
B,
|
|
G,
|
|
H,
|
|
J,
|
|
R,
|
|
C,
|
|
S;
|
|
R = new FSS.WebGLRenderer;
|
|
C = new FSS.CanvasRenderer;
|
|
S = new FSS.SVGRenderer; (function(b) {
|
|
D && X.removeChild(D.element);
|
|
switch (b) {
|
|
case "webgl":
|
|
D = R;
|
|
break;
|
|
case s:
|
|
D = C;
|
|
break;
|
|
case k:
|
|
D = S
|
|
}
|
|
D.setSize(z.offsetWidth, z.offsetHeight);
|
|
X.appendChild(D.element)
|
|
})(m.renderer);
|
|
B = new FSS.Scene;
|
|
b(); (function() {
|
|
var b,
|
|
d;
|
|
for (b = B.lights.length -
|
|
1; 0 <= b; b--) d = B.lights[b],
|
|
B.remove(d);
|
|
D.clear();
|
|
for (b = 0; b < n.count; b++) d = new FSS.Light(n.ambient, n.diffuse),
|
|
d.ambientHex = d.ambient.format(),
|
|
d.diffuseHex = d.diffuse.format(),
|
|
B.add(d),
|
|
d.mass = Math.randomInRange(0.5, 1),
|
|
d.velocity = FSS.Vector3.create(),
|
|
d.acceleration = FSS.Vector3.create(),
|
|
d.force = FSS.Vector3.create(),
|
|
d.ring = document.createElementNS(FSS.SVGNS, "circle"),
|
|
d.ring.setAttributeNS(null, "stroke", d.ambientHex),
|
|
d.ring.setAttributeNS(null, "stroke-width", "0.5"),
|
|
d.ring.setAttributeNS(null, "fill", "none"),
|
|
d.ring.setAttributeNS(null, "r", "10"),
|
|
d.core = document.createElementNS(FSS.SVGNS, "circle"),
|
|
d.core.setAttributeNS(null, "fill", d.diffuseHex),
|
|
d.core.setAttributeNS(null, "r", "4")
|
|
})();
|
|
window.addEventListener("resize",
|
|
function(b) {
|
|
d(z.offsetWidth, z.offsetHeight);
|
|
g()
|
|
});
|
|
z.addEventListener("click",
|
|
function(b) {
|
|
FSS.Vector3.set(y, b.x, D.height - b.y);
|
|
FSS.Vector3.subtract(y, E);
|
|
n.autopilot = !n.autopilot; (void 0).updateDisplay()
|
|
});
|
|
z.addEventListener("mousemove",
|
|
function(b) {
|
|
FSS.Vector3.set(y, b.x, D.height - b.y);
|
|
FSS.Vector3.subtract(y, E)
|
|
});
|
|
d(z.offsetWidth, z.offsetHeight);
|
|
h()
|
|
} ();
|
|
|
|
} |