Space.as

From Organic Design wiki
Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, this is only useful for a historic record of work done. You may find a link to the currently used concept or function in this article, if not you can contact the author to find out what has taken the place of this legacy item.
// Liscenced under LGPL: www.gnu.org/copyleft/lesser.html
//
// space.as - 3D space with dynamics and recursion
// Nad - 2003 for Amanda's SIC-Games project
//

// Initialise passed movieclip as a 3D space
create.space = function( name, layer ) {
	this.createEmptyMovieClip( name, layer );
	var newSpace = this[name];
	newSpace._x = _root.width / 2;
	newSpace._y = _root.height / 2;
	newSpace.create = _root.space.create;
	newSpace.reduce = _root.space.reduce;
	newSpace.space = newSpace;	// create() function used at all levels expects space to be this.space;
	newSpace.rotation = 0;	// global rotation of space
	newSpace.zbuff = [];	// Z ordered symbol-refs of all 3D objects
	newSpace.zbLength = 0;	// Since zbuff is sorted, we can use an end-ptr instead of pushing/splicing
	newSpace.children = [];	// Top layer children
	newSpace.template = _root.template;	// Template object for creating new symbols into the space
	newSpace.zSCREEN = 1000;	// Distance from origin (eye) to screen
	// Sort function for zbuff array
	newSpace.zcmp = function( a, b ) { return a.az < b.az; };
	return newSpace;
	};


// SPACE.CREATE
// Method: The space and all its objects inherit this method for creating child objects
//  	Creates a symbol for the 3D-object (spacer is used if classes[thisClass].symbol == null)
//	The passed data object extends the space.template used to initialise the new object
space = {};
space.create = function( parameters ) {
	var space = this.space;
	var time = _root.time;
	var instanceName = parameters[1];
	if ( instanceName == null ) instanceName = 'i' + int( 1000000 * Math.random() ) + getTimer();
	var symbol, hasSymbol = false;
	var class = parameters[0];
	var classRef = _root.classes[class];
	if ( class == 'obj' ) symbol = parameters[2];
	else symbol = classRef.symbol;
	// Create new child symbol using the 3D-object template (space.template)
	space.createEmptyMovieClip( instanceName, space.zbLength );
	var child = space[instanceName];
	if ( symbol != null ) {
		_root.createSymbol( child, symbol, 'swf', 0 );
		child.hasSymbol = true;
		} else child.hasSymbol = false;
	var template = space.template;
	for ( var i in template ) child[i] = template[i];
	this.children.push( child );		// object available in hierachical update tree of reduce() calls
	space.zbuff[space.zbLength++] = child;	// place object into z-buffer for layer sorting
	// Populate new child symbol/object
	for ( var i in classRef ) child[i] = classRef[i];
	child.space = space;
	child.class = class;
	child.parent = this;
	child.children = [];
	child.time = time;
	child.colour = new Color( child.swf );
	child.die = false;
	// Set the objects' dynamics() and call its init() - parameters[0] is 3DO-class
	if ( classRef.init != null ) classRef.init.call( child, parameters );
	return child;
	};


