Click here to Skip to main content
15,881,380 members
Articles / Web Development / HTML

SilverLight Introduction

Rate me:
Please Sign up or sign in to vote.
4.62/5 (35 votes)
26 May 20074 min read 159.3K   1.1K   78  
Tinkering with SilverLight to get a 3D scene rendered in the Browser.
/**
 * @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;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Architect GE India Innovation Center
India India
Suchit is an Architect at GE India Innovation Center, Hyderabad.

He architected and developed portions of Proficy RX, a Process Analytical Technology (PAT) Solution of GE Fanuc Intelligent Platforms.

He also is the Architect of OPC Server for hardware devices of GE Sensing. These devices sense temperature, humidity, combustibles, fluid flow, pressure and various engineering parameters - primarily used in Industrial Automation & Process Control applications.

Interests: Computer Graphics, Mathematical Modeling, Scientific Applications Development.

He lives in Hyderabad India with. Loves reading books.

Comments and Discussions