/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * AnalyticCurve
 * @class AnalyticCurve 
 * @constructor  
 */
EJSS_DRAWING2D.AnalyticCurve = {	
	
	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy
  	
		dest.setNumPoints(source.getNumPoints());
		dest.setMinimun(source.getMinimun());
		dest.setMaximun(source.getMaximun());
		dest.setVariable(source.getVariable());
		dest.setFunctionX(source.getFunctionX());
		dest.setFunctionY(source.getFunctionY());  	
  	},
  
	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("NumPoints", element.setNumPoints, element.getNumPoints);
		controller.registerProperty("Minimum", element.setMinimun, element.getMinimun);
		controller.registerProperty("Maximum", element.setMaximun, element.getMaximun);
		controller.registerProperty("Variable", element.setVariable, element.getVariable);
		controller.registerProperty("FunctionX", element.setFunctionX, element.getFunctionX);
		controller.registerProperty("FunctionY", element.setFunctionY, element.getFunctionY);		
    
        controller.registerAction("OnError");

	}
			
};

/**
 * Creates a 2D AnalyticCurve
 * @method analyticCurve
 */
EJSS_DRAWING2D.analyticCurve = function(name) {
	var self = EJSS_DRAWING2D.element(name);
 
 	var mMinimun; 	
 	var mMaximun;
 	var mVariable = "t";
 	var mFunctionX = "t";
 	var mFunctionY = "0";
	var mParameters = {};
 	var mNumPoints = screen.width * 5;

	self.getClass = function() {
		return "ElementAnalyticCurve";
	}
	 
	/**
	 * Sets parameters for the evaluation of the function
	 * @method setParameters
	 * @param parameters Object { "p1" : value1, "p2" : value2, ...}
	 * @return void
	 */
	self.setParameters = function(parameters) {
	  for (var param in parameters) { 
          mParameters[param] = parameters[param];
	    }
	  self.setChanged(true);
    }

	/**
	 * Gets parameters for the evaluation of the function
	 * @method getParameters
	 * @return Object { "p1" : value1, "p2" : value2, ...}
	 */
	self.getParameters = function() {
	  return mParameters;
    }
	 
	/**
	 * Returns bounds for an element
	 * @method getBounds
	 * @return Object{left,rigth,top,bottom}
	 */
	self.getBounds = function(element) {
	    var x = self.getX(), y = self.getY();
	    var sx = self.getSizeX(), sy = self.getSizeY();
	  	var mx = sx/2, my = sy/2;  	
	  	var d = self.getRelativePositionOffset(sx,sy);
	   	// calculate points
	   	var parser = EJSS_DRAWING2D.functionsParser();
	   	var exprfx;
	   	var exprfy;
	   	var mustReturn = false;
	   	try {
	   	  exprfx = parser.parse(mFunctionX);
	   	}
	   	catch (errorfx) {
  	   	  console.log ("Analytic curve error parsing FunctionX: "+mFunctionX);
	   	  mustReturn = true;
	   	}
	   	if (!mustReturn) {
	   	  try {
	   	    exprfy = parser.parse(mFunctionY);
	   	  }
	   	  catch (errorfy) {
  	   	    console.log ("Analytic curve error parsing FunctionY: "+mFunctionY);
	   	    mustReturn = true;
	   	  }
	   	}
	   	if (mustReturn) {
	   	  self.getController().invokeAction("OnError");
	   	  return {
	   	    left: ((x+d[0])-mx)-sx,
			right: ((x+d[0])-mx)+sx,
			top: ((y+d[1])-my)+sy,
			bottom: ((y+d[1])-my)-sy
	   	  };
	   	}  	

		var min = ( (typeof mMinimun == "undefined" || mMinimun === null) ?  self.getPanel().getRealWorldXMin() : mMinimun);
//		console.log ("Minimumm = "+mMinimun+" : min = "+min);
		var max = ( (typeof mMaximun == "undefined" || mMaximun === null) ?  self.getPanel().getRealWorldXMax() : mMaximun);

	   	var step = (max-min)/mNumPoints;
		var points = [];	   	
	    var vblevalue = {};
	    for (var param in mParameters) { 
          vblevalue[param] = mParameters[param];
	    }
	    try {
	   	  for(var j=0, i=min; i<max; i+=step) {
	   		vblevalue[mVariable] = i;
	   		var fxvalue = exprfx.evaluate(vblevalue);
	   		var fyvalue = exprfy.evaluate(vblevalue);
	   		  if(!isNaN(fxvalue) && !isNaN(fyvalue)) {
		   		points[j] = [];		   		   		
		    	points[j][0] = fxvalue;	
				points[j++][1] = fyvalue;
			}
		  }
		}
		catch (errorEvaluate) {
	      self.getController().invokeAction("OnError");
		  return {
	   	    left: ((x+d[0])-mx)-sx,
			right: ((x+d[0])-mx)+sx,
			top: ((y+d[1])-my)+sy,
			bottom: ((y+d[1])-my)-sy
	   	  };
		} 
	
		// calculate max and min
		var maxfx = 0;
		var maxfy = 0;
		var minfx = 0;
		var minfy = 0;
		if(points.length > 0) {
			maxfx = points[0][0], minfx = points[0][0];
			maxfy = points[0][1], minfy = points[0][1];
		  	for(var k=0; k<points.length; k++) {
		    	if(points[k][0] > maxfx) maxfx = points[k][0];
		    	if(points[k][0] < minfx) minfx = points[k][0];
		    	if(points[k][1] > maxfy) maxfy = points[k][1];
		    	if(points[k][1] < minfy) minfy = points[k][1];
			}
		} 
		
		return {
			left: ((x+d[0])-mx)+minfx*sx,
			right: ((x+d[0])-mx)+maxfx*sx,
			top: ((y+d[1])-my)+maxfy*sy,
			bottom: ((y+d[1])-my)+minfy*sy
		}
	};
  	 		 
	/** 
	 * @method setNumPoint
	 * @param numpoint
	 */
	self.setNumPoints = function (numpoints) {
	  	if(mNumPoints != numpoints) {
	  		mNumPoints = numpoints;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getNumPoint
	 * @return
	 */
	self.getNumPoints = function() { 
		return mNumPoints; 
	}

	/** 
	 * @method setMinimun
	 * @param minimun
	 */
	self.setMinimun = function (minimun) {
	  	if(mMinimun != minimun) {
	  		mMinimun = minimun;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getMinimun
	 * @return
	 */
	self.getMinimun = function() { 
		return mMinimun; 
	}

	/** 
	 * @method setMaximun
	 * @param maximun
	 */
	self.setMaximun = function (maximun) {
	  	if(mMaximun != maximun) {
	  		mMaximun = maximun;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getMaximun
	 * @return
	 */
	self.getMaximun = function() { 
		return mMaximun; 
	}

	/** 
	 * @method setVariable
	 * @param variable
	 */
	self.setVariable = function (variable) {
	  	if(mVariable != variable) {
	  		mVariable = variable;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getVariable
	 * @return
	 */
	self.getVariable = function() { 
		return mVariable; 
	}

	/** 
	 * @method setFunctionX
	 * @param functionx
	 */
	self.setFunctionX = function (functionx) {
	  	if(mFunctionX != functionx) {
	  		mFunctionX = functionx;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getFunctionX
	 * @return
	 */
	self.getFunctionX = function() { 
		return mFunctionX; 
	}

	/** 
	 * @method setFunctionY
	 * @param functiony
	 */
	self.setFunctionY = function (functiony) {
	  	if(mFunctionY != functiony) {
	  		mFunctionY = functiony;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getFunctionY
	 * @return
	 */
	self.getFunctionY = function() { 
		return mFunctionY; 
	}


	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.AnalyticCurve.registerProperties(self, controller);
	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	self.setSize([1,1]);
    self.setRelativePosition("SOUTH_WEST");
		
	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * Arrow is a class to display vectors
 * @class EJSS_DRAWING2D.Arrow 
 * @parent EJSS_DRAWING2D.Element
 * @constructor  
 */
EJSS_DRAWING2D.Arrow = {
    NONE : 0,
    ANGLE : 1,
    CIRCLE : 2,
    RECTANGLE : 3,
    LINE : 4,
    CURVE : 5,
    TRIANGLE: 6,
    DIAMOND: 7,     
    WEDGE: 8,
	POINTED: 9,
	INVANGLE: 10,
    INVTRIANGLE: 11,
    
    SPLINE_NONE : 0,
    SPLINE_CUBIC : 1,
    SPLINE_QUADRATIC : 2,
    SPLINE_CUBIC_WITH_MIDDLE_POINT : 3,
    SPLINE_QUADRATIC_WITH_MIDDLE_POINT : 4,
    
	    	
    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy
  	
      dest.setRelativePosition(source.getRelativePosition());
      dest.setMarkEnd(source.getMarkEnd());
      dest.setMarkEndWidth(source.getMarkEndWidth());
      dest.setMarkEndHeight(source.getMarkEndHeight());
      dest.setMarkEndColor(source.getMarkEndColor());
      dest.setMarkEndStroke(source.getMarkEndStroke());
      dest.setMarkEndOrient(source.getMarkEndOrient());
      dest.setMarkStart(source.getMarkStart());
      dest.setMarkStartWidth(source.getMarkStartWidth());
      dest.setMarkStartHeight(source.getMarkStartHeight());
	  dest.setMarkStartColor(source.getMarkStartColor());
	  dest.setMarkStartStroke(source.getMarkStartStroke());
      dest.setMarkStartOrient(source.getMarkStartOrient());

      dest.setMarkMiddle(source.getMarkMiddle());
      dest.setMarkMiddleWidth(source.getMarkMiddleWidth());
      dest.setMarkMiddleHeight(source.getMarkMiddleHeight());
      dest.setMarkMiddleColor(source.getMarkMiddleColor());
      dest.setMarkMiddleColorSnd(source.getMarkMiddleColorSnd());
      dest.setMarkMiddleStroke(source.getMarkMiddleStroke());
      dest.setMarkMiddleOrient(source.getMarkMiddleOrient());
      dest.setMarkProportion(source.getMarkProportion());

      dest.setSplineType(source.getSplineType());

  	},


    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class
      
      controller.registerProperty("SplineType",element.setSplineType);

      controller.registerProperty("InteractionPosition", element.setInteractionPosition);
      controller.registerProperty("Offset", element.setRelativePosition, element.getRelativePosition);
      controller.registerProperty("MarkEnd",element.setMarkEnd);
      controller.registerProperty("MarkEndWidth",element.setMarkEndWidth);
      controller.registerProperty("MarkEndHeight",element.setMarkEndHeight);
      controller.registerProperty("MarkEndDiameter",element.setMarkEndDiameter);
      controller.registerProperty("MarkEndColor",element.setMarkEndColor);     
      controller.registerProperty("MarkEndStroke",element.setMarkEndStroke);
      /*** 
	  * Orientation of the marker. Values are "auto" or the angle to rotate the marker 
	  * @property MarkEndOrient 
	  * @type int|String
	  * @default "auto"
	  */ 
      controller.registerProperty("MarkEndOrient",element.setMarkEndOrient);
      
      controller.registerProperty("MarkStart",element.setMarkStart);
      controller.registerProperty("MarkStartWidth",element.setMarkStartWidth);
      controller.registerProperty("MarkStartHeight",element.setMarkStartHeight);
      controller.registerProperty("MarkStartDiameter",element.setMarkStartDiameter);
	  controller.registerProperty("MarkStartColor",element.setMarkStartColor);
	  controller.registerProperty("MarkStartStroke",element.setMarkStartStroke);
      /*** 
	  * Orientation of the marker. Values are "auto" or the angle to rotate the marker 
	  * @property MarkStartOrient 
	  * @type int|String
	  * @default "auto"
	  */ 
	  controller.registerProperty("MarkStartOrient",element.setMarkStartOrient);

      controller.registerProperty("MarkMiddle",element.setMarkMiddle);
      controller.registerProperty("MarkMiddleWidth",element.setMarkMiddleWidth);
      controller.registerProperty("MarkMiddleHeight",element.setMarkMiddleHeight);
      controller.registerProperty("MarkMiddleDiameter",element.setMarkMiddleDiameter);
      controller.registerProperty("MarkMiddleColor",element.setMarkMiddleColor);
      controller.registerProperty("MarkMiddleColorSnd",element.setMarkMiddleColorSnd);
      controller.registerProperty("MarkMiddleStroke",element.setMarkMiddleStroke);
      /*** 
	  * Orientation of the marker. Values are "auto" or the angle to rotate the marker 
	  * @property MarkMiddleOrient 
	  * @type int|String
	  * @default "auto"
	  */ 
      controller.registerProperty("MarkMiddleOrient",element.setMarkMiddleOrient);
      
      controller.registerProperty("MarkProportion",element.setMarkProportion);
    },  
};

/**
 * Creates a 2D Arrow
 * @method arrow
 */
EJSS_DRAWING2D.arrow = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  // Implementation variables
  var mMarkProportion = 0.5;
  var mMarkStart = EJSS_DRAWING2D.Arrow.NONE;
  var mMarkStartColor = "none";
  var mMarkStartWidth = 12;
  var mMarkStartHeight = 10;
  var mMarkStartStroke = -1;
  var mMarkStartOrient = "auto";
  var mMarkEnd = EJSS_DRAWING2D.Arrow.ANGLE;
  var mMarkEndColor = "none";
  var mMarkEndWidth = 12;
  var mMarkEndHeight = 12;
  var mMarkEndStroke = -1;
  var mMarkEndOrient = "auto";
  var mMarkMiddle = EJSS_DRAWING2D.Arrow.NONE;
  var mMarkMiddleColor = "none";  
  var mMarkMiddleColorSnd = "none";  
  var mMarkMiddleWidth = 12;
  var mMarkMiddleHeight = 10;
  var mMarkMiddleStroke = -1;
  var mMarkMiddleOrient = "auto";
  var mSplineType = EJSS_DRAWING2D.Arrow.SPLINE_NONE;

  self.getClass = function() {
  	return "ElementArrow";
  }

  self.setInteractionPosition = function(position) {
    if (typeof position == "string") position = EJSS_DRAWING2D.Element[position.toUpperCase()];
    var inter = self.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION);
    if (inter.getPositionOffset() != position) {
      inter.setPositionOffset(position);
      self.setChanged(true);
    }	
  }

  self.getInteractionPosition = function() {
    var inter = self.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION);
    return inter.getPositionOffset();
  }
  
  self.setMarkProportion = function(prop) {
    if (mMarkProportion != prop) {
      mMarkProportion = prop;
      self.setChanged(true);
    }
  };

  self.getMarkProportion = function() {
  	return mMarkProportion;
  }	

  self.setMarkStart = function(mark) {
    if (typeof mark === 'string')
      mMarkStart = EJSS_DRAWING2D.Arrow[mark.toUpperCase()];
    else 
      mMarkStart = mark;
  };

  self.getMarkStart = function() {
  	return mMarkStart;
  }	

  self.setMarkStartColor = function(color) { 
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    if (color!=mMarkStartColor) {
      mMarkStartColor = color; 
      self.setChanged(true);
    }
    return self;
  };
    
  self.getMarkStartColor = function() { 
    return mMarkStartColor; 
  };

  self.setMarkStartStroke = function(width) {
    if (mMarkStartStroke != width) {
      mMarkStartStroke = width;
      self.setChanged(true);
    }
  };

  self.getMarkStartStroke = function() {
  	return mMarkStartStroke;
  }	

  self.setMarkStartWidth = function(width) {
    if (mMarkStartWidth != width) {
      mMarkStartWidth = width;
      self.setChanged(true);
    }
  };

  self.getMarkStartWidth = function() {
  	return mMarkStartWidth;
  }	

  self.setMarkStartHeight = function(height) {
    if (mMarkStartHeight != height) {
      mMarkStartHeight = height;
      self.setChanged(true);
    }
  };

  self.getMarkStartHeight = function() {
  	return mMarkStartHeight;
  }	

  self.setMarkStartDiameter = function(diameter) {
    if ((mMarkStartHeight != diameter) || (mMarkStartWidth != diameter)) {
      mMarkStartHeight = diameter;
      mMarkStartWidth = diameter;
      self.setChanged(true);
    }
  };

  self.getMarkStartOrient = function() {
  	return mMarkStartOrient;
  }	

  self.setMarkStartOrient = function(orient) {
    if (mMarkStartOrient != orient) {
      mMarkStartOrient = orient;
      self.setChanged(true);
    }
  };

  self.setMarkEnd = function(mark) {
    if (typeof mark === 'string')
      mMarkEnd = EJSS_DRAWING2D.Arrow[mark.toUpperCase()];
    else 
      mMarkEnd = mark;
  };

  self.getMarkEnd = function() {
  	return mMarkEnd;
  }	

  self.setMarkEndColor = function(color) { 
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    if (color!=mMarkEndColor) {
      mMarkEndColor = color; 
      self.setChanged(true);
    }
    return self;
  };
    
  self.getMarkEndColor = function() { 
    return mMarkEndColor; 
  };
  
  self.setMarkEndStroke = function(width) {
    if (mMarkEndStroke != width) {
      mMarkEndStroke = width;
      self.setChanged(true);
    }
  };

  self.getMarkEndStroke = function() {
  	return mMarkEndStroke;
  }	

  self.setMarkEndWidth = function(width) {
    if (mMarkEndWidth != width) {
      mMarkEndWidth = width;
      self.setChanged(true);
    }
  };

  self.getMarkEndWidth = function() {
  	return mMarkEndWidth;
  }	

  self.setMarkEndHeight = function(height) {
    if (mMarkEndHeight != height) {
      mMarkEndHeight = height;
      self.setChanged(true);
    }
  };

  self.getMarkEndHeight = function() {
  	return mMarkEndHeight;
  }	

  self.setMarkEndDiameter = function(diameter) {
    if ((mMarkEndHeight != diameter) || (mMarkEndWidth != diameter)) {
      mMarkEndHeight = diameter;
      mMarkEndWidth = diameter;
      self.setChanged(true);
    }
  };

  self.getMarkEndOrient = function() {
  	return mMarkEndOrient;
  }	

  self.setMarkEndOrient = function(orient) {
    if (mMarkEndOrient != orient) {
      mMarkEndOrient = orient;
      self.setChanged(true);
    }
  };

  self.setMarkMiddle = function(mark) {
    if (typeof mark === 'string')
      mMarkMiddle = EJSS_DRAWING2D.Arrow[mark.toUpperCase()];
    else 
      mMarkMiddle = mark;
  };

  self.getMarkMiddle = function() {
  	return mMarkMiddle;
  }	

  self.setMarkMiddleColor = function(color) { 
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    if (color!=mMarkMiddleColor) {
      mMarkMiddleColor = color; 
      self.setChanged(true);
    }
    return self;
  };
    
  self.getMarkMiddleColor = function() { 
    return mMarkMiddleColor; 
  };

  self.setMarkMiddleStroke = function(width) {
    if (mMarkMiddleStroke != width) {
      mMarkMiddleStroke = width;
      self.setChanged(true);
    }
  };

  self.getMarkMiddleStroke = function() {
  	return mMarkMiddleStroke;
  }	

  self.setMarkMiddleColorSnd = function(color) { 
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    if (color!=mMarkMiddleColorSnd) {
      mMarkMiddleColorSnd = color; 
      self.setChanged(true);
    }
    return self;
  };
    
  self.getMarkMiddleColorSnd = function() { 
    return mMarkMiddleColorSnd; 
  };

  self.setMarkMiddleWidth = function(width) {
    if (mMarkMiddleWidth != width) {
      mMarkMiddleWidth = width;
      self.setChanged(true);
    }
  };

  self.getMarkMiddleWidth = function() {
  	return mMarkMiddleWidth;
  }	

  self.setMarkMiddleHeight = function(height) {
    if (mMarkMiddleHeight != height) {
      mMarkMiddleHeight = height;
      self.setChanged(true);
    }
  };

  self.getMarkMiddleHeight = function() {
  	return mMarkMiddleHeight;
  }	

  self.setMarkMiddleDiameter = function(diameter) {
    if ((mMarkMiddleHeight != diameter) || (mMarkMiddleWidth != diameter)) {
      mMarkMiddleHeight = diameter;
      mMarkMiddleWidth = diameter;
      self.setChanged(true);
    }
  };

  self.getMarkMiddleOrient = function() {
  	return mMarkMiddleOrient;
  }	

  self.setMarkMiddleOrient = function(orient) {
    if (mMarkMiddleOrient != orient) {
      mMarkMiddleOrient = orient;
      self.setChanged(true);
    }
  };

  // ----------------------------------------------------
  // SVG Splines
  // ----------------------------------------------------

  
  /**
   * 
   */
  self.setSplineType = function(spline) {
    if (mSplineType != spline) {
      mSplineType = spline;
      self.setChanged(true);
    }
  };

  self.getSplineType = function() {
    return mSplineType;
  } 

  // ----------------------------------------------------
  // Properties and copy
  // ----------------------------------------------------

  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Arrow.registerProperties(self,controller);
  };
  
  self.copyTo = function(element) {
    EJSS_DRAWING2D.Arrow.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([0.1,0.1]);
  self.setRelativePosition("SOUTH_WEST");
  // self.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setPositionOffset("SOUTH_WEST");

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//ArrowSet
//---------------------------------

/***
 * ArrowSet is a set of Arrows
 * @class EJSS_DRAWING2D.ArrowSet 
 * @constructor  
 */
EJSS_DRAWING2D.ArrowSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING2D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("Offset", 
          function(v) { set.setToEach(function(element,value) { element.setRelativePosition(value); }, v); }
      );
      controller.registerProperty("InteractionPosition", 
          function(v) { set.setToEach(function(element,value) { element.setInteractionPosition(value); }, v); }
      );      

      controller.registerProperty("MarkEnd", 
          function(v) { set.setToEach(function(element,value) { element.setMarkEnd(value); }, v); }
      );
      controller.registerProperty("MarkEndWidth", 
          function(v) { set.setToEach(function(element,value) { element.setMarkEndWidth(value); }, v); }
      );
      controller.registerProperty("MarkEndHeight", 
          function(v) { set.setToEach(function(element,value) { element.setMarkEndHeight(value); }, v); }
      );
      controller.registerProperty("MarkEndDiameter", 
          function(v) { set.setToEach(function(element,value) { element.setMarkEndDiameter(value); }, v); }
      );
      controller.registerProperty("MarkEndColor", 
          function(v) { set.setToEach(function(element,value) { element.setMarkEndColor(value); }, v); }
      );
      controller.registerProperty("MarkEndStroke", 
          function(v) { set.setToEach(function(element,value) { element.setMarkEndStroke(value); }, v); }
      );
      controller.registerProperty("MarkEndRotate", 
          function(v) { set.setToEach(function(element,value) { element.setMarkEndRotate(value); }, v); }
      );
      /*** 
	  * Orientation of the marker. Values are "auto" or the angle to rotate the marker 
	  * @property MarkEndOrient 
	  * @type int|String
	  * @default "auto"
	  */ 
      controller.registerProperty("MarkEndOrient", 
          function(v) { set.setToEach(function(element,value) { element.setMarkEndOrient(value); }, v); }
      );
      

      controller.registerProperty("MarkStart", 
          function(v) { set.setToEach(function(element,value) { element.setMarkStart(value); }, v); }
      );
      controller.registerProperty("MarkStartWidth", 
          function(v) { set.setToEach(function(element,value) { element.setMarkStartWidth(value); }, v); }
      );
      controller.registerProperty("MarkStartHeight", 
          function(v) { set.setToEach(function(element,value) { element.setMarkStartHeight(value); }, v); }
      );
      controller.registerProperty("MarkStartDiameter", 
          function(v) { set.setToEach(function(element,value) { element.setMarkStartDiameter(value); }, v); }
      );
      controller.registerProperty("MarkStartColor", 
          function(v) { set.setToEach(function(element,value) { element.setMarkStartColor(value); }, v); }
      );
      controller.registerProperty("MarkStartStroke", 
          function(v) { set.setToEach(function(element,value) { element.setMarkStartStroke(value); }, v); }
      );
      controller.registerProperty("MarkStartRotate", 
          function(v) { set.setToEach(function(element,value) { element.setMarkStartRotate(value); }, v); }
      );
      /*** 
	  * Orientation of the marker. Values are "auto" or the angle to rotate the marker 
	  * @property MarkStartOrient 
	  * @type int|String
	  * @default "auto"
	  */ 
      controller.registerProperty("MarkStartOrient", 
          function(v) { set.setToEach(function(element,value) { element.setMarkStartOrient(value); }, v); }
      );


      controller.registerProperty("MarkMiddle", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddle(value); }, v); }
      );
      controller.registerProperty("MarkMiddleWidth", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddleWidth(value); }, v); }
      );
      controller.registerProperty("MarkMiddleHeight", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddleHeight(value); }, v); }
      );
      controller.registerProperty("MarkMiddleDiameter", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddleDiameter(value); }, v); }
      );
      controller.registerProperty("MarkMiddleColor", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddleColor(value); }, v); }
      );
      controller.registerProperty("MarkMiddleColorSnd", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddleColorSnd(value); }, v); }
      );
      controller.registerProperty("MarkMiddleStroke", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddleStroke(value); }, v); }
      );
      controller.registerProperty("MarkMiddleRotate", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddleRotate(value); }, v); }
      );
      controller.registerProperty("MarkProportion", 
          function(v) { set.setToEach(function(element,value) { element.setMarkProportion(value); }, v); }
      );
      /*** 
	  * Orientation of the marker. Values are "auto" or the angle to rotate the marker 
	  * @property MarkMiddleOrient 
	  * @type int|String
	  * @default "auto"
	  */ 
      controller.registerProperty("MarkMiddleOrient", 
          function(v) { set.setToEach(function(element,value) { element.setMarkMiddleOrient(value); }, v); }
      );


    }

};


/**
 * Creates a set of Segments
 * @method arrowSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.arrowSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.arrow, mName);

  // Static references
  var ArrowSet = EJSS_DRAWING2D.ArrowSet;		// reference for ArrowSet
  
  self.registerProperties = function(controller) {
    ArrowSet.registerProperties(self,controller);
  };


  return self;
};/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * An Axis is the 2D drawable used by PlottingPanels as decoration  
 * @class EJSS_DRAWING2D.Axis 
 * @parent EJSS_DRAWING2D.Segment
 * @constructor  
 */
EJSS_DRAWING2D.Axis = {
	// axis orientation 
	AXIS_VERTICAL   : 0,
	AXIS_HORIZONTAL : 1,
	// ticks position (reference for text)
	TICKS_UP   : 2,
	TICKS_DOWN : 3,
	// ticks increment
	SCALE_NUM	: 0,	// decimal
	SCALE_LOG	: 1,	// logarithmic

	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

		// ticks
		controller.registerProperty("Step", element.setStep, element.getStep);
		controller.registerProperty("TickStep", element.setTickStep, element.getTickStep);
		controller.registerProperty("Ticks", element.setTicks, element.getTicks);
		controller.registerProperty("AutoTicks", element.setAutoTicks, element.getAutoTicks);				
		controller.registerProperty("FixedTick", element.setFixedTick, element.getFixedTick);				

		// axis properties
		controller.registerProperty("Orient", element.setOrient, element.getOrient);
		controller.registerProperty("Scale", element.setScale, element.getScale);
		
		// ticks properties
		controller.registerProperty("TicksMode", element.setTicksMode, element.getTicksMode);
		controller.registerProperty("TicksSize", element.setTicksSize, element.getTicksSize);
	
		// auto ticks		
		controller.registerProperty("AutoStepMin", element.setAutoStepMin, element.getAutoStepMin);				
		controller.registerProperty("AutoTicksRange", element.setAutoTicksRange, element.getAutoTicksRange);				
		
		// ticks text properties
		controller.registerProperty("TextPosition", element.setTextPosition, element.getTextPosition);
		controller.registerProperty("ScalePrecision", element.setScalePrecision, element.getScalePrecision);		
		controller.registerProperty("Font", element.getFont().setFont);
		controller.registerProperty("FontFamily", element.getFont().setFontFamily);
		controller.registerProperty("FontSize", element.getFont().setFontSize);
		controller.registerProperty("LetterSpacing", element.getFont().setLetterSpacing);
		controller.registerProperty("FontOutlineColor", element.getFont().setOutlineColor);
		controller.registerProperty("FontWeight", element.getFont().setFontWeight);
		controller.registerProperty("FontFillColor", element.getFont().setFillColor);		
		
		// show
		controller.registerProperty("Show", element.setShow, element.getShow);
	}
			
};

/**
 * Creates a 2D Axis
 * @method axis
 */
EJSS_DRAWING2D.axis = function(mName) {
	var self = EJSS_DRAWING2D.segment(mName);
 
 	// drawing priority: mAutoTicks - mTicks - mStep
 	var mAutoTicks = true;								// auto-ticks
 	var mTicks = 0;										// number of ticks
 	var mStep = 20; 									// step between ticks in pixels
 	var mTickStep = 0; 									// step between ticks in real coordinates
	var mFixedTick = Number.NaN;								// ticks fixed in axis	

	// axis properties	
	var mOrient = EJSS_DRAWING2D.Axis.AXIS_VERTICAL;	// axis orientation
	var mScale = [-1,1];									// axis scale
	var mInvertedScaleY = false;						// whether inverted scale in Y

	// ticks properties
 	var mTicksMode = EJSS_DRAWING2D.Axis.SCALE_NUM;		// axis scale
 	var mTextPosition = EJSS_DRAWING2D.Axis.TICKS_UP;	// tick orientation
	var mTicksSize = 10;								// tick pixel size

	// ticks text properties
	var mScalePrecision = 1;							// number of decimals
	var mFont = EJSS_DRAWING2D.font(mName);				// font for text
 
	// auto ticks 	
	var mAutoStepMin = 40;					// step minimun in pixels
	var mAutoTicksRange = [5,10,20];		// ticks range: 
											//   is the step minimun possible in mAutoTicksRange[length-1]?
											//   and in mAutoTicksRange[length-2]? ... 
											//   then mAutoTicksRange[length-2] is the number of ticks
	// show
	var mShow = true;
	   
	self.getClass = function() {
		return "ElementAxis";
	}

	self.setShow = function(show) {
		if(mShow != show) {
			mShow = show;
			self.setChanged(true);
		}
	}

	self.getShow = function() {
		return mShow;
	}

	self.getFont = function() {
		return mFont;
	}
		
	/** 
	 * @method setAutoTicksRangeX
	 * @param range
	 */
	self.setAutoTicksRange = function (range) {
	  	if(mAutoTicksRange != range) {
	  		mAutoTicksRange = range;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getAutoTicksRange
	 * @return
	 */
	self.getAutoTicksRange = function() { 
		return mAutoTicksRange; 
	}

	/** 
	 * @method setAutoStepMin
	 * @param min
	 */
	self.setAutoStepMin = function (min) {
	  	if(mAutoStepMin != min) {
	  		mAutoStepMin = min;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getAutoStepMin
	 * @return
	 */
	self.getAutoStepMin = function() { 
		return mAutoStepMin; 
	}

	/** 
	 * @method setFixedTick
	 * @param fixed
	 */
	self.setFixedTick = function (fixed) {
	  	if(mFixedTick != fixed) {
	  		mFixedTick = fixed;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getFixedTick
	 * @return
	 */
	self.getFixedTick = function() { 
		return mFixedTick; 
	}
			
	/***
	 * @method setAutoTicks
	 * @param auto
	 */
	self.setAutoTicks = function (auto) {
	  	if(mAutoTicks != auto) {
	  		mAutoTicks = auto;
	  		self.setChanged(true);
	  	}
	}
	  
	/***
	 * @method getAutoTicks
	 * @return
	 */
	self.getAutoTicks = function() { 
		return mAutoTicks; 
	}
			 
	/** 
	 * @method setStep
	 * @param stet
	 */
	self.setStep = function (step) {
		if(mStep != step) {
	  		mStep = step;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getStep
	 * @return
	 */
	self.getStep = function() { 
		return mStep; 
	}

	/** 
	 * @method setTickStep
	 * @param stet
	 */
	self.setTickStep = function (TickStep) {
		if(mTickStep != TickStep) {
	  		mTickStep = TickStep;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getTickStep
	 * @return
	 */
	self.getTickStep = function() { 
		return mTickStep; 
	}
		 
	/** 
	 * @method setTicks
	 * @param ticks
	 */
	self.setTicks = function (ticks) {
	  	if(mTicks != ticks) {
	  		mTicks = ticks;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getTicks
	 * @return
	 */
	self.getTicks = function() { 
		return mTicks; 
	}

	/** 
	 * @method setTicksMode
	 * @param ticksMode
	 */
	self.setTicksMode = function (ticksMode) {
    	if (typeof ticksMode == "string") ticksMode = EJSS_DRAWING2D.Grid[ticksMode.toUpperCase()];
    	if(mTicksMode != ticksMode) {
    		mTicksMode = ticksMode;
    		self.setChanged(true);
    	}	
	}
	  
	/**
	 * @method getTicksMode
	 * @return
	 */
	self.getTicksMode = function() { 
		return mTicksMode; 
	}

	/** 
	 * @method setTextPosition
	 * @param textPosition
	 */
	self.setTextPosition = function (textPosition) {
    	if (typeof textPosition == "string") textPosition = EJSS_DRAWING2D.Axis[textPosition.toUpperCase()];
    	if(mTextPosition != textPosition) {
    		mTextPosition = textPosition;
    		self.setChanged(true);
    	}	
	}
	  
	/**
	 * @method getTextPosition
	 * @return
	 */
	self.getTextPosition = function() { 
		return mTextPosition; 
	}

	/** 
	 * @method setOrient
	 * @param orient
	 */
	self.setOrient = function (orient) {
    	if (typeof orient == "string") orient = EJSS_DRAWING2D.Axis[orient.toUpperCase()];
    	if (mOrient != orient) {
    		mOrient = orient;
    		self.setChanged(true);	
    	}	
	}
	  
	/**
	 * @method getOrient
	 * @return
	 */
	self.getOrient = function() { 
		return mOrient; 
	}

	/** 
	 * @method setScale
	 * @param scale
	 */
	self.setScale = function (scale) {
		if(mScale != scale) {
	  		mScale = scale;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getScale
	 * @return
	 */
	self.getScale = function() { 
		return mScale; 
	}

	/** 
	 * @method setScalePrecision
	 * @param scalePrecision
	 */
	self.setScalePrecision = function (scalePrecision) {
		if(mScalePrecision != scalePrecision) {
	  		mScalePrecision = scalePrecision;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getScalePrecision
	 * @return
	 */
	self.getScalePrecision = function() { 
		return mScalePrecision; 
	}

	/** 
	 * @method setTicksSize
	 * @param ticksSize
	 */
	self.setTicksSize = function (ticksSize) {
		if(mTicksSize != ticksSize) {
	  		mTicksSize = ticksSize;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getTicksSize
	 * @return
	 */
	self.getTicksSize = function() { 
		return mTicksSize; 
	}
	
	/**
	 * Set inverted scale in Y
	 * @method setInvertedScaleY
	 */
	self.setInvertedScaleY = function(invertedscale) {
		if(mInvertedScaleY != invertedscale) {
			mInvertedScaleY = invertedscale;
	  		self.setChanged(true);
		}
	};

	/**
	 * Get inverted scale in Y
	 * @method getInvertedScaleY
	 * @return boolean
	 */
	self.getInvertedScaleY = function() {
		return mInvertedScaleY;
	};
	
	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.Axis.registerProperties(self, controller);
	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	mFont.setChangeListener(function (change) { self.setChanged(true); });

	return self;
};

/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * ByteRaster
 * @class ByteRaster 
 * @constructor  
 */
EJSS_DRAWING2D.ByteRaster = {	
	
	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Data", element.setData, element.getData);
		controller.registerProperty("NumColors", element.getColorMapper().setNumberOfColors);
		controller.registerProperty("Colors", element.getColorMapper().setColorPalette);
		controller.registerProperty("Palette", element.getColorMapper().setPaletteType);	
        controller.registerProperty("AutoUpdate", element.setAutoupdate);  
        controller.registerProperty("RGBData", element.setRGBData);  
	}
			
};

/**
 * Creates a 2D ByteRaster
 * @method byteRaster
 */
EJSS_DRAWING2D.byteRaster = function(name) {
	var self = EJSS_DRAWING2D.element(name);
 
 	var mColorMapper = EJSS_DRAWING2D.colorMapper(20, EJSS_DRAWING2D.ColorMapper.REDBLUE_SHADE); 	
	var mData = [];
	var mDataChanged = false;
	var mAutoUpdate=true;
	var mRGBData = false;
 
	self.getClass = function() {
		return "ElementByteRaster";
	}

	/** 
	 * @method setData
	 * @param data
	 */
	self.setData = function (data) {
	  if (mAutoUpdate || mData != data) {
	  		mData = data;
	  		mDataChanged = true;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getData
	 * @return
	 */
	self.getData = function() { 
		return mData; 
	}
	 
  self.setRGBData= function (rgbData) {
    mRGBData = rgbData;
    if (mAutoUpdate) mDataChanged = true;
  }

  self.isRGBData= function () {
    return mRGBData;
  }
	 
  self.setAutoupdate= function (auto) {
    mAutoUpdate = auto;
    if (mAutoUpdate) mDataChanged = true;
  }
	 
	/** 
	 * @method setColorMapper
	 * @param colormapper
	 */
	self.setColorMapper = function (colormapper) {
	  	if(mColorMapper != colormapper) {
	  		mColorMapper = colormapper;
	  		mDataChanged = true;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getColorMapper
	 * @return
	 */
	self.getColorMapper = function() { 
		return mColorMapper; 
	}
	 
	self.setIndexedColor = function (value) {
	  //console.log ("Value = "+value);
	  var color = value.color; 
	  var colorStr = (color[3]===undefined) ? "rgb("+color[0]+","+color[1]+","+color[2]+")" : "rgb("+color[0]+","+color[1]+","+color[2]+","+color[3]+")";
	  console.log(self.getName()+": setIndexedColor("+value.index+","+colorStr+")");
	  var colors = mColorMapper.getColors();
	  colors[value.index] = colorStr;  
	};
	
	self.getDataChanged = function() {
		return mDataChanged;
	}

	self.setDataChanged = function(dataChanged) {
		mDataChanged = dataChanged;
	}

  self.invalidate = function() {
    mDataChanged = true;
  }

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.ByteRaster.registerProperties(self, controller);
	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	mColorMapper.setChangeListener(function (change) { self.setChanged(true); });
	return self;
};

 /*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Canvas
 * @class Canvas 
 * @constructor  
 */
EJSS_DRAWING2D.Canvas = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy
  	
  		dest.setDimensions(source.getDimensions());
  	},


    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

		controller.registerProperty("Dimensions", element.setDimensions, element.getDimensions);

    },
};

/**
 * Creates a 2D Segment
 * @method Canvas
 */
EJSS_DRAWING2D.canvas = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  var mDrawables = [];
  var mxMin = -10;
  var mxMax = 10;
  var myMin = -10;
  var myMax = 10;

  self.getClass = function() {
  	return "ElementCanvas";
  };

  // xMin, xMax, yMin, yMax
  self.setDimensions = function(dimensions) {
  	xMin = dimensions[0];
  	xMax = dimensions[1];
  	yMin = dimensions[2];
  	yMax = dimensions[3];

  	self.setChanged(true);
  }
  
  self.getDimensions = function() {
  	return [xMin,xMax,yMin,yMax];
  }

  self.addDrawable = function(drawable) {
    mDrawables.push(drawable);
  	self.setChanged(true);
  }

  self.addImageField = function(data, xMin, xMax, xPoints, yMin, yMax, yPoints, autoscale, zMin, zMax) {
  	var imgField = function() {
  	  var shape = {};
  	  shape.imageField = true;
	  shape.data = data || undefined;
	  shape.xMin = xMin === undefined ? -5: xMin;
	  shape.xMax = xMax === undefined ? 5: xMax;
	  shape.nx = xPoints || 128;
	  shape.yMin = yMin === undefined ? -5: yMin;
	  shape.yMax = yMax === undefined ? 5: yMax;
	  shape.ny = yPoints || 128;
	  shape.autoscale = autoscale || true;
	  shape.lower = zMin === undefined ? -1: zMin;
	  shape.upper = zMax === undefined ? 1: zMax;
	  shape.center = (shape.lower+shape.upper)/2;  // usually zero
	  shape.maskRadius=0;  // no circular mask if radius=0

	  function arrayCoordinates(min, max, points) {
	    var coordinates =[];
	    var step = (max - min) /(points -1)
	    var val = min;
	    for (var i = 0; i < points; i++) {
	        //pushes in each of the x values
	        coordinates.push(val);
	        val += step;
	    }
	    return coordinates;
	  }
	 
	  shape.xPos = arrayCoordinates(shape.xMin, shape.xMax, shape.nx);
	  shape.yPos = arrayCoordinates(shape.yMin, shape.yMax, shape.ny); 	  
	 
	  shape.updateData = function (newData) {
	    //updates the contour lines if data are changed
	    shape.data = newData || shape.data;
	    shape.xPos = arrayCoordinates(shape.xMin, shape.xMax, shape.nx);
	    shape.yPos = arrayCoordinates(shape.yMin, shape.yMax, shape.ny);
	    
	    self.setChanged(true);
	  }
	
	  shape.setThreshold = function (lower, upper) {
	    shape.lower = lower === undefined ? shape.lower: lower;
	    shape.upper = upper === undefined ? shape.upper: upper;
	    
	    self.setChanged(true);
	  }
	  
	  // Draw a curcular image
	  shape.setCircularMask = function (radius) {
	    shape.maskRadius = radius;
	    
	    self.setChanged(true);
	  }
 
 	  return shape;
  	}();
  	
  	self.addDrawable(imgField);
  	return imgField;
  }
 
  self.getDrawables = function() {
  	return mDrawables;
  }

  this.clearObjects = function () {
    mDrawables =[];
  	self.setChanged(true);
  }
  
  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Canvas.registerProperties(self,controller);
  };
  
  self.copyTo = function(element) {
    EJSS_DRAWING2D.Canvas.copyTo(self,element);
  };
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  return self;
};



/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * CellLattice
 * @class CellLattice 
 * @constructor  
 */
EJSS_DRAWING2D.CellLattice = {	
	
	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Data", element.setData, element.getData);
		controller.registerProperty("ShowGrid", element.setShowGrid, element.getShowGrid);
		controller.registerProperty("NumColors", element.getColorMapper().setNumberOfColors);
		controller.registerProperty("Colors", element.getColorMapper().setColorPalette);
		controller.registerProperty("Palette", element.getColorMapper().setPaletteType);
        controller.registerProperty("AutoUpdate", element.setAutoupdate);  
	}
			
};

/**
 * Creates a 2D CellLattice
 * @method cellLattice
 */
EJSS_DRAWING2D.cellLattice = function(name) {
	var self = EJSS_DRAWING2D.element(name);
 
 	var mColorMapper = EJSS_DRAWING2D.colorMapper(20, EJSS_DRAWING2D.ColorMapper.REDBLUE_SHADE); 	
 	var mShowGrid = true;
	var mData = [];
	var mAutoUpdate=true;
 
	self.getClass = function() {
		return "ElementCellLattice";
	}


	/** 
	 * @method setData
	 * @param data
	 */
	self.setData = function (data) {
	  if (mAutoUpdate || mData != data) {
	    mData = data;
		mDataChanged = true;
		self.setChanged(true);
	  }
	}
	  
	/**
	 * @method getData
	 * @return
	 */
	self.getData = function() { 
		return mData; 
	}
	 
	self.setAutoupdate= function (auto) {
	  mAutoUpdate = auto;
      if (mAutoUpdate) mDataChanged = true;
    }
	
	/** 
	 * @method setColorMapper
	 * @param colormapper
	 */
	self.setColorMapper = function (colormapper) {
	  	if(mColorMapper != colormapper) {
	  		mColorMapper = colormapper;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getColorMapper
	 * @return
	 */
	self.getColorMapper = function() { 
		return mColorMapper; 
	}
	 
	/** 
	 * @method setShowGrid
	 * @param showgrid
	 */
	self.setShowGrid = function (showgrid) {
	  	if(mShowGrid != showgrid) {
	  		mShowGrid = showgrid;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getShowGrid
	 * @return
	 */
	self.getShowGrid = function() { 
		return mShowGrid; 
	}


	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.CellLattice.registerProperties(self, controller);
	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	return self;
};

 
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Cursor
 * @class Cursor 
 * @constructor  
 */
EJSS_DRAWING2D.Cursor = {
    HORIZONTAL : 0,
    VERTICAL : 1,
    CROSSHAIR : 2,
    
	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class
		
		controller.registerProperty("CursorType", element.setCursorType, element.getCursorType);
	},
};

/**
 * Creates a 2D Cursor
 * @method cursor
 */
EJSS_DRAWING2D.cursor = function(name) {
	var self = EJSS_DRAWING2D.element(name);	
	var Cursor = EJSS_DRAWING2D.Cursor;
	
	var mCursorType;

	self.getClass = function() {
		return "ElementCursor";
	}
	
	self.setCursorType = function(cursorType) {
	  var InteractionTarget = EJSS_DRAWING2D.InteractionTarget;
	  var target = self.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION);
      if (typeof cursorType === 'string') 
        mCursorType = EJSS_DRAWING2D.Cursor[cursorType.toUpperCase()];
      else mCursorType = cursorType;
      switch (mCursorType) {
        default : target.setSensitivityType(InteractionTarget.SENSITIVITY_ANY);break;
        case Cursor.HORIZONTAL : target.setSensitivityType(InteractionTarget.SENSITIVITY_HORIZONTAL);break;
        case Cursor.VERTICAL : target.setSensitivityType(InteractionTarget.SENSITIVITY_VERTICAL);break;
     }   
	}

	self.getCursorType = function() {
	  return mCursorType;
	}

	self.isChanged = function() {
		return true;
	}

	self.registerProperties = function(controller) {
		Cursor.registerProperties(self, controller);
	};

	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	//self.setSize([0.1,0.1]);
	self.setMeasured(false);
	mCursorType = Cursor.CROSSHAIR;
	self.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setSensitivityType(EJSS_DRAWING2D.InteractionTarget.SENSITIVITY_ANY);

	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A Custom element is a 2D drawing element that executes a user function for drawing 
 * @class EJSS_DRAWING2D.Custom
 * @parent EJSS_DRAWING2D.Element
 * @constructor  
 */
EJSS_DRAWING2D.Custom = {

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class
    },  


};

/**
 * Creates a Custom 2D Element
 * @method custom
 */
EJSS_DRAWING2D.custom = function (name) {
  var self = EJSS_DRAWING2D.element(name);
  var mFunction;
  
  self.getClass = function() {
  	return "ElementCustom";
  };
  
    self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Custom.registerProperties(self,controller);
  };


  /**
  * A function to call when the element must draw.
  * The function will receive the graphic context of the panel in which it draws
  */
  self.setFunction = function(customFunction) {
    mFunction = customFunction;
  };
  
  self.getFunction = function() {
    return mFunction;
  };
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1]);

  return self;
};



/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García 
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for 2D drawing.
 * @module 2Ddrawing
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A DrawingPanel is a 2D drawing panel with no decoration. 
 * @class EJSS_DRAWING2D.DrawingPanel
 * @see EJSS_DRAWING2D.PlottingPanel
 * @constructor
 */
EJSS_DRAWING2D.DrawingPanel = {
	MOUSE_ENTERED : 0,
	MOUSE_EXITED : 1,
	MOUSE_PRESSED : 2,
	MOUSE_DRAGGED : 3,
	MOUSE_RELEASED : 4,

	GRAPHICS2D_SVG : 0,
	GRAPHICS2D_CANVAS : 1,

	SCALE_NUM : 0,
	SCALE_LOG : 1,
	
	ENABLED_NONE : 0,
	ENABLED_ANY : 1,
	ENABLED_X : 2,
	ENABLED_Y : 3,
	ENABLED_NO_MOVE : 4,

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		// No super class
		// EJSS_INTERFACE.Element.registerProperties(element.getGraphics(), controller);

		element.setController(controller);

	  	controller.registerProperty("AutoScale", element.setAutoScale, element.getAutoScale);
	 /*** 
	  * Whether the panel should autoscale in the X dimension
	  * @property AutoScaleX 
	  * @type boolean
	  * @default "false"
	  */ 
		controller.registerProperty("AutoScaleX", element.setAutoScaleX, element.getAutoScaleX);
	 /*** 
	  * Whether the panel should autoscale in the Y dimension
	  * @property AutoScaleY 
	  * @type boolean
	  * @default "false"
	  */ 
		controller.registerProperty("AutoScaleY", element.setAutoScaleY, element.getAutoScaleY);		
		controller.registerProperty("InvertedScaleY", element.setInvertedScaleY, element.getInvertedScaleY);
	 /*** 
	  * The minimum value for the X coordinates of elements. 
	  * If specified, the minimum is respected even if AutoscaleX is true and there are no elements close to the minimum.   
	  * @property MinimumX 
	  * @type int or double
	  * @default "-1"
	  */ 
		controller.registerProperty("MinimumX", element.setWorldXMin, element.getWorldXMin);
	 /*** 
	  * The maximum value for the X coordinates of elements. 
	  * If specified, the maximum is respected even if AutoscaleX is true and there are no elements close to the maximum.   
	  * @property MaximumX 
	  * @type int or double
	  * @default "+1"
	  */ 
		controller.registerProperty("MaximumX", element.setWorldXMax, element.getWorldXMax);
	 /*** 
	  * The minimum value for the Y coordinates of elements. 
	  * If specified, the minimum is respected even if AutoscaleY is true and there are no elements close to the minimum.   
	  * @property MinimumY 
	  * @type int or double
	  * @default "-1"
	  */ 
		controller.registerProperty("MinimumY", element.setWorldYMin, element.getWorldYMin);
	 /*** 
	  * The maximum value for the Y coordinates of elements. 
	  * If specified, the maximum is respected even if AutoscaleY is true and there are no elements close to the maximum.   
	  * @property MaximumXY
	  * @type int or double
	  * @default "+1"
	  */ 
		controller.registerProperty("MaximumY", element.setWorldYMax, element.getWorldYMax);
		controller.registerProperty("Bounds", element.setWorldCoordinates, element.getWorldCoordinates);
	 /*** 
	  * The type of scale for the X axis 
	  * @property ScaleXType
	  * @type int or String. One of: <ul>
	  *   <li>EJSS_DRAWING2D.DrawingPanel.SCALE_NUM ("SCALE_NUM" for short)</li>
	  *   <li>EJSS_DRAWING2D.DrawingPanel.SCALE_LOG ("SCALE_LOG" for short)</li>
	  * </ul>
	  * @default "SCALE_NUM"
	  */ 
		controller.registerProperty("ScaleXType", function(v) {
			element.setTypeScaleX(v);
			element.scale();
		}, element.getTypeScaleX);
	 /*** 
	  * The type of scale for the Y axis 
	  * @property ScaleYType
	  * @type int or String. One of: <ul>
	  *   <li>EJSS_DRAWING2D.DrawingPanel.SCALE_NUM ("SCALE_NUM" for short)</li>
	  *   <li>EJSS_DRAWING2D.DrawingPanel.SCALE_LOG ("SCALE_LOG" for short)</li>
	  * </ul>
	  * @default "SCALE_NUM"
	  */ 
		controller.registerProperty("ScaleYType", function(v) {
			element.setTypeScaleY(v);
			element.scale();
		}, element.getTypeScaleY);
	 /*** 
	  * When autoscaling the X axis, the percentage of the X range that should be left (on both sides) around the elements 
	  * @property MarginX
	  * @type int or double (in the range [0,100])
	  * @default "0"
	  */ 
		controller.registerProperty("MarginX", element.setMarginX,element.getMarginX);
	 /*** 
	  * When autoscaling the Y axis, the percentage of the Y range that should be left (on both sides) around the elements 
	  * @property MarginY
	  * @type int or double (in the range [0,100])
	  * @default "0"
	  */ 
		controller.registerProperty("MarginY", element.setMarginY,element.getMarginY);

		controller.registerProperty("Parent", element.getGraphics().setParent, element.getGraphics().getParent);
	 /*** 
	  * A classname for the element that can be used in CSS files 
	  * @property ClassName
	  * @type String
	  * @default "undefined"
	  */ 
        controller.registerProperty("ClassName", element.getGraphics().setClassName);

	 /*** 
	  * The width of the HTML5 element.
	  * See <a href="http://www.w3schools.com/cssref/pr_dim_width.asp">this link</a> for possible values.
	  * @property Width
	  * @type int or String 
	  * @default "auto"
	  */ 
		controller.registerProperty("Width", function(v) {
			if (element.getGraphics().setWidth(v))
				element.scale();
		}, element.getGraphics().getBox().width);
	 /*** 
	  * The height of the HTML5 element.
	  * See <a href="http://www.w3schools.com/cssref/pr_dim_width.asp">this link</a> for possible values.
	  * @property Height
	  * @type int or String 
	  * @default "auto"
	  */ 
		controller.registerProperty("Height", function(v) {
			if (element.getGraphics().setHeight(v))
				element.scale();
		}, element.getGraphics().getBox().height);

		//    controller.registerProperty("X",null,function() { return mInteraction.lastPoint[0]; });
		//    controller.registerProperty("Y",null,function() { return mInteraction.lastPoint[1]; });

	 /*** 
	  * Whether the panel should modify its scales to keep a 1:1 aspect ratio. 
	  * @property SquareAspect
	  * @type boolean 
	  * @default "false"
	  */ 
		controller.registerProperty("SquareAspect", element.setSquareAspect);
	 /*** 
	  * The type of graphics implementation of the panel. 
	  * Changing the graphics mode may influence what elements are actually displayed and the interaction response of the panel.
	  * But it may also influence the rendering speed.   
	  * @property GraphicsMode
	  * @type int or String. One of: <ul>
	  *   <li>EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_SVG ("SVG" for short): SVG based drawing, full interaction, may be slower</li> 
	  *   <li>EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_CANVAS ("CANVAS" for short): Canvas-based drawing, reduced interaction, usually faster</li>
	  * </ul>
	  * @default "SCALE_NUM"
	  */ 
		controller.registerProperty("GraphicsMode", element.setGraphicsMode);
	 /*** 
	  * Whether the panel should respond to user interaction.
	  * This is set to "false" by default, since 'listening' to interaction slows down the rendering.  
	  * @property Enabled
	  * @type boolean 
	  * @default "false"
	  */ 
		controller.registerProperty("Enabled", element.getPanelInteraction().setEnabled);
	 /*** 
	  * Whether to enable the move event listener.
	  * This is set to "false" by default and is ignored when Enabled is set to "false".  
	  * @property StopMoveEvents
	  * @type boolean 
	  * @default "false"
	  */ 
		controller.registerProperty("StopMoveEvents", element.getPanelInteraction().setStopMoveEvents);
	 /*** 
	  * Whether to propagate events and gestures to the containing HTML element.
	  * This may be useful, for instance, to prevent ePub readers from reacting to what should be just an interaction with the panel. 
	  * @property StopEventPropagation
	  * @type boolean 
	  * @default "true"
	  */ 
		controller.registerProperty("StopEventPropagation", element.getPanelInteraction().setStopGestures);

	 /*** 
	  * The dimensions of a strip around the main panel drawing area
	  * @property Gutters
	  * @type int[4] representing [left,top,right,bottom], the size of the strip (in pixels) in that part of the panel  
	  * @default "[0,0,0,0] for DrawingPanels, [50,50,50,50] for PlottingPanels"
	  */ 
		controller.registerProperty("Gutters", element.setGutters);
	 /*** 
	  * The stroke color for the outer border of the panel (exterior border of the gutter).
	  * See <a href="http://www.w3schools.com/cssref/css_colornames.asp">this link</a> for possible values.
	  * @property GuttersLineColor 
	  * @type string Any valid CSS color name. "none" for no color
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "Black"
	  */                          
		controller.registerProperty("GuttersLineColor", element.getGuttersStyle().setLineColor);
	 /*** 
	  * The stroke width for the outer border of the panel (exterior border of the gutter).
	  * @property GuttersLineWidth 
	  * @type int width in pixels
	  * @default "1"
	  */                          
		controller.registerProperty("GuttersLineWidth", element.getGuttersStyle().setLineWidth);
	 /*** 
	  * Whether to draw the outer border of the panel (exterior border of the gutter).
	  * @property GuttersDrawLines 
	  * @type boolean
	  * @default "true"
	  */                          
		controller.registerProperty("GuttersDrawLines", element.getGuttersStyle().setDrawLines);
	 /*** 
	  * The fill color for the gutter.
	  * See <a href="http://www.w3schools.com/cssref/css_colornames.asp">this link</a> for possible values.
	  * @property GuttersColor 
	  * @type string Any valid CSS color name. "none" for no color
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "rgb(239,239,255) for DrawingPanels, rgb(211,216,255) for PlottingPanels"
	  */                          
		controller.registerProperty("GuttersColor",     element.getGuttersStyle().setFillColor);
	 /*** 
	  * Whether to fill the gutter.
	  * @property GuttersFill 
	  * @type boolean
	  * @default "true"
	  */                          
		controller.registerProperty("GuttersFill",      element.getGuttersStyle().setDrawFill);
	 /*** 
	  * SVG shape rendering for the gutters
	  * @property GuttersRendering 
	  * @type string
	  * @values "auto","optimizeSpeed","crispEdges","geometricPrecision"  
	  * @default "auto"
	  */                          
		controller.registerProperty("GuttersRendering", element.getStyle().setShapeRendering);

	 /*** 
	  * The fill color for the main drawing area of the panel
	  * See <a href="http://www.w3schools.com/cssref/css_colornames.asp">this link</a> for possible values.
	  * @property Background 
	  * @type string Any valid CSS color name. "none" for no color
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "rgb(239,239,255) for DrawingPanels, White for PlottingPanels"
	  */                          
		controller.registerProperty("Background", element.getStyle().setFillColor);
	 /*** 
	  * The stroke color for the border of the main drawing area of the panel (inner border of the gutters)
	  * See <a href="http://www.w3schools.com/cssref/css_colornames.asp">this link</a> for possible values.
	  * @property Foreground 
	  * @type string Any valid CSS color name. "none" for no color
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "Black"
	  */                          
		controller.registerProperty("Foreground", element.getStyle().setLineColor);
	 /*** 
	  * The stroke color for the border of the main drawing area of the panel (inner border of the gutters).
	  * Same as "Foreground".
	  * See <a href="http://www.w3schools.com/cssref/css_colornames.asp">this link</a> for possible values.
	  * @property LineColor 
	  * @type string Any valid CSS color name. "none" for no color
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "Black"
	  */                          
		controller.registerProperty("LineColor", element.getStyle().setLineColor);
	 /***
	  * The stroke width for the border of the main drawing area of the panel (inner border of the gutters).
	  * @property LineWidth 
	  * @type int width in pixels
	  * @default "1"
	  */                          
		controller.registerProperty("LineWidth", element.getStyle().setLineWidth);
	 /*** 
	  * Whether to draw the inner border of the panel (inside border of the gutter).
	  * @property DrawLines 
	  * @type boolean
	  * @default "true"
	  */                          
		controller.registerProperty("DrawLines", element.getStyle().setDrawLines);
	 /*** 
	  * The fill color for the main drawing area of the panel.
	  * Same as "Background"
	  * See <a href="http://www.w3schools.com/cssref/css_colornames.asp">this link</a> for possible values.
	  * @property FillColor 
	  * @type string Any valid CSS color name. "none" for no color
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "rgb(239,239,255) for DrawingPanels, White for PlottingPanels"
	  */                          
		controller.registerProperty("FillColor", element.getStyle().setFillColor);
	 /*** 
	  * Whether to fill the main drawing area of the panel.
	  * @property DrawFill 
	  * @type boolean
	  * @default "true"
	  */                          
		controller.registerProperty("DrawFill", element.getStyle().setDrawFill);
	 /*** 
	  * SVG shape rendering for the gutters
	  * @property GuttersRendering 
	  * @type string
	  * @values "auto","optimizeSpeed","crispEdges","geometricPrecision"  
	  * @default "auto"
	  */                          
		controller.registerProperty("ShapeRendering", element.getStyle().setShapeRendering);

	 /*** 
	  * Whether the element is visible
	  * @property Visibility 
	  * @type boolean
	  * @default true
	  */                          
	    controller.registerProperty("Visibility", element.getGraphics().getStyle().setVisibility);      
	 /*** 
	  * The CSS display property of the element
	  * See <a href="http://www.w3schools.com/cssref/pr_class_display.asp">this link</a> for possible values.
	  * @property Display 
	  * @type string
	  * @default "inline"
	  */                          
        controller.registerProperty("Display", element.getGraphics().getStyle().setDisplay); 
	 /*** 
	  * An object with one or more CSS properties.
	  * Example { "background-color" : "red", "float" : "right" }
	  * @property CSS 
	  * @type Object 
	  * @default "inline"
	  */                          
		controller.registerProperty("CSS", element.getGraphics().getStyle().setCSS);

		controller.registerProperty("TRMessage", element.getMessageDecoration("TR").setText);
		controller.registerProperty("TRMessageFont", element.getMessageDecoration("TR").getFont().setFont);
		controller.registerProperty("TRMessageColor", element.getMessageDecoration("TR").getFont().setFillColor);
		controller.registerProperty("TRMessageFillColor", element.getMessageDecoration("TR").getStyle().setFillColor);
		controller.registerProperty("TRMessageLineColor", element.getMessageDecoration("TR").getStyle().setLineColor);		
		
		controller.registerProperty("TLMessage", element.getMessageDecoration("TL").setText);
		controller.registerProperty("TLMessageFont", element.getMessageDecoration("TL").getFont().setFont);
		controller.registerProperty("TLMessageColor", element.getMessageDecoration("TL").getFont().setFillColor);
		controller.registerProperty("TLMessageFillColor", element.getMessageDecoration("TL").getStyle().setFillColor);
		controller.registerProperty("TLMessageLineColor", element.getMessageDecoration("TL").getStyle().setLineColor);		

		controller.registerProperty("BRMessage", element.getMessageDecoration("BR").setText);
		controller.registerProperty("BRMessageFont", element.getMessageDecoration("BR").getFont().setFont);
		controller.registerProperty("BRMessageColor", element.getMessageDecoration("BR").getFont().setFillColor);
		controller.registerProperty("BRMessageFillColor", element.getMessageDecoration("BR").getStyle().setFillColor);
		controller.registerProperty("BRMessageLineColor", element.getMessageDecoration("BR").getStyle().setLineColor);		

		controller.registerProperty("BLMessage", element.getMessageDecoration("BL").setText);
		controller.registerProperty("BLMessageFont", element.getMessageDecoration("BL").getFont().setFont);
		controller.registerProperty("BLMessageColor", element.getMessageDecoration("BL").getFont().setFillColor);
		controller.registerProperty("BLMessageFillColor", element.getMessageDecoration("BL").getStyle().setFillColor);
		controller.registerProperty("BLMessageLineColor", element.getMessageDecoration("BL").getStyle().setLineColor);		

		controller.registerProperty("CoordinatesFormat", element.getCoordinates().setFormat);
		controller.registerProperty("ShowCoordinates", element.setShowCoordinates);
		controller.registerProperty("ShowAreaRectangle", element.setShowAreaRectangle);
		controller.registerProperty("EnabledZooming", element.setEnabledZooming);
		controller.registerProperty("EnabledDragging", element.setEnabledDragging);

	 /*** 
	  * The type of cursor when an element is moved.
	  * See <a href="http://www.w3schools.com/cssref/pr_class_cursor.asp">this link</a> for possible values.
	  * @property CursorTypeForMove 
	  * @type String
	  * @default "move"
	  */ 
		controller.registerProperty("CursorTypeForMove", element.getInteraction().setCursorTypeForMove);

        controller.registerAction("OnDoubleClick", element.getInteraction().getInteractionPoint);      
		controller.registerAction("OnMove", element.getInteraction().getInteractionPoint, element.getOnMoveHandler);
		controller.registerAction("OnPress", element.getInteraction().getInteractionPoint, element.getOnPressHandler);
		controller.registerAction("OnDrag", element.getInteraction().getInteractionPoint, element.getOnDragHandler);
		controller.registerAction("OnRelease", element.getInteraction().getInteractionBounds, element.getOnReleaseHandler);
		controller.registerAction("OnResize", element.getInteraction().getInteractionBounds, element.scale);
		controller.registerAction("OnOrientationChange", element.getInteraction().getOrientation, element.scale);
		controller.registerAction("OnZoom", element.getInteraction().getPinchDistance, element.getOnZoomHandler);
	}
};

/**
 * Constructor for DrawingPanel
 * @method drawingPanel
 * @param mName string
 * @returns An abstract 2D drawing panel
 */
EJSS_DRAWING2D.drawingPanel = function(mName,mGraphicsMode) {
	var self = {}; // reference returned	

	// Graphics implementation
	var mGraphics;

	// Instance variables
	var mStyle = EJSS_DRAWING2D.style(mName);	// style for panel
	var mBottomDecorations = [];				// decorations list for panel
	var mElements = [];							// elements list for panel
	var mElementsChanged = false;				// whether elements list has changed
	var mAutoScaleX = false;					// whether auto-scale with measure elements in X
	var mAutoScaleY = false;					// whether auto-scale with measure elements in Y
	var mInvertedScaleY = false;				// whether inverted scale in Y
	var mTypeScaleX = 0;						// type of scale in X
	var mTypeScaleY = 0;						// type of scale in Y
	var mTopDecorations = [];					// top decorations list for panel
    var mShowCoordinates = true;				// whether coordinates in panel
    var mShowAreaRectangle = true;				// whether area rectangle in panel
	var mEnabledDragging = false;				// whether dragging panel
	var mEnabledZooming = false;				// whether scaling panel
	var mZoomRate = 1.10;						// scaling rate
	var mZoomLimits = [0.1,1000];				// zooming limits (width min and max)
	var mCollectersList = [];		            // Array of all control elements that need a call to dataCollected() after data collection

	// Configuration variables	
	var xmindef = -1, xmaxdef = +1, ymindef = -1, ymaxdef = +1;  // default dimensions
	var mWorld = {
		// preferred dimensions
		xminPreferred : NaN, xmaxPreferred : NaN, yminPreferred : NaN, ymaxPreferred : NaN,
		// measured dimensions
		xminMeasured : NaN, xmaxMeasured : NaN, yminMeasured : NaN, ymaxMeasured : NaN,
		// margin
		xmargin : 0, ymargin : 0,
		// real dimensions after scalation, based on preferred dimensions and added margin
		xmin : -1, xmax : +1, ymin : -1, ymax : +1,
		// keep square aspect
		squareAspect : false,
		// origin in panel
		xorigin : 0, yorigin : 0,
		// pixel per unit for panel
		xscale : 1, yscale : 1		
	};

	// Gutters for panel
	var mGutters = {
		left : 0, right : 0, top : 0, bottom : 0, // sizes
		visible : false, // whether the gutters are visible
	};
	var mGuttersStyle = EJSS_DRAWING2D.style(mName + " gutters")	// style

	// Implementation variables
	var mPanelChanged = true;	// whether panel changed (style, decorations, gutters)
	var mMustScale = true;		// whether panel must scale
	var mEnabledRedering = true;// whether redering is enabled
	
	var mController = {// dummy controller object
		propertiesChanged : function() { },
		invokeAction : function() { }
	};

	// ----------------------------------------
	// Instance functions
	// ----------------------------------------

	/***
	 * Get name for drawing panel
	 * @method getName
	 * @return string
	 */
	self.getName = function() {
		return mName;
	};

	/***
	 * Returns the graphics implementation
	 * @method getGraphics
	 * @return Graphics
	 */
	self.getGraphics = function() {
		return mGraphics;
	};

	/***
	 * Returns the svg image in Base64 format
	 * @method importGraphics
	 * @param callback
	 * @return string 
	 */
    self.importGraphics = function(callback) {
    	if(mGraphics.importSVG)
    		return mGraphics.importSVG(callback);
    	return null;
    }
    
	/***
	 * Return the drawing style of the inner rectangle for panel
	 * @method getStyle
	 * @return Style
	 */
	self.getStyle = function() {
		return mStyle;
	};


	/***
	 * Get the graphics mode
	 * @method getGraphicsMode
	 * @return One of the possible graphics implementation mode: EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_SVG or EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_CANVAS
	 */
	self.getGraphicsMode = function() {
	  return mGraphicsMode;
	};
	
	/***
	 * Get the graphics mode
	 * @method getGraphicsModeName
	 * @return The implementation mode as a string: either "SVG" or "CANVAS"
	 */
	self.getGraphicsModeName = function() {
	  switch (mGraphicsMode) {
	    case EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_CANVAS : return "CANVAS";
	    default:
	    case EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_SVG : return "SVG";
	  }
	};


	/***
	 * Set graphics
	 * @method setGraphicsMode
	 * @param mode One of the possible graphics implementation mode: 
	 *   EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_SVG or EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_CANVAS. Or simply "CANVAS" or "SVG"
	 */
	self.setGraphicsMode = function(mode) {
		// get mode
    	if (typeof mode === 'string') {
    	    if (mode.indexOf("GRAPHICS2D_")!=0) mode = "GRAPHICS2D_" + mode;
      		mode = EJSS_DRAWING2D.DrawingPanel[mode.toUpperCase()] | EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_SVG;
      	} else if(typeof mode === 'undefined') {
			mode = EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_SVG;      		
      	}
    	
      	if (mode == mGraphicsMode) return;
      	mGraphicsMode = mode; 
		var exists = typeof mGraphics !== 'undefined';
		
		if(exists) {
			// get params
			var parent = mGraphics.getParent();
			var width = mGraphics.getWidth();
			var height = mGraphics.getHeight();
			var style = mGraphics.getEventContext().getAttribute("style");
			
			// remove current graphics
			var ele = document.getElementById(mName);
	   		ele.parentNode.removeChild(ele);			
		}
		
		// create new graphics
		if (mode == EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_SVG) {
			mGraphics = EJSS_GRAPHICS.svgGraphics(mName);
		} else if (mode == EJSS_DRAWING2D.DrawingPanel.GRAPHICS2D_CANVAS) {
			mGraphics = EJSS_GRAPHICS.canvasGraphics(mName);
		} else {
			console.log("WARNING: setGraphics() - Graphics not supported");
		}
		
		if(exists) {
			// set params
			mGraphics.setParent(parent);
			mGraphics.setWidth(width);	
			mGraphics.setHeight(height);	
			mGraphics.getEventContext().setAttribute("style",style);	
			mElementsChanged = true;
			
			// interactions
			mInteraction.reload();			
		}
	};
	
	/***
	 * Get auto-scale
	 * @method getAutoScale
	 * @return boolean
	 */
	self.getAutoScale = function() {
		return (mAutoScaleX || mAutoScaleY);
	};

	/***
	 * Set auto-scale
	 * @method setAutoScale
	 * @param auto
	 */
	self.setAutoScale = function(autoscale) {		
		self.setAutoScaleX(autoscale);
		self.setAutoScaleY(autoscale);
	};

	/***
	 * Set auto-scale in X
	 * @method setAutoScaleX
	 * @param auto
	 */
	self.setAutoScaleX = function(autoscale) {
		mAutoScaleX = autoscale;
	};

	/***
	 * Get auto-scale in X
	 * @method getAutoScaleX
	 * @return boolean
	 */
	self.getAutoScaleX = function() {
		return mAutoScaleX;
	};

	/***
	 * Set auto-scale in Y
	 * @method setAutoScaleY
	 * @param auto
	 */
	self.setAutoScaleY = function(autoscale) {
		mAutoScaleY = autoscale;
	};

	/***
	 * Get auto-scale in Y
	 * @method getAutoScaleY
	 * @return boolean
	 */
	self.getAutoScaleY = function() {
		return mAutoScaleY;
	};

	/***
	 * Set inverted scale in Y
	 * @method setInvertedScaleY
	 * @param inverted
	 */
	self.setInvertedScaleY = function(invertedscale) {
		if(mInvertedScaleY != invertedscale) {
			mInvertedScaleY = invertedscale;
			
			// InvertedScaleY ONLY supports mTypeScaleY NUM
			if(mInvertedScaleY) mTypeScaleY = EJSS_DRAWING2D.DrawingPanel.SCALE_NUM;
			
			// report decorations
			var changed = self.reportDecorations("bounds");
			// decorations may request for a repaint
			if (changed) mPanelChanged = true;			
		}
	};

	/***
	 * Get inverted scale in Y
	 * @method getInvertedScaleY
	 * @return boolean
	 */
	self.getInvertedScaleY = function() {
		return mInvertedScaleY;
	};

	/***
	 * Set type of scale in X
	 * @method setTypeScaleX
	 * @param type
	 */
	self.setTypeScaleX = function(typescale) {
    	if (typeof typescale == "string") typescale = EJSS_DRAWING2D.DrawingPanel[typescale.toUpperCase()];
		mTypeScaleX = typescale;
	};

	/***
	 * Get type of scale in X
	 * @method getTypeScaleX
	 * @return int
	 */
	self.getTypeScaleX = function() {
		return mTypeScaleX;
	};

	/***
	 * Set type of scale in Y
	 * @method setTypeScaleY
	 * @param type
	 */
	self.setTypeScaleY = function(typescale) {
		if (typeof typescale == "string") typescale = EJSS_DRAWING2D.DrawingPanel[typescale.toUpperCase()];
		mTypeScaleY = typescale;
		
		// InvertedScaleY ONLY supports mTypeScaleY NUM
		if (mTypeScaleY == EJSS_DRAWING2D.DrawingPanel.SCALE_LOG) mInvertedScaleY = false;	
	};

	/***
	 * Get type of scale in Y
	 * @method getTypeScaleY
	 * @return int
	 */
	self.getTypeScaleY = function() {
		return mTypeScaleY;
	};

	// ----------------------------------------
	// World coordinates
	// ----------------------------------------

	/***
	 * Sets the preferred minimum X coordinate for the panel
	 * @method setWorldXMin
	 * @param xmin
	 */
	self.setWorldXMin = function(xmin) {
		if (xmin !== mWorld.xminPreferred) {
			mWorld.xminPreferred = xmin;
		}
	};

	/***
	 * Returns the preferred minimum X coordinate for the panel
	 * @method getWorldXMin
	 * @return double
	 */
	self.getWorldXMin = function() {
		return mWorld.xminPreferred;
	};

	/***
	 * Sets the preferred maximum X coordinate for the panel
	 * @method setWorldXMax
	 * @param xmax
	 */
	self.setWorldXMax = function(xmax) {
		if (xmax !== mWorld.xmaxPreferred) {
			mWorld.xmaxPreferred = xmax;
		}
	};

	/***
	 * Returns the preferred maximum X coordinate for the panel
	 * @method getWorldXMax
	 * @return double
	 */
	self.getWorldXMax = function() {
		return mWorld.xmaxPreferred;
	};

	/***
	 * Sets the preferred minimum Y coordinate for the panel
	 * @method setWorldYMin
	 * @param ymin
	 */
	self.setWorldYMin = function(ymin) {
		if (ymin !== mWorld.yminPreferred) {
			mWorld.yminPreferred = ymin;
		}
	};

	/***
	 * Returns the preferred minimum Y coordinate for the panel
	 * @method getWorldYMin
	 * @return double
	 */
	self.getWorldYMin = function() {
		return mWorld.yminPreferred;
	};

	/***
	 * Sets the preferred maximum Y coordinate for the panel
	 * @method setWorldYMax
	 * @param ymax
	 */
	self.setWorldYMax = function(ymax) {
		if (ymax !== mWorld.ymaxPreferred) {
			mWorld.ymaxPreferred = ymax;
		}
	};

	/***
	 * Returns the preferred maximum Y coordinate for the panel
	 * @method getWorldYMax
	 * @return double
	 */
	self.getWorldYMax = function() {
		return mWorld.ymaxPreferred;
	};

	/***
	 * Sets the preferred user coordinates for the panel
	 * @method setWorldCoordinates
	 * @param bounds
	 */
	self.setWorldCoordinates = function(bounds) {
		self.setWorldXMin(bounds[0]);
		self.setWorldXMax(bounds[1]);
		self.setWorldYMin(bounds[2]);
		self.setWorldYMax(bounds[3]);
	};

	/***
	 * Gets the preferred user coordinates for the panel
	 * @method getWorldCoordinates
	 * @return bounds
	 */
	self.getWorldCoordinates = function() {
		return [self.getWorldXMin(), self.getWorldXMax(), self.getWorldYMin(), self.getWorldYMax()];
	};

	/***
	 * Gets the measured coordinates for the panel
	 * @method getMeasuredCoordinates
	 * @return bounds
	 */
	self.getMeasuredCoordinates = function() {
		return [mWorld.xminMeasured, mWorld.xmaxMeasured, mWorld.yminMeasured, mWorld.ymaxMeasured];
	};

	/***
	 * Gets the user coordinates for the panel
	 * @method getRealWorldCoordinates
	 * @return bounds
	 */
	self.getRealWorldCoordinates = function() {
		return [mWorld.xmin, mWorld.xmax, mWorld.ymin, mWorld.ymax];
	};

	/***
	 * Returns the minimum X coordinate for the panel
	 * @method getRealWorldXMin
	 * @return double
	 */
	self.getRealWorldXMin = function() {
		return mWorld.xmin;
	};

	/***
	 * Returns the maximum X coordinate for the panel
	 * @method getRealWorldXMax
	 * @return double
	 */
	self.getRealWorldXMax = function() {
		return mWorld.xmax;
	};

	/***
	 * Returns the minimum Y coordinate for the panel
	 * @method getRealWorldYMin
	 * @return double
	 */
	self.getRealWorldYMin = function() {
		return mWorld.ymin;
	};

	/***
	 * Returns the maximum Y coordinate for the panel
	 * @method getRealWorldYMax
	 * @return double
	 */
	self.getRealWorldYMax = function() {
		return mWorld.ymax;
	};

	/***
	 * Whether the panel should keep a 1:1 aspect ratio between X and Y coordinates
	 * @method setSquareAspect
	 * @param boolean
	 */
	self.setSquareAspect = function(square) {
		if (square !== mWorld.squareAspect) {
			mWorld.squareAspect = square;
			mMustScale = true;
		}
	};

	/***
	 * Set margin X
	 * @method setMarginX
	 * @param margin
	 */
	self.setMarginX = function(margin) {
		if(mWorld.xmargin != margin) {
			mWorld.xmargin = margin;
			mMustScale = true;			
		}		
	};
	
	/***
	 * Get margin X
	 * @method getMarginX
	 * @return margin
	 */	
	self.getMarginX = function() {
		return mWorld.xmargin;
	}

	/***
	 * Set margin Y
	 * @method setMarginY
	 * @param margin
	 */
	self.setMarginY = function(margin) {
		if(mWorld.ymargin != margin) {
			mWorld.ymargin = margin;
			mMustScale = true;			
		}		
	};
	
	/***
	 * Get margin Y
	 * @method getMarginY
	 * @return margin
	 */	
	self.getMarginY = function() {
		return mWorld.ymargin;
	}

	// ----------------------------------------
	// Decorations and elements
	// ----------------------------------------

	/***
	 * Allow to drag panel
	 * @method setEnabledDragging
	 * @parem motion ENABLED_NONE: 0, ENABLED_ANY: 1, ENABLED_X: 2, ENABLED_Y: 3, ENABLED_NO_MOVE: 4
	 */
	self.setEnabledDragging = function(motion) {
		if ( typeof motion == "string") {
			value = EJSS_DRAWING2D.DrawingPanel[motion.toUpperCase()];
			mEnabledDragging = (typeof value === 'undefined')?EJSS_DRAWING2D.DrawingPanel.ENABLED_NONE:value;
		}
	    else mEnabledDragging = motion;
	}

	/***
	 * Type of motion allowed to drag panel
	 * @method getEnabledDragging
	 * @return motion
	 */
	self.getEnabledDragging = function() {
		return mEnabledDragging;
	}

	/***
	 * Allow to scale panel
	 * @method setEnabledZooming
	 * @param allowed
	 */
	self.setEnabledZooming = function(allowed) {
		mEnabledZooming = allowed;
	}

	/***
	 * Whether panal scaling is allowed
	 * @method getEnabledZooming
	 * @return boolean
	 */
	self.getEnabledZooming = function() {
		return mEnabledZooming;
	}

	/***
	 * Set zoom rate
	 * @method setZoomRate
	 * @param rate
	 */
	self.setZoomRate = function(rate) {
		mZoomRate = rate;
	}

	/***
	 * Get zoom rate
	 * @method getZoomRate
	 * @return rate
	 */
	self.getZoomRate = function() {
		return mZoomRate;
	}

	/***
	 * Set zoom limits
	 * @method setZoomLimits
	 * @param limits [min,max]
	 */
	self.setZoomLimits = function(limits) {
		mZoomLimits = limits;
	}

	/***
	 * Get zoom limits
	 * @method getZoomLimits
	 * @return limits [min,max]
	 */
	self.getZoomLimits = function() {
		return mZoomLimits;
	}
	
	/***
	 * Show coordinates in panel
	 * @method setShowCoordinates
	 * @param boolean
	 */
	self.setShowCoordinates = function(show) {
		mShowCoordinates = show;
	}

	/***
	 * Whether coordinates in panel are showed
	 * @method getShowCoordinates
	 * @return boolean
	 */
	self.getShowCoordinates = function() {
		return mShowCoordinates;
	}

	/***
	 * Show area rectangle in panel
	 * @method setShowAreaRectangle
	 * @param boolean
	 */
	self.setShowAreaRectangle = function(show) {
		mShowAreaRectangle = show;
	}

	/***
	 * Show area rectangle in panel
	 * @method getShowAreaRectangle
	 * @param boolean
	 */
	self.getShowAreaRectangle = function() {
		return mShowAreaRectangle;
	}

	/***
	 * Adds a decoration to the panel. Decorations are drawn before any other elements.
	 * @method addDecoration
	 * @param drawable decoration element
	 * @param position integer
	 * @param istop top decoration
	 */
	self.addDecoration = function(drawable, position, istop) {
		if (istop)
			EJSS_TOOLS.addToArray(mTopDecorations, drawable, position);
		else
			EJSS_TOOLS.addToArray(mBottomDecorations, drawable, position);
		if (drawable.setPanel)// set this panel to decoration element
			drawable.setPanel(self);
		return self;
	};

	/***
	 * Removes a decoration
	 * @method removeDecoration
	 * @param drawable decoration element
	 */
	self.removeDecoration = function(drawable) {
		EJSS_TOOLS.removeFromArray(mBottomDecorations, drawable);
		EJSS_TOOLS.removeFromArray(mTopDecorations, drawable);
		if (drawable.setPanel)// remove this panel to decoration element
			drawable.setPanel(null);
		return self;
	};

	/***
	 * Add a element to the panel. Elements are asked to draw themselves
	 * whenever the panel needs to render. For this purpose, they will receive a
	 * calls to draw().
	 * Elements are reported of changes in the world coordinates of the panel, in case
	 * they need to recalculate themselves.
	 * @method addElement
	 * @param element Element
	 * @param position int
	 */
	self.addElement = function(element, sibling) {
		if(typeof sibling === 'undefined') {
			EJSS_TOOLS.addToArray(mElements, element);			
		} else if (typeof sibling === 'object') { // sibling object
			var index = EJSS_TOOLS.arrayObjectIndexOf(mElements,sibling);
			EJSS_TOOLS.addToArray(mElements, element, index);
		} else { // position
			EJSS_TOOLS.addToArray(mElements, element, sibling);
		}
		// set this panel to decoration element
		element.setPanel(self);
		if (element.dataCollected) mCollectersList.push(element);
		// elements list has changed
		mElementsChanged = true;
	};

	/***
	 * Remove a element to the panel.
	 * @method removeElement
	 * @param element Element
	 */
	self.removeElement = function(element) {
		EJSS_TOOLS.removeFromArray(mElements, element);
		mInteraction.clearInteractionElement(element);
		element.setPanel(null);
		if (element.dataCollected) EJSS_TOOLS.removeFromArray(mCollectersList, element);
		// elements list has changed
		mElementsChanged = true;
	};

	/***
	 * Return the array of a elements.
	 * @method getElements
	 * @return Elements
	 */
	self.getElements = function() {
		return mElements;
	};

	/***
	 * Return the position of a element.
	 * @method indexOfElement
	 * @param element Element
	 * @return integer
	 */
	self.indexOfElement = function(element) {
		return mElements.indexOf(element);
	};

	// ----------------------------------------
	// Gutters (empty or decorated area around the drawing area)
	// ----------------------------------------

	/***
	 * Sets the gutters dimensions
	 * @method setGutters
	 * @param rect the number of pixels for the drawing area
	 */
	self.setGutters = function(rect) {
		var left = rect[0], top = rect[1], right = rect[2], bottom = rect[3];

		var changed = false;
		if (left !== mGutters.left) {
			mGutters.left = left;
			changed = true;
		}
		if (top !== mGutters.top) {
			mGutters.top = top;
			changed = true;
		}
		if (right !== mGutters.right) {
			mGutters.right = right;
			changed = true;
		}
		if (bottom !== mGutters.bottom) {
			mGutters.bottom = bottom;
			changed = true;
		}
		if (changed) {
			mGutters.visible = (mGutters.left > 0 || mGutters.top > 0 || mGutters.right > 0 || mGutters.bottom > 0);
			mPanelChanged = true;
			mMustScale = true;
		}		
	};

	/***
	 * Return the bounding gutters
	 * @method getGutters
	 * @return Gutters
	 */
	self.getGutters = function() {
		return mGutters;
	};

	/***
	 * Return the drawing style of the bounding gutters
	 * @method getGuttersStyle
	 * @return Style
	 */
	self.getGuttersStyle = function() {
		return mGuttersStyle;
	};

	// ----------------------------------------
	// Apply transformations and conversions
	// ----------------------------------------

	/***
	 * Converts a Y pixel value so that 0 is at the bottom
	 * @method toPixelAxisY
	 * @param y double
	 */
	self.toPixelAxisY = function(y) {
		return (mWorld.yorigin - y) - (mWorld.yscale * mWorld.ymin);;
	};

	/***
	 * Converts a X pixel value so that 0 is at the left
	 * @method toPixelAxisX
	 * @param x double
	 */
	self.toPixelAxisX = function(x) {
		return (mWorld.xorigin + x) - (mWorld.xscale * mWorld.xmin);
	};

	/***
	 * To be used only after a call to render()!
	 * Projects a point from world coordinates to pixel coordinates.
	 * @method toPixelPosition
	 * @param point double[] The original coordinates
	 * @param scale double[] The type of scale (LOG or NUM)
	 * @return double[] An array with the result
	 */
	self.toPixelPosition = function(point,scale) {
		var scaleX = (typeof scale != "undefined")? scale : mTypeScaleX; // if scale is undefined, it uses the panel scale
		var scaleY = (typeof scale != "undefined")? scale : mTypeScaleY; // if scale is undefined, it uses the panel scale
		var pos = [];
		if(scaleX == EJSS_DRAWING2D.DrawingPanel.SCALE_LOG) // LOG
	    	pos[0] = self.toPixelLogScale(point[0], mWorld.xorigin, mWorld.xmin, mWorld.xmax, mWorld.xscale); 						    		   		
		else // NUM
			pos[0] = mWorld.xorigin + mWorld.xscale * (point[0] - mWorld.xmin);		
		
		if(scaleY == EJSS_DRAWING2D.DrawingPanel.SCALE_LOG) // LOG
			pos[1] = self.toPixelLogScale(point[1], mWorld.yorigin, mWorld.ymin, mWorld.ymax, mWorld.yscale);		
		else { // NUM
			if(!mInvertedScaleY)
				pos[1] = mWorld.yorigin + mWorld.yscale * (point[1] - mWorld.ymin);
			else
				pos[1] = mGutters.top + mWorld.yscale * (mWorld.ymin - point[1]);			
		}
			
		return pos;
	};

	/***
	 * To be used only after a call to render()!
	 * Projects a module from world coordinates to pixel coordinates
	 * @method toPixelMod
	 * @param point double[] The original module
	 * @return double[] The same array once transformed
	 */
	self.toPixelMod = function(mod) {
		var pmod = [];
		pmod[0] = mod[0] * mWorld.xscale;
		pmod[1] = mod[1] * mWorld.yscale;		
		return pmod;
	};

	/***
	 * To be used only after a call to render()!
	 * Projects a point from pixel coordinates to world coordinates
	 * @method toPanelPosition
	 * @param point double[] The original coordinates
	 * @param scale double[] The type of scale (LOG or NUM)
	 * @return double[] The same array once transformed
	 */
	self.toPanelPosition = function(point,scale) {	
		var scaleX = (typeof scale != "undefined")? scale : mTypeScaleX; // if scale is undefined, it uses the panel scale
		var scaleY = (typeof scale != "undefined")? scale : mTypeScaleY; // if scale is undefined, it uses the panel scale
		var pos = [];
		if(scaleX == EJSS_DRAWING2D.DrawingPanel.SCALE_LOG)  // LOG 
	    	pos[0] = self.toPanelLogScale(point[0], mWorld.xorigin, mWorld.xmin, mWorld.xmax, mWorld.xscale); 						
		 else  // NUM 
			pos[0] = mWorld.xmin + (point[0] - mWorld.xorigin) / mWorld.xscale;
		
		if(scaleY == EJSS_DRAWING2D.DrawingPanel.SCALE_LOG)  // LOG
			pos[1] = self.toPanelLogScale(point[1], mWorld.yorigin, mWorld.ymin, mWorld.ymax, mWorld.yscale);
		else { // NUM
			if(!mInvertedScaleY)
				pos[1] = mWorld.ymin + (point[1] - mWorld.yorigin) / mWorld.yscale;
			else
				pos[1] = mWorld.ymin - (point[1] - mGutters.top) / mWorld.yscale;			
		}
		return pos;
	};

	/***
	 * To be used only after a call to render()!
	 * Projects a module from pixel coordinates to world coordinates
	 * @method toPanelMod
	 * @param point double[] The original module
	 * @return double[] The same array once transformed
	 */
	self.toPanelMod = function(mod) {
		var pmod = [];
		pmod[0] = ((mWorld.xscale == 0)? 0 : mod[0]/mWorld.xscale);
		pmod[1] = ((mWorld.yscale == 0)? 0 : mod[1]/mWorld.yscale);		
		return pmod;
	};

	/***
	 * Projects a value in world coordinates from a decimal scale 
	 * (defined by origin, min, max and pixratio) to a log scale in pixel coordinates
	 * @method toPixelLogScale
	 * @param value double The original value
	 * @param origin double The origin in panel (pixels)
	 * @param min double The min value in world coordinates
	 * @param max double The max value in world coordinates
	 * @param pixratio double The pixels per unit for panel
	 * @return double The value transformed
	 */
	self.toPixelLogScale = function(value, origin, min, max, pixratio) {
    	var minscale = (min <= 0)? 1 : Math.log(min)/Math.log(10);		// min log value
    	var maxscale = (max <= 0)? 1 : Math.log(max)/Math.log(10);		// max log value
		var segsize = (max - min) * pixratio;
    	var scalesize = maxscale - minscale;			// size of log scale
    	var sizeratio = segsize / scalesize;			// pixel per log value
			
   		var step = value <= 0? 0 : Math.log(value) / Math.log(10);
   		return origin + sizeratio * (step - minscale);   		
	}

	/***
	 * Projects a value in pixel coordinates from a decimal scale 
	 * (defined by origin, min, max and pixratio) to a log scale in world coordinates
	 * @method toPanelLogScale
	 * @param value double The original value
	 * @param origin double The origin in panel (pixels)
	 * @param min double The min value in world coordinates
	 * @param max double The max value in world coordinates
	 * @param pixratio double The pixels per unit for panel
	 * @return double The value transformed
	 */
	self.toPanelLogScale = function(value, origin, min, max, pixratio) {
    	var minscale = (min < 0)? 0 : Math.log(min)/Math.log(10);		// min log value
    	var maxscale = (max < 0)? 0 : Math.log(max)/Math.log(10);		// max log value
		var segsize = (max - min) * pixratio;
    	var scalesize = maxscale - minscale;			// size of log scale
    	var sizeratio = segsize / scalesize;			// pixel per log value
		
		return Math.pow(10, minscale + (value - origin) / sizeratio); 								
	}

	/***
	 * Get pixel position of the origin
	 * @method getPixelPositionWorldOrigin
	 * @return array pixel position
	 */
	self.getPixelPositionWorldOrigin = function() {
		return [mWorld.xorigin,mWorld.yorigin];
	}

	/***
	 * Return drawing box (excluding gutters)
	 * @method getInnerRect
	 * @return box  
	 */
	self.getInnerRect = function() {
	 	var bounds = self.getRealWorldCoordinates();
	    if(!self.getInvertedScaleY())
			var pos = self.toPixelPosition([bounds[0],bounds[3]]);
		else
			var pos = self.toPixelPosition([bounds[0],bounds[2]]);		    
	    var size = self.toPixelMod([bounds[1]-bounds[0],bounds[3]-bounds[2]]);     
	    
	    return {x: pos[0], width: Math.abs(size[0]), y: pos[1], height: Math.abs(size[1])}
	},

	/***
	 * Force scale again
	 * @method scale
	 */
	self.scale = function() {
		mMustScale = true;
		mElementsChanged = true;
	};
	
	/***
	 * Refresh all elements
	 * @method touch
	 */
	self.touch = function() {
		mMustScale = true;
		mElementsChanged = true;
		for (var i = 0, n = mElements.length; i < n; i++)
			mElements[i].setChanged(true);
	};
	
	/***
	 * Recomputes the scales of the panel.
	 * @method recomputeScales
	 */
	self.recomputeScales = function() {	
		var newWorld = false;
		
		// start with the preferred min-max values plus margin.
		var rangeX = (mWorld.xmaxMeasured-mWorld.xminMeasured)/2;
		var rangeY = (mWorld.ymaxMeasured-mWorld.yminMeasured)/2;
		var xmin = mWorld.xminMeasured - Math.abs(rangeX*mWorld.xmargin/100); 
		var xmax = mWorld.xmaxMeasured + Math.abs(rangeX*mWorld.xmargin/100);
		var ymin = mWorld.yminMeasured - Math.abs(rangeY*mWorld.ymargin/100); 
		var ymax = mWorld.ymaxMeasured + Math.abs(rangeY*mWorld.ymargin/100);

		// get sizes and scale
		var box = mGraphics.getBox();
		var width = box.width - (mGutters.left + mGutters.right); // width in pixels
		var height = box.height - (mGutters.bottom + mGutters.top); // height in pixels
		var xPixPerUnit = width / (xmax - xmin); // the x scale in pixels
		var yPixPerUnit = height / (ymax - ymin); // the y scale in pixels
		var stretch, diff;
		if (mWorld.squareAspect) {// keep square aspect
			stretch = Math.abs(xPixPerUnit / yPixPerUnit);
			// relation between x scale and y scale
			if (stretch >= 1) {// make the x range bigger so that aspect ratio is one
				stretch = Math.min(stretch, width);
				// limit the stretch
				diff = (xmax - xmin) * (stretch - 1) / 2.0;
				xmin -= diff;
				xmax += diff;
				xPixPerUnit = width / (xmax - xmin);
				//Math.max(width-leftGutter-rightGutter, 1)/(xmax-xmin);  // the x scale in pixels per unit
			} else {// make the y range bigger so that aspect ratio is one
				stretch = Math.max(stretch, 1.0 / height);
				// limit the stretch
				diff = (ymax - ymin) * (1.0 / stretch - 1) / 2.0;
				ymin -= diff;
				ymax += diff;
				yPixPerUnit = height / (ymax - ymin);
				//Math.max(height-bottomGutter-topGutter, 1)/(ymax-ymin); // the y scale in pixels per unit
			}
		}
		// new world, because new scale
		newWorld = (mWorld.xscale != xPixPerUnit || mWorld.yscale != -yPixPerUnit);
		
		// centered
		mWorld.xscale = xPixPerUnit;
		mWorld.yscale = -yPixPerUnit;
		mWorld.xorigin = mGutters.left + 0.5;
		mWorld.yorigin = height + mGutters.top + 0.5;
		mMustScale = false;
		
		// Check for actual changes
		if ((mWorld.xmin !== xmin) || (mWorld.xmax !== xmax) || (mWorld.ymin !== ymin) || (mWorld.ymax !== ymax)) {
			mWorld.xmin = xmin;
			mWorld.xmax = xmax;
			mWorld.ymin = ymin;
			mWorld.ymax = ymax;
			// report decorations
			var changed = self.reportDecorations("bounds");
			// decorations may request for a repaint
			if (changed)
				mPanelChanged = true;
			// new World
			newWorld = true;
		}
		
		// if new world, then report elements
		if(newWorld) {			
			for (var i = 0, n = mElements.length; i < n; i++)
				mElements[i].setMustProject(true);
		}
	};

	/***
	 * Report event to decoration elements
	 * @method reportDecorations
	 * @param event
	 */
    self.reportDecorations = function(event) {
		var changed = false, i, n;
		// bottom group
		for (i = 0, n = mBottomDecorations.length; i < n; i++) {
			changed |= (mBottomDecorations[i].panelChangeListener && mBottomDecorations[i].panelChangeListener("bounds"));
		}
		// top group
		for (i = 0, n = mTopDecorations.length; i < n; i++) {
			if (mTopDecorations[i].panelChangeListener)
				mTopDecorations[i].panelChangeListener("bounds");
		}
		return changed;    	
    }

	/***
	 * Recalculate world dimensions using measure elements
	 * @method checkMeasure
	 * @return boolean whether dimesions update
	 */
	self.checkMeasure = function() {
		// init measured dimensions 
		var xminMeasured = Number.MAX_VALUE;
		var xmaxMeasured = -Number.MAX_VALUE;
		var yminMeasured = Number.MAX_VALUE;
		var ymaxMeasured = -Number.MAX_VALUE;

		// if autoscale, get measured dimensions		
		if(self.getAutoScaleY() || self.getAutoScaleX()) {							
			for (var i = 0, n = mElements.length; i < n; i++) {
				// considering measured elements (no groups) 
				if (mElements[i].isMeasured() && !mElements[i].isGroup() && 
					(mElements[i].getGroup() == null || mElements[i].getGroup().isMeasured())) {					
					if(mElements[i].isPixelSize()) { // for PixelSize, only the position
						var pos = mElements[i].getPosition();					 
						xminMeasured = Math.min(xminMeasured, pos[0]);
						xmaxMeasured = Math.max(xmaxMeasured, pos[0]);
						yminMeasured = Math.min(yminMeasured, pos[1]);
						ymaxMeasured = Math.max(ymaxMeasured, pos[1]);							
					} else { // get bounds
						var bounds = mElements[i].getAbsoluteBounds();					 
						xminMeasured = Math.min(xminMeasured, bounds.left);
						xmaxMeasured = Math.max(xmaxMeasured, bounds.right);
						yminMeasured = Math.min(yminMeasured, bounds.bottom);
						ymaxMeasured = Math.max(ymaxMeasured, bounds.top);
					}
				}
			}
		}
				
		// update meausured dimensions
		var updatedX = false;
		if (self.getAutoScaleX())  { // auto-scale X
			// measured dimensions bigger than preferred dimensions  
			if(!isNaN(mWorld.xminPreferred)) xminMeasured = Math.min(xminMeasured,mWorld.xminPreferred);
			if(!isNaN(mWorld.xmaxPreferred)) xmaxMeasured = Math.max(xmaxMeasured,mWorld.xmaxPreferred);
			// measured dimensions always positive  
      		if (xmaxMeasured <= xminMeasured) {
				// measuared dimensions not valid values for X
				updatedX = ((mWorld.xminMeasured != xmindef) || (mWorld.xmaxMeasured != xmaxdef));
				mWorld.xminMeasured = xmindef;
				mWorld.xmaxMeasured = xmaxdef;							
      		} else {
				// update measured dimensions
				updatedX = ((mWorld.xminMeasured != xminMeasured) || (mWorld.xmaxMeasured != xmaxMeasured)); 
				mWorld.xminMeasured = xminMeasured;
				mWorld.xmaxMeasured = xmaxMeasured;
			}			
		} else if (!isNaN(mWorld.xminPreferred) && !isNaN(mWorld.xmaxPreferred)) { // preferred X
			// update measured dimensions 
			updatedX = ((mWorld.xminMeasured != mWorld.xminPreferred) || (mWorld.xmaxMeasured != mWorld.xmaxPreferred));
			mWorld.xminMeasured = mWorld.xminPreferred;
			mWorld.xmaxMeasured = mWorld.xmaxPreferred;			
		} else { // no values for X
			updatedX = ((mWorld.xminMeasured != xmindef) || (mWorld.xmaxMeasured != xmaxdef));
			mWorld.xminMeasured = xmindef;
			mWorld.xmaxMeasured = xmaxdef;													
		}
		
		var updatedY = false;
		if (self.getAutoScaleY())  { // auto-scale Y
			// measured dimensions bigger than preferred dimensions  
			if(!isNaN(mWorld.yminPreferred)) yminMeasured = Math.min(yminMeasured,mWorld.yminPreferred);
			if(!isNaN(mWorld.ymaxPreferred)) ymaxMeasured = Math.max(ymaxMeasured,mWorld.ymaxPreferred);
			// measured dimensions always positive  
      		if (ymaxMeasured <= yminMeasured) {
				// measuared dimensions not valid values for Y
				updatedY = ((mWorld.yminMeasured != ymindef) || (mWorld.ymaxMeasured != ymaxdef));
				mWorld.yminMeasured = ymindef;
				mWorld.ymaxMeasured = ymaxdef;							
      		} else {
				// update measured dimensions
				updatedY = ((mWorld.yminMeasured != yminMeasured) || (mWorld.ymaxMeasured != ymaxMeasured)); 
				mWorld.yminMeasured = yminMeasured;
				mWorld.ymaxMeasured = ymaxMeasured;
			}			
		} else if (!isNaN(mWorld.yminPreferred) && !isNaN(mWorld.ymaxPreferred)) { // preferred Y
			// update measured dimensions 
			updatedY = ((mWorld.yminMeasured != mWorld.yminPreferred) || (mWorld.ymaxMeasured != mWorld.ymaxPreferred));
			mWorld.yminMeasured = mWorld.yminPreferred;
			mWorld.ymaxMeasured = mWorld.ymaxPreferred;			
		} else { // no values for Y
			updatedY = ((mWorld.yminMeasured != ymindef) || (mWorld.ymaxMeasured != ymaxdef));
			mWorld.yminMeasured = ymindef;
			mWorld.ymaxMeasured = ymaxdef;													
		}
		
		return (updatedX || updatedY);
	};
	
	// ----------------------------------------
	// Drawing functions
	// ----------------------------------------

	/***
	 * Reset the scene
	 * @method reset
	 */
	self.reset = function() {
		mGraphics.reset();
	};

	/***
	 * Disable rendering
	 * @method disable
	 */
	self.disable = function() {
		mEnabledRedering = false;
	}
	
	/***
	 * Enable rendering
	 * @method enable
	 */
	self.enable = function() {
		mEnabledRedering = true;
	}	
	
	/***
	 * Render the scene
	 * @method render
	 */
	self.render = function() {
		if(mEnabledRedering) {
			var reseted = false;
			if (mGraphics.setImageData || mElementsChanged) { 
				// whether canvas or elements added or removed, reset the scene
				mGraphics.reset();
				reseted = true;
				mElementsChanged = false;
			}
	
	        // check for data collection
			for (var i = 0, n = mCollectersList.length; i < n; i++)
				mCollectersList[i].dataCollected();
				
			// get measured dimensions
			var measuredWorld = self.checkMeasure();
	
			if (mMustScale || measuredWorld) // recompute scales
				self.recomputeScales();
	
			if (mPanelChanged || reseted) {// whether panel changed or reseted
				// draw self
				mGraphics.drawPanel(self);
			}
	
			// draw the bottom decorations
			mGraphics.draw(mBottomDecorations);
	
			// draw visible elements
			mGraphics.draw(mElements, reseted);
	
			if (mPanelChanged || reseted) {// whether panel changed or reseted
				// draw the gutter region
				mGraphics.drawGutters(self);
			}
	
			// draw the top decorations
			mGraphics.draw(mTopDecorations);
	
			// set changed to false
			mPanelChanged = false;
			for (var i = 0, n = mElements.length; i < n; i++)
				mElements[i].setChanged(false);
		}
	};

	// ----------------------------------------
	// Interaction
	// ----------------------------------------

	/***
	 * Return Panel Interaction
	 * @method getPanelInteraction
	 * @param panel interaction
	 */
	self.getPanelInteraction = function() {
		return mInteraction;
	};

	/***
	 * Returns the controller object
	 * @method getController
	 * @return Controller
	 */
	self.getController = function() {
		return mController;
	};

	/***
	 * Set the controller
	 * @method setController
	 * @param Controller
	 */
	self.setController = function(controller) {
		mController = controller;
	};

	// ----------------------------------------------------
	// Properties
	// ----------------------------------------------------

	self.getOnMoveHandler = null;

	self.getOnZoomHandler = function() {
		if (mEnabledZooming) {
			var delta = mInteraction.getInteractionZoomDelta();
			var bounds = self.getWorldCoordinates();
			// if not world coordinates, then get real world coordinates
			if(isNaN(bounds[0]) || isNaN(bounds[1]) || isNaN(bounds[2]) || isNaN(bounds[3]))
				bounds = self.getMeasuredCoordinates();
			var incw = +((1 - mZoomRate) * (bounds[1]-bounds[0]) * delta / 2);
			var inch = +((1 - mZoomRate) * (bounds[3]-bounds[2]) * delta / 2);
			// check limits
			var neww = Math.abs(bounds[0] - bounds[1]) -  2*incw;
			if ( neww > mZoomLimits[0] && neww < mZoomLimits[1]) {				
				// set new world coordinates
				self.setWorldCoordinates([
					bounds[0] + incw, bounds[1] - incw,
					bounds[2] + inch, bounds[3] - inch			 
				]);			
				self.getController().propertiesChanged("MinimumX","MaximumX","MinimumY","MaximumY","Bounds");
			}
		}
	}

	self.getOnPressHandler = function() {
		// info text decoration
		if (mShowCoordinates) {
			var point = mInteraction.getInteractionPoint();
			var bounds = self.getRealWorldCoordinates();
			if ((bounds[0] < point[0]) && (bounds[1] > point[0]) &&
				(bounds[3] > point[1]) && (bounds[2] < point[1])) { // whether event location over panel
				mCoorDecoration.setText(point);
				mCoorDecoration.setVisible(true);
				mCoorDecoration.getController().propertiesChanged("Visible", "Text");	
			}
		}		
	};

	self.getOnDragHandler = function() {
		// info text decoration
		if (mShowCoordinates) {
			var point = mInteraction.getInteractionPoint();
			var bounds = self.getRealWorldCoordinates();
			if ((bounds[0] < point[0]) && (bounds[1] > point[0]) &&
				(bounds[3] > point[1]) && (bounds[2] < point[1])) { // whether event location over panel
				mCoorDecoration.setText(point);
				mCoorDecoration.setVisible(true);
				mCoorDecoration.getController().propertiesChanged("Visible", "Text");			
			}
		}
		
		// update drag decoration
		if (mShowAreaRectangle) {
			var boundsInter = mInteraction.getInteractionBounds();
			if(boundsInter.length > 0 && mTypeScaleX == EJSS_DRAWING2D.DrawingPanel.SCALE_NUM && 
					mTypeScaleY == EJSS_DRAWING2D.DrawingPanel.SCALE_NUM) {
				mDragDecoration.setBounds(boundsInter);
				mDragDecoration.setVisible(true);
				mDragDecoration.getController().propertiesChanged("Position", "X", "Y", "Size", "SizeX", "SizeY", "Visible");
			} else {
				mDragDecoration.setVisible(false);		
				mDragDecoration.getController().propertiesChanged("Visible");			
			}
		}
		
		// drag drawing
		if (mEnabledDragging != 0 &&  mEnabledDragging != 4) {
			var distance = mInteraction.getInteractionDistance();
			if(distance.length > 0 && mTypeScaleX == EJSS_DRAWING2D.DrawingPanel.SCALE_NUM && 
					mTypeScaleY == EJSS_DRAWING2D.DrawingPanel.SCALE_NUM) {
				var bounds = self.getWorldCoordinates();
				// if not world coordinates, then get measured coordinates
				if(isNaN(bounds[0]) || isNaN(bounds[1]) || isNaN(bounds[2]) || isNaN(bounds[3]))
					bounds = self.getMeasuredCoordinates();				
				if (mEnabledDragging == 1) {
					self.setWorldCoordinates([ bounds[0] + distance[0], bounds[1] + distance[0],
						bounds[2] + distance[1], bounds[3] + distance[1]]); 					
				} else if (mEnabledDragging == 2) {
					self.setWorldCoordinates([bounds[0] + distance[0],bounds[1] + distance[0], bounds[2], bounds[3]]); 
				} else {
					self.setWorldCoordinates([bounds[0], bounds[1], bounds[2] + distance[1], bounds[3] + distance[1]]); 
				}
				self.getController().propertiesChanged("MinimumX","MaximumX","MinimumY","MaximumY","Bounds");
			}					
		}
	};

	self.getOnReleaseHandler = function() {
		// info text decoration
		if (mShowCoordinates) {
			mCoorDecoration.setVisible(false);
			mCoorDecoration.getController().propertiesChanged("Visible");
		}
				
		// hidden drag decoration
		if (mShowAreaRectangle) {
			mDragDecoration.setVisible(false);		
			mDragDecoration.getController().propertiesChanged("Visible");
		}
	};

	self.getMessageDecoration = function(whichOne) {
		switch(whichOne) {
			case "TL" :
				return mTLMessageDecoration;
			case "BR" :
				return mBRMessageDecoration;
			case "BL" :
				return mBLMessageDecoration;
		}
		return mTRMessageDecoration;
	};

	self.getCoordinates = function() {
		return mCoorDecoration;
	};

	self.getInteraction = function() {
		return mInteraction;
	};

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.DrawingPanel.registerProperties(self, controller);
	};

  	/***
   	* Get JSON object with private variables
   	* @method serialize
   	* @visibility private
   	*/
  	self.serialize = function() {
  	  return { 
		mStyle: mStyle.serialize(),
		
		mTopDecorations: mTopDecorations,
		mBottomDecorations: mBottomDecorations,
		mElements: mElements,
		mCollectersList: mCollectersList,
		
		mAutoScaleX: mAutoScaleX, mAutoScaleY: mAutoScaleY,
		mInvertedScaleY: mInvertedScaleY, mTypeScaleX: mTypeScaleX,
		mTypeScaleY: mTypeScaleY, mShowCoordinates: mShowCoordinates, 
		
		xmindef: xmindef, xmaxdef: xmaxdef, ymindef: ymindef, ymaxdef: ymaxdef,
		
		mWorld: mWorld,		
		mGutters: mGutters,
		mGuttersStyle: mGuttersStyle.serialize(),
				
		// mInteraction: mInteraction.serialize() 
	  };
  	}
  
  	/***
   	* Set JSON object with private variables
   	* @method unserialize
   	* @parem json JSON object
   	* @visibility private
   	*/
  	self.unserialize = function(json) {
		mStyle.unserialize(json.mStyle), 

		// not support references changing
		// mBottomDecorations = json.mBottomDecorations,
		// mTopDecorations = json.mTopDecorations,
		// mElements = json.mElements,
		// mCollectersList = json.mCollectersList,
		
		mAutoScaleX = json.mAutoScaleX, mAutoScaleY = json.mAutoScaleY,
		mInvertedScaleY = json.mInvertedScaleY, mTypeScaleX = json.mTypeScaleX,
		mTypeScaleY = json.mTypeScaleY, mShowCoordinates = json.mShowCoordinates, 
		
		xmindef = json.xmindef, xmaxdef = json.xmaxdef, ymindef = json.ymindef, ymaxdef = json.ymaxdef,
		
		mWorld = json.mWorld,		
		mGutters = json.mGutters,
		mGuttersStyle.unserialize(json.mGuttersStyle),
		
		// mInteraction = json.mInteraction 
	
		mPanelChanged = true;
  	}

	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	// set graphics mode
	self.setGraphicsMode(mGraphicsMode);

	// style
	mStyle.setLineColor('black');
	mStyle.setFillColor('rgb(239,239,255)');
	mStyle.setChangeListener(function(change) {
		mPanelChanged = true;
	});

	mGuttersStyle.setChangeListener(function(reporter) {
		mPanelChanged = true;
	});

	var mDragDecoration = EJSS_DRAWING2D.shape(mName + "__cursorBox__");
	mDragDecoration.setShapeType(EJSS_DRAWING2D.Shape.RECTANGLE);
	mDragDecoration.getStyle().setDrawFill(false);
	mDragDecoration.getStyle().setLineColor("black");
	mDragDecoration.setRelativePosition("CENTER");
	mDragDecoration.setVisible(false);
	self.addDecoration(mDragDecoration, 0, true);

	var mTLMessageDecoration = EJSS_DRAWING2D.text(mName + "__tlmessage__");
	mTLMessageDecoration.setRelativePosition("NORTH_WEST");
	mTLMessageDecoration.getStyle().setDrawLines(true);
	mTLMessageDecoration.getStyle().setDrawFill(true);
	mTLMessageDecoration.getStyle().setFillColor("yellow");
	mTLMessageDecoration.getStyle().setLineColor("black");
	mTLMessageDecoration.getFont().setFontSize(12);
	mTLMessageDecoration.setPosition([mWorld.xmin, mWorld.ymax]);
	mTLMessageDecoration.setFramed(true);
	mTLMessageDecoration.panelChangeListener = function(event) {
		if (event == "bounds") {
			if(self.getInvertedScaleY())
				mTLMessageDecoration.setPosition([mWorld.xmin, mWorld.ymin]);
			else
				mTLMessageDecoration.setPosition([mWorld.xmin, mWorld.ymax]);	
		}			
	};
	self.addDecoration(mTLMessageDecoration, 0, true);

	var mBLMessageDecoration = EJSS_DRAWING2D.text(mName + "__blmessage__");
	mBLMessageDecoration.setRelativePosition("SOUTH_WEST");
	mBLMessageDecoration.getStyle().setDrawLines(true);
	mBLMessageDecoration.getStyle().setDrawFill(true);
	mBLMessageDecoration.getStyle().setFillColor("yellow");
	mBLMessageDecoration.getStyle().setLineColor("black");
	mBLMessageDecoration.getFont().setFontSize(12);
	mBLMessageDecoration.setPosition([mWorld.xmin, mWorld.ymin]);
	mBLMessageDecoration.setFramed(true);
	mBLMessageDecoration.panelChangeListener = function(event) {
		if (event == "bounds")
			if(self.getInvertedScaleY())
				mBLMessageDecoration.setPosition([mWorld.xmin, mWorld.ymax]);
			else		
				mBLMessageDecoration.setPosition([mWorld.xmin, mWorld.ymin]);
	};
	self.addDecoration(mBLMessageDecoration, 0, true);

	var mBRMessageDecoration = EJSS_DRAWING2D.text(mName + "__brmessage__");
	mBRMessageDecoration.setRelativePosition("SOUTH_EAST");
	mBRMessageDecoration.getStyle().setDrawLines(true);
	mBRMessageDecoration.getStyle().setDrawFill(true);
	mBRMessageDecoration.getStyle().setFillColor("yellow");
	mBRMessageDecoration.getStyle().setLineColor("black");
	mBRMessageDecoration.getFont().setFontSize(12);
	mBRMessageDecoration.setPosition([mWorld.xmax, mWorld.ymin]);
	mBRMessageDecoration.setFramed(true);
	mBRMessageDecoration.panelChangeListener = function(event) {
		if (event == "bounds")
			if(self.getInvertedScaleY())
				mBRMessageDecoration.setPosition([mWorld.xmax, mWorld.ymax]);
			else
				mBRMessageDecoration.setPosition([mWorld.xmax, mWorld.ymin]);
	};
	self.addDecoration(mBRMessageDecoration, 0, true);

	var mTRMessageDecoration = EJSS_DRAWING2D.text(mName + "__trmessage__");
	mTRMessageDecoration.setRelativePosition("NORTH_EAST");
	mTRMessageDecoration.getStyle().setDrawLines(true);
	mTRMessageDecoration.getStyle().setDrawFill(true);
	mTRMessageDecoration.getStyle().setFillColor("yellow");
	mTRMessageDecoration.getStyle().setLineColor("black");
	mTRMessageDecoration.getFont().setFontSize(12);
	mTRMessageDecoration.setPosition([mWorld.xmax, mWorld.ymax]);
	mTRMessageDecoration.setFramed(true);
	mTRMessageDecoration.panelChangeListener = function(event) {
		if (event == "bounds")
			if(self.getInvertedScaleY())
				mTRMessageDecoration.setPosition([mWorld.xmax, mWorld.ymin]);
			else
				mTRMessageDecoration.setPosition([mWorld.xmax, mWorld.ymax]);
	};
	self.addDecoration(mTRMessageDecoration, 0, true);

	var mCoorDecoration = EJSS_DRAWING2D.infoText(mName + "__coor__");
	mCoorDecoration.setRelativePosition("SOUTH_WEST");
	mCoorDecoration.getFont().setFontSize(12);
	mCoorDecoration.getStyle().setFillColor("yellow");
	mCoorDecoration.setPosition([mWorld.xmin, mWorld.ymin]);
	mCoorDecoration.setFormat("x:0.##,y:0.##");
	mCoorDecoration.setFramed(true);
	mCoorDecoration.setVisible(false);
	mCoorDecoration.panelChangeListener = function(event) {
		if (event == "bounds")
			if(self.getInvertedScaleY())
				mCoorDecoration.setPosition([mWorld.xmin, mWorld.ymax]);
			else		
				mCoorDecoration.setPosition([mWorld.xmin, mWorld.ymin]);
	};
	self.addDecoration(mCoorDecoration, 0, true);

	self.getGuttersStyle().setLineColor('black');
	self.getGuttersStyle().setFillColor('rgb(239,239,255)');
	self.getGuttersStyle().setShapeRendering("RENDER_CRISPEDGES");

	// interactions handler
	var mInteraction = EJSS_DRAWING2D.panelInteraction(self);	

//	mGraphics.getEventContext().addEventListener("dblclick", function(event) { event.preventDefault(); event.stopPropagation(); return false; } ); 
    mGraphics.getEventContext().addEventListener("dblclick", function(event){ event.preventDefault(); event.stopPropagation(); return false; }, true);
//	window.document.addEventListener("dblclick", function(event) { event.preventDefault(); event.stopPropagation(); return false; } ); 

	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * Basic Element 2D 
 * @class EJSS_DRAWING2D.Element
 */
EJSS_DRAWING2D.Element = {
	CENTER   : 0,
	NORTH    : 1,
	SOUTH    : 2,
	EAST     : 3,
	WEST     : 4,
	NORTH_EAST  :  5,
	NORTH_WEST  :  6,
	SOUTH_EAST  :  7,
	SOUTH_WEST  :  8,
        
    /**
     * Registers properties in a ControlElement
     */
    registerProperties : function(element,controller) {
      var TARGET_POSITION = EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION;
      var TARGET_SIZE = EJSS_DRAWING2D.PanelInteraction.TARGET_SIZE;
      var style = element.getStyle();
                        
      element.setController(controller);

	 /*** 
	  * Parent of the element
	  * @property Parent 
	  * @type Panel|Group
	  */  
      controller.registerProperty("Parent",element.setParent,element.getParent); 

	 /*** 
	  * Position in X
	  * @property X 
	  * @type double
	  * @default 0
	  */  
      controller.registerProperty("X",element.setX,element.getX);
      
	 /*** 
	  * Position in Y
	  * @property Y 
	  * @type double
	  * @default 0
	  */        
      controller.registerProperty("Y",element.setY,element.getY);
      
	 /*** 
	  * Coordinates X and Y
	  * @property Position 
	  * @type double[2]
	  * @default [0,0]
	  */        
      controller.registerProperty("Position",element.setPosition,element.getPosition);
      
	 /*** 
	  * Whether the position is in pixels
	  * @property PixelPosition 
	  * @type boolean
	  * @default false
	  */              
      controller.registerProperty("PixelPosition",element.setPixelPosition,element.getPixelPosition);
      
	 /*** 
	  * Diameter, i.e. the value of the width and the height 
	  * @property Diameter 
	  * @type double
	  */                    
      controller.registerProperty("Diameter",function(diameter) {
      	element.setSize([diameter,diameter]);
      });
      
	 /*** 
	  * Radius, i.e. the half of the diameter 
	  * @property Radius 
	  * @type double
	  */                    
      controller.registerProperty("Radius",function(radius) {
      	element.setSize([radius*2,radius*2]);
      });
      
	 /*** 
	  * Size along the X axis
	  * @property SizeX 
	  * @type double
	  * @default 1
	  */                          
      controller.registerProperty("SizeX",element.setSizeX,element.getSizeX);

	 /*** 
	  * Size along the Y axis
	  * @property SizeY 
	  * @type double
	  * @default 1
	  */                          
      controller.registerProperty("SizeY",element.setSizeY,element.getSizeY);

	 /*** 
	  * Size along the X and Y axes 
	  * @property Size 
	  * @type double[2]
	  * @default [1,1]
	  */                          
      controller.registerProperty("Size",element.setSize,element.getSize);
            
	 /*** 
	  * Whether the size is in pixels
	  * @property PixelSize 
	  * @type boolean
	  * @default false
	  */                          
      controller.registerProperty("PixelSize",element.setPixelSize,element.getPixelSize);
	  
	 /*** 
	  * Position of the bounding box of the element
	  * @property Bounds 
	  * @type Object{left,rigth,top,bottom}
	  */                          
      controller.registerProperty("Bounds",element.setBounds,element.getBounds);

	 /*** 
	  * Position of the coordinates X and Y relative to the element 
	  * @property RelativePosition 
	  * @type string|int
	  * @values "CENTER":0,"NORTH":1,"SOUTH":2,"EAST":3,"WEST":4,"NORTH_EAST":5,"NORTH_WEST":6,
	  * "SOUTH_EAST":7,"SOUTH_WEST":8 
	  * @default "CENTER"
	  */                          
      controller.registerProperty("RelativePosition",element.setRelativePosition,element.getRelativePosition);

	 /*** 
	  * Whether the element is visible
	  * @property Visibility 
	  * @type boolean
	  * @default true
	  */                          
      controller.registerProperty("Visibility",element.setVisible,element.isVisible);
            
	 /*** 
	  * Measurability of the element
	  * @property Measured 
	  * @type boolean
	  * @default true
	  */                          
      controller.registerProperty("Measured",element.setMeasured,element.isMeasured);

	 /*** 
	  * Internal transformation of the element
	  * @property Transformation 
	  * @type int|float[6]|string
	  * @values int:angle in radians to rotate the element with origin its position, 
	  * float[9]: transformation matrix - [scaleX, skewY, skewX, scaleY, translateX, translateY]
	  * @see http://www.w3.org/TR/SVG-Transforms/
	  * @default 0
	  */                          
      controller.registerProperty("Transformation",element.setTransformation);

	 /*** 
	  * Rotation of the element
	  * @property Rotate 
	  * @type int
	  * @values int:angle in radians to rotate the element with origin its position 
	  * @see http://www.w3.org/TR/SVG-Transforms/
	  * @default 0
	  */                          
      controller.registerProperty("Rotate",element.setRotate);

	 /*** 
	  * Scale transformation of the element in X
	  * @property ScaleX 
	  * @type float
	  * @values float: scale in X 
	  * @see http://www.w3.org/TR/SVG-Transforms/
	  * @default 1
	  */                          
      controller.registerProperty("ScaleX",element.setScaleX);

	 /*** 
	  * Scale transformation of the element in Y
	  * @property ScaleY 
	  * @type float
	  * @values float: scale in Y 
	  * @see http://www.w3.org/TR/SVG-Transforms/
	  * @default 1
	  */                          
      controller.registerProperty("ScaleY",element.setScaleY);

	 /*** 
	  * Skew transformation of the element in X
	  * @property SkewX 
	  * @type float
	  * @values float: skew angle in X 
	  * @see http://www.w3.org/TR/SVG-Transforms/
	  * @default 1
	  */                          
      controller.registerProperty("SkewX",element.setSkewX);

	 /*** 
	  * Skew transformation of the element in Y
	  * @property SkewY 
	  * @type float
	  * @values float: skew angle in Y 
	  * @see http://www.w3.org/TR/SVG-Transforms/
	  * @default 1
	  */                          
      controller.registerProperty("SkewY",element.setSkewY);
  
	 /*** 
	  * Stroke color
	  * @property LineColor 
	  * @type string
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "Black"
	  */                          
      controller.registerProperty("LineColor",style.setLineColor,style.getLineColor);

	 /*** 
	  * Stroke width
	  * @property LineWidth 
	  * @type double
	  * @default 0.5
	  */                          
      controller.registerProperty("LineWidth",style.setLineWidth,style.getLineWidth);
      
	 /*** 
	  * Whether the stroke is drawed
	  * @property DrawLines 
	  * @type boolean
	  * @default true
	  */                          
      controller.registerProperty("DrawLines",style.setDrawLines,style.getDrawLines);

	 /*** 
	  * Fill color
	  * @property FillColor 
	  * @type string
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "none"
	  */                          
      controller.registerProperty("FillColor",style.setFillColor,style.getFillColor);
      
	 /*** 
	  * Whether the fill color is drawed
	  * @property DrawFill 
	  * @type boolean
	  * @default true
	  */                          
      controller.registerProperty("DrawFill",style.setDrawFill,style.getDrawFill);

	 /*** 
	  * SVG shape rendering 
	  * @property ShapeRendering 
	  * @type string
	  * @values "auto","optimizeSpeed","crispEdges","geometricPrecision"  
	  * @default "auto"
	  */                          
      controller.registerProperty("ShapeRendering",style.setShapeRendering,style.getShapeRendering);
      
	 /*** 
	  * Inline SVG attributtes  
	  * @property Attributes 
	  * @type object
	  * @values {nameAttr1:value1,nameAttr2:value2,..}
	  */                                
      controller.registerProperty("Attributes",style.setAttributes,style.getAttributes);

	 /*** 
	  * Whether the user could change the position   
	  * @property EnabledPosition 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("EnabledPosition",function(enabled) {
        element.getInteractionTarget(TARGET_POSITION).setMotionEnabled(enabled);
      });

	 /*** 
	  * Whether the group position also changes when the element position changes    
	  * @property MovesGroup 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("MovesGroup", function(affects) {
        element.getInteractionTarget(TARGET_POSITION).setAffectsGroup(affects);
      });

	 /*** 
	  * Whether the user could change the size   
	  * @property EnabledSize 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("EnabledSize",function(enabled) {
        element.getInteractionTarget(TARGET_SIZE).setMotionEnabled(enabled);
      });

	 /*** 
	  * Whether the group size also changes when the element size changes    
	  * @property ResizesGroup 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("ResizesGroup", function(affects) {
        element.getInteractionTarget(TARGET_SIZE).setAffectsGroup(affects);
      });
      
	 /*** 
	  * Sensitivity in pixels when the user touchs the element    
	  * @property Sensitivity 
	  * @type int
	  * @default 20
	  */                                      
      controller.registerProperty("Sensitivity", function (sense) {
        element.getInteractionTarget(TARGET_POSITION).setSensitivity(sense);
        element.getInteractionTarget(TARGET_SIZE).setSensitivity(sense);
      });

	 /*** 
	  * Event when double click     
	  * @action OnDoubleClick 
	  */                                      
      controller.registerAction("OnDoubleClick", element.getOnDoubleClickInformation);      
	 /*** 
	  * Event when the mouse enters in the element     
	  * @action OnEnter 
	  */                                      
      controller.registerAction("OnEnter",element.getOnEnterInformation);

	 /*** 
	  * Event when the mouse exits the element     
	  * @action OnExit 
	  */                                      
      controller.registerAction("OnExit",element.getOnExitInformation);

	 /*** 
	  * Event when the mouse clicks over the element     
	  * @action OnPress 
	  */                                      
      controller.registerAction("OnPress",element.getOnPressInformation);

	 /*** 
	  * Event when the mouse drags the element     
	  * @action OnDrag 
	  */                                      
      controller.registerAction("OnDrag",element.getOnDragInformation);

	 /*** 
	  * Event when the mouse release the element     
	  * @action OnRelease 
	  */                                      
      controller.registerAction("OnRelease",element.getOnReleaseInformation);
    },
    
    /**
     * Copies one element into another
     */
    copyTo : function(source, dest) {
      var InteractionTarget = EJSS_DRAWING2D.InteractionTarget;
      var TARGET_POSITION = EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION;
      var TARGET_SIZE = EJSS_DRAWING2D.PanelInteraction.TARGET_SIZE;
      
      EJSS_DRAWING2D.Style.copyTo(source.getStyle(),dest.getStyle());
      
      dest.setVisible(source.isVisible());
      dest.setMeasured(source.isMeasured());

      dest.setPosition(source.getPosition());
      dest.setPixelPosition(source.isPixelPosition());
      dest.setSize(source.getSize());
      dest.setPixelSize(source.isPixelSize());
      dest.setTransformation(source.getTransformation());
      dest.setRotate(source.getRotate());

	  dest.setRelativePosition(source.getRelativePosition());

      InteractionTarget.copyTo(source.getInteractionTarget(TARGET_POSITION),dest.getInteractionTarget(TARGET_POSITION));
      InteractionTarget.copyTo(source.getInteractionTarget(TARGET_SIZE),dest.getInteractionTarget(TARGET_SIZE));
      
      dest.setParent(source.getParent(), source);
    },
    
  	/**
   	* Returns offsets of the element with respect to its center.  
   	* @method getRelativePositionOffset
   	* @param position relative position 
   	* @param sx X size
   	* @param sy Y size
   	* @return [x,y]
   	*/
  	getRelativePositionOffset : function(position, sx, sy, invertedy) {
  		var dx = 0;
  		var dy = 0;
	    switch(position) {
	      default : 
	      case EJSS_DRAWING2D.Element.CENTER    : dx = 0; 		  dy = 0; 		break;
	      case EJSS_DRAWING2D.Element.NORTH     : dx = 0; 		  dy = -(sy/2); break;
	      case EJSS_DRAWING2D.Element.SOUTH     : dx = 0; 		  dy = (sy/2);  break;
	      case EJSS_DRAWING2D.Element.EAST      : dx = -(sx/2);    dy = 0; 		break;
	      case EJSS_DRAWING2D.Element.WEST      : dx = (sx/2);     dy = 0; 		break;
	      case EJSS_DRAWING2D.Element.NORTH_EAST: dx = -(sx/2);    dy = -(sy/2); break;
	      case EJSS_DRAWING2D.Element.NORTH_WEST: dx = (sx/2);     dy = -(sy/2); break;
	      case EJSS_DRAWING2D.Element.SOUTH_EAST: dx = -(sx/2);    dy = (sy/2);  break;
	      case EJSS_DRAWING2D.Element.SOUTH_WEST: dx = (sx/2);     dy = (sy/2);  break;
	    }
	    if(typeof invertedy != 'undefined' && invertedy) dy = -dy;
		return [dx,dy]; 
	},

  	/**
   	* Returns offsets of the element with respect to its south-west.  
   	* @method getSWRelativePositionOffset
   	* @param position relative position 
   	* @param sx X size
   	* @param sy Y size
   	* @return [x,y]
   	*/
  	getSWRelativePositionOffset : function(position, sx, sy, invertedy) {
  		var dx = 0;
  		var dy = 0;
	   	switch(position) {
	      default : 
	      case EJSS_DRAWING2D.Element.CENTER    : dx = -(sx/2); 	dy = (sy/2); break;
	      case EJSS_DRAWING2D.Element.NORTH     : dx = -(sx/2); 	dy = sy; 	break;
	      case EJSS_DRAWING2D.Element.SOUTH     : dx = -(sx/2); 	dy = 0;  	break;
	      case EJSS_DRAWING2D.Element.EAST      : dx = -sx;    	dy = (sy/2); break;
	      case EJSS_DRAWING2D.Element.WEST      : dx = 0;     	dy = (sy/2); break;
	      case EJSS_DRAWING2D.Element.NORTH_EAST: dx = -sx;    	dy = sy; 	break;
	      case EJSS_DRAWING2D.Element.NORTH_WEST: dx = 0;     	dy = sy; 	break;
	      case EJSS_DRAWING2D.Element.SOUTH_EAST: dx = -sx;    	dy = 0;  	break;
	      case EJSS_DRAWING2D.Element.SOUTH_WEST: dx = 0;     	dy = 0;  	break;
	    }
	    if(typeof invertedy != 'undefined' && invertedy) dy = -dy;	    
		return [dx,dy]; 
  	}
};

/**
 * Constructor for Element
 * @method element
 * @param mName string
 * @returns An abstract 2D element
 */
EJSS_DRAWING2D.element = function(mName) {
  var self = {};							// reference returned 

  // Static references
  var Element = EJSS_DRAWING2D.Element;		// reference for Element
  var PanelInteraction = EJSS_DRAWING2D.PanelInteraction;	// reference for PanelInteraction

  // Instance variables
  var mStyle = EJSS_DRAWING2D.style(mName);	// style for element 
  var mVisible = true;						// whether visible in drawing
  var mMeasured = true;						// whether measure for element
  var mRelativePosition = Element.CENTER;	// relative position

  // Position and size
  var mX = 0;						// position X
  var mY = 0;						// position Y
  var mPixelPosition = false; 		// whether the position is in pixels
  var mSizeX = 1;					// size X
  var mSizeY = 1;					// size Y
  var mPixelSize = false; 			// whether the size is in pixels
  var mTransformation = [];			// transformation for element
  var mRotate = 0;					// rotation for element

  // Interaction      
  var mInteraction = {
      positionTarget : EJSS_DRAWING2D.interactionTarget(self,PanelInteraction.TARGET_POSITION, Element.CENTER),
      sizeTarget     : EJSS_DRAWING2D.interactionTarget(self,PanelInteraction.TARGET_SIZE, Element.NORTH_EAST)
   };

  // Implementation variables    
  var mPanel = null;				// drawing panel for element
  var mGroup = null;				// group for element
  var mSet = null;				    // The set it belongs to (if any)  
  var mIndexInSet = -1;				// The index of the element in a set (if any)  

  var mProjectedPosition = [];		// projected position for element
  var mProjectedSize = [];			// projected size for element

  var mMustProject = true;			// whether drawing panel changed, then element needs to project
  var mElementChanged = true;		// whether element changed (position, size, or group)

  var mController = { 				// dummy controller object
      propertiesChanged : function() {},
      invokeAction : function() {}
  };

  var mCustomObject = null; // a placeholder to keep and object
  
  // ----------------------------------------
  // Public functions
  // ----------------------------------------
  
  /***
   * Gets name for element
   * @method getName
   * @visibility public
   * @return string
   */
  self.getName = function() {
    return mName;
  };  

  /***
   * Sets the property Parent
   * @method setParent
   * @param parent Panel or Group
   * @visibility public
   */
  self.setParent = function(parent, sibling) {
  	if(parent.render) { // is a panel  		
  		self.setGroup(null);
  		parent.addElement(self);
  		self.setPanel(parent);
  	} else if (parent.getClass() == "ElementGroup") { // is a group
  		self.setGroup(parent);
  		self.getGroupPanel().addElement(self, sibling);
  		self.setPanel(self.getGroupPanel());
  	} else { //
  		console.log("WARNING: setParent() - Parent not valid : "+ parent.getName()); 
  	}
  };

  /***
   * Gets the property Parent
   * @method getParent
   * @return Panel|Group
   * @visibility public
   */
  self.getParent = function() {
  	var parent;
  	if(mGroup !== null) parent = self.getGroup();
  	else parent = self.getPanel();
  	return parent  
  };

  /***
   * Is the element a group  
   * @method isGroup
   * @return boolean
   * @visibility public
   */
  self.isGroup = function() {
  	return false;
  }

  /***
   * Return the style associated to the element
   * @method getStyle
   * @return Style 
   * @visibility public
   */
  self.getStyle = function() { 
    return mStyle; 
  };

  /***
   * Store an object for internal use
   * @method setCustomObject
   * @visibility protected
   */
  self.setCustomObject = function(object) { mCustomObject = object; }

  /***
   * Retrieve an object for internal use
   * @method getCustomObject
   * @return Object
   * @visibility protected
   */
  self.getCustomObject = function() { return mCustomObject; }

  // ----------------------------------------
  // Position of the element
  // ----------------------------------------

  /***
   * Sets the property X
   * @method setX(x)
   * @param x double
   * @visibility public
   */
  self.setX = function(x, dummy) { 
    if (mX!=x) { 
      mX = x; 
      mElementChanged = true; 
    } 
  };

  /***
   * Gets the property X
   * @method getX()
   * @return double
   * @visibility public
   */
  self.getX = function() { 
    return mX; 
  };

  /***
   * Sets the property Y
   * @method setY(y)
   * @param y double
   * @visibility public
   */
  self.setY = function(y) {  
    if (mY!=y) { 
      mY = y; 
      mElementChanged = true; 
    } 
  };

  /***
   * Gets the property Y
   * @method getY()
   * @return double
   * @visibility public
   */
  self.getY = function() { 
    return mY; 
  };

  /***
   * Sets the property Position
   * @method setPosition
   * @param position double[2]
   * @visibility public
   */
  self.setPosition = function(position) {
    self.setX(position[0]);
    self.setY(position[1]);
  };

  /***
   * Gets the property Position
   * @method getPosition
   * @return double[2]
   * @visibility public
   */
  self.getPosition = function() { 
    return [mX, mY]; 
  };
  
  /***
   * Sets the property PixelPosition
   * @method setPixelPosition
   * @param pixel boolean
   * @visibility public
   */
  self.setPixelPosition = function(pixel) {
    if (mPixelPosition!=pixel) { 
      mPixelPosition = pixel;
      mElementChanged = true; 
    }
  };

  /***
   * Returns the property PixelPosition
   * @method isPixelPosition
   * @return boolean
   * @visibility public
   */
  self.isPixelPosition = function() {
    return mPixelPosition;
  };
  
  // ----------------------------------------
  // Size of the element
  // ----------------------------------------

  /***
   * Sets the property SizeX
   * @method setSizeX
   * @param sizeX double
   * @visibility public
   */
  self.setSizeX = function(sizeX) { 
    if (mSizeX!=sizeX) { 
      mSizeX = sizeX; 
      mElementChanged = true; 
    } 
  };

  /***
   * Gets the property SizeX
   * @method getSizeX
   * @return double
   * @visibility public
   */
  self.getSizeX = function() { 
    return mSizeX; 
  };

  /***
   * Sets the property SizeY
   * @method setSizeY
   * @param sizeY double
   * @visibility public
   */
  self.setSizeY = function(sizeY) { 
    if (mSizeY!=sizeY) { 
      mSizeY = sizeY; 
      mElementChanged = true; 
    }
  };

  /***
   * Gets the property SizeY
   * @method getSizeY
   * @return double
   * @visibility public
   */
  self.getSizeY = function() { 
    return mSizeY; 
  };

  /***
   * Sets the property Size
   * @method setSize
   * @param position double[2]
   * @visibility public
   */
  self.setSize = function(size) {
    self.setSizeX(size[0]);
    self.setSizeY(size[1]);
  };

  /***
   * Gets the property Size
   * @method getSize
   * @return double[]
   * @visibility public
   */
  self.getSize = function() {
    return [self.getSizeX(), self.getSizeY()];
  };
  
  /***
   * Sets the property PixelSize
   * @method setPixelSize
   * @param pixel boolean
   * @visibility public
   */
  self.setPixelSize = function(pixel) {
    if (mPixelSize!=pixel) { 
      mPixelSize = pixel;
      mElementChanged = true; 
    }
  };

  /***
   * Gets the property PixelSize
   * @method isPixelSize
   * @return boolean
   * @visibility public
   */
  self.isPixelSize = function() {
    return mPixelSize;
  };
    
  /***
   * Sets bounds for an element
   * @method setBounds
   * @param Object{left,rigth,top,bottom}|[left,rigth,top,bottom]
   * @visibility public
   */
  self.setBounds = function(bounds) {
  	var left,right,top,bottom;
  	
  	if(bounds.left) {
		left = bounds.left;
		right = bounds.right;
		top = bounds.top;
		bottom = bounds.bottom;  		
  	} else {
		left = bounds[0];
		right = bounds[1];
		top = bounds[2];
		bottom = bounds[3];  		  		
  	}
  	
	var sx = right-left;
	var sy = bottom-top;
	  
  	var d = self.getRelativePositionOffset(sx,sy);
  	var mx = sx/2, my = sy/2;  	

	var x = left + mx - d[0];
	var y = top + my - d[1];
	 
    self.setX(x);
    self.setY(y); 
    self.setSizeX(sx);
    self.setSizeY(sy); 
  };
    
  /***
   * Returns bounds for an element
   * @method getBounds
   * @return Object{left,rigth,top,bottom}
   * @visibility public
   */
  self.getBounds = function() {
  	var size = (mPixelSize? mPanel.toPanelMod([mSizeX,-mSizeY]) : [mSizeX,mSizeY]); 
  	
  	var mx = size[0]/2, my = size[1]/2;  	
  	var d = self.getRelativePositionOffset(size[0],size[1]);
	return {
		left: (mX+d[0])-mx,
		right: (mX+d[0])+mx,
		top: (mY+d[1])+my,
		bottom: (mY+d[1])-my
	}
  };

  /***
   * Returns bounds for an element (after applying groups) 
   * @method getAbsoluteBounds
   * @param withTransf whether transformation must be considered, note it will be 
   *  false (or undefinded) when transformations are applied by svg (drawing)
   * @return Object{left,rigth,top,bottom}
   * @visibility public
   */
  self.getAbsoluteBounds = function(withTransf) {
	var bounds = self.getBounds();	
	var p1 = self.toGroupSpace([bounds.left, bounds.top],withTransf);
	var p2 = self.toGroupSpace([bounds.right, bounds.bottom],withTransf);

	return {left: p1[0], top: p1[1], right: p2[0], bottom: p2[1]};
  };

  //---------------------------------
  // relative position
  //---------------------------------

  /**
   * Sets the relative position of the element with respect to its (x,y) coordinates.
   * @method setRelativePosition
   * @param position relative position
   */
  self.setRelativePosition = function(position) {
    if (typeof position == "string") position = Element[position.toUpperCase()];
    if (mRelativePosition != position) {
      mRelativePosition = position;
      mElementChanged = true; 
    }
  };
  
  /**
   * Gets the relative position of the element with respect to its (x,y) coordinates.
   * @method getRelativePosition
   * @return relative position
   */
  self.getRelativePosition = function() { 
    return mRelativePosition;
  };  

  /**
   * Returns offsets of the element with respect to its center.  
   * @method getRelativePositionOffset
   * @param s1 X size or size array
   * @param s2 Y size
   * @return [x,y]
   */
  self.getRelativePositionOffset = function(s1, s2) {
	var sx, sy;
  	if (Array.isArray(s1)) { // is array?
  		sx = s1[0];
  		sy = s1[1];
  	} else {
  		sx = s1;
  		sy = s2;
  	}  	
  	
  	var invertedScale = mPanel.getInvertedScaleY? mPanel.getInvertedScaleY():false;
  	return Element.getRelativePositionOffset(mRelativePosition, sx, sy, invertedScale);
  }

  // -------------------------------------
  // Visible and measure
  // -------------------------------------

  /***
   * Sets the property Visibility
   * @method setVisible
   * @param visible boolean
   * @visibility public
   */
  self.setVisible = function(visible) {
    if (visible!=mVisible) { 
      mVisible = visible; 
      mElementChanged = true;
    } 
  };

  /***
   * Gets the property Visibility
   * @method isVisible
   * @return boolean
   * @visibility public
   */
  self.isVisible = function() { 
    return mVisible; 
  };

  /***
   * Returns the real visibility status of the element, 
   * which will be false if it belongs to an invisible group
   * @method isGroupVisible
   * @return boolean
   * @visibility public
   */
  self.isGroupVisible = function() {
    var el = mGroup;
    while (typeof el != "undefined" && el !== null) {
      if (!el.isVisible()) return false;
      el = el.getGroup();
    }
    return mVisible;
  };

  /***
   * Sets the property Measured
   * @method setMeasured
   * @param measured boolean
   * @visibility public
   */
  self.setMeasured = function(measured) { 
    mMeasured = measured; 
  };

  /***
   * Gets the property Measured
   * @method isMeasured
   * @return boolean
   * @visibility public
   */
  self.isMeasured = function() { 
    return mMeasured; 
  };

  // ----------------------------------------------------
  // Transformations
  // ----------------------------------------------------

  /***
   * Sets the property Transformation
   * @method setTransformation
   * @param trans transformation - [scaleX, skewY, skewX, scaleY, translateX, translateY]
   * @visibility public
   */
  self.setTransformation = function(trans) {
    if (typeof trans == "undefined" || trans === null) { 
    	mTransformation = [];
    } else if (Array.isArray(trans) && trans.length>5) {  // matrix 
	    if (!EJSS_TOOLS.compareArrays(mTransformation,trans)) {
	      mTransformation = trans.slice();
	      mElementChanged = true;
	    }
    } else if (mRotate != trans) {  // rotation
		mRotate = trans;
		mElementChanged = true;    		
    }
  };

  /***
   * Gets the property Transformation
   * @method getTransformation
   * @return Transformation - [scaleX, skewY, skewX, scaleY, translateX, translateY] 
   * @visibility public
   */
  self.getTransformation = function() {
    return mTransformation;
  };

  /***
   * Sets the property Rotate
   * @method setRotate
   * @param Rotate angle 
   * @visibility public
   */
  self.setRotate = function(angle) {
	if (mRotate != angle) {
		mRotate = angle;
		mElementChanged = true;    		
    }
  };

  /***
   * Gets the property Rotate
   * @method getRotate
   * @return Rotate angle 
   * @visibility public
   */
  self.getRotate = function() {
    return mRotate;
  };

  /***
   * Sets the property ScaleX
   * @method setScaleX
   * @param scale rate  
   * @visibility public
   */
  self.setScaleX = function(rate) {
	if (mTransformation.length == 0)
		mTransformation = [1, 0, 0, 1, 0, 0];
	if (mTransformation[0] != rate) {
		mTransformation[0] = rate;
		mElementChanged = true;    		
    }
  };

  /***
   * Gets the property ScaleX
   * @method getScaleX
   * @return scale rate  
   * @visibility public
   */
  self.getScaleX = function() {
	if (mTransformation.length == 0)
		return 1;
	else
		return mTransformation[0];
  };

  /***
   * Sets the property ScaleY
   * @method setScaleY
   * @param scale rate  
   * @visibility public
   */
  self.setScaleY = function(rate) {
	if (mTransformation.length == 0)
		mTransformation = [1, 0, 0, 1, 0, 0];
	if (mTransformation[3] != rate) {
		mTransformation[3] = rate;
		mElementChanged = true;    		
    }
  };

  /***
   * Gets the property ScaleY
   * @method getScaleY
   * @return scale rate  
   * @visibility public
   */
  self.getScaleY = function() {
	if (mTransformation.length == 0)
		return 1;
	else
		return mTransformation[3];
  };

  /***
   * Sets the property SkewX
   * @method setSkewX
   * @param skew angle  
   * @visibility public
   */
  self.setSkewX = function(angle) {
	if (mTransformation.length == 0)
		mTransformation = [1, 0, 0, 1, 0, 0];
	if (mTransformation[2] != angle) {
		mTransformation[2] = angle;
		mElementChanged = true;    		
    }
  };

  /***
   * Gets the property SkewX
   * @method getSkewX
   * @return skew angle  
   * @visibility public
   */
  self.getSkewX = function() {
	if (mTransformation.length == 0)
		return 0;
	else
		return mTransformation[2];
  };

  /***
   * Sets the property SkewY
   * @method setSkewY
   * @param skew angle  
   * @visibility public
   */
  self.setSkewY = function(angle) {
	if (mTransformation.length == 0)
		mTransformation = [1, 0, 0, 1, 0, 0];
	if (mTransformation[1] != angle) {
		mTransformation[1] = angle;
		mElementChanged = true;    		
    }
  };

  /***
   * Gets the property SkewY
   * @method getSkewY
   * @return skew angle  
   * @visibility public
   */
  self.getSkewY = function() {
	if (mTransformation.length == 0)
		return 0;
	else
		return mTransformation[1];
  };

  // ----------------------------------------
  // Private functions
  // ----------------------------------------

  /***
   * Gets information for element
   * @method getInfo
   * @visibility private
   * @return string
   */
  self.getInfo = function() {
  	var info = "x=" + mX.toFixed(2) + " y=" + mY.toFixed(2);
  	  	
    return info;
  };     

  // ----------------------------------------
  // Panel and group 
  // ----------------------------------------

  /***
   * Sets the panel for this element
   * @method setPanel
   * @param panel DrawingPanel
   * @visibility private
   */
  self.setPanel = function(panel) {
    mPanel = panel;
    mMustProject = true;
  };

  /***
   * Gets the panel for this element
   * @method getPanel
   * @return DrawingPanel
   * @visibility private
   */
  self.getPanel = function() { 
    return mPanel;
  };

  /***
   * Returns the DrawingPanel in which it (or its final ancestor group) is displayed
   * @method getGroupPanel
   * @return DrawingPanel
   * @visibility private
   */
  self.getGroupPanel = function() { 
    var el = self;
    while (el.getGroup()) el = el.getGroup();
    return el.getPanel();
  };

  /***
   * Sets the group of this element
   * @method setGroup
   * @param group Group
   * @visibility private
   */
  self.setGroup = function(group) {
//	    if (mGroup) mGroup.removeChild(self);
	    mGroup = group;
//	    if (mGroup) mGroup.addChild(self);
	    mElementChanged = true; 
  };

  /***
   * Get the group of this element, if any
   * @method getGroup
   * @return Group
   * @visibility private
   */
  self.getGroup = function() { 
    return mGroup; 
  };

  // ----------------------------------------
  // Set
  // ----------------------------------------

  /***
   * Sets the index of this element in the set
   * @method setSet
   * @param set ElementSet
   * @param index int
   * @visibility private
   */
  self.setSet = function(set,index) {
    mSet = set;
    mIndexInSet = index;
  };

  /***
   * Gets the set of this element, if any
   * @method getSet
   * @return ElementSet
   * @visibility private
   */
  self.getSet = function() { 
    return mSet; 
  };

  /***
   * Gets the index of this element in a set, if any
   * @method getSetIndex
   * @return int
   * @visibility private
   */
  self.getSetIndex = function() { 
    return mIndexInSet; 
  };

  // ----------------------------------------
  // Changes
  // ----------------------------------------

  /***
   * Whether the element has changed
   * @method isChanged
   * @return boolean
   * @visibility private
   */
  self.isChanged = function() {
    return mElementChanged;
  };

  /***
   * Tells the element that it has changed
   * Typically used by subclasses when they change something.
   * @method setChanged
   * @param changed boolean
   * @visibility private
   */
  self.setChanged = function(changed) {
    mElementChanged = changed;
  };

  /***
   * Returns whether the element group has changed
   * @method isGroupChanged
   * @return boolean
   * @visibility private
   */
  self.isGroupChanged = function() {
    var el = self.getGroup();
    while (typeof el != "undefined" && el !== null) {
      if (el.isChanged()) return true;
      el = el.getGroup();
    }
    return false;
  };

  /***
   * Tells the element whether it should reproject its points because the panel
   * has changed its projection parameters. Or, the other way round, sets it to false
   * if someone (typically methods in subclasses) took care of this already
   * @method setMustProject
   * @param needsIt boolean
   * @visibility private
   */
  self.setMustProject = function(needsIt) {
    mMustProject = needsIt;
  };

  /***
   * Whether the element needs to project. Typically used by the dawing panel 
   * whenever it changes its scales
   * @method isMustProject
   * @return boolean
   * @visibility private
   */
  self.isMustProject = function() { 
    return mMustProject;  
  };

  // ----------------------------------------
  // Conversions
  // ----------------------------------------

  /***
   * Returns pixel coordinates 
   * @method getPixelPosition
   * @param withTransf whether transformation must be considered, note it will be 
   *  false (or undefinded) when transformations are applied by svg (drawing)
   * @return double[2]
   * @visibility private
   */
  self.getPixelPosition = function(withTransf) {
	if (self.isChanged() || self.isGroupChanged() || self.isMustProject() || withTransf) {  	
	  	// get projected position
	  	mProjectedPosition = self.getPixelPositionOf(mX,mY,withTransf);
	  	// get projected size
	  	mProjectedSize = self.getPixelSizeOf(mSizeX,mSizeY);

	    self.setMustProject(withTransf); // must be projected the next time  				  		
	}
	return mProjectedPosition;
  };

  /***
   * Returns pixel sizes 
   * @method getPixelSizes
   * @param withTransf whether transformation must be considered, note it will be 
   *  false (or undefinded) when transformations are applied by svg (drawing)
   * @return double[2]
   * @visibility private
   */
  self.getPixelSizes = function(withTransf) {
	if (self.isChanged() || self.isGroupChanged() || self.isMustProject()) {  	
	  	// get projected position
	  	mProjectedPosition = self.getPixelPositionOf(mX,mY,withTransf);
	  	// get projected size
	  	mProjectedSize = self.getPixelSizeOf(mSizeX,mSizeY);

	    self.setMustProject(withTransf); // must be projected the next time 				  		
	}
	return mProjectedSize;
  };

  /***
   * Returns the absolute world sizes in the panel (after applying groups)  
   * @method getAbsoluteSize
   * @return double[2]
   * @visibility private
   */
  self.getAbsoluteSize = function() {
  	var size = (mPixelSize? mPanel.toPanelMod([mSizeX,-mSizeY]) : [mSizeX,mSizeY]); 
    return self.toGroupSpaceMod(size);
  };

  /***
   * Returns the absolute position in the panel (after applying groups)  
   * @method getAbsolutePosition
   * @param withTransf whether transformation must be considered, note it will be 
   *  false (or undefinded) when transformations are applied by svg (drawing)
   * @return double[2]
   * @visibility private
   */
  self.getAbsolutePosition = function(withTransf) {
  	var pos;
  	if(mPixelPosition) {
  		if(mGroup === null)
  			// pos using the panel scale 
  			pos = mPanel.toPanelPosition([mX,mY])
  		else
  			// with group, only support scale NUM  
  			pos = mPanel.toPanelPosition([mX,mY],EJSS_DRAWING2D.DrawingPanel.SCALE_NUM)
  	} else {
  		pos = [mX,mY]; 
  	}
    return self.toGroupSpace(pos,withTransf);
  };

  /***
   * Sets the absolute position in the panel (after applying groups)  
   * Note: Transformations are not considered and it is not supported when pixel position is used
   * @method setAbsolutePosition
   * @return double[2]
   * @visibility private
   */
  self.setAbsolutePosition = function(position) {
  	if(mPixelPosition) {
  		console.log("setAbsolutePosition not supported!");
  	} else {
  		self.setPosition(self.toElementSpace(position)); 
  	}
  };

  /***
   * Sets the absolute position X in the panel (after applying groups)  
   * Note: Transformations are not considered and it is not supported when pixel position is used
   * @method setAbsoluteX
   * @return double
   * @visibility private
   */
  self.setAbsoluteX = function(x) {
  	if(mPixelPosition) {
  		console.log("setAbsoluteX not supported!");
  	} else {
  		self.setX(self.toElementSpace([x,0])[0]); 
  	}
  };

  /***
   * Sets the absolute position Y in the panel (after applying groups)  
   * Note: Transformations are not considered and it is not supported when pixel position is used
   * @method setAbsoluteY
   * @return double
   * @visibility private
   */
  self.setAbsoluteY = function(y) {
  	if(mPixelPosition) {
  		console.log("setAbsoluteY not supported!");
  	} else {
  		self.setY(self.toElementSpace([0,y])[1]); 
  	}
  };

  /***
   * Returns pixel sizes for the given sizes in the element's world coordinates
   * @method getPixelSizeOf
   * @return double[2]
   * @visibility private
   */
  self.getPixelSizeOf = function(sx,sy) {
	if (mPixelSize) return [sx,-sy];
    else return self.getGroupPanel().toPixelMod(self.toGroupSpaceMod([sx,sy]));
  };
 
  /***
   * Returns pixel coordinates for the given point in the element's world coordinates
   * @method getPixelPositionOf
   * @param withTransf whether transformation must be considered, note it will be 
   *  false (or undefinded) when transformations are applied by svg (drawing)
   * @return double[2]
   * @visibility private
   */
  self.getPixelPositionOf = function(x,y,withTransf) {
  	if(mPixelPosition) {
  	  if (mGroup) {
  	    var groupPos = mGroup.getPixelPosition();
  	    return [groupPos[0]+x,groupPos[1]-y];
  	  }
  	  return [self.getGroupPanel().toPixelAxisX(x), self.getGroupPanel().toPixelAxisY(y)];
  	}
  	else {
  		if(mGroup === null)
  			// pos using the panel scale 
  			return self.getGroupPanel().toPixelPosition(self.toGroupSpace([x,y]));
  			// with group, only support scale NUM  
  		return self.getGroupPanel().toPixelPosition(
  			self.toGroupSpace([x,y],withTransf),
  			EJSS_DRAWING2D.DrawingPanel.SCALE_NUM);
  	}
  };


  /***
   * Transforms a module (longitude or size) in the element's world coordinates to
   * the group's world coordinates
   * @method toGroupSpaceMod
   * @param mod double[] The original module in the body frame
   * @return double[] The same array once transformed
   * @visibility private
   */
  self.toGroupSpaceMod = function(mod) {
    var el = mGroup;
    while (typeof el != "undefined" && el !== null) { // apply group transformations 
      // scale vector considering [sizeX,sizeY]
      mod[0] *= el.getSizeX();
      mod[1] *= el.getSizeY();
      el = el.getGroup(); // next group
    }
    return mod;
  };
  
  /***
   * Transforms a double[] point in the element's world coordinates to
   * the group's world coordinates
   * @method toGroupSpace
   * @param point double[] The original coordinates in the body frame
   * @param withTransf bool Considering group transformations
   * @return double[] The same array once transformed
   * @visibility private
   */
  self.toGroupSpace = function(point, withTransf) {
    var el = mGroup;
    while (typeof el != "undefined" && el !== null) { // apply group transformations
      // scale point considering [sizeX,sizeY]
      point[0] *= el.getSizeX();
      point[1] *= el.getSizeY();
      // translate point
      point[0] += el.getX();
      point[1] += el.getY();
      el = el.getGroup();	// next group
    }

    // transformations
    if(withTransf) {
	   el = mGroup;
	   while (typeof el != "undefined" && el !== null) { // apply group transformations
	      	  // note: ignore transformation matrix (please use Sensitivity = 0) 
		      // rotation point
		      var rot = el.getRotate();
			  if (rot != 0) { // rotation angle 	
			      var pos = el.getAbsolutePosition();
			      point = EJSS_TOOLS.Mathematics.rotate(pos,point,-rot);
			      
			  }	// other transfs not considered      	
	      el = el.getGroup();	// next group
       }
    }

    return point;
  };

  /***
   * Transforms a double[] point in the group's world coordinates to
   * the element's world coordinates
   * Note: Transformations are not considered!
   * @method toElementSpace
   * @param point double[] The original coordinates in the body frame
   * @return double[] The same array once translated
   * @visibility private
   */
  self.toElementSpace = function(point) {
  	var list = [];
    var el = mGroup;
    while (typeof el != "undefined" && el !== null) { 
      list.push(el);
      el = el.getGroup();	// next group
    }
	list = list.reverse();
	
	var listLength = list.length;
	for (var i = 0; i < listLength; i++) {
      var el = list[i];
      // translate point
      point[0] -= el.getX();
      point[1] -= el.getY();
      // scale point considering [sizeX,sizeY]
	  if(el.getSizeX() == 0) point[0] = 0;
      else point[0] /= el.getSizeX();
	  if(el.getSizeY() == 0) point[1] = 0;
      else point[1] /= el.getSizeY();
    }
    return point;
  };

  // ----------------------------------------
  // Interaction
  // ----------------------------------------

  /***
   * Returns the controller object
   * @method getController
   * @return Controller
   * @visibility private
   */
  self.getController = function () {
    return mController;
  };

  /***
   * Sets the controller
   * @method setController
   * @param Controller
   * @visibility private
   */
  self.setController = function (controller) {
    mController = controller;
  };

  /***
   * Returns one of the interaction targets defined by the element
   * @method getInteractionTarget
   * @param target
   * @visibility private
   */
  self.getInteractionTarget = function(target) {
    switch (target) {
      case PanelInteraction.TARGET_POSITION : return mInteraction.positionTarget;
      case PanelInteraction.TARGET_SIZE : return mInteraction.sizeTarget;
    }
    return null;
  };

  /***
   * Returns array of the interaction targets defined by the element
   * @method getInteractionTarget
   * @return targets
   * @visibility private
   */
  self.getInteractionTargets = function() {
  	return [mInteraction.positionTarget, mInteraction.sizeTarget];
  };

  /***
   * Returns array of the interaction targets defined by the element
   * @method getInteractionTarget
   * @return targets
   * @visibility private
   */
  self.getInteractionInformation = function() {
  	return { info : self.getPosition(), element : self, point : self.getGroupPanel().getPanelInteraction().getInteractionPoint() };
  };

  self.getOnDoubleClickInformation = function() {
	var info = self.getInteractionInformation();
	info.action = 'OnDoubleClick';
	return info;
  };

  self.getOnEnterInformation = function() {
	var info = self.getInteractionInformation();
	info.action = 'OnEnter';
	return info;
  };

  self.getOnExitInformation = function() {
	var info = self.getInteractionInformation();
	info.action = 'OnExit';
	return info;
  };
	  
  self.getOnPressInformation = function() {
	var info = self.getInteractionInformation();
	info.action = 'OnPress';
	return info;
  };

  self.getOnDragInformation = function() {
	var info = self.getInteractionInformation();
	info.action = 'OnDrag';
	return info;
  };

  self.getOnReleaseInformation = function() {
	var info = self.getInteractionInformation();
	info.action = 'OnRelease';
	return info;
  };
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------
  
  /***
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   * @visibility private
   */
  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Element.registerProperties(self,controller);
  };

  /***
   * Copies itself to another element
   * @method copyTo
   * @param element Element
   * @visibility private
   */
  self.copyTo = function(element) {
    EJSS_DRAWING2D.Element.copyTo(self,element);
  };

  /***
   * Get JSON object with private variables
   * @method serialize
   * @visibility private
   */
  self.serialize = function() {
  	return { 
  		mStyle: mStyle.serialize(), 
  		mVisible: mVisible, 
  		mMeasured: mMeasured, 
  		mRelativePosition: mRelativePosition,
  		
  		mX: mX, mY: mY, mPixelPosition: mPixelPosition,
  		mSizeX: mSizeX, mSizeY: mSizeY, mPixelSize: mPixelSize,
  		mTransformation: mTransformation,
		
		mInteraction: {
			//positionTarget: mInteraction.positionTarget.serialize(),
			//sizeTarget: mInteraction.sizeTarget.serialize()
		},
		
		mPanel: (mPanel?mPanel.getName():mPanel), mGroup: (mGroup?mGroup.getName():mGroup), 
		mSet: (mSet?mSet.getName():mSet), mIndexInSet: mIndexInSet,
		
		mProjectedPosition: mProjectedPosition, mProjectedSize: mProjectedSize,		
		 
  		};
  }
  
  /***
   * Set JSON object with private variables
   * @method unserialize
   * @parem json JSON object
   * @visibility private
   */
  self.unserialize = function(json) {
	mStyle.unserialize(json.mStyle), 
	mVisible = json.mVisible, 
	mMeasured = json.mMeasured, 
	mRelativePosition = json.mRelativePosition,
	
	mX = json.mX, mY = json.mY, mPixelPosition = json.mPixelPosition,
	mSizeX = json.mSizeX, mSizeY = json.mSizeY, mPixelSize = json.mPixelSize,
	mTransformation = json.mTransformation,
	
	// mInteraction.positionTarget = json.mInteraction.positionTarget,
	// mInteraction.sizeTarget = json.mInteraction.sizeTarget,
	
	// not support references changing
	//	mPanel = _view[json.mPanel], mGroup = json.mGroup, mSet = json.mSet, 
	mIndexInSet = json.mIndexInSet,
	
	mProjectedPosition = json.mProjectedPosition, mProjectedSize = json.mProjectedSize,		

    mElementChanged = true; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  mStyle.setChangeListener(function (change) { mElementChanged = true; });
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * ElementSet
 * @class ElementSet 
 * @constructor  
 */
EJSS_DRAWING2D.ElementSet = {
    
    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * Registers properties in a ControlElement
     * @method 
     * @param element The element with the properties
     * @param controller A ControlElement that becomes the element controller
     */
    registerProperties : function(set,controller) {
      var TARGET_POSITION = EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION;
      var TARGET_SIZE = EJSS_DRAWING2D.PanelInteraction.TARGET_SIZE;

      set.setController(controller); // remember it, in case you change the number of elements
      set.setToEach(function(element,value) { element.setController(value); }, controller); // make all elements in the set report to the same controller

      controller.registerProperty("Parent", 
      	  function(panel) {
      	  	set.setParent(panel); 
      		set.setToEach(function(element,value) { element.setParent(value); }, set); 
      	  } );

      controller.registerProperty("NumberOfElements", set.setNumberOfElements);

      controller.registerProperty("ElementInteracted", set.setElementInteracted, set.getElementInteracted);

//      controller.registerProperty("x",function(v) { set.foreach("setX",v); }, function() { return set.getall("getX"); });
      controller.registerProperty("X",
          function(v) { set.setToEach(function(element,value) { element.setX(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getX(); } ); }      
          );
      controller.registerProperty("Y",
          function(v) { set.setToEach(function(element,value) { element.setY(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getY(); } ); }      
      );
      controller.registerProperty("Position",
          function(v) { set.setToEach(function(element,value) { element.setPosition(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getPosition(); } ); }      
      );
      controller.registerProperty("PixelPosition", 
          function(v) { set.setToEach(function(element,value) { element.setPixelPosition(value); }, v); }
      );

      controller.registerProperty("Diameter",
          function(v) { set.setToEach(function(element,value) { element.setSize([value,value]); }, v); }
          );
      controller.registerProperty("Radius",
          function(v) { set.setToEach(function(element,value) { element.setSize([value*2,value*2]); }, v); }
          );
      controller.registerProperty("SizeX",
          function(v) { set.setToEach(function(element,value) { element.setSizeX(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getSizeX(); } ); }      
          );
      controller.registerProperty("SizeY",
          function(v) { set.setToEach(function(element,value) { element.setSizeY(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getSizeY(); } ); }      
      );
      controller.registerProperty("Size",
          function(v) { set.setToEach(function(element,value) { element.setSize(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getSize(); } ); }      
      );
      controller.registerProperty("PixelSize", 
          function(v) { set.setToEach(function(element,value) { element.setPixelSize(value); }, v); }
      );

      controller.registerProperty("Bounds",function(bounds) {
        var setBounds = function(element,bound) {
          element.setX(bound[0]);
          element.setY(bound[2]); 
          element.setSizeX(bound[1]-bound[0]);
          element.setSizeY(bound[3]-bound[2]);
        };
        set.setToEach(setBounds, bounds);
      },
      function() {
        var getBounds = function(element) {
          return [element.getX(),element.getX()+element.getSizeX(),
                  element.getY(),element.getY()+element.getSizeY()]; 
        };
        return set.getFromEach(getBounds);
      });

      controller.registerProperty("Visibility", 
          function(v) { set.setToEach(function(element,value) { element.setVisible(value); }, v); }
      );
      controller.registerProperty("Measured", 
          function(v) { set.setToEach(function(element,value) { element.setMeasured(value); }, v); }
      );

      controller.registerProperty("Transformation",
      	  function(v) { set.setToEach(function(element,value) { element.setTransformation(value); }, v);
      });

      controller.registerProperty("Rotate",
      	  function(v) { set.setToEach(function(element,value) { element.setRotate(value); }, v);
      });

      controller.registerProperty("ScaleX",
      	  function(v) { set.setToEach(function(element,value) { element.setScaleX(value); }, v);
      });

      controller.registerProperty("ScaleY",
      	  function(v) { set.setToEach(function(element,value) { element.setScaleY(value); }, v);
      });

      controller.registerProperty("SkewX",
      	  function(v) { set.setToEach(function(element,value) { element.setSkewX(value); }, v);
      });

      controller.registerProperty("SkewY",
      	  function(v) { set.setToEach(function(element,value) { element.setSkewY(value); }, v);
      });

      controller.registerProperty("RelativePosition", 
          function(v) { set.setToEach(function(element,value) { element.setRelativePosition(value); }, v); }
      );

      controller.registerProperty("LineColor", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setLineColor(value); }, v); }
      );
      controller.registerProperty("LineWidth", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setLineWidth(value); }, v); }
      );
      controller.registerProperty("DrawLines", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setDrawLines(value); }, v); }
      );
      controller.registerProperty("FillColor", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setFillColor(value); }, v); }
      );
      controller.registerProperty("DrawFill", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setDrawFill(value); }, v); }
      );
      controller.registerProperty("Attributes", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setAttributes(value); }, v); }
      );
      controller.registerProperty("ShapeRendering", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setShapeRendering(value); }, v); }
      );

      controller.registerProperty("EnabledPosition", 
          function(v) { set.setToEach(function(element,value) { element.getInteractionTarget(TARGET_POSITION).setMotionEnabled(value); }, v); }
      );
      controller.registerProperty("MovesGroup", 
          function(v) { set.setToEach(function(element,value) { element.getInteractionTarget(TARGET_POSITION).setAffectsGroup(value); }, v); }
      );
      controller.registerProperty("EnabledSize", 
          function(v) { set.setToEach(function(element,value) { element.getInteractionTarget(TARGET_SIZE).setMotionEnabled(value); }, v); }
      );
      controller.registerProperty("ResizesGroup", 
          function(v) { set.setToEach(function(element,value) { element.getInteractionTarget(TARGET_SIZE).setAffectsGroup(value); }, v); }
      );
      controller.registerProperty("Sensitivity", 
          function(v) { set.setToEach(function(element,value) { 
            element.getInteractionTarget(TARGET_POSITION).setSensitivity(value); 
            element.getInteractionTarget(TARGET_SIZE).setSensitivity(value); 
          }, v); }
      );

      var dataFunction = function() { 
        //var index = set.getGroupPanel().getPanelInteraction().getIndexElement();
        //set.setElementInteracted(index);
        //return { index: index, position: set.getElements()[index].getPosition() };
        var panel = set.getGroupPanel();
        var index = panel.getPanelInteraction().getIndexElement();
        var element = panel.getElements()[index];
        var elementIndex = element.getSetIndex(); 
        set.setElementInteracted(elementIndex);
  	    controller.propertiesChanged("ElementInteracted");
	    //controller.reportInteractions();	 
        return { index: elementIndex, position: element.getPosition() };   
      };
      
      // Actions
      controller.registerAction("OnDoubleClick",   dataFunction);
      controller.registerAction("OnEnter",   dataFunction);
      controller.registerAction("OnExit",    dataFunction);
      controller.registerAction("OnPress",   dataFunction);
      controller.registerAction("OnDrag",    dataFunction);
      controller.registerAction("OnRelease", dataFunction);
    }
};

/**
 * Element set
 * Creates a basic abstract ElementSet
 * @method elementSet
 * @param mConstructor the function that creates new elements (will be used as element = mConstructor(name))
 * @returns An abstract 2D element set
 */
EJSS_DRAWING2D.elementSet = function (mConstructor, mName) {  
  var self = EJSS_DRAWING2D.group(mName);

  // Static references
  var ElementSet = EJSS_DRAWING2D.ElementSet;		// reference for ElementSet
  
  // Configuration variables
  var mElementList = []; 
  var mNumberOfElementsSet = false;

  // Implementation variables  
  var mElementInteracted = -1;

  // Last list of removed elements
  var mLastElementList = [];

  // ----------------------------------------
  // Configuration methods
  // ----------------------------------------

  /**
   * Sets the number of element of this set
   * @method setNumberOfElements
   * @param numberOfElements the number of elements, must be >= 1
   */
  self.setNumberOfElements = function(numberOfElements) {
	mNumberOfElementsSet = true;
	adjustNumberOfElements(numberOfElements);
  };

  /*
   * Adjusts the number of element of this set
   * @method adjustNumberOfElements
   * @param numberOfElements the number of elements, must be >= 1
   */
  function adjustNumberOfElements(numberOfElements) {
    // keep original settings for the new elements
    var name = self.getName ? self.getName() : "unnamed";
    numberOfElements = Math.max(1,numberOfElements);
    var diff = mElementList.length-numberOfElements;
    if (diff > 0) {
    	mLastElementList = mElementList.splice(numberOfElements, diff);
		for (var j = 0; j < mLastElementList.length; j++) { 
			var panel = mLastElementList[j].getPanel(); // remove element from panel
			if(panel) panel.removeElement(mLastElementList[j])
		} 
    } else if (diff < 0) {
    	mLastElementList = [];
    	var controller = self.getController();
    	var oldElement = mElementList[mElementList.length-1];
		for (var i = mElementList.length; i < numberOfElements; i++) {
			var element = mConstructor(name+"["+i+"]"); // new element			
			element.setSet(self,i);
			oldElement.copyTo(element);
			element.setController(controller);
		  	mElementList.push(element);	
		} 
    }
  };

  /**
   * Returns the array with all elements in the set
   * @method getElements
   * @return array of Elements
   */
  self.getElements = function() {
    return mElementList;
  };

  /**
   * Returns the element in the set at the given index
   * @method getElement
   * @return Element
   */
  self.getElement = function(index) {
    return mElementList[index];
  };
  
  /**
   * Returns last list of removed elements and reset the value
   * @method getLastElements
   * @return last list of Elements
   */
  self.getLastElements = function() {
  	var ret = mLastElementList.slice();
  	mLastElementList = [];
    return ret;
  };
  
  self.setElementInteracted = function(index) {
    mElementInteracted = index;
  };

  self.getElementInteracted = function() {
    return mElementInteracted;
  };
  
  // ----------------------------------------
  // Relation to its panel
  // ----------------------------------------
  
  var super_setPanel = self.setPanel;

  self.setPanel = function(panel) {
    super_setPanel(panel);
    for (var i=0,n=mElementList.length;i<n;i++) mElementList[i].setPanel(panel);
  };

  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  /**
   * Applies to each element in the set the function f with argument v, or v[i], if it is an array
   * @method setToEach
   * @param f function
   * @param v arguments
   */
  self.setToEach = function(f,v) {
    if (Array.isArray(v)) {
      if (!mNumberOfElementsSet) { 
        if (mElementList.length < v.length) adjustNumberOfElements(v.length);
      }
      for (var i=0,n=Math.min(mElementList.length,v.length);i<n;i++) f(mElementList[i],v[i]);
    }
    else {
      for (var i=0,n=mElementList.length;i<n;i++) f(mElementList[i],v);
    }
  };

  /**
   * Returns an array with the result of applying the function to each element of the set
   * @method getFromEach
   * @param f function
   * @return f function return 
   */
  self.getFromEach = function(f) {
    var value = [];
    for (var i=0, n=mElementList.length;i<n;i++) value[i] = f(mElementList[i]);
    return value;
  };

  /**
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   */
  self.registerProperties = function(controller) {
    ElementSet.registerProperties(self,controller);
  };
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
	
  var element = mConstructor(mName + "[0]"); // new element			
  element.setController(self.getController());
  element.setSet(self,0);
  mElementList.push(element);
  	
  return self;
};
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Grid
 * @class Grid 
 * @constructor  
 */
EJSS_DRAWING2D.Grid = {	
	SCALE_NUM	: 0,	// decimal
	SCALE_LOG	: 1,	// logarithmic
	
	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("StepX", element.setStepX, element.getStepX);
		controller.registerProperty("StepY", element.setStepY, element.getStepY);
		controller.registerProperty("TicksX", element.setTicksX, element.getTicksX);
		controller.registerProperty("TicksY", element.setTicksY, element.getTicksY);
		controller.registerProperty("TickStepX", element.setTickStepX, element.getTickStepX);
		controller.registerProperty("TickStepY", element.setTickStepY, element.getTickStepY);
		controller.registerProperty("TicksXMode", element.setTicksXMode, element.getTicksXMode);
		controller.registerProperty("TicksYMode", element.setTicksYMode, element.getTicksYMode);

		controller.registerProperty("ShowX", element.setShowX, element.getShowX);
		controller.registerProperty("ShowY", element.setShowY, element.getShowY);

		controller.registerProperty("FixedTickX", element.setFixedTickX, element.getFixedTickX);				
		controller.registerProperty("FixedTickY", element.setFixedTickY, element.getFixedTickY);				
		controller.registerProperty("ScaleX", element.setScaleX, element.getScaleX);				
		controller.registerProperty("ScaleY", element.setScaleY, element.getScaleY);				
		
		controller.registerProperty("ScalePrecisionX", element.setScalePrecisionX, element.getScalePrecisionX);		
		controller.registerProperty("ScalePrecisionY", element.setScalePrecisionY, element.getScalePrecisionY);		
		
		// auto ticks
		controller.registerProperty("AutoTicksX", element.setAutoTicksX, element.getAutoTicksX);				
		controller.registerProperty("AutoTicksY", element.setAutoTicksY, element.getAutoTicksY);				
		controller.registerProperty("AutoStepXMin", element.setAutoStepXMin, element.getAutoStepXMin);				
		controller.registerProperty("AutoStepYMin", element.setAutoStepYMin, element.getAutoStepYMin);				
		controller.registerProperty("AutoTicksXRange", element.setAutoTicksXRange, element.getAutoTicksXRange);				
		controller.registerProperty("AutoTicksYRange", element.setAutoTicksYRange, element.getAutoTicksYRange);
		
		// style
		controller.registerProperty("LineColorX", element.setLineColorX, element.getLineColorX);
		controller.registerProperty("LineWidthX", element.setLineWidthX, element.getLineWidthX);
		controller.registerProperty("ShapeRenderingX", element.setShapeRenderingX, element.getShapeRenderingX);
		controller.registerProperty("LineColorY", element.setLineColorY, element.getLineColorY);
		controller.registerProperty("LineWidthY", element.setLineWidthY, element.getLineWidthY);
		controller.registerProperty("ShapeRenderingY", element.setShapeRenderingY, element.getShapeRenderingY);						
	}
			
};

/**
 * Creates a 2D Grid
 * @method grid
 */
EJSS_DRAWING2D.grid = function(name) {
	var self = EJSS_DRAWING2D.element(name);
 
  	// drawing priority: mAutoTicks - mTicks - mStep
 	var mAutoTicksX = true;			// auto-ticks in X
 	var mAutoTicksY = true;			// auto-ticks in Y
 	var mTicksX = 0;				// number of ticks in X
 	var mTicksY = 0;				// number of ticks in Y
 	var mStepX = 0;					// step between ticks in pixels in X
 	var mStepY = 0;					// step between ticks in pixels in Y
    var mTickStepX = 0;
    var mTickStepY = 0;
	var mShowX = true;				// show X
	var mShowY = true;				// show Y

	var mFixedTickX = Number.NaN;			// ticks fixed in axis X	
	var mFixedTickY = Number.NaN;			// ticks fixed in axis Y	
	var mScaleX = [-1,1];			// X axis scale
	var mScaleY = [-1,1];			// Y axis scale
	var mScalePrecisionX = 1;		// number of decimals X
	var mScalePrecisionY = 1;		// number of decimals Y
	
	// ticks properties
 	var mTicksXMode = EJSS_DRAWING2D.Grid.SCALE_NUM;		// X axis scale 
 	var mTicksYMode = EJSS_DRAWING2D.Grid.SCALE_NUM;		// Y axis scale
 
	// auto ticks 	
	var mAutoStepXMin = 40;		// step x minimun in pixels
	var mAutoStepYMin = 40;		// step y minimun in pixels
	var mAutoTicksXRange = [5,10,20];		// ticks x range
	var mAutoTicksYRange = [5,10,20];		// ticks y range
											//   is the step minimun possible in mAutoTicksRange[length-1]?
											//   and in mAutoTicksRange[length-2]? ... 
											//   then mAutoTicksRange[length-2] is the number of ticks   

	// styles
	var mLineColorX = 'black';
	var mLineWidthX = 0.5;
	var mShapeRenderingX = EJSS_DRAWING2D.Style.RENDER_AUTO;
	var mLineColorY = 'black';
	var mLineWidthY = 0.5;
	var mShapeRenderingY = EJSS_DRAWING2D.Style.RENDER_AUTO;

	self.getClass = function() {
		return "ElementGrid";
	}

	/** 
	 * @method setAutoTicksRangeY
	 * @param range
	 */
	self.setAutoTicksRangeY = function (range) {
	  	if(mAutoTicksYRange != range) {
	  		mAutoTicksYRange = range;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getAutoTicksRangeY
	 * @return
	 */
	self.getAutoTicksRangeY = function() { 
		return mAutoTicksYRange; 
	}

	/** 
	 * @method setAutoTicksRangeX
	 * @param range
	 */
	self.setAutoTicksRangeX = function (range) {
	  	if(mAutoTicksXRange != range) {
	  		mAutoTicksXRange = range;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getAutoTicksRangeX
	 * @return
	 */
	self.getAutoTicksRangeX = function() { 
		return mAutoTicksXRange; 
	}

	/** 
	 * @method setAutoStepYMin
	 * @param min
	 */
	self.setAutoStepYMin = function (min) {
	  	if(mAutoStepYMin != min) {
	  		mAutoStepYMin = min;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getAutoStepYMin
	 * @return
	 */
	self.getAutoStepYMin = function() { 
		return mAutoStepYMin; 
	}

	/** 
	 * @method setAutoStepXMin
	 * @param min
	 */
	self.setAutoStepXMin = function (min) {
	  	if(mAutoStepXMin != min) {
	  		mAutoStepXMin = min;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getAutoStepXMin
	 * @return
	 */
	self.getAutoStepXMin = function() { 
		return mAutoStepXMin; 
	}

	/** 
	 * @method setScaleY
	 * @param scale
	 */
	self.setScaleY = function (scale) {
	  	if(mScaleY != scale) {
	  		mScaleY = scale;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getScaleY
	 * @return
	 */
	self.getScaleY = function() { 
		return mScaleY; 
	}
	
	/** 
	 * @method setScaleX
	 * @param scale
	 */
	self.setScaleX = function (scale) {
	  	if(mScaleX != scale) {
	  		mScaleX = scale;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getScaleX
	 * @return
	 */
	self.getScaleX = function() { 
		return mScaleX; 
	}
	
	/** 
	 * @method setScalePrecisionX
	 * @param scalePrecisionX
	 */
	self.setScalePrecisionX = function (scalePrecisionX) {
		if(mScalePrecisionX != scalePrecisionX) {
	  		mScalePrecisionX = scalePrecisionX;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getScalePrecisionX
	 * @return
	 */
	self.getScalePrecisionX = function() { 
		return mScalePrecisionX; 
	}

	/** 
	 * @method setScalePrecisionY
	 * @param scalePrecisionY
	 */
	self.setScalePrecisionY = function (scalePrecisionY) {
		if(mScalePrecisionY != scalePrecisionY) {
	  		mScalePrecisionY = scalePrecisionY;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getScalePrecisionY
	 * @return
	 */
	self.getScalePrecisionY = function() { 
		return mScalePrecisionY; 
	}

	/** 
	 * @method setFixedTickX
	 * @param fixed
	 */
	self.setFixedTickX = function (fixed) {
	  	if(mFixedTickX != fixed) {
	  		mFixedTickX = fixed;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getFixedTickX
	 * @return
	 */
	self.getFixedTickX = function() { 
		return mFixedTickX; 
	}

	/** 
	 * @method setFixedTickY
	 * @param fixed
	 */
	self.setFixedTickY = function (fixed) {
	  	if(mFixedTickY != fixed) {
	  		mFixedTickY = fixed;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getFixedTickY
	 * @return
	 */
	self.getFixedTickY = function() { 
		return mFixedTickY; 
	}
			
	/** 
	 * @method setAutoTicksX
	 * @param auto
	 */
	self.setAutoTicksX = function (auto) {
	  	if(mAutoTicksX != auto) {
	  		mAutoTicksX = auto;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getAutoTicksX
	 * @return
	 */
	self.getAutoTicksX = function() { 
		return mAutoTicksX; 
	}

	/** 
	 * @method setAutoTicksY
	 * @param auto
	 */
	self.setAutoTicksY = function (auto) {
	  	if(mAutoTicksY != auto) {
	  		mAutoTicksY = auto;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getAutoTicksY
	 * @return
	 */
	self.getAutoTicksY = function() { 
		return mAutoTicksY; 
	}
	 
	/** 
	 * @method setStepX
	 * @param stetX
	 */
	self.setStepX = function (stepX) {
	  	if(mStepX != stepX) {
	  		mStepX = stepX;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getStepX
	 * @return
	 */
	self.getStepX = function() { 
		return mStepX; 
	}

	/** 
	 * @method setStepY
	 * @param stetY
	 */
	self.setStepY = function (stepY) {
		if(mStepY != stepY) {
	  		mStepY = stepY;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getStepY
	 * @return
	 */
	self.getStepY = function() { 
		return mStepY; 
	}

	/** 
	 * @method setTickStepX
	 * @param stetX
	 */
	self.setTickStepX = function (TickStepX) {
	  	if(mTickStepX != TickStepX) {
	  		mTickStepX = TickStepX;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getTickStepX
	 * @return
	 */
	self.getTickStepX = function() { 
		return mTickStepX; 
	}

	/** 
	 * @method setTickStepY
	 * @param stetY
	 */
	self.setTickStepY = function (TickStepY) {
		if(mTickStepY != TickStepY) {
	  		mTickStepY = TickStepY;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getTickStepY
	 * @return
	 */
	self.getTickStepY = function() { 
		return mTickStepY; 
	}
	
	/** 
	 * @method setTicksX
	 * @param stetX
	 */
	self.setTicksX = function (ticksX) {
		if(mTicksX != ticksX) {			
	  		mTicksX = ticksX;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getTicksX
	 * @return
	 */
	self.getTicksX = function() { 
		return mTicksX; 
	}

	/** 
	 * @method setTicksY
	 * @param stetY
	 */
	self.setTicksY = function (ticksY) {
	  	if(mTicksY != ticksY) {
	  		mTicksY = ticksY;
	  		self.setChanged(true);
	  	}
	}
	  	  
	/**
	 * @method getTicksY
	 * @return
	 */
	self.getTicksY = function() { 
		return mTicksY; 
	}

	/** 
	 * @method setTicksXMode
	 * @param ticksXMode
	 */
	self.setTicksXMode = function (ticksXMode) {
    	if (typeof ticksXMode == "string") ticksXMode = EJSS_DRAWING2D.Grid[ticksXMode.toUpperCase()];
    	if(mTicksXMode != ticksXMode) {
    		mTicksXMode = ticksXMode;	
    		self.setChanged(true);
    	}
	}
	  
	/**
	 * @method getTicksXMode
	 * @return
	 */
	self.getTicksXMode = function() { 
		return mTicksXMode; 
	}

	/** 
	 * @method setTicksYMode
	 * @param ticksYMode
	 */
	self.setTicksYMode = function (ticksYMode) {
    	if (typeof ticksYMode == "string") ticksXMode = EJSS_DRAWING2D.Grid[ticksYMode.toUpperCase()];
    	if(mTicksYMode != ticksYMode) {
    		mTicksYMode = ticksYMode;
    		self.setChanged(true);
    	}	
	}

	/** 
	 * @method setShowX
	 * @param show
	 */
	self.setShowX = function (show) {
	  	if(mShowX != show) {
	  		mShowX = show;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getShowX
	 * @return
	 */
	self.getShowX = function() { 
		return mShowX; 
	}

	/** 
	 * @method setShowY
	 * @param show
	 */
	self.setShowY = function (show) {
	  	if(mShowY != show) {
	  		mShowY = show;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getShowY
	 * @return
	 */
	self.getShowY = function() { 
		return mShowY; 
	}
	  
	/**
	 * @method getTicksYMode
	 * @return
	 */
	self.getTicksYMode = function() { 
		return mTicksYMode; 
	}

	/**
	* Set the line color of the element
	* @param color a stroke style
	*/
  	self.setLineColorX = function(color) { 
	    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
	    if (color!=mLineColorX) {
	      mLineColorX = color; 
		  self.setChanged(true);
	    }
	    return self;
  	};
    
	/**
	* Get the line color
	*/
	self.getLineColorX = function() { 
    	return mLineColorX; 
  	};

	/**
	* Set the line width of the element
	* @param width a stroke width (may be double, such as 0.5, the default)
	*/
	self.setLineWidthX = function(width) { 
	    if (width!=mLineWidthX) {
	      mLineWidthX = width; 
		  self.setChanged(true);
	    }
	};

	/**
	* Get the line width
	*/
  	self.getLineWidthX = function() { 
  		return mLineWidthX; 
  	};

	/**
	* Sets shape rendering
	*/
	self.setShapeRenderingX = function(rendering) {
	    if (rendering.substring(0,6) == "RENDER") rendering = EJSS_DRAWING2D.Style[rendering.toUpperCase()];
	    if (mShapeRenderingX != rendering) {
	      mShapeRenderingX = rendering;
		  self.setChanged(true);
	    }
	};
  
  	self.getShapeRenderingX = function() { 
    	return mShapeRenderingX;
  	};

	/**
	* Set the line color of the element
	* @param color a stroke style
	*/
  	self.setLineColorY = function(color) { 
	    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
	    if (color!=mLineColorY) {
	      mLineColorY = color; 
		  self.setChanged(true);
	    }
	    return self;
  	};
    
	/**
	* Get the line color
	*/
	self.getLineColorY = function() { 
    	return mLineColorY; 
  	};

	/**
	* Set the line width of the element
	* @param width a stroke width (may be double, such as 0.5, the default)
	*/
	self.setLineWidthY = function(width) { 
	    if (width!=mLineWidthY) {
	      mLineWidthY = width; 
		  self.setChanged(true);
	    }
	};

	/**
	* Get the line width
	*/
  	self.getLineWidthY = function() { 
  		return mLineWidthY; 
  	};

	/**
	* Sets shape rendering
	*/
	self.setShapeRenderingY = function(rendering) {
	    if (rendering.substring(0,6) == "RENDER") rendering = EJSS_DRAWING2D.Style[rendering.toUpperCase()];
	    if (mShapeRenderingY != rendering) {
	      mShapeRenderingY = rendering;
		  self.setChanged(true);
	    }
	};
  
  	self.getShapeRenderingY = function() { 
    	return mShapeRenderingY;
  	};

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.Grid.registerProperties(self, controller);
	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Group
 * @class Group 
 * @constructor  
 */
EJSS_DRAWING2D.Group = {

};

/**
 * Creates a group
 * @method group
 */
EJSS_DRAWING2D.group = function (name) {
  var self = EJSS_DRAWING2D.element(name);
  
//  var mChildren = [];

  self.getClass = function() {
  	return "ElementGroup";
  }
  
  self.isGroup = function() {
  	return true;
  }
  
//  self.addChild = function(child) {
//	  EJSS_TOOLS.addToArray(mChildren,child);
//  }
//  
//  self.removeChild = function(child) {
//	  EJSS_TOOLS.removeFromArray(mChildren,child);
//  }
//  
//  self.superSetChanged = self.setChanged;
//  
//  self.setChanged = function(changed) {
//	self.superSetChanged(changed);
//	if (changed) for (var i=0,n=mChildren.length; i<n; i++) mChildren[i].setChanged(changed);
//  }
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//GroupSet
//---------------------------------

/**
 * GroupSet
 * @class GroupSet 
 * @constructor  
 */
EJSS_DRAWING2D.GroupSet = {
    
};


/**
 * Creates a set of Segments
 * @method groupSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.groupSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.group, mName);

  return self;
};
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A Histogram displays a diagram of frequencies of data
 * @class EJSS_DRAWING2D.Histogram 
 * @parent EJSS_DRAWING2D.Element
 * @constructor  
 */
EJSS_DRAWING2D.Histogram = {

	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy  	
  		
		dest.setInput(source.getInput());
		dest.setEnabled(source.getEnabled());
		dest.setOccurrences(source.getOccurrences());
		dest.setClearAtInput(source.getClearAtInput());
		dest.setDiscrete(source.getDiscrete());
		dest.setNormalized(source.getNormalized());
		dest.setBinWidth(source.getBinWidth());
  	},
  	
  	
	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class
		
	 /*** 
	  * The input value to append
	  * @property PixelPosition 
	  * @type double or double[]
	  * @default false
	  */      		
	 	controller.registerProperty("Input", element.addInput);

	 /*** 
	  * Whether the histogram actually accepts input values
	  * @property Active 
	  * @type boolean
	  * @default true
	  */      		
	 	controller.registerProperty("Active", element.setActive, element.getActive);

	 /*** 
	  * The number of the occurrences of the input
	  * @property Occurrences 
	  * @type int or double
	  * @default 1
	  */      		
	 	controller.registerProperty("Occurrences", element.setOccurrences, element.getOccurrences);

	 /*** 
	  * Whether to clear old data whenever new data gets in
	  * @property ClearAtInput 
	  * @type boolean
	  * @default false
	  */      		
	 	controller.registerProperty("ClearAtInput", element.setClearAtInput, element.getClearAtInput);

	 /*** 
	  * Whether the bins are discrete or continuous. A discrete bin looks like a thin vertical line, 
	  * a continuous one looks like a bar with the width of the bin
	  * @property Discrete 
	  * @type boolean
	  * @default false
	  */      		
	 	controller.registerProperty("Discrete", element.setDiscrete, element.getDiscrete);

	 /*** 
	  * Whether the data are normalized to one
	  * @property Normalized 
	  * @type boolean
	  * @default false
	  */      		
		controller.registerProperty("Normalized", element.setNormalized, element.getNormalized);

	 /*** 
	  * The width of the bins. This is the maximum distance that makes two inputs contribute 
	  * to the same bin height. As an example, is the with is 0.5, the inputs 1.1 and 1.4 
	  * will add their occurrences to the same bin
	  * @property BinWidth 
	  * @type int or double
	  * @default 1
	  */      		
	 	controller.registerProperty("BinWidth", element.setBinWidth, element.getBinWidth);

	 /*** 
	  * Fix bin in the input 
	  * @property FixBin 
	  * @type double
	  * @default 0
	  */      		
	 	controller.registerProperty("FixBin", element.setFixBin, element.getFixBin);
	 	
	}
};

/**
 * Creates a 2D Histogram
 * @method Histogram
 */
EJSS_DRAWING2D.histogram = function(mName) {
	var self = EJSS_DRAWING2D.element(mName);
 
    // Instance variables
	var mCurrentInput = [];
	var mActive = true;
	var mOccurrences = 1;
	var mClearAtInput = false;
	var mDiscrete = false;
    var mNormalized = false;    
    var mBinWidth = 1;
    var mFixBin = 0;
    var mBars = null;
		 
	self.getClass = function() {
		return "ElementHistogram";
	}

  /**
   * Makes it a collector for the view
   * @method dataCollected()
   * @see EJSS_CORE.view
   */
  self.dataCollected  = function() { }  
  
	self.getInput = function() { 
	  	return mCurrentInput; 
	};

	self.addInput = function(input) {
	    if (!mActive) return;
	  	if (mClearAtInput) self.clear();
	  	for (var i=0; i<mOccurrences; i++) {
			if(input instanceof Array) mCurrentInput = mCurrentInput.concat(input);  		
			else mCurrentInput.push(input);
		}
		mBars = null;
	    self.setChanged(true);
	};

	self.clear = function() {
		mCurrentInput = [];
		mBars = null;
		self.setChanged(true);
	}

	self.getClearAtInput = function() { 
	 	return mClearAtInput; 
	}

	self.setClearAtInput = function(clear) {
		if(mClearAtInput != clear) {
	   		mClearAtInput = clear;
	  		self.setChanged(true);
		}
	}

	self.getActive = function() { 
	 	return mActive; 
	}

	self.setActive = function(active) {
		if(mActive != active) {
	   		mActive = active;
	  		self.setChanged(true);
		}
	}

	self.getOccurrences = function() { 
	 	return mOccurrences; 
	}

	self.setOccurrences = function(occurrences) {
		if(mOccurrences != occurrences) {
	   		mOccurrences = occurrences;
	  		self.setChanged(true);
		}
	}

	self.getDiscrete = function() { 
	 	return mDiscrete; 
	}

	self.setDiscrete = function(discrete) {
		if(mDiscrete != discrete) {
	   		mDiscrete = discrete;
	  		self.setChanged(true);
		}
	}
	  
	self.getNormalized = function() { 
	  	return mNormalized; 
	};

	self.setNormalized = function(normalized) {
	  	if(mNormalized != normalized) {
	  		mNormalized = normalized;
	  		mBars = null;
	  		self.setChanged(true);
	  	}
	};

	self.getBinWidth = function() {
	  	return mBinWidth;
	};

	self.setBinWidth = function(width) {
	  	if(mBinWidth != width) {
	  		mBinWidth = width;
	  		mBars = null;
	  		self.setChanged(true);
	  	}
	};

	self.getFixBin = function() {
	  	return mFixBin;
	};

	self.setFixBin = function(fixbin) {
	  	if(mFixBin != fixbin) {
	  		mFixBin = fixbin;
	  		mBars = null;
	  		self.setChanged(true);
	  	}
	};
		
	/**
	 * Computes the histogram as a sequence of [x,height]
	 */      
    self.getBars = function() {
      if (mBars!=null) return mBars;
      if (mCurrentInput.length==0) {
        mBars = [[0,0]];
        return mBars;
      }
      
	  mBars = [];
	  mCurrentInput = mCurrentInput.sort(function(a, b){return a-b});
      // minimum bin
	  var minbin;
	  var halfBin = mBinWidth/2;
	
	  if (mBinWidth == 0) minbin = mCurrentInput[0];
	  else if (mFixBin-halfBin > mCurrentInput[0]) minbin = mFixBin - (Math.floor((mFixBin-halfBin-mCurrentInput[0])/mBinWidth) + 1) * mBinWidth;
	  else if (mFixBin+halfBin < mCurrentInput[0]) minbin = mFixBin + (Math.floor((mCurrentInput[0]-mFixBin-halfBin)/mBinWidth)    ) * mBinWidth;
	  else minbin = mFixBin;
		
		//console.log ("inputs = "+input);
		//console.log ("minbin = "+minbin);
      // iterator
	  var cbin = minbin;	
	  var count = 0;
	  var nInput = mCurrentInput.length;
	
      for (var j=0; j < nInput; j++) {
	    if (mCurrentInput[j] <= cbin + halfBin) count ++
	    else {
		  if (count > 0) {
			if (mNormalized) count /= nInput;
			mBars.push([cbin,count]);
		  }
		  if (mBinWidth == 0) cbin = mCurrentInput[j];
		  else cbin = cbin + (Math.floor((mCurrentInput[j]-cbin-halfBin)/mBinWidth) + 1) * mBinWidth;
		  count = 1;
	    }
      }
      // last bar
      if (count > 0) {
		if (mNormalized) count /= nInput;
		mBars.push([cbin,count]);
	  }
	  return mBars;
	}
	     
	/**
	 * Returns bounds for an element
	 * @override
	 * @method getBounds
	 * @return Object{left,rigth,top,bottom}
	 */
	self.getBounds = function(element) {
	  	self.getBars();
	  	var nBars = mBars.length;
		var xmin = mBars[0][0]; 
		var xmax = mBars[nBars-1][0];
		var ymin = 0; 
		var ymax = 0;
		for (var j=0; j<nBars; j++) ymax = Math.max(ymax,mBars[j][1]);

	    var x = self.getX(), y = self.getY();
	    var sx = self.getSizeX(), sy = self.getSizeY();
	  	var mx = sx/2, my = sy/2;  	
	  	var d = self.getRelativePositionOffset(sx,sy);
	  	
		return {
			left: ((x+d[0])-mx)+xmin*sx,
			right: ((x+d[0])-mx)+xmax*sx,
			top: ((y+d[1])-my)+ymax*sy,
			bottom: ((y+d[1])-my)+ymin*sy
		}
	};  
	
	/**
	 * Returns bounds for an element
	 * @override
	 * @method getBounds
	 * @return Object{left,rigth,top,bottom}
	 */
	self.getBoundsOld = function(element) {
	  	var xmin, xmax, ymin, ymax;
	  	  	
	    var len = mCurrentInput.length;
	    if(len == 0) {
	    	xmin = xmax = ymin = ymax = 0;
	    }             
	    else {
	    	// order and get min and max
	    	console.log ("mCurrentInput="+mCurrentInput);
	    	var ordered = mCurrentInput.sort(function(a, b){return a-b});
	    	var nInput = ordered.length;
			xmin = ordered[0]; xmax = ordered[nInput-1];
			
			// count elements
			ymax = 0; ymin = 0;    	
			var counts = [];
			ordered.forEach(function(x) { counts[x] = (counts[x] || 0)+1; });
			if (mNormalized) {
			  for(var y in counts) {
			    counts[y] /= nInput;
			  }
			} 
			 
			for(var y in counts) { 
				if(counts[y]>ymax) ymax = counts[y]; 
			}									
		}    
	    var x = self.getX(), y = self.getY();
	    var sx = self.getSizeX(), sy = self.getSizeY();
	  	var mx = sx/2, my = sy/2;  	
	  	var d = self.getRelativePositionOffset(sx,sy);
	  	
		return {
			left: ((x+d[0])-mx)+xmin*sx,
			right: ((x+d[0])-mx)+xmax*sx,
			top: ((y+d[1])-my)+ymax*sy,
			bottom: ((y+d[1])-my)+ymin*sy
		}
	};  
              
	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.Histogram.registerProperties(self, controller);
	};
  
  	self.copyTo = function(element) {
    	EJSS_DRAWING2D.Histogram.copyTo(self,element);
  	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	self.getStyle().setFillColor("Blue");
	self.getStyle().setLineColor("Black");
  	self.setRelativePosition("SOUTH_WEST");

	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Image
 * @class Image 
 * @constructor  
 */
EJSS_DRAWING2D.Image = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy
  	
		dest.setImageUrl(source.getImageUrl());
  	},


    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

      controller.registerProperty("ImageUrl",element.setImageUrl);
      controller.registerProperty("Encode",element.setEncode);
    },
};

/**
 * Creates a 2D Segment
 * @method image
 */
EJSS_DRAWING2D.image = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  var mUrl = "";		// image url
  var mCode = "";
  var mChangedImage = false;	// indica si la fuente de la imagen ha cambiado 

  self.getClass = function() {
  	return "ElementImage";
  };

  self.setImageUrl = function(url) {
    var set = self.getSet(); 
  	var resPathFunction = (set!=null) ? set.getResourcePath : self.getResourcePath; 
  	if (resPathFunction!=null) {
  	  url = resPathFunction(url);
  	  //alert ("ImageUrl set to = "+url+"\n");
  	}
  	else console.log ("No getResourcePath function for "+self.getName()+". Texture = "+url);
  	if(mUrl != url) {
  		mUrl = url;
  		self.setChanged(true);
  		self.setChangedImage(true);
  	}
  }	;

  self.getImageUrl = function() {
  	return mUrl;
  };

  self.forceImageUrl = function(url) {
    console.log ("Setting image to " +url);
    mUrl = window.URL.createObjectURL(url);
  	self.setChanged(true);
  	self.setChangedImage(true);
  };
  
  self.setChangedImage = function(changed) {
  	mChangedImage = changed;
  };

  self.getChangedImage = function() {
  	return mChangedImage;
  };
  
  self.setEncode = function(code) {
  	if(mCode.length != code.length) { // less cost
  		mCode = code;
  		self.setChanged(true);
  		self.setChangedImage(true);
  	}
  };

  self.getEncode = function() {
  	return mCode;
  };
  
  self.getImageData = function(callback) {
    var size = self.getPixelSizes();     

  	var img = new Image();
  	img.src = mUrl;
  	
  	var canvas = document.createElement('canvas');
	var context = canvas.getContext('2d');
	canvas.width = Math.abs(size[0])+1;
	canvas.height = Math.abs(size[1])+1;
	
	if (callback) {
	  	img.onload = function() {
			context.drawImage(img,  
			  		0, 0, img.width, img.height, 
			        0, 0, canvas.width, canvas.height);
		  	callback(context.getImageData(0, 0, canvas.width, canvas.height));
		}
  	} else {
		context.drawImage(img,  
		  		0, 0, img.width, img.height, 
		        0, 0, canvas.width, canvas.height);
		return context.getImageData(0, 0, canvas.width, canvas.height);  		
  	}
  }

  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Image.registerProperties(self,controller);
  };
  
  self.copyTo = function(element) {
    EJSS_DRAWING2D.Image.copyTo(self,element);
  };
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//ImageSet
//---------------------------------

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * ImageSet
 * @class ImageSet 
 * @constructor  
 */
EJSS_DRAWING2D.ImageSet = {
    
    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING2D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      
      controller.registerProperty("ImageUrl", 
          function(v) { set.setToEach(function(element,value) { element.setImageUrl(value); }, v); }
      );
    }    
};


/**
 * Creates a set of Segments
 * @method imageSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.imageSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.image, mName);

  // Static references
  var ImageSet = EJSS_DRAWING2D.ImageSet;		// reference for ImageSet

  /**
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   */
  self.registerProperties = function(controller) {
    ImageSet.registerProperties(self,controller);
  };

  return self;
};/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas analyticCurve
 */
EJSS_CANVASGRAPHICS.analyticCurve = function(mContext, mElement) {  

	// draw analyticCurve
	function drawAnalyticCurve(points, x, y, sx, sy) {
		mContext.beginPath();
		for(var i=0; i<points.length; i++) {
		  var point = points[i];	  
	      var xx = x + point[0]*sx;
	      var yy = y + point[1]*sy;
	      var type = point[2];		
		  if ((i==0) || (type == 0))
		  	mContext.moveTo(xx,yy);		// move 
	      else       	
	      	mContext.lineTo(xx,yy);		// line
		}  	  	
	}  	

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

	// calculate points	    	 
	var numPoints = mElement.getNumPoints();
	var mMinimun = mElement.getMinimun();
	var mMaximun = mElement.getMaximun();
		var min = ( (typeof mMinimun == "undefined" || mMinimun === null) ?  mElement.getPanel().getRealWorldXMin() : mMinimun);
		var max = ( (typeof mMaximun == "undefined" || mMaximun === null) ?  mElement.getPanel().getRealWorldXMax() : mMaximun);
	
//	var min = (mElement.getMinimun()? mElement.getMinimun() : mElement.getPanel().getRealWorldXMin());
//	var max = (mElement.getMaximun()? mElement.getMaximun() : mElement.getPanel().getRealWorldXMax());
	var vble = mElement.getVariable();
	var fx = mElement.getFunctionX();
	var fy = mElement.getFunctionY();	
   	var parser = EJSS_DRAWING2D.functionsParser();
   	
    var exprfx;
	var exprfy;
	var mustReturn = false;
	try {
	  exprfx = parser.parse(fx);
	}
	catch (errorfx) {
  	  console.log ("Analytic curve error parsing FunctionX: "+fx);
	  mustReturn = true;
	}
	if (!mustReturn) {
	  try {
	    exprfy = parser.parse(fy);
	  }
	  catch (errorfy) {
  	    console.log ("Analytic curve error parsing FunctionY: "+fy);
	   	mustReturn = true;
	  }
	}
	if (mustReturn) {
	  mElement.getController().invokeAction("OnError");
	  return;
	}
	
   	var step = (max-min)/(numPoints-1);
   	
	var points = [];
    var vblevalue = {};
    var parameters = mElement.getParameters();
    for (var param in parameters) { 
      vblevalue[param] = parameters[param];
	 }
    
   	try {
   	  for(var j=0, i=min; i<=max; i+=step) {
   		vblevalue[vble] = i;
   		  var fxvalue = exprfx.evaluate(vblevalue);
   		  var fyvalue = exprfy.evaluate(vblevalue);
	   	  if(!isNaN(fxvalue) && !isNaN(fyvalue)) {   		
	   		points[j] = [];
	    	points[j][0] = fxvalue;	
			points[j++][1] = fyvalue;
		  }
		}
	}
	catch(error) {
	  mElement.getController().invokeAction("OnError");
	 } // do not complain
	
	// draw points
    drawAnalyticCurve(points, x-mx, y-my, size[0], size[1])     

	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas arrow
 */
EJSS_CANVASGRAPHICS.arrow = function(mContext, mElement) {  
	var Arrow = EJSS_DRAWING2D.Arrow;

	function createMarkDef(px, py, mark, width, height, stroke, color, sizex, sizey) {
		mContext.save();	
		// measure
		width /= 2;
		height /= 2;
		var angle = (sizex==0)? Math.PI/2 : Math.atan(sizey/sizex);
		angle += ((sizex < 0)?-Math.PI/2:Math.PI/2);
		switch(mark) {
			case Arrow.TRIANGLE:
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.rotate(angle);
	            mContext.moveTo(0,height/2);
	            mContext.lineTo(width,height/2);
	            mContext.lineTo(0,-height/2);
	            mContext.lineTo(-width,height/2);
	            mContext.closePath();           
				break;			
			case Arrow.ANGLE:
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.rotate(angle);
	            mContext.moveTo(width,height);
	            mContext.lineTo(0,0);
	            mContext.lineTo(-width,height);
				break;			
			case Arrow.LINE:
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.rotate(angle);
	            mContext.moveTo(width,0);
	            mContext.lineTo(-width,0);
				break;			
			case Arrow.RECTANGLE:
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.rotate(angle);
	            mContext.moveTo(0,-height);
	            mContext.lineTo(width,-height);
	            mContext.lineTo(width,height);
	            mContext.lineTo(-width,height);
	            mContext.lineTo(-width,-height);
	            mContext.closePath();
				break;			
			case Arrow.POINTED:
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.rotate(angle);
	            mContext.moveTo(0,height/4);
	            mContext.lineTo(width,height);
	            mContext.lineTo(0,-height/4);
	            mContext.lineTo(-width,height);
	            mContext.closePath();   	            	       
				break;
			case Arrow.CIRCLE:
				var size = (width < height) ? width : height;
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.arc(0, 0, size, 0, 2 * Math.PI, false);	            
				break;
			case Arrow.DIAMOND:
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.rotate(angle);
	            mContext.moveTo(0,-height);
	            mContext.lineTo(width,0);
	            mContext.lineTo(0,height);
	            mContext.lineTo(-width,0);
	            mContext.closePath();
				break;				
			case Arrow.INVTRIANGLE:					
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.rotate(angle);
	            mContext.moveTo(0,-height/2);
	            mContext.lineTo(-width,-height/2);
	            mContext.lineTo(0,height/2);
	            mContext.lineTo(width,-height/2);
	            mContext.closePath();           
				break;			
			case Arrow.INVANGLE:
	            mContext.beginPath();
	            mContext.translate(px,py);
	            mContext.rotate(angle);
	            mContext.moveTo(-width,-height);
	            mContext.lineTo(0,0);
	            mContext.lineTo(width,-height);
				break;	
										
			case Arrow.WEDGE:
			case Arrow.CURVE:
				console.log('Type of Arrow: not supported');
				break;								
		}		
		
		// set style
		if (color != 'none') {
			if(mark != Arrow.ANGLE && mark != Arrow.INVANGLE) {
				mContext.fillStyle = color;
		    	mContext.fill();
			}
			if(mark != Arrow.POINTED) {
		      	mContext.lineWidth = stroke;
			    mContext.strokeStyle = color;
			    mContext.stroke();		
			}
	    }
	    mContext.restore();
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

    // draw
	if((size[0] != 0) || (size[1] != 0)) {
		// draw line
	  	mContext.beginPath();
		mContext.moveTo(x-mx, y-my);
	    mContext.lineTo(x+mx, y+my);
	
		// set style
	    var style = mElement.getStyle(); 
		if (style.getDrawFill()  && style.getFillColor() != 'none') {
	      mContext.fillStyle = style.getFillColor();
	      mContext.fill();
	    }
	    if (style.getDrawLines()) {
	      mContext.lineWidth = style.getLineWidth();
	      mContext.strokeStyle = style.getLineColor();
	      mContext.stroke();
	    }	

		// create end mark	
		if(mElement.getMarkEnd() != Arrow.NONE) {
			var endMax = (mElement.getMarkProportion() * EJSS_TOOLS.Mathematics.norm([size[0],size[1]])) ;
			var endW = (endMax != 0 && endMax < mElement.getMarkEndWidth()) ? endMax : mElement.getMarkEndWidth();
			var endH = (endMax != 0 && endMax < mElement.getMarkEndHeight()) ? endMax : mElement.getMarkEndHeight();
			var color = (mElement.getMarkEndColor() == "none") ? mElement.getStyle().getLineColor() : mElement.getMarkEndColor();
			var stroke = (mElement.getMarkEndStroke() < 0) ? mElement.getStyle().getLineWidth() : mElement.getMarkEndStroke();   

			// mElement.getMarkEndOrient() is ignored
			createMarkDef(x+mx, y+my, mElement.getMarkEnd(), endW, endH, stroke, color, mx, my);
		}
				
		// create mid mark
		if(mElement.getMarkMiddle() != Arrow.NONE) {
			var midMax = (mElement.getMarkProportion() * EJSS_TOOLS.Mathematics.norm([size[0],size[1]])) ;
			var midW = (midMax != 0 && midMax < mElement.getMarkMiddleWidth()) ? midMax : mElement.getMarkMiddleWidth();
			var midH = (midMax != 0 && midMax < mElement.getMarkMiddleHeight()) ? midMax : mElement.getMarkMiddleHeight();
			var color = (mElement.getMarkMiddleColor() == "none") ? mElement.getStyle().getLineColor() : mElement.getMarkMiddleColor();
			var stroke = (mElement.getMarkMiddleStroke() < 0) ? mElement.getStyle().getLineWidth() : mElement.getMarkMiddleStroke();   

			// mElement.getMarkEndOrient() is ignored
			createMarkDef(x, y, mElement.getMarkMiddle(), midW, midH, stroke, color, mx, my);
		}
		
		// create start mark
		if(mElement.getMarkStart() != Arrow.NONE) {
			var stMax = (mElement.getMarkProportion() * EJSS_TOOLS.Mathematics.norm([size[0],size[1]])) ;
			var stW = (stMax != 0 && stMax < mElement.getMarkStartWidth()) ? stMax : mElement.getMarkStartWidth();
			var stH = (stMax != 0 && stMax < mElement.getMarkStartHeight()) ? stMax : mElement.getMarkStartHeight();
			var color = (mElement.getMarkStartColor() == "none") ? mElement.getStyle().getLineColor() : mElement.getMarkStartColor();
			var stroke = (mElement.getMarkStartStroke() < 0) ? mElement.getStyle().getLineWidth() : mElement.getMarkStartStroke();   

			// mElement.getMarkStartOrient() is ignored
			createMarkDef(x-mx, y-my, mElement.getMarkStart(), stW, stH, stroke, color, mx, my);
		}		    	
	}
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas axis
 */
EJSS_CANVASGRAPHICS.axis = function(mContext, mElement) {  

	// draw axis line
	function drawLine(x, y, mx, my, orient, inverted) {
		if(orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL) {	// vertical axis
			if(inverted) {
				my = -my;
				y = -y;	
			}
			mContext.moveTo(x,y-my);
			mContext.lineTo(x,y+my);
		} else {	// horizontal axis
			mContext.moveTo(x-mx,y);
			mContext.lineTo(x+mx,y);
		}		
	}

	function drawTick(x, y, ticksize, orient) {
		if(orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL) {	// vertical axis
			mContext.moveTo((x-ticksize/2),y);
			mContext.lineTo((x+ticksize/2),y);
		} else {
			mContext.moveTo(x,(y-ticksize/2));
			mContext.lineTo(x,(y+ticksize/2));
		}					
	}
	
	// text for tick in position (x,y) with font and mode
	function tickText (x, y, text, font, horizontal) {
		mContext.save();

	 	var font = mElement.getFont();
	 	var fondtxt = "";
	 	fondtxt += (font.getFontStyle() != 'none')? font.getFontStyle() + ' ':'';
	 	fondtxt += (font.getFontWeight() != 'none')? font.getFontWeight() + ' ':'';
	 	fondtxt += font.getFontSizeString() + 'px ';
	 	fondtxt += font.getFontFamily();
		mContext.font = fondtxt;	
		mContext.fillStyle = font.getFillColor();

		if (horizontal) {
		  x -= mContext.measureText(text).width/2 + 0.5;
		}	
		else { // vertical axis
		  y += font.getFontSize()/2; // - 0.5;
		}

		mContext.fillText(text, x, y);		
				
		mContext.restore();		    
	}		
	
	if(mElement.getShow()) { // show

		// get position of the element center 
	    var pos = mElement.getPixelPosition();
	    var size = mElement.getPixelSizes();     
	    var offset = mElement.getRelativePositionOffset(size);  
	    var x = pos[0]+offset[0];
	    var y = pos[1]+offset[1];
		
		// get sizes 		
	    var mx = size[0]/2;
	    var my = size[1]/2;
				
		// properties	    
	    var style = mElement.getStyle();		// element style   	 	
		var orient = mElement.getOrient();		// axis orientation (vertical or horizontal)
		var inverted = mElement.getInvertedScaleY();
	    
	    // draw the line for axis
	  	mContext.beginPath();
		drawLine(x, y, mx, my, orient, inverted);
		 		
		// get axis size in pixel
		var segsize = (orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL)? Math.abs(size[1]):Math.abs(size[0]);		
		 		
		// draw axis (based on ticks mode)
		var ticksmode = mElement.getTicksMode();			// decimal or logarithmic
		if (ticksmode == EJSS_DRAWING2D.Axis.SCALE_LOG) { // logarithmic

		    var scale = mElement.getScale();		// axis scale 			
			if(scale[0] > 0 && scale[1] > 0) {  // valid scale			
				// get number of axis ticks
				var ticks = mElement.getTicks(); 	
	
			    // scale
				var scalePrecision = mElement.getScalePrecision();	// number of decimals for text
				
				// draw ticks 
				var ticksize = mElement.getTicksSize();				// ticks size in pixels
				EJSS_GRAPHICS.GraphicsUtils.drawLogTicks(x, y, mx, my, segsize, ticksize, scale, ticks, orient, drawTick);		
			
				// draw ticks text 
				var font = mElement.getFont();
			    var textPosition = mElement.getTextPosition();		// text position (UP or DOWN)
				EJSS_GRAPHICS.GraphicsUtils.drawLogTicksText (x, y, mx, my, ticks, ticksize, scale, scalePrecision, font, textPosition, orient, tickText)			
			}
			
		} else if (ticksmode == EJSS_DRAWING2D.Axis.SCALE_NUM) { // decimal
			 			 		
			// calculate step in pixels	
		    var step = 0;     // step for ticks (pixels)
		    var tickstep = 0; // step for ticks (real units)
			if (!mElement.getAutoTicks()) {		// no auto-ticks
		    	var ticks = mElement.getTicks();	// number of ticks
				// whether the number of ticks exits, changes step for ticks and scale 
			    if (ticks != 0) { step = segsize/ticks; } else {
			    	step = mElement.getStep();
			    	tickstep = mElement.getTickStep(); 
			    } 	    	
			} else {	// auto-ticks
				var stepmin = mElement.getAutoStepMin();		// step min in pixels
				var ticksrange = mElement.getAutoTicksRange();	// ticks range
				// find step based on ticks range
				for(var i=ticksrange.length-1; i>=0; i--)	{	
					step = Math.abs(segsize/ticksrange[i]);
					if (step*1.001 >= stepmin) break;
				}
			}
		    
			var scalePrecision = mElement.getScalePrecision();	// number of decimals for text
			var scale = mElement.getScale();		// axis scale 	
			// values for scale
		    if(tickstep == 0) {
				var scalestep = Math.abs((scale[1] - scale[0]) * step / segsize);  // step in axis scale
			} else {
				var scalestep = tickstep;
				step = Math.abs((scalestep * segsize) / (scale[1] - scale[0])); 
			}			
			
			// adjust step to decimals of precision
			var decimals = Math.pow(10,scalePrecision);
			var scalestepTmp = Math.round(scalestep * decimals) / decimals;
			if(scalestepTmp > 0) {
				scalestep = scalestepTmp; 
				step = Math.abs(scalestepTmp * segsize / (scale[1] - scale[0]));
			}
			
			// check fixed tick
			var fixedTicks = mElement.getFixedTick();	  
			var tickfixed = scale[1];
			if (!isNaN(fixedTicks)) {
			  if (fixedTicks < scale[0]) tickfixed = fixedTicks + (Math.floor((scale[0]-fixedTicks)/scalestep)+1)*scalestep;
			  else if (fixedTicks > scale[1])  tickfixed = fixedTicks - (Math.floor((fixedTicks-scale[1])/scalestep)+1)*scalestep;
		      else tickfixed = fixedTicks; 
			}		
	
			// tick fixed in axis scale
			var scaleshift = Math.abs((scale[0] - tickfixed) % scalestep);						
			var dist = Math.abs(scaleshift-scalestep);	// fitting shift
			if(scaleshift < 0.001 || dist < 0.001) scaleshift = 0;						
			var shift = segsize * scaleshift / Math.abs(scale[1] - scale[0]);	// shift in pixels				
										    		
			// draw ticks based on step and shift
			var ticksize = mElement.getTicksSize();				// ticks size in pixels
			EJSS_GRAPHICS.GraphicsUtils.drawDecTicks(x, y, mx, my, ticksize, step, shift, orient, inverted, drawTick);		
		
			// draw ticks text based on scaleshift, scalestep and scale
			var font = mElement.getFont();
		    var textPosition = mElement.getTextPosition();		// text position (UP or DOWN)
			EJSS_GRAPHICS.GraphicsUtils.drawDecTicksText (x, y, mx, my, ticksize, step, shift, scale, scalePrecision, scalestep, scaleshift, font, textPosition, orient, inverted, tickText)
								
		}

		// set style
	    var style = mElement.getStyle(); 
		if (style.getDrawFill() && style.getFillColor() != 'none') {
	      mContext.fillStyle = style.getFillColor();
	      mContext.fill();
	    }
	    if (style.getDrawLines()) {
	      mContext.lineWidth = style.getLineWidth();
	      mContext.strokeStyle = style.getLineColor();
	      mContext.stroke();
	    }						
	}

}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas byteRaster
 */
EJSS_CANVASGRAPHICS.byteRaster = function(mContext, mElement) {
	
	if(mElement.getDataChanged()) { // new data or colors
		var data = mElement.getData();
		var colors = mElement.getColorMapper().getColors();			
		var xlen = data.length;
		var ylen = data[0].length;
		var num = mElement.getColorMapper().getNumberOfColors();

		// get position of the mElement center 
	    var pos = mElement.getPixelPosition();
	    var size = mElement.getPixelSizes();     
	    var offset = mElement.getRelativePositionOffset(size);  
	    var x = pos[0]+offset[0];
	    var y = pos[1]+offset[1];
		
		// get half sizes 		
	    var mx = Math.abs(size[0]/2);
	    var my = Math.abs(size[1]/2);    
		
		var idcanvas = mContext.canvas.id;
		
		// using worker
		EJSS_SVGGRAPHICS.Utils.ImageDataCanvas (xlen, ylen, num, data, colors,
			function(img) {
				// I do not know why the reference mContext is lost
				var canvas = document.getElementById(idcanvas);
			  	mContext = canvas.getContext("2d");
				mContext.putImageData(img,x-mx,y-my);
			});			    	    
	    mElement.setDataChanged(false);

	}

}

/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas canvas
 */
EJSS_CANVASGRAPHICS.canvas = function(mContext, mElement) {  
  
	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	var dim = mElement.getDimensions();
  	var deltaX = dim[1] - dim[0];
  	var deltaY = dim[3] - dim[2];
    
	var pixWth = Math.abs(size[0]);
	var pixHgt = Math.abs(size[1]);
	var pxPerUnitX = pixWth / deltaX;
	var pxPerUnitY = pixHgt / deltaY;
	var pxTopLeftX = x - pixWth / 2;
	var pxTopLeftY = y + pixHgt / 2;
   
    //draws all objects within this.drawables
    var drawables = mElement.getDrawables();
    for (var i = 0; i < drawables.length; i++) {
      if(typeof drawables[i].imageField != "undefined")
        EJSS_GRAPHICS.GraphicsUtils.drawImageField(mContext, 
        	pxTopLeftX, pxTopLeftY, xMin, yMin, pixWth, pixHgt, pxPerUnitX, pxPerUnitY, drawables[i]);
      else if(typeof drawables[i].run != "undefined")
      	drawables[i].draw(mContext, pxTopLeftX, pxTopLeftY, xMin, yMin, pixWth, pixHgt, pxPerUnitX, pxPerUnitY);
    }
      
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas cellLattice
 */
EJSS_CANVASGRAPHICS.cellLattice = function(mContext, mElement) {  

	function drawGrid(left, top, right, bottom, stepx, stepy) {
		mContext.beginPath();
		
		// vertical lines
		if (stepx == 0) stepx = Math.abs(right-left);
	    for (var i = left; i <= right; i = i+stepx) {
	  	  mContext.moveTo(i,top);
	  	  mContext.lineTo(i,bottom); 
	    }
	    // horizontal lines
	    if (stepy == 0) stepy = Math.abs(top-bottom);
	    for (var i = bottom; i >= top; i = i-stepy) {
	  	  mContext.moveTo(left,i);
	  	  mContext.lineTo(right,i); 
	    }
	}  
	
	function rectCell(x, y, sx, sy, fill) {
		mContext.beginPath();
			    		
		var mx = sx/2, my = sy/2;
		mContext.moveTo(x-mx,y+my);
		mContext.lineTo(x+mx,y+my);
		mContext.lineTo(x+mx,y-my);
		mContext.lineTo(x-mx,y-my);
		mContext.lineTo(x-mx,y+my);
		
		mContext.fillStyle = fill;
        mContext.fill();
      	mContext.lineWidth = 1;
     	mContext.strokeStyle = fill;
     	mContext.stroke();
	}

	function gridCell(x, y, sx, sy, stepx, stepy, style) {
		// set attributes
		var mx = sx/2, my = sy/2;    	   	    
		var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
		drawGrid(left, top, right, bottom, stepx, stepy);
						
		// set style
		if (style.getDrawFill() && style.getFillColor() != 'none') {
	      mContext.fillStyle = style.getFillColor();
	      mContext.fill();
	    }
	    if (style.getDrawLines()) {
	      mContext.lineWidth = style.getLineWidth();
	      mContext.strokeStyle = style.getLineColor();
	      mContext.stroke();
	    }	
	}

	// get position of the mElement center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

	// draw cells
	var style = mElement.getStyle();     
	var data = mElement.getData();
	var colors = mElement.getColorMapper().getColors();
	var xlen = data.length;
	var ylen = data[0].length;

    var stepx = Math.abs(size[0]/xlen);
    var stepy = Math.abs(size[1]/ylen);
   	
  	var left = x-mx+stepx/2, bottom = y-my-stepy/2;		
  	for(var i=0; i<ylen; i++) { 
  		for(var j=0; j<xlen; j++) {
  			// draw rectangle
  			rectCell(left+stepx*j, bottom-stepy*i, stepx, stepy, colors[data[j][i]]);
  		}  		
  	}
  	 	
  	// draw grid
  	var showGrid = mElement.getShowGrid();
	if (showGrid) gridCell(x, y, size[0], size[1], stepx, stepy, style);	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas cursor
 */
EJSS_CANVASGRAPHICS.cursor = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    
  	// type of cursor
	var cursorType = mElement.getCursorType();

    // cursor size
    var panel = mElement.getGroupPanel();
    var bounds = panel.getRealWorldCoordinates(); // xmin,xmax,ymin,ymax
	var bottomLeft = panel.toPixelPosition([bounds[0],bounds[2]]);    
	var topRight   = panel.toPixelPosition([bounds[1],bounds[3]]);    

  	mContext.beginPath();	    
	if (cursorType!=EJSS_DRAWING2D.Cursor.VERTICAL) {
	  var x1 = bottomLeft[0], x2 = topRight[0];
	  var y = pos[1];
	  // keep the cursor inside the panel
	  if (y>bottomLeft[1]) y = bottomLeft[1];
	  else if (y<topRight[1]) y = topRight[1];
	  
	  mContext.moveTo(x1,y);
	  mContext.lineTo(x2,y);
	}

	if (cursorType!=EJSS_DRAWING2D.Cursor.HORIZONTAL) {
	  var x = pos[0];
	  var y1 = bottomLeft[1], y2 = topRight[1];
	  // keep the cursor inside the panel
	  if (x<bottomLeft[0]) x = bottomLeft[0];
	  else if (x>topRight[0]) x = topRight[0];
	  
	  mContext.moveTo(x,y1);
	  mContext.lineTo(x,y2);
	}
      
	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas ellipse
 */
EJSS_CANVASGRAPHICS.ellipse = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var xm = pos[0]+offset[0];
    var ym = pos[1]+offset[1];
	
	// get sizes 		
    var w = Math.abs(size[0]);
    var h = Math.abs(size[1]);

    // draw
    var kappa = .5522848,
      // x = x-middle, y = y-middle
      x = xm - w / 2,       // x-middle
      y = ym - h / 2;       // y-middle      
      ox = (w / 2) * kappa, // control point offset horizontal
      oy = (h / 2) * kappa, // control point offset vertical
      xe = x + w,           // x-end
      ye = y + h,           // y-end

  	mContext.beginPath();
  	mContext.moveTo(x, ym);
  	mContext.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  	mContext.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  	mContext.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  	mContext.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
      
	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas grid
 */
EJSS_CANVASGRAPHICS.grid = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

    var showx = mElement.getShowX();
    var showy = mElement.getShowY(); 
    
	// draw axis X (based on ticks mode)
	if(showx) {
		mContext.beginPath();

		var ticksXmode = mElement.getTicksXMode();			// decimal or logarithmic
		if (ticksXmode == EJSS_DRAWING2D.Grid.SCALE_LOG) { // logarithmic
				
		    // scale
		    var scaleX = mElement.getScaleX();		// X axis scale 	
			
			if(scaleX[0] > 0 && scaleX[1] > 0) {  // valid scale
				// get number of axis ticks
			    var ticksX = mElement.getTicksX();
			    
				// set attributes
			    var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
		    	EJSS_GRAPHICS.GraphicsUtils.drawLogGrid(left, top, right, bottom, ticksX, scaleX, 1,
					function(xx,yy) { mContext.moveTo(xx,yy) }, 
					function(xx,yy) { mContext.lineTo(xx,yy) }); 
			} 
					    		
		} else if (ticksXmode == EJSS_DRAWING2D.Grid.SCALE_NUM) { // decimal
	    	    
		 	// create path	    	   	    
		   	var stepx = 0;	// step for ticks
		   	var tickstepx = 0;
			if (!mElement.getAutoTicksX()) {	  // no auto-ticks    
			    var ticksX = mElement.getTicksX();
			    // whether the number of sticks exits, changes step for ticks
			    if (ticksX != 0) { stepx = Math.abs(size[0]/ticksX); } else {
			    	stepx = mElement.getStepX();
			    	tickstepx = mElement.getTickStepX(); 
			    } 	    	

			} else { // auto-ticks
				var stepxmin = mElement.getAutoStepXMin();		// step y min
				var ticksxrange = mElement.getAutoTicksRangeX();		// ticks x range
		
				// find stepx
				for(var i=ticksxrange.length-1; i>=0; i--)	{	
					stepx = Math.abs(size[0]/ticksxrange[i]);
					if (stepx >= stepxmin) break;
				}
			}	
			
		    // step for scale
			var scalePrecisionX = mElement.getScalePrecisionX();	// number of decimals for scale
		    var scaleX = mElement.getScaleX();		// X axis scale 	
		    if(tickstepx == 0) {
				var scalestepX = Math.abs((scaleX[1] - scaleX[0]) * stepx / size[0]);  // step in X axis scale
			} else {
				var scalestepX = tickstepx;
				stepx = Math.abs((scalestepX * size[0]) / (scaleX[1] - scaleX[0])); 
			}			
						
			// adjust step to decimals of precision	
			var decimalsX = Math.pow(10,scalePrecisionX);
			var scalestepXTmp = Math.round(scalestepX * decimalsX) / decimalsX;
			if(scalestepXTmp > 0) {
				scalestepX = scalestepXTmp;
				stepx = Math.abs(scalestepXTmp * size[0] / (scaleX[1] - scaleX[0]));
			}

			// check fixed tick
			var fixedTicks = mElement.getFixedTickX();	  
			var tickfixed = scaleX[1];
			if (!isNaN(fixedTicks)) {
			  if (fixedTicks < scaleX[0]) tickfixed = fixedTicks + (Math.floor((scaleX[0]-fixedTicks)/scalestepX)+1)*scalestepX;
			  else if (fixedTicks > scaleX[1])  tickfixed = fixedTicks - (Math.floor((fixedTicks-scaleX[1])/scalestepX)+1)*scalestepX;
		      else tickfixed = fixedTicks; 
			}		
				
			// tick fixed in axis scale
			var scaleshift = Math.abs((scaleX[0] - tickfixed) % scalestepX);						
			var dist = Math.abs(scaleshift-scalestepX);	// fitting shift
			if(scaleshift < 0.001 || dist < 0.001) scaleshift = 0;						
			var shiftx = Math.abs(size[0] * scaleshift / (scaleX[1] - scaleX[0]));	// shift in pixels				

			// set attributes
		    var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
	    	EJSS_GRAPHICS.GraphicsUtils.drawDecGrid(left, top, right, bottom, shiftx, stepx, 1,
					function(xx,yy) { mContext.moveTo(xx,yy) }, 
					function(xx,yy) { mContext.lineTo(xx,yy) }); 
		}		
		
		// set style
		mContext.lineWidth = mElement.getLineWidthX();
		mContext.strokeStyle = mElement.getLineColorX();
		mContext.stroke();
	}

	// draw axis Y (based on ticks mode)
	if(showy) {
		mContext.beginPath();

		var ticksYmode = mElement.getTicksYMode();			// decimal or logarithmic
		if (ticksYmode == EJSS_DRAWING2D.Grid.SCALE_LOG) { // logarithmic
						
		    // scale
		    var scaleY = mElement.getScaleY();		// Y axis scale 	

			if(scaleY[0] > 0 && scaleY[1] > 0) {  // valid scale
				// get number of axis ticks
			    var ticksY = mElement.getTicksY();
			
				// set attributes
			    var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
		    	EJSS_GRAPHICS.GraphicsUtils.drawLogGrid(left, top, right, bottom, ticksY, scaleY, -1,
					function(xx,yy) { mContext.moveTo(xx,yy) }, 
					function(xx,yy) { mContext.lineTo(xx,yy) }); 			
			}
					    		
		} else if (ticksYmode == EJSS_DRAWING2D.Grid.SCALE_NUM) { // decimal
	    
		 	// create path	    	   	    
		   	var stepy = 0;	// step for ticks
		   	var tickstepy = 0;
			if (!mElement.getAutoTicksY()) {	  // no auto-ticks    
			    var ticksY = mElement.getTicksY();
			    // whether the number of sticks exits, changes step for ticks
			    if (ticksY != 0) { stepy = Math.abs(size[1]/ticksY); } else {
			    	stepy = mElement.getStepY();
			    	tickstepy = mElement.getTickStepY(); 
			    } 	    	
			} else { // auto-ticks
				var stepymin = mElement.getAutoStepYMin();		// step y min		
				var ticksyrange = mElement.getAutoTicksRangeY();		// ticks y range
		
				// find stepy
				for(var i=ticksyrange.length-1; i>=0; i--)	{	
					stepy = Math.abs(size[1]/ticksyrange[i]);
					if (stepy >= stepymin) break;
				}						
			}	
			
		    // step for scale
			var scalePrecisionY = mElement.getScalePrecisionY();	// number of decimals for scale
		    var scaleY = mElement.getScaleY();		// Y axis scale 	
		    if(tickstepy == 0) {
				var scalestepY = Math.abs((scaleY[1] - scaleY[0]) * stepy / size[1]);  // step in Y axis scale 
			} else {
				var scalestepY = tickstepy;
				stepy = Math.abs((scalestepY * size[1]) / (scaleY[1] - scaleY[0])); 
			}			
			
			// adjust step to decimals of precision	
			var decimalsY = Math.pow(10,scalePrecisionY);
			var scalestepYTmp = Math.round(scalestepY * decimalsY) / decimalsY;
			if(scalestepYTmp > 0) {
				scalestepY = scalestepYTmp;
				stepy = Math.abs(scalestepYTmp * size[1] / (scaleY[1] - scaleY[0]));
			}
			
			// check fixed tick
			var fixedTicks = mElement.getFixedTickY();	  
			var tickfixed = scaleY[1];
			if (!isNaN(fixedTicks)) {
			  if (fixedTicks < scaleY[0]) tickfixed = fixedTicks + (Math.floor((scaleY[0]-fixedTicks)/scalestepY)+1)*scalestepY;
			  else if (fixedTicks > scaleY[1])  tickfixed = fixedTicks - (Math.floor((fixedTicks-scaleY[1])/scalestepY)+1)*scalestepY;
		      else tickfixed = fixedTicks; 
			}		
	
			// tick fixed in axis scale
			var scaleshift = Math.abs((scaleY[0] - tickfixed) % scalestepY);						
			var dist = Math.abs(scaleshift-scalestepY);	// fitting shift
			if(scaleshift < 0.001 || dist < 0.001) scaleshift = 0;						
			var shifty = Math.abs(size[1] * scaleshift / (scaleY[1] - scaleY[0]));	// shift in pixels				
																								            				            
			// set attributes
		    var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
	    	EJSS_GRAPHICS.GraphicsUtils.drawDecGrid(left, top, right, bottom, shifty, stepy, -1,
					function(xx,yy) { mContext.moveTo(xx,yy) }, 
					function(xx,yy) { mContext.lineTo(xx,yy) }); 
		}

		// set style
		mContext.lineWidth = mElement.getLineWidthY();
		mContext.strokeStyle = mElement.getLineColorY();
		mContext.stroke();
	}	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas histogram
 */
EJSS_CANVASGRAPHICS.histogram = function(mContext, mElement) {  

	// draw area bar	
	function areaBar(barx1, barx2, bary, axis, barstyle) {		
		mContext.beginPath();
		mContext.moveTo(barx1,axis);
		mContext.lineTo(barx1,bary);
		mContext.lineTo(barx2,bary);
		mContext.lineTo(barx2,axis);

		// set style
		if (barstyle.getDrawFill() && barstyle.getFillColor() != 'none') {
		  mContext.fillStyle = barstyle.getFillColor();
		  mContext.fill();
		}
		if (barstyle.getDrawLines()) {
		  mContext.lineWidth = barstyle.getLineWidth();
		  mContext.strokeStyle = barstyle.getLineColor();
		  mContext.stroke();
		}		
	}  	

	// get position of the element center 	
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
	
	// get style        
	var style = mElement.getStyle(); 
        
	// get histogram parameters
	var discrete =  mElement.getDiscrete();
 	var halfBin = mElement.getBinWidth()/2;
 	var bars = mElement.getBars();

	var axisx = mElement.getGroupPanel().getPixelPositionWorldOrigin()[1];
	for (var j=0; j < bars.length; j++) { // create bars
	  var barx1, barx2;
	  var cbin = bars[j][0];
	  if (discrete) {
        barx1 = barx2 = (x-mx) + cbin*size[0];
	  } 
	  else {
		barx1 = (x-mx) + (cbin-halfBin)*size[0];	// Bar x
		barx2 = (x-mx) + (cbin+halfBin)*size[0];	// Bar x
	  }
	  var bary = (y-my) + bars[j][1]*size[1];  // Bar y
	  areaBar(barx1, barx2, bary, axisx, style);
    }
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas image
 */
EJSS_CANVASGRAPHICS.image = function(mContext, mElement, transformCallBack) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];

	// get sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);
    
    // draw
    var img;
    if (!mElement.getChangedImage()) img = mElement.getCustomObject();
    if (img) {
		mContext.save();
		transformCallBack(mElement);
    	mContext.drawImage(img, (x-mx), (y-my), Math.abs(size[0]), Math.abs(size[1]));
		mContext.restore();
    }
    else{
    	img = new Image();
    	mElement.setCustomObject(img);
  	    img.onload = function () {
    			mContext.save();
    			transformCallBack(mElement);
    	    	mContext.drawImage(img, (x-mx), (y-my), Math.abs(size[0]), Math.abs(size[1]));
    			mContext.restore();
    	};
	 	var code = mElement.getEncode();
	 	if(code.length > 0) { // http://webcodertools.com/imagetobase64converter/Create
	 		img.src = 'data:png;base64,'+ code;
	 	} else {
	    	img.src = mElement.getImageUrl();
	    }
    }

    // indica que la fuente de la imagen ha sido actualizada  
    mElement.setChangedImage(false);
}  
/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas mesh
 */
EJSS_CANVASGRAPHICS.mesh = function(mContext, mElement) {  
  var mA; 			// int[]
  var mB; 			// int[]
  var mCells;		// {cellA, cellB, cellZ, cellVector}
  var mBoundary;	// {boundaryA (int[][]), boundaryB (int[][])}
  
  // fill cell
  function fillCell(xs, ys, numpoints, color, levelIndex) {
  	mContext.beginPath();

	for(var i=0; i<numpoints; i++) {
      if(i==0) 
        mContext.moveTo(xs[0], ys[0]);
      else 
      	mContext.lineTo(xs[i], ys[i]);
	}  	  	
    mContext.lineTo(xs[0], ys[0]);

  	var fill = color.indexToColor(levelIndex);

  	mContext.fillStyle = fill;
  	mContext.fill();
  	mContext.lineWidth = 1.25;
  	mContext.strokeStyle = fill;
  	mContext.stroke();
  }   
 
  // draw cell
  function drawCell(xs, ys, fill, style) {
  	mContext.beginPath();

	var numpoints = xs.length;
	for(var i=0; i<numpoints; i++) {
      if(i==0) 
        mContext.moveTo(xs[0], ys[0]);
      else 
      	mContext.lineTo(xs[i], ys[i]);
	}  	  	
    mContext.lineTo(xs[0], ys[0]);
    
	// set style
	if (fill && style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	    
  }  

  function drawSegment(xs, ys, color, lineWidth) {
  	mContext.beginPath();

	var numpoints = xs.length;
	for(var i=0; i<numpoints; i++) {
      if(i==0) 
        mContext.moveTo(xs[0], ys[0]);
      else 
      	mContext.lineTo(xs[i], ys[i]);
	}  	  	
    mContext.lineTo(xs[0], ys[0]);
    
	// set style
  	mContext.lineWidth = lineWidth + 0.5;
  	mContext.strokeStyle = color;
  	mContext.stroke();
  }  
  
  function drawVectors(cellA,cellB,field,lenVector) { 
  	for(var i=0; i<cellA.length; i++) {
	
		// draw line
		var a = cellA[i];
		var b = cellB[i];
		var a1 = field[i][0];
		var b1 = field[i][1];
		var len = EJSS_TOOLS.Mathematics.norm([a1,b1]);
		var sa = a1*lenVector/len;
		var sb = b1*lenVector/len;
		
	    // draw
		if((sa != 0) || (sb != 0)) {    			
			var stMax = (0.3 * EJSS_TOOLS.Mathematics.norm([sa,sb])) ;
			var stW = (stMax != 0 && stMax < 12) ? stMax : 12;
		    var angle = Math.atan2(sa,sb);
		  	mContext.beginPath();
			mContext.moveTo(a, b);
		    mContext.lineTo(a+sa - stW*Math.cos(angle), b+sb - stW*Math.sin(angle));
		    mContext.lineTo(a+sa - stW*Math.cos(angle-Math.PI/6), b+sb - stW*Math.sin(angle-Math.PI/6));
		    mContext.lineTo(a+sa, y+my);
		    mContext.lineTo(a+sa - stW*Math.cos(angle+Math.PI/6), b+sb - stW*Math.sin(angle+Math.PI/6));
		    mContext.lineTo(a+sa - stW*Math.cos(angle), b+sb - stW*Math.sin(angle));
		
			// set style
	      	mContext.lineWidth = 1;
	     	mContext.strokeStyle = 'black';
	      	mContext.stroke();
		}
	}  	
  }
	
	// get position of the mElement center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
	var mPoints = mElement.getPoints();	
    if (mPoints == null) return;    
    
  	// project the points
    var nPoints = mPoints.length;
    mA = new Array(nPoints);
    mB = new Array(nPoints);
    for (var i=0; i<nPoints; i++) {
      mA[i] = x-mx + mPoints[i][0]*size[0];
      mB[i] = y-my + mPoints[i][1]*size[1];
    }

	// build cells
	var cells = mElement.getCells();
	var field = mElement.getFieldAtPoints();
	if (field) {
		mCells = EJSS_GRAPHICS.GraphicsUtils.buildCells(mA,mB,cells,field,false);
	} else {
	  field = mElement.getFieldAtCells();
	  mCells = EJSS_GRAPHICS.GraphicsUtils.buildCells(mA,mB,cells,field,true);
	}

    // first draw the cells
    if (cells != null) {    
	  var style = mElement.getStyle();
      if (field != null) {
	      // draw polygons    	  
		  var color = mElement.getColorCoded();
	      EJSS_GRAPHICS.GraphicsUtils.drawPolygons(mCells, color, 
	      	function(xs, ys) { drawCell(xs, ys, false, style) },
	      	function(xs, ys, numpoints, color, levelIndex) { fillCell(xs, ys, numpoints, color, levelIndex) });
      }
      else {
	      EJSS_GRAPHICS.GraphicsUtils.drawPolygons(mCells, color, 
	      	function(xs, ys) { drawCell(xs, ys, true, style) },
	      	function(xs, ys, numpoints, color, levelIndex) { });
      }
    }

	// build boundary
	var boundary = mElement.getBoundary();
	if(mElement.getDrawBoundary() && boundary != null) {	 
	  var labels = mElement.getBoundaryLabels();
	  var colors = mElement.getBoundaryColors();
	  var lineWidth = mElement.getBoundaryLineWidth();	
	  mBoundary = EJSS_GRAPHICS.GraphicsUtils.buildBoundary(mA, mB, boundary);
 
      // draw boundary
      EJSS_GRAPHICS.GraphicsUtils.drawBoundary(mBoundary, labels, colors, 
      	function(xs, ys, color) { drawSegment(xs,ys,color,lineWidth) }); 
	}

	// draw vectors
	if(mElement.getDataType() >= 4 && field != null) {
		var lenVector = mElement.getVectorLength() * size[0]; // scale for size[0]		
        for (var i=0, n=cells.length; i<n; i++) {
          // draw vectors
          drawVectors(mCellA[i],mCellB[i],field[i],lenVector);
        }
    }	

}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas pipe
 */
EJSS_CANVASGRAPHICS.pipe = function(mContext, mElement) {  
    var points = mElement.getPoints();
    if (!points || points.length<=0) return;  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];

	
	// get half sizes 		
    var sx = Math.abs(size[0]);
    var sy = Math.abs(size[1]);

 	var xmin = x-sx/2;
 	var ymin = y+sy/2;

    var style = mElement.getStyle(); 

	// draw container
    mContext.beginPath();
    mContext.moveTo(xmin,ymin);
    var x = xmin;
    var y = ymin; 
    for (var i=0, n=points.length; i<n; i++) {
      var point = points[i];
      x += Math.round(point[0]*sx);
      y += Math.round(-point[1]*sy);
      mContext.lineTo(x,y);
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = 2*style.getLineWidth()+mElement.getPipeWidth();
	  mContext.strokeStyle = style.getLineColor();
	  mContext.stroke();
    }

	// draw liquid
    mContext.beginPath();
    mContext.moveTo(xmin,ymin);
    var x = xmin;
    var y = ymin;
    for (var i=0, n=points.length; i<n; i++) {
      var point = points[i];
      x += Math.round(point[0]*sx);
      y += Math.round(-point[1]*sy);
      mContext.lineTo(x,y);
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = mElement.getPipeWidth();
	  mContext.strokeStyle = style.getFillColor();
	  mContext.stroke();
    }    
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas point
 */
EJSS_CANVASGRAPHICS.point = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var xm = pos[0]+offset[0];
    var ym = pos[1]+offset[1];
	
    // draw
  	mContext.beginPath();
  	mContext.arc(xm,ym,2,0,2*Math.PI);
      
	// set style
    var style = mElement.getStyle(); 
    mContext.fillStyle = style.getFillColor();
    mContext.fill();
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas polygon
 */
EJSS_CANVASGRAPHICS.polygon = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
    // draw
  	mContext.beginPath();
	var points = mElement.getPoints();	    	    
	for(var i=0; i<points.length; i++) {
	  var point = points[i];	  
      var xx = (x-mx) + point[0]*size[0];
      var yy = (y-my) + point[1]*size[1];
      var type = point[2];
	  if ((i==0) || (type == 0)) // 0 is NOT CONNECTION
        mContext.moveTo(xx, yy);
      else 
      	mContext.lineTo(xx, yy);
	}  	  	
      
	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas rectangle
 */
EJSS_CANVASGRAPHICS.rectangle = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
    // draw
  	mContext.beginPath();
  	mContext.moveTo(x-mx,y+my);
  	mContext.lineTo(x+mx,y+my);
  	mContext.lineTo(x+mx,y-my);
  	mContext.lineTo(x-mx,y-my);
  	mContext.lineTo(x-mx,y+my);

	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas round rectangle
 */
EJSS_CANVASGRAPHICS.roundRectangle = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var mx = pos[0]+offset[0];
    var my = pos[1]+offset[1];
	
	// get sizes 		
    var width = Math.abs(size[0]);
    var height = Math.abs(size[1]);
    var x = mx - width / 2;
    var y = my - height / 2;

    // get element radius
    var radius = Math.abs(mElement.getCornerRadius());
    
    // draw
    mContext.beginPath();
    mContext.moveTo(x + radius, y);
    mContext.lineTo(x + width - radius, y);
    mContext.quadraticCurveTo(x + width, y, x + width, y + radius);
    mContext.lineTo(x + width, y + height - radius);
    mContext.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
    mContext.lineTo(x + radius, y + height);
    mContext.quadraticCurveTo(x, y + height, x, y + height - radius);
    mContext.lineTo(x, y + radius);
    mContext.quadraticCurveTo(x, y, x + radius, y);
    mContext.closePath();
      
	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas canvas
 */
EJSS_CANVASGRAPHICS.canvas = function(mContext, mElement) {  
  
	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
  	var deltaX = mElement.getMaximumX() - mElement.getMinimumX();
  	var deltaY = mElement.getMaximumY() - mElement.getMinimumY();
    
	var pixWth = Math.abs(size[0]);
	var pixHgt = Math.abs(size[1]);
	var pxPerUnitX = pixWth / deltaX;
	var pxPerUnitY = pixHgt / deltaY;
	var pxTopLeftX = x - pixWth / 2;
	var pxTopLeftY = y + pixHgt / 2;
   
    //draws all objects within this.drawables
    EJSS_GRAPHICS.GraphicsUtils.drawImageField(context, 
    	pxTopLeftX, pxTopLeftY, mElement.getMinimumX(), mElement.getMinimumY(), 
    	pixWth, pixHgt, pxPerUnitX, pxPerUnitY, {data: mElement.getData()});
    
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas segment
 */
EJSS_CANVASGRAPHICS.segment = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

    // draw
  	mContext.beginPath();
  	mContext.moveTo(x-mx, y-my);
  	mContext.lineTo(x+mx, y+my);
      
	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas spring
 */
EJSS_CANVASGRAPHICS.spring = function(mContext, mElement) {  
	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

 	// get spring radius	    	
    var radius = mElement.getGroupPanel().toPixelMod([mElement.getRadius(),0])[0];

	// set attributes
	mContext.beginPath();
	EJSS_GRAPHICS.GraphicsUtils.drawSpring(mElement.getLoops(), mElement.getPointsPerLoop(), 
			radius, mElement.getSolenoid(), mElement.getThinExtremes(), x-mx, y-my, size[0], size[1],
			function(xx,yy) { mContext.moveTo(xx,yy) }, 
			function(xx,yy) { mContext.lineTo(xx,yy) });    
      
	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas tank
 */
EJSS_CANVASGRAPHICS.tank = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);

    var level = mElement.getPixelSizeOf(0,mElement.getLevel());  

 	var xmin = x-mx, xmax = x+mx;
 	var ymin = y+my, ymax = y-my;
 	var ylevel = ymin + level[1];
    var style = mElement.getStyle();
    
    // draw liquid
  	mContext.beginPath();
    mContext.moveTo(xmin + level[0],ylevel);
    mContext.lineTo(xmin,ymin);
    mContext.lineTo(xmax,ymin);
    mContext.lineTo(xmax + level[0],ylevel);
    mContext.fillStyle = mElement.getLevelColor();
    mContext.fill();

	// draw container
	mContext.beginPath();
	mContext.moveTo(xmin,ymax);
	mContext.lineTo(xmin,ymin);
	mContext.lineTo(xmax,ymin);
	mContext.lineTo(xmax,ymax);
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    mContext.lineWidth = style.getLineWidth();
    mContext.strokeStyle = style.getLineColor();
    mContext.stroke();
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas text
 */
EJSS_CANVASGRAPHICS.text = function(mContext, mElement) {  
	// font 
 	var font = mElement.getFont();
 	var fondtxt = "";
 	fondtxt += (font.getFontStyle() != 'none')? font.getFontStyle() + ' ':'';
 	fondtxt += (font.getFontWeight() != 'none')? font.getFontWeight() + ' ':'';
 	fondtxt += font.getFontSizeString() + ((font.getFontSizeString().toString().indexOf('px') > -1)?' ':'px ');
 	fondtxt += font.getFontFamily();
	mContext.font = fondtxt;

	// txt 	
 	var txt = mElement.getText(); 	

	// get position of the element center 
    var pos = mElement.getPixelPosition();
	var marginFrame = mElement.getFramed()?4:0; 
    var size = [mContext.measureText(txt).width + marginFrame*2, font.getFontSize() + marginFrame*2];
    var offset = EJSS_DRAWING2D.Element.getSWRelativePositionOffset(mElement.getRelativePosition(), size[0], size[1]);  
    var xmargin = mElement.getMarginX();
    var ymargin = mElement.getMarginY();
    var x = pos[0]+offset[0]+xmargin+marginFrame;
    var y = pos[1]+offset[1]+ymargin-marginFrame;

    if (mElement.getFramed()) { // framed    
    	var tbmargin = marginFrame;
    	var rlmargin = marginFrame;
    	var wlen = size[0] - marginFrame*2;
    	var hlen = size[1] - marginFrame*2;    	
    	
	    // draw box
	    if(wlen > 0) {
	    	var bottom = y + tbmargin, top = y - hlen - tbmargin;
	    	var left = x - rlmargin, rigth = x + wlen + tbmargin;
	    	mContext.save();	 		 
		  	mContext.beginPath();
  			mContext.moveTo(left, bottom);
  			mContext.lineTo(rigth, bottom);
  			mContext.lineTo(rigth, top);
  			mContext.lineTo(left, top);
			mContext.lineTo(left, bottom);

			// set style
		    var style = mElement.getStyle(); 
			if (style.getDrawFill() && style.getFillColor() != 'none') {
		      mContext.fillStyle = style.getFillColor();
		      mContext.fill();
		    }
		    if (style.getDrawLines()) {
		      mContext.lineWidth = style.getLineWidth();
		      mContext.strokeStyle = style.getLineColor();
		      mContext.stroke();
		    }	
		    mContext.restore();
		}
	}
		    
	// angle of text  	 
	var angletext = 0;
	switch(mElement.getWritingMode()) {
		case EJSS_DRAWING2D.Text.MODE_TOPDOWN : angletext = Math.PI / 2; break;
		case EJSS_DRAWING2D.Text.MODE_RIGTHLEFT : angletext = Math.PI; break;
		case EJSS_DRAWING2D.Text.MODE_DOWNTOP : angletext = 3 * Math.PI / 2; break;
		case EJSS_DRAWING2D.Text.MODE_LEFTRIGHT: angletext = 0; break;
	} 	  	  	

  	mContext.translate(pos[0]+xmargin,pos[1]+ymargin);
  	mContext.rotate(angletext);
  	mContext.translate(-(pos[0]+xmargin),-(pos[1]+ymargin));

	// draw text		
	mContext.fillStyle = font.getFillColor();
	mContext.fillText(mElement.getText(), x, y);
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas trace
 */
EJSS_CANVASGRAPHICS.trace = function(mContext, mElement) {  

	// draw path for trail
	function drawTrail(points, x, y, sx, sy) {
	  	mContext.beginPath();
		for(var i=0; i<points.length; i++) {
		  var point = points[i];	  
	      var xx = x + point[0]*sx;
	      var yy = y + point[1]*sy;
	      var type = point[2];		
		  if ((i==0) || (type == 0)) // 0 is NOT CONNECTION
		  	mContext.moveTo(xx,yy);		// move 
	      else       	
	      	mContext.lineTo(xx,yy);		// line
		}  	  	
	}  	

	// creates ellipse mark
	function ellipseMark(markx, marky, size, markstyle) {
		// get position of the element center 
	    var xm = markx;
	    var ym = marky;
		
		// get sizes 		
	    var w = Math.abs(size[0]);
	    var h = Math.abs(size[1]);
	
	    // draw
	    var kappa = .5522848,
	      // x = x-middle, y = y-middle
	      x = xm - w / 2,       // x-middle
	      y = ym - h / 2;       // y-middle      
	      ox = (w / 2) * kappa, // control point offset horizontal
	      oy = (h / 2) * kappa, // control point offset vertical
	      xe = x + w,           // x-end
	      ye = y + h,           // y-end
	
	  	mContext.beginPath();
	  	mContext.moveTo(x, ym);
	  	mContext.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
	  	mContext.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
	  	mContext.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
	  	mContext.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
	      
		// set style
		if (markstyle.getDrawFill() && markstyle.getFillColor() != 'none') {
	      mContext.fillStyle = markstyle.getFillColor();
	      mContext.fill();
	    }
	    if (markstyle.getDrawLines()) {
	      mContext.lineWidth = markstyle.getLineWidth();
	      mContext.strokeStyle = markstyle.getLineColor();
	      mContext.stroke();
	    }	  	
	}  	

	// creates rectangle mark
	function rectangleMark(markx, marky, size, markstyle) {
		// get position of the element center 
	    var x = markx;
	    var y = marky;
		
		// get sizes 		
	    var sx = Math.abs(size[0]);
	    var sy = Math.abs(size[1]);

	    // draw
	  	mContext.beginPath();
	  	mContext.rect(x-sx/2, y-sy/2, sx, sy);
	      
		// set style
		if (markstyle.getDrawFill() && markstyle.getFillColor() != 'none') {
	      mContext.fillStyle = markstyle.getFillColor();
	      mContext.fill();
	    }
	    if (markstyle.getDrawLines()) {
	      mContext.lineWidth = markstyle.getLineWidth();
	      mContext.strokeStyle = markstyle.getLineColor();
	      mContext.stroke();
	    }	
	}  	

	// creates area mark
	function areaMark(lastmarkx, lastmarky, markx, marky, axis, markstyle) {	
	    // draw
	  	mContext.beginPath();
	  	mContext.moveTo(lastmarkx,axis);
	  	mContext.lineTo(lastmarkx,lastmarky);
	  	mContext.lineTo(markx,marky);
	  	mContext.lineTo(markx,axis);
	
		// set style
		if (markstyle.getDrawFill() && markstyle.getFillColor() != 'none') {
	      mContext.fillStyle = markstyle.getFillColor();
	      mContext.fill();
	    }
	    if (markstyle.getDrawLines()) {
	      mContext.lineWidth = markstyle.getLineWidth();
	      mContext.strokeStyle = markstyle.getLineColor();
	      mContext.stroke();
	    }		
	}  	

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
        
	// draw trail
    drawTrail(mElement.getPoints(), x-mx, y-my, size[0], size[1]);

	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
     
	// draw marks     
	var points = mElement.getPoints();
	var lastpointX, lastpointY;	// for mark area
	for(var i=0; i<points.length; i++) {
		var point = points[i];	  
		var markx = (x-mx) + point[0]*size[0];		// mark x
		var marky = (y-my) + point[1]*size[1];     // mark y
	    var markType = point[3];    		// mark type
		var markstyle = point[4];			// mark style
		var marksize = [point[5],point[6]];		// mark size

	    if (markType == EJSS_DRAWING2D.Trace.ELLIPSE) { // circle
			ellipseMark(markx, marky, marksize, markstyle);
	    } else if (markType == EJSS_DRAWING2D.Trace.RECTANGLE) { // rectangle
			rectangleMark(markx, marky, marksize, markstyle);
	    } else if (markType == EJSS_DRAWING2D.Trace.BAR) { // bar
		    // create bar  		    	
			//var axisx = mElement.getGroupPanel().getPixelPositionWorldOrigin()[1];
			var axisx = mElement.getGroupPanel().toPixelPosition([0,0])[1];
			
	    	marksize[1] = axisx-marky;			// bar size
	    	marky = marky + marksize[1]/2;		// new mark y	    	
	    	rectangleMark(markx, marky, marksize, markstyle);
		}
		else if (markType == EJSS_DRAWING2D.Trace.AREA) { // area
	    	if(i!=0) {				  
				// create area
				//var axisx = mElement.getGroupPanel().getPixelPositionWorldOrigin()[1];
				var axisx = mElement.getGroupPanel().toPixelPosition([0,0])[1];
				var lastmarkx = (x-mx) + lastpointX*size[0];		// mark x
				var lastmarky = (y-my) + lastpointY*size[1];     // mark y
				areaMark(mMark, lastmarkx, lastmarky, markx, marky, axisx, markstyle);
	    	} 
	    	lastpointX = point[0];    	
	    	lastpointY = point[1];
	    }		  
	}  	  		  

}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas trail
 */
EJSS_CANVASGRAPHICS.trail = function(mContext, mElement) {  

  /** adds a segment to the trail
   */
   function addSegment(points,num,style,pX,pY,sX,sY) {
     if (num<=0) return; // Nothing to add
	 	 	
	mContext.beginPath();
	for (var i=0; i<num; i++) {
	  var point = points[i];	  
	  var xx = pX + point[0]*sX;
	  var yy = pY + point[1]*sY;
	  var type = point[2];		
	  if ((i==0) || (type == 0)) // 0 is NOT CONNECTION
	    mContext.moveTo(xx,yy);
      else
        mContext.lineTo(xx,yy);       	
 	}  	  	

/*
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
*/
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
   }

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
	
	var pX = x-mx;
	var pY = y-my;
	var sX = size[0];
	var sY = size[1];
	
	var segmentCount = mElement.getSegmentsCount();
	if (segmentCount>0) {
	  for (var i=0; i<segmentCount; i++) { 
	  	var num = mElement.getSegmentPoints(i).length;
	    addSegment(mElement.getSegmentPoints(i),num,mElement.getSegmentStyle(i),pX,pY,sX,sY);
	  }
	}
	
	//mElement.dataCollected(); // add temporary points
	var num = mElement.getCurrentPoints().length; 
	addSegment(mElement.getCurrentPoints(),num,mElement.getStyle(),pX,pY,sX,sY);
	
}/**
 * Deployment for 2D Canvas drawing.
 * @module CanvasGraphics 
 */

var EJSS_CANVASGRAPHICS = EJSS_CANVASGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A Canvas wheel
 */
EJSS_CANVASGRAPHICS.wheel = function(mContext, mElement) {  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var xm = pos[0]+offset[0];
    var ym = pos[1]+offset[1];
	
	// get sizes 		
    var w = Math.abs(size[0]);
    var h = Math.abs(size[1]);

	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
    // draw
    var kappa = .5522848,
      // x = x-middle, y = y-middle
      x = xm - w / 2,       // x-middle
      y = ym - h / 2;       // y-middle      
      ox = (w / 2) * kappa, // control point offset horizontal
      oy = (h / 2) * kappa, // control point offset vertical
      xe = x + w,           // x-end
      ye = y + h,           // y-end

  	mContext.beginPath();
	// set circle atts
  	mContext.moveTo(x, ym);
  	mContext.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  	mContext.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  	mContext.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  	mContext.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
      
	// set cross atts
	mContext.moveTo(xm-mx,ym);
	mContext.lineTo(xm+mx,ym);
	mContext.moveTo(xm,ym-my);
	mContext.lineTo(xm,ym+my);
	
	// set style
    var style = mElement.getStyle(); 
	if (style.getDrawFill() && style.getFillColor() != 'none') {
      mContext.fillStyle = style.getFillColor();
      mContext.fill();
    }
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Deployment for 2D drawing.
 * @module 2Dgraphics 
 */

var EJSS_GRAPHICS = EJSS_GRAPHICS || {};

/**
 * SVGGraphics class
 * @class SVGGraphics 
 * @constructor  
 */
EJSS_GRAPHICS.CanvasGraphics = {

  /**
   * Return document box
   * @return box  
   */
  getOffsetRect: function(graphics) {
  	return EJSS_GRAPHICS.GraphicsUtils.getOffsetRect(graphics);
  }
};

/**
 * Constructor for Element
 * @param mName Identifier Canvas element in HTML document
 * @returns A Canvas graphics
 */
EJSS_GRAPHICS.canvasGraphics = function(mName) {
  var self = EJSS_INTERFACE.canvas(mName);	// reference returned     
  var mInterfaceGraphics = self.getDOMElement();
  var mContext = self.getContext();

  /**
   * Return canvas for event handle
   * @return canvas element 
   */
  self.getEventContext = function() {  	
    return mInterfaceGraphics;
  };

  /**
   * Return canvas document box
   * @return width integer 
   */
  self.getBox = function() {
  	var box = EJSS_GRAPHICS.CanvasGraphics.getOffsetRect(self)
  	box.width -= 1;
  	box.height -= 1;
  	return box;
  };

  /**
   * Reset the Canvas graphics
   */
  self.reset = function() {
  	var rect = EJSS_GRAPHICS.CanvasGraphics.getOffsetRect(self);
  	mContext.clearRect(0, 0, rect.width, rect.height);
  };

  /**
   * Draw Elements
   * @param elements array of Elements and Element Sets
   * @param force whether force draw elements
   */
  self.draw = function(elements,force) {  	
    for (var i=0, n=elements.length; i<n; i++) { 
      var element = elements[i];
      if (element.isGroupVisible()) {
		  	self.drawElement(element, self.transformElement);
	  }  	
    }
  };
    
  /**
   * Draw Element
   * @param element
   */
  self.drawElement = function (element, transformCallBack) {
  	var Shape = EJSS_DRAWING2D.Shape;
  	var cl = element.getClass();
	
	if (cl === 'ElementImage') {
		EJSS_CANVASGRAPHICS.image(mContext,element,transformCallBack);
	} else {
		mContext.save();
		transformCallBack(element);

	  	switch (cl) {
			case "ElementCustom": 
			  {
			    var customFunction = element.getFunction();
			    if (customFunction) customFunction(mContext,element);	
			  }
			  break;
	  		case "ElementShape": 
				switch (element.getShapeType()) {
				  default : 
				  case Shape.ELLIPSE         : EJSS_CANVASGRAPHICS.ellipse(mContext,element); 	break;
				  case Shape.RECTANGLE       : EJSS_CANVASGRAPHICS.rectangle(mContext,element);	break;
				  case Shape.ROUND_RECTANGLE : EJSS_CANVASGRAPHICS.roundRectangle(mContext,element); break;
				  case Shape.WHEEL           : EJSS_CANVASGRAPHICS.wheel(mContext,element); 		break;
				  case Shape.NONE            : // do not break;
				  case Shape.POINT           : EJSS_CANVASGRAPHICS.point(mContext,element); 		break;
				}
				break;
			case "ElementSegment": 	EJSS_CANVASGRAPHICS.segment(mContext,element);	break;
			case "ElementArrow":	EJSS_CANVASGRAPHICS.arrow(mContext,element);	break;
			case "ElementText":		EJSS_CANVASGRAPHICS.text(mContext,element);		break;
			case "ElementSpring":	EJSS_CANVASGRAPHICS.spring(mContext,element);		break;
			case "ElementTrail":	EJSS_CANVASGRAPHICS.trail(mContext,element);		break;
			case "ElementTrace":	EJSS_CANVASGRAPHICS.trace(mContext,element);		break;
			case "ElementGrid":		EJSS_CANVASGRAPHICS.grid(mContext,element);		break;
			case "ElementAxis":		EJSS_CANVASGRAPHICS.axis(mContext,element);		break;
			case "ElementCursor":	EJSS_CANVASGRAPHICS.cursor(mContext,element);		break;
			case "ElementPolygon":	EJSS_CANVASGRAPHICS.polygon(mContext,element);	break;
			case "ElementAnalyticCurve":	EJSS_CANVASGRAPHICS.analyticCurve(mContext,element);	break;
			case "ElementCellLattice": 	EJSS_CANVASGRAPHICS.cellLattice(mContext,element);	break; 			
			case "ElementByteRaster": 	EJSS_CANVASGRAPHICS.byteRaster(mContext,element);	break;			
			case "ElementMesh": 	EJSS_CANVASGRAPHICS.mesh(mContext,element);		break;			
			case "ElementTank":   	EJSS_CANVASGRAPHICS.tank(mContext,element);		break;
			case "ElementPipe":   	EJSS_CANVASGRAPHICS.pipe(mContext,element);		break;
			case "ElementHistogram":EJSS_CANVASGRAPHICS.histogram(mContext,element);		break;

			case "ElementScalarField":	EJSS_CANVASGRAPHICS.scalarField(mContext,element);		break;
			case "ElementCanvas":	EJSS_CANVASGRAPHICS.canvas(mContext,element);		break;

			case "ElementVideo":   	Console.log('Not supported ElementVideo in Canvas!');	break;
		}		
		mContext.restore();
	}
  };

  /**
   * Transform Element
   * @param element
   */
  self.transformElement = function (element) {
  	// first, group transform 
  	if(element.getGroup() != null)
  		self.transformElement(element.getGroup())
	
	// second, my transform  	
	var trans = element.getTransformation();
  	var rot = element.getRotate();
  	if (Array.isArray(trans) && trans.length > 0) {  
    	console.log("Array transformations are not supported in Canvas!");  	
	} else if (typeof trans === "string") {  // SVG format
    	console.log("SVG-based transformations are not supported in Canvas!");  	
	} else if (rot != 0) { // && trans!=0) {  // rotation angle
	    var pos = element.getPixelPosition();	
      	mContext.translate(pos[0],pos[1]);
      	mContext.rotate(-rot);
      	mContext.translate(-pos[0],-pos[1]);
	}	  	
  }

  /**
   * Draw gutters
   * @param gutters
   */
  self.drawGutters = function(panel) {
  	var gutters = panel.getGutters();
  	
  	if (gutters.visible) {    	 			
	 	// get gutters position     
	    var inner = panel.getInnerRect();
	    
	 	// get panel position
	    var x = 0.5, y = 0.5;
	    var box = self.getBox();
	    var sx = box.width, sy = box.height;
	    
	 	var guttersStyle = panel.getGuttersStyle();	 
	 	var panelStyle = panel.getStyle();   

        if (guttersStyle.getDrawFill()) { // create path	    	   	    
	      mContext.beginPath();
		  mContext.moveTo(x,y);
		  mContext.lineTo(x,y+sy);
		  mContext.lineTo((x+sx),(y+sy));
		  mContext.lineTo((x+sx),y);
		  mContext.lineTo(x,y);
		  mContext.moveTo(inner.x,inner.y);
		  mContext.lineTo(inner.x + inner.width, inner.y);
		  mContext.lineTo(inner.x + inner.width, inner.y + inner.height);
		  mContext.lineTo(inner.x, inner.y + inner.height);
		  mContext.lineTo(inner.x,inner.y);
		  mContext.moveTo(x,y);
	      mContext.closePath();
	      mContext.fillStyle = guttersStyle.getFillColor();
	      mContext.fill();
	    }
	    if (guttersStyle.getDrawLines()) { // Outer border
	      mContext.beginPath();
		  mContext.moveTo(x,y);
		  mContext.lineTo(x,y+sy);
		  mContext.lineTo((x+sx),(y+sy));
		  mContext.lineTo((x+sx),y);
		  mContext.lineTo(x,y);
	      mContext.closePath();
	      mContext.lineWidth = guttersStyle.getLineWidth();
	      mContext.strokeStyle = guttersStyle.getLineColor();
	      mContext.stroke();
        }
	    if (guttersStyle.getDrawLines()) { // Inner border
	      mContext.beginPath();
		  mContext.moveTo(inner.x,inner.y);
		  mContext.lineTo(inner.x + inner.width, inner.y);
		  mContext.lineTo(inner.x + inner.width, inner.y + inner.height);
		  mContext.lineTo(inner.x, inner.y + inner.height);
		  mContext.lineTo(inner.x,inner.y);
	      mContext.closePath();
	      mContext.lineWidth = panelStyle.getLineWidth();
	      mContext.strokeStyle = panelStyle.getLineColor();
	      mContext.stroke();
        }
	}  	
  };
  
  /**
   * Draw panel
   * @param panel
   */
  self.drawPanel = function(panel) {
 	// get position
    var x = 0.5, y = 0.5;
    var box = self.getBox();
	
    mContext.beginPath();
    mContext.rect(x, y, box.width, box.height);
    
    // style
    var style = panel.getStyle();
    mContext.fillStyle = style.getFillColor();
    mContext.fill();
    if (style.getDrawLines()) {
      mContext.lineWidth = style.getLineWidth();
      mContext.strokeStyle = style.getLineColor();
      mContext.stroke();
    }	
	
  };
  
 return self;     
      
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Deployment for 2D drawing.
 * @module 2Dgraphics 
 */

var EJSS_GRAPHICS = EJSS_GRAPHICS || {};

/**
 * GraphicsUtils class
 * @class GraphicsUtils 
 * @constructor  
 */
EJSS_GRAPHICS.GraphicsUtils = {

	/**
	 * Return document box
	 * @return box  
	 */
	getOffsetRect: function(graphics) {
	    var width, height, top, left;
	    var element = graphics.getDOMElement();
	
		// (0) width and height
		var strw = graphics.getWidth();
		var strh = graphics.getHeight();  		
		width = parseFloat(strw);
		var isWidthNumber = (!isNaN(width) && isFinite(strw));	 
		height = parseFloat(strh);	 
		var isHeightNumber = (!isNaN(height) && isFinite(strh));
	
	    // (1) left and top
	    if(element.getBoundingClientRect) { // this option is better
			// **myPanel shows the VISIBLE elements
			var mypanel = element.getElementById? element.getElementById(".myPanel"):null;
			if(mypanel && mypanel.childElementCount>0) { 
				var box = mypanel.getBoundingClientRect();
			} else {
		    	var box = element.getBoundingClientRect();
		    }
			top = box.top;
			left = box.left;	  		
			if(!isWidthNumber) width = box.width;
			if(!isHeightNumber) height = box.height;
		} else if(element.offsetTop !== undefined) { // else this one
	    	top = element.offsetTop;
	    	left = element.offsetLeft;
			if(!isWidthNumber) width = element.offsetWidth;
			if(!isHeightNumber) height = element.offsetHeight;
	
	    } else { // use parent offset (buggy!)
			// **the parent MUST fit the SVG
	   		var parent = element.parentNode || document.getElementById(element.id).parentNode;
			while ((parent && !parent.offsetTop) && (parent && !parent.previousElementSibling && !parent.nextElementSibling)) {
	    		parent = parent.parentNode;
			};
			top = parent.offsetTop;
			left = parent.offsetLeft;
			if(!isWidthNumber) width = parent.offsetWidth;
			if(!isHeightNumber) height = parent.offsetHeight;
		}				    			 
	
	    var body = document.body;
	    var docElem = document.documentElement;
	     
	    // (2) we don't need absolute position, so scroll is ignored
	    // var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
	    // var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
	     
	    // (3)
	    var clientTop = docElem.clientTop || body.clientTop || 0;
	    var clientLeft = docElem.clientLeft || body.clientLeft || 0;
	     
	    // (4)
	    top =  top - clientTop;
	    left = left - clientLeft;
	
	    return { 
			top: top,
			left: left,
			width: width,
			height: height,
			bottom: top + height,
			right: left + width
		};     
	},

	/**
	 * Draw a spring
	*/
	drawSpring: function(loops, pointsPerLoop, radius, solenoid, thinExtremes, x, y, sx, sy, moveTo, lineTo) {
		var segments = loops * pointsPerLoop;
		var delta = 2.0 * Math.PI / pointsPerLoop;
		if (radius < 0) delta *= -1;
		var pre = pointsPerLoop / 2;
		// normalize sizes        	
		var size = [sx, sy, 0];
		var u1 = EJSS_TOOLS.Mathematics.normalTo(size);
		var u2 = EJSS_TOOLS.Mathematics.normalize(EJSS_TOOLS.Mathematics.crossProduct(size, u1));
		
	    for (var i=0; i<=segments; i++) {
	      var k;
	      if (thinExtremes) {	// extremes
	        if (i < pre) k = 0;
	        else if (i < pointsPerLoop) k = i - pre;
	        else if (i > (segments - pre)) k = 0;
	        else if (i > (segments - pointsPerLoop)) k = segments - i - pre;
	        else k = pre;
	      }
	      else k = pre;
	      var angle = Math.PI/2 + i*delta;
	      var cos = Math.cos(angle), sin = Math.sin(angle);
	      var xx = x + i*sx/segments + k*radius*(cos*u1[0] + sin*u2[0])/pre;
	      var yy = y + i*sy/segments + k*radius*(cos*u1[1] + sin*u2[1])/pre;
	      if (solenoid != 0.0)  {
	        var cte = k*Math.cos(i*2*Math.PI/pointsPerLoop)/pre;
	        xx += solenoid*cte*sx;
	        yy += solenoid*cte*sy;
	      }
	      if (i==0)
	      	moveTo(xx,yy); 
	      else 
	      	lineTo(xx,yy);
	    }
	},	
  
	/**
	 * Draw ticks for axis
	*/
	drawDecTicks: function(x, y, mx, my, ticksize, step, shift, orient, inverted, drawTick) {
		var UTILS = EJSS_SVGGRAPHICS.Utils;
		
		// bounds	
		var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
		var cright = UTILS.crispValue(right);
		var cbottom = UTILS.crispValue(bottom);
	
		// drawing ticks (only support mode NUM (decimal))
	    if(orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL) {
	    	// draw vertical axis (inverted or not are the same)
	    	for (var i = cbottom-shift; i >= top-0.5; i -= step) {		
				drawTick(x, i, ticksize, orient);
			}
	    } else {
	    	// draw horizontal axis
	    	for (var i = left+shift; i <= cright+0.5; i += step) {	// left to right	    		
				drawTick(i, y, ticksize, orient);
			}
		}					
	},
	
  
	/**
	 * Draw ticks text for axis
	*/
	drawDecTicksText: function(x, y, mx, my, ticksize, step, shift, scale, scalePrecision, scalestep, scaleshift, font, textPosition, orient, inverted, tickText) {
		var UTILS = EJSS_SVGGRAPHICS.Utils;
		
		// bounds	
		//console.log ("Axis = "+ orient+" scaleshif = "+scaleshift);
		var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
		var cright = UTILS.crispValue(right);
		var cbottom = UTILS.crispValue(bottom);
	
		// drawing ticks (only support mode NUM (decimal))
	    if(orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL) {
			// draw tick text			
	    	for (var j = 0, i = cbottom-shift; i >= top-0.5; i -= step, j++) {
	    		if(inverted)
	    			var number = scale[1] - scaleshift - (scalestep*j); // number for text
	    		else							
					var number = scale[0] + scaleshift + (scalestep*j); // number for text
		    	var text = parseFloat(number.toFixed(scalePrecision)).toFixed(scalePrecision);  // fixed precision				
				if(textPosition == EJSS_DRAWING2D.Axis.TICKS_DOWN) {
					var dx = ticksize/2; // tick size middle + 2
					tickText(mGroup, x+dx, i, text, font, false);
				} else {
					var dx = text.length * Math.floor(font.getFontSize()/2); // text long plus tick size 
					dx += text.length * font.getNumberLetterSpacing();
					dx += Math.floor(ticksize/2); 				
					tickText(x-dx, i, text, font, false);
				}
			}    	
	    } else {
			// draw tick text
	    	for (var j = 0, i = left+shift; i <= cright+0.5; i += step, j++) {	// left to right	    		
				var number = scale[0] + scaleshift + (scalestep*j); // number for text
		    	var text = parseFloat(number.toFixed(scalePrecision)).toFixed(scalePrecision);  // fixed precision		    					
				if(textPosition == EJSS_DRAWING2D.Axis.TICKS_DOWN) {
					var dy = Math.floor(ticksize/2) + 2; // tick size middle + 2
					tickText(i, y-ticksize, text, font, true);
				} else {
					var dy = font.getFontSize() + Math.floor(ticksize/2); // size font + tick size middle 
					tickText(i, y+dy, text, font, true);
				}			
		   	}
		}
	
	},
	
	/**
	 * Draw ticks for log axis
	*/
	drawLogTicks: function(x, y, mx, my, segsize, ticksize, scale, numticks, orient, drawTick) {
		var UTILS = EJSS_SVGGRAPHICS.Utils;
		
		// bounds	
		var left = x-mx, bottom = y-my;
		var cbottom = UTILS.crispValue(bottom);
	
		// drawing ticks (only support mode NUM (decimal))
	    if(orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL) {
	    	// draw vertical axis
	    	var minscale = Math.log(scale[0])/Math.log(10);		// min log value
	    	var maxscale = Math.log(scale[1])/Math.log(10);		// max log value
	    	var scalesize = maxscale - minscale;			// size of log scale
	    	var numbands = Math.ceil(scalesize);			// num of log bands
	    	var sizeratio = segsize / scalesize;			// pixel per log value
	    	
	    	// generate ticks in each band
	    	for (var i = 0; i <= numbands; i++) {
	    		var bandmin = Math.pow(10,i+minscale);		// band min
	    		var bandmax = Math.pow(10,i+minscale+1);	// band max
	    		var bandshift = bandmax / numticks;			// band part
		    	for (var j = 0; j < numticks-1; j++) {	    		
		    		// log pos	    		
		    		var number = bandmin + j * bandshift;  // number for text
					if(number != 0 && number >= scale[0] && number <= scale[1]) {
			    		var step = Math.log(number) / Math.log(10);
			    		var pos = cbottom - (step - minscale) * sizeratio;   		
						drawTick(x, pos, ticksize, orient);
					}  	   		    		    					
				}
			}
	    } else {
	    	// draw horizontal axis
	    	var minscale = Math.log(scale[0])/Math.log(10);		// min log value
	    	var maxscale = Math.log(scale[1])/Math.log(10);		// max log value
	    	var scalesize = maxscale - minscale;			// size of log scale
	    	var numbands = Math.ceil(scalesize);			// num of log bands
	    	var sizeratio = segsize / scalesize;			// pixel per log value
	    	
	    	// generate ticks in each band
	    	for (var i = 0; i <= numbands; i++) {
	    		var bandmin = Math.pow(10,i+minscale);		// band min
	    		var bandmax = Math.pow(10,i+minscale+1);	// band max
	    		var bandshift = bandmax / numticks;			// band part
		    	for (var j = 0; j < numticks-1; j++) {	    		
		    		// log pos	    		
		    		var number = bandmin + j * bandshift;  // number for text
					if(number != 0 && number >= scale[0] && number <= scale[1]) {
			    		var step = Math.log(number) / Math.log(10);
			    		var pos = left + (step - minscale) * sizeratio;   		
						drawTick(pos, y, ticksize, orient);
					}  	   		    		    					
				}
			}
		}					
	},
		
	/**
	 * Draw ticks text for log axis
	*/
	drawLogTicksText: function(x, y, mx, my, segsize, numticks, ticksize, scale, scalePrecision, font, textPosition, orient, tickText) {
		var UTILS = EJSS_SVGGRAPHICS.Utils;
		
		// bounds	
		var left = x-mx, bottom = y-my;
		var cbottom = UTILS.crispValue(bottom);
	
		// drawing ticks (only support mode NUM (decimal))
	    if(orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL) {
			// draw tick text			
	    	var minscale = Math.log(scale[0])/Math.log(10);		// min log value
	    	var maxscale = Math.log(scale[1])/Math.log(10);		// max log value
	    	var scalesize = maxscale - minscale;			// size of log scale
	    	var numbands = Math.ceil(scalesize);			// num of log bands
	    	var sizeratio = segsize / scalesize;			// pixel per log value
	    	
	    	// generate ticks in each band
	    	for (var i = 0; i <= numbands; i++) {
	    		var bandmin = Math.pow(10,i+minscale);		// band min
	    		var bandmax = Math.pow(10,i+minscale+1);	// band max
	    		var bandshift = bandmax / numticks;			// band part
		    	for (var j = 0; j < numticks-1; j++) {	    		
		    		// log pos	 		    		   		
		    		var number = bandmin + j * bandshift;  // number for text
					if(number != 0 && number >= scale[0] && number <= scale[1]) {
			    		var step = Math.log(number) / Math.log(10);
			    		var pos = cbottom - (step - minscale) * sizeratio;   		
						
						// draw text
						if(j == 0) { // exp format
							var text = number.toExponential(scalePrecision); 		    											
						} else {  // fixed precision
				    		var text = parseFloat((number/bandmin).toFixed(scalePrecision)).toFixed(scalePrecision); 		    					
						}
						if(textPosition == EJSS_DRAWING2D.Axis.TICKS_DOWN) {
							var dx = ticksize/2; // tick size middle + 2
							tickText(x+dx, pos, text, font, false);
						} else {
							var dx = text.length * Math.floor(font.getFontSize()/2); // text long plus tick size 
							dx += text.length * font.getNumberLetterSpacing();
							dx += Math.floor(ticksize/2); 				
							tickText(x-dx, pos, text, font, false);
						}
					}  	   		    		    					
				}
			}
	    } else {
	    	// draw horizontal axis
	    	var minscale = Math.log(scale[0])/Math.log(10);		// min log value
	    	var maxscale = Math.log(scale[1])/Math.log(10);		// max log value
	    	var scalesize = maxscale - minscale;			// size of log scale
	    	var numbands = Math.ceil(scalesize);			// num of log bands
	    	var sizeratio = segsize / scalesize;			// pixel per log value
	    	
	    	// generate ticks in each band
	    	for (var i = 0; i <= numbands; i++) {
	    		var bandmin = Math.pow(10,i+minscale);		// band min
	    		var bandmax = Math.pow(10,i+minscale+1);	// band max
	    		var bandshift = bandmax / numticks;			// band part
		    	for (var j = 0; j < numticks-1; j++) {	    		
		    		// log pos	 		    		   		
		    		var number = bandmin + j * bandshift;  // number for text
					if(number != 0 && number >= scale[0] && number <= scale[1]) {
			    		var step = Math.log(number) / Math.log(10);
			    		var pos = left + (step - minscale) * sizeratio;   		
						
						// draw text
						if(j == 0) { // exp format
							var text = number.toExponential(scalePrecision); 		    											
						} else {  // fixed precision
				    		var text = parseFloat((number/bandmin).toFixed(scalePrecision)).toFixed(scalePrecision); 		    					
						}
						if(textPosition == EJSS_DRAWING2D.Axis.TICKS_DOWN) {
							var dy = Math.floor(ticksize/2) + 2; // tick size middle + 2
							tickText(pos, y-ticksize, text, font, true);
						} else {
							var dy = font.getFontSize() + Math.floor(ticksize/2); // size font + tick size middle 
							tickText(pos, y+dy, text, font, true);
						}			
					}  	   		    		    					
				}
			}
		}
	},

	/**
	 * Draw grid
	*/
	drawDecGrid: function (left, top, right, bottom, sh, step, orientation, moveTo, lineTo) {
		var UTILS = EJSS_SVGGRAPHICS.Utils;

		var cleft = UTILS.crispValue(left), cright = UTILS.crispValue(right);
		var ctop = UTILS.crispValue(top), cbottom = UTILS.crispValue(bottom);

    	if(orientation == -1) {
    		if (step == 0) step = Math.abs(ctop-cbottom);
		    for (var i = cbottom-sh; i >= top-0.5; i -= step) { // bottom to top
	    		moveTo(left,i);
	    		lineTo(right,i);
		    }		    	
    	} else {
    		if (step == 0) step = Math.abs(cright-cleft);
		    for (var i = left+sh; i <= cright+0.5; i += step) { // left to right
		    	moveTo(i,top);
		    	lineTo(i,bottom);
		    }    		
    	}
	},
	
	/**
	 * Draw log grid
	*/
	drawLogGrid: function (left, top, right, bottom, numTicks, scale, orientation, moveTo, lineTo) {	
		var UTILS = EJSS_SVGGRAPHICS.Utils;

		var cleft = UTILS.crispValue(left), cright = UTILS.crispValue(right);
		var ctop = UTILS.crispValue(top), cbottom = UTILS.crispValue(bottom);

		var path = "";   

    	var minscale = Math.log(scale[0])/Math.log(10);		// min log value
    	var maxscale = Math.log(scale[1])/Math.log(10);		// max log value
    	var scalesize = maxscale - minscale;			// size of log scale
    	var numbands = Math.ceil(scalesize);			// num of log bands
    	if(orientation == -1) 
    		var sizeratio = (cbottom-ctop) / scalesize;		// pixel per log value
    	else
    		var sizeratio = (cright-cleft) / scalesize;		// pixel per log value
    	
    	// generate ticks in each band
    	for (var i = 0; i <= numbands; i++) {
    		var bandmin = Math.pow(10,i+minscale);		// band min
    		var bandmax = Math.pow(10,i+minscale+1);	// band max
    		var bandshift = bandmax / numTicks;			// band part
	    	for (var j = 0; j < numTicks-1; j++) {	    		
	    		// log pos	    		
	    		var number = bandmin + j * bandshift;  // number for text
				if(number != 0 && number >= scale[0] && number <= scale[1]) {
		    		var step = Math.log(number) / Math.log(10);
		    		if(orientation == -1) {
		    			var pos = cbottom - (step - minscale) * sizeratio;
						moveTo(cleft,pos);
						lineTo(cright,pos);
		    		} else { 
		    			var pos = cleft + (step - minscale) * sizeratio;
						moveTo(pos,ctop);
						lineTo(pos,cbottom);
					}
				}  	   		    		    					
			}
		}
	    return path;
	},
	
	/**
	 * Build the cells for mesh
	*/
	buildCells: function(a, b, cells, field, fieldGivenPerCell) {    
		var cellA;
		var cellB;
		var cellZ;
		var cellVector;
		
	    if (cells != null && cells.length>0) {
	      var nCells = cells.length;
	      cellA = new Array(nCells);
	      cellB = new Array(nCells);
	      cellZ = new Array(nCells);
	      cellVector = new Array(nCells);
	      var dimension = 1;
	      if (field != null) {
	        if (fieldGivenPerCell) dimension = field[0][0].length;
	        else dimension = field[0].length;
	      }
	      for (var i=0; i<nCells; i++) {
	        var cell = cells[i];
	        var nCellPoints = cell.length;
	        cellA[i] = new Array(nCellPoints);
	        cellB[i] = new Array(nCellPoints);
	        for (var j=0; j<nCellPoints; j++) {
	          var p = cell[j];
	          cellA[i][j] = a[p];
	          cellB[i][j] = b[p];
	        }
	        if (field != null) {
		      cellZ[i] = new Array(nCellPoints);        
		      cellVector[i] = new Array(nCellPoints);        
	          if (!fieldGivenPerCell) {
	            for (var j=0; j<nCellPoints; j++) {
	              var p = cell[j];
	              cellZ[i][j] = (dimension<=1) ? field[p][0] : EJSS_TOOLS.Mathematics.norm(field[p]);
	              cellVector[i][j] = new Array(dimension);
	              for (var k=0; k<dimension; k++) {
	                cellVector[i][j][k] = field[p][k];
	              }
	            }
	          }
	          else {
	            var cellField = field[i];
	            for (var j=0; j<nCellPoints; j++) {
	              cellZ[i][j] = (dimension<=1) ? cellField[j][0] : EJSS_TOOLS.Mathematics.norm(cellField[j]);
	              cellVector[i][j] = new Array(dimension);
	              for (var k=0; k<dimension; k++) {
	                cellVector[i][j][k] = cellField[j][k];
	              }
	            }
	          }
	        }        
	      }
	    }
	   	return {cellA: cellA, cellB: cellB, cellZ: cellZ, cellVector: cellVector};
	},
		
	/**
	 * Fill cells with the given values for mesh
	 */
	drawPolygons: function(cells, color, drawCell, fillCell){ 
		var cellA = cells.cellA;
		var cellB = cells.cellB;
		var cellZ = cells.cellZ;
		
	    for (var i=0, n=cellA.length; i<n; i++) {
		    var a = cellA[i];
		    var b = cellB[i];
			var values = cellZ[i]; 
		
			if(typeof values != 'undefined') {
				// fill cell	
			    var nVertex = a.length;
			    var vertexIndex = new Array(nVertex);
			
			    // Compute vertex indexes and their range
			    var minIndex = Number.MAX_VALUE, maxIndex = Number.MIN_VALUE; 
			    for(var j=0; j<nVertex; j++) {
			      var valueIndex = color.doubleToIndex(values[j]); // from -1 to numColors
			      minIndex = Math.min(valueIndex, minIndex);
			      maxIndex = Math.max(valueIndex, maxIndex);
			      vertexIndex[j] = valueIndex; 
			    }
			
			    // Computes the subpoligon in each region
			    var mThresholds = color.getColorThresholds()
			    
			    var newCornersX = new Array(2*nVertex);
			    var newCornersY = new Array(2*nVertex);    
			    for (var levelIndex = minIndex; levelIndex<=maxIndex; levelIndex++) { // for each level affected
			      var pointsAdded = 0;
			      for (var point = 0; point<nVertex; point++) { // for each point
			        var nextPoint = (point+1) % nVertex;
			
			        if (vertexIndex[point]<=levelIndex && vertexIndex[nextPoint]>=levelIndex) { // There is a bottom-up change of level
			          if (vertexIndex[point]==levelIndex) { // the point is on the current level
			            newCornersX[pointsAdded] = a[point];
			            newCornersY[pointsAdded] = b[point];
			            pointsAdded++;
			          }
			          else { // Add the point where the change occurs
			            //(x,y,levels[l]) = (x_p, y_p, z_p) + lambda((x_n, y_n, z_n)-(x_p, y_p, z_p))
			            //where (x_p, y_p) are point's coord and z_p can be calculated as the projection of (x_p, y_p) over levels vector 
			            //and (x_n, y_n) are next's coord and z_n can be calculated as the projection of (x_n, y_n) over levels vector
			            var lambda = (mThresholds[levelIndex]-values[point])/(values[nextPoint]-values[point]);
			            newCornersX[pointsAdded] = Math.round(a[point]+lambda*(a[nextPoint]-a[point]));
			            newCornersY[pointsAdded] = Math.round(b[point]+lambda*(b[nextPoint]-b[point]));
			            /*
					    if (newCornersX[pointsAdded]>300) {
					     console.log("level 1 added = "+newCornersX[pointsAdded]+ " division by "+(values[nextPoint]-values[point]));
					     console.log("Values = "+values[point]+ ", "+values[nextPoint]);
					     console.log("point index = "+vertexIndex[point]+ " next point index = "+vertexIndex[nextPoint]);
					     console.log("Should be point index = "+color.doubleToIndex(values[point])+ " next point index = "+color.doubleToIndex(values[nextPoint]));
					     console.log("Points= "+a[point]+ ", "+a[nextPoint]);
					     console.log("lambda= "+lambda);
			       for (var i=0,n=mThresholds.length; i<n; i++) console.log("Thresholds "+mThresholds[i]);
					     console.log("mThreshold= "+mThresholds[levelIndex]+ " level index = "+levelIndex);
					    }
					    */
			            pointsAdded++;        
			          }
			          if (vertexIndex[nextPoint]>levelIndex) { // This segment contributes with a second point
			            var lambda = (mThresholds[levelIndex+1]-values[point])/(values[nextPoint]-values[point]);
			            newCornersX[pointsAdded] = Math.round(a[point]+lambda*(a[nextPoint]-a[point]));
			            newCornersY[pointsAdded] = Math.round(b[point]+lambda*(b[nextPoint]-b[point]));
			            pointsAdded++;
			          }
			        } //end bottom-up
			        else if (vertexIndex[point]>=levelIndex && vertexIndex[nextPoint]<=levelIndex) { // There is a top-down change of level
			          if (vertexIndex[point]==levelIndex) { // the point is on the current level
			            newCornersX[pointsAdded] = a[point];
			            newCornersY[pointsAdded] = b[point];
			            pointsAdded++;
			          }
			          else { // Add the point where the change occurs
			            var lambda = (mThresholds[levelIndex+1]-values[point])/(values[nextPoint]-values[point]);
			            newCornersX[pointsAdded] = Math.round(a[point]+lambda*(a[nextPoint]-a[point]));
			            newCornersY[pointsAdded] = Math.round(b[point]+lambda*(b[nextPoint]-b[point]));
			            pointsAdded++;
			          }
			          if (vertexIndex[nextPoint]<levelIndex) { // This segment contributes with a second point
			            var lambda = (mThresholds[levelIndex]-values[point])/(values[nextPoint]-values[point]);
			            newCornersX[pointsAdded] = Math.round(a[point]+lambda*(a[nextPoint]-a[point]));
			            newCornersY[pointsAdded] = Math.round(b[point]+lambda*(b[nextPoint]-b[point]));
			            pointsAdded++;
			          }
			        }    
			      } // end for each point
			
			      if (pointsAdded>0) { // Draw the subpoligon
			      	  fillCell(newCornersX, newCornersY, pointsAdded, color, levelIndex);
			      }      	  
			    } //end for each level
			}
			
			// draw cell
			drawCell(a,b);
		}
	},
	
	
	/**
	 * Build the boundary for mesh
	*/
	buildBoundary: function(a, b, boundary) {        
	  	var boundaryA;
	  	var boundaryB;
	  	
	    if (boundary!=null && boundary.length>0) {
	      var num = boundary.length;
	      boundaryA = new Array(num);
	      boundaryB = new Array(num);
	      for (var i=0; i<num; i++) {
	        var segment = boundary[i];
	        var len = segment.length;
	        boundaryA[i] = new Array(len);
	        boundaryB[i] = new Array(len);
	        for (var j=0; j<len; j++) {
	          var p = segment[j];
	          boundaryA[i][j] = a[p];
	          boundaryB[i][j] = b[p];
	        }
	      }
	    }
	    return {boundaryA: boundaryA, boundaryB: boundaryB};
	},
	
	/**
	 * Build the boundary for mesh
	*/
	drawBoundary: function(boundary, labels, colors, drawSegment) {   
 		var boundaryA = boundary.boundaryA;
 		var boundaryB = boundary.boundaryB;
 		var segmentColor = 'black';
	    for (var i=0, n=boundaryA.length; i<n; i++) {
	        if (labels != null) {
	          var colorIndex = labels[i];
	          if ( colors!=null && colorIndex<colors.length) 
	          	segmentColor = colors[colorIndex];
	        }
	        drawSegment(boundaryA[i],boundaryB[i],segmentColor);
	    }     
	},
	
	/**
	 * Draw Image Field in Canvas
	*/
	drawImageField: function(ctx, pxTopLeftX, pxTopLeftY, xMin, yMin, pixWth, pixHgt, pxPerUnitX, pxPerUnitY, shape) {
	     function xToPix(x) {
		   return pxTopLeftX +(x - xMin) * pxPerUnitX;
		 }
		  
		 function yToPix(y) {
		   return pxTopLeftY -(y - yMin) * pxPerUnitY;
		 }
	
	    var left=Math.round(xToPix(shape.xMin));
	    var top=Math.round(yToPix(shape.yMax)); 
	    var width=Math.round(pixWth); 
	    var height=Math.round(pixHgt);
	    var imageData = ctx.getImageData(left, top, width, height);  // parameters must be integers
	    var maskpx=this.maskRadius*pxPerUnitX;
	    var r=0;
	    var g=0;
	    var b=0;
	    for (var i=0; i<width; i++) {
	      var ii= Math.floor(i*shape.nx/width);
	      //console.log("i="+i+"  ii="+ii);
	      for (var j=0; j<height; j++) {
	        var jj=Math.floor(j*shape.ny/height);
	        g=0;
	        if(shape.data[ii][jj]<shape.lower){
	          b=255;  // blue below lower
	          r=0;
	        }else if(shape.data[ii][jj]<shape.center){
	          b=255*(shape.center-shape.data[ii][jj])/(shape.center-shape.lower);
	          r=0;
	        }else if(shape.data[ii][jj]<shape.upper){
	          b=0;
	          r=255*(shape.data[ii][jj]-shape.center)/(shape.upper-shape.center);
	        }else{  // red above upper
	          b=0;
	          r=255;
	        }
	        if(shape.maskRadius>0){  // check for circular mask
	          //var ri=Math.sqrt(this.xPos[ii]*this.xPos[ii]+this.yPos[jj]*this.yPos[jj]);
	          //if(ri>this.maskRadius){
	          if(Math.sqrt((i-width/2)*(i-width/2)+(j-height/2)*(j-height/2))>maskpx){
	            r=g=224;
	            b=255;
	          }
	        }
	        var index = 4*i+4*width*j;
	        imageData.data[index+0] = r;  //red
	        imageData.data[index+1] = g;  //green
	        imageData.data[index+2] = b;  // blue
	      }
	    }
	    ctx.putImageData(imageData, left, top);		
	}	
};

/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG analyticCurve
 */
EJSS_SVGGRAPHICS.analyticCurve = function(mGraphics, mElement) {
	
	// generates path for analyticCurve
	function pathForanalyticCurve(points, x, y, sx, sy) {
		var path = "";
		for(var i=0; i<points.length; i++) {
		  var point = points[i];	  
	      var xx = x + point[0]*sx;
	      var yy = y + point[1]*sy;
	      var type = point[2];		
		  if ((i==0) || (type == 0)) // 0 is NOT CONNECTION
		  	path += " M " + xx + " " + yy;		// move 
	      else       	
	      	path += " L " + xx + " " + yy;		// line
		}  	  	
	    return path;
	}  	

	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

	// calculate points	    	 
	var numPoints = mElement.getNumPoints();
	var mMinimun = mElement.getMinimun();
	var mMaximun = mElement.getMaximun();
		var min = ( (typeof mMinimun == "undefined" || mMinimun === null) ?  mElement.getPanel().getRealWorldXMin() : mMinimun);
		var max = ( (typeof mMaximun == "undefined" || mMaximun === null) ?  mElement.getPanel().getRealWorldXMax() : mMaximun);
	
//	var min = (mElement.getMinimun()? mElement.getMinimun() : mElement.getPanel().getRealWorldXMin());
//	var max = (mElement.getMaximun()? mElement.getMaximun() : mElement.getPanel().getRealWorldXMax());
	var vble = mElement.getVariable();
	var fx = mElement.getFunctionX();
	var fy = mElement.getFunctionY();	
   	var parser = EJSS_DRAWING2D.functionsParser();
   	
    var exprfx;
	var exprfy;
	var mustReturn = false;
	try {
	  exprfx = parser.parse(fx);
	}
	catch (errorfx) {
  	  console.log ("Analytic curve error parsing FunctionX: "+fx);
	  mustReturn = true;
	}
	if (!mustReturn) {
	  try {
	    exprfy = parser.parse(fy);
	  }
	  catch (errorfy) {
  	    console.log ("Analytic curve error parsing FunctionY: "+fy);
	   	mustReturn = true;
	  }
	}
	if (mustReturn) {
	  mElement.getController().invokeAction("OnError");
	  return;
	}
	
   	var step = (max-min)/(numPoints-1);
   	
	var points = [];
    var vblevalue = {};
    var parameters = mElement.getParameters();
    for (var param in parameters) { 
      vblevalue[param] = parameters[param];
	 }
    
   	try {
   	  for(var j=0, i=min; i<=max; i+=step) {
   		vblevalue[vble] = i;
   		  var fxvalue = exprfx.evaluate(vblevalue);
   		  var fyvalue = exprfy.evaluate(vblevalue);
	   	  if(!isNaN(fxvalue) && !isNaN(fyvalue)) {   		
	   		points[j] = [];
	    	points[j][0] = fxvalue;	
			points[j++][1] = fyvalue;
		  }
		}
	}
	catch(error) {
	  mElement.getController().invokeAction("OnError");
	 } // do not complain
	
	// draw points
    var analyticCurvepath = pathForanalyticCurve(points, x-mx, y-my, size[0], size[1])     
    if(analyticCurvepath !== "") {
    	mShape.setAttribute('d', analyticCurvepath);
    		
		// set style
	    var style = mElement.getStyle(); 
	    if(style.getDrawFill())	
	    	mShape.setAttribute("fill",style.getFillColor());
	    else 
	    	mShape.setAttribute("fill","none");    
	    if(style.getDrawLines()) {
	    	mShape.setAttribute("stroke",style.getLineColor());
	    	mShape.setAttribute("stroke-width",style.getLineWidth());
	    } else {
	    	mShape.setAttribute("stroke","none");
	    	mShape.setAttribute("stroke-width",0);    	
	    }        
		mShape.setAttribute("shapeRendering",style.getShapeRendering());  	
		
	    var attributes = style.getAttributes();
	    for (var attr in attributes) {
	      mShape.setAttribute(attr,attributes[attr]);
	    }		
	}

	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG arrow
 */
EJSS_SVGGRAPHICS.arrow = function(mGraphics, mElement) {  
	var UTILS = EJSS_SVGGRAPHICS.Utils;
	var Arrow = EJSS_DRAWING2D.Arrow;
	
	function createMarkDef(name, mark, orient, width, height, stroke, color, color2, sizex, sizey) {
		// create marker
		var path, path2;		
		var marker = mGraphics.getElementById(name);						
		if(marker === null) {
			marker = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
			marker.setAttribute('id', name);
			path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
			path.setAttribute('id', name + ".markpath");
			marker.appendChild(path); 					
			path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
			path2.setAttribute('id', name + ".markpath2");
			marker.appendChild(path2); 					
			mGraphics.getElementsByTagName('defs')[0].appendChild(marker);
		} else {
			path = mGraphics.getElementById(name + ".markpath");		
			path2 = mGraphics.getElementById(name + ".markpath2");
			// for bug in IE
			mGraphics.getElementsByTagName('defs')[0].insertBefore(marker, marker);						
		}
				
		var vwidth = width + 2*stroke;
		var vheight = height + 2*stroke;
		marker.setAttribute('viewBox', (-stroke) + " " + (-stroke) + " " + vwidth + " " + vheight);
		marker.setAttribute('refX', width/2);
		marker.setAttribute('refY', height/2);
		marker.setAttribute('markerUnits', 'userSpaceOnUse');
		marker.setAttribute('markerWidth', vwidth);
		marker.setAttribute('markerHeight', vheight);
		marker.setAttribute('orient', orient);

		// create path
		path.setAttribute('fill', color);								
		path.setAttribute('stroke', color);   
		path.setAttribute('stroke-width', stroke);		
		path.setAttribute('style', '  marker-start :none;   marker-end :none; ');
		path2.setAttribute('fill', color2);								
		path2.setAttribute('stroke', color2);   
		path2.setAttribute('stroke-width', stroke);		
		path2.setAttribute('style', '  marker-start :none;   marker-end :none; ');
		switch(mark) {
			case Arrow.TRIANGLE:				
				var str = "M 0 0 L " + width + " " + (height/2) + " L 0 " + height + " z";
				path.setAttribute('d', str);
				break;			
			case Arrow.INVTRIANGLE:		
				var str = "M " + width + " 0 L 0 " + (height/2) + " L " + width + " " + height + " z";
				path.setAttribute('d', str);
				break;			
			case Arrow.ANGLE:
				// cut extremes
				// marker.setAttribute('viewBox', "0 0 " + vwidth + " " + height);
				marker.setAttribute('markerHeight', height);
				marker.setAttribute('refX', width);
				// create angle
				var p1 = -(stroke-1);
				var p2 = height+(stroke-1);					
				var str = "M 0 " + p1 + " L " + width + " " + (height/2) + " L 0 " + p2;				  
				path.setAttribute('d', str);
				path.setAttribute('fill', 'none');								
				break;			
			case Arrow.INVANGLE:
				// cut extremes
				// marker.setAttribute('viewBox', "0 0 " + vwidth + " " + height);
				marker.setAttribute('markerHeight', height);
				marker.setAttribute('refX', 1);
				// create angle
				var p1 = -(stroke-1);
				var p2 = height+(stroke-1);					
				var str = "M " + width + " " + p1 + " L 0 " + (height/2) + " L " + width + " " + p2;				  
				path.setAttribute('d', str);
				path.setAttribute('fill', 'none');								
				break;			
			case Arrow.LINE:				
				var str = "M " + (width/2) + " 0 L " + (width/2) + " " + height + " z"; 
				path.setAttribute('d', str);
				break;			
			case Arrow.RECTANGLE:			
				var str = "M 0 0 L " + width + " 0 L " + width + " " + height + " L 0 " + height + " z"; 
				path.setAttribute('d', str);
				break;			
			case Arrow.DIAMOND:
				var str = "M " + (width/2) + " 0 L " + width + " " + (height/2) + " L " + (width/2) + " " + height + " L 0 " + (height/2) + " z"; 
				path.setAttribute('d', str);
				break;			
			case Arrow.CIRCLE:
				var radius = (width < height) ? width : height;
				radius /= 2;
				var str = "M " + (width/2) + " " + (height/2) + " m " + (-radius) + ", 0 a " + radius + "," + radius + " 0 1,0 " + (radius*2) + ",0" + 
					" a " + radius + "," + radius + " 0 1,0 " + (-radius*2) + ",0"; 				  
				path.setAttribute('d', str);
				break;
			case Arrow.WEDGE:
				// without stroke
				path.setAttribute('stroke-width', 0);
				path2.setAttribute('stroke-width', 0.5);
				// create wedge (fixed position)
				var angle_rot = ((typeof sizey === "undefined") || (sizey===0))? 0 : Math.atan(sizex/sizey);
				var radius = (width < height) ? width : height;
				radius /= 2;
				var x = (width/2);
				var y = (height/2);
				var p11 = x + radius*Math.cos(angle_rot);
				var p12 = y - radius*Math.sin(angle_rot);		
				var p21 = x + radius*Math.cos(Math.PI/3.25 - angle_rot);
				var p22 = y + radius*Math.sin(Math.PI/3.25 - angle_rot);				
				var wedge = " M " + x + " " + y + " L " + p11 + "," + p12 + " A " + radius + "," + radius + " 0 0,1 " + p21 + "," + p22; 				  
				var circle = "M " + x + " " + y + " m " + (-radius) + ", 0 a " + radius + "," + radius + " 0 1,0 " + (radius*2) + ",0" + 
					 " a " + radius + "," + radius + " 0 1,0 " + (-radius*2) + ",0 ";
				path.setAttribute('d', circle);
				path2.setAttribute('d', wedge);				
				break;		
			case Arrow.POINTED:
				// without stroke
				marker.setAttribute('refX', width);
				path.setAttribute('stroke-width', 0);
				// create pointed mark
				var str = "M 0 0 L " + width + " " + (height/2) + " L 0 " + height + 
					" L " + (width/2) + " " + (height/2) + " L 0 0 z";
				path.setAttribute('d', str);
				break;			
			case Arrow.CURVE:
				// without stroke
				marker.setAttribute('refX', width);
				path.setAttribute('stroke-width', 0);
				// create curve mark
				var radius1 = width*3;
				var radius2 = height/2.5;
				var str = "M 0 0 " +
					" A " + radius1 + "," + radius1 + " 1 0,0 " + width + "," + (height/2) + 
					" A " + radius1 + "," + radius1 + " 1 0,0 " + " 0," + height + 
					" A " + radius2 + "," + radius2 + " 1 0,0 " + " 0," + (height/2) + 
					" A " + radius2 + "," + radius2 + " 1 0,0 " + " 0,0 z";
				path.setAttribute('d', str);
				break;								
		}		
		
	}
		
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get line
	var mLine = mGraphics.getElementById(mElement.getName());	
	if (mLine === null) { 	// exits?
	    mLine = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mLine.setAttribute("id", mElement.getName());
	    group.appendChild(mLine);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
	// draw tip
	if((size[0] != 0) || (size[1] != 0)) {    			
		// create end mark	
		if(mElement.getMarkEnd() != Arrow.NONE) {
			var endMax = (mElement.getMarkProportion() * EJSS_TOOLS.Mathematics.norm([size[0],size[1]])) ;
			var endW = (endMax != 0 && endMax < mElement.getMarkEndWidth()) ? endMax : mElement.getMarkEndWidth();
			var endH = (endMax != 0 && endMax < mElement.getMarkEndHeight()) ? endMax : mElement.getMarkEndHeight();
			var color = (mElement.getMarkEndColor() == "none") ? mElement.getStyle().getLineColor() : mElement.getMarkEndColor();
			var stroke = (mElement.getMarkEndStroke() < 0) ? mElement.getStyle().getLineWidth() : mElement.getMarkEndStroke();   
			createMarkDef(mElement.getName() + ".end", mElement.getMarkEnd(), mElement.getMarkEndOrient(), endW, endH, stroke, color);
			mLine.setAttribute('marker-end', 'url(#' + mElement.getName() + ".end" + ')');
		} else {
			mLine.removeAttribute('marker-end');
		}
				
		// create mid mark
		if(mElement.getMarkMiddle() != Arrow.NONE) {
			var midMax = (mElement.getMarkProportion() * EJSS_TOOLS.Mathematics.norm([size[0],size[1]])) ;
			var midW = (midMax != 0 && midMax < mElement.getMarkMiddleWidth()) ? midMax : mElement.getMarkMiddleWidth();
			var midH = (midMax != 0 && midMax < mElement.getMarkMiddleHeight()) ? midMax : mElement.getMarkMiddleHeight();
			var color = (mElement.getMarkMiddleColor() == "none") ? mElement.getStyle().getLineColor() : mElement.getMarkMiddleColor();
			var colorsnd = (mElement.getMarkMiddleColorSnd() == "none") ? mElement.getStyle().getLineColor() : mElement.getMarkMiddleColorSnd();
			var stroke = (mElement.getMarkMiddleStroke() < 0) ? mElement.getStyle().getLineWidth() : mElement.getMarkMiddleStroke();						
			createMarkDef(mElement.getName() + ".mid", mElement.getMarkMiddle(), mElement.getMarkMiddleOrient(), midW, midH, stroke, color, colorsnd, size[0], size[1]);
			mLine.setAttribute('marker-mid', 'url(#' + mElement.getName() + ".mid" + ')');
		} else {
			mLine.removeAttribute('marker-mid');
		}
		
		// create start mark
		if(mElement.getMarkStart() != Arrow.NONE) {
			var stMax = (mElement.getMarkProportion() * EJSS_TOOLS.Mathematics.norm([size[0],size[1]])) ;
			var stW = (stMax != 0 && stMax < mElement.getMarkStartWidth()) ? stMax : mElement.getMarkStartWidth();
			var stH = (stMax != 0 && stMax < mElement.getMarkStartHeight()) ? stMax : mElement.getMarkStartHeight();		
			var color = (mElement.getMarkStartColor() == "none") ? mElement.getStyle().getLineColor() : mElement.getMarkStartColor();
			var stroke = (mElement.getMarkStartStroke() < 0) ? mElement.getStyle().getLineWidth() : mElement.getMarkStartStroke();						
			createMarkDef(mElement.getName() + ".start", mElement.getMarkStart(), mElement.getMarkStartOrient(), stW, stH, stroke, color);		
			mLine.setAttribute('marker-start', 'url(#' + mElement.getName() + ".start" + ')');			
		} else {
			mLine.removeAttribute('marker-start');
		}
	} else {
		mLine.removeAttribute('marker-end');
		mLine.removeAttribute('marker-mid');
		mLine.removeAttribute('marker-start');		
	}
		
	// draw line
	  var CR = UTILS.crispValue;

	  var style = mElement.getStyle();
    switch (mElement.getSplineType()) {
      default : 
      case Arrow.SPLINE_NONE :
        if(style.getShapeRendering() == "crispEdges") {
          if(mElement.getMarkMiddle() != Arrow.NONE) {  // point in the middle
              mLine.setAttribute('d', "M " + CR(x-mx) + " " + CR(y-my) + 
                " L " + CR(x) + " " + CR(y) +
                " L " + CR(x+mx) + " " + CR(y+my));   
          } else {
              mLine.setAttribute('d', "M " + CR(x-mx) + " " + CR(y-my) +
                " L " + CR(x+mx) + " " + CR(y+my));
            }
          } else {
          if(mElement.getMarkMiddle() != Arrow.NONE) {  // point in the middle
              mLine.setAttribute('d', "M " + (x-mx) + " " + (y-my) + 
                  " L " + (x) + " " + (y) + 
                  " L " + (x+mx) + " " + (y+my));   
          } else {
              mLine.setAttribute('d', "M " + (x-mx) + " " + (y-my) + 
                  " L " + (x+mx) + " " + (y+my));
           }
        }       
        break;
      case Arrow.SPLINE_CUBIC :
        var controlX = Math.abs(mx)*0.25;
        if(style.getShapeRendering() == "crispEdges") {
          if(mElement.getMarkMiddle() != Arrow.NONE) {  // point in the middle
              mLine.setAttribute('d', "M " + CR(x-mx) + " " + CR(y-my) + 
                  " Q " + CR(x-controlX) + " " + CR(y-my) +", " + CR(x) + " " + CR(y) +
                  " Q " + CR(x+controlX) + " " + CR(y+my)+ ", " + CR(x+mx) + " " + CR(y+my));   
          } else {
            mLine.setAttribute('d', "M " + CR(x-mx) + " " + CR(y-my) +
                " C " + CR(x) + " " + CR(y-my) +", " + CR(x) +
                " " + CR(y+my)+ ", " + CR(x+mx) + " " + CR(y+my));
            }
          } else {
          if(mElement.getMarkMiddle() != Arrow.NONE) {  // point in the middle
              mLine.setAttribute('d', "M " + (x-mx) + " " + (y-my) + 
                  " Q " + (x-controlX) + " " + (y-my) + ", " + (x)    + " " + (y) +
                  " Q " + (x+controlX) + " " + (y+my) + ", " + (x+mx) + " " + (y+my) 
                  );   
//                  " C " + (x)    + " " + (y-my) +", " + (x) +" " + (y-my)+ ", " + (x)    + " " + (y) +
//                  " C " + (x) + " " + (y+my) +", " + (x)    +" " + (y+my)+ ", " + (x+mx) + " " + (y+my));   
          } else {
              mLine.setAttribute('d', "M " + (x-mx) + " " + (y-my) + 
                  " C " + x + " " + (y-my) +", " + x +" " + (y+my)+ ", " + (x+mx) + " " + (y+my) +
                  " L " + (x+mx) + " " + (y+my));
           }
        }       
        break;
    }

	// set style
    if(style.getDrawFill()) 	
    	mLine.setAttribute("fill",style.getFillColor());    	
    else  
    	mLine.setAttribute("fill","none");
    if(style.getDrawLines()) {
    	mLine.setAttribute("stroke",style.getLineColor());
    	mLine.setAttribute("stroke-width",style.getLineWidth());
    } else {
    	mLine.setAttribute("stroke","none");
    	mLine.setAttribute("stroke-width",0);    	
    }        
	mLine.setAttribute("shapeRendering",style.getShapeRendering());  	

    var attributes = style.getAttributes();
    for (var attr in attributes) {
      mLine.setAttribute(attr,attributes[attr]);
    }

	return mLine;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG axis
 */
EJSS_SVGGRAPHICS.axis = function(mGraphics, mElement) {
	var UTILS = EJSS_SVGGRAPHICS.Utils;

	// draw axis line
	function drawLine(group, x, y, mx, my, style, orient, inverted) {
	    var mLine = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    group.appendChild(mLine);	    

		if(orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL) {	// vertical axis
			if(inverted) {
				my = -my;
				y = -y;	
			}
		 	// set attributes	    	
		    if(style.getShapeRendering() == "crispEdges") {
		    	mLine.setAttribute('d', "M " + UTILS.crispValue(x) + " " + UTILS.crispValue(y-0.5-my) 
		    		+ " L " + UTILS.crispValue(x) + " " + UTILS.crispValue(y-0.5+my));
		    } else {
		    	mLine.setAttribute('d', "M " + (x) + " " + (y-my) + " L " + (x) + " " + (y+my));
			}  	   		    		    		
		} else {	// horizontal axis
		 	// set attributes	    	
		    if(style.getShapeRendering() == "crispEdges") {
		    	mLine.setAttribute('d', "M " + UTILS.crispValue(x+0.5-mx) + " " + UTILS.crispValue(y) 
		    		+ " L " + UTILS.crispValue(x+0.5+mx) + " " + UTILS.crispValue(y));
		    } else {
		    	mLine.setAttribute('d', "M " + (x-mx) + " " + (y) + " L " + (x+mx) + " " + (y));
			}  	   		
		}		
	}

	function drawTick(group, x, y, ticksize, style, orient) {
		if(orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL) {	// vertical axis
		    var mTick = document.createElementNS("http://www.w3.org/2000/svg","path"); 
		    group.appendChild(mTick);
		    if(style.getShapeRendering() == "crispEdges") {
		    	mTick.setAttribute('d', "M " + UTILS.crispValue(x-ticksize/2) + " " + UTILS.crispValue(y) 
		    		+ " L " + UTILS.crispValue(x+ticksize/2) + " " + UTILS.crispValue(y));
		    } else {
		    	mTick.setAttribute('d', "M " + (x-ticksize/2) + " " + (y) + " L " + (x+ticksize/2) + " " + (y));
			}  	   					
		} else {
		    var mTick = document.createElementNS("http://www.w3.org/2000/svg","path"); 
		    group.appendChild(mTick);
		    if(style.getShapeRendering() == "crispEdges") {
		    	mTick.setAttribute('d', "M " + UTILS.crispValue(x) + " " + UTILS.crispValue(y-ticksize/2) 
		    		+ " L " + UTILS.crispValue(x) + " " + UTILS.crispValue(y+ticksize/2));
		    } else {
		    	mTick.setAttribute('d', "M " + (x) + " " + (y-ticksize/2) + " L " + (x) + " " + (y+ticksize/2));
			}  	   		    		    								
		}					
	}	
	
	// text for tick in position (x,y) with font and mode
	function tickText (group, x, y, text, font, horizontal) {
	    // create SVG element group
	    var mTxtGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    group.appendChild(mTxtGroup);	    
	    // create text element
	    var mText = document.createElementNS("http://www.w3.org/2000/svg","text"); 
	    mTxtGroup.appendChild(mText);	    
				    
		// set attributes	    
		mText.setAttribute("font-family",font.getFontFamily());
		mText.setAttribute("font-size",font.getFontSizeString());
	    mText.setAttribute("fill",font.getFillColor());
    	mText.setAttribute("stroke",font.getOutlineColor());    	
	    mText.setAttribute("stroke-width",font.getFontWeight());
	    mText.setAttribute("letterSpacing",font.getLetterSpacing());
	    mText.textContent = text;
		if (horizontal) {
		  x -= mText.getComputedTextLength()/2 + 0.5;
		}	
		else { // vertical axis
		  y += font.getFontSize()/2; // - 0.5;
		}
		mText.setAttribute("x",x);
		mText.setAttribute("y",y);
	    
		// angle of text  	 
/*		var angletext = 0;
		switch(mode) {
			case EJSS_DRAWING2D.Text.MODE_TOPDOWN : angletext = 90; break;
			case EJSS_DRAWING2D.Text.MODE_RIGTHLEFT : angletext = 180; break;
			case EJSS_DRAWING2D.Text.MODE_DOWNTOP : angletext = 270; break;
			case EJSS_DRAWING2D.Text.MODE_LEFTRIGHT: angletext = 0; break;
		}
	    mText.setAttribute("transform","rotate(" + angletext + " " + x + " " + y +")"); 		
*/ 	  	  
	}		

	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // remove SVG element (not reusing element)
	var mGroup = mGraphics.getElementById(mElement.getName());	
	if (mGroup !== null) group.removeChild(mGroup);
	
	if(mElement.getShow()) { // show
	
	    // create SVG element
	    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mGroup.setAttribute("id", mElement.getName());
	    group.appendChild(mGroup);	    
	
		// get position of the mElement center 
	    var pos = mElement.getPixelPosition();
	    var size = mElement.getPixelSizes();     
	    var offset = mElement.getRelativePositionOffset(size);  
	    var x = pos[0]+offset[0];
	    var y = pos[1]+offset[1];
		
		// get half sizes 		
	    var mx = size[0]/2;
	    var my = size[1]/2;

		// properties	    
	    var style = mElement.getStyle();		// element style   	 	
		var orient = mElement.getOrient();		// axis orientation (vertical or horizontal)
		var inverted = mElement.getInvertedScaleY();
	    
	    // draw the line for axis
		drawLine(mGroup, x, y, mx, my, style, orient, inverted);
		 		
		// get axis size in pixel
		var segsize = (orient == EJSS_DRAWING2D.Axis.AXIS_VERTICAL)? Math.abs(size[1]):Math.abs(size[0]);		
		 		
		// draw axis (based on ticks mode)
		var ticksmode = mElement.getTicksMode();			// decimal or logarithmic
		if (ticksmode == EJSS_DRAWING2D.Axis.SCALE_LOG) { // logarithmic

		    var scale = mElement.getScale();		// axis scale 			
			if(scale[0] > 0 && scale[1] > 0) {  // valid scale			
				// get number of axis ticks
				var ticks = mElement.getTicks(); 	
	
			    // scale
				var scalePrecision = mElement.getScalePrecision();	// number of decimals for text
				
				// draw ticks 
				var ticksize = mElement.getTicksSize();				// ticks size in pixels
				EJSS_GRAPHICS.GraphicsUtils.drawLogTicks(x, y, mx, my, segsize, ticksize, scale, ticks, orient, 
						function(x, y, ticksize, orient) {
							drawTick(mGroup, x, y, ticksize, style, orient);			
						});		
			
				// draw ticks text 
				var font = mElement.getFont();
			    var textPosition = mElement.getTextPosition();		// text position (UP or DOWN)
				EJSS_GRAPHICS.GraphicsUtils.drawLogTicksText (x, y, mx, my, segsize, ticks, ticksize, scale, scalePrecision, font, textPosition, orient,
						function(x, y, text, font, horizontal) {
							tickText (mGroup, x, y, text, font, horizontal);
						});			
			}
			
		} else if (ticksmode == EJSS_DRAWING2D.Axis.SCALE_NUM) { // decimal
			 			 		
			// calculate step in pixels	
		    var step = 0;     // step for ticks (pixels)
		    var tickstep = 0; // step for ticks (real units)
			if (!mElement.getAutoTicks()) {		// no auto-ticks
		    	var ticks = mElement.getTicks();	// number of ticks
				// whether the number of ticks exits, changes step for ticks and scale 
			    if (ticks != 0) { step = segsize/ticks; } else {
			    	step = mElement.getStep();
			    	tickstep = mElement.getTickStep(); 
			    } 	    	
			} else {	// auto-ticks
				var stepmin = mElement.getAutoStepMin();		// step min in pixels
				var ticksrange = mElement.getAutoTicksRange();	// ticks range
				// find step based on ticks range
				for(var i=ticksrange.length-1; i>=0; i--)	{	
					step = Math.abs(segsize/ticksrange[i]);
					if (step*1.001 >= stepmin) break;
				}
			}
		    
			var scalePrecision = mElement.getScalePrecision();	// number of decimals for text
			var scale = mElement.getScale();		// axis scale 	
			// values for scale
		    if(tickstep == 0) {
				var scalestep = Math.abs((scale[1] - scale[0]) * step / segsize);  // step in axis scale
			} else {
				var scalestep = tickstep;
				step = Math.abs((scalestep * segsize) / (scale[1] - scale[0])); 
			}			
			
			// adjust step to decimals of precision
			var decimals = Math.pow(10,scalePrecision);
			var scalestepTmp = Math.round(scalestep * decimals) / decimals;
			if(scalestepTmp > 0) {
				scalestep = scalestepTmp; 
				step = Math.abs(scalestepTmp * segsize / (scale[1] - scale[0]));
			}
			
			// check fixed tick
			var fixedTicks = mElement.getFixedTick();	  
			var tickfixed = scale[1];
			if (!isNaN(fixedTicks)) {
			  if (fixedTicks < scale[0]) tickfixed = fixedTicks + (Math.floor((scale[0]-fixedTicks)/scalestep)+1)*scalestep;
			  else if (fixedTicks > scale[1])  tickfixed = fixedTicks - (Math.floor((fixedTicks-scale[1])/scalestep)+1)*scalestep;
		      else tickfixed = fixedTicks; 
			}		
	
			// tick fixed in axis scale
			var scaleshift = Math.abs((scale[0] - tickfixed) % scalestep);						
			var dist = Math.abs(scaleshift-scalestep);	// fitting shift
			if(scaleshift < 0.001 || dist < 0.001) scaleshift = 0;						
			var shift = segsize * scaleshift / Math.abs(scale[1] - scale[0]);	// shift in pixels				
										    		
			// draw ticks based on step and shift
			var ticksize = mElement.getTicksSize();				// ticks size in pixels
			EJSS_GRAPHICS.GraphicsUtils.drawDecTicks(x, y, mx, my, ticksize, step, shift, orient, inverted, 
					function(x, y, ticksize, orient) {
						drawTick(mGroup, x, y, ticksize, style, orient);
					});		
		
			// draw ticks text based on scaleshift, scalestep and scale
			var font = mElement.getFont();
		    var textPosition = mElement.getTextPosition();		// text position (UP or DOWN)
			EJSS_GRAPHICS.GraphicsUtils.drawDecTicksText (x, y, mx, my, ticksize, step, shift, scale, scalePrecision, scalestep, scaleshift, font, textPosition, orient, inverted, 
					function(x, y, text, font, horizontal) {
						tickText(mGroup, x, y, text, font, horizontal);
					});
		}
					
		// set style
	    if(style.getDrawFill()) 	
	    	mGroup.setAttribute("fill",style.getFillColor());
	    else 
	    	mGroup.setAttribute("fill","none");    
	    if(style.getDrawLines()) {
	    	mGroup.setAttribute("stroke",style.getLineColor());
	    	mGroup.setAttribute("stroke-width",style.getLineWidth());
	    } else {
	    	mGroup.setAttribute("stroke","none");
	    	mGroup.setAttribute("stroke-width",0);    	
	    }        
		mGroup.setAttribute("shapeRendering",style.getShapeRendering());  	
	
	    var attributes = style.getAttributes();
	    for (var attr in attributes) {
	      mGroup.setAttribute(attr,attributes[attr]);
	    }
	}
	
	return mGroup;         
}
/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG byteRaster
 */
EJSS_SVGGRAPHICS.byteRaster = function(mGraphics, mElement) {
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape		
	var mGroup = mGraphics.getElementById(mElement.getName());
	var mShape;
	if (mGroup == null) { 	// exists?
	    // create SVG mElement group
	    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mGroup.setAttribute("id", mElement.getName());
	    group.appendChild(mGroup);	    
	    
		mShape = document.createElementNS("http://www.w3.org/2000/svg","image");
		mShape.setAttribute("id", mElement.getName() + ".img");
		mGroup.appendChild(mShape); 	    
	} else {
		mShape = mGraphics.getElementById(mElement.getName() + ".img");
	}
	
	if(mElement.getDataChanged()) { // new data or colors
		var data = mElement.getData();
		var colors = mElement.getColorMapper().getColors();			
		var xlen = data.length;
		if (xlen>0) {
		  var ylen = data[0].length;
		  var num = mElement.getColorMapper().getNumberOfColors();
		
		// Worker previous
		// var png = EJSS_SVGGRAPHICS.Utils.PNGCanvas (xlen, ylen, num, data, colors);		
	    // mShape.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', png);	    
	    // mElement.setDataChanged(false);		 	    	    	

		  // using worker
		  EJSS_SVGGRAPHICS.Utils.PNGCanvas (xlen, ylen, num, data, colors,
			function(png) {
				mShape.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', png);		
			});		
	    }	    	    
	    mElement.setDataChanged(false);		 	    	    	
	}

	// get position of the mElement center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);    

	mShape.setAttribute("x",x-mx);
	mShape.setAttribute("y",y-my);
	mShape.setAttribute("width",Math.abs(size[0]));
	mShape.setAttribute("height",Math.abs(size[1]));

	var style = mElement.getStyle();				// element style
    var attributes = style.getAttributes();
    for (var attr in attributes) {
      mShape.setAttribute(attr,attributes[attr]);
    }

	return mGroup;         
}

/*  // Alternative using SVG

    // create other SVG mElement group
    var mPixels = document.createElementNS("http://www.w3.org/2000/svg","g"); 
    mPixels.setAttribute("id", mElement.getName() + ".pixels");    	   

	var myBasePixel = document.createElementNS("http://www.w3.org/2000/svg","path");	
	myBasePixel.setAttribute("stroke-width",1.5);	    
     	
  	for(var i=0; i<xlen; i++) { 
  		for(var j=0; j<ylen; j++) {
  			// draw pixel
			color = colors[data[i][j]];
      		var myPixel = myBasePixel.cloneNode(true);      		
			myPixel.setAttribute('d', "M " + (i) + " " + (j) + " l 0 1");
			myPixel.setAttribute("stroke",color);
			mPixels.appendChild(myPixel);			
  		}  		
	}	

	// scale  	  	  	  		
  	var left = x-mx, top = y+my;
    var stepx = Math.abs(size[0]/xlen);
    var stepy = Math.abs(size[1]/ylen);
    mPixels.setAttribute("transform","scale(" + (stepx) + "," + (stepy) +") translate(" + left + "," + top + ")"); 
*/
/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG canvas
 */
EJSS_SVGGRAPHICS.canvas = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	var myCanvasSrc;	
	if (mShape === null) { 	// exits?
	    // create SVG element
	   	mShape = document.createElementNS("http://www.w3.org/2000/svg","foreignObject");
	    mShape.setAttribute("id",mElement.getName());	   
	    group.appendChild(mShape);
	    // create canvas src 
		myCanvasSrc = document.createElement('canvas');
		myCanvasSrc.setAttribute("id", mElement.getName() + ".canvas");

		mShape.appendChild(myCanvasSrc);	    
	}
	else {
		myCanvasSrc = document.getElementById(mElement.getName() + ".canvas");
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];

	myCanvasSrc.width = Math.abs(size[0]);
	myCanvasSrc.height = Math.abs(size[1]);
	
	mShape.width = Math.abs(size[0]);
	mShape.height = Math.abs(size[1]);
		
	// get half sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);
    
 	// set SVG element	
	mShape.setAttribute("x",(x-mx));
	mShape.setAttribute("y",(y-my));    	    					  	
	
	var dim = mElement.getDimensions();
  	var deltaX = dim[1] - dim[0];
  	var deltaY = dim[3] - dim[2];
    
	var pixWth = Math.abs(size[0]);
	var pixHgt = Math.abs(size[1]);
	var pxPerUnitX = pixWth / deltaX;
	var pxPerUnitY = pixHgt / deltaY;
	var pxTopLeftX = x - pixWth / 2;
	var pxTopLeftY = y + pixHgt / 2;
   
    //draws all objects within this.drawables
    var drawables = mElement.getDrawables();
    var context = myCanvasSrc.getContext('2d');
    context.fillRect(0,0,myCanvasSrc.width-0.5,myCanvasSrc.height-0.5);
    for (var i = 0; i < drawables.length; i++) {
      if(typeof drawables[i].imageField != "undefined")
        EJSS_GRAPHICS.GraphicsUtils.drawImageField(context, 
        	pxTopLeftX, pxTopLeftY, xMin, yMin, pixWth, pixHgt, pxPerUnitX, pxPerUnitY, drawables[i]);
      else if(typeof drawables[i].run != "undefined")
      	drawables[i].draw(context,
      		pxTopLeftX, pxTopLeftY, xMin, yMin, pixWth, pixHgt, pxPerUnitX, pxPerUnitY);
    }

	var attributes = mElement.getStyle().getAttributes();
	for (var attr in attributes) {
	  	mShape.setAttribute(attr,attributes[attr]);
	}    	    	    					  	
	
	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG cellLattice
 */
EJSS_SVGGRAPHICS.cellLattice = function(mGraphics, mElement) {	
	var UTILS = EJSS_SVGGRAPHICS.Utils;   

	function pathForGrid_crispEdges(left, top, right, bottom, stepx, stepy) {
		var path = "";   
		
		var cleft = UTILS.crispValue(left), cright = UTILS.crispValue(right);
		var ctop = UTILS.crispValue(top), cbottom = UTILS.crispValue(bottom);
		// vertical lines
		if (stepx == 0) stepx = Math.abs(right-left);
	    for (var i = left; i <= Math.max(right,cright)+0.5; i = i+stepx) {
	  	  path += " M " + UTILS.crispValue(i) + " " + ctop + " L " + UTILS.crispValue(i) + " " + cbottom; 
	    }
	    // horizontal lines
	    if (stepy == 0) stepy = Math.abs(top-bottom);
	    for (var i = bottom; i >= Math.min(top,ctop)-0.5; i = i-stepy) {
	  	  path += " M " + cleft + " " + UTILS.crispValue(i) + " L " + cright + " " + UTILS.crispValue(i); 
	    }
	    return path;
	}  
	
	function pathForGrid(left, top, right, bottom, stepx, stepy) {	 	
		var path = "";   
		// vertical lines
		if (stepx == 0) stepx = Math.abs(right-left);
	    for (var i = left; i <= right; i = i+stepx) {
	  	  path += " M " + i + " " + top + " L " + i + " " + bottom; 
	    }
	    // horizontal lines
	    if (stepy == 0) stepy = Math.abs(top-bottom);
	    for (var i = bottom; i >= top; i = i-stepy) {
	  	  path += " M " + left + " " + i + " L " + right + " " + i; 
	    }
	    return path;
	}  
	
	function rectCell(group, x, y, sx, sy, rendering, fill) {
		// create rectangle
	    var mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    group.appendChild(mShape);	   
	    		
		// create path	
		var mx = sx/2, my = sy/2;    	   	    
		if(rendering == "crispEdges") {
		    mShape.setAttribute('d', 
		    	 "M " + UTILS.crispValue(x-mx) + " " + UTILS.crispValue(y+my) + 
		    	" L " + UTILS.crispValue(x+mx) + " " + UTILS.crispValue(y+my) + 
		    	" L " + UTILS.crispValue(x+mx) + " " + UTILS.crispValue(y-my) + 
		    	" L " + UTILS.crispValue(x-mx) + " " + UTILS.crispValue(y-my) + " z");
		} else {
		    mShape.setAttribute('d', 
		    	 "M " + (x-mx) + " " + (y+my) + 
		    	" L " + (x+mx) + " " + (y+my) + 
		    	" L " + (x+mx) + " " + (y-my) + 
		    	" L " + (x-mx) + " " + (y-my) + " z");
		}  	 		
		mShape.setAttribute("fill",fill);
		mShape.setAttribute("stroke",fill);
	}

	function gridCell(group, x, y, sx, sy, stepx, stepy, style) {
		// create rectangle
	    var mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    group.appendChild(mShape);	   

		// set attributes
		var mx = sx/2, my = sy/2;    	   	    
		var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
	    if(style.getShapeRendering() == "crispEdges") 
	    	mShape.setAttribute('d', pathForGrid_crispEdges(left, top, right, bottom, stepx, stepy));
	    else {
	    	mShape.setAttribute('d', pathForGrid(left, top, right, bottom, stepx, stepy));
	    }
						
	    if(style.getDrawLines()) {
	    	mShape.setAttribute("stroke",style.getLineColor());
	    	mShape.setAttribute("stroke-width",style.getLineWidth());
	    } else {
	    	mShape.setAttribute("stroke","none");
	    	mShape.setAttribute("stroke-width",0);    	
	    }        
	    
		var attributes = style.getAttributes();
		for (var attr in attributes) {
		    mShape.setAttribute(attr,attributes[attr]);
		}	    
	}
	
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mGroup = mGraphics.getElementById(mElement.getName());			
	if (mGroup !== null) { 	// exits?
		group.removeChild(mGroup);
	}
    // create SVG mElement group
    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
    mGroup.setAttribute("id", mElement.getName());
    group.appendChild(mGroup);	    

	// get position of the mElement center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

	// draw cells
	var style = mElement.getStyle();     
	var data = mElement.getData();
	var colors = mElement.getColorMapper().getColors();
	var xlen = data.length;
	var ylen = data[0].length;

    var stepx = Math.abs(size[0]/xlen);
    var stepy = Math.abs(size[1]/ylen);
   	
  	var left = x-mx+stepx/2, bottom = y-my-stepy/2;		
  	for(var i=0; i<ylen; i++) { 
  		for(var j=0; j<xlen; j++) {
  			// draw rectangle
  			rectCell(mGroup, left+stepx*j, bottom-stepy*i, stepx, stepy, style.getShapeRendering(), colors[data[j][i]]);
  		}  		
  	}
  	 	
  	// draw grid
  	var showGrid = mElement.getShowGrid();
	if (showGrid) gridCell(mGroup, x, y, size[0], size[1], stepx, stepy, style);
	  	
	return mGroup;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG cursor
 */
EJSS_SVGGRAPHICS.cursor = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape	
	var mGroup = mGraphics.getElementById(mElement.getName());
	var mLine1, mLine2;
	if (mGroup === null) { 	// not exists
	    // create SVG element group
	    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mGroup.setAttribute("id", mElement.getName());
	    group.appendChild(mGroup);
	    // create line1 element
	    mLine1 = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mLine1.setAttribute("id", mElement.getName() + ".line1");
	    mGroup.appendChild(mLine1);	    
	    // create line2 element
	    mLine2 = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mLine2.setAttribute("id", mElement.getName() + ".line2");
	    mGroup.appendChild(mLine2);	    	    	    
	} else {
		mLine1 = mGraphics.getElementById(mElement.getName() + ".line1");
		mLine2 = mGraphics.getElementById(mElement.getName() + ".line2");
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    
  	// type of cursor
	var cursorType = mElement.getCursorType();

    // cursor size
    var panel = mElement.getGroupPanel();
    var bounds = panel.getRealWorldCoordinates(); // xmin,xmax,ymin,ymax
	var bottomLeft = panel.toPixelPosition([bounds[0],bounds[2]]);    
	var topRight   = panel.toPixelPosition([bounds[1],bounds[3]]);    
	    
	var style = mElement.getStyle();
	mLine1.setAttribute("stroke-width",0);   	 	
	if (cursorType!=EJSS_DRAWING2D.Cursor.VERTICAL) {
	  var x1 = bottomLeft[0], x2 = topRight[0];
	  var y = pos[1];
	  // keep the cursor inside the panel
	  if (y>bottomLeft[1]) y = bottomLeft[1];
	  else if (y<topRight[1]) y = topRight[1];
	  if (style.getShapeRendering() == "crispEdges") {
	    mLine1.setAttribute('d', "M " + UTILS.crispValue(x1) + " " + UTILS.crispValue(y) 
	    		+ " L " + UTILS.crispValue(x2) + " " + UTILS.crispValue(y));
	  } 
	  else {
	    mLine1.setAttribute('d', "M " + x1 + " " + y + " L " + x2 + " " + y);
	  }  	  
	  mLine1.setAttribute("stroke-width",style.getLineWidth());   	 	
	}
	mLine2.setAttribute("stroke-width",0);   	 	
	if (cursorType!=EJSS_DRAWING2D.Cursor.HORIZONTAL) {
	  var x = pos[0];
	  var y1 = bottomLeft[1], y2 = topRight[1];
	  // keep the cursor inside the panel
	  if (x<bottomLeft[0]) x = bottomLeft[0];
	  else if (x>topRight[0]) x = topRight[0];
	  if (style.getShapeRendering() == "crispEdges") {
	    mLine2.setAttribute('d', "M " + UTILS.crispValue(x) + " " + UTILS.crispValue(y1) 
	    		+ " L " + UTILS.crispValue(x) + " " + UTILS.crispValue(y2));
	  } 
	  else {
	    mLine2.setAttribute('d', "M " + x + " " + y1 + " L " + x + " " + y2);
	  }  	  
	  mLine2.setAttribute("stroke-width",style.getLineWidth());   	 	
	}
	if (style.getDrawFill()) mGroup.setAttribute("fill",style.getFillColor());
    else mGroup.setAttribute("fill","none");    
	mGroup.setAttribute("stroke",style.getLineColor());
	//mGroup.setAttribute("stroke-width",style.getLineWidth());
	mGroup.setAttribute("shapeRendering",style.getShapeRendering());  
    
    var attributes = style.getAttributes();
	for (var attr in attributes) {
      mGroup.setAttribute(attr,attributes[attr]);
    }			 	  	
	
	return mGroup;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG ellipse
 */
EJSS_SVGGRAPHICS.ellipse = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","ellipse"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);
    
 	// set attributes	    	    
    mShape.setAttribute("cx",x);
    mShape.setAttribute("cy",y);
    mShape.setAttribute("rx",mx);
    mShape.setAttribute("ry",my);
	
	// set style
    var style = mElement.getStyle(); 
    if(style.getDrawFill())	
    	mShape.setAttribute("fill",style.getFillColor());
    else 
    	mShape.setAttribute("fill","none");    
    if(style.getDrawLines()) {
    	mShape.setAttribute("stroke",style.getLineColor());
    	mShape.setAttribute("stroke-width",style.getLineWidth());
    } else {
    	mShape.setAttribute("stroke","none");
    	mShape.setAttribute("stroke-width",0);    	
    }        
	mShape.setAttribute("shapeRendering",style.getShapeRendering());  	

	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	mShape.setAttribute(attr,attributes[attr]);
	}

	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG grid
 */
EJSS_SVGGRAPHICS.grid = function(mGraphics, mElement) {
	
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // remove SVG element (not reusing element)
	var mGroup = mGraphics.getElementById(mElement.getName());	
	if (mGroup !== null) group.removeChild(mGroup);
	
    // create SVG element
    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
    mGroup.setAttribute("id", mElement.getName());
    group.appendChild(mGroup);	    
    var mShapeX = document.createElementNS("http://www.w3.org/2000/svg","path"); 
    mShapeX.setAttribute("id", mElement.getName() + "X");
    mGroup.appendChild(mShapeX);	    
    var mShapeY = document.createElementNS("http://www.w3.org/2000/svg","path"); 
    mShapeY.setAttribute("id", mElement.getName() + "Y");
    mGroup.appendChild(mShapeY);

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;

    var showx = mElement.getShowX();
    var showy = mElement.getShowY(); 
    
    var pathX = "";
    var pathY = "";
    
	// draw axis X (based on ticks mode)
	if(showx) {
		var ticksXmode = mElement.getTicksXMode();			// decimal or logarithmic
		if (ticksXmode == EJSS_DRAWING2D.Grid.SCALE_LOG) { // logarithmic
				
		    // scale
		    var scaleX = mElement.getScaleX();		// X axis scale 	
			
			if(scaleX[0] > 0 && scaleX[1] > 0) {  // valid scale
				// get number of axis ticks
			    var ticksX = mElement.getTicksX();
			    
				// set attributes
			    var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
		    	EJSS_GRAPHICS.GraphicsUtils.drawLogGrid(left, top, right, bottom, ticksX, scaleX, 1,
					function(xx,yy) { pathX += " M " + xx + " " + yy; }, 
					function(xx,yy) { pathX += " L " + xx + " " + yy; }); 
			} 
					    		
		} else if (ticksXmode == EJSS_DRAWING2D.Grid.SCALE_NUM) { // decimal
	    	    
		 	// create path	    	   	    
		   	var stepx = 0;	// step for ticks
		   	var tickstepx = 0;
			if (!mElement.getAutoTicksX()) {	  // no auto-ticks    
			    var ticksX = mElement.getTicksX();
			    // whether the number of sticks exits, changes step for ticks
			    if (ticksX != 0) { stepx = Math.abs(size[0]/ticksX); } else {
			    	stepx = mElement.getStepX();
			    	tickstepx = mElement.getTickStepX(); 
			    } 	    	

			} else { // auto-ticks
				var stepxmin = mElement.getAutoStepXMin();		// step y min
				var ticksxrange = mElement.getAutoTicksRangeX();		// ticks x range
		
				// find stepx
				for(var i=ticksxrange.length-1; i>=0; i--)	{	
					stepx = Math.abs(size[0]/ticksxrange[i]);
					if (stepx >= stepxmin) break;
				}
			}	
			
		    // step for scale
			var scalePrecisionX = mElement.getScalePrecisionX();	// number of decimals for scale
		    var scaleX = mElement.getScaleX();		// X axis scale 	
		    if(tickstepx == 0) {
				var scalestepX = Math.abs((scaleX[1] - scaleX[0]) * stepx / size[0]);  // step in X axis scale
			} else {
				var scalestepX = tickstepx;
				stepx = Math.abs((scalestepX * size[0]) / (scaleX[1] - scaleX[0])); 
			}			
						
			// adjust step to decimals of precision	
			var decimalsX = Math.pow(10,scalePrecisionX);
			var scalestepXTmp = Math.round(scalestepX * decimalsX) / decimalsX;
			if(scalestepXTmp > 0) {
				scalestepX = scalestepXTmp;
				stepx = Math.abs(scalestepXTmp * size[0] / (scaleX[1] - scaleX[0]));
			}

			// check fixed tick
			var fixedTicks = mElement.getFixedTickX();	  
			var tickfixed = scaleX[1];
			if (!isNaN(fixedTicks)) {
			  if (fixedTicks < scaleX[0]) tickfixed = fixedTicks + (Math.floor((scaleX[0]-fixedTicks)/scalestepX)+1)*scalestepX;
			  else if (fixedTicks > scaleX[1])  tickfixed = fixedTicks - (Math.floor((fixedTicks-scaleX[1])/scalestepX)+1)*scalestepX;
		      else tickfixed = fixedTicks; 
			}		
				
			// tick fixed in axis scale
			var scaleshift = Math.abs((scaleX[0] - tickfixed) % scalestepX);						
			var dist = Math.abs(scaleshift-scalestepX);	// fitting shift
			if(scaleshift < 0.001 || dist < 0.001) scaleshift = 0;						
			var shiftx = Math.abs(size[0] * scaleshift / (scaleX[1] - scaleX[0]));	// shift in pixels				

			// set attributes
		    var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
	    	EJSS_GRAPHICS.GraphicsUtils.drawDecGrid(left, top, right, bottom, shiftx, stepx, 1,
					function(xx,yy) { pathX += " M " + xx + " " + yy; }, 
					function(xx,yy) { pathX += " L " + xx + " " + yy; }); 
		}		
	}

	// draw axis Y (based on ticks mode)
	if(showy) {
		var ticksYmode = mElement.getTicksYMode();			// decimal or logarithmic
		if (ticksYmode == EJSS_DRAWING2D.Grid.SCALE_LOG) { // logarithmic
						
		    // scale
		    var scaleY = mElement.getScaleY();		// Y axis scale 	

			if(scaleY[0] > 0 && scaleY[1] > 0) {  // valid scale
				// get number of axis ticks
			    var ticksY = mElement.getTicksY();
			
				// set attributes
			    var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
		    	EJSS_GRAPHICS.GraphicsUtils.drawLogGrid(left, top, right, bottom, ticksY, scaleY, -1,
					function(xx,yy) { pathY += " M " + xx + " " + yy; }, 
					function(xx,yy) { pathY += " L " + xx + " " + yy; }); 	
			}
					    		
		} else if (ticksYmode == EJSS_DRAWING2D.Grid.SCALE_NUM) { // decimal
	    
		 	// create path	    	   	    
		   	var stepy = 0;	// step for ticks
		   	var tickstepy = 0;
			if (!mElement.getAutoTicksY()) {	  // no auto-ticks    
			    var ticksY = mElement.getTicksY();
			    // whether the number of sticks exits, changes step for ticks
			    if (ticksY != 0) { stepy = Math.abs(size[1]/ticksY); } else {
			    	stepy = mElement.getStepY();
			    	tickstepy = mElement.getTickStepY(); 
			    } 	    	
			} else { // auto-ticks
				var stepymin = mElement.getAutoStepYMin();		// step y min		
				var ticksyrange = mElement.getAutoTicksRangeY();		// ticks y range
		
				// find stepy
				for(var i=ticksyrange.length-1; i>=0; i--)	{	
					stepy = Math.abs(size[1]/ticksyrange[i]);
					if (stepy >= stepymin) break;
				}						
			}	
			
		    // step for scale
			var scalePrecisionY = mElement.getScalePrecisionY();	// number of decimals for scale
		    var scaleY = mElement.getScaleY();		// Y axis scale 	
		    if(tickstepy == 0) {
				var scalestepY = Math.abs((scaleY[1] - scaleY[0]) * stepy / size[1]);  // step in Y axis scale 
			} else {
				var scalestepY = tickstepy;
				stepy = Math.abs((scalestepY * size[1]) / (scaleY[1] - scaleY[0])); 
			}			
			
			// adjust step to decimals of precision	
			var decimalsY = Math.pow(10,scalePrecisionY);
			var scalestepYTmp = Math.round(scalestepY * decimalsY) / decimalsY;
			if(scalestepYTmp > 0) {
				scalestepY = scalestepYTmp;
				stepy = Math.abs(scalestepYTmp * size[1] / (scaleY[1] - scaleY[0]));
			}
			
			// check fixed tick
			var fixedTicks = mElement.getFixedTickY();	  
			var tickfixed = scaleY[1];
			if (!isNaN(fixedTicks)) {
			  if (fixedTicks < scaleY[0]) tickfixed = fixedTicks + (Math.floor((scaleY[0]-fixedTicks)/scalestepY)+1)*scalestepY;
			  else if (fixedTicks > scaleY[1])  tickfixed = fixedTicks - (Math.floor((fixedTicks-scaleY[1])/scalestepY)+1)*scalestepY;
		      else tickfixed = fixedTicks; 
			}		
	
			// tick fixed in axis scale
			var scaleshift = Math.abs((scaleY[0] - tickfixed) % scalestepY);						
			var dist = Math.abs(scaleshift-scalestepY);	// fitting shift
			if(scaleshift < 0.001 || dist < 0.001) scaleshift = 0;						
			var shifty = Math.abs(size[1] * scaleshift / (scaleY[1] - scaleY[0]));	// shift in pixels				
																								            				            
			// set attributes
		    var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
	    	EJSS_GRAPHICS.GraphicsUtils.drawDecGrid(left, top, right, bottom, shifty, stepy, -1,
					function(xx,yy) { pathY += " M " + xx + " " + yy; }, 
					function(xx,yy) { pathY += " L " + xx + " " + yy; }); 	    	
		}
	}
	
	// set path and style X
	if(pathX.length > 0) {
		mShapeX.setAttribute('d', pathX);
		mShapeX.setAttribute("shapeRendering",mElement.getShapeRenderingX());  	
    	mShapeX.setAttribute("stroke",mElement.getLineColorX());
    	mShapeX.setAttribute("stroke-width",mElement.getLineWidthX());    		
	}

	// set path and style Y
	if(pathY.length > 0) {
		mShapeY.setAttribute('d', pathY);
		mShapeY.setAttribute("shapeRendering",mElement.getShapeRenderingY());  	
    	mShapeY.setAttribute("stroke",mElement.getLineColorY());
    	mShapeY.setAttribute("stroke-width",mElement.getLineWidthY());    		
	}

	var attributes = mElement.getStyle().getAttributes();
	for (var attr in attributes) {
	  	mShapeX.setAttribute(attr,attributes[attr]);
	  	mShapeY.setAttribute(attr,attributes[attr]);
	}

	return mGroup;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG group
 */
EJSS_SVGGRAPHICS.group = function(mGraphics, mElement) {

	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG histogram
 */
 EJSS_SVGGRAPHICS.histogram = function(mGraphics, mElement) {  
	
	// creates area bar	
	function areaBar(bar, barx1, barx2, bary, axis, barstyle) {		
	 	// set attributes	    	    
	    bar.setAttribute('d', 
	    	 "M " + barx1 + " " + axis + 
	    	" L " + barx1 + " " + bary + 
	    	" L " + barx2 + " " + bary + 
	    	" L " + barx2 + " " + axis + " z");
	
		// set style
	    if(barstyle.getDrawFill())	
	    	bar.setAttribute("fill",barstyle.getFillColor());
	    else 
	    	bar.setAttribute("fill","none");    
	    if(barstyle.getDrawLines()) {
	    	bar.setAttribute("stroke",barstyle.getLineColor());
	    	bar.setAttribute("stroke-width",barstyle.getLineWidth());
	    } else {
	    	bar.setAttribute("stroke","none");
	    	bar.setAttribute("stroke-width",0);    	
	    }        
		bar.setAttribute("shapeRendering",barstyle.getShapeRendering());  	
	}  	
		
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape		
	var mGroup = mGraphics.getElementById(mElement.getName());
	if (mGroup === null) { 	// exists?
	    // create SVG element group
	    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mGroup.setAttribute("id", mElement.getName());
	    group.appendChild(mGroup);	    
	} else {
		// remove Bars
		var mBars = mGraphics.getElementById(mElement.getName() + ".bars");
		mGroup.removeChild(mBars);
	}
    // create Bars element
    var mBars = document.createElementNS("http://www.w3.org/2000/svg","g"); 
    mBars.setAttribute("id", mElement.getName() + ".bars");
    mGroup.appendChild(mBars);	    

	// get position of the element center 	
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
	
	// get style        
	var style = mElement.getStyle(); 
        
    
	// get histogram parameters
	var discrete =  mElement.getDiscrete();
 	var halfBin = mElement.getBinWidth()/2;
 	var bars = mElement.getBars();

	var axisx = mElement.getGroupPanel().getPixelPositionWorldOrigin()[1];
	for (var j=0; j < bars.length; j++) { // create bars
      var mBar = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	  mBar.setAttribute("id", mElement.getName() + ".bar" + j);
	  mBars.appendChild(mBar);	 
	  var barx1, barx2;
	  var cbin = bars[j][0];
	  if (discrete) {
        barx1 = barx2 = (x-mx) + cbin*size[0];
	  } 
	  else {
		barx1 = (x-mx) + (cbin-halfBin)*size[0];	// Bar x
		barx2 = (x-mx) + (cbin+halfBin)*size[0];	// Bar x
	  }
	  var bary = (y-my) + bars[j][1]*size[1];  // Bar y
	  areaBar(mBar, barx1, barx2, bary, axisx, style);
    }

	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	mGroup.setAttribute(attr,attributes[attr]);
	}    	    	    			
		
	return mGroup;         
}
/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG image
 */
EJSS_SVGGRAPHICS.image = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","image"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);
    
 	// set attributes
 	var current = mShape.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
 	if(!current || mElement.getChangedImage()) {
	 	var code = mElement.getEncode();
	 	if(code.length > 0) { // http://webcodertools.com/imagetobase64converter/Create
	 		mShape.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', 'data:png;base64,'+code);
	 	} else {
	    	mShape.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', mElement.getImageUrl());
	    	// mShape.onerror = function() {  };
	    }
	    // indica que la fuente de la imagen ha sido actualizada  
	    mElement.setChangedImage(false);
	}
	
	mShape.setAttribute('preserveAspectRatio', 'none')

	mShape.setAttribute("x",(x-mx));
	mShape.setAttribute("y",(y-my));
	mShape.setAttribute("width",Math.abs(size[0]));
	mShape.setAttribute("height",Math.abs(size[1]));
	
	// set style
    var style = mElement.getStyle(); 
    if(style.getDrawFill())	
    	mShape.setAttribute("fill",style.getFillColor());
    else 
    	mShape.setAttribute("fill","none");    
    if(style.getDrawLines()) {
    	mShape.setAttribute("stroke",style.getLineColor());
    	mShape.setAttribute("stroke-width",style.getLineWidth());
    } else {
    	mShape.setAttribute("stroke","none");
    	mShape.setAttribute("stroke-width",0);    	
    }        
	mShape.setAttribute("shapeRendering",style.getShapeRendering());  	

	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	mShape.setAttribute(attr,attributes[attr]);
	}

	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG mesh
 */
EJSS_SVGGRAPHICS.mesh = function(mGraphics, mElement) {
  var mA; 			// int[]
  var mB; 			// int[]
  var mCells;		// {cellA, cellB, cellZ, cellVector}
  var mBoundary;	// {boundaryA (int[][]), boundaryB (int[][])}
  
  // fill cell
  function fillCell(group, xs, ys, numpoints, color, levelIndex) {
  	var poly = document.createElementNS("http://www.w3.org/2000/svg","polygon"); 
  	group.appendChild(poly);
	
	var path = "";
	for(var i=0; i<numpoints; i++) {
   	  path += xs[i] + "," + ys[i] + " ";
	}
	path += xs[0] + "," + ys[0] + " ";  	  	
	
	poly.setAttribute('points', path);
  	var fill = color.indexToColor(levelIndex);
  	poly.setAttribute("fill",fill);	  
  	poly.setAttribute("stroke",fill);
  	poly.setAttribute("stroke-width",1.25);  
  }   
 
  // draw cell
  function drawCell(group, xs, ys, fill, style) {
    var poly = document.createElementNS("http://www.w3.org/2000/svg","path"); 
    group.appendChild(poly);
    
	var path = "";
	var numpoints = xs.length-1;
	for(var i=0; i<numpoints; i++) {
	  if (i==0)
	  	path += " M " + xs[i] + " " + ys[i];	 
      else       	
      	path += " L " + xs[i] + " " + ys[i];	
	}  	  	
   	path += " L " + xs[i] + " " + ys[i] + " Z";
   	
    poly.setAttribute('d', path);
  
    // set style
    if(fill && style.getDrawFill()) 	
	  poly.setAttribute("fill",style.getFillColor());
    else 
	  poly.setAttribute("fill","none");    
    if(style.getDrawLines()) {
	  poly.setAttribute("stroke",style.getLineColor());
	  poly.setAttribute("stroke-width",style.getLineWidth());
    } else {
	  poly.setAttribute("stroke","none");
	  poly.setAttribute("stroke-width",0);    	
    }        
    poly.setAttribute("shapeRendering",style.getShapeRendering());      
  }  

  function drawSegment(group, xs, ys, color, lineWidth) {
    var mPoly = document.createElementNS("http://www.w3.org/2000/svg","path"); 
    group.appendChild(mPoly);
    
	var path = "";
	var numpoints = xs.length-1;
	for(var i = 0; i<numpoints; i++) {
	  if (i==0) // 0 is NOT CONNECTION
	  	path += " M " + xs[i] + " " + ys[i];	 
      else       	
      	path += " L " + xs[i] + " " + ys[i];	
	}  	  	
   	path += " L " + xs[i] + " " + ys[i] + " Z";
	
    mPoly.setAttribute('d', path);
	
    // set style
    mPoly.setAttribute("fill","none");    
    mPoly.setAttribute("stroke", color);
    mPoly.setAttribute("stroke-width", lineWidth + 0.5);
  }  
  
  function drawVectors(group,cellA,cellB,field,lenVector) { 
    var map = document.createElementNS("http://www.w3.org/2000/svg","g"); 
    group.appendChild(map);	    
  	for(var i=0; i<cellA.length; i++) {
	    // create SVG element group
	    var vector = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    map.appendChild(vector);	    
	    // create path element
	    var line = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    vector.appendChild(line);	    
	    // create cross element
	    var tip = document.createElementNS("http://www.w3.org/2000/svg","polygon"); 
	    vector.appendChild(tip);	    
	
		// draw line
		var a = cellA[i];
		var b = cellB[i];
		var a1 = field[i][0];
		var b1 = field[i][1];
		var len = EJSS_TOOLS.Mathematics.norm([a1,b1]);
		var sa = a1*lenVector/len;
		var sb = b1*lenVector/len;
		
		var str = "M " + a + " " + b + " L " + (a+sa) + " " + (b+sb);
	   	line.setAttribute('d', str);
	
		// draw tip
		if((sa != 0) || (sb != 0)) {
			var endsize;
		    var maxsize = 12; // tip max size
		    var proportion = 0.3; // 30% length
	
			// calculate tip size	    
			var len = EJSS_TOOLS.Mathematics.norm([sa,sb]);
	    	endsize = len * proportion;
	    	if (endsize > maxsize) endsize = maxsize;
		
		    // position of arrow end
		    var tipx = a + sa;
		    var tipy = b + sb;
		
		  	// draw arrow end (only triangle)
		  	var points = tipx +","+ tipy + " " +
		  				 (tipx-endsize) +","+ (tipy+endsize/3) + " " + 
		  				 (tipx-endsize) +","+ (tipy-endsize/3);	        
		    tip.setAttribute('points', points);	
			
			// angle of arrow end  	  	  	  	
		    var anglearrow = Math.atan2(sa,-sb);
		    anglearrow = anglearrow * 180 / Math.PI;
		    tip.setAttribute("transform","rotate(" + (anglearrow-90) + " " + tipx + " " + tipy +")"); // minus 90!
		}
		
	   	vector.setAttribute("stroke","black");
	   	vector.setAttribute("stroke-width",0.5);
	}  	
  }
	
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;

    // create SVG element
    var mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
    mGroup.setAttribute("id", mElement.getName());
    	
    // remove SVG element (not reusing element)
	var mOldGroup = mGraphics.getElementById(mElement.getName());	
	if (mOldGroup !== null) mOldGroup.parentNode.replaceChild(mGroup, mOldGroup);		
	else group.appendChild(mGroup);	

	// get position of the mElement center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
	var mPoints = mElement.getPoints();	
    if (mPoints == null) return;    
    
  	// project the points
    var nPoints = mPoints.length;
    mA = new Array(nPoints);
    mB = new Array(nPoints);
    for (var i=0; i<nPoints; i++) {
      mA[i] = x-mx + mPoints[i][0]*size[0];
      mB[i] = y-my + mPoints[i][1]*size[1];
    }

	// build cells
	var cells = mElement.getCells();
	var field = mElement.getFieldAtPoints();
	if (field) {
		mCells = EJSS_GRAPHICS.GraphicsUtils.buildCells(mA,mB,cells,field,false);
	} else {
	  field = mElement.getFieldAtCells();
	  mCells = EJSS_GRAPHICS.GraphicsUtils.buildCells(mA,mB,cells,field,true);
	}

    // first draw the cells
    if (cells != null) {    
	  var style = mElement.getStyle();
      if (field != null) {
	      // draw polygons    	  
		  var color = mElement.getColorCoded();
	      EJSS_GRAPHICS.GraphicsUtils.drawPolygons(mCells, color, 
	      	function(xs, ys) { drawCell(mGroup, xs, ys, false, style) },
	      	function(xs, ys, numpoints, color, levelIndex) { fillCell(mGroup, xs, ys, numpoints, color, levelIndex) });
      }
      else {
	      EJSS_GRAPHICS.GraphicsUtils.drawPolygons(mCells, color, 
	      	function(xs, ys) { drawCell(mGroup, xs, ys, true, style) },
	      	function(xs, ys, numpoints, color, levelIndex) { });
      }
    }

	// build boundary
	var boundary = mElement.getBoundary();
	if(mElement.getDrawBoundary() && boundary != null) {	 
	  var labels = mElement.getBoundaryLabels();
	  var colors = mElement.getBoundaryColors();
	  var lineWidth = mElement.getBoundaryLineWidth();	
	  mBoundary = EJSS_GRAPHICS.GraphicsUtils.buildBoundary(mA, mB, boundary);
 
      // draw boundary
      EJSS_GRAPHICS.GraphicsUtils.drawBoundary(mBoundary, labels, colors, 
      	function(xs, ys, color) { drawSegment(mGroup,xs,ys,color,lineWidth) }); 
	}

	// draw vectors
	if(mElement.getDataType() >= 4 && field != null) {
		var lenVector = mElement.getVectorLength() * size[0]; // scale for size[0]		
        for (var i=0, n=cells.length; i<n; i++) {
          // draw vectors
          drawVectors(mGroup,mCellA[i],mCellB[i],field[i],lenVector);
        }
    }	
	
	var attributes = mElement.getStyle().getAttributes();
	for (var attr in attributes) {
	  	mGroup.setAttribute(attr,attributes[attr]);
	}    	    	    	
	
	return mGroup;         
}

 /**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG pipe
 */
EJSS_SVGGRAPHICS.pipe = function(mGraphics, mElement) {
	var UTILS = EJSS_SVGGRAPHICS.Utils;   
	
	// get element group
    var group, elementGroup = mElement.getGroup();
    if (elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);
	}
	var container = mGraphics.getElementById(mElement.getName()+":container");
	if (container === null) {	    
	    container = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    container.setAttribute("id", mElement.getName()+":container");
	    mShape.appendChild(container);
	 }	
	var liquid = mGraphics.getElementById(mElement.getName()+":liquid");
	if (liquid === null) {    
	    liquid = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    liquid.setAttribute("id", mElement.getName()+":liquid");
	    mShape.appendChild(liquid);	    
	}

    var points = mElement.getPoints();
    if (!points || points.length<=0) return;  

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];

	
	// get half sizes 		
    var sx = Math.abs(size[0]);
    var sy = Math.abs(size[1]);
        
 	// create path	    	   	    
 	var xmin = x-sx/2;
 	var ymin = y+sy/2;
    var style = mElement.getStyle(); 
    var crispy = (style.getShapeRendering() == "crispEdges"); 
    
    var containerPath = "";
    var liquidPath = "";
    var piece;
    if (crispy) piece = "M " + UTILS.crispValue(xmin) + " " + UTILS.crispValue(ymin);
    else        piece = "M " + xmin + " " + ymin; 
    containerPath = piece;
    liquidPath    = piece;
    for (var i=0, n=points.length; i<n; i++) {
      var point = points[i];
      var x = Math.round(point[0]*sx);
      var y = Math.round(-point[1]*sy);
      if (crispy) piece =  " l " + UTILS.crispValue(x) + " " + UTILS.crispValue(y);
      else        piece =  " l " + x + " " + y; 
      containerPath += piece;
      liquidPath += piece;
    }

	container.setAttribute('d',containerPath);
	liquid.setAttribute('d',liquidPath);
				
	// set style
	if (style.getDrawFill())  {
	  liquid.setAttribute("stroke",style.getFillColor());
      liquid.setAttribute("stroke-width",mElement.getPipeWidth());
    }
    else {
	  liquid.setAttribute("stroke","none");
      liquid.setAttribute("stroke-width",0);
    }  
    liquid.setAttribute("fill","none");    
	liquid.setAttribute("shapeRendering",style.getShapeRendering());  	

    if (style.getDrawLines()) {
      container.setAttribute("stroke",style.getLineColor());
      container.setAttribute("stroke-width",2*style.getLineWidth()+mElement.getPipeWidth());
    }
    else {
      container.setAttribute("stroke","none");
      container.setAttribute("stroke-width",0);
    }
    container.setAttribute("fill","none");    
	container.setAttribute("shapeRendering",style.getShapeRendering());  	

	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	liquid.setAttribute(attr,attributes[attr]);
	  	container.setAttribute(attr,attributes[attr]);
	}

	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG point
 */
EJSS_SVGGRAPHICS.point = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","ellipse"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	    
 	// set attributes	    	    
    mShape.setAttribute("cx",x);
    mShape.setAttribute("cy",y);
    mShape.setAttribute("rx",2);
    mShape.setAttribute("ry",2);
	
	// set style
    var style = mElement.getStyle(); 
   	mShape.setAttribute("fill",style.getFillColor());
   	
	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	mShape.setAttribute(attr,attributes[attr]);
	}
   	
	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG polygon
 */
EJSS_SVGGRAPHICS.polygon = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
	// set attributes
	var points = mElement.getPoints();	    	    
    var polpath = "";
	for(var i=0; i<points.length; i++) {
	  var point = points[i];	  
      var xx = (x-mx) + point[0]*size[0];
      var yy = (y-my) + point[1]*size[1];
      var type = point[2];
	  if ((i==0) || (type == 0)) // 0 is NOT CONNECTION
	  	polpath += " M " + xx + " " + yy;		// move 
      else       	
     	polpath += " L " + xx + " " + yy;		// line
	}  	  	

    if(polpath !== "") {
    	mShape.setAttribute('d', polpath);	

		// set style
	    var style = mElement.getStyle(); 
	    if(style.getDrawFill())	
	    	mShape.setAttribute("fill",style.getFillColor());
	    else 
	    	mShape.setAttribute("fill","none");    
	    if(style.getDrawLines()) {
	    	mShape.setAttribute("stroke",style.getLineColor());
	    	mShape.setAttribute("stroke-width",style.getLineWidth());
	    } else {
	    	mShape.setAttribute("stroke","none");
	    	mShape.setAttribute("stroke-width",0);    	
	    }        
		mShape.setAttribute("shapeRendering",style.getShapeRendering());

		var attributes = style.getAttributes();
		for (var attr in attributes) {
		  	mShape.setAttribute(attr,attributes[attr]);
		}		  	
	}
	
	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG rectangle
 */
EJSS_SVGGRAPHICS.rectangle = function(mGraphics, mElement) {
	var UTILS = EJSS_SVGGRAPHICS.Utils;   
	
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());		
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
 	// create path	    	   	    
    var style = mElement.getStyle(); 
    if(style.getShapeRendering() == "crispEdges") {
	    mShape.setAttribute('d', 
	    	 "M " + UTILS.crispValue(x-mx) + " " + UTILS.crispValue(y+my) + 
	    	" L " + UTILS.crispValue(x+mx) + " " + UTILS.crispValue(y+my) + 
	    	" L " + UTILS.crispValue(x+mx) + " " + UTILS.crispValue(y-my) + 
	    	" L " + UTILS.crispValue(x-mx) + " " + UTILS.crispValue(y-my) + " z");
    } else {
	    mShape.setAttribute('d', 
	    	 "M " + (x-mx) + " " + (y+my) + 
	    	" L " + (x+mx) + " " + (y+my) + 
	    	" L " + (x+mx) + " " + (y-my) + 
	    	" L " + (x-mx) + " " + (y-my) + " z");
	}  	    
		
	// set style
    if(style.getDrawFill())	
    	mShape.setAttribute("fill",style.getFillColor());
    else 
    	mShape.setAttribute("fill","none");    
    if(style.getDrawLines()) {
    	mShape.setAttribute("stroke",style.getLineColor());
    	mShape.setAttribute("stroke-width",style.getLineWidth());
    } else {
    	mShape.setAttribute("stroke","none");
    	mShape.setAttribute("stroke-width",0);    	
    }        
	mShape.setAttribute("shapeRendering",style.getShapeRendering());  	

	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	mShape.setAttribute(attr,attributes[attr]);
	}

	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG round rectangle
 */
EJSS_SVGGRAPHICS.roundRectangle = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","rect"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);
    
    // get element radius
    var radius = Math.abs(mElement.getCornerRadius());
    
 	// set attributes	   
	mShape.setAttribute("x", x-mx);
	mShape.setAttribute("y", y-my);
	mShape.setAttribute("width", Math.abs(size[0]));
	mShape.setAttribute("height", Math.abs(size[1]));	    	
	mShape.setAttribute("rx", radius);
	mShape.setAttribute("ry", radius);
	
	// set style
    var style = mElement.getStyle(); 
    if(style.getDrawFill())	
    	mShape.setAttribute("fill",style.getFillColor());
    else 
    	mShape.setAttribute("fill","none");    
    if(style.getDrawLines()) {
    	mShape.setAttribute("stroke",style.getLineColor());
    	mShape.setAttribute("stroke-width",style.getLineWidth());
    } else {
    	mShape.setAttribute("stroke","none");
    	mShape.setAttribute("stroke-width",0);    	
    }        
	mShape.setAttribute("shapeRendering",style.getShapeRendering());  	

	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	mShape.setAttribute(attr,attributes[attr]);
	}

	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG scalarField
 */
EJSS_SVGGRAPHICS.scalarField = function(mGraphics, mElement) {	
	var UTILS = EJSS_SVGGRAPHICS.Utils;   

	function pathForGrid_crispEdges(left, top, right, bottom, stepx, stepy) {
		var path = "";   
		
		var cleft = UTILS.crispValue(left), cright = UTILS.crispValue(right);
		var ctop = UTILS.crispValue(top), cbottom = UTILS.crispValue(bottom);
		// vertical lines
		if (stepx == 0) stepx = Math.abs(right-left);
	    for (var i = left; i <= Math.max(right,cright)+0.5; i = i+stepx) {
	  	  path += " M " + UTILS.crispValue(i) + " " + ctop + " L " + UTILS.crispValue(i) + " " + cbottom; 
	    }
	    // horizontal lines
	    if (stepy == 0) stepy = Math.abs(top-bottom);
	    for (var i = bottom; i >= Math.min(top,ctop)-0.5; i = i-stepy) {
	  	  path += " M " + cleft + " " + UTILS.crispValue(i) + " L " + cright + " " + UTILS.crispValue(i); 
	    }
	    return path;
	}  
	
	function pathForGrid(left, top, right, bottom, stepx, stepy) {	 	
		var path = "";   
		// vertical lines
		if (stepx == 0) stepx = Math.abs(right-left);
	    for (var i = left; i <= right; i = i+stepx) {
	  	  path += " M " + i + " " + top + " L " + i + " " + bottom; 
	    }
	    // horizontal lines
	    if (stepy == 0) stepy = Math.abs(top-bottom);
	    for (var i = bottom; i >= top; i = i-stepy) {
	  	  path += " M " + left + " " + i + " L " + right + " " + i; 
	    }
	    return path;
	}  
	
	function rectCell(group, x, y, sx, sy, rendering, fill) {
		// create rectangle
	    var mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    group.appendChild(mShape);	   
	    		
		// create path	
		var mx = sx/2, my = sy/2;    	   	    
		if(rendering == "crispEdges") {
		    mShape.setAttribute('d', 
		    	 "M " + UTILS.crispValue(x-mx) + " " + UTILS.crispValue(y+my) + 
		    	" L " + UTILS.crispValue(x+mx) + " " + UTILS.crispValue(y+my) + 
		    	" L " + UTILS.crispValue(x+mx) + " " + UTILS.crispValue(y-my) + 
		    	" L " + UTILS.crispValue(x-mx) + " " + UTILS.crispValue(y-my) + " z");
		} else {
		    mShape.setAttribute('d', 
		    	 "M " + (x-mx) + " " + (y+my) + 
		    	" L " + (x+mx) + " " + (y+my) + 
		    	" L " + (x+mx) + " " + (y-my) + 
		    	" L " + (x-mx) + " " + (y-my) + " z");
		}  	 		
		mShape.setAttribute("fill",fill);
		mShape.setAttribute("stroke",fill);
	}

	function gridCell(group, x, y, sx, sy, stepx, stepy, style) {
		// create rectangle
	    var mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    group.appendChild(mShape);	   

		// set attributes
		var mx = sx/2, my = sy/2;    	   	    
		var left = x-mx, right = x+mx, top = y+my, bottom = y-my;
	    if(style.getShapeRendering() == "crispEdges") 
	    	mShape.setAttribute('d', pathForGrid_crispEdges(left, top, right, bottom, stepx, stepy));
	    else {
	    	mShape.setAttribute('d', pathForGrid(left, top, right, bottom, stepx, stepy));
	    }
						
	    if(style.getDrawLines()) {
	    	mShape.setAttribute("stroke",style.getLineColor());
	    	mShape.setAttribute("stroke-width",style.getLineWidth());
	    } else {
	    	mShape.setAttribute("stroke","none");
	    	mShape.setAttribute("stroke-width",0);    	
	    }        
	    
		var attributes = style.getAttributes();
		for (var attr in attributes) {
		    mShape.setAttribute(attr,attributes[attr]);
		}	    
	}
	
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mGroup = mGraphics.getElementById(mElement.getName());			
	if (mGroup !== null) { 	// exits?
		group.removeChild(mGroup);
	}
    // create SVG mElement group
    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
    mGroup.setAttribute("id", mElement.getName());
    group.appendChild(mGroup);	    

	var data = mElement.getData();
	var xlen = data.length;	
	if (xlen>0) {
		// get position of the mElement center 
	    var pos = mElement.getPixelPosition();
	    var size = mElement.getPixelSizes();     
	    var offset = mElement.getRelativePositionOffset(size);  
	    var x = pos[0]+offset[0];
	    var y = pos[1]+offset[1];
		
		// get half sizes 		
	    var mx = size[0]/2;
	    var my = size[1]/2;
	    
		// draw cells
		var style = mElement.getStyle();     
		var colors = mElement.getColorMapper().getColors();
		if(mElement.getAutoscaleZ()) mElement.getColorMapper().setAutoscaleArray2(data);
		
		var ylen = data[0].length;
	
	    var stepx = Math.abs(size[0]/xlen);
	    var stepy = Math.abs(size[1]/ylen);
	   	
	  	var left = x-mx+stepx/2, bottom = y-my-stepy/2;	
	  	for(var i=0; i<ylen; i++) { 
	  		for(var j=0; j<xlen; j++) {
	  			// get color based on max and min
	  			var value = data[j][i];
	  			var color = mElement.getColorMapper().doubleToColor(value);
	  			// draw rectangle
	  			rectCell(mGroup, left+stepx*j, bottom-stepy*i, stepx, stepy, style.getShapeRendering(), color);
	  		}  		
	  	}
	  	 	
	  	// draw grid
	  	var showGrid = mElement.getShowGrid();
		if (showGrid) gridCell(mGroup, x, y, size[0], size[1], stepx, stepy, style);	
	}
	  	
	return mGroup;         
}
/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG segment
 */
EJSS_SVGGRAPHICS.segment = function(mGraphics, mElement) {
	var UTILS = EJSS_SVGGRAPHICS.Utils;   

	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
 	// set attributes	    	
    var style = mElement.getStyle();   	 	
    if(style.getShapeRendering() == "crispEdges") {
    	mShape.setAttribute('d', "M " + UTILS.crispValue(x-mx) + " " + UTILS.crispValue(y-my) 
    		+ " L " + UTILS.crispValue(x+mx) + " " + UTILS.crispValue(y+my));
    } else {
    	mShape.setAttribute('d', "M " + (x-mx) + " " + (y-my) + " L " + (x+mx) + " " + (y+my));
	}  	    
	
	// set style
    if(style.getDrawFill())	
    	mShape.setAttribute("fill",style.getFillColor());
    else 
    	mShape.setAttribute("fill","none");    
    if(style.getDrawLines()) {
    	mShape.setAttribute("stroke",style.getLineColor());
    	mShape.setAttribute("stroke-width",style.getLineWidth());
    } else {
    	mShape.setAttribute("stroke","none");
    	mShape.setAttribute("stroke-width",0);    	
    }        
	mShape.setAttribute("shapeRendering",style.getShapeRendering());  	

    var attributes = style.getAttributes();
    for (var attr in attributes) {
      mShape.setAttribute(attr,attributes[attr]);
    }
    
	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG spring
 */
EJSS_SVGGRAPHICS.spring = function(mGraphics, mElement) {

	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);	    
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
 	// get spring radius	    	
    var radius = mElement.getGroupPanel().toPixelMod([mElement.getRadius(),0])[0];

	// set attributes
	var springpath = "";
	EJSS_GRAPHICS.GraphicsUtils.drawSpring(mElement.getLoops(), mElement.getPointsPerLoop(), 
			radius, mElement.getSolenoid(), mElement.getThinExtremes(), x-mx, y-my, size[0], size[1],
			function(xx,yy) { springpath += " M " + xx + " " + yy; }, 
			function(xx,yy) { springpath += " L " + xx + " " + yy; });   
			
    if (springpath !== "") {
	    mShape.setAttribute('d', springpath);
	    		
		// set style
	    var style = mElement.getStyle(); 	
	    if(style.getDrawFill())	
	    	mShape.setAttribute("fill",style.getFillColor());
	    else 
	    	mShape.setAttribute("fill","none");    
	    if(style.getDrawLines()) {
	    	mShape.setAttribute("stroke",style.getLineColor());
	    	mShape.setAttribute("stroke-width",style.getLineWidth());
	    } else {
	    	mShape.setAttribute("stroke","none");
	    	mShape.setAttribute("stroke-width",0);    	
	    }        
		mShape.setAttribute("shapeRendering",style.getShapeRendering());

		var attributes = style.getAttributes();
		for (var attr in attributes) {
		  	mShape.setAttribute(attr,attributes[attr]);
		}		  	
	}
	
	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG rectangle
 */
EJSS_SVGGRAPHICS.tank = function(mGraphics, mElement) {
	var UTILS = EJSS_SVGGRAPHICS.Utils;   
	
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mShape.setAttribute("id", mElement.getName());
	    group.appendChild(mShape);
	}
	var container = mGraphics.getElementById(mElement.getName()+":container");
	if (container === null) {	    
	    container = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    container.setAttribute("id", mElement.getName()+":container");
	    mShape.appendChild(container);
	 }	
	var liquid = mGraphics.getElementById(mElement.getName()+":liquid");
	if (liquid === null) {    
	    liquid = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    liquid.setAttribute("id", mElement.getName()+":liquid");
	    mShape.appendChild(liquid);	    
	}


	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];

	
	// get half sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);
        
    var level = mElement.getPixelSizeOf(0,mElement.getLevel());  

 	// create path	    	   	    
 	var xmin = x-mx, xmax = x+mx;
 	var ymin = y+my, ymax = y-my;
 	var ylevel = ymin + level[1];
    var style = mElement.getStyle(); 
    if(style.getShapeRendering() == "crispEdges") {
	    liquid.setAttribute('d', 
	    	 "M " + UTILS.crispValue(xlevel) + " " + UTILS.crispValue(ylevel) + 
	    	" L " + UTILS.crispValue(xmin) + " " + UTILS.crispValue(ymin) + 
	    	" L " + UTILS.crispValue(xmax) + " " + UTILS.crispValue(ymin) + 
	    	" L " + UTILS.crispValue(xlevel) + " " + UTILS.crispValue(ylevel) +
	    	" Z");
	    if (style.getDrawFill()) container.setAttribute('d', 
	    	 "M " + UTILS.crispValue(xmin) + " " + UTILS.crispValue(ymax) + 
	    	" L " + UTILS.crispValue(xmin) + " " + UTILS.crispValue(ymin) + 
	    	" L " + UTILS.crispValue(xmax) + " " + UTILS.crispValue(ymin) + 
	    	" L " + UTILS.crispValue(xmax) + " " + UTILS.crispValue(ymax) +" Z");
	    else container.setAttribute('d', 
	    	 "M " + UTILS.crispValue(xmin) + " " + UTILS.crispValue(ymax) + 
	    	" L " + UTILS.crispValue(xmin) + " " + UTILS.crispValue(ymin) + 
	    	" L " + UTILS.crispValue(xmax) + " " + UTILS.crispValue(ymin) + 
	    	" L " + UTILS.crispValue(xmax) + " " + UTILS.crispValue(ymax));
    } else {
	    liquid.setAttribute('d', 
	    	 "M " + (xmin + level[0]) + " " + (ylevel) + 
	    	" L " + (xmin) + " " + (ymin) + 
	    	" L " + (xmax) + " " + (ymin) + 
	    	" L " + (xmax + level[0]) + " " + (ylevel) +
	    	" Z");
	    if (style.getDrawFill()) container.setAttribute('d', 
	    	 "M " + (xmin) + " " + (ymax) + 
	    	" L " + (xmin) + " " + (ymin) + 
	    	" L " + (xmax) + " " + (ymin) + 
	    	" L " + (xmax) + " " + (ymax) + " Z");
	     else container.setAttribute('d', 
	    	 "M " + (xmin) + " " + (ymax) + 
	    	" L " + (xmin) + " " + (ymin) + 
	    	" L " + (xmax) + " " + (ymin) + 
	    	" L " + (xmax) + " " + (ymax) );
	}  	    
		
	// set style
    liquid.setAttribute("stroke","none");
    liquid.setAttribute("stroke-width",0);    	
    liquid.setAttribute("fill",mElement.getLevelColor());
	liquid.setAttribute("shapeRendering",style.getShapeRendering());  	

    if (style.getDrawFill()) container.setAttribute("fill",style.getFillColor());
    else container.setAttribute("fill","none");    
    container.setAttribute("stroke",style.getLineColor());
    container.setAttribute("stroke-width",style.getLineWidth());
	container.setAttribute("shapeRendering",style.getShapeRendering());  	

	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	liquid.setAttribute(attr,attributes[attr]);
	  	container.setAttribute(attr,attributes[attr]);
	}

	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG text
 */
EJSS_SVGGRAPHICS.text = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mGroup = mGraphics.getElementById(mElement.getName());
	var mInnerGroup, mText, mBox;
	if (mGroup === null) { 	// exits?
	    // create SVG element group
	    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mGroup.setAttribute("id", mElement.getName());
	    group.appendChild(mGroup);	    
		// create SVG inner element group
	    mInnerGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mInnerGroup.setAttribute("id", mElement.getName() + ".inner");
	    mGroup.appendChild(mInnerGroup);	    
	    // create box element
	    mBox = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mBox.setAttribute("id", mElement.getName() + ".box");
	    mInnerGroup.appendChild(mBox);	    
	    // create text element
	    mText = document.createElementNS("http://www.w3.org/2000/svg","text"); 
	    mText.setAttribute("id", mElement.getName() + ".text");
	    mInnerGroup.appendChild(mText);	    
	} else {
		mInnerGroup = mGraphics.getElementById(mElement.getName() + ".inner");
		mText = mGraphics.getElementById(mElement.getName() + ".text");
		mBox = mGraphics.getElementById(mElement.getName() + ".box");
	}

	// remove previous text
	while (mText.firstChild) {
	    mText.removeChild(mText.firstChild);
	}	

	// draw text	    		
	var font = mElement.getFont();
	mText.setAttribute("font-style",font.getFontStyle());
	mText.setAttribute("font-weight",font.getFontWeight());	    	
	mText.setAttribute("font-size",font.getFontSizeString());
	mText.setAttribute("font-family",font.getFontFamily());
	
    mText.setAttribute("fill",font.getFillColor());
   	mText.setAttribute("stroke",font.getOutlineColor());
   	mText.setAttribute("stroke-width",font.getOutlineWidth());
    mText.setAttribute("letterSpacing",font.getLetterSpacing());
    
	// get max length and max height	
	var text = mElement.getText();
	var arr = text.split("\n");
	var maxLength;
	var maxHeight;
	var drawingSize = mElement.getDrawingSize();
	if(drawingSize[0] == -1) {
		if (typeof text == 'undefined' || text == null) text = "";
		maxLength = 0;
		maxHeight = font.getFontSize();
	    mText.textContent = "";
		mText.setAttribute("visibility", "hidden");
	  	for (i = 0; i < arr.length; i++) {
	  		mText.textContent = arr[i];
	  		var len = mText.getComputedTextLength();
	  		if(len > maxLength) maxLength = len;
	  		if(i > 0) maxHeight += 1.8 * font.getFontSize();
		}
		mText.setAttribute("visibility", "inherit");
	    mText.textContent = "";		
	    mElement.setDrawingSize([maxLength,maxHeight]);
	} else {
		maxLength = drawingSize[0];
		maxHeight = drawingSize[1];
	}	
	
	// get position of the element center 
    var pos = mElement.getPixelPosition();
	var marginFrame = mElement.getFramed()?4:0; 
	// var bBox = mText.getBBox();	
    // var size = [bBox.width + marginFrame*2, bBox.height + marginFrame*2];
// Paco    var size = [1.15*maxLength + marginFrame*2, maxHeight + marginFrame*2];
    var size = [maxLength + marginFrame*2, maxHeight/1.5 + marginFrame*2];
    var offset = EJSS_DRAWING2D.Element.getSWRelativePositionOffset(
    		mElement.getRelativePosition(), size[0], size[1]);  
    var xmargin = mElement.getMarginX();
    var ymargin = mElement.getMarginY();
    var x = pos[0]+offset[0]+xmargin+marginFrame;
    var y = pos[1]+offset[1]+ymargin-marginFrame;

	// text position	
	mText.setAttribute("x",x);
	mText.setAttribute("y",y);

	// set text keeping newlines
  	for (i = arr.length-1; i >= 0; i--) {
		var ts = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
        if(i == arr.length-1) ts.setAttribute("y", y);
        else ts.setAttribute("dy", "-1.2em");
        ts.setAttribute("x", x);
		ts.textContent = arr[i];
		mText.appendChild(ts);
	}

    if (mElement.getFramed()) { // framed    
    	var tbmargin = marginFrame;
    	var rlmargin = marginFrame;
    	var wlen = size[0] - marginFrame*2;
    	var hlen = size[1] - marginFrame*2;    	
    	
	    // draw box
	    if(wlen > 0) {
	    	var bottom = y + tbmargin, top = y - hlen - tbmargin;
	    	var left = x - rlmargin, rigth = x + wlen + tbmargin;		 		 
			mBox.setAttribute('d', 
			   	 "M " + left + " " + bottom + 
			   	" L " + rigth + " " + bottom + 
			   	" L " + rigth + " " + top + 
			   	" L " + left + " " + top + " z");    
		    var style = mElement.getStyle(); 
		    if(style.getDrawFill())	
		    	mBox.setAttribute("fill",style.getFillColor());
		    else 
		    	mBox.setAttribute("fill","none");    
		    if(style.getDrawLines()) {
		    	mBox.setAttribute("stroke",style.getLineColor());
		    	mBox.setAttribute("stroke-width",style.getLineWidth());
		    } else {
		    	mBox.setAttribute("stroke","none");
		    	mBox.setAttribute("stroke-width",0);    	
		    }       
			var attributes = style.getAttributes();
			for (var attr in attributes) {
			  	mShape.setAttribute(attr,attributes[attr]);
			}		    
		} else {
	    	mBox.setAttribute("stroke","none");
	    	mBox.setAttribute("fill","none");    		
		}
	}
		    
	// angle of text  	 
	var angletext = 0;
	switch(mElement.getWritingMode()) {
		case EJSS_DRAWING2D.Text.MODE_TOPDOWN : angletext = 90; break;
		case EJSS_DRAWING2D.Text.MODE_RIGTHLEFT : angletext = 180; break;
		case EJSS_DRAWING2D.Text.MODE_DOWNTOP : angletext = 270; break;
		case EJSS_DRAWING2D.Text.MODE_LEFTRIGHT: angletext = 0; break;
	} 	  	  	
    mInnerGroup.setAttribute("transform","rotate(" + angletext + " " + (pos[0]+xmargin) + " " + (pos[1]+ymargin) + ")"); 
    
    // avoid pointer events in mobiles
    mGroup.setAttribute('pointer-events', 'none');

	return mGroup;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG trace
 */
EJSS_SVGGRAPHICS.trace = function(mGraphics, mElement) {  
	
	// generates path for trail
	function pathForTrail(points, x, y, sx, sy) {
		var path = "";
		for(var i=0; i<points.length; i++) {
		  var point = points[i];	  
	      var xx = x + point[0]*sx;
	      var yy = y + point[1]*sy;
	      var type = point[2];		
		  if ((i==0) || (type == 0)) // 0 is NOT CONNECTION
		  	path += " M " + xx + " " + yy;		// move 
	      else       	
	      	path += " L " + xx + " " + yy;		// line
		}  	  	
	    return path;
	}  	

	// creates ellipse mark
	function ellipseMark(mark, markx, marky, size, markstyle) {
		// get position of the element center 
	    var x = markx;
	    var y = marky;
		
		// get half sizes 		
	    var mx = Math.abs(size[0]/2);
	    var my = Math.abs(size[1]/2);

	 	// set attributes	    	    
	    mark.setAttribute("cx",x);
	    mark.setAttribute("cy",y);
	    mark.setAttribute("rx",mx);
	    mark.setAttribute("ry",my);
		
		// set style
	    if(markstyle.getDrawFill())	
	    	mark.setAttribute("fill",markstyle.getFillColor());
	    else 
	    	mark.setAttribute("fill","none");    
	    if(markstyle.getDrawLines()) {
	    	mark.setAttribute("stroke",markstyle.getLineColor());
	    	mark.setAttribute("stroke-width",markstyle.getLineWidth());
	    } else {
	    	mark.setAttribute("stroke","none");
	    	mark.setAttribute("stroke-width",0);    	
	    }        
		mark.setAttribute("shapeRendering",markstyle.getShapeRendering());  	
	}  	

	// creates rectangle mark
	function rectangleMark(mark, markx, marky, size, markstyle) {
		// get position of the element center 
	    var x = markx;
	    var y = marky;
		
		// get half sizes 		
	    var mx = Math.abs(size[0]/2);
	    var my = Math.abs(size[1]/2);

	 	// set attributes	    	    
	    mMark.setAttribute('d', 
	    	 "M " + (x-mx) + " " + (y+my) + 
	    	" L " + (x+mx) + " " + (y+my) + 
	    	" L " + (x+mx) + " " + (y-my) + 
	    	" L " + (x-mx) + " " + (y-my) + " z");
	
		// set style
	    if(markstyle.getDrawFill())	
	    	mark.setAttribute("fill",markstyle.getFillColor());
	    else 
	    	mark.setAttribute("fill","none");    
	    if(markstyle.getDrawLines()) {
	    	mark.setAttribute("stroke",markstyle.getLineColor());
	    	mark.setAttribute("stroke-width",markstyle.getLineWidth());
	    } else {
	    	mark.setAttribute("stroke","none");
	    	mark.setAttribute("stroke-width",0);    	
	    }        
		mark.setAttribute("shapeRendering",markstyle.getShapeRendering());  	
	}  	

	// creates area mark
	function areaMark(mark, lastmarkx, lastmarky, markx, marky, axis, markstyle) {		
	 	// set attributes	    	    
	    mMark.setAttribute('d', 
	    	 "M " + lastmarkx + " " + axis + 
	    	" L " + lastmarkx + " " + lastmarky + 
	    	" L " + markx + " " + marky + 
	    	" L " + markx + " " + axis + " z");
	
		// set style
	    if(markstyle.getDrawFill())	
	    	mark.setAttribute("fill",markstyle.getFillColor());
	    else 
	    	mark.setAttribute("fill","none");    
	    if(markstyle.getDrawLines()) {
	    	mark.setAttribute("stroke",markstyle.getLineColor());
	    	mark.setAttribute("stroke-width",markstyle.getLineWidth());
	    } else {
	    	mark.setAttribute("stroke","none");
	    	mark.setAttribute("stroke-width",0);    	
	    }        
		mark.setAttribute("shapeRendering",markstyle.getShapeRendering());  	
	}  	
		
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
	// create SVG element
	var mTraceGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	group.appendChild(mTraceGroup);	    

    // remove SVG element (not reusing element)
	var lastGroup = mGraphics.getElementById(mElement.getName());	
	if (lastGroup !== null) { 
		group.insertBefore(mTraceGroup,lastGroup);
		group.removeChild(lastGroup);
	}
   	
    // create SVG element group
    mTraceGroup.setAttribute("id", mElement.getName());
    	    
    // create trail element
    var mTrail = document.createElementNS("http://www.w3.org/2000/svg","path"); 
    mTrail.setAttribute("id", mElement.getName() + ".trail");
    mTraceGroup.appendChild(mTrail);	    

    // create marks element
    var mMarks = document.createElementNS("http://www.w3.org/2000/svg","g"); 
    mMarks.setAttribute("id", mElement.getName() + ".marks");
    mTraceGroup.appendChild(mMarks);	    

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
        
	// draw trail
    var trailpath = pathForTrail(mElement.getPoints(), x-mx, y-my, size[0], size[1])     
    if(trailpath !== "") {
    	mTrail.setAttribute('d', trailpath);
    			
		// set style for trail
	    var style = mElement.getStyle(); 
	    if(style.getDrawFill()) 	
	    	mTrail.setAttribute("fill",style.getFillColor());
	    else 
	    	mTrail.setAttribute("fill","none");    
	    if(style.getDrawLines()) {
	    	mTrail.setAttribute("stroke",style.getLineColor());
	    	mTrail.setAttribute("stroke-width",style.getLineWidth());
	    } else {
	    	mTrail.setAttribute("stroke","none");
	    	mTrail.setAttribute("stroke-width",0);    	
	    }        
		mTrail.setAttribute("shapeRendering",style.getShapeRendering());  	
    			
		// draw marks     
		var points = mElement.getPoints();
		var styleList = mElement.getMarkStyleList();
		var lastpointX, lastpointY;	// for mark area
		for(var i=0; i<points.length; i++) {
			var point = points[i];	  
			var markx = (x-mx) + point[0]*size[0];		// mark x
			var marky = (y-my) + point[1]*size[1];     // mark y
		    var markType = point[3];    		// mark type
			var markstyle = styleList.length>i?styleList[i]:point[4];	// mark style
			var marksize = [point[5],point[6]];		// mark size
			var markaxis = point[7];			
	
		    if (markType == EJSS_DRAWING2D.Trace.ELLIPSE) { // circle
			    var mMark = document.createElementNS("http://www.w3.org/2000/svg","ellipse"); 
			    mMark.setAttribute("id", mElement.getName() + ".mark" + i);
			    mMarks.appendChild(mMark);	    		    	
				ellipseMark(mMark, markx, marky, marksize, markstyle);
		    } else if (markType == EJSS_DRAWING2D.Trace.RECTANGLE) { // rectangle
			    var mMark = document.createElementNS("http://www.w3.org/2000/svg","path"); 
			    mMark.setAttribute("id", mElement.getName() + ".mark" + i);
			    mMarks.appendChild(mMark);	    		    	
				rectangleMark(mMark, markx, marky, marksize, markstyle);
		    } else if (markType == EJSS_DRAWING2D.Trace.BAR) { // bar
			    var mMark = document.createElementNS("http://www.w3.org/2000/svg","path"); 
			    mMark.setAttribute("id", mElement.getName() + ".mark" + i);
			    mMarks.appendChild(mMark);	  
			    // create bar  		    	
				//var axisx = mElement.getGroupPanel().getPixelPositionWorldOrigin()[1];
				var axisx = mElement.getGroupPanel().toPixelPosition([0,markaxis])[1];
				
		    	marksize[1] = axisx-marky;			// bar size
		    	marky = marky + marksize[1]/2;		// new mark y	    	
		    	rectangleMark(mMark, markx, marky, marksize, markstyle);
			}
			else if (markType == EJSS_DRAWING2D.Trace.AREA) { // area
		    	if(i!=0) {				  
				    var mMark = document.createElementNS("http://www.w3.org/2000/svg","path"); 
				    mMark.setAttribute("id", mElement.getName() + ".mark" + i);
				    mMarks.appendChild(mMark);	    		    	
					// create area
					//var axisx = mElement.getGroupPanel().getPixelPositionWorldOrigin()[1];
					
    				var axisx = mElement.getGroupPanel().toPixelPosition([0,markaxis])[1];
					var lastmarkx = (x-mx) + lastpointX*size[0];		// mark x
					var lastmarky = (y-my) + lastpointY*size[1];     // mark y
					areaMark(mMark, lastmarkx, lastmarky, markx, marky, axisx, markstyle);
		    	} 
		    	lastpointX = point[0];    	
		    	lastpointY = point[1];
		    }		  
		}  	  		  
	
		var attributes = style.getAttributes();
		for (var attr in attributes) {
		  	mTraceGroup.setAttribute(attr,attributes[attr]);
		}    	    	    			
	}
		
	return mTraceGroup;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG trail
 */
EJSS_SVGGRAPHICS.trail = function(mGraphics, mElement) {

  /** adds a segment to the trail
   */
   function addSegment(group,points,num,style,pX,pY,sX,sY) {
     if (num<=0) return; // Nothing to add
	
	var segment = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	group.appendChild(segment);	    
	
    var path = "";
	for (var i=0; i<num; i++) {
	  var point = points[i];	  
	  var xx = pX + point[0]*sX;
	  var yy = pY + point[1]*sY;
	  var type = point[2];		
	  if ((i==0) || (type == 0)) // 0 is NOT CONNECTION
	  	path += " M " + xx + " " + yy;		// move 
      else       	
     	path += " L " + xx + " " + yy;		// line
 	}  	  	
    segment.setAttribute('d', path);
	if (style.getDrawFill()) segment.setAttribute("fill",style.getFillColor());
	else segment.setAttribute("fill","none");    
	if (style.getDrawLines()) {
	  segment.setAttribute("stroke",style.getLineColor());
	  segment.setAttribute("stroke-width",style.getLineWidth());
	} else {
	  segment.setAttribute("stroke","none");
	  segment.setAttribute("stroke-width",0);    	
	}        
	segment.setAttribute("shapeRendering",style.getShapeRendering());
		
	var attributes = style.getAttributes();
	for (var attr in attributes) segment.setAttribute(attr,attributes[attr]);
  }

	// get element group
    var group, elementGroup = mElement.getGroup();
    if (elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    
	// create SVG element
	var mTrail = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	group.appendChild(mTrail);	    

    // remove SVG element (not reusing element)
	var lastTrail = mGraphics.getElementById(mElement.getName());	
	if (lastTrail !== null) { 
		group.insertBefore(mTrail,lastTrail);
		group.removeChild(lastTrail);
	}

	// set name 
	mTrail.setAttribute("id", mElement.getName());
    	
	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
	
	var pX = x-mx;
	var pY = y-my;
	var sX = size[0];
	var sY = size[1];
	
	var segmentCount = mElement.getSegmentsCount();
	if (segmentCount>0) {
	  for (var i=0; i<segmentCount; i++) { 
	  	var num = mElement.getSegmentPoints(i).length;
	    addSegment(mTrail,mElement.getSegmentPoints(i),num,mElement.getSegmentStyle(i),pX,pY,sX,sY);
	  }
	}
	
	//mElement.dataCollected(); // add temporary points
	var num = mElement.getCurrentPoints().length; 
	addSegment(mTrail,mElement.getCurrentPoints(),num,mElement.getStyle(),pX,pY,sX,sY);
	
	return mTrail;


}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};
EJSS_SVGGRAPHICS.Utils = {};

/**
 * @param value
 * @returns crisp value
 */
EJSS_SVGGRAPHICS.Utils.crispValue = function(value) {
  	return (Math.round(+value.toFixed(4)+0.5)-0.5);  	
}

/**
 * @param value
 * @returns crisp top value
 */
EJSS_SVGGRAPHICS.Utils.crispTop = function(value) {
  	return (Math.round(+value.toFixed(4)+0.5)-0.5);  	
}

/**
 * @param value
 * @returns round value
 */
EJSS_SVGGRAPHICS.Utils.round = function(value) {
  	return +value.toFixed(4);  	
}

/**
 * @param HSL, where H ∈ [0°, 360°), S ∈ [0, 1], L ∈ [0, 1]
 * @return rgb
 */
EJSS_SVGGRAPHICS.Utils.hsl2rgb = function (hsl) {
	var H = hsl[0], S = hsl[1], L = hsl[2];
    /* calculate chroma */
    var C = (1 - Math.abs((2 * L) - 1)) * S;

    /* Find a point (R1, G1, B1) along the bottom three faces of the RGB cube, with the same hue and chroma as our color (using the intermediate value X for the second largest component of this color) */
    var H_ = H / 60;
    var X = C * (1 - Math.abs((H_ % 2) - 1));
    var R1, G1, B1;

    if (H === undefined || isNaN(H) || H === null) {
        R1 = G1 = B1 = 0;
    }
    else {
        if (H_ >= 0 && H_ < 1) { R1 = C; G1 = X; B1 = 0;
        } else if (H_ >= 1 && H_ < 2) { R1 = X; G1 = C; B1 = 0;
        } else if (H_ >= 2 && H_ < 3) { R1 = 0; G1 = C; B1 = X;
        } else if (H_ >= 3 && H_ < 4) { R1 = 0; G1 = X; B1 = C;
        } else if (H_ >= 4 && H_ < 5) { R1 = X; G1 = 0; B1 = C;
        } else if (H_ >= 5 && H_ < 6) { R1 = C; G1 = 0; B1 = X; }
    }

    /* Find R, G, and B by adding the same amount to each component, to match lightness */
    var m = L - (C / 2);
    var R, G, B;
    /* Normalise to range [0,255] by multiplying 255 */
    R = (R1 + m) * 255;
    G = (G1 + m) * 255;
    B = (B1 + m) * 255;

    R = Math.round(R);
    G = Math.round(G);
    B = Math.round(B);

    return { r: R, g: G, b: B };
}


// params: imageData, width, height, depth, data, colors
// return: imageData
EJSS_SVGGRAPHICS.Utils.PNGCanvasWorker = function(params) {
	var imageData = params.data.params.imageData;
	var width = params.data.params.width;
	var height = params.data.params.height;
	var depth = params.data.params.depth;
	var data = params.data.params.data;
	var colors = params.data.params.colors;
	var index = params.data.id;    
	
	function Rgb(rgb){
		var rgb = rgb.substring(4, rgb.length-1)
	     	.replace(/ /g, '')
	     	.split(',');
	    return {r: rgb[0], g: rgb[1], b: rgb[2]};
	}
	
	function Hsl(hsl){
		var hsl = hsl.substring(4, hsl.length-1)
	     	.replace(/ /g, '')
	     	.replace(/%/g, '')
	     	.split(',');
	    return {h: hsl[0], s: (hsl[1]/100), l: (hsl[2]/100)};
	}
		
	function setPixel(imageData, x, y, r, g, b, a) {
	    index = (x + y * imageData.width) * 4;
	    imageData.data[index+0] = r;
	    imageData.data[index+1] = g;
	    imageData.data[index+2] = b;
	    imageData.data[index+3] = a;
	}
	
	var cinf = colors[0];
	var csup = colors[colors.length-1];
	for(var i=0; i<width; i++) { 
		for(var j=0; j<height; j++) {
			// get color from palette
			var index = data[i][height-j-1];
			var color = index<0 ? cinf : (index<colors.length) ? colors[index] : csup;
			// check color
			if (typeof color == "undefined") color = cinf;
			// get rgb from color  			
			if (color.substring(0,3) == "rgb") {
				rgb = Rgb(color);
			} else if (color.substring(0,3) == "hsl") {
				rgb = EJSS_SVGGRAPHICS.Utils.hsl2rgb(Hsl(color));
			}  			
			// draw pixel
  			setPixel(imageData, i, j, rgb.r, rgb.g, rgb.b, 255);
  		}  		
	}
	
    self.postMessage({ result: imageData, id: index });					  
}

EJSS_SVGGRAPHICS.Utils.PNGCanvasNoWorker = function(imageData, width, height, depth, data, colors) {
	function Rgb(rgb){
		var rgb = rgb.substring(4, rgb.length-1)
         	.replace(/ /g, '')
         	.split(',');
	    return {r: rgb[0], g: rgb[1], b: rgb[2]};
	}

	function Hsl(hsl){
		var hsl = hsl.substring(4, hsl.length-1)
         	.replace(/ /g, '')
         	.replace(/%/g, '')
         	.split(',');
	    return {h: hsl[0], s: (hsl[1]/100), l: (hsl[2]/100)};
	}
		
	function setPixel(imageData, x, y, r, g, b, a) {
	    index = (x + y * imageData.width) * 4;
	    imageData.data[index+0] = r;
	    imageData.data[index+1] = g;
	    imageData.data[index+2] = b;
	    imageData.data[index+3] = a;
	}
	
	// set data
	var cinf = colors[0];
	var csup = colors[colors.length-1];
  	for(var i=0; i<width; i++) { 
  		for(var j=0; j<height; j++) {
  			// get color from palette
  			var index = data[i][height-j-1];
  			var color = index<0 ? cinf : (index<colors.length) ? colors[index] : csup;
  			// check color
  			if (typeof color == "undefined") color = cinf;
  			// get rgb from color  			
			if (color.substring(0,3) == "rgb") {
				rgb = Rgb(color);
			} else if (color.substring(0,3) == "hsl") {
				rgb = EJSS_SVGGRAPHICS.Utils.hsl2rgb(Hsl(color));
			}  			
  			// draw pixel
  			setPixel(imageData, i, j, rgb.r, rgb.g, rgb.b, 255);
  		}  		
	}		
		
};

EJSS_SVGGRAPHICS.Utils.PNGCanvas = function(width, height, depth, data, colors, callback) {	
	var element = document.createElement("canvas");
	element.setAttribute('width',width);
	element.setAttribute('height',height);
	var c = element.getContext("2d");
	
	// create a new pixel array
	var imageData = c.createImageData(width, height);
	
	if(EJSS_TOOLS.Worker && EJSS_TOOLS.Worker.runFunction) {
		EJSS_TOOLS.Worker.runFunction("PNGCanvasWorker", EJSS_SVGGRAPHICS.Utils.PNGCanvasWorker, 
			{ imageData: imageData, 
			  width: width, height: height, depth: depth, 
			  data: data, colors: colors}, 
			function(result){ 			
				c.putImageData(result.result, 0, 0); // at coords 0,0	
				callback(element.toDataURL());
			});		
	} else {
		EJSS_SVGGRAPHICS.Utils.PNGCanvasNoWorker(imageData, width, height, depth, data, colors);
				
		// copy the image data back onto the canvas
		c.putImageData(imageData, 0, 0); // at coords 0,0		
		callback(element.toDataURL());	
	}
	
};

EJSS_SVGGRAPHICS.Utils.ImageDataCanvas = function(width, height, depth, data, colors, callback) {	
	var element = document.createElement("canvas");
	element.setAttribute('width',width);
	element.setAttribute('height',height);
	var c = element.getContext("2d");
	
	// create a new pixel array
	var imageData = c.createImageData(width, height);
	
	EJSS_TOOLS.Worker.runFunction("PNGCanvasWorker", EJSS_SVGGRAPHICS.Utils.PNGCanvasWorker, 
		{ imageData: imageData, 
		  width: width, height: height, depth: depth, 
		  data: data, colors: colors}, 
		function(result){		
			callback(result);
		});
};/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG video
 */
EJSS_SVGGRAPHICS.video = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape
	var mShape = mGraphics.getElementById(mElement.getName());	
	var myVidSrc;	
	if (mShape === null) { 	// exits?
	    // create SVG element
	   	mShape = document.createElementNS("http://www.w3.org/2000/svg","foreignObject");
	    mShape.setAttribute("id",mElement.getName());	   
	    group.appendChild(mShape);
	    // create video src 
		myVidSrc = document.createElement('video');
		myVidSrc.setAttribute("id", mElement.getName() + ".video");

		// select src
		navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia || navigator.oGetUserMedia;
		if (navigator.getUserMedia && mElement.getWebCam()) {       
	   		navigator.getUserMedia({video: true}, function(s) { myVidSrc.src = window.URL.createObjectURL(s); }, function(e) {} );
		} else {	
			myVidSrc.src = mElement.getVideoUrl();
		}		

		mShape.appendChild(myVidSrc);	    
	}
	else {
		myVidSrc = document.getElementById(mElement.getName() + ".video");
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = Math.abs(size[0]/2);
    var my = Math.abs(size[1]/2);
    
 	// set video src attrs
 	myVidSrc.loop = mElement.getLoop();	
 	myVidSrc.type = mElement.getType();
 	myVidSrc.poster = mElement.getPoster();
	myVidSrc.autoplay = mElement.getAutoPlay();
	myVidSrc.controls = mElement.getControls();
	if(!isNaN(myVidSrc.duration) && (myVidSrc.duration >= mElement.getCurrentTime()))
		 myVidSrc.currentTime = mElement.getCurrentTime();
	myVidSrc.width = Math.abs(size[0]);
	myVidSrc.height = Math.abs(size[1]);
	
 	// set SVG element	
	mShape.setAttribute("x",(x-mx));
	mShape.setAttribute("y",(y-my));
	
	// play video 
	if(mElement.isPlay()) 
		myVidSrc.play()
	else 
		myVidSrc.pause();	 	
	
	var attributes = mElement.getStyle().getAttributes();
	for (var attr in attributes) {
	  	mShape.setAttribute(attr,attributes[attr]);
	}    	    	    					  	
	
	return mShape;         
}/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_SVGGRAPHICS = EJSS_SVGGRAPHICS || {};

/**
 * @param mGraphics Element where draw
 * @param mElement Element to draw
 * @returns A SVG wheel
 */
EJSS_SVGGRAPHICS.wheel = function(mGraphics, mElement) {  
	// get element group
    var group, elementGroup = mElement.getGroup();
    if(elementGroup !== null) 
    	group = mGraphics.getElementById(elementGroup.getName());
    else 
    	group = mGraphics;
    	
    // get shape	
	var mGroup = mGraphics.getElementById(mElement.getName());
	var mCircle, mCross;
	if (mGroup === null) { 	// exits?
	    // create SVG element group
	    mGroup = document.createElementNS("http://www.w3.org/2000/svg","g"); 
	    mGroup.setAttribute("id", mElement.getName());
	    group.appendChild(mGroup);	    
	    // create ellipse element
	    mCircle = document.createElementNS("http://www.w3.org/2000/svg","ellipse"); 
	    mCircle.setAttribute("id", mElement.getName() + ".circle");
	    mGroup.appendChild(mCircle);	    
	    // create cross element
	    mCross = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mCross.setAttribute("id", mElement.getName() + ".cross");
	    mGroup.appendChild(mCross);	    
	} else {
		mCircle = mGraphics.getElementById(mElement.getName() + ".circle");
		mCross = mGraphics.getElementById(mElement.getName() + ".cross");		
	}

	// get position of the element center 
    var pos = mElement.getPixelPosition();
    var size = mElement.getPixelSizes();     
    var offset = mElement.getRelativePositionOffset(size);  
    var x = pos[0]+offset[0];
    var y = pos[1]+offset[1];
	
	// get half sizes 		
    var mx = size[0]/2;
    var my = size[1]/2;
    
	// set circle atts
    mCircle.setAttribute("cx",x);
    mCircle.setAttribute("cy",y);
    mCircle.setAttribute("rx",Math.abs(mx));
    mCircle.setAttribute("ry",Math.abs(my));
      	
	// set cross atts
    mCross.setAttribute('d', 
    	 "M " + (x-mx) + " " + y + 
    	" L " + (x+mx) + " " + y + 
    	" M " + x + " " + (y-my) + 
    	" L " + x + " " + (y+my));
	
	// set style
    var style = mElement.getStyle(); 
    if(style.getDrawFill()) 	
    	mGroup.setAttribute("fill",style.getFillColor());
    else 
    	mGroup.setAttribute("fill","none");    
    if(style.getDrawLines()) {
    	mGroup.setAttribute("stroke",style.getLineColor());
    	mGroup.setAttribute("stroke-width",style.getLineWidth());
    } else {
    	mGroup.setAttribute("stroke","none");
    	mGroup.setAttribute("stroke-width",0);    	
    }        
	mGroup.setAttribute("shapeRendering",style.getShapeRendering());  	

	var attributes = style.getAttributes();
	for (var attr in attributes) {
	  	mGroup.setAttribute(attr,attributes[attr]);
	}    	    	    					  	

	return mGroup;         
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Deployment for 2D drawing.
 * @module 2Dgraphics 
 */

var EJSS_GRAPHICS = EJSS_GRAPHICS || {};

/**
 * SVGGraphics class
 * @class SVGGraphics 
 * @constructor  
 */
EJSS_GRAPHICS.SvgGraphics = {

  /**
   * Return document box
   * @return box  
   */
  getOffsetRect: function(graphics) {
  	return EJSS_GRAPHICS.GraphicsUtils.getOffsetRect(graphics);
  }
};

/**
 * Constructor for Element
 * @param mName Identifier SVG element in HTML document
 * @returns A SVG graphics
 */
EJSS_GRAPHICS.svgGraphics = function(mName) {
  var self = EJSS_INTERFACE.svgGraphics(mName);	// reference returned     
  var mInterfaceGraphics = self.getDOMElement();
  var mDefs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
  mInterfaceGraphics.appendChild(mDefs);	     



  /**
   * Return SVG document for event handle
   * @return SVG element 
   */
  self.getEventContext = function() {  	
    return mInterfaceGraphics;
  };
  
  /**
   * Return SVG document box
   * @return width integer 
   */
  self.getBox = function() {
  	var box = EJSS_GRAPHICS.CanvasGraphics.getOffsetRect(self)
  	box.width -= 1;
  	box.height -= 1;
  	return box;
  };

  /**
   * Remove a shape
   * @param name
   */
  self.remove = function(name) {	
    var myElement = mInterfaceGraphics.getElementById(name);		// get the SVG element from document svg
    if (myElement !== null) { 
    	mInterfaceGraphics.removeChild(myElement);
    	return true;
    }
    return false;
  };

  /**
   * Reset the SVG graphics
   */
  self.reset = function() {	
	while (mInterfaceGraphics.firstChild) {
	    mInterfaceGraphics.removeChild(mInterfaceGraphics.firstChild);
	} 
  	// create defs
//  	mDefs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
  	mInterfaceGraphics.appendChild(mDefs);	     
  };

  
  self.getDefs = function() { return mDefs; }
  
  /**
   * Draw Elements
   * @param elements array of Elements and Element Sets
   * @param force whether force draw elements
   */
  self.draw = function(elements,force) {
    for (var i=0, n=elements.length; i<n; i++) { 
      var element = elements[i];
      if(element.getElements) {	// whether element set
      	var lasts = element.getLastElements();
  		for(var j=0, m=lasts.length; j<m; j++) { // remove last removed elements
			var mShape = mInterfaceGraphics.getElementById(lasts[j].getName());
			if(mShape) mShape.parentNode.removeChild(mShape);
  		}
      }
  	  if(force || element.isChanged() || element.isGroupChanged() || element.isMustProject()) {
  		var build = self.drawElement(element);
		if(typeof build != "undefined" && build != null)  		 
			self.transformElement(element, build);
	  }
    }
  };
    
  /**
   * Draw Element
   * @param element
   */
  self.drawElement = function (element) {
  	var Shape = EJSS_DRAWING2D.Shape;			
    // console.log("Drawing element "+element.getName());
    var build = null;
	
  	switch (element.getClass()) {
		case "ElementCustom":
		  {
		    var customFunction = element.getFunction();
		    if (customFunction) build = customFunction(mInterfaceGraphics,element);	
		  }
		  break;
  		case "ElementGroup": 	build = EJSS_SVGGRAPHICS.group(mInterfaceGraphics,element);	break;
  		case "ElementShape": 
			switch (element.getShapeType()) {
			  default : 
			  case Shape.ELLIPSE         : build = EJSS_SVGGRAPHICS.ellipse(mInterfaceGraphics,element); 	break;
			  case Shape.RECTANGLE       : build = EJSS_SVGGRAPHICS.rectangle(mInterfaceGraphics,element);	break;
			  case Shape.ROUND_RECTANGLE : build = EJSS_SVGGRAPHICS.roundRectangle(mInterfaceGraphics,element); break;
			  case Shape.WHEEL           : build = EJSS_SVGGRAPHICS.wheel(mInterfaceGraphics,element); 		break;
			  case Shape.NONE            : // do not break;
			  case Shape.POINT           : build = EJSS_SVGGRAPHICS.point(mInterfaceGraphics,element); 		break;
			}
			break;
		case "ElementSegment": 	build = EJSS_SVGGRAPHICS.segment(mInterfaceGraphics,element);	break;
		case "ElementImage":   	build = EJSS_SVGGRAPHICS.image(mInterfaceGraphics,element);		break;
		case "ElementVideo":   	build = EJSS_SVGGRAPHICS.video(mInterfaceGraphics,element);		break;
		case "ElementArrow":	build = EJSS_SVGGRAPHICS.arrow(mInterfaceGraphics,element);		break;
		case "ElementText":		build = EJSS_SVGGRAPHICS.text(mInterfaceGraphics,element);		break;
		case "ElementSpring":	build = EJSS_SVGGRAPHICS.spring(mInterfaceGraphics,element);		break;
		case "ElementTrail":	build = EJSS_SVGGRAPHICS.trail(mInterfaceGraphics,element);		break;
		case "ElementTrace":	build = EJSS_SVGGRAPHICS.trace(mInterfaceGraphics,element);		break;
		case "ElementGrid":		build = EJSS_SVGGRAPHICS.grid(mInterfaceGraphics,element);		break;
		case "ElementAxis":		build = EJSS_SVGGRAPHICS.axis(mInterfaceGraphics,element);		break;
		case "ElementCursor":	build = EJSS_SVGGRAPHICS.cursor(mInterfaceGraphics,element);		break;
		case "ElementPolygon":	build = EJSS_SVGGRAPHICS.polygon(mInterfaceGraphics,element);	break;
		case "ElementAnalyticCurve":	build = EJSS_SVGGRAPHICS.analyticCurve(mInterfaceGraphics,element);	break;
		case "ElementCellLattice": 	build = EJSS_SVGGRAPHICS.cellLattice(mInterfaceGraphics,element);	break; 			
		case "ElementByteRaster": 	build = EJSS_SVGGRAPHICS.byteRaster(mInterfaceGraphics,element);	break;			
		case "ElementMesh": 	build = EJSS_SVGGRAPHICS.mesh(mInterfaceGraphics,element);		break;			
		case "ElementTank":   	build = EJSS_SVGGRAPHICS.tank(mInterfaceGraphics,element);		break;
		case "ElementPipe":   	build = EJSS_SVGGRAPHICS.pipe(mInterfaceGraphics,element);		break;
		case "ElementHistogram":build = EJSS_SVGGRAPHICS.histogram(mInterfaceGraphics,element);		break;

		case "ElementScalarField":	build = EJSS_SVGGRAPHICS.scalarField(mInterfaceGraphics,element);		break;
		case "ElementCanvas":	build = EJSS_SVGGRAPHICS.canvas(mInterfaceGraphics,element);		break;
	}
	if(build) {
		if (!element.isGroupVisible()) 
		   build.setAttribute("visibility","hidden");
		else
	       build.setAttribute("visibility","visible");
	}			

  	return build;
  };

  /**
   * Transform Element
   * @param element
   */
  self.transformElement = function (element, build) {
	var trstr = "";
	var trans = element.getTransformation();
	var rot = element.getRotate();
	var pos = element.getPixelPosition();	

    if (trans.length > 0) {  // matrix
    	trstr = "translate(" + pos[0] + " " + pos[1] + ")";
		trstr += "matrix(" + trans[0] + " " + trans[1] + " " + trans[2] + " " +
		 			trans[3] + " " + trans[4] + " " + trans[5] + ")";  	
    	trstr += "translate(-" + pos[0] + " -" + pos[1] + ")";
		// build.setAttribute("transform-origin", pos[0] + " " + pos[1]); // not supported in Safari and Mozilla
	}
	if (rot != 0) {  // rotation angle 	
	    var angle = EJSS_TOOLS.Mathematics.degrees(-rot);	
		trstr += " rotate(" + angle + " " + pos[0] + " " + pos[1]+ ")";			
	}	  
    //if (trstr.length>0) 
    	build.setAttribute("transform",trstr);
  }

  /**
   * Draw gutters
   * @param gutters
   */
  self.drawGutters = function(panel) {
  	var gutters = panel.getGutters();
  	
  	if (gutters.visible) {    	 			
	 	var UTILS = EJSS_SVGGRAPHICS.Utils;  	   	    

		var mShape = mInterfaceGraphics.getElementById(".myGutters");
		var mOuterBorder = mInterfaceGraphics.getElementById(".myOuterBorder");
		var mInnerBorder = mInterfaceGraphics.getElementById(".myInnerBorder");
		if (mShape === null) { 	// exits?
		    // create SVG element
		    mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
		    mShape.setAttribute("id", ".myGutters");
		    mShape.setAttribute("stroke","none");
	    	mShape.setAttribute("stroke-width",0);    	
		    mInterfaceGraphics.appendChild(mShape);	    

		    mOuterBorder = document.createElementNS("http://www.w3.org/2000/svg","path"); 
		    mOuterBorder.setAttribute("id", ".myOuterBorder");
		    mOuterBorder.setAttribute("fill","none");    
		    mInterfaceGraphics.appendChild(mOuterBorder);	    

		    mInnerBorder = document.createElementNS("http://www.w3.org/2000/svg","path"); 
		    mInnerBorder.setAttribute("id", ".myInnerBorder");
		    mInnerBorder.setAttribute("fill","none");    
		    mInterfaceGraphics.appendChild(mInnerBorder);	
		        
		}

	 	// get gutters position    
	    var inner = panel.getInnerRect(); 

	 	// get panel position
	    var x = 0.5, y = 0.5;
	    var box = self.getBox();
    	var sx = box.width, sy = box.height;

	 	// create paths	    	   	    
	 	var guttersStyle = panel.getGuttersStyle();	 
	 	var panelStyle = panel.getStyle();   
	    if(panelStyle.getShapeRendering() == "crispEdges") {
		    mShape.setAttribute('d', 
		    	 "M " + UTILS.crispValue(x) + " " + UTILS.crispValue(y) + 
		    	" L " + UTILS.crispValue(x) + " " + UTILS.crispValue(y+sy) + 
		    	" L " + UTILS.crispValue(x+sx) + " " + UTILS.crispValue(y+sy) + 
		    	" L " + UTILS.crispValue(x+sx) + " " + UTILS.crispValue(y) +
		    	" L " + UTILS.crispValue(x) + " " + UTILS.crispValue(y) + 
		    	" M " + UTILS.crispValue(inner.x) + " " + UTILS.crispValue(inner.y) + 
		    	" L " + UTILS.crispValue(inner.x+inner.width) + " " + UTILS.crispValue(inner.y) + 
		    	" L " + UTILS.crispValue(inner.x+inner.width) + " " + UTILS.crispValue(inner.y+inner.height) + 
		    	" L " + UTILS.crispValue(inner.x) + " " + UTILS.crispValue(inner.y+inner.height) + 
		    	" L " + UTILS.crispValue(inner.x) + " " + UTILS.crispValue(inner.y) + " z");
		    mOuterBorder.setAttribute('d', 
		    	 "M " + UTILS.crispValue(x) + " " + UTILS.crispValue(y) + 
		    	" L " + UTILS.crispValue(x) + " " + UTILS.crispValue(y+sy) + 
		    	" L " + UTILS.crispValue(x+sx) + " " + UTILS.crispValue(y+sy) + 
		    	" L " + UTILS.crispValue(x+sx) + " " + UTILS.crispValue(y) +
		    	" L " + UTILS.crispValue(x) + " " + UTILS.crispValue(y) + " z");
		    mInnerBorder.setAttribute('d', 
		    	 "M " + UTILS.crispValue(inner.x) + " " + UTILS.crispValue(inner.y) + 
		    	" L " + UTILS.crispValue(inner.x+inner.width) + " " + UTILS.crispValue(inner.y) + 
		    	" L " + UTILS.crispValue(inner.x+inner.width) + " " + UTILS.crispValue(inner.y+inner.height) + 
		    	" L " + UTILS.crispValue(inner.x) + " " + UTILS.crispValue(inner.y+inner.height) + 
		    	" L " + UTILS.crispValue(inner.x) + " " + UTILS.crispValue(inner.y) + " z");
	    } else {
		    mShape.setAttribute('d', 
		    	 "M " + (x) + " " + (y) + 
		    	" L " + (x) + " " + (y+sy) + 
		    	" L " + (x+sx) + " " + (y+sy) + 
		    	" L " + (x+sx) + " " + (y) +
		    	" L " + (x) + " " + (y) + 
		    	" M " + (inner.x) + " " + (inner.y) + 
		    	" L " + (inner.x+inner.width) + " " + (inner.y) + 
		    	" L " + (inner.x+inner.width) + " " + (inner.y+inner.height) + 
		    	" L " + (inner.x) + " " + (inner.y+inner.height) + 
		    	" L " + (inner.x) + " " + (inner.y) + " z");
		    mOuterBorder.setAttribute('d', 
		    	 "M " + (x) + " " + (y) + 
		    	" L " + (x) + " " + (y+sy) + 
		    	" L " + (x+sx) + " " + (y+sy) + 
		    	" L " + (x+sx) + " " + (y) +
		    	" L " + (x) + " " + (y) + " z");
		    mInnerBorder.setAttribute('d',
		    	"M " + (inner.x) + " " + (inner.y) + 
		    	" L " + (inner.x+inner.width) + " " + (inner.y) + 
		    	" L " + (inner.x+inner.width) + " " + (inner.y+inner.height) + 
		    	" L " + (inner.x) + " " + (inner.y+inner.height) + 
		    	" L " + (inner.x) + " " + (inner.y) + " z");
		}  	    
		
		// set style (appling into gutters, i.e. panel, so panel style)
	    if(guttersStyle.getDrawFill())	
	    	mShape.setAttribute("fill",guttersStyle.getFillColor());
	    else 
	    	mShape.setAttribute("fill","none");    
	    if(guttersStyle.getDrawLines()) {
	    	mOuterBorder.setAttribute("stroke",guttersStyle.getLineColor());
	    	mOuterBorder.setAttribute("stroke-width",guttersStyle.getLineWidth());
	    } else {
	    	mOuterBorder.setAttribute("stroke","none");
	    	mOuterBorder.setAttribute("stroke-width",0);    	
	    }        
	    if(panelStyle.getDrawLines()) {
	    	mInnerBorder.setAttribute("stroke",panelStyle.getLineColor());
	    	mInnerBorder.setAttribute("stroke-width",panelStyle.getLineWidth());
	    } else {
	    	mInnerBorder.setAttribute("stroke","none");
	    	mInnerBorder.setAttribute("stroke-width",0);    	
	    }        
		mShape.setAttribute("shapeRendering",panelStyle.getShapeRendering());
		mOuterBorder.setAttribute("shapeRendering",guttersStyle.getShapeRendering());
		mInnerBorder.setAttribute("shapeRendering",panelStyle.getShapeRendering());
	}  	
  };
   /**
   * Draw panel
   * @param panel
   */
  self.drawPanel = function(panel) {
 	var UTILS = EJSS_SVGGRAPHICS.Utils;   

	var mShape = mInterfaceGraphics.getElementById(".myPanel");
	if (mShape === null) { 	// exits?
	    // create SVG element
	    mShape = document.createElementNS("http://www.w3.org/2000/svg","path"); 
	    mShape.setAttribute("id", ".myPanel");
	    mInterfaceGraphics.appendChild(mShape);	    
	}

 	// get position
    var x = 0.5, y = 0.5;
    var box = self.getBox();
    var sx = box.width, sy = box.height;

 	// create path	    	   	    
 	var style = panel.getStyle();	    	   	   
    if(style.getShapeRendering() == "crispEdges") {
	    mShape.setAttribute('d', 
	    	 "M " + UTILS.crispValue(x) + " " + UTILS.crispValue(y) + 
	    	" L " + UTILS.crispValue(x) + " " + UTILS.crispValue(y+sy) + 
	    	" L " + UTILS.crispValue(x+sx) + " " + UTILS.crispValue(y+sy) + 
	    	" L " + UTILS.crispValue(x+sx) + " " + UTILS.crispValue(y) + " z");
    } else {
	    mShape.setAttribute('d', 
	    	 "M " + (x) + " " + (y) + 
	    	" L " + (x) + " " + (y+sy) + 
	    	" L " + (x+sx) + " " + (y+sy) + 
	    	" L " + (x+sx) + " " + (y) + " z");
	}  	    
	
	// set style (appling into gutters, i.e. panel, so panel style)
    if(style.getDrawFill())	
    	mShape.setAttribute("fill",style.getFillColor());
    else 
    	mShape.setAttribute("fill","none");
    	
    if(style.getDrawLines()) {
    	mShape.setAttribute("stroke",style.getLineColor());
    	mShape.setAttribute("stroke-width",style.getLineWidth());
    } else {
    	mShape.setAttribute("stroke","none");
    	mShape.setAttribute("stroke-width",0);    	
    }        

	mShape.setAttribute("shapeRendering",style.getShapeRendering());
  };
   
  self.importSVG = function(callback) {
    var box = self.getBox();

	// canvas
    var canvas = document.createElement("canvas");
    canvas.width = box.width+1;
    canvas.height = box.height+1;
    var ctx = canvas.getContext("2d");

	// fix size
  	var dom = self.getDOMElement();
  	dom.setAttribute("width", canvas.width);
  	dom.setAttribute("height", canvas.height);
    var svg_xml = (new XMLSerializer()).serializeToString(dom);    

    var img = new Image();
//    img.src = "data:image/svg+xml;base64," + btoa(svg_xml);
    img.src = "data:image/svg+xml;utf8," + svg_xml;

    img.onload = function() {
    	console.log("tam " + canvas.width + " " + canvas.height);
        ctx.drawImage(img,0,0);
        if(callback) callback(canvas.toDataURL("image/png"));
    } 
    return img.src;
  }
    
 return self;     
      
}
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * InfoText
 * @class InfoText 
 * @constructor  
 */
EJSS_DRAWING2D.InfoText = {

	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Text.registerProperties(element, controller);
		// super class
		
		controller.registerProperty("Info",  element.setInfo);
		controller.registerProperty("Format",  element.setFormat);		
	},
};

/**
 * Creates a 2D InfoText
 * @method infoText
 */
EJSS_DRAWING2D.infoText = function(name) {
	var self = EJSS_DRAWING2D.text(name);
	var super_getText = self.getText;
	
	var mInfo;
	var mFormat;			// replace ".^", ".##" por los números pasados

	/**
	 * Set info function or element with getInfo function
	 */
	self.setInfo = function(info) {
		mInfo = info;
	}

	self.getText = function() {
		if(typeof mInfo != "undefined") {	// exits info function 
			if(mInfo.getInfo) 
				return mInfo.getInfo();
			else if (typeof mInfo == "function") 
				return mInfo();
		} else { // uses text with the format
			var text = super_getText();			
			// process text
			return fillString(mFormat,text);
		}
	}

	self.setFormat = function(format) {
		mFormat = format;
	}

	self.isChanged = function() {
		return true;
	}

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.InfoText.registerProperties(self, controller);
	};

	/*
	 * Fill formatted string
	 */
	function fillString(format, text) {
		var t = "";
		var txt = format;
		var numbers = format.match(/[0-9]*\.(\^|#)+/g);
		if (numbers==null) return text;
		if(!Array.isArray(text)) {
			text = JSON.parse("["+text+"]");
		}

		for (var i=0; i<numbers.length; i++) {
			var precision = numbers[i].split("#").length - 1;
			// check text
			if(isNaN(text[i])) 
				t = text[i];
			else
				t = text[i].toFixed(precision)
			txt = txt.replace(numbers[i], t);
		}
		
		return txt;		
	}
	
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module Drawing2D 
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Knob
 * @class Knob 
 * @constructor  
 */
EJSS_DRAWING2D.Knob = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
	EJSS_DRAWING2D.Meter.registerProperties(element,controller); // super class

    controller.registerAction("OnPress" ,element.getValue); 
    controller.registerAction("OnRelease" ,element.getValue); 
    controller.registerAction("OnChange" ,element.getValue); 
    
  }

};

/**
 * Knob function
 * Creates a 2D element that looks and behaves like a knob
 * @method knob
 * @param mName the name of the element
 * @returns An abstract interface element
 */
EJSS_DRAWING2D.knob = function (mName) {
  var self = EJSS_DRAWING2D.meter(mName);
  
  var mThreshold = Math.PI/5;
  var pressed_shift = [0,0];  // initial dragging shift  

  // Implementation variables
  var mKnobExterior,mKnobMiddle,mKnobInterior;

  var mDummyContainer;
  var mSVGDefs = 
	  "<svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' version='1.1'>"+
      "  <defs>"+
      "    <linearGradient id='KnobExteriorGradient' x1='0%' y1='0%' x2='100%' y2='100%'>"+
      "      <stop offset='0%'   stop-color='rgb(250,250,250)' />"+
      "      <stop offset='100%' stop-color='rgb(100,100,100)' />"+
      "    </linearGradient>"+
      "    <linearGradient id='KnobMiddleGradient' x1='0%' y1='0%' x2='100%' y2='100%'>"+
      "      <stop offset='0%'   stop-color='rgb(200,200,200)' />"+
      "      <stop offset='100%' stop-color='rgb(150,150,150)' />"+
      "    </linearGradient>"+
      "    <linearGradient id='KnobInteriorGradient' x1='0%' y1='0%' x2='100%' y2='100%'>"+
      "      <stop offset='0%'   stop-color='rgb(127,127,127)' />"+
      "      <stop offset='100%' stop-color='rgb(250,250,250)' />"+
      "    </linearGradient>"+
      "    <filter id='KnobFilter' x='0' y='0' width='200%' height='200%'>"+
      "      <feOffset result='offOut' in='SourceGraphic' dx='1' dy='1' />"+
      "      <feGaussianBlur result='blurOut' in='offOut' stdDeviation='2' />"+
      "      <feBlend in='SourceGraphic' in2='blurOut' mode='normal' />"+
      "    </filter>"+
      "  </defs>"+
      "</svg>";

  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------
  
  // ----------------------------------------------------
  // Properties overwritten
  // ----------------------------------------------------

  self.super_setRadius = self.setRadius;

  self.setRadius = function(value) {
	  self.super_setRadius(value);
	  mKnobExterior.setChanged(true);
	  mKnobMiddle.setChanged(true);
	  mKnobInterior.setChanged(true);
  }

  self.setFillColor = function(value) { mKnobInterior.getStyle().setFillColor(value); };

  // ----------------------------------------------------
  // Properties and copies
  // ----------------------------------------------------

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
	EJSS_DRAWING2D.Knob.registerProperties(self,controller);
  };
  
  // ----------------------------------------------------
  // private or protected functions
  // ----------------------------------------------------

  self.super_addParticularChildren = self.addParticularChildren;

  self.addParticularChildren = function() {
	  self.super_addParticularChildren();
	  mKnobExterior.setParent(self);
	  mKnobMiddle.setParent(self);
	  mKnobInterior.setParent(self);
  };

  self.createBasics = function() { 
	  /*
	  var stop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
	  stop1.setAttribute("offset","0%");
	  stop1.setAttribute("stop-color","rgb(250,250,250)");

	  var stop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
	  stop2.setAttribute("offset","100%");
	  stop2.setAttribute("stop-color","rgb(100,100,100)");

	  var linearGradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
	  linearGradient.setAttribute("id", "#KnobExteriorGradient");
	  linearGradient.setAttribute("x1","0%");
	  linearGradient.setAttribute("y1","0%");
	  linearGradient.setAttribute("x2","100%");
	  linearGradient.setAttribute("y2","100%");
	  linearGradient.appendChild(stop1);
	  linearGradient.appendChild(stop2);

	  var feOffset = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
	  feOffset.setAttribute("result","offOut");
	  feOffset.setAttribute("in","SourceGraphic");
	  feOffset.setAttribute("dx","1");
	  feOffset.setAttribute("dy","1");

	  var gaussianFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
	  gaussianFilter.setAttribute("result","blurOut");
	  gaussianFilter.setAttribute("in","offOut");
	  gaussianFilter.setAttribute("stdDeviation","2");

	  var feBlend = document.createElementNS("http://www.w3.org/2000/svg", "feBlend");
	  feOffset.setAttribute("in","SourceGraphic");
	  feOffset.setAttribute("in2","blurOut");
	  feOffset.setAttribute("mode","normal");

	  var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
	  filter.setAttribute("id","#KnobFilter");
	  filter.setAttribute("x","0");
	  filter.setAttribute("y","0");
	  filter.setAttribute("width","200%");
	  filter.setAttribute("height","200%");
	  filter.appendChild(feOffset);
	  filter.appendChild(gaussianFilter);
	  filter.appendChild(feBlend);
	  
	  /*
	  var defs = self.getParentPanel()getGraphics().getDefs(); // document.createElementNS("http://www.w3.org/2000/svg", "defs");
	  defs.appendChild(linearGradient);
	  defs.appendChild(filter);
	  
	 // self.getGraphics().appendChild(defs);
	  */

	  var mDummyContainer = document.getElementById(mName+".dummy_container");	
	  if (mDummyContainer === null) { 	// exits?
		  mDummyContainer = document.createElement('div');
	      mDummyContainer.setAttribute("id", mName+".dummy_container");
	      mDummyContainer.style.width = "0px";
	      mDummyContainer.style.height = "0px";
		  document.body.appendChild(mDummyContainer);
		  mDummyContainer.innerHTML = mSVGDefs;	    
	  }	
	  
	  mKnobExterior = EJSS_DRAWING2D.shape(mName+".exterior_shape");
	  mKnobExterior.setSize([1,1]);
	  mKnobExterior.getStyle().setDrawLines(false);
	  mKnobExterior.getStyle().setAttributes({ fill: "url(#KnobExteriorGradient)", filter:"url(#KnobFilter)"});

	  mKnobMiddle = EJSS_DRAWING2D.shape(mName+".middle_shape");
	  mKnobMiddle.setSize([0.9,0.9]);
	  mKnobMiddle.getStyle().setDrawLines(false);
	  mKnobMiddle.getStyle().setAttributes({ fill: "url(#KnobMiddleGradient)"});

	  mKnobInterior = EJSS_CORE.promoteToControlElement(
			  EJSS_DRAWING2D.shape(mName+".inner_shape"),self.getView(),mName+".inner_shape");
	  mKnobInterior.setY(0.3);
	  mKnobInterior.setSize([0.2,0.2]);
	  mKnobInterior.getStyle().setDrawLines(false);
	  mKnobInterior.getStyle().setAttributes({ fill: "url(#KnobInteriorGradient)"});

	  mKnobInterior.setProperty("EnabledPosition","ENABLED_NO_MOVE");
      mKnobInterior.setProperty("Sensitivity",10);
	  mKnobInterior.setProperty("OnDrag",dragged);
	  mKnobInterior.setProperty("OnPress",pressed);
	  mKnobInterior.setProperty("OnRelease",released);
  }
    
  self.super_createForeground = self.createForeground;
  
  self.createForeground = function() {
	  self.super_createForeground();
	  self.getElement('brand').setPosition([0,-0.7]);
	  self.getElement('units').setPosition([0, 0.9]);
  };

  self.adjustPosition = function() {
	  var actualValue = self.getValue();
	  if (actualValue<self.getMinimum()) {
		  actualValue = self.getMinimum();
//		  mArrow.getStyle().setFillColor(mDangerColor);
	  }
	  else if (actualValue>self.getMaximum()) {
		  actualValue = self.getMaximum();
//		  mArrow.getStyle().setFillColor(mDangerColor);
	  }
	  else {
//		  mArrow.getStyle().setFillColor(mColor);
	  }
      var angle = self.interpolateAngle(actualValue);
	  mKnobInterior.setPosition([0.3*Math.cos(angle),0.3*Math.sin(angle)]);
  }

  self.foregroundIsInside = function() { return false; };

  function pressed(point,info) {
	  var controller = self.getController();    		
	  if (controller) controller.invokeImmediateAction("OnPress");
	  
	  // init dragging	 
	  var point = info.point;
	  var i_point = info.element.getAbsolutePosition(true);
	  pressed_shift = [point[0]-i_point[0], point[1]-i_point[1]];	  
  }

  function released(point,info) {
	  var controller = self.getController();    		
	  if (controller) controller.invokeImmediateAction("OnRelease");	  
  }

  function dragged(point,info) {
	  var point = info.point;
	  var element = info.element;
	  var drag_point = [point[0] - pressed_shift[0], point[1] - pressed_shift[1]];	  
	  var group_pos = self.getAbsolutePosition(true);	  
	  var group_size = self.getSize();
	  drag_point[0] = drag_point[0] - group_pos[0];
	  drag_point[1] = (drag_point[1] - group_pos[1]) * (group_size[0]/group_size[1]);
	  var angle = self.interpolateAngle(self.getValue());
	  var rotation = Math.atan2(-drag_point[0],drag_point[1])+Math.PI/2;
	  if (Math.abs(rotation-angle)>mThreshold) return;
	  rotation = self.valueFromAngle(rotation);
	  element.setPosition([0.3*Math.cos(rotation),0.3*Math.sin(rotation)]);
	  var controller = self.getController();    		
	  if (controller) {
		controller.immediatePropertyChanged("Value");
		controller.invokeImmediateAction("OnChange");
	  }	        
  }
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module Drawing2D 
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Knob
 * @class Knob 
 * @constructor  
 */
EJSS_DRAWING2D.KnobOld = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
	EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Value", element.setValue, element.getValue);
    controller.registerProperty("Minimum", element.setMinimum, element.getMinimum);
    controller.registerProperty("Maximum", element.setMaximum, element.getMaximum);

    controller.registerAction("OnPress" ,element.getValue); 
    controller.registerAction("OnRelease" ,element.getValue); 
    controller.registerAction("OnChange" ,element.getValue); 
    
    controller.registerProperty("Size", element.setSize);
	
  }

};

/**
 * Knob function
 * Creates a 2D element that looks and behaves like a knob
 * @method knob
 * @param mName the name of the element
 * @returns An abstract interface element
 */
EJSS_DRAWING2D.knobOld = function (mName) {
  var self = EJSS_DRAWING2D.group(mName);
  
  // Configuration variables
  var mValue = 0.0;
  var mMaximum =  1.0;
  var mMinimum = -1.0;

  var mNumberOfMarks = 21;
  var mBigMark = 4;
  var mMediumMark = 2;
  var mDigits = 0;

  var mMinimumAngle = Math.PI/2 + 1.5*Math.PI/2;
  var mMaximumAngle = Math.PI/2 - 1.5*Math.PI/2;

  var mMinAngle = -Math.PI*0.7;
  var mMaxAngle =  Math.PI*0.7;
  var mThreshold = Math.PI/5;

  // Implementation variables
  var mKnobExterior,mKnobMiddle,mKnobInterior;
  var mSegments, mTexts, mBrand, mUnits;

  var mDummyContainer;
  var mSVGDefs = 
	  "<svg xmlns='http://www.w3.org/2000/svg' version='1.1'>"+
      "  <defs>"+
      "    <linearGradient id='KnobExteriorGradient' x1='0%' y1='0%' x2='100%' y2='100%'>"+
      "      <stop offset='0%'   stop-color='rgb(250,250,250)' />"+
      "      <stop offset='100%' stop-color='rgb(100,100,100)' />"+
      "    </linearGradient>"+
      "    <linearGradient id='KnobMiddleGradient' x1='0%' y1='0%' x2='100%' y2='100%'>"+
      "      <stop offset='0%'   stop-color='rgb(200,200,200)' />"+
      "      <stop offset='100%' stop-color='rgb(150,150,150)' />"+
      "    </linearGradient>"+
      "    <linearGradient id='KnobInteriorGradient' x1='0%' y1='0%' x2='100%' y2='100%'>"+
      "      <stop offset='0%'   stop-color='rgb(127,127,127)' />"+
      "      <stop offset='100%' stop-color='rgb(250,250,250)' />"+
      "    </linearGradient>"+
      "    <filter id='KnobFilter' x='0' y='0' width='200%' height='200%'>"+
      "      <feOffset result='offOut' in='SourceGraphic' dx='1' dy='1' />"+
      "      <feGaussianBlur result='blurOut' in='offOut' stdDeviation='2' />"+
      "      <feBlend in='SourceGraphic' in2='blurOut' mode='normal' />"+
      "    </filter>"+
      "  </defs>"+
      "</svg>";

  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------
  
  /**
   * Sets the value of the element. 
   * @method setValue
   * @param value
   */
  self.setValue = function(value) {
    if (mValue!=value) {
    	mValue = value;
    	adjustPosition();
    }
  }
  
  /**
   * @method getValue
   * @return current value
   */
  self.getValue = function() { 
    return mValue; 
  };    

  /**
   * Sets the minimum value of the element. 
   * @method setMinimum
   * @param value
   */
  self.setMinimum = function(value) {
    if (mMinimum!=value) {
    	mMinimum = value;
    	adjustPosition();
    }
  }
  
  /**
   * @method getMinimum
   * @return current value
   */
  self.getMinimum = function() { 
    return mMinimum; 
  };    

  /**
   * Sets the minimum value of the element. 
   * @method setMaximum
   * @param value
   */
  self.setMaximum = function(value) {
	if (mMaximum!=value) {
		mMaximum = value;
		adjustPosition();		  
	}
  }
  
  /**
   * @method getMaximum
   * @return current value
   */
  self.getMaximum = function() { 
    return mMaximum; 
  };    

  /**
   * Sets the size of the element. 
   * @method setSize
   * @param value
   */
  self.setSize = function(value) {
	  self.setSizeX(value);
	  self.setSizeY(value);
  }
  
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
	createKnob();
	EJSS_DRAWING2D.Knob.registerProperties(self,controller);
  };
  
  self.superSetParent = self.setParent;

  self.setParent = function(parent, sibling) {
		self.superSetParent(parent,sibling);
		mKnobExterior.setParent(self);
		mKnobMiddle.setParent(self);
		mKnobInterior.setParent(self);
	  }

  
  function createKnob() {
	  /*
	  var stop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
	  stop1.setAttribute("offset","0%");
	  stop1.setAttribute("stop-color","rgb(250,250,250)");

	  var stop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop");
	  stop2.setAttribute("offset","100%");
	  stop2.setAttribute("stop-color","rgb(100,100,100)");

	  var linearGradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
	  linearGradient.setAttribute("id", "#KnobExteriorGradient");
	  linearGradient.setAttribute("x1","0%");
	  linearGradient.setAttribute("y1","0%");
	  linearGradient.setAttribute("x2","100%");
	  linearGradient.setAttribute("y2","100%");
	  linearGradient.appendChild(stop1);
	  linearGradient.appendChild(stop2);

	  var feOffset = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
	  feOffset.setAttribute("result","offOut");
	  feOffset.setAttribute("in","SourceGraphic");
	  feOffset.setAttribute("dx","1");
	  feOffset.setAttribute("dy","1");

	  var gaussianFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
	  gaussianFilter.setAttribute("result","blurOut");
	  gaussianFilter.setAttribute("in","offOut");
	  gaussianFilter.setAttribute("stdDeviation","2");

	  var feBlend = document.createElementNS("http://www.w3.org/2000/svg", "feBlend");
	  feOffset.setAttribute("in","SourceGraphic");
	  feOffset.setAttribute("in2","blurOut");
	  feOffset.setAttribute("mode","normal");

	  var filter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
	  filter.setAttribute("id","#KnobFilter");
	  filter.setAttribute("x","0");
	  filter.setAttribute("y","0");
	  filter.setAttribute("width","200%");
	  filter.setAttribute("height","200%");
	  filter.appendChild(feOffset);
	  filter.appendChild(gaussianFilter);
	  filter.appendChild(feBlend);
	  
	  /*
	  var defs = self.getParentPanel()getGraphics().getDefs(); // document.createElementNS("http://www.w3.org/2000/svg", "defs");
	  defs.appendChild(linearGradient);
	  defs.appendChild(filter);
	  
	 // self.getGraphics().appendChild(defs);
	  */
	  
	  mDummyContainer = document.createElement('div');
	  document.body.appendChild(mDummyContainer);
	  mDummyContainer.innerHTML = mSVGDefs;
	  
	  mKnobExterior = EJSS_DRAWING2D.shape(mName+".exterior_shape");
	  mKnobExterior.setSize([1,1]);
	  mKnobExterior.getStyle().setDrawLines(false);
	  mKnobExterior.getStyle().setAttributes({ fill: "url(#KnobExteriorGradient)", filter:"url(#KnobFilter)"});

	  mKnobMiddle = EJSS_DRAWING2D.shape(mName+".middle_shape");
	  mKnobMiddle.setSize([0.9,0.9]);
	  mKnobMiddle.getStyle().setDrawLines(false);
	  mKnobMiddle.getStyle().setAttributes({ fill: "url(#KnobMiddleGradient)"});

	  mKnobInterior = EJSS_CORE.promoteToControlElement(
			  EJSS_DRAWING2D.shape(mName+".inner_shape"),self.getView(),mName+".inner_shape");
	  mKnobInterior.setY(0.3);
	  mKnobInterior.setSize([0.2,0.2]);
	  mKnobInterior.getStyle().setDrawLines(false);
	  mKnobInterior.getStyle().setAttributes({ fill: "url(#KnobInteriorGradient)"});

	  mKnobInterior.setProperty("EnabledPosition","ENABLED_NO_MOVE");
      mKnobInterior.setProperty("Sensitivity",10);
	  mKnobInterior.setProperty("OnDrag",dragged);
	  mKnobInterior.setProperty("OnPress",pressed);
	  mKnobInterior.setProperty("OnRelease",released);

//	  self.addElement(mKnobExterior);
//	  self.addElement(mKnobMiddle);
//	  self.addElement(mKnobInterior);


  }
    
  function adjustPosition() {
	  mValue = Math.max(Math.min(mMaximum,mValue),mMinimum);
	  var angle = mMaxAngle - (mValue-mMinimum)/(mMaximum-mMinimum)*(mMaxAngle-mMinAngle) + Math.PI/2;
	  mKnobInterior.setPosition([0.3*Math.cos(angle),0.3*Math.sin(angle)]);
  }

  function pressed(point,info) {
	  var controller = self.getController();    		
	  if (controller) controller.invokeImmediateAction("OnPress");
  }

  function released(point,info) {
	  var controller = self.getController();    		
	  if (controller) controller.invokeImmediateAction("OnRelease");
  }

  function dragged(point,info) {
	  var point = info.point;
	  var element = info.element;
	  var group_pos = self.getAbsolutePosition(true);
	  point[0] -= group_pos[0];
	  point[1] -= group_pos[1];
	  var angle = mMaxAngle - (mValue-mMinimum)/(mMaximum-mMinimum)*(mMaxAngle-mMinAngle);
	  var rotation = Math.atan2(-point[0],point[1]);
	  if (Math.abs(rotation-angle)>mThreshold) return;
	  rotation = Math.min(Math.max(rotation,mMinAngle),mMaxAngle);
	  mValue = mMaximum - (rotation-mMinAngle)/(mMaxAngle-mMinAngle)*(mMaximum-mMinimum);
	  var angle = rotation+Math.PI/2;
	  element.setPosition([0.3*Math.cos(angle),0.3*Math.sin(angle)]);
	  var controller = self.getController();    		
	  if (controller) {
		controller.immediatePropertyChanged("Value");
		controller.invokeImmediateAction("OnChange");
	  }	        

  }
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Mesh
 * @class Mesh 
 * @constructor  
 */
EJSS_DRAWING2D.Mesh = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy
  	
  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Data", element.setData);
		controller.registerProperty("DataType", element.setDataType);
		controller.registerProperty("Mesh", element.setMesh);

		controller.registerProperty("Points", element.setPoints);
		controller.registerProperty("Cells", element.setCells);
		controller.registerProperty("FieldAtPoints", element.setFieldAtPoints);
		controller.registerProperty("FieldAtCells", element.setFieldAtCells);
		controller.registerProperty("VectorLength", element.setVectorLength);
		
		controller.registerProperty("Boundary", element.setBoundary);
		controller.registerProperty("BoundaryLabels", element.setBoundaryLabels);
		controller.registerProperty("BoundaryColors", element.setBoundaryColors);
		controller.registerProperty("BoundaryWidth", element.setBoundaryLineWidth);
		controller.registerProperty("DrawBoundary", element.setDrawBoundary);
	}
};


/**
 * Creates a 2D Mesh
 * A Mesh is a collection of polygons, where each vertex has associated a scalar values value to it.
 * The mesh can be drawn using a color for each polygon, or a color code that uses the vertex values to find areas of equal value.
 * This second feature can be configure setting the levels to distinguish and the color of each of the levels (and below and above them)
 * The Mesh can also be given a set of lines which can be drawn to show the boundaries or special areas of the mesh, each with a different color.  
 * 
 * @method mesh
 */
EJSS_DRAWING2D.mesh = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  // Configuration variables
  var mDataType = -1;	    // An int that overrides the mData type
  var mPoints; 				// double[][]
  var mValues; 				// double[][]
  var mCellValues; 			// double[][][]
  var mMeshGeometry; 		// int[][]
  var mAutoscaleZ = true; 
  var mVectorLength = 0.03;

  var mBoundaryGeometry;		// int[][]
  var mBoundaryLabels;			// int[]
  var mBoundaryColors = null; 	// color[] an array with a color for each index. If null, the element color is used
  var mBoundaryStroke = 1;	  	// stroke
  var mBoundaryDraw = true;
  
  // Implementation variables
  var mColor = EJSS_DRAWING2D.colorCoded(16, EJSS_DRAWING2D.ColorMapper.SPECTRUM); 		// ColorCoded

  // -------------------------------------
  // Public methods for mesh geometry
  // -------------------------------------

  self.getClass = function() {
  	return "ElementMesh";
  }

  /**
   * Indicates what type to consider for the setData argument.
   * Allows using the same data for different displays.
   * @param type int or String // one of 0=MESH_2D, 1=MESH_3D, 2=SCALAR_2D_FIELD, 3=SCALAR_3D_FIELD, 4=VECTOR_2D_FIELD, 5=VECTOR_3D_FIELD
   */
  self.setDataType = function(type) {
    switch (type) {
      case "MESH_2D" : mDataType = 0; break;
      case "MESH_3D" : mDataType = 1; break;
      case "SCALAR_2D_FIELD" : mDataType = 2; break;
      case "SCALAR_3D_FIELD" : mDataType = 3; break;
      case "VECTOR_2D_FIELD" : mDataType = 4; break;
      case "VECTOR_3D_FIELD" : mDataType = 5; break;
      default : mDataType = type; 
     }
    self.setChanged(true);
  }
  
  self.getDataType = function() {
  	return mDataType;
  }

  /**
   * Provides the data based on the following structure
   * @method setData
   *     "data": {
   *		"type": int, // one of  0=MESH_2D, 1=MESH_3D, SCALAR_2D_FIELD, SCALAR_3D_FIELD, VECTOR_2D_FIELD, VECTOR_3D_FIELD
   *        "problem_mesh": mesh, // the mesh that defined the problem
   *        "solution_mesh": mesh, // the mesh that contains the solution
   *        "solution_values": double[][][], // the values of the field for the solution mesh
   *	  }
   * @param data 
   */
  self.setData = function(data) {
    if (!data) return false;
    if (mDataType<0 || mDataType>5) {
   		self.setDataType(data["type"]);
    }
    switch (mDataType) {
      case 0 : // 2D mesh
      case 1 : // 3D mesh
        self.setMesh(data["problem_mesh"]); 
        return true;
      case 2 : // scalar (real or complex) 2D solution
      case 3 : // scalar (real or complex) 3D solution
      case 4 : // vector (real or complex) 2D solution
      case 5 : // vector (real or complex) 3D solution
        self.setMesh(data["solution_mesh"]);
        self.setFieldAtCells(data["solution_values"]);
        return true;
     }
     return false; // no correct data
  }

  /**
   * Provides the mesh based on the following structure
   * @method setMesh
   *     "mesh": {
   *		"points":[[x0,y0],[x1,y1],...],
   *		"cells":[[p00,p01,p02],[p10,p11,p12],...],
   *		"boundary":[[p00,p01],[p10,p11],...], // end points for each segment of the boundary		
   *		"boundary_labels":int [l0, l1, l2,...] // one for each boundary segment
   *	} 
   * @param mesh 
   */
  self.setMesh = function(mesh) {
    self.setPoints(mesh["points"]);
    self.setCells(mesh["cells"]);
    self.setBoundary(mesh["boundary"]);
    self.setBoundaryLabels(mesh["boundary_labels"]);
    self.setChanged(true);
  }
  
//  self.getMesh = function() {
//  	return {"points": mPoints,  "cells": mCells, "values": mValues, "boundary": mBoundaryGeometry};
//  }

  /**
   * Provides the array of points that conform the mesh and its boundary
   * @method setPoints
   * @param meshs The double[nPoints][2] where nPoints is the number of points in the mesh
   */
  self.setPoints = function(points) {
    mPoints = points;
    self.setChanged(true);
  }
  
  self.getPoints = function() {
  	return mPoints;
  }

  /**
   * Provides the field value for each point in the mesh
   * @method setValues
   * @param values The double[nPoints]array with the value for each mesh vertex
   */
  self.setFieldAtPoints = function(values) {
    mValues = values;
    mCellValues = null;
    if(mAutoscaleZ && mValues != null) mColor.setAutoscaleArray2(mValues); 
//    console.log ("Field at points changed");
    self.setChanged(true);
  }
  
  self.getFieldAtPoints = function() {
  	return mValues;
  }

  /**
   * Provides the geometry of the mesh
   * @method setCells
   * @param cells the int[nCells][nPoints] array with the points in each mesh.
   * For example,if the first mesh is a triangle joining points 0,3,7, one gets: meshs[0] = { 0, 3, 7 }; 
   */
  self.setCells = function(cells) {
    mMeshGeometry = cells;
    self.setChanged(true);
  }

  self.getCells = function() {
  	return mMeshGeometry;
  }

  /**
   * Provides the field value for each point in the mesh
   * @method setValues
   * @param values The double[nPoints]array with the value for each mesh vertex
   */
  self.setFieldAtCells = function(values) {
    mCellValues = values;
    mValues = null;
    if(mAutoscaleZ && mCellValues != null) mColor.setAutoscaleArray3(mCellValues); 
    self.setChanged(true);
  }
  
    self.getFieldAtCells = function() {
  	return mCellValues;
  }
  
  /**
   * Provides the geometry of the mesh
   * @method setVectorLength
   * @param length the double length for vectors in the field 
   */
  self.setVectorLength = function(length) {
    if (mVectorLength !== length) {
      mVectorLength = length;
      self.setChanged(true);
    }
  }

  self.getVectorLength = function() {
  	return mVectorLength;
  }

  
  /**
   * Provides the data for the boundary.
   * @method setBoundary
   * @param meshs The int[nOfSegments][nPointsInSegment] array with the mesh information, where:
   * <ul>
   * <li>First index = nOfSegments : number of segments in the boundary</li>
   * <li>Second index = nPointsInSegment : the points in this segment</li>
   * </ul>
   */
  self.setBoundary = function(boundary) {
    mBoundaryGeometry = boundary;
    self.setChanged(true);
  }

  self.getBoundary = function() {
  	return mBoundaryGeometry;
  }

  /**
   * Provides the label for each boundary segment
   * @method setBoundaryLabels
   * @param values The int[nOfSegments] array with the label for each boundary segment
   */
  self.setBoundaryLabels = function(labels) {
    mBoundaryLabels = labels;
    self.setChanged(true);
  }

  self.getBoundaryLabels = function() {
  	return mBoundaryLabels;
  }

  /**
   * The color to use for each boundary index 
   * There must be a color for each index 
   * If not enough colors are given, or colors is null, the element draw color is used
   * @method setBoundaryColors
   * @param colors
   */
  self.setBoundaryColors = function(colors) {
    mBoundaryColors = colors;
    self.setChanged(true);
  }
  
  self.getBoundaryColors = function() {
    return mBoundaryColors;
  }
  
  self.setBoundaryLineWidth = function(width) {
    mBoundaryStroke = Math.max(1, width);
    self.setChanged(true);
  }

  self.getBoundaryLineWidth = function() {
    return mBoundaryStroke;
  }

  self.setBoundaryStroke = function(stroke) {
    mBoundaryStroke = stroke;
    self.setChanged(true);
  }

  self.getBoundaryStroke = function() {
    return mBoundaryStroke;
  }

  self.setDrawBoundary = function(draw) {
    mBoundaryDraw = draw;
    self.setChanged(true);
  }

  self.getDrawBoundary = function() {
    return mBoundaryDraw;
  }

  // -------------------------------------
  // Public methods for coloring the meshs
  // -------------------------------------

  /**
   * Returns the ColorCoded for customization
   * @method getDrawer
   * @return
   */
  self.getColorCoded = function() {
    return mColor;
  }
  
  /**
   * Sets the autoscale flag and the floor and ceiling values for the colors.
   *
   * If autoscaling is true, then the min and max values of z are span the colors.
   *
   * If autoscaling is false, then floor and ceiling values limit the colors.
   * Values below min map to the first color; values above max map to the last color.
   *
   * @method setAutoscaleZ
   * @param isAutoscale
   * @param floor
   * @param ceil
   */
  self.setAutoscaleZ = function (isAutoscale, floor, ceil) {
    mAutoscaleZ = isAutoscale;
    if (mAutoscaleZ) {
    	if(mValues != null)	mColor.setAutoscaleArray2(mValues); 
    	else if(mCellValues != null) mColor.setAutoscaleArray3(mCellValues); 
    } else mColor.setScale(floor, ceil);
    self.setChanged(true);
  }
  
  /**
   * Gets the autoscale flag for z.
   *
   * @method isAutoscaleZ
   * @return boolean
   */
  self.isAutoscaleZ = function() {
    return mAutoscaleZ;
  }

  /**
   * Returns bounds for an element
   * @override
   * @method getBounds
   * @return Object{left,rigth,top,bottom}
   */
  self.getBounds = function(element) {
  	var xmin=0, xmax=0, ymin=0, ymax=0;
  	var points = self.getPoints();
  	if (points && points.length>0) {
      var len = points.length;
      xmax = xmin = points[0][0];
	  ymax = ymin = points[0][1];    	
	  for(var j=1; j<len; j++) {
			var x = points[j][0], y = points[j][1];
			if(x>xmax) xmax=x; if(y>ymax) ymax=y; if(x<xmin) xmin=x; if(y<ymin) ymin=y;
	  }
	}    
    var x = self.getX(), y = self.getY();
    var sx = self.getSizeX(), sy = self.getSizeY();
  	var mx = sx/2, my = sy/2;  	
  	var d = self.getRelativePositionOffset(sx,sy);
	var result =  {
		left: ((x+d[0])-mx)+xmin*sx,
		right: ((x+d[0])-mx)+xmax*sx,
		top: ((y+d[1])-my)+ymax*sy,
		bottom: ((y+d[1])-my)+ymin*sy
	}
	return result;
  };  

  self.registerProperties = function(controller) {
	EJSS_DRAWING2D.Mesh.registerProperties(self, controller);
  };
	
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1]);
  self.setRelativePosition("SOUTH_WEST");

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Shape
 * @class Shape 
 * @constructor  
 */
EJSS_DRAWING2D.Meter = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

      controller.registerProperty("Value", element.setValue, element.getValue);
      controller.registerProperty("Minimum", element.setMinimum, element.getMinimum);
      controller.registerProperty("Maximum", element.setMaximum, element.getMaximum);

      controller.registerProperty("AngleForMinimum", element.setAngleForMinimum, element.getAngleForMinimum);
      controller.registerProperty("AngleForMaximum", element.setAngleForMaximum, element.getAngleForMaximum);

      controller.registerProperty("NumberOfMarks",element.setNumberOfMarks,element.getNumberOfMarks);
      controller.registerProperty("BigMark",element.setBigMark,element.getBigMark);
      controller.registerProperty("MediumMark",element.setMediumMark,element.getMediumMark);
      controller.registerProperty("Digits",element.setDigits,element.getDigits);

      controller.registerProperty("Radius", element.setRadius);
      controller.registerProperty("CircleColor",element.setCircleColor);
      controller.registerProperty("CircleWidth",element.setCircleWidth);
      controller.registerProperty("FillColor",element.setFillColor);
      
      controller.registerProperty("Foreground",element.setForeground);
      controller.registerProperty("LineWidth",element.setLineWidth);

      controller.registerProperty("ArrowColor",element.setArrowColor);
      controller.registerProperty("ArrowWidth",element.setArrowWidth);

      controller.registerProperty("DrawLines",element.setDrawLines);
      controller.registerProperty("DrawText",element.setDrawText);

      controller.registerProperty("Brand",element.setBrand);
      controller.registerProperty("Units",element.setUnits);
      
      controller.registerProperty("MarksFont",element.setMarksFont);
      controller.registerProperty("BrandFont",element.setBrandFont);
      controller.registerProperty("UnitsFont",element.setUnitsFont);
    }

};

/**
 * Creates a 2D circular meter
 * @method shape
 */
EJSS_DRAWING2D.meter = function (mName) {
  var self = EJSS_DRAWING2D.group(mName);

  // Configuration variables
  var mValue = 0.0;
  var mMinimum =   0;
  var mMaximum = 100;

  var mNumberOfMarks = 21;
  var mBigMark = 4;
  var mMediumMark = 2;
  var mDigits = 0;

  var mAngleForMinimum = Math.PI/2 + 1.5*Math.PI/2;
  var mAngleForMaximum = Math.PI/2 - 1.5*Math.PI/2;

  // Implementation variables
  var mArrow, mCircle, mSegments, mTexts, mBrand, mUnits;
  var mColor = "Black", mDangerColor = "Red";
  var mForegroundCreated = false;

  var mElements = [];
  
  self.getElement = function(keyword) {
	  return mElements[keyword];
  };
  
  // ----------------------------------------------------
  // public functions
  // ----------------------------------------------------
  
  /**
   * Sets the value of the element. 
   * @method setValue
   * @param value
   */
  self.setValue = function(value) {
    if (mValue!=value) {
    	mValue = value;
    	self.adjustPosition();
    }
  }
  
  /**
   * @method getValue
   * @return current value
   */
  self.getValue = function() { 
    return mValue; 
  };    

  /**
   * Sets the minimum value of the element. 
   * @method setMinimum
   * @param value
   */
  self.setMinimum = function(value) {
    if (mMinimum!=value) {
    	mMinimum = value;
        self.setChanged(true);
    }
  }
  
  /**
   * @method getMinimum
   * @return current minimum
   */
  self.getMinimum = function() { 
    return mMinimum; 
  };    

  /**
   * Sets the minimum value of the element. 
   * @method setMaximum
   * @param value
   */
  self.setMaximum = function(value) {
	if (mMaximum!=value) {
		mMaximum = value;
        self.setChanged(true);
	}
  }
  
  /**
   * @method getMaximum
   * @return current maximum
   */
  self.getMaximum = function() { 
    return mMaximum; 
  };    

  /**
   * Sets the angle at which to display the minimum value 
   * @method setAngleForMinimum
   * @param value
   */
  self.setAngleForMinimum = function(value) {
    if (mAngleForMinimum!=value) {
    	mAngleForMinimum = value;
        self.setChanged(true);
    }
  }
  
  /**
   * @method getAngleForMinimum
   * @return current angle at which to display the minimum value
   */
  self.getAngleForMinimum = function() { 
    return mAngleForMinimum; 
  };    

  /**
   * Sets the angle at which to display the maximum value 
   * @method setAngleForMaximum
   * @param value
   */
  self.setAngleForMaximum = function(value) {
	if (mAngleForMaximum!=value) {
		mAngleForMaximum = value;
        self.setChanged(true);
	}
  }
  
  /**
   * @method getAngleForMaximum
   * @return current angle at which to display the maximum value 
   */
  self.getAngleForMaximum = function() { 
    return mAngleForMaximum; 
  };    

  /***
   * Sets the number of marks
   * @method setNumberOfMarks(value)
   * @visibility public
   * @param value int
   */ 
  self.setNumberOfMarks = function(value) {
	  if (value!=mNumberOfMarks) {
		  mNumberOfMarks = value;
        self.setChanged(true);
	  }
  };

  /***
   * Gets the number of marks
   * @method getNumberOfMarks
   * @visibility public
   * @return int
   */
  self.getNumberOfMarks = function() {
  	return mNumberOfMarks;
  };

  /***
   * Sets the interval for big marks
   * @method setBigMark(value)
   * @visibility public
   * @param value int 
   */ 
  self.setBigMark = function(value) {
	  if (value!=mBigMark) {
        mBigMark = value;
        self.setChanged(true);
	  }
  };

  /***
   * Gets the interval for big marks
   * @method getBigMark
   * @visibility public
   * @return int
   */
  self.getBigMark = function() {
  	return mBigMark;
  };

  /***
   * Sets the interval for medium marks
   * @method setMediumMark(value)
   * @visibility public
   * @param value int
   */ 
  self.setMediumMark = function(value) {
	  if (value!=mMediumMark) {
		  mMediumMark = value;
        self.setChanged(true);
	  }
  };
 
  /***
   * Gets the interval for medium marks
   * @method getMediumMark
   * @visibility public
   * @return value int
   */ 
  self.getMediumMark = function() {
  	return mMediumMark;
  };


  /***
   * Sets the number of digits for the marks
   * @method setDigits(value)
   * @visibility public
   * @param value int 
   */ 
  self.setDigits = function(value) {
	  if (value!=mDigits) {
		  mDigits = value;
        self.setChanged(true);
	  }
  };

  /***
   * Gets the number of digits for the marks
   * @method getPrecision
   * @visibility public
   * @return int
   */
  self.getDigits = function() {
  	return mDigits;
  };
  
  // ----------------------------------------------------
  // Properties overwritten
  // ----------------------------------------------------

  /**
   * Sets the radius of the element. 
   * @method setRadius
   * @param value
   */
  self.setRadius = function(value) {
	  self.setSizeX(2*value);
	  self.setSizeY(2*value);
	  if (mCircle) mCircle.setChanged(true);
	  if (mArrow) mArrow.setChanged(true);
	  mSegments.setToEach(function(element) { element.setChanged(true); });
	  mTexts.setToEach(function(element) { element.setChanged(true); });
	  if (mBrand) mBrand.setChanged(true);
	  if (mUnits) mUnits.setChanged(true);
  };

  self.setCircleColor = function(value) { mCircle.getStyle().setLineColor(value); };
  
  self.setCircleWidth = function(value) { mCircle.getStyle().setLineWidth(value); };

  self.setFillColor = function(value) { mCircle.getStyle().setFillColor(value); };

  self.setArrowColor = function(value) {
	  if (value=="Red") mDangerColor = "Yellow";
	  else mDangerColor = "Red";
	  mColor = value;
	  mArrow.getStyle().setLineColor(value);
	  mArrow.getStyle().setFillColor(value);
  };

  self.setArrowWidth = function(value) { mArrow.getStyle().setLineWidth(value); }

  self.setForeground = function(value) { 
	  mSegments.setToEach(function(element) { element.getStyle().setLineColor(value); });
	  mTexts.setToEach(function(element) { element.getFont().setFillColor(value); });
	  mBrand.getFont().setFillColor(value);
	  mUnits.getFont().setFillColor(value);
}
  
  self.setLineWidth = function(value) { 
	  mSegments.setToEach(function(element) { element.getStyle().setLineWidth(value); });
  }

  self.setDrawLines = function(value) { 
	  mSegments.setToEach(function(element,value) { element.setVisible(value); },value);
  }
  
  self.setDrawText = function(value) { 
	  mTexts.setToEach(function(element,value) { element.setVisible(value); },value);
  }

  self.setFont = function(value) { 
	  mTexts.setToEach(function(element) { element.getFont().setFont(value); });
	  mBrand.getFont().setFont(value);
	  mUnits.getFont().setFont(value);
  }

  self.setFont = function(value) { 
	  mTexts.setToEach(function(element) { element.getFont().setFont(value); });
	  mBrand.getFont().setFont(value);
	  mUnits.getFont().setFont(value);
  }
  self.setMarksFont = function(value) { 
	  mTexts.setToEach(function(element) { element.getFont().setFont(value); });
  }
  self.setBrandFont = function(value) { mBrand.getFont().setFont(value); }
  self.setUnitsFont = function(value) { mUnits.getFont().setFont(value); }

  self.setBrand = function(value) { mBrand.setText(value); }
  self.setUnits= function(value) { mUnits.setText(value); }

  // ----------------------------------------------------
  // Properties and copies
  // ----------------------------------------------------

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Meter.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // private or protected functions
  // ----------------------------------------------------

  self.superSetParent = self.setParent;
  
  self.setParent = function(parent, sibling) {
	self.superSetParent(parent,sibling);
	if (!mForegroundCreated) {
	  self.createBasics();
	  self.createForeground();
	  mForegroundCreated = true;
    }
	self.addParticularChildren();
	self.adjustForeground();
  }

  self.dataCollected = function() {
	  if (self.isChanged()) {
		  self.adjustForeground();
		  self.setChanged(false);
	  }
  }; 
  
  self.addParticularChildren = function() {
		if (mCircle) mCircle.setParent(self);
		if (mSegments) mSegments.setToEach(function(element) { element.setParent(self); });
		if (mTexts) mTexts.setToEach(function(element) { element.setParent(self); });
		if (mBrand) mBrand.setParent(self);
		if (mUnits) mUnits.setParent(self);
		if (mArrow) mArrow.setParent(self);
  };

  self.createBasics = function() {
    mCircle = EJSS_DRAWING2D.shape(mName+".circle");
    mCircle.setSize([1,1]);
    mCircle.setShapeType("ELLIPSE");
    mCircle.getStyle().setLineWidth(3);
    mCircle.getStyle().setLineColor("Grey");
    mCircle.getStyle().setFillColor("White");
    mElements['circle'] = mCircle;

    mArrow = EJSS_DRAWING2D.arrow(mName+".arrow");
    mArrow.setSize([0.45,0]);
    mArrow.getStyle().setLineWidth(2);
    mArrow.setMarkStart("WEDGE");
    mArrow.setMarkEnd("POINTED");
    mElements['arrow'] = mArrow;
  };
  
  self.createForeground = function() {
    mSegments = EJSS_DRAWING2D.segmentSet(mName+".segments");
    mSegments.setToEach(function(element) { element.setRelativePosition("SOUTH_WEST"); });
    mElements['segments'] = mSegments;

    mTexts = EJSS_DRAWING2D.textSet(mName+".texts");
    mTexts.setToEach(function(element) { element.getFont().setFont("normal bold 12px \"Courier New\", Courier, monospace"); });
    mElements['texts'] = mTexts;

    mBrand = EJSS_DRAWING2D.text(mName+".brand");
    mBrand.setPosition([0,0.20]);
    mBrand.setText("Meter");
    mBrand.getFont().setFont("normal bold 12px \"Courier New\", Courier, monospace");
    mElements['brand'] = mBrand;

    mUnits = EJSS_DRAWING2D.text(mName+".Units");
    mUnits.setPosition([0,-0.25]);
    mUnits.setText("%");
    mUnits.getFont().setFont("normal bold 12px \"Courier New\", Courier, monospace");
    mElements['units'] = mUnits;
}
  
  self.interpolateAngle= function(value) {
	 return mAngleForMinimum + (value-mMinimum)/(mMaximum-mMinimum)*(mAngleForMaximum-mAngleForMinimum);
  };

  self.valueFromAngle= function(angle) {
	  angle = Math.max(Math.min(angle,mAngleForMinimum),mAngleForMaximum);
	  mValue = mMaximum - (angle-mAngleForMaximum)/(mAngleForMinimum-mAngleForMaximum)*(mMaximum-mMinimum);
      return angle;
  };

  self.adjustPosition = function() {
	  var actualValue = mValue;
	  if (mValue<mMinimum) {
		  actualValue = mMinimum;
		  mArrow.getStyle().setLineColor(mDangerColor);
		  mArrow.getStyle().setFillColor(mDangerColor);
	  }
	  else if (mValue>mMaximum) {
		  actualValue = mMaximum;
		  mArrow.getStyle().setLineColor(mDangerColor);
		  mArrow.getStyle().setFillColor(mDangerColor);
	  }
	  else {
		  actualValue = mValue;
		  mArrow.getStyle().setLineColor(mColor);
		  mArrow.getStyle().setFillColor(mColor);
	  }
	  var angle = mAngleForMinimum + (actualValue-mMinimum)/(mMaximum-mMinimum)*(mAngleForMaximum-mAngleForMinimum);
	  mArrow.setTransformation(angle);
  }

  self.foregroundIsInside = function() { return true; };
  
  self.adjustForeground = function() {
	  var MARK = 1.0/12;
	  
	  mSegments.setNumberOfElements(mNumberOfMarks);
	  var dAngle = (mAngleForMaximum-mAngleForMinimum)/(mNumberOfMarks-1);
	  var angle = mAngleForMinimum;
	  var mediumMarksCounter = 0;
	  var bigMarksCounter = 0;
	  var direction =  self.foregroundIsInside() ? -MARK : MARK;
	  
	  for (var i=0; i<mNumberOfMarks; i++) {
	    var segment = mSegments.getElement(i);
	    var xPos = Math.cos(angle)/2;
	    var yPos = Math.sin(angle)/2;
	    segment.setPosition([xPos,yPos]);
	    if (i%mBigMark==0) {
	      bigMarksCounter++;
	      segment.setSize([2*direction*xPos,2*direction*yPos]);
	    }
	    else if (i%mMediumMark==0) {
	      mediumMarksCounter++;
	      segment.setSize([1.5*direction*xPos,1.5*direction*yPos]);
	    }
	    else {
	      segment.setSize([direction*xPos,direction*yPos]);
	    }
	    angle += dAngle;
	  }
	  
	  mTexts.setNumberOfElements(bigMarksCounter); //+mediumMarksCounter);
	  var text_counter=0;
      var fontSize =  mTexts.getElement(0).getFont().getFontSize();
      direction =  self.foregroundIsInside() ? 1.7 : 2.5;
      if (fontSize>30) direction *= 1.5;
      else if (fontSize>20) direction *= 1.3;
      else if (fontSize>15) direction *= 1.1;
	  for (i=0; i<mNumberOfMarks; i++) {
	    var segment = mSegments.getElement(i);
	    if (i%mBigMark==0) { // || i%mediumMark==0) {
	      var text = mTexts.getElement(text_counter);
	      var pos = segment.getPosition();
	      var size = segment.getSize();
	      text.setPosition([pos[0]+direction*size[0],pos[1]+direction*size[1]]);
	      var mark_value = ((mNumberOfMarks-1-i)*mMinimum + i*mMaximum)/(mNumberOfMarks-1);
	      text.setText(""+mark_value.toFixed(mDigits));
	      text_counter++;
	    }
	  }
	  self.adjustPosition();  
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  return self;
};


/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Shape
 * @class Shape 
 * @constructor  
 */
EJSS_DRAWING2D.Pipe = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

      controller.registerProperty("Points",element.setPoints,element.getPoints);
      controller.registerProperty("PipeWidth",element.setPipeWidth,element.getPipeWidth);
    },

    /**
     * static copyTo method, to be used by sets
     */
    copyTo : function(source, dest) {
      EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy

      dest.setPoints(source.getPoints());
      dest.setPipeWidth(source.getPipeWidth());
    }

};

/**
 * Creates a 2D shape
 * @method shape
 */
EJSS_DRAWING2D.pipe = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  var mPoints = [[0.1,0.2],[0.1,0.3]];
  var mPipeWidth = 5;

  self.getClass = function() {
  	return "ElementPipe";
  };

  // ----------------------------------------------------
  // public functions
  // ----------------------------------------------------

  self.setPoints = function(points) {
      mPoints = points;
      self.setChanged(true);
  };

  self.getPoints = function() {
  	return mPoints;
  };

  self.setPipeWidth = function(width) { 
    mPipeWidth = width; 
    self.setChanged(true);
  };
    
  self.getPipeWidth = function() { 
    return mPipeWidth; 
  };
  
  // ----------------------------------------------------
  // Properties and copies
  // ----------------------------------------------------

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Pipe.registerProperties(self,controller);
  };

  /**
   * Copies itself to another element
   * Extended copyTo method. To be used by Sets
   * @method copyTo
   */
  self.copyTo = function(element) {
    EJSS_DRAWING2D.Pipe.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1]);
  self.setRelativePosition("SOUTH_WEST");
  self.getStyle().setLineWidth(1);
  self.getStyle().setFillColor("Blue");
  return self;
};


/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García 
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for 2D drawing.
 * @module 2Ddrawing
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A PlottingPanel is a 2D drawing panel with added decoration. The decoration includes axes, gutters and titles.
 * @class EJSS_DRAWING2D.PlottingPanel
 * @parent EJSS_DRAWING2D.DrawingPanel
 * @constructor
 */

EJSS_DRAWING2D.PlottingPanel = {

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.DrawingPanel.registerProperties(element, controller);
		// super class

		// title (text and font)
     /*** 
	  * The title at the top center of the panel
	  * @property Title
	  * @type String
	  * @default "Plot"
	  */ 
		controller.registerProperty("Title", element.getTitle().setText);
		controller.registerProperty("TitleFont", element.getTitle().getFont().setFont);
		controller.registerProperty("TitleColor", element.getTitle().getFont().setFillColor);
		controller.registerProperty("TitleMargin", element.getTitle().setMarginY);

		controller.registerProperty("TitleX", element.getTitleX().setText);
		controller.registerProperty("TitleXFont", element.getTitleX().getFont().setFont);
		controller.registerProperty("TitleXColor", element.getTitleX().getFont().setFillColor);
		controller.registerProperty("TitleXMargin", element.getTitleX().setMarginY);

		controller.registerProperty("TitleY", element.getTitleY().setText);
		controller.registerProperty("TitleYFont", element.getTitleY().getFont().setFont);
		controller.registerProperty("TitleYColor", element.getTitleY().getFont().setFillColor);
		controller.registerProperty("TitleYMargin", element.getTitleY().setMarginX);
 
		// Grid
		controller.registerProperty("GridXShow", element.getGrid().setShowX, element.getGrid().getShowX);
		controller.registerProperty("GridXTicks", element.getGrid().setTicksX, element.getGrid().getTicksX);
		controller.registerProperty("GridXStep", element.getGrid().setStepX, element.getGrid().getStepX);
		controller.registerProperty("GridXAutoStepMin", element.getGrid().setAutoStepXMin, element.getGrid().getAutoStepXMin);
		controller.registerProperty("GridXAutoTicksRange", element.getGrid().setAutoTicksXRange, element.getGrid().getAutoTicksXRange);
		controller.registerProperty("GridXScale", element.getGrid().setScaleX, element.getGrid().getScaleX);
		controller.registerProperty("GridXScalePrecision", element.getGrid().setScalePrecisionX, element.getGrid().getScalePrecisionX);
		controller.registerProperty("GridXFixedTick", element.getGrid().setFixedTickX, element.getGrid().getFixedTickX);				
		controller.registerProperty("GridXAutoTicks", element.getGrid().setAutoTicksX, element.getGrid().getAutoTicksX);
		controller.registerProperty("GridXLineColor", element.getGrid().setLineColorX);
		controller.registerProperty("GridXLineWidth", element.getGrid().setLineWidthX);
		controller.registerProperty("GridXShapeRendering", element.getGrid().setShapeRenderingX);

		controller.registerProperty("GridYShow", element.getGrid().setShowY, element.getGrid().getShowY);
		controller.registerProperty("GridYTicks", element.getGrid().setTicksY, element.getGrid().getTicksY);
		controller.registerProperty("GridYStep", element.getGrid().setStepY, element.getGrid().getStepY);
		controller.registerProperty("GridYAutoStepMin", element.getGrid().setAutoStepYMin, element.getGrid().getAutoStepYMin);
		controller.registerProperty("GridYAutoTicksRange", element.getGrid().setAutoTicksYRange, element.getGrid().getAutoTicksYRange);
		controller.registerProperty("GridYScale", element.getGrid().setScaleY, element.getGrid().getScaleY);
		controller.registerProperty("GridYScalePrecision", element.getGrid().setScalePrecisionY, element.getGrid().getScalePrecisionY);		
		controller.registerProperty("GridYFixedTick", element.getGrid().setFixedTickY, element.getGrid().getFixedTickY);
		controller.registerProperty("GridYAutoTicks", element.getGrid().setAutoTicksY, element.getGrid().getAutoTicksY);
		controller.registerProperty("GridYLineColor", element.getGrid().setLineColorY);
		controller.registerProperty("GridYLineWidth", element.getGrid().setLineWidthY);
		controller.registerProperty("GridYShapeRendering", element.getGrid().setShapeRenderingY);

		// axis
 		controller.registerProperty("AxisXShow", element.getAxisX().setShow);
		controller.registerProperty("AxisXLineColor", element.getAxisX().getStyle().setLineColor);
		controller.registerProperty("AxisXLineWidth", element.getAxisX().getStyle().setLineWidth);
		controller.registerProperty("AxisXShapeRendering", element.getAxisX().getStyle().setShapeRendering);		
		controller.registerProperty("AxisXFixedTick", element.getAxisX().setFixedTick, element.getAxisX().getFixedTick);
		controller.registerProperty("AxisXAutoTicks", element.getAxisX().setAutoTicks, element.getAxisX().getAutoTicks);
		controller.registerProperty("AxisXTicks", element.getAxisX().setTicks, element.getAxisX().getTicks);
		controller.registerProperty("AxisXStep", element.getAxisX().setStep, element.getAxisX().getStep);
		controller.registerProperty("AxisXTickStep", element.getAxisX().setTickStep, element.getAxisX().getTickStep);
		controller.registerProperty("AxisXAutoStepMin", element.getAxisX().setAutoStepMin, element.getAxisX().getAutoStepMin);
		controller.registerProperty("AxisXAutoTicksRange", element.getAxisX().setAutoTicksRange, element.getAxisX().getAutoTicksRange);
		controller.registerProperty("AxisXScale", element.getAxisX().setScale, element.getAxisX().getScale);
		controller.registerProperty("AxisXScalePrecision", element.getAxisX().setScalePrecision, element.getAxisX().getScalePrecision);	    
	    controller.registerProperty("AxisXFont", element.getAxisX().getFont().setFont);
		controller.registerProperty("AxisXFontColor", element.getAxisX().getFont().setFillColor);

 		controller.registerProperty("AxisYShow", element.getAxisY().setShow);
		controller.registerProperty("AxisYLineColor", element.getAxisY().getStyle().setLineColor);
		controller.registerProperty("AxisYLineWidth", element.getAxisY().getStyle().setLineWidth);
		controller.registerProperty("AxisYShapeRendering", element.getAxisY().getStyle().setShapeRendering);
		controller.registerProperty("AxisYFixedTick", element.getAxisY().setFixedTick, element.getAxisY().getFixedTick);
		controller.registerProperty("AxisYAutoTicks", element.getAxisY().setAutoTicks, element.getAxisY().getAutoTicks);
		controller.registerProperty("AxisYTicks", element.getAxisY().setTicks, element.getAxisY().getTicks);
		controller.registerProperty("AxisYStep", element.getAxisY().setStep, element.getAxisY().getStep);
		controller.registerProperty("AxisYTickStep", element.getAxisY().setTickStep, element.getAxisY().getTickStep);
		controller.registerProperty("AxisYAutoStepMin", element.getAxisY().setAutoStepMin, element.getAxisY().getAutoStepMin);
		controller.registerProperty("AxisYAutoTicksRange", element.getAxisY().setAutoTicksRange, element.getAxisY().getAutoTicksRange);
		controller.registerProperty("AxisYScale", element.getAxisY().setScale, element.getAxisY().getScale);
		controller.registerProperty("AxisYScalePrecision", element.getAxisY().setScalePrecision, element.getAxisY().getScalePrecision);
		controller.registerProperty("AxisYFont", element.getAxisY().getFont().setFont);
		controller.registerProperty("AxisYFontColor", element.getAxisY().getFont().setFillColor);

		// Axis and Grid
		controller.registerProperty("XFixedTick", function(v) {
			element.getAxisX().setFixedTick(v);
			element.getGrid().setFixedTickX(v);
		});
		controller.registerProperty("YFixedTick", function(v) {
			element.getAxisY().setFixedTick(v);
			element.getGrid().setFixedTickY(v);
		});

		//controller.registerProperty("GridXTicks", element.getGrid().setTicksX, element.getGrid().getTicksX);
		//controller.registerProperty("AxisXTicks", element.getAxisX().setTicks, element.getAxisX().getTicks);
		controller.registerProperty("XTicks", function(v) {
			element.getAxisX().setTicks(v);
			element.getGrid().setTicksX(v);
		});
		controller.registerProperty("YTicks", function(v) {
			element.getAxisY().setTicks(v);
			element.getGrid().setTicksY(v);
		});

		//controller.registerProperty("GridXStep", element.getGrid().setStepX, element.getGrid().getStepX);
		//controller.registerProperty("AxisXStep", element.getAxisX().setStep, element.getAxisX().getStep);
		controller.registerProperty("XStep", function(v) {
			element.getAxisX().setStep(v);
			element.getGrid().setStepX(v);
		});
		controller.registerProperty("YStep", function(v) {
			element.getAxisY().setStep(v);
			element.getGrid().setStepY(v);
		});

		controller.registerProperty("XTickStep", function(v) {
			element.getAxisX().setTickStep(v);
			element.getGrid().setTickStepX(v);
		});
		controller.registerProperty("YTickStep", function(v) {
			element.getAxisY().setTickStep(v);
			element.getGrid().setTickStepY(v);
		});

		//controller.registerProperty("GridXAutoStepMin", element.getGrid().setAutoStepXMin, element.getGrid().getAutoStepXMin);
		//controller.registerProperty("AxisXAutoStepMin", element.getAxisX().setAutoStepMin, element.getAxisX().getAutoStepMin);
		controller.registerProperty("XAutoStepMin", function(v) {
			element.getAxisX().setAutoStepMin(v);
			element.getGrid().setAutoStepXMin(v);
		});
		controller.registerProperty("YAutoStepMin", function(v) {
			element.getAxisY().setAutoStepMin(v);
			element.getGrid().setAutoStepYMin(v);
		});

		//controller.registerProperty("GridXAutoTicksRange", element.getGrid().setAutoTicksXRange, element.getGrid().getAutoTicksXRange);
		//controller.registerProperty("AxisXAutoTicksRange", element.getAxisX().setAutoTicksRange, element.getAxisX().getAutoTicksRange);
		controller.registerProperty("XAutoTicksRange", function(v) {
			element.getAxisX().setAutoTicksRange(v);
			element.getGrid().setAutoTicksXRange(v);
		});
		controller.registerProperty("YAutoTicksRange", function(v) {
			element.getAxisY().setAutoTicksRange(v);
			element.getGrid().setAutoTicksyRange(v);
		});

		//controller.registerProperty("GridXScale", element.getGrid().setScaleX, element.getGrid().getScaleX);
		//controller.registerProperty("AxisXScale", element.getAxisX().setScale, element.getAxisX().getScale);
		controller.registerProperty("XScale", function(v) {
			element.getAxisX().setScale(v);
			element.getGrid().setScaleX(v);
		});
		controller.registerProperty("YScale", function(v) {
			element.getAxisY().setScale(v);
			element.getGrid().setScaleY(v);
		});

		//controller.registerProperty("GridXScalePrecision", element.getGrid().setScalePrecisionX, element.getGrid().getScalePrecisionX);
		//controller.registerProperty("AxisXScalePrecision", element.getAxisX().setScalePrecision, element.getAxisX().getScalePrecision);
		controller.registerProperty("XScalePrecision", function(v) {
			element.getAxisX().setScalePrecision(v);
			element.getGrid().setScalePrecisionX(v);
		});
		controller.registerProperty("YScalePrecision", function(v) {
			element.getAxisY().setScalePrecision(v);
			element.getGrid().setScalePrecisionY(v);
		});

		//controller.registerProperty("GridXAutoTicks", element.getGrid().setAutoTicksX, element.getGrid().getAutoTicksX);
		//controller.registerProperty("AxisXAutoTicks", element.getAxisX().setAutoTicks, element.getAxisX().getAutoTicks);
		controller.registerProperty("XAutoTicks", function(v) {
			element.getAxisX().setAutoTicks(v);
			element.getGrid().setAutoTicksX(v);
		});
		controller.registerProperty("YAutoTicks", function(v) {
			element.getAxisY().setAutoTicks(v);
			element.getGrid().setAutoTicksY(v);
		});
		
	}
};

EJSS_DRAWING2D.plottingPanel = function(mName,mGraphicsMode) {
	var self = EJSS_DRAWING2D.drawingPanel(mName,mGraphicsMode);
	
	/**
	 * @method getGrid
	 * @return grid element
	 */
	self.getGrid = function() {
		return mGrid;
	};
	
	/***
	 * @method getAxisX()
	 * @return the axis element for the X dimension
     * @visibility public
	 * @see EJSS_DRAWING2D.Axis
	 */
	self.getAxisX = function() {
		return mAxisX;
	};
	
	/***
	 * @method getAxisY()
	 * @return the axis element for the Y dimension
     * @visibility public
	 * @see EJSS_DRAWING2D.Axis
	 */
	self.getAxisY = function() {
		return mAxisY;
	};
	
	/**
	 * @method getTitleX
	 * @return axis X title element
	 */
	self.getTitleX = function() {
		return mTitleX;
	};
	
	/**
	 * @method getTitleY
	 * @return axis Y title element
	 */
	self.getTitleY = function() {
		return mTitleY;
	};
	
	/**
	 * @method getTitle
	 * @return plot title element
	 */
	self.getTitle = function() {
		return mTitle;
	};
	
	/**
	 * @method getFixedTickX
	 * @return x fixed tick
	 */
	self.getFixedTickX = function() {
		return getGrid().getFixedTickX();
	};

	/**
	 * @method getFixedTickY
	 * @return y fixed tick
	 */
	self.getFixedTickY = function() {
		return getGrid().getFixedTickY();
	};

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.PlottingPanel.registerProperties(self, controller);
	};
	
	var mBottomGroup = EJSS_DRAWING2D.group(mName + ".bottomgroup");
	mBottomGroup.setRelativePosition("SOUTH_WEST");
	mBottomGroup.panelChangeListener = function(event) {
		if (event == "bounds") {
			var xmin = self.getRealWorldXMin();
			var xmax = self.getRealWorldXMax();
			var ymin = self.getRealWorldYMin();
			var ymax = self.getRealWorldYMax();
			var sx = Math.abs(xmax - xmin); 
			var sy = Math.abs(ymax - ymin);
			mBottomGroup.setSize([sx, sy]);
			mBottomGroup.setPosition([xmin, ymin]);
			return true;
		}
	};
	mBottomGroup.panelChangeListener("bounds");

	var mGrid = EJSS_DRAWING2D.grid(mName + ".grid");
	mGrid.setGroup(mBottomGroup);
	mGrid.setRelativePosition("SOUTH_WEST");
	mGrid.setLineColorX("lightgray");
	mGrid.setShapeRenderingX("RENDER_CRISPEDGES");
	mGrid.setLineColorY("lightgray");
	mGrid.setShapeRenderingY("RENDER_CRISPEDGES");
	mGrid.setSize([1,1]);
	mGrid.setPosition([0, 0]);
	mGrid.panelChangeListener = function(event) {
		if (event == "bounds") {
			var xmin = self.getRealWorldXMin();
			var xmax = self.getRealWorldXMax();
			mGrid.setScaleX([xmin, xmax]);
			mGrid.setTicksXMode(self.getTypeScaleX());
			var ymin = self.getRealWorldYMin();
			var ymax = self.getRealWorldYMax();
			mGrid.setScaleY([ymin, ymax]);
			mGrid.setTicksYMode(self.getTypeScaleY());		
			
			// mGrid.setInvertedScaleY(self.getInvertedScaleY());
			return true;
		}
	};
	var mTopGroup = EJSS_DRAWING2D.group(mName + ".topgroup");
	mTopGroup.setRelativePosition("SOUTH_WEST");
	mTopGroup.panelChangeListener = function(event) {
		if (event == "bounds") {
			var xmin = self.getRealWorldXMin();
			var xmax = self.getRealWorldXMax();
			var ymin = self.getRealWorldYMin();
			var ymax = self.getRealWorldYMax();
			var sx = Math.abs(xmax - xmin); 
			var sy = Math.abs(ymax - ymin);
			mTopGroup.setSize([sx, sy]);
			mTopGroup.setPosition([xmin, ymin]);
			return true;
		}
	};
	mTopGroup.panelChangeListener("bounds");

	var mAxisX = EJSS_DRAWING2D.axis(mName + ".axisX");
	mAxisX.setGroup(mTopGroup);
	mAxisX.setRelativePosition("WEST");
	mAxisX.getStyle().setLineColor("black");
	mAxisX.getStyle().setShapeRendering("RENDER_CRISPEDGES");
	mAxisX.getFont().setFontSize(10);
	mAxisX.setSize([1, 1]);
	mAxisX.setPosition([0, 0]);
	mAxisX.setOrient("AXIS_HORIZONTAL");
	mAxisX.panelChangeListener = function(event) {
		if (event == "bounds") {
			var xmin = self.getRealWorldXMin();
			var xmax = self.getRealWorldXMax();
			mAxisX.setScale([xmin, xmax]);
			mAxisX.setTicksMode(self.getTypeScaleX());
			
			if(self.getInvertedScaleY()) {
				mAxisX.setTextPosition("TICKS_DOWN");
				mTitleX.setMarginY(-30);
				mTitle.setMarginY(20);
			} else {
				mAxisX.setTextPosition("TICKS_UP");
				mTitleX.setMarginY(30);
				mTitle.setMarginY(-20);
			}
			return true;
		}
	};
	mAxisX.setTicksMode(self.getTypeScaleX());
	
	
	var mAxisY = EJSS_DRAWING2D.axis(mName + ".axisY");
	mAxisY.setGroup(mTopGroup);
	mAxisY.setRelativePosition("SOUTH");
	mAxisY.getStyle().setLineColor("black");
	mAxisY.getStyle().setShapeRendering("RENDER_CRISPEDGES");
	mAxisY.getFont().setFontSize(10);
	mAxisY.setSize([1, 1]);
	mAxisY.setPosition([0, 0]);
	mAxisY.setOrient("AXIS_VERTICAL");
	mAxisY.panelChangeListener = function(event) {
		if (event == "bounds") {
			var ymin = self.getRealWorldYMin();
			var ymax = self.getRealWorldYMax();
			mAxisY.setScale([ymin, ymax]);
			mAxisY.setTicksMode(self.getTypeScaleY());
			
			mAxisY.setInvertedScaleY(self.getInvertedScaleY());			
			return true;
		}
	};
	mAxisY.setTicksMode(self.getTypeScaleY());
	
	var mTitleX = EJSS_DRAWING2D.text(mName + ".titleX");
	mTitleX.setGroup(mTopGroup);
	mTitleX.setRelativePosition("CENTER");
	mTitleX.getFont().setFontSize(12);
	mTitleX.setPosition([0.5, 0]);
	mTitleX.setMarginY(30);
	mTitleX.setText("x");

	var mTitleY = EJSS_DRAWING2D.text(mName + ".titleY");
	mTitleY.setGroup(mTopGroup);
	mTitleY.setRelativePosition("CENTER");
	mTitleY.getFont().setFontSize(12);
	mTitleY.setWritingMode("MODE_DOWNTOP");
	mTitleY.setPosition([0, 0.5]);
	mTitleY.setMarginX(-40);
	mTitleY.setText("y");

	var mTitle = EJSS_DRAWING2D.text(mName + ".title");
	mTitle.setGroup(mTopGroup);
	mTitle.setRelativePosition("CENTER");
	mTitle.getFont().setFontSize(12);
	mTitle.setPosition([0.5, 1.0]);
	mTitle.setMarginY(-20);
	mTitle.setText("Plot");

	self.getStyle().setLineColor('black');
	self.getStyle().setFillColor('white');
	self.getStyle().setShapeRendering("RENDER_CRISPEDGES");

	self.setGutters([50, 50, 50, 50]);
	self.getGuttersStyle().setLineColor('black');
	self.getGuttersStyle().setFillColor('rgb(211,216,255)');
	self.getGuttersStyle().setShapeRendering("RENDER_CRISPEDGES");

	self.addDecoration(mBottomGroup);
	self.addDecoration(mGrid);
	self.addDecoration(mTopGroup, -1, true);
	self.addDecoration(mAxisX, -1, true);
	self.addDecoration(mAxisY, -1, true);
	self.addDecoration(mTitleX, -1, true);
	self.addDecoration(mTitleY, -1, true);
	self.addDecoration(mTitle, -1, true);

	return self;
};
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Polygon
 * @class Polygon 
 * @constructor  
 */
EJSS_DRAWING2D.Polygon = {
  	NO_CONNECTION : 0, 	// The next point will not be connected to the previous one
 	LINE_CONNECTION : 1,	// The next point will be connected to the previous one by a segment

	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy
  	
		dest.setPoints(source.getPoints());
  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Points", element.setPoints, element.getPoints);
		controller.registerProperty("PointsX", element.setPointsX);
		controller.registerProperty("PointsY", element.setPointsY);
		controller.registerProperty("LastPoint", element.addPoint, element.getLastPoint);		
	}
			
};

/**
 * Creates a 2D Polygon
 * @method polygon
 */
EJSS_DRAWING2D.polygon = function(name) {
	var self = EJSS_DRAWING2D.element(name);

  	var mPointList = []; 	// The current list of points  	
	var mTempX = null;
	var mTempY = null; 
	 
	self.getClass = function() {
		return "ElementPolygon";
	}
		  
  /**
   * Adds a new point to the Polygon.
   * @method addPoint
   * @param x double The X coordinate of the point 
   * 		or point double[] The double[2] array with the coordinates of the point.
   * @param y double The Y coordinate of the point.
   * @param style connected or not.
   */
  self.addPoint = function (x, y, style) {
    if(x instanceof Array)    	
    	addPoint(x[0], x[1], x[2]);
    else 
    	addPoint (x, y, style);
    self.setChanged(true);
  }

  /**
   * Adds an array of points to the Polygon.
   * @method addPoints
   * @param xInput double The double[] array with the X coordinates of the points.
   * 	or  point double[][] The double[nPoints][2] array with the coordinates of the points.
   * @param yInput double The double[] array with the Y coordinates of the points.
   */
  self.addPoints  = function(x, y) {
    if (x==undefined || x==null || x[0]==undefined) return;
  	if(x[0] instanceof Array) {    
    	for (var i=0,n=x.length; i<n; i++) addPoint (x[i][0],x[i][1],x[i][2]);  		
  	}
    else {
    	var n = Math.min(x.length,y.length);
    	for (var i=0; i<n; i++) addPoint (x[i],y[i]);
    }
    self.setChanged(true);
  }

  /**
   * Sets an array of points to the Polygon.
   * @method setPoints
   * @param x double The double[] array with the X coordinates of the points.
   * 	or  point double[][] The double[nPoints][2] array with the coordinates of the points.
   * @param y double The double[] array with the Y coordinates of the points.
   */
  self.setPoints  = function(x, y) {
  	self.clear();
  	self.addPoints(x,y);
  	self.setChanged(true);
  }


  /**
   * Adds the X coordinates of an array of points to the Polygon.
   * @method addXPoints
   * @param x double The double[] array with the X coordinates of the points.
   */
  self.setPointsX  = function(x) {
    if (mTempY==null) mTempX = x;
    else {
      self.setPoints(x,mTempY);
      mTempY = null;
    }
  }

  /**
   * Adds the Y coordinates of an array of points to the Polygon.
   * @method addYPoints
   * @param y double The double[] array with the Y coordinates of the points.
   */
  self.setPointsY  = function(y) {
    if (mTempX==null) mTempY = y;
    else {
      self.setPoints(mTempX,y);
      mTempX = null;
    }
  }

  /**
   * Returns all points.
   * @method getPoints
   * @return points
   */
  self.getPoints = function () {
    return mPointList; 
  }

  /**
   * Gets last point.
   * @method getLastPoint
   * @return point
   */
  self.getLastPoint = function () {
  	if(mPointList.length > 0) 
    	return mPointList[mPointList.length-1];
    else return [];
  }

  /**
   * Clears all points from all segments of the Polygon.
   * @method clear
   */
  self.clear = function() {
    mPointList = [];
  	self.setChanged(true);
  }
  
  /**
   * Returns bounds for an element
   * @override
   * @method getBounds
   * @return Object{left,rigth,top,bottom}
   */
  self.getBounds = function(element) {
  	var xmin, xmax, ymin, ymax;
  	var points = self.getPoints();
    var len = points.length;
    if(len == 0) {
    	xmin = xmax = ymin = ymax = 0;
    }             
    else {
		xmax = xmin = points[0][0];
		ymax = ymin = points[0][1];    	
		for(var j=1; j<len; j++) {
			var x = points[j][0], y = points[j][1];
			if(x>xmax) xmax=x; if(y>ymax) ymax=y; if(x<xmin) xmin=x; if(y<ymin) ymin=y;
		}
	}    
    var x = self.getX(), y = self.getY();
    var sx = self.getSizeX(), sy = self.getSizeY();
  	var mx = sy/2, my = sy/2;  	
  	var d = self.getRelativePositionOffset(sx,sy);
	return {
		left: ((x+d[0])-mx)+xmin*sx,
		right: ((x+d[0])-mx)+xmax*sx,
		top: ((y+d[1])-my)+ymax*sy, 
		bottom: ((y+d[1])-my)+ymin*sy
	}
  };  
  
  function addPoint(_x, _y, _style) {
    if (isNaN(_x) || isNaN(_y)) { 
    	return; 
    }
    
    mPointList[mPointList.length] = [_x, _y, _style];
  }


  self.registerProperties = function(controller) {
	EJSS_DRAWING2D.Polygon.registerProperties(self, controller);
  };
  
  self.copyTo = function(element) {
    EJSS_DRAWING2D.Polygon.copyTo(self,element);
  };
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	self.setSize([1,1]);
	self.setRelativePosition("SOUTH_WEST");
	
	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//PolygonSet
//---------------------------------

/**
 * PolygonSet
 * @class PolygonSet 
 * @constructor  
 */
EJSS_DRAWING2D.PolygonSet = {
    
    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING2D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("Points", 
          function(v) { set.setToEach(function(element,value) { element.setPoints(value); }, v); }
      );
      controller.registerProperty("LastPoint", 
          function(v) { set.setToEach(function(element,value) { element.addPoint(value); }, v); }
      );           
    }        
};

/**
 * Creates a set of Segments
 * @method polygonSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.polygonSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.polygon, mName);

  // Static references
  var PolygonSet = EJSS_DRAWING2D.PolygonSet;		// reference for PolygonSet

  /**
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   */
  self.registerProperties = function(controller) {
    PolygonSet.registerProperties(self,controller);
  };

  return self;
};/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Ruler
 * @class Ruler 
 * @constructor  
 */
EJSS_DRAWING2D.Ruler = {
  sROTATE_ICON : "data:image/png;base64, ",
  sMOVE_ICON   : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAAJEUlEQVR4Ae2af4xdRRXH37vv5/7o7nZ/1GZ3K2u7LYWFLVq1bER9aaDYBEKiWf9QNBKw8W8g8Q//WWKIiQaVGBNjFENjjH80xZQoJgoxSKLEBihQWjQVSBGppbu1sd12d7uPz3d2zuWm6bv3Prq7j33uJC8z99w5M+d75syZM+e+TGa1rGpgVQOrGmiMBgKmvaVQKHxjeHi41BgRGjTr+Ph4kamfzGaz1SDIVmm/snXr1p4GibO803rwT3nw55l9OpcLnBIGBwe7l1ea5Z8ty5QG/gJtLMCBn1lQSHAUWm75xVqGGScmJrTnDbxWft6DrwKedla0aj6ff45aimqesmfPngJoDPw0bVv5eYHX6nuavTuyfft28TRFiZr9ORAJ/BT13wXcgz+ey+XeWniXPetph/v7+1ubQQM/FiBM3MBrtbfh+PZ6oDL7Z6BtRDGnPe2sd4wvQc+vZCX0ePD/BYSZfUWA8vncExHaIdE6Ozs3Uk168Kd1RBIn3K13K7K0tbWtt1UG1DwgbooA2W9OkPp5o69bt24T7XcE3vPutHcrtf5qsVj4FcILWGZkZERBEBaQP0ClrSGgz4pmpa+vbzM+4RGex43WjPVjWmG/yn9oFECdzQ0pAJ/Lcj5Uq9UMq/16Q4Ro5KQ4t08yf9U7vFsbKUvD5m5padEe/ya/5or8GqbR1Ynr10Cjbl4DpVLpplKpsHN2dm6wu7s9Nz09cxbxZ+uHsII4FOnh+X/PCXBW5z+iW3So4/Acp4GOw90fZEiymEpXV/mqOoXMEvh8Hx537gP+Iu0LRIBa8VmeaTuanQoHent719Q5R2dHR8dwnTzpu5O366D3ywpc/OqlTWHlAPoKvOKbFv9C2yVCLBJ0NH8kuj7w/Jt+aQFdA+//NC5WpKhzccuWLf29jHjUgz9Nu9ra2vqJFLOAIziu/oA/58GfKJWK34N2c29v58e4L4zS3oVP+C71W34Od3MEzElomju2lMvlb9NBc5zxc+yPZajn5ejopnX0f9ULJkclM/0PdaID5canPR2CD4LMo9wFaub9ZGWAftTPYUp4iuekch3g5/jJkvAlzrp+ncSU+N6Dt5V34FnRSRg/lMTc3t7+RfqE4Gn/zHhGR0fbKpVKmedg167RNuqcaD5tlikWiz+HFuXdZLy1aubTrXHeg3fWhjJ/Uat/Iv2SlbdUle71Q4nMRHZ4++dkNQu/zD/HxsZaxCfglu4ymp49Lbd79259I0DP2WPipa2EyZ+oEwunzHY6maN1MhcKuVDxiQNYh5ERl562lXfJSiQ6xvs+65NQb5PwgJihX5WQ93711yrHgHfKoZvLBeIT7hMvK+rGMDp1bOnq6roBWU/RifkDJzuWsDeWKfqyUnEpqCMRAJa9PYgwe9nX++i/j1X5DfV++j3uLzbhMDx/3a/eDEJoFUf0Uquv+jIrH9KsD92u8+bMGNkqjm6jeCNlLTJ8jud7Cah+EASZh5jrYbbPBLSn/bwX4b3gZfkd3yQS/ZbG/4IHbysvAC5z6weSZp1p+n6uvXZty4fFrFIuF79DZf2mNmzY0O9esDXiwMs6xscXEiZbtgwOwDPFHJY1vsWPwfjlT19OlktoUZnPe2X+yMaoWaPJe6VxOpyPADUhDBR1mMyw4CXqqB40XupJsrvuKAOgy/IKaMxWyE5MVJQIXQsglxqjXWVL3G5Ct7WVtPKad051VBabV8qIKMR9hMFi/mxj1KzXrFkjYU+Z+TGIgf8HA/yVdy8yySHof6P9OLQJ6s38wuL3vAQj6eH8wCYSH7ryBgngM6YkorrN4oeHSNFZ3PXhBDSKxfy3qCZ5d4I53qZ9Apnfof4XtLehOcukHSqptTVjlki3mLJ+/Xrt2VMaBCHsi80L0OSh05SK53Wa7+ho+7KYZP4xK5/x+1+rnyFAupOK+QM3Ro3QWHs60G9sbFCnTIu+L+Kn/hLllYXglz4KLX3p6em5BkZ9wJAQ5g+O8uwETBgpC89xz6uVUEAUrm6CH7CPIX9kDFvFN8WfpmCRT9IvVJzAQ/tUHG9cNkbZWRf6MlD4xYbBpPmkMua30TkJUS4X7hHDjh07dKfIKTtsGWKvkLyOST/oXR684xWbp8dWANUJJfA+bnHgPxPLlOLlVbIEv6fsi81B+OIUZ8O+5reCbn9VzPpr9sKiPrMGo1N/yYPXESZHdiTyrmYT8A/zUuBdCC1eaJ+tyVDPi+7u7g30N8c4pcHZU4q6YgvhaZ/60kkhqlMCFrUfRdwMrcNHfRntW6I4HXP7IuDN+bbHTsJLLmZ3UOlUOKO6XvBpVjKDY1QUeFKDe2u4gec05UZA+7383nUYYU/CfJixXgK0vLdWT/2mI3NcnWYC+nzFbzcDH7vnU4552W4BK/9x3qQVzAa5GnCTXnGyBufZ9ewtRODlaPU/ASlYCql3jh0o+qccwWFAZpMn1aksIGmQFO91ejyAkM5MBd4rxFZe9Rl+P6SfuzilGPOKuywX+KigOfbstpmZmc/Pzc1txlEF1G/gFw4Rxx+YmprSjXO1LJsG7DhatglXJ3pPA+3txWs5YhUYNa6kvSMvgYRDdgIwthzk/1fB2z8IYrtKL07E9j5UqJtUthFWMD8/fz1KyFy8OK9/i9z2PmRfMSxlJHV/i4lKDOiDPLtAiHjA5fHtqjw0NETSp/wR3jfimI6KecXtYQtRCYDcbTAy4vMWDJFBfszoC/f54GWe2R6516iXXAlLNgGBjU9qhF9sHjKggH/BnCDW8FvRBwYGeoiLXqUp36CASF+g0mVxNMAVFClB/mCxSzsg3bWWge2LzU80CaurlJqFwb+UH8InHPM0+wijC5O20MotNb7YPAKiQwB2t0SU8TS/Z6FJIQb+NPnJei9EH0xFcdfX/d/S1JZeE3jR7N4v8D6TEzQPeEt7yRJszwM0vA7LEUoRRqMOwZMkbboAiTzhQnIE4EqZuy0A6FmUIAsIwVt6HHrTFb7dhbkAfTpz1hAFH0mONh14B4iPHjfSsE/ZWvkpc3iXSZI2pxJwjDtBdlgRoYHneSmO4uZU4GKiWrJIcDGFXKqxzOSyzXjkpFaaB2+WcGmtcYyWesyV0vFd4FDB1mOmmM4AAAAASUVORK5CYII=",

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

      controller.registerProperty("Minimum", element.setMinimum);
      controller.registerProperty("Maximum", element.setMaximum);

      controller.registerProperty("NumberOfMarks",element.setNumberOfMarks,element.getNumberOfMarks);
      controller.registerProperty("BigMark",element.setBigMark);
      controller.registerProperty("MediumMark",element.setMediumMark);
      controller.registerProperty("MarkFactor",element.setMarkFactor);
      controller.registerProperty("Digits",element.setDigits);

      controller.registerProperty("BorderWidth",element.setBorderWidth);
      controller.registerProperty("BorderColor",element.setBorderColor);
      controller.registerProperty("FillColor",element.setFillColor);
      controller.registerProperty("LineWidth",element.setLineWidth);
      controller.registerProperty("LineColor",element.setLineColor);
      controller.registerProperty("TextColor",element.setTextColor);
      controller.registerProperty("TextFont",element.setTextFont);

      controller.registerProperty("Width",element.setWidth);
      controller.registerProperty("Length",element.setLength);
      
      controller.registerProperty("DrawLines",element.setDrawLines);
      controller.registerProperty("DrawText",element.setDrawText);

      controller.registerProperty("Enabled",element.setEnabled);
      controller.registerProperty("Rotates",element.setRotates);

      controller.registerAction("OnDrag",element.getOnRotationInformation);
      controller.registerAction("OnRotation",element.getOnRotationInformation);

    }
};

/**
 * Creates a 2D ruler
 * @method ruler
 */
EJSS_DRAWING2D.ruler = function (mName) {
  var self = EJSS_DRAWING2D.group(mName);

  // Configuration variables
  var mLength = 1.1;
  var mWidth = 0.2;
  
  var mMinimum = 0;
  var mMaximum = 1;

  var mNumberOfMarks = 101;
  var mBigMark = 10;
  var mMediumMark = 5;
  var mMarkFactor = 0.1;
  var mDigits = 0;

  // Implementation variables
  var mGroup,mBody;
  var mSegments, mTexts;
  var mLeftHandle, mRightHandle;
  var mLeftRotate, mRightRotate;
  var mForegroundCreated = false;

  var mElements = [];
  var mScaleChanged = true;
  
  self.getElement = function(keyword) {
	  return mElements[keyword];
  };
  
  self.superSetChanged = self.setChanged;
  
  self.setChanged = function(changed) {
	self.superSetChanged(changed);
	if (changed) for (var i=0,n=mElements.length; i<n; i++) mElements[i].setChanged(changed);
  }

  
  // ----------------------------------------------------
  // public functions
  // ----------------------------------------------------

  self.setWidth = function(value) { 
	  if (value!=mWidth) {
		  mWidth = value;
		  self.mustAdjust();
	  }
  }
  self.getWidth = function() { return mWidth; }

  self.setLength = function(value) { 
	  if (value!=mLength) {
		  mLength = value;
		  self.mustAdjust();
      }
  }
  self.getLength = function() { return mLength; }

  /**
   * Sets the minimum value of the element. 
   * @method setMinimum
   * @param value
   */
  self.setMinimum = function(value) {
    if (mMinimum!=value) {
    	mMinimum = value;
    	mScaleChanged = true;
    }
  }
  
  /**
   * @method getMinimum
   * @return current minimum
   */
  self.getMinimum = function() { 
    return mMinimum; 
  };    

  /**
   * Sets the minimum value of the element. 
   * @method setMaximum
   * @param value
   */
  self.setMaximum = function(value) {
	if (mMaximum!=value) {
		mMaximum = value;
    	mScaleChanged = true;
	}
  }
  
  /**
   * @method getMaximum
   * @return current maximum
   */
  self.getMaximum = function() { 
    return mMaximum; 
  };    
  
  /***
   * Sets the number of marks
   * @method setNumberOfMarks(value)
   * @visibility public
   * @param value int
   */ 
  self.setNumberOfMarks = function(value) {
	  if (value!=mNumberOfMarks) {
		  mNumberOfMarks = value;
	      mScaleChanged = true;
	  }
  };

  /***
   * Gets the number of marks
   * @method getNumberOfMarks
   * @visibility public
   * @return int
   */
  self.getNumberOfMarks = function() {
  	return mNumberOfMarks;
  };


  /***
   * Sets the interval for big marks
   * @method setBigMark(value)
   * @visibility public
   * @param value int 
   */ 
  self.setBigMark = function(value) {
	  if (value!=mBigMark) {
        mBigMark = value;
    	mScaleChanged = true;
	  }
  };

  /***
   * Gets the interval for big marks
   * @method getBigMark
   * @visibility public
   * @return int
   */
  self.getBigMark = function() {
  	return mBigMark;
  };

  /***
   * Sets the interval for medium marks
   * @method setMediumMark(value)
   * @visibility public
   * @param value int 
   */ 
  self.setMediumMark = function(value) {
	  if (value!=mMediumMark) {
        mMediumMark = value;
    	mScaleChanged = true;
	  }
  };

  /***
   * Gets the interval for medium marks
   * @method getMediumMark
   * @visibility public
   * @return int
   */
  self.getMediumMark = function() {
  	return mMediumMark;
  };
  
  /***
   * Sets the factor that divide the value in marks
   * @method setMarkFactor(value)
   * @visibility public
   * @param value int|double 
   */ 
  self.setMarkFactor = function(value) {
	  if (value!=mMarkFactor) {
        mMarkFactor = value;
    	mScaleChanged = true;
	  }
  };

  /***
   * Gets the factor that divide the displayed value in marks
   * @method getMarkFactor
   * @visibility public
   * @return int|double
   */
  self.getMarkFactor = function() {
  	return mMarkFactor;
  };

  /***
   * Sets the maximum value to display in a mark
   * @method setMaximumMark(value)
   * @visibility public
   * @param value int|double 
   */ 
  self.setMaximumMark = function(value) {
	  if (value!=mMaximumMark) {
        mMaximumMark = value;
    	mScaleChanged = true;
	  }
  };

  /***
   * Gets the maximum value to display in a mark
   * @method getMaximumMark
   * @visibility public
   * @return value int|double 
   */ 
  self.getMaximumMark = function() {
  	return mMaximumMark;
  };

  /***
   * Sets the number of digits for the marks
   * @method setDigits(value)
   * @visibility public
   * @param value int 
   */ 
  self.setDigits = function(value) {
	  if (value!=mDigits) {
		mDigits = value;
	    mScaleChanged = true;
	  }
  };

  /***
   * Gets the number of digits for the marks
   * @method getPrecision
   * @visibility public
   * @return int
   */
  self.getDigits = function() {
  	return mDigits;
  };
  

  // ----------------------------------------------------
  // Properties overwritten
  // ----------------------------------------------------

  self.mustAdjust = function() { mScaleChanged = true; }
  
  self.setBorderWidth = function(value) {  mBody.getStyle().setLineWidth(value); }
  self.setBorderColor = function(value) {  mBody.getStyle().setLineColor(value); }
  self.setFillColor = function(value) { mBody.getStyle().setFillColor(value); }

  self.setLineWidth = function(value) { 
	  mSegments.setToEach(function(element) { element.getStyle().setLineWidth(value); });
  }
  self.setLineColor = function(value) { 
	  mSegments.setToEach(function(element) { element.getStyle().setLineColor(value); });
  }
  self.setDrawLines = function(value) { 
	  mSegments.setToEach(function(element,value) { element.setVisible(value); },value);
  }
  
  self.setTextColor = function(value) { 
	  mTexts.setToEach(function(element) { element.getFont().setFillColor(value); });
  }
  self.setTextFont = function(value) { 
	  mTexts.setToEach(function(element) { element.getFont().setFont(value); });
  }
  self.setDrawText = function(value) { 
	  mTexts.setToEach(function(element,value) { element.setVisible(value); },value);
  }

  self.setEnabled = function(value) { 
	  mLeftHandle.setVisible(value);
	  mRightHandle.setVisible(value);
  };

  self.setRotates = function(value) { 
	  mLeftRotate.setVisible(value);
	  mRightRotate.setVisible(value);
  };
  
  // ----------------------------------------------------
  // Properties and copies
  // ----------------------------------------------------

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Ruler.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // private functions
  // ----------------------------------------------------

  self.superSetParent = self.setParent;
  
  self.setParent = function(parent, sibling) {
	self.superSetParent(parent,sibling);
	if (!mForegroundCreated) {
      self.createRuler();
	  mForegroundCreated = true;
	}
	self.addParticularChildren();
	self.adjustForeground();
  }

  self.dataCollected = function() {
	  if (mScaleChanged) {
		  self.adjustForeground();
		  mScaleChanged = false;
	  }
  };

  self.addParticularChildren = function() { 
	mGroup.setParent(self);
	mBody.setParent(mGroup);
	mSegments.setToEach(function(element) { element.setParent(mGroup); });
	mTexts.setToEach(function(element) { element.setParent(mGroup); });
	mLeftHandle.setParent(self);
	mRightHandle.setParent(self);
	mLeftRotate.setParent(self);
	mRightRotate.setParent(self);
  };

  self.createBody = function() {
	var body = EJSS_DRAWING2D.shape(mName+".body");
	body.setShapeType("ROUND_RECTANGLE");
	body.setCornerRadius(5);
    return body;
  }

  self.createRuler = function() {
	mGroup = EJSS_DRAWING2D.group(mName+".group");
	mElements['group'] = mGroup;

	mBody = self.createBody();
	mBody.getStyle().setFillColor("rgba(0,255,0,0.2)");
	mElements['body'] = mBody;

	mSegments = EJSS_DRAWING2D.segmentSet(mName+".segments");
	mSegments.setToEach(function(element) { element.setRelativePosition("SOUTH_WEST"); });
	mSegments.setToEach(function(element) { element.getStyle().setLineWidth(0.5); });
	mElements['segments'] = mSegments;

	mTexts = EJSS_DRAWING2D.textSet(mName+".texts");
	mTexts.setToEach(function(element) { element.getFont().setFont("normal normal 8px \"Courier New\", Courier, monospace"); });
	mTexts.setToEach(function(element) { element.setRelativePosition("NORTH"); });
	mElements['texts'] = mTexts;

	mLeftHandle = EJSS_CORE.promoteToControlElement(
				EJSS_DRAWING2D.image(mName+".left_handle"),self.getView(),mName+".left_handle");
	mLeftHandle.setImageUrl(EJSS_DRAWING2D.Ruler.sMOVE_ICON);
	mLeftHandle.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setMotionEnabled("ENABLED_ANY");
	mLeftHandle.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setAffectsGroup(true);
	mLeftHandle.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setSensitivity(10);
	mLeftHandle.setSize([10,10]);
	mLeftHandle.setPixelSize(true);
	mLeftHandle.setProperty("OnDrag",moved);
	mElements['left_handle'] = mLeftHandle;

	mRightHandle = EJSS_CORE.promoteToControlElement(
				EJSS_DRAWING2D.image(mName+".right_handle"),self.getView(),mName+".right_handle");
	mRightHandle.setImageUrl(EJSS_DRAWING2D.Ruler.sMOVE_ICON);
	mRightHandle.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setMotionEnabled("ENABLED_ANY");
	mRightHandle.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setAffectsGroup(true);
	mRightHandle.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setSensitivity(10);
	mRightHandle.setProperty("OnDrag",moved);
	mRightHandle.setSize([10,10]);
	mRightHandle.setPixelSize(true);
	mElements['right_handle'] = mRightHandle;
		
	mLeftRotate = EJSS_CORE.promoteToControlElement(
			EJSS_DRAWING2D.image(mName+".left_rotate"),self.getView(),mName+".left_rotate");
	mLeftRotate.setImageUrl(EJSS_DRAWING2D.Ruler.sROTATE_ICON);
	mLeftRotate.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setMotionEnabled("ENABLED_NO_MOVE");
	mLeftRotate.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setSensitivity(10);
	mLeftRotate.setSize([8,8]);
	mLeftRotate.setPixelSize(true);
	mLeftRotate.setProperty("OnDrag",rotated);
	mElements['left_rotate'] = mLeftRotate;

	mRightRotate = EJSS_CORE.promoteToControlElement(
			EJSS_DRAWING2D.image(mName+".right_rotate"),self.getView(),mName+".right_rotate");
	mRightRotate.setImageUrl(EJSS_DRAWING2D.Ruler.sROTATE_ICON);
	mRightRotate.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setMotionEnabled("ENABLED_NO_MOVE");
	mRightRotate.getInteractionTarget(EJSS_DRAWING2D.PanelInteraction.TARGET_POSITION).setSensitivity(10);
	mRightRotate.setSize([8,8]);
	mRightRotate.setPixelSize(true);
	mRightRotate.setProperty("OnDrag",rotated);
	mElements['right_rotate'] = mRightRotate;

  };

  self.adjustBody = function() {
		mBody.setSize([mLength,mWidth]);
  };

  self.adjustForeground = function() {
	mLeftHandle.setPosition([-0.125*mLength,0]);
	mRightHandle.setPosition([0.125*mLength,0]);
	mLeftRotate.setPosition([-0.35*mLength,0]);
	mRightRotate.setPosition([0.35*mLength,0]);
	
	mGroup.setY(mWidth*0.3);
	self.adjustBody();
	
	var MARGIN = (mLength - Math.abs(mMaximum-mMinimum))/2;
	var MARK = mWidth/6;
	  
	mSegments.setNumberOfElements(mNumberOfMarks);
	var dx = Math.abs(mMaximum-mMinimum)/(mNumberOfMarks-1);
	var xPos = -mLength/2+MARGIN;
	var mediumMarksCounter = 0;
	var bigMarksCounter = 0;

	for (var i=0; i<mNumberOfMarks; i++) {
	  var segment = mSegments.getElement(i);
	  segment.setPosition([xPos,mWidth/2]);
	  if (i%mBigMark==0) {
	    bigMarksCounter++;
	    segment.setSize([0,-2*MARK]);
	  }
	  else if (i%mMediumMark==0) {
	    mediumMarksCounter++;
	    segment.setSize([0,-1.5*MARK]);
	  }
	  else segment.setSize([0,-MARK]);
	  xPos += dx;
	}
	
	mTexts.setNumberOfElements(bigMarksCounter);
	var text_counter=0;
	for (i=0; i<mNumberOfMarks; i++) {
	  var segment = mSegments.getElement(i);
	  if (i%mMediumMark==0) {
		  // locate units
	  }
	  if (i%mBigMark==0) {
	    var text = mTexts.getElement(text_counter);
	    text.setX(segment.getX());
	    text.setY(mWidth/2+segment.getSizeY()*1.1);
	    var mark_value = ((mNumberOfMarks-1-i)*mMinimum + i*mMaximum)/(mNumberOfMarks-1) /mMarkFactor;
	    text.setText(""+mark_value.toFixed(mDigits));
	    text_counter++;
	  }
	}
  };
   
  self.getOnRotationInformation = function() {
	  return { position : self.getPosition(), element : self, 
		  point : self.getGroupPanel().getPanelInteraction().getInteractionPoint(),
		  angle : self.getTransformation() };
  };

  function moved(point,info) {
	  var controller = self.getController();    		
	  if (controller) {
		controller.invokeImmediateAction("OnDrag");
	  }
  };

  self.computeRotation = function(rightIcon,point) {
	  if (rightIcon) return Math.atan2(point[1],point[0]);
	  else return Math.atan2(-point[1],-point[0]);
  };
  
  function rotated(point,info) {
	  var point = info.point;
	  var element = info.element;
	  var group_pos = self.getAbsolutePosition(true);
	  point[0] -= group_pos[0];
	  point[1] -= group_pos[1];
	  var rotation = self.computeRotation(element==mRightRotate,point);
	  self.setTransformation(rotation);
	  self.setChanged(true);
	  var controller = self.getController();    		
	  if (controller) {
//		controller.immediatePropertyChanged("Value");
		controller.invokeImmediateAction("OnRotation");
	  }	        

  }
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
};


/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * ScalarField
 * @class ScalarField 
 * @constructor  
 */
EJSS_DRAWING2D.ScalarField = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

		controller.registerProperty("ZData", element.setData, element.getData);
		controller.registerProperty("MinimumZ", element.setMinimumZ, element.getMinimumZ);
		controller.registerProperty("MaximumZ", element.setMaximumZ, element.getMaximumZ);
		controller.registerProperty("AutoscaleZ", element.setAutoscaleZ, element.getAutoscaleZ);
		controller.registerProperty("SymmetricZ", element.setSymmetricZ, element.getSymmetricZ);
		//controller.registerProperty("ExpandedZ", element.setMinimumX, element.getMinimumX);

		controller.registerProperty("FloorColor", element.setFloorColor, element.getFloorColor);
		controller.registerProperty("CeilColor", element.setCeilColor, element.getCeilColor);

		controller.registerProperty("ShowGrid", element.setShowGrid, element.getShowGrid);
		controller.registerProperty("NumColors", element.getColorMapper().setNumberOfColors);
		controller.registerProperty("Colors", element.getColorMapper().setColorPalette);
		controller.registerProperty("Palette", element.getColorMapper().setPaletteType);
        controller.registerProperty("AutoUpdate", element.setAutoupdate);  

    },
};

/**
 * Creates a 2D scalarField
 * @method ScalarField
 */
EJSS_DRAWING2D.scalarField = function (name) {
	var self = EJSS_DRAWING2D.element(name);
 
  	var mColorMapper = EJSS_DRAWING2D.colorCoded(16, EJSS_DRAWING2D.ColorMapper.SPECTRUM); 
	var mCeilColor = mColorMapper.getCeilColor();
	var mFloorColor = mColorMapper.getFloorColor();
 	var mShowGrid = true;
	var mData = [];
	var mAutoUpdate=true;
	var mzMax = 1;
	var mzMin = -1;
	var mAutoscaleZ = true;
	var mSymmetricZ = false;
 
	self.getClass = function() {
  		return "ElementScalarField";
	}

   	/**
   	* @method setFloorColor
   	* @param color
   	*/
  	self.setFloorColor = function (color) {
	  	if(mFloorColor != color) {
    		mFloorColor = color;
    		mColorMapper.setFloorCeilColor(mFloorColor,mCeilColor);
    		self.setChanged(true);
    	}
  	}
  
  	/**
   	* @method getFloorColor
   	* @return color
   	*/
  	self.getFloorColor = function() {
    	return mFloorColor;
  	}

   	/**
   	* @method setCeilColor
   	* @param color
   	*/
  	self.setCeilColor = function (color) {
	  	if(mCeilColor != color) {
    		mCeilColor = color;
    		mColorMapper.setFloorCeilColor(mFloorColor,mCeilColor);
    		self.setChanged(true);
    	}
  	}
  
  	/**
   	* @method getCeilColor
   	* @return color
   	*/
  	self.getCeilColor = function() {
    	return mCeilColor;
  	}

	/** 
	 * @method setData
	 * @param data
	 */
	self.setData = function (data) {
	  if (mAutoUpdate || mData != data) {
	    mData = data;
		mDataChanged = true;
		self.setChanged(true);
	  }
	}
	  
	/**
	 * @method getData
	 * @return
	 */
	self.getData = function() { 
		return mData; 
	}
	 
	self.setAutoupdate= function (auto) {
	  mAutoUpdate = auto;
      if (mAutoUpdate) mDataChanged = true;
    }
    
	/** 
	 * @method getMaximumZ
	 * @return max
	 */
  	self.getMaximumZ = function() {
  		return mzMax;
  	}

	/** 
	 * Sets ceiling value for the colors 
	 *
	 * @method setMaximumZ
	 * @param max
	 */
  	self.setMaximumZ = function(max) {
  		if(mzMax != max) {
  			mzMax = max;
	    	if (!mAutoscaleZ) {
	    		mColorMapper.setScale(mzMin, mzMax);		
	  		}
			self.setChanged(true);
  		};
  	}

	/** 
	 * @method getMinimumZ
	 * @return min
	 */
  	self.getMinimumZ = function() {
  		return mzMin;
  	}

	/** 
	 * Sets floor value for the colors 
	 *
	 * @method setMinimumZ
	 * @param min
	 */
  	self.setMinimumZ = function(min) {
  		if(mzMin != min) {
  			mzMin = min;
	    	if (!mAutoscaleZ) {
	    		mColorMapper.setScale(mzMin, mzMax);		
	  		}
			self.setChanged(true);
  		};
  	}

  	/**
   	* Sets the autoscale flag.
   	*
   	* If autoscaling is true, then the min and max values of z are span the colors.
   	*
   	* If autoscaling is false, then MaximumZ and MinimumZ values limit the colors.
   	* Values below min map to the first color; values above max map to the last color.
   	*
   	* @method setAutoscaleZ
   	* @param isAutoscale
   	*/
  	self.setAutoscaleZ = function (isAutoscale) {
	  	if(mAutoscaleZ != isAutoscale) {
	    	mAutoscaleZ = isAutoscale;
	    	self.setChanged(true);
	    }
  	}
  
  	/**
   	* Gets the autoscale flag for z.
   	*
   	* @method getAutoscaleZ
   	* @return boolean
   	*/
  	self.getAutoscaleZ = function() {
    	return mAutoscaleZ;
  	}

   	/**
   	* @method setSymmetricZ
   	* @param isAutoscale
   	*/
  	self.setSymmetricZ = function (symmetric) {
	  	if(mSymmetricZ != symmetric) {
    		mSymmetricZ = symmetric;
    		mColorMapper.setSymmetricZ(mSymmetricZ);
    		self.setChanged(true);
    	}
  	}
  
  	/**
   	* @method getSymmetricZ
   	* @return boolean
   	*/
  	self.getSymmetricZ = function() {
    	return mSymmetricZ;
  	}

	/** 
	 * @method setColorMapper
	 * @param colormapper
	 */
	self.setColorMapper = function (colormapper) {
	  	if(mColorMapper != colormapper) {
	  		mColorMapper = colormapper;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getColorMapper
	 * @return
	 */
	self.getColorMapper = function() { 
		return mColorMapper; 
	}
	 
	/** 
	 * @method setShowGrid
	 * @param showgrid
	 */
	self.setShowGrid = function (showgrid) {
	  	if(mShowGrid != showgrid) {
	  		mShowGrid = showgrid;
	  		self.setChanged(true);
	  	}
	}
	  
	/**
	 * @method getShowGrid
	 * @return
	 */
	self.getShowGrid = function() { 
		return mShowGrid; 
	}

	self.registerProperties = function(controller) {
    	EJSS_DRAWING2D.ScalarField.registerProperties(self,controller);
	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A Segment is a 2D drawing element that displays a single line 
 * @class EJSS_DRAWING2D.Segment
 * @parent EJSS_DRAWING2D.Element
 * @constructor  
 */
EJSS_DRAWING2D.Segment = {

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

      controller.registerProperty("Offset", element.setRelativePosition, element.getRelativePosition);
    },  


};

/**
 * Creates a 2D Segment
 * @method segment
 */
EJSS_DRAWING2D.segment = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  self.getClass = function() {
  	return "ElementSegment";
  };
  
    self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Segment.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([0.1,0.1]);
  self.setRelativePosition("SOUTH_WEST");

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//SegmentSet
//---------------------------------

/**
 * SegmentSet
 * @class SegmentSet 
 * @constructor  
 */
EJSS_DRAWING2D.SegmentSet = {
    
    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING2D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("Offset", 
          function(v) { set.setToEach(function(element,value) { element.setRelativePosition(value); }, v); }
      );
    }
    
};


/**
 * Creates a set of Segments
 * @method segmentSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.segmentSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.segment, mName);

  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.SegmentSet.registerProperties(self,controller);
  };
  
  return self;
};/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * SetSquare
 * @class SetSquare 
 * @constructor  
 */
EJSS_DRAWING2D.SetSquare = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Ruler.registerProperties(element,controller); // super class

      controller.registerProperty("Height",element.setHeight);
    }

};

/**
 * Creates a 2D shape
 * @method shape
 */
EJSS_DRAWING2D.setSquare = function (mName) {
  var self = EJSS_DRAWING2D.ruler(mName);
  
  var mHeight = self.getLength();

  // Implementation variables
  // ----------------------------------------------------
  // public functions
  // ----------------------------------------------------

  // ----------------------------------------------------
  // Properties and copies
  // ----------------------------------------------------

  self.setHeight = function(value) { 
	  if (value!=mHeight) {
		  mHeight = value;
		  self.mustAdjust();
      }
  }
  self.getHeight = function() { return mHeight; }

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.SetSquare.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // private functions
  // ----------------------------------------------------

  self.createBody = function() {
	var body = EJSS_DRAWING2D.polygon(mName+".body");
	return body;
  }

  self.adjustBody = function() {
	    var w = self.getWidth(), l = self.getLength(), h = self.getHeight();
	    var hyp = Math.sqrt(l*l+h*h);
	    var l2 = (h-w)*l/h - w - w*hyp/h; // interior length
	    var h2 = (h-w) - w*h/l - w*hyp/l; // interior height
	    
	    var points = [[0,0,1],[l,0,1],[0,-h,1],[0,0,1],
	                 [w,-w,0],[w,-w-h2,1],[w+l2,-w,1],[w,-w,1]];
	    for (var i=0; i<points.length; i++) {
	    	points[i][0] -= l/2;
	    	points[i][1] += w/2;
	    };
		self.getElement('body').setPoints(points);
		
		self.getElement('left_handle').setPosition([w+l2/4-l/2,0]);
		self.getElement('right_handle').setPosition([w+3*l2/4-l/2,0]);
		self.getElement('left_rotate').setPosition([w/2-l/2,0]);
		self.getElement('right_rotate').setPosition([w+l2+w/2-l/2,0]);	  
  };
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
    self.setMaximum(0.99);
	self.setNumberOfMarks(100);

  return self;
};


/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A Shape is a 2D drawing element that displays a 2D shape (such as a rectangle, ellipse, etc.) 
 * @class EJSS_DRAWING2D.Shape
 * @parent EJSS_DRAWING2D.Element
 */
EJSS_DRAWING2D.Shape = {
    NONE : 0,
    ELLIPSE : 1,
    RECTANGLE : 2,
    ROUND_RECTANGLE : 3,
    WHEEL : 4,
    POINT : 5,

    /**
     * Static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller);

	 /*** 
	  * Type of shape
	  * @property ShapeType 
	  * @type int|String
	  * @values 0:"NONE", 1:"ELLIPSE", 2:"RECTANGLE", 3:"ROUND_RECTANGLE", 4:"WHEEL", 5:"POINT" 
	  * @default "ELLIPSE" 
	  */  
      controller.registerProperty("ShapeType",element.setShapeType);
      
	 /*** 
	  * Radius for the corners of a round rectangular shape
	  * @property CornerRadius 
	  * @type int
	  * @default 10 
	  */  
      controller.registerProperty("CornerRadius",element.setCornerRadius);
    },

    /**
     * Static copyTo method, to be used by sets
     */
    copyTo : function(source, dest) {
      EJSS_DRAWING2D.Element.copyTo(source,dest);

      dest.setShapeType(source.getShapeType());
      dest.setCornerRadius(source.getCornerRadius());
    }

};

/**
 * Creates a 2D shape
 * @method shape
 */
EJSS_DRAWING2D.shape = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  // Instance variables
  var mCornerRadius = 10;
  var mShapeType = EJSS_DRAWING2D.Shape.ELLIPSE;

  // ----------------------------------------------------
  // Public functions
  // ----------------------------------------------------

  /***
   * Sets the value of the property ShapeType
   * @method setShapeType(shapeType)
   * @visibility public
   * @param shapeType int|String 
   */ 
  self.setShapeType = function(shapeType) {
    if (typeof shapeType === 'string')
      mShapeType = EJSS_DRAWING2D.Shape[shapeType.toUpperCase()];
    else 
      mShapeType = shapeType;
  };

  /***
   * Gets the value of the property ShapeType
   * @method getShapeType
   * @visibility public
   * @return int|String 
   */ 
  self.getShapeType = function() {
  	return mShapeType;
  }

  /***
   * Sets the value of the property CornerRadius
   * @method setCornerRadius
   * @param radius int
   */
  self.setCornerRadius = function(radius) {
    if(mCornerRadius != radius) {
    	mCornerRadius = radius;
    	self.setChanged(true);
    }
  };

  /***
   * Gets the value of the property CornerRadius
   * @method getCornerRadius
   * @return int
   */
  self.getCornerRadius = function() {
    return mCornerRadius;
  };

  // ----------------------------------------------------
  // Private functions
  // ----------------------------------------------------

  /***
   * Returns the class name
   * @method getClass
   * @visibility private
   * @return String
   */
  self.getClass = function() {
  	return "ElementShape";
  }

  /***
   * Extended registerProperties method
   * @method registerProperties
   * @param controller
   * @visibility private
   */
  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Shape.registerProperties(self,controller);
  };

  /***
   * Extended copyTo method
   * @method copyTo
   * @param Element
   * @visibility private
   */
  self.copyTo = function(element) {
    EJSS_DRAWING2D.Shape.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  /*** @property Size @default [0.1,0.1] */
  self.setSize([0.1,0.1]);
  
  /*** @property RelativePosition @default "CENTER" */
  self.setRelativePosition("CENTER");
  
  /*** @property FillColor @default "Blue" */  
  self.getStyle().setFillColor("Blue");
  
  /*** @property LineColor @default "Black" */  
  self.getStyle().setLineColor("Black");
    
  return self;
};


/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//ShapeSet
//---------------------------------

/**
 * ShapeSet
 * @class ShapeSet 
 * @constructor  
 */
EJSS_DRAWING2D.ShapeSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING2D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("ShapeType", 
          function(v) { set.setToEach(function(element,value) { element.setShapeType(value); }, v); }
      );
      controller.registerProperty("CornerRadius", 
          function(v) { set.setToEach(function(element,value) { element.setCornerRadius(value); }, v); }
      );
    }

};


/**
 * Creates a set of shapes
 * @method shapeSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.shapeSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.shape, mName);

  // Static references
  var ShapeSet = EJSS_DRAWING2D.ShapeSet;		// reference for ShapeSet
  
  self.registerProperties = function(controller) {
    ShapeSet.registerProperties(self,controller);
  };

  return self;
};
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García 
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for 2D drawing.
 * @module 2Ddrawing
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A SimplePanel is a 2D drawing panel with no decoration. 
 * @class EJSS_DRAWING2D.SimplePanel
 * @see EJSS_DRAWING2D.PlottingPanel
 * @constructor
 */
EJSS_DRAWING2D.SimplePanel = {
	GRAPHICS2D_SVG : "SVG",
	GRAPHICS2D_CANVAS : "Canvas",

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		// No super class
		// EJSS_INTERFACE.Element.registerProperties(element.getGraphics(), controller);

		controller.registerProperty("MinimumX", element.setWorldXMin, element.getWorldXMin);
		controller.registerProperty("MaximumX", element.setWorldXMax, element.getWorldXMax);
		controller.registerProperty("MinimumY", element.setWorldYMin, element.getWorldYMin);
		controller.registerProperty("MaximumY", element.setWorldYMax, element.getWorldYMax);
		controller.registerProperty("Bounds", element.setWorldCoordinates, element.getWorldCoordinates);


		controller.registerProperty("MarginX", element.setMarginX,element.getMarginX);
		controller.registerProperty("MarginY", element.setMarginY,element.getMarginY);

		controller.registerProperty("Parent", element.getGraphics().setParent, element.getGraphics().getParent);
		controller.registerProperty("Width", element.getGraphics().setWidth, element.getGraphics().getWidth);
		controller.registerProperty("Height", element.getGraphics().setHeight, element.getGraphics().getHeight);

		controller.registerProperty("Graphics", element.setGraphics);

		controller.registerProperty("Background", element.getStyle().setFillColor);
		controller.registerProperty("Foreground", element.getStyle().setLineColor);
		controller.registerProperty("LineColor", element.getStyle().setLineColor);
		controller.registerProperty("LineWidth", element.getStyle().setLineWidth);
		controller.registerProperty("DrawLines", element.getStyle().setDrawLines);
		controller.registerProperty("FillColor", element.getStyle().setFillColor);
		controller.registerProperty("DrawFill", element.getStyle().setDrawFill);
		controller.registerProperty("ShapeRendering", element.getStyle().setShapeRendering);

	    controller.registerProperty("Visibility", element.getGraphics().getStyle().setVisibility);      
        controller.registerProperty("Display", element.getGraphics().getStyle().setDisplay); 
		controller.registerProperty("CSS", element.getGraphics().getStyle().setCSS);
	}
};

/**
 * Constructor for SimplePanel
 * @method simplePanel
 * @param mName string
 * @returns An abstract 2D drawing panel
 */
EJSS_DRAWING2D.simplePanel = function(mName) {
	var self = {}; // reference returned	
	var mGraphics = EJSS_GRAPHICS.svgGraphics(mName); // graphics implementation (default: SVG)

	// Instance variables
	var mStyle = EJSS_DRAWING2D.style(mName);	// style for panel
	var mElements = [];							// elements list for panel
	var mElementsChanged = false;				// whether elements list has changed
	var mCollectersList = [];		            // Array of all control elements that need a call to dataCollected() after data collection

	// Configuration variables	
	var mWorld = {
		// preferred dimensions
		xminPreferred : -1, xmaxPreferred : 1, yminPreferred : -1, ymaxPreferred : 1,
		// origin in panel
		xorigin : 0, yorigin : 0,
		// pixel per unit for panel
		xscale : 1, yscale : 1		
	};

	// Implementation variables
	var mPanelChanged = true;	// whether panel changed (style, decorations, gutters)
	var mMustScale = true;		// whether panel must scale

	// ----------------------------------------
	// Instance functions
	// ----------------------------------------

	/**
	 * Get name for drawing panel
	 * @method getName
	 * @return string
	 */
	self.getName = function() {
		return mName;
	};

	/**
	 * Returns the graphics implementation
	 * @method getGraphics
	 * @return Graphics
	 */
	self.getGraphics = function() {
		return mGraphics;
	};

	/**
	 * Returns the svg image in Base64 format
	 * @method importGraphics
	 * @return string 
	 */
    self.importGraphics = function(callback) {
    	return mGraphics.importSVG(callback);
    }
    
	/**
	 * Return the drawing style of the inner rectangle for panel
	 * @method getStyle
	 * @return Style
	 */
	self.getStyle = function() {
		return mStyle;
	};

	/**
	 * Set graphics
	 * @method setGraphics
	 * @param type
	 */
	self.setGraphics = function(type) {
		if (type == EJSS_DRAWING2D.SimplePanel.GRAPHICS2D_SVG) {
			mGraphics = EJSS_GRAPHICS.svgGraphics(mName);
		} else if (type == EJSS_DRAWING2D.SimplePanel.GRAPHICS2D_CANVAS) {
			// mGraphics = EJSS_GRAPHICS.canvasGraphics(mName);
			console.log("WARNING: setGraphics() - Canvas not supported");
		} else {
			console.log("WARNING: setGraphics() - Graphics not supported");
		}
	};
	
	// ----------------------------------------
	// World coordinates
	// ----------------------------------------

	/**
	 * Sets the preferred minimum X coordinate for the panel
	 * @method setWorldXMin
	 * @param xmin
	 */
	self.setWorldXMin = function(xmin) {
		if (xmin !== mWorld.xminPreferred) {
			mWorld.xminPreferred = xmin;
			mMustScale = true;
		}
	};

	/**
	 * Returns the preferred minimum X coordinate for the panel
	 * @method getWorldXMin
	 * @return double
	 */
	self.getWorldXMin = function() {
		return mWorld.xminPreferred;
	};

	/**
	 * Sets the preferred maximum X coordinate for the panel
	 * @method setWorldXMax
	 * @param xmax
	 */
	self.setWorldXMax = function(xmax) {
		if (xmax !== mWorld.xmaxPreferred) {
			mWorld.xmaxPreferred = xmax;
			mMustScale = true;
		}
	};

	/**
	 * Returns the preferred maximum X coordinate for the panel
	 * @method getWorldXMax
	 * @return double
	 */
	self.getWorldXMax = function() {
		return mWorld.xmaxPreferred;
	};

	/**
	 * Sets the preferred minimum Y coordinate for the panel
	 * @method setWorldYMin
	 * @param ymin
	 */
	self.setWorldYMin = function(ymin) {
		if (ymin !== mWorld.yminPreferred) {
			mWorld.yminPreferred = ymin;
			mMustScale = true;
		}
	};

	/**
	 * Returns the preferred minimum Y coordinate for the panel
	 * @method getWorldYMin
	 * @return double
	 */
	self.getWorldYMin = function() {
		return mWorld.yminPreferred;
	};

	/**
	 * Sets the preferred maximum Y coordinate for the panel
	 * @method setWorldYMax
	 * @param ymax
	 */
	self.setWorldYMax = function(ymax) {
		if (ymax !== mWorld.ymaxPreferred) {
			mWorld.ymaxPreferred = ymax;
			mMustScale = true;
		}
	};

	/**
	 * Returns the preferred maximum Y coordinate for the panel
	 * @method getWorldYMax
	 * @return double
	 */
	self.getWorldYMax = function() {
		return mWorld.ymaxPreferred;
	};

	/**
	 * Sets the preferred user coordinates for the panel
	 * @method setWorldCoordinates
	 * @param bounds
	 */
	self.setWorldCoordinates = function(bounds) {
		self.setWorldXMin(bounds[0]);
		self.setWorldXMax(bounds[1]);
		self.setWorldYMin(bounds[2]);
		self.setWorldYMax(bounds[3]);
	};

	/**
	 * Gets the preferred user coordinates for the panel
	 * @method getWorldCoordinates
	 * @return bounds
	 */
	self.getWorldCoordinates = function() {
		return [self.getWorldXMin(), self.getWorldXMax(), self.getWorldYMin(), self.getWorldYMax()];
	};

	// ----------------------------------------
	// Elements
	// ----------------------------------------

	/**
	 * Add a element to the panel. Elements are asked to draw themselves
	 * whenever the panel needs to render. For this purpose, they will receive a
	 * calls to draw().
	 * Elements are reported of changes in the world coordinates of the panel, in case
	 * they need to recalculate themselves.
	 * @method addElement
	 * @param element Element
	 * @param position int
	 */
	self.addElement = function(element, position) {
		EJSS_TOOLS.addToArray(mElements, element, position);
		// set this panel to decoration element
		element.setPanel(self);
		if (element.dataCollected) mCollectersList.push(element);
		// elements list has changed
		mElementsChanged = true;
	};

	/**
	 * Remove a element to the panel.
	 * @method removeElement
	 * @param element Element
	 */
	self.removeElement = function(element) {
		EJSS_TOOLS.removeFromArray(mElements, element);
		element.setPanel(null);
		if (element.dataCollected) EJSS_TOOLS.removeFromArray(mCollectersList, element);
		// elements list has changed
		mElementsChanged = true;
	};

	/**
	 * Return the array of a elements.
	 * @method getElements
	 * @return Elements
	 */
	self.getElements = function() {
		return mElements;
	};

	/**
	 * Return the position of a element.
	 * @method indexOfElement
	 * @param element Element
	 * @return integer
	 */
	self.indexOfElement = function(element) {
		return mElements.indexOf(element);
	};

	// ----------------------------------------
	// Apply transformations and conversions
	// ----------------------------------------

	/**
	 * Converts a Y pixel value so that 0 is at the bottom
	 * @method toPixelAxisY
	 * @param y double
	 */
	self.toPixelAxisY = function(y) {
		return (mWorld.yorigin - y) - (mWorld.yscale * mWorld.yminPreferred);
	};

	/**
	 * Converts a X pixel value so that 0 is at the left
	 * @method toPixelAxisX
	 * @param x double
	 */
	self.toPixelAxisX = function(x) {
		return (mWorld.xorigin + x) - (mWorld.xscale * mWorld.xminPreferred);
	};

	/**
	 * To be used only after a call to render()!
	 * Projects a point from world coordinates to pixel coordinates.
	 * @method toPixelPosition
	 * @param point double[] The original coordinates
	 * @return double[] The same array once transformed
	 */
	self.toPixelPosition = function(point) {
		var pos = [];
		pos[0] = mWorld.xorigin + mWorld.xscale * (point[0] - mWorld.xminPreferred);		
		pos[1] = mWorld.yorigin + mWorld.yscale * (point[1] - mWorld.yminPreferred);			
		return pos;
	};

	/**
	 * To be used only after a call to render()!
	 * Projects a module from world coordinates to pixel coordinates
	 * @method toPixelMod
	 * @param point double[] The original module
	 * @return double[] The same array once transformed
	 */
	self.toPixelMod = function(mod) {
		var pmod = [];
		pmod[0] = mod[0] * mWorld.xscale;
		pmod[1] = mod[1] * mWorld.yscale;		
		return pmod;
	};

	/**
	 * To be used only after a call to render()!
	 * Projects a point from pixel coordinates to world coordinates
	 * @method toPanelPosition
	 * @param point double[] The original coordinates
	 * @return double[] The same array once transformed
	 */
	self.toPanelPosition = function(point) {	
		var pos = [];
		pos[0] = mWorld.xminPreferred + (point[0] - mWorld.xorigin) / mWorld.xscale;
		pos[1] = mWorld.yminPreferred + (point[1] - mWorld.yorigin) / mWorld.yscale;
		return pos;
	};

	/**
	 * To be used only after a call to render()!
	 * Projects a module from pixel coordinates to world coordinates
	 * @method toPanelMod
	 * @param point double[] The original module
	 * @return double[] The same array once transformed
	 */
	self.toPanelMod = function(mod) {
		var pmod = [];
		pmod[0] = ((mWorld.xscale == 0)? 0 : mod[0]/mWorld.xscale);
		pmod[1] = ((mWorld.yscale == 0)? 0 : mod[1]/mWorld.yscale);		
		return pmod;
	};

	/**
	 * Get pixel position of the origin
	 * @return array pixel position
	 */
	self.getPixelPositionWorldOrigin = function() {
		return [mWorld.xorigin,mWorld.yorigin];
	}

	/**
	 * Recomputes the scales of the panel.
	 * @method recomputeScales
	 */
	self.recomputeScales = function() {	
		// get sizes and scale
		var width = mGraphics.getWidth(); // width in pixels
		var height = mGraphics.getHeight(); // height in pixels
		var xPixPerUnit = width / (mWorld.xmaxPreferred - mWorld.xminPreferred); // the x scale in pixels
		var yPixPerUnit = height / (mWorld.ymaxPreferred - mWorld.yminPreferred); // the y scale in pixels
		
		// centered
		mWorld.xscale = xPixPerUnit;
		mWorld.yscale = -yPixPerUnit;
		mWorld.xorigin =  0.5;
		mWorld.yorigin = height + 0.5;
		
		mMustScale = false;
	};
	
	// ----------------------------------------
	// Drawing functions
	// ----------------------------------------

	/**
	 * Reset the scene
	 * @method reset
	 */
	self.reset = function() {
		mGraphics.reset();
	};
	
	/**
	 * Render the scene
	 * @method render
	 */
	self.render = function() {
		var reseted = false;
		if (mElementsChanged) {// whether elements added or removed, reset the scene
			mGraphics.reset();
			reseted = true;
			mElementsChanged = false;
		}

        // check for data collection
		for (var i = 0, n = mCollectersList.length; i < n; i++)
			mCollectersList[i].dataCollected();

		// recompute scales
		if (mMustScale) 
			self.recomputeScales();

		if (mPanelChanged || reseted) {// whether panel changed or reseted
			mGraphics.drawPanel(self);
		}

		// draw visible elements
		mGraphics.draw(mElements, reseted);

		// set changed to false
		mPanelChanged = false;
		for (var i = 0, n = mElements.length; i < n; i++)
			mElements[i].setChanged(false);
	};

	// ----------------------------------------------------
	// Properties
	// ----------------------------------------------------

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.SimplePanel.registerProperties(self, controller);
	};

	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	mStyle.setLineColor('black');
	mStyle.setFillColor('rgb(239,239,255)');
	mStyle.setChangeListener(function(change) {
		mPanelChanged = true;
	});


	return self;
};

/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Spring
 * @class Spring 
 * @constructor  
 */
EJSS_DRAWING2D.Spring = {
  DEF_RADIUS : 0.05,
  DEF_LOOPS : 8,
  DEF_PPL : 15,

	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy
  	
		dest.setRadius(source.getRadius());
		dest.setSolenoid(source.getSolenoid());
		dest.setThinExtremes(source.getThinExtremes());
		dest.setLoops(source.getLoops());
		dest.setPointsPerLoop(source.getPointsPerLoop());
  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Radius", element.setRadius, element.getRadius);
		controller.registerProperty("Solenoid", element.setSolenoid, element.getSolenoid);
		controller.registerProperty("ThinExtremes", element.setThinExtremes, element.getThinExtremes);
		controller.registerProperty("Loops", element.setLoops, element.getLoops);
		controller.registerProperty("PointsPerLoop", element.setPointsPerLoop, element.getPointsPerLoop);
	},
};

/**
 * Creates a 2D Spring
 * @method spring
 */
EJSS_DRAWING2D.spring = function(name) {
	var self = EJSS_DRAWING2D.element(name);
  	
	  // Configuration variables
	var mRadius = EJSS_DRAWING2D.Spring.DEF_RADIUS;
	var mSolenoid = 0.0;
	var mThinExtremes = true;
	var mLoops;
	var mPointsPerLoop;

	self.getClass = function() {
		return "ElementSpring";
	}

	/***
	 * Set radius
	 * @method setRadius
 	 * @param radius
	 */
	self.setRadius = function(radius) {
		if(mRadius != radius) {
			mRadius = radius;
			self.setChanged(true);
		}		
	}

	self.getRadius = function() {
		return mRadius;
	}

	self.setSolenoid = function(solenoid) {
		if(mSolenoid != solenoid) {
			mSolenoid = solenoid;
			self.setChanged(true);
		}
	}

	self.getSolenoid = function() {
		return mSolenoid;
	}

	self.setThinExtremes = function(thinExtremes) {
		if(mThinExtremes != thinExtremes) {
			mThinExtremes = thinExtremes;
			self.setChanged(true);
		}
	}

	self.getThinExtremes = function() {
		return mThinExtremes;
	}

	self.setLoops = function(loops) {
		if(mLoops != loops) {
			mLoops = loops;
			self.setChanged(true);
		}
	}

	self.getLoops = function() {
		return mLoops;
	}

	self.setPointsPerLoop = function(pointsPerLoop) {
		if(mPointsPerLoop != pointsPerLoop) {
			mPointsPerLoop = pointsPerLoop;
			self.setChanged(true);
		}
	}

	self.getPointsPerLoop = function() {
		return mPointsPerLoop;
	}

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.Spring.registerProperties(self, controller);
	};

  	self.copyTo = function(element) {
    	EJSS_DRAWING2D.Spring.copyTo(self,element);
  	};

	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

  	self.setSize([0.1,0.1]);
  	self.setLoops(EJSS_DRAWING2D.Spring.DEF_LOOPS);
  	self.setPointsPerLoop(EJSS_DRAWING2D.Spring.DEF_PPL);
	self.setRelativePosition("SOUTH_WEST");
	
	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//SpringSet
//---------------------------------

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * SpringSet
 * @class SpringSet 
 * @constructor  
 */
EJSS_DRAWING2D.SpringSet = {
	
    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING2D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("Radius", 
          function(v) { set.setToEach(function(element,value) { element.setRadius(value); }, v); }
      );
      controller.registerProperty("Solenoid", 
          function(v) { set.setToEach(function(element,value) { element.setSolenoid(value); }, v); }
      );           
      controller.registerProperty("ThinExtremes", 
          function(v) { set.setToEach(function(element,value) { element.setThinExtremes(value); }, v); }
      );           
      controller.registerProperty("Loops", 
          function(v) { set.setToEach(function(element,value) { element.setLoops(value); }, v); }
      );           
      controller.registerProperty("PointsPerLoop", 
          function(v) { set.setToEach(function(element,value) { element.setPointsPerLoop(value); }, v); }
      );           
    }        
};

/**
 * Creates a set of Segments
 * @method springSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.springSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.spring, mName);

  // Static references
  var SpringSet = EJSS_DRAWING2D.SpringSet;		// reference for SpringSet
  
  self.registerProperties = function(controller) {
    SpringSet.registerProperties(self,controller);
  };

  return self;
};/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Shape
 * @class Shape 
 * @constructor  
 */
EJSS_DRAWING2D.Tank = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

      controller.registerProperty("LevelColor",element.setLevelColor);
      controller.registerProperty("Level",element.setLevel);
      controller.registerProperty("Closed",element.setClosed);
    },

    /**
     * static copyTo method, to be used by sets
     */
    copyTo : function(source, dest) {
      EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy

      dest.setLevel(source.getLevel());
      dest.setLevelColor(source.getLevelColor());
    }

};

/**
 * Creates a 2D shape
 * @method shape
 */
EJSS_DRAWING2D.tank = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  var mLevel = 0;
  var mLevelColor = "blue";

  // Implementation variables
  var Tank = EJSS_DRAWING2D.Tank;

  self.getClass = function() {
  	return "ElementTank";
  }

  // ----------------------------------------------------
  // public functions
  // ----------------------------------------------------

  self.setLevel = function(level) {
      mLevel = level;
      self.setChanged(true);
  };

  self.getLevel = function() {
  	return mLevel;
  }

  /**
   * Set the color of the level
   * @param color a stroke style
   */
  self.setLevelColor = function(color) { 
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    if (color!=mLevelColor) {
      mLevelColor = color; 
      self.setChanged(true);
    }
    return self;
  };
    
  /**
   * Get the line color
   */
  self.getLevelColor = function() { 
    return mLevelColor; 
  };
  
  // ----------------------------------------------------
  // Properties and copies
  // ----------------------------------------------------

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    Tank.registerProperties(self,controller);
  };

  /**
   * Copies itself to another element
   * Extended copyTo method. To be used by Sets
   * @method copyTo
   */
  self.copyTo = function(element) {
    Tank.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([0.1,0.1]);
  self.setRelativePosition("CENTER");
  self.getStyle().setFillColor("White");
  self.getStyle().setLineColor("Black");
  self.getStyle().setDrawFill(false);
    
  return self;
};


/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A Text is a 2D drawing element that displays a text 
 * @class EJSS_DRAWING2D.Text
 * @parent EJSS_DRAWING2D.Element
 */
EJSS_DRAWING2D.Text = {
	MODE_TOPDOWN : 0,
	MODE_RIGTHLEFT : 1,
	MODE_DOWNTOP : 2,
	MODE_LEFTRIGHT: 3,
	
	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy  	
  		EJSS_DRAWING2D.Font.copyTo(source.getFont(),dest.getFont());
  		
		dest.setText(source.getText());
		dest.setWritingMode(source.getWritingMode());
  	},
  	
	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

	 	/*** 
	  	* Text
	  	* @property Text 
	  	* @type String
	  	* @default "" 
	  	*/  
		controller.registerProperty("Text", element.setText);
		controller.registerProperty("Framed", element.setFramed);
		controller.registerProperty("WritingMode", element.setWritingMode);

	 	/*** 
	  	* Font description in one declaration
	  	* @property Font 
	  	* @type String - https://www.w3schools.com/cssref/pr_font_font.asp
	  	* @default "20px Arial" 
	  	*/  
		controller.registerProperty("Font", function (e) {
				element.setDrawingSize([-1,-1]);
				element.getFont().setFont(e);
			});

	 	/*** 
	  	* Font family
	  	* @property FontFamily 
	  	* @type String
	  	* @default "Arial" 
	  	*/  
		controller.registerProperty("FontFamily", element.getFont().setFontFamily);

	 	/*** 
	  	* Font size
	  	* @property FontSize 
	  	* @type String
	  	* @default "20" 
	  	*/  
		controller.registerProperty("FontSize", function (e) {
				element.setDrawingSize([-1,-1]);
				element.getFont().setFontSize(e);
			});

		controller.registerProperty("LetterSpacing", element.getFont().setLetterSpacing);

	 	/*** 
	  	* Outline color
	  	* @property OutlineColor 
	  	* @type String
	  	* @default "none" 
	  	*/  
		controller.registerProperty("OutlineColor", element.getFont().setOutlineColor);

		controller.registerProperty("FontWeight", element.getFont().setFontWeight);

	 	/*** 
	  	* Fill color
	  	* @property FillColor 
	  	* @type String
	  	* @default "none" 
	  	*/  
		controller.registerProperty("FillColor", element.getFont().setFillColor);

	 	/*** 
	  	* Font style
	  	* @property FontStyle 
	  	* @type String
	  	* @default "normal" 
	  	*/  
		controller.registerProperty("FontStyle", element.getFont().setFontStyle);

		controller.registerProperty("MarginX", element.getMarginX().setMarginX);
		controller.registerProperty("MarginY", element.getMarginY().setMarginY);
	},
};

/**
 * Creates a 2D Text
 * @method text
 */
EJSS_DRAWING2D.text = function(mName) {
	var self = EJSS_DRAWING2D.element(mName);

	var mText = "";
	var mWritingMode = EJSS_DRAWING2D.Text.MODE_LEFTRIGHT;
	var mFont = EJSS_DRAWING2D.font(mName);	// font for text
	var mFramed = false; 
	var mMarginX = 0;
	var mMarginY = 0;
	
	var mDrawingSize = [-1,-1]; // autocalculated 

	self.getClass = function() {
		return "ElementText";
	}

  	/***
   	* Sets text
   	* @method setText
   	* @visibility public
   	* @param string
   	*/
	self.setText = function(text) {
		text = text + ""; // convert text to string
		if(mText != text) {
			mText = text;
			self.setChanged(true);
			mDrawingSize = [-1,-1];
		}
	}

  	/***
   	* Gets text
   	* @method getText
   	* @visibility public
   	* @return string
   	*/
	self.getText = function() {
		return mText;
	}

	self.setDrawingSize = function(drawingSize) {
		mDrawingSize = drawingSize;
	}

	self.getDrawingSize = function() {
		return mDrawingSize;
	}

	self.getSizeX = function() {		
		return mDrawingSize[0]; 
	}

	self.getSizeY = function() {
		return mDrawingSize[1];
	}

	self.setMarginX = function(margin) {
		if(mMarginX != margin) {
			mMarginX = margin;
			self.setChanged(true);
		}
	}

	self.getMarginX = function() {
		return mMarginX;
	}

	self.setMarginY = function(margin) {
		if(mMarginY != margin) {
			mMarginY = margin;
			self.setChanged(true);
		}
	}

	self.getMarginY = function() {
		return mMarginY;
	}

	self.getFont = function() {
		return mFont;
	}

	self.setFramed = function(framed) {
		if(mFramed != framed) {
			mFramed = framed;
			self.setChanged(true);
		}
	}

	self.getFramed = function() {
		return mFramed;
	}

	self.setWritingMode = function(writemode) {
	    if (typeof writemode == "string") writemode = EJSS_DRAWING2D.Text[writemode.toUpperCase()];
	    if (mWritingMode != writemode) {
	      mWritingMode = writemode;
	      self.setChanged(true);
	    }
	}

	self.getWritingMode = function() {
		return mWritingMode;
	}

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.Text.registerProperties(self, controller);
	};

  	self.copyTo = function(element) {
    	EJSS_DRAWING2D.Text.copyTo(self,element);
  	};

	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

  	mFont.setChangeListener(function (change) { self.setChanged(true); });
	self.setPixelSize(true); 
	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//TextSet
//---------------------------------

/**
 * TextSet
 * @class TextSet 
 * @constructor  
 */
EJSS_DRAWING2D.TextSet = {
	
    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING2D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      
      controller.registerProperty("Text", 
          function(v) { set.setToEach(function(element,value) { element.setText(value); }, v); }
      );
      controller.registerProperty("Framed", 
          function(v) { set.setToEach(function(element,value) { element.setFramed(value); }, v); }
      );           
      controller.registerProperty("WritingMode", 
          function(v) { set.setToEach(function(element,value) { element.setWritingMode(value); }, v); }
      );           
      controller.registerProperty("Font", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFont(value); }, v); }
      );          
      controller.registerProperty("FontFamily", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFontFamily(value); }, v); }
      );           
      controller.registerProperty("FontSize", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFontSize(value); }, v); }
      );           
      controller.registerProperty("LetterSpacing", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setLetterSpacing(value); }, v); }
      );           
      controller.registerProperty("OutlineColor", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setOutlineColor(value); }, v); }
      );           
      controller.registerProperty("FontWeight", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFontWeight(value); }, v); }
      );           
      controller.registerProperty("FillColor", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFillColor(value); }, v); }
      );           
    }         
    
};


/**
 * Creates a set of Texts
 * @method textSet
 * @param mName
 */
EJSS_DRAWING2D.textSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.text, mName);

  // Static references
  var TextSet = EJSS_DRAWING2D.TextSet;		// reference for TextSet

  /**
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   */
  self.registerProperties = function(controller) {
    TextSet.registerProperties(self,controller);
  };

  return self;
};

/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A Trace is a sophistication of the Trail element. 
 * The extra features are the possibility to specify markers that will be displayed at 
 * each point of the trace.
 * @class EJSS_DRAWING2D.Trace 
 * @parent EJSS_DRAWING2D.Trail
 * @constructor  
 */
EJSS_DRAWING2D.Trace = {
  ELLIPSE : 0, 	
  RECTANGLE : 1,	
  AREA : 2,	
  BAR : 3,

	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy  	
  		EJSS_DRAWING2D.Trail.copyTo(source,dest);
  		EJSS_DRAWING2D.Style.copyTo(source.getMarkStyle(),dest.getMarkStyle());
  		
		dest.setMarkType(source.getMarkType());
		dest.setMarkSize(source.getMarkSize());
  	},
  	
	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Trail.registerProperties(element, controller);
		// super class

	 /*** 
	  * Type of marker to draw
	  * @property MarkType 
	  * @type int|String
	  * @values 0="ELLIPSE", 1="RECTANGLE", 2:"AREA", 3:"BAR" 
	  * @default "ELLIPSE" 
	  */ 
	  controller.registerProperty("MarkType", element.setMarkType, element.getMarkType);

	 /*** 
	  * Axis for bar or area markers
	  * @property MarkAxisY 
	  * @type int
	  * @default 0 
	  */ 
	  controller.registerProperty("MarkAxisY", element.setMarkAxisY, element.getMarkAxisY);

	 /*** 
	  * Size of the marker to draw
	  * @property MarkSize 
	  * @type int[2] providing the width and height in pixels
	  * @default [0,0] 
	  */ 
		controller.registerProperty("MarkSize", element.setMarkSize, element.getMarkSize);

	 /*** 
	  * Color for the lines of the markers
	  * @property MarkLineColor
	  * @type String
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "Black"
	  */ 
      	controller.registerProperty("MarkLineColor",  element.getMarkStyle().setLineColor);

	 /*** 
	  * Marker stroke width
	  * @property MarkLineWidth 
	  * @type double
	  * @default 0.5
	  */ 
      	controller.registerProperty("MarkLineWidth",  element.getMarkStyle().setLineWidth);

	 /*** 
	  * Whether the marker lines are drawn
	  * @property MarkDrawLines 
	  * @type boolean
	  * @default true 
	  */ 
      	controller.registerProperty("MarkDrawLines",  element.getMarkStyle().setDrawLines);

	 /*** 
	  * The fill color for the markers
	  * @property MarkFillColor 
	  * @type String
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "Blue"
	  */ 
      	controller.registerProperty("MarkFillColor",  element.getMarkStyle().setFillColor);

	 /*** 
	  * Whether the marker are filled
	  * @property MarkDrawFill 
	  * @type boolean
	  * @default true
	  */ 
      	controller.registerProperty("MarkDrawFill",   element.getMarkStyle().setDrawFill);

	}

};

/**
 * Creates a 2D Trace
 * @method trace
 */
EJSS_DRAWING2D.trace = function(mName) {
	var self = EJSS_DRAWING2D.trail(mName);
 
    // Instance variables
	var mMarkType = EJSS_DRAWING2D.Trace.ELLIPSE;
    var mMarkStyle = EJSS_DRAWING2D.style(mName);	// style for mark
    var mMarkRelativePosition = EJSS_DRAWING2D.Element.CENTER;  
	var mMarkSizeX = 0;
	var mMarkSizeY = 0;
	var mMarkAxisY = 0;
	var mMarkStyleList = [];
		 
	self.getClass = function() {
		return "ElementTrace";
	}

	/***
	 * Set a list of styles
	 * @param list
     * @visibility public
	 */
	self.setMarkStyleList = function(list) {
	    if (typeof list == "undefined" || list === null) { 
	    	mMarkStyleList = [];
	    } else if (Array.isArray(list)) {  // list
	      	mMarkStyleList = list.slice();
	      	self.setChanged(true);
	    } 
	};
	
	/***
	 * Get the list of styles
	 * @method getMarkStyleList()
	 * @return list
     * @visibility public
	 */
	self.getMarkStyleList = function() {
	  return mMarkStyleList;
	};

	/**
	 * Sets the mark relative position of the element with respect to its (x,y) coordinates.
	 */
	self.setMarkRelativePosition = function(position) {
	  if (typeof position == "string") position = EJSS_DRAWING2D.Element[position.toUpperCase()];
	  if (mMarkRelativePosition != position) {
	    mMarkRelativePosition = position;
	  	self.setChanged(true);
	  }
	};
	  
	self.getMarkRelativePosition = function() { 
	  return mMarkRelativePosition;
	};

	/***
	 * Return the style of the mark
	 * @method getMarkStyle() 
     * @visibility public
	 */
	self.getMarkStyle = function() { 
	  return mMarkStyle; 
	};

	/***
	 * Set the size of the mark
	 * @method setMarkSize(size)
	 * @param size int[2]|double[2] array with the size in pixels
     * @visibility public
	 */
	self.setMarkSize = function(size) {
	  if((mMarkSizeX != size[0]) || (mMarkSizeY != size[1])) { 
	  	mMarkSizeX = size[0];
	  	mMarkSizeY = size[1];
	  	self.setChanged(true);
	  }
	};
	
	/***
	 * Get the sizes of the mark
	 * @method getMarkSize()
	 * @return double[]
     * @visibility public
	 */
	self.getMarkSize = function() {
	  return [mMarkSizeX, mMarkSizeY];
	};

	/***
	 * Set the type of the mark
	 * @method setMarkType(type)
	 * @param type int|String One of: "CENTER":0,"NORTH":1,"SOUTH":2,"EAST":3,"WEST":4,"NORTH_EAST":5,"NORTH_WEST":6,
	 *                 "SOUTH_EAST":7,"SOUTH_WEST":8 
     * @visibility public
	 */
	self.setMarkType = function(type) {
	  if (typeof type == "string") type = EJSS_DRAWING2D.Trace[type.toUpperCase()];
	  if(mMarkType != type) {
	  	mMarkType = type;
	  	self.setChanged(true);
	  }
	};
	
	/***
	 * Get the type of the mark
	 * @method getMarkType()
	 * @return int
     * @visibility public
	 */
	self.getMarkType = function() {
	  return mMarkType;
	};

	/***
	 * Set the limit axis for bar or area marks
	 * @method setMarkAxisY()
	 * @param int
     * @visibility public
	 */
	self.setMarkAxisY = function(axisY) {
	  if(mMarkAxisY != axisY) {
	  	mMarkAxisY = axisY;
	  	self.setChanged(true);
	  }
	};
  
	/***
	 * Get the limit axis for bar or area marks
	 * @method getMarkAxisY()
	 * @return int
     * @visibility public
	 */
	self.getMarkAxisY = function() {
	  return mMarkAxisY;
	};
  
   self.formatPoint = function(src) {
   		// override
   		var size = src.length;
   		src[size++] = mMarkType;
   		src[size] = EJSS_DRAWING2D.style();
   		EJSS_DRAWING2D.Style.copyTo(mMarkStyle,src[size++]);
		src[size++] = mMarkSizeX;
		src[size++] = mMarkSizeY;
		src[size] = mMarkAxisY;
		return src;
   }
  
	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.Trace.registerProperties(self, controller);
	};
  
  	self.copyTo = function(element) {
    	EJSS_DRAWING2D.Trace.copyTo(self,element);
  	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//TraceSet
//---------------------------------

/**
 * TraceSet
 * @class TraceSet 
 * @constructor  
 */
EJSS_DRAWING2D.TraceSet = {
  
    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var TrailSet = EJSS_DRAWING2D.TrailSet;
      
      TrailSet.registerProperties(set,controller);
      
	 /*** 
	  * Type of marker to draw
	  * @property MarkType 
	  * @type int|String
	  * @values 0="ELLIPSE", 1="RECTANGLE", 2:"AREA", 3:"BAR" 
	  * @default "ELLIPSE" 
	  */ 
	  controller.registerProperty("MarkType", 
          function(v) { set.setToEach(function(element,value) { element.setMarkType(value); }, v); }
      );
      
	 /*** 
	  * Size of the marker to draw
	  * @property MarkSize 
	  * @type int[2] providing the width and height in pixels
	  * @default [0,0] 
	  */ 
	  controller.registerProperty("MarkSize", 
          function(v) { set.setToEach(function(element,value) { element.setMarkSize(value); }, v); }
      );           
      
	 /*** 
	  * Color for the lines of the markers
	  * @property MarkLineColor
	  * @type String
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "Black"
	  */ 
	  controller.registerProperty("MarkLineColor", 
          function(v) { set.setToEach(function(element,value) { element.getMarkStyle().setLineColor(value); }, v); }
      );   
      
      /*** 
	  * Marker stroke width
	  * @property MarkLineWidth 
	  * @type double
	  * @default 0.5
	  */         
      controller.registerProperty("MarkLineWidth", 
          function(v) { set.setToEach(function(element,value) { element.getMarkStyle().setLineWidth(value); }, v); }
      );           
      
      /*** 
	  * Whether the marker lines are drawn
	  * @property MarkDrawLines 
	  * @type boolean
	  * @default true 
	  */
      controller.registerProperty("MarkDrawLines", 
          function(v) { set.setToEach(function(element,value) { element.getMarkStyle().setDrawLines(value); }, v); }
      );           
      
      /*** 
	  * The fill color for the markers
	  * @property MarkFillColor 
	  * @type String
	  * @see http://www.w3schools.com/cssref/css_colornames.asp
	  * @default "Blue"
	  */
	  controller.registerProperty("MarkFillColor", 
          function(v) { set.setToEach(function(element,value) { element.getMarkStyle().setFillColor(value); }, v); }
      );           
      
      /*** 
	  * Whether the marker are filled
	  * @property MarkDrawFill 
	  * @type boolean
	  * @default true
	  */
	  controller.registerProperty("MarkDrawFill", 
          function(v) { set.setToEach(function(element,value) { element.getMarkStyle().setDrawFill(value); }, v); }
      );           
      
      /*** 
	  * Position of the marker relative to the point 
	  * @property MarkRelativePosition 
	  * @type int|String
	  * @values "CENTER":0,"NORTH":1,"SOUTH":2,"EAST":3,"WEST":4,"NORTH_EAST":5,"NORTH_WEST":6,
	  * "SOUTH_EAST":7,"SOUTH_WEST":8 
	  * @default "CENTER
	  */
	  controller.registerProperty("MarkRelativePosition", 
          function(v) { set.setToEach(function(element,value) { element.setMarkRelativePosition(value); }, v); }
      );           
    }    
};


/**
 * Creates a set of Segments
 * @method traceSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.traceSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.trace, mName);

  // Static references
  var TraceSet = EJSS_DRAWING2D.TraceSet;		// reference for TraceSet
  
  self.registerProperties = function(controller) {
    TraceSet.registerProperties(self,controller);
  };

  return self;
};/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/***
 * A Trail is a two-dimensional drawable that displays a collection of points in the plane. 
 * The points are added to the trail either through the <tt>InputX</tt> and <tt>InputY</tt>
 * properties, or using the element’s <tt>addPoint</tt> or <tt>moveToPoint</tt> functions. 
 * The points are displayed using lines which connect the points, each with the previous one, 
 * thus forming a polygonal line (which looks like a trail). The trail can be disconnected 
 * at some points if the <tt>Connected</tt> property is set temporarily to false, or if 
 * the <tt>moveToPoint</tt> function is used.<br/> 
 * The number of points in a trail can be limited through the <tt>Maximum</tt> property. 
 * Adding points past the limit causes the trail to remove the first points in it.<br/>
 *<br/>
 * Trails can be broken into several segments using the <tt>newSegment</tt> function. 
 * Each segment behaves like a sub-trail, but segments can have different drawing 
 * styles (line width and color). Also, the user can delete the points in the last 
 * segment without affecting the previous segments.
 * @class EJSS_DRAWING2D.Trail 
 * @parent EJSS_DRAWING2D.Element
 * @constructor  
 */
EJSS_DRAWING2D.Trail = {
  NO_CONNECTION : 0, 	// The next point will not be connected to the previous one
  LINE_CONNECTION : 1,	// The next point will be connected to the previous one by a segment

	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy  	
  		
		dest.setActive(source.getActive());
		dest.setNoRepeat(source.getNoRepeat());
		dest.setClearAtInput(source.getClearAtInput());
		dest.setSkip(source.getSkip());
		dest.setInputLabels(source.getInputLabels());
		dest.setMaximumPoints(source.getMaximumPoints());
		dest.setConnectionType(source.getConnectionType());
		dest.setPoints(source.getPoints());
  	},
  	
	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING2D.Element.registerProperties(element, controller);
		// super class

	 /*** 
	  * Whether the trail should accept input points. 
	  * @property Active
	  * @type boolean
	  * @default true 
	  */  
		controller.registerProperty("Active", element.setActive, element.getActive);

	 /*** 
	  * Whether the trail ignores repeated input points 
	  * @property NoRepeat
	  * @type boolean
	  * @default false 
	  */  
		controller.registerProperty("NoRepeat", element.setNoRepeat, element.getNoRepeat);

	 /*** 
	  * Whether the trail clears all points when receiving new input. (Useful if input is an array.)
	  * @property ClearAtInput
	  * @type boolean
	  * @default false 
	  */  
		controller.registerProperty("ClearAtInput", element.setClearAtInput, element.getClearAtInput);

	 /*** 
	  * Whether the trail should display only one oout of many inpout points. 
	  * If Skip is positive, it indicates how many points the trail will take to display one.
	  * @property Skip
	  * @type int
	  * @default 0 
	  */  
		controller.registerProperty("Skip", element.setSkip, element.getSkip);

		controller.registerProperty("ColumnNames", element.setInputLabels, element.getInputLabels);

	 /*** 
	  * The maximum number of points the trail accepts. If more input is received, the trail
	  * will discard the corresponding first points.
	  * @property Maximum
	  * @type int
	  * @default 0 indicating no limit
	  */  
		controller.registerProperty("Maximum", element.setMaximumPoints, element.getMaximumPoints);

		controller.registerProperty("ConnectionType", element.setConnectionType, element.getConnectionType);		

	 /*** 
	  * Whether the next input point should be connected to the previous one. 
	  * @property Connected
	  * @type boolean
	  * @default true 
	  */  		
	  controller.registerProperty("Connected", element.setConnected, element.isConnected);		
		
		controller.registerProperty("Points", element.setPoints, element.getPoints);		
		controller.registerProperty("LastPoint", element.addPoint, element.getLastPoint);

	 /*** 
	  * The next input point for the trail. 
	  * @property Input
	  * @type int[2]|double[2] An array with a pair of [x,y] coordinates, 
	  * or int[n][2]|double[n][2] a double array with n pairs of [x,y] coordinates. 
	  * @default "none" 
	  */ 
	  controller.registerProperty("Input", element.addPoints);

	 /*** 
	  * The X coordinate for the next input point for the trail. 
	  * @property InputX
	  * @type int|double The X coordinate for the input point 
	  * or int[n]|double[n] a double array with the X coordinates for n input points. 
	  * @default "none" 
	  */ 
	  controller.registerProperty("InputX", element.addXPoints);				

	 /*** 
	  * The Y coordinate for the next input point for the trail. 
	  * @property InputY
	  * @type int|double The Y coordinate for the input point 
	  * or int[n]|double[n] a double array with the Y coordinates for n input points. 
	  * @default "none" 
	  */ 
	  controller.registerProperty("InputY", element.addYPoints);
	}
			
};

/**
 * Creates a 2D Trail
 * Trail implements dataCollected(), which makes it a Collector of data 
 * @see _view._collectData
 * @method trail
 */
EJSS_DRAWING2D.trail = function(name) {
	var self = EJSS_DRAWING2D.element(name);
 
    // Configuration variables
	var mActive = true;
	var mNoRepeat = false;
	var mClearAtInput = false;
	var mSkip = 0;
	var mInputLabels = ["x", "y"];	  
	var mMaximum = 0;
	var mConnectionType = EJSS_DRAWING2D.Trail.LINE_CONNECTION;
	
  	// Implementation variables
  	var mCounter = 0;
  	var mIsEmpty = true;
	//  private int firstPoint = 0; // the first point of the current path 
  	var mLastPoint = 0;	
  	var mFlushPoint = 0; // This is the last point which was not added because of the skip parameter  	
  	var mCurrentList = []; 	// The current list of points  	
  	var mSegmentList = []; // The list of past Segments, if any  	
  	var mSegmentStyle = []; // The list of past segment styles, if any
	var mTmpList = []; // The temporal list of new points
	var mTmpLastXAdded = 0;
	var mTmpLastYAdded = 0;
	 
	self.getClass = function() {
		return "ElementTrail";
	}

  /***
   * Sets the active state of the trail. An inactive trail ignores all input. 
   * @method setActive(active)
   * @param active boolean
   * @visibility public
   */
  self.setActive = function(active) {
    if(mActive != active) {
    	mActive = active;
    	self.setChanged(true);
    }
  }
  
  /***
   * Whether the trail is in active mode.
   * @method getActive()
   * @return boolean
   */
  self.getActive = function() { 
  	return mActive; 
  }
  
  /***
   * Sets the no repeat state of the trail.
   * When set, a trail will ignore (x,y) points which equal the last added point.
   * @method setNoRepeat(noRepeat)
   * @param noRepeat boolean
   * @visibility public
   */
  self.setNoRepeat = function (noRepeat) {
    if(mNoRepeat != noRepeat) {
    	mNoRepeat = noRepeat;
    	self.setChanged(true);
    }
  }
  
  /***
   * Whether the trail is in no repeat mode.
   * @method getNoRepeat()
   * @return boolean
   */
  self.getNoRepeat = function() { 
  	return mNoRepeat; 
  }

  /***
   * Sets the trail to clear existing points when receiving 
   * a new point or array of points.
   * @method setClearAtInput(clear)
   * @param clear boolean
   * @visibility public
   */
  self.setClearAtInput = function(clear) {
    if(mClearAtInput != clear) {
    	mClearAtInput = clear;
    	self.setChanged(true);
    }
  }
  
  /***
   * Whether the trail is in clear at input mode.
   * @method getClearAtInput()
   * @return boolean
   * @visibility public
   */
  self.getClearAtInput = function() { 
  	return mClearAtInput; 
  }

  /***
   * Sets the skip parameter. When the skip parameter is larger than zero,
   * the trail only considers one of every 'skip' points. That is, if skip is 3, 
   * the trail will consider only every third point sent to it. 
   * The default is zero, meaning all points must be considered.
   * @method setSkip(skip)
   * @param skip int
   * @visibility public
   */
  self.setSkip = function (skip) {
    if (mSkip != skip) {
      mSkip = skip;
      mCounter = 0;
      self.setChanged(true);
    }
  }
  
  /***
   * Returns the skip parameter of the trail.
   * @method getSkip()
   * @return int
   * @visibility public
   */
  self.getSkip = function () { 
  	return mSkip; 
  }

  /**
   * Sets the labels of the X,Y coordinates when the data is displayed in a table
   * @method setInputLabels(label)
   * @param label
   */
  self.setInputLabels = function (label) {
  	if((mInputLabels[0] != label[0]) || (mInputLabels[1] != label[1])) { 
  		mInputLabels[0] = label[0]; 
  		mInputLabels[1] = label[1]; 
  		self.setChanged(true);
  	}
  }

  /**
   * Returns the labels of the X,Y coordinates
   * @method getInputLabels()
   * @return labels
   */
  self.getInputLabels = function () { 
  	return mInputLabels; 
  }
  
  /***
   * Sets the maximum number of points for the trail.
   * Once the maximum is reached, adding a new point will cause
   * remotion of the first one. This is useful to keep trails
   * down to a reasonable size, since very long trails can slow
   * down the rendering (in certain implementations).
   * If the value is 0 (the default) the trail grows forever
   * without discarding old points.
   * @method setMaximumPoints(maximum)
   * @param maximum int
   * @visibility public
   */
  self.setMaximumPoints = function(maximum) {
  	var max = Math.max(maximum, 2);
  	if(mMaximum != max) {
    	mMaximum = max;
    	self.setChanged(true);
    }
  }

  /***
   * Returns the maximum number of points allowed for the trail
   * @method getMaximumPoints()
   * @return int
   * @visibility public
   */
  self.getMaximumPoints = function() {
    return mMaximum;
  }

  /***
   * Whether to connect next input point with the previous one
   * @method setConnected(connected)
   * @param connected boolean
   * @visibility public
   */
  self.setConnected = function (connected) {
   if (connected) self.setConnectionType(EJSS_DRAWING2D.Trail.LINE_CONNECTION);
   else self.setConnectionType(EJSS_DRAWING2D.Trail.NO_CONNECTION);
  }

  /***
   * Gets the connection state.
   * @method getConnected()
   * @return boolean
   * @visibility public
   */
  self.isConnected = function() {
    return mConnectionType===EJSS_DRAWING2D.Trail.LINE_CONNECTION;
  }

  /**
   * Sets the type of connection for the next point.
   * @method setConnectionType(type)
   * @param type int
   */
  self.setConnectionType = function (type) {
    if (typeof type == "string") type = EJSS_DRAWING2D.Trail[type.toUpperCase()];      	
    if(mConnectionType != type) {
    	mConnectionType = type;
    	self.setChanged(true);
    }
  }

  /**
   * Gets the connection type.
   * @method getConnectionType()
   * @see #setConnectionType(int)
   */
  self.getConnectionType = function() {
    return mConnectionType;
  }

  /***
   * Adds a new point to the trail.
   * @method addPoint(x,y,style)
   * @param x double The X coordinate of the point 
   * 		or point double[] The double[2] array with the coordinates of the point.
   * @param y double The Y coordinate of the point.
   * @param style int an optional connection style: 0 = EJSS_DRAWING2D.Trail.NO_CONNECTION, 1 = EJSS_DRAWING2D.Trail.LINE_CONNECTION
   * @visibility public
   */
  self.addPoint = function (x, y, style) {
    if (!mActive) return;
  	if (mClearAtInput) self.initialize();
    if(x instanceof Array)    	
    	addPoint(x[0], x[1], x[2]);
    else 
    	addPoint (x,y,style);
    self.setChanged(true);
  }


  /***
   * Adds an array of points to the trail.
   * @method addPoints(x,y)
   * @param x double The double[] array with the X coordinates of the points.
   * 	or  point double[][] The double[nPoints][2] array with the coordinates of the points.
   * @param y double The double[] array with the Y coordinates of the points.
   * @visibility public
   * @param len int The number of points to copy.
   * @visibility public
   */
  self.addPoints  = function(x, y, len) {
    if (!mActive) return;
  	if (mClearAtInput) self.initialize();
  	// number of points to copy
  	if (typeof len == "undefined") len = Number.MAX_SAFE_INTEGER;
	if ((typeof y == "undefined") || (y === null)) {
      if(x[0] instanceof Array) {    
    	for (var i=0,n=Math.min(len,x.length); i<n; i++) addPoint (x[i][0],x[i][1],x[i][2]);  		
  	  }
  	  else addPoint (x[0],x[1]);
	}
	else { // y is defined
    	for (var i=0,n=Math.min(len,x.length,y.length); i<n; i++) addPoint (x[i],y[i]);
  	}
    self.setChanged(true);
  }

  /**
   * Sets the array of X points for the input of the trail.
   * @method addXPoints(x)
   */
  self.addXPoints  = function(x) {
    if (!mActive) return;
  	var i = 0;
  	if(x instanceof Array) {
  		// fill points with the values  		
    	for (var n=x.length; i<n; i++) {
    		if(mTmpList[i]) mTmpList[i] = [x[i],mTmpList[i][1],mTmpList[i][2]];
    		else mTmpList[i] = [x[i],mTmpLastYAdded,mConnectionType];
    	}  		
    	mTmpLastXAdded = x[i];
  	}
    else { // only one value
    	if(mTmpList[i]) mTmpList[i] = [x,mTmpList[i][1],mTmpList[i][2]];
    	else mTmpList[i] = [x,mTmpLastYAdded,mConnectionType];
    	i++;
    	mTmpLastXAdded = x;    	
    }

	// fill points with the last value
	while (mTmpList[i]) {
		mTmpList[i] = [mTmpLastXAdded,mTmpList[i][1],mTmpList[i][2]];
		i++;
	}    	
  }

  /**
   * Adds an array of Y points to the trail.
   * @method addYPoints(y)
   */
  self.addYPoints  = function(y) {
    if (!mActive) return;
  	var i = 0;
  	if(y instanceof Array) {
  		// fill points with the values  		
    	for (var n=y.length; i<n; i++) {
    		if(mTmpList[i]) mTmpList[i] = [mTmpList[i][0],y[i],mTmpList[i][2]];
    		else mTmpList[i] = [mTmpLastXAdded,y[i],mConnectionType];
    	}  		
    	mTmpLastYAdded = y[i];
  	}
    else { // only one value
    	if(mTmpList[i]) mTmpList[i] = [mTmpList[i][0],y,mTmpList[i][2]];
    	else mTmpList[i] = [mTmpLastXAdded,y,mConnectionType];
    	i++;
    	mTmpLastYAdded = y;    	
    }

	// fill points with the last value
	while (mTmpList[i]) {
		mTmpList[i] = [mTmpList[i][0],mTmpLastYAdded,mTmpList[i][2]];
		i++;
	}    	
  }

  /**
   * Adds the temporal array of point to the trail.
   * @method dataCollected()
   */
  self.dataCollected  = function() {
  	if (mTmpList.length > 0) {
  	  self.addPoints(mTmpList);
      mTmpList = [];
  	  self.setChanged(true);
  	}
  }  

  /**
   * Sets an array of points to the trail. Equivalent to clear() + addPoints(x,y)
   * @method setPoints(x,y)
   * @param xInput double The double[] array with the X coordinates of the points.
   * 	or  point double[][] The double[nPoints][2] array with the coordinates of the points.
   * @param yInput double The double[] array with the Y coordinates of the points.
   */
  self.setPoints  = function(x, y) {
    if (!mActive) return;
  	self.clear();
  	self.addPoints(x,y);
  	self.setChanged(true);
  }
    
  /***
   * Moves to the new point without drawing.
   * (Equivalent to setting the connection type
   * to NO_CONNECTION and adding one single point, then setting the 
   * type back to its previous value.)
   * @method moveToPoint(x,y)
   * @param x double The X coordinate of the point.
   * 	or point double[] The double[2] array with the coordinates of the point.
   * @param y double The Y coordinate of the point.
   * @visibility public
   */
  self.moveToPoint = function(x, y) { 
    if (!mActive) return;
  	if (mClearAtInput) self.initialize();
  	if(x instanceof Array) 
    	addPoint(x[0], x[1], EJSS_DRAWING2D.Trail.NO_CONNECTION);    
    else	
    	addPoint(x,y, EJSS_DRAWING2D.Trail.NO_CONNECTION);
    self.setChanged(true);
  }

  self.getSegmentsCount = function() {
    return mSegmentList.length; 
  }

  self.getSegmentPoints = function(index) {
    return mSegmentList[index]; 
  }

  self.getSegmentStyle = function(index) {
    return mSegmentStyle[index]; 
  }

  /**
   * Returns current points.
   * @method getCurrentPoints
   * @return points
   */
  self.getCurrentPoints = function () {
    return mCurrentList;
  }
  
  /**
   * Returns all points.
   * @method getPoints
   * @return points
   */
  self.getPoints = function () {
    var points = [];
    var size = mSegmentList.length;          	
	for(var i=0; i<size; i++ )
		points = points.concat(mSegmentList[i]);
	points = points.concat(mCurrentList);

    return points; 
  }

  
  /**
   * Gets last point.
   * @method getLastPoint
   * @return point
   */
  self.getLastPoint = function () {
    return mLastPoint;
  }

  /***
   * Same as clear
   * @method reset()
   * @visibility public
   */
  self.reset = function() {
    self.clear();
  }

  /***
   * Clears all points from all segments of the trail.
   * @method clear()
   * @visibility public
   */
  self.clear = function() {
    mSegmentList = [];
    mSegmentStyle = [];
    self.initialize();
  }

  /***
   * Clears all points from the last segment of the trail.
   * @method clearLastSegment()
   * @visibility public
   */
  self.clearLastSegment = function() {
	if (mCurrentList.length<=0) {
      mSegmentList.pop();
      mSegmentStyle.pop();
	  self.setChanged(true);
    }
	self.initialize();
  }
  
  /***
   * Clears all points from the last segment of the trail, 
   * respecting previous segments.
   * @method initialize()
   * @visibility public
   */
  self.initialize  = function() {
  	  mTmpList = [];
	  mCurrentList = [];	  
	  mFlushPoint = 0;
	  mIsEmpty = true;
	  mCounter = 0;
	  mLastPoint = 0;
	  self.setChanged(true);
  }

  /***
   * Creates a new segment of the trail.
   * @method newSegment()
   * @visibility public
   */
  self.newSegment = function() {
    if (mFlushPoint != 0) {
    	var size = mCurrentList.length;
    	mCurrentList[size] = self.formatPoint(mFlushPoint);
    }
    var size = mSegmentList.length;
    mSegmentList[size] = mCurrentList;
    var segmentStyle = EJSS_DRAWING2D.style(self.getName()+" #"+size);
    EJSS_DRAWING2D.Style.copyTo(self.getStyle(),segmentStyle);
    mSegmentStyle[size] = segmentStyle;

	self.initialize();
  }  
  
  /**
   * Returns bounds for an element
   * @override
   * @method getBounds
   * @return Object{left,rigth,top,bottom}
   */
  self.getBounds = function(element) {
  	var xmin, xmax, ymin, ymax;
  	var points = self.getPoints();
  	
  	// add mTmpList
  	//if (mTmpList.length > 0) points = points.concat(mTmpList);
  	
  	  	
    var len = points.length;
    if(len == 0) {
    	xmin = xmax = ymin = ymax = 0;
    }             
    else {
		xmax = xmin = points[0][0];
		ymax = ymin = points[0][1];    	
		for(var j=1; j<len; j++) {
			var x = points[j][0], y = points[j][1];
			if(x>xmax) xmax=x; if(y>ymax) ymax=y; if(x<xmin) xmin=x; if(y<ymin) ymin=y;
		}
	}    
    var x = self.getX(), y = self.getY();
    var sx = self.getSizeX(), sy = self.getSizeY();
  	var mx = sx/2, my = sy/2;  	
  	var d = self.getRelativePositionOffset(sx,sy);
  	
	return {
		left: ((x+d[0])-mx)+xmin*sx,
		right: ((x+d[0])-mx)+xmax*sx,
		top: ((y+d[1])-my)+ymax*sy,
		bottom: ((y+d[1])-my)+ymin*sy
	}
  };  
  
  function addPoint(_x, _y, _type) {
    //if (!mActive) return;
    if (isNaN(_x) || isNaN(_y)) { 
    	mIsEmpty = true; 
    	return; 
    }
    if (mNoRepeat && mLastPoint[0]==_x && mLastPoint[1]==_y) 
    	return;
	if ((typeof _type == "undefined") || (_type === null))
		_type = mConnectionType;

    mFlushPoint = 0;
    mLastPoint = self.formatPoint([_x,_y,_type]);
    
    if (mSkip > 0) { // Only if the counter is 0      
      if(mCounter > 0) mFlushPoint = self.formatPoint([_x, _y, _type]);      
      if(++mCounter >= mSkip) mCounter = 0;	        
    }
    
    if (mFlushPoint == 0) {
	    if (mMaximum > 2 && mCurrentList.length >= mMaximum) {
	    	mCurrentList.splice(0,1) // remove the firstPoint
	    }
	    
	    mCurrentList[mCurrentList.length] = self.formatPoint([_x, _y, _type]);
	    mIsEmpty = false;
	}
  }

	// override for subclases (for example Trace)
   self.formatPoint = function(src) {
   		return src;   		
   }

	self.registerProperties = function(controller) {
		EJSS_DRAWING2D.Trail.registerProperties(self, controller);
	};
	
  	self.copyTo = function(element) {
    	EJSS_DRAWING2D.Trail.copyTo(self,element);
  	};
  
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	self.setSize([1,1]);
  /*** @property RelativePosition @default "SOUTH_WEST" */  
	self.setRelativePosition("SOUTH_WEST");

	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//TrailSet
//---------------------------------

/**
 * TrailSet
 * @class TrailSet 
 * @constructor  
 */
EJSS_DRAWING2D.TrailSet = {
  
    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING2D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("Active", 
          function(v) { set.setToEach(function(element,value) { element.setActive(value); }, v); }
      );
      controller.registerProperty("NoRepeat", 
          function(v) { set.setToEach(function(element,value) { element.setNoRepeat(value); }, v); }
      );           
      controller.registerProperty("ClearAtInput", 
          function(v) { set.setToEach(function(element,value) { element.setClearAtInput(value); }, v); }
      );           
      controller.registerProperty("Skip", 
          function(v) { set.setToEach(function(element,value) { element.setSkip(value); }, v); }
      );           
      controller.registerProperty("ColumnNames", 
          function(v) { set.setToEach(function(element,value) { element.setInputLabels(value); }, v); }
      );           
      controller.registerProperty("Maximum", 
          function(v) { set.setToEach(function(element,value) { element.setMaximumPoints(value); }, v); }
      );           
      controller.registerProperty("ConnectionType", 
          function(v) { set.setToEach(function(element,value) { element.setConnectionType(value); }, v); }
      );           
      controller.registerProperty("Connected", 
          function(v) { set.setToEach(function(element,value) { element.setConnected(value); }, v); }
      );           
      controller.registerProperty("Points", 
          function(v) { set.setToEach(function(element,value) { element.setPoints(value); }, v); }
      );           
      controller.registerProperty("LastPoint", 
          function(v) { set.setToEach(function(element,value) { element.addPoint(value); }, v); }
      );           
      controller.registerProperty("Input", 
          function(v) { set.setToEach(function(element,value) { element.addPoints(value); }, v); }
      );           
      controller.registerProperty("InputX", 
          function(v) { set.setToEach(function(element,value) { element.addXPoints(value); }, v); }
      );           
      controller.registerProperty("InputY", 
          function(v) { set.setToEach(function(element,value) { element.addYPoints(value); }, v); }
      );           
      
    }    
};


/**
 * Creates a set of Segments
 * @method trailSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING2D.trailSet = function (mName) {
  var self = EJSS_DRAWING2D.elementSet(EJSS_DRAWING2D.trail, mName);

  // Static references
  var TrailSet = EJSS_DRAWING2D.TrailSet;		// reference for TrailSet
  
  // ----------------------------------------------------
  // Set actions
  // ----------------------------------------------------

  /**
   * Resets all elements
   * @method reset
   */
  self.reset = function() {
    var elementList = self.getElements();
    for (var i=0,n=elementList.length;i<n;i++) elementList[i].reset();
  }
  
  /**
   * Initializes all elements
   * @method clear
   */
  self.initialize = function() {
    var elementList = self.getElements();
    for (var i=0,n=elementList.length;i<n;i++) elementList[i].initialize();
  }
  
  /**
   * Clears all elements
   * @method clear
   */
  self.clear = function() {
    var elementList = self.getElements();
    for (var i=0,n=elementList.length;i<n;i++) elementList[i].clear();
  }
  
  // ----------------------------------------------------
  // Custom methods
  // ----------------------------------------------------
  
  /**
   * Creates a new segment of one of the trails in the set
   * @method newSegment
   */
    self.newSegment = function(index) {
      self.getElements()[index].newSegment();
    }
  
  /**
   * Moves to the new point without drawing.
   * (Equivalent to setting the connection type
   * to NO_CONNECTION and adding one single point, then setting the 
   * type back to its previous value.)
   * @method moveToPoint
   * @param index int The index of the trail to move to 
   * @param x double The X coordinate of the point.
   * 	or point double[] The double[2] array with the coordinates of the point.
   * @param y double The Y coordinate of the point.
   */
   self.moveToPoint = function (index, x, y) {
      self.getElements()[index].moveToPoint(x,y);
    }
    
  /**
   * Adds a new point to one of the trails in the set
   * @method addPoint
   * @param index int The index of the trail to add to 
   * @param x double The X coordinate of the point 
   * 		or point double[] The double[2] array with the coordinates of the point.
   * @param y double The Y coordinate of the point.
   * @param style
   */
   self.addPoint = function (index, x, y, style) {
      self.getElements()[index].addPoint(x,y,style);
    }
  
  /**
   * Adds an array of points to one of the trails in the set
   * @method addPoints
   * @param index int The index of the trail to add to 
   * @param x double The double[] array with the X coordinates of the points.
   * 	or  point double[][] The double[nPoints][2] array with the coordinates of the points.
   * @param y double The double[] array with the Y coordinates of the points.
   */
    self.addPoints = function (index, x, y) {
      self.getElements()[index].addPoints(x,y);
    }
  

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  self.registerProperties = function(controller) {
    TrailSet.registerProperties(self,controller);
  };

  return self;
};/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Creates a ColorCoded object for 2D drawing
 * ColorCoded is a utility class that allows drawing a collection of polygons using a color code.
 * Each point in the polygon has a double value associated to it and the drawer uses a color mapper to find color subpolygons 
 * for each of the levels of the mapper
 * 
 */
EJSS_DRAWING2D.colorCoded = function (mNumColors, mPalette) {
  var self = EJSS_DRAWING2D.colorMapper(mNumColors, mPalette);  
    
  // Configuration variables
  var mSymmetricZ = false;
      
  // -------------------------------------
  // Public methods for coloring the tiles
  // -------------------------------------
      
  /**
   * Forces the z-scale to be symmetric about zero.
   * Forces zmax to be positive and zmin=-zmax when in autoscale mode.
   * @method setSymmetricZ
   * @param symmetric
   */
  self.setSymmetricZ = function(symmetric) {
    mSymmetricZ = symmetric;
  }
 
  /**
   * Gets the symmetric z flag.
   * @method isSymmetricZ 
   */
  self.isSymmetricZ = function(){
    return mSymmetricZ;
  }
  
  // -------------------------------------
  // Utility methods
  // -------------------------------------

  /**
   * Computes the extrema and sets the scales for the ColorMapper
   * @method setAutoscale
   * @param values
   */
  self.setAutoscale = function(values) {
    var min = Number.MAX_VALUE;
    var max = Number.MIN_VALUE;
    for (var i=0, n=values.length; i<n; i++) {
//      for (var j=0,m=values[i].length; j<m; j++) {        
//        var value = values[i][j];
		var value = values[i];
        max = Math.max (max, value);
        min = Math.min (min, value);            
//      }
    }
    var ceil = max;
    var floor = min;
    if (mSymmetricZ) {
      ceil = Math.max(Math.abs(min),Math.abs(max));
      floor = -ceil;
    }
    self.setScale(floor, ceil);
  }
 
   /**
   * Computes the extrema and sets the scales for the ColorMapper
   * @method setAutoscale2
   * @param values double[][]
   */
  self.setAutoscaleArray2 = function(values) {
    var min = Number.MAX_VALUE;
    var max = Number.MIN_VALUE;
    for (var i=0, n=values.length; i<n; i++) {
      for (var j=0,m=values[i].length; j<m; j++) {        
        var value = values[i][j];
        max = Math.max (max, value);
        min = Math.min (min, value);            
      }
    }
    var ceil = max;
    var floor = min;
    if (mSymmetricZ) {
      ceil = Math.max(Math.abs(min),Math.abs(max));
      floor = -ceil;
    }
    self.setScale(floor, ceil);
  }
  
     /**
   * Computes the extrema and sets the scales for the ColorMapper
   * @method setAutoscale3
   * @param values double[][][]
   */
  self.setAutoscaleArray3 = function(values) {
    var min = Number.MAX_VALUE;
    var max = Number.MIN_VALUE;
    for (var i=0, n=values.length; i<n; i++) {
      for (var j=0,m=values[i].length; j<m; j++) {        
        for (var k=0,p=values[i][j].length; k<p; k++) {        
          var value = values[i][j][k];
          max = Math.max (max, value);
          min = Math.min (min, value);
        }            
      }
    }
    var ceil = max;
    var floor = min;
    if (mSymmetricZ) {
      ceil = Math.max(Math.abs(min),Math.abs(max));
      floor = -ceil;
    }
    self.setScale(floor, ceil);
  }
  
  return self;	
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

EJSS_DRAWING2D.ColorMapper = {
  CUSTOM : -1,
  SPECTRUM : 0,
  GRAYSCALE : 1,
  DUALSHADE : 2,
  RED : 3,
  GREEN : 4,
  BLUE : 5,
  BLACK : 6,
  WIREFRAME : 7,     // special SurfacePlotter palette
  NORENDER : 8,      // special SurfacePlotter palette
  REDBLUE_SHADE : 9, // special SurfacePlotter palette

  /**
   * Gets a array of colors for use in data visualization.
   *
   * Colors are similar to the colors returned by a color mapper instance.
   * @param numColors
   * @param paletteType
   * @return
   */
  getColorPalette : function(numColors,paletteType) {

	function HSB2RGB (h,s,b){
		var hsb = { "h": Math.round(360*h), "s": s, "b": b };
		var rgb = { "r": 0, "g": 0, "b": 0 };
 
		if (hsb.s === 0){
			rgb.r = rgb.g = rgb.b = hsb.h;
			return rgb;
		}else{
			var Hi = Math.floor(hsb.h/60),
				f = (hsb.h/60) - Hi,
				p = hsb.b * (1 - hsb.s),
				q = hsb.b * (1 - (f * hsb.s)),
				t = hsb.b * (1 - (1 - f) * hsb.s);
			switch(Hi){
				case 0: rgb.r = hsb.b; rgb.g = t; rgb.b = p; break;
				case 1: rgb.r = q; rgb.g = hsb.b; rgb.b = p; break;
				case 2: rgb.r = p; rgb.g = hsb.b; rgb.b = t; break;
				case 3: rgb.r = p; rgb.g = q; rgb.b = hsb.b; break;
				case 4: rgb.r = t; rgb.g = p; rgb.b = hsb.b; break;
				case 5: rgb.r = hsb.b; rgb.g = p; rgb.b = q; break;
			}
			rgb.r = Math.round(rgb.r * 255);
			rgb.g = Math.round(rgb.g * 255);
			rgb.b = Math.round(rgb.b * 255);
 
			return rgb;
		}
	}
  	
  	var ColorMapper = EJSS_DRAWING2D.ColorMapper;
    var colors = [];    
	
    if(numColors<2) numColors = 2;
    
    for(var i = 0; i < numColors; i++) {
      var level = i/(numColors-1)*0.8;
      var porc1 = 1, porc2 = 1;
      var r = 0, g = 0, b = 0;
      switch(paletteType) {
         case ColorMapper.REDBLUE_SHADE :
           r = Math.floor((Math.max(0, -numColors-1+i*2)*255)/(numColors-1));           
           b = Math.floor((Math.max(0, numColors-1-i*2)*255)/(numColors-1));
           colors[i] = "rgb("+r+","+g+","+b+")";
           break;
         case ColorMapper.SPECTRUM :
           level = 0.8-level;     
           var rgb = HSB2RGB (level,porc1,porc2);      
           colors[i] = "rgb("+rgb.r+","+rgb.g+","+rgb.b+")";
           break;
         case ColorMapper.GRAYSCALE :
         case ColorMapper.BLACK :
           r = g = b = Math.floor(i*255/(numColors-1));
           colors[i] = "rgb("+r+","+g+","+b+")";           
           break;
         case ColorMapper.RED :
           r = Math.floor(i*255/(numColors-1));
           colors[i] = "rgb("+r+","+g+","+b+")";
           break;
         case ColorMapper.GREEN :
           g = Math.floor(i*255/(numColors-1));
           colors[i] = "rgb("+r+","+g+","+b+")";
           break;
         case ColorMapper.BLUE :
           b = Math.floor(i*255/(numColors-1));
           colors[i] = "rgb("+r+","+g+","+b+")";
           break;
         case ColorMapper.DUALSHADE :
         default :
           var tmp = i/(numColors-1);
           level = 0.8 * (1-tmp);
           porc2 = 0.2 + 1.6 * Math.abs(0.5-tmp);
           var rgb = HSB2RGB (level,porc1,porc2);      
           colors[i] = "rgb("+rgb.r+","+rgb.g+","+rgb.b+")";
           break;
      }
    }
    return colors;
  }

};

/**
 * Creates a Font object for 2D drawing
 */
EJSS_DRAWING2D.colorMapper = function (mNumColors, mPalette) {
  var ColorMapper = EJSS_DRAWING2D.ColorMapper;
  var self = {};  
  
  var mColors = [];  
  var mFloorColor = "darkgray";
  var mCeilColor = "lightgray";
  var mFloor = -1;
  var mCeil = 1;
  var mChangeListener;    
  var mPaletteType;
  var mThresholds;
      
  /**
   * Set a listener that will be called whenever there are Font changes.
   * I.e. a call to listener("change"); will be issued
   */
  self.setChangeListener = function(listener) {
    mChangeListener = listener;
  };
  
  /**
   * Sets the color palette.
   * @param _paletteType
   */
  self.setPaletteType = function(_paletteType) {
	if (typeof _paletteType == "string") _paletteType = EJSS_DRAWING2D.ColorMapper[_paletteType.toUpperCase()];
	mPaletteType = _paletteType;
    mFloorColor = "darkgray";
    mCeilColor = "lightgray";
    if((mPaletteType == EJSS_DRAWING2D.ColorMapper.GRAYSCALE) || 
    	(mPaletteType == EJSS_DRAWING2D.ColorMapper.BLACK)) {
      mFloorColor = "rgb(64,64,128)";
      mCeilColor = "rgb(255,191,191)";
    }
    mNumColors = Math.max(2, mNumColors); // need at least 2 colors
    mColors = ColorMapper.getColorPalette(mNumColors, mPaletteType);
    if (mChangeListener) mChangeListener("palette");
  }

  /**
   * Gets the colors;
   * @return
   */
  self.getColors = function() {
    return mColors;
  }

  /**
   * Gets the floor color;
   * @return
   */
  self.getFloorColor = function() {
    return mFloorColor;
  }

  /**
   * Gets the ceiling color.
   * @return
   */
  self.getCeilColor = function() {
    return mCeilColor;
  }

  /**
   * Gets the number of colors between the floor and ceiling values.
   * @return
   */
  self.getNumColors = function() {
    return mNumColors;
  }

  /**
   * Sets the floor and ceiling colors.
   *
   * @param _floorColor
   * @param _ceilColor
   */
  self.setFloorCeilColor = function(_floorColor,_ceilColor) {
    mFloorColor = _floorColor;
    mCeilColor = _ceilColor;
  }

  /**
   * Sets the floor and ceiling index.
   *
   * @param _floor
   * @param _ceil
   */
  self.setScale = function(_floor,_ceil) {
    mFloor = _floor;
    mCeil = _ceil;
    
    mThresholds = new Array(mColors.length + 1);
    var delta = (mCeil-mFloor) / mColors.length;
    for (var i=0,n=mColors.length; i<n; i++) mThresholds[i] = mFloor + i*delta;
    mThresholds[mColors.length] = mCeil;
  }

  /**
   * Returns the color palette.
   * @return mode
   */
  self.getPaletteType = function() {
    return mPaletteType;
  }

  /**
   * Sets the color palette.
   * @param _colors
   */
  self.setColorPalette = function(_colors) {  	
  	mFloorColor = "darkgray";
  	mCeilColor = "lightgray";
    mColors = _colors;
    mNumColors = _colors.length;
    mPaletteType = EJSS_DRAWING2D.ColorMapper.CUSTOM;
    if (mChangeListener) mChangeListener("colors");
  }

  /**
   * Sets the number of colors
   * @param _numColors
   */
  self.setNumberOfColors = function(_numColors) {
    if(_numColors == mNumColors) {
      return;
    }
    mNumColors = _numColors;
    if(mPaletteType == EJSS_DRAWING2D.ColorMapper.CUSTOM) {
      var newColors = [];
      for(var i = 0, n = Math.min(colors.length, mNumColors); i<n; i++) {
        newColors[i] = colors[i];
      }
      for(var i = colors.length; i<numColors; i++) {
        newColors[i] = colors[colors.length-1];
      }
      colors = newColors;
    } else {
      self.setPaletteType(mPaletteType);
    }
    if (mChangeListener) mChangeListener("numColors");
  }

  /**
   * Gets the number of colors
   * @param _numColors
   */
  self.getNumberOfColors = function() {
  	return mNumColors;
  }

 /**
   * Converts a double to an index in the color array.
   * @method doubleToIndex
   * @param value
   * @return the index in the array with the following exceptions:
   * <ul>
   *   <li>-1 if floor color</li>
   *   <li>colors.length if ceil color</li>
   * </ul> 
   */
  self.doubleToIndex = function(value) { 
    if(mFloor-value>Number.MIN_VALUE) {
      return -1;
    } else if(value-mCeil>Number.MIN_VALUE) {
      return mColors.length;
    }
    var index = 0;
    if(mCeil != mFloor)
    	index = Math.floor((mColors.length*(value-mFloor)/(mCeil-mFloor)));
    index = Math.max(0, index);
    return Math.min(index, mColors.length-1);
  }
  
  /**
   * Returns the color for an index
   * @method indexToColor
   */
  self.indexToColor = function(index) { 
    if (index<0) return mFloorColor;
    if (index>=mColors.length) return mCeilColor;
    return mColors[index];
  }

  /**
   * Returns the color for an index
   * @method indexToColor
   */
  self.doubleToColor = function(value) { return self.indexToColor(self.doubleToIndex(value)); } 
  
  /**
   * Returns the thresholds for color change. One more than colors, includes ceil and floor
   * @method getColorThresholds
   */
  self.getColorThresholds = function() { 
  	return mThresholds;
  }

  self.setPaletteType(mPalette); // default colors
  self.setScale(-1,1);
  return self;	
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Font object for 2D drawing
 * @class Font 
 * @constructor  
 */
EJSS_DRAWING2D.Font = {
  FONTSTYLES: ["normal", "italic", "oblique", "initial"],
  FONTWEIGHTS: ["normal", "lighter", "bold", "bolder", "initial", 
  	"100", "200", "300", "400", "500", "600", "700", "800", "900"],

  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * Copies one element into another
   */
  copyTo : function(source, dest) {
  	dest.setFontFamily(source.getFontFamily());
  	dest.setFontSize(source.getFontSizeString());
	dest.setLetterSpacing(source.getLetterSpacing());
	dest.setOutlineColor(source.getOutlineColor());
	dest.setOutlineWidth(source.getOutlineWidth());
	dest.setFontWeight(source.getFontWeight());
	dest.setFillColor(source.getFillColor());  	
  },

};

/**
 * Creates a Font object for 2D drawing
 */
EJSS_DRAWING2D.font = function (mName) {
  var self = {};
  
  var mFontFamily = "Arial"; 	// the type of font to use, for instance 'Arial' or 'Verdana'.
  var mFontSize = "20";			// the size of the font, for instance '12' or '24'.
  var mLetterSpacing = "normal";// spacing between letters, for instance '2' or '3'. Similar to kerning.
  var mLineColor = "none";		// the outline color of the font. By default text only has fill color, not stroke.
  var mLineWidth = "1";			// the outline width of the font. By default 1.
  var mFontWeight = "normal";	// the weight of the font.
  var mFillColor = "black";		// the fill color of the font. 	    
  var mFontStyle = "none";
  var mChangeListener;           
      
  /**
   * Set a listener that will be called whenever there are Font changes.
   * I.e. a call to listener("change"); will be issued
   */
  self.setChangeListener = function(listener) {
    mChangeListener = listener;
  };

  /**
   * Set the family of the font.
   * @method setFontFamily
   * @param fontFamily
   */
  self.setFontFamily = function(fontFamily) {
    if (mFontFamily != fontFamily) {
      mFontFamily = fontFamily;
      if (mChangeListener) mChangeListener("fontFamily");
    }
  };
      
  /**
   * Get the family of the font.
   * @method getFontFamily
   * @return font family
   */
  self.getFontFamily = function() { 
    return mFontFamily; 
  };

  /**
   * Set the size of the font.
   * @method setFontSize
   * @param fontSize
   */
  self.setFontSize = function(fontSize) {
    if (mFontSize != fontSize) {
      mFontSize = fontSize;
      if (mChangeListener) mChangeListener("fontSize");
    }
  };
      
  /**
   * Get the size of the font in pixels.
   * @method getFontSize
   * @return font size
   */
  self.getFontSize = function() {
  	var size;
  	if(!isNaN(mFontSize)) { 
  		size = +mFontSize; // number
    } else if(mFontSize.indexOf("em") != -1) {
    	size = 	+(mFontSize.substr(0,mFontSize.indexOf("em"))) * 10;	// aprox
    } else if(mFontSize.indexOf("px") != -1) { 
    	size = +(mFontSize.substr(0,mFontSize.indexOf("px"))); // px
    } else if(mFontSize.indexOf("vh") != -1) { 
		var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    	size = (h/100)*(+(mFontSize.substr(0,mFontSize.indexOf("vh")))); // vh    	
    } else if(mFontSize.indexOf("vw") != -1) { 
		var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    	size = (w/100)*(+(mFontSize.substr(0,mFontSize.indexOf("vw")))); // vw
    } else {
    	size = 0; // for example, rem
    }
    
    return size; 
  };

  /**
   * Get the size of the font as a String
   * @method getFontSizeString
   * @return font size as a String
   */
  self.getFontSizeString = function() { 
    return mFontSize; 
  };

  /**
   * Set the letter spacing of the font.
   * @method setLetterSpacing
   * @param letterSpacing
   */
  self.setLetterSpacing = function(letterSpacing) {
    if (mLetterSpacing != letterSpacing) {
      mLetterSpacing = letterSpacing;
      if (mChangeListener) mChangeListener("letterSpacing");
    }
  };

  /**
   * Get the letter spacing of the font.
   * @method getLetterSpacing
   * @return letter spacing
   */
  self.getLetterSpacing = function() {
    return mLetterSpacing;
  };
      
  /**
   * Get the letter spacing of the font.
   * @method getNumberLetterSpacing
   * @return letter spacing
   */
  self.getNumberLetterSpacing = function() {
  	var spacing;
  	if(!isNaN(mLetterSpacing)) { 
  		spacing = +mLetterSpacing; // number
    } else if(mLetterSpacing.indexOf("px") != -1) { 
    	spacing = +(mLetterSpacing.substr(0,mLetterSpacing.indexOf("px"))); // px
    } else {
    	spacing = 1; // for example, normal
    }
    return spacing; 
  };

  /**
   * Set the line color of the font.
   * @method setOutlineColor
   * @param lineColor
   */
  self.setOutlineColor = function(lineColor) {
    if (mLineColor != lineColor) {
      mLineColor = lineColor;
      if (mChangeListener) mChangeListener("lineColor");
    }
  };
      
  /**
   * Get the line color of the font.
   * @method getOutlineColor
   * @return line color
   */
  self.getOutlineColor = function() { 
    return mLineColor; 
  };

  /**
   * Set the line width of the font.
   * @method setOutlineWidth
   * @param lineWidth
   */
  self.setOutlineWidth = function(lineWidth) {
    if (mLineWidth != lineWidth) {
      mLineWidth = lineWidth;
      if (mChangeListener) mChangeListener("lineWidth");
    }
  };
      
  /**
   * Get the line color of the font.
   * @method getOutlineWidth
   * @return line width
   */
  self.getOutlineWidth = function() { 
    return mLineWidth; 
  };

  /**
   * Set the weight of the font.
   * @method setFontWeight
   * @param fontWeight
   */
  self.setFontWeight = function(fontWeight) {
	  fontWeight = fontWeight.toLowerCase(); 

    if (mFontWeight != fontWeight && (EJSS_DRAWING2D.Font.FONTWEIGHTS.indexOf(fontWeight) > -1)) {
      mFontWeight = fontWeight;
      if (mChangeListener) mChangeListener("fontWeight");
    }
  };
      
  /**
   * Get the weight of the font.
   * @method getFontWeight
   * @return fontWeight
   */
  self.getFontWeight = function() { 
    return mFontWeight; 
  };

  /**
   * Set the fill color of the font.
   * @method setFillColor
   * @param fillColor
   */
  self.setFillColor = function(fillColor) {
    if (mFillColor != fillColor) {
      mFillColor = fillColor;
      if (mChangeListener) mChangeListener("fillColor");
    }
  };
      
  /**
   * Get the fill color of the font.
   * @method getFillColor
   * @return fill color
   */
  self.getFillColor = function() { 
    return mFillColor; 
  };
  
  /**
   * Set the font style (normal, italic, oblique, initial, none).
   * @method setFontStyle
   * @param fontStyle
   */
  self.setFontStyle = function(fontstyle) {
	fontstyle = fontstyle.toLowerCase(); 
    if (mFontStyle != fontstyle && (EJSS_DRAWING2D.Font.FONTSTYLES.indexOf(fontstyle) > -1)) {
      mFontStyle = fontstyle;
      if (mChangeListener) mChangeListener("fontstyle");
    }
  };
      
  /**
   * Get the font style.
   * @method getFontStyle
   * @return fontStyle
   */
  self.getFontStyle = function() { 
    return mFontStyle; 
  };

  /**
   * @method getFont
   * @return font
   */
  self.getFont = function() {
  	return mFontStyle + " " + mLineWidth + " " + mFontSize + " " + mFontFamily;
  };

  /**
   * @method setFont
   * @param recap string, format: [style weight size[/lineHeight] [family]]
   */ 
  self.setFont = function (recap) {  
    if ((typeof(recap) !== 'string') || recap.length<=0) return;	
    var params = recap.split(" ");        
   	mFontStyle = params[0];		// style   	
   	mFontWeight = params[1];	// weight	
	var sizes = params[2].split("/");   
   	mFontSize = sizes[0];			// size
   	// ignoring sizes[1] lineHeight   	
   	if (params[3]) 
   		mFontFamily = recap.substring(recap.indexOf(params[3])); // family  
  }
      

  
  //---------------------------------
  // final initialization
  //---------------------------------
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 *
 *  
 * This code is Open Source and is provided "as is".
 */

/*
 Based on the parser by Matthew Crumley (email@matthewcrumley.com, http://silentmatt.com/)
*/

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};
// TODO: "Math.rint", "Math.toDegrees", "Math.toRadians", "Math.IEEEremainder"

EJSS_DRAWING2D.functionsParser = function() {
	var self = {};
	  	
	function object(o) {
		function F() {}
		F.prototype = o;
		return new F();
	}

	var TNUMBER = 0;
	var TOP1 = 1;
	var TOP2 = 2;
	var TVAR = 3;
	var TFUNCALL = 4;

	function Token(type_, index_, prio_, number_) {
		this.type_ = type_;
		this.index_ = index_ || 0;
		this.prio_ = prio_ || 0;
		this.number_ = (number_ !== undefined && number_ !== null) ? number_ : 0;
		this.toString = function () {
			switch (this.type_) {
			case TNUMBER:
				return this.number_;
			case TOP1:
			case TOP2:
			case TVAR:
				return this.index_;
			case TFUNCALL:
				return "CALL";
			default:
				return "Invalid Token";
			}
		};
	}

	function Expression(tokens, ops1, ops2, functions) {
		this.tokens = tokens;
		this.ops1 = ops1;
		this.ops2 = ops2;
		this.functions = functions;
	}

	// Based on http://www.json.org/json2.js
    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\'\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            "'" : "\\'",
            '\\': '\\\\'
        };

	function escapeValue(v) {
		if (typeof v === "string") {
			escapable.lastIndex = 0;
	        return escapable.test(v) ?
	            "'" + v.replace(escapable, function (a) {
	                var c = meta[a];
	                return typeof c === 'string' ? c :
	                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
	            }) + "'" :
	            "'" + v + "'";
		}
		return v;
	}

	Expression.prototype = {
		simplify: function (values) {
			values = values || {};
			var nstack = [];
			var newexpression = [];
			var n1;
			var n2;
			var f;
			var L = this.tokens.length;
			var item;
			var i = 0;
			for (i = 0; i < L; i++) {
				item = this.tokens[i];
				var type_ = item.type_;
				if (type_ === TNUMBER) {
					nstack.push(item);
				}
				else if (type_ === TVAR && (item.index_ in values)) {
					item = new Token(TNUMBER, 0, 0, values[item.index_]);
					nstack.push(item);
				}
				else if (type_ === TOP2 && nstack.length > 1) {
					n2 = nstack.pop();
					n1 = nstack.pop();
					f = this.ops2[item.index_];
					item = new Token(TNUMBER, 0, 0, f(n1.number_, n2.number_));
					nstack.push(item);
				}
				else if (type_ === TOP1 && nstack.length > 0) {
					n1 = nstack.pop();
					f = this.ops1[item.index_];
					item = new Token(TNUMBER, 0, 0, f(n1.number_));
					nstack.push(item);
				}
				else {
					while (nstack.length > 0) {
						newexpression.push(nstack.shift());
					}
					newexpression.push(item);
				}
			}
			while (nstack.length > 0) {
				newexpression.push(nstack.shift());
			}

			return new Expression(newexpression, object(this.ops1), object(this.ops2), object(this.functions));
		},

		substitute: function (variable, expr) {
			if (!(expr instanceof Expression)) {
				expr = new Parser().parse(String(expr));
			}
			var newexpression = [];
			var L = this.tokens.length;
			var item;
			var i = 0;
			for (i = 0; i < L; i++) {
				item = this.tokens[i];
				var type_ = item.type_;
				if (type_ === TVAR && item.index_ === variable) {
					for (var j = 0; j < expr.tokens.length; j++) {
						var expritem = expr.tokens[j];
						var replitem = new Token(expritem.type_, expritem.index_, expritem.prio_, expritem.number_);
						newexpression.push(replitem);
					}
				}
				else {
					newexpression.push(item);
				}
			}

			var ret = new Expression(newexpression, object(this.ops1), object(this.ops2), object(this.functions));
			return ret;
		},

		evaluate: function (values) {
			values = values || {};
			var nstack = [];
			var n1;
			var n2;
			var f;
			var L = this.tokens.length;
			var item;
			var i = 0;
			for (i = 0; i < L; i++) {
				item = this.tokens[i];
				var type_ = item.type_;
				if (type_ === TNUMBER) {
					nstack.push(item.number_);
				}
				else if (type_ === TOP2) {
					n2 = nstack.pop();
					n1 = nstack.pop();
					f = this.ops2[item.index_];
					nstack.push(f(n1, n2));
				}
				else if (type_ === TVAR) {
					if (item.index_ in values) {
						nstack.push(values[item.index_]);
					}
					else if (item.index_ in this.functions) {
						nstack.push(this.functions[item.index_]);
					}
					else {
						throw new Error("undefined variable: " + item.index_);
					}
				}
				else if (type_ === TOP1) {
					n1 = nstack.pop();
					f = this.ops1[item.index_];
					nstack.push(f(n1));
				}
				else if (type_ === TFUNCALL) {
					n1 = nstack.pop();
					f = nstack.pop();
					if (f.apply && f.call) {
						if (Object.prototype.toString.call(n1) == "[object Array]") {
							nstack.push(f.apply(undefined, n1));
						}
						else {
							nstack.push(f.call(undefined, n1));
						}
					}
					else {
						throw new Error(f + " is not a function");
					}
				}
				else {
					throw new Error("invalid Expression");
				}
			}
			if (nstack.length > 1) {
				throw new Error("invalid Expression (parity)");
			}
			return nstack[0];
		},

		toString: function (toJS) {
			var nstack = [];
			var n1;
			var n2;
			var f;
			var L = this.tokens.length;
			var item;
			var i = 0;
			for (i = 0; i < L; i++) {
				item = this.tokens[i];
				var type_ = item.type_;
				if (type_ === TNUMBER) {
					nstack.push(escapeValue(item.number_));
				}
				else if (type_ === TOP2) {
					n2 = nstack.pop();
					n1 = nstack.pop();
					f = item.index_;
					if (toJS && f == "^") {
						nstack.push("Math.pow(" + n1 + "," + n2 + ")");
					}
					else {
						nstack.push("(" + n1 + f + n2 + ")");
					}
				}
				else if (type_ === TVAR) {
					nstack.push(item.index_);
				}
				else if (type_ === TOP1) {
					n1 = nstack.pop();
					f = item.index_;
					if (f === "-") {
						nstack.push("(" + f + n1 + ")");
					}
					else {
						nstack.push(f + "(" + n1 + ")");
					}
				}
				else if (type_ === TFUNCALL) {
					n1 = nstack.pop();
					f = nstack.pop();
					nstack.push(f + "(" + n1 + ")");
				}
				else {
					throw new Error("invalid Expression");
				}
			}
			if (nstack.length > 1) {
				throw new Error("invalid Expression (parity)");
			}
			return nstack[0];
		},

		variables: function () {
			var L = this.tokens.length;
			var vars = [];
			for (var i = 0; i < L; i++) {
				var item = this.tokens[i];
				if (item.type_ === TVAR && (vars.indexOf(item.index_) == -1)) {
					vars.push(item.index_);
				}
			}

			return vars;
		},

		toJSFunction: function (param, variables) {
			var f = new Function(param, "with(Parser.values) { return " + this.simplify(variables).toString(true) + "; }");
			return f;
		}
	};

	function add(a, b) {
		return Number(a) + Number(b);
	}
	function sub(a, b) {
		return a - b; 
	}
	function mul(a, b) {
		return a * b;
	}
	function div(a, b) {
		return a / b;
	}
	function mod(a, b) {
		return a % b;
	}
	function concat(a, b) {
		return "" + a + b;
	}

	function neg(a) {
		return -a;
	}

	function random(a) {
		return Math.random() * (a || 1);
	}
	function fac(a) { //a!
		a = Math.floor(a);
		var b = a;
		while (a > 1) {
			b = b * (--a);
		}
		return b;
	}

	// TODO: use hypot that doesn't overflow
	function pyt(a, b) {
		return Math.sqrt(a * a + b * b);
	}

	function ifelse(a, b, c) {
		if(a) return b; else return c;
	}
	
	function step(a) {
	  if (a<0) return 0;
	  return 1;
	}

	function append(a, b) {
		if (Object.prototype.toString.call(a) != "[object Array]") {
			return [a, b];
		}
		a = a.slice();
		a.push(b);
		return a;
	}

	function greaterthan(a, b) {
		return (a > b)? 1 : 0;
	}

	function lessthan(a, b) {
		return (a < b)? 1 : 0;
	}

	function equalto(a, b) {
		return (a == b)? 1 : 0;
	}

	function notequalto(a, b) {
		return (a != b)? 1 : 0;
	}

	function Parser() {
		this.success = false;
		this.errormsg = "";
		this.expression = "";

		this.pos = 0;

		this.tokennumber = 0;
		this.tokenprio = 0;
		this.tokenindex = 0;
		this.tmpprio = 0;

		this.ops1 = {
			"sin": Math.sin,
			"cos": Math.cos,
			"tan": Math.tan,
			"asin": Math.asin,
			"acos": Math.acos,
			"atan": Math.atan,
			"sqrt": Math.sqrt,
			"log": Math.log,
			"abs": Math.abs,
			"ceil": Math.ceil,
			"floor": Math.floor,
			"round": Math.round,
			"-": neg,
			"neg": neg,
			"exp": Math.exp
		};

		this.ops2 = {
			"+": add,
			"-": sub,
			"*": mul,
			"/": div,
			"%": mod,
			"^": Math.pow,
			",": append,
			"||": concat,
			">": greaterthan,
			"<": lessthan,
			"==": equalto,
			"!=": notequalto
		};

		this.functions = {
			"random": random,
			"fac": fac,
			"min": Math.min,
			"max": Math.max,
			"pyt": pyt,
			"step": step,
			"pow": Math.pow,
			"atan2": Math.atan2,
			"if": ifelse
		};

		this.consts = {
		    //"e": Math.E,
			"E": Math.E,
			"pi": Math.PI,
			"PI": Math.PI
		};
	}

	self.parse = function (expr) {
		return new Parser().parse(expr);
	};

	self.evaluate = function (expr, variables) {
		return Parser.parse(expr).evaluate(variables);
	};

	Parser.Expression = Expression;

	Parser.values = {
		sign: Math.sign,
		sin: Math.sin,
		cos: Math.cos,
		tan: Math.tan,
		asin: Math.asin,
		acos: Math.acos,
		atan: Math.atan,
		sqrt: Math.sqrt,
		log: Math.log,
		abs: Math.abs,
		ceil: Math.ceil,
		floor: Math.floor,
		round: Math.round,
		random: random,
		fac: fac,
		exp: Math.exp,
		min: Math.min,
		max: Math.max,
		pyt: pyt,
		ifelse: ifelse,
		step: step,
		pow: Math.pow,
		atan2: Math.atan2,
		//e: Math.E,
		E: Math.E,
		pi: Math.PI,
		PI: Math.PI
	};

	var PRIMARY      = 1 << 0;
	var OPERATOR     = 1 << 1;
	var FUNCTION     = 1 << 2;
	var LPAREN       = 1 << 3;
	var RPAREN       = 1 << 4;
	var COMMA        = 1 << 5;
	var SIGN         = 1 << 6;
	var CALL         = 1 << 7;
	var NULLARY_CALL = 1 << 8;

	Parser.prototype = {
		parse: function (expr) {
			this.errormsg = "";
			this.success = true;
			var operstack = [];
			var tokenstack = [];
			this.tmpprio = 0;
			var expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
			var noperators = 0;
			this.expression = expr;
			this.pos = 0;

			while (this.pos < this.expression.length) {
				if (this.isOperator()) {
					if (this.isSign() && (expected & SIGN)) {
						if (this.isNegativeSign()) {
							this.tokenprio = 2;
							this.tokenindex = "-";
							noperators++;
							this.addfunc(tokenstack, operstack, TOP1);
						}
						expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
					}
					else if (this.isComment()) {

					}
					else {
						if ((expected & OPERATOR) === 0) {
							this.error_parsing(this.pos, "unexpected operator");
						}
						noperators += 2;
						this.addfunc(tokenstack, operstack, TOP2);
						expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
					}
				}
				else if (this.isNumber()) {
					if ((expected & PRIMARY) === 0) {
						this.error_parsing(this.pos, "unexpected number");
					}
					var token = new Token(TNUMBER, 0, 0, this.tokennumber);
					tokenstack.push(token);

					expected = (OPERATOR | RPAREN | COMMA);
				}
				else if (this.isString()) {
					if ((expected & PRIMARY) === 0) {
						this.error_parsing(this.pos, "unexpected string");
					}
					var token = new Token(TNUMBER, 0, 0, this.tokennumber);
					tokenstack.push(token);

					expected = (OPERATOR | RPAREN | COMMA);
				}
				else if (this.isLeftParenth()) {
					if ((expected & LPAREN) === 0) {
						this.error_parsing(this.pos, "unexpected \"(\"");
					}

					if (expected & CALL) {
						noperators += 2;
						this.tokenprio = -2;
						this.tokenindex = -1;
						this.addfunc(tokenstack, operstack, TFUNCALL);
					}

					expected = (PRIMARY | LPAREN | FUNCTION | SIGN | NULLARY_CALL);
				}
				else if (this.isRightParenth()) {
				    if (expected & NULLARY_CALL) {
						var token = new Token(TNUMBER, 0, 0, []);
						tokenstack.push(token);
					}
					else if ((expected & RPAREN) === 0) {
						this.error_parsing(this.pos, "unexpected \")\"");
					}

					expected = (OPERATOR | RPAREN | COMMA | LPAREN | CALL);
				}
				else if (this.isComma()) {
					if ((expected & COMMA) === 0) {
						this.error_parsing(this.pos, "unexpected \",\"");
					}
					this.addfunc(tokenstack, operstack, TOP2);
					noperators += 2;
					expected = (PRIMARY | LPAREN | FUNCTION | SIGN);
				}
				else if (this.isConst()) {
					if ((expected & PRIMARY) === 0) {
						this.error_parsing(this.pos, "unexpected constant");
					}
					var consttoken = new Token(TNUMBER, 0, 0, this.tokennumber);
					tokenstack.push(consttoken);
					expected = (OPERATOR | RPAREN | COMMA);
				}
				else if (this.isOp2()) {
					if ((expected & FUNCTION) === 0) {
						this.error_parsing(this.pos, "unexpected function");
					}
					this.addfunc(tokenstack, operstack, TOP2);
					noperators += 2;
					expected = (LPAREN);
				}
				else if (this.isOp1()) {
					if ((expected & FUNCTION) === 0) {
						this.error_parsing(this.pos, "unexpected function");
					}
					this.addfunc(tokenstack, operstack, TOP1);
					noperators++;
					expected = (LPAREN);
				}
				else if (this.isVar()) {
					if ((expected & PRIMARY) === 0) {
						this.error_parsing(this.pos, "unexpected variable");
					}
					var vartoken = new Token(TVAR, this.tokenindex, 0, 0);
					tokenstack.push(vartoken);

					expected = (OPERATOR | RPAREN | COMMA | LPAREN | CALL);
				}
				else if (this.isWhite()) {
				}
				else {
					if (this.errormsg === "") {
						this.error_parsing(this.pos, "unknown character");
					}
					else {
						this.error_parsing(this.pos, this.errormsg);
					}
				}
			}
			if (this.tmpprio < 0 || this.tmpprio >= 10) {
				this.error_parsing(this.pos, "unmatched \"()\"");
			}
			while (operstack.length > 0) {
				var tmp = operstack.pop();
				tokenstack.push(tmp);
			}
			if (noperators + 1 !== tokenstack.length) {
				//print(noperators + 1);
				//print(tokenstack);
				this.error_parsing(this.pos, "parity");
			}

			return new Expression(tokenstack, object(this.ops1), object(this.ops2), object(this.functions));
		},

		evaluate: function (expr, variables) {
			return this.parse(expr).evaluate(variables);
		},

		error_parsing: function (column, msg) {
			this.success = false;
			this.errormsg = "parse error [column " + (column) + "]: " + msg;
			throw new Error(this.errormsg);
		},

//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

		addfunc: function (tokenstack, operstack, type_) {
			var operator = new Token(type_, this.tokenindex, this.tokenprio + this.tmpprio, 0);
			while (operstack.length > 0) {
				if (operator.prio_ <= operstack[operstack.length - 1].prio_) {
					tokenstack.push(operstack.pop());
				}
				else {
					break;
				}
			}
			operstack.push(operator);
		},

		isNumber: function () {
			var r = false;
			var str = "";
			while (this.pos < this.expression.length) {
				var code = this.expression.charCodeAt(this.pos);
				if ((code >= 48 && code <= 57) || code === 46) {
					str += this.expression.charAt(this.pos);
					this.pos++;
					this.tokennumber = parseFloat(str);
					r = true;
				}
				else {
					break;
				}
			}
			return r;
		},

		// Ported from the yajjl JSON parser at http://code.google.com/p/yajjl/
		unescape: function(v, pos) {
			var buffer = [];
			var escaping = false;

			for (var i = 0; i < v.length; i++) {
				var c = v.charAt(i);
	
				if (escaping) {
					switch (c) {
					case "'":
						buffer.push("'");
						break;
					case '\\':
						buffer.push('\\');
						break;
					case '/':
						buffer.push('/');
						break;
					case 'b':
						buffer.push('\b');
						break;
					case 'f':
						buffer.push('\f');
						break;
					case 'n':
						buffer.push('\n');
						break;
					case 'r':
						buffer.push('\r');
						break;
					case 't':
						buffer.push('\t');
						break;
					case 'u':
						// interpret the following 4 characters as the hex of the unicode code point
						var codePoint = parseInt(v.substring(i + 1, i + 5), 16);
						buffer.push(String.fromCharCode(codePoint));
						i += 4;
						break;
					default:
						throw this.error_parsing(pos + i, "Illegal escape sequence: '\\" + c + "'");
					}
					escaping = false;
				} else {
					if (c == '\\') {
						escaping = true;
					} else {
						buffer.push(c);
					}
				}
			}
	
			return buffer.join('');
		},

		isString: function () {
			var r = false;
			var str = "";
			var startpos = this.pos;
			if (this.pos < this.expression.length && this.expression.charAt(this.pos) == "'") {
				this.pos++;
				while (this.pos < this.expression.length) {
					var code = this.expression.charAt(this.pos);
					if (code != "'" || str.slice(-1) == "\\") {
						str += this.expression.charAt(this.pos);
						this.pos++;
					}
					else {
						this.pos++;
						this.tokennumber = this.unescape(str, startpos);
						r = true;
						break;
					}
				}
			}
			return r;
		},

		isConst: function () {
			var str;
			for (var i in this.consts) {
				var L = i.length;
				str = this.expression.substr(this.pos, L);
				if (i === str) {
					this.tokennumber = this.consts[i];
					this.pos += L;
					return true;
				}
			}
			return false;
		},

		isOperator: function () {
			var code = this.expression.charCodeAt(this.pos);
			if (code === 43) { // +
				this.tokenprio = 0;
				this.tokenindex = "+";
			}
			else if (code === 45) { // -
				this.tokenprio = 0;
				this.tokenindex = "-";
			}
			else if (code === 124) { // |
				if (this.expression.charCodeAt(this.pos + 1) === 124) {
					this.pos++;
					this.tokenprio = 0;
					this.tokenindex = "||";
				}
				else {
					return false;
				}
			}
			else if (code === 61) { // ==
				if (this.expression.charCodeAt(this.pos + 1) === 61) {
					this.pos++;
					this.tokenprio = 0;
					this.tokenindex = "==";
				}
				else {
					return false;
				}
			}
			else if (code === 33) { // !=
				if (this.expression.charCodeAt(this.pos + 1) === 61) {
					this.pos++;
					this.tokenprio = 0;
					this.tokenindex = "!=";
				}
				else {
					return false;
				}
			}
			else if (code === 42) { // *
				this.tokenprio = 1;
				this.tokenindex = "*";
			}
			else if (code === 47) { // /
				this.tokenprio = 2;
				this.tokenindex = "/";
			}
			else if (code === 37) { // %
				this.tokenprio = 2;
				this.tokenindex = "%";
			}
			else if (code === 94) { // ^
				this.tokenprio = 3;
				this.tokenindex = "^";
			}
			else if (code === 60) { // <
				this.tokenprio = 2;
				this.tokenindex = "<";
			}
			else if (code === 62) { // >
				this.tokenprio = 2;
				this.tokenindex = ">";
			}
			else {
				return false;
			}
			this.pos++;
			return true;
		},

		isSign: function () {
			var code = this.expression.charCodeAt(this.pos - 1);
			if (code === 45 || code === 43) { // -
				return true;
			}
			return false;
		},

		isPositiveSign: function () {
			var code = this.expression.charCodeAt(this.pos - 1);
			if (code === 43) { // -
				return true;
			}
			return false;
		},

		isNegativeSign: function () {
			var code = this.expression.charCodeAt(this.pos - 1);
			if (code === 45) { // -
				return true;
			}
			return false;
		},

		isLeftParenth: function () {
			var code = this.expression.charCodeAt(this.pos);
			if (code === 40) { // (
				this.pos++;
				this.tmpprio += 10;
				return true;
			}
			return false;
		},

		isRightParenth: function () {
			var code = this.expression.charCodeAt(this.pos);
			if (code === 41) { // )
				this.pos++;
				this.tmpprio -= 10;
				return true;
			}
			return false;
		},

		isComma: function () {
			var code = this.expression.charCodeAt(this.pos);
			if (code === 44) { // ,
				this.pos++;
				this.tokenprio = -1;
				this.tokenindex = ",";
				return true;
			}
			return false;
		},

		isWhite: function () {
			var code = this.expression.charCodeAt(this.pos);
			if (code === 32 || code === 9 || code === 10 || code === 13) {
				this.pos++;
				return true;
			}
			return false;
		},

		isOp1: function () {
			var str = "";
			for (var i = this.pos; i < this.expression.length; i++) {
				var c = this.expression.charAt(i);
				if (c.toUpperCase() === c.toLowerCase()) {
					if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) {
						break;
					}
				}
				str += c;
			}
			if (str.length > 0 && (str in this.ops1)) {
				this.tokenindex = str;
				this.tokenprio = 5;
				this.pos += str.length;
				return true;
			}
			return false;
		},

		isOp2: function () {
			var str = "";
			for (var i = this.pos; i < this.expression.length; i++) {
				var c = this.expression.charAt(i);
				if (c.toUpperCase() === c.toLowerCase()) {
					if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) {
						break;
					}
				}
				str += c;
			}
			if (str.length > 0 && (str in this.ops2)) {
				this.tokenindex = str;
				this.tokenprio = 5;
				this.pos += str.length;
				return true;
			}
			return false;
		},

		isVar: function () {
			var str = "";
			for (var i = this.pos; i < this.expression.length; i++) {
				var c = this.expression.charAt(i);
				if (c.toUpperCase() === c.toLowerCase()) {
					if (i === this.pos || (c != '_' && (c < '0' || c > '9'))) {
						break;
					}
				}
				str += c;
			}
			if (str.length > 0) {
				this.tokenindex = str;
				this.tokenprio = 4;
				this.pos += str.length;
				return true;
			}
			return false;
		},

		isComment: function () {
			var code = this.expression.charCodeAt(this.pos - 1);
			if (code === 47 && this.expression.charCodeAt(this.pos) === 42) {
				this.pos = this.expression.indexOf("*/", this.pos) + 2;
				if (this.pos === 1) {
					this.pos = this.expression.length;
				}
				return true;
			}
			return false;
		}
	};

	return self;
}
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for 2D drawing.
 * @module 2Ddrawing
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * InteractionTarget class
 * @class InteractionTarget
 * @constructor
 */
EJSS_DRAWING2D.InteractionTarget = {
	ENABLED_FIXED: 0,
	ENABLED_NONE : 0,
	ENABLED_ANY : 1,
	ENABLED_X : 2,
	ENABLED_Y : 3,
	ENABLED_NO_MOVE : 4,

    SENSITIVITY_BOTH : 0,
    SENSITIVITY_HORIZONTAL : 1,
    SENSITIVITY_VERTICAL : 2,
    SENSITIVITY_ANY : 3,
    
    
	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

	/**
	 * Copies one element into another
	 */
	copyTo : function(source, dest) {
		dest.setMotionEnabled(source.getMotionEnabled());
		dest.setAffectsGroup(source.getAffectsGroup());
		dest.setActionCommand(source.getActionCommand());
		dest.setDataObject(source.getDataObject());
		dest.setSensitivity(source.getSensitivity());
	}
};

/**
 * Creates an interaction object for 2D interaction
 */
EJSS_DRAWING2D.interactionTarget = function(mElement, mType, mPosition) {
	var InteractionTarget = EJSS_DRAWING2D.InteractionTarget;
	var self = {};

	var Element = EJSS_DRAWING2D.Element;	// reference for Element

	var mMotionEnabled = InteractionTarget.ENABLED_NONE;
	var mAffectsGroup = false;
	var mCommand = null;
	var mDataObject = null;
	var mSensitivity = 20;		// reference to interact, distance to element center
	var mSensitivityType = InteractionTarget.SENSITIVITY_BOTH;

	/**
	 * Returns the owner element
	 */
	self.getElement = function() {
		return mElement;
	};

	/**
	 * Returns the type of target
	 */
	self.getType = function() {
		return mType;
	};

	/**
	 * Sets the motion capability of the target
	 * @param value One of:
	 * <ul>
	 *   <li> EJSS_DRAWING2D.InteractionTarget.ENABLED_NONE: the target is not responsive</li>
	 *   <li> EJSS_DRAWING2D.InteractionTarget.ENABLED_ANY: any motion (x,y) is allowed</li>
	 *   <li> EJSS_DRAWING2D.InteractionTarget.ENABLED_X: the target only responds to motion in the X direction</li>
	 *   <li> EJSS_DRAWING2D.InteractionTarget.ENABLED_Y: the target only responds to motion in the Y direction</li>
	 *   <li> EJSS_DRAWING2D.InteractionTarget.ENABLED_NO_MOVE: the target fires events but cannot be moved</li>
	 * </ul>
	 * If not specified, then ENABLED_ANY is assumed.
	 */
	self.setMotionEnabled = function(motion) {
		//var result = InteractionTarget.ENABLED_ANY;
		if ( typeof motion == "string") {
			value = InteractionTarget[motion.toUpperCase()];
			mMotionEnabled = (typeof value === 'undefined')?InteractionTarget.ENABLED_NONE:value;
		}
	    else mMotionEnabled = motion;
	};

	/**
	 * Returns the (perhaps partial) interaction capability of the target
	 */
	self.getMotionEnabled = function() {
		return mMotionEnabled;
	};
	
	/**
	 * Sets the sensitivity type
	 * @param value One of:
	 * <ul>
	 *   <li> EJSS_DRAWING2D.InteractionTarget.SENSITIVITY_ANY: the target is selected if (x,y) is close to its position (default)</li>
	 *   <li> EJSS_DRAWING2D.InteractionTarget.SENSITIVITY_HORIZONTAL: the target is selected if x is close to its horizontal position</li>
	 *   <li> EJSS_DRAWING2D.InteractionTarget.SENSITIVITY_VERTICAL: the target is selected if y is close to its vertical position</li>
	 * </ul>
	 */
	self.setSensitivityType = function(sensitivityType) {
		if (typeof sensitivityType == "string")
			mSensitivityType = InteractionTarget[sensitivityType.toUpperCase()];
	    else mSensitivityType = sensitivityType;
	};

	/**
	 * Returns the (perhaps partial) interaction capability of the target
	 */
	self.getSensitivityType = function() {
		return mSensitivityType;
	};

	/**
	 * Returns the (perhaps partial) interaction capability of the target
	 */
	self.isEnabled = function() {
		return mMotionEnabled != InteractionTarget.ENABLED_NONE;
	};
	
	self.setPositionOffset = function(position) { 
		mPosition = position;
	}

	self.getPositionOffset = function() { 
		return mPosition;
	}

	/**
	 * Gets pixel position for interaction target
	 */
	self.getPixelPosition = function() {
		// get position of the element center
		var pos = mElement.getPixelPosition(true);
		var size = mElement.getPixelSizes(true);
		var offset = mElement.getRelativePositionOffset(size);
		var x = pos[0] + offset[0];
		var y = pos[1] + offset[1];

		// get target relative position
		var inverted = mElement.getPanel().getInvertedScaleY();
		var d = Element.getRelativePositionOffset(mPosition, size[0], size[1], inverted);

		return [x - d[0], y - d[1]];
	};
	
	/**
	 * Gets position for interaction target
	 */
	self.getPosition = function() {
		// get position of the element center
		var pos = mElement.getAbsolutePosition(true);
		var size = mElement.getAbsoluteSize();
		// get target relative position
		var inverted = mElement.getPanel().getInvertedScaleY();
		var d = Element.getRelativePositionOffset(mPosition, size[0], size[1], inverted);
		var offset = mElement.getRelativePositionOffset(size);
		var x = pos[0] + offset[0] - d[0];
		var y = pos[1] + offset[1] - d[1];
		return [x,y];
	};
	
	/**
	 * Whether the interaction with the target affects the top-level group
	 * of the element that contains it (instead of only affecting the element).
	 * Default is false.
	 * @param value boolean
	 */
	self.setAffectsGroup = function(value) {
		mAffectsGroup = value;
	};

	/**
	 * Whether the target affects the top-level group
	 * @return boolean
	 */
	self.getAffectsGroup = function() {
		return mAffectsGroup;
	};

	/**
	 * Sets the action command for this target
	 * @param command String
	 */
	self.setSensitivity = function(sense) {
		mSensitivity = sense;
	};

	/**
	 * Returns the action command of this target
	 * @return String
	 */
	self.getSensitivity = function() {
		return mSensitivity;
	};

	/**
	 * Sets the action command for this target
	 * @param command String
	 */
	self.setActionCommand = function(command) {
		mCommand = command;
	};

	/**
	 * Returns the action command of this target
	 * @return String
	 */
	self.getActionCommand = function() {
		return mCommand;
	};

	/**
	 * A place holder for data objects
	 * @param _object Object
	 */
	self.setDataObject = function(object) {
		mDataObject = object;
	};

	/**
	 * Returns the data object
	 * @return Object
	 */
	self.getDataObject = function() {
		return mDataObject;
	};

	return self;
};

/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for 2D drawing.
 * @module 2Ddrawing
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * PanelInteraction class
 * @class PanelInteraction
 * @constructor
 */
EJSS_DRAWING2D.PanelInteraction = {
	TARGET_POSITION : 0,
	TARGET_SIZE : 1
};

/**
 * Constructor for Interaction
 * @param drawing panel
 * @returns An interaction
 */
EJSS_DRAWING2D.panelInteraction = function(mPanel) {
	var self = {};
	// reference returned

	// Static references
	var PanelInteraction = EJSS_DRAWING2D.PanelInteraction;	// reference for PanelInteraction

	var mEnabled = false;		// whether interaction is enabled
	var mStopGestures = false;  // whether gesture propagation is enabled
	var mStopMoveEvents = false;// wheteher move event listener is enabled when not clicking (recomended in CANVAS)
	var mLastPoint = [];		// last position on panel trying interaction
	var mFirstPoint = [];		// first position on panel trying interaction
	var mPreviousPoint = [];	// position previous to last position
	var mOnlyTarget = null;		// only InteractionTarget with interaction
	var mIsPanelOnlyTarget = false;	// whether the target is the panel 
	var mMouseIsDown = false;	// last event is mouse down
	var mLastTime = 0;			// last timestamp for mouse up
	var mPinching = 0;			// pinching (0: nothing, 1: panel, 2: element)
	var mPinchInitDistance = [0,0];		// distance between touches in pinching
	var mPinchRelDistance = [0,0];		// relative distance when move in pinching
	var mZoomDelta = 0;					// zooming delta
	var mCursorTypeForMove = "move"; // type of cursor for move event (ex., "move", "crosshair")

	var mElementInteracted = 0;	// index of interacted element into its element array

	/**
	 * Reload the interaction configuration
	 */
	self.reload = function() {
		mLastPoint = [];
		mFirstPoint = [];		
		mPreviousPoint = [];
		mOnlyTarget = null;		
		mMouseIsDown = false;	
		mLastTime = 0;			
		mPinching = 0;
		mPinchInitDistance = [0,0];
		mPinchRelDistance = [0,0];
		mZoomDelta = 0;
		mElementInteracted = 0;	
		
		mEnabled = !mEnabled;
		self.setEnabled(!mEnabled);
		mStopGestures = !mStopGestures;
		self.setStopGestures(!mStopGestures);			
	}
	
	/**
	 * Return panel
	 * @return drawing panel
	 */
	self.getPanel = function() {
		return mPanel;
	};

	self.getEnabled = function() {
		return mEnabled;
	};

	self.setStopMoveEvents = function(stop) {
		if (mStopMoveEvents != stop) {
			mStopMoveEvents = stop;

			if (mStopMoveEvents) {
				self.setHandler("move", (function() { return true; }));
			} else {
				self.setHandler("move", self.handleMouseMoveEvent);
			}
		}		
	}

	self.setStopGestures = function(stop) {
		if (mStopGestures != stop) {
			mStopGestures = stop;

			if (mStopGestures) {
				self.setHandler("gesturestart", function(e) {e.preventDefault();e.stopPropagation();return false;});
				self.setHandler("gesturechange", function(e) {e.preventDefault();e.stopPropagation();return false;});
				self.setHandler("gestureend", function(e) {e.preventDefault();e.stopPropagation();return false;});
			} else {
				self.setHandler("gesturestart", function() { return true; });
				self.setHandler("gesturechange", function() { return true; });
				self.setHandler("gestureend", function() { return true; });
			}
		}		
	}

	self.setEnabled = function(enabled) {
		if (mEnabled != enabled) {
			mEnabled = enabled;

			if (mEnabled) {
				if (!mStopMoveEvents) {
					self.setHandler("move", self.handleMouseMoveEvent);
				}
				self.setHandler("down", self.handleMouseDownEvent);
				self.setHandler("up", self.handleMouseUpEvent);
				self.setHandler("mousewheel",self.handleMouseWheelEvent);
			} else {
				self.setHandler("move", (function() { return true; }));
				self.setHandler("down", (function() { return true; }));
				self.setHandler("up", (function() { return true; }));
				self.setHandler("mousewheel", (function() { return true; }));
			}
		}
	};
	
	self.setCursorTypeForMove = function(type) {
		mCursorTypeForMove = type;
	}
	
	/**
	 * Return the last interaction point 
	 * @method getInteractionPoint
	 * @return double[]
	 */
	self.getInteractionPoint = function() {
		return mPanel.toPanelPosition(mLastPoint);
	};

	/**
	 * Return the distance between the previous and last interaction
	 * @method getInteractionDistance
	 * @return double[]
	 */
	self.getInteractionDistance = function() {
		if(mPreviousPoint.length == 0 || mLastPoint.length == 0) return [];
		var p = mPanel.toPanelPosition(mPreviousPoint);
		var l = mPanel.toPanelPosition(mLastPoint);
		return [p[0] - l[0], p[1] - l[1]];
	};

	/**
	 * Return the bounds of the last interaction rectangle 
	 * @method getInteractionBounds
	 * @return double[]
	 */
	self.getInteractionBounds = function() {
		if(mFirstPoint.length == 0 || mLastPoint.length == 0) return [];
		var f = mPanel.toPanelPosition(mFirstPoint);
		var l = mPanel.toPanelPosition(mLastPoint);
		return [f[0], l[0], f[1], l[1]];
	};

	/**
	 * Return the zooming delta
	 * @method getInteractionZoomDelta
	 * @return double[]
	 */
	self.getInteractionZoomDelta = function() {
		// zooming via mousewheel or pinching 
		if(mZoomDelta == 0 && mPinchRelDistance[0] == 0) return 0;
		if(mZoomDelta > 0 || mPinchRelDistance[0] > 0) return 1;
		return -1;
	};
	
	/**
	 * Return the only interaction target (only one touch)
	 * @method getInteractionElement
	 * @return element
	 */
	self.getInteractionElement = function() {
		return mOnlyTarget;
	};

  /**
   * Clears the current interaction elementn if it equals the given one
   * (for example, if it was deleted)
   * @method clearInteractionElement
   * @return element
   */
  self.clearInteractionElement = function(element) {
    if (mOnlyTarget && element===mOnlyTarget.getElement()) mOnlyTarget = null;
  };

	/**
	 * Return the screen orientation
	 * @method getOrientation
	 * @return orientation
	 */
	self.getOrientation = function() {
		return window.orientation;
	};
	
	/**
	 * Handler for mouse move event
	 * @method handleMouseMoveEvent
	 * @param event
	 * @param location
	 */
	self.handleMouseMoveEvent = function(event) {
		if(mStopGestures) {
		  // Prevent the browser from doing its default thing (scroll, zoom)
		  event.preventDefault();
		  // prevent the browser from propagation
		  event.stopPropagation();
		}

		// number of fingers over screen	
		var nFingers = (typeof event.touches != "undefined")? event.touches.length:1;

		// manage pinch		 
		if(nFingers == 2) {
    		var e0 = event.touches[0]; var e1 = event.touches[1];
    		var e0x = (e0.clientX || e0.x); var e0y = (e0.clientY || e0.y);
    		var e1x = (e1.clientX || e1.x); var e1y = (e1.clientY || e1.y);
			if(mPinching != 0) {
	    		var newDistance = [Math.abs(e0x-e1x),Math.abs(e0y-e1y)];
	    		var delta = [mPinchInitDistance[0]-newDistance[0],mPinchInitDistance[1]-newDistance[1]]; // used in zooming
	    		if (delta[0]<8 && delta[0]>-8 && delta[1]<8 && delta[1]>-8) { // minimum filter
	    			mPinchRelDistance = [0,0];
	    		} else {
		    		mPinchRelDistance = delta;
		    		mPinchInitDistance = newDistance;
	    		}
			} else {
				mPinching = 1;
	    		mPinchInitDistance = [Math.abs(e0x-e1x),Math.abs(e0y-e1y)];
				mPinchRelDistance = [0,0];			
			}
		} else {
			mPinching = 0;
			mPinchInitDistance = [0,0];
		}
		
		// touch locations 
		var locations = getEventLocations(event);

		// last and previous location		
		mPreviousPoint = mLastPoint;
		mLastPoint = locations[locations.length-1];
		
		if (mMouseIsDown) { // Mouse or finger(s) is down (drag)
			if (mOnlyTarget != null) { // whether mOnlyTarget exists, only one touch and target
				// get element or group
				var element = mOnlyTarget.getAffectsGroup() ? mOnlyTarget.getElement().getGroup() : mOnlyTarget.getElement();
				// register properties changed
				var point = mPanel.toPanelPosition(mLastPoint);
				var refpoint = mPanel.toPanelPosition(mPreviousPoint);
				propertiesChanged(element, mOnlyTarget, point, refpoint);
				// invoke actions
				element.getController().invokeAction("OnDrag");				
				if (mEnabled)// also onMove over panel
					mPanel.getController().invokeAction("OnMove");
			} else if(mIsPanelOnlyTarget) { // the only target is the panel
				if (mEnabled) { // onDrag over panel
					mPanel.getController().invokeAction("OnDrag");
				}
			} else { // multiple touches
				// invoke actions
				var targetHitList = [];
				for(var i=0; i<locations.length; i++) {					
					var targetHit = self.findInteractionTarget(mPanel.getElements(), locations[i]);
					if(targetHit != null) {
						targetHitList.push(targetHit);
						// get element or group
						var element = targetHit.getAffectsGroup() ? targetHit.getElement().getGroup() : targetHit.getElement();
						// register properties changed
						var point = mPanel.toPanelPosition(locations[i]);
						propertiesChanged(element, targetHit, point, targetHit.getPosition());
						// invoke actions
						element.getController().invokeAction("OnDrag");				
						if (mEnabled) // also onMove over panel
							mPanel.getController().invokeAction("OnMove");			
					}
				}
				// actions over panel
				if (mEnabled) { 
					if(mPinching == 0)
						mPanel.getController().invokeAction("OnDrag");
					else if (mPinching == 1 && targetHitList.length == 0) 
						mPanel.getController().invokeAction("OnZoom");						
				}
			}
				
		} else { // Mouse is up (move over panel and elements using mouse)
			var targetHit = self.findInteractionTarget(mPanel.getElements(), locations[0]);
			if (targetHit === null) { // no target
				if (mOnlyTarget != null)// whether mOnlyTarget exists
					mOnlyTarget.getElement().getController().invokeAction("OnExit");
				if (mEnabled) // onMove over panel
					mPanel.getController().invokeAction("OnMove");
				event.target.style.cursor = 'default';
			} else {
				if (mOnlyTarget != targetHit) {
					if (mOnlyTarget != null)// whether mOnlyTarget exists
						mOnlyTarget.getElement().getController().invokeAction("OnExit");
					targetHit.getElement().getController().invokeAction("OnEnter");
				}
				if (targetHit.isEnabled())
					event.target.style.cursor = mCursorTypeForMove;
				else
					event.target.style.cursor = 'default';
			}
			mOnlyTarget = targetHit;				
		}

		// all interactions are reported to View
		mPanel.getController().reportInteractions();
		
		return false;
	};

	/**
	 * Handler for mouse down event
	 * @method handleMouseDownEvent
	 * @param event
	 * @param location
	 */
	self.handleMouseDownEvent = function(event) {
		mMouseIsDown = true;

		if (mStopMoveEvents) {
			// for drag&drop 
			self.setHandler("move", self.handleMouseMoveEvent);
		}
					
		if(mStopGestures) {
		  // prevent the browser from doing its default thing (scroll, zoom)
		  event.preventDefault();
		  // prevent the browser from propagation
		  event.stopPropagation();
		}

		// number of fingers over screen	
		var nFingers = (typeof event.touches != "undefined")? event.touches.length:1;
			
		// manage pinch		 
		if(nFingers != 2) {
			mPinching = 0;
		}
		
		// touch locations 
		var locations = getEventLocations(event);

		// first, previous and last points
		mFirstPoint = locations[0]; // first location
		mPreviousPoint = mFirstPoint;
		mLastPoint = locations[locations.length-1];	// last location
				 
		// invoke actions
		var isPanelHit = false;
		var targetHit = null;
		for(var i=0; i<locations.length; i++) {
			// find target
			targetHit = self.findInteractionTarget(mPanel.getElements(), locations[i]);
			if (targetHit !== null) { // one target
				targetHit.getElement().getController().invokeAction("OnPress");
				event.target.style.cursor = mCursorTypeForMove;
			} else if (mEnabled) { // no target, then onPress over panel
				mPanel.getController().invokeAction("OnPress");
				isPanelHit = true;
			}
		}		
		
		// mOnlyTarget and mIsPanelOnlyTarget are usefull whether only one touch
		if(locations.length == 1 && nFingers == 1) { 					
			// targetHit is null if no target, so the target is the panel 
			mOnlyTarget = targetHit;	// last target
			mIsPanelOnlyTarget = isPanelHit;
		} else {
			// no only target
			mOnlyTarget = null;
			mIsPanelOnlyTarget = false;			
		}
		
		// all interactions are reported to View
		mPanel.getController().reportInteractions();		
		
		return false;
	};

	/**
	 * Handler for mouse up event
	 * @method handleMouseUpEvent
	 * @param event
	 * @param location
	 */
	self.handleMouseUpEvent = function(event) {
		mMouseIsDown = false;

		if (mStopMoveEvents) {
			// for drag&drop
			self.setHandler("move", (function() { return true; }));
		}

		if(mStopGestures) {
		  // prevent the browser from doing its default thing (scroll, zoom)
		  event.preventDefault();
		  // prevent the browser from propagation
		  event.stopPropagation();
		}

		// manage pinch		 
		if(mPinching != 0) { 
    		mPinching = 0;
    		mPinchRelDistance = [0,0];
    		mPinchInitDistance = [0,0];
		}

		// touch locations 
		var locations = getEventLocations(event);

		// last location
		mPreviousPoint = mLastPoint;
		mLastPoint = locations[locations.length-1];
			
		// whether event is dbclick
		var dblclick = (event.timeStamp - mLastTime < 500); // < 500 ms is dblclick
			
		if(mOnlyTarget != null) { // only one touch
			// mOnlyTarget is null if no element touched						
			mOnlyTarget.getElement().getController().invokeAction("OnRelease");
			if (dblclick) mOnlyTarget.getElement().getController().invokeAction("OnDoubleClick");
		} else {				
			// invoke actions		
			for(var i=0; i<locations.length; i++) {
				// find target
				var targetHit = self.findInteractionTarget(mPanel.getElements(), locations[i]);
				if (targetHit !== null) { // one target
					targetHit.getElement().getController().invokeAction("OnRelease");
					if (dblclick) targetHit.getElement().getController().invokeAction("OnDoubleClick");
				} else if (mEnabled) { // no target, then onRelease over panel
					mPanel.getController().invokeAction("OnRelease");
					if (dblclick) mPanel.getController().invokeAction("OnDoubleClick");
				}
			}
		}
				
		// last time to manage double click
		mLastTime = event.timeStamp; 

		// cursor style
		event.target.style.cursor = 'default';

		// all interactions are reported to View
		mPanel.getController().reportInteractions();
		
		return false;
	};

	/**
	 * Handler for mouse wheel event
	 * @method handleMouseWheelEvent
	 * @param event
	*/
	self.handleMouseWheelEvent = function(event) {
		// number of fingers over screen	
		var nFingers = (typeof event.touches != "undefined")? event.touches.length:0;
		
		if(nFingers == 0) {  	
			if(mStopGestures) {
			  // prevent the browser from doing its default thing (scroll, zoom)
			  event.preventDefault();
			  // prevent the browser from propagation
			  event.stopPropagation();
			}
	
			mZoomDelta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));
			mPanel.getController().invokeAction("OnZoom");	
	        
		    // all interactions are reported to View
		    mPanel.getController().reportInteractions();
		}
	};
	
	/**
	 * Find target element for the position
	 * @method findInteractionTarget
	 * @param location double[]
	 * @return double[]
	 */
	self.findInteractionTarget = function(elements, location) {
	    var InteractionTarget = EJSS_DRAWING2D.InteractionTarget;
		var target = null;
		for (var i = elements.length - 1; i >= 0; i--) {
			var element = elements[i];
			if (element.isGroupVisible()) {
				if (element.getElements) {// array of elements
					target = self.findInteractionTarget(element.getElements(), location);
					if (target !== null)
						return target;
				} else {// one element
					// find interaction target for element
					var targets = element.getInteractionTargets();
					for (var j = 0; j < targets.length; j++) {
						if (targets[j].isEnabled()) {
							var sensitivity = targets[j].getSensitivity();
							if (sensitivity > 0) {
								var projectedPosition = targets[j].getPixelPosition();
								var selected = false;
								switch(targets[j].getSensitivityType()) {
								  case InteractionTarget.SENSITIVITY_HORIZONTAL :
								    selected = (Math.abs(projectedPosition[1] - location[1]) < sensitivity); 
								    break;
								  case InteractionTarget.SENSITIVITY_VERTICAL :
								    selected = (Math.abs(projectedPosition[0] - location[0]) < sensitivity); 
								    break;
								  case InteractionTarget.SENSITIVITY_ANY :
								    selected = (Math.abs(projectedPosition[0] - location[0]) < sensitivity || Math.abs(projectedPosition[1] - location[1]) < sensitivity);
								    break;
								  default : // InteractionTarget.SENSITIVITY_BOTH
								    selected = (Math.abs(projectedPosition[0] - location[0]) < sensitivity && Math.abs(projectedPosition[1] - location[1]) < sensitivity);
								    break;
								}
								if (selected) {
									mElementInteracted = i;
									// index of element into element array
									// var pp = element.toGroupSpace(element.getPosition(),true);
									// console.log('pp' + pp[0] + ' ' + pp[1]);
									
									return targets[j];
								}
							}
					    }
					}
					// maybe, any location into element (pinching?)
					if(mPinching != 0) {
						target = element.getInteractionTarget(PanelInteraction.TARGET_SIZE);
						if (target.isEnabled() && !target.getSensitivity()) {
							var rect = element.getAbsoluteBounds();
							var pos = mPanel.toPanelPosition(location);
							if ((rect.left < pos[0]) && (rect.right > pos[0]) && (rect.bottom < pos[1]) && (rect.top > pos[1])) {
								mElementInteracted = i;
								// index of element into element array
								mPinching = 2; 
								return target;
							}
						}
					} 
					// maybe, any location into element (position interaction?)
					target = element.getInteractionTarget(PanelInteraction.TARGET_POSITION);
					if (target.isEnabled() && !target.getSensitivity()) {
						var rect = element.getAbsoluteBounds();
						var pos = mPanel.toPanelPosition(location);
						if ((rect.left < pos[0]) && (rect.right > pos[0]) && (rect.bottom < pos[1]) && (rect.top > pos[1])) {
							mElementInteracted = i;
							// index of element into element array
							return target;
						}
					}						
				}
			}
		}
		return null;
	};

	/**
	 * Return index of interacted element into element array
	 */
	self.getIndexElement = function() {
		return mElementInteracted;
	};
	
	/**
	 * Register properties changed for a motion of the mouse when dragging the element
	 * For subclasses use only!
	 */
	function propertiesChanged(element, target, point, targetPosition) {
		var InteractionTarget = EJSS_DRAWING2D.InteractionTarget;
		var PanelInteraction = EJSS_DRAWING2D.PanelInteraction;

		// reports changes for element
		if (target.getType() === PanelInteraction.TARGET_POSITION) {
    	    var dx = point[0]-targetPosition[0];
    	    var dy = point[1]-targetPosition[1];
			switch (target.getMotionEnabled()) {
				case InteractionTarget.ENABLED_ANY :
					element.setX(element.getX() + dx);
					element.setY(element.getY() + dy);
					element.getController().propertiesChanged("Position", "X", "Y");
					target.getElement().setChanged(true);
					element.setChanged(true);
					break;
				case InteractionTarget.ENABLED_X :
					element.setX(element.getX() + dx);
					element.getController().propertiesChanged("Position", "X");
					target.getElement().setChanged(true);
					element.setChanged(true);
					break;
				case InteractionTarget.ENABLED_Y :
					element.setY(element.getY() + dy);
					element.getController().propertiesChanged("Position", "Y");
					target.getElement().setChanged(true);
					element.setChanged(true);
					break;
			}
		} else if (target.getType() === PanelInteraction.TARGET_SIZE) {
			if(mPinching == 2) {
				// not used targetPosition, directly we have the pinching distance
	    	    var diff = mPanel.toPanelMod(mPinchRelDistance);
	    	    var dx = diff[0];
	    	    var dy = -diff[1];		
			} else {
	    	    var dx = point[0]-targetPosition[0];
	    	    var dy = point[1]-targetPosition[1];
			}
				
		    var targetElement = target.getElement();
			var absSize = targetElement.getAbsoluteSize();
			if (absSize[0]===0) absSize[0] = 1.0e-5;
			if (absSize[1]===0) absSize[1] = 1.0e-5;
			var rsizes = [dx/absSize[0], dy/absSize[1]];
			
			var size = element.getSize();
			if (size[0]===0 && rsizes[0]!==0) size[0] = 1.0e-5;
			if (size[1]===0 && rsizes[0]!==0) size[1] = 1.0e-5;				

			switch (target.getMotionEnabled()) {
				case InteractionTarget.ENABLED_ANY :
					element.setSizeX(size[0] + size[0] * rsizes[0]);
					element.setSizeY(size[1] + size[1] * rsizes[1]);					
					element.getController().propertiesChanged("Size", "SizeX", "SizeY");
					target.getElement().setChanged(true);
					element.setChanged(true);
					break;
				case InteractionTarget.ENABLED_X :
					element.setSizeX(size[0] + size[0] * rsizes[0]);
					element.getController().propertiesChanged("Size", "SizeX");
					target.getElement().setChanged(true);
					element.setChanged(true);
					break;
				case InteractionTarget.ENABLED_Y :
					element.setSizeY(size[1] + size[1] * rsizes[1]);					
					element.getController().propertiesChanged("Size", "SizeY");
					target.getElement().setChanged(true);
					element.setChanged(true);
					break;
			}
		}
	}

	/**
	 * Get locations for the touch events
	 * @method getEventLocations
	 * @param {Object} e
	 */
	function getEventLocations(e) {
		var box = mPanel.getGraphics().getBox();
		var oleft = box.left; // offset left in pixels
		var otop = box.top; // offset top in pixels
		var locations = [];

		if ( typeof e.changedTouches != "undefined") {
			for(var i=0; i<e.changedTouches.length; i++) {
				locations[i] = [];		
				// ignored window.pageYOffset and window.pageXOffset
		    	locations[i][0] = e.changedTouches[i].clientX - oleft;
		    	locations[i][1] = e.changedTouches[i].clientY - otop;
		   }
		} else {
			locations[0] = [];
			locations[0][0] = (e.clientX || e.x) - oleft;
			locations[0][1] = (e.clientY || e.y) - otop;
		}

		return locations;
	}

	self.setHandler = function(type, handler) {
		var graphics = mPanel.getGraphics();
		var context = graphics.getEventContext();
		switch (type) {
			case "move" :
				context.addEventListener('mousemove', handler, false);
				context.addEventListener('touchmove', handler, false);
				break;
			case "down" :
				context.addEventListener('mousedown', handler, false);
				context.addEventListener('touchstart', handler, false);
				break;
			case "up" :
				context.addEventListener('mouseup', handler, false);
				context.addEventListener('touchend', handler, false);
				break;
		   	case "mousewheel" :
		      	context.addEventListener('mousewheel', handler, false);
			  	break;	      

			// not used 
			case "gesturestart" : // Sent when the second finger touches the surface.
				context.addEventListener('gesturestart', handler, false);
				break;
			case "gesturechange" : //  Sent when both fingers move while still touching the surface.
				context.addEventListener('gesturechange', handler, false);
				break;
			case "gestureend" : //  Sent when the second finger lifts from the surface.
				context.addEventListener('gestureend', handler, false);
				break;
			default :
		}
		return false;
	};
	
	// change orientation
	var _super_onorientationchange = window.onorientationchange; 
		
	window.onorientationchange = function() {		
		if(_super_onorientationchange) _super_onorientationchange();
		mPanel.getController().invokeAction("OnOrientationChange");
		mPanel.getController().reportInteractions();
	};
	
	// resize
	var _super_onresize = window.onresize; 
	window.onresize = function() {
		if(_super_onresize) _super_onresize();		
		mPanel.getController().invokeAction("OnResize");
		mPanel.getController().reportInteractions();
	};

	// for default, stop propagation
	self.setStopGestures(true);
	
	return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Style object for 2D drawing
 * @class Style 
 * @constructor  
 */
EJSS_DRAWING2D.Style = {  
  RENDER_AUTO : "auto",
  RENDER_OPTSPEED: "optimizeSpeed",
  RENDER_CRISPEDGES: "crispEdges",
  RENDER_GEOPRECISION: "geometricPrecision", 
  
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * Copies one element into another
   */
  copyTo : function(source, dest) {
    dest.setDrawLines(source.getDrawLines());
    dest.setLineColor(source.getLineColor());
    dest.setLineWidth(source.getLineWidth());
    dest.setDrawFill(source.getDrawFill());
    dest.setFillColor(source.getFillColor());
    dest.setAttributes(source.getAttributes());
    dest.setShapeRendering(source.getShapeRendering());
  }
  
};

/**
 * Creates a Style object for 2D drawing
 */
EJSS_DRAWING2D.style = function (mName) {
  var Style = EJSS_DRAWING2D.Style;
  var self = {};
   
  var mDrawLines = true;
  var mLineColor = 'black';
  var mLineWidth = 0.5;
  var mDrawFill = true;
  var mFillColor = 'none';
  var mShapeRendering = Style.RENDER_AUTO;
  var mChangeListener; 
  var mAttributes = {};     

  /**
   * Set a listener that will be called whenever there are style changes.
   * I.e. a call to listener("change"); will be issued
   */
  self.setChangeListener = function(listener) {
    mChangeListener = listener;
  };
  
  //---------------------------------
  // lines
  //---------------------------------

  /**
   * Whether to draw the lines of the element
   */
  self.setDrawLines = function(draw) {
    if (draw!=mDrawLines) {
      mDrawLines = draw;
      if (mChangeListener) mChangeListener("drawlines");
    }
  };
  
  /**
   * Get the draw lines flag
   */
  self.getDrawLines = function() { 
    return mDrawLines; 
  };
  
  /**
   * Set the line color of the element
   * @param color a stroke style
   */
  self.setLineColor = function(color) { 
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    if (color!=mLineColor) {
      mLineColor = color; 
      if (mChangeListener) mChangeListener("linecolor");
    }
    return self;
  };
    
  /**
   * Get the line color
   */
  self.getLineColor = function() { 
    return mLineColor; 
  };

  /**
   * Set the line width of the element
   * @param width a stroke width (may be double, such as 0.5, the default)
   */
  self.setLineWidth = function(width) { 
    if (width!=mLineWidth) {
      mLineWidth = width; 
      if (mChangeListener) mChangeListener("linewidth");
    }
  };

  /**
   * Get the line width
   */
  self.getLineWidth = function() { return mLineWidth; };
  
  //---------------------------------
  // interior fill
  //---------------------------------

  /**
   * Whether to fill the interior of the element
   */
  self.setDrawFill = function(draw) {
    if (draw!=mDrawFill) {
      mDrawFill = draw;
      if (mChangeListener) mChangeListener("drawfill");
    }
  };
  
  /**
   * Get the draw fill flag
   */
  self.getDrawFill = function() { 
    return mDrawFill; 
  };

  /**
   * Set the fill color of the element
   * @param color a fill style
   */
  self.setFillColor = function(color) {
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    if (color!=mFillColor) {
      mFillColor = color; 
      if (mChangeListener) mChangeListener("fillcolor");
    }
  };
  
  /**
   * Get the fill color
   */
  self.getFillColor = function() { 
    return mFillColor; 
  };

  /**
   * Sets shape rendering
   */
  self.setShapeRendering = function(rendering) {
    if (rendering.substring(0,6) == "RENDER") rendering = Style[rendering.toUpperCase()];
    if (mShapeRendering != rendering) {
      mShapeRendering = rendering;
      if (mChangeListener) mChangeListener("shaperendering");
    }
  };
  
  self.getShapeRendering = function() { 
    return mShapeRendering;
  };

  /**
   * Sets optional attributes used by the element (such as SVG attributes)
   */
  self.setAttributes = function(attr) {
    if (attr != mAttributes) {
      mAttributes = attr;
      if (mChangeListener) mChangeListener("attributes");
    }
  };
  
  self.getAttributes = function() { 
    return mAttributes;
  };
  
  /***
   * Get JSON object with private variables
   * @method serialize
   * @visibility private
   */
  self.serialize = function() {
  	return { 
		mDrawLines: mDrawLines, mLineColor: mLineColor, mLineWidth: mLineWidth,
		mDrawFill: mDrawFill, mFillColor: mFillColor, mShapeRendering: mShapeRendering,
		mAttributes: mAttributes
  	};
  }
  
  /***
   * Set JSON object with private variables
   * @method unserialize
   * @parem json JSON object
   * @visibility private
   */
  self.unserialize = function(json) {
	mDrawLines = json.mDrawLines, mLineColor = json.mLineColor, mLineWidth = json.mLineWidth,
	mDrawFill = json.mDrawFill, mFillColor = json.mFillColor, mShapeRendering = json.mShapeRendering,
	mAttributes = json.mAttributes;
  }  
  
  //---------------------------------
  // final initialization
  //---------------------------------
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Transformation
 * @class Transformation 
 * @constructor  
 */
EJSS_DRAWING2D.Transformation = {
    
    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * @method compare
     */
    compare : function(mtr1, mtr2) {    	
    	return( mtr1.radians == mtr2.radians && mtr1.a == mtr2.a && mtr1.b == mtr2.b && mtr1.c == mtr2.c && mtr1.d == mtr2.d
    		 && mtr1.e == mtr2.e && mtr1.f == mtr2.f  )
    }
}

/**
 * Transformation object for 2D drawing
 * @class transformation 
 * @constructor  
 */

/**
 * Creates a transformation object for 2D drawing
 */
EJSS_DRAWING2D.transformation = function(transform) {
  transform = transform || { a:1, b:0, c:0, d:1, e:0, f:0 };
  var self = {
    a : transform.a, c : transform.c, e : transform.e,
    b : transform.b, d : transform.d, f : transform.f,
    radians : 0
  };
  
  self.toString = function() {
    return "a="+self.a+", c="+self.c+", e="+self.e+"\n"+
           "b="+self.b+", d="+self.d+", f="+self.f;  
  };
   
  self.setToIdentity = function() {
    self.a = 1; self.c = 0; self.e = 0; 
    self.b = 0; self.d = 1; self.f = 0;
    radians = 0;
  };
  
  self.setTransform = function(tr) {
    if (Array.isArray(tr) && tr.length>5) {
      self.a = tr[0]; self.c = tr[2]; self.e = tr[4];
      self.b = tr[1]; self.d = tr[3]; self.f = tr[5];
    } 
    else {
      self.a = tr.a; self.c = tr.c; self.e = tr.e; 
      self.b = tr.b; self.d = tr.d; self.f = tr.f;
      self.radians = tr.radians;
    }
  };
  
  self.setToTranslation = function(x,y) {
    self.a = 1; self.c = 0; self.e = x; 
    self.b = 0; self.d = 1; self.f = y;
  };

  self.setToRotation = function(radians) {
    var cos = Math.cos(radians), sin = Math.sin(radians);
    self.a = cos;  self.c = -sin; self.e = 0; 
    self.b = sin; self.d = cos; self.f = 0;
    self.radians = radians;
  };

  self.getRotation = function() {
    return self.radians;
  };
  
  self.translate = function(dx,dy) {
    self.e += dx;
    self.f += dy;
  };
  
  self.scale = function(sx,sy) {
    self.a *= sx; self.b *= sx;
    self.c *= sy; self.d *= sy;
  };
  
  self.concatenate = function(tr) {
    var a = self.a, b=self.b, c=self.c, d=self.d, e=self.e, f=self.f;
    self.a = a*tr.a + c*tr.b;
    self.b = b*tr.a + d*tr.b;
    self.c = a*tr.c + c*tr.d;
    self.d = b*tr.c + d*tr.d;
    self.e = a*tr.e + c*tr.f + e,
    self.f = b*tr.e + d*tr.f + f;
    self.radians += tr.radians;
  };
  
  self.transform = function(point) {
    var x = point[0], y = point[1];
    point[0] = self.a*x + self.c*y + self.e;
    point[1] = self.b*x + self.d*y + self.f;
    return point;
  };

  self.transformVector = function(vector) {
    var x = vector[0], y = vector[1];
    vector[0] = self.a*x + self.c*y;
    vector[1] = self.b*x + self.d*y;
    return vector;
  };
  
  self.inverseTransform = function(point) {
    var a = self.a, b=self.b, c=self.c, d=self.d, e=self.e, f=self.f;
    var den = a*d-b*c;
    if (den===0) return null;
    var x = point[0], y = point[1];
    point[0] = ( d*x - c*y + (c*f - d*e))/den;
    point[1] = (-b*x + a*y + (b*e - a*f))/den;
    return point;
  };
  
  //---------------------------------
  // final initialization
  //---------------------------------
  
  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * Video
 * @class Video 
 * @constructor  
 */
EJSS_DRAWING2D.Video = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * static registerProperties method
     */
    registerProperties : function(element,controller) {
      EJSS_DRAWING2D.Element.registerProperties(element,controller); // super class

      controller.registerProperty("VideoUrl",element.setVideoUrl);
      controller.registerProperty("CurrentTime",element.setCurrentTime);
      controller.registerProperty("ShowControls",element.setControls);
      controller.registerProperty("AutoPlay",element.setAutoPlay);
      controller.registerProperty("Type",element.setType);
      controller.registerProperty("Poster",element.setPoster);
      controller.registerProperty("Loop",element.setLoop);
      controller.registerProperty("WebCam",element.setWebCam);      
    },
};

/**
 * Creates a 2D Segment
 * @method video
 */
EJSS_DRAWING2D.video = function (name) {
  var self = EJSS_DRAWING2D.element(name);

  var mLoop = false;
  var mWebCam = false;
  var mType = "";
  var mPoster = "";
  var mAutoPlay = true;
  var mCurrentTime = 0;
  var mControls = true;
  var mPlay = true;
  var mUrl;		// Video url

  self.getClass = function() {
  	return "ElementVideo";
  }

  self.setLoop = function(loop) {
  	if(mLoop != loop) {
  		mLoop = loop;
  		self.setChanged(true);
  	}
  }	

  self.getLoop = function() {
  	return mLoop;
  }	

  self.setPoster = function(poster) {
  	if(mPoster != poster) {
  		mPoster = poster;
  		self.setChanged(true);
  	}
  }	

  self.getPoster = function() {
  	return mPoster;
  }	

  self.setType = function(type) {
  	if(mType != type) {
  		mType = type;
  		self.setChanged(true);
  	}
  }	

  self.getType = function() {
  	return mType;
  }	

  self.setAutoPlay = function(autoPlay) {
  	if(mAutoPlay != autoPlay) {
  		mAutoPlay = autoPlay;
  		mPlay = autoPlay;
  		self.setChanged(true);
  	}
  }	

  self.getAutoPlay = function() {
  	return mAutoPlay;
  }	

  self.setWebCam = function(webCam) {
  	if(mWebCam != webCam) {
  		mWebCam = webCam;
  		self.setChanged(true);
  	}
  }	

  self.getWebCam = function() {
  	return mWebCam;
  }	

// play,stop,controls

  self.setVideoUrl = function(url) {
  	if(mUrl != url) {
  		mUrl = url;
  		self.setChanged(true);
  	}
  }	

  self.getVideoUrl = function() {
  	return mUrl;
  }	

  self.setCurrentTime = function(currentTime) {
  	if(mCurrentTime != currentTime) {
  		mCurrentTime = currentTime;
  		self.setChanged(true);
  	}
  }	

  self.getCurrentTime = function() {
  	return mCurrentTime;
  }	

  self.setControls = function(controls) {
  	if(mControls != controls) {
  		mControls = controls;
  		self.setChanged(true);
  	}
  }	

  self.getControls = function() {
  	return mControls;
  }	

  self.play = function() {
  	if(!mPlay) {
	  	mPlay = true;
	  	self.setChanged(true);
	}
  }	

  self.stop = function() {
  	if(mPlay) {
	  	mPlay = false;
	  	mAutoPlay = false;
	  	self.setChanged(true);
	}
  }	
  
  self.isPlay = function() {
  	return mPlay;
  }

  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.Video.registerProperties(self,controller);
  };
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for drawing2D elements
 * @module drawing2D 
 */

var EJSS_DRAWING2D = EJSS_DRAWING2D || {};

/**
 * WebCamImage
 * @class WebCamImage 
 * @extends Image
 * @constructor  
 */
EJSS_DRAWING2D.WebCamImage = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_DRAWING2D.Image.registerProperties(element,controller); // super class

    controller.registerProperty("Url",element.setUrl);
    controller.registerProperty("On",element.setOnOff, element.isOnOff)

  },

};

/**
 * motionJPEG function
 * Creates a basic MotionJPEG image element
 * @method motionJPEG
 * @param name the name of the element
 * @returns A MotionJPEG image element
 */
EJSS_DRAWING2D.webCamImage = function (mName) {
  var self = EJSS_DRAWING2D.image(mName);
  navigator.getUserMedia  = navigator.getUserMedia ||
                          navigator.webkitGetUserMedia ||
                          navigator.mozGetUserMedia ||
                          navigator.msGetUserMedia;
  
  var mPlaying = true;
  var mUrl;
  var mPreviousUrl;
  
  self.setUrl = function(url) {
    //if (self.getResourcePath) url = self.getResourcePath(url);
    mUrl = url;
  }	

  self.getUrl = function() {
  	return mUrl;
  }	

  self.setOnOff = function(on) {
	if (on) {
	  if (!mPlaying) self.play();
	}
	else {
	  if (mPlaying) self.pause();
	}
  }

  self.isOnOff = function() {
	return mPlaying;
  }

  self.play = function() {
	if (mUrl) {
      if (mUrl != self.getImageUrl()) {
        mPreviousUrl = self.getImageUrl();
        if (mUrl=="local:") {
          if (navigator.getUserMedia) {
            navigator.getUserMedia({video: true}, 
              function(stream) { self.forceImageUrl(stream); }, 
              errorCallback);
          } 
          else self.setImageUrl(mPreviousUrl);
        }
        else self.setImageUrl(mUrl);
	  }
	}
	mPlaying = true;
  }

  self.pause = function() {
	mPlaying = false;
	if (mPreviousUrl) self.setImageUrl(mPreviousUrl);
  }

/*
  self.dataCollectedno = function() {
  	if (mPlaying && mUrl) {
    	// self.setChanged(true);
    	self.setChangedImage(true);
    }
  }
  
*/

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_DRAWING2D.WebCamImage.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Surface
 * @class Surface 
 * @constructor  
 */
EJSS_DRAWING3D.AnalyticCurve = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("NumPoints", element.setNumPoints);
		controller.registerProperty("MinValue", element.setMinValue);
		controller.registerProperty("MaxValue", element.setMaxValue);
		controller.registerProperty("Variable", element.setVariable);

		controller.registerProperty("FunctionX", element.setExpressionX);
		controller.registerProperty("FunctionY", element.setExpressionY);
		controller.registerProperty("FunctionZ", element.setExpressionZ);
	}

};

/**
 * Creates a 3D Surface
 * @method surface
 */
EJSS_DRAWING3D.analyticCurve = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  // Implementation variables
  var mParser = EJSS_DRAWING2D.functionsParser();
  var mExpressionX = "t";
  var mExpressionY = "t";
  var mExpressionZ = "0";

  var mNumPoints = screen.width / 10;
  var mMinValue = 0;
  var mMaxValue = 0;
  var mVariable = "t";
  
  self.getClass = function() {
  	return "ElementAnalyticCurve";
  }

  self.setNumPoints = function(n) {
  	if(mNumPoints != n) {
  	  mNumPoints = n;
  	  self.setMeshChanged(true);
  	}
  }

  self.setMinValue = function(n) {
  	if(mMinValue != n) {
  	  mMinValue = n;
  	  self.setMeshChanged(true);
  	}
  }

  self.setMaxValue = function(n) {
  	if(mMaxValue != n) {
  	  mMaxValue = n;
  	  self.setMeshChanged(true);
  	}
  }

  self.setVariable = function(n) {
  	if(mVariable != n) {
  	  mVariable = n;
  	  self.setMeshChanged(true);
  	}
  }

  self.setExpressionX = function(exp) {
  	if(mExpressionX != exp) {
  	  mExpressionX = exp;
  	  self.setMeshChanged(true);
  	}
  }

  self.setExpressionY = function(exp) {
  	if(mExpressionY != exp) {
  	  mExpressionY = exp;
  	  self.setMeshChanged(true);
  	}
  }

  self.setExpressionZ = function(exp) {
  	if(mExpressionZ != exp) {
  	  mExpressionZ = exp;
  	  self.setMeshChanged(true);
  	}
  }

  self.getNumPoints = function() {
  	return mNumPoints;
  }

  self.getMinValue = function() {
  	return mMinValue;
  }

  self.getMaxValue = function() {
  	return mMaxValue;
  }

  self.getVariable = function() {
  	return mVariable;
  }

  self.getExpressionX = function() {
  	return mParser.parse(mExpressionX);
  }

  self.getExpressionY = function() {
  	return mParser.parse(mExpressionY);
  }

  self.getExpressionZ = function() {
  	return mParser.parse(mExpressionZ);
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.AnalyticCurve.registerProperties(self, controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);
  self.getStyle().setDrawFill(false);
  self.getStyle().setDrawLines(true);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Surface
 * @class Surface 
 * @constructor  
 */
EJSS_DRAWING3D.AnalyticSurface = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("NumPoints1", element.setNumPoints1);
		controller.registerProperty("MinValue1", element.setMinValue1);
		controller.registerProperty("MaxValue1", element.setMaxValue1);
		controller.registerProperty("Variable1", element.setVariable1);

		controller.registerProperty("NumPoints2", element.setNumPoints2);
		controller.registerProperty("MinValue2", element.setMinValue2);
		controller.registerProperty("MaxValue2", element.setMaxValue2);
		controller.registerProperty("Variable2", element.setVariable2);

		controller.registerProperty("FunctionX", element.setExpressionX);
		controller.registerProperty("FunctionY", element.setExpressionY);
		controller.registerProperty("FunctionZ", element.setExpressionZ);
	}

};

/**
 * Creates a 3D Surface
 * @method surface
 */
EJSS_DRAWING3D.analyticSurface = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  // Implementation variables
  var mParser = EJSS_DRAWING2D.functionsParser();
  var mExpressionX = "u";
  var mExpressionY = "v";
  var mExpressionZ = "0";

  var mNumPoints1 = 100;
  var mMinValue1 = 0;
  var mMaxValue1 = 0;
  var mVariable1 = "u";
  
  var mNumPoints2 = 100;
  var mMinValue2 = 0;
  var mMaxValue2 = 0;
  var mVariable2 = "v";  

  self.getClass = function() {
  	return "ElementAnalyticSurface";
  }

  self.setNumPoints1 = function(n) {
  	if(mNumPoints1 != n) {
  	  mNumPoints1 = n;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setMinValue1 = function(n) {
  	if(mMinValue1 != n) {
  	  mMinValue1 = n;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setMaxValue1 = function(n) {
  	if(mMaxValue1 != n) {
  	  mMaxValue1 = n;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setVariable1 = function(n) {
  	if(mVariable1 != n) {
  	  mVariable1 = n;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setNumPoints2 = function(n) {
  	if(mNumPoints2 != n) {
  	  mNumPoints2 = n;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setMinValue2 = function(n) {
  	if(mMinValue2 != n) {
  	  mMinValue2 = n;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setMaxValue2 = function(n) {
  	if(mMaxValue2 != n) {
  	  mMaxValue2 = n;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setVariable2 = function(n) {
  	if(mVariable2 != n) {
  	  mVariable2 = n;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setExpressionX = function(exp) {
  	if(mExpressionX != exp) {
  	  mExpressionX = exp;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setExpressionY = function(exp) {
  	if(mExpressionY != exp) {
  	  mExpressionY = exp;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.setExpressionZ = function(exp) {
  	if(mExpressionZ != exp) {
  	  mExpressionZ = exp;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getNumPoints1 = function() {
  	return mNumPoints1;
  }

  self.getMinValue1 = function() {
  	return mMinValue1;
  }

  self.getMaxValue1 = function() {
  	return mMaxValue1;
  }

  self.getVariable1 = function() {
  	return mVariable1;
  }

  self.getNumPoints2 = function() {
  	return mNumPoints2;
  }

  self.getMinValue2 = function() {
  	return mMinValue2;
  }

  self.getMaxValue2 = function() {
  	return mMaxValue2;
  }

  self.getVariable2 = function() {
  	return mVariable2;
  }

  self.getExpressionX = function() {
  	return mParser.parse(mExpressionX);
  }

  self.getExpressionY = function() {
  	return mParser.parse(mExpressionY);
  }

  self.getExpressionZ = function() {
  	return mParser.parse(mExpressionZ);
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.AnalyticSurface.registerProperties(self, controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/***
 * Arrow is a class to display vectors in 3D
 * @class EJSS_DRAWING3D.Arrow 
 * @constructor  
 */
EJSS_DRAWING3D.Arrow = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy

		dest.setHeadHeight(source.getHeadHeight());
		dest.setHeadWidth(source.getHeadWidth());
        dest.setLineWidth(source.getLineWidth());

  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

  	 /*** 
	  * Ratio arrow_length/head_length 
	  * @property HeadHeight 
	  * @type double
	  * @default "8"
	  */ 
      	controller.registerProperty("HeadHeight", element.setHeadHeight);
  	 /*** 
	  * Ratio arrow_length/head_width 
	  * @property HeadWidth 
	  * @type double
	  * @default "20"
	  */ 
      	controller.registerProperty("HeadWidth", element.setHeadWidth);
     
      	controller.registerProperty("LineWidth",  element.setLineWidth, element.getLineWidth);
      	
	}

};

/**
 * Creates a 3D Arrow
 * @method arrow
 */
EJSS_DRAWING3D.arrow = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  // Implementation variables
  var mX = 0;
  var mY = 0;
  var mZ = 0;
  var mSizeX = 1;				
  var mSizeY = 1;					
  var mSizeZ = 1;		
  var mHeadHeight = 8;  // ratio arrow length / head long
  var mHeadWidth = 20;  // ratio arrow length / head width
  var mLineWidth = 0.01;

  self.getClass = function() {
  	return "ElementArrow";
  } 

  self.setHeadHeight = function(h) {
    if (mHeadHeight!=h) { 
      mHeadHeight = h; 
      self.setMeshChanged(true); 
    }   	
  }

  self.getHeadHeight = function() { 
    return mHeadHeight; 
  };

  self.setHeadWidth = function(w) {
    if (mHeadWidth!=w) { 
      mHeadWidth = w; 
      self.setMeshChanged(true); 
    }   	
  }

  self.getHeadWidth = function() { 
    return mHeadWidth; 
  };

  self.setX = function(x) { 
    if (mX!=x) { 
      mX = x; 
      self.setMeshChanged(true); 
    } 
  };

  self.getX = function() { 
    return mX; 
  };

  self.setY = function(y) {  
    if (mY!=y) { 
      mY = y; 
      self.setMeshChanged(true);
    } 
  };

  self.getY = function() { 
    return mY; 
  };

  self.setZ = function(z) {  
    if (mZ!=z) { 
      mZ = z; 
      self.setMeshChanged(true);
    } 
  };

  self.getZ = function() { 
    return mZ; 
  };

  self.setPosition = function(position) {
    self.setX(position[0]);
    self.setY(position[1]);
    self.setZ(position[2]);
  };

  self.getPosition = function() { 
    return [mX, mY, mZ]; 
  };

  self.setSizeX = function(sizeX) { 
    if (mSizeX!=sizeX) { 
      mSizeX = sizeX; 
      self.setMeshChanged(true);
    } 
  };

  self.setSizeY = function(sizeY) { 
    if (mSizeY!=sizeY) { 
      mSizeY = sizeY; 
      self.setMeshChanged(true);
    }
  };

  self.setSizeZ = function(sizeZ) { 
    if (mSizeZ!=sizeZ) { 
      mSizeZ = sizeZ; 
      self.setMeshChanged(true);
    }
  };

  self.getSizeX = function() { 
    return mSizeX; 
  };

  self.getSizeY = function() { 
    return mSizeY; 
  };

  self.getSizeZ = function() { 
    return mSizeZ; 
  };

  self.setSize = function(size) {
    self.setSizeX(size[0]);
    self.setSizeY(size[1]);
    self.setSizeZ(size[2]);
  };

  self.getSize = function() {
    return [self.getSizeX(), self.getSizeY(), self.getSizeZ()];
  };

  /**
   * Set the line width
   */
  self.setLineWidth = function(width) { 
    if (width!=mLineWidth) {
      mLineWidth = width; 
  	  self.setMeshChanged(true);
    }
  };

  /**
   * Get the line width
   */
  self.getLineWidth = function() { 
  	return mLineWidth; 
  };

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Arrow.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Arrow.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.getStyle().setDrawFill(true);
  self.getStyle().setDrawLines(false);
  self.setResolution([10,2]);
  // self.setPrivateTransformation([[54.735,-0.5,0.5,0,0,0,0]]); // 54.735 angle (arcsin(1/sqrt(3)))

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//ArrowSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/***
 * ArrowSet is a set of Arrows
 * @class EJSS_DRAWING3D.ArrowSet 
 * @constructor  
 */
EJSS_DRAWING3D.ArrowSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller); 
     
  	 /*** 
	  * Ratio arrow_length/head_length 
	  * @property HeadHeight 
	  * @type double
	  * @default "8"
	  */ 
	  controller.registerProperty("HeadHeight", 
          function(v) { set.setToEach(function(element,value) { element.setHeadHeight(value); }, v); }
      );
      
      /*** 
	  * Ratio arrow_length/head_width 
	  * @property HeadWidth 
	  * @type double
	  * @default "20"
	  */ 
      controller.registerProperty("HeadWidth", 
          function(v) { set.setToEach(function(element,value) { element.setHeadWidth(value); }, v); }
      );
        
      controller.registerProperty("LineWidth", 
          function(v) { set.setToEach(function(element,value) { element.setLineWidth(value); }, v); }
      );      
        
    }

};


/**
 * Creates a set of arrows
 * @method arrowSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.arrowSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.arrow,mName);

  // Static references
  var ArrowSet = EJSS_DRAWING3D.ArrowSet;		// reference for ArrowSet
  
  self.registerProperties = function(controller) {
    ArrowSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Basic
 * @class Basic 
 * @constructor  
 */
EJSS_DRAWING3D.Basic = {
    object_files:{},
    
    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  	
		dest.setVertices(source.getVertices());
		dest.setTriangles(source.getTriangles());
		dest.setNormals(source.getNormals());
		dest.setColors(source.getColors());
  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Description", element.setDescription);
		controller.registerProperty("Vertices", element.setVertices);
		controller.registerProperty("Triangles", element.setTriangles);		
		controller.registerProperty("Normals", element.setNormals);
		controller.registerProperty("Colors", element.setColors);
	}

};

/**
 * Creates a 3D Basic
 * @method basic
 */
EJSS_DRAWING3D.basic = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  // Implementation variables
  var mDataChanged = true;

  var mVertices = [];
  var mTriangles = [];
  var mNormals = [];
  var mColors = [];

  self.getClass = function() {
  	return "ElementBasic";
  }

  /**
   * Sets all parts of an object
   */
  self.setDescription = function(description) {
    var panel = self.getPanel();
    if (!panel) return;
    if (!panel.supportsWebGL()) return; // Or an error will block the rest of the interface
//      console.log("Setting description to "+description);
    if (typeof description === 'string') { // read description from file. The file must have an object called "Object3D_" + the file name (without extensions)
      var descName = "Object3D_"+EJSS_TOOLS.File.plainName(description);
//      console.log(description+" variable = "+descName);
      self.setDescription(window[descName]);
      //EJSS_TOOLS.File.loadJSfile(description, function() { console.log ("Setting description object to "+descName); self.setDescription(window[descName]); });
      return;
    }
//    console.log("Do setting description to "+description);
  
    if (description["vertices"])  self.setVertices(description["vertices"]);
    if (description["triangles"]) self.setTriangles(description["triangles"]);
    if (description["normals"])   self.setNormals(description["normals"]);
    if (description["colors"])    self.setColors(description["colors"]);
  }

  self.setVertices = function(vertices) {
    if (!EJSS_TOOLS.compareArrays(mVertices,vertices)) {
        mVertices = vertices.slice(); 
  	    self.setMeshChanged(true);      
    }
  }

  self.getVertices = function() {
  	return mVertices;
  }

  self.setTriangles = function(triangles) {
    if (!EJSS_TOOLS.compareArrays(mTriangles,triangles)) {
        mTriangles = triangles.slice(); 
  	    self.setMeshChanged(true);      
    }
  }

  self.getTriangles = function() {
  	return mTriangles;
  }

  self.setNormals = function(normals) {
    if (!EJSS_TOOLS.compareArrays(mNormals,normals)) {
        mNormals = normals.slice(); 
  	    self.setMeshChanged(true);      
    }
  }

  self.getNormals = function() {
  	return mNormals;
  }

  self.setColors = function(colors) {
    if (!EJSS_TOOLS.compareArrays(mColors,colors)) {
        mColors = colors.slice(); 
  	    self.setMeshChanged(true);      
    }
  }

  self.getColors = function() {
  	return mColors;
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Basic.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Basic.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//BasicSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * BasicSet
 * @class BasicSet 
 * @constructor  
 */
EJSS_DRAWING3D.BasicSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("Description", 
          function(v) { set.setToEach(function(element,value) { element.setDescription(value); }, v); }
      );
      controller.registerProperty("Vertices", 
          function(v) { set.setToEach(function(element,value) { element.setVertices(value); }, v); }
      );
      controller.registerProperty("Triangles", 
          function(v) { set.setToEach(function(element,value) { element.setTriangles(value); }, v); }
      );           
      controller.registerProperty("Normals", 
          function(v) { set.setToEach(function(element,value) { element.setNormals(value); }, v); }
      );           
      controller.registerProperty("Colors", 
          function(v) { set.setToEach(function(element,value) { element.setColors(value); }, v); }
      );           
    }        

};


/**
 * Creates a set of basics
 * @method basicSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.basicSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.basic,mName);

  // Static references
  var BasicSet = EJSS_DRAWING3D.BasicSet;		// reference for BasicSet
  
  self.registerProperties = function(controller) {
    BasicSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Box
 * @class Box 
 * @constructor  
 */
EJSS_DRAWING3D.Box = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  	
		dest.setReduceZby(source.getReduceZby());
  	
  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class
		
      	controller.registerProperty("ReduceZby", element.setReduceZby);

	}
};

/**
 * Creates a 3D Box
 * @method box
 */
EJSS_DRAWING3D.box = function (name) {
  var self = EJSS_DRAWING3D.element(name);
  var mReduceZby = 0.0;

  // Implementation variables
  self.getClass = function() {
  	return "ElementBox";
  }

  self.setReduceZby = function(reduce) {
  	if(mReduceZby != reduce) {
  	  mReduceZby = reduce;
  	  self.setMeshChanged(true);
  	}
  }

  self.getReduceZby = function() {
  	return mReduceZby;
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Box.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Box.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//BoxSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * BoxSet
 * @class BoxSet 
 * @constructor  
 */
EJSS_DRAWING3D.BoxSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("ReduceZby", 
          function(v) { set.setToEach(function(element,value) { element.setReduceZby(value); }, v); }
      );
    }

};


/**
 * Creates a set of boxs
 * @method boxSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.boxSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.box,mName);

  // Static references
  var BoxSet = EJSS_DRAWING3D.BoxSet;		// reference for BoxSet
  
  self.registerProperties = function(controller) {
    BoxSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Cone
 * @class Cone 
 * @constructor  
 */
EJSS_DRAWING3D.Cone = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  		EJSS_DRAWING3D.Cylinder.copyTo(source,dest);  	
  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Cylinder.registerProperties(element, controller);
		// super class
		
	}
};

/**
 * Creates a 3D Cone
 * @method cone
 */
EJSS_DRAWING3D.cone = function (name) {
  var self = EJSS_DRAWING3D.cylinder(name);

  // Implementation variables
  self.getClass = function() {
  	return "ElementCone";
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Cone.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Cone.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);
  self.setTopRadius(0);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//ConeSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * ConeSet
 * @class ConeSet 
 * @constructor  
 */
EJSS_DRAWING3D.ConeSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller); 
      
    }

};


/**
 * Creates a set of cones
 * @method coneSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.coneSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.cone,mName);

  // Static references
  var ConeSet = EJSS_DRAWING3D.ConeSet;		// reference for ConeSet
  
  self.registerProperties = function(controller) {
    ConeSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/***
 * Cylinder
 * @class EJSS_DRAWING3D.Cylinder
 * @constructor
 */
EJSS_DRAWING3D.Cylinder = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  	
		dest.setBottomRadius(source.getBottomRadius());  	
		dest.setTopRadius(source.getTopRadius());  	

  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

	 /*** 
	  * Bottom radius
	  * @property BottomRadius 
	  * @type number
	  * @default 1
	  */ 
		controller.registerProperty("BottomRadius", element.setBottomRadius);
	 /*** 
	  * Top radius
	  * @property TopRadius 
	  * @type number
	  * @default 1
	  */ 	
		controller.registerProperty("TopRadius", element.setTopRadius);
		
	 /*** 
	  * Minimum angle in drawing
	  * @property MinAngleU 
	  * @type number
	  * @default 0
	  */ 		
        controller.registerProperty("MinAngleU", element.setMinAngleU);
	 /*** 
	  * Maximum angle in drawing
	  * @property MaxAngleU 
	  * @type number
	  * @default 360
	  */ 		
        controller.registerProperty("MaxAngleU", element.setMaxAngleU);		
	}
};

/**
 * Creates a 3D Cylinder
 * @method cylinder
 */
EJSS_DRAWING3D.cylinder = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  // Implementation variables
  var mMinAngleU = 0; 		// the start angle (in degrees) for the parallels
  var mMaxAngleU = 360;		// the end angle (in degrees) for the parallels

  var mTopRadius = 1;
  var mBottomRadius = 1;

  self.getClass = function() {
  	return "ElementCylinder";
  }
  
	 /*** 
	  * Changes the position and Size Z and adds an extra transformation
	  * so that the axis of the element has the prescribed origin and end points   
	  * @method setOriginAndEnd 
	  * @param origin a double[3] array with the origin of the axis
	  * @param end a double[3] array with the end point of the axis
	  */ 
  self.setOriginAndEnd = function(origin,end) {
  	var xc = (origin[0]+end[0])/2;
    var yc = (origin[1]+end[1])/2;
    var zc = (origin[2]+end[2])/2;
    var dx = end[0]-origin[0], dy = end[1]-origin[1], dz = end[2]-origin[2];
    var distance = Math.sqrt(dx*dx+dy*dy+dz*dz);
    var angle = Math.acos(dz/distance);
    self.setPosition([xc,yc,zc]);
    self.setSizeZ(distance);
    self.setFirstTransformation([[angle,-dy, dx, 0, xc,yc,zc]]);
  }

  self.setMinAngleU = function(angle) {
  	if(mMinAngleU != angle) {
  	  mMinAngleU = angle;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getMinAngleU = function() {
  	return mMinAngleU;
  }

  self.setMaxAngleU = function(angle) {
  	if(mMaxAngleU != angle) {
  	  mMaxAngleU = angle;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getMaxAngleU = function() {
  	return mMaxAngleU;
  }

  self.setBottomRadius = function(radius) {
  	if(mBottomRadius != radius) {
  	  mBottomRadius = radius;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getBottomRadius = function() {
  	return mBottomRadius;
  }

  self.setTopRadius = function(radius) {
  	if(mTopRadius != radius) {
  	  mTopRadius = radius;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getTopRadius = function() {
  	return mTopRadius;
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Cylinder.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Cylinder.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//CylinderSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * CylinderSet
 * @class CylinderSet 
 * @constructor  
 */
EJSS_DRAWING3D.CylinderSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller); 

      controller.registerProperty("MinAngleU", 
          function(v) { set.setToEach(function(element,value) { element.setMinAngleU(value); }, v); }
      );      
      controller.registerProperty("MaxAngleU", 
          function(v) { set.setToEach(function(element,value) { element.setMaxAngleU(value); }, v); }
      );      
      controller.registerProperty("BottomRadius", 
          function(v) { set.setToEach(function(element,value) { element.setBottomRadius(value); }, v); }
      );      
      controller.registerProperty("TopRadius", 
          function(v) { set.setToEach(function(element,value) { element.setTopRadius(value); }, v); }
      );      
    }

};


/**
 * Creates a set of cylinders
 * @method cylinderSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.cylinderSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.cylinder,mName);

  // Static references
  var CylinderSet = EJSS_DRAWING3D.CylinderSet;		// reference for CylinderSet
  
  self.registerProperties = function(controller) {
    CylinderSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};


/***
 * Disk
 * @class EJSS_DRAWING3D.Disk
 * @constructor
 */
EJSS_DRAWING3D.Disk = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  		EJSS_DRAWING3D.Cylinder.copyTo(source,dest);
  	
  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Cylinder.registerProperties(element, controller);
		// super class
		
	      controller.registerProperty("Size", function(s) {
	      		s[2] = Math.min(s[0],s[1])/1000;
	      		element.setSize(s);
	      	});
	      controller.registerProperty("SizeX", function(sx) {
	      		var sy = element.getSizeY();
	      		element.setSizeZ(Math.min(sy,sx)/1000)
	      		element.setSizeX(sx);
	      	});
	      controller.registerProperty("SizeY",function(sy) {
	      		var sx = element.getSizeX();
	      		element.setSizeZ(Math.min(sy,sx)/1000)
	      		element.setSizeY(sy);
	      	});		
	}
};

/**
 * Creates a 3D Disk
 * @method disk
 */
EJSS_DRAWING3D.disk = function (name) {
  var self = EJSS_DRAWING3D.cylinder(name);

  // Implementation variables
  self.getClass = function() {
  	return "ElementDisk";
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Disk.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Disk.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,0.001]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/***
 * A DrawingPanel is a 3D drawing panel. 
 * @class EJSS_DRAWING3D.DrawingPanel
 * @constructor
 */
 EJSS_DRAWING3D.DrawingPanel = {  
  GRAPHICS3D_WEBGL : "WebGL",

  /**
   * static registerProperties method
   */
  registerProperties : function(element, controller) {
    // No super class
    
    element.setController(controller);
    
	 /*** 
	  * The minimum value for the X coordinates of elements. 
	  * @property MinimumX 
	  * @type int or double
	  * @default "-1"
	  */ 
    controller.registerProperty("MinimumX",element.setWorldXMin,element.getWorldXMin);
	 /*** 
	  * The maximum value for the X coordinates of elements. 
	  * @property MaximumX 
	  * @type int or double
	  * @default "+1"
	  */ 
    controller.registerProperty("MaximumX",element.setWorldXMax,element.getWorldXMax);
	 /*** 
	  * The minimum value for the Y coordinates of elements. 
	  * @property MinimumY 
	  * @type int or double
	  * @default "-1"
	  */ 
    controller.registerProperty("MinimumY",element.setWorldYMin,element.getWorldYMin);
	 /*** 
	  * The maximum value for the Y coordinates of elements. 
	  * @property MaximumY
	  * @type int or double
	  * @default "+1"
	  */ 
    controller.registerProperty("MaximumY",element.setWorldYMax,element.getWorldYMax);
	 /*** 
	  * The minimum value for the Z coordinates of elements. 
	  * @property MinimumZ 
	  * @type int or double
	  * @default "-1"
	  */ 
    controller.registerProperty("MinimumZ",element.setWorldZMin,element.getWorldZMin);
	 /*** 
	  * The maximum value for the Z coordinates of elements. 
	  * @property MaximumZ 
	  * @type int or double
	  * @default "+1"
	  */ 
    controller.registerProperty("MaximumZ",element.setWorldZMax,element.getWorldZMax);
    controller.registerProperty("Bounds",element.setWorldCoordinates,element.getWorldCoordinates);
    
    controller.registerProperty("Size",element.setSize,element.getSize);
	 /*** 
	  * Size for the X axis.
	  * @property SizeX
	  * @type int or double 
	  * @default 1
	  */ 
    controller.registerProperty("SizeX",element.setSizeX,element.getSizeX);
	 /*** 
	  * Size for the Y axis.
	  * @property SizeY
	  * @type int or double 
	  * @default 1
	  */ 
    controller.registerProperty("SizeY",element.setSizeY,element.getSizeY);
	 /*** 
	  * Size for the Z axis.
	  * @property SizeZ
	  * @type int or double 
	  * @default 1
	  */ 
    controller.registerProperty("SizeZ",element.setSizeZ,element.getSizeZ);

	 /*** 
	  * Camera rotation in Z axis.
	  * @property CameraAzimuth
	  * @type int or double (degrees) 
	  * @default 0
	  */ 
    controller.registerProperty("CameraAzimuth",element.setCamAzimuth,element.getCamAzimuth);
	 /*** 
	  * Camera rotation in Y axis.
	  * @property CameraAltitude
	  * @type int or double (degrees) 
	  * @default 0
	  */ 
    controller.registerProperty("CameraAltitude",element.setCamAltitude,element.getCamAltitude);
	 /*** 
	  * Camera rotation in X axis.
	  * @property CameraTilt
	  * @type int or double (degrees) 
	  * @default 0
	  */ 
    controller.registerProperty("CameraTilt",element.setCamTilt,element.getCamTilt);
	 /*** 
	  * Camera position in X axis.
	  * @property CameraX
	  * @type int or double 
	  * @default 4
	  */ 
    controller.registerProperty("CameraX",element.setCamLocX,element.getCamLocX);
	 /*** 
	  * Camera position in Y axis.
	  * @property CameraY
	  * @type int or double 
	  * @default 0
	  */ 
    controller.registerProperty("CameraY",element.setCamLocY,element.getCamLocY);
	 /*** 
	  * Camera position in Z axis.
	  * @property CameraZ
	  * @type int or double 
	  * @default 0
	  */ 
    controller.registerProperty("CameraZ",element.setCamLocZ,element.getCamLocZ);
    controller.registerProperty("CameraLocation",element.setCamLoc,element.getCamLoc);
	 /*** 
	  * Camera focus in X axis.
	  * @property CameraFocusX
	  * @type int or double 
	  * @default 0
	  */ 
    controller.registerProperty("CameraFocusX",element.setCamFocusX,element.getCamFocusX);
	 /*** 
	  * Camera focus in Y axis.
	  * @property CameraFocusY
	  * @type int or double 
	  * @default 0
	  */ 
    controller.registerProperty("CameraFocusY",element.setCamFocusY,element.getCamFocusY);
	 /*** 
	  * Camera focus in Z axis.
	  * @property CameraFocusZ
	  * @type int or double 
	  * @default 0
	  */ 
    controller.registerProperty("CameraFocusZ",element.setCamFocusZ,element.getCamFocusZ);
    controller.registerProperty("CameraFocus",element.setCamFocus,element.getCamFocus);
	 /*** 
	  * Camera up vector in X axis.
	  * @property CameraUpVectorX
	  * @type int or double 
	  * @default 0
	  */ 
    controller.registerProperty("CameraUpVectorX",element.setCamUpVectorX,element.getCamUpVectorX);
	 /*** 
	  * Camera up vector in Y axis.
	  * @property CameraUpVectorY
	  * @type int or double 
	  * @default 0
	  */ 
    controller.registerProperty("CameraUpVectorY",element.setCamUpVectorY,element.getCamUpVectorY);
	 /*** 
	  * Camera up vector in Z axis.
	  * @property CameraUpVectorZ
	  * @type int or double 
	  * @default 0
	  */ 
    controller.registerProperty("CameraUpVectorZ",element.setCamUpVectorZ,element.getCamUpVectorZ);
    controller.registerProperty("CameraUpVector",element.setCamUpVector,element.getCamUpVector);
	 /*** 
	  * Camera zoom rate.
	  * @property CameraZoomRate
	  * @type int or double 
	  * @default 1.10
	  */ 
    controller.registerProperty("CameraZoomRate",element.setZoomRate,element.getZoomRate);
	 /*** 
	  * Near plane in 3D projection.
	  * @property CameraNear 
	  * @type int
	  * @default 1
	  */ 
    controller.registerProperty("CameraNear",element.setNear,element.getNear);
	 /*** 
	  * Far plane in 3D projection.
	  * @property CameraFar 
	  * @type int
	  * @default 10000
	  */ 
    controller.registerProperty("CameraFar",element.setFar,element.getFar);
	 /*** 
	  *  Type of projection.  
	  * @property Projection 
	  * @type int or string 
	  * @values "PLANAR_XY": 0; "PLANAR_XZ": 1; "PLANAR_YZ": 2; "PERSPECTIVE_OFF": 3; "PERSPECTIVE_ON": 4;
	  * @default "PERSPECTIVE_ON"
	  */ 
	controller.registerProperty("Projection",element.setProjection, element.getProjection);
	 /*** 
	  * Field of view when perspective on. A parameter that gives more or less perspective to the projection.
	  * @property Perspective 
	  * @type int (degrees)
	  * @default "45"
	  */ 
	controller.registerProperty("Perspective",element.setFOV, element.getFOV);
    controller.registerProperty("FieldOfView",element.setFOV,element.getFOV);

	 /*** 
	  * Lights in scene.
	  * @property Lights 
	  * @type array of array 
	  * @default [[1,2,3]]
	  */ 
    controller.registerProperty("Lights",element.setLights,element.getLights);
    
    controller.registerProperty("DecorationType",element.setDecorationType);
    controller.registerProperty("VisibleDecorationBox",element.setVisibleDecorationBox);
    controller.registerProperty("VisibleDecorationAxis",element.setVisibleDecorationAxis);
    controller.registerProperty("VisibleDecorationBasicAxis",element.setVisibleDecorationBasicAxis);

    controller.registerProperty("Parent",element.getGraphics().setParent,element.getGraphics().getParent);
    controller.registerProperty("ClassName", element.getGraphics().setClassName);
    controller.registerProperty("Width",element.getGraphics().setWidth, element.getGraphics().getWidth);
    controller.registerProperty("Height",element.getGraphics().setHeight,element.getGraphics().getHeight);
    
    controller.registerProperty("ImageUrl",element.setImageUrl);

    controller.registerProperty("Graphics",element.setGraphics);
    controller.registerProperty("Enabled",element.setEnabled);    
    controller.registerProperty("Draggable",element.setDraggable);    

	controller.registerProperty("Background", element.getStyle().setFillColor);
    controller.registerProperty("Foreground",  element.getGraphics().getStyle().setBorderColor);
    controller.registerProperty("LineColor",  element.getGraphics().getStyle().setBorderColor);
    controller.registerProperty("LineWidth",  element.getGraphics().getStyle().setBorderWidth);
    controller.registerProperty("DrawLines",  element.getStyle().setDrawLines);
    controller.registerProperty("FillColor",  element.getStyle().setFillColor);
    controller.registerProperty("DrawFill",   element.getStyle().setDrawFill);

    controller.registerProperty("Visibility", element.getGraphics().getStyle().setVisibility);      
    controller.registerProperty("Display", element.getGraphics().getStyle().setDisplay); 
	controller.registerProperty("CSS", element.getGraphics().getStyle().setCSS);

    controller.registerAction("OnDoubleClick", element.getInteraction().getInteractionPoint);      
    controller.registerAction("OnMove", element.getInteraction().getInteractionPoint);      
    controller.registerAction("OnPress", element.getInteraction().getInteractionPoint);    
    controller.registerAction("OnDrag", element.getInteraction().getInteractionPoint, element.getOnDragHandler);    
	controller.registerAction("OnRelease", element.getInteraction().getInteractionBounds);
    controller.registerAction("OnZoom", element.getInteraction().getZoomDelta, element.getOnZoomHandler);    
        
  }

};

/**
 * Constructor for DrawingPanel
 * @method drawingPanel
 * @param mName string
 * @returns An abstract 3D drawing panel
 */
EJSS_DRAWING3D.drawingPanel = function (mName) {
  var self = {};										// reference returned 
  var mGraphics = EJSS_GRAPHICS.webGLGraphics(mName);	// graphics implementation (default: WebGL) 

  // Instance variables
  var mStyle = EJSS_DRAWING3D.style(mName);			// style for panel 
  var mDecorations = [];							// decorations list for panel
  var mElements = [];								// elements list for panel
  var mElementsChanged = true;						// whether elements list has changed 
  var mDraggable = 1;								// how panel responds to interaction
  var mImageBackground = false;						// whether background image 
  var mCollectersList = [];		            // Array of all control elements that need a call to dataCollected() after data collection
   
  // Configuration variables 
  var mWorld = {
  	  // preferred dimensions
      xminPreferred: -1, xmaxPreferred: +1, yminPreferred: -1, ymaxPreferred: +1, zminPreferred: -1, zmaxPreferred: +1,  
  };
  
  // Camera
  var mChangeCamera = true;
  var mAutoCamera = true;
  var mZoomRate = 1.10;
  var mCamera = {
  	location: new Vector(4, 0, 0),
  	focus: new Vector(0, 0, 0),
  	upvector: new Vector(0, 0, 1),
  	azimuth: 0,
  	altitude: 0,
  	tilt: 0,
  	delta: 0
  }    
  var mFOV = 45;
  var mNear = 1;
  var mFar = 10000;
  var mOrthographic = false;
  var mProjection = 4;
  
  // Size  
  var mSizeX = 1;					// size X
  var mSizeY = 1;					// size Y
  var mSizeZ = 1;					// size Z
  
  // Lights
  var mLights = [[1,2,3]];
  
  // Implementation variables
  var mPanelChanged = true;			// whether panel changed (style, decorations)
  var mChangeWorld = true;			// whether decorations must change
  var mInteraction;					// user interaction on panel
  
  var mController = { // dummy controller object
          propertiesChanged : function() {},
          invokeAction : function() {}
  };
  
  // ----------------------------------------
  // Instance functions
  // ----------------------------------------

  /***
   * Get name for drawing panel
   * @method getName
   * @return string
   */
  self.getName = function() {
    return mName;
  };

  /***
   * Returns the graphics implementation
   * @method getGraphics
   * @return Graphics
   */
  self.getGraphics = function() {
    return mGraphics;
  };
 
  /***
   * Whether webgl is supported
   * @method supportsWebGL
   * @return boolean
   */
  self.supportsWebGL = function() {
    return mGraphics.supportsWebGL();
  };
 
  /***
   * Return the drawing style of the inner rectangle for panel
   * @method getStyle
   * @return Style
   */
  self.getStyle = function() { 
    return mStyle; 
  };

  /***
   * Set graphics (only Webgl supported)
   * @method setGraphics
   * @param type
   */
  self.setGraphics = function(type) {
  	if(type == EJSS_DRAWING3D.DrawingPanel.GRAPHICS3D_WEBGL) {
		mGraphics = EJSS_GRAPHICS.webGLGraphics(mName); 
	} else {
		console.log("WARNING: setGraphics() - Graphics not supported");
	}	
  }

  // ----------------------------------------
  // World coordinates
  // ----------------------------------------

  /***
   * Sets the preferred minimum X coordinate for the panel
   * @method setWorldXMin
   * @param xmin
   */
  self.setWorldXMin = function(xmin)  {
    if (xmin !== mWorld.xminPreferred) { 
      mWorld.xminPreferred = xmin;
      mChangeWorld = true; 
    }
  };

  /***
   * Returns the preferred minimum X coordinate for the panel
   * @method getWorldXMin
   * @return double
   */
  self.getWorldXMin = function() {
    return mWorld.xminPreferred;
  };
  
  /***
   * Sets the preferred maximum X coordinate for the panel
   * @method setWorldXMax
   * @param xmax
   */
  self.setWorldXMax = function(xmax)  {
    if (xmax !== mWorld.xmaxPreferred) { 
      mWorld.xmaxPreferred = xmax;
      mChangeWorld = true; 
    }
  };

  /***
   * Returns the preferred maximum X coordinate for the panel
   * @method getWorldXMax
   * @return double
   */
  self.getWorldXMax = function() {
    return mWorld.xmaxPreferred;
  };

  /***
   * Sets the preferred minimum Y coordinate for the panel
   * @method setWorldYMin
   * @param ymin
   */
  self.setWorldYMin = function(ymin)  {
    if (ymin !== mWorld.yminPreferred) { 
      mWorld.yminPreferred = ymin;
      mChangeWorld = true; 
    }
  };
  
  /***
   * Returns the preferred minimum Y coordinate for the panel
   * @method getWorldYMin
   * @return double
   */
  self.getWorldYMin = function() {
    return mWorld.yminPreferred;
  };

  /***
   * Sets the preferred maximum Y coordinate for the panel
   * @method setWorldYMax
   * @param ymax
   */
  self.setWorldYMax = function(ymax)  {
    if (ymax !== mWorld.ymaxPreferred) { 
      mWorld.ymaxPreferred = ymax;
      mChangeWorld = true; 
    }
  };

  /***
   * Returns the preferred maximum Y coordinate for the panel
   * @method getWorldYMax
   * @return double
   */
  self.getWorldYMax = function() {
    return mWorld.ymaxPreferred;
  };

  /***
   * Sets the preferred minimum Y coordinate for the panel
   * @method setWorldZMin
   * @param zmin
   */
  self.setWorldZMin = function(zmin)  {
    if (zmin !== mWorld.zminPreferred) { 
      mWorld.zminPreferred = zmin;
      mChangeWorld = true; 
    }
  };
  
  /***
   * Returns the preferred minimum Z coordinate for the panel
   * @method getWorldZMin
   * @return double
   */
  self.getWorldZMin = function() {
    return mWorld.zminPreferred;
  };

  /***
   * Sets the preferred maximum Z coordinate for the panel
   * @method setWorldZMax
   * @param zmax
   */
  self.setWorldZMax = function(zmax)  {
    if (zmax !== mWorld.zmaxPreferred) { 
      mWorld.zmaxPreferred = zmax;
      mChangeWorld = true; 
    }
  };

  /***
   * Returns the preferred maximum Z coordinate for the panel
   * @method getWorldZMax
   * @return double
   */
  self.getWorldZMax = function() {
    return mWorld.zmaxPreferred;
  };

  /***
   * Sets the preferred user coordinates for the panel
   * @method setWorldCoordinates
   * @param bounds
   */
  self.setWorldCoordinates = function(bounds)  {
    self.setWorldXMin(bounds[0]);
    self.setWorldXMax(bounds[1]);
    self.setWorldYMin(bounds[2]);
    self.setWorldYMax(bounds[3]);
    self.setWorldZMin(bounds[4]);
    self.setWorldZMax(bounds[5]);
  };

  /***
   * Gets the preferred user coordinates for the panel
   * @method getWorldCoordinates
   * @return bounds
   */
  self.getWorldCoordinates = function()  {
  	return [self.getWorldXMin(),self.getWorldXMax(),
  			self.getWorldYMin(),self.getWorldYMax(),
  			self.getWorldZMin(),self.getWorldZMax()];
  };
  
  /***
   * Gets X coordinate for camera
   * @method getCamLocX
   * @return x
   */
  self.getCamLocX = function() {
  	return mCamera.location.getx();
  }

  /***
   * Gets Y coordinate for camera
   * @method getCamLocY
   * @return y
   */
  self.getCamLocY = function() {
  	return mCamera.location.gety();
  }

  /***
   * Gets Z coordinate for camera
   * @method getCamLocZ
   * @return z
   */
  self.getCamLocZ = function() {
  	return mCamera.location.getz();
  }

  /***
   * Gets coordinates for camera
   * @method getCamLoc
    * @return vector
  */
  self.getCamLoc = function() {
  	return mCamera.location.toArray();
  }

  /***
   * Sets X coordinate for camera
   * @method setCamLocX
   * @param x
   */
  self.setCamLocX = function(x) {
  	if(mCamera.location.getx() != x) {
  	  mCamera.location.setx(x);
      mChangeCamera = true;   		
  	}
    mAutoCamera = false;
  }

  /***
   * Sets Y coordinate for camera
   * @method setCamLocY
   * @param y
   */
  self.setCamLocY = function(y) {
  	if(mCamera.location.gety() != y) {
  	  mCamera.location.sety(y);
      mChangeCamera = true;   		
  	}
    mAutoCamera = false;
  }

  /***
   * Sets Z coordinate for camera
   * @method setCamLocZ
   * @param z
   */
  self.setCamLocZ = function(z) {
  	if(mCamera.location.getz() != z) {
  	  mCamera.location.setz(z);
      mChangeCamera = true;   		
  	}
    mAutoCamera = false;
  }
  
  /***
   * Sets coordinates for camera
   * @method setCamLoc
   * @param vector [x,y,z]
   */
  self.setCamLoc = function(v) {
  	self.setCamLocX(v[0]);
  	self.setCamLocY(v[1]);
  	self.setCamLocZ(v[2]);
  }

  /***
   * Sets tilt for camera
   * @method setCamTilt
   * @param tilt (degrees)
   */
  self.setCamTilt = function(t) {
  	if(mCamera.tilt != t) {
  	  mCamera.tilt = t;
      mChangeCamera = true;   		
  	}
  }

  /***
   * Gets tilt for camera
   * @method getCamTilt
   * @return tilt
   */
  self.getCamTilt = function() {
  	return mCamera.tilt;
  }
  
  /***
   * Sets azimuth for camera
   * @method setCamAzimuth
   * @param azimuth (degrees)
   */
  self.setCamAzimuth = function(az) {
  	if(mCamera.azimuth != az) {
  	  mCamera.azimuth = az;
      mChangeCamera = true;   		
  	}
  }

  /***
   * Gets azimuth for camera
   * @method getCamAzimuth
   * @return azimuth
   */
  self.getCamAzimuth = function() {
  	return mCamera.azimuth;
  }

  /***
   * Sets altitude for camera
   * @method setCamAltitude
   * @param altitude (degrees)
   */
  self.setCamAltitude = function(al) {
  	if(mCamera.altitude != al) {
  	  mCamera.altitude = al;
      mChangeCamera = true;   		
  	}
  }

  /***
   * Gets altitude for camera
   * @method getCamAltitude
   * @return altitude
   */
  self.getCamAltitude = function() {
  	return mCamera.altitude;
  }

  /***
   * Gets X focus for camera
   * @method getCamFocusX
   * @return x
   */
  self.getCamFocusX = function() {
  	return mCamera.focus.getx();
  }

  /***
   * Gets Y focus for camera
   * @method getCamFocusY
   * @return y
   */
  self.getCamFocusY = function() {
  	return mCamera.focus.gety();
  }

  /***
   * Gets Z focus for camera
   * @method getCamFocusZ
   * @return z
   */
  self.getCamFocusZ = function() {
  	return mCamera.focus.getz();
  }

  /***
   * Gets focus for camera
   * @method getCamFocus
   * @return vector
   */
  self.getCamFocus = function() {
  	mCamera.focus.toArray();
  }

  /***
   * Sets X focus for camera
   * @method setCamFocusX
   * @param x
   */
  self.setCamFocusX = function(x) {
  	if(mCamera.focus.getx() != x) {
  	  mCamera.focus.setx(x);
      mChangeCamera = true;   		
  	}
    mAutoCamera = false;
  }

  /***
   * Sets Y focus for camera
   * @method setCamFocusY
   * @param y
   */
  self.setCamFocusY = function(y) {
  	if(mCamera.focus.gety() != y) {
  	  mCamera.focus.sety(y);
      mChangeCamera = true;   		
  	}
    mAutoCamera = false;
  }

  /***
   * Sets Z focus for camera
   * @method setCamFocusZ
   * @param z
   */
  self.setCamFocusZ = function(z) {
  	if(mCamera.focus.getz() != z) {
  	  mCamera.focus.setz(z);
      mChangeCamera = true;   		
  	}
    mAutoCamera = false;
  }
  
  /***
   * Sets focus for camera
   * @method setCamFocus
   * @param vector [x,y,z]
   */
  self.setCamFocus = function(v) {
  	self.setCamFocusX(v[0]);
  	self.setCamFocusY(v[1]);
  	self.setCamFocusZ(v[2]);
  }

  /***
   * Gets X Up Vector for camera
   * @method getCamUpVectorX
   * @return x
   */
  self.getCamUpVectorX = function() {
  	return mCamera.upvector.getx();
  }

  /***
   * Gets Y Up Vector for camera
   * @method getCamUpVectorY
   */
  self.getCamUpVectorY = function() {
  	return mCamera.upvector.gety();
  }

  /***
   * Gets Z Up Vector for camera
   * @method getCamUpVectorZ
   * @return z
   */
  self.getCamUpVectorZ = function() {
  	return mCamera.upvector.getz();
  }

  /***
   * Gets Up Vector for camera
   * @method getCamUpVector
   * @return vector [x,y,z]
   */
  self.getCamUpVector = function() {
  	mCamera.upvector.toArray();
  }

  /***
   * Sets X Up Vector for camera
   * @method setCamUpVectorX
   * @param x
   */
  self.setCamUpVectorX = function(x) {
  	if(mCamera.upvector.getx() != x) {
  	  mCamera.upvector.setx(x);
      mChangeCamera = true;   		
  	}
  }

  /***
   * Sets Y Up Vector for camera
   * @method setCamUpVectorY
   * @param y
   */
  self.setCamUpVectorY = function(y) {
  	if(mCamera.upvector.gety() != y) {
  	  mCamera.upvector.sety(y);
      mChangeCamera = true;   		
  	}
  }

  /***
   * Sets Z Up Vector for camera
   * @method setCamUpVectorZ
   * @param z
   */
  self.setCamUpVectorZ = function(z) {
  	if(mCamera.upvector.getz() != z) {
  	  mCamera.upvector.setz(z);
      mChangeCamera = true;   		
  	}
  }
  
  /***
   * Sets Up Vector for camera
   * @method setCamUpVector
   * @param vector [x,y,z]
   */
  self.setCamUpVector = function(v) {
  	self.setCamUpVectorX(v[0]);
  	self.setCamUpVectorY(v[1]);
  	self.setCamUpVectorZ(v[2]);
  }

  /***
   * Gets auto camera
   * @method getAutoCamera
   * @return boolean
   */
  self.getAutoCamera = function() {
  	return mAutoCamera;
  }

  /**
   * Sets auto camera
   * @method setAutoCamera
   * @param boolean
   */
  self.setAutoCamera = function(auto) {
  	if(mAutoCamera != auto) {
  		mAutoCamera = auto;
  		mChangeCamera = true; 
  	}
  }

  /***
   * Gets zoom rate
   * @method getZoomRate
   * @return double
   */
  self.getZoomRate = function() {
  	return mZoomRate;
  }

  /***
   * Sets zoom rate
   * @method setZoomRate
   * @param double
   */
  self.setZoomRate = function(rate) {
	mZoomRate = rate;
  }

  /***
   * Set lights
   * @method setLights
   * @param array of array
   */
  self.setLights = function(lights) {
	mLights = lights;
  }
  
  /***
   * Gets lights
   * @method getLights
   * @return array of array
   */
  self.getLights = function() {
  	return mLights;
  }
  
  // ----------------------------------------
  // Size of the element
  // ----------------------------------------

  /***
   * Set the size along the X axis of the elements
   * @method setSizeX
   * @param sizeX double
   */
  self.setSizeX = function(sizeX) {
    if (mSizeX !== sizeX) { 
      mSizeX = sizeX;
      mChangeWorld = true; 
    }  	 
  };

  /***
   * Get the size along the X coordinate of the elements
   * @method getSizeX
   * @return double
   */
  self.getSizeX = function() { 
    return mSizeX; 
  };

  /***
   * Set the size along the Y axis of the elements
   * @method setSizeY
   * @param sizeY double
   */
  self.setSizeY = function(sizeY) { 
    if (mSizeY !== sizeY) { 
      mSizeY = sizeY;
      mChangeWorld = true; 
    }  	 
  };

  /***
   * Get the size along the Y coordinate of the elements
   * @method getSizeY
   * @return double
   */
  self.getSizeY = function() { 
    return mSizeY; 
  };

  /***
   * Set the size along the Z axis of the elements
   * @method setSizeZ
   * @param sizeZ double
   */
  self.setSizeZ = function(sizeZ) { 
    if (mSizeZ !== sizeZ) { 
      mSizeZ = sizeZ;
      mChangeWorld = true; 
    }  	 
  };

  /***
   * Get the size along the Z coordinate of the elements
   * @method getSizeZ
   * @return double
   */
  self.getSizeZ = function() { 
    return mSizeZ; 
  };

  /***
   * Set the size of the elements
   * @method setSize
   * @param position double[] an array of dimension 3 or an object with {x,y,z} properties
   */
  self.setSize = function(size) {
    self.setSizeX(size[0]);
    self.setSizeY(size[1]);
    self.setSizeZ(size[2]);
  };

  /***
   * Get the sizes of the elements
   * @method getSize
   * @return double[]
   */
  self.getSize = function() {
    return [self.getSizeX(), self.getSizeY(), self.getSizeZ()];
  };
  
  /***
   * Set background image url 
   * @param url
   */
  self.setImageUrl = function(url) {
  	mImageBackground = true;
  	
  	if (self.getResourcePath != null) {
  	  url = self.getResourcePath(url);
  	}
  	else {
  		console.log ("No getResourcePath function for panel. Texture = " + url);
  	}
  	self.getGraphics().getStyle().setBackgroundImage(url);
  }
  
  /***
   * Whether background image exists
   * @return boolean
   */
  self.isImageUrl = function() {
  	return mImageBackground;
  }
  
  // ----------------------------------------
  // Decorations and elements
  // ----------------------------------------

  /***
   * Adds a decoration to the panel. Decorations are drawn before any other elements.
   * @method addDecoration
   * @param drawable decoration element
   * @param position integer
   */
  self.addDecoration = function(drawable, position) {
  	EJSS_TOOLS.addToArray(mDecorations, drawable, position);
    if (drawable.setPanel) // set this panel to decoration element 
    	drawable.setPanel(self);	 
    return self;
  };

  /***
   * Removes a decoration
   * @method removeDecoration
   * @param drawable decoration element
   */
  self.removeDecoration = function(drawable) {
    EJSS_TOOLS.removeFromArray(mDecorations,drawable);
    if (drawable.setPanel) // remove this panel to decoration element 
    	drawable.setPanel(null);
    return self;
  };

  /***
   * Set visible decoration shown basic axis
   * @method setVisibleDecorationBasicAxis
   * @param boolean
   */
  self.setVisibleDecorationBasicAxis = function(visible) {
  	mBasicAxisXDecoration.setVisible(visible);
  	mBasicAxisYDecoration.setVisible(visible);
  	mBasicAxisZDecoration.setVisible(visible);
  }

  /***
   * Set visible decoration shown traditional axis
   * @method setVisibleDecorationAxis
   * @param boolean
   */
  self.setVisibleDecorationAxis = function(visible) {
  	mAxisXDecoration.setVisible(visible);
  	mAxisYDecoration.setVisible(visible);
  	mAxisZDecoration.setVisible(visible);
  }

  /***
   * Set visible decoration shown box
   * @method setVisibleDecorationBox
   * @param boolean
   */
  self.setVisibleDecorationBox = function(visible) {
  	mBoxDecoration.setVisible(visible);  	
  }

  /***
   * Set visible a specific type of decoration
   * @method setDecorationType
   * @param type "NONE", "AXES", "CUBE", "CENTERED_AXIS"
   */
  self.setDecorationType = function(type) {
    if (typeof(type)=='string') {
      switch (type) {
        default : 
        case "NONE" : type = 0; break; 
        case "AXES" : type = 1; break; 
        case "CUBE" : type = 2; break; 
        case "CENTERED_AXES" : type = 3; break; 
      }    
    }
    switch(type) {
      case 0 : 
        self.setVisibleDecorationBox(false); 
        self.setVisibleDecorationBasicAxis(false);
        self.setVisibleDecorationAxis(false);
        break;
      case 1 : 
        self.setVisibleDecorationBox(false); 
        self.setVisibleDecorationBasicAxis(false);
        self.setVisibleDecorationAxis(true);
        break;
      default:
      case 2 : 
        self.setVisibleDecorationBox(true); 
        self.setVisibleDecorationBasicAxis(false);
        self.setVisibleDecorationAxis(false);
        break;
      case 3 : 
        self.setVisibleDecorationBox(false); 
        self.setVisibleDecorationBasicAxis(true);
        self.setVisibleDecorationAxis(false);
        break;
     }
   }

  /***
   * Add a element to the panel. Elements are asked to draw themselves
   * whenever the panel needs to render. For this purpose, they will receive a
   * calls to draw().
   * Elements are reported of changes in the world coordinates of the panel, in case
   * they need to recalculate themselves.
   * @method addElement
   * @param element Element
   * @param position int
   */
  self.addElement = function(element, position) {
    EJSS_TOOLS.addToArray(mElements,element,position);
    // set this panel to decoration element
    element.setPanel(self);
	if (element.dataCollected) mCollectersList.push(element);
	// elements list has changed
	mElementsChanged = true;		
  };
  
  /***
   * Remove a element to the panel.
   * @method removeElement
   * @param element Element 
   */
  self.removeElement = function(element) {
    EJSS_TOOLS.removeFromArray(mElements,element);
    element.setPanel(null);
	if (element.dataCollected) EJSS_TOOLS.removeFromArray(mCollectersList, element);
	// elements list has changed
	mElementsChanged = true;
  };

  /***
   * Return the array of a elements.
   * @method getElements
   * @return Elements 
   */
  self.getElements = function() {
    return mElements;
  };
    
  /***
   * Return the position of a element.
   * @method indexOfElement
   * @param element Element
   * @return integer 
   */
  self.indexOfElement = function(element) {
    return mElements.indexOf(element);
  };
           
  // ----------------------------------------
  // Drawing functions
  // ----------------------------------------

  /***
   * Get the type of projection
   * @method getProjection
   * @return type 
   */
  self.getProjection = function() {
  	return mProjection;
  }
  
  /***
   * Set the type of projection
   * @method setProjection
   * @return type "PLANAR_XY", "PLANAR_XZ", "PLANAR_YZ", "PERSPECTIVE_OFF", "PERSPECTIVE_ON"
   */
  self.setProjection = function(proj) {
    if (typeof(proj)=='string') {
      switch (proj) {
        case "PLANAR_XY" : proj = 0; break;
      	case "PLANAR_XZ" : proj = 1; break; 
      	case "PLANAR_YZ" : proj = 2; break; 
      	case "PERSPECTIVE_OFF" : proj = 3; break; // PLANAR_ZY
        default : 
      	case "PERSPECTIVE_ON" : proj = 4; break; // PLANAR_ZY
      }    
    }
    if(mProjection != proj) {
	    switch (proj) {
	      case 0 : // PLANAR_XY
	  			mProjection = 0;
	  			self.setOrthographic(true);
	  			self.setCamUpVector([0,1,0]);
	  			self.setCamAzimuth(0);
	  			self.setCamAltitude(0);
	  			self.setCamTilt(0);
	  			break; 
	      case 1 : // PLANAR_XZ
	  			mProjection = 1;
	  			self.setOrthographic(true);
	  			self.setCamUpVector([0,0,-1]);
	  			self.setCamAzimuth(0);
	  			self.setCamAltitude(0);
	  			self.setCamTilt(0);
	  			break; 
	      case 2 : // PLANAR_YZ
	  			mProjection = 2;
	  			self.setOrthographic(true);
	  			self.setCamUpVector([0,1,0]);
	  			self.setCamAzimuth(0);
	  			self.setCamAltitude(0);
	  			self.setCamTilt(0);
	  			break; 
	      case 3 : // PLANAR_ZY
	  			mProjection = 3;
	  			self.setOrthographic(true);
	  			self.setCamUpVector([0,0,1]);
	  			self.setCamAzimuth(0);
	  			self.setCamAltitude(0);
	  			self.setCamTilt(0);
	  			break; 
	      default : 
	      case 4 : // PLANAR_ZY
	  			mProjection = 4;
	  			self.setOrthographic(false);
	  			self.setCamUpVector([0,0,1]);
	  			self.setCamAzimuth(0);
	  			self.setCamAltitude(0);
	  			self.setCamTilt(0);
	  			break; 
	    }        	
	    mChangeWorld = true;
    }
  }

  /***
   * Whether the projection is orthographic
   * @method getOrthographic
   * @return boolean
   */
  self.getOrthographic = function() {
  	return mOrthographic;
  }
  
  /***
   * Set orthographic projection
   * @method setOrthographic
   * @param boolean
   */
  self.setOrthographic = function(orth) {
    if (mOrthographic !== orth) { 
      mOrthographic = orth;
      mChangeWorld = true; 
    }  	 
  }

  /***
   * Get field of view in non-orthographic projection
   * @method getFOV
   * @param int
   */
  self.getFOV = function() {
  	return mFOV;
  }
  
  /***
   * Set field of view in non-orthographic projection
   * @method setFOV
   * @return int
   */
  self.setFOV = function(fov) {
    if (mFOV !== fov) { 
      mFOV = fov;
      mChangeWorld = true; 
    }  	 
  }

  /***
   * Get far plane in non-orthographic projection
   * @method getFar
   * @param number
   */
  self.getFar = function() {
  	return mFar;
  }

  /***
   * Set far plane in non-orthographic projection
   * @method setFar
   * @return number
   */
  self.setFar = function(far) {
    if (mFar !== far) { 
      mFar = far;
      mChangeWorld = true; 
    }  	 
  }

  /***
   * Get near plane in non-orthographic projection
   * @method getNear
   * @param number
   */
  self.getNear = function() {
  	return mNear;
  }
  
  /***
   * Set near plane in non-orthographic projection
   * @method setNear
   * @return number
   */
  self.setNear = function(near) {
    if (mNear !== near) { 
      mNear = near;
      mChangeWorld = true; 
    }  	 
  }

  /***
   * Reset the scene
   * @method reset
   */
  self.reset = function() {
  	// dimensions for orthographic view
  	var dim = {};
  	if(mOrthographic) {
 		// using world
		var aspect = mGraphics.getAspect(); // width / height
		var size = new Vector(mSizeX,mSizeY,mSizeZ);
		var min = new Vector(mWorld.xminPreferred,mWorld.yminPreferred,mWorld.zminPreferred).multiply(size);
		var max = new Vector(mWorld.xmaxPreferred,mWorld.ymaxPreferred,mWorld.zmaxPreferred).multiply(size);
		var diff = max.subtract(min);

        switch (mProjection) {
          case 0: // "PLANAR_XY"
			var ratio = (diff.y/diff.x) * aspect;
            if(ratio>=1.0) {
            	var width = (diff.x * ratio) / 2; var height = diff.y / 2;
			} else {
            	var width = diff.x / 2; var height = (diff.y * (1 / ratio - 1)) / 2;
          	} 
          	break; 
          case 1: // "PLANAR_XZ"
			var ratio = (diff.z/diff.x) * aspect;
            if(ratio>=1.0) {
            	var width = (diff.x * ratio) / 2; var height = diff.z / 2;
			} else {
            	var width = diff.x / 2; var height = (diff.z * (1 / ratio - 1)) / 2;
          	} 
          	break; 
          case 2: // "PLANAR_YZ"
			var ratio = (diff.y/diff.z) * aspect;
            if(ratio>=1.0) {
            	var width = (diff.z * ratio) / 2; var height = diff.y / 2;
			} else {
            	var width = diff.z / 2; var height = (diff.y * (1 / ratio - 1)) / 2;
          	} 
            break;
       	  default: // "PERSPECTIVE_OFF" "PLANAR_ZY"
			var ratio = (diff.z/diff.y) * aspect;
            if(ratio>=1.0) {
            	var width = (diff.y * ratio) / 2; var height = diff.z / 2;
			} else {
            	var width = diff.y / 2; var height = (diff.z * (1 / ratio - 1)) / 2;
          	} 
            break;
		}					
		// with margin
		var rate = 1.10;
   		dim = {left: -width*rate, right: width*rate, bottom: -height*rate, top: height*rate}; 
  	} // else pespective view based on fov

    mGraphics.reset(mOrthographic, dim, mFOV, mNear, mFar);
  }
  
  /***
   * Render the scene
   * @method render
   */
  self.render = function() {
    if(mElementsChanged || mChangeWorld)  // whether elements added or removed, reset the scene 
		self.reset();

    // check for data collection
	for (var i = 0, n = mCollectersList.length; i < n; i++)
		mCollectersList[i].dataCollected();
			
    if (mPanelChanged || mElementsChanged) // whether style panel changed or reseted    	    	 
      	mGraphics.drawPanel(self);

	// change decorations
    if(mChangeWorld) {
		// listener for decorations
		for (i = 0, n = mDecorations.length; i < n; i++) {
			if (mDecorations[i].panelChangeListener)
				mDecorations[i].panelChangeListener("bounds");
		}    	

		// auto change camera position	
		if(mAutoCamera) {
			// focus
			var size = new Vector(mSizeX,mSizeY,mSizeZ);
			var min = new Vector(mWorld.xminPreferred,mWorld.yminPreferred,mWorld.zminPreferred).multiply(size);
			var max = new Vector(mWorld.xmaxPreferred,mWorld.ymaxPreferred,mWorld.zmaxPreferred).multiply(size);
	  		mCamera.focus = max.add(min).divide(2);			
			if(!mOrthographic) { // Axis YZ
				// location (it depends on the fov)
				var aspect = mGraphics.getAspect();
				var diff = max.subtract(min);
            	var apply = (diff.z*aspect>diff.y)?diff.z:diff.y; 				
				var near = 1.1 * (apply/2) / Math.tan(mFOV*Math.PI/360); // near, but far 1.1
				mCamera.location = mCamera.focus.clone();
				mCamera.location.x = max.x + near;
			} else {
				mCamera.location = mCamera.focus.clone();
		        switch (mProjection) {
		          case 0: // "PLANAR_XY"
					mCamera.location.z = mWorld.zmaxPreferred*mSizeZ*4; // a little far		          
		          	break; 
		          case 1: // "PLANAR_XZ"
   					mCamera.location.y = mWorld.ymaxPreferred*mSizeY*4; // a little far
		          	break; 
		          case 2: // "PLANAR_YZ"
		            mCamera.location.x = mWorld.xmaxPreferred*mSizeX*4; // a little far
		            break;
		          default: // "PERSPECTIVE_OFF" 
					mCamera.location.x = mWorld.xmaxPreferred*mSizeX*4; // a little far
		          	break;
				}				
			}
	    }
    }
        	
    // check upvector
    if(mCamera.upvector.cross(mCamera.location.subtract(mCamera.focus)).length() == 0) {
    	// upvector and camera direction are paralell 
    	mCamera.upvector = mCamera.upvector.rotate(); // for upvector (0,0,1) to (1,0,0)
    }
    
    // locate camera
    // if(mChangeCamera)
    	mGraphics.putCamera(mOrthographic,mCamera);

    // draw visible elements
   	mGraphics.draw(mDecorations); // draw the decorations
	mGraphics.draw(mElements);
	
	// set changed to false
	mChangeWorld = false;
	mPanelChanged = false;
	mChangeCamera = false;
	mElementsChanged = false;	
	for (var i=0, n=mElements.length; i<n; i++) {
		mElements[i].setProjChanged(false);
		mElements[i].setMeshChanged(false);
	} 
  };

  // ----------------------------------------
  // Interaction
  // ----------------------------------------
  
  /***
   * Whether the panel should respond to user interaction
   * @method setEnabled
   * @param enabled boolean
   */
  self.setEnabled = function(enabled) { 
  	mInteraction.setEnabled(enabled);
  };
 
  /***
   * How the panel should respond to user interaction
   * @method setDraggable
   * @param draggable "NONE", "ANY", "AZIMUTH", "ALTITUDE"
   */
  self.setDraggable = function(draggable) {
    if (typeof(draggable)=='string') {
      switch (draggable) {
        default : 
        case "NONE" : draggable = 0; break; 
        case "ANY" : draggable = 1; break; 
        case "AZIMUTH" : draggable = 2; break; 
        case "ALTITUDE" : draggable = 3; break;
      }    
    }
	mDraggable = draggable;
  };

  /***
   * Return panel interaction
   * @method getPanelInteraction
   * @return panel interaction
   */
  self.getPanelInteraction = function() { 
  	return mInteraction;
  };
 
 
  /***
   * Returns the controller object
   * @method getController
   * @return Controller
   */
  self.getController = function () {
    return mController;
  };

  /***
   * Set the controller
   * @method setController
   * @param Controller
   */
  self.setController = function (controller) {
    mController = controller;
  };
    

  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.getInteraction = function() {
    return mInteraction;
  };

  self.registerProperties = function(controller) {
    EJSS_DRAWING3D.DrawingPanel.registerProperties(self,controller);
  };
  
  self.getOnDragHandler = function() {
  	if(mDraggable > 0) {
	  	var deltas = self.getPanelInteraction().getInteractionDeltas();

		// delta[0] move right/left
        switch (mProjection) {
          case 0: // "PLANAR_XY"
		    if(mDraggable == 2 || mDraggable == 1) {
			    mCamera.altitude += deltas[0];
		    }
          	break; 
          case 1: // "PLANAR_XZ"
		    if(mDraggable == 2 || mDraggable == 1) {	    
		    	mCamera.azimuth -= deltas[0];
			}  		
          	break; 
          case 2: // "PLANAR_YZ"
		    if(mDraggable == 2 || mDraggable == 1) {
			    mCamera.altitude += deltas[0];
		    }
          	break; 
          default: // "PERSPECTIVE_OFF": 
		    if(mDraggable == 2 || mDraggable == 1) {
			    mCamera.azimuth += deltas[0];
		    }
		    break;
		}

		// delta[1] move up/down
        switch (mProjection) {
          case 0: // "PLANAR_XY"
		    if(mDraggable == 3 || mDraggable == 1) {	    
		    	mCamera.tilt += deltas[1];
			}  		
          	break; 
          case 1: // "PLANAR_XZ"
		    if(mDraggable == 3 || mDraggable == 1) {	    
		    	mCamera.tilt += deltas[1];
			}  		
          	break; 
          case 2: // "PLANAR_YZ" 
		    if(mDraggable == 3 || mDraggable == 1) {	    
		    	mCamera.azimuth -= deltas[1];
			}  		
		    break;
          default: // "PERSPECTIVE_OFF"
		    if(mDraggable == 3 || mDraggable == 1) {	    
		    	mCamera.altitude += deltas[1];
			}  		
		    break;
		}
		
  	}

	self.getController().propertiesChanged("CameraAzimuth","CameraAltitude","CameraTilt");  	
  	
  	mChangeCamera = true;
  }     

  self.getOnZoomHandler = function() {
  	var delta = self.getPanelInteraction().getInteractionZoomDelta();
	var eyevector = mCamera.focus.subtract(mCamera.location);
	if(delta > 0) { // move 10% distance from location to focus camera
	  	mCamera.location = mCamera.focus.subtract(eyevector.divide(mZoomRate)); 
	  	mCamera.delta = mZoomRate;
	} else if(delta < 0) {
	  	mCamera.location = mCamera.focus.subtract(eyevector.multiply(mZoomRate));
	  	mCamera.delta = 2.0 - mZoomRate; 
	}
	self.getController().propertiesChanged("CameraLocation","CameraX","CameraY","CameraZ");

  	mChangeCamera = true;
  }     
    
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  mStyle.setFillColor([0.83,0.85,1]);
  mStyle.setMeshChangeListener(function (change) { mPanelChanged = true; });
  mStyle.setProjChangeListener(function (change) { mPanelChanged = true; });

  // box decoration
  var mBoxDecoration = EJSS_DRAWING3D.box(mName + "box");
  mBoxDecoration.getStyle().setDrawFill(false);
  mBoxDecoration.getStyle().setDrawLines(true);
  mBoxDecoration.getStyle().setLineColor("black");
  mBoxDecoration.setVisible(true);
  mBoxDecoration.setSize([2,2,2]);  
  mBoxDecoration.panelChangeListener = function(event) {
	if (event == "bounds") {
		var posx = (mWorld.xmaxPreferred + mWorld.xminPreferred)/2;
		var posy = (mWorld.ymaxPreferred + mWorld.yminPreferred)/2;
		var posz = (mWorld.zmaxPreferred + mWorld.zminPreferred)/2;
		var sx = Math.abs(mWorld.xmaxPreferred - mWorld.xminPreferred);
		var sy = Math.abs(mWorld.ymaxPreferred - mWorld.yminPreferred);
		var sz = Math.abs(mWorld.zmaxPreferred - mWorld.zminPreferred);		
		mBoxDecoration.setPosition([posx,posy,posz]);
		mBoxDecoration.setSize([sx,sy,sz]);
	}
  };  
  self.addDecoration(mBoxDecoration, 0);

  // axis decoration
  var mAxisXDecoration = EJSS_DRAWING3D.arrow(mName + "axisX");
  mAxisXDecoration.setColor("red");
  mAxisXDecoration.setVisible(false);
  mAxisXDecoration.panelChangeListener = function(event) {
	if (event == "bounds") {
  		mAxisXDecoration.setPosition([mWorld.xminPreferred,mWorld.yminPreferred,mWorld.zminPreferred]);
		var sx = Math.abs(mWorld.xmaxPreferred - mWorld.xminPreferred);
  		mAxisXDecoration.setSize([sx,0,0]);
  	}
  };  
  self.addDecoration(mAxisXDecoration, 0);

  var mAxisYDecoration = EJSS_DRAWING3D.arrow(mName + "axisY");
  mAxisYDecoration.setPosition([0,0,0]);
  mAxisYDecoration.setSize([0,1,0]);
  mAxisYDecoration.setColor("green");
  mAxisYDecoration.setVisible(false);
  mAxisYDecoration.panelChangeListener = function(event) {
	if (event == "bounds") {
  		mAxisYDecoration.setPosition([mWorld.xminPreferred,mWorld.yminPreferred,mWorld.zminPreferred]);
		var sy = Math.abs(mWorld.ymaxPreferred - mWorld.yminPreferred);  		
  		mAxisYDecoration.setSize([0,sy,0]);
  	}
  };  
  self.addDecoration(mAxisYDecoration, 0);

  var mAxisZDecoration = EJSS_DRAWING3D.arrow(mName + "axisZ");
  mAxisZDecoration.setPosition([0,0,0]);
  mAxisZDecoration.setSize([0,0,1]);
  mAxisZDecoration.setColor("blue");
  mAxisZDecoration.setVisible(false);
  mAxisZDecoration.panelChangeListener = function(event) {
	if (event == "bounds") {		
  		mAxisZDecoration.setPosition([mWorld.xminPreferred,mWorld.yminPreferred,mWorld.zminPreferred]);
		var sz = Math.abs(mWorld.zmaxPreferred - mWorld.zminPreferred);		
  		mAxisZDecoration.setSize([0,0,sz]);
  	}
  };  
  self.addDecoration(mAxisZDecoration, 0);

  // basic axis decoration
  var mBasicAxisXDecoration = EJSS_DRAWING3D.arrow(mName + "baxisX");
  mBasicAxisXDecoration.setPosition([0,0,0]);
  mBasicAxisXDecoration.setSize([1,0,0]);
  mBasicAxisXDecoration.setColor("red");
  mBasicAxisXDecoration.setVisible(false);
  mBasicAxisXDecoration.panelChangeListener = function(event) {
	if (event == "bounds") {
		var sx = Math.abs(mWorld.xmaxPreferred - mWorld.xminPreferred)/2;
  		mBasicAxisXDecoration.setSize([sx,0,0]);
  	}
  };    
  self.addDecoration(mBasicAxisXDecoration, 0);

  var mBasicAxisYDecoration = EJSS_DRAWING3D.arrow(mName + "baxisY");
  mBasicAxisYDecoration.setPosition([0,0,0]);
  mBasicAxisYDecoration.setSize([0,1,0]);
  mBasicAxisYDecoration.setColor("green");
  mBasicAxisYDecoration.setVisible(false);
  mBasicAxisYDecoration.panelChangeListener = function(event) {
	if (event == "bounds") {
		var sy = Math.abs(mWorld.ymaxPreferred - mWorld.yminPreferred)/2;  		
  		mBasicAxisYDecoration.setSize([0,sy,0]);
  	}
  };    
  self.addDecoration(mBasicAxisYDecoration, 0);

  var mBasicAxisZDecoration = EJSS_DRAWING3D.arrow(mName + "baxisZ");
  mBasicAxisZDecoration.setPosition([0,0,0]);
  mBasicAxisZDecoration.setSize([0,0,1]);
  mBasicAxisZDecoration.setColor("blue");
  mBasicAxisZDecoration.setVisible(false);
  mBasicAxisZDecoration.panelChangeListener = function(event) {
	if (event == "bounds") {
		var sz = Math.abs(mWorld.zmaxPreferred - mWorld.zminPreferred)/2;		
  		mBasicAxisZDecoration.setSize([0,0,sz]);
  	}
  };  
  self.addDecoration(mBasicAxisZDecoration, 0);
  
  mInteraction = EJSS_DRAWING3D.panelInteraction(self);
  
  return self;
};






/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/***
 * Element is the basic class for 3D elements
 * @class EJSS_DRAWING3D.Element 
 * @constructor  
 */
EJSS_DRAWING3D.Element = {
    
    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * Copies one element into another
     * @method copyTo
     */
    copyTo : function(source, dest) {
      EJSS_DRAWING3D.Style.copyTo(source.getStyle(),dest.getStyle());
      dest.setVisible(source.isVisible());
      dest.setMeasured(source.isMeasured());

      dest.setPosition(source.getPosition());
      dest.setSize(source.getSize());
      dest.setTransformation(source.getTransformation());
      dest.setExtraTransformation(source.getExtraTransformation());
      dest.setTextureUrl(source.getTextureUrl());

	  dest.setResolution(source.getResolution());

      dest.setParent(source.getParent());
    },
    
    /**
     * Registers properties in a ControlElement
     * @method registerProperties
     * @param element The element with the properties
     * @param controller A ControlElement that becomes the element controller
     */
    registerProperties : function(element,controller) {
      element.setController(controller);

	 /*** 
	  * Parent of the element
	  * @property Parent 
	  * @type Panel|Group
	  */  
      controller.registerProperty("Parent", element.setParent, element.getParent); 

	 /*** 
	  * Position in X
	  * @property X 
	  * @type double
	  * @default 0
	  */  
      controller.registerProperty("X",element.setX,element.getX);
	 /*** 
	  * Position in Y
	  * @property Y 
	  * @type double
	  * @default 0
	  */  
      controller.registerProperty("Y",element.setY,element.getY);
	 /*** 
	  * Position in Z
	  * @property Z 
	  * @type double
	  * @default 0
	  */  
      controller.registerProperty("Z",element.setZ,element.getZ);
	 /*** 
	  * Coordinates X, Y, and Z
	  * @property Position 
	  * @type double[3]
	  * @default [0,0,0]
	  */        
      controller.registerProperty("Position",element.setPosition,element.getPosition);

	 /*** 
	  * Size along the X axis
	  * @property SizeX 
	  * @type double
	  * @default 1
	  */                          
      controller.registerProperty("SizeX",element.setSizeX,element.getSizeX);
	 /*** 
	  * Size along the Y axis
	  * @property SizeY 
	  * @type double
	  * @default 1
	  */                          
      controller.registerProperty("SizeY",element.setSizeY,element.getSizeY);
	 /*** 
	  * Size along the Z axis
	  * @property SizeZ 
	  * @type double
	  * @default 1
	  */                          
      controller.registerProperty("SizeZ",element.setSizeZ,element.getSizeZ);
	 /*** 
	  * Size along the X, Y, and Z axes 
	  * @property Size 
	  * @type double[3]
	  * @default [1,1,1]
	  */                          
      controller.registerProperty("Size",element.setSize,element.getSize);

      controller.registerProperty("TextureUrl",element.setTextureUrl,element.getTextureUrl);

      controller.registerProperty("Transformation",element.setTransformation);

      controller.registerProperty("Visibility",  element.setVisible, element.isVisible);
      controller.registerProperty("Measured", element.setMeasured, element.isMeasured);

      controller.registerProperty("AlwaysUpdated", element.setAlwaysUpdated, element.isAlwaysUpdated);

      controller.registerProperty("Transparency",  element.getStyle().setTransparency, element.getStyle().getTransparency);

      controller.registerProperty("AmbientColor",  element.getStyle().setAmbientColor, element.getStyle().getAmbientColor);
      controller.registerProperty("FillColor",  element.getStyle().setFillColor, element.getStyle().getFillColor);
      controller.registerProperty("SpecularColor",  element.getStyle().setSpecularColor, element.getStyle().getSpecularColor);
      controller.registerProperty("AmbientReflection",  element.getStyle().setAmbientReflection, element.getStyle().getAmbientReflection);
      controller.registerProperty("ColorReflection",  element.getStyle().setColorReflection, element.getStyle().getColorReflection);
      controller.registerProperty("SpecularReflection",  element.getStyle().setSpecularReflection, element.getStyle().getSpecularReflection);
      controller.registerProperty("Shininess",  element.getStyle().setShininessVal, element.getStyle().getShininessVal);

      controller.registerProperty("PaletteFloor",  element.getStyle().setPaletteFloor, element.getStyle().getPaletteFloor);
      controller.registerProperty("PaletteCeil",  element.getStyle().setPaletteCeil, element.getStyle().getPaletteCeil);
      controller.registerProperty("PaletteFloorColor",  element.getStyle().setPaletteFloorColor, element.getStyle().getPaletteFloorColor);
      controller.registerProperty("PaletteCeilColor",  element.getStyle().setPaletteCeilColor, element.getStyle().getPaletteCeilColor);

      controller.registerProperty("LineColor",  element.getStyle().setLineColor, element.getStyle().getLineColor);
      controller.registerProperty("LineWidth",  element.getStyle().setLineWidth, element.getStyle().getLineWidth);
      controller.registerProperty("DrawLines",  element.getStyle().setDrawLines, element.getStyle().getDrawLines);
      controller.registerProperty("DrawFill",   element.getStyle().setDrawFill, element.getStyle().getDrawFill);
      controller.registerProperty("ClosedTop",   element.getStyle().setClosedTop, element.getStyle().getClosedTop);
      controller.registerProperty("ClosedBottom",   element.getStyle().setClosedBottom, element.getStyle().getClosedBottom);
      controller.registerProperty("ClosedLeft",   element.getStyle().setClosedLeft, element.getStyle().getClosedLeft);
      controller.registerProperty("ClosedRight",   element.getStyle().setClosedRight, element.getStyle().getClosedRight);

	  controller.registerProperty("Resolution", element.setResolution);
      controller.registerProperty("Color", element.setColor);

	 /*** 
	  * Whether the user could change the position   
	  * @property EnabledPosition 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("EnabledPosition",function(enabled) {
        //element.getInteractionTarget(TARGET_POSITION).setMotionEnabled(enabled);
      });

	 /*** 
	  * Whether the group position also changes when the element position changes    
	  * @property MovesGroup 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("MovesGroup", function(affects) {
        //element.getInteractionTarget(TARGET_POSITION).setAffectsGroup(affects);
      });

	 /*** 
	  * Whether the user could change the size   
	  * @property EnabledSize 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("EnabledSize",function(enabled) {
        //element.getInteractionTarget(TARGET_SIZE).setMotionEnabled(enabled);
      });

	 /*** 
	  * Whether the group size also changes when the element size changes    
	  * @property ResizesGroup 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("ResizesGroup", function(affects) {
        //element.getInteractionTarget(TARGET_SIZE).setAffectsGroup(affects);
      });


      // Actions
      controller.registerAction("OnEnter",   element.getPosition);
      controller.registerAction("OnExit",    element.getPosition);
      controller.registerAction("OnPress",   element.getPosition);
      controller.registerAction("OnDrag",    element.getPosition);
      controller.registerAction("OnRelease", element.getPosition);
      
      controller.registerAction("OnLoadTexture", null, null, function() { element.setProjChanged(true);});
    }
};

/**
 * Constructor for Element
 * @method element
 * @param mName string
 * @returns An abstract 3D element
 */
EJSS_DRAWING3D.element = function(mName) {
  var self = {};							// reference returned 

  // Static references
  var Element = EJSS_DRAWING3D.Element;		// reference for Element

  // Instance variables
  var mStyle = EJSS_DRAWING3D.style(mName);	// style for element 
  var mVisible = true;								// whether visible in drawing
  var mMeasured = true;								// whether measure for element

  var mTextureUrl = "";

  // Position and size
  var mX = 0;						// position X
  var mY = 0;						// position Y
  var mZ = 0;						// position Z
  var mSizeX = 1;					// size X
  var mSizeY = 1;					// size Y
  var mSizeZ = 1;					// size Z
  var mTransformation = [];			// transformations for element
  var mExtraTransformations = [];			// extra transformations for element
  var mFirstTransformation = []; // very first transformation, not to be set by the user directly

  // Implementation variables    
  var mPanel = null;				// drawing panel for element
  var mGroup = null;				// group for element
  var mSet = null;				    // The set it belongs to (if any)  
  var mIndexInSet = -1;				// The index of the element in a set (if any)  
  var mMeshChanged = true;		    // whether element mesh changed (resolution, ...)
  var mProjChanged = true;			// whether element projection changed (position, size, or group)
  var mAlwaysUpdated = false;		// whether element mesh must be always drawn

  var mResolutionU = 20;
  var mResolutionV = 20;
  var mResolutionR = 0;
  
  var mController = { 				// dummy controller object
      propertiesChanged : function() {},
      invokeAction : function() {}
  };

  // ----------------------------------------
  // Instance functions
  // ----------------------------------------

  /***
   * Copies itself to another element
   * @method copyTo
   * @param element Element
   */
  self.copyTo = function(element) {
    Element.copyTo(self,element);
  };
  
  /***
   * Get name for element
   * @method getName
   * @return string
   */
  self.getName = function() {
    return mName.replace(/\[|\]/g,"");
  };  

  /***
   * Set texture url
   * @method setTextureUrl
   * @param url
   */
  self.setTextureUrl = function(url) {
//  	console.log ("Texture = "+url);
//  	console.log ("Set = "+mSet);
//  	if (mSet!=null) console.log ("ResPathF = "+mSet.getResourcePath);
  	var resPathFunction = (mSet!=null) ? mSet.getResourcePath : self.getResourcePath; 
  	if (resPathFunction!=null) {
  	  url = resPathFunction(url);
  	  //alert ("  set to = "+url+"\n");
  	}
  	else { 
  		console.log ("No getResourcePath function for "+self.getName()+". Texture = " + url);
  	}
  	mTextureUrl = url;
  }

  /***
   * Get texture url
   * @method getTextureUrl
   * @return url
   */
  self.getTextureUrl = function() {
  	return mTextureUrl;
  }

  // -------------------------------------
  // Visible, style, measure
  // -------------------------------------

  /***
   * Sets the visibility of the element
   * @method setVisible
   * @param visible boolean
   */
  self.setVisible = function(visible) { 
    mVisible = visible; 
  };

  /***
   * Whether the element is visible
   * @method isVisible
   * @return boolean
   */
  self.isVisible = function() { 
    return mVisible; 
  };

  /***
   * Returns the real visibility status of the element, which will be false if
   * it belongs to an invisible group
   * @method isGroupVisible
   * @return boolean
   */
  self.isGroupVisible = function() {
    var el = mGroup;
    while (typeof el != "undefined" && el !== null) {
      if (!el.isVisible()) return false;
      el = el.getGroup();
    }
    return mVisible;
  };

  /***
   * Return the style (defined in DrawingPanel.js) of the inner rectangle
   * @method getStyle 
   * @return boolean
   */
  self.getStyle = function() { 
    return mStyle; 
  };

  /***
   * Sets the measurability of the element
   * @method setMeasured
   * @param measured boolean
   */
  self.setMeasured = function(measured) { 
    mMeasured = measured; 
  };

  /***
   * Whether the element is measured
   * @method isMeasured
   * @return boolean
   */
  self.isMeasured = function() { 
    return mMeasured; 
  };

  /***
   * Set color 
   * @method setColor
   * @param color
   */
  self.setColor = function(color) {
  	self.getStyle().setFillColor(color);
  	self.getStyle().setLineColor(color);
  }

  /***
   * Set color 
   * @method setColor
   * @param color
   */
  self.setResolution = function(resolution) {
  	if (Array.isArray(resolution)) {
  		self.setResolutionU(resolution[0]);
  		self.setResolutionV(resolution[1]);
  		if(resolution[2]) self.setResolutionR(resolution[2]);
  	} else {
  		self.setResolutionU(resolution);
  		self.setResolutionV(resolution);  		
  	}
  }
	
  /** TODO **/
  self.getResolution = function() {
  	return [mResolutionU,mResolutionV,mResolutionR];
  }

  self.setResolutionU = function(resolution) {
  	if (mResolutionU!=resolution) {
  		mResolutionU = resolution;
  		mMeshChanged = true; 
  	}
  }

  self.getResolutionU = function() {
  	return mResolutionU;
  }

  self.setResolutionV = function(resolution) {
  	if (mResolutionV!=resolution) {
  		mResolutionV = resolution;
  		mMeshChanged = true; 
  	}
  }

  self.getResolutionV = function() {
  	return mResolutionV;
  }

  self.setResolutionR = function(resolution) {
  	if (mResolutionR!=resolution) {
  		mResolutionR = resolution;
  		mMeshChanged = true; 
  	}
  }

  self.getResolutionR = function() {
  	return mResolutionR;
  }

  // ----------------------------------------
  // Position of the element
  // ----------------------------------------

  /***
   * Set the X coordinate of the element
   * @method setX
   * @param x double
   */
  self.setX = function(x) { 
    if (mX!=x) { 
      mX = x; 
      mProjChanged = true; 
    } 
  };

  /***
   * Get the X coordinate of the element
   * @method getX
   * @return double
   */
  self.getX = function() { 
    return mX; 
  };

  /***
   * Set the Y coordinate of the element
   * @method setY
   * @param y double
   */
  self.setY = function(y) {  
    if (mY!=y) { 
      mY = y; 
      mProjChanged = true; 
    } 
  };

  /***
   * Get the Y coordinate of the element
   * @method getY
   * @return double
   */
  self.getY = function() { 
    return mY; 
  };

  /***
   * Set the Z coordinate of the element
   * @method setZ
   * @param z double
   */
  self.setZ = function(z) {  
    if (mZ!=z) { 
      mZ = z; 
      mProjChanged = true; 
    } 
  };

  /***
   * Get the Z coordinate of the element
   * @method getZ
   * @return double
   */
  self.getZ = function() { 
    return mZ; 
  };

  /***
   * Set the coordinates of the element
   * @method setPosition
   * @param position double[] an array of dimension 3
   */
  self.setPosition = function(position) {
    self.setX(position[0]);
    self.setY(position[1]);
    self.setZ(position[2]);
  };

  /***
   * Get the coordinates of the element
   * @method getPosition
   * @return double[3]
   */
  self.getPosition = function() { 
    return [mX, mY, mZ]; 
  };
    
  // ----------------------------------------
  // Size of the element
  // ----------------------------------------

  /***
   * Set the size along the X axis of the element
   * @method setSizeX
   * @param sizeX double
   */
  self.setSizeX = function(sizeX) { 
    if (mSizeX!=sizeX) { 
      mSizeX = sizeX; 
      mProjChanged = true; 
    } 
  };

  /***
   * Get the size along the X coordinate of the element
   * @method getSizeX
   * @return double
   */
  self.getSizeX = function() { 
    return mSizeX; 
  };

  /***
   * Set the size along the Y axis of the element
   * @method setSizeY
   * @param sizeY double
   */
  self.setSizeY = function(sizeY) { 
    if (mSizeY!=sizeY) { 
      mSizeY = sizeY; 
      mProjChanged = true; 
    }
  };

  /***
   * Get the size along the Y coordinate of the element
   * @method getSizeY
   * @return double
   */
  self.getSizeY = function() { 
    return mSizeY; 
  };

  /***
   * Set the size along the Z axis of the element
   * @method setSizeZ
   * @param sizeZ double
   */
  self.setSizeZ = function(sizeZ) { 
    if (mSizeZ!=sizeZ) { 
      mSizeZ = sizeZ; 
      mProjChanged = true; 
    }
  };

  /***
   * Get the size along the Z coordinate of the element
   * @method getSizeZ
   * @return double
   */
  self.getSizeZ = function() { 
    return mSizeZ; 
  };

  /***
   * Set the size of the element
   * @method setSize
   * @param position double[] an array of dimension 3 or an object with {x,y,z} properties
   */
  self.setSize = function(size) {
    self.setSizeX(size[0]);
    self.setSizeY(size[1]);
    self.setSizeZ(size[2]);
  };

  /***
   * Get the sizes of the element
   * @method getSize
   * @return double[]
   */
  self.getSize = function() {
    return [self.getSizeX(), self.getSizeY(), self.getSizeZ()];
  };
      
  // ----------------------------------------
  // Panel and group
  // ----------------------------------------

  /***
   * To be used internally by DrawingPanel only! Sets the panel for this element.
   * @method setPanel
   * @param panel DrawingPanel
   */
  self.setPanel = function(panel) {
    mPanel = panel;
    mProjChanged = true;     
  };

  /***
   * For internal use only, use getGroupPanel() instead. Gets the panel for this element.
   * @method getPanel
   * @return DrawingPanel
   */
  self.getPanel = function() { 
    return mPanel;
  };

  /***
   * Returns the DrawingPanel in which it (or its final ancestor group) is displayed.
   * @method getGroupPanel
   * @return drawing3D.DrawingPanel
   */
  self.getGroupPanel = function() { 
    var el = self;
    while (el.getGroup()) el = el.getGroup();
    return el.getPanel();
  };

  /***
   * To be used internally by Group only! Sets the group of this element.
   * @method setGroup
   * @param group Group
   */
  self.setGroup = function(group) {
    mGroup = group;
    mProjChanged = true; 
  };

  /***
   * Get the group of this element, if any
   * @method getGroup
   * @return Group
   */
  self.getGroup = function() { 
    return mGroup; 
  };

  /***
   * To be used internally by ElementSet only! Sets the index of this element in the set
   * @method setSet
   * @param set ElementSet
   * @param index int
   */
  self.setSet = function(set,index) {
    mSet = set;
    mIndexInSet = index;
  };

  /***
   * Get the index of this element in a set, if any
   * @method getSetIndex
   * @return int
   */
  self.getSetIndex = function() { 
    return mIndexInSet; 
  };

  /***
   * Set the parent
   * @method setParent
   * @param parent Panel or Element
   */
  self.setParent = function(parent) {
  	if(parent.render) { // is a panel  		
  		self.setGroup(null);
  		parent.addElement(self);
  		self.setPanel(parent);
  	} else if (parent.getClass() == "ElementGroup") { // is a group
  		self.setGroup(parent);
  		self.getGroupPanel().addElement(self);
  		self.setPanel(self.getGroupPanel());
  	} else { //
  		console.log("WARNING: setParent() - Parent not valid : "+ parent.getName()); 
  	}
  };

  /***
   * Get the parent
   * @method getParent
   * @return parent Panel or Element
   */
  self.getParent = function() {
  	var parent;
  	if(mGroup !== null) parent = self.getGroup();
  	else parent = self.getPanel();
  	return parent  
  };

  // ----------------------------------------------------
  // Transformations
  // ----------------------------------------------------

  /***
   * Sets the internal transformation of the element.
   * @method setTransformation
   * @param tr list of transformation arrays supports: rotation based on vector [angle, x, y, z, cx, cy, cz], 
   *  rotation based on element center [angle, x, y, z], custom axes [x1, x2, x3, y1, y2, y3, z1, z2, z3] or
   *  full transformation with a matrix 4x4 
   */
  self.setTransformation = function(tr) {
  	if (typeof tr == "undefined" || tr === null) tr = [];
  	if(!EJSS_TOOLS.compareArrays(mTransformation, tr)) {
  		mTransformation = tr;
    	mProjChanged = true;
    }
  };

  self.setFirstTransformation = function(tr) {
  	if (typeof tr == "undefined" || tr === null) tr = [];
  	if(!EJSS_TOOLS.compareArrays(mTransformation, tr)) {
  		mFirstTransformation = tr;
    	mProjChanged = true;
    }
  };

  self.setExtraTransformation = function(tr) {
    mExtraTransformations = tr;
  }

  self.getExtraTransformation = function() {
    return mExtraTransformations;
  }

  self.addExtraTransformation = function(tr) {
  	if (typeof tr == "undefined" || tr === null) return;
    mExtraTransformations.push(tr);
    mProjChanged = true;
  };

  self.removeExtraTransformation = function(tr) {
  	if (typeof tr == "undefined" || tr === null) return;
  	for (var i=0; i<mExtraTransformations.length; i++) {
      var oneTr = mExtraTransformations[i];
      if (oneTr===tr) {
        mExtraTransformations.splice(i,1);
        mProjChanged = true;
        return;
      }
    }
  };

  function centerTransform(transform) {
    var centeredTr = [];
    for (var i=0; i<transform.length; i++) { 
      var tr = transform[i]; 
      if (tr.length>4) centeredTr.push(tr);
      else centeredTr.push(tr.concat(self.getPosition())); // set element center
    }
    return centeredTr;
  };
    
  /***
   * Get the internal transformation of the element.
   * @method getTransformation
   * @return Transformation 
   */
  self.getFullTransformation = function() {
    var fullTr = centerTransform(mFirstTransformation).concat(centerTransform(mTransformation)); //.concat(mFirstTransformation);
    for (var i=0; i<mExtraTransformations.length; i++) {
      fullTr.push(mExtraTransformations[i].getArray());
    }
    return fullTr;
  };
  
  /***
   * Get the internal transformation of the element.
   * @method getTransformation
   * @return Transformation 
   */
  self.getTransformation = function() {
    return mTransformation;
  };

  /***
   * Get the group transformation of the element.
   * @method getGroupTransformation
   * @return Transformation 
   */
  self.getGroupTransformation = function() {
  	var tr = EJSS_DRAWING3D.transformation();
    var el = self;
    while (typeof el != "undefined" && el !== null) {
      tr.concatenate(el.getFullTransformation());
      el = el.getGroup();
    }
    return tr;
  };

  // ----------------------------------------
  // Changes
  // ----------------------------------------

  /***
   * Whether element mesh must be always drawn
   * @method isAlwaysUpdated
   * @return boolean
   */
  self.isAlwaysUpdated = function() {
    return mAlwaysUpdated;
  };

  /***
   * Tells whether element mesh must be always drawn
   * @method setAlwaysUpdated
   * @param always boolean
   */
  self.setAlwaysUpdated = function(always) {
    mAlwaysUpdated = always;
  };

  /***
   * Whether the element mesh has changed
   * @method isChanged
   * @return boolean
   */
  self.isMeshChanged = function() {
    return mMeshChanged;
  };

  /***
   * Tells the element mesh that it has changed.
   * Typically used by subclasses when they change something.
   * @method setMeshChanged
   * @param changed boolean
   */
  self.setMeshChanged = function(changed) {
    mMeshChanged = changed;
  };

  /***
   * Whether the element projection has changed
   * @method isChanged
   * @return boolean
   */
  self.isProjChanged = function() {
    return mProjChanged;
  };

  /***
   * Tells the element projection that it has changed.
   * Typically used by subclasses when they change something.
   * @method setProjChanged
   * @param changed boolean
   */
  self.setProjChanged = function(changed) {
    mProjChanged = changed;
  };

  /***
   * Returns whether the element group has changed.
   * @method isGroupChanged
   * @return boolean
   */
  self.isGroupChanged = function() {
    var el = self.getGroup();
    while (typeof el != "undefined" && el !== null) {
      if (el.isChanged()) return true;
      el = el.getGroup();
    }
    return false;
  };

  // ----------------------------------------
  // Interaction
  // ----------------------------------------

  /***
   * Returns the controller object
   * @method getController
   * @return Controller
   */
  self.getController = function () {
    return mController;
  };

  /***
   * Set the controller
   * @method setController
   * @param Controller
   */
  self.setController = function (controller) {
    mController = controller;
  };

  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  /***
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   */
  self.registerProperties = function(controller) {
    Element.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  mStyle.setMeshChangeListener(function (change) { mMeshChanged = true; });
  mStyle.setProjChangeListener(function (change) { mProjChanged = true; });
  
  return self;

};

//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------------------




/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/***
 * ElementSet is the basic class for a set of Elements
 * @class EJSS_DRAWING3D.ElementSet 
 * @constructor  
 */
EJSS_DRAWING3D.ElementSet = {
    
    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

    /**
     * Registers properties in a ControlElement
     * @method 
     * @param element The element with the properties
     * @param controller A ControlElement that becomes the element controller
     */
    registerProperties : function(set,controller) {
      set.setController(controller); // remember it, in case you change the number of elements
      set.setToEach(function(element,value) { element.setController(value); }, controller); // make all elements in the set report to the same controller

      controller.registerProperty("Parent", 
      	  function(panel) {
      	  	set.setParent(panel); 
      		set.setToEach(function(element,value) { element.setParent(value); }, set); 
      	  } );

      controller.registerProperty("NumberOfElements", set.setNumberOfElements);

      controller.registerProperty("ElementInteracted", set.setElementInteracted, set.getElementInteracted);

//      controller.registerProperty("x",function(v) { set.foreach("setX",v); }, function() { return set.getall("getX"); });
      controller.registerProperty("X",
          function(v) { set.setToEach(function(element,value) { element.setX(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getX(); } ); }      
          );
      controller.registerProperty("Y",
          function(v) { set.setToEach(function(element,value) { element.setY(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getY(); } ); }      
      );
      controller.registerProperty("Z",
          function(v) { set.setToEach(function(element,value) { element.setZ(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getZ(); } ); }      
      );
      controller.registerProperty("Position",
          function(v) { set.setToEach(function(element,value) { element.setPosition(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getPosition(); } ); }      
      );

      controller.registerProperty("SizeX",
          function(v) { set.setToEach(function(element,value) { element.setSizeX(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getSizeX(); } ); }      
          );
      controller.registerProperty("SizeY",
          function(v) { set.setToEach(function(element,value) { element.setSizeY(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getSizeY(); } ); }      
      );
      controller.registerProperty("SizeZ",
          function(v) { set.setToEach(function(element,value) { element.setSizeZ(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getSizeZ(); } ); }      
      );
      controller.registerProperty("Size",
          function(v) { set.setToEach(function(element,value) { element.setSize(value); }, v); },
          function()  { return set.getFromEach(function(element) { return element.getSize(); } ); }      
      );
      controller.registerProperty("TextureUrl", 
          function(v) { set.setToEach(function(element,value) { element.setTextureUrl(value); }, v); }
      );

      controller.registerProperty("Transformation",
      	  function(v) { set.setToEach(function(element,value) { element.setTransformation(value); }, v);
      });

      controller.registerProperty("Visibility", 
          function(v) { set.setToEach(function(element,value) { element.setVisible(value); }, v); }
      );
      controller.registerProperty("Measured", 
          function(v) { set.setToEach(function(element,value) { element.setMeasured(value); }, v); }
      );

      controller.registerProperty("Transparency", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setTransparency(value); }, v); }
      );
      controller.registerProperty("AmbientColor", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setAmbientColor(value); }, v); }
      );
      controller.registerProperty("FillColor", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setFillColor(value); }, v); }
      );
      controller.registerProperty("SpecularColor", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setSpecularColor(value); }, v); }
      );
      controller.registerProperty("AmbientReflection", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setAmbientReflection(value); }, v); }
      );
      controller.registerProperty("ColorReflection", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setColorReflection(value); }, v); }
      );
      controller.registerProperty("SpecularReflection", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setSpecularReflection(value); }, v); }
      );
      controller.registerProperty("Shininess", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setShininessVal(value); }, v); }
      );

      controller.registerProperty("LineColor", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setLineColor(value); }, v); }
      );
      controller.registerProperty("LineWidth", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setLineWidth(value); }, v); }
      );
      controller.registerProperty("DrawLines", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setDrawLines(value); }, v); }
      );
      controller.registerProperty("FillColor", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setFillColor(value); }, v); }
      );
      controller.registerProperty("DrawFill", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setDrawFill(value); }, v); }
      );

      controller.registerProperty("ClosedTop", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setAttributes(value); }, v); }
      );
      controller.registerProperty("ClosedBottom", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setAttributes(value); }, v); }
      );
      controller.registerProperty("ClosedLeft", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setAttributes(value); }, v); }
      );
      controller.registerProperty("ClosedRight", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setAttributes(value); }, v); }
      );

      controller.registerProperty("Resolution", 
          function(v) { set.setToEach(function(element,value) { element.getStyle().setAttributes(value); }, v); }
      );
      controller.registerProperty("Color", 
          function(v) { set.setToEach(function(element,value) { element.setColor(value); }, v); }
      );

	 /*** 
	  * Whether the user could change the position   
	  * @property EnabledPosition 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("EnabledPosition",
        function(v) { set.setToEach(function(element,value) {  }, v); }
        //element.getInteractionTarget(TARGET_POSITION).setMotionEnabled(enabled);
      );

	 /*** 
	  * Whether the group position also changes when the element position changes    
	  * @property MovesGroup 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("MovesGroup", 
        function(v) { set.setToEach(function(element,value) {  }, v); }
        //element.getInteractionTarget(TARGET_POSITION).setAffectsGroup(affects);
      );

	 /*** 
	  * Whether the user could change the size   
	  * @property EnabledSize 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("EnabledSize",
        function(v) { set.setToEach(function(element,value) {  }, v); }
        //element.getInteractionTarget(TARGET_SIZE).setMotionEnabled(enabled);
      );

	 /*** 
	  * Whether the group size also changes when the element size changes    
	  * @property ResizesGroup 
	  * @type boolean
	  * @default false
	  */                                
      controller.registerProperty("ResizesGroup", 
        function(v) { set.setToEach(function(element,value) {  }, v); }
        //element.getInteractionTarget(TARGET_SIZE).setAffectsGroup(affects);
      );


      var dataFunction = function() { 
        //var index = set.getGroupPanel().getPanelInteraction().getIndexElement();
        //set.setElementInteracted(index);
        //return { index: index, position: set.getElements()[index].getPosition() };
        var panel = set.getGroupPanel();
        var index = panel.getPanelInteraction().getIndexElement();
        var element = panel.getElements()[index];
        var elementIndex = element.getSetIndex(); 
        set.setElementInteracted(elementIndex);
  	    controller.propertiesChanged("ElementInteracted");
	    //controller.reportInteractions();	 
        return { index: elementIndex, position: element.getPosition() };   
      };
      
      // Actions
      controller.registerAction("OnEnter",   dataFunction);
      controller.registerAction("OnExit",    dataFunction);
      controller.registerAction("OnPress",   dataFunction);
      controller.registerAction("OnDrag",    dataFunction);
      controller.registerAction("OnRelease", dataFunction);
      
      // controller.registerAction("OnLoadTexture", null, null, function() { element.setProjChanged(true);});
    }
};

/**
 * Element set
 * Creates a basic abstract ElementSet
 * @method elementSet
 * @param mConstructor the function that creates new elements (will be used as element = mConstructor(name))
 * @returns An abstract 2D element set
 */
EJSS_DRAWING3D.elementSet = function (mConstructor, mName) {  
  var self = EJSS_DRAWING3D.group(mName);

  // Static references
  var ElementSet = EJSS_DRAWING3D.ElementSet;		// reference for ElementSet
  
  // Configuration variables
  var mElementList = []; 
  var mNumberOfElementsSet = false;

  // Implementation variables  
  var mElementInteracted = -1;

  // Last list of removed elements
  var mLastElementList = [];

  // ----------------------------------------
  // Configuration methods
  // ----------------------------------------

  /**
   * Sets the number of element of this set
   * @method setNumberOfElements
   * @param numberOfElements the number of elements, must be >= 1
   */
  self.setNumberOfElements = function(numberOfElements) {
	mNumberOfElementsSet = true;
	adjustNumberOfElements(numberOfElements);
  };
  
  /*
   * Adjusts the number of element of this set
   * @method adjustNumberOfElements
   * @param numberOfElements the number of elements, must be >= 1
   */
  function adjustNumberOfElements(numberOfElements) {
    // keep original settings for the new elements
    var name = self.getName ? self.getName() : "unnamed";
    numberOfElements = Math.max(1,numberOfElements);
    var diff = mElementList.length-numberOfElements;
    if (diff > 0) {
    	mLastElementList = mElementList.splice(numberOfElements, diff);
		for (var j = 0; j < mLastElementList.length; j++) { 
			var panel = mLastElementList[j].getPanel(); // remove element from panel
			if(panel) panel.removeElement(mLastElementList[j])
		} 
    } else if (diff < 0) {
    	mLastElementList = [];
    	var controller = self.getController();
    	var oldElement = mElementList[mElementList.length-1];
		for (var i = mElementList.length; i < numberOfElements; i++) {
			var element = mConstructor(name+"["+i+"]"); // new element			
			element.setSet(self,i);
			oldElement.copyTo(element);
			element.setController(controller);
		  	mElementList.push(element);	
		} 
    }
  };

  /**
   * Returns the array with all elements in the set
   * @method getElements
   * @return array of Elements
   */
  self.getElements = function() {
    return mElementList;
  };

  /**
   * Returns last list of removed elements and reset the value
   * @method getLastElements
   * @return last list of Elements
   */
  self.getLastElements = function() {
  	var ret = mLastElementList.slice();
  	mLastElementList = [];
    return ret;
  };
  
  self.setElementInteracted = function(index) {
    mElementInteracted = index;
  };

  self.getElementInteracted = function() {
    return mElementInteracted;
  };
  
  // ----------------------------------------
  // Relation to its panel
  // ----------------------------------------
  
  var super_setPanel = self.setPanel;

  self.setPanel = function(panel) {
    super_setPanel(panel);
    for (var i=0,n=mElementList.length;i<n;i++) mElementList[i].setPanel(panel);
  };

  self.setProjChanged = function(needsIt) {
    for (var i=0,n=mElementList.length;i<n;i++) mElementList[i].setProjChanged(needsIt);
  };
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  /**
   * Applies to each element in the set the function f with argument v, or v[i], if it is an array
   * @method setToEach
   * @param f function
   * @param v arguments
   */
  self.setToEach = function(f,v) {
    if (Array.isArray(v)) {
      if (!mNumberOfElementsSet) { 
    	if (mElementList.length < v.length) adjustNumberOfElements(v.length);
      }
      for (var i=0,n=Math.min(mElementList.length,v.length);i<n;i++) f(mElementList[i],v[i]);
    }
    else {
      for (var i=0,n=mElementList.length;i<n;i++) f(mElementList[i],v);
    }
  };

  /**
   * Returns an array with the result of applying the function to each element of the set
   * @method getFromEach
   * @param f function
   * @return f function return 
   */
  self.getFromEach = function(f) {
    var value = [];
    for (var i=0, n=mElementList.length;i<n;i++) value[i] = f(mElementList[i]);
    return value;
  };

  /**
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   */
  self.registerProperties = function(controller) {
    ElementSet.registerProperties(self,controller);
  };
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
	
  var element = mConstructor(mName + "[0]"); // new element			
  element.setController(self.getController());
  element.setSet(self,0);
  mElementList.push(element);
  	
  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Ellipsoid
 * @class Ellipsoid 
 * @constructor  
 */
EJSS_DRAWING3D.Ellipsoid = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  	
		dest.setMinAngleU(source.getMinAngleU());
		dest.setMaxAngleU(source.getMaxAngleU());
		dest.setMinAngleV(source.getMinAngleV());
		dest.setMaxAngleV(source.getMaxAngleV());
		
  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

      controller.registerProperty("MinAngleU", element.setMinAngleU);
      controller.registerProperty("MaxAngleU", element.setMaxAngleU);
      controller.registerProperty("MinAngleV", element.setMinAngleV);
      controller.registerProperty("MaxAngleV", element.setMaxAngleV);
		
	}

};

/**
 * Creates a 3D Ellipsoid
 * @method ellipsoid
 */
EJSS_DRAWING3D.ellipsoid = function (name) {
  var self = EJSS_DRAWING3D.element(name);
  var mMinAngleU = 0; 		// the start angle (in degrees) for the parallels
  var mMaxAngleU = 360;		// the end angle (in degrees) for the parallels
  var mMinAngleV = -90;		// the start angle (in degrees) for the meridians
  var mMaxAngleV = 90;		// the end angle (in degrees) for the meridians

  // Implementation variables
  self.getClass = function() {
  	return "ElementEllipsoid";
  }

  self.setMinAngleU = function(angle) {
  	if(mMinAngleU != angle) {
  	  mMinAngleU = angle;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getMinAngleU = function() {
  	return mMinAngleU;
  }

  self.setMaxAngleU = function(angle) {
  	if(mMaxAngleU != angle) {
  	  mMaxAngleU = angle;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getMaxAngleU = function() {
  	return mMaxAngleU;
  }
  
  self.setMinAngleV = function(angle) {
  	if(mMinAngleV != angle) {
  	  mMinAngleV = angle;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getMinAngleV = function() {
  	return mMinAngleV;
  }

  self.setMaxAngleV = function(angle) {
  	if(mMaxAngleV != angle) {
  	  mMaxAngleV = angle;
  	  self.setMeshChanged(true);
  	}  	
  }

  self.getMaxAngleV = function() {
  	return mMaxAngleV;
  }
    
  self.copyTo = function(element) {
	EJSS_DRAWING3D.Ellipsoid.copyTo(self,element);
  };
      
  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Ellipsoid.registerProperties(self, controller);
  };
	    
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);
  self.setResolution([30,20]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//EllipsoidSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * EllipsoidSet
 * @class EllipsoidSet 
 * @constructor  
 */
EJSS_DRAWING3D.EllipsoidSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller);
       
      controller.registerProperty("MinAngleU", 
          function(v) { set.setToEach(function(element,value) { element.setMinAngleU(value); }, v); }
      );    
      controller.registerProperty("MaxAngleU", 
          function(v) { set.setToEach(function(element,value) { element.setMaxAngleU(value); }, v); }
      );    
      controller.registerProperty("MinAngleV", 
          function(v) { set.setToEach(function(element,value) { element.setMinAngleV(value); }, v); }
      );    
      controller.registerProperty("MaxAngleV", 
          function(v) { set.setToEach(function(element,value) { element.setMaxAngleV(value); }, v); }
      );                             
    }

};


/**
 * Creates a set of ellipsoids
 * @method ellipsoidSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.ellipsoidSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.ellipsoid,mName);

  // Static references
  var EllipsoidSet = EJSS_DRAWING3D.EllipsoidSet;		// reference for EllipsoidSet
  
  self.registerProperties = function(controller) {
    EllipsoidSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Group
 * @class Group 
 * @constructor  
 */
EJSS_DRAWING3D.Group = {

};

/**
 * Creates a group
 * @method group
 */
EJSS_DRAWING3D.group = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  self.getClass = function() {
  	return "ElementGroup";
  }
  
  self.isGroup = function() {
  	return true;
  }
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Constructor for Interaction
 * @param drawing panel
 * @returns An interaction
 */
EJSS_DRAWING3D.panelInteraction = function(mPanel) {
  var self = {};	// reference returned 

  var mEnabled = false;			// whether interaction is enable 
  var mLastLocation = [0,0];	// last position on panel trying interaction 
  var mDeltaX = 0;
  var mDeltaY = 0;
  var mMouseIsDown = false;		// last event is mouse down
  var mUpLastTime = 0;			// last timestamp for mouse up  
  var mZoomLastTime = 0;		// last timestamp for pinching  
  var mZoomDelta = 0;
  var mLastZoom = 0;
  var mPinching = false;
  var mPinchInitDistance = [0,0];

  /**
   * Return panel
   * @return drawing panel 
   */
  self.getPanel = function() {  	
    return mPanel;
  };  

  self.getEnabled = function() {
  	return mEnabled;
  } 

  self.setEnabled = function(enabled) {
  	if(mEnabled != enabled) {
	  	mEnabled = enabled;
	  	
	  	if(mEnabled) {
		  self.setHandler("move",self.handleMouseMoveEvent);
		  self.setHandler("down",self.handleMouseDownEvent);
		  self.setHandler("up",self.handleMouseUpEvent);     		
		  self.setHandler("mousewheel",self.handleMouseWheelEvent);
		  // self.setHandler("pinch",self.handlePinchGesture)
	  	} else { 
		  self.setHandler("move",(function() {}));
		  self.setHandler("down",(function() {}));
		  self.setHandler("up",(function() {}));    			  		
		  self.setHandler("mousewheel",(function() {}));
		  // self.setHandler("pinch",(function() {}));
	  	}
  	}
  } 

  /**
   * Return the last interaction point
   * @method getInteractionPoint
   * @return double[]
   */
  self.getInteractionPoint = function() {
    // mLastPoint = mPanel.toPanelPosition([location[0],location[1]]);
    return mLastLocation;
  };

  /**
   * Return the last interaction point deltas
   * @method getInteractionDeltas
   * @return double[]
   */
  self.getInteractionDeltas = function() {
    return [mDeltaX, mDeltaY];
  };

  /**
   * Return the last interaction zoom delta
   * @method getInteractionZoomDelta
   * @return double
   */
  self.getInteractionZoomDelta = function() {
    return mZoomDelta;
  };

  /**
   * Handler for mouse wheel event
   * @method handleMouseWheelEvent
   * @param event
   */
  self.handleMouseWheelEvent = function(event) {
	// number of fingers over screen	
	var nFingers = (typeof event.touches != "undefined")? event.touches.length:0;
	
	if(nFingers == 0) {  	
        // Prevent the browser from doing its default thing (scroll, zoom)
	    event.preventDefault(); 

		mZoomDelta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail)));	
        mPanel.getController().invokeAction("OnZoom");
        
	    // all interactions are reported to View
	    mPanel.getController().reportInteractions();
	}
  };

  /**
   * Handler for pinch gesture (only supported by safari and iOS)
   * @method handlePinchGesture
   * @param event
   */
  self.handlePinchGesture = function(event) {  	
	if(event.timeStamp - mZoomLastTime > 100) { 
		// Prevent the browser from doing its default thing (scroll, zoom)
		event.preventDefault();
				
		if (event.scale < mLastZoom) {
		    // User moved fingers closer together
		    mZoomDelta = 1;                
		} else if (event.scale >= mLastZoom) {
		    // User moved fingers further apart
		    mZoomDelta = -1;
		}
		mLastZoom = event.scale;
		
		mPanel.getController().invokeAction("OnZoom");
		
		// all interactions are reported to View
		mPanel.getController().reportInteractions();
		
		mZoomLastTime = event.timeStamp;
	} 
	
  };
  
  /**
   * Handler for mouse move event
   * @method handleMouseMoveEvent
   * @param event
   * @param location
   */
  self.handleMouseMoveEvent = function(event) {
	// number of fingers over screen	
	var nFingers = (typeof event.touches != "undefined")? event.touches.length:1;
	
	if(nFingers == 1) {  	
	  	var currentX = mLastLocation[0];
	  	var currentY = mLastLocation[1];  	
	    mLastLocation = getEventLocation(event); 
	    
	    mDeltaX = mLastLocation[0] - currentX;
	    mDeltaY = mLastLocation[1] - currentY; 
	    
	    if (mMouseIsDown) { // Mouse is down
      	  mPanel.getController().invokeAction("OnDrag");
	      event.target.style.cursor = 'move';
	    }
	    else { // Mouse is up
	      event.target.style.cursor = 'default';
	    }
	
	}
	
	if (nFingers == 2) {
		if (event.timeStamp - mZoomLastTime > 25) { // time filter
			// Prevent the browser from doing its default thing (scroll, zoom)
			event.preventDefault();
			
			var e0 = event.touches[0]; var e1 = event.touches[1];
			var e0x = (e0.clientX || e0.x); var e0y = (e0.clientY || e0.y);
			var e1x = (e1.clientX || e1.x); var e1y = (e1.clientY || e1.y);
			if(mPinching) {
	    		var newDistance = [Math.abs(e0x-e1x),Math.abs(e0y-e1y)];
	    		var delta = newDistance[0]-mPinchInitDistance[0]; 
	    		if (delta<3 && delta>-3) { // minimum filter
	    			mZoomDelta = 0;
	    		} else {
	    			mZoomDelta = delta;
	    			mPinchInitDistance = newDistance;								
	    		}
				mPanel.getController().invokeAction("OnZoom");
			} else {
				mPinching = true;
	    		mPinchInitDistance = [Math.abs(e0x-e1x),Math.abs(e0y-e1y)];
	    		mZoomDelta = 0;
			}					
			mZoomLastTime = event.timeStamp;
		}
	} else {
		mPinching = false;
		mPinchInitDistance = [0,0];
	}		
	
    // all interactions are reported to View
    mPanel.getController().reportInteractions();
  };

  /**
   * Handler for mouse down event
   * @method handleMouseDownEvent
   * @param event
   * @param location
   */
  self.handleMouseDownEvent = function(event) {
	// number of fingers over screen	
	var nFingers = (typeof event.touches != "undefined")? event.touches.length:1;
	
	if(nFingers == 1) {  	
	  	mMouseIsDown = true;
	  	
	  	mLastLocation = getEventLocation(event);   		
    	mPanel.getController().invokeAction("OnPress");
	    self.pick(mLastLocation[0],mLastLocation[1]);
	    
	    // Prevent the browser from doing its default thing (scroll, zoom)
	    event.preventDefault(); 
	    
	    // all interactions are reported to View
	    mPanel.getController().reportInteractions();
	}    
	
	mPinching = false;
  };

  /**
   * Handler for mouse up event
   * @method handleMouseUpEvent
   * @param event
   * @param location
   */
  self.handleMouseUpEvent = function(event) {
	// number of fingers over screen	
	var nFingers = (typeof event.touches != "undefined")? event.touches.length:1;
	
	if(nFingers == 1) {  	  	
	    mMouseIsDown = false; 
	
	  	mLastLocation = getEventLocation(event);
	
      	mPanel.getController().invokeAction("OnRelease");       
	    event.target.style.cursor = 'default';
	
		if(event.timeStamp - mUpLastTime < 500) { // < 500 ms is dblclick
			mPanel.getController().invokeAction("OnDoubleClick");
		}
		mUpLastTime = event.timeStamp; 
	    
	    // all interactions are reported to View
	    mPanel.getController().reportInteractions();
	} 
	
	mPinching = false;   
  };  

  /**
   * Get location for the touch event 
   * @method getEventLocation 
   * @param {Object} e
   */
  function getEventLocation(e) {
  	var x, y;
	var box = mPanel.getGraphics().getBox();
	var oleft = box.left; // offset left in pixels
	var otop = box.top; // offset top in pixels
  	
    if ((typeof e.changedTouches != "undefined") && (e.changedTouches.length === 1)) {    	
      	x = e.changedTouches[0].pageX;
      	y = e.changedTouches[0].pageY;      
	} else {
	    x = e.x || e.clientX;
	    y = e.y || e.clientY;
	}  	  	

  	return [x-oleft, y-otop];
  }
 
  self.setHandler = function(type, handler) {
	  var graphics = mPanel.getGraphics();
	  var context = graphics.getEventContext();
	  switch (type) {
	    case "move" : 
		  context.addEventListener( 'mousemove', handler, false );
		  context.addEventListener( 'touchmove', handler, false );
	      break;
	    default : 
	    case "down" :
	      context.addEventListener( 'mousedown', handler, false );
	      context.addEventListener( 'touchstart', handler, false );
	      break;
	    case "up" :
	      context.addEventListener( 'mouseup', handler, false );
	      context.addEventListener( 'touchend', handler, false );
	      break;
	    case "mousewheel" :
	      context.addEventListener( 'mousewheel', handler, false );
		  break;	      
	    case "pinch" :
	      context.addEventListener( 'gesturestart', function(event) { mLastZoom = 1; }, false );
	      context.addEventListener( 'gesturechange', handler, false );
	      break;
	  }
	  return false;
	}

  /**
   * Picking in webgl
   */   
  self.pick = function (mousex, mousey) {
    // we want to read the pixel at x, y -- so we really need a rectangle from x-1,y-1 with witdth and height equal to 1
    var x = mousex - 1;
    var y = mousey - 1;
    var w = 1;
    var h = 1;
    
    var gl = mPanel.getGraphics().getContext();
    if(!gl) return;
        
    var data = new Uint8Array(w*h*4); // w * h * 4
    gl.readPixels(x, y, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data);
    if(data.data) data=data.data;

// http://stackoverflow.com/questions/7156971/webgl-readpixels-is-always-returning-0-0-0-0
// http://webgldemos.thoughtsincomputation.com/engine_tests/picking

/*    
    var indices = null, index, i;
    for (i = 2; i < data.length; i += 4) {
      if (data[i] > 0) // check the 'blue' key (2)
      {
        index = decodeFromColor(data[i-2], data[i-1], data[i], data[i+1]);
        if (index)
        {
          if (!indices) indices = {};
          indices[index] = index;
        }
      }
    }
    self.context.bindFramebuffer(GL_FRAMEBUFFER, null);
    self.context.viewport(0,0,self.context.gl.viewportWidth,self.context.gl.viewportHeight);

    self.context.enable(GL_BLEND);
        
    if (indices) {
      var ind = [];
      for (i in indices) ind.push(indices[i]);
      
      if (ind[0]) return this.objects[ind];
    }
      
    return null;  	
*/  }
    
  return self;          
}  
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Style object for 3D drawing
 * @class Style 
 * @constructor  
 */
EJSS_DRAWING3D.Style = {
    
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * Copies one element into another
   */
  copyTo : function(source, dest) {
    dest.setClosedTop(source.getClosedTop());
    dest.setClosedBottom(source.getClosedBottom());
    dest.setClosedLeft(source.getClosedLeft());
    dest.setClosedRight(source.getClosedRight());
    dest.setDrawLines(source.getDrawLines());
    dest.setLineColor(source.getLineColor());
    dest.setLineWidth(source.getLineWidth());
    dest.setDrawFill(source.getDrawFill());
    dest.setTransparency(source.getTransparency());
    
	dest.setAmbientColor(source.getAmbientColor());
	dest.setFillColor(source.getFillColor());
	dest.setSpecularColor(source.getSpecularColor());
	dest.setAmbientReflection(source.getAmbientReflection());
	dest.setColorReflection(source.getColorReflection());
	dest.setSpecularReflection(source.getSpecularReflection());
	dest.setShininessVal(source.getShininessVal());
	
	dest.setPaletteFloor(source.getPaletteFloor());
	dest.setPaletteCeil(source.getPaletteCeil());
	dest.setPaletteFloorColor(source.getPaletteFloorColor());
	dest.setPaletteCeilColor(source.getPaletteCeilColor());	    
  },
  
};

/**
 * Creates a Style object for 3D drawing
 */
EJSS_DRAWING3D.style = function (mName) {
  var self = {};
  
  var mClosedTop = true;
  var mClosedBottom = true;
  var mClosedLeft = true;
  var mClosedRight = true;
  var mLineColor = [0,0,0];
  var mLineWidth = 0.5;     
  
  // type of material
  var mAmbientColor = [0.0,0.0,0.0];
  var mFillColor = [0.0,0.0,0.0];
  var mSpecularColor = [1.0,1.0,1.0];
  var mAmbientReflection = 1.0;
  var mColorReflection = 1.0;
  var mSpecularReflection = 1.0;
  var mShininessVal = 10;

  // color palette of material
  var mPaletteFloor; // e.g. [0.0,0.0,-5.0];
  var mPaletteCeil;  // e.g. [0.0,0.0,5.0];
  var mPaletteFloorColor = [1.0,0.0,-1.0];
  var mPaletteCeilColor = [-1.0,0.0,1.0];

  var mDrawLines = false;
  var mDrawFill = true;
  var mTransparency = 0.0;  
  var mProjChangeListener;       
  var mMeshChangeListener;       

  /**
   * Set a listener that will be called whenever there are style changes.
   * I.e. a call to listener("change"); will be issued
   */
  self.setProjChangeListener = function(listener) {
    mProjChangeListener = listener;
  };

  self.setMeshChangeListener = function(listener) {
    mMeshChangeListener = listener;
  };

  //---------------------------------
  // Closed
  //---------------------------------

  self.setClosedTop = function(closed) {
    if (closed!=mClosedTop) {
      mClosedTop = closed;
      if (mMeshChangeListener) mMeshChangeListener("closedtop");
    }
  };
  
  self.getClosedTop = function() { 
    return mClosedTop; 
  };

  self.setClosedBottom = function(closed) {
    if (closed!=mClosedBottom) {
      mClosedBottom = closed;
      if (mMeshChangeListener) mMeshChangeListener("closedbottom");
    }
  };
  
  self.getClosedBottom = function() { 
    return mClosedBottom; 
  };
  
  self.setClosedLeft = function(closed) {
    if (closed!=mClosedLeft) {
      mClosedLeft = closed;
      if (mMeshChangeListener) mMeshChangeListener("closedleft");
    }
  };
  
  self.getClosedLeft = function() { 
    return mClosedLeft; 
  };
  
  self.setClosedRight = function(closed) {
    if (closed!=mClosedRight) {
      mClosedRight = closed;
      if (mMeshChangeListener) mMeshChangeListener("closedright");
    }
  };
  
  self.getClosedRight = function() { 
    return mClosedRight; 
  };
  
  //---------------------------------
  // lines
  //---------------------------------

  /**
   * Whether to draw the lines of the element
   */
  self.setDrawLines = function(draw) {
    if (draw!=mDrawLines) {
      mDrawLines = draw;
      if (mProjChangeListener) mProjChangeListener("drawlines");
    }
  };
  
  /**
   * Get the draw lines flag
   */
  self.getDrawLines = function() { 
    return mDrawLines; 
  };
  
  /**
   * Set the line color of the element
   * @param color a stroke style
   */
  self.setLineColor = function(color) { 
    if (typeof color === "string") color = EJSS_TOOLS.DisplayColors.getArrayColor(color);
    if (!EJSS_TOOLS.compareArrays(color,mLineColor)) {
      mLineColor = color.slice(); 
      if (mProjChangeListener) mProjChangeListener("linecolor");
    }
    return self;
  };
    
  /**
   * Get the line color
   */
  self.getLineColor = function() { 
    return mLineColor; 
  };

  /**
   * Set the line width of the element
   * @param width a stroke width (may be double, such as 0.5, the default)
   */
  self.setLineWidth = function(width) { 
    if (width!=mLineWidth) {
      mLineWidth = width; 
      if (mProjChangeListener) mProjChangeListener("linewidth");
    }
  };

  /**
   * Get the line width
   */
  self.getLineWidth = function() { return mLineWidth; };
  
  //---------------------------------
  // interior fill
  //---------------------------------

  /**
   * Whether to fill the interior of the element
   */
  self.setDrawFill = function(draw) {
    if (draw!=mDrawFill) {
      mDrawFill = draw;
      if (mProjChangeListener) mProjChangeListener("drawfill");
    }
  };
  
  /**
   * Get the draw fill flag
   */
  self.getDrawFill = function() { 
    return mDrawFill; 
  };

  /**
   * Set the fill color of the element
   * @param color a fill style
   */
  self.setFillColor = function(color) {
    if (typeof color === "string") color = EJSS_TOOLS.DisplayColors.getArrayColor(color);
    if (!EJSS_TOOLS.compareArrays(color,mFillColor)) {
      mFillColor = color.slice(); 
      if (mProjChangeListener) mProjChangeListener("fillcolor");
    }
  };

  /**
   * Get the fill color
   */
  self.getFillColor = function() { 
    return mFillColor; 
  };
  
  /**
   * Set the Ambient color of the element
   * @param color ambient color
   */
  self.setAmbientColor = function(color) {
    if (typeof color === "string") color = EJSS_TOOLS.DisplayColors.getArrayColor(color);
    if (!EJSS_TOOLS.compareArrays(color,mAmbientColor)) {
      mAmbientColor = color.slice(); 
      if (mProjChangeListener) mProjChangeListener("ambientcolor");
    }
  };
  
  /**
   * Get the Ambient color
   */
  self.getAmbientColor = function() { 
    return mAmbientColor; 
  };

  /**
   * Set the Specular color of the element
   * @param color specular color
   */
  self.setSpecularColor = function(color) {
    if (typeof color === "string") color = EJSS_TOOLS.DisplayColors.getArrayColor(color);
    if (!EJSS_TOOLS.compareArrays(color,mSpecularColor)) {
      mSpecularColor = color.slice(); 
      if (mProjChangeListener) mProjChangeListener("specularcolor");
    }
  };
  
  /**
   * Get the Specular color
   */
  self.getSpecularColor = function() { 
    return mSpecularColor; 
  };

  /**
   * Set the Ambient Reflection of the element
   * @param k coefficient
   */
  self.setAmbientReflection = function(k) {
    if (mAmbientReflection != k) {
      mAmbientReflection = k; 
      if (mProjChangeListener) mProjChangeListener("ambientreflection");
    }
  };
  
  /**
   * Get the Ambient reflection
   */
  self.getAmbientReflection = function() { 
    return mAmbientReflection; 
  };

  /**
   * Set the Color Reflection of the element
   * @param k coefficient
   */
  self.setColorReflection = function(k) {
    if (mColorReflection != k) {
      mColorReflection = k; 
      if (mProjChangeListener) mProjChangeListener("colorreflection");
    }
  };
  
  /**
   * Get the Color reflection
   */
  self.getColorReflection = function() { 
    return mColorReflection; 
  };

  /**
   * Set the Specular Reflection of the element
   * @param k coefficient
   */
  self.setSpecularReflection = function(k) {
    if (mSpecularReflection != k) {
      mSpecularReflection = k; 
      if (mProjChangeListener) mProjChangeListener("specularreflection");
    }
  };
  
  /**
   * Get the Specular reflection
   */
  self.getSpecularReflection = function() { 
    return mSpecularReflection; 
  };

  /**
   * Set the shininess of the element
   * @param shininess coefficient
   */
  self.setShininessVal = function(shininess) {
    if (mShininessVal != shininess) {
      mShininessVal = shininess; 
      if (mProjChangeListener) mProjChangeListener("shininess");
    }
  };
  
  /**
   * Get the shininess 
   */
  self.getShininessVal = function() { 
    return mShininessVal; 
  };

  /**
   * Set transparency
   * @param transparency
   */
  self.setTransparency = function(transparency) {
    if (transparency!=mTransparency) {
      mTransparency = transparency; 
      if (mProjChangeListener) mProjChangeListener("transparency");
    }
  };
  
  /**
   * Get transparency
   */
  self.getTransparency = function() { 
    return mTransparency; 
  };

  /**
   * Set the palette floor of the element
   * @param floor palette floor
   */
  self.setPaletteFloor = function(floor) {
    if (floor && !EJSS_TOOLS.compareArrays(floor,mPaletteFloor)) {
      mPaletteFloor = floor.slice(); 
      if (mProjChangeListener) mProjChangeListener("palettefloor");
    }
  };

  /**
   * Get the palette floor
   */
  self.getPaletteFloor = function() { 
    return mPaletteFloor; 
  };

  /**
   * Set the palette ceil of the element
   * @param ceil palette ceil
   */
  self.setPaletteCeil = function(ceil) {
    if (ceil && !EJSS_TOOLS.compareArrays(ceil,mPaletteCeil)) {
      mPaletteCeil = ceil.slice(); 
      if (mProjChangeListener) mProjChangeListener("paletteceil");
    }
  };

  /**
   * Get the palette ceil
   */
  self.getPaletteCeil = function() { 
    return mPaletteCeil; 
  };

  /**
   * Set the palette floor color of the element
   * @param color palette floor color
   */
  self.setPaletteFloorColor = function(color) {
    if (!EJSS_TOOLS.compareArrays(color,mPaletteFloorColor)) {
      mPaletteFloorColor = color.slice(); 
      if (mProjChangeListener) mProjChangeListener("palettefloorcolor");
    }
  };

  /**
   * Get the palette floor color
   */
  self.getPaletteFloorColor = function() { 
    return mPaletteFloorColor; 
  };

  /**
   * Set the palette ceil color of the element
   * @param color palette ceil color
   */
  self.setPaletteCeilColor = function(color) {
    if (!EJSS_TOOLS.compareArrays(color,mPaletteCeilColor)) {
      mPaletteCeilColor = color.slice(); 
      if (mProjChangeListener) mProjChangeListener("paletteceilcolor");
    }
  };

  /**
   * Get the palette ceil color
   */
  self.getPaletteCeilColor = function() { 
    return mPaletteCeilColor; 
  };


  //---------------------------------
  // final initialization
  //---------------------------------
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Transformation object for 3D elements.
 * A transformation is an array of 7 numbers indicating
 *  [ rotation angle (in degrees), 
 *    x,y,z coordinates of the rotation axis, 
 *    x,y,z coordinates of the origin of the rotation ] 
 * @class Transformation 
 * @constructor  
 */
EJSS_DRAWING3D.Transformation = {
    /**
     * Registers properties in a ControlElement
     * @method registerProperties
     * @param element The element with the properties
     * @param controller A ControlElement that becomes the element controller
     */
    registerProperties : function(element,controller) {
      element.setController(controller);
      controller.registerProperty("Parent", element.setParent, element.getParent); 
      controller.registerProperty("Axis", element.setAxis, element.getAxis); 
      controller.registerProperty("Angle", element.setAngle, element.getAngle); 
      controller.registerProperty("Origin", element.setOrigin, element.getOrigin); 
    }
};

/**
 * Creates a Rotation around the X axis
 */
EJSS_DRAWING3D.rotationX = function (mName) {
  var self = EJSS_DRAWING3D.transformation();
  self.setAxis([1,0,0]);
  return self;  
}
  
/**
 * Creates a Rotation around the Y axis
 */
EJSS_DRAWING3D.rotationY = function (mName) {
  var self = EJSS_DRAWING3D.transformation();
  self.setAxis([0,1,0]);
  return self;  
}

/**
 * Creates a Rotation around the Z axis
 */
EJSS_DRAWING3D.rotationZ = function (mName) {
  var self = EJSS_DRAWING3D.transformation();
  self.setAxis([0,0,1]);
  return self;  
}

/**
 * Creates a Transformation object for 3D element
 */
EJSS_DRAWING3D.transformation = function (mName) {
  var self = {};
  
  var mParent = null;
  var mArray = [0,0,0,1,0,0,0];

  var mController = { 				// dummy controller object
      propertiesChanged : function() {},
      invokeAction : function() {}
  };
  
  self.getArray = function() {
    return mArray;
  };
  
  self.setChanged = function(changed) {
    if (changed) mParent.setProjChanged(true);
  };

  /**
   * Set the parent
   * @method setParent
   * @param parent Element
   */
  self.setParent = function(parent) {
    if (mParent) {
      mParent.removeExtraTransformation(self);
    }
    mParent = parent;
    if (mParent) {
      mParent.addExtraTransformation(self);
    }
  };

  self.getParent = function() {
    return mParent;
  };
  
  //---------------------------------
  // Customization
  //---------------------------------

  /**
   * Set the rotation angle
   * @method setAngle
   * @param angle int the angle in degrees
   */
  self.setAngle = function(angle) {
    mArray[0] = angle;
  };

  self.getAngle = function() {
    return mArray[0];
  };

  /**
   * Set the rotation axis
   * @method setAxis
   * @param axis double[3] the coordinates of the axis
   */
  self.setAxis = function(axis) {
    mArray[1] = axis[0];
    mArray[2] = axis[1];
    mArray[3] = axis[2];
  };

  self.getAxis = function() {
    return mArray.slice(1,4);
  };
  
  /**
   * Set the rotation origin
   * @method setOrigin
   * @param origin double[3] the coordinates of the origin
   */
  self.setOrigin = function(origin) {
    mArray[4] = origin[0];
    mArray[5] = origin[1];
    mArray[6] = origin[2];
  };

  self.getOrigin = function() {
    return mArray.slice(5);
  };
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  /**
   * Returns the controller object
   * @method getController
   * @return Controller
   */
  self.getController = function () {
    return mController;
  };

  /**
   * Set the controller
   * @method setController
   * @param Controller
   */
  self.setController = function (controller) {
    mController = controller;
  };
  
  /**
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   */
  self.registerProperties = function(controller) {
    EJSS_DRAWING3D.Transformation.registerProperties(self,controller);
  };
  
  //---------------------------------
  // final initialization
  //---------------------------------
  
  return self;
};

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL custom
 */

EJSS_WEBGLGRAPHICS.analyticCurve = function(mGL, mElement) {         
      evaluate = function(expression, v1, p1, v2, p2) {
       var vblevalue = {};
       vblevalue[v1] = p1;
	   vblevalue[v2] = p2;
       return expression.evaluate(vblevalue);
      };

	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName(), 
	  	{coords:false, normals:false, triangles:false, lines:true, colors:false});

	  var numRows = mElement.getNumPoints();
	  var minRows = mElement.getMinValue();
	  var maxRows = mElement.getMaxValue();
	  var d1 = Math.abs((maxRows - minRows) / (numRows - 1));
	  
	  var exprX = mElement.getExpressionX();		
	  var exprY = mElement.getExpressionY();
	  var exprZ = mElement.getExpressionZ();
	  
	  var var1 = mElement.getVariable();
	  
	  var point1 = minRows;	  					 
	  for (var i = 0; i < numRows; i++) {
		// expresion X
		var x = evaluate(exprX, var1, point1);
		// expresion Y
		var y = evaluate(exprY, var1, point1); 
		// expresion Z		 
		var z = evaluate(exprZ, var1, point1);

	  	mesh.vertices.push([x,y,z]);
	  	if(i!=0) mesh.lines.push([i-1, i]);

		point1 = point1 + d1;
	  }
	  
	  mesh.compile();
	  	  	  
	  mGL.addElement(mElement.getName() + ".mesh", mesh);

      return mesh;
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL custom
 */

EJSS_WEBGLGRAPHICS.analyticSurface = function(mGL, mElement) {   
    
    createSurfaceBuffers = function(xpoints, ypoints, data3D, mesh){
        for (var i = 0; i < xpoints - 1; i++) {
            for (var j = 0; j < ypoints - 1; j++) {
                // Create surface vertices.
                var rawP1 = data3D[j + (i * ypoints)];
                var rawP2 = data3D[j + (i * ypoints) + ypoints];
                var rawP3 = data3D[j + (i * ypoints) + ypoints + 1];
                var rawP4 = data3D[j + (i * ypoints) + 1];
                
                mesh.vertices.push(rawP1);
                mesh.vertices.push(rawP2);
                mesh.vertices.push(rawP3);
                mesh.vertices.push(rawP4);                
            }
        }
                
        var numQuads = ((xpoints - 1) * (ypoints - 1)) / 2;        
        for (var i = 0; i < (numQuads * 8); i += 4) {
            mesh.triangles.push([i, i+1, i+2]);
            mesh.triangles.push([i, i+2, i+3]);

            mesh.lines.push([i,i+1]);             
            mesh.lines.push([i,i+3]);             
            mesh.lines.push([i+2,i+3]);             
            mesh.lines.push([i+2,i+1]);             			

            mesh.coords.push([0,0]);
            mesh.coords.push([0,1]);
            mesh.coords.push([1,0]);
            mesh.coords.push([1,1]);            
        }        
    };
  
  evaluate = function(expression, v1, p1, v2, p2) {
    var vblevalue = {};
    vblevalue[v1] = p1;
	vblevalue[v2] = p2;
    return expression.evaluate(vblevalue);
  };

	  var numRows = mElement.getNumPoints1();
	  var minRows = mElement.getMinValue1();
	  var maxRows = mElement.getMaxValue1();
	  var numCols = mElement.getNumPoints2();
	  var minCols = mElement.getMinValue2();
	  var maxCols = mElement.getMaxValue2();	 
	  var d1 = Math.abs((maxRows - minRows) / (numRows - 1));
	  var d2 = Math.abs((maxCols - minCols) / (numCols - 1));
	  
	  var exprX = mElement.getExpressionX();		
	  var exprY = mElement.getExpressionY();
	  var exprZ = mElement.getExpressionZ();
	  
	  var var1 = mElement.getVariable1();
	  var var2 = mElement.getVariable2();
	  
	  var data3ds = new Array();
	  var index = 0;
	  var point1 = minRows;	  					 
	  for (var i = 0; i < numRows; i++) {
		var point2 = minCols;								
		for (var j = 0; j < numCols; j++) {
			// expresion X
			var x = evaluate(exprX, var1, point1, var2, point2);
			// expresion Y
			var y = evaluate(exprY, var1, point1, var2, point2); 
			// expresion Z		 
			var z = evaluate(exprZ, var1, point1, var2, point2);
			data3ds[index] = [x,y,z];
			index++;			
			point2 = point2 + d2;
		}				
		point1 = point1 + d1;
	  }
	  	  	  
	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName());
	  
	  createSurfaceBuffers(numRows, numCols, data3ds, mesh);

	  // compute normals
	  mesh.computeNormals();
	  
	  mesh.compile();
	  	  	  
	  mGL.addElement(mElement.getName() + ".mesh", mesh);

      return mesh;
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL arrow
 */
EJSS_WEBGLGRAPHICS.arrow = function(mGL, mElement) {
  	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName(), 
	  	{coords:false, normals:true, triangles:true, lines:false, colors:false});
  	  	
  	  // end point
  	  var endpoint = new Vector(mElement.getSize());
  	  var lenendpoint = endpoint.length();
  	  
  	  // resolution used to arrow head and segment
	  var resolutionU = mElement.getResolutionU();
	  var headWidth = mElement.getHeadWidth();
	  var headHeight = mElement.getHeadHeight();
	  
	  // generate vertex for head (head point at (0,0,0))
	  var hvertices3D = [new Vector(0, 0, 0)];
	  var hradius = (lenendpoint / headWidth); 	  
	  for (var x = 0; x < resolutionU; x++ ) {
		var u = x / (resolutionU - 1);

		var xpos = hradius * Math.sin( u * Math.PI * 2 );
		var ypos = hradius * Math.cos( u * Math.PI * 2 );
		var zpos = (- lenendpoint / headHeight);   

		hvertices3D.push(new Vector(xpos, ypos, zpos));
	  }

	  // generate vertex for segment
	  var svertices3D = [];
	  var sradius = mElement.getLineWidth() / 2;
	  for (var x = 0; x < resolutionU; x++ ) {
		var u = x / (resolutionU - 1);
		var xpos = sradius * Math.sin( u * Math.PI * 2 );
		var ypos = sradius * Math.cos( u * Math.PI * 2 );
		var zpos = (- lenendpoint / headHeight);   

		svertices3D.push(new Vector([xpos, ypos, zpos])); // top vertex
		svertices3D.push(new Vector([xpos, ypos, 0])); // bottom vertex
	  }
	  	  
	  // rotate and translate head
	  var cross = endpoint.cross(new Vector(0,0,1));
	  if(cross.length()==0) cross.y = 1; // if endoint in axis Z, get axis Y to rotate 
	  // console.log("cross:" + cross.x + " " + cross.y + " " + cross.z + " ");	  
	  var angle = endpoint.angle(new Vector(0,0,1));
	  // console.log("angle:" + angle);
	  var result = new Matrix();
	  Matrix.rotate(-angle,cross.x,cross.y,cross.z,0,0,0,result);
	  for(var i=0; i<hvertices3D.length; i++) { // head vertex
	  	var vector = result.transformPoint(hvertices3D[i]);
	  	mesh.vertices.push(vector.add(endpoint).toArray());	  		
	  }
	  for(var i=0; i<svertices3D.length; i++) { // segment vertex
	  	var vector = result.transformPoint(svertices3D[i]);
	  	if (i % 2 == 0) {
	  		// add enpoint to translate	  	
	  		mesh.vertices.push(vector.add(endpoint).toArray());	  		
	  	} else {
	  		mesh.vertices.push(vector.toArray());
	  	}	  		  		
	  }
	  	  
	  // create surface triangles for head
	  for (var x = 1; x < resolutionU; x++ ) {
	    var first = x;		    
	    var second = x + 1;		
        mesh.triangles.push([0, first, second]);
	  }		
			  
	  // create surface triangles for segment
	  for (var x = 0; x < resolutionU - 1; x++ ) {
	    var first = 2*x + hvertices3D.length;		    
	    var fourth = 2*x + 2 + hvertices3D.length;
	    var second = 2*x + 1 + hvertices3D.length;
	    var third = 2*x + 3 + hvertices3D.length;
			
        mesh.triangles.push([third, second, first]);      
        mesh.triangles.push([fourth, third, first]);
	  }		
			  
	  // bottom cap
 	  mesh.vertices.push([0, 0, 0]);
      for (var i = 0; i < resolutionU - 1; i++) {
      	var first = 2*i+1 + hvertices3D.length;;
      	var second = 2*i+3 + hvertices3D.length;;
      	var third = mesh.vertices.length - 1;
       	mesh.triangles.push([first, second, third]);
      }        
			  
      mesh.computeNormals();	  
	        
	  mesh.compile();	
	
	  mGL.addElement(mElement.getName() + ".mesh", mesh);
          
      return mesh;
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL basic
 */

EJSS_WEBGLGRAPHICS.basic = function(mGL, mElement) {   
	  mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName());
	  // set values
  	  mesh.setVertices(mElement.getVertices());	  
	  mesh.setCoords(mElement.getVertices());
	  mesh.setTriangles(mElement.getTriangles());
      mesh.setColors(mElement.getColors());

	  // compute normals
	  if (mElement.getNormals().length != 0) {
	  	mesh.setNormals(mElement.getNormals());
	  } else {
	  	mesh.computeNormals();
	  };
  
	  // compute wire
	  mesh.computeWireframe();
	  
	  mesh.compile();
	  
	  mGL.addElement(mElement.getName() + ".mesh", mesh);
      
      return mesh;
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL box
 */
EJSS_WEBGLGRAPHICS.box = function(mGL, mElement) {
	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName());
	  
	  var octant = [[-1,-1,-1], [1,-1,-1], [-1,1,-1], [1,1,-1], [-1,-1,1], [1,-1,1], [-1,1,1], [1,1,1]];
	  
	  var coords = [[0,0],[1,0],[0,1],[1,1]];

	  var normals = [
	   [-1, 0, 0], // -x
	   [+1, 0, 0], // +x
	   [0, -1, 0], // -y
	   [0, +1, 0], // +y
	   [0, 0, -1], // -z
	   [0, 0, +1]  // +z
	  ];
	  
	  var boxData = [
	   [0, 2, 4, 6], // -x
	   [1, 3, 5, 7], // +x
	   [0, 4, 1, 5], // -y
	   [2, 6, 3, 7], // +y
	   [0, 2, 1, 3], // -z
	   [4, 6, 5, 7]  // +z
	  ];
	
  	  var isClosedTop = mElement.getStyle().getClosedTop();
  	  var isClosedBottom = mElement.getStyle().getClosedBottom();
	  var isClosedRight = mElement.getStyle().getClosedRight();
	  var isClosedLeft = mElement.getStyle().getClosedLeft();	
	  var reduceZby = mElement.getReduceZby();
	
	  // build box
	  for (var i = 0; i < boxData.length; i++) {
	    var data = boxData[i], v = i * 4;
	    for (var j = 0; j < 4; j++) {
	      var d = data[j];
	      if(d==6 || d==7)
	      	mesh.vertices.push([octant[d][0], octant[d][1], octant[d][2] - (2*reduceZby)]);
	      else
      	  	mesh.vertices.push(octant[d]);
      	  mesh.coords.push(coords[j]);
      	  mesh.normals.push(normals[i]);
	    }
	    if(!(i == 2 && !isClosedLeft) &&
	       !(i == 3 && !isClosedRight) &&
	       !(i == 5 && !isClosedTop) &&
	       !(i == 4 && !isClosedBottom)) {
	    	mesh.triangles.push([v, v + 1, v + 2]);
	    	mesh.triangles.push([v + 2, v + 1, v]);    
	    	mesh.triangles.push([v + 2, v + 1, v + 3]);    
	    	mesh.triangles.push([v + 3, v + 1, v + 2]);
	    }
	    mesh.lines.push([v,v+1]);
	    mesh.lines.push([v,v+2]);
	    mesh.lines.push([v+2,v+3]);
	    mesh.lines.push([v+1,v+3]);
	  }
	  	  		  	 
	  mesh.compile();
	
	  mGL.addElement(mElement.getName() + ".mesh", mesh);
         
      return mesh;
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL cylinder
 */
EJSS_WEBGLGRAPHICS.cylinder = function(mGL, mElement) {
  	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName());
  	  	
	  var resolutionV = mElement.getResolutionV();
	  var resolutionU = mElement.getResolutionU() + 1;
	  
	  var radiusTop = mElement.getTopRadius();
	  var radiusBottom = mElement.getBottomRadius();      
	  var mMinAngleU = mElement.getMinAngleU();
	  var mMaxAngleU = mElement.getMaxAngleU();
      
	  // generate vertex for body
      var maxNumber = 0;
      var minNumber = 0;
	  for (var z = 0; z < resolutionV; z ++ ) {	
		var v = z / (resolutionV - 1);
		var radius = v * ( radiusBottom - radiusTop ) + radiusTop;

	  	minNumber = Math.floor(mMinAngleU * resolutionU / 360); 
	  	maxNumber = Math.floor(mMaxAngleU * resolutionU / 360);		  	
		for (var x = minNumber; x < maxNumber; x ++ ) {
			var u = x / (resolutionU - 1);

			var xpos = radius * Math.sin( u * Math.PI * 2 );
			var ypos = radius * Math.cos( u * Math.PI * 2 );
			var zpos = - v * 2 + 1;

			mesh.vertices.push([xpos, ypos, zpos]);
	        mesh.coords.push([u,v]);      			
		}
	  }
	  
	  // build central axis
	  for (var z = 0; z < resolutionV; z ++ ) {	
	      var zpos = - 1 + (z * (2 / (resolutionV - 1))); // proportional in axis
	      	      
		  mesh.vertices.push([0, 0, zpos]);
		  mesh.coords.push([1, z / (resolutionV - 1)]);		
  	  }	  
	  
	  // create surface triangles
	  var sizeU = (maxNumber - minNumber);
      for (var z = 0; z < resolutionV - 1; z++) {			
		  for (var x = 0; x < sizeU - 1; x++ ) {
		    var first = (z * sizeU) + x;		    
		    var second = ((z + 1) * sizeU) + x;
		    var third = ((z + 1) * sizeU) + x + 1;
		    var fourth = (z * sizeU) + x + 1;
			
	        mesh.triangles.push([third, second, first]);
	        mesh.triangles.push([fourth, third, first]);      
	        
		    mesh.lines.push([third, second]);
		    mesh.lines.push([fourth, third]);
		    mesh.lines.push([first, fourth]);
		  }
		
		  // last close sphera
	      if((mMinAngleU != 0) || (mMaxAngleU != 360)) { // quesito 		      		  			  	
		    
			// close left and right
	        var firstAxis = mesh.vertices.length - (z + 1); // points central axis
			if(mElement.getStyle().getClosedLeft()) {
		    	mesh.triangles.push([third, fourth, firstAxis - 1]);
		    	mesh.triangles.push([fourth, firstAxis, firstAxis - 1]);

		    	mesh.lines.push([third, firstAxis - 1]);
		    	mesh.lines.push([fourth, firstAxis]);
		    	mesh.lines.push([firstAxis, firstAxis - 1]);
		    }
		    if(mElement.getStyle().getClosedRight()) {
				// points first latitude
		    	var firstMin = z * sizeU;
				var secondMin = (z + 1) * sizeU;
		    	mesh.triangles.push([firstAxis - 1, firstMin, secondMin]);
		    	mesh.triangles.push([firstAxis - 1, firstAxis, firstMin]);

		    	mesh.lines.push([secondMin, firstMin]);
		    	mesh.lines.push([secondMin, firstAxis - 1]);
		    	mesh.lines.push([firstMin, firstAxis]);
		    	mesh.lines.push([firstAxis, firstAxis - 1]);
		    }			    
		  }			      			
	  }
		
	  // top cap
  	  var isClosedTop = mElement.getStyle().getClosedTop();
	  if ( isClosedTop && radiusTop > 0 ) {
		mesh.vertices.push([0, 0, 1]);		
        for (var i = 0; i < sizeU - 1; i++) {
           	mesh.triangles.push([mesh.vertices.length - 1, i+1, i]);
	        mesh.coords.push([1, (i / (sizeU - 2))]);
	               	
		    mesh.lines.push([mesh.vertices.length - 1, i]);
		    mesh.lines.push([i, i+1]);	               	
        }        
	  }

	  // bottom cap
  	  var isClosedBottom = mElement.getStyle().getClosedBottom();
	  if ( isClosedBottom && radiusBottom > 0 ) {
		mesh.vertices.push([0, 0, -1]);
        for (var i = (resolutionV-1)*sizeU; i < sizeU*resolutionV; i++) {
           	mesh.triangles.push([i, i+1, mesh.vertices.length - 1]);
           	mesh.coords.push([1, (i / (sizeU - 2))]);

		    mesh.lines.push([mesh.vertices.length - 1, i]);
		    mesh.lines.push([i, i+1]);	               	
        }        
	  }			  
	  
	  // compute normals
	  if((mMinAngleU == 0) && (mMaxAngleU == 360)) 
	  	mesh.normals = mesh.vertices;
	  else
	    mesh.computeNormals();	  
	        
	  mesh.compile();	
	
	  mGL.addElement(mElement.getName() + ".mesh", mesh);
        
     return mesh;
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL ellipsoid
 */
EJSS_WEBGLGRAPHICS.ellipsoid = function(mGL, mElement) {
	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName());
	  	  
	  var indexer = EJSS_WEBGLGRAPHICS.indexer();
	
	  // generate vertex for body
	  var latitudeBands = mElement.getResolutionV();  	
	  var longitudeBands = mElement.getResolutionU();
	  for (var latNumber = 0; latNumber <= latitudeBands; latNumber++) {
		  var theta = latNumber * Math.PI / latitudeBands;
		  var sinTheta = Math.sin(theta);
		  var cosTheta = Math.cos(theta);
		
		  for (var longNumber = 0; longNumber <= longitudeBands; longNumber++) {
		    var phi = longNumber * 2 * Math.PI / longitudeBands;
		    var sinPhi = Math.sin(phi);
		    var cosPhi = Math.cos(phi);
		
		    var y = cosPhi * sinTheta;
		    var z = -cosTheta;
		    var x = -sinPhi * sinTheta;
		    var u = 1 - (longNumber / longitudeBands);
		    var v = (latNumber / latitudeBands);
		
			mesh.vertices.push([-x, y, z]);
			mesh.coords.push([u,v]);
		  }		  	
	  }
	  	
      // generate vertex for central axis
	  for (var latNumber = 0; latNumber <= latitudeBands; latNumber++) {		
	      var v =  latNumber / latitudeBands;
		  var theta = latNumber * Math.PI / latitudeBands;	      
	      var z = -Math.cos(theta);
	      // var z = - 1 + (latNumber * (2 / latitudeBands)); // proportional in axis
		
	      mesh.vertices.push([0, 0, z]);
		  mesh.coords.push([0,v]);
	  }	  
  		
	  // limits for sphera
	  var mMinAngleU = mElement.getMinAngleU();
	  var mMaxAngleU = mElement.getMaxAngleU();
	  var mMinAngleV = mElement.getMinAngleV() + 90;
	  var mMaxAngleV = mElement.getMaxAngleV() + 90;
	  
	  // build triangles
	  var latNumber;
  	  var minLatNumber = Math.floor(mMinAngleV * latitudeBands / 180); 
  	  var maxLatNumber = Math.floor(mMaxAngleV * latitudeBands / 180);
	  for (latNumber = minLatNumber; latNumber < maxLatNumber; latNumber++) {
	  	  var longNumber;	  	  
	  	  // close sphera
	  	  var minLongNumber = Math.floor(mMinAngleU * longitudeBands / 360); 
	  	  var maxLongNumber = Math.floor(mMaxAngleU * longitudeBands / 360);
	  	  var first, second;
		  for (longNumber = minLongNumber; longNumber < maxLongNumber; longNumber++) {
		    first = (latNumber * (longitudeBands + 1)) + longNumber;
		    second = first + longitudeBands + 1;
		    mesh.triangles.push([first, second, first + 1]);
		    mesh.triangles.push([second, second + 1, first + 1]);
		    
		    mesh.lines.push([first, second]);
		    mesh.lines.push([first, first+1]);
		    mesh.lines.push([second, second+1]);
		  }
		  			  
		  // last close sphera
	      if((mMinAngleU != 0) || (mMaxAngleU != 360)) { 	// quesito					  	  	
			// close left and right
	        var firstAxis = mesh.vertices.length - (latitudeBands - latNumber + 1); // points central axis
			if(mElement.getStyle().getClosedLeft()) {
		    	mesh.triangles.push([first + 1, second + 1, firstAxis]);
		    	mesh.triangles.push([second + 1, firstAxis + 1, firstAxis]);

		    	mesh.lines.push([first+1, second+1]);
		    	mesh.lines.push([first+1, firstAxis]);
		    	mesh.lines.push([second+1, firstAxis+1]);
		    	mesh.lines.push([firstAxis, firstAxis+1]);
		    }
		    if(mElement.getStyle().getClosedRight()) {
				// points first latitude
		    	var firstMin = (latNumber * (longitudeBands + 1)) + minLongNumber;
				var secondMin = firstMin + longitudeBands + 1;
		    	mesh.triangles.push([firstAxis, secondMin, firstMin]);
		    	mesh.triangles.push([firstAxis, firstAxis + 1, secondMin]);

		    	mesh.lines.push([firstMin, secondMin]);
		    	mesh.lines.push([firstMin, firstAxis]);
		    	mesh.lines.push([secondMin, firstAxis+1]);
		    	mesh.lines.push([firstAxis, firstAxis+1]);
		    }			    
		  }			      	
	  }
	  
	  // close bottom
      if(mElement.getStyle().getClosedBottom() && (mMinAngleV > 0)) {      	  
		  var first, longNumber, axis;
		  for (longNumber = minLongNumber; longNumber < maxLongNumber; longNumber++) {
		    first = (minLatNumber * (longitudeBands + 1)) + longNumber;
		    axis = mesh.vertices.length - (latitudeBands - minLatNumber + 1);
		    mesh.triangles.push([first + 1, axis, first]);
		    
		    mesh.lines.push([first+1,axis]);
		    mesh.lines.push([first,first+1]);
		  }      	
      }
      
	  // close top
      if(mElement.getStyle().getClosedTop() && (mMaxAngleV < 180)) {
      	  var first, longNumber, axis;
		  for (longNumber = minLongNumber; longNumber < maxLongNumber; longNumber++) {
		    first = (maxLatNumber * (longitudeBands + 1)) + longNumber;
		    axis = mesh.vertices.length - (latitudeBands - maxLatNumber + 1);
		    mesh.triangles.push([first, axis, first + 1]);

		    mesh.lines.push([first+1,axis]);
		    mesh.lines.push([first,first+1]);
		  }      	      	
      }		      
	    	  
	  // compute normals
	  if((mMinAngleU == 0) && (mMaxAngleU == 360)) 
	  	mesh.normals = mesh.vertices;
	  else
	    mesh.computeNormals();	  
  	 
	  // compute wire
	  // mesh.computeWireframe();
	  
	  mesh.compile();
	  
	  mGL.addElement(mElement.getName() + ".mesh", mesh);
   
      return mesh;
}

// Represents a 4x4 matrix stored in row-major order that uses Float32Arrays
// when available. Matrix operations can either be done using convenient
// methods that return a new matrix for the result or optimized methods
// that store the result in an existing matrix to avoid generating garbage.

var hasFloat32Array = (typeof Float32Array != 'undefined');

// ### new GL.Matrix([elements])
// 
// This constructor takes 9 or 16 arguments in row-major order, which can be passed
// individually, as a list, or even as four lists, one for each row. If the
// arguments are omitted then the identity matrix is constructed instead.
function Matrix() {
  var m = Array.prototype.concat.apply([], arguments);
  if (!m.length) {
    m = [
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1
    ];
  } else if (m.length == 9) {
    m = [
      m[0], m[3], m[6], 0,
      m[1], m[4], m[7], 0,
      m[2], m[5], m[8], 0,
         0,    0,    0, 1
    ];  	
  }
  this.m = hasFloat32Array ? new Float32Array(m) : m;
}

Matrix.prototype = {
  // ### .inverse()
  // 
  // Returns the matrix that when multiplied with this matrix results in the
  // identity matrix.
  inverse: function() {
    return Matrix.inverse(this, new Matrix());
  },

  // ### .transpose()
  // 
  // Returns this matrix, exchanging columns for rows.
  transpose: function() {
    return Matrix.transpose(this, new Matrix());
  },

  // ### .multiply(matrix)
  // 
  // Returns the concatenation of the transforms for this matrix and `matrix`.
  // This emulates the OpenGL function `glMultMatrix()`.
  multiply: function(matrix) {
    return Matrix.multiply(this, matrix, new Matrix());
  },

  // ### .transformPoint(point)
  // 
  // Transforms the vector as a point with a w coordinate of 1. This
  // means translations will have an effect, for example.
  transformPoint: function(v) {
    var m = this.m;
    return new Vector(
      m[0] * v.x + m[1] * v.y + m[2] * v.z + m[3],
      m[4] * v.x + m[5] * v.y + m[6] * v.z + m[7],
      m[8] * v.x + m[9] * v.y + m[10] * v.z + m[11]
    ).divide(m[12] * v.x + m[13] * v.y + m[14] * v.z + m[15]);
  },

  // ### .transformPoint(vector)
  // 
  // Transforms the vector as a vector with a w coordinate of 0. This
  // means translations will have no effect, for example.
  transformVector: function(v) {
    var m = this.m;
    return new Vector(
      m[0] * v.x + m[1] * v.y + m[2] * v.z,
      m[4] * v.x + m[5] * v.y + m[6] * v.z,
      m[8] * v.x + m[9] * v.y + m[10] * v.z
    );
  }
};

// ### GL.Matrix.inverse(matrix[, result])
// 
// Returns the matrix that when multiplied with `matrix` results in the
// identity matrix. You can optionally pass an existing matrix in `result`
// to avoid allocating a new matrix. This implementation is from the Mesa
// OpenGL function `__gluInvertMatrixd()` found in `project.c`.
Matrix.inverse = function(matrix, result) {
  result = result || new Matrix();
  var m = matrix.m, r = result.m;

  r[0] = m[5]*m[10]*m[15] - m[5]*m[14]*m[11] - m[6]*m[9]*m[15] + m[6]*m[13]*m[11] + m[7]*m[9]*m[14] - m[7]*m[13]*m[10];
  r[1] = -m[1]*m[10]*m[15] + m[1]*m[14]*m[11] + m[2]*m[9]*m[15] - m[2]*m[13]*m[11] - m[3]*m[9]*m[14] + m[3]*m[13]*m[10];
  r[2] = m[1]*m[6]*m[15] - m[1]*m[14]*m[7] - m[2]*m[5]*m[15] + m[2]*m[13]*m[7] + m[3]*m[5]*m[14] - m[3]*m[13]*m[6];
  r[3] = -m[1]*m[6]*m[11] + m[1]*m[10]*m[7] + m[2]*m[5]*m[11] - m[2]*m[9]*m[7] - m[3]*m[5]*m[10] + m[3]*m[9]*m[6];

  r[4] = -m[4]*m[10]*m[15] + m[4]*m[14]*m[11] + m[6]*m[8]*m[15] - m[6]*m[12]*m[11] - m[7]*m[8]*m[14] + m[7]*m[12]*m[10];
  r[5] = m[0]*m[10]*m[15] - m[0]*m[14]*m[11] - m[2]*m[8]*m[15] + m[2]*m[12]*m[11] + m[3]*m[8]*m[14] - m[3]*m[12]*m[10];
  r[6] = -m[0]*m[6]*m[15] + m[0]*m[14]*m[7] + m[2]*m[4]*m[15] - m[2]*m[12]*m[7] - m[3]*m[4]*m[14] + m[3]*m[12]*m[6];
  r[7] = m[0]*m[6]*m[11] - m[0]*m[10]*m[7] - m[2]*m[4]*m[11] + m[2]*m[8]*m[7] + m[3]*m[4]*m[10] - m[3]*m[8]*m[6];

  r[8] = m[4]*m[9]*m[15] - m[4]*m[13]*m[11] - m[5]*m[8]*m[15] + m[5]*m[12]*m[11] + m[7]*m[8]*m[13] - m[7]*m[12]*m[9];
  r[9] = -m[0]*m[9]*m[15] + m[0]*m[13]*m[11] + m[1]*m[8]*m[15] - m[1]*m[12]*m[11] - m[3]*m[8]*m[13] + m[3]*m[12]*m[9];
  r[10] = m[0]*m[5]*m[15] - m[0]*m[13]*m[7] - m[1]*m[4]*m[15] + m[1]*m[12]*m[7] + m[3]*m[4]*m[13] - m[3]*m[12]*m[5];
  r[11] = -m[0]*m[5]*m[11] + m[0]*m[9]*m[7] + m[1]*m[4]*m[11] - m[1]*m[8]*m[7] - m[3]*m[4]*m[9] + m[3]*m[8]*m[5];

  r[12] = -m[4]*m[9]*m[14] + m[4]*m[13]*m[10] + m[5]*m[8]*m[14] - m[5]*m[12]*m[10] - m[6]*m[8]*m[13] + m[6]*m[12]*m[9];
  r[13] = m[0]*m[9]*m[14] - m[0]*m[13]*m[10] - m[1]*m[8]*m[14] + m[1]*m[12]*m[10] + m[2]*m[8]*m[13] - m[2]*m[12]*m[9];
  r[14] = -m[0]*m[5]*m[14] + m[0]*m[13]*m[6] + m[1]*m[4]*m[14] - m[1]*m[12]*m[6] - m[2]*m[4]*m[13] + m[2]*m[12]*m[5];
  r[15] = m[0]*m[5]*m[10] - m[0]*m[9]*m[6] - m[1]*m[4]*m[10] + m[1]*m[8]*m[6] + m[2]*m[4]*m[9] - m[2]*m[8]*m[5];

  var det = m[0]*r[0] + m[1]*r[4] + m[2]*r[8] + m[3]*r[12];
  for (var i = 0; i < 16; i++) r[i] /= det;
  return result;
};

// ### GL.Matrix.transpose(matrix[, result])
// 
// Returns `matrix`, exchanging columns for rows. You can optionally pass an
// existing matrix in `result` to avoid allocating a new matrix.
Matrix.transpose = function(matrix, result) {
  result = result || new Matrix();
  var m = matrix.m, r = result.m;
  r[0] = m[0]; r[1] = m[4]; r[2] = m[8]; r[3] = m[12];
  r[4] = m[1]; r[5] = m[5]; r[6] = m[9]; r[7] = m[13];
  r[8] = m[2]; r[9] = m[6]; r[10] = m[10]; r[11] = m[14];
  r[12] = m[3]; r[13] = m[7]; r[14] = m[11]; r[15] = m[15];
  return result;
};

// ### GL.Matrix.multiply(left, right[, result])
// 
// Returns the concatenation of the transforms for `left` and `right`. You can
// optionally pass an existing matrix in `result` to avoid allocating a new
// matrix. This emulates the OpenGL function `glMultMatrix()`.
Matrix.multiply = function(left, right, result) {
  result = result || new Matrix();
  var a = left.m, b = right.m, r = result.m;

  r[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12];
  r[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13];
  r[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14];
  r[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15];

  r[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12];
  r[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13];
  r[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14];
  r[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15];

  r[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12];
  r[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13];
  r[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14];
  r[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15];

  r[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12];
  r[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13];
  r[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14];
  r[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15];

  return result;
};

// ### GL.Matrix.identity([result])
// 
// Returns an identity matrix. You can optionally pass an existing matrix in
// `result` to avoid allocating a new matrix. This emulates the OpenGL function
// `glLoadIdentity()`.
Matrix.identity = function(result) {
  result = result || new Matrix();
  var m = result.m;
  m[0] = m[5] = m[10] = m[15] = 1;
  m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[11] = m[12] = m[13] = m[14] = 0;
  return result;
};

// ### GL.Matrix.perspective(fov, aspect, near, far[, result])
// 
// Returns a perspective transform matrix, which makes far away objects appear
// smaller than nearby objects. The `aspect` argument should be the width
// divided by the height of your viewport and `fov` is the top-to-bottom angle
// of the field of view in degrees. You can optionally pass an existing matrix
// in `result` to avoid allocating a new matrix. This emulates the OpenGL
// function `gluPerspective()`.
Matrix.perspective = function(fov, aspect, near, far, result) {
  var y = Math.tan(fov * Math.PI / 360) * near;
  var x = y * aspect;
  return Matrix.frustum(-x, x, -y, y, near, far, result);
};

// ### GL.Matrix.frustum(left, right, bottom, top, near, far[, result])
// 
// Sets up a viewing frustum, which is shaped like a truncated pyramid with the
// camera where the point of the pyramid would be. You can optionally pass an
// existing matrix in `result` to avoid allocating a new matrix. This emulates
// the OpenGL function `glFrustum()`.
Matrix.frustum = function(l, r, b, t, n, f, result) {
  result = result || new Matrix();
  var m = result.m;

  m[0] = 2 * n / (r - l);
  m[1] = 0;
  m[2] = (r + l) / (r - l);
  m[3] = 0;

  m[4] = 0;
  m[5] = 2 * n / (t - b);
  m[6] = (t + b) / (t - b);
  m[7] = 0;

  m[8] = 0;
  m[9] = 0;
  m[10] = -(f + n) / (f - n);
  m[11] = -2 * f * n / (f - n);

  m[12] = 0;
  m[13] = 0;
  m[14] = -1;
  m[15] = 0;

  return result;
};

// ### GL.Matrix.ortho(left, right, bottom, top, near, far[, result])
// 
// Returns an orthographic projection, in which objects are the same size no
// matter how far away or nearby they are. You can optionally pass an existing
// matrix in `result` to avoid allocating a new matrix. This emulates the OpenGL
// function `glOrtho()`.
Matrix.ortho = function(l, r, b, t, n, f, result) {
  result = result || new Matrix();
  var m = result.m;

  m[0] = 2 / (r - l);
  m[1] = 0;
  m[2] = 0;
  m[3] = -(r + l) / (r - l);

  m[4] = 0;
  m[5] = 2 / (t - b);
  m[6] = 0;
  m[7] = -(t + b) / (t - b);

  m[8] = 0;
  m[9] = 0;
  m[10] = -2 / (f - n);
  m[11] = -(f + n) / (f - n);

  m[12] = 0;
  m[13] = 0;
  m[14] = 0;
  m[15] = 1;

  return result;
};

// ### GL.Matrix.scale(x, y, z[, result])
// 
// This emulates the OpenGL function `glScale()`. You can optionally pass an
// existing matrix in `result` to avoid allocating a new matrix. 
Matrix.scale = function(x, y, z, result) {
  result = result || new Matrix();
  var m = result.m;

  m[0] = x;
  m[1] = 0;
  m[2] = 0;
  m[3] = 0;

  m[4] = 0;
  m[5] = y;
  m[6] = 0;
  m[7] = 0;

  m[8] = 0;
  m[9] = 0;
  m[10] = z;
  m[11] = 0;

  m[12] = 0;
  m[13] = 0;
  m[14] = 0;
  m[15] = 1;

  return result;
};

// ### GL.Matrix.translate(x, y, z[, result])
// 
// This emulates the OpenGL function `glTranslate()`. You can optionally pass
// an existing matrix in `result` to avoid allocating a new matrix. 
Matrix.translate = function(x, y, z, result) {
  result = result || new Matrix();
  var m = result.m;

  m[0] = 1;
  m[1] = 0;
  m[2] = 0;
  m[3] = x;

  m[4] = 0;
  m[5] = 1;
  m[6] = 0;
  m[7] = y;

  m[8] = 0;
  m[9] = 0;
  m[10] = 1;
  m[11] = z;

  m[12] = 0;
  m[13] = 0;
  m[14] = 0;
  m[15] = 1;

  return result;
};

// ### GL.Matrix.rotate(a, x, y, z, cx, cy, cz[, result])
// 
// Returns a matrix that rotates by `a` radians around the vector `x, y, z` and center 'cx, cy, cz'.
// You can optionally pass an existing matrix in `result` to avoid allocating
// a new matrix. This emulates the OpenGL function `glRotate()`. 
Matrix.rotate = function(a, x, y, z, cx, cy, cz, result) {
  if (!a || (!x && !y && !z)) {
    return Matrix.identity(result);
  }

  result = result || new Matrix();
  var m = result.m;

  var d = Math.sqrt(x*x + y*y + z*z);
  // a *= Math.PI / 180; 
  x /= d; y /= d; z /= d;
  var c = Math.cos(a), s = Math.sin(a), t = 1 - c;

  m[0] = x * x * t + c;
  m[1] = x * y * t - z * s;
  m[2] = x * z * t + y * s;
  m[3] = cx - m[0]*cx - m[1]*cy - m[2]*cz;

  m[4] = y * x * t + z * s;
  m[5] = y * y * t + c;
  m[6] = y * z * t - x * s;
  m[7] = cy - m[4]*cx - m[5]*cy - m[6]*cz;

  m[8] = z * x * t - y * s;
  m[9] = z * y * t + x * s;
  m[10] = z * z * t + c;
  m[11] = cz - m[8]*cx - m[9]*cy - m[10]*cz;

  m[12] = 0;
  m[13] = 0;
  m[14] = 0;
  m[15] = 1;

  return result;
};

// ### GL.Matrix.lookAt(ex, ey, ez, cx, cy, cz, ux, uy, uz[, result])
// 
// Returns a matrix that puts the camera at the eye point `ex, ey, ez` looking
// toward the center point `cx, cy, cz` with an up direction of `ux, uy, uz`.
// You can optionally pass an existing matrix in `result` to avoid allocating
// a new matrix. This emulates the OpenGL function `gluLookAt()`.
Matrix.lookAt = function(ex, ey, ez, cx, cy, cz, ux, uy, uz, result) {
  result = result || new Matrix();
  var m = result.m;

  var e = new Vector(ex, ey, ez);
  var c = new Vector(cx, cy, cz);
  var u = new Vector(ux, uy, uz);
  var f = e.subtract(c).unit();
  var s = u.cross(f).unit();
  var t = f.cross(s).unit();

  m[0] = s.x;
  m[1] = s.y;
  m[2] = s.z;
  m[3] = -s.dot(e);

  m[4] = t.x;
  m[5] = t.y;
  m[6] = t.z;
  m[7] = -t.dot(e);

  m[8] = f.x;
  m[9] = f.y;
  m[10] = f.z;
  m[11] = -f.dot(e);

  m[12] = 0;
  m[13] = 0;
  m[14] = 0;
  m[15] = 1;

  return result;
};
/**
 * Deployment for 3D SVG drawing.
 * @module SVGGraphics 
 */
	
// Represents indexed triangle geometry with arbitrary additional attributes.
// You need a shader to draw a mesh; meshes can't draw themselves.
// 
// A mesh is a collection of `Buffer` objects which are either vertex buffers
// (holding per-vertex attributes) or index buffers (holding the order in which
// vertices are rendered). By default, a mesh has a position vertex buffer called
// `vertices` and a triangle index buffer called `triangles`. New buffers can be
// added using `addVertexBuffer()` and `addIndexBuffer()`. Two strings are
// required when adding a new vertex buffer, the name of the data array on the
// mesh instance and the name of the GLSL attribute in the vertex shader.
// 
// Example usage:
// 
//     var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL, { coords: true, lines: true });
// 
//     // Default attribute "vertices", available as "gl_Vertex" in
//     // the vertex shader
//     mesh.setVertices([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]]);
// 
//     // Optional attribute "coords" enabled in constructor,
//     // available as "gl_TexCoord" in the vertex shader
//     mesh.setCoords([[0, 0], [1, 0], [0, 1], [1, 1]]);
// 
//     // Custom attribute "weights", available as "weight" in the
//     // vertex shader
//     mesh.addVertexBuffer('weights', 'weight');
//     mesh.setBuffer('weights',[1, 0, 0, 1]);
// 
//     // Default index buffer "triangles"
//     mesh.setTriangles([[0, 1, 2], [2, 1, 3]]);
// 
//     // Optional index buffer "lines" enabled in constructor
//     mesh.setLines([[0, 1], [0, 2], [1, 3], [2, 3]]);
// 
//     // Upload provided data to GPU memory
//     mesh.compile();

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

// Represents a collection of vertex buffers and index buffers. Each vertex
// buffer maps to one attribute in GLSL and has a corresponding property set
// on the Mesh instance. There is one vertex buffer by default: `vertices`,
// which maps to `gl_Vertex`. The `coords`, `normals`, and `colors` vertex
// buffers map to `gl_TexCoord`, `gl_Normal`, and `gl_Color` respectively,
// and can be enabled by setting the corresponding options to true. There are
// two index buffers, `triangles` and `lines`, which are used for rendering
// `mGL.TRIANGLES` and `mGL.LINES`, respectively. Only `triangles` is enabled by
// default, although `computeWireframe()` will add a normal buffer if it wasn't
// initially enabled.

/**
 * @param mGL Element where draw
 * @returns A WebGL mesh
 */
EJSS_WEBGLGRAPHICS.mesh = function(mGL, id, options) {
  var self = {};
  options = options || {coords:true, normals:true, triangles:true, lines:true, colors:true};
  id = id || "mesh";  
  var vertexBuffers = {};
  var indexBuffers = {};
  
  self.getId = function() {
  	return id;
  }
  
  self.getVertexBuffers = function() {
  	return vertexBuffers;
  }

  self.getIndexBuffers = function() {
  	return indexBuffers;
  }
  
  self.setVertices = function(b) {
  	self.vertices = b;
  }

  self.getVertices = function() {
  	return self.vertices;
  }

  self.setCoords = function(b) {
  	self.coords = b;
  }

  self.getCoords = function() {
  	return self.coords;
  }

  self.setNormals = function(b) {
  	self.normals = b;
  }

  self.getNormals = function() {
  	return self.normals;
  }

  self.setColors = function(b) {
  	self.colors = b;
  }

  self.getColors = function() {
  	return self.colors;
  }

  self.setTriangles = function(b) {
  	self.triangles = b;
  }

  self.getTriangles = function() {
  	return self.triangles;
  }

  self.setLines = function(b) {
  	self.lines = b;
  }

  self.getLines = function() {
  	return self.lines;
  }
  
  self.setBuffer = function(name, b) {
  	self[name] = b;
  }
  
  // Add a new vertex buffer with a list as a property called `name` on this object
  // and map it to the attribute called `attribute` in all shaders that draw this mesh.
  self.addVertexBuffer = function(name, attribute) {
    var buffer = vertexBuffers[attribute] = EJSS_WEBGLGRAPHICS.buffer(mGL, mGL.ARRAY_BUFFER, Float32Array);
    buffer.setName(name);
    self[name] = [];
  }

  // Add a new index buffer with a list as a property called `name` on this object.
  self.addIndexBuffer = function(name) {
    var buffer = indexBuffers[name] = EJSS_WEBGLGRAPHICS.buffer(mGL, mGL.ELEMENT_ARRAY_BUFFER, Uint16Array);
    self[name] = [];
  }

  // Upload all attached buffers to the GPU in preparation for rendering. This
  // doesn't need to be called every frame, only needs to be done when the data
  // changes.
  self.compile = function() {
    for (var attribute in vertexBuffers) {
      var buffer = vertexBuffers[attribute];
      buffer.setData(self[buffer.getName()]);
      buffer.compile();
    }

    for (var name in indexBuffers) {
      var buffer = indexBuffers[name];
      buffer.setData(self[name]);
      buffer.compile();
    }
  }

  // Transform all vertices by `matrix` and all normals by the inverse transpose
  // of `matrix`.
  self.transform = function(matrix) {
    self.vertices = self.vertices.map(function(v) {
      return matrix.transformPoint(Vector.fromArray(v)).toArray();
    });
    if (self.normals) {
      var invTrans = matrix.inverse().transpose();
      self.normals = self.normals.map(function(n) {
        return invTrans.transformVector(Vector.fromArray(n)).unit().toArray();
      });
    }
    self.compile();
    return self;
  }

  // Computes a new normal for each vertex from the average normal of the
  // neighboring triangles. This means adjacent triangles must share vertices
  // for the resulting normals to be smooth.
  self.computeNormals = function() {
    if (!self.normals) self.addVertexBuffer('normals', 'gl_Normal');
    for (var i = 0; i < self.vertices.length; i++) {
      self.normals[i] = new Vector();
    }
    for (var i = 0; i < self.triangles.length; i++) {
      var t = self.triangles[i];
      var a = Vector.fromArray(self.vertices[t[0]]);
      var b = Vector.fromArray(self.vertices[t[1]]);
      var c = Vector.fromArray(self.vertices[t[2]]);
      var normal = b.subtract(a).cross(c.subtract(a)).unit();
      self.normals[t[0]] = self.normals[t[0]].add(normal);
      self.normals[t[1]] = self.normals[t[1]].add(normal);
      self.normals[t[2]] = self.normals[t[2]].add(normal);
    }
    for (var i = 0; i < self.vertices.length; i++) {
      self.normals[i] = self.normals[i].unit().toArray();
    }
    self.compile();
    return self;
  }

  // Populate the `lines` index buffer from the `triangles` index buffer.
  self.computeWireframe = function() {
    var indexer = EJSS_WEBGLGRAPHICS.indexer();
    for (var i = 0; i < self.triangles.length; i++) {
      var t = self.triangles[i];
      for (var j = 0; j < t.length; j++) {
        var a = t[j], b = t[(j + 1) % t.length];
        indexer.add([Math.min(a, b), Math.max(a, b)]);
      }
    }
    if (!self.lines) self.addIndexBuffer('lines');
    self.lines = indexer.getUnique();
    self.compile();
    return self;
  }

  // Computes the axis-aligned bounding box, which is an object whose `min` and
  // `max` properties contain the minimum and maximum coordinates of all vertices.
  self.getAABB = function() {
    var aabb = { min: new Vector(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE) };
    aabb.max = aabb.min.negative();
    for (var i = 0; i < self.vertices.length; i++) {
      var v = Vector.fromArray(self.vertices[i]);
      aabb.min = Vector.min(aabb.min, v);
      aabb.max = Vector.max(aabb.max, v);
    }
    return aabb;
  }

  // Computes a sphere that contains all vertices (not necessarily the smallest
  // sphere). The returned object has two properties, `center` and `radius`.
  self.getBoundingSphere = function() {
    var aabb = self.getAABB();
    var sphere = { center: aabb.min.add(aabb.max).divide(2), radius: 0 };
    for (var i = 0; i < self.vertices.length; i++) {
      sphere.radius = Math.max(sphere.radius,
        Vector.fromArray(self.vertices[i]).subtract(sphere.center).length());
    }
    return sphere;
  }

  self.addVertexBuffer('vertices', 'gl_Vertex');
  if (options.coords) self.addVertexBuffer('coords', 'gl_TexCoord');
  if (options.normals) self.addVertexBuffer('normals', 'gl_Normal');
  if (options.colors) self.addVertexBuffer('colors', 'gl_Color');
  if (!('triangles' in options) || options.triangles) self.addIndexBuffer('triangles');
  if (options.lines) {
  		self.addIndexBuffer('lines');
  }

  return self;
}	

// 
// Generates indices into a list of unique objects from a stream of objects
// that may contain duplicates. This is useful for generating compact indexed
// meshes from unindexed data.
EJSS_WEBGLGRAPHICS.indexer = function() {
  var self = {};
  var unique = [];
  // var indices = [];
  var map = {};

  self.getUnique = function() {
  	return unique;
  }

  // 
  // Adds the object `obj` to `unique` if it hasn't already been added. Returns
  // the index of `obj` in `unique`.
  self.add = function(obj) {
    var key = JSON.stringify(obj);
    if (!(key in map)) {
      map[key] = unique.length;
      unique.push(obj);
    }
    return map[key];
  }
  
  return self;
};

// 
// Provides a simple method of uploading data to a GPU buffer. Example usage:
// 
//     var vertices = EJSS_WEBGLGRAPHICS.buffer(mGL, mGL.ARRAY_BUFFER, Float32Array);
//     var indices = EJSS_WEBGLGRAPHICS.buffer(mGL, mGL.ELEMENT_ARRAY_BUFFER, Uint16Array);
//     vertices.setData([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]]);
//     indices.setData([[0, 1, 2], [2, 1, 3]]);
//     vertices.compile();
//     indices.compile();
// 
EJSS_WEBGLGRAPHICS.buffer = function(mGL, target, type) {
  var self = {};
  var buffer = null;
  var target = target;
  var type = type;
  var mName = "";
  var mData = [];

  self.setData = function(b) {
  	mData = b;
  }

  self.setName = function(name) {
  	mName = name;
  }

  self.getBuffer = function() {
  	return buffer;
  }

  self.getName = function() {
  	return mName;
  }

  // 
  // Upload the contents of `data` to the GPU in preparation for rendering. The
  // data must be a list of lists where each inner list has the same length. For
  // example, each element of data for vertex normals would be a list of length three.
  // This will remember the data length and element length for later use by shaders.
  // The type can be either `mGL.STATIC_DRAW` or `mGL.DYNAMIC_DRAW`, and defaults to
  // `mGL.STATIC_DRAW`.
  // 
  // This could have used `[].concat.apply([], this.data)` to flatten
  // the array but Google Chrome has a maximum number of arguments so the
  // concatenations are chunked to avoid that limit.
  self.compile = function(tp) {
    var data = [];
    for (var i = 0, chunk = 10000; i < mData.length; i += chunk) {
      data = Array.prototype.concat.apply(data, mData.slice(i, i + chunk));
    }
    var spacing = mData.length ? data.length / mData.length : 0;
    if (spacing != Math.round(spacing)) throw 'buffer elements not of consistent size, average size is ' + spacing;
    buffer = buffer || mGL.createBuffer();
    buffer.length = data.length;
    buffer.spacing = spacing;
    mGL.bindBuffer(target, buffer);
    mGL.bufferData(target, new type(data), tp || mGL.STATIC_DRAW);
  }
  
  return self;
}
/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL plane
 */
EJSS_WEBGLGRAPHICS.plane = function(mGL, mElement) {
	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL, mElement.getName());
	  		 
	  var dirA = mElement.getDirectionA();
	  var dirB = mElement.getDirectionB();
	  var sizeA = mElement.getSizeA();
	  var sizeB = mElement.getSizeB();
	  var vecZ = Vector.cross(new Vector(dirA), new Vector(dirB), new Vector()); 
	  var dirZ = vecZ.unit().toArray();
	  
	  var trans = new Matrix( [
	  		dirA[0],dirB[0],dirZ[0],0,
	  		dirA[1],dirB[1],dirZ[1],0,
	  		dirA[2],dirB[2],dirZ[2],0,
	  		0,0,0,1,
	  		]);
	  
	  var resolution = mElement.getResolutionU();     // tesselation level 	
	  for (var y = 0; y <= resolution; y++) {
	    var t = (y / resolution) * sizeB;
	    for (var x = 0; x <= resolution; x++) {
	      var s = (x / resolution) * sizeA;	      
	      var vec = new Vector([2 * s - 1, 2 * t - 1, 0]);
	      var res = trans.transformVector(vec);	      
	      mesh.vertices.push(res.toArray());
	      mesh.coords.push([s, t]);
	      mesh.normals.push([0, 0, 1]);	      
	      if (x < resolution && y < resolution) {
	        var i = x + y * (resolution + 1);
	        mesh.triangles.push([i + resolution + 1, i + 1, i]);
	        mesh.triangles.push([i + resolution + 2, i + 1, i + resolution + 1]);
	        
	        mesh.lines.push([i + resolution + 1, i]);
	        mesh.lines.push([i + 1, i]);
	        mesh.lines.push([i + resolution + 2, i + 1]);
	        mesh.lines.push([i + resolution + 2, i + resolution + 1]);
	      }
	    }
	  }
	  
	  mesh.compile();
	
	  mGL.addElement(mElement.getName() + ".mesh", mesh);
        
      return mesh;
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL segment
 */
EJSS_WEBGLGRAPHICS.segment = function(mGL, mElement) {
  	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName(), 
	  	{coords:false, normals:true, triangles:true, lines:false, colors:false});

	  var resolutionU = mElement.getResolutionU();
	  var radius = mElement.getLineWidth() / 2;
      
	  // generate vertex for body
	  var vertices3D = [];	  
	  for (var x = 0; x < resolutionU; x++ ) {
		var u = x / (resolutionU - 1);
		var xpos = radius * Math.sin( u * Math.PI * 2 );
		var ypos = radius * Math.cos( u * Math.PI * 2 );

		vertices3D.push(new Vector([xpos, ypos, 0])); // top vertex
		vertices3D.push(new Vector([xpos, ypos, 0])); // bottom vertex
	  }
	  
	  // rotate and translate top and bottom
  	  var endpoint = new Vector(mElement.getSize());
	  var cross = endpoint.cross(new Vector(0,0,1));
	  if(cross.length()==0) cross.y = 1; // if endoint in axis Z, get axis Y to rotate 
	  // console.log("cross:" + cross.x + " " + cross.y + " " + cross.z + " ");	  
	  var angle = endpoint.angle(new Vector(0,0,1));
	  // console.log("angle:" + angle);
	  var result = new Matrix();
	  Matrix.rotate(-angle,cross.x,cross.y,cross.z,0,0,0,result);
	  for(var i=0; i<vertices3D.length; i++) {
	  	var vector = result.transformPoint(vertices3D[i]);
	  	if (i % 2 == 0) {
	  		// add enpoint to translate	  	
	  		mesh.vertices.push(vector.add(endpoint).toArray());	  		
	  	} else {
	  		mesh.vertices.push(vector.toArray());
	  	}
	  }
	  
	  // create surface triangles
	  for (var x = 0; x < resolutionU - 1; x++ ) {
	    var first = 2*x;		    
	    var fourth = 2*x + 2;
	    var second = 2*x + 1;
	    var third = 2*x + 3;
			
        mesh.triangles.push([third, second, first]);      
        mesh.triangles.push([fourth, third, first]);
	  }		
		
	  // top cap
	  mesh.vertices.push(endpoint.toArray());		
      for (var i = 0; i < resolutionU - 1; i++) {
      	var first = mesh.vertices.length - 1;
      	var second = 2*i+2;
      	var third = 2*i;
      	mesh.triangles.push([first, second, third]);
      }        

	  // bottom cap
 	  mesh.vertices.push([0, 0, 0]);
      for (var i = 0; i < resolutionU - 1; i++) {
      	var first = 2*i+1;
      	var second = 2*i+3;
      	var third = mesh.vertices.length - 1;
       	mesh.triangles.push([first, second, third]);
      }        
	  	        
	  mesh.computeNormals();	
	  
	  mesh.compile();	
	
	  mGL.addElement(mElement.getName() + ".mesh", mesh);
        
     return mesh;

/* Previously it was possible to manage lineWidth and then the segment was easy ...
 	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName(), 
	  	{coords:false, normals:false, triangles:false, lines:true, colors:false});
	  
      mesh.vertices.push([0,0,0]);
      mesh.vertices.push([2,2,2]);

	  mesh.lines.push([0,1]);
	  
	  mesh.compile();
           
      return mesh;
*/      
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

EJSS_WEBGLGRAPHICS.Shader = {
	//
	// Basic shaders (no lights), using for lines
	// (https://mattdesl.svbtle.com/drawing-lines-is-hard)
	// (https://jsfiddle.net/vy0we5wb/4)
	BasicVS: 
     [
     'void main() {',
     '  mat4 m = gl_ProjectionMatrix * gl_ViewMatrix * gl_ModelMatrix;',
     '  gl_Position = m * vec4(gl_Vertex.xyz, 1.0);',
     '}'
     ].join( '\r\n' ),
          
    BasicFS: 
     [
     'uniform vec3 color;',
     'uniform float opacity;',
     'void main() {',
     '    gl_FragColor = vec4(color, opacity);',
     '}'          
     ].join( '\r\n' ),
     
    //
    // Shaders using color array (only basic element)
    //
	ColorVS:
     [
     'varying vec4 color;',
     'void main() {',
     ' color = gl_Color;',
     ' gl_Position = gl_ProjectionMatrix * gl_ViewMatrix * gl_ModelMatrix * vec4(gl_Vertex.xyz, 1.0);',
     '}'
     ].join( '\r\n' ),
     
    ColorFS: 
     [
     'varying vec4 color;',
     'void main() {',
     ' gl_FragColor = color;',
     '}'    
     ].join( '\r\n' ),
	
	//
	// Shaders with light 
	// see: http://multivis.net/lecture/phong.html
	//
	LightVS:
     [
     'uniform float ka;',
     'uniform float kd;',
     'uniform float ks;',
     'uniform float shininessVal;',
     'uniform vec3 ambientColor;',
     'uniform vec3 diffuseColor;',
     'uniform vec3 specularColor;',
     'uniform vec3 lights[10];',
     'uniform int numlight;',
     'varying vec3 tmpcolor;',
     'void main() {',
     '  vec4 vmPos = gl_ViewMatrix * gl_ModelMatrix * vec4(gl_Vertex.xyz, 1.0);',
     '  vec3 normal = normalize(gl_NormalMatrix * gl_Normal);',
     '  tmpcolor = vec3(0);',
     '	for(int i = 0; i < 10; i++) { ',
     '		if(i == numlight) break;',
     '		vec3 light = normalize(lights[i] - vmPos.xyz);',
     '      float lambertian = abs(dot(normal, light));',
     '      float specular = 0.0;',
     '      if(lambertian > 0.0) {',
     '        vec3 reflect = reflect(-light, normal);',
     '        vec3 vector = normalize(-vmPos.xyz);',
     '        float specAngle = max(dot(reflect, vector), 0.0);',
     '        specular = pow(specAngle, shininessVal);',
     '      }',
     '      tmpcolor += vec3(ka * ambientColor +',
     '        kd * lambertian * diffuseColor +',
     '        ks * specular * specularColor);',
     '	} ',
     '  gl_Position = gl_ProjectionMatrix * vmPos;',
     '}'
     ].join( '\r\n' ),
     
    LightFS: 
     [
     'uniform float opacity;',
     'varying vec3 tmpcolor;',
     'void main() {',
   	 '  gl_FragColor = vec4(tmpcolor, opacity);',
     '}'        
     ].join( '\r\n' ),
     
    // Shaders with light using color array (only basic element)
	LightAndColorVS:
     [
     'uniform float ka;',
     'uniform float kd;',
     'uniform float ks;',
     'uniform float shininessVal;',
     'uniform vec3 ambientColor;',
     'uniform vec3 specularColor;',
     'uniform vec3 lights[10];',
     'uniform int numlight;',
     'varying vec3 tmpcolor;',
     'void main() {',
     '  vec4 vmPos = gl_ViewMatrix * gl_ModelMatrix * vec4(gl_Vertex.xyz, 1.0);',
     '  vec3 normal = normalize(gl_NormalMatrix * gl_Normal);',
     '  tmpcolor = vec3(0);',
     '	for(int i = 0; i < 10; i++) { ',
     '		if(i == numlight) break;',
     '		vec3 light = normalize(lights[i] - vmPos.xyz);',
     '      float lambertian = abs(dot(normal, light));',
     '      float specular = 0.0;',
     '      if(lambertian > 0.0) {',
     '        vec3 reflect = reflect(-light, normal);',
     '        vec3 vector = normalize(-vmPos.xyz);',
     '        float specAngle = max(dot(reflect, vector), 0.0);',
     '        specular = pow(specAngle, shininessVal);',
     '      }',
     '      tmpcolor += vec3(ka * ambientColor +',
     '        kd * lambertian * vec3(gl_Color) +',
     '        ks * specular * specularColor);',
     '	} ',
     '  gl_Position = gl_ProjectionMatrix * vmPos;',
     '}'
     ].join( '\r\n' ),
     
    LightAndColorFS: 
     [
     'uniform float opacity;',
     'varying vec3 tmpcolor;',
     'void main() {',
   	 '  gl_FragColor = vec4(tmpcolor, opacity);',
     '}'     
     ].join( '\r\n' ),

	//
	// Shaders for color scale
	//  Work in process!
	//    
	PaletteColorVS:
     [	
     'uniform vec3 floor;',
     'uniform vec3 ceil;',
     'uniform vec3 floorColor;',
     'uniform vec3 ceilColor;',
     'varying vec3 tmpcolor;',
     'void main() {',
     '  vec4 vmPos = gl_ViewMatrix * gl_ModelMatrix * vec4(gl_Vertex.xyz, 1.0);',
     '  vec3 normal = normalize(gl_NormalMatrix * gl_Normal);',
     '  vec3 diff = ceil - floor;',
     '  vec3 diffFloor = vmPos.xyz - floor;',
     '  vec3 diffCeil = vmPos.xyz - ceil;',
     '  float value1 = dot(diff, diffFloor);',
     '  if(value1 < 0.0) {',
     '    tmpcolor = floorColor;',
     '  } else {',
     '    float value2 = dot(diff, diffCeil);',
     '    if(value2 > 0.0) {',
     '      tmpcolor = ceilColor;',
     '    } else {',
     '      float value = length(diffFloor) / (length(diffFloor) + length(diffCeil));',
     '      tmpcolor = floorColor + value * (ceilColor - floorColor);',
     '    }',
     '  }',
     '  gl_Position = gl_ProjectionMatrix * vmPos;',
     '}'
     ].join( '\r\n' ),
     
    PaletteColorFS: 
     [
     'uniform float opacity;',
     'varying vec3 tmpcolor;',
     'void main() {',
   	 '  gl_FragColor = vec4(tmpcolor, opacity);',
     '}'      
     ].join( '\r\n' ),
          
	//
	// Shaders for texture
	//
	BasicVSwithTex :
     [	
	 'varying vec2 coord;',
	 'void main() {',
	 '  coord = gl_TexCoord.xy;',
	 '  gl_Position = gl_ProjectionMatrix * gl_ViewMatrix * gl_ModelMatrix * vec4(gl_Vertex.xyz, 1.0);',
	 '}'
     ].join( '\r\n' ),

	BasicFSwithTex:
     [	 
	 'uniform sampler2D texture;',
	 'varying vec2 coord;',
	 'void main() {',
	 '  gl_FragColor = texture2D(texture, coord);',
	 '}'	        
     ].join( '\r\n' ),
}

// Provides a convenient wrapper for WebGL shaders. A few uniforms and attributes,
// prefixed with `gl_`, are automatically added to all shader sources to make
// simple shaders easier to write.
// 
// Example usage:
// 
//     var shader = EJSS_WEBGLGRAPHICS.shader(mGL, '\
//       void main() {\
//         gl_Position = gl_ProjectionMatrix * gl_ViewMatrix * gl_ModelMatrix * gl_Vertex;\
//       }\
//     ', '\
//       uniform vec4 color;\
//       void main() {\
//         gl_FragColor = color;\
//       }\
//     ');
// 
//     shader.uniforms({
//       color: [1, 0, 0, 1]
//     }).draw(mesh);


/**
 * Compiles a shader program using the provided vertex and fragment shaders.
 * @method shader
 * @param vertexSource
 * @param fragmentSource 
 */
EJSS_WEBGLGRAPHICS.shader = function(mGL, mName, vertexSource, fragmentSource) {  
  var self = {};
  
  // Headers are prepended to the sources to provide some automatic functionality.
  var header = [
    'uniform mat3 gl_NormalMatrix;',
    'uniform mat4 gl_ModelMatrix;',
    'uniform mat4 gl_ViewMatrix;',
    'uniform mat4 gl_ProjectionMatrix;',''
    ].join('\r\n'); 
  var vertexHeader = [
    // '#extension GL_OES_standard_derivatives : enable', '',
    header, 
    'attribute vec4 gl_Vertex;',
    'attribute vec4 gl_TexCoord;',
    'attribute vec3 gl_Normal;',
    'attribute vec4 gl_Color;',''
    ].join('\r\n');
    
  var fragmentHeader = [
    // '#extension GL_OES_standard_derivatives : enable', '', 
    'precision highp float;', 
    header
    ].join('\r\n');
  
  // Attributes	 
  var attributes = {};
  // Uniforms locations
  var uniformLocations = {};

  // Check for the use of built-in matrices that require expensive matrix
  // multiplications to compute, and record these in `usedMatrices`.  
  function getUsedMatrices(vertexSource, fragmentSource) {
    var source = vertexSource + fragmentSource;
    var usedMatrices = {};
	EJSS_WEBGLGRAPHICS.Utils.regexMap(/\b(gl_[^;]*)\b;/g, header, function(groups) {
	  var name = groups[1];
	  if (source.indexOf(name) != -1) {
	    var capitalLetters = name.replace(/[a-z_]/g, '');
	    usedMatrices[capitalLetters] = mName + name;
	  }
	});
	return usedMatrices;
  }

  // The `gl_` prefix must be substituted for something else to avoid compile
  // errors, since it's a reserved prefix. This prefixes all reserved names with
  // `_`. The header is inserted after any extensions, since those must come
  // first.
  function fix(header, source) {
    var replaced = {};
    var match = /^((\s*\/\/.*\n|\s*#extension.*\n)+)[^]*$/.exec(source);
    source = match ? match[1] + header + source.substr(match[1].length) : header + source;
    EJSS_WEBGLGRAPHICS.Utils.regexMap(/\bgl_\w+\b/g, header, function(result) {
      if (!(result in replaced)) {
        source = source.replace(new RegExp('\\b' + result + '\\b', 'g'), mName + result);
        replaced[result] = true;
      }
    });
    return source;
  }
  
  // Compile and link errors are thrown as strings.
  function compileSource(type, source) {
    var shader = mGL.createShader(type);
    mGL.shaderSource(shader, source);
    mGL.compileShader(shader);
    if (!mGL.getShaderParameter(shader, mGL.COMPILE_STATUS)) {
      throw 'compile error: ' + mGL.getShaderInfoLog(shader);
    }
    return shader;
  }
   
  // 
  // Set a uniform for each property of `uniforms`. The correct `mGL.uniform*()` method is
  // inferred from the value types and from the stored uniform sampler flags.
  self.uniforms = function(uniforms) {
    mGL.useProgram(program);

    for (var name in uniforms) {
      var location = uniformLocations[name] || mGL.getUniformLocation(program, name);
      if (!location) continue;
      uniformLocations[name] = location;
      var value = uniforms[name];
      if (value instanceof Vector) {
        value = [value.x, value.y, value.z];
      } else if (value instanceof Matrix) {
        value = value.m;
      }
      if (EJSS_WEBGLGRAPHICS.Utils.isArray(value)) {
      	if(EJSS_WEBGLGRAPHICS.Utils.isArray(value[0])) {
      		// array of vector 3D (ex. lights)
      		var newvalue = [];
      		for(var j=0; j<value.length; j++) { 
      			newvalue.push(value[j][0]); 
      			newvalue.push(value[j][1]); 
      			newvalue.push(value[j][2]); 
      		}
      		mGL.uniform3fv(location, new Float32Array(newvalue)); 
      	} else {
	        switch (value.length) {
	          case 1: mGL.uniform1fv(location, new Float32Array(value)); break;
	          case 2: mGL.uniform2fv(location, new Float32Array(value)); break;
	          case 3: mGL.uniform3fv(location, new Float32Array(value)); break;
	          case 4: mGL.uniform4fv(location, new Float32Array(value)); break;
	          // Matrices are automatically transposed, since WebGL uses column-major
	          // indices instead of row-major indices.
	          case 9: mGL.uniformMatrix3fv(location, false, new Float32Array([
	            value[0], value[3], value[6],
	            value[1], value[4], value[7],
	            value[2], value[5], value[8]
	          ])); break;
	          case 16: mGL.uniformMatrix4fv(location, false, new Float32Array([
	            value[0], value[4], value[8], value[12],
	            value[1], value[5], value[9], value[13],
	            value[2], value[6], value[10], value[14],
	            value[3], value[7], value[11], value[15]
	          ])); break;
	          default: throw 'don\'t know how to load uniform "' + name + '" of length ' + value.length;
			}
        }
      } else if (EJSS_WEBGLGRAPHICS.Utils.isNumber(value)) { 
        ((name.indexOf('texture') != -1 || name.indexOf('num') != -1) ? mGL.uniform1i : mGL.uniform1f).call(mGL, location, value);
      } else {
        throw 'attempted to set uniform "' + name + '" to invalid value ' + value;
      }
    }
  }

  // 
  // Sets all uniform matrix attributes, binds all relevant buffers, and draws the
  // mesh geometry as indexed triangles or indexed lines. Set `mode` to `mGL.LINES`
  // (and either add indices to `lines` or call `computeWireframe()`) to draw the
  // mesh in wireframe.
  self.draw = function(mesh, mode) {
    self.drawBuffers(mesh.getVertexBuffers(),
      mesh.getIndexBuffers()[mode == mGL.LINES ? 'lines' : 'triangles'],
      arguments.length < 2 ? mGL.TRIANGLES : mode);
  }

  // ### .drawBuffers(vertexBuffers, indexBuffer, mode)
  // 
  // Sets all uniform matrix attributes, binds all relevant buffers, and draws the
  // indexed mesh geometry. The `vertexBuffers` argument is a map from attribute
  // names to `Buffer` objects of type `mGL.ARRAY_BUFFER`, `indexBuffer` is a `Buffer`
  // object of type `mGL.ELEMENT_ARRAY_BUFFER`, and `mode` is a WebGL primitive mode
  // like `mGL.TRIANGLES` or `mGL.LINES`. This method automatically creates and caches
  // vertex attribute pointers for attributes as needed.
  self.drawBuffers = function(vertexBuffers, indexBuffer, mode) {
    // Only construct up the built-in matrices we need for this shader.
    var used = getUsedMatrices(vertexSource, fragmentSource);
    var MM = mGL.modelMatrix;
    var VM = mGL.viewMatrix;
    var PM = mGL.projectionMatrix;
    var matrices = {};
    if (used.MM) matrices[used.MM] = MM;
    if (used.VM) matrices[used.VM] = VM;
    if (used.PM) matrices[used.PM] = PM;
    if (used.NM) {
		var m = VM.multiply(MM).inverse().m; // MVMI
      	matrices[used.NM] = [m[0], m[4], m[8], m[1], m[5], m[9], m[2], m[6], m[10]];
    }
    self.uniforms(matrices);

    // Create and enable attribute pointers as necessary.
    var length = 0;
    for (var attribute in vertexBuffers) {
      var buffer = vertexBuffers[attribute];
      var location = attributes[attribute] ||
        mGL.getAttribLocation(program, attribute.replace(/^(gl_.*)$/, mName + '$1'));
      if (location == -1 || !buffer.getBuffer()) continue;
      attributes[attribute] = location;
      mGL.bindBuffer(mGL.ARRAY_BUFFER, buffer.getBuffer());
      mGL.enableVertexAttribArray(location);
      mGL.vertexAttribPointer(location, buffer.getBuffer().spacing, mGL.FLOAT, false, 0, 0);
      length = buffer.getBuffer().length / buffer.getBuffer().spacing;
    }

    // Disable unused attribute pointers.
    for (var attribute in attributes) {
      if (!(attribute in vertexBuffers)) {
        mGL.disableVertexAttribArray(attributes[attribute]);
      }
    }
    
    // Draw the geometry.
    if (length && (!indexBuffer || indexBuffer.getBuffer())) {
      if (indexBuffer) {
        mGL.bindBuffer(mGL.ELEMENT_ARRAY_BUFFER, indexBuffer.getBuffer());
        mGL.drawElements(mode, indexBuffer.getBuffer().length, mGL.UNSIGNED_SHORT, 0);
      } else {
        mGL.drawArrays(mode, 0, length);
      }
    }
  }
 
  // 
  // Initialization 
  var program = mGL.createProgram();
  mGL.attachShader(program, compileSource(mGL.VERTEX_SHADER, fix(vertexHeader, vertexSource)));
  mGL.attachShader(program, compileSource(mGL.FRAGMENT_SHADER, fix(fragmentHeader, fragmentSource)));
  mGL.linkProgram(program);
  if (!mGL.getProgramParameter(program, mGL.LINK_STATUS)) {
    throw 'link error: ' + mGL.getProgramInfoLog(program);
  }
 
  return self;  
}


/**
 * Deployment for 2D SVG drawing.
 * @module SVGGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL custom
 */

EJSS_WEBGLGRAPHICS.spring = function(mGL, mElement) {   
	function drawSpring(loops, pointsPerLoop, radius, solenoid, thinExtremes, origp, endp, moveTo, lineTo) {
		var segments = loops * pointsPerLoop;
		var delta = 2.0 * Math.PI / pointsPerLoop;
		if (radius < 0) delta *= -1;
		var pre = pointsPerLoop / 2;
		// normalize sizes        	
		var u1 = EJSS_TOOLS.Mathematics.normalTo([endp.x,endp.y,endp.z]);
		var u2 = EJSS_TOOLS.Mathematics.normalize(EJSS_TOOLS.Mathematics.crossProduct([endp.x,endp.y,endp.z], u1));
		
	    for (var i=0; i<=segments; i++) {
	      var k;
	      if (thinExtremes) {	// extremes
	        if (i < pre) k = 0;
	        else if (i < pointsPerLoop) k = i - pre;
	        else if (i > (segments - pre)) k = 0;
	        else if (i > (segments - pointsPerLoop)) k = segments - i - pre;
	        else k = pre;
	      }
	      else k = pre;
	      var angle = Math.PI/2 + i*delta;
	      var cos = Math.cos(angle), sin = Math.sin(angle);
	      var xx = origp.x + i*endp.x/segments + k*radius*(cos*u1[0] + sin*u2[0])/pre;
	      var yy = origp.y + i*endp.y/segments + k*radius*(cos*u1[1] + sin*u2[1])/pre;
	      var zz = origp.z + i*endp.z/segments + k*radius*(cos*u1[2] + sin*u2[2])/pre;
	      if (solenoid != 0.0)  {
	        var cte = k*Math.cos(i*2*Math.PI/pointsPerLoop)/pre;
	        xx += solenoid*cte*endp.x;
	        yy += solenoid*cte*endp.y;
	        zz += solenoid*cte*endp.z;
	      }
	      if (i==0)
	      	moveTo(xx,yy,zz); 
	      else 
	      	lineTo(xx,yy,zz);
	    }
	}

    var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName());

  	var endpoint = new Vector(mElement.getSize());
  	
  	// after it is translated
  	// var origpoint = new Vector(mElement.getPosition());
    var origpoint = new Vector([0,0,0]);
    
	// set attributes
	var index = 0;
	drawSpring(mElement.getLoops(), mElement.getPointsPerLoop(), 
			mElement.getRadius(), mElement.getSolenoid(), mElement.getThinExtremes(), origpoint, endpoint,
			function(xx,yy,zz) { mesh.vertices.push([xx, yy, zz]); index++;}, 
			function(xx,yy,zz) { mesh.vertices.push([xx, yy, zz]); mesh.lines.push([index-1,index]); index++});   
	
	// compute normals
	mesh.computeNormals();
	   		
	mesh.compile();
	  	  	  
	mGL.addElement(mElement.getName() + ".mesh", mesh);

    return mesh;
    
}/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL custom
 */

EJSS_WEBGLGRAPHICS.surface = function(mGL, mElement) {   
    
    createSurfaceBuffers = function(xpoints, ypoints, data3D, mesh){
        for (var i = 0; i < xpoints - 1; i++) {
            for (var j = 0; j < ypoints - 1; j++) {
                // Create surface vertices.
                var rawP1 = data3D[j + (i * ypoints)];
                var rawP2 = data3D[j + (i * ypoints) + ypoints];
                var rawP3 = data3D[j + (i * ypoints) + ypoints + 1];
                var rawP4 = data3D[j + (i * ypoints) + 1];
                
                mesh.vertices.push(rawP1);
                mesh.vertices.push(rawP2);
                mesh.vertices.push(rawP3);
                mesh.vertices.push(rawP4);                                
            }
        }
                
        var numQuads = ((xpoints - 1) * (ypoints - 1)) / 2;        
        for (var i = 0; i < (numQuads * 8); i += 4) {
            mesh.triangles.push([i, i+1, i+2]);
            mesh.triangles.push([i, i+2, i+3]);
            
            mesh.lines.push([i,i+1]);             
            mesh.lines.push([i,i+3]);             
            mesh.lines.push([i+2,i+3]);             
            mesh.lines.push([i+2,i+1]);             
            
            mesh.coords.push([0,0]);
            mesh.coords.push([0,1]);
            mesh.coords.push([1,0]);
            mesh.coords.push([1,1]);
        }        
    };

	  var data = mElement.getData();
	  if(data || data.length > 0) {
		  var numRows = data.length;
		  var numCols = data[0].length;

		  // prepare points
	      var data3ds = new Array();
	      var index = 0;
	      for (var i = 0; i < numRows; i++) {
	        for (var j = 0; j < numCols; j++) {
	            data3ds[index] = data[i][j];
	            index++; 
	        }
	      }
		  
		  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName());
		  
		  createSurfaceBuffers(numRows, numCols, data3ds, mesh);
	
	  	  // compute normals
	  	  mesh.computeNormals();
		   		
		  mesh.compile();
		  	  	  
		  mGL.addElement(mElement.getName() + ".mesh", mesh);

	  }	  	  
    
      return mesh;
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL text
 */
EJSS_WEBGLGRAPHICS.text = function(mGL, mElement) {
	  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL, mElement.getName());

	  var font = mElement.getFont();
	  var backGroundColor = mElement.getBackground();
	  var text = mElement.getText();
	  var color = font.getFillColor();	  
	  
	  // build canvas with text
	  var canvas = document.createElement("canvas");		 
	  var context = canvas.getContext("2d");
	  context = canvas.getContext("2d");

	  // fit height
	  var height = font.getFontSize();
	  if(mElement.getFitText())
	  	height = Math.max(canvas.height,height);

	  // font 
 	  var fondtxt = "";
 	  fondtxt += (font.getFontStyle() != 'none')? font.getFontStyle() + ' ':'';
 	  fondtxt += (font.getFontWeight() != 'none')? font.getFontWeight() + ' ':'';
 	  fondtxt += height + 'px ';
 	  fondtxt += font.getFontFamily();
	  context.font = fondtxt;
		 
	  if(backGroundColor) {
		context.fillStyle = backGroundColor;
		context.fillRect(0, 0, canvas.width, canvas.height);
	  }
		 
	  context.textAlign = "center";
	  context.textBaseline = "middle";
	  context.fillStyle = color;
	  context.fillText(text, canvas.width / 2, canvas.height / 2, canvas.width);
		 
      // add texture 
	  var texture = EJSS_WEBGLGRAPHICS.Texture.fromImage(mGL,canvas);
	  mGL.addElement(mElement.getName() + ".texture", texture);
  		  
      // create mesh
	  var detail = 1;     // tesselation level 	
	  for (var y = 0; y <= detail; y++) {
	    var t = y / detail;
	    for (var x = 0; x <= detail; x++) {
	      var s = x / detail;
	      mesh.vertices.push([0 , 2 * t - 1, 2 * s - 1]);
	      mesh.coords.push([t, s]);	      
	      if (x < detail && y < detail) {
	        var i = x + y * (detail + 1);
	        mesh.triangles.push([i, i + 1, i + detail + 1]);
	        mesh.triangles.push([i + detail + 1, i + 1, i + detail + 2]);
	      }
	    }
	  }
	  
	  // compute normals	  	
	  mesh.computeNormals();
	  
	  // compute wire
	  mesh.computeWireframe();
	  	  
	  mesh.compile();
				 	  		  	
	  mGL.addElement(mElement.getName() + ".mesh", mesh);
        
      return mesh;
}

// Provides a simple wrapper around WebGL textures that supports render-to-texture.

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

EJSS_WEBGLGRAPHICS.Texture = {
// ### GL.Texture.fromImage(image[, options])
// 
// Return a new image created from `image`, an `<img>` tag.
fromImage: function(mGL, image, options) {
  options = options || {};
  var texture = EJSS_WEBGLGRAPHICS.texture(mGL, image.width, image.height, options);
  try {
    mGL.texImage2D(mGL.TEXTURE_2D, 0, texture.getFormat(), texture.getFormat(), texture.getType(), image);
  } catch (e) {
    if (location.protocol == 'file:') {
    	// for Chrome --allow-file-access-from-files
      throw 'image not loaded for security reasons (serve this page over "http://" instead)';
    } else {
      throw 'image not loaded for security reasons (image must originate from the same ' +
        'domain as this page or use Cross-Origin Resource Sharing)';
    }
  }
  if (options.minFilter && options.minFilter != mGL.NEAREST && options.minFilter != mGL.LINEAR) {
    mGL.generateMipmap(mGL.TEXTURE_2D);
  }
  return texture;
},

// ### GL.Texture.fromURL(url[, options])
// 
// Returns a checkerboard texture that will switch to the correct texture when
// it loads.
fromURL: function(mGL, url, options, callback) {
  checkerboardCanvas = (function() {
    var c = document.createElement('canvas').getContext('2d');
    c.canvas.width = c.canvas.height = 128;
    for (var y = 0; y < c.canvas.height; y += 16) {
      for (var x = 0; x < c.canvas.width; x += 16) {
        c.fillStyle = (x ^ y) & 16 ? '#FFF' : '#DDD';
        c.fillRect(x, y, 16, 16);
      }
    }
    return c.canvas;
  })();
  var texture = EJSS_WEBGLGRAPHICS.Texture.fromImage(mGL, checkerboardCanvas, options);
  var image = new Image();
  image.onload = function() {
  	EJSS_WEBGLGRAPHICS.Texture.fromImage(mGL, image, options).swapWith(texture);
  	callback();
  };
  image.src = url;
  return texture;
}

}

// ### new GL.Texture(width, height[, options])
//
// The arguments `width` and `height` give the size of the texture in texels.
// WebGL texture dimensions must be powers of two unless `filter` is set to
// either `mGL.NEAREST` or `mGL.LINEAR` and `wrap` is set to `mGL.CLAMP_TO_EDGE`
// (which they are by default).
//
// Texture parameters can be passed in via the `options` argument.
// Example usage:
// 
//     var t = new GL.Texture(256, 256, {
//       // Defaults to mGL.LINEAR, set both at once with "filter"
//       magFilter: mGL.NEAREST,
//       minFilter: mGL.LINEAR,
// 
//       // Defaults to mGL.CLAMP_TO_EDGE, set both at once with "wrap"
//       wrapS: mGL.REPEAT,
//       wrapT: mGL.REPEAT,
// 
//       format: mGL.RGB, // Defaults to mGL.RGBA
//       type: mGL.FLOAT // Defaults to mGL.UNSIGNED_BYTE
//     });
EJSS_WEBGLGRAPHICS.texture = function(mGL, width, height, options) {
  var self = {};
  options = options || {};

  var framebuffer;
  var renderbuffer;

  var id = mGL.createTexture();
  var format = options.format || mGL.RGBA;
  var type = options.type || mGL.UNSIGNED_BYTE;


  self.getId = function() {
  	return id;
  }
  
  self.setId = function(v) {
  	id = v;
  }
  
  self.getFormat = function() {
  	return format;
  }
  
  self.setFormat = function(v) {
  	format = v;
  }
  
  self.getType = function() {
  	return type;
  }
  
  self.setType = function(v) {
  	type = v;
  }

  self.getWidth = function() {
  	return width;
  }

  self.setWidth = function(v) {
  	width = v;
  }

  self.getHeight = function() {
    return height;	
  }
  
  self.setHeight = function(v) {
  	height = v;
  }

  // ### .bind([unit])
  // 
  // Bind this texture to the given texture unit (0-7, defaults to 0).
  self.bind = function(unit) {
    mGL.activeTexture(mGL.TEXTURE0 + (unit || 0));
    mGL.bindTexture(mGL.TEXTURE_2D, id);
  },

  // ### .unbind([unit])
  // 
  // Clear the given texture unit (0-7, defaults to 0).
  self.unbind = function(unit) {
    mGL.activeTexture(mGL.TEXTURE0 + (unit || 0));
    mGL.bindTexture(mGL.TEXTURE_2D, null);
  },

  // ### .drawTo(callback)
  // 
  // Render all draw calls in `callback` to this texture. This method sets up
  // a framebuffer with this texture as the color attachment and a renderbuffer
  // as the depth attachment. It also temporarily changes the viewport to the
  // size of the texture.
  // 
  // Example usage:
  // 
  //     texture.drawTo(function() {
  //       mGL.clearColor(1, 0, 0, 1);
  //       mGL.clear(mGL.COLOR_BUFFER_BIT);
  //     });
  self.drawTo = function(callback) {
    var v = mGL.getParameter(mGL.VIEWPORT);
    framebuffer = framebuffer || mGL.createFramebuffer();
    renderbuffer = renderbuffer || mGL.createRenderbuffer();
    mGL.bindFramebuffer(mGL.FRAMEBUFFER, framebuffer);
    mGL.bindRenderbuffer(mGL.RENDERBUFFER, renderbuffer);
    if (width != renderbuffer.width || height != renderbuffer.height) {
      renderbuffer.width = width;
      renderbuffer.height = height;
      mGL.renderbufferStorage(mGL.RENDERBUFFER, mGL.DEPTH_COMPONENT16, width, height);
    }
    mGL.framebufferTexture2D(mGL.FRAMEBUFFER, mGL.COLOR_ATTACHMENT0, mGL.TEXTURE_2D, id, 0);
    mGL.framebufferRenderbuffer(mGL.FRAMEBUFFER, mGL.DEPTH_ATTACHMENT, mGL.RENDERBUFFER, renderbuffer);
    mGL.viewport(0, 0, width, height);

    callback();

    mGL.bindFramebuffer(mGL.FRAMEBUFFER, null);
    mGL.bindRenderbuffer(mGL.RENDERBUFFER, null);
    mGL.viewport(v[0], v[1], v[2], v[3]);
  },

  // ### .swapWith(other)
  // 
  // Switch this texture with `other`, useful for the ping-pong rendering
  // technique used in multi-stage rendering.
  self.swapWith = function(other) {
    var temp;
    temp = other.getId(); other.setId(id); id = temp;
    temp = other.getWidth(); other.setWidth(width); width = temp;
    temp = other.getHeight(); other.setHeight(height); height = temp;
  }
  
  mGL.bindTexture(mGL.TEXTURE_2D, id);
  mGL.pixelStorei(mGL.UNPACK_FLIP_Y_WEBGL, 1);
  // mGL.pixelStorei(mGL.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
  mGL.texParameteri(mGL.TEXTURE_2D, mGL.TEXTURE_MAG_FILTER, options.filter || options.magFilter || mGL.LINEAR);
  mGL.texParameteri(mGL.TEXTURE_2D, mGL.TEXTURE_MIN_FILTER, options.filter || options.minFilter || mGL.LINEAR);
  mGL.texParameteri(mGL.TEXTURE_2D, mGL.TEXTURE_WRAP_S, options.wrap || options.wrapS || mGL.CLAMP_TO_EDGE);
  mGL.texParameteri(mGL.TEXTURE_2D, mGL.TEXTURE_WRAP_T, options.wrap || options.wrapT || mGL.CLAMP_TO_EDGE);
  mGL.texImage2D(mGL.TEXTURE_2D, 0, format, width, height, 0, format, type, null);

  return self;  
};

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};

/**
 * @param mGL Element where draw
 * @param mElement Element to draw
 * @returns A WebGL trail
 */

EJSS_WEBGLGRAPHICS.trail = function(mGL, mElement) {   
  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName(), 
	{coords:false, normals:true, triangles:true, lines:false, colors:false});
	  
  var resolutionU = mElement.getResolutionU();
  var radius = mElement.getLineWidth() / 2;

  var points = mElement.getPoints();
  for(var pos=0; pos<points.length-1; pos++) {
  	  var origpoint = new Vector(points[pos]);
  	  var endpoint = new Vector(points[pos+1]);
  	  var line = endpoint.subtract(origpoint);
      
	  // generate vertex for body
	  var vertices3D = [];	  
	  for (var x = 0; x < resolutionU; x++ ) {
		var u = x / (resolutionU - 1);
		var xpos = radius * Math.sin( u * Math.PI * 2 );
		var ypos = radius * Math.cos( u * Math.PI * 2 );

		vertices3D.push(new Vector([xpos, ypos, 0])); // top vertex
		vertices3D.push(new Vector([xpos, ypos, 0])); // bottom vertex
	  }
	  
	  // rotate and translate top and bottom
	  var cross = line.cross(new Vector(0,0,1));
	  if(cross.length()==0) cross.y = 1; // if endoint in axis Z, get axis Y to rotate 
	  // console.log("cross:" + cross.x + " " + cross.y + " " + cross.z + " ");	  
	  var angle = line.angle(new Vector(0,0,1));
	  // console.log("angle:" + angle);
	  var result = new Matrix();
	  Matrix.rotate(-angle,cross.x,cross.y,cross.z,0,0,0,result);
	  for(var i=0; i<vertices3D.length; i++) {
	  	var vector = result.transformPoint(vertices3D[i]);
	  	if (i % 2 == 0) {
	  		// add endpoint to translate	  	
	  		mesh.vertices.push(vector.add(endpoint).toArray());	  		
	  	} else {
	  		// add origpoint to translate
	  		mesh.vertices.push(vector.add(origpoint).toArray());
	  	}
	  }
	  
	  // create surface triangles
	  for (var x = 0; x < resolutionU - 1; x++ ) {
	    var first = 2 * (x + pos * resolutionU);		    
	    var fourth = 2 * (x  + pos * resolutionU) + 2;
	    var second = 2 * (x  + pos * resolutionU) + 1;
	    var third = 2 * (x  + pos * resolutionU) + 3;
			
        mesh.triangles.push([third, second, first]);      
        mesh.triangles.push([fourth, third, first]);
	  }		
  }		

  // last top cap
  mesh.vertices.push(new Vector(points[points.length-1]).toArray());		
  for (var i = 0; i < resolutionU - 1; i++) {
  	var first = mesh.vertices.length - 1;
  	var second = 2*i+2 + (2 * (points.length - 2) * resolutionU);
  	var third = 2*i + (2 * (points.length - 2) * resolutionU);
  	mesh.triangles.push([first, second, third]);
  }        

  // first bottom cap
  mesh.vertices.push(new Vector(points[0]).toArray());
  for (var i = 0; i < resolutionU - 1; i++) {
  	var first = 2*i+1;
  	var second = 2*i+3;
  	var third = mesh.vertices.length - 1;
   	mesh.triangles.push([first, second, third]);
  }        

  // nodes cap
  for(var pos=1; pos<points.length-1; pos++) {
	for (var x = 0; x < resolutionU - 1; x++ ) {
		var first = 2 * (x + (pos - 1) * resolutionU);		    
		var fourth = 2 * (x + (pos - 1) * resolutionU) + 2;
		var second = 2 * (x + pos * resolutionU) + 1;
		var third = 2 * (x + pos * resolutionU) + 3;			
        mesh.triangles.push([third, second, first]);      
        mesh.triangles.push([fourth, third, first]);
	}		    		
  }
   
  mesh.computeNormals();	
	  
  mesh.compile();	
	
  mGL.addElement(mElement.getName() + ".mesh", mesh);
        
  return mesh;
  
  
/* Previously it was possible to manage lineWidth and then the segment was easy ...
  var mesh = EJSS_WEBGLGRAPHICS.mesh(mGL,mElement.getName(), 
  	{coords:false, normals:false, triangles:false, lines:true, colors:false});
  	  
  var points = mElement.getPoints();
  var vertices = [];
  var lines = [];
  var index = 0;
  for(var i=0; i<points.length; i++) {
  	vertices[i] = [points[i][0],points[i][1],points[i][2]];
  	if(i!=0 && points[i][3] == 1) { // 0 is NOT CONNECTION
  		lines[index++] = [i-1, i];
  	}
  }
  
  // set values
  mesh.setVertices(vertices);	  	  
  if (lines.length > 0) mesh.setLines(lines);
  
  mesh.compile();
  
  mGL.addElement(mElement.getName() + ".mesh", mesh);
   
  return mesh;
*/   
}

/**
 * Deployment for 3D SVG drawing.
 * @module WebGLGraphics 
 */

var EJSS_WEBGLGRAPHICS = EJSS_WEBGLGRAPHICS || {};
EJSS_WEBGLGRAPHICS.Utils = {};

EJSS_WEBGLGRAPHICS.Utils.regexMap = function(regex, text, callback) {
  while ((result = regex.exec(text)) != null) {
    callback(result);
  }
}

EJSS_WEBGLGRAPHICS.Utils.isArray = function(obj) {
  var str = Object.prototype.toString.call(obj);
  return str == '[object Array]' || str == '[object Float32Array]';
}

EJSS_WEBGLGRAPHICS.Utils.isNumber = function(obj) {
  var str = Object.prototype.toString.call(obj);
  return str == '[object Number]' || str == '[object Boolean]';
}
// Provides a simple 3D vector class. Vector operations can be done using member
// functions, which return new vectors, or static functions, which reuse
// existing vectors to avoid generating garbage.
function Vector(x, y, z) {
  if(typeof x == 'object') {
  	this.x = x[0] || 0;
  	this.y = x[1] || 0;
  	this.z = x[2] || 0;  	
  } else {
  	this.x = x || 0;
  	this.y = y || 0;
  	this.z = z || 0;
  }
}

// ### Instance Methods
// The methods `add()`, `subtract()`, `multiply()`, and `divide()` can all
// take either a vector or a number as an argument.
Vector.prototype = {
  setx: function(x) {
  	this.x = x;
  },
  sety: function(y) {
  	this.y = y;
  },
  setz: function(z) {
  	this.z = z;
  },
  getx: function() {
  	return this.x;
  },
  gety: function() {
  	return this.y;
  },
  getz: function() {
  	return this.z;
  },
  rotate: function() {
  	return new Vector(this.z, this.x, this.y);
  },
  negative: function() {
    return new Vector(-this.x, -this.y, -this.z);
  },
  add: function(v) {
    if (v instanceof Vector) return new Vector(this.x + v.x, this.y + v.y, this.z + v.z);
    else return new Vector(this.x + v, this.y + v, this.z + v);
  },
  subtract: function(v) {
    if (v instanceof Vector) return new Vector(this.x - v.x, this.y - v.y, this.z - v.z);
    else return new Vector(this.x - v, this.y - v, this.z - v);
  },
  multiply: function(v) {
    if (v instanceof Vector) return new Vector(this.x * v.x, this.y * v.y, this.z * v.z);
    else return new Vector(this.x * v, this.y * v, this.z * v);
  },
  divide: function(v) {
    if (v instanceof Vector) return new Vector(this.x / v.x, this.y / v.y, this.z / v.z);
    else return new Vector(this.x / v, this.y / v, this.z / v);
  },
  equals: function(v) {
    return this.x == v.x && this.y == v.y && this.z == v.z;
  },
  dot: function(v) {
    return this.x * v.x + this.y * v.y + this.z * v.z;
  },
  cross: function(v) {
    return new Vector(
      this.y * v.z - this.z * v.y,
      this.z * v.x - this.x * v.z,
      this.x * v.y - this.y * v.x
    );
  },
  length: function() {
    return Math.sqrt(this.dot(this));
  },
  unit: function() {
    return this.divide(this.length());
  },
  min: function() {
    return Math.min(Math.min(this.x, this.y), this.z);
  },
  max: function() {
    return Math.max(Math.max(this.x, this.y), this.z);
  },
  angle: function(v) {
  	return Math.acos(this.dot(v)/(this.length()*v.length()));
  },
  toAngles: function() {
    return {
      theta: Math.atan2(this.z, this.x),
      phi: Math.asin(this.y / this.length())
    };
  },
  toArray: function(n) {
    return [this.x, this.y, this.z].slice(0, n || 3);
  },
  clone: function() {
    return new Vector(this.x, this.y, this.z);
  },
  init: function(x, y, z) {
    this.x = x; this.y = y; this.z = z;
    return this;
  }
};

// ### Static Methods
// `Vector.randomDirection()` returns a vector with a length of 1 and a
// statistically uniform direction. `Vector.lerp()` performs linear
// interpolation between two vectors.
Vector.negative = function(a, b) {
  b.x = -a.x; b.y = -a.y; b.z = -a.z;
  return b;
};
Vector.add = function(a, b, c) {
  if (b instanceof Vector) { c.x = a.x + b.x; c.y = a.y + b.y; c.z = a.z + b.z; }
  else { c.x = a.x + b; c.y = a.y + b; c.z = a.z + b; }
  return c;
};
Vector.subtract = function(a, b, c) {
  if (b instanceof Vector) { c.x = a.x - b.x; c.y = a.y - b.y; c.z = a.z - b.z; }
  else { c.x = a.x - b; c.y = a.y - b; c.z = a.z - b; }
  return c;
};
Vector.multiply = function(a, b, c) {
  if (b instanceof Vector) { c.x = a.x * b.x; c.y = a.y * b.y; c.z = a.z * b.z; }
  else { c.x = a.x * b; c.y = a.y * b; c.z = a.z * b; }
  return c;
};
Vector.divide = function(a, b, c) {
  if (b instanceof Vector) { c.x = a.x / b.x; c.y = a.y / b.y; c.z = a.z / b.z; }
  else { c.x = a.x / b; c.y = a.y / b; c.z = a.z / b; }
  return c;
};
Vector.cross = function(a, b, c) {
  c.x = a.y * b.z - a.z * b.y;
  c.y = a.z * b.x - a.x * b.z;
  c.z = a.x * b.y - a.y * b.x;
  return c;
};
Vector.unit = function(a, b) {
  var length = a.length();
  b.x = a.x / length;
  b.y = a.y / length;
  b.z = a.z / length;
  return b;
};
Vector.fromAngles = function(theta, phi) {
  return new Vector(Math.cos(theta) * Math.cos(phi), Math.sin(phi), Math.sin(theta) * Math.cos(phi));
};
Vector.randomDirection = function() {
  return Vector.fromAngles(Math.random() * Math.PI * 2, Math.asin(Math.random() * 2 - 1));
};
Vector.min = function(a, b) {
  return new Vector(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z));
};
Vector.max = function(a, b) {
  return new Vector(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z));
};
Vector.lerp = function(a, b, fraction) {
  return b.subtract(a).multiply(fraction).add(a);
};
Vector.fromArray = function(a) {
  return new Vector(a[0], a[1], a[2]);
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Deployment for 3D drawing.
 * @module 3Dgraphics 
 */

var EJSS_GRAPHICS = EJSS_GRAPHICS || {};

/**
 * WebGLGraphics class
 * @class WebGLGraphics 
 * @constructor  
 */
EJSS_GRAPHICS.WebGLGraphics = {
    
  /**
   * Return document box
   * @return box  
   */
  getOffsetRect: function(graphics) {
  	return EJSS_GRAPHICS.GraphicsUtils.getOffsetRect(graphics);
  }
};

/**
 * Constructor for Element
 * @param mName Identifier Canvas element in HTML document
 * @returns A Canvas graphics
 */  
EJSS_GRAPHICS.webGLGraphics = function(mName) {
  var self = EJSS_INTERFACE.webGLCanvas(mName);	// reference returned     
  var canvas = self.getDOMElement();

  // get context
  var mGL = self.getContext();
  
  // blending vars
  var mDepths = {};
  var mmBlendingElements = [];

  var mTransformationOrder = 0; // 0: first trans/scale and then transformation, 1: first transformation and then trans/scale 

  self.supportsWebGL = function() {
    return mGL;
  };
  
  /**
   * Return canvas for event handle
   * @return Canvas element 
   */
  self.getEventContext = function() {  	
    return canvas;
  };
  
  /**
   * Return canvas document box
   * @return width integer 
   */
  self.getBox = function() {
  	var box = EJSS_GRAPHICS.WebGLGraphics.getOffsetRect(self)
  	canvas.width = box.width;
  	canvas.height = box.height; 
  	box.width -= 1;
  	box.height -= 1;
  	return box;
  };
    
  /**
   * Return aspect ratio of canvas 
   * @return double 
   */
  self.getAspect = function() {
  	var box = EJSS_GRAPHICS.WebGLGraphics.getOffsetRect(self)
	var height = Math.max(1, box.height);  // prevent divide by 0

  	return box.width / height;
  }

  /**
   * Set transformation order 
   * @return order 
   */
  self.setTransformationOrder = function(order) {
  	mTransformationOrder = order;
  }
    
  /**
   * Get transformation order 
   * @return order 
   */
  self.getTransformationOrder = function() {
  	return mTransformationOrder;
  }
    
  /**
   * Remove a shape
   * @param name
   */
  self.remove = function(name) {	
    
  };

  /**
   * Reset the canvas
   */
  self.reset = function(ort,dim,fov,near,far) {	  
  	if(!mGL) return;

    addMatrixStack();

	// viewport
    var box = self.getBox();
	var height = Math.max(1, box.height);  // prevent divide by 0
	mGL.viewport(0, 0, box.width, height);

	// projection matrix
    mGL.matrixMode(mGL.PROJECTION);
    mGL.loadIdentity();
	if(!ort) 
		mGL.perspective(fov, box.width / height, near, far);
	else {
		mGL.ortho(dim.left, dim.right, dim.bottom, dim.top, -near, far);								
	}
	
	// view matrix
    mGL.matrixMode(mGL.VIEW);     
    
    // rendering of both sides of each face (front and back)
  	mGL.disable(mGL.CULL_FACE);  
	mGL.enable(mGL.POLYGON_OFFSET_FILL);
	// mGL.polygonOffset(0.5, 0.5);
	
    // without blending for default	
	mGL.enable(mGL.DEPTH_TEST);
	mGL.depthFunc(mGL.LESS);
	mGL.depthMask(true);

    mGL.clear(mGL.COLOR_BUFFER_BIT | mGL.DEPTH_BUFFER_BIT);	
  };

  self.putCamera = function(ort,camera) {
  	if(!mGL) return;
  	
    mGL.clear(mGL.COLOR_BUFFER_BIT | mGL.DEPTH_BUFFER_BIT);

	// ort size
	if(ort) {
    	mGL.matrixMode(mGL.PROJECTION);
    	if (camera.delta != 0) {
			mGL.scale(camera.delta, camera.delta, camera.delta);		    		
	    	camera.delta = 0;    		
    	}
	}
	    	
  	// camera position
    mGL.matrixMode(mGL.VIEW);     	
    mGL.loadIdentity();
    mGL.lookAt(camera.location.x, camera.location.y, camera.location.z,
    			camera.focus.x, camera.focus.y, camera.focus.z,
    			camera.upvector.x, camera.upvector.y, camera.upvector.z); // up Vector
    mGL.rotate(EJSS_TOOLS.Mathematics.radians(camera.tilt), 1, 0, 0, 
    	camera.focus.x, camera.focus.y, camera.focus.z);    
    mGL.rotate(EJSS_TOOLS.Mathematics.radians(camera.altitude), 0, 1, 0, 
    	camera.focus.x, camera.focus.y, camera.focus.z);    
    mGL.rotate(EJSS_TOOLS.Mathematics.radians(camera.azimuth), 0, 0, 1, 
    	camera.focus.x, camera.focus.y, camera.focus.z);    
  }
	 
  // Ordered list of blending elements
  function addToAlphaList(element) {
	// element depth
	var v = new Vector(element.getPosition());
	var pv = mGL.viewMatrix.transformVector(v);
	var depth = pv.z;

	mDepths[element.getName()] = depth;
		
	// Binary search
	var less, more, itteration = 1, inserted = false, index = Math.floor(mBlendingElements.length/2);
	while(!inserted) {
		less = (index === 0 || mDepths[mBlendingElements[index-1].getName()] <= depth);
		more = (index >= mBlendingElements.length || mDepths[mBlendingElements[index].getName()] >= depth);
		if(less && more) {
			mBlendingElements.splice(index, 0, element);
			inserted = true;
		} else {
			itteration++;
			var step = Math.ceil(mBlendingElements.length/Math.pow(2,itteration));
			if(!less) {
				index = Math.max(0, index-step);
			} else {
				index = Math.min(mBlendingElements.length, index+step);
			}
		}
	}
  };

  /**
   * Draw Elements
   * @param elements array of Elements and Element Sets
   * @param force whether force draw elements
   */
  self.draw = function(elements,force) {  	  
  	if(!mGL) return;  		
  	
  	// globals (they could be local vars)
  	mDepths = {};
  	mBlendingElements = [];
  	
	var noBlendingElements = [];  		
  	var allElements = elements.slice();  	

	while (allElements.length) {
      var element = allElements.shift();
      if(element.getElements) {	// whether element set
      	var lasts = element.getLastElements();
  		for(var j=0, m=lasts.length; j<m; j++) { // remove last removed elements
			mGL.removeElement(lasts[j].getName() + ".mesh");
  		}    
  		// update array of elements used in iteration
  		allElements = element.getElements().concat(allElements);
      } else { // else one element
  		// it is neccesary to order elements to blending (http://delphic.me.uk/webglalpha.html)       	
		if(element.getStyle().getTransparency() > 0.0) {
			addToAlphaList(element);
		} else {
			noBlendingElements.push(element);
		}
      }
    }    
   
    // drawing elements without blending
	for(i = 0, l = noBlendingElements.length; i < l; i++) {
		var element = noBlendingElements[i];
		self.drawElement(element,force);
	}    
   
    // drawing elements with blending
	mGL.depthMask(false);
    mGL.enable(mGL.BLEND);
    mGL.blendFunc(mGL.SRC_ALPHA, mGL.ONE_MINUS_SRC_ALPHA);

    // draw ordered elements by depth
	for(i = 0, l = mBlendingElements.length; i < l; i++) {
		var element = mBlendingElements[i];
		self.drawElement(element,force);
		//console.log("Drawing element " + element.getName() + " - " + mDepths[element.getName()]);
	}    
	
	// disable blending
	mGL.disable(mGL.BLEND);
	mGL.depthMask(true);   
  };

  function applyTranformation(gtr) {
	  if (gtr && gtr.length > 0) {
			if (EJSS_WEBGLGRAPHICS.Utils.isArray(gtr[0])) { // array of rotations
				for (var i=0; i<gtr.length; i++) {
					if (gtr[i].length == 7) { 
						// rotation 
						mGL.rotate(gtr[i][0],gtr[i][1],gtr[i][2],gtr[i][3],
							gtr[i][4],gtr[i][5],gtr[i][6]); // angle,x,y,z,cx,cy,cz								
					} else {
						// transformation matrix
						mGL.applyMatrix(gtr[i]);
					}
				}
			} else { // only one rotation
				if (gtr.length == 7) { 
					// rotation 
					mGL.rotate(gtr[0],gtr[1],gtr[2],gtr[3],
						gtr[4],gtr[5],gtr[6]); // angle,x,y,z,cx,cy,cz								
				} else {
					// transformation matrix
					mGL.applyMatrix(gtr);
				}
			}
	   }  	
  }

  /**
   * Draw Element
   * @param element
   */
  self.drawElement = function (element, force) {
    // console.log("Drawing element "+element.getName());
    var build = null;
  	if (element.isGroupVisible()) {
        var build = mGL.getElement(element.getName() + ".mesh");
        if(!build || element.isMeshChanged() || element.isAlwaysUpdated()) {
          // if (build) mGL.removeElement(element.getName() + ".mesh");  		
      	  switch (element.getClass()) {
			case "ElementArrow": 	build = EJSS_WEBGLGRAPHICS.arrow(mGL,element);	break;
			case "ElementSegment": 	build = EJSS_WEBGLGRAPHICS.segment(mGL,element);	break;
			case "ElementSphere": 		build = EJSS_WEBGLGRAPHICS.ellipsoid(mGL,element);	break;
			case "ElementEllipsoid": 	build = EJSS_WEBGLGRAPHICS.ellipsoid(mGL,element);	break;						
			case "ElementBox": 		build = EJSS_WEBGLGRAPHICS.box(mGL,element);	break;
			case "ElementCylinder":	build = EJSS_WEBGLGRAPHICS.cylinder(mGL,element);	break;
			case "ElementCone":		build = EJSS_WEBGLGRAPHICS.cylinder(mGL,element);	break;
			case "ElementTetrahedron":		build = EJSS_WEBGLGRAPHICS.cylinder(mGL,element);	break;
			case "ElementDisk":		build = EJSS_WEBGLGRAPHICS.cylinder(mGL,element);	break;
			case "ElementBasic": 	build = EJSS_WEBGLGRAPHICS.basic(mGL,element);	break;
			case "ElementPlane": 	build = EJSS_WEBGLGRAPHICS.plane(mGL,element);	break;
			case "ElementSurface": 	build = EJSS_WEBGLGRAPHICS.surface(mGL,element);	break;
			case "ElementAnalyticSurface": 	build = EJSS_WEBGLGRAPHICS.analyticSurface(mGL,element);	break;
			case "ElementText": 	build = EJSS_WEBGLGRAPHICS.text(mGL,element);	break;
			case "ElementTrail": 	build = EJSS_WEBGLGRAPHICS.trail(mGL,element);	break;
			case "ElementAnalyticCurve":	build = EJSS_WEBGLGRAPHICS.analyticCurve(mGL,element);	break;
			case "ElementSpring":	build = EJSS_WEBGLGRAPHICS.spring(mGL,element);	break;
		  }
		}
		// if (build && (element.isMeshChanged() || element.isProjChanged() || force)) {
		if (build) {
    		mGL.matrixMode(mGL.MODEL);
    		mGL.loadIdentity();

			var groups = [];
			var el = element;
		    while (el.getGroup()) {
		    	el = el.getGroup();
   		        groups.unshift(el);
   		    }
			
			for (var g=0; g<groups.length; g++) {
			  el = groups[g];
			  
			  // group transformation
		      var gtr = el.getFullTransformation();
			  if (mTransformationOrder != 0) applyTranformation(gtr); 

		      // scale and translate
  	  		  mGL.translate(			
				el.getX()*element.getPanel().getSizeX(),
  	  			el.getY()*element.getPanel().getSizeY(),
  	  			el.getZ()*element.getPanel().getSizeZ());  	  		  	
			  mGL.scale(el.getSizeX(), el.getSizeY(), el.getSizeZ());
		      
			  if (mTransformationOrder == 0) applyTranformation(gtr); 
		    }		
			
			// individual transformation
			var tr = element.getFullTransformation(); // only support rotation
			if (mTransformationOrder != 0) applyTranformation(tr); 

		    // scale and translate
  	  		mGL.translate(element.getX()*element.getPanel().getSizeX(),
  	  			element.getY()*element.getPanel().getSizeY(),
  	  			element.getZ()*element.getPanel().getSizeZ());			

			if (mTransformationOrder == 0) applyTranformation(tr); 

			// adjust sizes
			if(element.getClass() == "ElementArrow" || element.getClass() == "ElementSegment" || element.getClass() == "ElementSpring" ) { // size is used to create the arrow
				mGL.scale(element.getPanel().getSizeX(),
	  	  			element.getPanel().getSizeY(),
	  	  			element.getPanel().getSizeZ());				
			} else if(element.getClass() == "ElementTrail" || element.getClass() == "ElementSurface") { // creados con su tamaño real
				mGL.scale(element.getPanel().getSizeX()*element.getSizeX(),
	  	  			element.getPanel().getSizeY()*element.getSizeY(),
	  	  			element.getPanel().getSizeZ()*element.getSizeZ());								
			} else { // creados con el doble de su tamaño
				mGL.scale(element.getPanel().getSizeX()*element.getSizeX()/2,
	  	  			element.getPanel().getSizeY()*element.getSizeY()/2,
	  	  			element.getPanel().getSizeZ()*element.getSizeZ()/2);				
			}
			
			// draw mesh
			self.drawMesh(mGL,element,build);

			// after transformations
    		mGL.matrixMode(mGL.VIEW);
		}
  	}  	  	
  	return build;
  };
 
  /**
   * Draw panel
   * @param panel
   */
  self.drawPanel = function(panel) {
 	if(!mGL) return;
 	
 	var style = panel.getStyle(); 	// style element	    	   	   

	// set background
    if(!panel.isImageUrl()) {	
		var color = style.getFillColor();
    	mGL.clearColor(color[0], color[1], color[2], (typeof color[3]!=="undefined") ? color[3] : 1.0);
    } else {
    	mGL.clearColor(0, 0, 0, 1.0);
    }
        
    return self;

  };
    
  /**
   * Draw mesh
   * @param mesh
   */
  self.drawMesh = function(mGL,element,mesh) {
  	  var opacity = (1.0 - element.getStyle().getTransparency() / 255);

  	  // get shader and texture
  	  var textureUrl = element.getTextureUrl();
	  if (element.getClass() == "ElementText" || 
	  		(textureUrl && textureUrl.length > 0)) {
		
 	  	 // Shader for texture 
		 var shader = mGL.getElement(mesh.getId() + ".shader_tex");
		 var texture = mGL.getElement(mesh.getId() + ".texture");	 
		 shader = shader || EJSS_WEBGLGRAPHICS.shader(mGL, mesh.getId() + "_tex",
		 	EJSS_WEBGLGRAPHICS.Shader["BasicVSwithTex"], EJSS_WEBGLGRAPHICS.Shader["BasicFSwithTex"]); 
	
	     if(texture) {
		  	texture.bind();
		  	shader.uniforms({ texture: 0 });
		  	shader.draw(mesh);
		  	texture.unbind();   
		  
		  	mGL.addElement(mesh.getId() + ".texture", texture);	
	     } else {
	        var tx = EJSS_WEBGLGRAPHICS.Texture.fromURL(mGL, textureUrl, {}, 
	      	function(tx) {
		  		element.getController().invokeAction("OnLoadTexture");	
		  		element.getController().reportInteractions();	    
	      	});

	    	tx.bind();
	    	shader.uniforms({ texture: 0 });
	    	shader.draw(mesh);
	    	tx.unbind();   	
			mGL.addElement(mesh.getId() + ".texture", tx);	      
	     }	
		
	     mGL.addElement(mesh.getId() + ".shader_tex", shader);
	  } else {		
		 // Shader for wire
		 var drawLines = element.getStyle().getDrawLines();
	  	 if (drawLines) {
	  		var shader = mGL.getElement(mesh.getId() + ".shader_lines");
	    	shader = shader || EJSS_WEBGLGRAPHICS.shader(mGL, mesh.getId() + "_lines", 
		 		EJSS_WEBGLGRAPHICS.Shader["BasicVS"], EJSS_WEBGLGRAPHICS.Shader["BasicFS"]);
		 		shader.uniforms({ color: element.getStyle().getLineColor(), opacity: opacity });	  	 		
	  	  	shader.draw(mesh, mGL.LINES);
	  	  	mGL.addElement(mesh.getId() + ".shader_lines", shader);
	    }            	
	  	// Shader for fill
		var drawFill = element.getStyle().getDrawFill();
	  	if (drawFill) {
		    var shader = mGL.getElement(mesh.getId() + ".shader_fill");
		
			var drawColors = (typeof element.getColors != 'undefined' && element.getColors().length > 0);	
			var drawLights = (element.getGroupPanel().getLights().length > 0);
			var hasPalette = (typeof element.getStyle().getPaletteFloor() != 'undefined');
			
			if(hasPalette) {
		    	shader = shader || EJSS_WEBGLGRAPHICS.shader(mGL, mesh.getId() + "_fill", 
			 		EJSS_WEBGLGRAPHICS.Shader["PaletteColorVS"], EJSS_WEBGLGRAPHICS.Shader["PaletteColorFS"]);
			 		shader.uniforms({ 
			 			floor: element.getStyle().getPaletteFloor(),
			 			ceil: element.getStyle().getPaletteCeil(),
			 			floorColor: element.getStyle().getPaletteFloorColor(),
			 			ceilColor: element.getStyle().getPaletteCeilColor()
			 		}); 					 					
			} else {
				if(!drawColors) { // draw one color
					 if(!drawLights) {
				    	shader = shader || EJSS_WEBGLGRAPHICS.shader(mGL, mesh.getId() + "_fill", 
					 		EJSS_WEBGLGRAPHICS.Shader["BasicVS"], EJSS_WEBGLGRAPHICS.Shader["BasicFS"]);
					 		shader.uniforms({ color: element.getStyle().getFillColor() }); 					 	
					 } else {
				    	shader = shader || EJSS_WEBGLGRAPHICS.shader(mGL, mesh.getId() + "_fill", 
					 		EJSS_WEBGLGRAPHICS.Shader["LightVS"], EJSS_WEBGLGRAPHICS.Shader["LightFS"]);
					 		shader.uniforms({ 
					 			ambientColor: element.getStyle().getAmbientColor(),
					 			diffuseColor: element.getStyle().getFillColor(),
					 			specularColor: element.getStyle().getSpecularColor(),
					 			ka: element.getStyle().getAmbientReflection(),
					 			kd: element.getStyle().getColorReflection(),
					 			ks: element.getStyle().getSpecularReflection(),
					 			shininessVal: element.getStyle().getShininessVal()
					 		}); 					 	
					 }
				} else { // draw with color array (only basic element)
					 if(!drawLights) {
				    	shader = shader || EJSS_WEBGLGRAPHICS.shader(mGL, mesh.getId() + "_fill", 
					 		EJSS_WEBGLGRAPHICS.Shader["ColorVS"], EJSS_WEBGLGRAPHICS.Shader["ColorFS"]);					 	
					 } else {
				    	shader = shader || EJSS_WEBGLGRAPHICS.shader(mGL, mesh.getId() + "_fill", 
					 		EJSS_WEBGLGRAPHICS.Shader["LightAndColorVS"], EJSS_WEBGLGRAPHICS.Shader["LightAndColorFS"]); 
					 		shader.uniforms({ 
					 			ambientColor: element.getStyle().getAmbientColor(),
					 			specularColor: element.getStyle().getSpecularColor(),
					 			ka: element.getStyle().getAmbientReflection(),
					 			kd: element.getStyle().getColorReflection(),
					 			ks: element.getStyle().getSpecularReflection(),
					 			shininessVal: element.getStyle().getShininessVal()
					 		}); 					 	
					 }					
				}				
			}			

			var lights = element.getGroupPanel().getLights();
			var numlight = lights.length;
		    shader.uniforms({ numlight: numlight, light: lights, opacity: opacity });
	  	    shader.draw(mesh, mGL.TRIANGLES);
		    mGL.addElement(mesh.getId() + ".shader_fill", shader);
		}
	  }  	
  }

// A value to bitwise-or with new enums to make them distinguishable from the
// standard WebGL enums.
var ENUM = 0x12340000;

// Implement the OpenGL modelview and projection matrix stacks, along with some
// other useful GLU matrix functions.
function addMatrixStack() {
  mGL.MODEL = ENUM | 1;
  mGL.VIEW = ENUM | 2;
  mGL.PROJECTION = ENUM | 3;
  var tempMatrix = new Matrix();
  var resultMatrix = new Matrix();
  mGL.modelMatrix = new Matrix();
  mGL.viewMatrix = new Matrix();
  mGL.projectionMatrix = new Matrix();
  var modelStack = [];
  var viewStack = [];
  var projectionStack = [];
  var matrix, stack;
  mGL.elements = {};	// shaders and mesh defined
  
  mGL.addElement = function(id, ele) {
  	// add or update  	
  	mGL.elements[id] = ele;
  }  

  mGL.getElement = function(id) {
  	return mGL.elements[id];
  }  

  mGL.removeElement = function(id) {
  	delete mGL.elements[id];
  }  

  mGL.matrixMode = function(mode) {
    switch (mode) {
      case mGL.MODEL:
        matrix = 'modelMatrix';
        stack = modelStack;
        break;
      case mGL.VIEW:
        matrix = 'viewMatrix';
        stack = viewStack;
        break;
      case mGL.PROJECTION:
        matrix = 'projectionMatrix';
        stack = projectionStack;
        break;
      default:
        throw 'invalid matrix mode ' + mode;
    }
  };
  mGL.loadIdentity = function() {
    Matrix.identity(mGL[matrix]);
  };
  mGL.loadMatrix = function(m) {
    var from = m.m, to = mGL[matrix].m;
    for (var i = 0; i < 16; i++) {
      to[i] = from[i];
    }
  };
  mGL.multMatrix = function(m) {
    mGL.loadMatrix(Matrix.multiply(mGL[matrix], m, resultMatrix));
  };
  mGL.perspective = function(fov, aspect, near, far) {
    mGL.multMatrix(Matrix.perspective(fov, aspect, near, far, tempMatrix));
  };
  mGL.frustum = function(l, r, b, t, n, f) {
    mGL.multMatrix(Matrix.frustum(l, r, b, t, n, f, tempMatrix));
  };
  mGL.ortho = function(l, r, b, t, n, f) {
    mGL.multMatrix(Matrix.ortho(l, r, b, t, n, f, tempMatrix));
  };
  mGL.scale = function(x, y, z) {
    mGL.multMatrix(Matrix.scale(x, y, z, tempMatrix));
  };
  mGL.translate = function(x, y, z) {
    mGL.multMatrix(Matrix.translate(x, y, z, tempMatrix));
  };
  mGL.applyMatrix = function(m) {
    mGL.multMatrix(new Matrix(m));
  };
  mGL.rotate = function(a, x, y, z, cx, cy, cz) {
  	cx = cx || 0;
  	cy = cy || 0;
  	cz = cz || 0;
    mGL.multMatrix(Matrix.rotate(a, x, y, z, cx, cy, cz, tempMatrix));
  };
  mGL.lookAt = function(ex, ey, ez, cx, cy, cz, ux, uy, uz) {
    mGL.multMatrix(Matrix.lookAt(ex, ey, ez, cx, cy, cz, ux, uy, uz, tempMatrix));
  };
  mGL.pushMatrix = function() {
    stack.push(Array.prototype.slice.call(mGL[matrix].m));
  };
  mGL.popMatrix = function() {
    var m = stack.pop();
    mGL[matrix].m = hasFloat32Array ? new Float32Array(m) : m;
  };
  mGL.matrixMode(mGL.MODEL);
}

 return self;           
}


/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Plane
 * @class Plane 
 * @constructor  
 */
EJSS_DRAWING3D.Plane = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  	
		dest.setDirectionA(source.getDirectionA());
		dest.setDirectionB(source.getDirectionB());
		dest.setSizeA(source.getSizeA());
		dest.setSizeB(source.getSizeB());
  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("DirectionA", element.setDirectionA);
		controller.registerProperty("DirectionB", element.setDirectionB);

		controller.registerProperty("SizeA", element.setSizeA);
		controller.registerProperty("SizeB", element.setSizeB);
		
	}

};

/**
 * Creates a 3D Plane
 * @method plane
 */
EJSS_DRAWING3D.plane = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  // Implementation variables
  var mDirectionA = [1,0,0];
  var mDirectionB = [0,1,0];
  var mSizeA = 1;
  var mSizeB = 1;

  self.getClass = function() {
  	return "ElementPlane";
  }

  self.setDirectionA = function(direction) {
    if (!EJSS_TOOLS.compareArrays(mDirectionA,direction)) {
        mDirectionA = direction.slice(); 
  	    self.setMeshChanged(true);      
    }  	
  }

  self.getDirectionA = function() {
  	return mDirectionA;
  }

  self.setDirectionB = function(direction) {
    if (!EJSS_TOOLS.compareArrays(mDirectionB,direction)) {
        mDirectionB = direction.slice(); 
  	    self.setMeshChanged(true);      
    }  	
  }

  self.getDirectionB = function() {
  	return mDirectionB;
  }

  self.setSizeA = function(size) {
  	if(mSizeA != size) {
  		mSizeA = size;
  		self.setMeshChanged(true);
  	}
  }

  self.getSizeA = function() {
  	return mSizeA;
  }

  self.setSizeB = function(size) {
  	if(mSizeB != size) {
  		mSizeB = size;
  		self.setMeshChanged(true);
  	}
  }

  self.getSizeB = function() {
  	return mSizeB;
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Plane.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Plane.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//PlaneSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * PlaneSet
 * @class PlaneSet 
 * @constructor  
 */
EJSS_DRAWING3D.PlaneSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller);
       
      controller.registerProperty("DirectionA", 
          function(v) { set.setToEach(function(element,value) { element.setDirectionA(value); }, v); }
      );    
      controller.registerProperty("DirectionB", 
          function(v) { set.setToEach(function(element,value) { element.setDirectionB(value); }, v); }
      );    

      controller.registerProperty("SizeA", 
          function(v) { set.setToEach(function(element,value) { element.setSizeA(value); }, v); }
      );    
      controller.registerProperty("SizeB", 
          function(v) { set.setToEach(function(element,value) { element.setSizeB(value); }, v); }
      );    
    }

};


/**
 * Creates a set of planes
 * @method planeSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.planeSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.plane,mName);

  // Static references
  var PlaneSet = EJSS_DRAWING3D.PlaneSet;		// reference for PlaneSet
  
  self.registerProperties = function(controller) {
    PlaneSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Segment
 * @class Segment 
 * @constructor  
 */
EJSS_DRAWING3D.Segment = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy

        dest.setLineColor(source.getLineColor());
        dest.setLineWidth(source.getLineWidth());

  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

      	controller.registerProperty("LineColor",  element.setLineColor, element.getLineColor);
      	controller.registerProperty("LineWidth",  element.setLineWidth, element.getLineWidth);

	}

};

/**
 * Creates a 3D Segment
 * @method segment
 */
EJSS_DRAWING3D.segment = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  // Implementation variables
  var mX = 0;
  var mY = 0;
  var mZ = 0;
  var mSizeX = 1;				
  var mSizeY = 1;					
  var mSizeZ = 1;		
  var mLineWidth = 0.01;

  // Implementation variables
  self.getClass = function() {
  	return "ElementSegment";
  }

  self.setX = function(x) { 
    if (mX!=x) { 
      mX = x; 
      self.setMeshChanged(true); 
    } 
  };

  self.getX = function() { 
    return mX; 
  };

  self.setY = function(y) {  
    if (mY!=y) { 
      mY = y; 
      self.setMeshChanged(true);
    } 
  };

  self.getY = function() { 
    return mY; 
  };

  self.setZ = function(z) {  
    if (mZ!=z) { 
      mZ = z; 
      self.setMeshChanged(true);
    } 
  };

  self.getZ = function() { 
    return mZ; 
  };

  self.setPosition = function(position) {
    self.setX(position[0]);
    self.setY(position[1]);
    self.setZ(position[2]);
  };

  self.getPosition = function() { 
    return [mX, mY, mZ]; 
  };

  self.setSizeX = function(sizeX) { 
    if (mSizeX!=sizeX) { 
      mSizeX = sizeX; 
      self.setMeshChanged(true);
    } 
  };

  self.setSizeY = function(sizeY) { 
    if (mSizeY!=sizeY) { 
      mSizeY = sizeY; 
      self.setMeshChanged(true);
    }
  };

  self.setSizeZ = function(sizeZ) { 
    if (mSizeZ!=sizeZ) { 
      mSizeZ = sizeZ; 
      self.setMeshChanged(true);
    }
  };

  self.getSizeX = function() { 
    return mSizeX; 
  };

  self.getSizeY = function() { 
    return mSizeY; 
  };

  self.getSizeZ = function() { 
    return mSizeZ; 
  };

  self.setSize = function(size) {
    self.setSizeX(size[0]);
    self.setSizeY(size[1]);
    self.setSizeZ(size[2]);
  };

  self.getSize = function() {
    return [self.getSizeX(), self.getSizeY(), self.getSizeZ()];
  };

  /**
   * Set the line color
   */
  self.setLineColor = function(color) { 
  	self.getStyle().setFillColor(color);
  };
    
  /**
   * Get the line color
   */
  self.getLineColor = function() { 
    return self.getStyle().getFillColor();
  };

  /**
   * Set the line width
   */
  self.setLineWidth = function(width) { 
    if (width!=mLineWidth) {
      mLineWidth = width; 
  	  self.setMeshChanged(true);
    }
  };

  /**
   * Get the line width
   */
  self.getLineWidth = function() { 
  	return mLineWidth; 
  };


  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Segment.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Segment.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);
  self.getStyle().setDrawFill(true);
  self.getStyle().setDrawLines(false);
  self.setResolution([24,2]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//SegmentSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * SegmentSet
 * @class SegmentSet 
 * @constructor  
 */
EJSS_DRAWING3D.SegmentSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller);

      controller.registerProperty("LineWidth", 
          function(v) { set.setToEach(function(element,value) { element.setLineWidth(value); }, v); }
      );      
      controller.registerProperty("LineColor", 
          function(v) { set.setToEach(function(element,value) { element.setLineColor(value); }, v); }
      );      
       
    }

};


/**
 * Creates a set of segments
 * @method segmentSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.segmentSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.segment,mName);

  // Static references
  var SegmentSet = EJSS_DRAWING3D.SegmentSet;		// reference for SegmentSet
  
  self.registerProperties = function(controller) {
    SegmentSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/***
 * Sphere
 * @class EJSS_DRAWING3D.Sphere 
 * @constructor  
 */
EJSS_DRAWING3D.Sphere = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  		EJSS_DRAWING3D.Ellipsoid.copyTo(source,dest);
  	
		dest.setRadius(source.getRadius());
  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Ellipsoid.registerProperties(element, controller);
		// super class

	 /*** 
	  * Radius
	  * @property Radius 
	  * @type number
	  * @default 0.5
	  */ 
      controller.registerProperty("Radius", element.setRadius);
		
	}

};

/**
 * Creates a 3D Sphere
 * @method sphere
 */
EJSS_DRAWING3D.sphere = function (name) {
  var self = EJSS_DRAWING3D.ellipsoid(name);
  var mRadius = 0.5;
  
  // Implementation variables
  self.getClass = function() {
  	return "ElementSphere";
  }

  self.setRadius = function(radius) {
  	mRadius = radius;
  	self.setSize([2*radius,2*radius,2*radius]);
  }

  self.getRadius = function() {
  	return mRadius;
  }
    
  self.copyTo = function(element) {
	EJSS_DRAWING3D.Sphere.copyTo(self,element);
  };
      
  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Sphere.registerProperties(self, controller);
  };
	    
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);
  self.setResolution([30,20]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//SphereSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * SphereSet
 * @class SphereSet 
 * @constructor  
 */
EJSS_DRAWING3D.SphereSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller); 

      controller.registerProperty("Radius", 
          function(v) { set.setToEach(function(element,value) { element.setRadius(value); }, v); }
      );    

    }

};


/**
 * Creates a set of spheres
 * @method sphereSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.sphereSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.sphere,mName);

  // Static references
  var SphereSet = EJSS_DRAWING3D.SphereSet;		// reference for SphereSet
  
  self.registerProperties = function(controller) {
    SphereSet.registerProperties(self,controller);
  };

  return self;
};
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Spring
 * @class Spring 
 * @constructor  
 */
EJSS_DRAWING3D.Spring = {
  DEF_RADIUS : 1,
  DEF_LOOPS : 8,
  DEF_PPL : 50,

	// ----------------------------------------------------
	// Static methods
	// ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  	
		dest.setRadius(source.getRadius());
		dest.setSolenoid(source.getSolenoid());
		dest.setThinExtremes(source.getThinExtremes());
		dest.setLoops(source.getLoops());
		dest.setPointsPerLoop(source.getPointsPerLoop());		
  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class	
		controller.registerProperty("Radius", element.setRadius, element.getRadius);
		controller.registerProperty("ThinExtremes", element.setThinExtremes, element.getThinExtremes);

		controller.registerProperty("Solenoid", element.setSolenoid, element.getSolenoid);
		controller.registerProperty("Loops", element.setLoops, element.getLoops);
		controller.registerProperty("PointsPerLoop", element.setPointsPerLoop, element.getPointsPerLoop);
		
	},
};

/**
 * Creates a 2D Spring
 * @method spring
 */
EJSS_DRAWING3D.spring = function(name) {
	var self = EJSS_DRAWING3D.element(name);
 
	  // Configuration variables
	var mRadius = EJSS_DRAWING3D.Spring.DEF_RADIUS;
	var mSolenoid = 0.0;
	var mThinExtremes = true;
	var mLoops;
	var mPointsPerLoop;
  var mX = 0;
  var mY = 0;
  var mZ = 0;
  var mSizeX = 1;				
  var mSizeY = 1;					
  var mSizeZ = 1;	
  
	self.getClass = function() {
		return "ElementSpring";
	}

	self.setRadius = function(radius) {
		if(mRadius != radius) {
			mRadius = radius;
			self.setMeshChanged(true);
		}		
	}

	self.getRadius = function() {
		return mRadius;
	}

	self.setSolenoid = function(solenoid) {
		if(mSolenoid != solenoid) {
			mSolenoid = solenoid;
			self.setMeshChanged(true);
		}
	}

	self.getSolenoid = function() {
		return mSolenoid;
	}

	self.setThinExtremes = function(thinExtremes) {
		if(mThinExtremes != thinExtremes) {
			mThinExtremes = thinExtremes;
			self.setMeshChanged(true);
		}
	}

	self.getThinExtremes = function() {
		return mThinExtremes;
	}

	self.setLoops = function(loops) {
		if(mLoops != loops) {
			mLoops = loops;
			self.setMeshChanged(true);
		}
	}

	self.getLoops = function() {
		return mLoops;
	}

	self.setPointsPerLoop = function(pointsPerLoop) {
		if(mPointsPerLoop != pointsPerLoop) {
			mPointsPerLoop = pointsPerLoop;
			self.setMeshChanged(true);
		}
	}

	self.getPointsPerLoop = function() {
		return mPointsPerLoop;
	}

  self.setX = function(x) { 
    if (mX!=x) { 
      mX = x; 
      self.setMeshChanged(true); 
    } 
  };

  self.getX = function() { 
    return mX; 
  };

  self.setY = function(y) {  
    if (mY!=y) { 
      mY = y; 
      self.setMeshChanged(true);
    } 
  };

  self.getY = function() { 
    return mY; 
  };

  self.setZ = function(z) {  
    if (mZ!=z) { 
      mZ = z; 
      self.setMeshChanged(true);
    } 
  };

  self.getZ = function() { 
    return mZ; 
  };

  self.setPosition = function(position) {
    self.setX(position[0]);
    self.setY(position[1]);
    self.setZ(position[2]);
  };

  self.getPosition = function() { 
    return [mX, mY, mZ]; 
  };

  self.setSizeX = function(sizeX) { 
    if (mSizeX!=sizeX) { 
      mSizeX = sizeX; 
      self.setMeshChanged(true);
    } 
  };

  self.setSizeY = function(sizeY) { 
    if (mSizeY!=sizeY) { 
      mSizeY = sizeY; 
      self.setMeshChanged(true);
    }
  };

  self.setSizeZ = function(sizeZ) { 
    if (mSizeZ!=sizeZ) { 
      mSizeZ = sizeZ; 
      self.setMeshChanged(true);
    }
  };

  self.getSizeX = function() { 
    return mSizeX; 
  };

  self.getSizeY = function() { 
    return mSizeY; 
  };

  self.getSizeZ = function() { 
    return mSizeZ; 
  };

  self.setSize = function(size) {
    self.setSizeX(size[0]);
    self.setSizeY(size[1]);
    self.setSizeZ(size[2]);
  };

  self.getSize = function() {
    return [self.getSizeX(), self.getSizeY(), self.getSizeZ()];
  };

	self.registerProperties = function(controller) {
		EJSS_DRAWING3D.Spring.registerProperties(self, controller);
	};

  	self.copyTo = function(element) {
    	EJSS_DRAWING3D.Spring.copyTo(self,element);
  	};

	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

  	self.setSize([1,1,1]);
    self.getStyle().setDrawFill(true);
    self.getStyle().setDrawLines(true);
    	
  	self.setLoops(EJSS_DRAWING2D.Spring.DEF_LOOPS);
  	self.setPointsPerLoop(EJSS_DRAWING2D.Spring.DEF_PPL);
	
	return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//SpringSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * SpringSet
 * @class SpringSet 
 * @constructor  
 */
EJSS_DRAWING3D.SpringSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("Radius", 
          function(v) { set.setToEach(function(element,value) { element.setRadius(value); }, v); }
      );
      controller.registerProperty("ThinExtremes", 
          function(v) { set.setToEach(function(element,value) { element.setThinExtremes(value); }, v); }
      );
      controller.registerProperty("Solenoid", 
          function(v) { set.setToEach(function(element,value) { element.setSolenoid(value); }, v); }
      );           
      controller.registerProperty("Loops", 
          function(v) { set.setToEach(function(element,value) { element.setLoops(value); }, v); }
      );           
      controller.registerProperty("PointsPerLoop", 
          function(v) { set.setToEach(function(element,value) { element.setPointsPerLoop(value); }, v); }
      );           
    }        
};


/**
 * Creates a set of springs
 * @method springSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.springSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.spring,mName);

  // Static references
  var SpringSet = EJSS_DRAWING3D.SpringSet;		// reference for SpringSet
  
  self.registerProperties = function(controller) {
    SpringSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Surface
 * @class Surface 
 * @constructor  
 */
EJSS_DRAWING3D.Surface = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  	
		dest.setData(source.getData());
  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Data", element.setData);
		
	}

};

/**
 * Creates a 3D Surface
 * @method surface
 */
EJSS_DRAWING3D.surface = function (name) {
  var self = EJSS_DRAWING3D.element(name);

  // Implementation variables
  var mData = [];

  self.getClass = function() {
  	return "ElementSurface";
  }

  self.setData = function(data) {
    if (!EJSS_TOOLS.compareArrays(mData,data)) {
        mData = data.slice(); 
  	    self.setMeshChanged(true);      
    }  	
  }

  self.getData = function() {
  	return mData;
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Surface.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Surface.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//SurfaceSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * SurfaceSet
 * @class SurfaceSet 
 * @constructor  
 */
EJSS_DRAWING3D.SurfaceSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller);
       
      controller.registerProperty("Data", 
          function(v) { set.setToEach(function(element,value) { element.setData(value); }, v); }
      );    
    }

};


/**
 * Creates a set of surfaces
 * @method surfaceSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.surfaceSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.surface,mName);

  // Static references
  var SurfaceSet = EJSS_DRAWING3D.SurfaceSet;		// reference for SurfaceSet
  
  self.registerProperties = function(controller) {
    SurfaceSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Tetrahedron
 * @class Tetrahedron 
 * @constructor  
 */
EJSS_DRAWING3D.Tetrahedron = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  		EJSS_DRAWING3D.Cylinder.copyTo(source,dest);
  	
  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Cylinder.registerProperties(element, controller);
		// super class
		
	}
};

/**
 * Creates a 3D Tetrahedron
 * @method tetrahedron
 */
EJSS_DRAWING3D.tetrahedron = function (name) {
  var self = EJSS_DRAWING3D.cylinder(name);

  // Implementation variables
  self.getClass = function() {
  	return "ElementTetrahedron";
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Tetrahedron.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Tetrahedron.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);
  self.setTopRadius(0);
  self.setResolutionU(4);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//TetrahedronSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * TetrahedronSet
 * @class TetrahedronSet 
 * @constructor  
 */
EJSS_DRAWING3D.TetrahedronSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller); 
      
    }

};


/**
 * Creates a set of tetrahedrons
 * @method tetrahedronSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.tetrahedronSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.tetrahedron,mName);

  // Static references
  var TetrahedronSet = EJSS_DRAWING3D.TetrahedronSet;		// reference for TetrahedronSet
  
  self.registerProperties = function(controller) {
    TetrahedronSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Text
 * @class Text 
 * @constructor  
 */
EJSS_DRAWING3D.Text = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING3D.Element.copyTo(source,dest); // super class copy
  		EJSS_DRAWING2D.Font.copyTo(source.getFont(),dest.getFont());

		dest.setText(source.getText());
		dest.setBackground(source.getBackground());
  	},


	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Text", element.setText);
		controller.registerProperty("Background", element.setBackground);
		controller.registerProperty("FitText", element.setFitText);
				
		controller.registerProperty("Font", element.getFont().setFont);
		controller.registerProperty("FontFamily", element.getFont().setFontFamily);
		controller.registerProperty("FontSize", element.getFont().setFontSize);
		controller.registerProperty("FontColor", element.getFont().setFillColor);
		controller.registerProperty("FontStyle", element.getFont().setFontStyle);		
	}

};

/**
 * Creates a 3D Text
 * @method text
 */
EJSS_DRAWING3D.text = function (name) {
  var self = EJSS_DRAWING3D.element(name);
  
  // Implementation variables
  var mFont = EJSS_DRAWING2D.font(name);	// font for text

  var mText = "";
  var mBackground = "rgba(255,255,255,0)";
  var mFitText = false;

  self.getClass = function() {
  	return "ElementText";
  }

  self.setBackground = function(color) { 
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    if (color != mBackground) {
      mBackground = color; 
  	  self.setMeshChanged(true);
    }
    return self;
  };

  self.getBackground = function() {
  	return mBackground;
  }

  self.setText = function(text) {
  	if(mText != text) {
  	  mText = text;
  	  self.setMeshChanged(true);
    }
  }

  self.getText = function() {
  	return mText;
  }

  self.setFitText = function(fit) {
  	if(mFitText != fit) {
  	  mFitText = fit;
  	  self.setMeshChanged(true);
    }
  }

  self.getFitText = function() {
  	return mFitText;
  }

  self.getFont = function() {
	return mFont;
  }

  self.registerProperties = function(controller) {
	EJSS_DRAWING3D.Text.registerProperties(self, controller);
  };

  self.copyTo = function(element) {
	EJSS_DRAWING3D.Text.copyTo(self,element);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.setSize([1,1,1]);
  self.getStyle().setTransparency(255);

  return self;
};



/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//TextSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * TextSet
 * @class TextSet 
 * @constructor  
 */
EJSS_DRAWING3D.TextSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      
      ElementSet.registerProperties(set,controller);
      controller.registerProperty("Text", 
          function(v) { set.setToEach(function(element,value) { element.setText(value); }, v); }
      );
      controller.registerProperty("Background", 
          function(v) { set.setToEach(function(element,value) { element.setBackground(value); }, v); }
      );
      controller.registerProperty("Font", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFont(value); }, v); }
      );           
      controller.registerProperty("FontFamily", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFontFamily(value); }, v); }
      );           
      controller.registerProperty("FontSize", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFontSize(value); }, v); }
      );           
      controller.registerProperty("FontColor", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFillColor(value); }, v); }
      );           
      controller.registerProperty("FontStyle", 
          function(v) { set.setToEach(function(element,value) { element.getFont().setFontStyle(value); }, v); }
      );           
    }        

};


/**
 * Creates a set of texts
 * @method textSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.textSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.text,mName);

  // Static references
  var TextSet = EJSS_DRAWING3D.TextSet;		// reference for TextSet
  
  self.registerProperties = function(controller) {
    TextSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

var EJSS_DRAWING3D = EJSS_DRAWING3D || {};

/**
 * Trail
 * @class Trail 
 * @constructor  
 */
EJSS_DRAWING3D.Trail = {
  NO_CONNECTION : 0, 	// The next point will not be connected to the previous one
  LINE_CONNECTION : 1,	// The next point will be connected to the previous one by a segment

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

  	/**
   	* Copies one element into another
   	*/
  	copyTo : function(source, dest) {
      	EJSS_DRAWING2D.Element.copyTo(source,dest); // super class copy  	
  		
		dest.setActive(source.getActive());
		dest.setNoRepeat(source.getNoRepeat());
		dest.setClearAtInput(source.getClearAtInput());
		dest.setSkip(source.getSkip());
		dest.setInputLabels(source.getInputLabels());
		dest.setMaximumPoints(source.getMaximumPoints());
		dest.setConnectionType(source.getConnectionType());
		dest.setPoints(source.getPoints());

		dest.setLineColor(source.getLineColor());
		dest.setLineWidth(source.getLineWidth());
  	},

	/**
	 * static registerProperties method
	 */
	registerProperties : function(element, controller) {
		EJSS_DRAWING3D.Element.registerProperties(element, controller);
		// super class

		controller.registerProperty("Active", element.setActive, element.getActive);
		controller.registerProperty("NoRepeat", element.setNoRepeat, element.getNoRepeat);
		controller.registerProperty("ClearAtInput", element.setClearAtInput, element.getClearAtInput);
		controller.registerProperty("Skip", element.setSkip, element.getSkip);
		controller.registerProperty("ColumnNames", element.setInputLabels, element.getInputLabels);
		controller.registerProperty("Maximum", element.setMaximumPoints, element.getMaximumPoints);
		controller.registerProperty("ConnectionType", element.setConnectionType, element.getConnectionType);		
		controller.registerProperty("Connected", element.setConnected, element.isConnected);		
		controller.registerProperty("Points", element.setPoints, element.getPoints);		
		controller.registerProperty("LastPoint", element.addPoint, element.getLastPoint);
		controller.registerProperty("Input", element.addPoints);
		controller.registerProperty("InputX", element.addXPoints);				
		controller.registerProperty("InputY", element.addYPoints);
		controller.registerProperty("InputZ", element.addZPoints);

      	controller.registerProperty("LineColor",  element.setLineColor, element.getLineColor);
      	controller.registerProperty("LineWidth",  element.setLineWidth, element.getLineWidth);		
	}

};

/**
 * Creates a 3D Trail
 * Trail implements dataCollected(), which makes it a Collector of data 
 * @method trail
 */
EJSS_DRAWING3D.trail = function (name) {
  var self = EJSS_DRAWING3D.element(name);

    // Configuration variables
	var mActive = true;
	var mNoRepeat = false;
	var mClearAtInput = false;
	var mSkip = 0;
	var mInputLabels = ["x", "y", "z"];	  
	var mMaximum = 0;
	var mConnectionType = EJSS_DRAWING3D.Trail.LINE_CONNECTION;
	
  	// Implementation variables
  	var mCounter = 0;
  	var mIsEmpty = true;
	//  private int firstPoint = 0; // the first point of the current path 
  	var mLastPoint = 0;	
  	var mFlushPoint = 0; // This is the last point which was not added because of the skip parameter  	
  	var mCurrentList = []; 	// The current list of points  	
  	var mSegmentList = []; // The list of past Segments, if any  	
	var mTmpList = []; // The temporal list of new points
	var mTmpLastXAdded = 0;
	var mTmpLastYAdded = 0;
	var mTmpLastZAdded = 0;
	 
    var mLineWidth = 0.01;
	 
	self.getClass = function() {
		return "ElementTrail";
	}

  /**
   * Set the line color
   */
  self.setLineColor = function(color) { 
  	self.getStyle().setFillColor(color);
  };
    
  /**
   * Get the line color
   */
  self.getLineColor = function() { 
    return self.getStyle().getFillColor();
  };

  /**
   * Set the line width
   */
  self.setLineWidth = function(width) { 
    if (width!=mLineWidth) {
      mLineWidth = width; 
  	  self.setMeshChanged(true);
    }
  };

  /**
   * Get the line width
   */
  self.getLineWidth = function() { 
  	return mLineWidth; 
  };

  /**
   * Set/unset the active state of the trail. 
   * The trail does not use this state, but just keeps an internal value that
   * programs can consult in order not to add data to the trail.
   * In other words, a trail will always honor an addPoint() command.
   * But programs can consult this internal value to decide whether or not
   * to send data to a trail.
   * Default value is true.
   * @method setActive
   * @param _active
   */
  self.setActive = function(active) {
    if(mActive != active) {
    	mActive = active;
    	self.setMeshChanged(true);
    }
  }
  
  /**
   * Whether the trail is in active mode.
   * @method getActive
   * @return
   */
  self.getActive = function() { 
  	return mActive; 
  }
  
  /** 
   * Sets the no repeat state of the trail.
   * When set, a trail will ignore (x,y) points which equal the last added point.
   * @method setNoRepeat
   * @param _noRepeat
   */
  self.setNoRepeat = function (noRepeat) {
    if(mNoRepeat != noRepeat) {
    	mNoRepeat = noRepeat;
    	self.setMeshChanged(true);
    }
  }
  
  /**
   * Whether the trail is in no repeat mode. Default value is false.
   * @method getNoRepeat
   * @return
   */
  self.getNoRepeat = function() { 
  	return mNoRepeat; 
  }

  /**
   * Sets the trail to clear existing points when receiving 
   * a new point or array of points.
   * @method setClearAtInput
   * @param _clear
   */
  self.setClearAtInput = function(clear) {
    if(mClearAtInput != clear) {
    	mClearAtInput = clear;
    	self.setMeshChanged(true);
    }
  }
  
  /**
   * Whether the trail is in clear at input mode.
   * @method getClearAtInput
   * @return
   */
  self.getClearAtInput = function() { 
  	return mClearAtInput; 
  }

  /**
   * Sets the skip parameter. When the skip parameter is larger than zero,
   * the trail only considers one of every 'skip' points. That is, if skip is 3, 
   * the trail will consider only every third point sent to it. 
   * The default is zero, meaning all points must be considered.
   * @method setSkip
   * @param _skip
   */
  self.setSkip = function (skip) {
    if (mSkip != skip) {
      mSkip = skip;
      mCounter = 0;
      self.setMeshChanged(true);
    }
  }
  
  /**
   * Returns the skip parameter of the trail.
   * @method getSkip
   * @return
   */
  self.getSkip = function () { 
  	return mSkip; 
  }

  /**
   * Sets the labels of the X,Y,Z coordinates when the data is displayed in a table
   * @method setInputLabels
   * @param _label
   */
  self.setInputLabels = function (label) {
  	if((mInputLabels[0] != label[0]) || (mInputLabels[1] != label[1]) || (mInputLabels[2] != label[2])) { 
  		mInputLabels[0] = label[0]; 
  		mInputLabels[1] = label[1];
  		mInputLabels[2] = label[2]; 
  		self.setMeshChanged(true);
  	}
  }

  /**
   * Returns the labels of the X,Y,Z coordinates
   * @method getInputLabels
   * @return labels
   */
  self.getInputLabels = function () { 
  	return mInputLabels; 
  }
  
  /**
   * Sets the maximum number of points for the trail.
   * Once the maximum is reached, adding a new point will cause
   * remotion of the first one. This is useful to keep trails
   * down to a reasonable size, since very long trails can slow
   * down the rendering (in certain implementations).
   * If the value is 0 (the default) the trail grows forever
   * without discarding old points.
   * @method setMaximumPoints
   * @param maximum int
   */
  self.setMaximumPoints = function(maximum) {
  	var max = Math.max(maximum, 2);
  	if(mMaximum != max) {
    	mMaximum = max;
    	self.setMeshChanged(true);
    }
  }

  /**
   * Returns the maximum number of points allowed for the trail
   * @method getMaximumPoints
   * @return int
   */
  self.getMaximumPoints = function() {
    return mMaximum;
  }

  /**
   * Sets the type of connection for the next point.
   * @method setConnectionType
   * @param type int
   */
  self.setConnected = function (connected) {
   if (connected) self.setConnectionType(EJSS_DRAWING3D.Trail.LINE_CONNECTION);
   else self.setConnectionType(EJSS_DRAWING3D.Trail.NO_CONNECTION);
  }

  /**
   * Gets the connection type.
   * @method getConnectionType
   * @see #setConnectionType(int)
   */
  self.isConnected = function() {
    return mConnectionType===EJSS_DRAWING3D.Trail.LINE_CONNECTION;
  }

  /**
   * Sets the type of connection for the next point.
   * @method setConnectionType
   * @param type int
   */
  self.setConnectionType = function (type) {
    if (typeof type == "string") type = EJSS_DRAWING3D.Trail[type.toUpperCase()];      	
    if(mConnectionType != type) {
    	mConnectionType = type;
    	self.setMeshChanged(true);
    }
  }

  /**
   * Gets the connection type.
   * @method getConnectionType
   * @see #setConnectionType(int)
   */
  self.getConnectionType = function() {
    return mConnectionType;
  }

  /**
   * Adds a new point to the trail.
   * @method addPoint
   * @param x double The X coordinate of the point 
   * 		or point double[] The double[2] array with the coordinates of the point.
   * @param y double The Y coordinate of the point.
   * @param z double The Z coordinate of the point.
   * @param style
   */
  self.addPoint = function (x, y, z, style) {
  	if (mClearAtInput) self.initialize();
    if(x instanceof Array)    	
    	addPoint(x[0], x[1], x[2], style);
    else 
    	addPoint (x,y,z,style);
    self.setMeshChanged(true);
  }


  /**
   * Adds an array of points to the trail.
   * @method addPoints
   * @param xInput double The double[] array with the X coordinates of the points.
   * 	or  point double[][] The double[nPoints][2] array with the coordinates of the points.
   * @param yInput double The double[] array with the Y coordinates of the points.
   * @param zInput double The double[] array with the Z coordinates of the points.
   * @param style
   */
  self.addPoints  = function(x, y, z) {
  	if (mClearAtInput) self.initialize();
  	if(x[0] instanceof Array) {    
    	for (var i=0,n=x.length; i<n; i++) addPoint (x[i][0],x[i][1],x[i][2],mConnectionType);  		
  	}
    else if(x && y && z) {    	
    	var n = Math.min(x.length,y.length,z.length);
    	for (var i=0; i<n; i++) addPoint (x[i],y[i],z[i],mConnectionType);
    }
    self.setMeshChanged(true);
  }

  /**
   * Adds an array of X points to the trail.
   * @method addXPoints
   */
  self.addXPoints  = function(x) {
  	var i = 0;
  	if(x instanceof Array) {
  		// fill points with the values  		
    	for (var n=x.length; i<n; i++) {
    		if(mTmpList[i]) mTmpList[i] = [x[i],mTmpList[i][1],mTmpList[i][2],mTmpList[i][3]];
    		else mTmpList[i] = [x[i],mTmpLastYAdded,mTmpLastZAdded,mConnectionType];
    	}  		
    	mTmpLastXAdded = x[i];
  	}
    else { // only one value
    	if(mTmpList[i]) mTmpList[i] = [x,mTmpList[i][1],mTmpList[i][2],mTmpList[i][3]];
    	else mTmpList[i] = [x,mTmpLastYAdded,mTmpLastZAdded,mConnectionType];
    	i++;
    	mTmpLastXAdded = x;    	
    }

	// fill points with the last value
	while (mTmpList[i]) {
		mTmpList[i] = [mTmpLastXAdded,mTmpList[i][1],mTmpList[i][2],mTmpList[i][3]];
		i++;
	}    	

    //self.setMeshChanged(true);
  }

  /**
   * Adds an array of Y points to the trail.
   * @method addYPoints
   */
  self.addYPoints  = function(y) {
  	var i = 0;
  	if(y instanceof Array) {
  		// fill points with the values  		
    	for (var n=y.length; i<n; i++) {
    		if(mTmpList[i]) mTmpList[i] = [mTmpList[i][0],y[i],mTmpList[i][2],mTmpList[i][3]];
    		else mTmpList[i] = [mTmpLastXAdded,y[i],mTmpLastZAdded,mConnectionType];
    	}  		
    	mTmpLastYAdded = y[i];
  	}
    else { // only one value
    	if(mTmpList[i]) mTmpList[i] = [mTmpList[i][0],y,mTmpList[i][2],mTmpList[i][3]];
    	else mTmpList[i] = [mTmpLastXAdded,y,mTmpLastZAdded,mConnectionType];
    	i++;
    	mTmpLastYAdded = y;    	
    }

	// fill points with the last value
	while (mTmpList[i]) {
		mTmpList[i] = [mTmpList[i][0],mTmpLastYAdded,mTmpList[i][2],mTmpList[i][3]];
		i++;
	}    	

    //self.setMeshChanged(true);
  }

  /**
   * Adds an array of Z points to the trail.
   * @method addZPoints
   */
  self.addZPoints  = function(z) {
  	var i = 0;
  	if(z instanceof Array) {
  		// fill points with the values  		
    	for (var n=z.length; i<n; i++) {
    		if(mTmpList[i]) mTmpList[i] = [mTmpList[i][0],mTmpList[i][1],z[i],mTmpList[i][3]];
    		else mTmpList[i] = [mTmpLastXAdded,mTmpLastYAdded,z[i],mConnectionType];
    	}  		
    	mTmpLastZAdded = z[i];
  	}
    else { // only one value
    	if(mTmpList[i]) mTmpList[i] = [mTmpList[i][0],mTmpList[i][1],z,mTmpList[i][3]];
    	else mTmpList[i] = [mTmpLastXAdded,mTmpLastYAdded,z,mConnectionType];
    	i++;
    	mTmpLastZAdded = z;    	
    }

	// fill points with the last value
	while (mTmpList[i]) {
		mTmpList[i] = [mTmpList[i][0],mTmpList[i][1],mTmpLastZAdded,mTmpList[i][3]];
		i++;
	}    	

    // self.setMeshChanged(true);
  }

  /**
   * Adds the temporal array of point to the trail.
   * @method dataCollected
   */
  self.dataCollected  = function() {
  	if (mTmpList.length > 0) {
  	  self.addPoints(mTmpList);
      mTmpList = [];
  	  self.setMeshChanged(true);
  	}
  }   

  /**
   * Sets an array of points to the trail.
   * @method setPoints
   * @param xInput double The double[] array with the X coordinates of the points.
   * 	or  point double[][] The double[nPoints][2] array with the coordinates of the points.
   * @param yInput double The double[] array with the Y coordinates of the points.
   * @param zInput double The double[] array with the Z coordinates of the points.
   * @param style
   */
  self.setPoints  = function(x, y, z, style) {
  	self.clear();
  	self.addPoints(x,y,z,style);
  	self.setMeshChanged(true);
  }
    
  /**
   * Moves to the new point without drawing.
   * (Equivalent to setting the connection type
   * to NO_CONNECTION and adding one single point, then setting the 
   * type back to its previous value.)
   * @method moveToPoint
   * @param x double The X coordinate of the point.
   * 	or point double[] The double[2] array with the coordinates of the point.
   * @param y double The Y coordinate of the point.
   * @param z double The Z coordinate of the point.
   */
  self.moveToPoint = function(x, y, z) { 
  	if (mClearAtInput) self.initialize();
  	if(x instanceof Array) 
    	addPoint(x[0], x[1], x[2], EJSS_DRAWING3D.Trail.NO_CONNECTION);    
    else	
    	addPoint(x, y, z, EJSS_DRAWING3D.Trail.NO_CONNECTION);
    self.setMeshChanged(true);
  }

  /**
   * Returns all points.
   * @method getPoints
   * @return points
   */
  self.getPoints = function () {
    var points = [];
    var size = mSegmentList.length;          	
	for(var i=0; i<size; i++ )
		points = points.concat(mSegmentList[i]);
	points = points.concat(mCurrentList);

    return points; 
  }

  /**
   * Gets last point.
   * @method getLastPoint
   * @return point
   */
  self.getLastPoint = function () {
    return mLastPoint;
  }

  /**
   * Same as clear
   * @method reset
   */
  self.reset = function() {
    self.clear();
  }

  /**
   * Clears all points from all segments of the trail.
   * @method clear
   */
  self.clear = function() {
    mSegmentList = [];
    mCurrentSegment = 0;    
    self.initialize();
  }

  /**
   * Clears all points from the last segment of the trail, 
   * respecting previous segments.
   * @method initialize
   */
  self.initialize  = function() {
	  mCurrentList = [];	  
	  mFlushPoint = 0;
	  mIsEmpty = true;
	  mCounter = 0;
	  mLastPoint = 0;
	  self.setMeshChanged(true);
  }

  /**
   * Creates a new segment of the trail.
   * @method newSegment
   */
  self.newSegment = function() {
    if (mFlushPoint != 0) {
    	var size = mCurrentList.length;
    	mCurrentList[size] = self.formatPoint(mFlushPoint);
    }
    var size = mSegmentList.length;
    mSegmentList[size] = mCurrentList;

	self.initialize();
  }  
  
  function addPoint(_x, _y, _z, _type) {
    //if (!mActive) return;
    if (isNaN(_x) || isNaN(_y)) { 
    	mIsEmpty = true; 
    	return; 
    }
    if (mNoRepeat && mLastPoint[0]==_x && mLastPoint[1]==_y && mLastPoint[2]==_z) 
    	return;
	if ((typeof _type == "undefined") || (_type === null))
		_type = mConnectionType;

    mFlushPoint = 0;
    mLastPoint = self.formatPoint([_x,_y,_z,_type]);
    
    if (mSkip > 0) { // Only if the counter is 0      
      if(mCounter > 0) mFlushPoint = self.formatPoint([_x, _y, _z, _type]);      
      if(++mCounter >= mSkip) mCounter = 0;	        
    }
    
    if (mFlushPoint == 0) {
	    if (mMaximum > 2 && mCurrentList.length >= mMaximum) {
	    	mCurrentList.splice(0,1) // remove the firstPoint
	    }
	    
	    mCurrentList[mCurrentList.length] = self.formatPoint([_x, _y, _z, _type]);
	    mIsEmpty = false;
	}
  }

	// override for subclases (for example Trace)
   self.formatPoint = function(src) {
   		return src;   		
   }

	self.registerProperties = function(controller) {
		EJSS_DRAWING3D.Trail.registerProperties(self, controller);
	};
	 
  	self.copyTo = function(element) {
    	EJSS_DRAWING2D.Trail.copyTo(self,element);
  	};
  	 
	// ----------------------------------------------------
	// Final start-up
	// ----------------------------------------------------

	self.setSize([1,1,1]);
    self.getStyle().setDrawFill(true);
    self.getStyle().setDrawLines(false);
  	self.setResolution([24,2]);

	return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

//---------------------------------
//TrailSet
//---------------------------------

/**
 * Framework for 3D drawing.
 * @module 3Ddrawing 
 */

/**
 * TrailSet
 * @class TrailSet 
 * @constructor  
 */
EJSS_DRAWING3D.TrailSet = {

    /**
     * static registerProperties method
     */
    registerProperties : function(set,controller) {
      var ElementSet = EJSS_DRAWING3D.ElementSet;
      ElementSet.registerProperties(set,controller);
		       
      controller.registerProperty("Active", 
          function(v) { set.setToEach(function(element,value) { element.setActive(value); }, v); }
      );    
      controller.registerProperty("NoRepeat", 
          function(v) { set.setToEach(function(element,value) { element.setNoRepeat(value); }, v); }
      );    
      controller.registerProperty("ClearAtInput", 
          function(v) { set.setToEach(function(element,value) { element.setClearAtInput(value); }, v); }
      );    
      controller.registerProperty("Skip", 
          function(v) { set.setToEach(function(element,value) { element.setSkip(value); }, v); }
      );    
      controller.registerProperty("ColumnNames", 
          function(v) { set.setToEach(function(element,value) { element.setInputLabels(value); }, v); }
      );    
      controller.registerProperty("Maximum", 
          function(v) { set.setToEach(function(element,value) { element.setMaximumPoints(value); }, v); }
      );    
      controller.registerProperty("ConnectionType", 
          function(v) { set.setToEach(function(element,value) { element.setConnectionType(value); }, v); }
      );    
      controller.registerProperty("Connected", 
          function(v) { set.setToEach(function(element,value) { element.setConnected(value); }, v); }
      );    
      controller.registerProperty("Points", 
          function(v) { set.setToEach(function(element,value) { element.setPoints(value); }, v); }
      );    
      controller.registerProperty("LastPoint", 
          function(v) { set.setToEach(function(element,value) { element.addPoint(value); }, v); }
      );           
      controller.registerProperty("Input", 
          function(v) { set.setToEach(function(element,value) { element.addPoints(value); }, v); }
      );           
      controller.registerProperty("InputX", 
          function(v) { set.setToEach(function(element,value) { element.addXPoints(value); }, v); }
      );           
      controller.registerProperty("InputY", 
          function(v) { set.setToEach(function(element,value) { element.addYPoints(value); }, v); }
      );      
      controller.registerProperty("InputZ", 
          function(v) { set.setToEach(function(element,value) { element.addZPoints(value); }, v); }
      );      
      controller.registerProperty("LineWidth", 
          function(v) { set.setToEach(function(element,value) { element.setLineWidth(value); }, v); }
      );
      controller.registerProperty("LineColor", 
          function(v) { set.setToEach(function(element,value) { element.setLineColor(value); }, v); }
      );                  
    }

};


/**
 * Creates a set of trails
 * @method trailSet
 * @param mView
 * @param mName
 */
EJSS_DRAWING3D.trailSet = function (mName) {
  var self = EJSS_DRAWING3D.elementSet(EJSS_DRAWING3D.trail,mName);

  // Static references
  var TrailSet = EJSS_DRAWING3D.TrailSet;		// reference for TrailSet
  
  self.registerProperties = function(controller) {
    TrailSet.registerProperties(self,controller);
  };

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * ArrayPanel
 * @class ArrayPanel 
 * @constructor  
 */
EJSS_INTERFACE.ArrayPanel = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Editable", element.setEditable, element.getEditable);
	controller.registerProperty("Transpose", element.setTranspose, element.getTranspose);
	controller.registerProperty("DataArray", element.setDataArray, element.getDataArray);
    
    controller.registerProperty("ColumnsWidth", element.setColumnsWidth, element.getColumnsWidth);
    controller.registerProperty("HeadersText", element.setHeadersText, element.getHeadersText);
    controller.registerProperty("HeadersCSS", element.setHeadersCSS, element.getHeadersCSS);
    controller.registerProperty("CellsFormat", element.setCellsFormat, element.getCellsFormat);    
    controller.registerProperty("CellsCSS", element.setCellsCSS, element.getCellsCSS);

	controller.registerProperty("BodyHeight", element.setBodyHeight, element.getBodyHeight);
	controller.registerProperty("EditableBkgd", element.setEditableBkgd, element.getEditableBkgd);
	controller.registerProperty("NonEditableBkgd", element.setNonEditableBkgd, element.getNonEditableBkgd);

	controller.registerAction("OnChange",element.getElementInteracted); 
  },

};

/**
 * ArrayPanel function
 * ArrayPanel implements dataCollected(), which makes it a Collector of data 
 * @see _view._collectData
 * @method ArrayPanel
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.arrayPanel = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  // Configuration variables
  var mTranspose=false;
  var mColumnsWidth;
  var mHeadersText;
  var mCellsFormat;
  var mHeadersCSS;
  var mCellsCSS;
  var mValues;
  var mEditable;
  var mEditableBkgd="White";
  var mNonEditableBkgd="LightGrey";
  var mElementInteracted = { row: -1, column: -1};

  // Implementation variables
  var mMustRecreate = true;
  
  var mEdition = false;
  var mTmp = "";
  var mBackColor = "";

  self.enableEPub = function() {
  	if (self.getDOMElement().disabled) self.getDOMElement().disabled = false;
  }
   
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  /**
   * @method setTranspose
   * @param transpose
   */
  self.setTranspose = function(transpose) {
    if (mTranspose!=transpose) {
   	  mTranspose = transpose;
  	  mMustRecreate = true;
  	}
  };

  /**
   * @method getTranspose
   */
  self.getTranspose = function() { 
    return mTranspose; 
  }; 

  /**
   * @method setEditable
   * @param editable
   */
  self.setEditable = function(editable) {
   	mEditable = editable;
  };

  /**
   * @method getEditable
   * @return editable
   */
  self.getEditable = function() { 
    return mEditable; 
  }; 

  /**
   * @method setDataArray
   * @param values
   */
  self.setDataArray = function(values) {
  	if (!EJSS_TOOLS.compareArrays(mValues,values)) {
   	  mValues = values.slice();
  	  mMustRecreate = true;
  	}
  };

  /**
   * @method getDataArray
   * @return values
   */
  self.getDataArray = function() { 
    return mValues; 
  }; 
  
  /**
   * @method setColumnsWidth
   * @param names
   */
  self.setColumnsWidth = function(width) {
   	mColumnsWidth = width;
  };

  /**
   * @method getColumnsWidth
   * @return names
   */
  self.getColumnsWidth = function() { 
    return mColumnsWidth; 
  }; 
  
  /**
   * @method setHeadersText
   * @param names
   */
  self.setHeadersText = function(names) {
   	mHeadersText = names;
  };

  /**
   * @method getHeadersText
   * @return names
   */
  self.getHeadersText = function() { 
    return mHeadersText; 
  };    

  /**
   * @method setCellsFormat
   * @param format
   */
  self.setCellsFormat = function(format) {
   	mCellsFormat = format;
  };

  /**
   * @method getCellsFormat
   * @return format
   */
  self.getCellsFormat = function() { 
    return mCellsFormat; 
  };    

  /**
   * @method setHeadersCSS
   * @param css Object
   */
  self.setHeadersCSS = function(css) {
   	mHeadersCSS = css;
  };
 
 /**
   * @method getHeadersCSS
   * @return Object
   */
  self.getHeadersCSS = function() { 
    return mHeadersCSS; 
  };    
    
  /**
   * @method setCellsCSS
   * @param css Object
   */
  self.setCellsCSS = function(css) {
   	mCellsCSS = css;
  };
 
 /**
   * @method getCellsCSS
   * @return Object
   */
  self.getCellsCSS = function() { 
    return mCellsCSS; 
  };    
  
  /**
  * Sets the color for editable cells
  * @method setEditableBkgd
  * @param color The color for editable cells
  */
  self.setEditableBkgd = function(color) {
    if (mEditableBkgd!=color) {
      mEditableBkgd = color;
      mMustRecreate = true;
    }
  }

  /**
  * Gets the color for editable cells
  * @method getEditableBkgd
  * @return The color for editable cells
  */
  self.getEditableBkgd = function(color) {
    return mEditableBkgd;
  }
  
  /**
  * Sets the color for non editable cells
  * @method setNonEditableBkgd
  * @param color The color for non editable cells
  */
  self.setNonEditableBkgd = function(color) {
    if (mNonEditableBkgd!=color) {
      mNonEditableBkgd = color;
      mMustRecreate = true;
    }
  }

  /**
  * Gets the color for non editable cells
  * @method getNonEditableBkgd
  * @return The color for non editable cells
  */
  self.getNonEditableBkgd = function(color) {
    return mNonEditableBkgd;
  }

    /**
   * Set the width 
   * @method setBodyHeight
   * @param value string or int
   */
  self.setBodyHeight = function(value) { 
    if (typeof value !== "string") value = value + "px";
    tbdy.style.height=value;
  };

  /**
   * Get the width 
   * @method getBodyHeight
   * @return the height
   */
  self.getBodyHeight = function() { 
    return tbdy.style.height;
  };
  
  /**
   * Get the last element of the array interacted by the user 
   * @method getElementInteracted
   * @return An object with two properties: cell.row, cell.column
   */
  self.getElementInteracted = function() { 
    return mElementInteracted;
  };
  
  
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.ArrayPanel.registerProperties(self,controller);
  };
  
  self.enableEPub = function() {
  	if (self.getDOMElement().disabled) self.getDOMElement().disabled = false;
  };

  self.render = function() {
    if (mMustRecreate && !mEdition) {
      createTable();
      mMustRecreate = false;
    }
    if (typeof _isEPub !== 'undefined' && _isEPub)
     self.enableEPub();
  }  
  
  // ----------------------------------------
  // Operation methods
  // ----------------------------------------
        
  function createTable() {
    //console.log("Creating table");
   	tbdy.innerHTML = '';
   	thead.innerHTML = '';

	if (mHeadersText) { // column names
      var tr = document.createElement('tr');
      tr.style.border = "inherit";
      var n = mHeadersText.length;            
      for (var i=0; i<n; i++) {
        var th = document.createElement('th');
        if (mColumnsWidth && mColumnsWidth[i]) {
	      if (typeof mColumnsWidth[i] === "number") 
	      	th.style.width = mColumnsWidth[i] + "px";
	      else if (typeof mColumnsWidth[i] === "string") 
	      	th.style.width[i] = mColumnsWidth[i];
	     }
	    if (mHeadersCSS && mHeadersCSS[i]) 
	     	EJSS_INTERFACE.Style.setCSS(th,mHeadersCSS[i]);
		th.innerHTML = mHeadersText[i];		        	
      	tr.appendChild(th);
      }
      if(n == 1) th.colSpan = "100%";      
      thead.appendChild(tr);
    }

    if (mValues) { // column values
      var rows=0, cols=0;
      if (mTranspose) {
        cols = mValues.length;
	    for (var i=0; i<cols; i++) rows = Math.max(rows,mValues[i].length);
      }
      else {
        rows = mValues.length;
	    for (var i=0; i<rows; i++) cols = Math.max(cols,mValues[i].length);
      }
      if (rows<=0 || cols<=0) return;
	  for (var i=0; i<rows; i++) {
	    var tr = document.createElement('tr');
	    tr.style.border = "inherit";
	    for (var j=0; j<cols; j++) {
	      var td = document.createElement('td');
	      if (mColumnsWidth && mColumnsWidth[j]) {
		    if      (typeof mColumnsWidth[j] === "number") td.style.width = mColumnsWidth[j] + "px";
		    else if (typeof mColumnsWidth[j] === "string") td.style.width = mColumnsWidth[j];
		  }
	      var format; // (mCellsFormat && (typeof mCellsFormat[j] !== "undefined"))? mCellsFormat[j] : mCellsFormat;
	      if (mCellsFormat) {
	       if (mCellsFormat instanceof Array) format = mCellsFormat[j];
	       else format = mCellsFormat;
	      } 	
	      var editable; // = ((!mEditable) || mEditable[j]);
	      if (mEditable instanceof Array) {
	        editable = mEditable[j];
	      }
	      else if (typeof mEditable === "undefined") {
	        editable = true;
	      }
	      else editable = mEditable;
 	      if (mTranspose) {
	        if (i>=mValues[j].length) editable = false;
	        else td.innerHTML = formatValue(mValues[j][i],format);
	      }
	      else {
	        if (j>=mValues[i].length) editable = false;
	        else td.innerHTML = formatValue(mValues[i][j],format); // Not transpose
	      }
	      if (editable) {
	        td.contentEditable = true;
	       	td.onblur = self.handleOnBlur;
	       	td.onfocus = self.handleOnFocus;
	       	td.onkeydown = self.handleOnKeydown;
	        td.style.backgroundColor = mEditableBkgd;
	      }
	      else td.style.backgroundColor = mNonEditableBkgd;
	      if (mCellsCSS && mCellsCSS[j]) EJSS_INTERFACE.Style.setCSS(td,mCellsCSS[j]);
	      tr.appendChild(td);
	    }
	    tbdy.appendChild(tr);
      }
    }

  }
  
  self.handleOnBlur = function(ev) {
  	var target = ev.target || ev.srcElement;
  	if(mEdition) {
  		mEdition = false;
  		checkValue(target);
  	}
  }
  
  self.handleOnFocus = function(ev) {
  	var target = ev.target || ev.srcElement;
  	mTmp = target.innerHTML;
  	mBackColor = target.style.backgroundColor;
  }
  
  self.handleOnKeydown = function(key) {
  	var ev = key || window.event;
  	var charCode = (ev.which) ? ev.which : ev.keyCode;  
  	var target = ev.target || ev.srcElement;	  
    // console.log("Key = "+charCode);
	if (charCode==13) { // return
		mEdition = false;
		checkValue(target);  	
		mTmp = target.innerHTML;		
	} else if (charCode==27) { // escape
		target.innerHTML = mTmp;
	} else {
		target.style.backgroundColor = "yellow";
		mEdition = true;
	}
  }
  
  function checkValue(td) { 
    var rex = /(<([^>]+)>)/ig;
    var value = td.innerHTML.replace(rex , "");  // remove HTML tag
    var parsedValue = parseFloat(value);
    if (!isNaN(parsedValue) && isFinite(value)) {
      td.style.backgroundColor = mBackColor;
      if(mTmp != value) {
      	  // set value
          var cellIndex  = td.cellIndex;  
          var rowIndex = td.parentNode.rowIndex;
          if(mHeadersText) rowIndex-=1;  
          if (mTranspose)  mValues[cellIndex][rowIndex] = parsedValue;	
      	  else mValues[rowIndex][cellIndex] = parsedValue; 
      	  
		  var controller = self.getController();    		
		  if (controller) {
			controller.propertiesChanged("DataArray");
			if (mTranspose) {
			  mElementInteracted.row = cellIndex;
			  mElementInteracted.column = rowIndex;
			}
			else{
			  mElementInteracted.row = rowIndex;
			  mElementInteracted.column = cellIndex;
			}
			controller.invokeAction("OnChange");
			controller.reportInteractions();
		  }
	  }	    
    }
    else
      td.style.backgroundColor = "red";
  };
  
  function formatValue(value, format) {  	  
  	  if (typeof format != "undefined" && format.length > 0) { // format
		var index = format.indexOf('.');
  		var indexE = format.toUpperCase().indexOf('E');
		if (indexE == -1) { // decimal notation
	  	  var digits = 0;
	  	  if (index >= 0) digits = Number(format.length-index-1);
	  	  return parseFloat(value).toFixed(digits);
		} 
		else { // scientific notation
	  	  var digits = 0;
	  	  if (index >= 0) digits = Number(indexE-index-1);
		  return parseFloat(value).toExponential(digits);	
		}
	  } 
	  return value; 
	}
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement('table');
  var tbdy = document.createElement('tbody');
  var thead = document.createElement('thead');
  mElement.style.display="inline-block";
  mElement.appendChild(thead);
  mElement.appendChild(tbdy);

  mElement.id = mName;
  document.body.appendChild(mElement);  
  mElement.className = "ArrayPanel";
  self.setDOMElement(mElement);
    
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Audio
 * @class Audio 
 * @extends Media
 * @constructor  
 */
EJSS_INTERFACE.Audio = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Media.registerProperties(element,controller); // super class

    controller.registerProperty("AudioUrl",element.setUrl, element.getUrl);
  },

};

/**
 * Audio function
 * Creates a basic Audio
 * @method audio
 * @param name the name of the element
 * @returns A sound element
 */
EJSS_INTERFACE.audio = function (mName) {
  var self = EJSS_INTERFACE.media(mName);

  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Audio.registerProperties(self,controller);
  };

  function startsWith(fullStr, str) {
      return (fullStr.match("^"+str)==str);
   }

  self.setUrl = function(url) {
	  url = self.getResourcePath(url,false);
	  srcElement.src = url;
  }	

  self.getUrl = function() {
	  return srcElement.src;
  }	

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  var mElement = document.createElement("audio");  
  mElement.id = mName;
  document.body.appendChild(mElement);
  var srcElement = document.createElement("source");  
  mElement.appendChild(srcElement);
  self.setDOMElement(mElement);
  
  self.addDOMEvents();
    
  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * boxPanel
 * @class boxPanel 
 * @constructor  
 */
EJSS_INTERFACE.BoxPanel = {

  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Panel.registerProperties(element,controller); // super class

	controller.registerProperty("Draggable", element.setDraggable);	

  },

  showOkDialog: function(message, okfunction, width) {
  	var mypanel = document.getElementById(".myBoxPanelOk") || EJSS_INTERFACE.boxPanel(".myBoxPanelOk").getDOMElement();
  	var mybackpanel = document.getElementById(".myBoxPanelOk.back");
	// message
    var myMsg = document.getElementById(".myBoxPanelOk.msg");
    myMsg.style.visibility = "visible";
    myMsg.innerHTML = message + "<br/>";
    
    // ok bt
  	var myOkButton =  document.getElementById(".myBoxPanelOk.okbt");
  	myOkButton.onclick = okfunction;
  	myOkButton.style.display="inline";
  	  	
	// width message
   	mypanel.style.width = width || "200px";
   	if (mypanel.style.position == 'fixed')	// it was not draggable
		mypanel.style.marginLeft = parseFloat(mypanel.style.width)/-2 + "px";
    
    mypanel.style.visibility = "visible";
    mybackpanel.style.visibility = "visible";
  },
  
  showOkCancelDialog: function(message, okfunction, cancelfunction, width) {
  	var mypanel = document.getElementById(".myBoxPanelCancelOk") || EJSS_INTERFACE.boxPanel(".myBoxPanelCancelOk").getDOMElement();
  	var mybackpanel = document.getElementById(".myBoxPanelCancelOk.back");

	// message
    var myMsg = document.getElementById(".myBoxPanelCancelOk.msg");
    myMsg.style.visibility = "visible";
    myMsg.innerHTML = message + "<br/>";
    
    // ok bt
  	var myOkButton =  document.getElementById(".myBoxPanelCancelOk.okbt");
  	myOkButton.onclick = okfunction;
  	myOkButton.style.display="inline";
  	
  	// cancel bt  	
  	var myCancelButton =  document.getElementById(".myBoxPanelCancelOk.cancelbt");
  	myCancelButton.onclick = cancelfunction;
  	myCancelButton.style.display="inline";
  	
	// width message
   	mypanel.style.width = width || "200px";    	
   	if (mypanel.style.position == 'fixed')	// it was not draggable
		mypanel.style.marginLeft = parseFloat(mypanel.style.width)/-2 + "px";
    
    mypanel.style.visibility = "visible";
    mybackpanel.style.visibility = "visible";
  },
  
  showInputDialog: function(message, okfunction, cancelfunction, width) {
  	var mypanel = document.getElementById(".BoxPanelInput") || EJSS_INTERFACE.boxPanel(".BoxPanelInput").getDOMElement();
  	var mybackpanel = document.getElementById(".BoxPanelInput.back");

	// message
    var myMsg = document.getElementById(".BoxPanelInput.msg");
    myMsg.style.visibility = "visible";
    myMsg.innerHTML = message;

	// input
    var myIn = document.getElementById(".BoxPanelInput.input");
    myIn.style.display="inline";
    
    // ok bt
  	var myOkButton =  document.getElementById(".BoxPanelInput.okbt");
  	myOkButton.onclick = function() { okfunction(myIn.value); };
  	myOkButton.style.display="inline";
  	
  	// cancel bt  	
  	var myCancelButton =  document.getElementById(".BoxPanelInput.cancelbt");
  	myCancelButton.onclick = cancelfunction;
  	myCancelButton.style.display="inline";
  	
	// width message
   	mypanel.style.width = width || "200px";    	
   	if (mypanel.style.position == 'fixed')	// it was not draggable
		mypanel.style.marginLeft = parseFloat(mypanel.style.width)/-2 + "px";
    
    mypanel.style.visibility = "visible";
    mybackpanel.style.visibility = "visible";
  	
  },  

  showSelectDialog: function(message, options, okfunction, cancelfunction, size, width) {
  	var mypanel = document.getElementById(".BoxPanelSelect") || EJSS_INTERFACE.boxPanel(".BoxPanelSelect").getDOMElement();
  	var mybackpanel = document.getElementById(".BoxPanelSelect.back");

	// message
    var myMsg = document.getElementById(".BoxPanelSelect.msg");
    myMsg.style.visibility = "visible";
    myMsg.innerHTML = message;

	// input
    var mySelect = document.getElementById(".BoxPanelSelect.select");
	mySelect.innerHTML = "";
	for(var i=0; i<options.text.length; i++) {
		var option = document.createElement("option");
		if(option.value && option.value[i])
			option.setAttribute("value", options.value[i]);
		option.innerHTML = options.text[i];		
		mySelect.appendChild(option);      
 	}
 	if(size) mySelect.size = size;
    mySelect.style.display="block";
    mySelect.style.margin="0 auto";
    
    // ok bt
  	var myOkButton =  document.getElementById(".BoxPanelSelect.okbt");
  	myOkButton.onclick = function() { okfunction(options.value[mySelect.selectedIndex]); };
  	myOkButton.style.display="inline";
  	
  	// cancel bt  	
  	var myCancelButton =  document.getElementById(".BoxPanelSelect.cancelbt");
  	myCancelButton.onclick = cancelfunction;
  	myCancelButton.style.display="inline";
  	
	// width message
   	mypanel.style.width = width || "200px";    	
   	if (mypanel.style.position == 'fixed')	// it was not draggable
		mypanel.style.marginLeft = parseFloat(mypanel.style.width)/-2 + "px";
    
    mypanel.style.visibility = "visible";
    mybackpanel.style.visibility = "visible";
  }  

};

/**
 * boxPanel function
 * Creates a basic boxPanel
 * @method boxPanel
 * @param name the name of the element
 * @returns A basic boxPanel
 */
EJSS_INTERFACE.boxPanel = function (mName) {
  var self = EJSS_INTERFACE.panel(mName);
  
  var offset = { x: 0, y: 0 };

  self.setVisibility = function(visible) {
 	mElement.style.visibility = visible;
 	self.getStyle().setVisibility(visible);
  };
  
  self.setDraggable = function(draggable) {
  	if(draggable) {
  		self.getDOMElement().addEventListener('mousedown', mouseDown, false);
  		self.getDOMElement().addEventListener('touchstart', mouseDown, false);
  		window.addEventListener('mouseup', mouseUp, false);
  		window.addEventListener('touchend', mouseUp, false);
  	} else {
  		self.getDOMElement().removeEventListener('mousedown', mouseDown, false);
  		self.getDOMElement().removeEventListener('touchstart', mouseDown, false);
  		window.removeEventListener('mouseup', mouseUp, false);
  		window.removeEventListener('touchend', mouseUp, false);
  	}
  }

  function mouseUp() {
 	window.removeEventListener('mousemove', popupMove, true);
 	window.removeEventListener('touchmove', popupMove, true);
  }

  function mouseDown(e){
  	var box = self.getDOMElement().getBoundingClientRect();
  	offset.x = e.clientX - box.left;
  	offset.y = e.clientY - box.top;
  	window.addEventListener('mousemove', popupMove, true);
  }

  function popupMove(e){
  	self.getDOMElement().style.position = 'absolute';
  	self.getDOMElement().style.margin = '0px';
  	var top = e.clientY - offset.y;
  	var left = e.clientX - offset.x;
  	self.getDOMElement().style.top = top + 'px';
   	self.getDOMElement().style.left = left + 'px';
  }

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.BoxPanel.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  // background
  var mBackElement = document.createElement("div");  
  document.body.appendChild(mBackElement);  
  mBackElement.className = "myBoxPanelBackground";
  mBackElement.id = mName + ".back";
  mBackElement.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
  mBackElement.style.position = "fixed";
  mBackElement.style.top = "1%";
  mBackElement.style.left = "1%";
  mBackElement.style.width = "98%";
  mBackElement.style.height = "98%";  
  mBackElement.style.zIndex = "99998";
  mBackElement.style.visibility = "hidden";

  // message
  var mMsgElement = document.createElement("span");  
  mMsgElement.id = mName + ".msg";
  mMsgElement.style.visibility = "hidden";
  self.getDOMElement().appendChild(mMsgElement);  

  // input
  var mInputElement = document.createElement("input");  
  mInputElement.id = mName + ".input";
  mInputElement.type = 'text';
  mInputElement.style.display="none";
  mInputElement.size = "20";
  self.getDOMElement().appendChild(mInputElement);  

  // select
  var mSelectElement = document.createElement("select");  
  mSelectElement.id = mName + ".select";
  mSelectElement.style.display="none";
  mSelectElement.size = "4";
  self.getDOMElement().appendChild(mSelectElement);  

  // ok button
  var mOkBtElement = document.createElement("button");  
  mOkBtElement.id = mName + ".okbt";
  mOkBtElement.innerHTML = "Ok";
  mOkBtElement.style.verticalAlign = "middle";
  mOkBtElement.style.display="none";
  mOkBtElement.addEventListener('click', function(){
		document.getElementById(mName).style.visibility="hidden";
		document.getElementById(mName + ".back").style.visibility="hidden";
		document.getElementById(mName + ".msg").style.visibility="hidden";
		document.getElementById(mName + ".input").style.display="none";  		
		document.getElementById(mName + ".okbt").style.display="none";  		
		document.getElementById(mName + ".cancelbt").style.display="none";
  	});
  self.getDOMElement().appendChild(mOkBtElement);

  // cancel button
  var mCancelBtElement = document.createElement("button");  
  mCancelBtElement.id = mName + ".cancelbt";
  mCancelBtElement.innerHTML = "Cancel";
  mCancelBtElement.style.verticalAlign = "middle";
  mCancelBtElement.style.display="none";
  mCancelBtElement.addEventListener('click', function(){
		document.getElementById(mName).style.visibility="hidden";
		document.getElementById(mName + ".back").style.visibility="hidden";
		document.getElementById(mName + ".msg").style.visibility="hidden";
		document.getElementById(mName + ".input").style.display="none";
		document.getElementById(mName + ".okbt").style.display="none";  		
		document.getElementById(mName + ".cancelbt").style.display="none";
  	});
  self.getDOMElement().appendChild(mCancelBtElement);  

  // modal  
  self.getDOMElement().className = "BoxPanel";
  self.getStyle().setCSS({position:"fixed", top:"1px", left:"50%"});
  self.getStyle().setCSS({zIndex:"99999", visibility:"hidden"});
  self.getStyle().setCSS({marginLeft:"-250px", width:"500px"});
  self.getStyle().setCSS({background:"lightgray"});
  self.getStyle().setCSS({borderWidth: "1px"});
  self.getStyle().setCSS({borderStyle: "solid"});
  self.getStyle().setCSS({borderColor: "red"});  
  self.getStyle().setCSS({overflow:"hidden", wordWrap:"break-word"});
    
  self.setDraggable(true);
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Button
 * @class Button 
 * @constructor  
 */
EJSS_INTERFACE.Button = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Text", element.setText);
	controller.registerProperty("Disabled", element.setDisabled);	
	controller.registerProperty("ImageUrl",element.setImageUrl);
		
    controller.registerAction("OnClick");
    controller.registerAction("OnPress");
    controller.registerAction("OnRelease");
  },

};

/**
 * Button function
 * Creates a basic Button
 * @method button
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.button = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mUrl = "";
  var mText = "";
  	
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setDisabled = function(disabled) {
  	self.getDOMElement().disabled = disabled; 
  	self.getDOMElement().style.pointerEvents = (disabled?"none":"");
  }

  self.getDisabled = function() {
    return self.getDOMElement().disabled;
  }

  self.setText = function(text) {
    if (mText==text) return;
  	mText = text;
  	applyChange();
  	/*
  	if(mUrl.length > 0) {
  		self.getDOMElement().innerHTML = '<img style="vertical-align:inherit;" src="'+mUrl+
  			'"/><span style="white-space:pre;">'+mText.toString()+'</span>';
  	} else {
    	self.getDOMElement().innerHTML = '<span style="white-space:pre;">'+mText.toString()+'</span>';
    } 
    */
  }

  self.getText = function() {
    return mText;
  }

  self.setImageUrl = function(url) {
  	var newUrl = self.getResourcePath(url);
  	if (newUrl==mUrl) return;
  	mUrl = newUrl;
  	applyChange();
  	/*
  	if(mText.length > 0) {
  		self.getDOMElement().innerHTML = '<img style="vertical-align:inherit;" src="'+mUrl+
  			'"/><span style="white-space:pre;">'+mText.toString()+'</span>';
  	} else {
  		self.getDOMElement().innerHTML = '<img src="'+mUrl+'"/>';
  	}
  	*/ 
  }
  
  function applyChange() {
  	self.getDOMElement().innerHTML = "";
    if(mUrl.length > 0) {
        var dom_img = document.createElement("img");
        dom_img.onclick = function(e) { 
            e.preventDefault(); e.stopPropagation();
            applyChange();
            return false;
        };
        dom_img.src = mUrl;
        dom_img.style = "vertical-align:inherit;-webkit-touch-callout:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;";
        dom_img.alt = mName;
        self.getDOMElement().appendChild(dom_img);
    }
    //if(mUrl.length > 0) 
    //	self.getDOMElement().innerHTML = '<img style="vertical-align:inherit;-webkit-touch-callout:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;" src="'+mUrl+'"/>';
    if(mText.length > 0)
    	self.getDOMElement().innerHTML += '<span style="white-space:pre;-webkit-touch-callout:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;vertical-align:inherit;">'+mText.toString()+'</span>';
  }
  self.getImageUrl = function() {
    return mUrl;
  }

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Button.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("button");  
  mElement.id = mName;
  mElement.style.verticalAlign = "middle";
  mElement.style.webkitTouchCallout = "none";
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);

  self.getDOMElement().oncontextmenu = function(e) {
  	e.preventDefault(); e.stopPropagation();
    return false;  	
  }  
  
  self.getDOMElement().onmousedown = function(e) {
    var controller = self.getController();
    if (controller) controller.invokeAction("OnPress").reportInteractions();
    e.preventDefault(); e.stopPropagation();
    return false;
    };

  self.getDOMElement().addEventListener("touchstart", function(e) {
    var controller = self.getController();
    if (controller) controller.invokeAction("OnPress").reportInteractions();
    e.preventDefault(); e.stopPropagation();
    return false;
  });

  self.getDOMElement().onmouseup = function(e) {
    var controller = self.getController();
    if (controller) {
      controller.invokeAction("OnRelease");
      controller.invokeAction("OnClick").reportInteractions();
    }
    e.preventDefault(); e.stopPropagation();
    return false;
  };

  self.getDOMElement().addEventListener("touchend", function(e) {
    var controller = self.getController();
    if (controller) {
      controller.invokeAction("OnRelease");
      controller.invokeAction("OnClick").reportInteractions();
    }
    e.preventDefault(); e.stopPropagation();
    return false;
  });
    
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS Canvaswork for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Canvas
 * @class Canvas 
 * @constructor  
 */
EJSS_INTERFACE.Canvas = {
  	
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Height", element.setHeight);
	controller.registerProperty("Width", element.setWidth);
	controller.registerProperty("ImageData", element.setImageData);

	controller.registerProperty("Enabled", element.setEnabled, element.isEnabled);

	controller.registerAction("OnPress",element.getInteractionPoint);	
	controller.registerAction("OnDrag",element.getInteractionPoint);	
	controller.registerAction("OnRelease",element.getInteractionPoint);	
	controller.registerAction("OnEnter",element.getInteractionPoint);	
	controller.registerAction("OnExit",element.getInteractionPoint);	
	controller.registerAction("OnMove",element.getInteractionPoint);	
  },

};

/**
 * Canvas function
 * Creates a basic Canvas
 * @method canvas
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.canvas = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mHeight = 0;
  var mWidth = 0; 
  var mLastPoint = [0,0];
  var mImageData;
  var mDragging = false;
  var mEnabled;
  
  self.render = function() {
    if (mImageData) mContext.putImageData(mImageData, 0, 0);
  }

  /**
   * @param data an array of [r,g,b,alpha]
   */
  self.setImageData = function (data) {
    if (!mImageData) mImageData = mContext.createImageData(mWidth, mHeight);
    if (data.data) data = data.data;
    for (var i=0; i<data.length; i++) mImageData.data[i] = data[i];
  }
  
  self.clear = function() {
    mContext.clearRect(0, 0, mWidth, mHeight);
  }
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------
  
  /**
   * @method setWidth
   * @param width
   */
  self.setWidth = function(width) { 
  	var element = self.getDOMElement();

    if (mWidth != width) {
      mWidth = width;
      if((typeof width === 'string') && width.indexOf("%") != -1 && 
      		element.parentNode && element.parentNode.offsetWidth) {
      	width = element.parentNode.offsetWidth * parseFloat(width)/100
      } else if(typeof width === 'string') {
      	width = parseFloat(width);
      }

      if(typeof width === 'number'){
      	element.setAttribute('width', width);     	
      } 
      return true;
    } 
    return false;
  };

  /**
   * @method getWidth
   * @return width
   */
  self.getWidth = function() { 
  	var element = self.getDOMElement();

  	// check values %
	if((typeof mWidth === 'string') && mWidth.indexOf("%") != -1 && 
     		element.parentNode && element.parentNode.offsetWidth) {
      	var value = element.parentNode.offsetWidth * parseFloat(mWidth)/100

	  	if(typeof value === 'number'){
	  		var orig = element.getAttribute('width');
	  		if(value!=orig) element.setAttribute('width', value);     	
	  	} 
    }
  	
    return mWidth; 
  };
  
  /**
   * @method setHeight
   * @param height
   */
  self.setHeight = function(height) { 
  	var element = self.getDOMElement();

    if (mHeight != height) {
      mHeight = height;
      if((typeof height === 'string') && height.indexOf("%") != -1 && 
      		element.parentNode && element.parentNode.offsetHeight) {
      	height = element.parentNode.offsetHeight * parseFloat(height)/100
      }

   	  self.getDOMElement().setAttribute('height', height);      
      return true;
    } 
    return false;
  };
  
  /**
   * @method getHeight
   * @return height
   */
  self.getHeight = function() { 
  	var element = self.getDOMElement();
  	
  	// check values %
	if((typeof mHeight === 'string') && mHeight.indexOf("%") != -1 && 
     		element.parentNode && element.parentNode.offsetHeight) {
      	value = element.parentNode.offsetHeight * parseFloat(mHeight)/100

	  	if(typeof value === 'number'){
	  		var orig = element.getAttribute('height');
	  		if(value!=orig) element.setAttribute('height', value);     	
	  	} 
    }  
 
    return mHeight; 
  };

  self.getContext = function() {
    return mContext;
  }

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Canvas.registerProperties(self,controller);
  };
  
  // ----------------------------------------------------
  // Interaction
  // ----------------------------------------------------
  
  self.setEnabled = function(enabled) {
    mEnabled = enabled;
    var element = self.getDOMElement();
    if (mEnabled) {
      element.onmousedown = handlerOnPress; 
      element.onmouseup = handlerOnRelease; 
      element.onmouseover = handlerOnEnter; 
      element.onmouseout = handlerOnExit; 
      element.onmousemove = handlerOnMove; 
      element.addEventListener("touchstart", handlerOnPress, false);
      element.addEventListener("touchend", handlerOnRelease, false);
      element.addEventListener("touchmove", handlerOnMove, false);
    }
    else {
      element.onmousedown = undefined;
      element.onmouseup = undefined; 
      element.onmouseover = undefined; 
      element.onmouseout = undefined; 
      element.onmousemove = undefined; 
      element.removeEventListener("touchstart", handlerOnPress);
      element.removeEventListener("touchend", handlerOnRelease);
      element.removeEventListener("touchmove", handlerOnMove);
    }
  }
  
  self.isEnabled = function() {
    return mEnabled;
  }

  self.getInteractionPoint = self.getMousePosition = function() {
  	return mLastPoint;
  }  

  // ----------------------------------------------------
  // Interaction functions and handlers
  // ----------------------------------------------------
 
  function getLastEventLocation(e) {
  	var pos = EJSS_GRAPHICS.CanvasGraphics.getOffsetRect(self);
	var locations = [];

	if ( typeof e.changedTouches != "undefined") {
		for(var i=0; i<e.changedTouches.length; i++) {
			locations[i] = [];		
	    	locations[i][0] = e.changedTouches[i].pageX - window.pageXOffset - pos.left;
	    	locations[i][1] = e.changedTouches[i].pageY - window.pageYOffset - pos.top;
	   }
	} else {
		locations[0] = [];
		locations[0][0] = (e.clientX || e.x) - pos.left;
		locations[0][1] = (e.clientY || e.y) - pos.top;
	}

	return locations[0];
  }

  // OnDrag
  function handlerOnPress(e) {
	e.preventDefault();
    var controller = self.getController();    
    if (controller) {
    	mLastPoint = getLastEventLocation(e);
    	controller.invokeAction("OnPress").reportInteractions();
    }
    mDragging = true;
    return false;
  }  
  
  // OnRelease
  function handlerOnRelease(e) {
	e.preventDefault();
    var controller = self.getController();
    if(mDragging && controller) {
    	mLastPoint = getLastEventLocation(e);
    	controller.invokeAction("OnRelease").reportInteractions();
    }
    mDragging = false;
    return false;
  }  
    
  // OnEnter
  function handlerOnEnter(e) {
	e.preventDefault();
    var controller = self.getController();
    mDragging = false;    
  	if (controller) {
  		mLastPoint = getLastEventLocation(e);
  		controller.invokeAction("OnEnter").reportInteractions();
  	}
    return false;
  }  
    
  // OnExit
  function handlerOnExit(e) {
	e.preventDefault();
    var controller = self.getController();
	mDragging = false;        
  	if (controller) {
  		mLastPoint = getLastEventLocation(e);
  		controller.invokeAction("OnExit").reportInteractions();
  	}
    return false;
  }  
  
  // OnMove
  function handlerOnMove(e) {
	e.preventDefault();
    var controller = self.getController();    
  	if (controller) {  		
  		mLastPoint = getLastEventLocation(e);
  		if(mDragging)
  			controller.invokeAction("OnDrag").reportInteractions();
  		else
  			controller.invokeAction("OnMove").reportInteractions(); 
  	}
    return false;
  }  

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("canvas");  
  mElement.setAttribute('id', mName);
  mElement.style.overflow = "hidden";
  var mContext = mElement.getContext('2d');
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);
  self.setEnabled(true);
  self.setWidth(500);
  self.setHeight(500);
      
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * CheckBox
 * @class CheckBox 
 * @constructor  
 */
EJSS_INTERFACE.CheckBox = {
	OFF : false,
	ON: true,
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Text", element.setText);
	controller.registerProperty("ImageUrl",element.setImageUrl);

	controller.registerProperty("TextOn", element.setTextOn);
	controller.registerProperty("ImageOnUrl",element.setImageOnUrl);
	controller.registerProperty("TextOff", element.setTextOff);
	controller.registerProperty("ImageOffUrl",element.setImageOffUrl);

	controller.registerProperty("Checked",element.setChecked, element.getChecked);	
	controller.registerProperty("Value", element.setValue, element.getValue);

    controller.registerProperty("LabelCSS", element.setLabelCSS);
    controller.registerProperty("CheckboxCSS", element.setCheckboxCSS);

    controller.registerAction("OnChange", element.getChecked); 
    controller.registerAction("OnCheckOn");
    controller.registerAction("OnCheckOff");
  },	

};

/**
 * CheckBox function
 * Creates a basic CheckBox
 * @method checkBox
 * @param name the name of the element
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.checkBox = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
    
  var mUrl = new Image();
  var mUrlOn = new Image();
  var mUrlOff = new Image();
  var mText = "";
  var mTextOn = "";
  var mTextOff = "";
    
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setText = function(text) {
  	mText = text;
  	applyChange();
  }

  self.setImageUrl = function(url) {
  	mUrl.src = self.getResourcePath(url);
  	applyChange();
  }

  self.setTextOff = function(text) {
  	mTextOff = text;
  	applyChange();
  }

  self.getTextOff = function() {
    return mTextOff;
  }

  self.setImageOffUrl = function(url) {
  	mUrlOff.src = self.getResourcePath(url);
  	applyChange();
  }

  self.getImageUrlOff = function() {
    return mUrlOff.src;
  }

  self.setTextOn = function(text) {
  	mTextOn = text;
  	applyChange();
  }

  self.getTextOn = function() {
    return mTextOn;
  }

  self.setImageOnUrl = function(url) {
  	mUrlOn.src = self.getResourcePath(url);
  	applyChange();
  }

  self.getImageUrlOn = function() {
    return mUrlOn.src;
  }

  self.setChecked = function(state) {
  	if (mCBElement.checked != state) {
  		mCBElement.checked = state;
  		applyChange();
  	}
  }

  self.getChecked = function() {
  	return mCBElement.checked;
  }

  /**
   * @method setValue
   * @param value double
   */
  self.setValue = function(value) { 
    mCBElement.value = value; 
  };

  /**
   * @method getValue
   * @return double
   */
  self.getValue = function() { 
    return mCBElement.value; 
  };
  
  function applyChange() {
  	mLBElement.innerHTML = ""; 	
	if (mCBElement.checked) { // on			
    	if(mUrlOn.src.length > 0) 
    		mLBElement.appendChild(mUrlOn);    	
    	else if (mTextOn.length > 0)
    		mLBElement.innerHTML = mTextOn.toString();
    	else if (mUrl.src.length > 0)
			mLBElement.appendChild(mUrl);
    	else if (mText.length > 0)	
    		mLBElement.innerHTML = mText.toString();
	} else { // off
    	if(mUrlOff.src.length > 0) 
    		mLBElement.appendChild(mUrlOff);
    	else if (mTextOff.length > 0)
    		mLBElement.innerHTML = mTextOff.toString(); 					
    	else if (mUrl.src.length > 0)
			mLBElement.appendChild(mUrl);
    	else if (mText.length > 0)	
    		mLBElement.innerHTML = mText.toString();
	}
  }
   
  /**
   * @method setLabelCSS
   * @param css
   */
  self.setLabelCSS = function(css) {  	
  	EJSS_INTERFACE.Style.setCSS(mLBElement,css);
  }

  /**
   * @method setCheckboxCSS
   * @param css
   */
  self.setCheckboxCSS = function(css) {  	
  	EJSS_INTERFACE.Style.setCSS(mCBElement,css);
  }
  
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.CheckBox.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  // set
  var mElement = document.createElement("fieldset");  
  mElement.id = mName;  
  mElement.style.border = 0;
  mElement.style.display = "inline-block";  
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
  
  // checkbox
  var mCBElement = document.createElement("input");  
  mCBElement.id = mName + ".checkbox";
  mCBElement.type = "checkbox";
  mElement.appendChild(mCBElement);  

  mCBElement.onchange = function() {
  	applyChange();
    var controller = self.getController();
	if (controller) {
		controller.propertiesChanged("Checked");
    	controller.invokeAction("OnChange");
		if(mCBElement.checked)
			controller.invokeAction("OnCheckOn");
		else
			controller.invokeAction("OnCheckOff");
		controller.reportInteractions();
	}
  };
  
  mCBElement.ontouchend = function(e) {
  	e.stopPropagation();
  };

  mCBElement.onmouseup = function(e) {
  	e.stopPropagation();
  };

  // checkbox label
  var mLBElement = document.createElement("label");  
  mLBElement.id = mName + ".label";
  mLBElement.htmlFor = mName + ".checkbox";  
  mLBElement.style.display = "inline-block";
  mElement.appendChild(mLBElement);     

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * ComboBox
 * @class ComboBox 
 * @constructor  
 */
EJSS_INTERFACE.ComboBox = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Options", element.setOptions, element.getOptions);
    controller.registerProperty("SelectedOptions", element.setSelectedOptions, element.getSelectedOptions);
    controller.registerProperty("Multiple", element.setMultiple);
    controller.registerProperty("Size", element.setSize);
	
    controller.registerAction("OnChange");
    controller.registerAction("OnFocus");
  },

};

/**
 * ComboBox function
 * @method comboBox
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.comboBox = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  var mOptions = [];
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.ComboBox.registerProperties(self,controller);
  };
  
  // ----------------------------------------
  // Setters and getters
  // ----------------------------------------
  
  /**
   * @method setSize
   * @param size
   */
  self.setSize = function(size) { 
    self.getDOMElement().size = size; 
  };

  /**
   * @method getSize
   * @return size
   */
  self.getSize = function() { 
    return self.getDOMElement().size; 
  };  
  
  /**
   * @method setMultiple
   * @param multiple
   */
  self.setMultiple = function(multiple) { 
    self.getDOMElement().multiple = multiple; 
  };

  /**
   * @method getMultiple
   * @return multiple
   */
  self.getMultiple = function() { 
    return self.getDOMElement().multiple; 
  };
  
  /**
   * @method setSelectedOptions
   * @param options
   */
  self.setSelectedOptions = function(options) { 
	var dom = self.getDOMElement();
	for(var i=0; i<dom.options.length; i++) {
		dom.options[i].selected = false;
	  	for(var j=0; j<options.length; j++) { 
	    	if (dom.options[i].value == options[j]) {
	    		dom.options[i].selected = true;
	    		break;
	    	}
	  	}
	}	
  };

  /**
   * @method getSelectedOptions
   * @return options
   */
  self.getSelectedOptions = function() {
  	var mSelectedOptions = []; 
	var dom = self.getDOMElement();
	for(var j=0,i=0; i<dom.options.length; i++) {
		if(dom.options[i].selected)
			mSelectedOptions[j++] = dom.options[i].value; 
	}	  	  	
    return mSelectedOptions; 
  };

  self.getSelectedOptionsIndexes = function() {
  	var mSelectedOptionsIs = []; 
	var dom = self.getDOMElement();
	for(var j=0,i=0; i<dom.options.length; i++) {
		if(dom.options[i].selected)
			mSelectedOptionsIs[j++] = i; 
	}	  	  	
    return mSelectedOptionsIs; 
  }

  /**
   * @method setOptions
   * @param values
   */
  self.setOptions = function(values) { 
    if (EJSS_TOOLS.compareArrays(mOptions,values)) return; // Same options, no need to change them
 	mOptions = [];
  	// get selected options
  	var selected = self.getSelectedOptions();
  	// set option list
	var dom = self.getDOMElement();  
	dom.innerHTML = "";
	for(var i=0; i<values.length; i++) {
		var option = document.createElement("option");
		option.setAttribute("value", values[i]);
		option.innerHTML = values[i];		
		dom.appendChild(option);
		mOptions[i] = values[i];      
 	}
 	// set selected options
 	self.setSelectedOptions(selected);  	 
  };

  /**
   * @method getOptions
   * @return values
   */
  self.getOptions = function() {
  	var copyOptions = []; 
	var dom = self.getDOMElement();
	for(var i=0; i<dom.options.length; i++) {
	  copyOptions[i] = dom.options[i].value; 
	}	  	  	
    return copyOptions;   	     
  };

  /**
   * Set the editable property
   * @method setEditable
   * @param editable boolean
   */
  self.setEditable = function(editable) { 
    self.getDOMElement().readOnly = !editable; 
  };

  /**
   * @method getEditable
   * @return bool
   */
  self.getEditable = function() { 
    return !self.getDOMElement().readOnly; 
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  var mElement = document.createElement("select");
  mElement.id = mName;   
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
  
  self.getDOMElement().onfocus = function() {
    var controller = self.getController();
	       
    self.getStyle().setBackgroundColor("white");      
	if (controller) {
		controller.propertiesChanged("Options");
		controller.invokeAction("OnFocus");
		controller.reportInteractions();
	}  	
  }	
  
  self.getDOMElement().onchange = function() {
    var controller = self.getController();
	       
    self.getStyle().setBackgroundColor("white");      
	if (controller) {
		controller.propertiesChanged("SelectedOptions");
		controller.invokeAction("OnChange");
		controller.reportInteractions();
	}
  }

  self.getDOMElement().onkeydown = function() {
    self.getStyle().setBackgroundColor("yellow");
  }
  
  self.getDOMElement().ontouchend = function(e) {
    e.stopPropagation();
  }
  
  self.getDOMElement().onmouseup = function(e) {
    e.stopPropagation();
  }
       
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * DataTable
 * @class DataTable 
 * @constructor  
 */
EJSS_INTERFACE.DataTable = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Active", element.setActive, element.isActive);
    controller.registerProperty("NoRepeat", element.setNoRepeat, element.isNoRepeat);
    controller.registerProperty("ClearAtInput", element.setClearAtInput, element.isClearAtInput);
    controller.registerProperty("Skip", element.setSkip, element.getSkip);
	controller.registerProperty("AddToTop", element.setAddToTop, element.isAddToTop);
    controller.registerProperty("Maximum", element.setMaximumPoints, element.getMaximumPoints);
    controller.registerProperty("Input", element.appendRow);
    controller.registerProperty("ColumnsWidth", element.setColumnsWidth, element.getColumnsWidth);
    controller.registerProperty("HeadersText", element.setHeadersText, element.getHeadersText);
    controller.registerProperty("HeadersCSS", element.setHeadersCSS, element.getHeadersCSS);
    controller.registerProperty("CellsFormat", element.setCellsFormat, element.getCellsFormat);    
    controller.registerProperty("CellsCSS", element.setCellsCSS, element.getCellsCSS);

	controller.registerProperty("Width", element.setWidth, element.getStyle().getWidth);
	controller.registerProperty("BodyHeight", element.setBodyHeight, element.getBodyHeight);
  },

};

/**
 * DataTable function
 * DataTable implements dataCollected(), which makes it a Collector of data 
 * @see _view._collectData
 * @method dataTable
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.dataTable = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  // Configuration variables
  var mActive = true;
  var mAddToTop = false;
  var mNoRepeat = false;
  var mClearAtInput = false;
  var mSkip = 0;
  var mMaximumPoints = 0;

  var mColumnsWidth = [];
  var mHeadersText = [];
  var mCellsFormat = [];
  var mHeadersCSS = [];
  var mCellsCSS = [];

  // Implementation variables
  var mValues = [];
  var mLastValueAdded = [];
  var mNumRows = 0;
  var mCounter = 0;
  var mMustRecreate = true;
  var mChanged = true;
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------
  
  /**
   * Set/unset the active state of the element. 
   * Default value is true.
   * @method setActive
   * @param _active
   */
  self.setActive = function(active) {
    mActive = active;
  }
  
  /**
   * Whether the element accepts points.
   * @method isActive
   * @return
   */
  self.isActive = function() { 
  	return mActive; 
  }
  
    /**
   * Set/unset whether the table adds new points to its top 
   * Default value is true.
   * @method setAddToTop
   * @param _active
   */
  self.setAddToTop = function(toTop) {
    mAddToTop = toTop;
  }
  
  /**
   * Whether the table adds new points to its top
   * @method isAddToTop
   * @return
   */
  self.isAddToTop = function() { 
  	return mAddToTop; 
  }
  
  /**
   * @method setNoRepeat
   * @param norepeat
   */
  self.setNoRepeat = function(norepeat) {
  	mNoRepeat = norepeat;
  };

  /**
   * @method isNoRepeat
   * @return norepeat
   */
  self.isNoRepeat = function() { 
    return norepeat; 
  }; 
  
  /**
   * Sets the element to clear existing points when receiving 
   * a new point or array of points.
   * @method setClearAtInput
   * @param _clear
   */
  self.setClearAtInput = function(clear) {
     mClearAtInput = clear;
  }
  
  /**
   * Whether the trail is in clear at input mode.
   * @method isClearAtInput
   * @return
   */
  self.isClearAtInput = function() { 
  	return mClearAtInput; 
  }
  
  /**
   * @method setSkip
   * @param Skip
   */
  self.setSkip = function(skip) {
  	mSkip = skip;
  };

  /**
   * @method getSkip
   * @return stride
   */
  self.getSkip = function() { 
    return mSkip; 
  };   
 
  /**
   * @method setMaximumPoints
   * @param num
   */
  self.setMaximumPoints = function(maximum) {
  	mMaximumPoints = maximum;
  	if (mValues.length>mMaximumPoints) {
  	  if (mAddToTop) mValues = mValues.slice(0,mMaximumPoints);
  	  else mValues.splice(0,mValues.length-mMaximumPoints);
  	  mMustRecreate = true;
  	}
  };

  /**
   * @method getMaximumPoints
   * @return num
   */
  self.getMaximumPoints = function() { 
    return mMaximumPoints; 
  };    
  
  /**
   * @method setColumnsWidth
   * @param names
   */
  self.setColumnsWidth = function(width) {
    if (!EJSS_TOOLS.compareArrays(mColumnsWidth,width)) {
    	mColumnsWidth = width;
        mMustRecreate = true;
    }
  };

  /**
   * @method getHeadersText
   * @return names
   */
  self.getColumnsWidth = function() { 
    return mColumnsWidth; 
  }; 
  
  /**
   * @method setHeadersText
   * @param names
   */
  self.setHeadersText = function(names) {
    if (!EJSS_TOOLS.compareArrays(mHeadersText,names)) {
    	mHeadersText = names;
        mMustRecreate = true;
    }
  };

  /**
   * @method getHeadersText
   * @return names
   */
  self.getHeadersText = function() { 
    return mHeadersText; 
  };    

  /**
   * @method setCellsFormat
   * @param format
   */
  self.setCellsFormat = function(format) {
    if (!EJSS_TOOLS.compareArrays(mCellsFormat,format)) {
      	mCellsFormat = format;
        mChanged = true;
    }
  };

  /**
   * @method getCellsFormat
   * @return format
   */
  self.getCellsFormat = function() { 
    return mCellsFormat; 
  };    

  /**
   * @method setHeadersCSS
   * @param css Object
   */
  self.setHeadersCSS = function(css) {
    if (!EJSS_TOOLS.compareArrays(mHeadersCSS,css)) {
    	mHeadersCSS = css;
        mMustRecreate = true;
    }
  };
 
 /**
   * @method getHeadersCSS
   * @return Object
   */
  self.getHeadersCSS = function() { 
    return mHeadersCSS; 
  };    
    
  /**
   * @method setCellsCSS
   * @param css Object
   */
  self.setCellsCSS = function(css) {
    if (!EJSS_TOOLS.compareArrays(mCellsCSS,css)) {
    	mCellsCSS = css;
        mMustRecreate = true;
    }
  };
 
 /**
   * @method getCellsCSS
   * @return Object
   */
  self.getCellsCSS = function() { 
    return mCellsCSS; 
  };    
  
  /**
   * Set the width 
   * @method setBodyHeight
   * @param value string or int
   */
  self.setBodyHeight = function(value) { 
    if (typeof value !== "string") value = value + "px";
    tbdy.style.height=value;
  };

  /**
   * Get the width 
   * @method getBodyHeight
   * @return the height
   */
  self.getBodyHeight = function() { 
    return tbdy.style.height;
  };
  
  /**
   * Set the width 
   * @method setWidth
   * @param value string or int
   */
  self.setWidth = function(value) { 
    if (typeof value !== "string") value = value + "px";
    tbdy.style.width=value;
    thead.style.width=value;
    self.getStyle().setWidth(value);
  };

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.DataTable.registerProperties(self,controller);
  };
  
  // ----------------------------------------
  // Operation methods
  // ----------------------------------------
  
  /**
   * Same as clear
   * @method reset
   */
  self.reset = function() {
    self.clear();
  }
  
    /**
   * Same as clear
   * @method reset
   */
  self.initialize = function() {
    self.clear();
  }
  
  /**
   * Set the value displayed by the element
   * @method setValue
   * @param value double
   */
  self.clear = function(value) {
  	mValues  = [];
  	mCounter = 0;
    mMustRecreate = true;
  };

  self.appendRow = function(value) {
    if (!mActive) return;
    if (mClearAtInput) self.clear();
    if (value[0] instanceof Array) {
      for (var i=0,n=value.length; i<n; i++) self.addPoint(value[i]);
    }
    else self.addPoint(value);
  }

  /**
   * Set the value displayed by the element
   * @method setValue
   * @param value double
   */
  self.addPoint = function(value) {
    if (mSkip>0) {
      if (mCounter<mSkip) {
        mCounter++;
        return;
      }
      mCounter = 0; // reset the counter
    } 
    if (mNoRepeat && EJSS_TOOLS.compareArrays(value,mLastValueAdded)) return;
    mLastValueAdded = value.slice(0);
    if (mAddToTop) {
      if (mMaximumPoints>0 && mValues.length>=mMaximumPoints) mValues.pop();
      mValues.unshift(mLastValueAdded);
    }
    else {
      if (mMaximumPoints>0 && mValues.length>=mMaximumPoints) mValues.shift();
      mValues.push(mLastValueAdded);
    }
  	mChanged = true;
  };

   /**
   * Implementation of dataCollecte
   * @method dataCollected
   */
  self.dataCollected  = function() { } // Does nothing, really
  
  self.render = function() {
    if (mMustRecreate) {
      createTable();
      fillData();
    }
    else if (mChanged) {
      fillData();
    }
  }
      
  function createTable() {
    //console.log("Creating table");
   	tbdy.innerHTML = '';
   	thead.innerHTML = '';

    if (typeof mHeadersText != "undefined" && mHeadersText.length > 0) { // column names
      var tr = document.createElement('tr');
      tr.style.border = "inherit";
      for (var i=0,n=mHeadersText.length; i<n; i++) {
          var th = document.createElement('th');
          if (mColumnsWidth[i]) {
            if (typeof mColumnsWidth[i] !== "string") th.style.width = mColumnsWidth[i] + "px";
            else th.style.width = mColumnsWidth[i];
          }
          if (mHeadersCSS[i]) EJSS_INTERFACE.Style.setCSS(th,mHeadersCSS[i]);
       	  th.innerHTML = mHeadersText[i];		        	
       	  tr.appendChild(th);
      }
      thead.appendChild(tr);
    }
    mNumRows = 0;
    mMustRecreate = false;
  }
  
  function createRow(columns) {
      // console.log ("Creating row");
      var tr = document.createElement('tr');
	  tr.style.border = "inherit";
	  for (var i=0; i<columns; i++) {
		var td = document.createElement('td');
        if (mColumnsWidth[i]) {
          if (typeof mColumnsWidth[i] !== "string") td.style.width = mColumnsWidth[i] + "px";
          else td.style.width = mColumnsWidth[i];
        }
        if (mCellsCSS[i]) EJSS_INTERFACE.Style.setCSS(td,mCellsCSS[i]);
	    tr.appendChild(td);
	  }
      tbdy.appendChild(tr);
      mNumRows++;
  }

  function fillData() {
    // console.log("Filling data");
    mChanged = false;
    var hasFormats = (typeof mCellsFormat != "undefined");
    for (var i=0, n=mValues.length; i<n; i++) {
      var row = mValues[i];
      if (i>=mNumRows) createRow(row.length);  
      var tabRow = tbdy.rows[i];
      if (hasFormats) {
        for (var j=0, m=row.length; j<m; j++) {
          //console.log ("cell "+j+" = "+tabRow.cells[j]);
          tabRow.cells[j].innerHTML = formatValue(row[j],mCellsFormat[j]);
        }
      }
      else {
        for (var j=0, m=row.length; j<m; j++) tabRow.cells[j].innerHTML = formatValue(row[j]);
      }
    }
    if (mAddToTop) {
      if (mValues.length>=0) { // Select first row
        var firstOne = tbdy.rows[0];
        tbdy.scrollTop = firstOne.offsetBottom;
      }
    }
    else { 
      if (mValues.length>0) { // Select last row
        var lastOne = tbdy.rows[mValues.length-1];
        tbdy.scrollTop = lastOne.offsetTop;
      }
    }
  }

  function formatValue(value, format) {
	  if (typeof value !== 'number') return value;
  	  if (typeof format != "undefined") { // format
		var index = format.indexOf('.');
  		var indexE = format.toUpperCase().indexOf('E');
		if (indexE == -1) { // decimal notation
	  	  var digits = 0;
	  	  if (index >= 0) digits = Number(format.length-index-1);
	  	  return parseFloat(value).toFixed(digits);
		} 
		else { // scientific notation
	  	  var digits = 0;
	  	  if (index >= 0) digits = Number(indexE-index-1);
		  return parseFloat(value).toExponential(digits);	
		}
	  } 
	  return parseFloat(value).toFixed(3); 
	}
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement('table');
  var tbdy = document.createElement('tbody');
  var thead = document.createElement('thead');
  mElement.style.display="inline-block";
  mElement.appendChild(thead);
  mElement.appendChild(tbdy);
  mElement.className = "DataTable";
  
  mElement.id = mName;
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
    
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Element
 * @class Element 
 * @constructor  
 */

EJSS_INTERFACE.Element = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
      element.setController(controller);

      controller.registerProperty("Tooltip",  element.setTitle);	

      controller.registerProperty("Foreground",  element.getStyle().setColor);
      controller.registerProperty("Background",  element.getStyle().setBackgroundColor);
      controller.registerProperty("BackgroundImage",  element.getStyle().setBackgroundImage);
      controller.registerProperty("BorderWidth",  element.getStyle().setBorderWidth);
      controller.registerProperty("BorderColor",  element.getStyle().setBorderColor);
      controller.registerProperty("BorderStyle",   element.getStyle().setBorderStyle);
      controller.registerProperty("Padding",   element.getStyle().setPadding);
      
	  controller.registerProperty("Width", element.getStyle().setWidth, element.getStyle().getWidth);
	  controller.registerProperty("Height", element.getStyle().setHeight, element.getStyle().getHeight);
      controller.registerProperty("LineHeight", element.getStyle().setLineHeight, element.getStyle().getLineHeight);

	  controller.registerProperty("TextAlign", element.getStyle().setTextAlign);      
	  controller.registerProperty("VerticalAlign", element.getStyle().setVerticalAlign);      
	  controller.registerProperty("Visibility", element.getStyle().setVisibility);      
      controller.registerProperty("Display", element.getStyle().setDisplay);      
	  controller.registerProperty("Disabled", element.setDisabled);	

	  controller.registerProperty("Float", element.getStyle().setFloat);
	  controller.registerProperty("Position", element.getStyle().setPosition);
	  controller.registerProperty("Left", element.getStyle().setLeft);
	  controller.registerProperty("Top", element.getStyle().setTop);
	  controller.registerProperty("Bottom", element.getStyle().setBottom);
	  controller.registerProperty("Overflow", element.getStyle().setOverflow);
	  controller.registerProperty("BoxShadow", element.getStyle().setShadow);
	  controller.registerProperty("Margin", element.getStyle().setMargin);
	      
      controller.registerProperty("Parent", element.setParent);
      
      controller.registerProperty("ClassName", element.setClassName);
      
      controller.registerProperty("CSS", element.getStyle().setCSS);
      controller.registerProperty("Font", element.getStyle().setFont);
   
      controller.registerProperty("FontFamily", element.getStyle().setFontFamily);
      controller.registerProperty("FontSize", element.getStyle().setFontSize);
      controller.registerProperty("LetterSpacing", element.getStyle().setLetterSpacing);
      controller.registerProperty("OutlineColor", element.getStyle().setOutlineColor);
      controller.registerProperty("FontWeight", element.getStyle().setFontWeight);		
      controller.registerProperty("FillColor", element.getStyle().setFillColor);
      controller.registerProperty("FontStyle", element.getStyle().setFontStyle);
      
      controller.registerProperty("Transform", element.getStyle().setTransform);
      controller.registerProperty("WhiteSpace", element.getStyle().setWhiteSpace);
  }
};

/**
 * Creates a basic Element
 * @method element
 * @param name the name of the element
 */
EJSS_INTERFACE.element = function (mName) {
  var self = {};
  var mDOMElement;
  var mParent;
  
  // Instance variables
  var mStyle = EJSS_INTERFACE.style(mName);	// style for element 
  var mController = null; // An object that implements invokeAction and propertiesChanged
  
  /**
   * Returns the DOM object element (i.e the button, textfield, etc.)
   */
  self.getDOMElement = function() {
    return mDOMElement;
  };

  /**
   * Set the DOM object element (i.e the button, textfield, etc.)
   */
  self.setDOMElement = function(element) {
    mDOMElement = element;
  };

  /**
   * @method setParent
   * @param name
   */
  self.setParent = function(parent) {
  	mParent = parent;
  	if (typeof parent == "string") { // an HTML object
  		var domObject = document.getElementById(parent);
      	domObject.appendChild(self.getDOMElement());
    }
    else if(parent.getDOMElement) { // an interface element 
    	parent.appendChild(self);
 	} else { // an DOM element
		parent.appendChild(self.getDOMElement());
    }       
  };
  
  /**
   * @method getParent
   * @return name
   */
  self.getParent = function() {
    return mParent;       
  };  

  /**
   * @method getName
   * @return name
   */
  self.getName = function() {
    return mName;       
  };  

  /**
   * Default resource finder
   */
  self.getResourcePath = function(filename) {
  	return filename;
  };
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setClassName = function(name) {
  	self.getDOMElement().className = name; 
  };

  self.getClassName = function() {
    return self.getDOMElement().className;
  };

  self.setDisabled = function(disabled) {
  	self.getDOMElement().disabled = disabled;
  	self.getDOMElement().style.pointerEvents = (disabled?"none":"");
  };

  self.getDisabled = function() {
    return self.getDOMElement().disabled;
  };
  
  /**
   * @method setTitle
   * @param title
   */
  self.setTitle = function(title) {
    self.getDOMElement().title = title;       
  };  

  /**
   * @method getTitle
   * @return title
   */
  self.getTitle = function() {
    return self.getDOMElement().title;       
  };  

  /**
   * Return the style (defined in DrawingPanel.js) of the inner rectangle
   * @method getStyle 
   */
  self.getStyle = function() { 
    return mStyle; 
  };

  /**
   * Returns the controller object
   * @method getController
   * @return Controller
   */
  self.getController = function () {
    return mController;
  };

  /**
   * Set the controller
   * @method setController
   * @param Controller
   */
  self.setController = function (controller) {
    mController = controller;
  };
  
  /**
   * Registers properties in a ControlElement
   * @method registerProperties
   * @param controller A ControlElement that becomes the element controller
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Element.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  return self;
};

/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

/**
* EJSS framework for interface element.
* @module interface
*/

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
* FileUpload
* @class FileUpload
* @constructor
*/
EJSS_INTERFACE.FileUpload = {
 // ----------------------------------------------------
 // Static methods
 // ----------------------------------------------------

 /**
  * static registerProperties method
  */
  registerProperties : function(element,controller) {
   EJSS_INTERFACE.Button.registerProperties(element,controller); // super class

   controller.registerProperty("Accept", element.setAccept, element.getAccpet);
   controller.registerProperty("Multiple", element.setMultiple, element.getMultiple);

   controller.registerAction("OnChange");
 },

};

/**
* FileUpload function
* @method fileUpload
* @param name the name of the element
* @returns An interface element
*/
EJSS_INTERFACE.fileUpload = function (mName) {
 var self = EJSS_INTERFACE.button(mName);
 var mTarget = {};

 /**
  * @method getValue
  * @return string
  */
 self.getValue = function() {
   return mElement.value;
 };

 self.setValue = function(value) {
   mElement.value = value;
 };

 self.getTarget = function() {
     return mTarget;
 }

 // "audio/*,video/*,image/*,MIME_type"
 self.getAccept = function() {
     return self.getDOMElement().accept;
 }

 self.setAccept = function(accept) {
     self.getDOMElement().accept = accept;
 }

 self.getMultiple = function() {
     return self.getDOMElement().multiple;
 }

 self.setMultiple = function(multiple) {
     self.getDOMElement().multiple = multiple;
 }
 
 self.click = function() {
   	 mElement.click(); 	
 }

 /**
  * Extended registerProperties method. To be used by promoteToControlElement
  * @method registerProperties
  */
 self.registerProperties = function(controller) {
   EJSS_INTERFACE.FileUpload.registerProperties(self,controller);
 };

 // ----------------------------------------------------
 // Final start-up
 // ----------------------------------------------------

 var mElement = document.createElement("input");
 mElement.id = mName+".file";
 mElement.type = 'file';
 mElement.style.display = 'none';
 self.getDOMElement().parentNode.appendChild(mElement);
 
 self.getDOMElement().onmousedown = function(e) {
   mElement.click();
   e.preventDefault(); e.stopPropagation();
   return false;
 }

  self.getDOMElement().addEventListener("touchend", function(e) {
   mElement.click();
   e.stopPropagation();
   return false;   
  }, {passive: true});

  mElement.addEventListener("change", function(e) {
   mTarget = event.target;
   var controller = self.getController();
   if (controller) {
       controller.invokeAction("OnChange");
       controller.reportInteractions();
   }
  }, {passive: true});

 return self;
};/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * FunctionField
 * @class FunctionField 
 * @constructor  
 */
EJSS_INTERFACE.FunctionField = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.TextField.registerProperties(element,controller); // super class
    controller.registerAction("OnError");
  },

};

/**
 * FunctionField function
 * @method functionField
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.functionField = function (mName) {
  var self = EJSS_INTERFACE.textField(mName);
  
  var mParser = EJSS_DRAWING2D.functionsParser();
  var mExpression = mParser.parse("0");
  var mSuperSetValue = self.setValue;
  var mCurrentValue;
  var mParameters = {};
  var mBackground = self.getStyle().getBackgroundColor();
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------


  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.FunctionField.registerProperties(self,controller);
  };
  
  // ----------------------------------------
  // Setters and getters
  // ----------------------------------------
  
  	/**
	 * Sets parameters for the evaluation of the function
	 * @method setParameters
	 * @param parameters Object { "p1" : value1, "p2" : value2, ...}
	 * @return void
	 */
	self.setParameters = function(parameters) {
	  for (var param in parameters) { 
          mParameters[param] = parameters[param];
	    }
    }
    
   self.parseValue = function (value) {
	if (value) {
	  if (value!=mCurrentValue) {
       //console.log("Parsing value to "+value);
	    try {
	      mExpression = mParser.parse(value);
	      self.getStyle().setBackgroundColor(mBackground);
	    }
	    catch (error) {
	   	  self.getController().invokeAction("OnError");
	      console.log("FunctionField: Error parsing function \""+value+ "\" : " + error.message + "\n");
	      self.getStyle().setBackgroundColor("red");
	    }
	  mCurrentValue = value;
	  }
	}
	else {
	  mCurrentValue = "0";
	  mExpression = mParser.parse("0");
	  self.getStyle().setBackgroundColor(mBackground);
	}
   }
    
  /**
   * Set the value displayed by the element
   * @method setValue
   * @param value double
   */
  self.setValue = function(value) { 
    mSuperSetValue(value);
    self.parseValue(value);
  };

  /**
   * Evaluate expression
   * @return double
   */
  self.evaluate = function(variablesValues) {
    var vblevalue = {};
    for (var param in mParameters) { 
      vblevalue[param] = mParameters[param];
	}
    for (var values in variablesValues) { 
      vblevalue[values] = variablesValues[values];
	}
	try {
      return mExpression.evaluate(vblevalue);
    }
    catch (error) {
      self.getController().invokeAction("OnError");
      return NaN;
    }
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Html
 * @class Html 
 * @constructor  
 */
EJSS_INTERFACE.Html = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Html", element.setHtml);
	controller.registerProperty("Url", element.setUrl);
	controller.registerProperty("InheritCSS", element.setInheritCSS)

  },

};

/**
 * Html function
 * Creates a basic Html
 * @method html
 * @param name the name of the element
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.html = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  var inheritCSS = false;
    
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setHtml = function(text) {
    var windoc = self.getDOMElement().contentWindow;
    var domdoc = windoc.document;
    domdoc.write(text);

	// head
    if(inheritCSS) {
		if (windoc.parent) {
		  var arrStyleSheets = windoc.parent.document.querySelectorAll("link[rel=stylesheet]");
		  for (var i = 0; i < arrStyleSheets.length; i++) {
		    var newLink = document.createElement("link");
		    newLink.rel  = arrStyleSheets[i].rel;
		    newLink.href = arrStyleSheets[i].href;
		    domdoc.head.appendChild(newLink);
		  }
		}    	
    }
  };

  self.getHtml = function() {
    var domdoc = self.getDOMElement().contentWindow.document;
    return domdoc.body.innerHTML;
  };

  self.setUrl = function(url) {
    if( url.indexOf("http:") == 0 || url.indexOf("https:") == 0);
    else url = self.getResourcePath(url);
	self.getDOMElement().src = url;   	  	
  };

  self.setInheritCSS = function(inherit) {
  	interitCSS = inherit;
  };

  self.getInheritCSS = function() {
  	return interitCSS;
  };

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Html.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("iframe");    
  mElement.id = mName;
  mElement.src="about:blank"; 
  mElement.width="100%";
  
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);
  self.getStyle().setPadding("0px");
  self.getStyle().setMargin("0px");
  self.getStyle().setBorderStyle("none");
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Image
 * @class Image 
 * @constructor  
 */
EJSS_INTERFACE.Image = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("ImageUrl",element.setImageUrl);

  },

};

/**
 * Image function
 * Creates a basic Image
 * @method image
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.image = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  	
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setImageUrl = function(url) {
    var resPathFunction = self.getResourcePath; 
	if (resPathFunction!=null) {
	  url = resPathFunction(url.toString());
//		  console.log (self.getName()+ " SoundUrl set to = "+url+"\n");
	}
	else console.log ("No getResourcePath function for "+self.getName()+". URL = "+url);
    self.getDOMElement().src = url.toString(); 
  };

  self.getImageUrl = function() {
    return self.getDOMElement().src;
  };

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Image.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("img");  
  mElement.id = mName;
  mElement.alt = mName;
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);  

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * ImageAndTextButton
 * @class ImageAndTextButton 
 * @constructor  
 */
EJSS_INTERFACE.ImageAndTextButton = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Text", element.setText);
	controller.registerProperty("ImageUrl",element.setImageUrl);

    controller.registerAction("OnClick");
  },

};

/**
 * ImageAndTextButton function
 * Creates a basic ImageAndTextButton
 * @method imageAndTextButton
 * @param name the name of the element
 * @returns Image & Label
 */
EJSS_INTERFACE.imageAndTextButton = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  var mText = "";
  var mUrl = "";
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setText = function(text) {
  	mText = text.toString();
  	self.getDOMElement().innerHTML = mUrl + mText; 
  }

  self.getText = function() {
    return mText;
  }

  self.setImageUrl = function(url) {
  	mUrl = '<img alt="'+mName+'" src="'+self.getResourcePath(url)+'"/>'
  	self.getDOMElement().innerHTML = mUrl + mText;
  }

  self.getImageUrl = function() {
    return mUrl;
  }

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.ImageAndTextButton.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("button");  
  mElement.id = mName;
  mElement.style.borderStyle = "none";
  mElement.style.background = "inherit";
//  mElement.style.backgroundColor = "transparent"; 
  mElement.style.cursor = "inherit";
  mElement.style.webkitBoxShadow = "inset 0 0px 0 rgba(255,255,255,0),0 0px 0px rgba(0,0,0,0)";
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);

  self.getDOMElement().onclick = function(e) {
    var controller = self.getController();
    if (controller) controller.invokeAction("OnClick").reportInteractions();
    e.preventDefault(); e.stopPropagation();
    return false;
  };
    
  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Label
 * @class Label 
 * @constructor  
 */
EJSS_INTERFACE.Label = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Text", element.setText);

  },

};

/**
 * Label function
 * Creates a basic Label
 * @method label
 * @param name the name of the element
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.label = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
    
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setText = function(text) {
    self.getDOMElement().innerHTML = '<span style="white-space:pre;">'+text.toString()+'</span>'; 
  }

  self.getText = function() {
    return self.getDOMElement().innerHTML;
  }

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Label.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("label");  
  mElement.id = mName;
  mElement.style.display = "inline-block";
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);
    
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * MathML
 * @class MathML 
 * @constructor  
 */
EJSS_INTERFACE.MathML = {
  	
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Content",element.setContent, element.getContent);

  },

};

/**
 * MathML function
 * Creates a basic MathML
 * @method mathML
 * @param name the name of the element
 * @returns A MathML element
 */
EJSS_INTERFACE.mathML = function (mName) {
  var self = EJSS_INTERFACE.element(mName);

  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------
  
  /**
   * @method setContent
   * @param content
   */
  self.setContent = function(content) {
  	self.getDOMElement().innerHTML = content;
  };

  /**
   * @method getContent
   * @return content
   */
  self.getContent = function() { 
    return self.getDOMElement().innerHTML; 
  };

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.MathML.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("math");
  mElement.setAttribute('id', mName);
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Media
 * @class Media 
 * @constructor  
 */
EJSS_INTERFACE.Media = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Url",element.setUrl);
    controller.registerProperty("Controls",element.setControls);
    controller.registerProperty("Loop",element.setLoop);
    controller.registerProperty("CurrentTime",element.setCurrentTime, element.getCurrentTime);
    controller.registerProperty("Volume",element.setVolume);
    controller.registerProperty("Autoplay",element.setAutoplay);
    controller.registerProperty("Type",element.setType);

    controller.registerAction("OnEnded");
    controller.registerAction("OnLoadedData");
    controller.registerAction("OnPlay");
    controller.registerAction("OnPause");
//    controller.registerAction("OnTimeUpdate");
  },

};

/**
 * Media function
 * Protected constructor
 * @method media
 * @param name the name of the element
 * @returns An abstract media element
 */
EJSS_INTERFACE.media = function (mName) {
  var self = EJSS_INTERFACE.element(mName);

  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setUrl = function(url) {
    if (self.getResourcePath) url = self.getResourcePath(url);
	self.getDOMElement().src = url;
  }	

  self.getUrl = function() {
  	return self.getDOMElement().src;
  }	

  self.setType = function(type) {
	self.getDOMElement().type = type;
  }	

  self.getType = function() {
  	return self.getDOMElement().type;
  }	

  self.setControls = function(controls) {
  	if (controls)
  		self.getDOMElement().setAttribute("controls","controls");
  	else
  		self.getDOMElement().removeAttribute("controls");
  }
  
  self.play = function() {
  	self.getDOMElement().play();
  }

  self.pause = function() {
  	self.getDOMElement().pause();
  }

  self.getCurrentTime = function() {
    return self.getDOMElement().currentTime;
  } 

  self.setCurrentTime = function(time) {
    self.getDOMElement().currentTime = time;
  } 
  
  self.setLoop = function(on) {
    self.getDOMElement().loop = on;
  } 
  
  self.getVolume = function() {
  	return self.getDOMElement().volume;
  }

  self.setVolume = function(volume) {
    self.getDOMElement().volume = volume;
  } 

  self.getAutoplay = function() {
  	return self.getDOMElement().autoplay;
  }

  self.setAutoplay = function(autoplay) {
    self.getDOMElement().autoplay = autoplay;
  } 

  self.isPaused = function() {
  	return self.getDOMElement().paused; 
  }

  self.isEnded = function() {
  	return self.getDOMElement().ended; 
  }

  self.getDuration = function() {
  	return self.getDOMElement().duration; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.addDOMEvents = function() {
    self.getDOMElement().onended = function() {
      var controller = self.getController();
      if (controller) controller.invokeAction("OnEnded").reportInteractions();
    };
    self.getDOMElement().onloadeddata = function() {
      var controller = self.getController();
      if (controller) controller.invokeAction("OnLoadedData").reportInteractions();
    };
    self.getDOMElement().onplay = function() {
      var controller = self.getController();
      if (controller) controller.invokeAction("OnPlay").reportInteractions();
    };
    self.getDOMElement().onpause = function() {
      var controller = self.getController();
      if (controller) controller.invokeAction("OnPause").reportInteractions();
    };
  /* This degrades performance by a lot!
    self.getDOMElement().ontimeupdate = function() {
      var controller = self.getController();
      if (controller) {
    	controller.propertiesChanged("CurrentTime");
		controller.invokeAction("OnTimeUpdate");
		controller.reportInteractions();
      }
    };
    */
    
  }
    
  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * MotionJPEG
 * @class MotionJPEG 
 * @extends Media
 * @constructor  
 */
EJSS_INTERFACE.MotionJPEG = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Canvas.registerProperties(element,controller); // super class

    controller.registerProperty("Url",element.setUrl);
    controller.registerProperty("AsBackground",element.setAsBackground, element.isAsBackground);
    controller.registerProperty("On",element.setOnOff, element.isOnOff);
    controller.registerProperty("UseWebCam",element.useWebCam);
    controller.registerProperty("Flip",element.setFlip);
    controller.registerProperty("UseFrontCam",element.setFrontCam);
  },

};

/**
 * motionJPEG function
 * Creates a basic MotionJPEG
 * @method motionJPEG
 * @param name the name of the element
 * @returns A MotionJPEG element
 */
EJSS_INTERFACE.motionJPEG = function (mName) {
  var self = EJSS_INTERFACE.canvas(mName);
  var mImage = new Image();
  var mVideo = document.createElement("video");
  var isBackground = false;
  var mPlaying = true;
  var mStream;
  var mUrl;
  var mUseWebCam = false;
  var mFlip = false;
  var mFrontCam = true;
  
  function initCam() {  	
      navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
      if (navigator.getUserMedia){
  	    // check if previous videos running
  	    if(mStream) {
  	  	  var tracks = mStream.getTracks();
  	  	  for(var i=0; i<tracks.length; i++)
			tracks[i].stop();
  	    }
        
        function successCallback(stream){
          mStream = stream;
		  if (mPlaying) mVideo.srcObject = mStream; 	          
        	console.log("Success = " + mStream);
        }
        
        function errorCallback(error){
        	console.log("Error = "+error);
        }
        
		var videoConstraints = {
			facingMode: mFrontCam?"user":"environment"
		}
		        
        navigator.getUserMedia({video: videoConstraints}, successCallback, errorCallback);
      }	  	
  }	
  
  self.useWebCam = function(useWebCam) {
  	if(mUseWebCam != useWebCam) {
  		mUseWebCam = useWebCam;
  		if(mUseWebCam) {
  			initCam();
		} else {
			mUrl = null;
			self.pause();
		}
  	}
  }

  self.setFrontCam = function(frontCam) {
	if (mFrontCam != frontCam) {
	  	mFrontCam = frontCam;
  		if(mUseWebCam) {
	  		initCam();	
  		}
	}
  }	

  self.getFrontCam = function() {
  	return mFrontCam;
  }	
  
  self.setUrl = function(url) {
    if (self.getResourcePath) url = self.getResourcePath(url);
    mUrl = url;
    if (mUrl && mPlaying) {
    	mImage.src = mUrl;
    	mImage.crossOrigin = "Anonymous";
    }
    mUseWebCam = false; 
  }	

  self.getUrl = function() {
	if(mUseWebCam)
		return mStream;
	else
  		return mUrl;
  }	

  self.setOnOff = function(on) {
	if (on) {
	  if (!mPlaying) self.play();
	}
	else {
	  if (mPlaying) self.pause();
	}
  }

  self.isOnOff = function() {
	return mPlaying;
  }

  self.play = function() {
	if (mUrl) 
    	if(mUseWebCam)
    		mVideo.srcObject = mStream;
    	else
    		mImage.src = mUrl;	
	mPlaying = true;
  }

  self.pause = function(noclear) {
	mPlaying = false;
	if(typeof noclear == 'undefined' || !noclear) self.clear();
  }

  self.getCanvasContext = function() {
  	return self.getContext();
  }

  self.getSnapshot = function() {
  	return self.getContext().getImageData(0, 0, self.getWidth(), self.getHeight());
  }
  
  self.isAsBackground = function() {
  	return isBackground;
  }

  self.setAsBackground = function(back) {
    if (back != isBackground) {
  	  isBackground = back;
      if (isBackground) self.getStyle().setCSS({ "position":"absolute", "z-index":"-100" });
	  else self.getStyle().setCSS({ "position":" ", "z-index":" " });	  		
  	}
  } 

  self.render = function() {
  	if (mPlaying && (mUrl || mStream)) {
  		var canvas = self.getDOMElement();
		self.getContext().save();
		var flip = mFrontCam? mFlip:!mFlip; // with Back Camera the flip is inverse
   		if (flip) {
			self.getContext().translate(canvas.width,0);
			self.getContext().scale(-1,1);
		}
    	if(mUseWebCam) {
    	   if (mVideo.readyState === mVideo.HAVE_ENOUGH_DATA)
	  			self.getContext().drawImage(mVideo, 0, 0, canvas.width, canvas.height);
		} else
	  		self.getContext().drawImage(mImage,  
  					0, 0, mImage.width, mImage.height, 
               		0, 0, canvas.width, canvas.height);
		self.getContext().restore();
  	}
  }
  
  self.setFlip = function(flip) {
  	mFlip = flip;
  }
  
  self.getFlip = function() {
  	return mFlip;
  }
        
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.MotionJPEG.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  mVideo.autoplay="true";
  mVideo.style="display:none;"

  self.getStyle().setBackgroundColor('rgb(239,239,255)');
  self.setEnabled(false);
  self.setWidth(500);
  self.setHeight(500);

  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * NumberField
 * @class NumberField 
 * @constructor  
 */
EJSS_INTERFACE.NumberField = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Value", element.setValue, element.getValue);
    controller.registerProperty("Editable", element.setEditable);
    controller.registerProperty("Format", element.setFormat);
    controller.registerProperty("Size", element.setSize);
    
    controller.registerAction("OnChange"); // ,element.getValue); // Not really needed and causes problems with remote models
  },

};

/**
 * NumberField function
 * @method numberField
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.numberField = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mDigits = 3;
  var mCNotation = false;
  var mEdition = false;
  var mTmp = "";
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------


  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.NumberField.registerProperties(self,controller);
  };
  
  self.enableEPub = function() {
  	if (self.getDOMElement().disabled) self.getDOMElement().disabled = false;
  };

  if (typeof _isEPub !== 'undefined' && _isEPub) {
    self.render = function() {
      self.enableEPub();
    }
  };
  
  // ----------------------------------------
  // Setters and getters
  // ----------------------------------------
  
  /**
   * Set the value displayed by the element
   * @method setValue
   * @param value double
   */
  self.setValue = function(value) { 
  	if(!mEdition) {  	
	    if (typeof value == "undefined") 
	    	self.getDOMElement().value = "value undefined";
	    else {
			if (!isNaN(parseFloat(value)) && isFinite(value)) {
	    		if(mCNotation) {
	    			self.getDOMElement().value = parseFloat(value).toExponential(mDigits);	
	    		} else {   	
	    			self.getDOMElement().value = parseFloat(value).toFixed(mDigits);
	    		}	    	
	    	} else if (value === Number.POSITIVE_INFINITY) {
	    		self.getDOMElement().value = "∞";
	    	} else if (value === Number.NEGATIVE_INFINITY) {
	    		self.getDOMElement().value = "-∞";
			} else if (isNaN(value)) {
				self.getDOMElement().value = "∅";
			}
	    }
	}
  };

  /**
   * Get the value displayed by the element
   * @method getValue
   * @return double
   */
  self.getValue = function() {
  	var value = self.getDOMElement().value;
	if (!isNaN(parseFloat(value)) && isFinite(value)) {   	       	       
  	  	value = parseFloat(value);
    } else if (value === "∞") {
		value = Number.POSITIVE_INFINITY;
	} else if (value === "-∞") {
		value = Number.NEGATIVE_INFINITY;
	} else if (value === "∅") {
		value = Number.NaN;
  	}      	    	  	 
    return value; 
  };

  /**
   * Set the editable property
   * @method setEditable
   * @param editable boolean
   */
  self.setEditable = function(editable) { 
    self.getDOMElement().readOnly = !editable; 
    if (editable) self.getStyle().setBackgroundColor("white");
    else self.getStyle().setBackgroundColor("lightgrey");
  };

  /**
   * @method getEditable
   * @return bool
   */
  self.getEditable = function() { 
    return !self.getDOMElement().readOnly; 
  };

  /**
   * Set the size 
   * @method setSize
   * @param value string
   */
  self.setSize = function(value) { 
    self.getDOMElement().size = value; 
  };

  /**
   * Get the size
   * @method getSize
   * @return double
   */
  self.getSize = function() { 
    return self.getDOMElement().size; 
  };

  /**
   * Set the format with which to display the value
   * @method setFormat
   * @param format String
   */
  self.setFormat = function(format) {
  	var index = format.indexOf('.');
  	var indexE = format.toUpperCase().indexOf('E');
	if (indexE == -1) { // decimal notation
	  	var digits = 0;
	  	if (index >= 0) digits = Number(format.length-index-1);
	    if (mCNotation || mDigits != digits) {
	      mDigits = digits;
	      mCNotation = false;
	      self.setValue(self.getValue());
	    }
	} else { // scientific notation
	  	var digits = 0;
	  	if (index >= 0) digits = Number(indexE-index-1);
	    if (!mCNotation || mDigits != digits) {
	      mDigits = digits;
	      mCNotation = true;
	      self.setValue(self.getValue());
	    }			
	} 
  };

  function checkValue() {
    var value = self.getDOMElement().value;
    if (!isNaN(parseFloat(value)) && isFinite(value)) {
      self.getStyle().setBackgroundColor("white");
      if(mTmp != value) {
		  var controller = self.getController();    		
		  if (controller) {
			controller.propertiesChanged("Value");
			controller.invokeAction("OnChange");
			controller.reportInteractions();
		  }
	  }	    
    }
    else if (value === "∞" || value === "-∞" || value === "∅")
      self.getStyle().setBackgroundColor("white");
    else {
      self.getStyle().setBackgroundColor("red");
      return false; 
    }      
    return true;
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("input");  
  // mElement.type = 'text';
  mElement.type = 'number';	// directly numeric keyboard in tablets and mobiles  
  mElement.id = mName;
  mElement.value = 0.0;
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);

  self.getDOMElement().onkeydown = function(key) {  	
    var dom = self.getDOMElement();
  	if(!dom.readOnly) {
  	  	var ev = key || window.event;
	  	var charCode = (ev.which) ? ev.which : ev.keyCode;  	  
  	    // console.log("Key = "+charCode);
  		if (charCode==13) { // return
  			mEdition = false;
  			if (checkValue())
  			  mTmp = self.getDOMElement().value;		
  		} else if (charCode==27) { // escape
  			self.getDOMElement().value = mTmp;
  		} else {
  			self.getStyle().setBackgroundColor("yellow");
  			mEdition = true;
  		}
    }
  }

  self.getDOMElement().onfocus = function(dom) {
  	mTmp = self.getDOMElement().value;
  }

  self.getDOMElement().onblur = function(dom) {
  	if(!dom.readOnly && mEdition) {
  		mEdition = false;
  		checkValue();
  	}
  }

  self.getDOMElement().ontouchend = function(e) {
    e.stopPropagation();
  }      

  self.getDOMElement().onmouseup = function(e) {
    e.stopPropagation();
  }

  // associate event for ipad
  if (typeof _isEPub != "undefined" && _isEPub) {
	  self.getDOMElement().ontouchstart = function(event) {
  	    if (self.getDOMElement().disabled) self.getDOMElement().disabled = false;
	  	event.stopPropagation();
    };
  }

  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * panel
 * @class panel 
 * @constructor  
 */
EJSS_INTERFACE.Panel = {

  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Html", element.setHtml, element.getHtml);

    controller.registerAction("OnClick");
    controller.registerAction("OnPress");
    controller.registerAction("OnRelease");
    controller.registerAction("OnMove");
    
  },

};

/**
 * panel function
 * Creates a basic panel
 * @method panel
 * @param name the name of the element
 * @returns A basic panel
 */
EJSS_INTERFACE.panel = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
   
  /**
   * @method appendChild
   * @param name
   */
  self.appendChild = function(element) {
  	var child = element.getDOMElement();
    self.getDOMElement().appendChild(child);       
  };

  /**
   * @method removeChild
   * @param name
   */
  self.removeChild = function(element) {
  	var child = element.getDOMElement();
    self.getDOMElement().removeChild(child);       
  };

  /**
   * @method insertBefore
   * @param name
   */
  self.insertBefore = function(newele,refele) {
  	var newchild = newele.getDOMElement();
  	var refchild = refele.getDOMElement();
    self.getDOMElement().insertBefore(newchild,refchild);       
  };

  /**
   * Set the inner html
   * @method setHtml
   * @param value string
   */
  self.setHtml = function(value) { 
    self.getDOMElement().innerHTML = value; 
  };

  /**
   * Get the inner html
   * @method getHtml
   * @return string
   */
  self.getHtml = function() { 
    return self.getDOMElement().innerHTML; 
  };

 
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Panel.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("div");  
  mElement.id = mName;
  mElement.contenteditable = true;
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);  
  self.getStyle().setPadding("0px");
  self.getStyle().setMargin("0px");
  self.getStyle().setTextAlign("TEXTA_CENTER");  
  self.getStyle().setCSS({ "display": "block", "margin-left": "auto", "margin-right": "auto" });
  
  // enable inputs (iBooks)
  self.getDOMElement().addEventListener("touchstart", function(event) {
     var ips = document.getElementsByTagName("input");
     for(var i = 0; ips.length > i ; i++) {
  	 	ips[i].disabled = false;
  	 }
  });
    
  // onPress
  self.getDOMElement().addEventListener("mousedown", function(e) {
    var controller = self.getController();
    if (controller) controller.invokeAction("OnPress").reportInteractions();
  });

  self.getDOMElement().addEventListener("touchstart", function(e) {
    var controller = self.getController();
    if (controller) controller.invokeAction("OnPress").reportInteractions();
  });

  // onClick/onRelease
  self.getDOMElement().addEventListener("mouseup", function(e) {
    var controller = self.getController();
    if (controller) {
      controller.invokeAction("OnRelease");
      controller.invokeAction("OnClick").reportInteractions();
    }
  });

  self.getDOMElement().addEventListener("touchend", function(e) {
    var controller = self.getController();
    if (controller) {
      controller.invokeAction("OnRelease");
      controller.invokeAction("OnClick").reportInteractions();
    }
  });

  // onMove
  self.getDOMElement().addEventListener("mousemove", function(e) {
    var controller = self.getController();
    if (controller) {
      controller.invokeAction("OnMove").reportInteractions();
    }
  });

  self.getDOMElement().addEventListener("touchmove", function(e) {
    var controller = self.getController();
    if (controller) {
      controller.invokeAction("OnMove").reportInteractions();
    }
  });
    
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * PasswordField
 * @class PasswordField 
 * @constructor  
 */
EJSS_INTERFACE.PasswordField = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Value", element.setValue, element.getValue);
    controller.registerProperty("Editable", element.setEditable);
	controller.registerProperty("Size", element.setSize);
	
    controller.registerAction("OnChange");
  },

};

/**
 * PasswordField function
 * @method passwordField
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.passwordField = function (mName) {
  var self = EJSS_INTERFACE.element(mName);

  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------


  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.PasswordField.registerProperties(self,controller);
  };
  
  self.enableEPub = function() {
  	if (self.getDOMElement().disabled) self.getDOMElement().disabled = false;
  };

  if (typeof _isEPub !== 'undefined' && _isEPub) {
    self.render = function() {
      self.enableEPub();
    }
  };  
  
  
  // ----------------------------------------
  // Setters and getters
  // ----------------------------------------
  
  /**
   * Set the value displayed by the element
   * @method setValue
   * @param value double
   */
  self.setValue = function(value) { 
    self.getDOMElement().value = value; 
  };

  /**
   * Get the value displayed by the element
   * @method getValue
   * @return double
   */
  self.getValue = function() { 
    return self.getDOMElement().value; 
  };

  /**
   * Set the editable property
   * @method setEditable
   * @param editable boolean
   */
  self.setEditable = function(editable) { 
    self.getDOMElement().readOnly = !editable; 
    if (editable) self.getStyle().setBackgroundColor("white");
    else self.getStyle().setBackgroundColor("lightgrey");
  };

  /**
   * @method getEditable
   * @return bool
   */
  self.getEditable = function() { 
    return !self.getDOMElement().readOnly; 
  };

  /**
   * Set the size 
   * @method setSize
   * @param value string
   */
  self.setSize = function(value) { 
    self.getDOMElement().size = value; 
  };

  /**
   * Get the size
   * @method getSize
   * @return double
   */
  self.getSize = function() { 
    return self.getDOMElement().size; 
  };
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("input");  
  mElement.id = mName;
  mElement.type = 'password';
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
  
  self.getDOMElement().onkeydown = function(key) {
    var dom = self.getDOMElement();
  	if(!dom.readOnly) {
  	  var ev = key || window.event;
	  var charCode = (ev.which) ? ev.which : ev.keyCode;  	  
  	  // console.log("Key = "+charCode);
  		if (charCode == 13) {
  			self.getStyle().setBackgroundColor("white");
	    	var controller = self.getController();    		
	    	if (controller) {
	        	controller.propertiesChanged("Value");
	      		controller.invokeAction("OnChange");
	      		controller.reportInteractions();
	    	}	      			  			
  		} 
  		else 
  			self.getStyle().setBackgroundColor("yellow");
    }    
  }

  self.getDOMElement().onblur = function(dom) {
  	if(!dom.readOnly)
  		self.getStyle().setBackgroundColor("white");
  }
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * RadioButton
 * @class RadioButton 
 * @constructor  
 */
EJSS_INTERFACE.RadioButton = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Text", element.setText);
	controller.registerProperty("ImageUrl",element.setImageUrl);

	controller.registerProperty("TextOn", element.setTextOn);
	controller.registerProperty("ImageOnUrl",element.setImageOnUrl);
	controller.registerProperty("TextOff", element.setTextOff);
	controller.registerProperty("ImageOffUrl",element.setImageOffUrl);

	controller.registerProperty("Checked",element.setChecked, element.getChecked);	
	controller.registerProperty("Value", element.setValue, element.getValue);

    controller.registerProperty("LabelCSS", element.setLabelCSS);
    controller.registerProperty("RadioCSS", element.setRadioCSS);
    
	controller.registerProperty("Parent", function (parent) {
			element.setParent(parent);			
			element.setKey(element.getDOMElement().parentNode.id);
		});

    controller.registerAction("OnChange", element.getChecked); 
    controller.registerAction("OnCheckOn");
    controller.registerAction("OnCheckOff");

  },

};

/**
 * RadioButton function
 * Creates a basic radioButton
 * @method RadioButton
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.radioButton = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mUrl = new Image();
  var mUrlOn = new Image();
  var mUrlOff = new Image();
  var mText = "";
  var mTextOn = "";
  var mTextOff = "";
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setKey = function(key) {
    mRBElement.name = key;     
  }

  self.getKey = function() {
    return mRBElement.name;
  }

  self.setText = function(text) {
  	mText = text;
  	applyChange();
  }

  self.setImageUrl = function(url) {
  	mUrl.src = self.getResourcePath(url);
  	applyChange();
  }

  self.setTextOff = function(text) {
  	mTextOff = text;
  	applyChange();
  }

  self.getTextOff = function() {
    return mTextOff;
  }

  self.setImageOffUrl = function(url) {
  	mUrlOff.src = self.getResourcePath(url);
  	applyChange();
  }

  self.getImageUrlOff = function() {
    return mUrlOff.src;
  }

  self.setTextOn = function(text) {
  	mTextOn = text;
  	applyChange();
  }

  self.getTextOn = function() {
    return mTextOn;
  }

  self.setImageOnUrl = function(url) {
  	mUrlOn.src = self.getResourcePath(url);
  	applyChange();
  }

  self.getImageUrlOn = function() {
    return mUrlOn.src;
  }

  self.setChecked = function(state) {
  	if (mRBElement.checked != state) {
  		mRBElement.checked = state;
  		applyChange();
  	}
  }
  
  self.getChecked = function() {
  	return mRBElement.checked;
  }

  /**
   * @method setValue
   * @param value double
   */
  self.setValue = function(value) { 
    mRBElement.value = value; 
  };

  /**
   * @method getValue
   * @return double
   */
  self.getValue = function() { 
    return mRBElement.value; 
  };

  function applyChange() {
	mLBElement.innerHTML = "";
	if (mRBElement.checked) { // on
    	if(mUrlOn.src.length > 0) 
    		mLBElement.appendChild(mUrlOn);    	
    	else if (mTextOn.length > 0)
    		mLBElement.innerHTML = mTextOn.toString();
    	else if (mUrl.src.length > 0)
			mLBElement.appendChild(mUrl);
    	else if (mText.length > 0)	
    		mLBElement.innerHTML = mText.toString();
	} else { // off
    	if(mUrlOff.src.length > 0) 
    		mLBElement.appendChild(mUrlOff);
    	else if (mTextOff.length > 0)
    		mLBElement.innerHTML = mTextOff.toString(); 					
    	else if (mUrl.src.length > 0)
			mLBElement.appendChild(mUrl);
    	else if (mText.length > 0)	
    		mLBElement.innerHTML = mText.toString();
	}
  }

  /**
   * @method setLabelCSS
   * @param css
   */
  self.setLabelCSS = function(css) {  	
  	EJSS_INTERFACE.Style.setCSS(mLBElement,css);
  }

  /**
   * @method setRadioCSS
   * @param css
   */
  self.setRadioCSS = function(css) {  	
  	EJSS_INTERFACE.Style.setCSS(mRBElement,css);
  }
  
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.RadioButton.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  // set
  var mElement = document.createElement("fieldset");  
  mElement.id = mName;  
  mElement.style.border = 0;
  mElement.style.display = "inline-block";   
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
  
  // radiobutton
  var mRBElement = document.createElement("input");  
  mRBElement.id = mName + ".radio";
  mRBElement.type = "radio";
  mElement.appendChild(mRBElement);  

  mRBElement.onchange = function() {
  	mRBElement.remoteonchange();

	// onchange for other radiobuttons with the same name
	var eles = document.getElementsByTagName("fieldset");
	for(var i=0; i<eles.length; i++) {
		if (eles[i].childNodes && eles[i].childNodes[0]) {
			var rd = eles[i].childNodes[0];
			if ((rd.type=="radio") && rd.remoteonchange && (rd.name==mRBElement.name) && (rd.id!=mRBElement.id))
				rd.remoteonchange();
		}
	}	
  };

  mRBElement.ontouchend = function(e) {
  	e.stopPropagation();
  }
  
  mRBElement.onmouseup = function(e) {
  	e.stopPropagation();
  }

  // onchange for other radiobuttons
  mRBElement.remoteonchange = function(state) {  	
	applyChange();
    var controller = self.getController();
	if (controller) {		
		controller.propertiesChanged("Checked");
		controller.invokeAction("OnChange");
		if(mRBElement.checked)
			controller.invokeAction("OnCheckOn");
		else
			controller.invokeAction("OnCheckOff");
		controller.reportInteractions();
	}
  }      
  
  // radiobutton label
  var mLBElement = document.createElement("label");  
  mLBElement.id = mName + ".label";
  mLBElement.htmlFor = mName + ".radio";
  mLBElement.style.display = "inline-block";
  mElement.appendChild(mLBElement);     
            
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Separator
 * @class Separator 
 * @constructor  
 */
EJSS_INTERFACE.Separator = {
	HORIZONTAL: 0,
	VERTICAL: 1,
	
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Orientation", element.setOrientation);

	controller.registerProperty("Width", element.setWidth);
	
  },

};

/**
 * Separator function
 * Creates a basic Separator
 * @method separator
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.separator = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mOrientation = EJSS_INTERFACE.Separator.HORIZONTAL;
  var mWidth = "100%";
  var mHeight = "2px";
    	
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setOrientation = function(orient) {
   	if (typeof orient == "string") orient = EJSS_INTERFACE.Separator[orient.toUpperCase()];
    if(mOrientation != orient) {
	  mOrientation = orient;
	  if (mOrientation == EJSS_INTERFACE.Separator.VERTICAL) {
	  	self.getDOMElement().style.display = "inline";
  	  	self.getDOMElement().style.height = self.getDOMElement().style.width;
  	  	self.getDOMElement().style.width = mHeight;
  	  }
	  else {
	  	self.getDOMElement().style.display = "none";
	  	self.getDOMElement().style.width = self.getDOMElement().style.height;
	  	self.getDOMElement().style.height = mHeight;
	  }  	  	 
    }
  }

  self.getOrientation = function() {
    return mOrientation;
  }

  /**
   * @method setWidth
   * @param width
   */
  self.setWidth = function(width) { 
    if (mWidth!=width) { 
      mWidth = width; 
	  if (mOrientation == EJSS_INTERFACE.Separator.VERTICAL)
  	  	self.getDOMElement().style.height = mWidth;
	  else
	  	self.getDOMElement().style.width = mWidth;  	  	 
    } 
  };

  /**
   * @method getWidth
   * @return width
   */
  self.getWidth = function() { 
    return mWidth; 
  };
  
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Separator.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("hr");  
  mElement.id = mName;
  mElement.style.height = mHeight;
  mElement.style.width = mWidth;
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);
    
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Slider
 * @class Slider 
 * @constructor  
 */
EJSS_INTERFACE.Slider = {
	PRECISION : 1.0e5,
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Value", element.setValue, element.getValue);
    controller.registerProperty("Minimum", element.setMin);
    controller.registerProperty("Maximum", element.setMax);
    controller.registerProperty("Step", element.setStep);
    
    controller.registerProperty("TextWidth", element.setTextWidth);    
    controller.registerProperty("ShowText", element.setShowText);
    controller.registerProperty("Format", element.setFormat);
	controller.registerProperty("Disabled", element.setDisabled);

    controller.registerProperty("LabelCSS", element.setLabelCSS);
    controller.registerProperty("RangeCSS", element.setRangeCSS);

    controller.registerAction("OnChange" ,element.getValue); 
    controller.registerAction("OnRelease" ,element.getValue); 
    
  },
 

};

/**
 * Slider function
 * @method slider
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.slider = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
    
  var mShowText = false;
  var mDigits = 3;
  var mStepSetByUser = false;
    
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------


  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Slider.registerProperties(self,controller);
  };
  
  // ----------------------------------------
  // Setters and getters
  // ----------------------------------------
  
  /**
   * Set the value displayed by the element
   * @method setValue
   * @param value double
   */
  self.setValue = function(value) { 
    if (typeof value == "undefined") 
    	mSLElement.value = "value undefined";
    else {
    	if (!isNaN(parseFloat(value)) && isFinite(value)) {
    		mSLElement.value = parseFloat(value);
    		if (mShowText) mLBElement.innerHTML = parseFloat(value).toFixed(mDigits);
    	}
    }
  };

  /**
   * Get the value displayed by the element
   * @method getValue
   * @return double
   */
  self.getValue = function() {
  	var value = mSLElement.value;
  	if (!isNaN(parseFloat(value)) && isFinite(value)) {   	       	       
  	  return parseFloat(value);
  	}     	    	  	 
    return value; 
  };

  /**
   * Set the format with which to display the value
   * @method setFormat
   * @param format String
   */
  self.setFormat = function(format) {
  	var index = format.indexOf('.');
  	var digits = 0;
  	if (index >= 0) digits = Number(format.length-index-1);
    if (mDigits != digits) {
      mDigits = digits;
      self.setValue(self.getValue());
    }
  };

  /**
   * Set the step 
   * @method setStep
   * @param value string
   */
  self.setStep = function(value) { 
	if (isNaN(value)) {
	  mStepSetByUser = false;
	  mSLElement.step = (mSLElement.max-mSLElement.min) / EJSS_INTERFACE.Slider.PRECISION;
	}
	else {
	  mStepSetByUser = true;
      mSLElement.step = value;
	}
  };

  /**
   * Get the step
   * @method getStep
   * @return double
   */
  self.getStep = function() { 
    return mSLElement.step; 
  };

  /**
   * @method setMax
   * @param value string
   */
  self.setMax = function(value) { 
    mSLElement.max = value; 
    if (!mStepSetByUser) mSLElement.step = (mSLElement.max-mSLElement.min) / EJSS_INTERFACE.Slider.PRECISION;
  };

  /**
   * @method getMax
   * @return double
   */
  self.getMax = function() { 
    return mSLElement.max; 
  };

  /**
   * @method setMin
   * @param value string
   */
  self.setMin = function(value) { 
    mSLElement.min = value; 
    if (!mStepSetByUser) mSLElement.step = (mSLElement.max-mSLElement.min) / EJSS_INTERFACE.Slider.PRECISION;
  };

  /**
   * @method getMin
   * @return double
   */
  self.getMin = function() { 
    return mSLElement.min; 
  };

  /**
   * @method setShowText
   * @param value string
   */
  self.setShowText = function(value) {
  	mShowText = value;    
  	if (mShowText) mLBElement.innerHTML = "";  	
  };

  /**
   * @method getShowText
   * @return double
   */
  self.getShowText = function() { 
    return mShowText; 
  };

  /**
   * @method setTextWidth
   * @param value string
   */
  self.setTextWidth = function(value) {
  	mLBElement.style.width = value;    
  };

  /**
   * @method getTextWidth
   * @return value
   */
  self.getTextWidth = function(value) {
  	return mLBElement.style.width;    
  };

  /**
   * @method setDisabled
   * @param disabled
   */
  self.setDisabled = function(disabled) {
  	mSLElement.disabled = disabled; 
  }

  /**
   * @method getDisabled
   * @return value
   */
  self.getDisabled = function() {
    return mSLElement.disabled;
  }

  /**
   * @method setLabelCSS
   * @param css
   */
  self.setLabelCSS = function(css) {  	
  	EJSS_INTERFACE.Style.setCSS(mLBElement,css);
  }

  /**
   * @method setRangeCSS
   * @param css
   */
  self.setRangeCSS = function(css) {  	
  	EJSS_INTERFACE.Style.setCSS(mSLElement,css);
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("div");  
  mElement.id = mName;
  mElement.style.display = "inline-block";
  mElement.style.overflow = "hidden"; /* Makes #outer contain its floated children */
  mElement.style.textAlign = "left";
  mElement.style.verticalAlign = "middle";
  mElement.style.webkitTouchCallout = "none";
  mElement.style.webkitUserSelect = "none";
  mElement.style.userSelect = "none";  
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
  
  var mLBElement = document.createElement("label");  
  mLBElement.id = mName + ".label";  
  mLBElement.style.float = "left"; /* Make this div as wide as its contents */
  mElement.appendChild(mLBElement);

  var mDIElement = document.createElement("div");  
  mDIElement.id = mName + ".div2";
  mDIElement.style.overflow = "hidden"; /* Make this div take up the rest of the horizontal space, and no more */
  mElement.appendChild(mDIElement);

  var mSLElement = document.createElement("input");
  mSLElement.style.verticalAlign = "middle";
  mSLElement.style.width = "98%";  
  mSLElement.style.height = "98%";  
  mSLElement.type = 'range';
  mSLElement.id = mName + ".slider";
  mSLElement.value = 0;
  mDIElement.appendChild(mSLElement);  

  mSLElement.oninput = function() {  	
	  var controller = self.getController();    		
	  if (controller) {
		controller.propertiesChanged("Value");
		controller.invokeAction("OnChange");
		controller.reportInteractions();
	  }	        
	  return false;
  }

  mSLElement.onmouseup = function(e) {  	
	  var controller = self.getController();    		
	  if (controller) {
		controller.invokeAction("OnRelease");
		controller.reportInteractions();
	  }	        
      e.stopPropagation();
  }

  mSLElement.ontouchend = function(e) {  	
	  var controller = self.getController();    		
	  if (controller) {
		controller.invokeAction("OnRelease");
		controller.reportInteractions();
	  }	        
      e.stopPropagation();
  }

  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Style object for 2D drawing
 * @class Style 
 * @constructor  
 */
EJSS_INTERFACE.Style = { 
  BORDER_SOLID: "solid",
  BORDER_DOTTED: "dotted",
  BORDER_DASHED: "dashed",
  
  TEXTA_LEFT : "left", 				// Aligns the text to the left 	
  TEXTA_RIGHT : "right", 			// Aligns the text to the right 
  TEXTA_CENTER : "center", 			// Centers the text 	
  TEXTA_JUSTIFY : "justify", 		// Stretches the lines so that each line has equal width (like in newspapers and magazines)  	
  
  VISIB_VISIBLE: "visible",
  VISIB_HIDDEN: "hidden",
       
  FLOAT_LEFT :  "left",
  FLOAT_RIGHT:  "right",
  FLOAT_NONE: "none",
  FLOAT_CENTER: "none",
  	
  POSITION_STATIC : "static",		// Elements render in order, as they appear in the document flow 		
  POSITION_ABSOLUTE: "absolute", 	// The element is positioned relative to its first positioned (not static) ancestor element	
  POSITION_FIXED: "fixed",			// The element is positioned relative to the browser window 	
  POSITION_RELATIVE: "relative", 	// The element is positioned relative to its normal position, so "left:20" adds 20 pixels to the element's LEFT position	
  POSITION_INHERIT: "inherit", 		// The value of the position property is inherited from the parent element

  OVERFLOW_VISIBLE: "visible", 		// The overflow is not clipped. It renders outside the element's box. This is default 		
  OVERFLOW_HIDDEN: "hidden", 		// The overflow is clipped, and the rest of the content will be invisible 	
  OVERFLOW_SCROLL: "scroll", 		// The overflow is clipped, but a scroll-bar is added to see the rest of the content 		
  OVERFLOW_AUTO: "auto", 			// If overflow is clipped, a scroll-bar should be added to see the rest of the content
  	       

  setCSS: function (element,css) {
	var rdashAlpha = /-([\da-z])/gi;
	var fcamelCase = function(all, letter) {return ( letter + "" ).toUpperCase();};

    var style = element.style;
    if(typeof css !== 'object') {
    	// CSS with addition
    	var cssvals = css.trim().split('{');
    	if(cssvals[0] && (cssvals[0].length > 0)) { // for example ".myBorder { border-width: 5px }" 
    		var classname = cssvals[0].trim();			
	    	var x, classes = document.styleSheets[0].rules || document.styleSheets[0].cssRules; // fails in local tests, use web server (CORS)
	    	for(x = 0; x<classes.length; x++) {
	        	if(classes[x].selectorText == classname) break;
	    	}
	    	// set values
	    	var classesCss = (classes[x].cssText)? classes[x].cssText:classes[x].style.cssText; 
	    	classesCss = classesCss.match(/{.*}/g)[0]; // properties
		    var css_rows = classesCss.slice(1, classesCss.length-2).split(';');    	
			for (elem in css_rows) {
	        	var elem_parts = css_rows[elem].split(':');
	        	if(elem_parts.length == 2) {
	        		var property_name = elem_parts[0].trim();
	        		var property_value = elem_parts[1].trim();
	        	
	        		var camelCase = property_name.replace(rdashAlpha, fcamelCase);
	        		style[camelCase]=property_value;
	        	}        	
			}	    
		}
		if(cssvals[1]) { // for example "{ border-width: 5px }" 
			var addition = cssvals[1].trim();  
		    var css_rows = addition.slice(0, addition.length-2).split(';');    	
			for (elem in css_rows) {
	        	var elem_parts = css_rows[elem].split(':');
	        	if(elem_parts.length == 2) {
	        		var property_name = elem_parts[0].trim();
	        		var property_value = elem_parts[1].trim();
	        	
	        		var camelCase = property_name.replace(rdashAlpha, fcamelCase);
	        		style[camelCase]=property_value;
	        	}        	
			}	    				
		}
	} else {
		// object, for example {"xx":"value",...}
	    for (var prop in css) {
	    	if(typeof css[prop] !== 'object') { // property for current element
				var camelCase = prop.replace(rdashAlpha, fcamelCase);
				style[camelCase]=css[prop];
			} else { // property for child element, for example {".checkbox": {"xx":"value",...} }
				try {
					var childelement = document.getElementById(element.id + prop);
					var childcss = css[prop];
					var childstyle = childelement.style;
	    			for (var childprop in childcss) {
						var camelCase = childprop.replace(rdashAlpha, fcamelCase);
						childstyle[camelCase]=childcss[childprop];
	    			}
				} catch(error) {
  					console.error(error);
  				}
			}
	    }
	}				
  	
  }  	       
};

/**
 * Creates a Style object for 2D drawing
 */
EJSS_INTERFACE.style = function (mName) {
  var Style = EJSS_INTERFACE.Style;
  var self = {};

  var mBorderColor = "white";
  var mBorderWidth = 0;
  var mBorderStyle = Style.BORDER_SOLID;  


  //---------------------------------
  // General
  //---------------------------------  

  /**
   * Sets a number of CSS properties
   * @param css object
   */ 
  self.setCSS = function (css) {
  	Style.setCSS(document.getElementById(mName),css);
  };
  

  /**
   * Set the visibility 
   * @method setVisibility
   * @param visible boolean
   */
  self.setVisibility = function(visible) { 
    document.getElementById(mName).style.visibility = visible ? "visible" : "hidden";
  	//if (value.substring(0,6) == "VISIB_") value = Style[value.toUpperCase()];
	//document.getElementById(mName).style.visibility = value; 
  };

  /**
   * Whether the element is visible
   * @method getVisibility
   * @return boolean
   */
  self.getVisibility = function() { 
    return document.getElementById(mName).style.visibility=="visible"; 
  };

  //---------------------------------
  // Align
  //---------------------------------  

  /**
   * Set the vertical align 
   * @method setVerticalAlign
   * @param value string
   */
  self.setVerticalAlign = function(value) { 
	document.getElementById(mName).style.verticalAlign = value; 
  };

  /**
   * Get the vertical align
   * @method getVerticalAlign
   * @return string
   */
  self.getVerticalAlign = function() { 
    return document.getElementById(mName).style.verticalAlign; 
  };


  /**
   * Set the text align 
   * @method setTextAlign
   * @param value string
   */
  self.setTextAlign = function(value) { 
  	if (value.substring(0,6) == "TEXTA_") value = Style[value.toUpperCase()];
	document.getElementById(mName).style.textAlign = value; 
  };

  /**
   * Get the text align
   * @method getTextAlign
   * @return string
   */
  self.getTextAlign = function() { 
    return document.getElementById(mName).style.textAlign; 
  };

  //---------------------------------
  // Size
  //---------------------------------  

  /**
   * @method setLineHeight
   * @param value string or int
   */
  self.setLineHeight = function(value) { 
    if (typeof value !== "string") value = value + "px";
    document.getElementById(mName).style.lineHeight = value;
  };

  /**
   * @method getLineHeight
   * @return height
   */
  self.getLineHeight = function() { 
    return document.getElementById(mName).style.lineHeight; 
  };
  
  /**
   * @method setHeight
   * @param value string or int
   */
  self.setHeight = function(value) { 
    if (typeof value !== "string") value = value + "px";
    document.getElementById(mName).style.height = value;
  };

  /**
   * @method getHeight
   * @return height
   */
  self.getHeight = function() { 
    return document.getElementById(mName).style.height; 
  };

  /**
   * Set the width 
   * @method setWidth
   * @param value string or int
   */
  self.setWidth = function(value) { 
    if (typeof value !== "string") value = value + "px";
    document.getElementById(mName).style.width = value;
  };

  /**
   * Get the width
   * @method getWidth
   * @return string
   */
  self.getWidth = function() { 
    return document.getElementById(mName).style.width; 
  };

  //---------------------------------
  // position
  //---------------------------------
 
  /**
   * @method setOverflow
   * @param overflow
   */
  self.setOverflow = function(overflow) {
    if (overflow.substring(0,6) == "OVERFL") overflow = Style[overflow.toUpperCase()];
    document.getElementById(mName).style.overflow = overflow;       
  };
  
  /**
   * @method getOverflow
   * @return overflow
   */
  self.getOverflow = function() { 
    return document.getElementById(mName).style.overflow;
  };
    
  /**
   * @method setPosition
   * @param position
   */
  self.setPosition = function(position) {
    if (position.substring(0,6) == "POSITI") position = Style[position.toUpperCase()];
    document.getElementById(mName).style.position = position;       
  };
  
  /**
   * @method getPosition
   * @return position
   */
  self.getPosition = function() { 
    return document.getElementById(mName).style.position;
  };
  
  /**
   * @method setFloat
   * @param floatstyle
   */
  self.setFloat = function(floatstyle) {
    if (floatstyle.substring(0,6) == "FLOAT_") floatstyle = Style[floatstyle.toUpperCase()];
    document.getElementById(mName).style.float = floatstyle;       
  };
  
  /**
   * @method getFloat
   * @return float
   */
  self.getFloat = function() { 
    return document.getElementById(mName).style.float;
  };

  /**
   * @method setDisplay
   * @param displayStyle
   */
  self.setDisplay = function(displayStyle) {
    document.getElementById(mName).style.display = displayStyle;       
  };
  
  /**
   * @method getDisplay
   * @return displayStyle
   */
  self.getDisplay = function() { 
    return document.getElementById(mName).style.display;
  };
  
  /**
   * @method setTop
   * @param top
   */
  self.setTop = function(top) { 
    document.getElementById(mName).style.top = top;
  };

  /**
   * @method getTop
   * @return top
   */
  self.getTop = function() { 
    return document.getElementById(mName).style.top; 
  };

  /**
   * @method setBottom
   * @param bottom
   */
  self.setBottom = function(bottom) { 
    document.getElementById(mName).style.bottom = bottom;
  };

  /**
   * @method getBottom
   * @return left
   */
  self.getBottom = function() { 
    return document.getElementById(mName).style.bottom; 
  };

  /**
   * @method setLeft
   * @param left
   */
  self.setLeft = function(left) { 
    document.getElementById(mName).style.left = left;
  };

  /**
   * @method getLeft
   * @return left
   */
  self.getLeft = function() { 
    return document.getElementById(mName).style.left; 
  };
     
  //---------------------------------
  // borders, margin, shadow, padding
  //---------------------------------

  /**
   * @method setBorderStyle
   * @param style
   */
  self.setBorderStyle = function(style) {
    if (style.substring(0,6) == "BORDER") style = Style[style.toUpperCase()];
    mBorderStyle = style;
    document.getElementById(mName).style.border = mBorderWidth + "px " + mBorderStyle + " " + mBorderColor;       
  };
  
  /**
   * @method getBorderStyle
   * @return style
   */
  self.getBorderStyle = function() { 
    return mBorderStyle;
  };

  /**
   * @method setBorderWidth
   * @param width
   */
  self.setBorderWidth = function(width) {
    mBorderWidth = width;
    document.getElementById(mName).style.border = mBorderWidth + "px " + mBorderStyle + " " + mBorderColor;
  };
  
  /**
   * @method getBorderWidth
   * @return width
   */
  self.getBorderWidth = function() { 
    return mBorderWidth; 
  };

  /**
   * @method setBorderColor
   * @param color
   */
  self.setBorderColor = function(color) {
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    mBorderColor = color;
    document.getElementById(mName).style.border = mBorderWidth + "px " + mBorderStyle + " " + mBorderColor;
  };
  
  /**
   * @method getBorderColor
   * @return color
   */
  self.getBorderColor = function() { 
    return mBorderColor; 
  };

  /**
   * @method setPadding
   * @param padding
   */
  self.setPadding = function(padding) {
    document.getElementById(mName).style.padding = padding;
  };
  
  /**
   * @method getPadding
   * @return padding
   */
  self.getPadding = function() { 
    return document.getElementById(mName).style.padding; 
  };

  /**
   * @method setShadow
   * @param boolean
   */
  self.setShadow = function(shadow) {
    if (shadow)
  	  document.getElementById(mName).style.boxShadow = shadow; //"4px 4px 8px #888888";
    else
  	  document.getElementById(mName).style.boxShadow = "";
  };

  /**
   * @method getShadow
   * @return bool
   */
  self.getShadow = function() { 
    return (document.getElementById(mName).style.boxShadow != ""); 
  };

  /**
   * @method setMargin
   * @param margin
   */
  self.setMargin = function(margin) { 
    document.getElementById(mName).style.margin = margin;
  };

  /**
   * @method getMargin
   * @return width
   */
  self.getMargin = function() { 
    return document.getElementById(mName).style.margin; 
  };


  //---------------------------------
  // interior fill
  //---------------------------------

 
 /**
   * @method setBackgroundImage
   * @param url
   */
  self.setBackgroundImage = function(url) {  	  	
  	document.getElementById(mName).style.backgroundImage="url(" + url + ")";
  	document.getElementById(mName).style.backgroundSize="100% 100%";
  };
  
  /**
   * @method getBackgroundImage
   * @return url
   */
  self.getBackgroundImage = function() { 
    return document.getElementById(mName).style.backgroundImage; 
  };
  
  /**
   * @method setBackgroundColor
   * @param color
   */
  self.setBackgroundColor = function(color) {
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    document.getElementById(mName).style.backgroundColor = color;
  };
  
  /**
   * @method getBackgroundColor
   * @return color
   */
  self.getBackgroundColor = function() { 
    return document.getElementById(mName).style.backgroundColor; 
  };
  
  /**
   * @method setColor
   * @param color
   */
  self.setColor = function(color) {
    if (typeof color !== "string") color = EJSS_TOOLS.DisplayColors.getLineColor(color);
    document.getElementById(mName).style.color = color;
  };
  
  /**
   * @method getColor
   * @return color
   */
  self.getColor = function() { 
    return document.getElementById(mName).style.color; 
  };

  //---------------------------------
  // Font
  //---------------------------------
  
  /**
   * @method getFont
   * @return font
   */
  self.getFont = function() { 
    return document.getElementById(mName).style.font; 
  };

  /**
   * @method setFont
   * @param recap string, format: [style weight size[/lineHeight] [family]]
   */ 
  self.setFont = function (recap) {
    if ((typeof(recap) !== 'string') || recap.length<=0) return;	
  	var style = document.getElementById(mName).style;
    var params = recap.split(" ");        
   	style.fontStyle = params[0];		// style   	
   	style.fontWeight = params[1];		// weight	
	var sizes = params[2].split("/");      	   	
   	style.fontSize = sizes[0];			// size
   	if (sizes[1]) 
   		style.lineHeight = sizes[1];	// lineHeight   	
   	if (params[3]) 
   		style.fontFamily = recap.substring(recap.indexOf(params[3])); // family
  };
      
  /**
   * Set the font style.
   * @method setFontStyle
   * @param fontStyle
   */
  self.setFontStyle = function(fontStyle) {
  	document.getElementById(mName).style.fontStyle = fontStyle;
  };
      
  /**
   * Get the font style.
   * @method getFontStyle
   * @return fontStyle
   */
  self.getFontStyle = function() { 
    return document.getElementById(mName).style.fontStyle;
  };
      
  /**
   * Set the family of the font.
   * @method setFontFamily
   * @param fontFamily
   */
  self.setFontFamily = function(fontFamily) {
    document.getElementById(mName).style.fontFamily = fontFamily;
  };
      
  /**
   * Get the family of the font.
   * @method getFontFamily
   * @return font family
   */
  self.getFontFamily = function() { 
    return document.getElementById(mName).style.fontFamily; 
  };

  /**
   * Set the size of the font.
   * @method setFontSize
   * @param fontSize
   */
  self.setFontSize = function(fontSize) {
    document.getElementById(mName).style.fontSize = fontSize;
  };
      
  /**
   * Get the size of the font.
   * @method getFontSize
   * @return font size
   */
  self.getFontSize = function() { 
    return document.getElementById(mName).style.fontSize; 
  };

  /**
   * Set the letter spacing of the font.
   * @method setLetterSpacing
   * @param letterSpacing
   */
  self.setLetterSpacing = function(letterSpacing) {
    document.getElementById(mName).style.letterSpacing = letterSpacing;
  };
      
  /**
   * Get the letter spacing of the font.
   * @method getLetterSpacing
   * @return letter spacing
   */
  self.getLetterSpacing = function() { 
    return document.getElementById(mName).style.letterSpacing; 
  };

  /**
   * Set the line color of the font.
   * @method setOutlineColor
   * @param lineColor
   */
  self.setOutlineColor = function(lineColor) {
    document.getElementById(mName).style.stroke = lineColor;
  };
      
  /**
   * Get the line color of the font.
   * @method getOutlineColor
   * @return line color
   */
  self.getOutlineColor = function() { 
    return document.getElementById(mName).style.stroke; 
  };

  /**
   * Set the line width of the font.
   * @method setFontWeight
   * @param lineWidth
   */
  self.setFontWeight = function(lineWidth) {
    document.getElementById(mName).style.fontWeight = lineWidth;
  };
      
  /**
   * Get the line width of the font.
   * @method getFontWeight
   * @return line width
   */
  self.getFontWeight = function() { 
    return document.getElementById(mName).style.fontWeight; 
  };

  /**
   * Set the fill color of the font.
   * @method setFillColor
   * @param fillColor
   */
  self.setFillColor = function(fillColor) {
    document.getElementById(mName).style.fill = fillColor;
  };
      
  /**
   * Get the fill color of the font.
   * @method getFillColor
   * @return fill color
   */
  self.getFillColor = function() { 
    return document.getElementById(mName).style.fill; 
  };

  /**
   * Set transform.
   * @method setTransform
   * @param transform
   */
  self.setTransform = function(transform) {
  	document.getElementById(mName).style.transform = transform;
  	document.getElementById(mName).style["-ms-transform"] = transform;
  	document.getElementById(mName).style["-webkit-transform"] = transform;
  };
  
  /**
   * Get transform.
   * @method getTransform
   * @return transform
   */
  self.getTransform = function() {
  	return document.getElementById(mName).style.transform;
  };
  
  /**
   * Set how to show white spaces.
   * @method setWhiteSpace
   * @param whiteSpace
   */
  self.setWhiteSpace = function(whiteSpace) {
  	document.getElementById(mName).style.whiteSpace = whiteSpace;
  };
  
  /**
   * Get how to show white spaces.
   * @method getWhiteSpace
   * @return whiteSpace
   */
  self.getWhiteSpace = function() {
  	return document.getElementById(mName).style.whiteSpace;
  };
  
  
  //---------------------------------
  // final initialization
  //---------------------------------
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS SVGGraphicswork for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * SVGGraphics
 * @class SVGGraphics 
 * @constructor  
 */
EJSS_INTERFACE.SvgGraphics = {
  	
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Height", element.setHeight);
	controller.registerProperty("Width", element.setWidth);

  },

};

/**
 * SVGGraphics function
 * Creates a basic SVGGraphics
 * @method svgGraphics
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.svgGraphics = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mHeight = 0;
  var mWidth = 0; 

  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------
  
  /**
   * @method setWidth
   * @param width
   */
  self.setWidth = function(width) { 
    if (mWidth!=width) { 
      mWidth = width;
      self.getDOMElement().setAttribute('width', mWidth);
      return true;
    } 
    return false;
  };

  /**
   * @method getWidth
   * @return width
   */
  self.getWidth = function() { 
    return mWidth; 
  };
  
  /**
   * @method setHeight
   * @param height
   */
  self.setHeight = function(height) { 
    if (mHeight!=height) { 
      mHeight = height; 
      self.getDOMElement().setAttribute('height', mHeight);
      return true;
    } 
    return false;
  };

  /**
   * @method getHeight
   * @return height
   */
  self.getHeight = function() { 
    return mHeight; 
  };

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.SvgGraphics.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
  mElement.setAttribute('id', mName);
  mElement.style.overflow = "hidden";
  mElement.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
  self.setWidth(500);
  self.setHeight(500);
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * TabbedPanel
 * @class TabbedPanel 
 * @constructor  
 */
EJSS_INTERFACE.TabbedPanel = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Titles", element.setTitles, element.getTitles);
    controller.registerProperty("Selected", element.setSelected, element.getSelected);

    
	  controller.registerProperty("Font",  element.setFont);
	  controller.registerProperty("Foreground",  element.setColor);
      controller.registerProperty("Background",  element.setBackgroundColor);
      controller.registerProperty("FillColor", element.setFillColor);

  },

};

/**
 * TabbedPanel function
 * @method TabbedPanel
 * @param name the name of the element
 * @returns A panel with tabs
 */
EJSS_INTERFACE.tabbedPanel = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mTitles = [];
  var mSelected = 0;
  
  var mBackground = null;

  /**
   * @method appendChild
   * @param name
   */
  self.appendChild = function(element) {
  	var child = element.getDOMElement();
    mDIVElement.appendChild(child);       
    select();   
  };

  /**
   * @method removeChild
   * @param name
   */
  self.removeChild = function(element) {
  	var child = element.getDOMElement();
    mDIVElement.removeChild(child);      
    select();    
  };

  /**
   * @method insertBefore
   * @param name
   */
  self.insertBefore = function(newele,refele) {
  	var newchild = newele.getDOMElement();
  	var refchild = refele.getDOMElement();
    mDIVElement.insertBefore(newchild,refchild);    
    select();   
  };
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.getTitles = function() {
    return mTitles;
  };

  self.setTitles = function(items) {
    if (!EJSS_TOOLS.compareArrays(mTitles,items)) {
    	mTitles = items;
        updateTitles();
    }
  };

  self.getSelected = function() {
    return mSelected;
  };

  self.setSelected = function(index) {
    if (mSelected != index) {
    	mSelected = index;
        select();
    }
  };

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.TabbedPanel.registerProperties(self,controller);
  };

  function updateTitles() {
	mTABElement.innerHTML = "";

	for(var i=0; i<mTitles.length; i++) {
		var title = mTitles[i];
		
	    var mLIElement = document.createElement("li");  
	    mLIElement.className="_TabbedPanel.li";
		mLIElement.id = mName + ".item";
	    mTABElement.appendChild(mLIElement);
	    mLIElement.style.background = mBackground ? mBackground : self.getStyle().getBackgroundColor();

		mLIElement.onmouseover = function () {
			this.style.cursor = "pointer";
		};

		mLIElement.onmouseout = function () {
			this.style.cursor = "auto";
		};
	    
	    var mAElement = document.createElement("a");  
	    mAElement.className="_TabbedPanel.a";
		mAElement.id = mName + ".item.a." + i;
		// mAElement.style = "user-select: none; display: inline-block; color: black; text-align: center; padding: 7px 8px; text-decoration: none; transition: 0.3s; font-size: 14px;";
		mAElement.innerHTML = title;
	    mLIElement.appendChild(mAElement);
	    mAElement.style.color = self.getStyle().getColor();
	    mAElement.style.fontStyle = self.getStyle().getFontStyle();
	    mAElement.style.fontWeight = self.getStyle().getFontWeight();
	    mAElement.style.fontSize = self.getStyle().getFontSize();
	    mAElement.style.fontFamily = self.getStyle().getFontFamily();
	    mAElement.style.lineHeight = self.getStyle().getLineHeight();
				
	  	mAElement.onclick = function() {
		    var parts = this.id.split(".");
    		mSelected = parseInt(parts[parts.length-1]);
	  		select();
	  	};
	  	select();
	}
  }

  function select() {
    // active panel
    for (var j = 0; j < mDIVElement.childNodes.length; j++) {
        mDIVElement.childNodes[j].style.display = "none";
    }
    if (mDIVElement.childNodes.length > mSelected)
    	mDIVElement.childNodes[mSelected].style.display = "block";
			
    // active tab
    for (var j = 0; j < mTABElement.childNodes.length; j++) {
        mTABElement.childNodes[j].firstChild.style.backgroundColor = "transparent";
    }
    if (mTABElement.childNodes.length > mSelected)
		mTABElement.childNodes[mSelected].firstChild.style.backgroundColor =
			self.getStyle().getFillColor();
  }

  // ----------------------------------------------------
  // Override parent behaviour
  // ----------------------------------------------------

  /**
   * @method setBackgroundColor
   * @param color
   */
  self.setFont = function(font) {
	  self.getStyle().setFont(font);
	  updateTitles();
  };

  /**
   * @method setBackgroundColor
   * @param color
   */
  self.setFillColor = function(color) {
	  self.getStyle().setFillColor(color);
	  select();
  };

  /**
   * @method setBackgroundColor
   * @param color
   */
  self.setBackgroundColor = function(color) {
	  mBackground = color;
	  updateTitles();
  };
  
  /**
   * @method setColor
   * @param color
   */
  self.setColor = function(color) {
	  self.getStyle().setColor(color);
	  updateTitles();
  };


  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("span");
  mElement.className="_TabbedPanel.span";
  mElement.id = mName;
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);

  var mTABElement = document.createElement("ul");  
  mTABElement.className="_TabbedPanel.ul";
  mTABElement.id = mName + ".ul";
  mElement.appendChild(mTABElement);

  var mDIVElement = document.createElement("span");  
  mDIVElement.id = mName + ".divs";
  mElement.appendChild(mDIVElement);  

  updateTitles();
  self.getStyle().setFillColor("#ccc");

  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/***
 * TextArea
 * @class TextArea 
 * @constructor  
 */
EJSS_INTERFACE.TextArea = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Value", element.setValue, element.getValue);
    controller.registerProperty("Editable", element.setEditable);
    controller.registerProperty("Rows", element.setRows);
    controller.registerProperty("Columns", element.setCols);
    controller.registerProperty("ChangeColorOnEdit", element.setChangeColorOnEdit);

    controller.registerAction("OnChange");
  },

};

/**
 * TextArea function
 * @method textArea
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.textArea = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  var mChangeColorOnEdit = true;
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------


  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.TextArea.registerProperties(self,controller);
  };
 
  self.enableEPub = function() {
  	if (self.getDOMElement().disabled) self.getDOMElement().disabled = false;
  };
  
  if (typeof _isEPub !== 'undefined' && _isEPub) {
    self.render = function() {
      self.enableEPub();
    }
  };  
   
  // ----------------------------------------
  // Setters and getters
  // ----------------------------------------

  /***
   * Whether to change color onkeydown/onblur
   * @method setChangeColorOnEdit
   * @param change boolean
   */
  self.setChangeColorOnEdit = function(change) { 
    mChangeColorOnEdit = change; 
  };

  /**
   * Set the value displayed by the element
   * @method setValue
   * @param value double
   */
  self.setValue = function(value) { 
    self.getDOMElement().value = value; 
  };
  
  /***
   * Add the text and scroll to the bottom
   * @method addText
   * @param value double
   */
  self.appendText = function(value) { 
	var domElement = self.getDOMElement();
	domElement.value += value
	domElement.scrollTop = domElement.scrollHeight;
  };

  /***
   * Clear the text area
   * @method clear
   */
  self.clear = function() { 
    self.getDOMElement().value = ""; 
  };

  /**
   * Get the value displayed by the element
   * @method getValue
   * @return double
   */
  self.getValue = function() { 
    return self.getDOMElement().value; 
  };

  /**
   * Set the editable property
   * @method setEditable
   * @param editable boolean
   */
  self.setEditable = function(editable) { 
    self.getDOMElement().readOnly = !editable; 
    if (!editable) self.getStyle().setBackgroundColor("lightgrey");
  };

  /**
   * @method getEditable
   * @return bool
   */
  self.getEditable = function() { 
    return !self.getDOMElement().readOnly; 
  };

  /**
   * Set the cols 
   * @method setCols
   * @param value
   */
  self.setCols = function(cols) { 
    self.getDOMElement().cols = cols; 
  };

  /**
   * Get the cols
   * @method getCols
   * @return double
   */
  self.getCols = function() { 
    return self.getDOMElement().cols; 
  };

  /**
   * Set the rows
   * @method setRows
   * @param value
   */
  self.setRows = function(rows) { 
    self.getDOMElement().rows = rows; 
  };

  /**
   * Get the rows
   * @method getRows
   * @return double
   */
  self.getRows = function() { 
    return self.getDOMElement().rows; 
  };
    
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("textarea");  
  mElement.id = mName;
  mElement.style.verticalAlign = "middle";
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);

  self.getDOMElement().onkeydown = function(key) {
    var dom = self.getDOMElement();
  	if(!dom.readOnly) {
  		if (mChangeColorOnEdit) self.getStyle().setBackgroundColor("yellow");
    }
  }

  self.getDOMElement().onblur = function(dom) {
  	if(!dom.readOnly) {
  		if (mChangeColorOnEdit) self.getStyle().setBackgroundColor("white");  
    	var controller = self.getController();    		
    	if (controller) {
        	controller.propertiesChanged("Value");
      		controller.invokeAction("OnChange");
      		controller.reportInteractions();
    	}	    
  	}  	
  }

  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * TextField
 * @class TextField 
 * @constructor  
 */
EJSS_INTERFACE.TextField = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

    controller.registerProperty("Value", element.setValue, element.getValue);
    controller.registerProperty("Editable", element.setEditable);
    controller.registerProperty("Size", element.setSize);

    controller.registerAction("OnChange");
  },

};

/**
 * TextField function
 * @method textField
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.textField = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mEdition = false;
  var mTmp = "";
  
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------


  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.TextField.registerProperties(self,controller);
  };
  
  self.enableEPub = function() {
  	if (self.getDOMElement().disabled) self.getDOMElement().disabled = false;
  };
  
  if (typeof _isEPub !== 'undefined' && _isEPub) {
    self.render = function() {
      self.enableEPub();
    }
  };
    
  // ----------------------------------------
  // Setters and getters
  // ----------------------------------------
  
  /**
   * Set the value displayed by the element
   * @method setValue
   * @param value double
   */
  self.setValue = function(value) { 
  	if(!mEdition) {	
	    if (typeof value == "undefined") // produces and error in Safari 
	    	self.getDOMElement().value = "value undefined";
	    else {
    		self.getDOMElement().value = value;
	    }
	}
  };

  /**
   * Get the value displayed by the element
   * @method getValue
   * @return double
   */
  self.getValue = function() { 
    return self.getDOMElement().value; 
  };

  /**
   * Set the editable property
   * @method setEditable
   * @param editable boolean
   */
  self.setEditable = function(editable) { 
    self.getDOMElement().readOnly = !editable; 
    if (editable) self.getStyle().setBackgroundColor("white");
    else self.getStyle().setBackgroundColor("lightgrey");
  };

  /**
   * @method getEditable
   * @return bool
   */
  self.getEditable = function() { 
    return !self.getDOMElement().readOnly; 
  };

  /**
   * Set the size 
   * @method setSize
   * @param value string
   */
  self.setSize = function(value) { 
    self.getDOMElement().size = value; 
  };

  /**
   * Get the size
   * @method getSize
   * @return double
   */
  self.getSize = function() { 
    return self.getDOMElement().size; 
  };
  
  function checkValue() {
    var value = self.getDOMElement().value;
    self.getStyle().setBackgroundColor("white");
    if (self.parseValue) self.parseValue(value);
    if(mTmp != value) {
	    var controller = self.getController();    		
	    if (controller) {
		  controller.propertiesChanged("Value");
		  controller.invokeAction("OnChange");
		  controller.reportInteractions();
	    }
	}	    
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("input");  
  mElement.id = mName;
  mElement.type = 'text';
  document.body.appendChild(mElement);  
  self.setDOMElement(mElement);
  
  self.getDOMElement().onkeydown = function(key) {  	
    var dom = self.getDOMElement();
  	if(!dom.readOnly) {
  	  	var ev = key || window.event;
	  	var charCode = (ev.which) ? ev.which : ev.keyCode;  	  
  		if (charCode==13) { // return
  			mEdition = false;
  			checkValue();
  			mTmp = self.getDOMElement().value;
  		} else if (charCode==27) { // escape
  			self.getDOMElement().value = mTmp;  		
  		} else {
  			self.getStyle().setBackgroundColor("yellow");
  			mEdition = true;
  		}
    }      
  }

  self.getDOMElement().onfocus = function(dom) {
  	mTmp = self.getDOMElement().value;
  }

  self.getDOMElement().onblur = function(dom) {
  	if(!dom.readOnly && mEdition) {
  		checkValue();
  		mEdition = false;
  	}
  }
    
  self.getDOMElement().ontouchend = function(e) {
    e.stopPropagation();
  }

  self.getDOMElement().onmouseup = function(e) {
    e.stopPropagation();
  }
      
  // associate event for ipad
  if (typeof _isEPub !== 'undefined' && _isEPub) {
    self.getDOMElement().ontouchstart = function(event) {
	  if (self.getDOMElement().disabled) self.getDOMElement().disabled = false;
	  event.stopPropagation();
    };
  }

  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * TwoStateButton
 * @class TwoStateButton 
 * @constructor  
 */
EJSS_INTERFACE.TwoStateButton = {
	OFF : false,
	ON: true,
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("TextOn", element.setTextOn);
	controller.registerProperty("ImageOnUrl",element.setImageUrlOn);
	controller.registerProperty("TextOff", element.setTextOff);
	controller.registerProperty("ImageOffUrl",element.setImageUrlOff);

	controller.registerProperty("State",element.setState, element.getState);	

    controller.registerAction("OnClick");
    controller.registerAction("OffClick");
  },

};

/**
 * TwoStateButton function
 * Creates a basic TwoStateButton
 * @method twoStateButton
 * @param name the name of the element
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.twoStateButton = function (mName) {
  var TSButton = EJSS_INTERFACE.TwoStateButton;
  var self = EJSS_INTERFACE.element(mName);
  
  var mUrlOn = "";
  var mUrlOff = "";
  var mTextOn = "";
  var mTextOff = "";
  var mState = -1;
  	
  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setTextOff = function(text) {
  	mTextOff = text;
  	applyChange();  	 
  }

  self.getTextOff = function() {
    return mTextOff;
  }

  self.setImageUrlOff = function(url) {  	
  	var newUrl = self.getResourcePath(url);
  	if (newUrl==mUrlOff) return;
  	mUrlOff = newUrl;
  	applyChange();
  }

  self.getImageUrlOff = function() {
    return mUrlOff;
  }

  self.setTextOn = function(text) {
  	mTextOn = text;
  	applyChange();
  }

  self.getTextOn = function() {
    return mTextOn;
  }

  self.setImageUrlOn = function(url) {
  	var newUrl = self.getResourcePath(url);
  	if (newUrl==mUrlOn) return;
  	mUrlOn = newUrl;
  	applyChange();
  }

  self.getImageUrlOn = function() {
    return mUrlOn;
  }

  self.setState = function(state) {
  	if (typeof state == "string") state = TSButton[state.toUpperCase()];
  	if (state != mState) {  		
	  	mState = state;
	  	applyChange();
	}
  }

  self.getState = function() {
  	return mState;
  }

  function applyChange() {
  	self.getDOMElement().innerHTML = "";
	if (mState) { // on
    	if(mUrlOn.length > 0) 
    	    self.getDOMElement().innerHTML = '<img alt="' + mName + '" style="vertical-align:inherit;-webkit-touch-callout:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;" src="'+mUrlOn+'"/>';
    	if(mTextOn.length > 0)
    	    self.getDOMElement().innerHTML += '<span style="white-space:pre;vertical-align:inherit;-webkit-touch-callout:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;">'+mTextOn.toString()+'</span>';
	} else { // off
    	if(mUrlOff.length > 0) 
    	    self.getDOMElement().innerHTML = '<img alt="' + mName + '" style="vertical-align:inherit;-webkit-touch-callout:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;" src="'+mUrlOff+'"/>';
    	if(mTextOff.length > 0)
    	    self.getDOMElement().innerHTML += '<span style="white-space:pre;vertical-align:inherit;-webkit-touch-callout:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;">'+mTextOff.toString()+'</span>';
	}  	
  }

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.TwoStateButton.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("button");  
  mElement.id = mName;
  mElement.style.verticalAlign = "middle";
  mElement.style.webkitTouchCallout = "none";
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);


  self.getDOMElement().oncontextmenu = function(e) {
  	e.preventDefault(); e.stopPropagation();
    return false;  	
  }  

  function onClickListener(e) {
  	var state = self.getState();
  	self.setState(!state);
    var controller = self.getController();
    if (controller) {
    	controller.propertiesChanged("State");
    	if(state)
    		controller.invokeAction("OnClick");
    	else
    		controller.invokeAction("OffClick");
    	controller.reportInteractions();
    }
  	e.preventDefault(); e.stopPropagation();    
    return false;    
  };

  self.getDOMElement().addEventListener("mouseup", onClickListener);
  self.getDOMElement().addEventListener("touchend", onClickListener);
    
  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * Video
 * @class Video 
 * @extends Media
 * @constructor  
 */
EJSS_INTERFACE.Video = {
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Media.registerProperties(element,controller); // super class

    controller.registerProperty("VideoUrl",element.setUrl);
    controller.registerProperty("Background",element.setBackground, element.getBackground)
    controller.registerProperty("PosterUrl",element.setPoster)
  },

};

/**
 * Video function
 * Creates a basic Video
 * @method video
 * @param name the name of the element
 * @returns A video element
 */
EJSS_INTERFACE.video = function (mName) {
  var self = EJSS_INTERFACE.media(mName);
  var isBackground = false;

  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------

  self.setPoster = function(url) {
    if (self.getResourcePath) url = self.getResourcePath(url);
	self.getDOMElement().poster = url;
  }	

  self.getPoster = function() {
  	return self.getDOMElement().poster;
  }	

  self.getBackground = function() {
  	return isBackground;
  }

  self.setBackground = function(back) {
  	if(back != isBackground) {
  		isBackground = back;
  		if(isBackground) {
	  		self.getStyle().setWidth("100%");
	  		self.getStyle().setHeight("100%");
	  		self.getStyle().setCSS({ "position":"absolute", "z-index":"-100" });
	  	} else {
	  		self.getStyle().setWidth(" ");
	  		self.getStyle().setHeight(" ");
	  		self.getStyle().setCSS({ "position":" ", "z-index":" " });	  		
	  	}
  	}
  } 

  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.Video.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("video");  
  mElement.id = mName;
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);
  self.addDOMEvents();
    
  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS WebGLCanvaswork for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * WebGLCanvas
 * @class WebGLCanvas 
 * @constructor  
 */
EJSS_INTERFACE.WebGLCanvas = {
  	
  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Element.registerProperties(element,controller); // super class

	controller.registerProperty("Height", element.setHeight);
	controller.registerProperty("Width", element.setWidth);

  },

};

/**
 * WebGLCanvas function
 * Creates a basic WebGLCanvas
 * @method webGLCanvas
 * @param name the name of the element
 * @param drawingPanel2D
 * @returns An abstract 2D element
 */
EJSS_INTERFACE.webGLCanvas = function (mName) {
  var self = EJSS_INTERFACE.element(mName);
  
  var mHeight = 0;
  var mWidth = 0; 
  
  var mGL;

  // ----------------------------------------------------
  // Properties
  // ----------------------------------------------------
  
  /**
   * @method setWidth
   * @param width
   */
  self.setWidth = function(width) { 
    if (mWidth!=width) { 
      mWidth = width;
      self.getStyle().setWidth(mWidth);
      return true;
    } 
    return false;
  };

  /**
   * @method getWidth
   * @return width
   */
  self.getWidth = function() { 
    return mWidth; 
  };
  
   /**
   * @method setHeight
   * @param height
   */
  self.setHeight = function(height) { 
    if (mHeight!=height) { 
      mHeight = height; 
      self.getStyle().setHeight(mHeight);
      return true;
    } 
    return false;
  };

  /**
   * @method getHeight
   * @return height
   */
  self.getHeight = function() { 
    return mHeight; 
  };

  self.getContext = function() {
  	return mGL;
  };
  
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.WebGLCanvas.registerProperties(self,controller);
  };

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  var mElement = document.createElement("canvas");  
  mElement.setAttribute('id', mName);
  document.body.appendChild(mElement);
  self.setDOMElement(mElement);
//  self.getStyle().setBorderColor("black");
//  self.getStyle().setBorderWidth(1);  
  
  self.setWidth(500);
  self.setHeight(500);
    
  // test support
  try { mGL = mElement.getContext('webgl'); } catch (e) { }    
  try { mGL = mGL || mElement.getContext('experimental-webgl'); } catch (e) { }
  try { mGL = mGL || mElement.getContext('webkit-3d'); } catch (e) { }
  try { mGL = mGL || mElement.getContext('moz-webgl'); } catch (e) { }
    
  if (!mGL)  {
  	self.getStyle().setBackgroundColor("rgb(239,239,255)");
  	
  	var ctx = mElement.getContext('2d');
  	ctx.font = "bold 16px Arial";
  	ctx.fillText("WebGL not supported!", 100, 100);
  } else {
  	// mGL.getExtension('OES_standard_derivatives');
  }
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * EJSS framework for interface element.
 * @module interface 
 */

var EJSS_INTERFACE = EJSS_INTERFACE || {};

/**
 * WrappedPanel
 * @class WrappedPanel 
 * @constructor  
 */
EJSS_INTERFACE.WrappedPanel = {

  // ----------------------------------------------------
  // Static methods
  // ----------------------------------------------------

  /**
   * static registerProperties method
   */ 
   registerProperties : function(element,controller) {
    EJSS_INTERFACE.Panel.registerProperties(element,controller); // super class
      
  },

};

/**
 * panel wrapper function
 * Creates a special top-level basic panel
 * @method wrappedPanel
 * @param name the name of the element
 * @returns A basic panel
 */
EJSS_INTERFACE.wrappedPanel = function (mName) {
  if (typeof _isEPub === 'undefined' || !_isEPub) { 
    return EJSS_INTERFACE.panel (mName);
  }
  
  // We created this element because of a bug in Apple's ePub reader in iOS.
  // When Apple fixed this, we changed it to (only) make sure the children
  // appear in the same ePub page

  var self = EJSS_INTERFACE.panel(mName);

/*  
  self.adjustPosition = function() {     
    mWrapper.style.height = self.getDOMElement().getBoundingClientRect().height + "px";
    mWrapper.style.width = self.getDOMElement().getBoundingClientRect().width + "px";

    var topShell = mWrapper.getBoundingClientRect().top;
    var leftShell = mWrapper.getBoundingClientRect().left;

    var body = document.body;
    var docElem = document.documentElement;
    var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
    var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;

    var clientTop = docElem.clientTop || body.clientTop || 0;

    self.getDOMElement().style.top = (topShell + scrollTop) + "px";
    self.getDOMElement().style.left = (leftShell + scrollLeft) + "px";    
  };
  
  var super_setParent = self.setParent;
  
  self.setParent = function(parent) {
  	if (typeof parent == "string") { // an HTML object
  		var domObject = document.getElementById(parent);
      	domObject.appendChild(mWrapper);
    }
    else if (parent.getDOMElement) { // an interface element 
    	parent.getDOMElement().appendChild(mWrapper);
 	} else { // an DOM element
		parent.appendChild(mWrapper);
    }
  	super_setParent(parent);
  };
*/    
  /**
   * Extended registerProperties method. To be used by promoteToControlElement
   * @method registerProperties
   */
  self.registerProperties = function(controller) {
    EJSS_INTERFACE.WrappedPanel.registerProperties(self,controller);
  };


  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

//  var mWrapper = document.createElement("div");  
//  EJSS_INTERFACE.Style.setCSS(mWrapper,{ "display": "block", "margin-left": "auto", "margin-right": "auto", "page-break-inside":"avoid" });
//  document.body.appendChild(mWrapper);  

//  var mElement = document.createElement("div");  
//  mElement.id = mName;
//  mElement.contenteditable = true;
//  document.body.appendChild(mElement);  
//  self.setDOMElement(mElement);  
  self.getStyle().setPadding("0px");
  self.getStyle().setMargin("0px");
  self.getStyle().setTextAlign("TEXTA_CENTER");  
  // self.getStyle().setCSS({ "page-break-inside":"avoid", "display": "inline-block", "margin-left": "auto", "margin-right": "auto", "position":"absolute" });   
  self.getStyle().setCSS({ "page-break-inside":"avoid", "margin-left": "auto", "margin-right": "auto"});
  
  return self;
};

/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García 
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for Animations
 * @module core
 */

var EJSS_CORE = EJSS_CORE || {};

/**
 * Model
 * @class Model 
 * @constructor  
 */
EJSS_CORE.Model = {	
	MODE_JAVA_STYLE     : 1,
	MODE_RESPECT_TIME   : 2,
	MODE_RESPECT_DELAY  : 3
};

/**
 * Creates an animation for a model.
 * The model must implement the function:
 * - stepModel()  : steps the model for each animation frame
 * and have a view.
 * @method createAnimation
 */
EJSS_CORE.createAnimation = function() {
	var self = {};
	var MINIMUM_DELAY = 1;

	// --- Configuration variables

	var mDelay = MINIMUM_DELAY;
	var mStepsPerDisplay = 1;
	var mAbortSPDLoop = false;
	// abort the Steps Per Display loop in the step() function
	var mShouldUpdateView = true;
	var mAutoplay = false;
	var mShouldBreak = false;

	// --- Implementation variables
	var mLastTime = +new Date();
	var mIsPlaying = false;
	var mView = null;
	var mInitialRealTime = NaN;
	var mSleepTime = mDelay;
	var mExcededTime = 0;
	var mMinimumDelay = 10;
	var mCurrentTimeOut;
	var mRunCount = 0;	
	var mRunTime = +new Date();

	var mFocus = true;
	var mRunAlways = false;

	var mOnloadFunc;
	
	var mPendingTasks = []; // Array of functions to call when the animation is idle. 
	
	// mode=0 run function adjusts a step time to mDelay
	// mode=1 run function takes mDelay at the end of each step
	var mRunningMode = 	EJSS_CORE.Model.MODE_JAVA_STYLE;  
	
	// ------------------------------------------------------------------------
	// This part is used by the model subclass at creation time
	// ------------------------------------------------------------------------

	/**
	 * Sets the view for this model.
	 * @method setView
	 */
	self.setView = function(view) {
		mView = view;
		mView._setReportInteractionMethod(function() {
			if (!mIsPlaying) {
				if (mView._readInteractions(mRunCount, mRunTime, mIsPlaying)) {// The user interacted with the view
					self.automaticResetSolvers();
					self.update();
				}
			}
		});
	};

	/**
	 * Returns the view for this model.
	 * @method getView
	 * @return the view of this model
	 */
	self.getView = function() {
		return mView;
	};
  
	// ------------------------------------------------------------------------
	// Serialize and Unserialize
	// ------------------------------------------------------------------------

	/**
	 * Serialize model
	 * @method serialize
	 * @return json string
	 */
  	self.serialize = function() {
  		var encodedModel = {};
  		if(self._userSerialize) encodedModel = self._userSerialize();
		var encodedView = mView.serialize();
		
		var encoded = {model: encodedModel, view: encodedView};
		
		return JSON.stringify(encoded, function(key, value) {
			if(typeof value == 'number') {
				if (isNaN(value)) value = "__NaN";
				else if (!isFinite(value)) value = "__Infinity";
			}
			return value;
		});				
  	};

	/**
	 * Unserialize model
	 * @method unserialize
	 * @param json string
	 */
  	self.unserialize = function(json) {
		var encoded = JSON.parse(json, function(key, value) {
			if(value == "__NaN") value = NaN;
			else if (value == "__Infinity") value = Infinity;
			return value;
		});		

		if(encoded.model) {
	  		var encodedModel = encoded.model;
	  		if(self._userUnserialize) self._userUnserialize(encodedModel);
		}
		  		
		if(encoded.view) {
			var encodedView = encoded.view;
			mView.unserialize(encodedView);
		}
  	};

	// ------------------------------------------------------------------------
	// This part is used for configuration
	// ------------------------------------------------------------------------

	/**
	 * Sets the running mode.
	 * @method setMode
	 */
	self.setMode = function(mode) {
		self.setRunningMode(mode);
	};


	/**
	 * Sets the running mode.
	 * @method setMode
	 */
	self.setRunningMode = function(mode) {
	  if (typeof mode === 'string') mRunningMode = EJSS_CORE.Model[mode.toUpperCase()];
      else mRunningMode = mode;
	};

	/**
	 * Gets the running mode.
	 * @method getMode
	 */
	self.getMode = function() {
		return mRunningMode;
	};

	/**
	 * Whether the simulation should pause when it looses focus
	 * @method setRunAlways
	 * @param always true if the simulation should run even when focus is lost. Default is false
	 */
	self.setRunAlways = function(always) {
		mRunAlways = always;
	};

    self.setPauseOnPageExit = function(pause) {
		mRunAlways = !pause;
    };
    
	/**
	 * Sets the number of frames per second
	 * @method setFPS
	 */
	self.setFPS = function(fps) {
		mDelay = Math.max(1000 / fps, MINIMUM_DELAY);
	};

	/**
	 * Returns the number of frames per second
	 * @method getFPS
	 */
	self.getFPS = function() {
		return Math.floor(1000.0 / mDelay);
	};
	
	/**
	 * Sets the delay between animation steps
	 * @method setDelay
	 */
	self.setDelay = function(delay) {
		mDelay = Math.max(delay, 0);
	};
	
	/**
	 * Gets the delay between animation steps
	 * @method setDelay
	 */
	self.getDelay = function(delay) {
		return mDelay;
	};
	
	/**
	 * Sets the minimum delay between animation steps
	 * @method setMinimumDelay
	 */
	self.setMinimumDelay = function(delay) {
		mMinimumDelay = Math.max(delay, 0);
	};

	/**
	 * Sets the number of model steps before refreshing the view
	 * @method setStepsPerDisplay
	 * @param steps the number of model steps
	 */
	self.setStepsPerDisplay = function(steps) {
		if (steps >= 1)
			mStepsPerDisplay = steps;
	};

	/**
	 * Sets whether the simulation should update the view in each step
	 * Default is true.
	 * @method setUpdateView
	 * @param mustDo Whether to update the view
	 */
	self.setUpdateView = function(mustDo) {
		mShouldUpdateView = mustDo;
	};

	/**
	 * Sets whether the simulation should be set to play mode when it is reset.
	 * Default is false.
	 * @method setAutoplay
	 * @param play Whether it should play
	 */
	self.setAutoplay = function(play) {
		mAutoplay = play;
	};

	/**
	 * Whether the animation is playing
	 * @method isPlaying
	 */
	self.isPlaying = function() {
		return mIsPlaying;
	};

	/**
	 * Whether the animation is paused
	 * @method isPaused
	 */
	self.isPaused = function() {
		return !mIsPlaying;
	};

	/**
	 * Gets current running count
	 * @method getRunCount 
	 */
	self.getRunCount = function() {
		return mRunCount;
	}
	
	/**
	 * Gets current running time
	 * @method getRunTime 
	 */
	self.getRunTime = function() {
		return mRunTime;
	}

	/**
	 * Initializes running count and time
	 * @method initRunCountAndTime 
	 */
	self.initRunCountAndTime = function() {
		mRunCount = 0;	
		mRunTime = +new Date();
	}

	// -----------------------------
	// Controlling the execution
	// -----------------------------

	/**
	 * Resets the real time
	 * @method resetRealTime
	 * @return
	 */
	self.resetRealTime = function() {
		var now = +Date();
		mInitialRealTime = 1000 * self.getRealTime() - now;
	};

	/**
	 * Starts the animation
	 * @method play
	 */
	self.play = function() {
		if (mIsPlaying) return;

		mLastTime = +new Date();
		// self.resetRealTime();
		mSleepTime = mDelay;
		mIsPlaying = true;
		mCurrentTimeOut = window.setTimeout(self.run, mMinimumDelay);
		//self.run();
	};

	/**
	 * Stops the animation
	 * @method pause
	 */
	self.pause = function() {
		mIsPlaying = false;
		mAbortSPDLoop = true;
		if (mCurrentTimeOut!=0) {
		  window.clearTimeout(mCurrentTimeOut);
		  mCurrentTimeOut = 0;
		}
	};

	/**
	 * Runs the animation
	 * @method run
	 */
	self.run = function() {
		mRunCount++;	// register steps
		mRunTime = +new Date(); // register step time
		
		var timeout = 1; //mCurrentTimeOut;
		var elapsed;
		if (!mIsPlaying) return;
		if (mFocus || mRunAlways) {
		  switch (mRunningMode) {
		    default : 
		    case EJSS_CORE.Model.MODE_JAVA_STYLE :			  
		      self.step();
		      mLastTime = +new Date();
			  elapsed = mLastTime - mRunTime;
			  timeout = Math.max (mMinimumDelay, mDelay - elapsed);
			  break;
		    case EJSS_CORE.Model.MODE_RESPECT_DELAY :
		      self.step();
			  timeout = mDelay;
			  mLastTime = +new Date();
			  break;
			case EJSS_CORE.Model.MODE_RESPECT_TIME : 
			  elapsed = mRunTime - mLastTime;
			  if (elapsed >= mSleepTime - mExcededTime) {
				var step = 0;
				if(mSleepTime) {
				  step = Math.floor((elapsed + mExcededTime)/mSleepTime);
				  mExcededTime = (elapsed + mExcededTime) - (step * mSleepTime);				
				}
				var stepModelTime = self.step(step);
				if(mSleepTime && (stepModelTime > elapsed) && (step > 100)) { // impossible running 
				  alert('Imposible running with delay ' + mSleepTime + " ms.");
				  mIsPlaying = false; // stop model
				  processSafeTasks();
				  mExcededTime = 0;							
				}
					
					// mView.print(step + " " + elapsed + " " + mSleepTime + " " + stepModelTime);
					
					// if (!isNaN(mInitialRealTime))
					//	mSleepTime = (1000 * self.getRealTime() - now - mInitialRealTime);
					// else
				mSleepTime = mDelay;
					//    System.out.println ("Sleep time = "+mSleepTime);
				mLastTime = mRunTime;		// update the last running time
					
			  }
			  break; 
			} // end of switch

			// This is done as quickly as possible
			var thisTimeOut = mCurrentTimeOut;
			self._readCapturedInteractions();
			if (mView._readInteractions(mRunCount, mRunTime, mIsPlaying) || // The user interacted with the view
			    processSafeTasks()) { // there were safe tasks pending
			 self.automaticResetSolvers();
			  self.update();
			}
			// Check if the interaction introduced a new timeout or cancelled the current one.
			if (thisTimeOut!=mCurrentTimeOut) return;
		} // end of if (mFocus) 
		else {			
		  mLastTime = +new Date();
		}
	     // Just in case
		if (mView._readInteractions(mRunCount, mRunTime, mIsPlaying) || // The user interacted with the view
			processSafeTasks()) { // there were safe tasks pending
		  self.automaticResetSolvers();
		  self.update();
		}

		mCurrentTimeOut = window.setTimeout(self.run, timeout);
		// console.log ("Timeout set to "+mCurrentTimeOut+ " for timeout "+timeout);
	};

	self.onExit = function() {
		self.pause();
		mView._readInteractions(mRunCount, mRunTime, mIsPlaying); // In case the user interacted with the view
		processSafeTasks();
		//    mView.onExit();
		self.freeMemory();
	};

	/**
	 * Add a task that must be run only
	 * when the model is not doing any calculation.
	 */
	self.performTaskSafely = function (taskFunction) {
	  if (!mIsPlaying) {
		  console.log ("Calling the task now! ");
		taskFunction();
		self.automaticResetSolvers();
		self.update(true); // Force it
	  }
	  else {
		  console.log ("Delaying the task! ");
		  mPendingTasks.push(taskFunction);
	  }
	}

	/*
	 * For internal use only.
	 * Processed tasks that have been signaled to be run only
	 * when the model is not doing any calculation.
	 * @return true if any task was executed, false otherwise 
	 */
	function processSafeTasks() { // call this when the model computation won't be affected
      if (mPendingTasks.length==0) return false;
	  var tasks = mPendingTasks.slice(); // In case a task modifies the pending tasks
	  mPendingTasks = [];
      for (var i=0; i<tasks.length; i++) tasks[i]();
      return true;
	};
	
	//------------------------------------
	//Simulation logic based on the model
	//------------------------------------

	/**
	 * Resets the animation
	 * @reuseview reuse view
	 * @method reset
	 */
	self.reset = function(reuseview) {
		if(!reuseview) {
			self.pause();
			self.resetModel();
		}
		// stepModel must be defined by the 'subclass'

		mView._setReportNeeded(false);
		mView._reset();
		if(!reuseview) {
			mView._initValues();
			mView._initialize();

			self.initializeModel();
		}
		
		self.updateModel();
		if (mShouldUpdateView) {
			mView._update();
			mView._render();
		} else
			mView._collectData();
		mView._setReportNeeded(true);
		if (mAutoplay)
			self.play();
        mView._resized();
	};

	/**
	 * Initializes the animation
	 * @method initialize
	 */
	self.initialize = function() {
		mView._initialize();

		self.initializeModel();
		self.updateModel();

		if (mShouldUpdateView) {
			mView._update();
			mView._render();
		} else
			mView._collectData();
	};

	/**
	 * Updates the simulation
	 * @method update
	 */	
	self.update = function(forceIt) {
		if(! (mFocus || mRunAlways || forceIt) ) return;

		self.updateModel();
		if (mShouldUpdateView) {
			mView._update();
			mView._render();
		} 
		else mView._collectData();
		
		if (processSafeTasks()) { // do it again
		  self.automaticResetSolvers();
		  self.updateModel();
		  if (mShouldUpdateView) {
		    mView._update();
			mView._render();
		  } 
		  else mView._collectData();
		}
	};
	
	/**
	 * Steps the animation once
	 * @method step
	 * @return stepModel time
	 */
	self.step = function(forceStepsPerDisplay) {	
		var now = +new Date();		
		if (!forceStepsPerDisplay) forceStepsPerDisplay = 1;
		var steps = Math.max(forceStepsPerDisplay,mStepsPerDisplay);
		if (steps > 1) {
			mAbortSPDLoop = false;
			for (var i = 1; i < steps; i++) {
				if (mAbortSPDLoop) {// The user called pause() in between this loop
					self.update();
					return;
				}
				self.stepModel();
				self.updateModel();
				mView._collectData();
			}
		}
		// Now the final step
		self.stepModel();
		var stepModelTime = (+new Date()) - now;
		
		// stepModel must be defined by the 'subclass'
		self.update();
		
		return stepModelTime;
	};

	/**
	 * Sets a break flag
	 * @method setShouldBreak
	 */
	self.setShouldBreak = function(shouldDo) {
		mShouldBreak = shouldDo;
	};

	/**
	 * Returns the break flag
	 * @method getShouldBreak
	 */
	self.getShouldBreak = function() {
		return mShouldBreak;
	};

  	self._autoSelectView = function (viewsInfo) {
      var height = screen.height;
      var width = screen.width;
      
      var sel = [];
      var min = Number.MAX_VALUE;
      for(var i=0; i<viewsInfo.length; i++) { // find the best width
          var dis = width - viewsInfo[i]["width"];
          if(dis >= 0) {
              if(dis < min) {
                  min = dis;
                  sel = [i];
              } else if (dis == min) {
                  sel.push(i);
              }
          }
      }
      var ret = -1;
      min = Number.MAX_VALUE;
      for(var i=0; i<sel.length; i++) { // find the best height
          var dis = Math.abs(height - viewsInfo[sel[i]]["height"]);
        if(dis < min) {
            min = dis;
            ret = sel[i];
        }
      }
      return ret;
  	};

 	self.addToOnload = function(onloadfunc) {
 		mOnloadFunc = onloadfunc;
 	}
 	
 	self.onload = function() {
		if(typeof mOnloadFunc == "function")
			mOnloadFunc();
 	}
  	
	/**
	 * Utility to parse input parameters. 
	 * @method parseInputParameters
	 * @param inputParameters a String with the parameters to parse
	 */
	self.parseInputParameters = function(inputParameters) {
		if (typeof inputParameters === 'string' || inputParameters instanceof String) { 
			// The EjsAPP Moodle plug-in codifies them
			try {
			  inputParameters = EJSS_TOOLS.Decode.hex_to_ascii(inputParameters);
			  inputParameters = EJSS_TOOLS.Decode.decode(inputParameters);
			  if      (inputParameters[0]=="'" && inputParameters[inputParameters.length-1]=="'") inputParameters = inputParameters.substring(1,inputParameters.length-1);
			  else if (inputParameters[0]=='"' && inputParameters[inputParameters.length-1]=='"') inputParameters = inputParameters.substring(1,inputParameters.length-1);
			  inputParameters = JSON.parse(inputParameters);
			}
			catch (exception) {
				console.log("Error trying to parse input parameters: "+inputParameters);
				return null;
			}
		}
		return inputParameters;
	};

	//---------------------------------
	// To be modified or overwritten by subclasses
	//---------------------------------

	var mResetList = [];
	var mInitializationList = [];
	var mEvolutionList = [];
	var mFixedRelationsList = [];

	self.getRealTime = function() { return NaN; };
	self.automaticResetSolvers = function() { };
	self.freeMemory = function() { };

	/**
	 * Adds a function to the reset part of the model
	 * @method _addToReset
	 * @param aFunction the function to add
	 */
	self.addToReset = function(aFunction) {
		mResetList.push(aFunction);
	};

	/**
	 * Adds a function to the initialization part of the model
	 * @method _addToReset
	 * @param aFunction the function to add
	 */
	self.addToInitialization = function(aFunction) {
		mInitializationList.push(aFunction);
	};

	/**
	 * Adds a function to the evolution part of the model
	 * @method _addToReset
	 * @param aFunction the function to add
	 */
	self.addToEvolution = function(aFunction) {
		mEvolutionList.push(aFunction);
	};

	/**
	 * Adds a function to the fixed relations part of the model
	 * @method _addToReset
	 * @param aFunction the function to add
	 */
	self.addToFixedRelations = function(aFunction) {
		mFixedRelationsList.push(aFunction);
	};

	 self.addFixedRel = function(code){_model.addToFixedRelations(function() { eval(code);});};
	 
	/**
	 * Resets the model
	 */
	self.resetModel = function() {
		processFunctionList(mResetList);
	};
	
	/**
	 * Initializes the model
	 */
	self.initializeModel = function() {
		processFunctionList(mInitializationList);
	};
	
	/**
	 * Steps the model
	 */
	self.stepModel = function() {
		processFunctionList(mEvolutionList);
	};
	
	/**
	 * Updates the model
	 */
	self.updateModel = function() {
		processFunctionList(mFixedRelationsList);
	};

	function processFunctionList(functionList) {
		for (var i = 0, n = functionList.length; i < n; i++) {
			mShouldBreak = false;
			functionList[i]();
			if (mShouldBreak)
				return;
		}
	}
	
	//---------------------------------
	// final initialization
	//---------------------------------

	window.onblur  = function() { 
		mFocus = false; 
		if (mView) mView._onBlur();
    };
	
    window.onfocus = function() { 
		mFocus = true;  
		if (mView) mView._onFocus();
    };
    
	var _super_onorientationchange = window.onorientationchange;
	window.onorientationchange = function() {		
		if(_super_onorientationchange) _super_onorientationchange();
	    if (mView) mView._resized();
	};
	
	// resize
	var _super_onresize = window.onresize; 
	window.onresize = function() {
		if(_super_onresize) _super_onresize();		
	    if (mView) mView._resized();
	};
	
	return self;

};

/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García 
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for Animations
 * @module core
 */

var EJSS_CORE = EJSS_CORE || {};

/**
 * Creates an animation for a model.
 * The model must implement the function:
 * - stepModel()  : steps the model for each animation frame
 * and have a view.
 * @method createAnimation
 */
EJSS_CORE.createAnimationLMS = function() {
	var self = EJSS_CORE.createAnimation();
    var SAVETEXT_Q = "Choose a name for the file";
    var READFILE_Q = "Select a file to read";

	// --- Configuration variables
	
	// ------------------------------------------------------------------------
	// States functions (UNED - Luis de la Torre)
	// ------------------------------------------------------------------------
    
    var context_id;
    var user_id;
    var ejsapp_id;
    var moodle_upload_file;
    var moodle_send_file_list;

    self.sendCallback = function() {};
    
    self.setStatusParams = function(_context_id, _user_id, _ejsapp_id, _moodle_upload_file, _moodle_send_file_list, _moodle_callback) {
    	context_id = _context_id;
    	user_id = _user_id;
    	ejsapp_id = _ejsapp_id;
    	moodle_upload_file = _moodle_upload_file;
    	moodle_send_file_list = _moodle_send_file_list;
    	if(_moodle_callback) self.sendCallback = _moodle_callback;
    	self.declareRegisterFuncs_UNED();
    };
    
    self.getUserID = function() {
    	return user_id;
	};

    self.getContextID = function() {
        return context_id;
    };

    self.getActivityID = function() {
        return ejsapp_id;
    };
        
    self.sendSnapshot = function(svgpanel, user_file, callback_ok, callback_error) {
    	if(!moodle_upload_file) return;

    	svgpanel.importGraphics(function(png) {
			var http = new XMLHttpRequest();
			var params = "user_file="+user_file+"&file="+encodeURIComponent(png)+"&type=png"+"&context_id="+context_id+"&user_id="+user_id+"&ejsapp_id="+ejsapp_id;
			http.open("POST", moodle_upload_file, true);

			//Send the proper header information along with the request
			http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

			http.onreadystatechange = function() {//Call a function when the state changes.
			    if(http.readyState == 4 && http.status == 200) {
			        if(callback_ok) callback_ok();
			    } else {
			    	if(callback_error) callback_error();
			    }
			};
			http.send(params);
	     });
    };
    
    self.sendState = function(user_file, callback_ok, callback_error) {
    	if(!moodle_upload_file) return;

    	var json = self.serialize();
		var http = new XMLHttpRequest();
		var params = "user_file="+user_file+"&file="+encodeURIComponent(json)+"&type=json"+"&context_id="+context_id+"&user_id="+user_id+"&ejsapp_id="+ejsapp_id;
		http.open("POST", moodle_upload_file, true);

		//Send the proper header information along with the request
		http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

		http.onreadystatechange = function() {//Call a function when the state changes.
		    if(http.readyState == 4 && http.status == 200) {
		        if(callback_ok) callback_ok();
		    } else {
		    	if(callback_error) callback_error();
		    }
		};
		http.send(params);
    };

    self.sendText = function(user_file, content, type, callback_ok, callback_error) {
    	if(!moodle_upload_file) return;

		var http = new XMLHttpRequest();
		var params = "user_file="+user_file+"&file="+encodeURIComponent(content)+"&type="+type+"&context_id="+context_id+"&user_id="+user_id+"&ejsapp_id="+ejsapp_id;
		http.open("POST", moodle_upload_file, true);

		//Send the proper header information along with the request
		http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

		http.onreadystatechange = function() {//Call a function when the state changes.
		    if(http.readyState == 4 && http.status == 200) {
		        if(callback_ok) callback_ok();
		    } else {
		    	if(callback_error) callback_error();
		    }
		};
		http.send(params);
    };

    self.getState = function(url, callback_ok, callback_error) {
		var http = new XMLHttpRequest();
		http.open("GET", url, true);
		http.setRequestHeader("Cache-Control", "no-cache");
		
		http.onreadystatechange = function() {//Call a function when the state changes.
		    if(http.readyState == 4 && http.status == 200) {
		    	self.unserialize(http.responseText);
		        if(callback_ok) callback_ok();
		    } else {
		    	if(callback_error) callback_error();
		    }
		};
		http.send();
   };

    self.getText = function(url, callback_ok, callback_error) {
		var http = new XMLHttpRequest();
		http.open("GET", url, true);
		http.setRequestHeader("Cache-Control", "no-cache");
		
		http.onreadystatechange = function() {//Call a function when the state changes.
		    if(http.readyState == 4 && http.status == 200) {
		        if(callback_ok) callback_ok(http.responseText);
		    } else {
		    	if(callback_error) callback_error();
		    }
		};
		http.send();
   };

    self.getStateFiles = function(type, callback_ok, callback_error) {
    	if(!ejsapp_id) return;

		var http = new XMLHttpRequest();
		var get_url = moodle_send_file_list + "?ejsapp_id="+ejsapp_id+"&type="+type;
		http.open("GET", get_url, true);

		http.onreadystatechange = function() {//Call a function when the state changes.
		    if(http.readyState == 4 && http.status == 200) {
                var list = {};
                list["name"] = [];
                list["url"] = [];
                var data = JSON.parse(http.responseText);
                for (var i = 0; i < data.file_names.length; i++) {
                    list["name"].push(data.file_names[i].file_name);
                    list["url"].push(data.file_paths[i].file_path);
                }
		        if(callback_ok) callback_ok(list);
		    } else {
		    	if(callback_error) callback_error();
		    }
		};
		http.send();
    };
    
    self.saveText = function(name,type,content) {
        if (!content) { // then the second parameter is actually content, not type
            var content = type;
            type = null;
        }
    	if(context_id)
			EJSS_INTERFACE.BoxPanel.showInputDialog(SAVETEXT_Q, function(name) {
	  			self.sendText(name,content,type,function(){self.sendCallback();});
			});
		else
			self.sendText(name,content,type,function(){self.sendCallback();});
    };
    
    self.saveState = function(name) {
    	if(context_id) 
			EJSS_INTERFACE.BoxPanel.showInputDialog(SAVETEXT_Q, function(name) {
	  			self.sendState(name,function(){self.sendCallback();});
			});
		else     	    	
			self.sendState(name,function(){self.sendCallback();});
    };
    
    self.saveImage = function(name,panelname) {
    	var view = self.getView();
   		if(context_id) 
			EJSS_INTERFACE.BoxPanel.showInputDialog(SAVETEXT_Q, function(name) {
			  self.sendSnapshot(view[panelname], name, function(){self.sendCallback();});
			});    	
		else     	    	
			self.sendSnapshot(view[panelname], name, function(){self.sendCallback();});
    };
    
    self.readState = function(url,type) {
    	if(!url) {
    		var tp = (typeof type !== "undefined")? type:".json";
			self.getStateFiles(tp,function(lf){
			    var options = {text: lf.name, value: lf.url};
			    EJSS_INTERFACE.BoxPanel.showSelectDialog(READFILE_Q,options, function(url) {
			      self.getState(url);
			    });
			});
		} else    	
			self.getState(url);
    };
    
    self.readText = function(url,type,varname_or_callback) {
    	if(!url) {
			self.getStateFiles(type,function(lf){
			    var options = {text: lf.name, value: lf.url};
			    EJSS_INTERFACE.BoxPanel.showSelectDialog(READFILE_Q,options, function(url) {
			      self.getText(url,function(text){
			      	if(typeof varname_or_callback === "function") {
			      		varname_or_callback(text);	
			      	} else {
				      	var json = {};
				      	json[varname_or_callback] = text;
				      	self._userUnserialize(json);			      		
			      	}
			      });
			    });
			});
		} else    	
			self.getText(url,function(text){
		      	if(typeof varname_or_callback === "function") {
					varname_or_callback(text);	      		
		      	} else {
			      	var json = {};
			      	json[varname_or_callback] = text;
			      	self._userUnserialize(json);			
		      	}
			});
   };

	// ------------------------------------------------------------------------
	// Interactions functions (UNED - Luis de la Torre)
	//	CaptureStream functions: save interactions data in remote server (interoperability)
	// 	Capture functions: save interactions data in local (save experience)
	// ------------------------------------------------------------------------

	var mUserEvents = [];
	var mWebSocket;
	var mEventSource;
	var _stylesBak = {};
	var _isPlayingCaptureStream = false;
	
	self.getEventInteractionsStream = function(event) {
	    var target = event.target || event.srcElement;		

		var myevent = {"pageX": event.pageX, "pageY": event.pageY, 
			"offsetLeft": target.offsetLeft, "offsetTop": target.offsetTop,
			"target": target.id, "timeStamp": +new Date()}; 
			// "timeStamp": event.timeStamp}; the timestamp might be relative! (it depends on the browser)

		myevent["runCount"] = self.getRunCount();
   		myevent["runTime"] = self.getRunTime();   	
   		myevent["type"] = event.type;			   					
		myevent["event"] = "touch";
		
		mWebSocket.send(JSON.stringify(myevent));
	};

	self.startCaptureStream = function(port,win,fail,open,close) {
		// build uri
		var loc = window.location;
		var uri = (loc.protocol === "https:")? "wss:":"ws:";
		uri += "//" + loc.host + ":" + port;
		// uri += loc.pathname + "/to/ws";	
			
	  	try {
			mWebSocket = new WebSocket(uri);
			if(typeof win !== "undefined") win();
 			
 			mWebSocket.onopen = function () {
				self.initRunCountAndTime(); 				
				self.getView().registerInteractions(mWebSocket, false);
				
				document.addEventListener('mousemove', self.getEventInteractionsStream);
				document.addEventListener('touchmove', self.getEventInteractionsStream);
				document.addEventListener('mousedown', self.getEventInteractionsStream);
				document.addEventListener('touchstart', self.getEventInteractionsStream);
 			}
            mWebSocket.onclose = function(evt) {
            	self.stopCaptureStream();
            	close(evt);
            };
            mWebSocket.onmessage = function (evt) { 
              console.log("Message is received: " + evt.data);
           };				
		}
		catch(exception) {
			if(typeof fail !== "undefined") fail(exception);
			return;
		}
	};

	self.stopCaptureStream = function() {
	  	try { mWebSocket.close(); } catch(exception) {}

		self.getView().unregisterInteractions();

		document.removeEventListener('mousemove', self.getEventInteractionsStream);
		document.removeEventListener('touchmove', self.getEventInteractionsStream);
		document.removeEventListener('mousedown', self.getEventInteractionsStream);
		document.removeEventListener('touchstart', self.getEventInteractionsStream);
	};

	self.playCaptureStream = function(uri,win,fail,open,close) {
		var FREC = 10;
		
       	if(_isPlayingCaptureStream) return;
       	_isPlayingCaptureStream = true;
       	 
       	var captured = [];
		
		// start event source
		// var lastCapturedTime = 0;
	  	try {
			mEventSource = new EventSource(uri);
			if(typeof win !== "undefined") win();
 			
 			mEventSource.onopen = open;
            mEventSource.onclose = function(evt) {
				var s = document.getElementById("_my_mouse");   
				if(s) document.body.removeChild(s);            	
            	close(evt);
            };
            mEventSource.onmessage = function (evt) {
              	var alldata = JSON.parse(evt.data);
              	console.log("Now: " + Date.now() + "-" + alldata.timeStamp);
				captured = captured.concat(alldata.interactions);
           };			
		}
		catch(exception) {
			if(typeof fail !== "undefined") fail(exception);
			return;
		}

		// start play capture stream
		setTimeout(function() {

		// set captured interactions
		self.initRunCountAndTime();
		_capturedInteractions = [];

		var lastTime = 0;
		var ixCaptured = 0;

		var crun = 0;
		var ctime = 0;
		var ltime = 0;
		var diffLeft = 0;
		var diffTop = 0;

		var myTimer = setInterval(function() {
			var view = self.getView();
			var ix = ixCaptured;
			while (ix < captured.length) {
				var data = captured[ix++];
				
				if (typeof data["isPlaying"] !== "undefined" && data["isPlaying"]) { // isPlaying
	              	_capturedInteractions.push(data);	
	              	ixCaptured++;              	
	            } else if(data.runCount == crun && ctime >= data.timeStamp - data.runTime 
	            			&& ltime <= data.timeStamp - data.runTime) {
	              	ixCaptured++;
	              	ltime = data.timeStamp - data.runTime;
	              	
	              	//console.log(" -timestamps (now-server-created): " + 
	              	//	Date.now() + "-" + data.timeStamp);
	
					// styles bak
					for(var i in _stylesBak) {
						var element = view[i];
						element.getDOMElement().style.border = _stylesBak[i];
					}
					_stylesBak = {};
	
	              	if(typeof data.event !== "undefined") {
						// do event
						var x = data.pageX;
						var y = data.pageY;
						var target = data.target;
						if(typeof target !== "undefined" && target.length > 0) {
							var element = document.getElementById(target);
							if(element != null) {
								diffLeft = data.offsetLeft - element.offsetLeft;
								diffTop = data.offsetTop - element.offsetTop;								
							}
						}
						x -= diffLeft;
						y += diffTop;
						
						var s = document.getElementById("_my_mouse");
						if(!s) {
							var s = document.createElement('div');
							s.id = "_my_mouse";
							s.style.position = 'absolute';
						  	s.style.margin = '0';
						  	s.style.border = '3px solid red';
						  	document.body.appendChild(s);					
						}
						s.style.left  = x + 'px';
						s.style.top = y + 'px';              		
	              	} else if(typeof data.property !== "undefined") { // property
						var element = view[data.element];
						element.propertyChanged(data.property,data.data).reportInteractions();
					} else if(typeof data.action !== "undefined") { // action
						var element = view[data.element];
						element.invokeAction(data.action).reportInteractions();
						if(typeof _stylesBak[element.getName()] == "undefined") 
							_stylesBak[element.getName()] = element.getDOMElement().style.border;
						element.getDOMElement().style.border = '1px solid red';							
	          		}
				}				
			}
			
			// update time
			var runCount = self.getRunCount();
			if(crun != runCount) {
				crun = runCount;
				ctime = 0;
				ltime = 0;
			} else {
				ctime += FREC;
			}
			
			if(!_isPlayingCaptureStream) {
				clearInterval(myTimer);
			}
		}, FREC);

			
		}, 1000);
	};

	self.pauseCaptureStream = function() {
		try { mEventSource.close(); } catch(exception) {}
		_isPlayingCaptureStream = false;
	};
	
	self.getEventInteractions = function(event) {
	    var target = event.target || event.srcElement;		

		var myevent = {"pageX": event.pageX, "pageY": event.pageY, 
			"offsetLeft": target.offsetLeft, "offsetTop": target.offsetTop,
			"target": target.id, "timeStamp": +new Date()}; 
			// "timeStamp": event.timeStamp}; the timestamp might be relative! (it depends on the browser)

		myevent["runCount"] = self.getRunCount();
   		myevent["runTime"] = self.getRunTime();   	
   		myevent["type"] = event.type;			
		myevent["event"] = "touch";

		mUserEvents.push(myevent);
	};

	self.startCapture = function() {	
		self.initRunCountAndTime();
		self.getView().registerInteractions(false, false);
		
		mUserEvents = [];
		document.addEventListener('mousemove', self.getEventInteractions);
		document.addEventListener('touchmove', self.getEventInteractions);
		document.addEventListener('mousedown', self.getEventInteractions);
		document.addEventListener('touchstart', self.getEventInteractions);
	};
	
	self.getCapture = function() {		
		var interactions = {"interactions": self.getView().getRegInteractions(), "events": mUserEvents};
		// console.log(JSON.stringify(interactions));
		return interactions;
	}
		
	self.stopCapture = function() {
		self.getView().unregisterInteractions();

		document.removeEventListener('mousemove', self.getEventInteractions);
		document.removeEventListener('touchmove', self.getEventInteractions);
		document.removeEventListener('mousedown', self.getEventInteractions);
		document.removeEventListener('touchstart', self.getEventInteractions);
		
		return self.getCapture();
	};
	
	self._readCapturedInteractions = function() {
		var view = self.getView();
		// read captured interactions
		var n = _capturedInteractions.length;
		for(var i = 0; i < n; i++) {
			var inter = _capturedInteractions[i];
			if(inter["runCount"] == self.getRunCount() && inter["isPlaying"]) {  
				if(inter.property !== "undefined") { // property
					var element = view[inter.element];
					element.propertyChanged(inter.property,inter.data).reportInteractions();
				}
				if(inter.action !== "undefined") { // action
					var element = view[inter.element];
					element.invokeAction(inter.action).reportInteractions();
					if(typeof _stylesBak[element.getName()] == "undefined") 
						_stylesBak[element.getName()] = element.getDOMElement().style.border;
					element.getDOMElement().style.border = '1px solid red';							
				}
				inter["runCount"] = -1; // done
			}
		}
	};
	
	var _isPlayingCapture = false;
	var _resetPlayingCapture = false;
	var _stepPlayingCapture = 1;
	var _isPlayingBeforePause = false;
	var _capturedInteractions = [];
	self.playCapture = function(captured, callback, step) {
		var FREC = 10;
		var actions = captured.interactions;
		var lactions = actions.length;
		var caction = 0;
		var events = captured.events;
		var levents = events.length;
		var cevent = 0;

		if(_isPlayingCapture) return;
		_isPlayingCapture = true;
		_resetPlayingCapture = false;
		self.reset();
				
		// set step in playing
		if(typeof step !== "undefined") _stepPlayingCapture = step;
		self.setDelay(self.getDelay() / _stepPlayingCapture); 

		// set captured interactions
		self.initRunCountAndTime();
		_capturedInteractions = actions;
		
		// start playing
		var crun = 0;
		var ctime = 0;
		var diffLeft = 0;
		var diffTop = 0;
		var myTimer = setInterval(function() {
			var view = self.getView();
			
			// styles bak
			for(var i in _stylesBak) {
				var element = view[i];
				element.getDOMElement().style.border = _stylesBak[i];
			}
			_stylesBak = {};

			// play capture
			if(_isPlayingCapture) {

				while(caction < lactions && actions[caction]["runCount"] <= crun
					&& ctime >= actions[caction].timeStamp - actions[caction].runTime) {
					// do action
					// console.log("Action: " + JSON.stringify(actions[caction]));
					if(!actions[caction]["isPlaying"]) {  
						if(typeof actions[caction].property !== "undefined") { // property
							var element = view[actions[caction].element];
							element.propertyChanged(actions[caction].property,actions[caction].data).reportInteractions();
						}
						if(typeof actions[caction].action !== "undefined") { // action
							var element = view[actions[caction].element];
							element.invokeAction(actions[caction].action).reportInteractions();
							if(typeof _stylesBak[element.getName()] == "undefined") 
								_stylesBak[element.getName()] = element.getDOMElement().style.border;
							element.getDOMElement().style.border = '1px solid red';							
						}
					}
					caction++;
				}
				
				while(cevent < levents && events[cevent]["runCount"] <= crun
					&& ctime >= events[cevent].timeStamp - events[cevent].runTime) {
					// do event
					var x = events[cevent].pageX;
					var y = events[cevent].pageY;
					var target = events[cevent].target;
					if(typeof target !== "undefined" && target.length > 0) {
						var element = document.getElementById(target);
						if(element != null) {
							diffLeft = events[cevent].offsetLeft - element.offsetLeft;
							diffTop = events[cevent].offsetTop - element.offsetTop;
						}
					}
					x -= diffLeft;
					y += diffTop;
					
					var s = document.getElementById("_my_mouse");
					if(!s) {
						var s = document.createElement('div');
						s.id = "_my_mouse";
						s.style.position = 'absolute';
					  	s.style.margin = '0';
					  	s.style.border = '3px solid red';
					  	document.body.appendChild(s);					
					}
					s.style.left  = x + 'px';
					s.style.top = y + 'px';
					 
					cevent++;
				}
				
				// update time
				var runCount = self.getRunCount();
				if(crun != runCount) {
					crun = runCount;
					ctime = 0;
				} else {
					ctime += FREC * _stepPlayingCapture;
				}
				
				if(cevent >= levents && caction >= lactions) {
					var s = document.getElementById("_my_mouse");   
					if(s) document.body.removeChild(s);
					clearInterval(myTimer);
					_isPlayingCapture = false;
					self.setDelay(self.getDelay() * _stepPlayingCapture); 
					_capturedInteractions = [];
					callback();
				}
			}
			if(_resetPlayingCapture) {
				clearInterval(myTimer);
				_isPlayingCapture = false;
				self.setDelay(self.getDelay() * _stepPlayingCapture); 
				_capturedInteractions = [];
				callback();
			}
			
		}, FREC);
	};

	self.pauseCapture = function() {
		_isPlayingBeforePause = self.isPlaying();
		self.pause();
		_isPlayingCapture = false;
	};
	
	self.resumeCapture = function() {
		if(_isPlayingBeforePause) self.play();
		_isPlayingCapture = true;
	};

	self.resetCapture = function() {
		_resetPlayingCapture = true;
	};

	self.changeCaptureStep = function(step) {
		self.setDelay(self.getDelay() * _stepPlayingCapture); 
		_stepPlayingCapture = step;
		self.setDelay(self.getDelay() / _stepPlayingCapture); 
	};
	
	// ------------------------------------------------------------------------
	// More interactions functions (UNED - Luis de la Torre)
	//	Register functions: save interactions, model calls, ... in local (learning analytics)
	// ------------------------------------------------------------------------

	// Only declared when moodle server
	self.declareRegisterFuncs_UNED = function() {
		var mRegModelCalls = [];
		var mRegUserEvents = [];
		var mIsRegModelCalls = false;

		self.pushRegModelCalls = function(func) {
			var mycall = {"function": func, "timeStamp": +new Date()};
			mycall["runCount"] = self.getRunCount();
	   		mycall["runTime"] = self.getRunTime();   				
	
			mRegModelCalls.push(mycall);		
		}

		self.pushRegUserEvents = function(event) {
		    var target = event.target || event.srcElement;
			var myevent = {"pageX": event.pageX, "pageY": event.pageY, 
				"offsetLeft": target.offsetLeft, "offsetTop": target.offsetTop,
				"target": target.id, "timeStamp": +new Date()}; 
				// "timeStamp": event.timeStamp}; the timestamp might be relative! (it depends on the browser)
	
			myevent["runCount"] = self.getRunCount();
	   		myevent["runTime"] = self.getRunTime();   	
	   		myevent["type"] = event.type;			
			myevent["event"] = "touch";
	
			mRegUserEvents.push(myevent);
		}
	
		self.startRegister = function(events) {	
			if (!mIsRegModelCalls) {
				mIsRegModelCalls = true;
				self.initRunCountAndTime();
				self.getView().registerInteractions(false, false);
				mRegModelCalls = [];				
				mRegUserEvents = [];
				if(events) {
					document.addEventListener('mousemove', self.pushRegUserEvents);
					document.addEventListener('touchmove', self.pushRegUserEvents);
					document.addEventListener('mousedown', self.pushRegUserEvents);
					document.addEventListener('touchstart', self.pushRegUserEvents);			
				}			
			}
		}
		
		self.getRegister = function(empty) {	
			var interactions = {"interactions": self.getView().getRegInteractions(empty), 
				"model": mRegModelCalls, "events": mRegUserEvents};
			// console.log(JSON.stringify(interactions));
			if(empty) {
				mRegModelCalls = [];
				mRegUserEvents = [];
			}
			return interactions;
		}
			
		self.stopRegister = function() {
			self.getView().unregisterInteractions();
			mIsRegModelCalls = false;
			
			document.removeEventListener('mousemove', self.pushRegUserEvents);
			document.removeEventListener('touchmove', self.pushRegUserEvents);
			document.removeEventListener('mousedown', self.pushRegUserEvents);
			document.removeEventListener('touchstart', self.pushRegUserEvents);
			
			return self.getRegister();
		}
		
	    self.sendRegister = function(empty, callback_ok, callback_error) {	
	    	if(!moodle_upload_file) return;
	    	
			var json = JSON.stringify(self.getRegister(empty), function(key, value) {
				if(typeof value == 'number') {
					if (isNaN(value)) value = "__NaN";
					else if (!isFinite(value)) value = "__Infinity";
				}
				return value;
			});				
	
			var http = new XMLHttpRequest();
			var params = "file="+encodeURIComponent(json)+"&type=actions"+
				"&user_id="+user_id+"&ejsapp_id="+ejsapp_id;
			http.open("POST", moodle_upload_file, true);
	
			//Send the proper header information along with the request
			http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	
			http.onreadystatechange = function() {//Call a function when the state changes.
			    if(http.readyState == 4 && http.status == 200) {
			        if(callback_ok) callback_ok();
			    } else {
			    	if(callback_error) callback_error();
			    }
			};
			http.send(params);
	    }
	 }
	    	
	// ------------------------------------------------------------------------
	// Interactions functions (Loo Kang - Singapore)
	//	Register functions: save interactions, model calls, ... in local (learning analytics)
	// ------------------------------------------------------------------------

	// Only declared when moodle server
	var _sg_url = new URL(document.location);
	var _sg_view_id = (typeof _sg_url.searchParams != 'undefined')?_sg_url.searchParams.get('view_id'):false; 
	if (_sg_view_id) {
		var _sg_user_id = _sg_url.searchParams.get('user_id');
		var _sg_wstoken = _sg_url.searchParams.get('wstoken');
	    var _sg_moodle_url = _sg_url.searchParams.get('url');
		var _sg_wsfunction = _sg_url.searchParams.get('wsfunction');
	
		var mRegModelCalls = [];
		var mRegUserEvents = [];
		var mIsRegModelCalls = false;
		
		self.pushRegModelCalls = function(func) {
			var mycall = {"function": func};
			mRegModelCalls.push(mycall);		
		}
	    
		self.pushRegUserEvents = function(event) {
		    var target = event.target || event.srcElement;		
			var myevent = {"type": event.type, "target": target.id}; 
			mRegUserEvents.push(myevent);
		};
	
		self.startRegister = function(events) {	
			if (!mIsRegModelCalls) {
				mIsRegModelCalls = true;
				self.getView().registerInteractions(false, false);
				self.getView().setShortRegInteractions(true);
				mRegModelCalls = [];
				mRegUserEvents = [];
				if(events) {
					document.addEventListener('mousedown', self.pushRegUserEvents);
					document.addEventListener('touchstart', self.pushRegUserEvents);			
				}			
			}
		};
		
		self.isRegisterStarted = function() {
			return mIsRegModelCalls;
		};
		
		self.getRegister = function(empty) {	
			var interactions = {
				"interactions": self.getView().getRegInteractions(empty), 
				"model": mRegModelCalls, 
				"events": mRegUserEvents };
			// console.log(JSON.stringify(interactions));
			if(empty) {
				mRegModelCalls = [];
				mRegUserEvents = [];
			}
			return interactions;
		};
			
		self.stopRegister = function() {
			self.getView().unregisterInteractions();
			mIsRegModelCalls = false;
			
			document.removeEventListener('mousedown', self.pushRegUserEvents);
			document.removeEventListener('touchstart', self.pushRegUserEvents);
			
			return self.getRegister();
		};
		
	    self.sendRegister = function(empty, callback_ok, callback_error) {
	    	if(!_sg_moodle_url) return;
	
			var userdata = self.getRegister(empty);
			if (userdata.interactions.length == 0 && 
				userdata.model.length == 0 &&
				userdata.events.length == 0) 
				return; // nothing to send
			
			var json = JSON.stringify(userdata, function(key, value) {
				if(typeof value == 'number') {
					if (isNaN(value)) value = "__NaN";
					else if (!isFinite(value)) value = "__Infinity";
				}
				return value;
			});
			
			var http = new XMLHttpRequest();
			var params = "view_id="+_sg_view_id+"&info="+encodeURIComponent(json)+"&user_id="+_sg_user_id;
			http.open("POST", _sg_moodle_url + "?wstoken=" + _sg_wstoken + "&wsfunction=" + _sg_wsfunction, true);
	
			//Send the proper header information along with the request
			http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	
			http.onreadystatechange = function() {//Call a function when the state changes.
			    if(http.readyState == 4 && http.status == 200) {
			        if(callback_ok) callback_ok();
			    } else {
			    	if(callback_error) callback_error();
			    }
			};
			http.send(params);
	    };


		// sending info to moodle		
		setInterval(function(){ 
			if (_model) {
				if(!_model.isRegisterStarted()) _model.startRegister();		
				_model.sendRegister(true);
			}
		}, 3000); 
	}
    
	// ------------------------------------------------------------------------
	// Extending model functions
	// ------------------------------------------------------------------------
	 
	self.___play = self.play;   
	self.play = function() {
		if(mIsRegModelCalls) self.pushRegModelCalls("play");
		self.___play();
	}    
	
	self.___pause = self.pause;   
	self.pause = function() {
		if(mIsRegModelCalls) self.pushRegModelCalls("pause");
		self.___pause();
	}
	
	self.___reset = self.reset;   
	self.reset = function(reuseview) {
		if(mIsRegModelCalls) self.pushRegModelCalls("reset");
		self.___reset();	
    }

	self.___initialize = self.initialize;
	self.initialize = function() {
		if(mIsRegModelCalls) self.pushRegModelCalls("initialize");
		self.___initialize();
    }
    
    
    return self;
	
};

/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.Bootstrap2IntervalData = {
};

/**
 * Constructor for Bootstrap2IntervalData
 * @returns A Bootstrap2IntervalData
 */
EJSS_ODE_INTERPOLATION.bootstrap2IntervalData = function(aState, aRate, bState, bRate, ode) {
  var self = EJSS_ODE_INTERPOLATION.bootstrapIntervalData(aState,aRate,bState,bRate,ode); // reference returned
    
  var mBt2_c2;
  var mBt2_c3;
  var mBt2_c4;
  var mBt2_c5;
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    var deltaTime = self.getDeltaTime();
    var leftState = self.getLeftState();
    var leftRate  = self.getLeftRate();
	var step = (time - self.getLeft())/deltaTime;
    return leftState[index] + step*(deltaTime*leftRate[index] + step*(mBt2_c2[index] + step*(mBt2_c3[index] + step*(mBt2_c4[index]+step*mBt2_c5[index]))));
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    self.bootstrap2((time - self.getLeft())/self.getDeltaTime(), state, beginIndex, length);
    return state; 
  }

  self.bootstrap2 = function(step, state, beginIndex, length) {
    var deltaTime = self.getDeltaTime();
    var leftState = self.getLeftState();
    var leftRate  = self.getLeftRate();
    var index = beginIndex;
	for (var i=0; i<length; i++) {
      state[i] = leftState[index] + step*(deltaTime*leftRate[index] + step*(mBt2_c2[index] + step*(mBt2_c3[index] + step*(mBt2_c4[index]+step*mBt2_c5[index]))));
      index++;
	}
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

    // Coefficients of the second bootstrap
    var BETA1 = 0.7;
    var BETA2 = 0.85;
    var bt2_den = 2*BETA1*(BETA1-1)*BETA2*(BETA2-1)*(BETA2-BETA1)*(10*BETA1*BETA2-5*BETA2-5*BETA1+3);
    var bt2_cf11 = BETA1*(-3*BETA1+2);
    var bt2_cf01 = BETA1*(-3*BETA1+4)-1; 
    var bt2_cys1 = 6*BETA1*(BETA1-1);
    var bt2_cf12 = BETA2*(-3*BETA2+2);
    var bt2_cf02 = BETA2*(-3*BETA2+4)-1; 
    var bt2_cys2 = 6*BETA2*(BETA2-1);
    var bt2_m44 = BETA1*(2+BETA1*(-6+4*BETA1));
    var bt2_m54 = BETA2*(2+BETA2*(-6+4*BETA2));
    var bt2_m45 = BETA1*(4+BETA1*(-9+5*BETA1*BETA1));
    var bt2_m55 = BETA2*(4+BETA2*(-9+5*BETA2*BETA2));
    
    var dimension = aState.length;
    var timeIndex = dimension-1;
    var deltaTime = self.getDeltaTime();
  
    var state_bt2 = new Array(dimension);
    var rate_bt21 = new Array(dimension);
    var rate_bt22 = new Array(dimension);
    mBt2_c2 = new Array(dimension);
    mBt2_c3 = new Array(dimension);
    mBt2_c4 = new Array(dimension);
    mBt2_c5 = new Array(dimension);
    
    self.bootstrap1(BETA1,state_bt2,0,timeIndex);
    state_bt2[timeIndex] = self.getLeft() + BETA1*deltaTime;
    ode.getRate(state_bt2, rate_bt21);

    self.bootstrap1(BETA2,state_bt2,0,timeIndex);
    state_bt2[timeIndex] = self.getLeft()  + BETA2*deltaTime;
    ode.getRate(state_bt2, rate_bt22);

    for (var i=0; i<dimension; i++) {
      var dif = bState[i]-aState[i];
      var f0 = deltaTime*aRate[i];
      var f1 = deltaTime*bRate[i];
      var e1 = deltaTime*rate_bt21[i] + bt2_cf11*f1 + bt2_cf01*f0 + bt2_cys1*dif;
      var e2 = deltaTime*rate_bt22[i] + bt2_cf12*f1 + bt2_cf02*f0 + bt2_cys2*dif;
      var c4 = (bt2_m55*e1 - bt2_m45*e2)/bt2_den;
      var c5 = (bt2_m44*e2 - bt2_m54*e1)/bt2_den;
      var c3 = f1 + f0 - 2*dif - 2*c4 - 3*c5;
      mBt2_c5[i] = c5;
      mBt2_c4[i] = c4;
      mBt2_c3[i] = c3;
      mBt2_c2[i] = dif - f0 - c3 - c4 - c5;
    }
 
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.BootstrapIntervalData = {
};

/**
 * Constructor for BootstrapIntervalData
 * @returns A BootstrapIntervalData
 */
EJSS_ODE_INTERPOLATION.bootstrapIntervalData = function(aState, aRate, bState, bRate, ode) {
  var self = EJSS_ODE_INTERPOLATION.hermiteIntervalData(aState,aRate,bState,bRate); // reference returned
  
  var mBt1_c2;
  var mBt1_c3;
  var mBt1_c4;
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    var deltaTime = self.getDeltaTime();
    var leftState = self.getLeftState();
    var leftRate  = self.getLeftRate();
	var step = (time - self.getLeft())/deltaTime;
	return leftState[index] + step*(deltaTime*leftRate[index] + step*(mBt1_c2[index] + step*(mBt1_c3[index] + step*mBt1_c4[index])));
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    self.bootstrap1((time - self.getLeft())/self.getDeltaTime(), state, beginIndex, length);
    return state; 
  }

  self.bootstrap1 = function(step, state, beginIndex, length) {
    var deltaTime = self.getDeltaTime();
    var leftState = self.getLeftState();
    var leftRate  = self.getLeftRate();
    var index = beginIndex;
	for (var i=0; i<length; i++) {
      state[i] = leftState[index] + step*(deltaTime*leftRate[index] + step*(mBt1_c2[index] + step*(mBt1_c3[index] + step*mBt1_c4[index])));
      index++;
	}
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

    // Coefficients of the first bootstrap
    var ALPHA = 0.25;
    var bt1_den = ALPHA*(ALPHA-1)*(4*ALPHA-2);
    var bt1_cf1 = ALPHA*(-3*ALPHA+2);
    var bt1_cf0 = ALPHA*(-3*ALPHA+4)-1; 
    var bt1_cys = 6*ALPHA*(ALPHA-1);
    
    var dimension = aState.length;
    var deltaTime = self.getDeltaTime();
  
    var state_bt1 = new Array(dimension);
    var rate_bt1  = new Array(dimension);
    mBt1_c2    = new Array(dimension);
    mBt1_c3    = new Array(dimension);
    mBt1_c4    = new Array(dimension);
    self.hermite(ALPHA, state_bt1, 0, dimension-1);
    state_bt1[dimension-1] = self.getLeft() + ALPHA*deltaTime;
    ode.getRate(state_bt1, rate_bt1);

    for (var i=0; i<dimension; i++) {
      var dif = bState[i]-aState[i];
      var f0 = deltaTime*aRate[i];
      var f1 = deltaTime*bRate[i];
      var c4 = (deltaTime*rate_bt1[i] + bt1_cf1*f1 + bt1_cf0*f0 + bt1_cys*dif)/bt1_den;
      var c3 = f1 + f0 - 2*dif - 2*c4;
      mBt1_c4[i] = c4;
      mBt1_c3[i] = c3;
      mBt1_c2[i] = dif - f0 - c3 - c4;
    }
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.ConstantConditionData = {

};

/**
 * Constructor for ConstantConditionData
 * @returns A ConstantConditionData
 */
EJSS_ODE_INTERPOLATION.constantConditionData = function(aState) {
  var self = EJSS_ODE_INTERPOLATION.intervalData(Number.NaN,Number.NaN); // reference returned 
  var mState;
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    return mState[index];
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    var index = beginIndex;
    for (var i=0; i<length; i++) {
      state[i] = mState[index];
      index++;
    }
    return state; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
    var dimension = aState.length;
    mState = new Array(dimension);
    for (var i=0; i<dimension; i++) {
      mState[i] = aState[i];
    }
   
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.Dopri5IntervalData = {

};

/**
 * Constructor for Dopri5IntervalData
 * @returns Dopri5IntervalData
 */
EJSS_ODE_INTERPOLATION.dopri5IntervalData = function(aState, bState, coeffs) {
  var self = EJSS_ODE_INTERPOLATION.extraStepsIntervalData(aState,bState,coeffs); // reference returned
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    var theta = (time-self.getLeft())/self.getDeltaTime();
    var theta1 = 1 - theta;
    var coeffs = self.getCoeffs();
    return coeffs[0][index] + theta*(coeffs[1][index] + 
        theta1*(coeffs[2][index] + theta*(coeffs[3][index] + theta1*coeffs[4][index])));
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    var theta = (time-self.getLeft())/self.getDeltaTime();
    var theta1 = 1 - theta;
    var coeffs = self.getCoeffs();
    var index = beginIndex;
    for (var i=0; i<length; i++) {
      state[i] = coeffs[0][index] + theta*(coeffs[1][index] + 
          theta1*(coeffs[2][index] + theta*(coeffs[3][index] + theta1*coeffs[4][index])));
      index++;
    }
    return state; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.Dopri853IntervalData = {

};

/**
 * Constructor for Dopri853IntervalData
 * @returns Dopri853IntervalData
 */
EJSS_ODE_INTERPOLATION.dopri853IntervalData = function(aState, bState, coeffs) {
  var self = EJSS_ODE_INTERPOLATION.extraStepsIntervalData(aState,bState,coeffs); // reference returned
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    var theta = (time-self.getLeft())/self.getDeltaTime();
    var theta1 = 1 - theta;
    var coeffs = self.getCoeffs();
    return coeffs[0][index] + theta * (coeffs[1][index] + theta1 * (coeffs[2][index] + theta * (coeffs[3][index] + 
        theta1 * (coeffs[4][index] + theta * (coeffs[5][index] + theta1 * (coeffs[6][index] + theta * coeffs[7][index]))))));
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    var theta = (time-self.getLeft())/self.getDeltaTime();
    var theta1 = 1 - theta;
    var coeffs = self.getCoeffs();
    var index = beginIndex;
    for (var i=0; i<length; i++) {
      state[i] = coeffs[0][index] + theta * (coeffs[1][index] + theta1 * (coeffs[2][index] + theta * (coeffs[3][index] + 
          theta1 * (coeffs[4][index] + theta * (coeffs[5][index] + theta1 * (coeffs[6][index] + theta * coeffs[7][index]))))));
      index++;
    }
    return state; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.EulerIntervalData = {

};

/**
 * Constructor for EulerIntervalData
 * @returns An EulerIntervalData
 */
EJSS_ODE_INTERPOLATION.eulerIntervalData = function(state, rate, right) {
  var self = EJSS_ODE_INTERPOLATION.intervalData(state[state.length-1],right); // reference returned 
  var mLeftState;
  var mLeftRate;
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    var step = time - self.getLeft();
    return mLeftState[index] + step*mLeftRate[index];
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    var step = time - self.getLeft();
    var index = beginIndex;
    for (var i=0; i<length; i++) {
      state[i] = mLeftState[index] + step*mLeftRate[index];
      index++;
    }
    return state; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
    var dimension = state.length;
    mLeftState = new Array(dimension);
    mLeftRate = new Array(dimension);
    for (var i=0; i<dimension; i++) {
      mLeftState[i] = state[i];
      mLeftRate[i]  =  rate[i];
    }
   
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.EulerRichardsonIntervalData = {

};

/**
 * Constructor for EulerRichardsonIntervalData
 * @returns An EulerRichardsonIntervalData
 */
EJSS_ODE_INTERPOLATION.eulerRichardsonIntervalData = function(state, rate, right, K2) {
  var self = EJSS_ODE_INTERPOLATION.intervalData(state[state.length-1],right); // reference returned
  var mTimeIndex;
  var mStepSize; 
  var mLeftState;
  var mLeftRate;
  var mK2;
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    var theta = (time-self.getLeft())/mStepSize;
    var b2 = theta*theta*mStepSize;
    var b1 = mStepSize*theta - b2;
    return mLeftState[index]+ b1*mLeftRate[index] + b2*mK2[index];
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    var theta = (time-self.getLeft())/mStepSize;
    var b2 = theta*theta*mStepSize;
    var b1 = mStepSize*theta - b2;
    var index = beginIndex;
    for (var i=0; i<length; i++) {
      state[i] = mLeftState[index]+ b1*mLeftRate[index] + b2*mK2[index];
      index++;
    }
    return state; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
    var dimension = state.length;
    mLeftState = new Array(dimension);
    mLeftRate  = new Array(dimension);
    mK2        = new Array(dimension);
    for (var i=0; i<dimension; i++) {
      mLeftState[i] = state[i];
      mLeftRate[i]  =  rate[i];
      mK2[i]        =    K2[i];
    }
    mTimeIndex = dimension-1;
    mStepSize = right - state[mTimeIndex];

  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.ExtraStepsIntervalData = {

};

/**
 * Constructor for ExtraStepsIntervalData
 * @returns ExtraStepsIntervalData
 */
EJSS_ODE_INTERPOLATION.extraStepsIntervalData = function(aState, bState, coeffs) {
  var self = EJSS_ODE_INTERPOLATION.intervalData(aState[aState.length-1],bState[bState.length-1]); // reference returned
  var mTimeIndex;
  var mDeltaTime; 
  var mCoeffs;
  
  // --------------------------------------------
  // Getters
  // --------------------------------------------

  self.getTimeIndex = function() { return mTimeIndex; }

  self.getDeltaTime = function() { return mDeltaTime; }

  self.getCoeffs = function() { return mCoeffs; }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
    var dimension = aState.length;
    mTimeIndex = dimension-1;
    mDeltaTime = bState[mTimeIndex]-aState[mTimeIndex];
      
    var length = coeffs.length; 
    mCoeffs = new Array(length);
	for (var i=0; i<length; i++) {
	  mCoeffs[i] = new Array(dimension);
      for (var j=0; j<dimension; j++) {
	    mCoeffs[i][j] = coeffs[i][j];
	  }
	}

  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.HermiteIntervalData = {

};

/**
 * Constructor for HermiteIntervalData
 * @returns A HermiteIntervalData
 */
EJSS_ODE_INTERPOLATION.hermiteIntervalData = function(aState, aRate, bState, bRate) {
  var self = EJSS_ODE_INTERPOLATION.intervalData(aState[aState.length-1],bState[bState.length-1]); // reference returned
  var mTimeIndex;
  var mDeltaTime; 
  var mLeftState;
  var mLeftRate;
  var mRightState;
  var mRightRate;
  
  // --------------------------------------------
  // Getters
  // --------------------------------------------

  self.getTimeIndex = function() { return mTimeIndex; }

  self.getDeltaTime = function() { return mDeltaTime; }
  
  self.getLeftState = function() { return mLeftState; }

  self.getLeftRate  = function() { return mLeftRate; }

  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    var theta = (time - self.getLeft())/mDeltaTime;
    var minus1 = theta - 1;
    var prod1 = theta*minus1;
    var prod2 = prod1*(1 - 2*theta);
    var coefX0 = - minus1 - prod2;
    var coefX1 = theta + prod2;
    var coefF0 = prod1*minus1*mDeltaTime;
    var coefF1 = prod1*theta*mDeltaTime;
    return coefX0*mLeftState[index] + coefX1*mRightState[index] + coefF0*mLeftRate[index] + coefF1*mRightRate[index];
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
	self.hermite((time - self.getLeft())/mDeltaTime, state, beginIndex, length);
    return state; 
  }

  /**
   * Does the Hermite interpolation for a given theta = (t - left)/Delta
   * for the given indexes
   * Will also be used by subclasses
   * @param theta
   * @param state
   */
  self.hermite = function(theta, state, beginIndex, length) {
	var minus1 = theta - 1;
	var prod1 = theta*minus1;
	var prod2 = prod1*(1 - 2*theta);
	var coefX0 = - minus1 - prod2;
	var coefX1 = theta + prod2;
	var coefF0 = prod1*minus1*mDeltaTime;
	var coefF1 = prod1*theta*mDeltaTime;
	var index = beginIndex;
	for (var i=0; i<length; i++) {
	  state[i] = coefX0*mLeftState[index] + coefX1*mRightState[index] + coefF0*mLeftRate[index] + coefF1*mRightRate[index];
      index++;
	}
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
    var dimension = aState.length;
    mLeftState  = new Array(dimension);
    mLeftRate   = new Array(dimension);
    mRightState = new Array(dimension);
    mRightRate  = new Array(dimension);

    for (var i=0; i<dimension; i++) {
      mLeftState[i]  = aState[i];
      mLeftRate[i]   = aRate[i];
      mRightState[i] = bState[i];
      mRightRate[i]  = bRate[i];
    }
    mTimeIndex = dimension-1;
    mDeltaTime = bState[mTimeIndex]-aState[mTimeIndex];		

  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.InitialConditionData = {

};

/**
 * Constructor for InitialConditionData
 * @returns InitialConditionData
 */
EJSS_ODE_INTERPOLATION.initialConditionData = function(mDDE) {
  var self = EJSS_ODE_INTERPOLATION.intervalData(Number.NaN,Number.NaN); // reference returned 
  var mState;
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
    mDDE.getInitialCondition(time, mState);
    return mState[index];
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    mDDE.getInitialCondition(time, mState);
    var index = beginIndex;
    for (var i=0; i<length; i++) {
      state[i] = mState[index];
      index++;
    }
    return state; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
    var state = mDDE.getState();
    var dimension = state.length;
    mState = new Array(dimension);
   
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.IntervalData = {

};

/**
 * Constructor for IntervalData
 * @returns An abstract IntervalData
 */
EJSS_ODE_INTERPOLATION.intervalData = function(mLeft, mRight) {
  var self = {};							// reference returned 

  /**
   * Returns the left side of the interval
   * @return double
   */
  self.getLeft = function() { return mLeft; };

  /**
   * Returns the right side of the interval
   * @return double
   */
  self.getRight = function() { return mRight; };

  /**
   * Changes the right side of the interval
   * @param rightSide
   */
  self.setRight = function(rightSide) { mRight = rightSide; };

  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  /**
   * Retrieve the state for the given time for one index
   * @param time double the given time for the state
   * @param index the index to interpolate 
   * @return double the interpolated value 
   */
  self.interpolate = function(time, index) { return Number.NaN;  }

  /**
   * Retrieve the state for the given time for one index or a subset of indexes
   * @param time double the given time for the state
   * @param state double[] a place holder for the returned state
   * @param beginIndex the first index to interpolate
   * @param length the range of indexes pf the state to interpolate   
   * @return double[] the array with the data, same as passed state, 
   */
  self.interpolateState = function(time, state, beginIndex, length) { return state; }
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * Abstract object to be used as base for ode state interpolation 
 */
EJSS_ODE_INTERPOLATION.Radau5IntervalData = {

};

/**
 * Constructor for Radau5IntervalData
 * @returns Radau5IntervalData
 */
EJSS_ODE_INTERPOLATION.radau5IntervalData = function(aState, aRate, coeffs) {
  var self = EJSS_ODE_INTERPOLATION.extraStepsIntervalData(aState,bState,coeffs); // reference returned
  var mFinalTime = bState[bState.length-1];
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  self.interpolate = function(time, index) { 
	var theta = (time-mFinalTime)/self.getDeltaTime();
    var coeffs = self.getCoeffs();
    var c1m1 = EJSS_ODE_SOLVERS.Radau5.c1m1;
    var c2m1 = EJSS_ODE_SOLVERS.Radau5.c2m1;
	return coeffs[0][index] + theta * (coeffs[1][index] + (theta - c2m1) * (coeffs[2][index] + (theta - c1m1) * coeffs[3][index]));
  }

  self.interpolateState = function(time, state, beginIndex, length) { 
    var theta = (time-mFinalTime)/mDeltaTime;
    var coeffs = self.getCoeffs();
    var c1m1 = EJSS_ODE_SOLVERS.Radau5.c1m1;
    var c2m1 = EJSS_ODE_SOLVERS.Radau5.c2m1;
    var index = beginIndex;
    for (var i=0; i<length; i++) {
      state[i] = coeffs[0][index] + theta * (coeffs[1][index] + (theta - c2m1) * (coeffs[2][index] + (theta - c1m1) * coeffs[3][index]));
      index++;
    }
    return state; 
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for ODE state interpolation 
 */

var EJSS_ODE_INTERPOLATION = EJSS_ODE_INTERPOLATION || {};

/**
 * StateHistory is an object that stores past values of a vector valued real function (i.e. f: R --> Rn) and uses 
 * this information to provide interpolated values at different instants of time.
 * The current implementation uses a linked list of IntervalData, each of which is responsible for a half-open (on the right) interval [left,right).
 * For proper operation, the IntervalData must be ordered from old to new and the left side of each interval 
 * must match the right side of the previous interval. Intervals are closed on the left side, and open on the right: [a,b)
 * However, if a new interval is added that overlaps the last one, this newer interval takes over the responsibility for the intersection of both intervals.
 * Notice that right or left is not actually relevant. If any of the intervals happens to have the left ends greater than the right end, then
 * the collection is consider to run backwards, and everything is taken care of properly.  
 * Finally, there may be an optional 'last resource' IntervalData object that takes care of providing an interpolation for values not covered by the regular
 * intervals. This is useful,for instance, for the pre-initial conditions of DelayDifferentialEquations.
 *  
 * @author Francisco Esquembre
 * @version Jan 2014
 */
EJSS_ODE_INTERPOLATION.StateHistory = {

};

/**
 * Constructor for StateHistory
 * @returns StateHistory
 */
EJSS_ODE_INTERPOLATION.stateHistory = function(ode) {
  var self = {};							// reference returned 

  var mIntervalList = [];
  var mForwards = true;
  var mLastResourceInterval;
  
  var mMinimumLength=0;
  var mUserLength=0;
  var mActualLength=0;

  /**
   * Sets the length of the history requested to the solver.
   * The user will then be able to ask for values of the state as far as the current time minus this length.
   * stepSize is the default for plain ODE, getMaximumDelay() is the minimum used by DDEs.
   * Setting a value of Infinity makes the solver to keep the history for ever (i.e. as much as computer memory permits)
   * @param length
   */
  self.setLength = function(length) {
    mUserLength = Math.abs(length);
    mActualLength = Math.max(mMinimumLength, mUserLength);
  }
	
  /**
   * Sets the minimum length of the history requested to the solver.
   * Not to be used by users
   * @param length
   */
  self.setMinimumLength= function(length) {
    mMinimumLength = Math.abs(length);
    mActualLength = Math.max(mMinimumLength, mUserLength);
  }
  
  /**
   * Adds an IntervalData at the end of the memory
   * @param data
   */
  self.addIntervalData = function (data) {
    mForwards = (data.getLeft()<=data.getRight());
    var length = mIntervalList.length;
	if (mIntervalList.length>0) {
	  var lastIndex = mIntervalList.length-1;
      var lastInterval = mIntervalList[lastIndex]; 
      var toRemove = 0;
//      console.log("Adding interval ["+data.getLeft()+","+data.getRight()+")\n");
      if (mForwards) {
        while (lastInterval!=null && lastInterval.getLeft()>=data.getLeft()) {
          toRemove++;
          lastIndex--;
//          console.log("... will remove interval ["+lastInterval.getLeft()+","+lastInterval.getRight()+")\n");
          if (lastIndex<0) lastInterval = null; 
		  else lastInterval = mIntervalList[lastIndex];
		}
	    if (lastInterval!=null && data.getLeft()<lastInterval.getRight()) lastInterval.setRight(data.getLeft());
      }
	  else {
        while (lastInterval!=null && lastInterval.getLeft()<=data.getLeft()) {
          toRemove++;
          lastIndex--;
		  if (lastIndex<0) lastInterval = null; 
		  else lastInterval = mIntervalList[lastIndex];
		}
	    if (lastInterval!=null && data.getLeft()>lastInterval.getRight()) lastInterval.setRight(data.getLeft());
	  }
      if (toRemove>0) mIntervalList.splice(lastIndex,toRemove);
	}
	mIntervalList.push(data);
//	console.log(self.toString());
  }
	
  self.toString = function() {
	var txt = "History has now "+mIntervalList.length+" intervals: ";
	for (var i=0,n=mIntervalList.length; i<n; i++) {
	  var interval = mIntervalList[i];
	  if (i>0) txt += ", ";
	  txt += " ["+interval.getLeft()+","+ interval.getRight()+")";
	}
	return txt;
  }
	
  /**
   * Clears all the memory data
   */
  self.clearAll = function() { mIntervalList = []; }
	
  /**
   * Clears all intervals whose data is older than the given time. 
   * This method can be used to save memory when the data is not needed anymore.
   * The last resource interval is not cleared.
   * @param currentTime
   */
  self.clean = function(currentTime) {
  	if (!isFinite(mActualLength)) return;
    if (mActualLength==0) { // remember only the new interval
      clearAll();
      return;
    }
    var toBeRemoved = 0;
    var length = mIntervalList.length;
    if (mForwards) {
      currentTime -= mActualLength;
      for (var i=0; i<length; i++) {
	    var interval = mIntervalList[i];
	    if (interval.getRight()>currentTime) break; // This one cannot be removed
//        console.log("... will clean interval ["+interval.getLeft()+","+interval.getRight()+")\n");
	    toBeRemoved++;
      }
    }
    else {
      currentTime += mActualLength;
      for (var i=0; i<length; i++) {
	    var interval = mIntervalList[i];
	    if (interval.getRight()<currentTime) break; // This one cannot be removed
	    toBeRemoved++;
	  }
	}
	if (toBeRemoved>0) mIntervalList.splice(0,toBeRemoved);
  }

  /**
   * Finds the interval responsible for interpolating data at this time, including the last resource interval, if all other intervals fail
   * @param time double the given time for the state
   * @return IntervalData the data responsible for this instant of time
   */
  self.findInterval = function(time) {
    var length = mIntervalList.length;
    if (mForwards) {
      for (var i=length-1; i>=0; i--) {
	    var interval = mIntervalList[i];
	    if (interval.getLeft()<=time) return interval;
	  }
	}
	else { // backwards
      for (var i=length-1; i>=0; i--) {
	    var interval = mIntervalList[i];
	    if (interval.getLeft()>=time) return interval;
	  }
	}
    return mLastResourceInterval;
  }

  /**
   * Retrieve the state for the given time for one index
   * @param time double the given time for the state
   * @param index the index to interpolate 
   * @return double the interpolated value 
   */
  self.interpolate = function(time, index) {
    var interval = self.findInterval(time);
    return interval.interpolate(time,index);
  }

  /**
   * Retrieve the state for the given time for one index or a subset of indexes
   * @param time double the given time for the state
   * @param state double[] a place holder for the returned state
   * @param beginIndex the first index to interpolate. 0 is not specified
   * @param length If specified, only this range of indexes are interpolated, the whole state is interpolated if unspecified   
   * @return double[] the array with the data, same as passed state, 
   */
  self.interpolateState = function(time, state, beginIndex, length) {
//    console.log("Interpolate for t="+time);
//    console.log(self.toString());
    var interval = self.findInterval(time);
    if (!length) length = state.length;
    if (!beginIndex) beginIndex = 0;
    return interval.interpolateState(time,state,beginIndex,length);
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  if (ode.getInitialCondition) { // it is a DelayDifferentialEquation
    mLastResourceInterval = EJSS_ODE_INTERPOLATION.initialConditionData(ode);
    ode.setStateHistory(self);
  }
  else { // it is a regular ODE
    mLastResourceInterval = EJSS_ODE_INTERPOLATION.constantConditionData(ode.getState());
  }
  
  return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

EJSS_ODE_SOLVERS.EVENT_TYPE = {
  STATE_EVENT : 0,
  POSITIVE_EVENT : 1,
  CROSSING_EVENT : 2
};
  
EJSS_ODE_SOLVERS.EVENT_METHOD = {
  BISECTION : 0,
  SECANT    : 1
};
  
EJSS_ODE_SOLVERS.ERROR = {
    NO_ERROR                     : 0, // Everything went ok
    INTERNAL_SOLVER_ERROR        : 1, // The solver produced an internal error, such as when it cannot do an internal step
    EVENT_NOT_FOUND              : 2, // The event was not found after using the maximum number of attempts
    ILLEGAL_EVENT_STATE          : 3, // An event of type STATE_EVENT was left in an illegal (negative) state
    ZENO_EFFECT                  : 4, // A Zeno effect was detected without a user action
    TOO_MANY_STEPS_ERROR         : 5, // The solver exceeded the number of internal steps
    DISCONTINUITY_PRODUCED_ERROR : 6, // Unrecoverable error produced by a discontinuity
    DID_NOT_CONVERGE             : 7  // An adaptive method did not converge
};

EJSS_ODE_SOLVERS.DISCONTINUITY_CODE = {
    DISCONTINUITY_PRODUCED_ERROR  : 0, // Unrecoverable error
    NO_DISCONTINUITY_ALONG_STEP   : 1, // There is no discontinuity along the given step
    DISCONTINUITY_ALONG_STEP      : 2, // There is a  discontinuity along the given step, but not exactly at its end
    DISCONTINUITY_JUST_PASSED     : 3, // There is a  discontinuity along the given step, and it is just before the current step end
    DISCONTINUITY_EXACTLY_ON_STEP : 4  // There is a  discontinuity along the given step, and its is exactly at the end of the step
};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.InterpolatorEventSolver = {
};

/**
 * Constructor for InterpolatorEventSolver
 * @returns InterpolatorEventSolver
 */
EJSS_ODE_SOLVERS.interpolatorEventSolver = function(mSolverEngine, mODE) {
  var self = {};							// reference returned 

  // Configuration variables
  var mEnableExceptions=true;                 // Whether to throw an exception when an error is produced. 
  var mUseBestInterpolation=false;            // Always use the best interpolation the solvers provide (even if it is more time consuming)
  var mStepSize = 0.1;                        // The step size for reading data from the solver
  var mAbsoluteTolerance = Number.NaN;        // The absolute tolerance for adaptive methods
  var mRelativeTolerance = Number.NaN;        // The relative tolerance for adaptive methods
  var mMaxEventStep=Number.POSITIVE_INFINITY; // The maximum advance before checking for an event
  var mZenoMaximumAllowedTimes=500;           // The number of times that a separation smaller than the threshold must occur to declare a Zeno effect
  var mProximityThreshold=2*Number.MIN_VALUE; // The threshold to consider two events as simultaneous or indicate a possible Zeno effect
  var mCoalesceCloseEvents = true;            // whether to coalesce close events (even if the action returns true)
  var mMaxInternalSteps = 10000;
  
  // Implementation variables
  // var mSolverEngine; // The underlying solver engine
  // var mODE;          // The ODE being solved
  
  var mRunsForwards = mStepSize>0;
  var mDimension; // The dimension of the problem (including time)
  var mTimeIndex; // The index of the time state, i.e. dimension-1
  var mErrorCode = EJSS_ODE_SOLVERS.ERROR.NO_ERROR; // The error code
  var mErrorMessage="No error";
  var mNumberOfAttempts=0;     // Actual number of attempts needed to locate the last event
  var mTest_ode_state;         // a place holder for intermediate states
  var mIntermediate_ode_state; // a second place holder for intermediate states
  
  var mEventList = []; // The list of events added by the user (except those of type DISCONTINUITY_EVENT, which are handled separately)
  var mHappened  = []; // The list of events that take place in a given interval
  var mTemp_list = []; // a temporary list of events
  var mLastEventDataTime=Number.NaN; // Remember the time of the last event
  var mLastEventData=null;           // Remember the last event
  var mZenoCounter=0;                // Count how many close events have happened
  var mZenoList = [];                // list of Zeno Listeners

  var mCurrentEventData=null;        // Placeholder for the current event, used to reset some events properly
  var mHasEventsOrDiscontinuities = false;
  var mHasDiscontinuities = false;
  var mDiscontinuityAtEnd = null;   // The first DISCONTINUITY_EVENT event founds in one step // version 2.0
  var mDiscontinuityList = [];      // The list of DISCONTINUITY_EVENT type of events added by the user // version 2.0

  var mDDEdiscontinuity=null;
  var mDDEdiscontinuityMaxIterations = 100;
  var mDDEdiscontinuityTolerance = 1.0e-8;
    
  // --------------------------------------------
  // Static methods
  // --------------------------------------------

  var sEPSILON=Number.NaN;

  self.getEPSILON = function() {
    if (isNaN(sEPSILON)) {
      sEPSILON = 1;
      while (sEPSILON+1!=1) sEPSILON /=2;
      sEPSILON *= 2;
//      console.log("EPSILON ="+sEPSILON+"\n");
    }
    return sEPSILON;
  };

  // --------------------------------------------
  // Configuration and getter methods
  // --------------------------------------------

  /**
   * Enables runtime exceptions if there is any error condition. 
   * If exceptions are disabled, the solver prints a warning to the standard error output and continues.
   * Exceptions are enabled by default.
   * @param enable boolean
   */
  self.setEnableExceptions = function(enable) { mEnableExceptions = enable; }

  /**
   * Request the internal solver to always use the best interpolation it can provide,
   * even if it is more time-consuming than the standard one. For examples, some RK
   * solvers will re-step from the initial step every time they are asked for their best
   * interpolation, which is very expensive in terms of function evaluations.
   * Best interpolation is however always used (irrespective of this parameter) to 
   * return the state after a successful step and after an event takes place. 
   * But standard interpolation is used by default for locating the events.
   * Setting this parameter to true forces the solver to use its best interpolation
   * even for event location.
   * @param best
   */
  self.setBestInterpolation = function(best) { mUseBestInterpolation = best; }
  
  /**
   * Sets the length of the memory requested to the solver. Must be a positive value.
   * The user will then be able to ask for values of the state as far as the current time minus this length.
   * 0 is the default for plain ODE, getMaximumDelay() is the minimum used by DDEs.
   * Setting a value of Infinity makes the solver to remember for ever (i.e. as much as computer memory permits)
   * @param length
   */
  self.setHistoryLength = function(length) { mSolverEngine.getStateHistory().setLength(length); }
    
  /**
   * Sets the reading step size. That is the step at which solutions are read from 
   * the equation. Most of the times, these solutions are obtained by interpolation.
   */
  self.setStepSize = function(stepSize) {
    if (mStepSize==stepSize) return;
    mStepSize = stepSize;
    mRunsForwards = mStepSize>0;
    self.setInternalStepSize(mSolverEngine.getStepSize()); // Make sure the interpolator solver runs in the same direction
  }

  /**
   * Returns the reading step
   */
  self.getStepSize = function() { return mStepSize; }

  /**
   * Asks adaptive solvers to estimate the best initial step after reinitialize().
   * If false, the given initial step (as set by setComputationStepSize()) is used.
   * @param estimate
   */
  self.setEstimateFirstStep = function(estimate) { mSolverEngine.setEstimateFirstStep(estimate); }
  
  /**
   * Sets the interpolator's internal step size. This is the step at which solutions are computed
   * for fixed step methods, and the initial step size (after reinitialize()) for variable
   * step methods. The value is taken absolutely in the direction of the reading step size.
   */
  self.setInternalStepSize = function(stepSize) {
    mSolverEngine.setStepSize(mRunsForwards ? Math.abs(stepSize) : -Math.abs(stepSize));
  }

  /**
   * Sets the interpolator's internal maximum step size. Has no effect on fixed-step solvers and on QSS methods.
   * @param stepSize
   */
  self.setMaximumInternalStepSize = function(stepSize) { mSolverEngine.setMaximumStepSize(stepSize); }
  
  /**
   * Sets the maximum number of internal steps the interpolator can take to reach a 
   * reading step. If the solver takes so large a number (of very small steps) this
   * typically means the solver has reached a singularity and must take too small steps.
   * It can also mean the solver has difficulties to reach the tolerance and the user
   * should consider either increasing this limit, or use another solver. 
   * 
   * @param steps
   */
  self.setMaximumInternalSteps = function(steps) { mMaxInternalSteps = steps; }

  /**
   * The preferred absolute and relative tolerance desired for the solution if the 
   * underlying solver supports it. If the solver does not support this feature, the 
   * method is ignored. Changing the tolerances may involve a re-computation 
   * of the current step.
   * @param tol
   */
  self.setTolerances = function(absTol, relTol) {
    mSolverEngine.setTolerances(mAbsoluteTolerance = Math.abs(absTol), mRelativeTolerance = Math.abs(relTol));
  }

  /**
   * Equivalent to setTolerances (tol,0)
   * @param tol
   */
  self.setTolerance = function(tol) { setTolerances(tol,0); }

  /**
   * Returns the maximum of the absolute and relative tolerances
   */
  self.getTolerance = function() { return Math.max(mAbsoluteTolerance, mRelativeTolerance); }
  
  /**
   * The tolerance for finding discontinuities created by DDE delays
   * @param tol
   */
  self.setDDETolerance = function(tol) { mDDEdiscontinuityTolerance = tol; }

  self.getDDETolerance = function() { return mDDEdiscontinuityTolerance; }

  /**
   * Number of iterations allowed to find discontinuities created by DDE delays
   * @param iterations
   */
  self.setDDEIterations = function(iterations) { mDDEdiscontinuityMaxIterations = iterations; }

  /**
   * Number of iterations needed to find the last discontinuity created by a DDE delay
   */
  self.getDDEIterations = function() { return mDDEdiscontinuityMaxIterations; }

  // -----------------------------------
  // Events and discontinuities
  // -----------------------------------

  self.addEvent = function(event) {
    mEventList.push(EJSS_ODE_SOLVERS.eventData(self,event,mODE.getState()));
    mHasEventsOrDiscontinuities = true;
  }
  
  self.removeEvent = function(event) {
    var index = -1;
    var length = mEventList.length;    
    for (var i=0; i<length; i++) {
      var data = mEventList[i];
      if (data.getEvent()==event) { index = i; break; }
    }
    if (index>=0) {
      var foundData = mEventList[index];
      if (mLastEventData==foundData) { mLastEventData = null; mZenoCounter = 0; }
      if (mCurrentEventData==foundData) mCurrentEventData = null;
      mEventList.splice(index,1);
    }
    mHasEventsOrDiscontinuities = mHasDiscontinuities || (mEventList.length>0);
  }

  self.addDiscontinuity = function(discontinuity) {
    mDiscontinuityList.push(EJSS_ODE_SOLVERS.discontinuityData(self,discontinuity,mODE.getState()));
    mHasDiscontinuities = true;
    mHasEventsOrDiscontinuities = true;
  }

  self.removeDiscontinuity = function(discontinuity) {
    var index = -1;
    var length = mDiscontinuityList.length;
    for (var i=0; i<length; i++) {
      var data = mDiscontinuityList[i];
      if (data.getDiscontinuity()==discontinuity) { index = i; break; }
    }
    if (index>=0) {
      mDiscontinuityList.splice(index,1);
    }
    mHasDiscontinuities = (mDiscontinuityList.length>0);
    mHasEventsOrDiscontinuities = mHasDiscontinuities || (mEventList.length>0);
  }

  self.removeAllEvents = function() {
    mEventList = [];
    mDiscontinuityList = [];
    mHasDiscontinuities = false;
    mHasEventsOrDiscontinuities = false;
    if (mDDEdiscontinuity!=null) self.addDiscontinuity(mDDEdiscontinuity);
  }
    
  /**
   * Sets the maximum step allowed before checking for an event.
   * Default is Number.POSITIVE_INFINITY
   * @param step
   */
  self.setMaximumEventStep = function(step) { mMaxEventStep = Math.abs(step); }

  /**
   * Returns the maximum step allowed before checking for an event.
   * @return
   */
  self.getMaximumEventStep = function() { return mMaxEventStep; }

  /**
   * If true, an event closer than the threshold to the previous one 
   * will not return even if the event action returns true. Default is true.
   * @param coalesce
   */
  self.setCoalesceCloseEvents = function(coalesce) { mCoalesceCloseEvents = coalesce; }
  
  /**
   * Whether an event closer than the threshold to the previous one 
   * will not return even if the event action returns true
   * @return
   */
  self.isCoalesceCloseEvents = function() { return mCoalesceCloseEvents; }
  
  /**
   * Sets the threshold that considers two events as close enough for coalescing
   * or indicating a possible Zeno-like effect. Default is 2*Number.MIN_VALUE
   * @param threshold The small separation that indicates a possible Zeno effect
   */
  self.setEventProximityThreshold = function(threshold) { mProximityThreshold = threshold; }

  /**
   * Return the threshold that considers two events as close enough for coalescing
   * or indicating a possible Zeno-like effect
   */
  self.getEventProximityThreshold = function() { return mProximityThreshold; }

  // -----------------------------------
  // Zeno-like effects 
  // -----------------------------------

  /**
   * If the solver finds more than _times successive events closer than the proximity threshold,
   * it will consider it a Zeno effect and call the registered ZenoEffectListeners. Default is 500.
   * @param times The number of times that a separation smaller than the threshold must occur to declare a Zeno effect. 
   * A zero or negative value disables the detection.
   */
  self.setZenoEffectDetection = function(times) { mZenoMaximumAllowedTimes = times; }

  /**
   * Returns the number of successive events closer than the proximity threshold,
   * that will be considered a Zeno effect.
   */
  self.getZenoEffectDetection = function() { return mZenoMaximumAllowedTimes; }

  /**
   * Adds a ZenoEffectListener that will be called if a Zeno-like effect situation occurs.
   */
  self.addZenoEffectListener = function(listener) { mZenoList.push(listener); }
  
  /**
   * Removes a previously added ZenoEffectListener 
   */
  self.removeZenoEffectListener = function(listener) { 
    var length = mZenoList.length;
    for (var i=0; i<length; i++) {
      if (mZenoList[i]==listener) { mZenoList.splice(i,1); return; }
    }
  }
  
  self.setZeroZenoCounter = function() { mZenoCounter = 0; }
  
  // --------------------------------------------  
  // Getter methods
  // --------------------------------------------

  /**
   * Returns the interpolator solver
   * @return SolverEngine
   */
  self.getSolverEngine = function() { return mSolverEngine; };

  /**
   * Returns the ODE to solve
   */
  self.getODE = function() { return mODE; }
  
  /**
   * Provides access to the internal StateHistory responsible for interpolations.
   * @return
   */
  self.getStateHistory = function() { return mSolverEngine.getStateHistory(); }

  /**
   * Returns the error code after a step
   */
  self.getErrorCode = function() { return mErrorCode; }
  
  /**
   * Returns the error message
   * @return
   */
  self.getErrorMessage = function() { return mErrorMessage; }

  /**
   * Returns the number of function evaluations required by the method to reach the tolerance 
   * @return
   */
  self.getCounter = function() { return mSolverEngine.getCounter(); }

  /**
   * The number of attempts that were required to locate the last event
   * @return
   */
  self.getNumberOfAttempts = function() { return mNumberOfAttempts; }

  self.getInternalStepSize = function() { return mSolverEngine.getInternalStepSize(); }
  
  /**
   * Returns the current value of the independent variable
   * @return
   */
  self.getIndependentVariableValue = function() { return mODE.getState()[mTimeIndex]; }
  
  /**
   * Same as getIndependentVariableValue ()
   * @return
   */
  self.getCurrentTime = function() { return mODE.getState()[mTimeIndex]; }


 // Internal use only
 
  self.getRunsForwards = function() { return mRunsForwards; }

  self.getCurrentEventData = function() { return mCurrentEventData; }

  self.getLastEventData = function() { return mLastEventData; }

  self.getLastEventDataTime = function() { return mLastEventDataTime; }

  // ----------------------------------------------------
  // Operation
  // ----------------------------------------------------
  
  /**
   * Initializes the solver. The step size is used to set the reading step size AND
   * also passed along to the ODESolverInterpolator for internal initialization.
   * The interpolator's internal step size can be changed with setInternalStepSize(double).
   * Calls to setStepSize() will not affect the internal step size of the interpolator.
   */
  self.initialize = function(stepSize) {
    var i, length, state;
    mStepSize = stepSize;
    mRunsForwards = mStepSize>0;
    mSolverEngine.initialize(mStepSize);
    state = mODE.getState(); 
    mDimension = state.length;
    mTimeIndex = mDimension - 1;
    mTest_ode_state = new Array(mDimension);
    mIntermediate_ode_state = new Array(mDimension);
    mErrorCode = EJSS_ODE_SOLVERS.ERROR.NO_ERROR;
    mErrorMessage = "No error";
    mZenoCounter = 0;
    mLastEventData = null;
    mCurrentEventData = null;       
    if (mDDEdiscontinuity!==null) {
      mDDEdiscontinuity.initialize(state);
      self.removeDiscontinuity(mDDEdiscontinuity);
      self.addDiscontinuity(mDDEdiscontinuity);
    }
    length = mEventList.length;
    for (i=0; i<length; i++) mEventList[i].reset(state);
    length = mDiscontinuityList.length;
    for (i=0; i<length; i++) mDiscontinuityList[i].reset(state);
  }
  
  /**
   * Take the next step as indicated by the stepSize
   * If an event is found, it steps up to that event.
   * @return
   */
  self.step = function() {
    if (mHasEventsOrDiscontinuities) return stepWithEvents();
    return stepWithoutEvents();
  }

  /**
   * Take the maximum possible step.
   * For adaptive-step methods, steps up to the maximum possible adaptive step. 
   * For fixed-step methods, steps up to the next step size.
   * If an event is found, it steps up to that event.
   * @return
   */
  self.maxStep = function() {
    if (mHasEventsOrDiscontinuities) return maxStepWithEvents();
    return maxStepWithoutEvents();
  }

  /**
   * Used to reinitialize the solver when the user changes directly the ODE state,
   * model parameters, or anything THAT MAY AFFECT THE EVENTS (such as the events tolerance, for instance).
   * EjsS calls automatically reinitialize(), which respects the currentDataEvent information when it detects
   * any other minor change.
   */
  self.userReinitialize = function() {
    mCurrentEventData = null;
    self.reinitialize();
  }

  /**
   * Does the minimum (soft) initialization of the solver for a given state.
   * Users MUST call reinitialize (or the harder initialize()) whenever they change directly the ODE state,
   * model parameters, or anything that may affect the events (such as the events tolerance, for instance).
   */
  self.reinitialize = function() {
    var i, length;
    var state = mODE.getState();
    mSolverEngine.reinitialize(state);
    mErrorCode = EJSS_ODE_SOLVERS.ERROR.NO_ERROR;
    mErrorMessage = "No error";
    if (mDDEdiscontinuity!=null) mDDEdiscontinuity.reset(state); // This must be called before before discData.reset() 
    length = mEventList.length;
    for (i=0; i<length; i++) mEventList[i].reset(state);
    length = mDiscontinuityList.length;
    for (i=0; i<length; i++) mDiscontinuityList[i].reset(state);
    mDiscontinuityAtEnd = null;
  }

  self.resetDiscontinuities = function(state) {
    if (mDDEdiscontinuity!==null) {
      mDDEdiscontinuity.reset(state); // This must be called before before discData.reset() 
      var length = mDiscontinuityList.length;
      for (var i=0; i<length; i++) mDiscontinuityList[i].reset(state);
    }
  }
  
  /**
   * Checks whether there would be a discontinuity before or at the given state and, in this last case, optionally marks it 
   * @param state The state to check for
   * @param isEndOfInterval Whether the point is the end of the integration interval. In this case, we mark the point as the discontinuity found
   * @return one of DISCONTINUITY_PRODUCED_ERROR, NO_DISCONTINUITY_ALONG_STEP, DISCONTINUITY_ALONG_STEP, or DISCONTINUITY_EXACTLY_ON_STEP
   */
  self.checkDiscontinuity = function(state, isEndOfInterval) {
    var length = mDiscontinuityList.length;
    var justHappened=null;
    for (var i=0; i<length; i++) {
      var data = mDiscontinuityList[i];
      var h = data.getDiscontinuity().evaluate(state);
      data.setH(h); 
      switch (data.getCurrentPosition()) {
        default : 
        case EJSS_ODE_SOLVERS.ProblemData.POSITIVE : 
          if (h<=0) {
//            if (h>-data.getDiscontinuity().getTolerance()) return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED; // h=0 is allowed in this direction
            return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_ALONG_STEP;
          }
          if (h<data.getDiscontinuity().getTolerance()) justHappened = data;
          break;
        case EJSS_ODE_SOLVERS.ProblemData.SMALL_POSITIVE :
          if (h<=0 && data.hasPositiveFlag()) {
            self.error(EJSS_ODE_SOLVERS.ERROR.ILLEGAL_EVENT_STATE, "The system started from an illegal state at "+state[mTimeIndex]+ " for the discontinuity "+data.getDiscontinuity());
            return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_PRODUCED_ERROR;
          }
          break;
        case EJSS_ODE_SOLVERS.ProblemData.ZERO :
          if (h<0 && data.hasPositiveFlag()) {
            self.error(EJSS_ODE_SOLVERS.ERROR.ILLEGAL_EVENT_STATE, "The system started from an illegal state at "+state[mTimeIndex]+ " for the discontinuity "+data.getDiscontinuity());
            return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_PRODUCED_ERROR;
          }
          else if (h>0 && data.hasNegativeFlag()) {
            self.error(EJSS_ODE_SOLVERS.ERROR.ILLEGAL_EVENT_STATE, "The system started from an illegal state at "+state[mTimeIndex]+ " for the discontinuity "+data.getDiscontinuity());
            return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_PRODUCED_ERROR;
          }
          break;
        case EJSS_ODE_SOLVERS.ProblemData.SMALL_NEGATIVE :
          if (data.hasNegativeFlag() && h>=0) {
            self.error(EJSS_ODE_SOLVERS.ERROR.ILLEGAL_EVENT_STATE, "The system started from an illegal state at "+state[mTimeIndex]+ " for the discontinuity "+data.getDiscontinuity());
            return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_PRODUCED_ERROR;
          }
          break;
        case EJSS_ODE_SOLVERS.ProblemData.NEGATIVE : 
          if (h>0) {
//            if (h>data.getDiscontinuity().getTolerance()) return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED; // h=0 is allowed in this direction
            return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_ALONG_STEP; // h=0 is allowed in this direction
          }
          if (h>-data.getDiscontinuity().getTolerance()) justHappened = data;
          break; 
      } // end of switch
    } // end of for
    if (justHappened!=null) {
      if (isEndOfInterval) {
          mDiscontinuityAtEnd = justHappened;
          mDiscontinuityAtEnd.setTime(state[mTimeIndex]);
//          System.err.println ("Disc at end is now "+justHappened+ " at time "+discontinuityAtEnd.getTime());
        }
      return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_EXACTLY_ON_STEP;
    }
    return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.NO_DISCONTINUITY_ALONG_STEP;  
  }
  
  // ----------------------------------------------------
  // ----------------------------------------------------
  // Private functions
  // ----------------------------------------------------
  // ----------------------------------------------------
    
  /**
   * Does the interpolation at the prescribed time.
   */
  function doTheInterpolation(time, state) {
    if (mUseBestInterpolation) mSolverEngine.bestInterpolate(time, state);
    else mSolverEngine.interpolate(time, state);
  }
  
  self.error = function(code, msg) {
    mErrorCode = code;
    mErrorMessage = msg;
    if (mEnableExceptions) throw {
      name : "ODE Solver exception",
      solver : mSolverEngine,
      message : msg
    };
    return Number.NaN;
  }
  
  // ----------------------------------------
  // Step without events or discontinuities
  // ----------------------------------------
  
  /**
   * Steps the ODE when there are no events
   * @return
   */
  function maxStepWithoutEvents() {
    var state = mODE.getState();
    
    // Check for a 0 rate for time
    var rate = mSolverEngine.getCurrentRate();
    var tBegin = state[mTimeIndex];
    if (tBegin+rate[mTimeIndex]==tBegin) return 0;
    
    var max_t = mSolverEngine.getMaximumTime(false); // false = no discontinuities
    if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver at "+state[mTimeIndex]);
    // Make sure the solver is not already at the maximum step
    if (tBegin==max_t) {
      max_t = mSolverEngine.internalStep(false);
      if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver at max step at "+state[mTimeIndex]);
    }
    doTheInterpolation (max_t, state);
    return max_t-tBegin;
  }
  
  /**
   * Steps the ODE when there are no events
   * @return
   */
  function stepWithoutEvents() {
    var state = mODE.getState();
    
    // Check for a 0 rate for time
    var rate = mSolverEngine.getCurrentRate();
    var tBegin = state[mTimeIndex];
    if (tBegin+rate[mTimeIndex]==tBegin) return 0;
    
    var tEnd = state[mTimeIndex] + mStepSize;
    var max_t = mSolverEngine.getMaximumTime(false); // false = no discontinuities
    if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver at "+state[mTimeIndex]);

    var counter = 0;
    // Make sure the solver can reach the expected time
    if (mRunsForwards) {
      while (max_t<tEnd) { 
        max_t = mSolverEngine.internalStep(false); // false = no discontinuities
        if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver forwards at "+state[mTimeIndex]);
        if ((++counter)>mMaxInternalSteps) { 
          var initTime = state[mTimeIndex];
          var currentTime = mSolverEngine.bestInterpolate(tEnd, new Array(mDimension))[mTimeIndex];
          return self.error (EJSS_ODE_SOLVERS.ERROR.TOO_MANY_STEPS_ERROR,"The solver exceeded the maximum of "+mMaxInternalSteps+  
              " internal steps\nat "+currentTime+ ", starting from "+initTime +" for an step of "+mStepSize);
        }
      }
    }
    else {
      while (max_t>tEnd) {
        max_t = mSolverEngine.internalStep(false); // false = no discontinuities
        if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver backwards at "+state[mTimeIndex]);
        if ((++counter)>mMaxInternalSteps) return self.error(EJSS_ODE_SOLVERS.ERROR.TOO_MANY_STEPS_ERROR,"The solver exceeded the number of internal steps at "+state[mTimeIndex]);
      }
    }
    doTheInterpolation (tEnd, state);
    return mStepSize;
  }

  // ----------------------------------------
  // Step with events and/or discontinuities
  // ----------------------------------------

  /**
   * Steps the ODE with events
   * @return
   */
  function maxStepWithEvents() {
    var state = mODE.getState();
    if (mZenoMaximumAllowedTimes>0 && mZenoCounter>mZenoMaximumAllowedTimes) {
      if (callZenoAction(state)) return 0;
    }

    var tBegin = state[mTimeIndex];
    // Check for a 0 rate for time
    var rate = mSolverEngine.getCurrentRate();
    if (tBegin+rate[mTimeIndex]==tBegin) return 0;

//    discontinuityAtEnd = null; // Do NOT clean the possible discontinuity
    var max_t = mSolverEngine.getMaximumTime(mHasDiscontinuities); // This detects discontinuities, if there are any
    if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver at "+state[mTimeIndex]);
    // Make sure the solver is not already at the maximum step
    if (tBegin==max_t) {
      max_t = mSolverEngine.internalStep(mHasDiscontinuities);
      if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver forwards at "+state[mTimeIndex]);
    }

    if (mLastEventData!=null) {
      if (!isNaN(mLastEventData.getMaxAdvance())) { // If a short-duration step was found, this helps avoid recurrent events
        max_t = mRunsForwards ? Math.min(max_t, mLastEventData.getMaxAdvance()) : Math.max(max_t, mLastEventData.getMaxAdvance());  
      }    
    }
    
    var tTest = mRunsForwards ? Math.min(tBegin+mMaxEventStep,max_t) : Math.max(tBegin-mMaxEventStep,max_t);

    while (true) {
      var problem=null;
      mCurrentEventData = null;       
      doTheInterpolation (tTest, mTest_ode_state);
      problem = self.findFirstEvent(state,tTest,mTest_ode_state); // Find the first event
      if (problem==null && mDiscontinuityAtEnd!=null && mDiscontinuityAtEnd.getTime()<=tTest) problem = mDiscontinuityAtEnd; // If no event, check for discontinuity (but regular events are acted on first)
      if (problem==null) {
        if (tTest==max_t) {
          for (var i=0; i<mDimension; i++) state[i] = mTest_ode_state[i];
          updateEventsAndDiscontinuities(state[mTimeIndex]);
          return max_t-tBegin;
        }
        tTest = mRunsForwards ? Math.min(tTest+mMaxEventStep,max_t) : Math.max(tTest-mMaxEventStep,max_t);
        continue;
      }

      // There was an event
      mCurrentEventData = problem;       
      if (mUseBestInterpolation) mSolverEngine.bestInterpolate(problem.getTime(), state); // So that we reinitialize with the best possible approximation 
      else for (var i=0; i<mDimension; i++) state[i] = mTest_ode_state[i]; // -- unnecessary -- interpolatorSolver.interpolate(eventData.time, state);
      var timeBefore = state[mTimeIndex];
      problem.action();
      self.reinitialize();
      state = mODE.getState(); // who knows if the event changes the ODE array pointer?
      if (timeBefore!=state[mTimeIndex]) { // If the event changes the time, it is meaningless to try to complete a step
        mZenoCounter = 0;
        mLastEventData = null;
        mCurrentEventData = null;
        problem.reset(state); // and that this event just happened has no meaning
        return (problem.getTime()-tBegin);
      }
      if (mLastEventData!=null) {
        if (Math.abs(mLastEventDataTime-problem.getTime())<mProximityThreshold) {
          mZenoCounter++;
        }
        else mZenoCounter = 0;
      }
      mLastEventData = problem;
      mLastEventDataTime = problem.getTime(); // because the internal value of lastEventData will change
      return (problem.getTime()-tBegin);
    }
  }

  /**
   * Steps the ODE with events
   * @return
   */
  function stepWithEvents() {
    var state = mODE.getState();
    if (mZenoMaximumAllowedTimes>0 && mZenoCounter>mZenoMaximumAllowedTimes) {
      if (callZenoAction(state)) return 0;
    }

    var tBegin = state[mTimeIndex];

    // Check for a 0 rate for time
    var rate = mSolverEngine.getCurrentRate();
    if (tBegin+rate[mTimeIndex]==tBegin) return 0;

    var tEnd = tBegin + mStepSize;
    
//    discontinuityAtEnd = null; // Do NOT clean the possible discontinuity
    var max_t = mSolverEngine.getMaximumTime(mHasDiscontinuities); // This detects discontinuities, if there are any
    if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver at "+state[mTimeIndex]);
    var tTest = mRunsForwards ? Math.min(tBegin+mMaxEventStep,tEnd) : Math.max(tBegin-mMaxEventStep,tEnd);

    var counter = 0;
    while (true) {
      var problem=null;
      mCurrentEventData = null;
      var notYetThere = mRunsForwards ? max_t<tTest : max_t>tTest;
      if (notYetThere) { // This is required because the interpolatorSolver cannot do an interpolation backwards from max_t
        mSolverEngine.bestInterpolate(max_t, mTest_ode_state);
        problem = self.findFirstEvent(state,max_t,mTest_ode_state); // Find the first event
        if (problem==null) problem = mDiscontinuityAtEnd; // If no event, check for discontinuity (but regular events are acted on first)
        if (problem==null) {
          for (var i=0; i<mDimension; i++) state[i] = mTest_ode_state[i]; // Update the current state
          // faster than interpolatorSolver.interpolate(max_t, state), since test_ode_state doesn't change if no event is found
          updateEventsAndDiscontinuities(state[mTimeIndex]);
          max_t = mSolverEngine.internalStep(mHasDiscontinuities);
          if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver looking for an event at "+state[mTimeIndex]);
          if ((++counter)>mMaxInternalSteps) return self.error (EJSS_ODE_SOLVERS.ERROR.TOO_MANY_STEPS_ERROR,"The solver exceeded the number of internal steps at "+state[mTimeIndex]);
          continue;
        }
      }
      else {
        doTheInterpolation (tTest, mTest_ode_state);
        problem = self.findFirstEvent(state,tTest,mTest_ode_state); // Find the first event
        if (problem==null && mDiscontinuityAtEnd!=null && mDiscontinuityAtEnd.time<=tTest) problem = mDiscontinuityAtEnd; // If no event, check for discontinuity (but regular events are acted on first)
        if (problem==null) {
          if (tTest==tEnd) {
            for (var i=0; i<mDimension; i++) state[i] = mTest_ode_state[i];
            updateEventsAndDiscontinuities(state[mTimeIndex]);
            return tEnd-tBegin;
          }
          tTest = mRunsForwards ? Math.min(tTest+mMaxEventStep,tEnd) : Math.max(tTest-mMaxEventStep,tEnd);
          continue;
        }
      }
      // There was an event or a discontinuity
      mCurrentEventData = problem;
      if (mUseBestInterpolation) mSolverEngine.bestInterpolate(problem.getTime(), state); // So that we reinitialize with the best possible approximation 
      else for (var i=0; i<mDimension; i++) state[i] = mTest_ode_state[i]; // -- unnecessary -- interpolatorSolver.interpolate(eventData.time, state);
      var timeBefore = state[mTimeIndex];
      var wantsToQuit = problem.action();
      self.reinitialize();
      counter = 0;
      state = mODE.getState(); // who knows if the event changes the ODE array pointer?
      if (timeBefore!=state[mTimeIndex]) { // If the event changes the time, it is meaningless to try to complete a step
        mZenoCounter = 0;
        mLastEventData = null;
        mCurrentEventData = null;
        problem.reset(state);
        return (problem.getTime()-tBegin);
      }
      if (mLastEventData!=null) {
        if (Math.abs(mLastEventDataTime-problem.getTime())<mProximityThreshold) {
          mZenoCounter++;
          if (mCoalesceCloseEvents) wantsToQuit = false; // If the events are too close, do not quit (this avoids unnecessary redraw)
        }
        else mZenoCounter = 0;
      }
      mLastEventData = problem;
      mLastEventDataTime = problem.getTime(); // because the internal value of lastEventData will change
      if (wantsToQuit) return (problem.getTime()-tBegin);
      if (mZenoMaximumAllowedTimes>0 && mZenoCounter>mZenoMaximumAllowedTimes) {
        if (callZenoAction(state)) return (problem.getTime()-tBegin);
      }
//      if (notYetThere) { // Respect the user's reading step (Is this really a good idea???)
//        max_t = runsForwards ? Math.min(max_t, interpolatorSolver.getMaximumTime()) : Math.max(max_t, interpolatorSolver.getMaximumTime());
//      }
//      else 
      mDiscontinuityAtEnd = null;
      max_t = mSolverEngine.getMaximumTime(mHasDiscontinuities); // This detects discontinuities, if there are any

      if (!isNaN(problem.getMaxAdvance())) { // If a short-duration step was found, this helps avoid recurrent events
        max_t = mRunsForwards ? Math.min(max_t, problem.getMaxAdvance()) : Math.max(max_t, problem.getMaxAdvance());  
      }
      if (isNaN(max_t)) return self.error (EJSS_ODE_SOLVERS.ERROR.INTERNAL_SOLVER_ERROR,"Error when stepping the solver after an event at "+state[mTimeIndex]);
    } // end of while
  }

  function updateEventsAndDiscontinuities (time) {
    var i, length;    
    length = mEventList.length;
    for (i=0; i<length; i++) { // Update the events
      var data = mEventList[i];
      data.findPosition(time,data.getH());
    }
    length = mDiscontinuityList.length;
    for (i=0; i<length; i++) { // Update the discontinuities
      var data = mDiscontinuityList[i];
      data.findPosition(time,data.getH());
    }
  }
  
  /**
   * A Zeno effect has been detected. Call the listeners.
   * If there are no listeners registered, then an error is issued, including possibly throwing an exception
   * @return true if the solver should stop the step and return a 0 step size
   */
  function callZenoAction(state) {
    if (mZenoList.length<=0) {
      self.error (EJSS_ODE_SOLVERS.ERROR.ZENO_EFFECT,"A Zeno-like effect has been detected.\nLast event was "+
          mLastEventData.getProblem() +" which took place at "+mLastEventDataTime);
      return true;
    }
    var returnAtZeno = false;
    var i;
    var length = mZenoList.length;
    for (i=0; i<length; i++) {
      var listener = mZenoList[i];
      if (listener.zenoEffectAction(mLastEventData.getProblem(), state)) returnAtZeno = true;
    }
    mZenoCounter = 0;
    return returnAtZeno; 
  }

  /**
   * Returns the first event that will take place next in the interval [current_state[timeIndex],tFinal].
   * @param double[] final_state contains the state at tFinal, if an event is found, it will contain the state at the event.
   * If no event is found, the final_state array will remain untouched.
   * The interpolator solver must be able to interpolate correctly in this interval!!!
   * Returns null if no event takes place in that interval. 
   */
  self.findFirstEvent = function(current_state, tFinal, final_state) {
    var i,length;
    mNumberOfAttempts = 0;
    // -- Find which events have happened
    var happensAtT1 = happensRightNow(current_state[mTimeIndex],final_state, mHappened,"at t1");
    if (happensAtT1!=null) {
      happensAtT1.time = current_state[mTimeIndex]; 
      happensAtT1.maxAdvance=tFinal; // limit the advance of the solver from this event. This helps avoid recurrent events
      for (i=0; i<mDimension; i++) final_state[i] = current_state[i];
      return happensAtT1;
    }
    // -- Check for no event
    if (mHappened.length<=0) return null; // IMPORTANT! final_state remains unchanged

    // --  Search until found or maximum of attempts is reached
    var doItAgain = true;
    length = mEventList.length;
    
    for (i=0; i<length; i++) {
      var eventData = mEventList[i];
      eventData.setHAfter(eventData.getH()); // evaluated in happensRightNow() (since no event happened at T1)
    } 
    while (doItAgain) {
      mNumberOfAttempts++;
      var tTest = nextPointToCheck (mHappened, current_state[mTimeIndex], tFinal); // The value of t where the event is expected to be
      doTheInterpolation (tTest, mIntermediate_ode_state);
      // Check if we discover an event at t1. This (short duration) event was not detected in [t1,tFinal]
      var shortDurationEvent = happensRightNow(current_state[mTimeIndex],mIntermediate_ode_state, mTemp_list, "short");
      if (shortDurationEvent!=null) { // This was a short-duration event!
        shortDurationEvent.time = current_state[mTimeIndex]; 
        shortDurationEvent.maxAdvance=tTest; // Try to avoid infinite loops
        for (i=0; i<mDimension; i++) final_state[i] = current_state[i];
        return shortDurationEvent;
      }
      
      if (mTemp_list.length<=0) { // No event in [t1,tTest]
        // check for event in tTest. This is a reduced check because all other possibilities were dealt with in happensRightNow()
        var happensInTtest=null;
        length = mHappened.length;
        for (i=0; i<length; i++) {
          var eventData = mHappened[i];
          if (eventData.getCurrentPosition()==EJSS_ODE_SOLVERS.ProblemData.POSITIVE) { 
            if (eventData.getH()<eventData.getEvent().getTolerance()) { happensInTtest = eventData; break; } // This one is the event
          }
          else { // NEGATIVE and crossing event
            if (eventData.getH()>-eventData.getEvent().getTolerance()) { happensInTtest = eventData; break; } // This one is the event
          }
        }
        if (happensInTtest!=null) { 
          happensInTtest.time = tTest; 
          happensInTtest.maxAdvance=tFinal; //Number.NaN;
          for (i=0; i<mDimension; i++) final_state[i] = mIntermediate_ode_state[i];
          return happensInTtest;
        }
        // repeat in [tTest,tFinal]
        for (i=0; i<mDimension; i++) current_state[i] = mIntermediate_ode_state[i];
        length = mEventList.length;
        for (i=0; i<length; i++) {
          var eventData = mEventList[i];
          eventData.findPosition(current_state[mTimeIndex],eventData.getH());
        }
        // -- Find which events have happened
        var happensNowInTtest = happensRightNow(current_state[mTimeIndex],final_state, mHappened, "at tTest");
        if (happensNowInTtest!=null) {
          happensNowInTtest.time = current_state[mTimeIndex]; 
          happensNowInTtest.maxAdvance=tFinal; //Number.NaN;
          for (i=0; i<mDimension; i++) final_state[i] = current_state[i];
          return happensNowInTtest;
        }
      }
      else { // temp_list is not null. I.e. there is at least one event in [t1,tTest] 
        // check for event in tTest. This is a reduced check because all other possibilities were dealt with in happensRightNow()
        // If there is any event previous to this, then this is not the point we are looking for.
        var notPreviousFound=true;
        var happensInTtest=null;
        length = mTemp_list.length;
        for (i=0; i<length; i++) {
          var data = mTemp_list[i];
          if (data.getCurrentPosition()==EJSS_ODE_SOLVERS.ProblemData.POSITIVE) { 
            if (data.getH()<=-data.getEvent().getTolerance()) { notPreviousFound = false; break; } // I happened earlier, so this is not the point
            // else if (data.getH()<data.getGeneralEvent().getTolerance()) no need to check since data.h < 0
            happensInTtest = data; // If there is no previous one, then it is me
          }
          else { // NEGATIVE and crossing event
            if (data.getH()>=data.getEvent().getTolerance()) { notPreviousFound = false; break; } // I happened earlier, so this is not the point
            // else if (data.h>-data.generalEvent.getTolerance()) // no need to check since data.h>0 
            happensInTtest = data; // If there is no previous one, then it is me
          }
        }
        if (notPreviousFound && happensInTtest!=null) { // This is the event!
          happensInTtest.time = tTest; 
          happensInTtest.maxAdvance=tFinal; //Number.NaN;
          for (i=0; i<mDimension; i++) final_state[i] = mIntermediate_ode_state[i];
          return happensInTtest; 
        }
        tFinal = tTest; 
        for (i=0; i<mDimension; i++) final_state[i] = mIntermediate_ode_state[i];
        length = mEventList.length;
        for (i=0; i<length; i++) {
          var eventData = mEventList[i];
          eventData.setHAfter(eventData.getH());
        }
        mHappened = [];
        length = mTemp_list.length;
        for (i=0; i<length; i++) mHappened.push(mTemp_list[i]);
      }
//       Check for the iteration limit
//      for (EventData data : happened) {
//        System.err.println ("Happened "+data.generalEvent+"\nCurrent state: ");
//        for (int i=0; i<dimension; i++) System.err.print(current_state[i]+", ");  
//        System.err.println ("\nFinal   state: ");
//        for (int i=0; i<dimension; i++) System.err.print(final_state[i]+", ");  
//        System.err.println ("\n");
//      }
      length = mHappened.length;
      for (i=0; i<length; i++) {
        var data = mHappened[i];
        if (mNumberOfAttempts>data.getEvent().getMaxIterations()) { doItAgain = false; break; }
      }
    }
    
    // If this happens, the event is most likely poorly designed!
    var remaining = mHappened[0]; // The event is any of those which remain in the list of happened
    self.error (EJSS_ODE_SOLVERS.ERROR.EVENT_NOT_FOUND,"Warning : Event not found after "+ mNumberOfAttempts+ " attempts at t=" + current_state[mTimeIndex]
        + " h = "+ remaining.getH()
        + ".\nPlease check the code of your event, decrease the initial step size, the tolerance of the solver,"
        +	"\nor the event maximum step, or increase the maximum number of attempts."
        + "\nFirst event remaining in the queue: "+remaining.getEvent());

    remaining.setTime((current_state[mTimeIndex]+tFinal)/2);
    remaining.setMaxAdvance(Number.NaN);
    mSolverEngine.bestInterpolate(remaining.time, final_state);
    return remaining;
  };
  
  /**
   * Fills the list with all events that happen between the current time and the provided final time.
   * Returns an event if it happens at the initial point, null otherwise
   */
  function happensRightNow(currentTime, final_state, list, id) {
    list.splice(0,list.length);
    var length = mEventList.length;
    for (var i=0; i<length; i++) {
      var eventData = mEventList[i];
      eventData.setH(eventData.getEvent().evaluate(final_state));
      switch (eventData.getCurrentPosition()) {
        default : 
        case EJSS_ODE_SOLVERS.ProblemData.POSITIVE : if (eventData.getH()<=0) list.push(eventData); break; // This event happens! 
        case EJSS_ODE_SOLVERS.ProblemData.SMALL_POSITIVE :
          if (eventData.getH()<=0 && (eventData.hasPositiveFlag() || eventData.getEventType()==EJSS_ODE_SOLVERS.EVENT_TYPE.STATE_EVENT)) return eventData;
          break;
        case EJSS_ODE_SOLVERS.ProblemData.ZERO :
          if (eventData.getH()<0) {
            if (eventData.hasPositiveFlag() || eventData.getEventType()==EJSS_ODE_SOLVERS.EVENT_TYPE.STATE_EVENT) return eventData;
          }
          else if (eventData.getH()>0) {
            if (eventData.hasNegativeFlag() && eventData.getEventType()==EJSS_ODE_SOLVERS.EVENT_TYPE.CROSSING_EVENT) return eventData;
          }
          break;
        case EJSS_ODE_SOLVERS.ProblemData.SMALL_NEGATIVE :
          if (eventData.getEventType()==EJSS_ODE_SOLVERS.EVENT_TYPE.STATE_EVENT) {
            if (eventData.getH()<=-eventData.getEvent().getTolerance()) return eventData;
          }
          else if (eventData.getEventType()==EJSS_ODE_SOLVERS.EVENT_TYPE.CROSSING_EVENT) {
            if (eventData.hasNegativeFlag() && eventData.getH()>=0) return eventData;
          }
          break;
        case EJSS_ODE_SOLVERS.ProblemData.NEGATIVE : 
          if (eventData.getEventType()==EJSS_ODE_SOLVERS.EVENT_TYPE.CROSSING_EVENT) { if (eventData.getH()>=0) list.push(eventData); }
          else if (eventData.getEventType()==EJSS_ODE_SOLVERS.EVENT_TYPE.STATE_EVENT) { // This should NOT happen, but I save the problem by saying that this event happens right now!
            self.error (EJSS_ODE_SOLVERS.ERROR.ILLEGAL_EVENT_STATE, "The system started from an illegal state at "+currentTime+ " for the state event "+eventData.getEvent());
            return eventData;
          }
          break; 
      } // end of switch
    } // end of for
    return null;
  }
  
  /**
   * The first point where events estimate the crossing takes place
   */
  function nextPointToCheck (list, t1, t2) {
    var i, length = list.length;
    var hB, tFirst = t2, dt = (t2 - t1), tMiddle = (t1+t2)/2;
    if (mRunsForwards) {
      for (i=0; i<length; i++) {
        var eventData = list[i];
        switch (eventData.getEvent().getRootFindingMethod()) {
          default : 
          case EJSS_ODE_SOLVERS.EVENT_METHOD.BISECTION : 
            tFirst = Math.min(tFirst, tMiddle); 
            break;
          case EJSS_ODE_SOLVERS.EVENT_METHOD.SECANT : 
//          System.err.println ("hBefore = "+eventData.hBefore);
//          System.err.println ("hAfter = "+eventData.hAfter);
//          System.err.println ("dt = "+dt);
//          System.err.println ("t1 = "+t1);
//          System.err.println ("tFirst = "+tFirst);
            hB = eventData.getHBefore();
            tFirst = Math.min(tFirst, t1 - hB * dt / (eventData.getHAfter() - hB)); 
            break;
        }
      }
    }
    else {
      for (i=0; i<length; i++) {
        var eventData = list[i];
        switch (eventData.getEvent().getRootFindingMethod()) {
          default : 
          case EJSS_ODE_SOLVERS.EVENT_METHOD.BISECTION : 
            tFirst = Math.max(tFirst, tMiddle); 
            break;
          case EJSS_ODE_SOLVERS.EVENT_METHOD.SECANT : 
            hB = eventData.getHBefore();
            tFirst = Math.max(tFirst, t1 - hB * dt / (eventData.getHAfter() - hB)); 
            break;
        }
      }
    }
    return tFirst;
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
 
    if (mODE.getDelays) { // It is a DelayDifferentialEquation
      mDDEdiscontinuity = EJSS_ODE_SOLVERS.ddeDiscontinuity(self,mODE);
    }
    mSolverEngine.setODE(self, mODE);
  
  return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.BogackiShampine23 = {
  B3_1 : 2.0/9.0,  B3_2 : 1.0/3.0, B3_3 : 4.0/9.0,
  B2_1 : 7.0/24.0, B2_2 : 1.0/4.0, B2_3 : 1.0/3.0, B2_4 : 1.0/8.0
};

/**
 * Constructor for BogackiShampine23
 * @returns BogackiShampine23
 */
EJSS_ODE_SOLVERS.bogackiShampine23 = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTimeAdaptive(3); // 3 is the method's order 
  var mRate2, mRate3, mOrder2;
  var superAllocateOtherArrays = self.allocateOtherArrays;

  self.getNumberOfEvaluations = function() { return 3; }
  
  self.allocateOtherArrays = function() {
    superAllocateOtherArrays();
    mRate2 = new Array(self.getDimension());
    mRate3 = new Array(self.getDimension());
    mOrder2 = new Array(self.getDimension());
  };

  self.computeIntermediateStep = function(step, state) {
    var halfStep = step/2;
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    
	var CTS = EJSS_ODE_SOLVERS.BogackiShampine23;
    var i;

    for (i=0; i<dimension; i++) state[i] = initialState[i] + halfStep*initialRate[i];
    ode.getRate(state, mRate2);
    
    var threeQuarterStep = 0.75*step;
    for (i=0; i<dimension; i++) state[i] = initialState[i] + threeQuarterStep*mRate2[i]; // 3/4
    ode.getRate(state, mRate3);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(CTS.B3_1*initialRate[i]+CTS.B3_2*mRate2[i]+CTS.B3_3*mRate3[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var halfStep = step/2;
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var CTS = EJSS_ODE_SOLVERS.BogackiShampine23;
    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
	    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + halfStep*initialRate[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate2);

    var threeQuarterStep = 0.75*step;
    for (i=0; i<dimension; i++) state[i] = initialState[i] + threeQuarterStep*mRate2[i]; // 3/4
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate3);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(CTS.B3_1*initialRate[i]+CTS.B3_2*mRate2[i]+CTS.B3_3*mRate3[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    // The final rate has already been computed (it was needed to estimate the error) 
    //ode.getRate(finalState, finalRate);
    return EJSS_ODE_INTERPOLATION.hermiteIntervalData(initialState, initialRate, finalState, finalRate);

  }

  self.computeApproximation = function(step) {
    var ode = self.getODE();
    var timeIndex = self.getTimeIndex();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    ode.getRate(finalState, finalRate);
    var CTS = EJSS_ODE_SOLVERS.BogackiShampine23;

    for (var i=0; i<timeIndex; i++) mOrder2[i] = initialState[i] + step*(CTS.B2_1*initialRate[i]+CTS.B2_2*mRate2[i]+CTS.B2_3*mRate3[i]+CTS.B2_4*finalRate[i]);
    mOrder2[timeIndex] = self.getInitialTime()  + step*initialRate[timeIndex];
    return self.computeError(mOrder2);
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.CashKarp45 = {
  A_11 :   1.0/5.0,
  A_21 :   3.0/40.0,     A_22 :  9.0/40.0,
  A_31 :   3.0/10.0,     A_32 : -9.0/10.0,   A_33 :  6.0/5.0,
  A_41 : -11.0/54.0,     A_42 :  5.0/2.0,    A_43 : -70.0/27.0,    A_44 : 35.0/27.0,
  A_51 : 1631.0/55296.0, A_52 : 175.0/512.0, A_53 : 575.0/13824.0, A_54 : 44275.0/110592.0, A_55 : 253.0/4096.0,
  // B4 are the 4th order coefficients
  B4_1 : 2825./27648., B4_2 : 0., B4_3 : 18575./48384., B4_4 : 13525./55296., B4_5 : 277./14336., B4_6 : 1./4.,
  // B5 are the 5th order coefficients
  B5_1 : 37./378.,     B5_2 : 0., B5_3 : 250./621.,     B5_4 : 125./594.,     B5_5 : 0.,          B5_6 : 512./1771. 
  //  E_1 = 277./64512.,   E_2 = 0.,  E_3 = -6925./370944., E_4 = 6925./202752.,  E_5 = 277./14336.,  E_6 = -277./7084.

};

/**
 * Constructor for CashKarp45
 * @returns CashKarp45
 */
EJSS_ODE_SOLVERS.cashKarp45 = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTimeAdaptive(5); // 5 is the method's order 
  var mRate2, mRate3, mRate4, mRate5, mRate6, mOrder4;
  var superAllocateOtherArrays = self.allocateOtherArrays;

  self.getNumberOfEvaluations = function() { return 6; }
  
  self.allocateOtherArrays = function() {
    superAllocateOtherArrays();
    mRate2 = new Array(self.getDimension());
    mRate3 = new Array(self.getDimension());
    mRate4 = new Array(self.getDimension());
    mRate5 = new Array(self.getDimension());
    mRate6 = new Array(self.getDimension());
    mOrder4 = new Array(self.getDimension());
  };

  self.computeIntermediateStep = function(step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    
	var CTS = EJSS_ODE_SOLVERS.CashKarp45;
    var i;

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*CTS.A_11*initialRate[i];
    ode.getRate(state, mRate2);
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_21*initialRate[i] + CTS.A_22*mRate2[i]);
    ode.getRate(state, mRate3);
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_31*initialRate[i]+CTS.A_32*mRate2[i]+CTS.A_33*mRate3[i]);
    ode.getRate(state, mRate4);
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_41*initialRate[i]+CTS.A_42*mRate2[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    ode.getRate(state, mRate5);
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_51*initialRate[i]+CTS.A_52*mRate2[i]+CTS.A_53*mRate3[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    ode.getRate(state, mRate6);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(CTS.B5_1*initialRate[i]+CTS.B5_2*mRate2[i]+CTS.B5_3*mRate3[i]+CTS.B5_4*mRate4[i]+CTS.B5_5*mRate5[i]+CTS.B5_6*mRate6[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var CTS = EJSS_ODE_SOLVERS.CashKarp45;
    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
	    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*CTS.A_11*initialRate[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate2);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_21*initialRate[i]+CTS.A_22*mRate2[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate3);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_31*initialRate[i]+CTS.A_32*mRate2[i]+CTS.A_33*mRate3[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate4);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_41*initialRate[i]+CTS.A_42*mRate2[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate5);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_51*initialRate[i]+CTS.A_52*mRate2[i]+CTS.A_53*mRate3[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate6);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(CTS.B5_1*initialRate[i]+CTS.B5_2*mRate2[i]+CTS.B5_3*mRate3[i]+CTS.B5_4*mRate4[i]+CTS.B5_5*mRate5[i]+CTS.B5_6*mRate6[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    ode.getRate(finalState, finalRate);
    return EJSS_ODE_INTERPOLATION.bootstrap2IntervalData(initialState, initialRate,finalState, finalRate, ode);

  }

  self.computeApproximation = function(step) {
    var timeIndex = self.getTimeIndex();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var CTS = EJSS_ODE_SOLVERS.CashKarp45;

    for (var i=0; i<timeIndex; i++) mOrder4[i] = initialState[i] + step*(CTS.B4_1*initialRate[i]+CTS.B4_2*mRate2[i]+CTS.B4_3*mRate3[i]+CTS.B4_4*mRate4[i]+CTS.B4_5*mRate5[i]+CTS.B4_6*mRate6[i]);
    mOrder4[timeIndex] = self.getInitialTime()  + step*initialRate[timeIndex];
    return self.computeError(mOrder4);
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.DoPri5 = {
  A_11 :     1.0/5.0,
  A_21 :     3.0/40.0,   A_22 :      9.0/40.0,
  A_31 :    44.0/45.0,   A_32 :    -56.0/15.0,   A_33 :    32.0/9.0,
  A_41 : 19372.0/6561.0, A_42 : -25360.0/2187.0, A_43 : 64448.0/6561.0, A_44 : -212.0/729.0,
  A_51 :  9017.0/3168.0, A_52 :   -355.0/33.0,   A_53 : 46732.0/5247.0, A_54 :   49.0/176.0, A_55 : -5103.0/18656.0,
  
  
  // B5 are the 5th order coefficients
  B5_1 : 35.0/384.0,  
  B5_2 : 0.0, 
  B5_3 : 500.0/1113.0, 
  B5_4 : 125.0/192.0, 
  B5_5 : -2187.0/6784.0, 
  B5_6 : 11.0/84.0,
  
  D_1 : -12715105075.0/11282082432.0,  
  D_2 : 0.0, 
  D_3 : 87487479700.0/32700410799.0, 
  D_4 : -10690763975.0/1880347072.0,
  D_5 : 701980252875.0/199316789632.0, 
  D_6 : -1453857185.0/822651844.0, 
  D_7 : 69997945.0/29380423.0,
  
  // Error coefficients
  E_1 : 71.0/57600.0, 
  E_2 : 0.0, 
  E_3 : -71.0/16695.0, 
  E_4 : 71.0/1920.0, 
  E_5 : -17253.0/339200.0, 
  E_6 : 22.0/525.0, 
  E_7 : -1.0/40.0
};

/**
 * Constructor for DoPri5
 * @returns DoPri5
 */
EJSS_ODE_SOLVERS.doPri5 = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTimeAdaptive(5); // 5 is the method's order 
  var mRate2, mRate3, mRate4, mRate5, mRate6, mCoeffs;
  var superAllocateOtherArrays = self.allocateOtherArrays;

  self.getNumberOfEvaluations = function() { return 6; }
  
  self.allocateOtherArrays = function() {
    superAllocateOtherArrays();
    var dim = self.getDimension();
    mRate2 = new Array(dim);
    mRate3 = new Array(dim);
    mRate4 = new Array(dim);
    mRate5 = new Array(dim);
    mRate6 = new Array(dim);
    mCoeffs = new Array(5);
    for (var i=0; i<5; i++) mCoeffs[i] = new Array(dim);
  };

  self.computeIntermediateStep = function(step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    
	var CTS = EJSS_ODE_SOLVERS.DoPri5;
    var i;

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*CTS.A_11*initialRate[i];
    ode.getRate(state, mRate2);
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_21*initialRate[i] + CTS.A_22*mRate2[i]);
    ode.getRate(state, mRate3);
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_31*initialRate[i]+CTS.A_32*mRate2[i]+CTS.A_33*mRate3[i]);
    ode.getRate(state, mRate4);
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_41*initialRate[i]+CTS.A_42*mRate2[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    ode.getRate(state, mRate5);
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_51*initialRate[i]+CTS.A_52*mRate2[i]+CTS.A_53*mRate3[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    ode.getRate(state, mRate6);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(CTS.B5_1*initialRate[i]+CTS.B5_2*mRate2[i]+CTS.B5_3*mRate3[i]+CTS.B5_4*mRate4[i]+CTS.B5_5*mRate5[i]+CTS.B5_6*mRate6[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];
  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var CTS = EJSS_ODE_SOLVERS.DoPri5;
    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
	    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*CTS.A_11*initialRate[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate2);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_21*initialRate[i]+CTS.A_22*mRate2[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate3);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_31*initialRate[i]+CTS.A_32*mRate2[i]+CTS.A_33*mRate3[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate4);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_41*initialRate[i]+CTS.A_42*mRate2[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate5);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_51*initialRate[i]+CTS.A_52*mRate2[i]+CTS.A_53*mRate3[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate6);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(CTS.B5_1*initialRate[i]+CTS.B5_2*mRate2[i]+CTS.B5_3*mRate3[i]+CTS.B5_4*mRate4[i]+CTS.B5_5*mRate5[i]+CTS.B5_6*mRate6[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    // The final rate has already been computed (it is needed to estimate the error) 
    // calculation of interpolation coefficients 
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    
	var CTS = EJSS_ODE_SOLVERS.DoPri5;
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    
    var dt = finalState[timeIndex]-initialState[timeIndex];
    for (var i=0; i<dimension; i++) {
      var initStateI = initialState[i]; // for efficiency
      var initRateI  = initialRate[i];
      var finalRateI = finalRate[i];
      mCoeffs[0][i] = initStateI; 
      var coeff1i = finalState[i] - initStateI;
      var coeff2i = dt*initRateI - coeff1i;
      mCoeffs[3][i] = coeff1i - dt*finalRateI - coeff2i;
      mCoeffs[4][i] = dt*(CTS.D_1*initRateI+CTS.D_2*mRate2[i]+CTS.D_3*mRate3[i]+CTS.D_4*mRate4[i]+CTS.D_5*mRate5[i]+CTS.D_6*mRate6[i]+CTS.D_7*finalRateI);
      mCoeffs[1][i] = coeff1i;
      mCoeffs[2][i] = coeff2i;
    }
    return EJSS_ODE_INTERPOLATION.dopri5IntervalData(initialState, finalState, mCoeffs);
  }

  self.computeApproximation = function(step) {
	var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    var absTol = self.getAbsTol();
    var relTol = self.getRelTol();

    var CTS = EJSS_ODE_SOLVERS.DoPri5;
    var dimension = self.getDimension();

    ode.getRate(finalState, finalRate);
    var error = 0;
    for (var i=0; i<dimension; i++) {
      var sk = absTol[i] + relTol[i] * Math.max(Math.abs(finalState[i]), Math.abs(initialState[i]));
      var errorI = (CTS.E_1*initialRate[i]+CTS.E_2*mRate2[i]+CTS.E_3*mRate3[i]+CTS.E_4*mRate4[i]+CTS.E_5*mRate5[i]+CTS.E_6*mRate6[i]+CTS.E_7*finalRate[i])/sk;
      error += errorI*errorI;
    }
    return Math.sqrt(error/dimension);
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.DoPri853 = {
  A_11 : 5.26001519587677318785587544488E-2,
  A_21 : 1.97250569845378994544595329183E-2, A_22 : 5.91751709536136983633785987549E-2,
  A_31 : 2.95875854768068491816892993775E-2, A_32 : 0.0, A_33 : 8.87627564304205475450678981324E-2,
  A_41 : 2.41365134159266685502369798665E-1, A_42 : 0.0, A_43 : -8.84549479328286085344864962717E-1,
  A_44 : 9.24834003261792003115737966543E-1,
  A_51 :  3.7037037037037037037037037037E-2, A_52 : 0.0, A_53 : 0.0, A_54 : 1.70828608729473871279604482173E-1,
  A_55 : 1.25467687566822425016691814123E-1,
  A_61 : 3.7109375E-2, A_62 : 0.0, A_63 : 0.0, A_64 : 1.70252211019544039314978060272E-1,
  A_65 : 6.02165389804559606850219397283E-2, A_66 : -1.7578125E-2,
  A_71 : 3.70920001185047927108779319836E-2, A_72 : 0.0, A_73 : 0.0, A_74 : 1.70383925712239993810214054705E-1,
  A_75 : 1.07262030446373284651809199168E-1, A_76 : -1.53194377486244017527936158236E-2, 
  A_77 : 8.27378916381402288758473766002E-3,
  A_81 : 6.24110958716075717114429577812E-1, A_82 : 0.0, A_83 : 0.0, A_84 : -3.36089262944694129406857109825E0,
  A_85 : -8.68219346841726006818189891453E-1, A_86 : 2.75920996994467083049415600797E1,
  A_87 :  2.01540675504778934086186788979E1, A_88 : -4.34898841810699588477366255144E1,
  A_91 : 4.77662536438264365890433908527E-1, A_92 : 0.0, A_93 : 0.0, A_94 : -2.48811461997166764192642586468E0,
  A_95 : -5.90290826836842996371446475743E-1, A_96 :  2.12300514481811942347288949897E1,
  A_97 :  1.52792336328824235832596922938E1,  A_98 : -3.32882109689848629194453265587E1,
  A_99 : -2.03312017085086261358222928593E-2,
  A_101 : -9.3714243008598732571704021658E-1, A_102 : 0.0, A_103 : 0.0, A_104 : 5.18637242884406370830023853209E0,
  A_105 : 1.09143734899672957818500254654E0,  A_106 : -8.14978701074692612513997267357E0,
  A_107 : -1.85200656599969598641566180701E1, A_108 : 2.27394870993505042818970056734E1,
  A_109 : 2.49360555267965238987089396762E0,  A_1010 : -3.0467644718982195003823669022E0,
  A_111 : 2.27331014751653820792359768449E0,    A_112 : 0.0, A_113 : 0.0, A_114 : -1.05344954667372501984066689879E1,
  A_115 : -2.00087205822486249909675718444E0, A_116 : -1.79589318631187989172765950534E1,
  A_117 : 2.79488845294199600508499808837E1,  A_118 : -2.85899827713502369474065508674E0,
  A_119 : -8.87285693353062954433549289258E0, A_1110 : 1.23605671757943030647266201528E1,
  A_1111 : 6.43392746015763530355970484046E-1,

  // B8 are the 11th order coefficients
  B8_1 : 5.42937341165687622380535766363E-2, B8_2 : 0.0, B8_3 : 0.0, B8_4 : 0.0, B8_5 : 0.0,
  B8_6 : 4.45031289275240888144113950566E0, B8_7 : 1.89151789931450038304281599044E0,
  B8_8 : -5.8012039600105847814672114227E0, B8_9 : 3.1116436695781989440891606237E-1,
  B8_10 : -1.52160949662516078556178806805E-1, B8_11 : 2.01365400804030348374776537501E-1,
  B8_12 : 4.47106157277725905176885569043E-2,

  AD13_1 : 5.61675022830479523392909219681E-2, AD13_2 : 0, AD13_3 : 0, AD13_4 : 0, AD13_5 : 0, AD13_6 : 0,
  AD13_7 : 2.53500210216624811088794765333E-1,  AD13_8  : -2.46239037470802489917441475441E-1,
  AD13_9 : -1.24191423263816360469010140626E-1, AD13_10 : 1.5329179827876569731206322685E-1,
  AD13_11 : 8.20105229563468988491666602057E-3, AD13_12 : 7.56789766054569976138603589584E-3,
  AD13_13 : -8.298E-3,

  AD14_1 : 3.18346481635021405060768473261E-2, AD14_2 : 0, AD14_3 : 0, AD14_4 : 0, AD14_5 : 0,
  AD14_6 : 2.83009096723667755288322961402E-2, AD14_7 : 5.35419883074385676223797384372E-2,
  AD14_8 : -5.49237485713909884646569340306E-2, AD14_9 : 0, AD14_10 : 0,
  AD14_11 : -1.08347328697249322858509316994E-4, AD14_12 : 3.82571090835658412954920192323E-4,
  AD14_13 : -3.40465008687404560802977114492E-4, AD14_14 : 1.41312443674632500278074618366E-1,

  AD15_1 : -4.28896301583791923408573538692E-1, AD15_2 : 0, AD15_3 : 0, AD15_4 : 0, AD15_5 : 0,
  AD15_6 : -4.69762141536116384314449447206E0, AD15_7 : 7.68342119606259904184240953878E0,
  AD15_8 : 4.06898981839711007970213554331E0,  AD15_9 : 3.56727187455281109270669543021E-1, AD15_10 : 0, AD15_11 : 0, AD15_12 : 0,
  AD15_13 : -1.39902416515901462129418009734E-3, AD15_14 : 2.9475147891527723389556272149E0, AD15_15 : -9.15095847217987001081870187138E0,

  D4_1 : -0.84289382761090128651353491142E+01, D4_2 : 0, D4_3 : 0, D4_4 : 0, D4_5 : 0,
  D4_6 : 0.56671495351937776962531783590E+00, D4_7 : -0.30689499459498916912797304727E+01,
  D4_8 : 0.23846676565120698287728149680E+01, D4_9 : 0.21170345824450282767155149946E+01,
  D4_10 : -0.87139158377797299206789907490E+00, D4_11 : 0.22404374302607882758541771650E+01,
  D4_12 : 0.63157877876946881815570249290E+00,  D4_13 : -0.88990336451333310820698117400E-01,
  D4_14 : 0.18148505520854727256656404962E+02,  D4_15 : -0.91946323924783554000451984436E+01,
  D4_16 : -0.44360363875948939664310572000E+01,

  D5_1 : 0.10427508642579134603413151009E+02, D5_2 : 0, D5_3 : 0, D5_4 : 0, D5_5 : 0,
  D5_6 : 0.24228349177525818288430175319E+03,  D5_7 : 0.16520045171727028198505394887E+03,
  D5_8 : -0.37454675472269020279518312152E+03, D5_9 : -0.22113666853125306036270938578E+02,
  D5_10 : 0.77334326684722638389603898808E+01, D5_11 : -0.30674084731089398182061213626E+02,
  D5_12 : -0.93321305264302278729567221706E+01, D5_13 : 0.15697238121770843886131091075E+02,
  D5_14 : -0.31139403219565177677282850411E+02, D5_15 : -0.93529243588444783865713862664E+01,
  D5_16 : 0.35816841486394083752465898540E+02,

  D6_1 : 0.19985053242002433820987653617E+02, D6_2 : 0, D6_3 : 0, D6_4 : 0, D6_5 : 0,
  D6_6 : -0.38703730874935176555105901742E+03, D6_7 : -0.18917813819516756882830838328E+03,
  D6_8 : 0.52780815920542364900561016686E+03,  D6_9 : -0.11573902539959630126141871134E+02,
  D6_10 : 0.68812326946963000169666922661E+01, D6_11 : -0.10006050966910838403183860980E+01,
  D6_12 : 0.77771377980534432092869265740E+00, D6_13 : -0.27782057523535084065932004339E+01,
  D6_14 : -0.60196695231264120758267380846E+02,D6_15 : 0.84320405506677161018159903784E+02,
  D6_16 : 0.11992291136182789328035130030E+02,

  D7_1 : -0.25693933462703749003312586129E+02, D7_2 : 0, D7_3 : 0, D7_4 : 0, D7_5 : 0,
  D7_6 : -0.15418974869023643374053993627E+03, D7_7 : -0.23152937917604549567536039109E+03,
  D7_8 : 0.35763911791061412378285349910E+03,  D7_9 : 0.93405324183624310003907691704E+02,
  D7_10 : -0.37458323136451633156875139351E+02,  D7_11 : 0.10409964950896230045147246184E+03,
  D7_12 : 0.29840293426660503123344363579E+02, D7_13 : -0.43533456590011143754432175058E+02,
  D7_14 : 0.96324553959188282948394950600E+02, D7_15 : -0.39177261675615439165231486172E+02,
  D7_16 : -0.14972683625798562581422125276E+03,

		  // E3 are the 3th order error coefficients
  E3_1 : -1.898007540724076157147023288760E-01, E3_2 : 0, E3_3 : 0, E3_4 : 0, E3_5 : 0,
  E3_6 : 4.45031289275240888144113950566E+00, E3_7 : 1.89151789931450038304281599044E+00,
  E3_8 : -5.8012039600105847814672114227E+00, E3_9 : -4.22682321323791962932445679177E-01,
  E3_10 : -1.52160949662516078556178806805E-01, E3_11 : 2.01365400804030348374776537501E-01,
  E3_12 : 2.26517921983608258118062039631E-02,

		  // E5 are the 5th order error coefficients
  E5_1 : 0.1312004499419488073250102996E-01, E5_2 : 0, E5_3 : 0, E5_4 : 0, E5_5 : 0,
  E5_6 : -0.1225156446376204440720569753E+01, E5_7 : -0.4957589496572501915214079952E+00,
  E5_8 : 0.1664377182454986536961530415E+01,  E5_9 : -0.3503288487499736816886487290E+00,
  E5_10 : 0.3341791187130174790297318841E+00, E5_11 : 0.8192320648511571246570742613E-01,
  E5_12 : -0.2235530786388629525884427845E-01

};

/**
 * Constructor for DoPri853
 * @returns DoPri853
 */
EJSS_ODE_SOLVERS.doPri853 = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTimeAdaptive(8); // 8 is the method's order 
  var mRate2,  mRate3,  mRate4,  mRate5,  mRate6,  mRate7,  mRate8, mRate9;
  var mRate10, mRate11, mRate12, mRate14, mRate15, mRate16, mAuxState;
  var mCoeffs;
  var superAllocateOtherArrays = self.allocateOtherArrays;

  self.getNumberOfEvaluations = function() { return 16; }
  
  self.allocateOtherArrays = function() {
    superAllocateOtherArrays();
    var dim = self.getDimension();
    mRate2 = new Array(dim);
    mRate3 = new Array(dim);
    mRate4 = new Array(dim);
    mRate5 = new Array(dim);
    mRate6 = new Array(dim);
    mRate7 = new Array(dim);
    mRate8 = new Array(dim);
    mRate9 = new Array(dim);
    mRate10 = new Array(dim);
    mRate11 = new Array(dim);
    mRate12 = new Array(dim);
    mRate13 = new Array(dim);
    mRate14 = new Array(dim);
    mRate15 = new Array(dim);
    mRate16 = new Array(dim);
    mAuxState = new Array(dim);
    mCoeffs = new Array(8);
    for (var i=0; i<8; i++) mCoeffs[i] = new Array(dim);
  };

  self.computeIntermediateStep = function(step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    
	var CTS = EJSS_ODE_SOLVERS.DoPri853;
    var i;

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*CTS.A_11*initialRate[i];
    ode.getRate(state, mRate2);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_21*initialRate[i]+CTS.A_22*mRate2[i]);
    ode.getRate(state, mRate3);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_31*initialRate[i]+CTS.A_32*mRate2[i]+CTS.A_33*mRate3[i]);
    ode.getRate(state, mRate4);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_41*initialRate[i]+CTS.A_42*mRate2[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    ode.getRate(state, mRate5);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_51*initialRate[i]+CTS.A_52*mRate2[i]+CTS.A_53*mRate3[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    ode.getRate(state, mRate6);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_61*initialRate[i]+CTS.A_62*mRate2[i]+CTS.A_63*mRate3[i]+CTS.A_64*mRate4[i]+
        CTS.A_65*mRate5[i]+CTS.A_66*mRate6[i]);
    ode.getRate(state, mRate7);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_71*initialRate[i]+CTS.A_72*mRate2[i]+CTS.A_73*mRate3[i]+CTS.A_74*mRate4[i]+
        CTS.A_75*mRate5[i]+CTS.A_76*mRate6[i]+CTS.A_77*mRate7[i]);
    ode.getRate(state, mRate8);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_81*initialRate[i]+CTS.A_82*mRate2[i]+CTS.A_83*mRate3[i]+CTS.A_84*mRate4[i]+
        CTS.A_85*mRate5[i]+CTS.A_86*mRate6[i]+CTS.A_87*mRate7[i]+CTS.A_88*mRate8[i]);
    ode.getRate(state, mRate9);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_91*initialRate[i]+CTS.A_92*mRate2[i]+CTS.A_93*mRate3[i]+CTS.A_94*mRate4[i]+
        CTS.A_95*mRate5[i]+CTS.A_96*mRate6[i]+CTS.A_97*mRate7[i]+CTS.A_98*mRate8[i]+CTS.A_99*mRate9[i]);
    ode.getRate(state, mRate10);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_101*initialRate[i]+CTS.A_102*mRate2[i]+CTS.A_103*mRate3[i]+CTS.A_104*mRate4[i]+
        CTS.A_105*mRate5[i]+CTS.A_106*mRate6[i]+CTS.A_107*mRate7[i]+CTS.A_108*mRate8[i]+CTS.A_109*mRate9[i]+CTS.A_1010*mRate10[i]);
    ode.getRate(state, mRate11);
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_111*initialRate[i]+CTS.A_112*mRate2[i]+CTS.A_113*mRate3[i]+CTS.A_114*mRate4[i]+
        CTS.A_115*mRate5[i]+CTS.A_116*mRate6[i]+CTS.A_117*mRate7[i]+CTS.A_118*mRate8[i]+CTS.A_119*mRate9[i]+CTS.A_1110*mRate10[i]+CTS.A_1111*mRate11[i]);
    ode.getRate(state, mRate12);
    for (i=0; i<timeIndex; i++) state[i] = initialState[i]+step*(CTS.B8_1*initialRate[i]+CTS.B8_2*mRate2[i]+CTS.B8_3*mRate3[i]+CTS.B8_4*mRate4[i]+
        CTS.B8_5*mRate5[i]+CTS.B8_6*mRate6[i]+CTS.B8_7*mRate7[i]+CTS.B8_8*mRate8[i]+CTS.B8_9*mRate9[i]+CTS.B8_10*mRate10[i]+CTS.B8_11*mRate11[i]+CTS.B8_12*mRate12[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];
  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var CTS = EJSS_ODE_SOLVERS.DoPri853;
    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
	    
    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*CTS.A_11*initialRate[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate2);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_21*initialRate[i]+CTS.A_22*mRate2[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate3);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_31*initialRate[i]+CTS.A_32*mRate2[i]+CTS.A_33*mRate3[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate4);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_41*initialRate[i]+CTS.A_42*mRate2[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate5);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_51*initialRate[i]+CTS.A_52*mRate2[i]+CTS.A_53*mRate3[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate6);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_61*initialRate[i]+CTS.A_62*mRate2[i]+CTS.A_63*mRate3[i]+CTS.A_64*mRate4[i]+
        CTS.A_65*mRate5[i]+CTS.A_66*mRate6[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate7);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_71*initialRate[i]+CTS.A_72*mRate2[i]+CTS.A_73*mRate3[i]+CTS.A_74*mRate4[i]+
        CTS.A_75*mRate5[i]+CTS.A_76*mRate6[i]+CTS.A_77*mRate7[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate8);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_81*initialRate[i]+CTS.A_82*mRate2[i]+CTS.A_83*mRate3[i]+CTS.A_84*mRate4[i]+
        CTS.A_85*mRate5[i]+CTS.A_86*mRate6[i]+CTS.A_87*mRate7[i]+CTS.A_88*mRate8[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate9);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_91*initialRate[i]+CTS.A_92*mRate2[i]+CTS.A_93*mRate3[i]+CTS.A_94*mRate4[i]+
        CTS.A_95*mRate5[i]+CTS.A_96*mRate6[i]+CTS.A_97*mRate7[i]+CTS.A_98*mRate8[i]+CTS.A_99*mRate9[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate10);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_101*initialRate[i]+CTS.A_102*mRate2[i]+CTS.A_103*mRate3[i]+CTS.A_104*mRate4[i]+
        CTS.A_105*mRate5[i]+CTS.A_106*mRate6[i]+CTS.A_107*mRate7[i]+CTS.A_108*mRate8[i]+CTS.A_109*mRate9[i]+CTS.A_1010*mRate10[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate11);

    for (i=0; i<dimension; i++) state[i] = initialState[i]+step*(CTS.A_111*initialRate[i]+CTS.A_112*mRate2[i]+CTS.A_113*mRate3[i]+CTS.A_114*mRate4[i]+
        CTS.A_115*mRate5[i]+CTS.A_116*mRate6[i]+CTS.A_117*mRate7[i]+CTS.A_118*mRate8[i]+CTS.A_119*mRate9[i]+CTS.A_1110*mRate10[i]+CTS.A_1111*mRate11[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate12);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i]+step*(CTS.B8_1*initialRate[i]+CTS.B8_2*mRate2[i]+CTS.B8_3*mRate3[i]+CTS.B8_4*mRate4[i]+
        CTS.B8_5*mRate5[i]+CTS.B8_6*mRate6[i]+CTS.B8_7*mRate7[i]+CTS.B8_8*mRate8[i]+CTS.B8_9*mRate9[i]+CTS.B8_10*mRate10[i]+CTS.B8_11*mRate11[i]+CTS.B8_12*mRate12[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    // The final rate has already been computed (it is needed to estimate the error) 
    // calculation of interpolation coefficients 
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    
	var CTS = EJSS_ODE_SOLVERS.DoPri853;
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var i;
    
    ode.getRate(finalState, finalRate);
    // calculation of interpolation coefficients 
    var deltaTime = finalState[timeIndex]-initialState[timeIndex];
    for (i=0; i<dimension; i++)
      mAuxState[i] = initialState[i]+deltaTime*(CTS.AD13_1*initialRate[i]+CTS.AD13_2*mRate2[i]+CTS.AD13_3*mRate3[i]+CTS.AD13_4*mRate4[i]+
          CTS.AD13_5*mRate5[i]+CTS.AD13_6*mRate6[i]+CTS.AD13_7*mRate7[i]+CTS.AD13_8*mRate8[i]+
          CTS.AD13_9*mRate9[i]+CTS.AD13_10*mRate10[i]+CTS.AD13_11*mRate11[i]+CTS.AD13_12*mRate12[i]+
          CTS.AD13_13*finalRate[i]);
    ode.getRate(mAuxState, mRate14);
    for (i=0; i<dimension; i++)
      mAuxState[i] = initialState[i]+deltaTime*(CTS.AD14_1*initialRate[i]+CTS.AD14_2*mRate2[i]+CTS.AD14_3*mRate3[i]+CTS.AD14_4*mRate4[i]+
          CTS.AD14_5*mRate5[i]+CTS.AD14_6*mRate6[i]+CTS.AD14_7*mRate7[i]+CTS.AD14_8*mRate8[i]+
          CTS.AD14_9*mRate9[i]+CTS.AD14_10*mRate10[i]+CTS.AD14_11*mRate11[i]+CTS.AD14_12*mRate12[i]+
          CTS.AD14_13*finalRate[i]+CTS.AD14_14*mRate14[i]);
    ode.getRate(mAuxState, mRate15);
    for (i=0; i<dimension; i++)
      mAuxState[i] = initialState[i]+deltaTime*(CTS.AD15_1*initialRate[i]+CTS.AD15_2*mRate2[i]+CTS.AD15_3*mRate3[i]+CTS.AD15_4*mRate4[i]+
          CTS.AD15_5*mRate5[i]+CTS.AD15_6*mRate6[i]+CTS.AD15_7*mRate7[i]+CTS.AD15_8*mRate8[i]+
          CTS.AD15_9*mRate9[i]+CTS.AD15_10*mRate10[i]+CTS.AD15_11*mRate11[i]+CTS.AD15_12*mRate12[i]+
          CTS.AD15_13*finalRate[i]+CTS.AD15_14*mRate14[i]+CTS.AD15_15*mRate15[i]);
    ode.getRate(mAuxState, mRate16);
    for (i=0; i<dimension; i++) {
      mCoeffs[0][i] = initialState[i]; // i'am not sure -> Y[i]
      mCoeffs[1][i] = finalState[i] - initialState[i];
      mCoeffs[2][i] = deltaTime*initialRate[i] - mCoeffs[1][i];
      mCoeffs[3][i] = mCoeffs[1][i] - deltaTime*finalRate[i] - mCoeffs[2][i];
      mCoeffs[4][i] = deltaTime*(CTS.D4_1*initialRate[i]+CTS.D4_2*mRate2[i]+CTS.D4_3*mRate3[i]+CTS.D4_4*mRate4[i]+
          CTS.D4_5*mRate5[i]+CTS.D4_6*mRate6[i]+CTS.D4_7*mRate7[i]+CTS.D4_8*mRate8[i]+
          CTS.D4_9*mRate9[i]+CTS.D4_10*mRate10[i]+CTS.D4_11*mRate11[i]+CTS.D4_12*mRate12[i]+
          CTS.D4_13*finalRate[i]+CTS.D4_14*mRate14[i]+CTS.D4_15*mRate15[i]+CTS.D4_16*mRate16[i]);
      mCoeffs[5][i] = deltaTime*(CTS.D5_1*initialRate[i]+CTS.D5_2*mRate2[i]+CTS.D5_3*mRate3[i]+CTS.D5_4*mRate4[i]+
          CTS.D5_5*mRate5[i]+CTS.D5_6*mRate6[i]+CTS.D5_7*mRate7[i]+CTS.D5_8*mRate8[i]+
          CTS.D5_9*mRate9[i]+CTS.D5_10*mRate10[i]+CTS.D5_11*mRate11[i]+CTS.D5_12*mRate12[i]+
          CTS.D5_13*finalRate[i]+CTS.D5_14*mRate14[i]+CTS.D5_15*mRate15[i]+CTS.D5_16*mRate16[i]);
      mCoeffs[6][i] = deltaTime*(CTS.D6_1*initialRate[i]+CTS.D6_2*mRate2[i]+CTS.D6_3*mRate3[i]+CTS.D6_4*mRate4[i]+
          CTS.D6_5*mRate5[i]+CTS.D6_6*mRate6[i]+CTS.D6_7*mRate7[i]+CTS.D6_8*mRate8[i]+
          CTS.D6_9*mRate9[i]+CTS.D6_10*mRate10[i]+CTS.D6_11*mRate11[i]+CTS.D6_12*mRate12[i]+
          CTS.D6_13*finalRate[i]+CTS.D6_14*mRate14[i]+CTS.D6_15*mRate15[i]+CTS.D6_16*mRate16[i]);
      mCoeffs[7][i] = deltaTime*(CTS.D7_1*initialRate[i]+CTS.D7_2*mRate2[i]+CTS.D7_3*mRate3[i]+CTS.D7_4*mRate4[i]+
          CTS.D7_5*mRate5[i]+CTS.D7_6*mRate6[i]+CTS.D7_7*mRate7[i]+CTS.D7_8*mRate8[i]+
          CTS.D7_9*mRate9[i]+CTS.D7_10*mRate10[i]+CTS.D7_11*mRate11[i]+CTS.D7_12*mRate12[i]+
          CTS.D7_13*finalRate[i]+CTS.D7_14*mRate14[i]+CTS.D7_15*mRate15[i]+CTS.D7_16*mRate16[i]);
    }
    return EJSS_ODE_INTERPOLATION.dopri853IntervalData(initialState, finalState, mCoeffs);
  }

  self.computeApproximation = function(step) {
	var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    var absTol = self.getAbsTol();
    var relTol = self.getRelTol();

    var CTS = EJSS_ODE_SOLVERS.DoPri853;
    var dimension = self.getDimension();

    var error3 = 0, error5 = 0;
    for(var i = 0; i < dimension; i++) {
      var sk = absTol[i] + relTol[i] * Math.max(Math.abs(finalState[i]), Math.abs(initialState[i]));
      var errorI3 = (CTS.E3_1*initialRate[i]+CTS.E3_2*mRate2[i]+CTS.E3_3*mRate3[i]+CTS.E3_4*mRate4[i]+
          CTS.E3_5*mRate5[i]+CTS.E3_6*mRate6[i]+CTS.E3_7*mRate7[i]+CTS.E3_8*mRate8[i]+
          CTS.E3_9*mRate9[i]+CTS.E3_10*mRate10[i]+CTS.E3_11*mRate11[i]+CTS.E3_12*mRate12[i])/sk;
      error3 += errorI3*errorI3;
      var errorI5 = (CTS.E5_1*initialRate[i]+CTS.E5_2*mRate2[i]+CTS.E5_3*mRate5[i]+CTS.E5_4*mRate4[i]+
          CTS.E5_5*mRate5[i]+CTS.E5_6*mRate6[i]+CTS.E5_7*mRate7[i]+CTS.E5_8*mRate8[i]+
          CTS.E5_9*mRate9[i]+CTS.E5_10*mRate10[i]+CTS.E5_11*mRate11[i]+CTS.E5_12*mRate12[i])/sk;
      error5 += errorI5*errorI5;
    }
    var den = error5 + 0.01*error3;
    if (den<=0.0) den = 1.0;
    return Math.abs(step)*error5*Math.sqrt(1.0/(dimension*den));
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.Euler = {

};

/**
 * Constructor for Euler
 * @returns Euler
 */
EJSS_ODE_SOLVERS.euler = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTime(); // reference returned 

  self.getNumberOfEvaluations = function() { return 1; }

  self.computeIntermediateStep = function(step, state) {
    var dimension = self.getDimension();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    for (var i=0; i<dimension; i++) state[i] = initialState[i] + step*initialRate[i];    
  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
	self.computeIntermediateStep(step,state);
    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();

    ode.getRate(finalState, finalRate);
    return EJSS_ODE_INTERPOLATION.eulerIntervalData(initialState, initialRate,finalState[self.getTimeIndex()]);
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.EulerRichardson = {

};

/**
 * Constructor for RungeKutta4
 * @returns RungeKutta4
 */
EJSS_ODE_SOLVERS.eulerRichardson = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTime(); // reference returned 
  var mRate2;

  self.getNumberOfEvaluations = function() { return 2; }

  self.allocateOtherArrays = function() {
    mRate2 = new Array(self.getDimension());
  };

  self.computeIntermediateStep = function(step, state) {
    var halfStep = step/2;
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    
    var i;
	  
    for (i=0; i<dimension; i++) state[i] = initialState[i] + halfStep*initialRate[i];
    ode.getRate(state, mRate2);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*mRate2[i];
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];
  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var halfStep = step/2;
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
    
	    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + halfStep*initialRate[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
	ode.getRate(state, mRate2);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*mRate2[i];
	state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    var timeIndex  = self.getDimension()-1;

    ode.getRate(finalState, finalRate);
    return EJSS_ODE_INTERPOLATION.eulerRichardsonIntervalData(initialState, initialRate, finalState[timeIndex], mRate2);
  }


  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.Fehlberg8 = {

};

/**
 * Constructor for Fehlberg8
 * @returns Fehlberg8
 */
EJSS_ODE_SOLVERS.fehlberg8 = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTime(); // reference returned 
  var mRate2, mRate3, mRate4, mRate5, mRate6, mRate7, mRate8, mRate9, mRate10, mRate11; // mRate12, mRate13;

  self.getNumberOfEvaluations = function() { return 13; }

  self.allocateOtherArrays = function() {
    mRate2 = new Array(self.getDimension());
    mRate3 = new Array(self.getDimension());
    mRate4 = new Array(self.getDimension());
    mRate5 = new Array(self.getDimension());
    mRate6 = new Array(self.getDimension());
    mRate7 = new Array(self.getDimension());
    mRate8 = new Array(self.getDimension());
    mRate9 = new Array(self.getDimension());
    mRate10 = new Array(self.getDimension());
    mRate11 = new Array(self.getDimension());
//    mRate12 = new Array(self.getDimension());
//    mRate13 = new Array(self.getDimension());
  };

  self.computeIntermediateStep = function(step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
        
    var CTS = EJSS_ODE_SOLVERS.Fehlberg78;
    var i;
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*CTS.A_11*initialRate[i];
    ode.getRate(state, mRate2);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_21*initialRate[i]+CTS.A_22*mRate2[i]);
    ode.getRate(state, mRate3);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_31*initialRate[i]+CTS.A_33*mRate3[i]);
    ode.getRate(state, mRate4);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_41*initialRate[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    ode.getRate(state, mRate5);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_51*initialRate[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    ode.getRate(state, mRate6);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_61*initialRate[i]+CTS.A_64*mRate4[i]+CTS.A_65*mRate5[i]+CTS.A_66*mRate6[i]);
    ode.getRate(state, mRate7);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_71*initialRate[i]+CTS.A_75*mRate5[i]+CTS.A_76*mRate6[i]+CTS.A_77*mRate7[i]);
    ode.getRate(state, mRate8);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_81*initialRate[i]+CTS.A_84*mRate4[i]+
        CTS.A_85*mRate5[i]+CTS.A_86*mRate6[i]+CTS.A_87*mRate7[i]+CTS.A_88*mRate8[i]);
    ode.getRate(state, mRate9);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_91*initialRate[i]+CTS.A_94*mRate4[i]+
        CTS.A_95*mRate5[i]+CTS.A_96*mRate6[i]+CTS.A_97*mRate7[i]+CTS.A_98*mRate8[i]+CTS.A_99*mRate9[i]);
    ode.getRate(state, mRate10);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_101*initialRate[i]+CTS.A_104*mRate4[i]+
        CTS.A_105*mRate5[i]+CTS.A_106*mRate6[i]+CTS.A_107*mRate7[i]+CTS.A_108*mRate8[i]+CTS.A_109*mRate9[i]+CTS.A_1010*mRate10[i]);
    ode.getRate(state, mRate11);
//    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_111*initialRate[i]+
//        CTS.A_116*mRate6[i]+CTS.A_117*mRate7[i]+CTS.A_118*mRate8[i]+CTS.A_119*mRate9[i]+CTS.A_1110*mRate10[i]);
//    ode.getRate(state, mRate12);
//    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_121*initialRate[i]+CTS.A_124*mRate4[i]+
//        CTS.A_125*mRate5[i]+CTS.A_126*mRate6[i]+CTS.A_127*mRate7[i]+CTS.A_128*mRate8[i]+CTS.A_129*mRate9[i]+CTS.A_1210*mRate10[i]+CTS.A_1212*mRate12[i]);
//    ode.getRate(state, mRate13);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(CTS.B7_1*initialRate[i]+CTS.B7_6*mRate6[i]+CTS.B7_7*mRate7[i]+CTS.B7_8*mRate8[i]+CTS.B7_9*mRate9[i]+CTS.B7_10*mRate10[i]+CTS.B7_11*mRate11[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex]; 
  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var timeRate = initialRate[timeIndex];
    var CTS = EJSS_ODE_SOLVERS.Fehlberg78;
    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*CTS.A_11*initialRate[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate2);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_21*initialRate[i]+CTS.A_22*mRate2[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate3);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_31*initialRate[i]+CTS.A_33*mRate3[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate4);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_41*initialRate[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate5);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_51*initialRate[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate6);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_61*initialRate[i]+CTS.A_64*mRate4[i]+CTS.A_65*mRate5[i]+CTS.A_66*mRate6[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate7);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_71*initialRate[i]+CTS.A_75*mRate5[i]+CTS.A_76*mRate6[i]+CTS.A_77*mRate7[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate8);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_81*initialRate[i]+CTS.A_84*mRate4[i]+
        CTS.A_85*mRate5[i]+CTS.A_86*mRate6[i]+CTS.A_87*mRate7[i]+CTS.A_88*mRate8[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate9);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_91*initialRate[i]+CTS.A_94*mRate4[i]+
        CTS.A_95*mRate5[i]+CTS.A_96*mRate6[i]+CTS.A_97*mRate7[i]+CTS.A_98*mRate8[i]+CTS.A_99*mRate9[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate10);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_101*initialRate[i]+CTS.A_104*mRate4[i]+
        CTS.A_105*mRate5[i]+CTS.A_106*mRate6[i]+CTS.A_107*mRate7[i]+CTS.A_108*mRate8[i]+CTS.A_109*mRate9[i]+CTS.A_1010*mRate10[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate11);

//    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_111*initialRate[i]+
//        CTS.A_116*mRate6[i]+CTS.A_117*mRate7[i]+CTS.A_118*mRate8[i]+CTS.A_119*mRate9[i]+CTS.A_1110*mRate10[i]);
//    switch (eventSolver.checkDiscontinuity(state, false)) {
//      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
//      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
//      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
//      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
//      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
//    }
//    mODE.getRate(state, mRate12);
//
//    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_121*initialRate[i]+CTS.A_124*mRate4[i]+
//        CTS.A_125*mRate5[i]+CTS.A_126*mRate6[i]+CTS.A_127*mRate7[i]+CTS.A_128*mRate8[i]+CTS.A_129*mRate9[i]+CTS.A_1210*mRate10[i]+CTS.A_1212*mRate12[i]);
//    switch (eventSolver.checkDiscontinuity(state, false)) {
//      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
//      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
//      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
//      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
//      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
//    }
//    mODE.getRate(state, mRate13);

    for (i=0; i<timeIndex; i++)  state[i] = initialState[i] + step*(CTS.B7_1*initialRate[i]+CTS.B7_6*mRate6[i]+CTS.B7_7*mRate7[i]+CTS.B7_8*mRate8[i]+CTS.B7_9*mRate9[i]+CTS.B7_10*mRate10[i]+CTS.B7_11*mRate11[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    
    ode.getRate(finalState, finalRate);
    return EJSS_ODE_INTERPOLATION.bootstrap2IntervalData(initialState, initialRate, finalState, finalRate, ode);
  }


  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.Fehlberg78 = {
  A_11 :   2.0/27.0,
  A_21 :   1.0/36.0, A_22 :  1.0/12.0,
  A_31 :   1.0/24.0,                   A_33 :  1.0/8.0,
  A_41 :   5.0/12.0,                   A_43 :-25.0/16.0, A_44 :  25.0/16.0,
  A_51 :   1.0/20.0,                                     A_54 :  1.0/4.0,  A_55 :   1.0/5.0,
  A_61 : -25.0/108.0,                                    A_64 :125.0/108.0, A_65 :-65.0/27.0, A_66 : 125.0/54.0,
  A_71 :  31.0/300.0,                                                       A_75 :  61.0/225.0, A_76 :  -2.0/9.0,   A_77 : 13.0/900.0,
  A_81 :   2.0,                                          A_84 :-53.0/6.0,   A_85 : 704.0/45.0,  A_86 :-107.0/9.0,   A_87 : 67.0/90.0 , 
    A_88 : 3.0,
  A_91 : -91.0/108.0,                                    A_94 : 23.0/108.0, A_95 : -976.0/135.0,A_96 : 311.0/54.0,  A_97 : -19.0/60.0,
    A_98 : 17.0/6.0,  A_99 : -1.0/12.0,
  A_101: 2383.0/4100.0,                                  A_104:-341.0/164.0,A_105: 4496.0/1025.0,A_106:-301.0/82.0, A_107 :2133.0/4100.0,
    A_108:45.0/82.0, A_109:45.0/164.0, A_1010 : 18.0/41.0,
  A_111: 3./205.0,                                                                               A_116:-6.0/41.0,   A_117 : -3.0/205.0,
    A_118:-3.0/41.0, A_119:3.0/41.0,   A_1110 : 6.0/41.0,
  A_121 : -1777.0/4100.0,                                A_124:-341.0/164.0,A_125:4496.0/1025.0, A_126:-289.0/82.0, A_127: 2193.0/4100.0,
    A_128:51.0/82.0, A_129:33.0/164.0, A_1210:12.0/41.0,                    A_1212:1.0,
	  
  // 7th order method
  B7_1 : 41.0/840.0, 
  B7_6 : 34.0/105.0, 
  B7_7 : 9.0/35.0, 
  B7_8 : 9.0/35.0, 
  B7_9 : 9.0/280.0, 
  B7_10 : 9.0/280.0, 
  B7_11 : 41.0/840.0,
		
  // 8th order error control
  B8_6 : 34.0/105.0, 
  B8_7 : 9.0/35.0, 
  B8_8 : 9.0/35.0, 
  B8_9 : 9.0/280.0, 
  B8_10 : 9.0/280.0, 
  B8_12 : 41.0/840.0, 
  B8_13 : 41.0/840.0
	  
};

/**
 * Constructor for Fehlberg78
 * @returns Fehlberg78
 */
EJSS_ODE_SOLVERS.fehlberg78 = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTimeAdaptive(7); // 7 is the method's order 
  var mRate2, mRate3, mRate4, mRate5, mRate6, mRate7, mRate8, mRate9, mRate10, mRate11, mRate12, mRate13;
  var superAllocateOtherArrays = self.allocateOtherArrays;

  self.getNumberOfEvaluations = function() { return 6; }
  
  self.allocateOtherArrays = function() {
    superAllocateOtherArrays();
    mRate2 = new Array(self.getDimension());
    mRate3 = new Array(self.getDimension());
    mRate4 = new Array(self.getDimension());
    mRate5 = new Array(self.getDimension());
    mRate6 = new Array(self.getDimension());
    mRate7 = new Array(self.getDimension());
    mRate8 = new Array(self.getDimension());
    mRate9 = new Array(self.getDimension());
    mRate10 = new Array(self.getDimension());
    mRate11 = new Array(self.getDimension());
    mRate12 = new Array(self.getDimension());
    mRate13 = new Array(self.getDimension());
    mOrder8 = new Array(self.getDimension());
  };

  self.computeIntermediateStep = function(step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    
	var CTS = EJSS_ODE_SOLVERS.Fehlberg78;
    var i;

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*CTS.A_11*initialRate[i];
    ode.getRate(state, mRate2);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_21*initialRate[i]+CTS.A_22*mRate2[i]);
    ode.getRate(state, mRate3);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_31*initialRate[i]+CTS.A_33*mRate3[i]);
    ode.getRate(state, mRate4);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_41*initialRate[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    ode.getRate(state, mRate5);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_51*initialRate[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    ode.getRate(state, mRate6);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_61*initialRate[i]+CTS.A_64*mRate4[i]+CTS.A_65*mRate5[i]+CTS.A_66*mRate6[i]);
    ode.getRate(state, mRate7);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_71*initialRate[i]+CTS.A_75*mRate5[i]+CTS.A_76*mRate6[i]+CTS.A_77*mRate7[i]);
    ode.getRate(state, mRate8);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_81*initialRate[i]+CTS.A_84*mRate4[i]+
        CTS.A_85*mRate5[i]+CTS.A_86*mRate6[i]+CTS.A_87*mRate7[i]+CTS.A_88*mRate8[i]);
    ode.getRate(state, mRate9);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_91*initialRate[i]+CTS.A_94*mRate4[i]+
        CTS.A_95*mRate5[i]+CTS.A_96*mRate6[i]+CTS.A_97*mRate7[i]+CTS.A_98*mRate8[i]+CTS.A_99*mRate9[i]);
    ode.getRate(state, mRate10);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_101*initialRate[i]+CTS.A_104*mRate4[i]+
        CTS.A_105*mRate5[i]+CTS.A_106*mRate6[i]+CTS.A_107*mRate7[i]+CTS.A_108*mRate8[i]+CTS.A_109*mRate9[i]+CTS.A_1010*mRate10[i]);
    ode.getRate(state, mRate11);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_111*initialRate[i]+
        CTS.A_116*mRate6[i]+CTS.A_117*mRate7[i]+CTS.A_118*mRate8[i]+CTS.A_119*mRate9[i]+CTS.A_1110*mRate10[i]);
    ode.getRate(state, mRate12);
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_121*initialRate[i]+CTS.A_124*mRate4[i]+
        CTS.A_125*mRate5[i]+CTS.A_126*mRate6[i]+CTS.A_127*mRate7[i]+CTS.A_128*mRate8[i]+CTS.A_129*mRate9[i]+CTS.A_1210*mRate10[i]+CTS.A_1212*mRate12[i]);
    ode.getRate(state, mRate13);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(CTS.B7_1*initialRate[i]+CTS.B7_6*mRate6[i]+CTS.B7_7*mRate7[i]+CTS.B7_8*mRate8[i]+CTS.B7_9*mRate9[i]+CTS.B7_10*mRate10[i]+CTS.B7_11*mRate11[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex]; 

  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var CTS = EJSS_ODE_SOLVERS.Fehlberg78;
    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
	    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*CTS.A_11*initialRate[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate2);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_21*initialRate[i]+CTS.A_22*mRate2[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate3);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_31*initialRate[i]+CTS.A_33*mRate3[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate4);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_41*initialRate[i]+CTS.A_43*mRate3[i]+CTS.A_44*mRate4[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate5);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_51*initialRate[i]+CTS.A_54*mRate4[i]+CTS.A_55*mRate5[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate6);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_61*initialRate[i]+CTS.A_64*mRate4[i]+CTS.A_65*mRate5[i]+CTS.A_66*mRate6[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate7);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_71*initialRate[i]+CTS.A_75*mRate5[i]+CTS.A_76*mRate6[i]+CTS.A_77*mRate7[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate8);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_81*initialRate[i]+CTS.A_84*mRate4[i]+
        CTS.A_85*mRate5[i]+CTS.A_86*mRate6[i]+CTS.A_87*mRate7[i]+CTS.A_88*mRate8[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate9);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_91*initialRate[i]+CTS.A_94*mRate4[i]+
        CTS.A_95*mRate5[i]+CTS.A_96*mRate6[i]+CTS.A_97*mRate7[i]+CTS.A_98*mRate8[i]+CTS.A_99*mRate9[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate10);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_101*initialRate[i]+CTS.A_104*mRate4[i]+
        CTS.A_105*mRate5[i]+CTS.A_106*mRate6[i]+CTS.A_107*mRate7[i]+CTS.A_108*mRate8[i]+CTS.A_109*mRate9[i]+CTS.A_1010*mRate10[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate11);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_111*initialRate[i]+
        CTS.A_116*mRate6[i]+CTS.A_117*mRate7[i]+CTS.A_118*mRate8[i]+CTS.A_119*mRate9[i]+CTS.A_1110*mRate10[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate12);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*(CTS.A_121*initialRate[i]+CTS.A_124*mRate4[i]+
        CTS.A_125*mRate5[i]+CTS.A_126*mRate6[i]+CTS.A_127*mRate7[i]+CTS.A_128*mRate8[i]+CTS.A_129*mRate9[i]+CTS.A_1210*mRate10[i]+CTS.A_1212*mRate12[i]);
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    mODE.getRate(state, mRate13);

    for (i=0; i<timeIndex; i++)  state[i] = initialState[i] + step*(CTS.B7_1*initialRate[i]+CTS.B7_6*mRate6[i]+CTS.B7_7*mRate7[i]+CTS.B7_8*mRate8[i]+CTS.B7_9*mRate9[i]+CTS.B7_10*mRate10[i]+CTS.B7_11*mRate11[i]);
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    ode.getRate(finalState, finalRate);
    return EJSS_ODE_INTERPOLATION.bootstrap2IntervalData(initialState, initialRate,finalState, finalRate, ode);

  }

  self.computeApproximation = function(step) {
    var timeIndex = self.getTimeIndex();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var CTS = EJSS_ODE_SOLVERS.Fehlberg78;

	for (var i=0; i<timeIndex; i++) mOrder8[i] = initialState[i]+step*(CTS.B8_6*mRate6[i]+CTS.B8_7*mRate7[i]+CTS.B8_8*mRate8[i]+CTS.B8_9*mRate9[i]+CTS.B8_10*mRate10[i]+CTS.B8_12*mRate12[i]+CTS.B8_13*mRate13[i]);
	mOrder8[timeIndex] = self.getInitialTime()  + step*initialRate[timeIndex];
	return self.computeError(mOrder8);
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.RungeKutta4 = {

};

/**
 * Constructor for RungeKutta4
 * @returns RungeKutta4
 */
EJSS_ODE_SOLVERS.rungeKutta4 = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTime(); // reference returned 
  var mRate2, mRate3, mRate4;

  self.getNumberOfEvaluations = function() { return 4; }

  self.allocateOtherArrays = function() {
    mRate2 = new Array(self.getDimension());
    mRate3 = new Array(self.getDimension());
    mRate4 = new Array(self.getDimension());
  };

  self.computeIntermediateStep = function(step, state) {
    var halfStep = step/2;
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    
    var i;
	  
    for (i=0; i<dimension; i++) state[i] = initialState[i] + halfStep*initialRate[i];
    ode.getRate(state, mRate2);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + halfStep*mRate2[i];
    ode.getRate(state, mRate3);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + step*mRate3[i];
    ode.getRate(state, mRate4);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(initialRate[i] + 2*mRate2[i] + 2*mRate3[i] + mRate4[i])/6.0;
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];
  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var halfStep = step/2;
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;

    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
    
	    
    for (i=0; i<dimension; i++) state[i] = initialState[i] + halfStep*initialRate[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
	ode.getRate(state, mRate2);

    for (i=0; i<dimension; i++) state[i] = initialState[i] + halfStep*mRate2[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
	ode.getRate(state, mRate3);

	for (i=0; i<dimension; i++) state[i] = initialState[i] + step*mRate3[i];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // admissible. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
	ode.getRate(state, mRate4);

    for (i=0; i<timeIndex; i++) state[i] = initialState[i] + step*(initialRate[i] + 2*mRate2[i] + 2*mRate3[i] + mRate4[i])/6.0;
	state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    ode.getRate(finalState, finalRate);
    return EJSS_ODE_INTERPOLATION.bootstrapIntervalData(initialState, initialRate, finalState, finalRate, ode);
  }


  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 * @author Francisco Esquembre
 * @version 1.0 Jan 2014
 */
EJSS_ODE_SOLVERS.SolverEngineDiscreteTime = {

};

/**
 * Constructor for SolverEngineDiscreteTime
 * @returns An abstract SolverEngineDiscreteTime
 */
EJSS_ODE_SOLVERS.solverEngineDiscreteTime = function() {
  var self = {};							// reference returned 

  // ODE variables
  var mErrorCode=EJSS_ODE_SOLVERS.ERROR.NO_ERROR; //InterpolatorEventSolver.NO_ERROR;
  var mDimension; // The length of the state array
  var mTimeIndex; // The index of the independent variable (usually the time)
  var mAccumulatedEvaluations = 0; // Number of evaluations so far
  var mStepSize = 0.1; // the preferred step size
  var mMaximumStepSize = Number.POSITIVE_INFINITY;
  var mInitialTime=0; // The time before the step is taken
  var mFinalTime=0;   // The time after the step is taken
  var mInitialState;  // The state array before the step is taken
  var mInitialRate;   // The rate array before the step is taken
  var mFinalState;    // The state array after the step is taken
  var mFinalRate;     // The rate array after the step is taken
  var mODE;
  var mEventSolver;

  // Memory
  var mStateHistory;
    
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  /**
   * The number of function evaluations per step
   * @return int
   */
  self.getNumberOfEvaluations = function() { return 0; };

  /**
   * Allocates other arrays needed for the method
   */
  self.allocateOtherArrays = function() {}; // do nothing

  /**
   * Computes one intermediate step not caring about precision
   * @param step double the length of the step
   * @param state double[] the target array
   * @return same as state
   */
  self.computeIntermediateStep = function(step, state) { return null; }

  /**
   * Computes one intermediate step not caring about precision but taking care of possible discontinuities
   * @param _step the length of the step
   * @param _state the target array
   * @return one of the flags in the checkDiscontinuity(double[]) method of ODEInterpolatorEventSolver
   * @see ODEInterpolatorEventSolver#checkDiscontinuity(double[])
   */
  self.computeCarefulIntermediateStep = function(solver, step, state) { return EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.NO_DISCONTINUITY_ALONG_STEP; }

  /**
   * Computes the final rate and creates a new IntervalData for interpolation
   */
  self.computeFinalRateAndCreateIntervalData = function() { return null; };
  
  // --------------------------------------------
  // Protected setters and getters
  // --------------------------------------------
  
    self.getODE = function() { return mODE; }

    self.getEventSolver = function() { return mEventSolver; }

    self.getDimension = function() { return mDimension; }

    self.getTimeIndex = function() { return mTimeIndex; }
     
    self.getInitialTime = function() { return mInitialTime; }

    self.getInitialState = function() { return mInitialState; }
  
    self.getInitialRate = function() { return mInitialRate; }

    self.getFinalState = function() { return mFinalState; }

    self.getFinalRate = function() { return mFinalRate; }
  
    self.setFinalTime = function(time) { mFinalTime = time; }

    self.getFinalTime = function() { return mFinalTime; }
    
    self.setErrorCode = function(code) { mErrorCode = code; }
    
    self.addToEvaluations = function(evals) { mAccumulatedEvaluations += evals; }


  // --------------------------------------------
  // Initialization
  // --------------------------------------------

  self.setODE = function(eventSolver, ode) { 
    mEventSolver = eventSolver;
    mODE = ode;
    var state = mODE.getState();
    mDimension = state.length;
    mTimeIndex = mDimension-1;
    mStateHistory = EJSS_ODE_INTERPOLATION.stateHistory(ode);
    if (mODE.getMaximumDelay) {
      mStateHistory.setMinimumLength(mODE.getMaximumDelay());
    }
  } 
  
  // ------------------------------------------------
  // Implementation of SolverEngine
  //------------------------------------------------
  
  self.initialize = function(stepSize) {
    mStepSize = stepSize;
    var state = mODE.getState();
    if (mInitialState==null || mInitialState.length!=state.length) {
      mDimension = state.length;
      mTimeIndex = mDimension-1;
      mInitialState = new Array(mDimension);
      mInitialRate  = new Array(mDimension);
      mFinalState   = new Array(mDimension);
      mFinalRate    = new Array(mDimension);
      self.allocateOtherArrays();
    }
    mAccumulatedEvaluations = 0;
    mStateHistory.clearAll();
    if (mODE.getMaximumDelay) {
      mStateHistory.setMinimumLength(Math.max(Math.abs(mODE.getMaximumDelay()),Math.abs(stepSize))); // Make sure we have enough memory for delays and events
    }
    else mStateHistory.setMinimumLength(stepSize); // Make sure we have enough memory for events
    self.reinitialize(state);
  }

  self.reinitialize = function(state) {
    mInitialTime = state[mTimeIndex];
    for (var i=0; i<mDimension; i++) mInitialState[i] = state[i];
    mODE.getRate(mInitialState, mInitialRate);
    mFinalTime = Number.NaN;
    mErrorCode = EJSS_ODE_SOLVERS.ERROR.NO_ERROR;
  }

  self.getCurrentRate = function() { return mInitialRate; }

  self.setStepSize = function(stepSize) { 
    mStepSize = stepSize; 
    if (mODE.getMaximumDelay) {
      mStateHistory.setMinimumLength(Math.max(Math.abs(mODE.getMaximumDelay()),Math.abs(stepSize))); // Make sure we have enough memory for delays and events
    }
    else mStateHistory.setMinimumLength(stepSize); // Make sure we have enough memory for events
  }

  self.setMaximumStepSize = function(stepSize) { mMaximumStepSize = Math.abs(stepSize); }

  self.getMaximumStepSize = function() { return mMaximumStepSize; }

  self.getStepSize = function() { return mStepSize; }
  
  self.getInternalStepSize = function() { return mFinalTime-mInitialTime; }
  
  self.setEstimateFirstStep = function(estimate) {}
  
  self.setTolerances = function(absTol, relTol) {}

  self.getMaximumTime = function(withDiscontinuities) {
    if (mErrorCode!=EJSS_ODE_SOLVERS.ERROR.NO_ERROR) return Number.NaN;
    if (isNaN(mFinalTime)) {
      self.computeOneStep(withDiscontinuities);
    }
    return mFinalTime; 
  }

  self.internalStep = function(withDiscontinuities) {
    var i;
    mInitialTime = mFinalTime;
    mErrorCode = EJSS_ODE_SOLVERS.ERROR.NO_ERROR;
    for (i=0; i<mDimension; i++) mInitialState[i] = mFinalState[i];
    for (i=0; i<mDimension; i++) mInitialRate[i]  = mFinalRate[i];
    self.computeOneStep(withDiscontinuities);
    return mFinalTime;  // the final time that was computed
  }

  self.getCounter = function()  { return mAccumulatedEvaluations; }
  
  self.getStateHistory = function() { return mStateHistory; }

  self.interpolate = function(time, state) {
    return mStateHistory.interpolateState(time, state);
  }

  /*
   * Provides 'brute-force' interpolation by re-stepping from the initial time every time
   */
  self.bestInterpolate = function(time, state) {
    var i;
    if (isNaN(mFinalTime)) return null;
    if (time==mFinalTime) {
      for (i=0; i<mDimension; i++) state[i] = mFinalState[i];
      return state;
    }
    if (time==mInitialTime) {
      for (i=0; i<mDimension; i++) state[i] = mInitialState[i];
      return state;
    }
    self.computeIntermediateStep(time-mInitialTime, state);
    return state;
  }

  /**
   * Returns the actual step size to take. Adaptive solvers override this method
   * @return
   */
  self.getActualStepSize = function() { return mStepSize; }
    
  self.findTheDiscontinuity = function(step) {
    var counter = 0;
    var left = 0;
    var right = step;
    var lastValid = 0;
    var maxAttempts = mEventSolver.getDDEIterations();
//    System.err.println ("Finding the discontinuity in "+mInitialState[mTimeIndex]+", "+(mInitialState[mTimeIndex]+step)+" ------<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
    var EPSILON = mEventSolver.getEPSILON();
    while (counter<maxAttempts) {
      if (Math.abs(left-right)<EPSILON) { // 1.0e-15) { // Too close
//        System.err.println("Left and right are equal!: "+counter+" / "+maxAttempts);
        break;
      }
      var testPoint = (left+right)/2;
//      System.err.println ("\nTest point = "+(mInitialState[mTimeIndex]+testPoint));
      switch (self.computeCarefulIntermediateStep(mEventSolver, testPoint, mFinalState)) {
        default : 
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_PRODUCED_ERROR : return Number.NaN; 
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.NO_DISCONTINUITY_ALONG_STEP  :  
          lastValid = mFinalState[mTimeIndex];
          left = testPoint; // and keep on searching
          break; 
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_EXACTLY_ON_STEP : 
//          System.err.println ("Found at point = "+mFinalState[mTimeIndex]+" ------<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
          return mFinalState[mTimeIndex] ; // discontinuity just at the end of the step
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED :
          right = right - (right-left)/4; // and keep on searching but closer to the right end
          break;
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_ALONG_STEP : // Iterate until finding the precise point of the discontinuity
          right = testPoint; // and keep on searching
          break;
      }
      counter++; // try again
    }
//    System.err.println("Not found. taking last valid point = "+lastValid+" ------<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
    self.computeCarefulIntermediateStep(mEventSolver, lastValid, mFinalState);
    return lastValid; // not found, so take the last valid point
  }
  
  /**
   * Computes mFinalState[] and mFinalTime out of the current mInitialState[] and mInitialTime,
   * taking into account the mStepSize and the tolerance (which ever applies).
   * Not final because Adaptive solvers make it otherwise.
   */
  self.computeOneStep = function(hasDiscontinuities) {
    var step = mStepSize;
//    System.err.println("Trying to step to "+(mInitialState[mTimeIndex]+step));
    if (hasDiscontinuities) {
      switch (self.computeCarefulIntermediateStep(mEventSolver, step, mFinalState)) {
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_PRODUCED_ERROR : 
          mFinalTime = Number.NaN; 
          return;
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.NO_DISCONTINUITY_ALONG_STEP  :  // no discontinuity in this interval
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_EXACTLY_ON_STEP : // discontinuity just at the end of the step
          mFinalTime = mFinalState[mTimeIndex];
          break; 
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED :
        case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_ALONG_STEP : // Iterate until finding the precise point of the discontinuity
          mFinalTime = self.findTheDiscontinuity(step);
          break;
      }
      if (isNaN(mFinalTime)) {
        mErrorCode=EJSS_ODE_SOLVERS.ERROR.DISCONTINUITY_PRODUCED_ERROR;
        return;
      }
    }
    else {
      self.computeIntermediateStep(step,mFinalState);
      mFinalTime = mFinalState[mTimeIndex];
    }
//    System.err.println("Will really step to "+mFinalState[mTimeIndex]);
    // Accumulate the counter
    mAccumulatedEvaluations += self.getNumberOfEvaluations();
    // Clean memory, if required
    mStateHistory.clean(mInitialTime);
    // Add new interval data to memory
    mStateHistory.addIntervalData(self.computeFinalRateAndCreateIntervalData());
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 * @author Francisco Esquembre
 * @version 1.0 Jan 2014
 */
EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive = {
  FAC1 : 0.33,
  FAC2 : 6,
  BETA : 0,
  SAFE : 0.9
};

/**
 * Constructor for SolverEngineDiscreteTimeAdaptive
 * @returns An abstract SolverEngineDiscreteTimeAdaptive
 */
EJSS_ODE_SOLVERS.solverEngineDiscreteTimeAdaptive = function(mMethodOrder) {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTime(); // reference returned 

  // step size estimation's variables
  var mExpO1 = 1.0 / mMethodOrder - EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.BETA * 0.75;
  var mErrOld = 1.e-4;
  var mFactor = 0;

  // variables
  var mEstimate=false; // Whether to mEstimate the first step size
  var mActualStepSize;
  var mAbsoluteTolerance=Number.NaN;
  var mRelativeTolerance=Number.NaN;
  var mAbsTol; // array of absolute tolerances
  var mRelTol; // array of relative tolerances
  
  var superReinitialize = self.reinitialize;
  var superSetMaximumStepSize = self.setMaximumStepSize;

  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  /**
   * Computes the proposed approximation for finalState[] and
   * returns the estimated error obtained
   * @return the estimated error
   */
  self.computeApproximation = function(step) {};

  // --------------------------------------------
  // Protected getters
  // --------------------------------------------
  
  self.getAbsTol = function() { return mAbsTol; }

  self.getRelTol = function() { return mRelTol; }
  
  // --------------------------------------------
  // Functions overriden
  // --------------------------------------------

  self.allocateOtherArrays = function() {
    var dimension = self.getDimension();
    mAbsTol = new Array(dimension);
    mRelTol = new Array(dimension);
    self.setTolerances(1.0e-6,1.0e-6);
  }

  self.reinitialize = function(state) {
    superReinitialize(state);
    if (mEstimate) mActualStepSize = limitStepSize(estimateFirstStepSize(self.getStepSize()));
    else mActualStepSize = limitStepSize(self.getStepSize());
  }

  self.setEstimateFirstStep = function(estimate) {
    mEstimate = estimate;
  }

  self.setMaximumStepSize = function(stepSize) {
    superSetMaximumStepSize(stepSize);
    mActualStepSize = limitStepSize(mActualStepSize);
  }
  
  self.setTolerances = function(absTol, relTol) {
    var i;
    var dimension = self.getDimension();
    if (absTol instanceof Array) {
      mAbsoluteTolerance = mRelativeTolerance = Number.NaN;
      for (i=0; i<dimension; i++) {
        mAbsTol[i] = absTol[i];
        mRelTol[i] = relTol[i];
      }
    }
    else {
      if (mAbsoluteTolerance==absTol && mRelativeTolerance==relTol) return;
      mAbsoluteTolerance = absTol;
      mRelativeTolerance = relTol;
      for (i=0; i<dimension; i++) {
        mAbsTol[i] = absTol;
        mRelTol[i] = relTol;
      }
    }
    mFinalTime = Number.NaN;
    mErrorCode = EJSS_ODE_SOLVERS.ERROR.NO_ERROR;
    if (mEstimate) mActualStepSize = limitStepSize(estimateFirstStepSize(self.getStepSize()));
    else mActualStepSize = limitStepSize(self.getStepSize());
  }
  
  self.computeOneStep = function(hasDiscontinuities) {
    var eventSolver = self.getEventSolver();
    var finalState = self.getFinalState();
    
    mErrorCode = EJSS_ODE_SOLVERS.ERROR.NO_ERROR;
    for (var iterations=0; iterations<500; iterations++) { // maximum number of attempts
      if (hasDiscontinuities) {
        switch (self.computeCarefulIntermediateStep(eventSolver, mActualStepSize, finalState)) {
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_PRODUCED_ERROR : 
            self.setFinalTime(Number.NaN); 
            return;
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.NO_DISCONTINUITY_ALONG_STEP  :  // no discontinuity in this interval
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_EXACTLY_ON_STEP : // discontinuity just at the end of the step
            self.setFinalTime(finalState[self.getTimeIndex()]);
            break; 
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED :
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_ALONG_STEP : // Iterate until finding the precise point of the discontinuity
            var finalTime = self.findTheDiscontinuity(mActualStepSize);
            self.setFinalTime(finalTime);
            mActualStepSize = finalTime - self.getInitialTime();
            break;
        }
        if (isNaN(self.getFinalTime())) {
          self.setErrorCode(EJSS_ODE_SOLVERS.ERROR.DISCONTINUITY_PRODUCED_ERROR);
          return;
        }
      }
      else {
        self.computeIntermediateStep(mActualStepSize,finalState);
        self.setFinalTime(finalState[self.getTimeIndex()]);
      }
      var err = self.computeApproximation(mActualStepSize);
      self.addToEvaluations(self.getNumberOfEvaluations());
      if (err<=1.0) {  // It has converged
        // Clean memory, if required
        self.getStateHistory().clean(self.getInitialTime());
        // Add new interval data to memory
        self.getStateHistory().addIntervalData(self.computeFinalRateAndCreateIntervalData());

        // adjust the step for next time
        if (iterations>0) mActualStepSize = limitStepSize(mActualStepSize>0 ? Math.min(mActualStepSize, estimatedStepSize(err)) : Math.max(mActualStepSize, estimatedStepSize(err)));
        else mActualStepSize = limitStepSize(estimatedStepSize(err)); // Can grow only if converged at first attempt
        return;
      }
      mActualStepSize = limitStepSize(mActualStepSize>0 ? Math.min(mActualStepSize, estimatedStepSize(err)) : Math.max(mActualStepSize, estimatedStepSize(err)));
    }
    // It did not converge
    self.setFinalTime(Number.NaN);
    self.setErrorCode(EJSS_ODE_SOLVERS.ERROR.DID_NOT_CONVERGE);
  }
  
  self.getActualStepSize = function() { return mActualStepSize; }

  // --------------------------------------------
  // Private or protected functions
  // --------------------------------------------

  /**
   * Based on code by Andrei Goussev and Yuri B. Senichenkov 
   * Adapted by Francisco Esquembre
   * @param compState the state to compare to
   * @return
   */
  self.computeError = function(state) {
    var error = 0;
    var dimension = self.getDimension();
    var initialState = self.getInitialState();
    var finalState = self.getFinalState();
    for(var i = 0; i < dimension; i++) {
      var sk = mAbsTol[i] + mRelTol[i] * Math.max(Math.abs(finalState[i]), Math.abs(initialState[i]));
      var errorI = (finalState[i]-state[i])/sk;
      error += errorI*errorI;
    }
    return Math.sqrt(error/dimension);
  }

  /**
   * Based on code by Andrei Goussev and Yuri B. Senichenkov 
   * Adapted by Francisco Esquembre
   * @param err
   * @return
   */
  function estimatedStepSize(err){
    var fac11 = 0;
    // taking decision for HNEW/H value
    if (err != 0) {
      fac11 = Math.pow(err,mExpO1);
      mFactor = fac11 / Math.exp(EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.BETA * Math.log(mErrOld)); // stabilization
      mFactor = Math.max (1.0/EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.FAC2, 
                  Math.min(1.0/EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.FAC1, 
                           mFactor/EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.SAFE)); // we require FAC1 <= HNEW/H <= FAC2
    }
    else {
      fac11 = 1.0/EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.FAC1;
      mFactor = 1.0/EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.FAC2;
    }
    if (err<=1.0) { // step accepted
      mErrOld = Math.max(err, 1.0e-4);
      return mActualStepSize / mFactor;
    }
    return mActualStepSize/Math.min(1.0/EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.FAC1, fac11/EJSS_ODE_SOLVERS.SolverEngineDiscreteTimeAdaptive.SAFE); // step rejected
  }

  /**
   * Makes sure the intended step does not exceed the maximum step
   * @param intendedStep
   * @return
   */
  function limitStepSize(intendedStep) {
    var ode = self.getODE();
    var delays;
    var i, length;
	if (intendedStep>=0) {
      if (ode.getDelays) { // Don't step more than half the minimum delay
	    delays = ode.getDelays(self.getInitialState());
	    length = delays.length;
	    for (i=0; i<length; i++) intendedStep = Math.min(intendedStep, delays[i]/2);
      }
	  return Math.min(intendedStep, self.getMaximumStepSize());
	}
    if (ode.getDelays) { // Don't step more than half the minimum delay
      delays = ode.getDelays(self.getInitialState());
      length = delays.length;
	  for (i=0; i<length; i++) intendedStep = Math.max(intendedStep, delays[i]/2);
	}
    return Math.max(intendedStep, -self.getMaximumStepSize()); 
  }
  
  /**
   * Based on code by Andrei Goussev and Yuri B. Senichenkov 
   * Adapted by Francisco Esquembre
   * @param hMax
   * @return
   */
  function estimateFirstStepSize(hMax){
    var i;
    var dimension = self.getDimension();
    var initialState = self.getInitialState();
    var initialRate = self.getInitialRate();
    var ode = self.getODE();
    
    var posneg =(hMax<0)?-1:1;
    hMax = Math.abs(hMax);
    var normF = 0.0, normX = 0.0 ;
    for(i = 0; i < dimension; i++) {
      var sk = mAbsTol[i] + mRelTol[i]*Math.abs(initialState[i]);
      var aux = initialRate[i]/sk;
      normF += aux*aux;
      aux = initialState[i]/sk;
      normX += aux*aux;
    }
    var h;
    if ((normF <= 1.e-10) || (normX <= 1.e-10)) h = 1.0e-6;
    else h = Math.sqrt(normX / normF) * 0.01;
    h = posneg*Math.min(h, hMax);
    // perform an Euler step and estimate the rate, reusing finalState (it is SAFE to do so)
    if (ode.getDelays) {
      var eventSolver = self.getEventSolver(); 
      eventSolver.resetDiscontinuities(initialState);
      var counter = 0;
      var done = false;
      while (!done) {
        for (i = 0; i < dimension; i++) finalState[i] = initialState[i] + h * initialRate[i];
        switch (eventSolver.checkDiscontinuity(finalState, false)) {
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_PRODUCED_ERROR  : return Number.NaN; 
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_JUST_PASSED     :
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_ALONG_STEP      : h = h/2; break;
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.DISCONTINUITY_EXACTLY_ON_STEP : done = true; break;  
          case EJSS_ODE_SOLVERS.DISCONTINUITY_CODE.NO_DISCONTINUITY_ALONG_STEP   : done = true; break; 
        }
        if ((++counter)>100) return Number.NaN;
      }
    }
    else {
      for (i = 0; i < dimension; i++) 
        finalState[i] = initialState[i] + h * initialRate[i];
    }
    ode.getRate(finalState, finalRate);
    var der2 = 0.0;
    for (i = 0; i < dimension; i++) {
      var sk = mAbsTol[i] + mRelTol[i] * Math.abs(initialState[i]);
      var aux = (finalRate[i] - initialRate[i]) / sk;
      der2 += aux*aux;
    }
    der2 = Math.sqrt(der2) / h;
    //step size is computed as follows
    //h^order * max ( norm (initialRate), norm (der2)) = 0.01
    var der12 = Math.max(Math.abs(der2), Math.sqrt(normF));
    var h1;
    if (der12 <= 1.0e-15) h1 = Math.max(1.0e-6, Math.abs(h) * 1.0e-3);
    else h1 = Math.exp((1.0 / mMethodOrder) * Math.log(0.01 / der12));
    h = posneg*Math.min(100*h, h1);
    if (hMax != 0) h = posneg*Math.min(Math.abs(h),hMax);
    return h;
  }
  
  
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.VelocityVerlet = {

};

/**
 * Constructor for RungeKutta4
 * @returns RungeKutta4
 */
EJSS_ODE_SOLVERS.velocityVerlet = function() {
  var self = EJSS_ODE_SOLVERS.solverEngineDiscreteTime(); // reference returned 
  var mAccelerationIndependentOfVelocity;
  var mRate2;

  superSetODE = self.setODE;
  
  self.setODE = function(eventSolver, ode) { 
	superSetODE(eventSolver,ode);
	if (ode.isAccelerationIndependentOfVelocity) mAccelerationIndependentOfVelocity = ode.isAccelerationIndependentOfVelocity();
	else mAccelerationIndependentOfVelocity = false;
  }
  
  self.getNumberOfEvaluations = function() { return 2; }

  self.allocateOtherArrays = function() {
    mRate2 = new Array(self.getDimension());
  };

  self.computeIntermediateStep = function(step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var i;
    
    var dt2 = (step*step)/2; // the step size squared
    // increment the positions using the velocity and acceleration
    for(i = 0; i<timeIndex; i+=2) state[i] = initialState[i] + step*initialRate[i] + dt2*initialRate[i+1];
    ode.getRate(state, mRate2);
    var halfStep = step/2;
    // increment the velocities with the average rate
    for(i = 1; i<timeIndex; i+=2) state[i] = initialState[i] + halfStep*(initialRate[i]+mRate2[i]);
    // the independent variable
    state[timeIndex] = self.getInitialTime() + step*initialRate[timeIndex];
  }

  self.computeCarefulIntermediateStep = function(eventSolver, step, state) {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var dimension = self.getDimension();
    var timeIndex = dimension-1;
    var CODE = EJSS_ODE_SOLVERS.DISCONTINUITY_CODE;
    var i;
    
    var dt2 = (step*step)/2; // the step size squared
    // increment the positions using the velocity and acceleration
    for (i = 0; i<timeIndex; i+=2) state[i] = initialState[i] + step*initialRate[i] + dt2*initialRate[i+1];
    switch (eventSolver.checkDiscontinuity(state, false)) {
      case CODE.DISCONTINUITY_PRODUCED_ERROR  : return CODE.DISCONTINUITY_PRODUCED_ERROR; 
      case CODE.DISCONTINUITY_JUST_PASSED     : return CODE.DISCONTINUITY_JUST_PASSED;   
      case CODE.DISCONTINUITY_ALONG_STEP      : return CODE.DISCONTINUITY_ALONG_STEP;
      case CODE.DISCONTINUITY_EXACTLY_ON_STEP : break; // doubtful. But check for the real point 
      case CODE.NO_DISCONTINUITY_ALONG_STEP   : break; // go ahead 
    }
    ode.getRate(state, mRate2);

    var halfStep = step/2;
    // increment the velocities with the average rate
    for (i = 1; i<timeIndex; i+=2) state[i] = initialState[i] + halfStep*(initialRate[i]+mRate2[i]);
	state[timeIndex] = self.getInitialTime() + step*timeRate;

    return eventSolver.checkDiscontinuity(state, true);
  }

  self.computeFinalRateAndCreateIntervalData = function() {
    var ode = self.getODE();
    var initialState = self.getInitialState();
    var initialRate  = self.getInitialRate();
    var finalState = self.getFinalState();
    var finalRate  = self.getFinalRate();
    var timeIndex  = self.getDimension()-1;

    if (mAccelerationIndependentOfVelocity) {
      for (var i=0,j=1; i<timeIndex; i+=2,j+=2) {
        finalRate[i] = finalState[j];
        finalRate[j] = mRate2[j];
      }
      finalRate[timeIndex] = mRate2[timeIndex];
    }
    else {
      ode.getRate(finalState, finalRate);
      // Accumulate the counter
      self.addToEvaluations(self.getNumberOfEvaluations());
    }
    return new EJSS_ODE_INTERPOLATION.hermiteIntervalData(initialState, initialRate, finalState, finalRate);
  }


  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * A Discontinuity that handles the discontinuities of a DDE
 * @author Francisco Esquembre
 * @version Jan 2014
 */
EJSS_ODE_SOLVERS.DDEDiscontinuity = {
};

/**
 * Constructor for DDEDiscontinuity
 * @returns DDEDiscontinuity
 */
EJSS_ODE_SOLVERS.ddeDiscontinuity = function(mSolver, mDDE) {
  var self = {};							// reference returned 

  var mDiscontinuities = [];
  var mDiIndex = [];
  var mRateForCorrections = [];

  self.initialize = function(state) {
    if (mSolver.getRunsForwards()) mDiscontinuities.push(Number.NEGATIVE_INFINITY);
    else mDiscontinuities.push(Number.POSITIVE_INFINITY);
    var initDisc = mDDE.getInitialConditionDiscontinuities();
    if (initDisc!=null) {
      var length = initDisc.length;
      for (var i=0; i<length; i++) mDiscontinuities.push(initDisc[i]);
    }
    mDiscontinuities.push(state[state.length-1]);
    var delays = mDDE.getDelays(state);
    mDiIndex = new Array(delays.length);
    self.reset(state);
    mRateForCorrections = new Array(state.length);
  }
    
  self.reset = function(state) {
    var length = mDiIndex.length
    for (var i=0; i<length; i++) mDiIndex[i] = 0;
    update(state);
  }
    
  function update(state) {
    var i;
    var delays = mDDE.getDelays(state);
    var n = delays.length;
    var discCount = mDiscontinuities.length;
    var timeIndex = state.length-1;
    // Now find the mDi
    if (mSolver.getRunsForwards()) {
      for (i=0; i<n; i++) {
        var limit = state[timeIndex]-delays[i]+self.getTolerance(); // add the tolerance so that to skip round-off errors
        for (var index=mDiIndex[i]; index<discCount; index++) {
          var disc = mDiscontinuities[index];
//          System.err.print("Disc["+index+"] = "+disc+" > limit "+limit+ "... ");
          if (disc>limit) {
//            System.err.println("YES");
            mDiIndex[i] = index;
            break;
          }
//          System.err.println("NO");
        }
      }
    }
    else {
      for (i=0; i<n; i++) {
        var limit = state[timeIndex]-delays[i]-self.getTolerance(); // add the tolerance so that to skip round-off errors
        for (var index=mDiIndex[i]; index<discCount; index++) {
          var disc = mDiscontinuities[index];
          if (disc<limit) {
            mDiIndex[i] = index;
            break;
          }
        }
      }
    }
//      System.err.println("Discont. are : ");
//      for (int i=0; i<discCount; i++) System.err.println("disc["+i+"] = "+mDiscontinuities.get(i));
//      System.err.println("di's are : ");
//      for (int i=0; i<n; i++) System.err.println("mdi["+i+"] = "+mDiIndex[i]);
//      System.err.println("\n");
  }
    
    // implementation of Discontinuity
    
  self.evaluate = function(state) {
    var i;
    var t = state[state.length-1];
    var delays = mDDE.getDelays(state);
    var n = delays.length;
    if (mSolver.getRunsForwards()) {
      var min = Number.POSITIVE_INFINITY;
      for (i=0; i<n; i++) {
        var di = mDiscontinuities[mDiIndex[i]];
//        System.err.println("Discont = "+di);
//        System.err.println("delay = "+delays[i]);
//        System.err.println("time = "+t);
//        System.err.println("min = "+(delays[i]+di-t));
        min = Math.min(min, delays[i]+di-t);
      }
//      System.err.println("Time = "+state[1]+ " Value returned = "+min);
      return min;
    }
    else {
      var max = Number.NEGATIVE_INFINITY;
      for (i=0; i<n; i++) {
        var di = mDiscontinuities[mDiIndex[i]];
        max = Math.max(max, delays[i]+di-t);
      }
      return max;
    }
  }
    
  /**
   * Advances time (and state) past the delay
   * @param state
   * @return
   */
  function correctTime(state) {
    var i;
    var dimension = state.length;
    var timeIndex = dimension-1;
    var step = self.getTolerance()/20;
    var maxIterations = mSolver.getDDEIterations();
    var history = mSolver.getStateHistory();
    var counter=0;
    if (mSolver.getRunsForwards()) {
      while ((++counter)<maxIterations) {
        mDDE.getRate(state, mRateForCorrections); // advance using Euler
        history.addIntervalData(EJSS_ODE_INTERPOLATION.eulerIntervalData(state,mRateForCorrections,state[timeIndex]+step));
        for (i=0; i<dimension; i++) state[i] += step*mRateForCorrections[i];
//        state[timeIndex] += step;
        var h = self.evaluate(state);
        if (h<0) {
//    	  System.out.println("Counter -------------- "+counter+ "/"+mDDEdiscontinuityMaxIterations);
      	  return state[timeIndex];
        }
      }
    }
    else {
      while ((++counter)<maxIterations) {
        mDDE.getRate(state, mRateForCorrections);
         history.addIntervalData(EJSS_ODE_INTERPOLATION.eulerIntervalData(state,mRateForCorrections,state[timeIndex]-step));
         for (i=0; i<dimension; i++) state[i] -= step*mRateForCorrections[i];
//        state[timeIndex] -= step;
        var h = evaluate(state);
        if (h>0) return state[timeIndex];
      }
    }
    return Number.NaN;
  }
    
  self.action = function() {
    var state = mDDE.getState();
//    System.err.println("Found delay discontinuity at "+state[1]+ " --------------------------------------");
//    System.err.println ("Correct state should be "+correctTime(state));
    var time = correctTime(state); // Automatically find the moment where it crosses the discontinuity
//    System.out.println("Corrected time ="+time);
//    interpolatorSolver.interpolate(time, state);
//    state[timeIndex] = time;
    mDiscontinuities.push(time);
    var eventData = mSolver.findFirstEvent(state,time,state); // Find a possible new event because of this time increase!
    if (eventData!=null) {
      mCurrentEventData = eventData;   
      // No need to interpolate because the state was changed in the call to findFirstEvent
//      if (mUseBestInterpolation) mSolverEngine.bestInterpolate(problem.getTime(), state); // So that we reinitialize with the best possible approximation 
//      else System.arraycopy(mTest_ode_state, 0, state, 0, mDimension); // -- unnecessary -- interpolatorSolver.interpolate(eventData.time, state);
      eventData.action();
    }
    
    // update(state); will be done by the reinitialize() method of the solver
    return true; // implies reset
  }
    
  self.getTolerance = function() { return mSolver.getDDETolerance(); }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
 
   return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * A class that handles the information of a StateEvent for given states
 * @author Francisco Esquembre
 * @version Jan 2014
 */
EJSS_ODE_SOLVERS.ProblemData = {
    POSITIVE       : 2,
    SMALL_POSITIVE : 1,
    ZERO           : 0,
    SMALL_NEGATIVE : -1,
    NEGATIVE       : -2
};

/**
 * Constructor for EventData
 * @returns EventData
 */
EJSS_ODE_SOLVERS.eventData = function(mSolver, mEvent, mState) {
  var self = {};							// reference returned 
  var mEventType = mEvent.getTypeOfEvent(); // The type of event (STATE_EVENT, POSITIVE_EVENT, or CROSSING_EVENT)
  var mTimeIndex = mState.length-1;
  
  var mPositiveFlag; // Whether the state has ever been positive (since last reset)
  var mNegativeFlag; // Whether the state has ever been negative (since last reset)
  var mCurrentPosition; // In which side of the [-Tol,+Tol] is h at the current time (POSITIVE, SMALL_POSITIVE, etc.)
  var mHBefore;  // The value of h at the current time
  var mHAfter;   // The value of h at the end of a period
  var mH;        // The value of time at the test point
  var mTime;     // the next time at which the event took place
  var mMaxAdvance; // The maximum the solver must advance after this event happens

  self.getEvent = function() { return mEvent; }
  self.getEventType = function() { return mEventType; }

  self.setH = function(h) { mH = h; }
  self.getH = function() { return mH; }

  self.getTime = function() { return mTime; }
  self.setTime = function(time) { mTime = time; }
    
  self.hasPositiveFlag = function() { return mPositiveFlag; }

  self.hasNegativeFlag = function() { return mNegativeFlag; }

  self.getHBefore = function() { return mHBefore; }

  self.setHAfter = function(h) { mHAfter = h; }
  self.getHAfter = function() { return mHAfter; }
  
  self.getCurrentPosition = function() { return mCurrentPosition; }

  self.getMaxAdvance = function() { return mMaxAdvance; }
  self.setMaxAdvance = function(advance) { mMaxAdvance = advance; }
    
  self.action = function() { return mEvent.action(); }
    
  self.getProblem = function() { return mEvent; }
    
  self.reset = function (state) {
//      System.out.println ("Resetting event at "+state[timeIndex] + " current h = "+hBefore);
    mPositiveFlag = false;
    mNegativeFlag = false;
    var h = mEvent.evaluate(state);
    self.findPosition (state[mTimeIndex],h);
    // At initialization times, a small posi(nega)tive is actually a posi(nega)tive
    if (mSolver.getCurrentEventData()!=self) { // unless this event just happened
      if (mEventType==EJSS_ODE_SOLVERS.EVENT_TYPE.CROSSING_EVENT) {
        if (h>0) mPositiveFlag = true;
        else if (h<0) mNegativeFlag = true;
      }
      else if (mEventType==EJSS_ODE_SOLVERS.EVENT_TYPE.POSITIVE_EVENT) {
        if (h>0) mPositiveFlag = true;
      }
//        System.out.println ("Positive flag = "+positiveFlag);
//        System.out.println ("Negative flag = "+negativeFlag);
    }
//      System.err.println ("Reiniting event at "+state[timeIndex] + "new h = "+generalEvent.evaluate(state));
  }

  self.findPosition = function (time, hValue) {
    mHBefore = hValue;
    if      (mHBefore>= mEvent.getTolerance()) { mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.POSITIVE; mPositiveFlag = true; }
    else if (mHBefore>0)  mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.SMALL_POSITIVE; 
    else if (mHBefore==0) mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.ZERO;
    else if (mHBefore>-mEvent.getTolerance()) mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.SMALL_NEGATIVE;
    else { mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.NEGATIVE; mNegativeFlag = true; }
    if (mEventType==EJSS_ODE_SOLVERS.EVENT_TYPE.STATE_EVENT && mCurrentPosition==EJSS_ODE_SOLVERS.ProblemData.NEGATIVE) {
      var msg = "The state event " + mEvent+" is in an illegal state: "+mHBefore+ " at "+time;
      if (mSolver.getLastEventData()==null) msg +="\nThere was no previous event"; 
      else msg +="\nLast previous event was "+mSolver.getLastEventData().getProblem()+", which took place at "+mSolver.getLastEventDataTime();
      mSolver.error(EJSS_ODE_SOLVERS.ERROR.ILLEGAL_EVENT_STATE, msg);
    }
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.reset(mState); 
  return self;
}

/**
 * Constructor for DiscontinuityData
 * @returns DiscontinuityData
 */
EJSS_ODE_SOLVERS.discontinuityData = function(mSolver, mDiscontinuity, mState) {
  var self = {};							// reference returned 

  var mTimeIndex = mState.length-1;
  
  var mPositiveFlag; // Whether the state has ever been positive (since last reset)
  var mNegativeFlag; // Whether the state has ever been negative (since last reset)
  var mCurrentPosition; // In which side of the [-Tol,+Tol] is h at the current time (POSITIVE, SMALL_POSITIVE, etc.)
  var mHBefore;  // The value of h at the current time
  var mH;        // The value of time at the test point
  var mTime;     // the next time at which the event took place

  self.getDiscontinuity = function() { return mDiscontinuity; }

  self.setH = function(h) { mH = h; }
  self.getH = function() { return mH; }

  self.setTime = function(time) { mTime = time; }
  self.getTime = function() { return mTime; }

  self.hasPositiveFlag = function() { return mPositiveFlag; }

  self.hasNegativeFlag = function() { return mNegativeFlag; }

  self.getHBefore = function() { return mHBefore; }

  self.setHAfter = function(h) { mHAfter = h; }
  self.getHAfter = function() { return mHAfter; }
  
  self.getCurrentPosition = function() { return mCurrentPosition; }

  self.getMaxAdvance = function() { return Number.NaN; }
    
  self.action = function() { return mDiscontinuity.action(); }
    
  self.getProblem = function() { return mDiscontinuity; }
    
  self.reset = function (state) {
    mPositiveFlag = false;
    mNegativeFlag = false;
    var h = mDiscontinuity.evaluate(state);
    self.findPosition (state[mTimeIndex],h);
    // At initialization times, a small posi(nega)tive is actually a posi(nega)tive
    if (mSolver.getCurrentEventData()!=self) { // unless this event just happened
      if (h>0) mPositiveFlag = true;
      else if (h<0) mNegativeFlag = true;
//        System.out.println ("Positive flag = "+positiveFlag);
//        System.out.println ("Negative flag = "+negativeFlag);
    }
//      System.err.println ("Reiniting event at "+state[timeIndex] + "new h = "+generalEvent.evaluate(state));
  }

  self.findPosition = function (time, hValue) {
    mHBefore = hValue;
    if      (mHBefore>= mDiscontinuity.getTolerance()) { mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.POSITIVE; mPositiveFlag = true; }
    else if (mHBefore>0)  mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.SMALL_POSITIVE; 
    else if (mHBefore==0) mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.ZERO;
    else if (mHBefore>-mDiscontinuity.getTolerance()) mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.SMALL_NEGATIVE;
    else { mCurrentPosition = EJSS_ODE_SOLVERS.ProblemData.NEGATIVE; mNegativeFlag = true; }
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  self.reset(mState); 
  return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Multistep solver 
 */
EJSS_ODE_SOLVERS.ODEMultistepSolver = {
  NO_ERROR : 0, 	// No error
  DID_NOT_CONVERGE : 1      	// 

};

/**
 * Constructor for SolverInterpolatorDiscreteTime
 * @returns An abstract SolverInterpolatorDiscreteTime
 */
EJSS_ODE_SOLVERS.createODEMultistepSolver = function(mODE) {
  var self = {}; // reference returned 

  var sMaxMessages = 3; // maximum number of error messages
  var mErr_code = EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR;
  var mMaxIterations = 200;
  var mEnableExceptions = false;
  var mErr_msg = "";
  var mFixedStepSize = 0.1;
  var mInternalODE = EJSS_ODE_SOLVERS.createODEMultistepSolverInternalODE(mODE);
  var mODEEngine = EJSS_ODE_SOLVERS.createCashKarp45(mInternalODE);
  
  // --------------------------------------------
  // Setters and getters
  // --------------------------------------------

  /**
   * Enables runtime exceptions if the solver does not converge.
   * @param enable boolean
   */
  self.enableRuntimeExceptions = function (enable) {
    mEnableExceptions = enable;
  }

  /**
   * Sets the maximum number of iterations.
   * @param n maximum
   */
  self.setMaxIterations = function(n) {
    mMaxIterations = Math.max(1, n);
  }

  /**
   * Sets the tolerance of the adaptive ODE solver.
   * @param tol the tolerance
   */
  self.setTolerance = function(tol) {
    tol = Math.abs(tol);
    mODEEngine.setTolerance(tol);
  }

  /**
   * Gets the tolerance of the adaptive ODE solver.
   * @return
   */
  self.getTolerance = function() {
    return mODEEngine.getTolerance();
  }
  
   /**
   * Gets the error code.
   * Error codes:
   *   EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR
   *   EJSS_ODE_SOLVERS.ODEMultistepSolver.DID_NOT_CONVERGE
   * @return int
   */
  self.getErrorCode = function() {
    return mErr_code;
  }
  
  // --------------------------------------------
  // Private or protected methods
  // --------------------------------------------
  
  /**
   * Initializes the ODE solver.
   *
   * ODE solvers use this method to allocate temporary arrays that may be required to carry out the solution.
   * The number of differential equations is determined by invoking getState().length on the ODE.
   *
   * @param stepSize
   */  
  self.initialize = function(stepSize) {
    mMaxMessages = 4; // reset the message counter to produce more messages
    mErr_msg = "";    //$NON-NLS-1$
    mErr_code = EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR;
    mFixedStepSize = stepSize;
    mInternalODE.setInitialConditions();
    mODEEngine.initialize(stepSize);
  };
    
  /**
   * Sets the initial step size.
   *
   * The step size may change if the ODE solver implements an adaptive step size algorithm
   * such as RK4/5.
   *
   * @param stepSize
   */
  self.setStepSize = function(stepSize) {
    mMaxMessages = 4;          // reset the message counter to produce more messages
    mFixedStepSize = stepSize; // the fixed step size
    if(stepSize<0) {
      mODEEngine.setStepSize(Math.max(-Math.abs(mODEEngine.getStepSize()), stepSize));
    } else { // stepSize is positive
      mODEEngine.setStepSize(Math.min(mODEEngine.getStepSize(), stepSize));
    }
  };
  
  /**
   * Sets the number of error messages if ODE solver did not converge.
   * @param n int
   */
  self.setMaximumNumberOfErrorMessages = function(n) {
    mMaxMessages = n;
  }

  /**
   * Gets the step size.
   * The step size is the fixed step size, not the size of the ODEAdaptiveSolver steps that are combined into a single step.
   *
   * @return the step size
   */
  self.getStepSize = function() {
    return mFixedStepSize;
  }

  // ----------------------------------------------------
  // stepping
  // ----------------------------------------------------
  
  
  /**
   * Steps (advances) the differential equations by the stepSize.
   *
   * The ODESolver invokes the ODE's getRate method to obtain the initial state of the system.
   * The ODESolver then advances the solution and copies the new state into the
   * state array at the end of the solution step if desired tolerance was reached.
   *
   * @return the actual step
   */
  self.step = function() {
    mErr_code = EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR;
    mInternalODE.setInitialConditions(); // stores the ode's initial conditions
    var remainder = 0;
    if(mFixedStepSize>0) {
      remainder = plus();
    } else {
      remainder = minus();
    }
    mInternalODE.update(); // updates the ode
    return mFixedStepSize-remainder; // the step size that was actually taken
  }

  // ----------------------------------------------------
  // Utils
  // ----------------------------------------------------
  
  /**
   * Steps the ode with a positive stepsize.
   *
   * @return the step size
   */
  function plus() {           // positive step size
    var tol = mODEEngine.getTolerance();
    var remainder = mFixedStepSize; // track the remaining step
    if((mODEEngine.getStepSize()<=0)||(                       // is the stepsize postive?
      mODEEngine.getStepSize()>mFixedStepSize)||(              // is the stepsize larger than what is requested?
        mFixedStepSize-mODEEngine.getStepSize()==mFixedStepSize // is the stepsize smaller than the precision?
          )) {
      mODEEngine.setStepSize(mFixedStepSize);                  // reset the step size and let it adapt to an optimum size
    }
    var counter = 0;
    while(remainder>tol*mFixedStepSize) {           // check to see if we are close enough
      counter++;
      var oldRemainder = remainder;
      if(remainder<mODEEngine.getStepSize()) {      // temporarily reduce the step size so that we hit the exact dt value
        var tempStep = mODEEngine.getStepSize(); // save the current optimum step size
        mODEEngine.setStepSize(remainder);          // set the step size to the remainder
        var delta = mODEEngine.step();
        remainder -= delta;
        mODEEngine.setStepSize(tempStep);           // restore the original step size
      } else {
        remainder -= mODEEngine.step();             // do a step and set the remainder
      }
      // check to see if roundoff error prevents further calculation.
      if ( (mODEEngine.getErrorCode()!=EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR) ||
           (Math.abs(oldRemainder-remainder)<=Number.MIN_VALUE) ||
           (tol*mFixedStepSize/10.0>mODEEngine.getStepSize()) || 
           (counter>mMaxIterations)) {
        mErr_msg = "ODEMultiStep did not converge. Remainder="+remainder; //$NON-NLS-1$
        mErr_code = EJSS_ODE_SOLVERS.ODEMultistepSolver.DID_NOT_CONVERGE;
        if (mEnableExceptions) {
          throw mErr_msg;
        }
        if (mMaxMessages>0) {
          mMaxMessages--;
          console.log(mErr_msg);
        }
        break;
      }
    }
    return remainder;
  }

  /**
   * Steps the ode with a negative stepsize.
   *
   * @return the step size
   */
  function minus() { // negative step size
    var tol = mODEEngine.getTolerance();
    var remainder = mFixedStepSize; // track the remaining step
    if ((mODEEngine.getStepSize()>=0)||(                       // is the step negative?
         mODEEngine.getStepSize()<mFixedStepSize)||(              // is the stepsize larger than what is requested?
         mFixedStepSize-mODEEngine.getStepSize()==mFixedStepSize // is the stepsize smaller than the precision?
          )) {
      mODEEngine.setStepSize(mFixedStepSize);                  // reset the step size and let it adapt to an optimum size
    }
    var counter = 0;
    while(remainder<tol*mFixedStepSize) {           // check to see if we are close enough
      counter++;
      var oldRemainder = remainder;
      if(remainder>mODEEngine.getStepSize()) {
        var tempStep = mODEEngine.getStepSize(); // save the current optimum step size
        mODEEngine.setStepSize(remainder);          // set the step RK4/5 size to the remainder
        var delta = mODEEngine.step();
        remainder -= delta;
        mODEEngine.setStepSize(tempStep);           // restore the original step size
      } else {
        remainder -= mODEEngine.step();             // do a step and set the remainder
      }
      // check to see if roundoff error prevents further calculation.
      if ( (mODEEngine.getErrorCode()!=EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR) ||
           (Math.abs(oldRemainder-remainder)<=Number.MIN_VALUE) || 
           (tol*mFixedStepSize/10.0<mODEEngine.getStepSize()) ||
           (counter>mMaxIterations)) {
        mErr_msg = "ODEMultiStep did not converge. Remainder="+remainder; //$NON-NLS-1$
        mErr_code = EJSS_ODE_SOLVERS.ODEMultistepSolver.DID_NOT_CONVERGE;
        if(enableExceptions) {
          throw mErr_msg;
        }
        if(mMaxMessages>0) {
          mMaxMessages--;
          console.log(mErr_msg);
        }
      }
    }
    return remainder;
  }
  


  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
}

EJSS_ODE_SOLVERS.createODEMultistepSolverInternalODE = function(mODE) {
  var self = {}; // reference returned 
  var mEngineState = [];

    /**
     * Gets the rate using the given state.
     *
     * @param state double[]
     * @param rate double[]
     */
    self.getRate = function(state, rate) {
      mODE.getRate(state, rate);
    }

    /**
     * Gets the state.
     *
     * @return double[]
     */
    self.getState = function() {
      return mEngineState;
    }

    /**
     * Sets the initial conditions using the current state of the ODE.
     *
     * @return double[]
     */
    self.setInitialConditions = function() {
      var state = mODE.getState();
      if(state==null) {
        return;
      }
      if((mEngineState==null)||(mEngineState.length!=state.length)) {
        mEngineState = new Array(state.length); // create an engine state with the correct size
      }
      for (var i=0,n=state.length; i<n; i++) {
        mEngineState[i] = state[i];
      }
    }

    /**
     * updates the ODE state using the engine's internal state.
     */
    self.update = function() {
      var state = mODE.getState();
      for (var i=0,n=state.length; i<n; i++) {
        state[i] = mEngineState[i];
      }
    }

  self.setInitialConditions();

  return self;
}

EJSS_ODE_SOLVERS.createCashKarp45 = function(mODE) {
  var self = {}; // reference returned 

  // embedding constants Dormand-Prince 4th and 5th order
//  var sA = [
//    [1.0/5.0], 
//    [3.0/40.0, 9.0/40.0], 
//    [3.0/10.0, -9.0/10.0, 6.0/5.0], 
//    [226.0/729.0, -25.0/27.0, 880.0/729.0, 55.0/729.0], 
//    [-181.0/270.0, 5.0/2.0, -266.0/297.0, -91.0/27.0, 189.0/55.0]
//    ];
  // ch contains the 5th order coefficients
//  var sB5 = [19.0/216.0, 0.0, 1000.0/2079.0, -125.0/216.0, 81.0/88.0, 5.0/56.0];
  // er array contains the error coefficients; the difference between the 4th and 5th order coefficients
  // er[0] is computed to be -11/360 = 31/540-19/216
//  var sEr = [-11.0/360.0, 0.0, 10.0/63.0, -55.0/72.0, 27.0/40.0, -11.0/280.0];

  // embedding constants Cash-Karp 4th and 5th order
  var sA = [
    [1.0/5.0], 
    [3.0/40.0, 9.0/40.0], 
    [3.0/10.0, -9.0/10.0, 6.0/5.0], 
    [-11.0/54.0, 5.0/2.0,  -70.0/27.0, 35.0/27.0],
    [1631.0/55296.0, 175.0/512.0, 575.0/13824.0, 44275.0/110592.0, 253.0/4096.0]
    ];
  // ch contains the 5th order coefficients
  var sB5 = [37.0/378.0, 0.0,  250.0/621.0, 125.0/594.0, 0.0, 512.0/1771.0];
  // er array contains the error coefficients; the difference between the 4th and 5th order coefficients
  // er[0] is computed to be -11/360 = 31/540-19/216
  var sEr = [ 277./64512.,    0.,   -6925./370944.,  6925./202752.,   277./14336.,   -277./7084.];
  var sNumStages = 6; // number of intermediate rate computations
  
  var mError_code = EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR;
  var mStepSize = 0.01;
  var mNumEqn = 0;
  var mTemp_state = [];
  var mK = []; // [][]
  var mTruncErr;
  var mTol = 1.0e-6;
  var mEnableExceptions = false;


  /**
   * Enables runtime exceptions if the solver does not converge.
   * @param enable boolean
   */
  self.enableRuntimeExpecptions = function(enable) {
    mEnableExceptions = enable;
  }

  /**
   * Sets the step size.
   *
   * The step size may change when the step method is invoked.
   *
   * @param stepSize
   */
  self.setStepSize = function(stepSize) {
    mStepSize = stepSize;
  }

  /**
   * Gets the step size.
   *
   * The stepsize is adaptive and may change as the step() method is invoked.
   *
   * @return the step size
   */
  self.getStepSize = function() {
    return mStepSize;
  }

  /**
   * Method setTolerance
   *
   * @param _tol
   */
  self.setTolerance = function(tol) {
    mTol = Math.abs(tol);
    if(mTol<1.0E-12) {
      var err_msg = "Error: Cash-Karp ODE solver tolerance cannot be smaller than 1.0e-12.";
      if(mEnableExceptions) {
        throw err_msg;
      }
      console.log(err_msg);
      mTol = 1.0e-12;
    }
  }

  /**
   * Method getTolerance
   *
   *
   * @return
   */
  self.getTolerance = function() {
    return mTol;
  }

  /**
   * Gets the error code.
   * Error codes:
   *   EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR
   *   EJSS_ODE_SOLVERS.ODEMultistepSolver.DID_NOT_CONVERGE
   * @return int
   */
  self.getErrorCode = function() {
    return mError_code;
  }

  /**
   * Initializes the ODE solver.
   *
   * Temporary state and rate arrays are allocated.
   * The number of differential equations is determined by invoking getState().length on the ODE.
   *
   * @param _stepSize
   */
  self.initialize = function(stepSize) {
    mStepSize = stepSize;
    var state = mODE.getState();
    if(state==null) { // state vector not defined.
      return;
    }
    if (mNumEqn!=state.length) {
      mNumEqn = state.length;
      mTemp_state = new Array(mNumEqn);
      mK = new Array(sNumStages); // six intermediate rates
      for (var i=0; i<sNumStages; i++) mK[i] = new Array(mNumEqn); 
    }
  }

  /**
   * Steps (advances) the differential equations by the stepSize.
   *
   * The ODESolver invokes the ODE's getRate method to obtain the initial state of the system.
   * The ODESolver then advances the solution and copies the new state into the
   * state array at the end of the solution step.
   *
   * @return the step size
   */
  self.step = function() {
    var i, j, s;
    var iterations = 10;
    var currentStep = mStepSize;
    var error = 0;
    var state = mODE.getState();

    mError_code = EJSS_ODE_SOLVERS.ODEMultistepSolver.NO_ERROR;
    mODE.getRate(state, mK[0]); // get the initial rate
    do {
      iterations--;
      currentStep = mStepSize;
      // Compute the k's
      for (s = 1; s<sNumStages; s++) {
        for (i = 0; i<mNumEqn; i++) {
          mTemp_state[i] = state[i];
          for (j = 0; j<s; j++) {
            mTemp_state[i] = mTemp_state[i]+mStepSize*sA[s-1][j]*mK[j][i];
          }
        }
        mODE.getRate(mTemp_state, mK[s]);
      }
      // Compute the error
      error = 0;
      for (i = 0; i<mNumEqn; i++) {
        mTruncErr = 0;
        for (s = 0; s<sNumStages; s++) {
          mTruncErr = mTruncErr+mStepSize*sEr[s]*mK[s][i];
        }
        error = Math.max(error, Math.abs(mTruncErr));
      }
      if (error<=Number.MIN_VALUE) { // error too small to be meaningful,
        error = mTol/1.0e5;         // increase stepSize x10
      }
      // find h step for the next try.
      if (error>mTol) {              // shrink, no more than x10
        var fac = 0.9*Math.pow(error/mTol, -0.25);
        mStepSize = mStepSize*Math.max(fac, 0.1);
      } 
      else if (error<mTol/10.0) {  // grow, but no more than factor of 10
        var fac = 0.9*Math.pow(error/mTol, -0.2);
        if (fac>1) {                // sometimes fac is <1 because error/tol is close to one
          mStepSize = mStepSize*Math.min(fac, 10);
        }
      }
    } while( (error>mTol) && (iterations>0) );
    // advance the state
    for (i = 0; i<mNumEqn; i++) {
      for (s = 0; s<sNumStages; s++) {
        state[i] += currentStep*sB5[s]*mK[s][i];
      }
    }
    if (iterations==0) {
      mError_code = EJSS_ODE_SOLVERS.ODEMultistepSolver.DID_NOT_CONVERGE;
      if(mEnableExceptions) {
        throw "DormanPrince45 ODE solver did not converge.";
      }
    }
    return currentStep; // the value of the step actually taken.
  }

  self.initialize(mStepSize);
  
  return self;
}

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Constructor for SolverInterpolatorDiscreteTime
 * @returns An abstract SolverInterpolatorDiscreteTime
 */
EJSS_ODE_SOLVERS.createEulerODESolver = function(mODE) {
  var self = EJSS_ODE_SOLVERS.createSolverInterpolatorDiscreteTime(mODE); // reference returned 

  var mRate;

  var super_initialize = self.initialize;

  self.initialize = function(stepSize) {
  	super_initialize(stepSize);
  	
    mRate = new Array(self.getDimension());
  };

  /**
   * Computes an intermmediate step
   * @param step double the step to take
   * @param state double[] the placeholder for the computed state
   */
  self.computeIntermediateStep = function(step, state) {  	
  	mODE.getRate(state,mRate);
  	for (var i=0,n=state.length; i<n; i++) {
  		state[i] = state[i] + step * mRate[i];
  	}
    return state;
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Constructor for SolverInterpolatorDiscreteTime
 * @returns An abstract SolverInterpolatorDiscreteTime
 */
EJSS_ODE_SOLVERS.createRungeKutta4ODESolver = function(mODE) {
  var self = EJSS_ODE_SOLVERS.createSolverInterpolatorDiscreteTime(mODE); // reference returned 

  var initialState;
  var mRate1,mRate2,mRate3,mRate4;

  var super_initialize = self.initialize;

  self.initialize = function(stepSize) {
  	super_initialize(stepSize);
  	
    initialState = new Array(self.getDimension());
    mRate1 = new Array(self.getDimension());
    mRate2 = new Array(self.getDimension());
    mRate3 = new Array(self.getDimension());
    mRate4 = new Array(self.getDimension());
  };

  /**
   * Computes an intermmediate step
   * @param step double the step to take
   * @param state double[] the placeholder for the computed state
   */
  self.computeIntermediateStep = function(step, state) {  	
  	mODE.getRate(state,mRate1);
  	for (var i=0,n=state.length; i<n; i++) {
  		initialState[i] = state[i];
  		state[i] = initialState[i] + step * mRate1[i]/2;
  	}
  	mODE.getRate(state,mRate2);
  	for (var i=0,n=state.length; i<n; i++) {
  		state[i] = initialState[i] + step * mRate2[i]/2;
  	}
  	mODE.getRate(state,mRate3);
  	for (var i=0,n=state.length; i<n; i++) {
  		state[i] = initialState[i] + step * mRate3[i];
  	}
  	mODE.getRate(state,mRate4);
  	for (var i=0,n=state.length; i<n; i++) {
  		state[i] = initialState[i] + step * (mRate1[i]+2*mRate2[i]+2*mRate3[i]+mRate4[i])/6;
  	}
    return state;
  }

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  
  return self;
}/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for numerics : ODE solving 
 */

var EJSS_ODE_SOLVERS = EJSS_ODE_SOLVERS || {};

/**
 * Abstract object to be used as base for ode solver which discretize the time 
 */
EJSS_ODE_SOLVERS.SolverInterpolatorDiscreteTime = {

};

/**
 * Constructor for SolverInterpolatorDiscreteTime
 * @returns An abstract SolverInterpolatorDiscreteTime
 */
EJSS_ODE_SOLVERS.createSolverInterpolatorDiscreteTime = function(mODE) {
  var self = {};							// reference returned 

  // ODE variables
  var mErrorCode=0; //InterpolatorEventSolver.NO_ERROR;
  var mDimension; // The length of the state array
  var mTimeIndex; // The index of the independent variable (usually the time)
  var mAccumulatedEvaluations = 0; // Number of evaluations so far
  var mStepSize = 0.1; // the preferred step size
  var mMaximumStepSize = Number.POSITIVE_INFINITY;
  var mInitialTime=0; // The time before the step is taken
  var mFinalTime=0;   // The time after the step is taken
  var mInitialState;  // The state array before the step is taken
  var mInitialRate;   // The rate array before the step is taken
  var mFinalState;    // The state array after the step is taken
  var mFinalRate;     // The rate array after the step is taken
  var mRelativeTolerance = 0;

  // DDE variables
  var mIsDDE = false;
  var mDiscontinuities = [];
  var mNextDiscontinuity;
  
  // Memory
  // var mStateHistory = EJSS_ODE_SOLVERS.createStateHistory();
  var mStateHistoryLength;
  
  // --------------------------------------------
  // Functions to be defined by subclasses
  // --------------------------------------------

  /**
   * The number of function evaluations per step
   * @return int
   */
  self.getNumberOfEvaluations = function() { return 0; };

  /**
   * Allocates other arrays needed for the method
   */
  self.allocateOtherArrays = function() {}; // do nothing

  /**
   * Computes one intermediate step not caring about precision
   * @param step double the length of the step
   * @param state double[] the target array
   * @return same as state
   */
  self.computeIntermediateStep = function(step, state) { return null; }

  /**
   * Computes one intermediate step not caring about precision but taking care of possible discontinuities
   * @param _step the length of the step
   * @param _state the target array
   * @return one of the flags in the checkDiscontinuity(double[]) method of ODEInterpolatorEventSolver
   * @see ODEInterpolatorEventSolver#checkDiscontinuity(double[])
   */
  self.computeCarefulIntermediateStep = function(eventSolver, step, state) { return 0; }

  /**
   * Sets the tolerance of the adaptive ODE solver.
   * @param tol the tolerance
   */
  self.setTolerance = function(tolerance) {
    mRelativeTolerance = Math.abs(tolerance);
  }
  
  // --------------------------------------------
  // Private or protected methods
  // --------------------------------------------

  self.getDimension = function() {
  	return mDimension;
  };
  
  /**
   * Initializes the ODE solver.
   *
   * ODE solvers use this method to allocate temporary arrays that may be required to carry out the solution.
   * The number of differential equations is determined by invoking getState().length on the ODE.
   *
   * @param stepSize
   */  
  self.initialize = function(stepSize) {
  	mStepSize = stepSize;

  	// Extract the components of the state (i.e. its properties)
  	var state = mODE.getState();
  	mDimension = state.length;
  	
  };
  
  /**
   * Steps (advances) the differential equations by the stepSize.
   *
   * The ODESolver invokes the ODE's getRate method to obtain the initial state of the system.
   * The ODESolver then advances the solution and copies the new state into the
   * state array at the end of the solution step.
   *
   * @return the step size
   */
  self.step = function() {
  	var state = mODE.getState();
    self.computeIntermediateStep(mStepSize,state);
  	return 0;
  }
    
  /**
   * Sets the initial step size.
   *
   * The step size may change if the ODE solver implements an adaptive step size algorithm
   * such as RK4/5.
   *
   * @param stepSize
   */
  self.setStepSize = function(stepSize) {
  	mStepSize = stepSize;
  };

  /**
   * Gets the step size.
   *
   * @return the step size
   */
  self.getStepSize = function() {
  	return mStepSize;
  };
  
  // ----------------------------------------------------
  // Utils
  // ----------------------------------------------------
  

  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------
  
  return self;
}
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García 
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for Animations
 * @module core
 */

var EJSS_CORE = EJSS_CORE || {};

/**
 * Creates a recorder for a model.
 * @method recorder
 */
EJSS_CORE.recorder = function(model) {
	var self = {};

	var mIsStarted = false;

	var mIsRecUserEvents = true;	// record user events (click event)	
	var mIsRecModelCalls = true;	// record model calls (play, stop, ...)
	var mIsRecInteractions = true;	// record view interactions

	var mVars = [];
	var mRecords = [];

	self.isStarted = function() {
		return mIsStarted;
	};

	self.setRecUserEvents = function(value) {
		mIsRecUserEvents = value;
	};

	self.isRecUserEvents = function() {
		return mIsRecUserEvents;
	};

	self.setRecModelCalls = function(value) {
		mIsRecModelCalls = value;
	};

	self.isRecModelCalls = function() {
		return mIsRecModelCalls;
	};

	self.setRecInteractions = function(value) {
		mIsRecInteractions = value;
	};

	self.isRecInteractions = function() {
		return mIsRecInteractions;
	};

	self.selectVars = function(vars) {
		mVars = vars.slice();
	};

	self.clear = function() {
		mRecords = [];
		model.getView().clearRegInteractions();
	};

	self.stop = function() {
		model.getView().unregisterInteractions();
		mIsStarted = false;		
		document.removeEventListener('mousedown', self.addEventRecord);
		document.removeEventListener('touchstart', self.addEventRecord);
		return self.getRecords();
	};
	
	self.start = function() {	
		if (!mIsStarted) {
			mIsStarted = true;
			mRecords = [];
			
			if (mIsRecInteractions) {
				model.getView().registerInteractions(false, self.addAutoRecord);
				model.getView().setShortRegInteractions(true);				
			}
						
			if(mIsRecUserEvents) {
				document.addEventListener('mousedown', self.addEventRecord);
				document.addEventListener('touchstart', self.addEventRecord);	
						
			}		
		}
	};
	    
	self.addEventRecord = function(event) {
		self.addRecord('_event.' + event.type, mVars);		
	};
	
	self.addAutoRecord = function(action, before) {
		if (before) {
			var tmp = {};
			if(typeof action['action'] != 'undefined') {
				tmp['type'] = 'action';
				tmp['name'] = action['action']; 
			} else {
				tmp['type'] = 'property';
				tmp['name'] = action['property']; 
				tmp['value'] = action['data']; 
			}
			self.addRecord('_view.'+action['element'], mVars, tmp);					
		} else {
			// last record
			var serial = model._userSerialize();
			mRecords[mRecords.length-1]['after'] = {};
			
			// select vars
			if (mVars.length == 0) {
				mRecords[mRecords.length-1]['after'] = serial;
			} else {
				for(v in mVars) {
					var e = mVars[v];
					mRecords[mRecords.length-1]['after'][e] = serial[e];
				}			
			}			
		}
	};
	 
	self.addRecord = function(tag, vars, tmp) {
		if (typeof vars == 'undefined')
			var vars = mVars;
			
		var serial = model._userSerialize();
		var record = {};
		record['before'] = {};
		
		// select vars
		if (vars.length == 0) {
			record['before'] = serial;
		} else {
			for(v in vars) {
				var e = vars[v];
				record['before'][e] = serial[e];
			}			
		}
		record["timeStamp"] = Date.now();
		record["tag"] = tag;
		if (typeof tmp != 'undefined')
			for(var key in tmp) record[key] = tmp[key];
		mRecords.push(record);		
	};

	self.getRecords = function() {		
		// console.log(JSON.stringify(mRecords));
		return mRecords;
	};
	
	self.downloadFile = function() {		
		EJSS_TOOLS.File.downloadText("records.json", JSON.stringify(self.getRecords()));
	};
	
	self.sendRecords = function(callback_ok, callback_error) {
		// check something to send
		if (mRecords.length == 0) return;
		// build JSON
		var json = JSON.stringify(mRecords, function(key, value) {
			if(typeof value == 'number') {
				if (isNaN(value)) value = "__NaN";
				else if (!isFinite(value)) value = "__Infinity";
			}
			return value;
		});
		// empty records
		mRecords = [];
		// send JSON to Moodle plugin
		var http = new XMLHttpRequest();
		var params = "view_id="+_record_view_id+"&info="+encodeURIComponent(json)+"&user_id="+_record_user_id;
		http.open("POST", _record_moodle_url + "?wstoken=" + _record_wstoken + "&wsfunction=" + _record_wsfunction, true);
		http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		http.onreadystatechange = function() {
			if(http.readyState == 4 && http.status == 200) {
				if(callback_ok) callback_ok();
			} else {
				if(callback_error) callback_error();
			}
		};
		http.send(params);
	};

	// ------------------------------------------------------------------------
	// Recording with Moodle server
	// ------------------------------------------------------------------------

	// check moodle server and plugin LA EjsS Moodle plugin
	var _record_url = new URL(document.location);
	var _record_view_id = (typeof _record_url.searchParams != 'undefined')?_record_url.searchParams.get('view_id'):false;
	if (_record_view_id) {
		var _record_user_id = _sg_url.searchParams.get('user_id');
		var _record_wstoken = _sg_url.searchParams.get('wstoken');
		var _record_moodle_url = _sg_url.searchParams.get('url');
		var _record_wsfunction = _sg_url.searchParams.get('wsfunction');

		// check Moodle server
		if(_record_wsfunction == 'report_get_lainteractions') {
			// sending info to moodle
			setInterval(function(){
				self.sendRecords();
			}, 3000);
		}
	}

	// ------------------------------------------------------------------------
	// Extending model functions
	// ------------------------------------------------------------------------
	 
	model.____play = model.play;   
	model.play = function() {
		if(mIsStarted && mIsRecModelCalls) self.addRecord("play");
		model.____play();
	};
	
	model.____pause = model.pause;   
	model.pause = function() {
		if(mIsStarted && mIsRecModelCalls) self.addRecord("pause");
		model.____pause();
	};
	
	model.____reset = model.reset;   
	model.reset = function(reuseview) {
		if(mIsStarted && mIsRecModelCalls) self.addRecord("reset");
		model.____reset();	
    };

	model.____initialize = model.initialize;
	model.initialize = function() {
		if(mIsStarted && mIsRecModelCalls) self.addRecord("initialize");
		model.____initialize();
    };
    
//	model.____step = model.step;
//	model.step = function() {
//		if(mIsStarted && mIsRecModelCalls) self.addRecord("step");
//		model.____step();
//    }
    
    return self;
	
};

/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia, Felix J Garcia-Clemente
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*
* This code is Open Source and is provided "as is".
*/

/**
 * Framework for view and control elements
 * @module core
 */

var EJSS_CORE = EJSS_CORE || {};

/**
 * A view which can connect to a remote model
 * @class RemoteView
 * @constructor
 */

/**
 * RemoteView: A subclass of View that connects to a remote model
 * Make you classes 'inherit' from this one by
 * - declaring:
 *   var _view = EJSS_CORE.createRemoteView(container);
 * - redeclaring (extending) the _reset() function, and
 * - returning _view.
 * 
 * createRemoteView("container") supposes:
 *  - mLocalModelPort is undefined, mLocalModelPort will be 8800 
 * 	- mModelsURL is undefined, mModelsURL will be location.protocol + "\\" + location.host + "/ejsS_library/models"
 *  - mModel is undefined, mModel will be the url substring between mModelsURL and _view, for example 
 *       http://155.54.1.1/ejsS_library/models/examples/mytest/_view/mytest.html -> mModel = "examples/mytest"
 * 
 * examples:
 * - createRemoteView("container")
 *       if this simulation is local, then remote view for an existing WS server (port 8800),
 *       else remote view for a new WS server for model located in the same location for this simulation
 * 
 * - createRemoteView("container", 8889)
 *       if this simulation is local, then remote view for an existing WS server (port 8889), 
 *       else remote view for a new WS server for model located in the location for this simulation
 * 
 *  - createRemoteView("container", 0, "http://millennium.inf.um.es/ejsS_library/models", "/examples/test1")
 *       if this simulation is local, then remote view for an existing WS server (port 8800),
 *       else remote view for a new WS server for model located in "http://millennium.inf.um.es/ejsS_library/models/examples/test1"
 * 
 * Note if history.state.mServerModelPort (see linking.php) exists, 
 *       then the simulation connects to a WS server running in port mServerModelPort (see broker.php)
 * 
 * @param mContainer view container
 * @param mLocalModelPort port for local WS server
 * @param mModelsURL root path for models (where you can find broker.php and linking.php)
 * @param mModel relative path for one model (mModelsURL+mModel is where you can find _view and _jar for the model)
 * 
 * @method createRemoteView
 */
EJSS_CORE.createRemoteView = function(mContainer, mLocalModelPort, mModelsURL, mModel) {  
		var self = EJSS_CORE.createView(mContainer);
	var mNumOpenTries = 10;
	var mOpenTimeout = 500;

	var mWebsocket;
	var mMessages = {};
	var mIsOpen = false;
	var mOpenTry = 0;  
	var mCurrentData = {};
	var mVariableList = [];
	var mActionList = [];
	var mPropertyChangesList = []; // list of property changes to report
	var mActionsInvokedList = []; // list of actions invoked to send
	var mUpdating = false;
	var mDelayedUpdate = false;
	var mVarsMetadata = [];
	var mMethodsMetadata = [];
	var mLinkingURL = "";
	var mShowLinkingButtons = true;
	var mServerModelPort = 0;
	var wrap;
	var mUri = "";	// uri to connect
	var countTo = 0;
	// ------------------------------------------------------------------------
	// Communication functions
	// ------------------------------------------------------------------------

	self.getClass = function() {
	return "ConnectionWS";
	};
	//JSV_CHANGES
	function sendMessage(command, data) {
	if (!mIsOpen) return;
	// console.log ("Sending message "+command+" with data:"+data);
	if (data!==undefined){
		//console.log (" <-- Sending message "+command+" with data: "+ JSON.stringify(data));
		mWebsocket.send(command + JSON.stringify(data));
	}else{
		//console.log (" <-- Sending message " + command + " with NO data: ");
		mWebsocket.send(command);
	}
	// reset socket : is this needed?
	var f = mWebsocket.onmessage;
	mWebsocket.onmessage = null;
	mWebsocket.onmessage = f;
	};

	//JSV_CHANGES
	function processInput(input) {
		console.log (" --> Processing input: "+input);
		var message;
		var usefulValues = {};
		if(input.charAt(0)=="{") {
			//This implies that the model is using pure JSON to send messages, then we are using the SDS (Smart Device Specification)
			message = JSON.parse(input);
			var method = "";
			if(message.hasOwnProperty("method")){
				method = message.method;
				if(method == "reset")	self._reset(); 
				else if(method == "initialize")	self._initialize(); 
				else if(wrap != undefined){
					usefulValues = wrap.extract(input);
					//console.log("Values extracted: "+ JSON.stringify(usefulValues));
					mCurrentData = usefulValues;
					coallesce(self._update);
				}
			}
			if(message.hasOwnProperty("apiVersion")){
				console.log("Metadata Obtained");
				wrap = new wrapper(mWebsocket,input);
				for(var variable in mVariableList){
					wrap.get(mVariableList[variable],true);
				}
			}
		}else{
			processNotSmartInput(input);
		}
		//console.log("mCurrentData : " + JSON.stringify(mCurrentData));
	}; 

   function processNotSmartInput(input) {
		var message;
		switch (input.charAt(0)) {
			case "R" : // reset 
				self._reset(); 
				break;
			case "I" : // initialize 
				self._initialize(); 
				break;
			case "U" : // update 
				mCurrentData = JSON.parse(input.substring(1)); 
				coallesce(self._update);
				break;
			case "C" : // collect
				mCurrentData = JSON.parse(input.substring(1)); 
				coallesce(self._collectData);
				break;
			case "M" : // View method
				message = JSON.parse(input.substring(1));
				if (!self[message.method]) console.log ("View function: <"+message.method+ "> with data: <"+message.data+"> ignored. View function does not exist.");
				else self[message.method](message.data); 

				break;
			case "P" : // Set the property of an element
				message = JSON.parse(input.substring(1));
				if (self[message.element]) self[message.element].setProperty(message.property,message.value);
				else console.log("setProperty<"+message.property+"> with value: <"+message.value+"> to element <"+message.element+"> ignored. Element does not exist!"); 
				break;
			case "E" : // call a method element with a single parameter
				message = JSON.parse(input.substring(1));
				if (self[message.element]) self[message.element][message.method](message.data);
				else console.log("Message <"+message.method+"> to element <"+message.element+"> ignored. Element does not exist!"); 
				break;
			case "F" : // call a method element with a map object (i.e a Javascript object) as parameter
				message = JSON.parse(input.substring(1));
				if (self[message.element]) self[message.element][message.method](message.data); 
				else console.log("Message with object <"+message.method+"> to element <"+message.element+"> ignored. Element does not exist!"); 
				break;
			case "D" : //calls a method to retrieve metadata information
				message = JSON.parse(input.substring(1));
				mVarsMetadata = message.variables;
				mMethodsMetadata = message.methods;
				break; 
			default:
				console.log("Not known message: Ignoring")
				break;
		}
   };
   
   function coallesce(viewFunction) {
     // if (mUpdating) {
       // mDelayedUpdate = true;
       // return;
     // }
     // mUpdating = true;
     // var f = mWebsocket.onmessage;
     // mWebsocket.onmessage = function(){ console.log("esperando ...");};
     viewFunction();
     self._render();
     // mUpdating = false;
     //sendMessage("O"); // OK : done processing Update of Collect
     

// var date = new Date();
// var curDate = null;
// 
// do { curDate = new Date(); } 
// while(curDate-date < 1000);
 
     // mWebsocket.onmessage = f;
    // mWebsocket.onmessage = function(message) {
        // self._processInput(message.data);
      // };
     // if (mDelayedUpdate) {
       // mDelayedUpdate = false;
       // _updateAndRender();
     // }
   };
   
  // ----------------------------------------------------
  // public methods
  // ----------------------------------------------------



  /**
   * Connect
   */
  self._connectToServer = function() {
    try {
      mWebsocket = new WebSocket(mUri);
      console.log('Connecting... (readyState ' + mWebsocket.readyState + ')');
      mWebsocket.onopen = function(message) {
		//console.log("This will be the call to the metadata method");
        mIsOpen = true;
        mOpenTry = 0;
        console.log("Openhd Event: " + message.type + " - Message: " + message.data);
        //sendMessage("D");
		//sendMessage("A", { variables : mVariableList, actions : mActionList });
		sendMessage("", { method : "getMetadata" });
      };
      mWebsocket.onclose = function(message) {
      	if (mOpenTry < mNumOpenTries) {
      		window.setTimeout(function(){ self._connectToServer(); }, mOpenTimeout); // try to connect again in 500 mseg
      		mOpenTry++;       	
        }
        mIsOpen = false;
        console.log("Closehd Event: " + message.type + " - Message: " + message.data + " - Reopen: " + mOpenTry);
      };
      mWebsocket.onerror = function(message) {
        mIsOpen = false;
        console.log("Errorhd Event: " + message.type + " - Message: " + message.data);
      };
      mWebsocket.onmessage = function(message) {
        processInput(message.data);
      };
    }
    catch(exception) {
      console.log(exception);
    }
    self._update();
    self._render();
  };
  
  self._getValue = function(variable) {
    if (mIsOpen) return mCurrentData[variable];
    return self._getInitialValue(variable); 
  };
  
  // ------------------------------------------------------------------------
  // This part is used automatically by ControlElements at run time
  // ------------------------------------------------------------------------

  super_registerVariable = self._registerVariable;
  super_registerAction = self._registerAction;
  super_startUp = self._startUp;
  
  self._startUp = function() {
    super_startUp();
    self._connectToServer();
  };
  
  self._registerVariable = function(variable, initialValue, inputOnly) {
    super_registerVariable(variable, initialValue);
    if (inputOnly) {
      self._linkVariable(variable,
        function() { return self._getValue(variable); },
        null, // no setter
        initialValue);
    } 
    else {
      mVariableList.push(variable);
      self._linkVariable(variable,
        function() { return self._getValue(variable); },
        function(value) { // setter
          // console.log("Adding property change:" + variable); 
          mPropertyChangesList.push( { name: variable, value: value } );
        }, initialValue);
    }
  };
  
  self._registerAction = function(action) {
    super_registerAction(action);
    mActionList.push(action);
    self._setAction(action, function(data) {
      // console.log("Adding action:" + action); 
      mActionsInvokedList.push( { name: action, argument: data } );
    });
  };
    
  self._reportInteraction = function () {
    self._readInteractions();
    //sendMessage("I", { properties : mPropertyChangesList, actions : mActionsInvokedList } );
    var names = {};
	var values = {};
	if(mPropertyChangesList.length>0){
		for(var propChange in mPropertyChangesList){
			if(mPropertyChangesList[propChange].hasOwnProperty("name"))
			names[propChange] = mPropertyChangesList[propChange].name;
			values[propChange] = mPropertyChangesList[propChange].value;
		}
		wrap.set(names, values, "");
	}
	if(mActionsInvokedList.length>0){
		for(var actInvoke in mActionsInvokedList){
			//if(mActionsInvokedList[argument].hasOwnProperty("argument"))
			wrap.callAction(mActionsInvokedList[actInvoke].name, [mActionsInvokedList[actInvoke].argument]);
			//else 
			//	wrap.set(mActionsInvokedList[actInvoke].name, undefined);
		}
	}
	mPropertyChangesList = [];
    mActionsInvokedList = [];
  };

   /*
    * Overrides its parent
   */
	self._interactionsNumber = function() {
		return mActionsInvokedList.length;
	};
	
  // ------------------------------------------------------------------------
  // Linking other server
  // ------------------------------------------------------------------------

  /**
   * Get linking url
   */
  self.getLinkingURL = function() {
  	return mLinkingURL; 
  };
  
  /**
   * Show linking buttons
   */  
  self.createLinkingButtons = function() {
	  self._addElement(EJSS_INTERFACE.button,"_linkingButton_")
	  .setAction("OnClick", function() { var str = self.getLinkingURL(); window.alert(str.toString());})
	  .setProperties({"Text":"@", "FontSize":"8px"});    
	
	  self._addElement(EJSS_INTERFACE.button,"_qrButton_")
	  .setAction("OnClick", function() {
	  		var str = encodeURIComponent(self.getLinkingURL());
	  		var url = "https://chart.googleapis.com/chart?cht=qr&chl=" + str + "&chs=400"; 
	  		window.open( url, '_blank');
	  	})
	  .setProperties({"Text":"QR", "FontSize":"8px"});
  };
  
  /**
   * Hidden linking buttons
   */  
  self.hiddenLinkingButtons = function() {
  	_view._linkingButton_.setVisibility("hidden");
  	_view._qrButton_.setVisibility("hidden");
  };

  /**
   * Show linking buttons
   */  
  self.showLinkingButtons = function() {
  	_view._linkingButton_.setVisibility("visible");
  	_view._qrButton_.setVisibility("visible");
  };
  
  //---------------------------------
  // final initialization
  //---------------------------------
         
   if (window.location.protocol.indexOf("file") != -1) { // is local
   		if (typeof mLocalModelPort == "undefined" || mLocalModelPort === null || mLocalModelPort == 0) { 
   			mLocalModelPort = 8800;
   		}

        // WS server uri
	    mUri =  "ws://localhost:" + mLocalModelPort;

		// not show linking buttons
		mShowLinkingButtons = false;
   } else {
	   if (history.state) {  // get server port
		 mServerModelPort = history.state.mServerModelPort;	// history sets port
		 mShowLinkingButtons = false;					    // not show linking buttons
	   }    

	   // get models url 
	   if (typeof mModelsURL == "undefined" || mModelsURL === null || mModelsURL == "") { // undefined models url
	  	 var host = window.location.host;
	  	 var proto = window.location.protocol;
	  	 mModelsURL = proto + "//" + host + "/ejsS_library/models";  // default models url  
	   }
	      
	   // get model path 
	   if (typeof mModel == "undefined" || mModel === null || mModel == "") { // undefined model path
	  	 var index = window.location.href.lastIndexOf("/_view"); // index for /_view
	  	 mModel = window.location.href.slice(mModelsURL.length, index);    
	   }

	  // run server model
	  var xmlhttp = new XMLHttpRequest();  
	  if(mServerModelPort > 0)	{ // known port
	  	xmlhttp.open("GET", mModelsURL + "/broker.php?model=" + mModel + "&port=" + mServerModelPort, true); 	  	
	  } else { // new connection (and new port)
	  	xmlhttp.open("GET", mModelsURL + "/broker.php?model=" + mModel, true);
	  }
	  
	  xmlhttp.onreadystatechange = function() {
		  // get response
		  if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
			  var response = JSON.parse(xmlhttp.responseText);
		      // WS server uri
			  mUri = response.webserver;
			  // linking url
			  mLinkingURL =  mModelsURL + "/linking.php?model=" + mModel + "&port=" + response.port; 
			  // console.log("Server response: " + xmlhttp.responseText);
			  
		      if (mShowLinkingButtons) self.createLinkingButtons();
		      
		      self._startUp();  
		  }
		  else 
		    console.log("WARNING: server no response!");
	  };
	  
	  xmlhttp.send();
   }
               
  return self;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Provides tools for main classes.
 * @module tools  
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

/**
 * Array
 * @class Array 
 * @constructor  
 */

/**
 * Adds an element to an array
 * @method addToArray
 */
EJSS_TOOLS.addToArray = function (array,object,position) {
  EJSS_TOOLS.removeFromArray(array,object); // first remove it
  if (position>=0) array.splice(position,0,object);
  else array.push(object);
};

/**
 * Element position in an array
 * @method arrayObjectIndexOf
 */
EJSS_TOOLS.arrayObjectIndexOf = function(array, object) {
  return array.indexOf(object);
};

/**
 * Removes an element from an array
 * @method removeFromArray
 */
EJSS_TOOLS.removeFromArray = function(array, object) {
  var index = array.indexOf(object);
  if (index>=0) array.splice(index,1);
};

/**
 * Compare two array
 * @method compareArrays
 */
EJSS_TOOLS.compareArrays = function(array1, array2) {
    if (!array1 || !array2)
        return false;

	// check arrays
	if (!array1 instanceof Array || !array2 instanceof Array) {
		return (array1 == array2);
	}

    // compare lengths - can save a lot of time
    if (array1.length != array2.length)
        return false;

    for (var i = 0; i < array1.length; i++) {
        // Check if we have nested arrays
        if (array1[i] instanceof Array && array2[i] instanceof Array) {
            // recurse into the nested arrays
            if (!EJSS_TOOLS.compareArrays(array1[i],array2[i]))
                return false;
        }
        else if (array1[i] != array2[i]) {
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;
        }
    }
    return true;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Provides tools for main classes.
 * @module tools  
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

/**
 * ConnectionWS
 * @class ConnectionWS 
 * @constructor  
 */
EJSS_TOOLS.ConnectionWS = {

    // ----------------------------------------------------
    // Static methods
    // ----------------------------------------------------

};

EJSS_TOOLS.connectionWS = function (mUri) {
  var self = {};							// reference returned 
  var websocket;
  var messages = {};
  var open;

  self.getClass = function() {
  	return "ConnectionWS";
  }

  self.get = function(property) {  	
  	var result = messages[property];
  	if((typeof result != "undefined") || (result == null)) 
  		return result;
  }

  self.set = function(property, args) {
  	console.log("set:"+property+":"+args.toString());
  	console.log("set:is open="+open);
  	if(open) {
  		var message = property + " " + args.toString();
  		websocket.send(message);  	
  		self.reset();
  	}
  }
  
  self.reset = function() {
  	var f = websocket.onmessage;
  	websocket.onmessage = null;
  	websocket.onmessage = f;  	
  }
 
  // ----------------------------------------------------
  // Public methods
  // ----------------------------------------------------

  /**
   * Connect
   */
  function connect() {
  	try {
		websocket = new WebSocket(mUri);
		console.log('Connecting... (readyState ' + websocket.readyState + ')');
	}
	catch(exception) {
		console.log(exception);
	}		
  };
      
  /**
   * Listener for events
   */  
  function listener(event,handler) {  	
	  switch (event) {
	    case "onopen" : 
	      websocket.onopen = function (e) {
	        handler(e);
	      };
	      break;
	    case "onmessage" : 
	      websocket.onmessage = function (e) {
	        handler(e);
	      };
	      break;
	    case "onclose" : 
	      websocket.onclose = function (e) {
	        handler(e);
	      };
	      break;
	    case "onerror" : 
	      websocket.onerror = function (e) {
	        handler(e);
	      };
	      break;      
	    default :
	      break; 
	  }
  }
  
  // ----------------------------------------------------
  // Private methods
  // ----------------------------------------------------
  
  function messagehd (message) {
	// message format: [keyword] [data]
	var words = message.data.split(" ");
	
	// store last message for keyword
	messages[words[0]] = message.data.substring(words[0].length+1);
  }  

  function openhd (message) {
  	open = true;
  	console.log("Openhd Event: " + message.type + " - Message: " + message.data);
  }  

  function closehd (message) {
  	open = false;
  	console.log("Closehd Event: " + message.type + " - Message: " + message.data);
  }  
      
  function errorhd (message) {
  	open = false;
  	console.log("Errorhd Event: " + message.type + " - Message: " + message.data);
  }  
      
  // ----------------------------------------------------
  // Final start-up
  // ----------------------------------------------------

  connect(); 
  listener("onopen", openhd);  
  listener("onclose", closehd);  
  listener("onerror", errorhd);  
  listener("onmessage", messagehd); 
  
  return self;
};

/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Provides tools for main classes.
 * @module tools  
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

/**
 * Decode
 * @class Decode
 * @constructor  
 */

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info/
*
**/
EJSS_TOOLS.Decode = {// private property
    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // public method for decoding

    hex_to_ascii : function (input) {
    	var hex  = input.toString();
 		var str = '';
 		for (var n = 0; n < hex.length; n += 2) {
 			str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
 		}
 		return str;
    },
    
    decode : function (input) {
      input = ""+input;
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while (i < input.length) {
            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));
            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;
            output = output + String.fromCharCode(chr1);
            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }
        }
        output = EJSS_TOOLS.Decode._utf8_decode(output);
        return output;
    },

    // private method for UTF-8 decoding

    _utf8_decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;
        while ( i < utftext.length ) {
            c = utftext.charCodeAt(i);
            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }

};


/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 */

/**
 * Framework for 2D drawing 
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

// Taken from the OSP core DisplayColors.java class

EJSS_TOOLS.DisplayColors = {
  phaseColors : {},
  lineColors : [ 
    "rgb(255,0,0)",   // red 
    "rgb(0,240,0)",   // darker green
    "rgb(0,0,255)",   // blue
    "rgb(240,240,0)", // darker yellow
    "rgb(0,240,240)", // darker cyan
    "rgb(255,0,255)"  // magenta
  ],
  markerColors : [ 
    "rgb(255,0,0)",   // red 
    "rgb(0,240,0)",   // darker green
    "rgb(0,0,255)",   // blue
    "rgb(240,240,0)", // darker yellow
    "rgb(0,240,240)", // darker cyan
    "rgb(255,0,255)"  // magenta
  ],
  arrayColors : { 
    "black": [0,0,0],
    "blue": [0,0,255],   		// blue
    "brown": [165,42,42],
    "cyan": [0,255,255],
    "darkblue": [0,0,160],	
    "darkcyan": [0,240,240], 	// darker cyan
    "darkgray": [64,64,64],   	// darker gray
    "darkgreen": [0,240,0],   	// darker green
    "darkgrey": [64,64,64],   	// darker gray
    "darkyellow": [240,240,0], 	// darker yellow
    "gray": [125,125,125],    
    "green": [0,255,0],
    "grey": [125,125,125],
    "lightblue": [173,216,230],
    "lightgray": [192,192,192],
    "lightgrey": [192,192,192],
    "magenta": [255,0,255],  	// magenta
    "maroon": [128,0,0],
    "olive": [128,128,0],
    "orange": [255,165,0],
    "pink": [255,175,175],
    "purple": [128,0,128],
    "red": [255,0,0],   		// red 
    "silver": [192,192,192],
    "white": [255,255,255],
    "yellow": [255,255,0]
  },

  /**
   * Gets a line color that matches the index.
   * @method getLineColor
   * @param index int
   * @return String
   */
  getLineColor : function(index) {
    var color = EJSS_TOOLS.DisplayColors.lineColors[index];
    if (color===undefined) {
      var h = ((index*360)/12)%360;
      color = "hsl("+h+",100%,50%)";
//      console.log("Added lineColor["+index+"] = "+color);
      EJSS_TOOLS.DisplayColors.lineColors[index] = color;
    }
    return color;
  },

  /**
   * Gets a marker color that matches the index.
   * @method getMarkerColor
   * @param index int
   * @return String
   */
  getMarkerColor : function(index) {
    var color = EJSS_TOOLS.DisplayColors.markerColors[index];
    if (color===undefined) {
      var h = ((index*360)/12)%360;
      color = "hsl("+h+",100%,100%)";
      EJSS_TOOLS.DisplayColors.markerColors[index] = color;
    }
    return color;
  },

  /**
   * Gets an array color that matches the color string.
   * @method getArrayColor
   * @param index string
   * @return array
   */
  getArrayColor : function(str) {
  	str = str.toLowerCase();
  	if(str.indexOf("rgb") == 0) { // rgb(r,g,b)
  		var color = str.substring(4, str.length-1).replace(/ /g, '').split(',');
  	} else if(str.indexOf("#") == 0) { // #xxxxxx
 		str = str.slice(1); //Remove the '#' char - if there is one.
		str = str.toUpperCase();
		var hex_alphabets = "0123456789ABCDEF";
		var color = new Array(3);
		var k = 0;
		var int1,int2;
		for(var i=0;i<6;i+=2) {
			int1 = hex_alphabets.indexOf(str.charAt(i));
			int2 = hex_alphabets.indexOf(str.charAt(i+1)); 
			color[k] = (int1 * 16) + int2;
			k++;
		}  
	}	  		
  	else { // black, red, ...
	    var color = EJSS_TOOLS.DisplayColors.arrayColors[str];
	    if (color === undefined) {
	      color = EJSS_TOOLS.DisplayColors.arrayColors["black"];
	    }
	}	
	
    return [color[0]/255, color[1]/255, color[2]/255];
  }

};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Framework for 2D drawing 
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

// Taken from example 09.07 of Flanagan's book: Javascript: the definite guide
//This function creates a new enumerated type.  The argument object specifies
//the names and values of each instance of the class. The return value
//is a constructor function that identifies the new class.  Note, however
//that the constructor throws an exception: you can't use it to create new
//instances of the type.  The returned constructor has properties that 
//map the name of a value to the value itself, and also a values array,
//a foreach() iterator function
EJSS_TOOLS.enumeration = function (namesToValues) {
 // This is the dummy constructor function that will be the return value.
 var enumeration = function() { throw "Can't Instantiate Enumerations"; };

 // Enumerated values inherit from this object.
 var proto = enumeration.prototype = {
     constructor: enumeration,                   // Identify type
     toString: function() { return this.name; }, // Return name
     valueOf: function() { return this.value; }, // Return value
     toJSON: function() { return this.name; }    // For serialization
 };

 enumeration.values = [];  // An array of the enumerated value objects

 // Now create the instances of this new type.
 for(var name in namesToValues) {         // For each value 
     var e = inherit(proto);          // Create an object to represent it
     e.name = name;                   // Give it a name
     e.value = namesToValues[name];   // And a value
     enumeration[name] = e;           // Make it a property of constructor
     enumeration.values.push(e);      // And store in the values array
 }
 // A class method for iterating the instances of the class
 enumeration.foreach = function(f,c) {
     for(var i = 0; i < this.values.length; i++) f.call(c,this.values[i]);
 };

 // Return the constructor that identifies the new type
 return enumeration;
};
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Provides tools for main classes.
 * @module tools  
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

/**
 * A utility with Math functions.
 * @class Mathematics 
 * @constructor  
 */

EJSS_TOOLS.File = {
  JSLoaded : "", // list of JS files already added
  SingletonDownload :  document.createElement("input"), 
  SingletonUpload :  document.createElement("input"),

  /**
   * Dynamically load a JS file 
   * @method loadJSfile
   * @return void
   */
  loadJSfile : function(filename, scriptLoaded) {
//    console.log("Wants to load "+filename);
    if (EJSS_TOOLS.File.JSLoaded.indexOf("["+filename+"]")!=-1) { // already added
      console.log("File already loaded "+filename);
      scriptLoaded();
      return;
    }
//    console.log("File loaded "+filename);
    EJSS_TOOLS.File.JSLoaded+="["+filename+"]" //List of files added in the form "[filename1],[filename2],etc"
    var fileref=document.createElement('script')
    fileref.setAttribute("type","text/javascript")
    fileref.setAttribute("src", filename);
    if (typeof scriptLoaded!="undefined") fileref.onload=scriptLoaded;
    document.getElementsByTagName("head")[0].appendChild(fileref);
  },
  
  plainName : function(filename) {
    var index = filename.lastIndexOf("/");
    if (index>=0) filename = filename.substring(index+1);
    index = filename.indexOf(".");
    if (index>0) filename = filename.substring(0,index);
    return filename;
  },
  

  downloadText : function(filename, content) {
	  var blob = new Blob([content]);
	  EJSS_TOOLS.File.SingletonDownload = document.createElement('a');
	  EJSS_TOOLS.File.SingletonDownload.href = window.URL.createObjectURL(blob); //"data:application/text;charset=utf-8," + text);
	  EJSS_TOOLS.File.SingletonDownload.download = filename;
	  EJSS_TOOLS.File.SingletonDownload.click();
	  window.URL.revokeObjectURL(blob);
  },

  uploadText : function(model,action) {
	  EJSS_TOOLS.File.SingletonUpload.type = 'file';
	  EJSS_TOOLS.File.SingletonUpload.accept = 'text/*';
	  EJSS_TOOLS.File.SingletonUpload.multiple = false;
	  EJSS_TOOLS.File.SingletonUpload.onchange = function(event) {
	    var reader = new FileReader();
	    reader.onload = function(progressEvent){
	      var parameter =  this.result;
	      model.performTaskSafely(function() { action(parameter); });
	      EJSS_TOOLS.File.SingletonUpload.value = "";
	    };
	    reader.readAsText(event.target.files[0]);
	  };
	  EJSS_TOOLS.File.SingletonUpload.click();
  },
  
  loadScript: function(scriptsrc) {
    var s = document.createElement('script');
    s.type='text/javascript';
    s.src = scriptsrc;
    document.getElementsByTagName('head')[0].appendChild(s);    
  },  

  loadJSONFile: function(url, callback) {     
	var xmlhttp = new XMLHttpRequest();	
	xmlhttp.onreadystatechange = function() {
	    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
	        var myArr = JSON.parse(xmlhttp.responseText);
	        callback(myArr);
	    }
	}
	xmlhttp.open("GET", url, true);
	xmlhttp.send();
  },

  loadCSVFile: function(url, callback) {     
	var xmlhttp = new XMLHttpRequest();	
	xmlhttp.onreadystatechange = function() {
	    if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
	    	// CSV parse
    		var allTextLines = xmlhttp.responseText.split(/\r\n|\n/);    		
    		var lines = [];
    		for (var i=1; i<allTextLines.length; i++) {
        		var data = allTextLines[i].split(',');
            	var tarr = [];
            	for (var j=0; j<data.length; j++) {
                	tarr.push(data[j]);
            	}
            	lines.push(tarr);
        	}
	        callback(lines);
	    }
	}
	xmlhttp.open("GET", url, true);
	xmlhttp.send();
  },

  requestShortUrl: function(json, callback) {
    var encoded = escape("http://urlecho.appspot.com/echo?status=200&Content-Type=text/plain&body=" + json);    

	// create ref to callback
	EJSS_TOOLS.File.CALLBACK = callback;
  	EJSS_TOOLS.File.callFunction = function(data) {
		EJSS_TOOLS.File.CALLBACK(data.shortUrl);  	
  	}

	// callback
	if(document.getElementById("__rtShortUrl") == null) {
	    var scb = document.createElement('script');
	    scb.type = 'text/javascript';
	    scb.id = "__rtShortUrl"
	    scb.innerHTML = "function rtShortUrl(data) { EJSS_TOOLS.File.callFunction(data); }"; 
	    document.getElementsByTagName('head')[0].appendChild(scb);    
	}
	
  	var s = document.getElementById("_requestShortUrl");
  	if(s != null) document.getElementsByTagName('head')[0].removeChild(s);
  	 
	s = document.createElement('script');
	s.id = "_requestShortUrl";
	s.type='text/javascript';
    s.src = "http://b1t.co/Site/api/External/MakeUrlWithGet?callback=rtShortUrl&url=" + encoded;
    document.getElementsByTagName('head')[0].appendChild(s);    
  }
  
};




/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

var EJSS_HARDWARE = EJSS_HARDWARE || {};

/*
 * Browser-compatible orientation tools
 */
EJSS_HARDWARE.orientationChange = function (mCallback) {
	var self = {};
	var mIOSkind = (getMobileOperatingSystem()!=="Android");
	var mOrientation = 0;

	self.getOrientation = function() { return mOrientation; }
	
	function readOrientation() {
		var orientation;
		if (mIOSkind) { // Android does it otherwise
			if (window.orientation=="90") orientation = 90;
			else if (window.orientation=="0") orientation = 0;
			else if (window.orientation=="-90") orientation = -90;
			else orientation = 180;
		}
		else { // iOS behaviour
			if (window.orientation=="90") orientation = -90;
			else if (window.orientation=="0") orientation = 180;
			else if (window.orientation=="-90") orientation = 90;
			else orientation = 0;
		}
		return orientation;
	}
	
	function getMobileOperatingSystem() {
		var userAgent = navigator.userAgent || navigator.vendor || window.opera;
		if( userAgent.match( /iPad/i ) || userAgent.match( /iPhone/i ) || 
				userAgent.match( /iPod/i ) ) return 'iOS';
		if( userAgent.match( /Android/i ) ) return 'Android';
		return 'unknown';
	};

	window.addEventListener("orientationchange", function() {
		mOrientation  = readOrientation();
		if (typeof mCallback != 'undefined') mCallback();
  	  }, false);

	mOrientation = readOrientation();
	
	return self;
};

/*
 * Browser-compatible accelerometer
 */
EJSS_HARDWARE.accelerometer = function () {
	var self = {};
	var mListeners = [];
	var mIsRunning = false;
	var mOrientationChange = EJSS_HARDWARE.orientationChange();
	var mData = { x : 0, y:0, z:0 };

	var mSensor;
	var mLastReadingTimestamp = 0;

	function deviceReadingHandler() {
	    var interval = 1.0/60;
	    if (mLastReadingTimestamp) {
	      interval = Math.round(mSensor.timestamp - mLastReadingTimestamp);
	    }
	    mLastReadingTimestamp = mSensor.timestamp;

		mData.x = mSensor.x;
		mData.y = mSensor.y;
		mData.z = mSensor.z;

		for (var i=0, n=mListeners.length; i<n; i++) {
			mListeners[i](mData, interval);
		}
	};

	function deviceMotionHandler(eventData) {
		var acceleration = eventData.accelerationIncludingGravity;
		var interval = eventData.interval;

		mData.x = acceleration.x;
		mData.y = acceleration.y;
		mData.z = acceleration.z;

		for (var i=0, n=mListeners.length; i<n; i++) {
			mListeners[i](mData, interval);
		}
	};

	self.isPresent = function() { 
		return ((typeof window.LinearAccelerationSensor != "undefined") || (typeof window.DeviceMotionEvent != "undefined"))
	};

	self.start = function() {
		if (self.isPresent()) {
			if ('LinearAccelerationSensor' in window) {
				mSensor = new LinearAccelerationSensor( { frequency: 60, referenceFrame: "device" } );
				mSensor.addEventListener('reading', deviceReadingHandler, false);
				mSensor.start();
			} else {
				window.addEventListener('devicemotion', deviceMotionHandler, false);
			}
			mIsRunning = true;
		}
	};

	self.stop = function() {
		if (self.isPresent()) {
			if ('LinearAccelerationSensor' in window) {
				window.removeEventListener('reading', deviceReadingHandler);
				try { mSensor.stop() } catch (e) { };
			} else {
				window.removeEventListener('devicemotion', deviceMotionHandler);
			}
		} 
		mData = { x : 0, y:0, z:0 };
		mIsRunning = false;
	};

	self.isRunning = function() { return mIsRunning; };
		
	self.getDeviceData = function() { return mData; };
	
	self.getViewData = function () {
		switch (mOrientationChange.getOrientation()) {
		  case   0 : return { x: mData.x, y: mData.y, z: mData.z }; break;
		  case  90 : return { x:-mData.y, y: mData.x, z: mData.z }; break;
		  case -90 : return { x: mData.y, y:-mData.x, z: mData.z }; break;
		  default  : return { x:-mData.x, y:-mData.y, z: mData.z }; break;
	    }
	};

	self.addListener = function(listener) { mListeners.push(listener); };

	self.removeListener = function(listener) {
		var index = mListeners.indexOf(listener);
		if (index>-1) mListeners = mListeners.splice(index,1); 
	};

	return self;
};

/*
 * Browser-compatible gyroscope
 */
EJSS_HARDWARE.gyroscope = function () {
	var self = {};
	var mListeners = [];
	var mIsRunning = false;
	var mData = { alpha: 0, beta: 0, gamma: 0 };

	var mSensor;
	var mLastReadingTimestamp = 0;

	function deviceReadingHandler() {
	    var interval = 1.0/60;
	    if (mLastReadingTimestamp) {
	      interval = Math.round(mSensor.timestamp - mLastReadingTimestamp);
	    }
	    mLastReadingTimestamp = mSensor.timestamp;

	    mData.alpha = rotation.alpha;
	    mData.beta  = rotation.beta;
        mData.gamma = rotation.gamma;

		for (var i=0, n=mListeners.length; i<n; i++) {
			mListeners[i](mData, interval);
		}
	};

	function deviceMotionHandler(eventData) {
	    var rotation = eventData.rotationRate;
		var interval = eventData.interval;

	    mData.alpha = rotation.alpha;
	    mData.beta  = rotation.beta;
        mData.gamma = rotation.gamma;

		for (var i=0, n=mListeners.length; i<n; i++) {
			mListeners[i](mData, interval);
		}
	};

	self.isPresent = function() { 
		return ((typeof window.Gyroscope != "undefined") || (typeof window.DeviceMotionEvent != "undefined")); 
	};

	self.start = function() {
		if (self.isPresent()) {
			if ('Gyroscope' in window) {
				mSensor = new Gyroscope();
				mSensor.addEventListener('reading', deviceReadingHandler, false);
				mSensor.start();
			} else {
				window.addEventListener('devicemotion', deviceMotionHandler, false);
			}
			mIsRunning = true;
		}
	};

	self.stop = function() {
		if (self.isPresent()) {
			if ('Gyroscope' in window) {
				window.removeEventListener('reading', deviceReadingHandler);
				try { mSensor.stop() } catch (e) { };
			} else {
				window.removeEventListener('devicemotion', deviceMotionHandler);
			}
		} 
		mData = { alpha: 0, beta: 0, gamma: 0 };
		mIsRunning = false;
	};

	self.isRunning = function() { return mIsRunning; };
		
	self.getDeviceData = function() { return mData; };
	
	self.addListener = function(listener) { mListeners.push(listener); };

	self.removeListener = function(listener) {
		var index = mListeners.indexOf(listener);
		if (index>-1) mListeners = mListeners.splice(index,1); 
	};

	return self;
};

/*
 * Browser-compatible orientation
 */
EJSS_HARDWARE.orientation = function () {
	var self = {};
	var mListeners = [];
	var mIsRunning = false;

	var mData = { alpha: 0, beta: 0, gamma: 0};

	function deviceOrientationHandler(eventData) {
		var interval = eventData.interval;
		
	    mData.alpha = eventData.alpha;
	    mData.beta  = eventData.beta;
        mData.gamma = eventData.gamma;
        
		for (var i=0, n=mListeners.length; i<n; i++) {
			mListeners[i](mData, interval);
		}
	};

	self.isPresent = function() { return (typeof window.DeviceOrientationEvent != "undefined"); };

	self.start = function() {
		if (self.isPresent()) {
			window.addEventListener('deviceorientation', deviceOrientationHandler, false);
			mIsRunning = true;
		}
	};

	self.stop = function() {
		if (self.isPresent()) window.removeEventListener('deviceorientation', deviceOrientationHandler);
		mData = { alpha: 0, beta: 0, gamma: 0};
		mIsRunning = false;
	};

	self.isRunning = function() { return mIsRunning; };

	self.getDeviceData = function() { return mData; };

	self.addListener = function(listener) { mListeners.push(listener); };

	self.removeListener = function(listener) {
		var index = mListeners.indexOf(listener);
		if (index>-1) mListeners = mListeners.splice(index,1); 
	};

	return self;
};

/*
 * Chrome-compatible light
 */
EJSS_HARDWARE.ambientLight = function () {
	var self = {};
	var mListeners = [];
	var mIsRunning = false;

	var mData = { lux: 0 };
	var mSensor;
	var mLastReadingTimestamp = 0;

	function deviceHandler() {
	    var interval = 1.0/60;
	    if (mLastReadingTimestamp) {
	      interval = Math.round(mSensor.timestamp - mLastReadingTimestamp);
	    }
	    mLastReadingTimestamp = mSensor.timestamp;

	    mData.lux = mSensor.illuminance;
        
		for (var i=0, n=mListeners.length; i<n; i++) {
			mListeners[i](mData, interval);
		}
	};

	self.isPresent = function() { return (typeof window.AmbientLightSensor != "undefined"); };

	self.start = function() {
		if (self.isPresent()) {
			mSensor = new AmbientLightSensor();
			mSensor.addEventListener('reading', deviceHandler, false);
			mSensor.start();
			mIsRunning = true;
		}
	};

	self.stop = function() {
		if (self.isPresent()) window.removeEventListener('reading', deviceHandler);
		try { mSensor.stop() } catch (e) { };
		mData = { lux: 0 };
		mIsRunning = false;
	};

	self.isRunning = function() { return mIsRunning; };

	self.getDeviceData = function() { return mData; };

	self.addListener = function(listener) { mListeners.push(listener); };

	self.removeListener = function(listener) {
		var index = mListeners.indexOf(listener);
		if (index>-1) mListeners = mListeners.splice(index,1); 
	};

	return self;
};

/*
 * Chrome-compatible magnometer
 */
EJSS_HARDWARE.magnometer = function () {
	var self = {};
	var mListeners = [];
	var mIsRunning = false;

	var mData = { x: 0, y: 0, z: 0 };
	var mSensor;
	var mLastReadingTimestamp = 0;

	function deviceHandler() {
	    var interval = 1.0/60;
	    if (mLastReadingTimestamp) {
	      interval = Math.round(mSensor.timestamp - mLastReadingTimestamp);
	    }
	    mLastReadingTimestamp = mSensor.timestamp;

	    mData.x = mSensor.x;
	    mData.y = mSensor.y;
	    mData.z = mSensor.z;
        
		for (var i=0, n=mListeners.length; i<n; i++) {
			mListeners[i](mData, interval);
		}
	};

	self.isPresent = function() { return (typeof window.Magnometer != "undefined"); };

	self.start = function() {
		if (self.isPresent()) {
			mSensor = new Magnometer();
			mSensor.addEventListener('reading', deviceHandler, false);
			mSensor.start();
			mIsRunning = true;
		}
	};

	self.stop = function() {
		if (self.isPresent()) window.removeEventListener('reading', deviceHandler);
		try { mSensor.stop() } catch (e) { };
		mData = { x: 0, y: 0, z: 0 };
		mIsRunning = false;
	};

	self.isRunning = function() { return mIsRunning; };

	self.getDeviceData = function() { return mData; };

	self.addListener = function(listener) { mListeners.push(listener); };

	self.removeListener = function(listener) {
		var index = mListeners.indexOf(listener);
		if (index>-1) mListeners = mListeners.splice(index,1); 
	};

	return self;
};

/*
 * Browser-compatible audio
 */
EJSS_HARDWARE.audio = function () {
	var self = {};
	var mIsRunning = false;

	var mData = { x: 0, y: 0, z: 0 };
	var mAudioCtx;
	var mAnalyser;

	self.isPresent = function() { 
  		return (navigator.AudioContext || navigator.webkitAudioContext);
	};

	self.start = function(callback_ok, callback_error) {
		if (self.isPresent()) {
  			// Audio Context
  			if (typeof mAudioCtx == 'undefined') {
  				mAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
			   	//set up the analyser
			  	mAnalyser = audioCtx.createAnalyser();
			  	mAnalyser.minDecibels = -20; 
			  	mAnalyser.maxDecibels = -80;
			  	mAnalyser.smoothingTimeConstant = 0.85;
			  	
			  	// filter
			  	var distortion = audioCtx.createWaveShaper();
			  	var gainNode = audioCtx.createGain();
			  	var biquadFilter = audioCtx.createBiquadFilter();
			  	var convolver = audioCtx.createConvolver();
			 
			  	// Call Get User Media
			  	navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function(stream) {
			       var source = mAudioCtx.createMediaStreamSource(stream);
			       source.connect(mAnalyser);
			       mAnalyser.connect(distortion);
			       distortion.connect(biquadFilter);
			       biquadFilter.connect(convolver);
			       convolver.connect(gainNode);
			       gainNode.connect(mAudioCtx.destination);
			       
			       if (typeof callback_ok == 'undefined') callback_ok();
					
				   mIsRunning = true;
			  	}).catch(function(err) {
			       console.log('The following getUserMedia error occured: ' + err);
			       if (typeof callback_error == 'undefined') callback_error();
			  	});	  	
			}
		}
	};

	self.stop = function() {
		// nothing to do
	};

	// https://webaudio.github.io/web-audio-api/#baseaudiocontext	
	self.getAudioContext = function() {
		return mAudioContext;
	}
	
	// https://webaudio.github.io/web-audio-api/#analysernode
	self.getAnalyser = function() {
		return mAnalyser;
	}

	self.isRunning = function() { return mIsRunning; };

	self.getDeviceData = function() { return mData; };

	return self;
};
/**
 * JSON-RPC Builder
 * author: Jesús Chacón <jcsombria@gmail.com>
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

/**
 * @class jsonrpcBuilder 
 * @constructor  
 */

EJSS_TOOLS.JsonrpcBuilder = {
	/** JSON-RPC Templates */
	template: {
		METHOD: '${METHOD}',
		PARAMS: '${PARAMS}',
		ID: '${ID}',
		REQUEST: '{jsonrpc: "2.0", method: ${METHOD}, params: ${PARAMS}, id: ${ID}}',
		RESPONSE_OK: '{jsonrpc: "2.0", result: ${RESULT}, params: ${PARAMS}, id: ${ID}}',
		RESPONSE_ERROR: '{jsonrpc: "2.0", method: ${METHOD}, params: ${PARAMS}, id: ${ID}}',
		ERROR: '{code: "2.0", message: ${MESSAGE}, data: ${DATA}}'
	},

	/**
	 * Build a JSON-RPC request object
	 *
	 * @param {string} method - The method to call
	 * @param {array|object} params - The params either by position (array) or by name (object).
	 * @param {string|integer} id - The id of the call
	 */
	request: function(method, params, id) {
		if(params && !(params instanceof Array)) throw new InvalidParamsException();
//		var pars = (params && !(params instanceof Array)) ? [params] : params;
		var request = {jsonrpc: '2.0', method: method};
		if(params) request.params = params;
		if(id) request.id = id;

		return request;
	},

	/**
	 * Build a JSON-RPC response object
	 *
	 * @param {object} result - The result to return
	 * @param {string|integer} id - The id of the call
	 */
	response: function(result, id) {
		return {
			jsonrpc: '2.0',
			result: result,
			id: id
		}
	},

	/**
	 * Build a JSON-RPC error response object
	 *
	 * @param {object} error - The error object
	 * @param {string|integer} id - The id of the call
	 */
	responseWithError: function(error, id) {
		return {
			jsonrpc: '2.0',
			error: error,
			id: id
		};
	},

	/**
	 * Build a JSON-RPC error object
	 *
	 * @param {integer} code - The result to return
	 * @param {string} message - The error description message 
	 * @param {object} data - Additional data
   */
	error: function(code, message, data) {
		return {
			code: code, 
			message: message, 
			data: data
		};
	},

	/**
	 * Send method calls in batch list
	 *
	 * @param {string} resString - The response in JSON-RPC format
   */
	parseResponse: function(resString) {
		var response = null;
		try {
			response = JSON.parse(resString);
/*			if(this.sent[response.id]) {
				var method = this.sent[response.id].method;
				this.response[method] = response;
			}*/
		} catch(error) {
			console.log(error);
		}
		return (response != null) ? response.result : null;
	}
}
/**
 * JSON-RPC Client
 * author: Jesús Chacón <jcsombria@gmail.com>
 *
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

/**
 * @class jsonrpcClient 
 * @constructor  
 */

EJSS_TOOLS.JsonrpcClient =  {
	methods : {
		connect: 'connect',
		open: 'open',
		getMetadata: 'getMetadata',
		getValue: 'getValue',
		setValue: 'setValue',
		close: 'close',
		disconnect: 'disconnect',
	}
};

EJSS_TOOLS.jsonrpcClient = function (mHost, mPort) {
	var self = {};
	var JsonrpcClient = EJSS_TOOLS.JsonrpcClient;
	var JsonrpcBuilder = EJSS_TOOLS.JsonrpcBuilder;
    var websocket;
    var mCallback = {};
    var mOpenCallback;
    var mCloseCallback;
    var mErrorCallback;
    var mMessageCallback;
	
	self.connect = function(callback) {
		return callMethod(JsonrpcClient.methods.connect, null, callback);
	};

	self.open = function(callback) {
		return callMethod(JsonrpcClient.methods.open, null, callback);
	};
	
	self.getValue = function(name, callback) {
		return callMethod(JsonrpcClient.methods.getValue, [name], callback);
	};

	self.setValue = function(data, callback) {
		return callMethod(JsonrpcClient.methods.setValue, data, callback);
	};

	self.getMetadata = function(callback) {
		return callMethod(JsonrpcClient.methods.getMetadata, null, callback);
	};

	self.disconnect = function(callback) {
		return callMethod(JsonrpcClient.methods.disconnect, null, callback);
	};

	self.close = function(callback) {
		return callMethod(JsonrpcClient.methods.close, null, callback);
	};

	function idGenerator(len) {
	    var text = "";
	    var charset = "abcdefghijklmnopqrstuvwxyz";
	    for(var i=0; i<len; i++) text+=charset.charAt(Math.floor(Math.random()*charset.length));
	    return text;
	};

	function callMethod (method, params, callback, _id) {
		var id = (_id)? _id:idGenerator(12);
		if(callback) mCallback[id] = callback;
		var request = JsonrpcBuilder.request(method, params, id);
		websocket.send(JSON.stringify(request));
	}; 
	
  	self.init = function(opencb,messcb,errorcb,closecb) {
  		try {
  			mOpenCallback = opencb;
  			mErrorCallback = errorcb;
  			mCloseCallback = closecb;
  			mMessageCallback = messcb;
  			
  			var uri = "ws://" + mHost;
  			if(mPort) uri += ":" + mPort;
			websocket = new WebSocket(uri);
			addListener();
			console.log('Connecting... (readyState ' + websocket.readyState + ')');
		}
		catch(exception) {
			console.log(exception);
		}		
  	};
  	
  	self.finish = function() {
  		try {
  			websocket.close();
  		}
		catch(exception) {
			console.log(exception);
		}		
  	};
  	
  	
   function addListener() {
    	websocket.onopen = function (message) {
    		if(mOpenCallback) mOpenCallback(message);
       		console.log("Openhd Event: " + message.type + " - Message: " + message.data);
      	};
      	websocket.onmessage = function (message) {
       		  var data = JSON.parse(message.data);
       		/*
       		if (counter%20==0) {
       		  console.log("Event On Message: " + message.type + " - Message: " + JSON.stringify(message));
       		  console.log("mCallback: " + JSON.stringify(mCallbackObject));
       		  console.log("data: " + JSON.stringify(data));
		      console.log ("MeCall["+data.id+"] = "+ mCallbackObject[data.id]);
       		  console.log("self: " + (!!self));
       		  }
       		counter++;
       		*/
    		if(mMessageCallback) mMessageCallback(mCallback);
			if(data.id && mCallback[data.id]) mCallback[data.id](data.result);
      	};
      	websocket.onclose = function (emessage) {
  			if(mCloseCallback) mCloseCallback(message);
  			console.log("Closehd Event: " + message.type + " - Message: " + message.data);
      	};
      	websocket.onerror = function (message) {
      		if(mErrorCallback) mErrorCallback(message);
  			console.log("Errorhd Event: " + message.type + " - Message: " + message.data);
      	};	 		
  	};
  	
  	return self;
}
/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Provides tools for main classes.
 * @module tools  
 */

var EJSS_TOOLS = EJSS_TOOLS || {};

/**
 * A utility with Math functions.
 * @class Mathematics 
 * @constructor  
 */

EJSS_TOOLS.Mathematics = {
  TWO_PI : 2*3.1415926, //Math.PI*2,
  TO_RADIANS : Math.PI / 180,
  LOG10SCALE : 1/Math.log(10),
  cosineAndSine : [],

  /**
   * Cosine and sine for degrees.
   * @method cosineAndSineForDegrees
   * @return Array[2] cosine and sine
   */
  cosineAndSineForDegrees : function(degrees) {
    degrees %= 360;
    if (degrees<0) degrees += 360;
    var values = this.cosineAndSine[degrees];
    if (values === undefined) {
      var radians = degrees*this.TO_RADIANS;
      values = [Math.cos(radians), Math.sin(radians)];
    }
    return values;
  },

  /**
   * Degrees to radians .
   * @method radians
   * @return double
   */  
  radians : function(degrees) {
  	return degrees * Math.PI / 180;
  },
  
  /**
   * Raians to degrees.
   * @method degrees
   * @return double
   */  
  degrees : function(radians) {
	var angle = radians * 180 / Math.PI;
	var r = angle % 360;
	angle = (r * 360 < 0) ? r + 360 : r;
	return angle;     	
  },
  
  /**
   * Norm.
   * @method norm
   * @param v vector
   * @return norm
   */
  norm : function (v) {
  	var sum = 0;
  	for(var i=0; i<v.length; i++) sum += v[i]*v[i]; 
    return Math.sqrt(sum);
  },

  /**
   * Cross product.
   * @method crossProduct
   * @param v1 vector
   * @param v2 vector
   * @return cross product
   */
  crossProduct : function(v1, v2) {
    return [v1[1]*v2[2]-v1[2]*v2[1], v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1]-v1[1]*v2[0]];
  },

  /**
   * Normalize.
   * @method normalize
   * @param v vector
   * @return vector normalized
   */
  normalize : function(v) {
    var r = EJSS_TOOLS.Mathematics.norm(v);
    if (r==0.0) return v;
    return [v[0]/r, v[1]/r, v[2]/r];
  },

  /**
   * Normal vector.
   * @method normalTo
   * @param v vector
   * @return normal vector
   */
  normalTo : function (v) {
    if(v[0]==0.0) {
      return [1.0, 0.0, 0.0];
    } else if(v[1]==0.0) {
      return [0.0, 1.0, 0.0];
    } else if(v[2]==0.0) {
      return [0.0, 0.0, 1.0];
    } else {
      var norm = EJSS_TOOLS.Mathematics.norm(v);
      return [-v[1]/norm, v[0]/norm, 0.0];
    }
  },
  
  /**
   * Rotate vector.
   */
  rotate : function(vc, v, radians) {
    var cos = Math.cos(radians),
        sin = Math.sin(radians),
        nx = (cos * (v[0] - vc[0])) + (sin * (v[1] - vc[1])) + vc[0],
        ny = (cos * (v[1] - vc[1])) - (sin * (v[0] - vc[0])) + vc[1];
    return [nx, ny];
  }  
  
};




/*
 * Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia
 * This code is part of the Easy Javascript Simulations authoring and simulation tool
 * 
 * This code is Open Source and is provided "as is".
 */

/**
 * Provides tools for managing sensors.
 * @module tools  
 */

var ejsSsensors = {
  // iOS device sensors
  iOS_accelerometer: {x:0.0, y:0.0, z:0.0},
  iOS_gyroscope: {x:0.0, y:0.0, z:0.0},
  setAccelerometer : function(accvalue) {
  	// function called by iOS 
    var value = JSON.parse(accvalue);
    var accelerometer = eval(value.result);
    
    if(window.innerWidth > window.innerHeight) // landscape    	
    	sensors.iOS_accelerometer = { x: accelerometer.y, y: -accelerometer.x, z: accelerometer.z};
    else // portrait        
        sensors.iOS_accelerometer = accelerometer;
  },
  setGyroscope : function(gyrvalue) {
    var value = JSON.parse(gyrvalue);
    var gyroscope = eval(value.result);

    if(window.innerWidth > window.innerHeight) // landscape    	
    	sensors.iOS_gyroscope = { x: gyroscope.y, y: -gyroscope.x, z: gyroscope.z};
    else // portrait        
        sensors.iOS_gyroscope = gyroscope;
  },

  // iOS external sensors
  iOS_staccelerometer: {x:0.0, y:0.0, z:0.0},
  iOS_stgyroscope: {x:0.0, y:0.0, z:0.0},
  iOS_stmagnetometer: {x: 0.0, y: 0.0, z: 0.0},
  iOS_sthumidity: 0.0,
  iOS_sttempamb: 0.0,
  iOS_sttempir: 0.0,
  iOS_stpressure: 0.0,
	
  setSensorTag : function(stvalue) {
    var value = JSON.parse(stvalue);
    var results = eval(value.result);
    sensors.iOS_staccelerometer = {x: results.accx, y: results.accy, z: results.accz};
    sensors.iOS_stgyroscope = {x: results.gyrox, y: results.gyroy, z: results.gyroz};
    sensors.iOS_stmagnetometer = {x: results.magx, y: results.magy, z: results.magz};
	sensors.iOS_sthumidity = results.hum;
	sensors.iOS_sttempamb = results.tamb;
	sensors.iOS_sttempir = results.tir;
	sensors.iOS_stpressure = results.press;
  },
  
  calliOSFunction: function(functionName, args, successCallback, errorCallback) {
  var url = "ejss://";
    var callInfo = {};
    callInfo.functionname = functionName;
    if (successCallback) callInfo.success = successCallback;
    if (errorCallback) callInfo.error = errorCallback;
    if (args) callInfo.args = args;
    url += JSON.stringify(callInfo);
    window.location = url;
  },

  // SensorTags
  //

  // Acceleration force along the x,y,z axis (m/s2)  
  isSupportedSensorTag: function() {
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    
    return is_iOSwebview;
  },
  runSensorTag: function(accperiod, magperiod) {
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    if (is_iOSwebview) {
      var args = [];
      args[0] = accperiod;
      args[1] = magperiod;
      sensors.calliOSFunction("runSensorTag", args, "sensors.setSensorTag");
    }
  },
  stopSensorTag: function() {
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    if (is_iOSwebview) {
      sensors.calliOSFunction("stopSensorTag");
    }
  },
  getSensorTagAccelerometer: function() {
    return sensors.iOS_staccelerometer;
  },
  getSensorTagGyroscope: function() {
    return sensors.iOS_stgyroscope;
  },
  getSensorTagMagnetometer: function() {
    return sensors.iOS_stmagnetometer;
  },
  getSensorTagTempAmb: function() {
    return sensors.iOS_sttempamb;
  },
  getSensorTagTempIR: function() {
    return sensors.iOS_sttempir;
  },
  getSensorTagPressure: function() {
    return sensors.iOS_stpressure;
  },
  getSensorTagHumidity: function() {
    return sensors.iOS_sthumidity;
  },
  
  
  // Motion Sensors
  //

  // Acceleration force along the x,y,z axis (m/s2)  
  isAccelerometer: function() {
    var is_Androidwebview = window.android && window.android.isAccelerometer();
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    
    return is_Androidwebview || is_iOSwebview;
  },
  runAccelerometer: function() {
    var is_Androidwebview = window.android && window.android.isAccelerometer();
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    if (is_Androidwebview) {
      window.android.runAccelerometer();
    } else if (is_iOSwebview) {
      sensors.calliOSFunction("runAccelerometer", "", "sensors.setAccelerometer");
    }
  },
  stopAccelerometer: function() {
    var is_Androidwebview = window.android && window.android.isAccelerometer();
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    if (is_Androidwebview) {
      window.android.stopAccelerometer();
    } else if (is_iOSwebview) {
      sensors.calliOSFunction("stopAccelerometer");
    }
  },
  getAccelerometer: function() {
    var is_Androidwebview = window.android && window.android.isAccelerometer();
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    if (is_Androidwebview) {
        var value = eval(window.android.getAccelerometer());
	    if(window.innerWidth > window.innerHeight) // landscape
	    	return value;
	    else // portrait
	        return {x: value.y, y: -value.x, z: value.z};
    } else if (is_iOSwebview) {
      return sensors.iOS_accelerometer;
    }
    return {x:0.0, y:0.0, z:0.0};
  },

  // Rate of rotation around the x,y,z axis. (rad/s)
  isGyroscope: function() {
  	var is_Androidwebview = window.android && window.android.isGyroscope();
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    
    return is_Androidwebview || is_iOSwebview;
  },
  runGyroscope: function() {
  	var is_Androidwebview = window.android && window.android.isGyroscope();
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    if (is_Androidwebview) {
      window.android.runGyroscope();
    } else if (is_iOSwebview) {
      sensors.calliOSFunction("runGyroscope", "", "sensors.setGyroscope");
    }
  },
  stopGyroscope: function() {
  	var is_Androidwebview = window.android && window.android.isGyroscope();
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    if (is_Androidwebview) {
      window.android.stopGyroscope();
    } else if (is_iOSwebview) {
      sensors.calliOSFunction("stopGyroscope");
    }
  },
  getGyroscope: function() {
  	var is_Androidwebview = window.android && window.android.isGyroscope();
    var is_iOSwebview = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent);
    if (is_Androidwebview) {
        var value = eval(window.android.getGyroscope());
	    if(window.innerWidth > window.innerHeight) // landscape
	    	return value;
	    else // portrait
	        return {x: value.y, y: -value.x, z: value.z};
    } else if (is_iOSwebview) {
      return sensors.iOS_gyroscope;
    }
    return {x:0.0, y:0.0, z:0.0};
  },

  // Force of gravity along the x,y,z axis. (m/s2)
  isGravity: function() {
  	var is_Androidwebview = window.android && window.android.isGravity();
    return is_Androidwebview;  	
  },
  runGravity: function() {
  	var is_Androidwebview = window.android && window.android.isGravity();
  	if (is_Androidwebview) {
  		window.android.runGravity();
  	}
  },
  stopGravity: function() {
  	var is_Androidwebview = window.android && window.android.isGravity();
  	if (is_Androidwebview) {
  		window.android.stopGravity();
  	}
  },
  getGravity: function() {
  	var is_Androidwebview = window.android && window.android.isGravity();
  	if (is_Androidwebview) {
  		return window.android.getGravity();
  	}  	
    return {x:0.0, y:0.0, z:0.0};
  },

  // Acceleration force along the x,y,z axis (excluding gravity). (m/s2)
  isLinearAcceleration: function() {
  	var is_Androidwebview = window.android && window.android.isLinearAcceleration();
    return is_Androidwebview;  	
  },
  runLinearAcceleration: function() {
  	var is_Androidwebview = window.android && window.android.isLinearAcceleration();
  	if (is_Androidwebview) {
  		window.android.runLinearAcceleration();
  	}
  },
  stopLinearAcceleration: function() {
  	var is_Androidwebview = window.android && window.android.isLinearAcceleration();
  	if (is_Androidwebview) {
  		window.android.stopLinearAcceleration();
  	}
  },
  getLinearAcceleration: function() {
  	var is_Androidwebview = window.android && window.android.isLinearAcceleration();
  	if (is_Androidwebview) {
  		return window.android.getLinearAcceleration();
  	}  	
    return {x:0.0, y:0.0, z:0.0};
  },

  // Rotation vector component along the x,y,z axis (x * sin(θ/2)).
  isRotationVector: function() {
  	var is_Androidwebview = window.android && window.android.isRotationVector();
    return is_Androidwebview;  	
  },
  runRotationVector: function() {
  	var is_Androidwebview = window.android && window.android.isRotationVector();
  	if (is_Androidwebview) {
  		window.android.runRotationVector();
  	}
  },
  stopRotationVector: function() {
  	var is_Androidwebview = window.android && window.android.isRotationVector();
  	if (is_Androidwebview) {
  		window.android.stopRotationVector();
  	}
  },
  getRotationVector: function() {
  	var is_Androidwebview = window.android && window.android.isRotationVector();
  	if (is_Androidwebview) {
  		return window.android.getRotationVector();
  	}  	
    return {x:0.0, y:0.0, z:0.0};
  },

// Position Sensors
//

// Geomagnetic field strength along the x,y,z axis. (μT)
  isMagneticField: function() {
  	var is_Androidwebview = window.android && window.android.isMagneticField();
    return is_Androidwebview;  	
  },
  runMagneticField: function() {
  	var is_Androidwebview = window.android && window.android.isMagneticField();
  	if (is_Androidwebview) {
  		window.android.runMagneticField();
  	}
  },
  stopMagneticField: function() {
  	var is_Androidwebview = window.android && window.android.isMagneticField();
  	if (is_Androidwebview) {
  		window.android.stopMagneticField();
  	}
  },
  getMagneticField: function() {
   	var is_Androidwebview = window.android && window.android.isMagneticField();
  	if (is_Androidwebview) {
  		return window.android.getMagneticField();
  	}  	
    return {x:0.0, y:0.0, z:0.0};
  },
 
  
  // Enviroment Sensors
  //
  // °C Ambient air temperature.
  isAmbientTemperature: function() {
  	var is_Androidwebview = window.android && window.android.isAmbientTemperature();
    return is_Androidwebview;  	
  },
  runAmbientTemperature: function() {
  	var is_Androidwebview = window.android && window.android.isAmbientTemperature();
  	if (is_Androidwebview) {
  		window.android.runAmbientTemperature();
  	}
  },
  stopAmbientTemperature: function() {
  	var is_Androidwebview = window.android && window.android.isAmbientTemperature();
  	if (is_Androidwebview) {
  		window.android.stopAmbientTemperature();
  	}
  },
  getAmbientTemperature: function() {
   	var is_Androidwebview = window.android && window.android.isAmbientTemperature();
  	if (is_Androidwebview) {
  		return window.android.getAmbientTemperature();
  	}  	
    return 0.0;
  },
   
  // lx Illuminance
  isLight: function() {
  	var is_Androidwebview = window.android && window.android.isLight();
    return is_Androidwebview;  	
  },
  runLight: function() {
  	var is_Androidwebview = window.android && window.android.isLight();
  	if (is_Androidwebview) {
  		window.android.runLight();
  	}
  },
  stopLight: function() {
  	var is_Androidwebview = window.android && window.android.isLight();
  	if (is_Androidwebview) {
  		window.android.stopLight();
  	}
  },
  getLight: function() {
   	var is_Androidwebview = window.android && window.android.isLight();
  	if (is_Androidwebview) {
  		return window.android.getLight();
  	}  	
    return 0.0;
  },

  // hPa or mbar Ambient air pressure.
  isPressure: function() {
  	var is_Androidwebview = window.android && window.android.isPressure();
    return is_Androidwebview;  	
  },
  runPressure: function() {
  	var is_Androidwebview = window.android && window.android.isPressure();
  	if (is_Androidwebview) {
  		window.android.runPressure();
  	}
  },
  stopPressure: function() {
  	var is_Androidwebview = window.android && window.android.isPressure();
  	if (is_Androidwebview) {
  		window.android.stopPressure();
  	}
  },
  getPressure: function() {
   	var is_Androidwebview = window.android && window.android.isPressure();
  	if (is_Androidwebview) {
  		return window.android.getPressure();
  	}  	
    return 0.0;
  },

  // % Ambient relative humidity
  isHumidity: function() {
  	var is_Androidwebview = window.android && window.android.isHumidity();
    return is_Androidwebview;  	
  },
  runHumidity: function() {
  	var is_Androidwebview = window.android && window.android.isHumidity();
  	if (is_Androidwebview) {
  		window.android.runHumidity();
  	}
  },
  stopHumidity: function() {
  	var is_Androidwebview = window.android && window.android.isHumidity();
  	if (is_Androidwebview) {
  		window.android.stopHumidity();
  	}
  },
  getHumidity: function() {
   	var is_Androidwebview = window.android && window.android.isHumidity();
  	if (is_Androidwebview) {
  		return window.android.getHumidity();
  	}  	
    return 0.0;
  }

};

// add sensors if exits
if (typeof sensors != 'undefined')
	sensors =  Object.assign({}, sensors, ejsSsensors);
else
	var sensors = ejsSsensors;
function _smartDevice(ws,data){
	this.websocket = ws;
	this.loadMetadata(data);
	return this;
}
  _smartDevice.prototype = {
	metadata: "",
	apis: [],
	models: "",
	modelsTemplate: [],
	apisFn: [],
	//modelsFn:[],
	
	loadMetadata: function(data){
		this.metadata = JSON.parse(data);
	},
	
	loadModels: function(){
		this.models = this.metadata.models;
		//this.loadTemplates();
	},
	
	loadApis: function(){
		this.apis = this.metadata.apis;
	},
	
	getModelTemplate: function(name){
		if(this.models.hasOwnProperty(name)){
			var model = JSON.parse(JSON.stringify(this.models[name]));
			var name = model.id;
			var properties = model.properties;
			return model;
		}else{
			writeToScreen('<span style="color: red;">ERROR:</span> ' + name + " is not a key -> " + "Model Not found");
			return null;
		}
	},

	generateMethodCall: function(operationName){
		//by now this is only to make simple request, not ready to nested models request
		for (var i = 0; i<this.apis.length; i++){
			var api = this.apis[i];
			var operations = api.operations;
			//console.log("API: " + JSON.stringify(api));
			//console.log("First Op: " + JSON.stringify(operations[0]));
			//return operations[0];
			for (var j = 0; j<operations.length; j++){
				var op = operations[j];
				//console.log("nickNames: " + operations[0].nickname);
				//console.log("parameters type : " + parameters.type);
				if (op.nickname == operationName || operationName == undefined){
					//By now we have only one parameter, which is a JSON message
					var parameters = op.parameters[0];
					//console.log("Model to Search: " + parameters.type);
					var usingModel = this.getModelTemplate(parameters.type);
					//console.log("parameters type : " + parameters.type);
					var params = usingModel.properties;	
					var required;
					if(usingModel.hasOwnProperty("required"))	required = usingModel.required;
					/*Esta forma de rellenar el cuerpo de la llamada y los parametros no me convence
					* Seguramente se pueda utilizar stringuify para mejorarlo, de esa forma, sería un 
					* json puro rellenable, y menos problemas para las referencias anidadas
					*/
					var jsonTosend = "{";var paramToSend = [];var notFirstTime = false;
					for(var par in params){
						if (notFirstTime)	{jsonTosend += ",";}
						else 				notFirstTime = true;
						jsonTosend += "'"+ par + "' : " + par;
						paramToSend.push(par);
					}
					jsonTosend += "}";
					var body =  "{";
					if (required != undefined){
						for(var req in required){
							//Hara falta añadir una comprobacion de tipos, de esta forma garantizamos
							//que funcione correctamente todo! Todas las $ref serán entonces objectos JSON
							body += "if( "+ required[req] +" == undefined)  { \n"+ 
							"console.log('Required parameter, "+required[req]+" : must be an input'); \n" + 
							"return null;} \n";
						}
					}
					body += "this.websocket.send(JSON.stringify("+ jsonTosend +"))";
					body += "}";
					//this.apisFn[op.nickname] = new Function(paramToSend,body);
					this[op.nickname] = new Function(paramToSend,body);
				}
			}
		}
		return true;
		
	},
	
	generateModelFilling: function(modelName){ 
		for (var mod in this.models){
			if (mod == modelName || modelName == undefined){
				//console.log("model : " + mod);
				var usingModel = this.models[mod];
				var params = usingModel.properties;	
				var required;
				if(usingModel.hasOwnProperty("required"))	required = usingModel.required;
				var paramToSend = [];
				var body = "";
				body += "{\n";	//Function begins 
				body += "\t var json2send = this.getModelTemplate('"+ mod +"').properties;\n";
				for(var par in params){
					paramToSend.push(par);
					body += "\t if( "+ par +" != undefined)  { \n"; 
					body += "\t\t json2send['"+ par +"'] = "+ par +";  \n";
					body += "\t }else{\n";
					body += "\t\t delete json2send."+ par +";\n";
					body += "\t }\n";
				}
				if (required != undefined){
					for(var req in required){
						body += "\t if( "+ req +" == undefined)  { \n"+ 
							"\t\t console.log('Required parameter, "+req+" : must be an input'); \n" + 
							"\t\t return null; \n";
						//body += "\t }else{ \n";
						//body += "\t\t json2send['"+ required[req] +"'] = "+ required[req] +"  \n";
						body += "\t }\n";
					}
				}
				body += "\t return json2send;";
				body += "}";	//Function ends
				//console.log(body);
				//this.modelsFn[mod] = new Function(paramToSend,body);
				this["fill"+mod] = new Function(paramToSend,body);
			}	
			
		}
		return true;
	},
	
	bindFunctions: function(){
		// We can use this kind of code to bind the methods. Then we can put them into the 
		// EjsS code, and overwrite other vars
		//var getSensorMetadata = Function.prototype.call.bind(_smartDevice.apisFn["getSensorMetadata"],"")
	},
	
	searchMethod: function(methodName){
		for (var i = 0; i<this.apis.length; i++){
			var api = this.apis[i];
			var operations = api.operations;
			for (var j = 0; j<operations.length; j++){
				var op = operations[j];
				//console.log("nickNames: " + op.nickname);
				if (op.nickname == methodName){
					return op;
				}
			}
		}
	},
	
	isValidMessage: function(strMsg,type){
		// This method will obtain the info inside a JSON msg against the metadata info
		// Then identify the method
		//First of all we need to know if is the response to some-call
		//Responses contain the name of the caller.
		//Actuator response contains the updated value
		//Sensor  response contain the value requested
		var jsonMsg = strMsg;// = JSON.parse(strMsg);				
		if(jsonMsg.hasOwnProperty("method")){
			var metadataMethod = this.searchMethod(jsonMsg.method);
		}else if(type!=undefined){
			var metadataMethod = this.searchMethod(type);
		}else{
			return false;
		}
		var responseModel = this.getModelTemplate(metadataMethod.type);
		var validMsg = false;
		var req ="";
		
		for (var i = 0; i<responseModel.required.length;i++){
			req = responseModel.required[i];
			//console.log("Required: " + JSON.stringify(responseModel.required[i]));
			if (!jsonMsg.hasOwnProperty(req)){
				validMsg = false;	
				console.log("Error processinMessage : " + strMsg + 
				" -> Property : " + req + " is a required field");
				return validMsg;
			}else{
				validMsg = true;
			}
		}
		return validMsg;
	},
	
	extract: function(names,strMsg){
		//console.log("is a valid Msg : " + this.isValidMessage(strMsg));
		if (this.isValidMessage(strMsg)){
			var dataExtracted = {};
			for (var i = 0; i<names.length; i++){
				name = names[i];
				//console.log("name : " + name + " values: " + strMsg[name]);
				dataExtracted[name] = strMsg[name];
			}
			return dataExtracted;
		}
		return undefined;
	}
	
};
function wrapper(ws,data){
	if(ws.hasOwnProperty("ip")){
		wsUri = "ws://"+ ws.ip + ":" + ws.port + "/";
		this.websocket = new WebSocket(wsUri);
	}else{
		this.websocket = ws;
	}
	this.smartdevice = new _smartDevice(this.websocket,data);
	this.smartdevice.loadApis();
	this.smartdevice.loadModels();
	this.smartdevice.generateMethodCall();
	this.smartdevice.generateModelFilling();
}

wrapper.prototype = {
	methods: {
  	connect: 	'connect', 		// Open a new connection
  	disconnect: 'disconnect', 	// Close the connection
  	get: 		'get', 			// Get some variables from the server
  	set: 		'set', 			// Set some variables in the server
	callAction: 'callAction',	// Calls a method od the model	
	extract:	"extract",		// Get useful info from a message
    eval: 		'eval', 		// Send code to evaluate
    open: 		'open', 		
    step: 		'step', 		
  },
	
  post: function(method, params) {
	//calling some method with no prebuild method, just send message
	if(method == undefined && !params.hasOwnProperty("method"))	return;	
	else if (method != undefined && !params.hasOwnProperty("method"))	params["method"] = method;
	//console.log("Wrapper calls post, method: " + method + " with: " + params);
	websocket.send(params);
  },
  
  sync: function(callback) {
	  
	  if(callback != undefined) {
		callback(response);
	  }
  },

  connect: function(callback) {
	this.smartdevice = new _smartDevice(fullData);
	this.smartdevice.loadApis();
	this.smartdevice.loadModels();
	this.smartdevice.generateMethodCall();
  },

  disconnect: function(callback) {

  },

  get: function(vars, eachStep, callback) {
	//get(var) is a getSensorData method
	//console.log("Wrapper calls get " + vars);
	if(eachStep === undefined || eachStep == false)	this.smartdevice.getSensorData("getSensorData",vars);
	else{
		//console.log("ConfigItem: " + this.smartdevice.fillConfigurationItem("updateFrequency",2.0));
		this.smartdevice.getSensorData("getSensorData",vars, [this.smartdevice.fillConfigurationItem("updateFrequency",2.0)]);
	}
  },
  
  set: function(vars, values, auth, callback) {
	//set(var, value) is a senActuatorData method
	//console.log("Wrapper calls set " + vars + " to: " + values);
	if (vars.length == 1)	this.smartdevice.sendActuatorData(auth, "sendActuatorData", "modelVars", [vars], [values]);
	else 					this.smartdevice.sendActuatorData(auth, "sendActuatorData", "modelVars", vars, values);
  },

  callAction: function(nickname, params, callback){	  
	//console.log("Wrapper calling action " + nickname + " with params: " + params);
	this.smartdevice.callAction("","callAction","modelMethods",nickname, params)
  },
  
  extract: function(strMsg, callback){
	//
	var jsonMsg = JSON.parse(strMsg);
	var vars = {};
	if(jsonMsg.hasOwnProperty("method")){
		var method = jsonMsg.method;
		var extracted = {};
		if(method == "getSensorData"){	
			vars = ["sensorId", "responseData"];
		}else if (method == "sendActuatorData"){
			vars = ["lastMeasured", "accessrole", "payload", "observerMode"];	
		}else if (method == "getSensorMetadata"){
			vars = ["sensors"];
		}else if (method == "getActuatorMetadata"){
			vars = ["actuators"];
		}else if (method == "getActionsMetadata"){
			vars = ["sensors"];
		}else if (method == "callAction"){
			vars = ["method","callerName","nickName","params"];
		}else if (method == "actionDataResponse"){
			vars = ["method","nickName"];
		}else{
			console.log("The method : " + jsonMsg.method + " is not handled in this wrapper. \n" +
			"Will be ignored.");
			return undefined;
		}
	}
	extracted = this.smartdevice.extract(vars, jsonMsg);
	//console.log("Wrapper calls extract " + vars + " from: \n" + JSON.stringify(jsonMsg) + " With value: " + JSON.stringify(extracted));
	return this.toEjssReadable(method,extracted);
  },
  
  eval: function(code, callback) {

  },

  open: function(model, callback) {

  },

  step: function(vars, callback) {

  },

  toEjssReadable: function(method,dataExtracted){
	if(method == "getSensorData"){	
		var nameValue = {};
		var valueNames = dataExtracted.responseData.valueNames;
		var dataValues = dataExtracted.responseData.data;
		for(var i = 0; i<valueNames.length;i++){
			nameValue[valueNames[i]] = dataValues[i];
		}
		//console.log("Data extracted and readable: " + JSON.stringify(nameValue));
		return nameValue;
	}else if (method == "sendActuatorData"){
		//It is just a confirmation, with additional data
		return true;
	}else if (method == "getSensorMetadata"){
		return dataExtracted;
	}else if (method == "getActuatorMetadata"){
		return dataExtracted;
	}else if (method == "getActionsMetadata"){
		return dataExtracted;
	}else if (method == "callAction"){
		return dataExtracted;
	}else if (method == "actionDataResponse"){
		return dataExtracted;
	}else{
		console.log("The method : " + method + " is nor handled in this wrapper. \n" +
		"Will be ignored.");
		return undefined;
	}
  }
	
}
/*
* Copyright (C) 2014 Francisco Esquembre and Felix J. Garcia and Félix J. García 
* This code is part of the Easy Javascript Simulations authoring and simulation tool
*/

/**
 * Framework for view and control elements
 * @module core
 */

var EJSS_CORE = EJSS_CORE || {};

/**
 * A view with properties and actions
 * @class View
 * @constructor
 */

/**
 * View: A base 'class' for a view that 'talks' (typically to a model)
 * via elements' properties and actions.
 * Make you classes 'inherit' from this one by
 * - declaring:
 *   var _view = EJSS_CORE.createView("_view");
 * - redeclaring (extending) the _reset() function, and
 * - returning _view.
 * @method createView
 */
EJSS_CORE.createView = function(container) {

	var self = {};
	var mTopLevelElement;  // The top level element
	var mReporter;					// function to call whenever there are user interactions
	var mVariablesList = {};		// Object with all variables registered
	var mActionsList = {};			// Object with all actions registered
	var mUnnamedUpdateList = [];	// Listeners without a variable
	var mUpdateList = [];			// Array with all variables with listeners
	var mResetableList = [];		// Array of all control elements that need a call to reset()
	var mInitializableList = [];	// Array of all control elements that need a call to initialize()
	var mRenderList = [];			// Array of all control elements that need a call to render()
	var mTouchList = [];			// Array of all control elements that accept a call to touch()
	var mCollectersList = [];		// Array of all control elements that need a call to dataCollected() after data collection
	var mInteractionList = [];		// Pending interaction commands
	var mEnableEPubList = [];       // Array of all control elements that need a call to enableEPub() when running on an ePub
	var mAdjustPositionList = [];		// Array of all control elements that accept a call to adjustPosition() 
    var mFontResizeListenerList = [];	// Array of listener for font resize events
    var mResizeListenerList = [];	    // Array of listener for view resize
    var mOrientationChangeListenerList = [];	    // Array of listener for view orientation change
	var mReportNeeded = true;
	var mInitValueList = [];
	var mResourcePath;
	var mLibraryPath;

	var mRegInteractions = [];			// Register of action interactions
	var mIsRegInteractions = false;	
	var mIsShortRegInteractions;
	var mWSRegInteractions;
	var mCBRegIngeractions;

    var mDescriptionPages = []; // Place holder for user defined description pages

	var mOnBlurList = [];	// Listeners for window.onblur
	var mOnFocusList = [];	// Listeners for window.onfocus
	var mDummyContainer;

	// ------------------------------------------------------------------------
	// This part is used by the view subclass at creation time
	// ------------------------------------------------------------------------

	self._getTopLevelElement = function() { return mTopLevelElement; };
	
	/**
	 * Clears all elements, links to actual model variable setters/getters, and actions.
	 * Only leaves the connection to the reporter (the model) untouched
	 * @method _clearAll
	 */
	self._clearAll = function() {
    mTopLevelElement.innerHTML = "";   // Remove all elements into the container
		//self[container].innerHTML = "";		// Remove all elements into the container
		mVariablesList = {};				// Object with all variables registered
		mActionsList = {};					// Object with all actions registered
		mUnnamedUpdateList = [];			// Listeners without a variable
		mUpdateList = [];					// Array with all variables with listeners
		mInitValueList = [];				// Array with all variables with init values
		mResetableList = [];				// Array of all control elements that need a call to reset()
		mInitializableList = [];			// Array of all control elements that need a call to initialize()
		mRenderList = [];					// Array of all control elements that need a call to render()
		mTouchList = [];					// Array of all control elements that accept a call to touch()
	    mCollectersList = [];		        // Array of all control elements that need a call to dataCollected() after data collection
	    mEnableEPubList = [];			    // Array of all control elements that need a call to enableEPub() when running on an ePub
	    mAdjustPositionList = [];		    // Array of all control elements that accept a call to adjustPosition() 
        mFontResizeListenerList = [];	    // Array of listener for font resize events
        mResizeListenerList = [];	        // Array of listener for view resize events
        mOrientationChangeListenerList = [];	// Array of listener for view orientation change
		mInteractionList = [];				// Pending interaction commands
		mReportNeeded = true;
	};

	self._setResourcePath = function(path) {
		mResourcePath = path;
	};


    function startsWith(fullStr, str) {
       return (fullStr.match("^"+str)==str);
    }

    /** 
     * Looks for the file in different places.
     * The user may define an array of base64 converted files for direct use 
    */
	self._getResourcePath = function(filename, forIbooks) {
	    // no filename
	    if (filename==null || filename.length<=0) return filename;
	    if (startsWith(filename,"local:")) return filename; 
	    if (startsWith(filename,"data:")) return filename; 
	    if (startsWith(filename,"blob:")) return filename; 
	    if (startsWith(filename,"http:") || startsWith(filename,"https:") || startsWith(filename,"ws:")) return filename; 
	    
        if (typeof __base64Images !== 'undefined') {
          var base64 = __base64Images[filename];
          if (base64) return base64;
        }
	    
	    if ((mLibraryPath) && (!startsWith(filename,mLibraryPath)) && (filename.charAt(0) == '/')) {
	    	// with libraryPath
			//console.log("Path is "+ mLibraryPath + "images" + filename);
			filename = mLibraryPath + "images" + filename;
		} else if ((mResourcePath) && (!startsWith(filename,mResourcePath)) && (filename.charAt(0) != '/')) { 
			// with resourcePath
			//console.log("Path2 is "+ mResourcePath + filename);
		  	filename = mResourcePath + filename;
		}
		
		// change for ibooks 
		if (forIbooks && startsWith(window.location.protocol,"ibooks")) {
			// get path
			var path = window.location.pathname;
			path = path.substring(0,path.lastIndexOf('/'));
			//if (path.charAt(0)=='/') path = path.substring(1);
			if (startsWith(filename,"./")) filename = filename.substring(2);
			filename = window.location.protocol + "://" + window.location.host + "/" + path + "/" + filename;
		}
		
		//console.log("Path3 is "+ filename);
		return filename;
	};

	self._setLibraryPath = function(path) {
		mLibraryPath = path;
	};
	
	
	self._showDocument = function (url) {
        window.open(self._getResourcePath(url));
    };
    
	self._addDescriptionPage = function (name,url) {
		mDescriptionPages[name] = url;
    };

	self._openDescriptionPage = function (name) {
        var url = mDescriptionPages[name];
        if (url) window.open(self._getResourcePath(url));
        else console.log("Desription page not found: "+name);
    };

	/**
	 * Adds a variable to the list of variables which
	 * - their values will be requested (and passed to listeners) when _update() is called.
	 * - can be linked to elements' properties and reported as part of interactions
	 * This is typically called by the view itself to define its input/output API
	 * @method _registerVariable
	 * @param variable the name of the variable
	 * @param initiaValue an optional initial value
	 */
	self._registerVariable = function(variable, initialValue) {
		mVariablesList[variable] = {
			getter : null,
			setter : null,
			listeners : [],
			collectors : [],
			value : initialValue
		};
	};

	/**
	 * Adds a set of variables to the list of variables which
	 * - their values will be requested (and passed to listeners) when _update() is called.
	 * - can be linked to elements' properties and reported as part of interactions
	 * This is typically called by the view itself to define its input/output API
	 * @method _registerVariables
	 * @param variables the names of the variables
	 */
	self._registerVariables = function(variables) {
		for (var i = 0; i < variables.length; i++)
			self._registerVariable(variables[i]);
	};

	/**
	 * Adds an action to the list of possible actions that can be triggered by
	 * user interaction.
	 * This is typically called by the view itself to define its input/output API
	 * @method _registerAction
	 * @param action the name of the action
	 */
	self._registerAction = function(action) {
		mActionsList[action] = {
			action : null
		};
	};

	/**
	 * Adds a set of actions to the list of possible actions that can be triggered by
	 * user interaction.
	 * This is typically called by the view itself to define its input/output API
	 * @method _registerActions
	 * @param action the names of the actions
	 */
	self._registerActions = function(actions) {
		for (var i = 0; i < actions.length; i++)
			self._registerAction(actions[i]);
	};

	/**
	 * Creates and adds a new control element to the view.
	 * If the name is not null, the element is added as a new property to the view.
	 * Example:
	 *   view._addElement (EJSS_DRAWING2D.createDrawingPanel,"drawingPanel",parent);
	 * You can access the new element by view.drawingPanel.
	 * @method _addElement
	 * @param constructor the function used to create the control element. Will be called
	 * as in constructor(name)
	 * @param name the optional name of the element, though some constructors require it
	 * @param parent an optional parent of the element, though you can use setPropertyt("parent",parent) later
	 * @param args optional args of the constructor
	 * @return the newly created control element
	 */
	self._addElement = function(constructor, name, parent, args) {
		var controlElement = EJSS_CORE.promoteToControlElement(constructor(name,args), self, name);
		if (controlElement.render)
			mRenderList.push(controlElement);
		if (controlElement.touch)
			mTouchList.push(controlElement);
		if (controlElement.dataCollected)
			mCollectersList.push(controlElement);
		if (controlElement.reset)
			mResetableList.push(controlElement);
		if (controlElement.initialize)
			mInitializableList.push(controlElement);
		if (controlElement.enableEPub)
			mEnableEPubList.push(controlElement);
		if (controlElement.adjustPosition)
			mAdjustPositionList.push(controlElement);
		if (name)
			self[name] = controlElement;
		if (parent)
			controlElement.setProperty("Parent", parent);
		return controlElement;
	};

	/**
	 * Returns the given initial value (if any) of a registered variable
	 * @method _getInitialValue
	 * @param variable the name of the variable
	 * @return the initial value or "undefined"
	 */
	self._getInitialValue = function(variable) {
		var variableDefinition = mVariablesList[variable];
		if (variableDefinition !== undefined)
			return variableDefinition.value;
		return undefined;
	};

	/**
	 * Starts up a view, even in the absence of a model
	 * @method _startUp
	 */
	self._startUp = function() {
		self._reset();
		self._initValues();
		self._initialize();
		self._update();
		self._render();
	};

	/**
	 * Sets particular behavior of the whole HTML view
	 * @method _startUp
	 */
	self._setRootProperty = function(model,property, value) {
      switch (property) {
		case "RunAlways" : model.setRunAlways(value); break;
		case "OnBlur"  : self._addOnBlurAction(value); break;
		case "OnFocus" : self._addOnFocusAction(value); break;
		case "InnerHTML" : self._setInnerHTML(value); break;
		case "OnResize" : self._addResizeListener(value); break;
		case "OnOrientationChange" : self._addOrientationChangeListener(value); break;
		default : 
			console.log("WARNING: View property not registered : " + property);
		break;
	  }
	};
	
	// ------------------------------------------------------------------------
	// This part is used by the model at creation time
	// ------------------------------------------------------------------------

	/**
	 * Sets the function to call whenever there are interactions
	 * @method _setReportInteractionMethod
	 * @param reporter the function that will be called when interactions must be reported
	 */
	self._setReportInteractionMethod = function(reporter) {
		mReporter = reporter;
	};

	/**
	 * Links a view variable to accessors that allow to read/write its value from/to the model
	 * The view will call:
	 * - getter() to read the value of the variable from the model
	 * - setter(value) to set the value of the variable to a new value
	 * @method _linkVariable
	 * @param variable the name of the view variable (previously registered by the view)
	 * @param getter the function that allows to read the value from the model
	 * @param setter the function that allows to set the value to the model
	 * @param initiaValue an optional initial value
	 * @return the variable definition object
	 */
	self._linkVariable = function(variable, getter, setter, initialValue) {
		var variableDefinition = mVariablesList[variable];
		if (!variableDefinition)
			console.log("WARNING: view._setAccessors() - Variable not found : " + variable);
		else {
			variableDefinition.getter = getter;
			variableDefinition.setter = setter;
			variableDefinition.value = initialValue;
			// Manage the update list
			var index = mUpdateList.indexOf(variableDefinition);
			if (variableDefinition.getter) {
				if ((variableDefinition.listeners.length > 0) && (index < 0))
					mUpdateList.push(variableDefinition);
			} else {// Has no getter, remove it from the update list, if there
				if (index >= 0)
					mUpdateList.splice(index, 1);
			}
			// Manage the init values list
			index = mInitValueList.indexOf(variableDefinition);
			if (variableDefinition.value) {
				if ((variableDefinition.listeners.length > 0) && (index < 0))
					mInitValueList.push(variableDefinition);
			} else {// Has no init value, remove it from the init values list, if there
				if (index >= 0)
					mInitValueList.splice(index, 1);
			}
		}
		return variableDefinition;
	};

	/**
	 * Provides a function to be called when the view invokes an action.
	 * The view will call whatToDo(data) where the optional data is provided
	 * by the element that invokes the action.
	 * @method _setAction
	 * @param action the name of the view action (previously registered by the view)
	 * @param whatToDo the model function to be invoked
	 * @return the action definition object
	 */
	self._setAction = function(action, whatToDo) {
		// console.log("Setting action "+action+ "  to "+whatToDo);
		var actionDefinition = mActionsList[action];
		if (!actionDefinition)
			console.log("WARNING: view._setAction() - Action not found : " + action);
		else
			actionDefinition.action = whatToDo;
		return actionDefinition;
	};

	// ------------------------------------------------------------------------
	// This part is used automatically by the model at run time
	// ------------------------------------------------------------------------

	/**
	 * Whether changes should be reported
	 * @method _setReportNeeded
	 * @param needed true if it is needed, false otherwise
	 */
	self._setReportNeeded = function(needed) {
		mReportNeeded = needed;
	};

	/**
	 * Asks all (resetable) elements to reset themselves
	 * @method _reset
	 */
	self._reset = function() {
		for (var i = 0, n = mResetableList.length; i < n; i++)
			mResetableList[i].reset();
	};

	/**
	 * Initialize elements to init values
	 * @method _initValues
	 */
	self._initValues = function() {
		for (var i = 0, n = mInitValueList.length; i < n; i++) {
			var variableDefinition = mInitValueList[i];
			var value = variableDefinition.value;
			if (value) {
				var listeners = variableDefinition.listeners;
				for (var j = 0, m = listeners.length; j < m; j++)
					listeners[j](value);
			}
		}
	};

	/**
	 * Asks all (initializable) elements to initialize themselves
	 * @method _initialize
	 */
	self._initialize = function() {
		for (var i = 0, n = mInitializableList.length; i < n; i++)
			mInitializableList[i].initialize();
	};

	/**
	 * Updates the view by requesting from the model all variables registered
	 * with the _linkVariable() function or properties of values directly
	 * linked to model getters (using _view.element.linkProperty()).
	 * @method _update
	 */
	self._update = function() {
		var i, n;
		// console.log ("Calling view update for "+mUpdateList.length+ " variables and "+ mUnnamedUpdateList.length+" unknwons");
		for (i = 0, n = mUpdateList.length; i < n; i++) {
			var variableDefinition = mUpdateList[i];
			//    console.log ("Checking variable "+variableDefinition.name);
			var listeners = variableDefinition.listeners;
			var value = variableDefinition.getter();
			for (var j = 0, m = listeners.length; j < m; j++)
				listeners[j](value);
		}
		for (i = 0, n = mUnnamedUpdateList.length; i < n; i++) {
			var unnamedDefinition = mUnnamedUpdateList[i];
			//    console.log ("Checking unnamed "+unnamedDefinition.getter);
			unnamedDefinition.propertySetter(unnamedDefinition.getter());
		}
	};

	/**
	 * Same as _update BUT passing values only to elements
	 * which collect data (i.e. implement a dataCollected() function)
	 * @method _collectData
	 */
	self._collectData = function() {
		var i, n;
		for (i = 0, n = mUpdateList.length; i < n; i++) {
			var variableDefinition = mUpdateList[i];
			//    console.log ("Checking variable "+variableDefinition.name);
			var listeners = variableDefinition.collectors;
			var value = variableDefinition.getter();
			for (var j = 0, m = listeners.length; j < m; j++)
				listeners[j](value);
		}
		for (i = 0, n = mUnnamedUpdateList.length; i < n; i++) {
			var unnamedDefinition = mUnnamedUpdateList[i];
			//    console.log ("Checking unnamed "+unnamedDefinition.getter);
			if (unnamedDefinition.isCollector)
				unnamedDefinition.propertySetter(unnamedDefinition.getter());
		}
		for (i = 0, n = mCollectersList.length; i < n; i++)
			mCollectersList[i].dataCollected();
	};

	/**
	 * Asks all (renderer) elements to render themselves
	 * @method _render
	 */
	self._render = function() {
		for (var i = 0, n = mRenderList.length; i < n; i++)
			mRenderList[i].render();
	};

	/**
	 * Asks all (renderer) elements to render themselves
	 * @method _render
	 */
	self._touch = function() {
		for (var i = 0, n = mTouchList.length; i < n; i++)
			mTouchList[i].touch();
	};

	/**
	 * enable ePub (for internal purposes)
	 * @method _render
	 */
	self._enableEPub = function() {
	  //alert ("enabling ePubs : "+_isEPub);
      if (typeof _isEPub !== 'undefined' && _isEPub)
	  	for (var i = 0, n = mEnableEPubList.length; i < n; i++)
			mEnableEPubList[i].enableEPub();
	};

	/***
	 * Add a function to be called when the font size changes
	 * @method _addFontResizeListener
	 * @param listener a function to be called with possible the following parameters
	 * listener(iBase,iSize,iDelta);
	 * where:
	 *  iBase the base font size in pixels 
	 *  iSize the current font size in pixels 
	 *  iDelta the change in pixels from the last size 
	 */
    self._addFontResizeListener = function(listener) {
      mFontResizeListenerList.push(listener);
    };

	/**
	 * To be called when the font resized
	 * @method _fontResized
	 * @param iBase the base font size in pixels 
	 * @param iSize the current font size in pixels 
	 * @param iDelta the change in pixels from the last size 
	 */
	self._fontResized = function(iBase,iSize,iDelta) {
  	   for (var i = 0, n = mAdjustPositionList.length; i < n; i++)
		 mAdjustPositionList[i].adjustPosition();
	  if (iBase) { // if iBase is undefined, this may have been caused by a resize event
	    for (var j = 0, m = mFontResizeListenerList.length; j < m; j++) 
		  mFontResizeListenerList[j](iBase,iSize,iDelta);
	  }
	};

	/***
	 * Add a function to be called when the view size changes
	 * @method _addResizeListener
	 * @param listener a function to be called with possible the following parameters
	 * listener(data);
	 * where:
	 *  data.width is the new inner width 
	 *  data.height is the new inner height 
	 */
    self._addResizeListener = function(listener) {
      mResizeListenerList.push(listener);
    };
    
	/**
	 * To be called when the view resized
	 * @method _fontResized
	 */
	self._resized = function() {
	   var data = { width : window.innerWidth, height : window.innerHeight };
  	   for (var i = 0, n = mAdjustPositionList.length; i < n; i++)
		 mAdjustPositionList[i].adjustPosition();
	   for (var j = 0, m = mResizeListenerList.length; j < m; j++) 
		  mResizeListenerList[j](data);
	};

	/***
	 * Add a function to be called when the view orientation changes
	 * @method _addOrientationChangeListener
	 * @param listener a function to be called with possible the following parameters
	 * listener(width,height);
	 * where:
	 *  width the new inner width 
	 *  height the new inner height 
	 */
    self._addOrientationChangeListener = function(listener) {
      mOrientationChangeListenerList.push(listener);
    };
    
	/**
	 * To be called when the view resized
	 * @method _fontResized
	 * @param width the new inner width 
	 * @param height the new inner height 
	 */
	self._orientationChanged = function(width,height) {
	   var data = { width : window.innerWidth, height : window.innerHeight };
  	   for (var i = 0, n = mAdjustPositionList.length; i < n; i++)
		 mAdjustPositionList[i].adjustPosition();
	   for (var j = 0, m = mOrientationChangeListenerList.length; j < m; j++) 
		  mOrientationChangeListenerList[j](data);
	};

	/**
	 * Process the accumulated interactions
	 * @method _readInteractions
	 * @return true if there was any interaction to process
	 */
	self._readInteractions = function(runCount, runTime, isPlaying) {
		// read user interactions
		if (mInteractionList.length <= 0)
			return false;

		// note that an interaction can create a new interaction, so we use directly mInteractionList.length
		for (var i = 0; i < mInteractionList.length; i++) { // order of the interactions is important
			var interaction = mInteractionList[i];

   			if (self.isRegInteractions()) { // before updating model vars
   				var inter = interaction["interaction"];
   				inter["data"] = interaction.data;
	   			inter["timeStamp"] = Date.now();   					
   				if(!self.isShortRegInteractions()) {
	   				inter["runCount"] = runCount;
	   				inter["runTime"] = runTime;   				
	   				inter["isPlaying"] = isPlaying;
   				}
   				self.pushRegInteractions(inter, true);
   			}
   			if (interaction.data && interaction.data.info) interaction.what(interaction.data.info,interaction.data); 
   			else interaction.what(interaction.data);

   			if (self.isRegInteractions()) { // after updating model vars
   				self.pushRegInteractions(inter, false);
   			}
		}
		mInteractionList = [];
		return true;
	};

	/**
	 * Return the number of accumulated interactions
	 * @method _interactionsNumber
	 * @return length
	 */
	self._interactionsNumber = function() {
		return mInteractionList.length;
	};

	/***
	 * Add a function to be called when the window in which the model runs looses focus
	 * @method _addOnBlurAction
	 * @param listener the function to be called 
	 */
    self._addOnBlurAction = function(listener) {
    	mOnBlurList.push(listener);
    };

    /***
     * Run OnBlur Actions
     * @method _onBlur
     */
    self._onBlur = function() {
    	for (var j = 0, m = mOnBlurList.length; j < m; j++) mOnBlurList[j]();
    };

    /***
	 * Add a function to be called when the window in which the model runs regains focus
	 * @method _addOnFocusAction
	 * @param listener the function to be called 
	 */
    self._addOnFocusAction = function(listener) {
    	mOnFocusList.push(listener);
    };
    
    /***
     * Run OnFocus Actions
     * @method _onFocus
     */
    self._onFocus = function() {
    	for (var j = 0, m = mOnFocusList.length; j < m; j++) mOnFocusList[j]();
    };

    /***
     * Run OnFocus Actions
     * @method _setInnerHTML
     */
    self._setInnerHTML = function(htmlCode) {
        if (typeof mDummyContainer === "undefined") {
    		mDummyContainer = document.createElement('div');
    		document.body.appendChild(mDummyContainer);
    	} 
    	mDummyContainer.innerHTML = htmlCode;
    };
    
	// ------------------------------------------------------------------------
	// This part is used automatically by ControlElements at run time
	// ------------------------------------------------------------------------

	/**
	 * Reports interactions (typically to a model)
	 * @method _reportInteraction
	 */
	self._reportInteraction = function() {
		if (mReporter && mReportNeeded)
			mReporter();
	};

	/**
	 * Registers a listener for a given variable. Whenever the variable changes,
	 * the element's setter function will be invoked with the value of the function
	 * @method _addListener
	 * @param variable the name of the view variable (previously registered by the view)
	 * @param propertySetter the function to invoke to set the value to the element's property
	 * @param isCollector whether the element registering the listener is a data collector
	 * @returns the variable definition object if the variable is registered, null otherwise
	 */
	self._addListener = function(variable, propertySetter, isCollector) {
		var variableDefinition = mVariablesList[variable];
		if (!variableDefinition)
			console.log("WARNING: view._addListener() - Variable not found : " + variable);
		else {
			if (variableDefinition.listeners.indexOf(propertySetter) < 0) {// avoid repetitions
				variableDefinition.listeners.push(propertySetter);
				if (isCollector)
					variableDefinition.collectors.push(propertySetter);
			}
			if (variableDefinition.getter) {// Add it to the update list
				if (mUpdateList.indexOf(variableDefinition) < 0)
					mUpdateList.push(variableDefinition);
			}
			if (variableDefinition.value) {// Add it to the init values list
				if (mInitValueList.indexOf(variableDefinition) < 0)
					mInitValueList.push(variableDefinition);
			}
		}
		return variableDefinition;
	};

	/**
	 * Registers a listener for a no particular variable. At each update,
	 * the element's setter function will be invoked with the value of the function
	 * @method _addUnkownListener
	 * @param variable the name of the view variable (previously registered by the view)
	 * @param propertySetter the function to invoke to set the value to the element's property
	 * @param isCollector whether the element registering the listener is a data collector
	 * @returns true if the listener existed already for this property, false otherwise
	 */
	self._addUnkownListener = function(getter, propertySetter, isCollector) {
		var found = false;
		for (var i = 0, n = mUnnamedUpdateList.length; i < n; i++) {
			var unnamedDefinition = mUnnamedUpdateList[i];
			if (unnamedDefinition.propertySetter === propertySetter) {
				unnamedDefinition.getter = getter;
				found = true;
				break;
			}
		}
		if (!found) {
			mUnnamedUpdateList.push({
				getter : getter,
				propertySetter : propertySetter,
				isCollector : isCollector
			});
		}
		return found;
	};

	/**
	 * Returns a previously registered action.
	 * @method _getAction
	 * @param name the name of the view variable (previously registered by the view)
	 * @returns the setter of the model variable if the variable is registered and has one, null otherwise
	 */
	self._getAction = function(action) {
		var actionDefinition = mActionsList[action];
		if (!actionDefinition)
			console.log("WARNING: view._getAction() - Action not found : " + action);
		return actionDefinition;
	};

	/**
	 * Adds an interaction to the list of pending interactions
	 * @method _addInteraction
	 * @param action the function to invoke
	 * @param data optional data for the action
	 */
	self._addInteraction = function(action, data, interaction) {		
	    if (!mReporter) { // The view is alone (there is no model)
	    	action(data); 
	    	return; 
	    } 
		if (mReportNeeded)
			mInteractionList.push({
				what : action,
				data : data,
				interaction: interaction
			});
	};

	//---------------------------------
	// utils
	//---------------------------------

	/**
	 * Formats a number
	 * @method _format
	 * @param value the value to format
	 * @param format The format to use
	 */
	self._format = function(value, format) {
		var index = format.indexOf('.');
		var digits = 0;
		if (index >= 0)
			digits = Number(format.length - index - 1);
		return parseFloat(value).toFixed(digits);
	};
	
	/**
	 * Print message
	 */
	self.print = function(str) {
		// find textArea
		var ta = document.getElementsByTagName("textarea");
		if(ta.length) 
			ta[0].innerHTML = str;	
		else 
			console.log(str);			
	};
	
    /**
     * Sets the visibility of the whole view
     */
  	self._setVisible = function(visibility) { 
      if (visibility) mTopLevelElement.style.display = "inherit";
      else mTopLevelElement.style.display = "none";
//  		if (visibility) document.getElementById(container).style.display = "inherit";
//  		else document.getElementById(container).style.display = "none";
  	}	
	
    /**
     * Switch the visibility of the whole view
     */
  	self._switchVisibility = function() {
      self._setVisible (mTopLevelElement.style.display == "none"); 
//  		self._setVisible (document.getElementById(container).style.display == "none"); 
  	}	
	
	/**
	 * Serialize
	 */
	self.serialize = function() {	
		var encoded = {};
		for(key in self) {
			if(self[key].serialize) { // an element
				encoded[key] = self[key].serialize();
			}
		}
		return encoded;		
	}	
	
	/**
	 * Unserialize
	 */	
	self.unserialize = function(decoded) {		
		for(key in decoded) {
			if(self[key] && self[key].unserialize) { // element in view
				self[key].unserialize(decoded[key]);
			} else {
				console.log("Impossible unserialize: " + key);
			}
		}
	}

	/**
	 * Register interactions 
	 */
	self.registerInteractions = function(websocket, callback) {
		mRegInteractions = [];
		mWSRegInteractions = websocket;
		mCBRegIngeractions = callback;
		mIsRegInteractions = true;
		mIsShortRegInteractions = false;
	}

	/**
	 * Unregister action interactions
	 */
	self.unregisterInteractions = function() {
		mIsRegInteractions = false;	
	}

	/**
	 * Get action interactions 
	 */
	self.getRegInteractions = function(empty) {
		var retRegInteractions = mRegInteractions.slice();
		if (empty) mRegInteractions = [];
		return retRegInteractions;
	}

	self.clearRegInteractions = function() {
		mRegInteractions = [];
	}
	
	self.isRegInteractions = function() {
		return mIsRegInteractions;
	}	

	self.setShortRegInteractions = function(shortreg) {
		mIsShortRegInteractions = shortreg;		
	}

	self.isShortRegInteractions = function() {
		return mIsShortRegInteractions;		
	}
	
	self.pushRegInteractions = function(action, before) {
		if (before) {
			if(mWSRegInteractions) 
				mWSRegInteractions.send(JSON.stringify(action)); 
			else if (mCBRegIngeractions)
				mCBRegIngeractions(action, before);							
			else
				mRegInteractions.push(action);			
		} else {
			if (mCBRegIngeractions)
				mCBRegIngeractions(action, before);							
		}
	}
		
	//---------------------------------
	// final initialization
	//---------------------------------

	if (typeof container == "string") {
	  mTopLevelElement = self[container] = document.getElementById(container);
	}
	else mTopLevelElement = container;

	return self;
};

//------------------------------------------------------------------------
//ControlElement: A base class to change elements via properties
//------------------------------------------------------------------------

/**
 * promoteToControlElement: This function enriches any object with property setters and getters
 * that allow it to set its properties/actions via setProperty/setAction methods and also to 'talk' to
 * a View using the linkVariable() method.
 * The object should implement a function called registerProperties(controller) that can
 * call registerProperty() and registerAction() and can then use
 * - controller.invokeAction() and
 * - controller.propertiesChanged()
 * whenever appropriated after user interaction.
 * @method promoteToControlElement
 * @param mElement the object to add properties to
 * @param mView the view with which to communicate
 * @param mName an optional name for the element
 */
EJSS_CORE.promoteToControlElement = function(mElement, mView, mName) {
	// --- Configuration variables
	mName = mName || 'unnamed';
	var mDataObject = null;
	var mProperties = {};
	var mActions = {};

	// ------------------------------------------------------------------------
	// Common properties
	// ------------------------------------------------------------------------

	mElement.getView = function() { return mView; }
	
	/**
	 * Gives a name to the element.
	 * Naming an element is optional, but the element may use its name to identify itself.
	 * @method setName
	 * @param name String
	 */
	mElement.setName = function(name) {
		mName = name;
	};

	/**
	 * Return the name of the element
	 * @method getName
	 */
	mElement.getName = function() {
		return mName;
	};

	mElement.getResourcePath = function(filename) {
		return mView._getResourcePath(filename);
	};

	/**
	 * A place holder for data objects
	 */
	mElement.setDataObject = function(data) {
	  mDataObject = data;
	};
	
  mElement.getDataObject = function(data) {
    return mDataObject;
  };

  // ------------------------------------------------------------------------
	// This part is used by the element at definition time
	// ------------------------------------------------------------------------

	/**
	 * Registers (defines) a property for the element (but gives it no value)
	 * Example: registerProperty("x",setX,getX);
	 * @method registerProperty
	 * @param property the name of the property
	 * @param setter the setter function to set the property value
	 * @param getter the getter function to get the property value (needed only for interaction
	 * with the element)
	 * @return return the element
	 */
	mElement.registerProperty = function(property, setter, getter) {
		mProperties[property] = {
			setter : setter,
			getter : getter,
			variable : null,
			variableSetter : null
		};
		return mElement;
	};

	/**
	 * Registers (defines) an action for the element (but assigns no real action)
	 * Example: registerAction("onPress"); or registerAction("onPress", self.getInteractedElement);
	 * @method registerAction
	 * @param action the name of the action
	 * @param helperFunction a function that will provide additional data about the action
	 * @param processFunction a function that will provide additional process about the action
	 * @param actionFunction a function that will provide default action
	 * @return return the element
	 */
	mElement.registerAction = function(action, helperFunction, processFunction, actionFunction) {
		mActions[action] = {
			action : null,
			helper : helperFunction,
			process : processFunction
		};
		
		// default action
		if(actionFunction) mElement.setAction(action, actionFunction);
		 
		return mElement;
	};

	// ------------------------------------------------------------------------
	// This part is used by the user when creating the view
	// ------------------------------------------------------------------------

	/**
	 * Sets the value of the property of the element to a constant value.
	 * Does it only once, now (different to linking).
	 * Example: setProperty("lineColor","red");
	 * @method setProperty
	 * @param property the name of the property
	 * @param value the value for the property
	 * @return the element, thus allowing for cascading calls
	 */
	mElement.setProperty = function(property, value) {
		var actionDefinition = mActions[property];
		if (actionDefinition) {
			return mElement.setAction(property, value);
		}
		var propertyDefinition = mProperties[property];
		if (!propertyDefinition) {
			console.log("WARNING: ControlElement setProperty() - Property not registered : " + property + " for element : " + mElement.getName());
		} else
			propertyDefinition.setter(value);
		return mElement;
	};

	/**
	 * Sets the values of a set property of the element to the constant values.
	 * Does it only once, now (different to linking).
	 * Example: setProperties({"lineColor":"red", "background":"black"});
	 * @method setProperties
	 * @param properties the names of the properties and their values
	 * @return return the element
	 */
	mElement.setProperties = function(properties) {
		for (var key in properties)
		mElement.setProperty(key, properties[key]);
		return mElement;
	};

	/**
	 * Links the given property to a variable.
	 * Future changes will be communicated:
	 *  - from the model to the view element at each call of view._update()
	 *  - from the model to the view element at each call of view._collectData(), only if the element defines the dataCollected property
	 *  - from the element to the model after interaction (if the getter is not null or undefined)
	 * Example:
	 * - linkProperty("x","ballX"); where "ballX" is a previously registers view variable
	 * - linkProperty("x",function() { return x; }, function(_v) { x = _v; }); where x is a model variable
	 * @method linkProperty
	 * @param property the name of the property
	 * @param variableOrGetter if a string, the name of the variable previously registered by the view,
	 * otherwise a model getter function to obtain the desired value for the property
	 * @param setter the model setter function (if a getter function was given). If variableOrGetter was a string and getter==="InputOnly" the link is only for input 
	 * @return the element, thus allowing for cascading calls
	 */
	mElement.linkProperty = function(property, variableOrGetter, setter) {
		var propertyDefinition = mProperties[property];
		if (!propertyDefinition) {
			console.log("WARNING: ControlElement linkProperty() - Element property not registered : " + property + " for element : " + mElement.getName());
			return mElement;
		}
		if ( typeof variableOrGetter === "string") {
			var viewVariable = mView._addListener(variableOrGetter, propertyDefinition.setter, mElement.dataCollected);
			if (!viewVariable) {
				console.log("WARNING: ControlElement linkProperty() - element : " + mElement.getName() + " - property :" + property + " - View variable not registered : " + variableOrGetter);
				return mElement;
			}
			if (setter==="InputOnly") {
			  //console.log ("ControlElement linkProperty() - element : " + mElement.getName() + " - property :" + property + " - is INPUT_ONLY");
			}
			else propertyDefinition.variable = viewVariable;
			propertyDefinition.variableSetter = null;
		} else {
			mView._addUnkownListener(variableOrGetter, propertyDefinition.setter, mElement.dataCollected);
			propertyDefinition.variableSetter = setter;
			propertyDefinition.variable = null;
		}
		return mElement;
	};

	/**
	 * Links the given property to a variable.
	 * Example:
	 * - linkProperties({"x":"ballX","y":"ballY"}); where "ballX" and "ballY" are previously registers view variables
	 * @method linkProperties
	 * @param properties the names of the properties and their variables
	 * @return return the element
	 */
	mElement.linkProperties = function(properties) {
		for (var key in properties)
		mElement.linkProperty(key, properties[key]);
		return mElement;
	};

	/**
	 * Sets the given action to a function
	 * Interactions with that action name will trigger the given function (one direction only)
	 * Examples:
	 * - setAction("onPress","start"); Where "start" is a previously registered view action
	 * - setAction("onPress",_play); where _play is a function defined by the model
	 * @method setAction
	 * @param name the name of the action
	 * @param action if a string, the name of a previously registered view action,
	 * otherwise it must be the function to call when the action is invoked. Setting to null disables the action
	 * @return the element, thus allowing for cascading calls
	 */
	mElement.setAction = function(name, action) {
		var actionDefinition = mActions[name];
		if (!actionDefinition) {
			console.log("WARNING: ControlElement setAction() - action not registered : " + name + " for element : " + mElement.getName);
			return mElement;
		}
		if ( typeof action === "string") {
			var viewAction = mView._getAction(action);
			if (!viewAction) {
				console.log("WARNING: ControlElement setAction() - element : " + mElement.getName() + " - View action not registered : " + action);
				return mElement;
			}
			actionDefinition.action = viewAction;
		} else {// It must be a function
			actionDefinition.action = {
				action : action
			};
		}
		return mElement;
	};

	/**
	 * Sets the given action to a function
	 * Interactions with that action name will trigger the given function (one direction only)
	 * Examples:
	 * - setAction("onPress","start"); Where "start" is a previously registered view action
	 * - setAction("onPress",_play); where _play is a function defined by the model
	 * @method setActions
	 * @param actions the names of the actions and their view actions
	 * @return return the element
	 */
	mElement.setActions = function(actions) {
		for (var key in actions)
		mElement.setAction(key, actions[key]);
		return mElement;
	};

	// ------------------------------------------------------------------------
	// This part is used by the elements under user interaction
	// ------------------------------------------------------------------------

	/**
	 * Invoke an action
	 * @method invokeAction
	 * @param name the name of the action
	 * @return return the element
	 */
	mElement.invokeAction = function(name) {
		var ia = {};
		if (mView.isRegInteractions()) {
			if(!mView.isShortRegInteractions())
				ia = {"action":name, "element":mElement.getName(), "timeStamp":Date.now()};
			else
				ia = {"action":name, "element":mElement.getName()};			
		}

		var actionDefinition = mActions[name];
		if (actionDefinition) {
			if (actionDefinition.process) {
				mView._addInteraction(actionDefinition.process, null, ia);
			}
			if (actionDefinition.action) {
  				var data = actionDefinition.helper ? actionDefinition.helper() : null;
				mView._addInteraction(actionDefinition.action.action, data, ia);
			}
		}
		return mElement;
	};

	mElement.invokeImmediateAction = function(name) {
		var actionDefinition = mActions[name];
		if (actionDefinition) {
			if (actionDefinition.process) {
				actionDefinition.process(null);
			}
			if (actionDefinition.action) {
  				var data = actionDefinition.helper ? actionDefinition.helper() : null;
				actionDefinition.action.action(data);
			}
		}
		return mElement;
	};

	/**
	 * Reports a change in all given properties caused by interaction with the element
	 * @method propertiesChanged
	 * @return true if any of the properties was linked to a variable
	 */
	mElement.propertiesChanged = function(/* ... properties */) {
		for (var i = 0, n = arguments.length; i < n; i++)
			mElement.propertyChanged(arguments[i]);
		return mElement;
	};

	mElement.propertyChanged = function(property, data) {
		var ia = {};
		if (mView.isRegInteractions()) {
			if(!mView.isShortRegInteractions())
				ia = {"property":property, "element":mElement.getName(), "timeStamp":Date.now()}
			else		
				ia = {"property":property, "element":mElement.getName()}
		}
		
		var propertyDefinition = mProperties[property];
		if (propertyDefinition) {// Linked to a function
			var setter = propertyDefinition.variableSetter; 
			if (!setter) {// It may be linked to a view variable
				if (propertyDefinition.variable)
					setter = propertyDefinition.variable.setter;
			}
			if (!setter) {// It may be linked to its own setter
				if (propertyDefinition.setter)
					setter = propertyDefinition.setter;
			}
			if (setter) {// Linked to anything
				// Obtain the property value now, in case it changes later
				// but delay setting the variable until requested
				var value = (typeof data !== "undefined")?data:propertyDefinition.getter();
				mView._addInteraction(setter, value, ia);					
			}
			// else : don't know what to do with this property
		}
		return mElement;
	};

	mElement.immediatePropertyChanged = function(property, data) {
		var ia = {};
		var propertyDefinition = mProperties[property];
		if (propertyDefinition) {// Linked to a function
			var setter = propertyDefinition.variableSetter; 
			if (!setter) {// It may be linked to a view variable
				if (propertyDefinition.variable)
					setter = propertyDefinition.variable.setter;
			}
			if (!setter) {// It may be linked to its own setter
				if (propertyDefinition.setter)
					setter = propertyDefinition.setter;
			}
			if (setter) {// Linked to anything
				// Obtain the property value now, in case it changes later
				// but delay setting the variable until requested
				var value = (typeof data !== "undefined")?data:propertyDefinition.getter();
				setter(value);					
			}
			// else : don't know what to do with this property
		}
		return mElement;
	};
	/**
	 * Report accumulated interactions including the interactiones derived by
	 * the actions and properties changed specified in the params
	 * @method reportInteractions
	 * @param actions array with the names of the actions
	 * @param properties array with the names of the properties
	 * @return return the element
	 */
	mElement.reportInteractions = function(actions, properties) {
		var i, n;
		// actions
		if (actions)
			for (i = 0, n = actions.length; i < n; i++)
				mElement.invokeAction(actions[i]);
		// properties
		if (properties)
			for (i = 0, n = properties.length; i < n; i++)
				mElement.propertiesChanged(properties[i]);

		if (mView._interactionsNumber() > 0)
			mView._reportInteraction();
		return mElement;
	};

	/**
	 * Sets the value of a property of the element.
	 * Example: getProperty("lineColor");
	 * @method getProperty
	 * @param property the name of the property
	 * @return value the value for the property, undefined if not found
	 */
	mElement.getProperty = function(property, value) {
		var propertyDefinition = mProperties[property];
		if (propertyDefinition && propertyDefinition.getter)
			return propertyDefinition.getter();
		return undefined;
	};

	/**
	 * Gets properties
	 * @method getProperties
	 * @return properties
	 */
	mElement.getProperties = function() {
		return mProperties;
	};
			
	//---------------------------------
	// final initialization
	//---------------------------------

	mElement.registerProperty("name", mElement.setName, mElement.getName);

	if (mElement.registerProperties)
		mElement.registerProperties(mElement);

	return mElement;
};