// SPACE.REDUCE
space.reduce = function() {
	var space = this.space;
	var zbuff = space.zbuff;

	// Execute the template.reduce() of each top-level child
	for ( var i = this.children.length-1; i >= 0; i-- ) {
		var obj = this.children[i];
		obj.ax = obj.ay = obj.az = 0;
		obj.reduce();
		// delete from this child list if marked for death
		if ( obj.die ) this.children.splice( i, 1 );
		}

	// Reduce the 3D coordinates to 2D for *ALL* objects with symbols (ie all layers, empty)
	// - ax,ay,az are absolute positions calculated in child reduce() exec layers
	for ( var i = 0; i < space.zbLength; i++ ) {
		var obj = zbuff[i];
		if ( obj.die ) obj.az = -100000000; // dead ones will get sorted to the end
		else {
			// Global rotation
			if ( space.rotation != 0 ) obj.rotate( 'ax', 'az', space.rotation );
			// Reduce 3D ( x,y,x -> _x,_y,scale )
			var m = space.zSCREEN / obj.az;
			obj._x = obj.ax * m;
			obj._y = obj.ay * m;
			if ( m < 0 ) m = 0; else if ( m > 10 ) m = 10;
			obj._xscale = obj._yscale = obj.scale * m;
			// Lightness ( based on 3D scale multiplier )
			if ( m < 0.5 ) light = 0.5;
			else if ( m < 1.5 ) light = 0.5 + ( m - 0.5 ); else light = 1;
			obj.colour.setTransform( { ra:obj.ra * light, ga:obj.ga * light, ba:obj.ba * light, ab:obj.alpha } );
			}
		}

	// Sort zbuff, set depths and remove dead children
	zbuff.sort( space.zcmp );
	while ( zbuff[space.zbLength-1].die ) zbuff[--space.zbLength].removeMovieClip();
	for ( var i = 0; i < space.zbLength; i++ ) zbuff[i].swapDepths(i);

	};


// TEMPLATE
// - Create a set of methods and properties inherited by all symbols created in the space
// - various dynamics() and effects() can add and maintain other properties
template = {};
template.space = null;			// set by create() A ref to the space object
template.create = space.create;	// Inherits generic create() method
template.parent = null;			// Set by create(), a ref to the parent of this object
template.children = null;		// Children of this object (must be initialised by create)
template.die = false;			// Object deletion must be handled ny the space.reduce()
template.motion = [];			// Motion functions to call in reduce()
template.colour = null;			// Set by create() - objects colour transform
template.scale = 100;			// symbol scale
template.alpha = 100;			// symbol scale
template.time = 0;				// Objects' absolute birth time
template.x = 0;					// Objects relative position (ie relative to birth location and time)
template.y = 0;					//
template.z = 0;					//
template.ra = 100;				// Colour transform properties
template.ga = 100;				//
template.ba = 100;				//

// per-frame method for 3D-objects (not the space itself which has its own frame function)
// - reduce() calculates the absolute position from the relative positions in the parent-child chain of each object
template.reduce = function() {
	var t = _root.time - this.time;

	// Update objects dynamic properties
	if ( !this.die ) {
		if ( this.dynamics != null ) this.dynamics(t);
		// Execute this objects' current motion effects x += f(t)
		for ( var i = 0; i < this.motion.length; i++ ) this.motion[i].call( this, t );
		// Update absolute location for space.reduce() to render
		this.ax += this.x;
		this.ay += this.y;
		this.az += this.z;
		}

	// Execute the reduce() of each child
	// - if this is dead, propagate death to descendents
	for ( var i = this.children.length-1; i >= 0; --i ) {
		var obj = this.children[i];
		if ( this.die ) obj.die = true;
		else {
			obj.ax = this.ax;
			obj.ay = this.ay;
			obj.az = this.az;
			}
		obj.reduce.call(obj);
		}
	};

// Rotate an object
template.rotate = function( x, y, a ) {
	a += Math.atan2( this.y, this.x );
	var r = Math.sqrt( this.x*this.x+this.y*this.y );
	this.x = Math.cos(a)*r;
	this.y = Math.sin(a)*r;
	};


// 3D OBJECT CLASSES

// NOTE: (Not using languages' classes here as actionscript too lame-arse)
// Each class has:
// - init(data)	used to create and layout child objects within itself (and can instantiate an asscoiated symbol too)
// - dynamics()	called from it's reduce() which then also calls space.update
//		each dynamics can call a set of effects functions depending on its state and time
//		NOTE: dynamics must base all changes on time
//		NOTE: dynamics are responsible for their coordinates being relative to parent
// - this is because some dynamics update their own coodinates and some their childrens
// - classes may has only a symbol and no init() or dynamics()

// these phases and layers should be tree nodes
classes = {};


// SPARK
classes.spark = {};
classes.spark.init = function(parameters) {
	this.lat = parameters[2];
	this.lon = parameters[3];
	};
classes.spark.symbol = 'radial';


