/**
* @author Alexey Gavrilov (alex@metalinkltd.com)
*/
/**
* Vector class
*/
function V(x, y, z) {
if (typeof x == "object") {
this.x = x[0];
this.y = x[1];
this.z = x[2];
} else {
this.x = x;
this.y = y;
this.z = z;
}
}
V.prototype.add = function (v) {
this.x += v.x;
this.y += v.y;
this.z += v.z;
return this;
}
V.prototype.subtract = function (v) {
this.x -= v.x;
this.y -= v.y;
this.z -= v.z;
return this;
}
V.prototype.clone = function() {
return new V(this.x, this.y, this.z);
}
V.prototype.module = function() {
return Math.sqrt(this.x*this.x + this.y*this.y + this.z*this.z);
}
V.prototype.mult = function(v) {
return this.x*v.x + this.y*v.y + this.z*v.z;
}
V.prototype.multV = function(v) {
return new V(
this.y*v.z - this.z*v.y,
-this.x*v.z + this.z*v.x,
this.x*v.y - this.y*v.x
);
}
V.prototype.scale = function(t) {
this.x *= t;
this.y *= t;
this.z *= t;
return this;
}
/**
* The model of our Methane
*/
var model = {
"walls" : {"minx":-250, "miny":-250, "minz": -150, "maxx": 250, "maxy": 250, "maxz": 150,
"shapes": Array(),
"makeWalls": function() {},
"renderWalls": function (front) {
for (s in this.shapes) {
if (front == this.shapes[s]._orientation) {
this.shapes[s].render();
}
}
}
},
"elastity" : 1.0,
"gravity" : -0.00,
"ballRadius" : 26,
"maxSpeed" : 5.0,
"setPOV" : function (angleZ, angleY, R) {
this.camera.setPOV(angleZ, angleY, R);
for (shape in this.walls.shapes) {
this.walls.shapes[shape].updateView();
}
},
"camera" : {
"POV": {"x": -1000, "y": -1000, "z": 500},
"FOV": 0.5,
"screen": {"wx": 500, "wy": 500},
"matrix": {
"xx": 1.0, "xy": 0.0, "xz": 0.0,
"yx": 0.0, "yy": 1.0, "yz": 0.0,
"zx": 0.0, "zy": 0.0, "zz": 1.0
},
makeMatrix: function() {
var L = 1.0*Math.sqrt(this.POV.x*this.POV.x + this.POV.y*this.POV.y + this.POV.z*this.POV.z);
this.matrix.zx = this.POV.x/L;
this.matrix.zy = this.POV.y/L;
this.matrix.zz = this.POV.z/L;
var n = Math.sqrt(this.matrix.zx*this.matrix.zx + this.matrix.zy*this.matrix.zy);
this.matrix.xx = this.matrix.zy/n;
this.matrix.xy = -this.matrix.zx/n;
this.matrix.xz = 0.0;
this.matrix.yx = /*this.matrix.zy*this.matrix.xz*/ - this.matrix.xy*this.matrix.zz;
this.matrix.yy = /*-this.matrix.zx*this.matrix.xz*/ + this.matrix.xx*this.matrix.zz;
this.matrix.yz = this.matrix.zx*this.matrix.xy - this.matrix.xx*this.matrix.zy;
},
getProjection: function (x, y, z) {
x -= this.POV.x;
y -= this.POV.y;
z -= this.POV.z;
var px = x*this.matrix.xx + y*this.matrix.xy + z*this.matrix.xz;
var py = x*this.matrix.yx + y*this.matrix.yy + z*this.matrix.yz;
var depth = -(x*this.matrix.zx + y*this.matrix.zy + z*this.matrix.zz);
var scale = (1.0/depth)*this.screen.wx/this.FOV;
px *= scale;
py *= scale;
px += model.camera.screen.wx/2;
py += model.camera.screen.wy/2;
return {"x": px, "y": py, "depth": depth, "scale": scale};
},
setPOV: function (angleZ, angleY, R) {
this.POV.z = R*Math.sin(angleZ);
this.POV.x = R*Math.cos(angleZ)*Math.cos(angleY);
this.POV.y = R*Math.cos(angleZ)*Math.sin(angleY);
this.makeMatrix();
}
}
};
// helper
var extend = function(subClass, baseClass)
{
// Create a new class that has an empty constructor
// with the members of the baseClass
function inheritance() {};
inheritance.prototype = baseClass.prototype;
// set prototype to new instance of baseClass
// _without_ the constructor
subClass.prototype = new inheritance();
subClass.prototype.constructor = subClass;
subClass.baseConstructor = baseClass;
// enable multiple inheritance
if (baseClass.base)
{
baseClass.prototype.base = baseClass.base;
}
subClass.base = baseClass.prototype;
}
/**
* Represents flat 3D-face object
*
* @param {Array} vertexes
*/
function Face(vertexes) {
if (vertexes == undefined) {
vertexes = [[100.0, 100.0, 0.0], [100.0,-100.0, 0.0], [-100.0, -100.0, 0.0], [-100.0, 100.0, 0.0]];
}
this._vertexA = vertexes;
this.pA = new Array();
this.updateView();
}
Face.prototype.getProjection = function() {
for (var i=0; i< this._vertexA.length; i++) {
this.pA[i] = model.camera.getProjection(this._vertexA[i][0], this._vertexA[i][1], this._vertexA[i][2]);
}
}
Face.prototype.render = function(orientation) {
if (this._orientation != orientation)
return false;
else
return true;
}
Face.prototype.getOrientation = function() {
var v0 = new V(this._vertexA[0]);
var v1 = new V(this._vertexA[1]);
var v2 = new V(this._vertexA[2]);
var pov = new V(model.camera.POV.x, model.camera.POV.y, model.camera.POV.z );
v2.subtract(v1);
v1.subtract(v0);
this._orientation = ( (v1.multV(v2)).mult( pov.subtract(v0) ) > 0 );
return this._orientation;
}
Face.prototype.updateView = function() {
this.getProjection();
this.getOrientation();
}
function WPFEFace(host, parent, vertexes) {
WPFEFace.baseConstructor.call(this, vertexes);
this._host = host;
this._elem = host.content.createFromXaml('<Polygon />');
this._parent = parent;
// set default style values
this._elem.stroke = "#FF0000EE";
this._elem.opacity = 0.3;
this._elem.fill = "#EEEEEEFF";
this._elem.StrokeMiterLimit = 1.0;
this.render();
}
extend(WPFEFace, Face);
WPFEFace.prototype.render = function(front) {
var path = "";
for (point in this.pA) {
path += this.pA[point].x + "," + this.pA[point].y + " ";
}
this._elem.points = path;
if (front)
this.bringToFront();
else
this.sendToBack();
}
WPFEFace.prototype.sendToBack = function() {
this._parent.children.remove(this._elem);
this._parent.children.insert(0, this._elem);
}
WPFEFace.prototype.bringToFront = function() {
this._parent.children.remove(this._elem);
this._parent.children.insert(this._parent.children.count-1, this._elem);
}
/**
* Base "platform-independent" class representing ball
*
* @param {Object} x
* @param {Object} y
* @param {Object} vx
* @param {Object} vy
*/
function Ball(x, y, z, vx, vy, vz) {
// default provisioning
if (x == undefined) {
x = model.walls.minx + (model.walls.maxx - model.walls.minx - 2*model.ballRadius)*Math.random();
y = model.walls.miny + (model.walls.maxy - model.walls.miny - 2*model.ballRadius)*Math.random();
z = model.walls.minz + (model.walls.maxz - model.walls.minz - 2*model.ballRadius)*Math.random();
vx = 2*model.maxSpeed*Math.random() - model.maxSpeed;
vy = 2*model.maxSpeed*Math.random() - model.maxSpeed;
vz = 2*model.maxSpeed*Math.random() - model.maxSpeed;
}
this._x = x;
this._y = y;
this._z = z;
this._vx = 0;//vx;
this._vy = 0;//vy;
this._vz = 0;//vz;
this._r = model.ballRadius; // d = 52 px
this._d = 2*this._r;
this._d2 = this._d*this._d;
}
Ball.prototype.move = function() {
this._x += this._vx;
this._y += this._vy;
this._z += this._vz;
this._vz += model.gravity;
this.getProjection();
// walls collisons
if (this._x < model.walls.minx + this._r && this._vx<0) {
this._vx = -this._vx*model.elastity;
}
if (this._y < model.walls.miny + this._r && this._vy<0) {
this._vy = -this._vy*model.elastity;
}
if (this._z < model.walls.minz + this._r && this._vz<0) {
this._vz = -this._vz*model.elastity;
}
if (this._x > model.walls.maxx - this._r && this._vx>0) {
this._vx = -this._vx*model.elastity;
}
if (this._y > model.walls.maxy - this._r && this._vy>0) {
this._vy = -this._vy*model.elastity;
}
if (this._z > model.walls.maxz - this._r && this._vz>0) {
this._vz = -this._vz*model.elastity;
}
}
Ball.prototype.getProjection = function() {
this.p = model.camera.getProjection(this._x, this._y, this._z);
}
Ball.prototype.doCollide = function(b) {
// calculate some vectors
var dx = this._x - b._x;
var dy = this._y - b._y;
var dz = this._z - b._z;
var dvx = this._vx - b._vx;
var dvy = this._vy - b._vy;
var dvz = this._vz - b._vz;
var distance2 = dx*dx + dy*dy + dz*dz;
if (Math.abs(dx) > this._d || Math.abs(dy) > this._d || Math.abs(dz) > this._d)
return false;
if (distance2 > this._d2)
return false;
// make absolutely elastic collision
var mag = dvx*dx + dvy*dy + dvz*dz;
// test that balls move towards each other
if (mag > 0)
return false;
mag /= distance2;
var delta_vx = dx*mag;
var delta_vy = dy*mag;
var delta_vz = dz*mag;
this._vx -= delta_vx;
this._vy -= delta_vy;
this._vz -= delta_vz;
b._vx += delta_vx;
b._vy += delta_vy;
b._vz += delta_vz;
return true;
}
/**
* WPF/e-specific implementation
*
* @param {Object} name
* @param {Object} x
* @param {Object} y
* @param {Object} vx
* @param {Object} vy
*/
function WPFEBall(host, name, x, y, vx, vy) {
WPFEBall.baseConstructor.call(this, x, y, vx, vy);
this._host = host;
this._name = name;
this._elem = host.content.findName(name);
this._parent = this._elem.getParent();
this.move();
}
extend(WPFEBall, Ball);
/*
WPFEBall.prototype.move = function(){
WPFEBall.base.move.call(this);
}
*/
WPFEBall.prototype.render = function() {
var scaleTransform = this._host.content.createFromXaml("<ScaleTransform />");
scaleTransform.ScaleX = this.p.scale;
scaleTransform.ScaleY = this.p.scale;
this._elem.RenderTransform = scaleTransform;
this._elem["canvas.left"] = this.p.x - model.ballRadius*this.p.scale;
this._elem["canvas.top"] = this.p.y - model.ballRadius*this.p.scale;
this.sendToBack();
}
WPFEBall.prototype.sendToBack = function() {
this._parent.children.remove(this._elem);
this._parent.children.insert(0, this._elem);
}
WPFEBall.prototype.bringToFront = function() {
this._parent.children.remove(this._elem);
this._parent.children.insert(this._parent.children.count-1, this._elem);
}
WPFEBall.prototype.clone = function(newName, is_bmp) {
// oops, wpf/e doesn't support objects cloning nor getting their XAML source!
// it's just too bad -- I had to paste all XAML right here
//
var newXAML = "";
if (this._is_bmp && this._is_bmp != undefined) {
newXAML = '<Canvas xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="' + newName + '" Width="54.6667" Height="54.6667" Canvas.Left="0" Canvas.Top="0"><Image Source="assets/ball.png"/></Canvas>';
} else {
newXAML = '<Canvas xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Name="' + newName + '" Width="52" Height="52" Canvas.Left="0" Canvas.Top="30"><Path Opacity="0.600000" StrokeThickness="2.000000" Stroke="#ffa6d000" StrokeMiterLimit="1.000000" Fill="#ffcbff00" Data="F1 M 51.000000,26.000000 C 51.000000,39.806641 39.807129,51.000000 26.000000,51.000000 C 12.192871,51.000000 1.000000,39.806641 1.000000,26.000000 C 1.000000,12.193359 12.192871,1.000000 26.000000,1.000000 C 39.807129,1.000000 51.000000,12.193359 51.000000,26.000000 Z"/><Path Opacity="0.640000" Data="F1 M 43.143066,13.087891 C 50.602051,22.888672 49.009766,36.642578 39.590332,43.812500 C 30.170898,50.980469 16.489258,48.842773 9.032715,39.042969 C 1.573242,29.240234 3.166016,15.486328 12.584961,8.316406 C 22.003906,1.149414 35.685547,3.285156 43.143066,13.087891 Z"><Path.Fill><RadialGradientBrush MappingMode="Absolute" GradientOrigin="156.791016,170.453125" Center="156.791016,170.453125" RadiusX="53.626404" RadiusY="53.626404"><RadialGradientBrush.GradientStops><GradientStop Offset="0.000000" Color="#ffffffff"/><GradientStop Offset="0.361685" Color="#fff5f7dd"/><GradientStop Offset="0.415730" Color="#ffebf0bc"/><GradientStop Offset="1.000000" Color="#ffcbff00"/></RadialGradientBrush.GradientStops><RadialGradientBrush.Transform><MatrixTransform Matrix="1.190000,0.165000,-0.165000,-1.281300,-113.414185,241.757843" /></RadialGradientBrush.Transform></RadialGradientBrush></Path.Fill></Path> <Path Fill="#ffffffff" Data="F1 M 23.100586,9.477539 C 24.741699,11.634766 23.116211,15.630859 19.470703,18.404297 C 15.825684,21.178711 11.540039,21.678711 9.899414,19.522461 C 8.258301,17.365234 9.883789,13.369141 13.529297,10.594727 C 17.174316,7.821289 21.459961,7.321289 23.100586,9.477539 Z"/></Canvas>';
}
var newNode = this._host.content.createFromXaml(newXAML);
this._parent.children.add(newNode);
return new WPFEBall(this._host, newName);
}
/**
* Abstract test class
*
* @param {Object} N
*/
function BallsTest(N) {
this._N = N; // number of objects
this._ballsO = new Array();
this._isRunning = false;
this._angleY = 0.0;
this._angleZ = Math.PI/4;;
this._distance = 1800;
model.setPOV(this._angleZ, this._angleY, this._distance);
}
BallsTest.prototype._showFPS = null;
BallsTest.prototype.start = function(N) {
if (this._isRunning) return false;
this._isRunning = true;
if (N != undefined) {
this._N = N;
}
var _this = this;
var moveBalls = function() {
if (_this._N > _this._ballsO.length)
return;
_this._F++;
// move balls
for (var i=0; i<_this._N; i++) {
_this._ballsO[i].move();
}
if (_this._moveBalls)
_this._moveBalls.call(_this);
// render front walls
model.walls.renderWalls(true);
// render balls
for (var i=0; i<_this._N; i++) {
_this._ballsO[i].render();
}
// render back walls
model.walls.renderWalls(false);
// process collisions
for (i=0; i<_this._N; i++) {
for (j=i+1; j<_this._N; j++) {
_this._ballsO[i].doCollide(_this._ballsO[j]);
}
}
}
this._int1 = setInterval(moveBalls, 5);
return true;
}
BallsTest.prototype.stop = function(){
if (!this._isRunning) return false;
this._isRunning = false;
clearInterval(this._int1);
clearInterval(this._int2);
return true;
}