// BALL
#include "ball.as"
classes.ball = {};
classes.ball.init = function(parameters) {
	this.lat = parameters[2];
	this.lon = parameters[3];
	this.motion = [ _root.motion.float ];
	};
classes.ball.symbol = 'ball';


// STAR-BURST CLASS
classes.starBurst = {};
classes.starBurst.symbol = 'radial';
classes.subBurst = {};
// starBurst: stars,force,x,y,z
classes.starBurst.init = function(parameters) {
	var guid = parameters[0];
	this.stars = parameters[2];
	this.force = parameters[3];
	this.x = 1.0 * parameters[4];
	this.y = 1.0 * parameters[5];
	this.z = 1.0 * parameters[5];
	this._visible = false;
	var ri = this.time;
	for ( var i = 0; i < this.stars; i++ ) {
		var lat = _root.rnd( ri, 1 ) * 2 * Math.PI;
		var lon = _root.rnd( ri + 10, 1 ) * 2 * Math.PI;
		ri += 20;
		var obj = this.create( [ 'subBurst', null, 4, 1.5, 500, lat, lon, ri ] );
		}
	};
// subBurst: stars,force,start,fadeout,lat,lon
classes.subBurst.init = function( parameters ) {
	this.stars = parameters[2];
	this.force = parameters[3];
	this.start = parameters[4];
	this.lat = parameters[5];
	this.lon = parameters[5];
	var ri = 1 * parameters[7];
	for ( var i = 0; i < this.stars; i++ ) {
		var lat = _root.rnd( ri, 3 ) * 2 * Math.PI;
		var lon = _root.rnd( ri + 31, 3) * 2 * Math.PI;
		ri += 51;
		var obj = this.create( [ 'spark', null, lat, lon ] );
		obj.index = i;
		}
	};
classes.subBurst.symbol = 'radial';
classes.starBurst.dynamics = function(t) {
	var onlyOne = true;
	// This is a subBurst
	if (this.start != null) {
		this.swf.gotoAndStop(4);
		if (t > this.start) {
			this.radius = (t-this.start)*this.force;
			onlyOne = false;
			}
		}
	else {
		this.swf.gotoAndStop(1);
		this.radius = 100+t*this.force;
		}
	this.force = this.force*0.97;
	if (this.alpha < 0) this.alpha = 0;
	var a = 100-(t/30);
	if (a < 0) {
		if (this.parent == this.space) this.die = true;
		a = 0;
		}
	// Update child positions: x = 0 + f(t) stylz
	for (var i = 0; i < this.children.length; i++) {
		var star = this.children[i];
		var s = this.radius*Math.cos(star.lon);
		star.x = s*Math.cos(star.lat+t/500);
		star.y = this.radius*Math.sin(star.lon)+t*t/8000;
		star.z = s*Math.sin(star.lat+t/500);
		if (onlyOne && (i>0)) star.alpha = 0; else star.alpha = a;
		}
	};
classes.subBurst.dynamics = classes.starBurst.dynamics;


// TORUS CLASS
classes.torus = {};
classes.torus.init = function( parameters ) {
	this.points = 1 * parameters[2];
	this.radius = 1.0 * parameters[3];
	this.speed = 1.0 * parameters[4];
	var arc = 2 * Math.PI / this.points;
	for ( var i = 0; i < this.points; i++ ) {
		obj = this.create( [ 'ball', null, 50 ] );
		obj.arc = arc * i;
		obj.floatOffset = Math.random() * 2 * Math.PI;
		j = i + 1;
		//obj.ra = (j & 1) ? 0xff : 0;
		//obj.ga = (j & 2) ? 0xff : 0;
		//obj.ba = (j & 4) ? 0xff : 0;
		}
	};
classes.torus.dynamics = function(t) {
	this.x = _root._xmouse - _root.width / 2;
	this.y = _root._ymouse - _root.height / 2;
	this.Z = 1000;
	for ( var i = 0; i < this.children.length; i++ ) {
		var point = this.children[i];
		var angle = point.arc + this.angle / this.children.length + t / this.speed;
		var r = this.parent.radius + this.radius * Math.sin( angle );
		point.x = this.x + r * Math.cos( this.angle );
		point.z = this.z + 5 * this.radius * Math.cos( angle );
		point.y = this.y + r * Math.sin( this.angle );
		}
	};


// IMPLODE CLASS
classes.implode = {};
classes.implode.init = function( parameters ) {
	this.stars = 25;
	this.radius = 200;
	this.x = 1.0 * parameters[2];
	this.y = 1.0 * parameters[3];
	this.z = 1.0 * parameters[4];
	var ri = this.time;
	for ( var i = 0; i < this.stars; i++ ) {
		var lat = _root.rnd( ri, 1 ) * 2 * Math.PI;
		var lon = _root.rnd( ri + 10, 1 ) * 2 * Math.PI;
		var obj = this.create( [ 'spark', null, lat, lon ] );
		obj.v = _root.rnd( ri + 20, 1 ) * 100 + 10;
		obj.scale = 50;
		ri += 30;
		}
	};
classes.implode.dynamics = function(t) {
	this.radius = 500 - t / 3;
	if ( this.radius < 100 ) this.radius = 100;
	var a = t / 10;
	if ( a > 200 ) this.die = true;
	if ( a > 100 ) a = 200 - a;
	for ( var i = 0; i < this.children.length; i++ ) {
		var star = this.children[i];
		var r = this.radius * star.v / 50;
		var s = r * Math.cos( star.lon );
		star.x = s * Math.cos( star.lat + t / star.v / 2 );
		star.y = r * Math.sin( star.lon);
		star.z = s * Math.sin( star.lat + t / star.v / 2 );
		star.alpha = a / 2;
		}
	};


// STARFIELD CLASS
classes.starField = {};
classes.starField.init = function(parameters) {
	this.stars = 100;
	this.radius = 2000;
	var ri = this.time;
	for ( var i = 0; i < this.stars; i++ ) {
		var lat = _root.rnd( ri, 1 ) * 2 * Math.PI;
		var lon = _root.rnd( ri + 10, 1 ) * 2 * Math.PI;
		var obj = this.create( [ 'spark', null, lat, lon ] );
		obj.v = _root.rnd( ri + 20, 1 ) * 100 + 10;
		obj.scale = 50;
		ri += 30;
		}
	};
classes.starField.dynamics = function(t) {
	this.radius = 500 - t/3;
	if ( this.radius < 100 ) this.radius = 100;
	var a = t / 10;
	if ( a > 200 ) this.die = true;
	if ( a > 100 ) a = 200 - a;
	for ( var i = 0; i < this.children.length; i++ ) {
		var star = this.children[i];
		var r = this.radius * star.v / 50;
		var s = r * Math.cos( star.lon );
		star.x = s * Math.cos( star.lat + t / star.v / 2 );
		star.y = r * Math.sin( star.lon );
		star.z = s * Math.sin( star.lat + t / star.v / 2 );
		star.alpha = a / 2;
		}
	};


// 3D MOTION f(t) CLASSES
// - 3D Motion functions called by an objects' reduce() if in its motion[] list

// Float
// - Adds the metaparticles movement to passed object
motion = {};
motion.float = function(t) {
	var a = this.floatOffset + t / 400;
	var m = ( this._width + this._height ) * this.scale / 1000;
	var api4 = a * Math.PI / 4;
	var api8 = a * Math.PI / 8;
	var cospi4 = Math.cos( api4 );
	var cospi8 = Math.cos( api8 );
	var sinpi4 = Math.sin( api4 );
	var sinpi8 = Math.sin( api8 );
	this.x += m * cospi4 * sinpi8;
	this.y += m * cospi4 * cospi8;
	this.z += m * sinpi4;
	};

// Square-sine
motion.squareSine = function(t) {
	var i = Math.floor( t/Math.PI ) % 6;
	if ( (i == 1) || (i == 2) ) return -1;
	if ( i > 3 ) return 1;
	return Math.cos(t);
	};