Source: phRender.js

const Gradients = require("./gradient");
const BoundingBox = require("./tools/boundingBox");
const Centroid = require("./tools/centroid");
const Vertices = require("./tools/vertex");

/** 
 * 
 * PhRender constructor
 * PhRender is the rendering engine for PhSim.
 * 
 * @constructor
 * @memberof PhSim
 * @param {CanvasRenderingContext2D} ctx - Canvas context
 * 
 */

var PhRender = function(ctx) {

	/**
	 * PhRender Context
	 * @type {CanvasRenderingContext2D}
	 */

	this.ctx = ctx;
}

/**
 * Default Alpha
 * This the alpha of an object that has no alpha defined.
 * 
 * @type {Number}
 */

PhRender.prototype.defaultAlpha = 1;

/**
 * Default stroke style.
 * This is the stroke style of an object that has no stroke style defined.
 * 
 * @type {String}
 */

PhRender.prototype.defaultStrokeStyle = "transparent";

/**
 * Default fill style
 * This is the default fill style of an object.
 * 
 * @type {String}
 */

PhRender.prototype.defaultFillStyle = "transparent";

/**
 * Setting context
 * That is, this function sets the `globalAlpha`, `strokeStyle`, `fillStyle` and `lineWidth`
 * properties of the {@link PhRender#ctx} member of the {@link PhRender} object
 * using a {@link PhSimObject} object.
 * 
 * @function
 * @param {PhSimObject} object 
 */

PhRender.prototype.setCtx = function(object) {
	
	this.ctx.lineCap = "round";

	if(typeof this.ctx.globalAlpha === "number") {
		this.ctx.globalAlpha = object.globalAlpha
	}

	else {
		this.ctx.globalAlpha = this.defaultAlpha;
	}

	this.ctx.strokeStyle = object.strokeStyle || this.defaultStrokeStyle;
	this.ctx.fillStyle = object.fillStyle || this.defaultFillStyle;

	if(object.lineWidth) {

		if(object.lineWidth === 0) {
			this.ctx.strokeStyle = "transparent"
		}

		else {
			this.ctx.lineWidth = object.lineWidth;
		}

	}

	else {
		this.ctx.strokeStyle = "transparent"
	}
	
}

PhRender.prototype.unsetCtx = function() {
	this.ctx.globalAlpha = 1;
}

/**
 * 
 * Render a a {@link polygon}.
 * 
 * @function
 * @param {Path} path 
 */

PhRender.prototype.renderPolygon = function (path) {

	var w;
	var h;

	this.setCtx(path);

	this.ctx.beginPath();

	this.ctx.moveTo(path.verts[0].x, path.verts[0].y);

	for(var j = 0; j < path.verts.length; j++) {
		this.ctx.lineTo(path.verts[j].x, path.verts[j].y);
	}

	this.ctx.closePath();
	
	this.ctx.stroke();
	this.ctx.fill();

	if(path.sprite && path.sprite.src) {

		var img = this.spriteImgObj[path.sprite.src];

		var centroid = Centroid.polygon(path);

		this.ctx.imageSmoothingEnabled = path.sprite.smooth;

		this.ctx.save();

		this.ctx.beginPath();

		this.ctx.moveTo(path.verts[0].x, path.verts[0].y);

		for(let j = 0; j < path.verts.length; j++) {
			this.ctx.lineTo(path.verts[j].x, path.verts[j].y);
		}

		this.ctx.closePath();

		this.ctx.translate(centroid.x,centroid.y);

		if(path.sprite.repeat) {

			var pattern = this.ctx.createPattern(img,"repeat");
			this.ctx.fillStyle = pattern;

			this.ctx.fill();
		}

		else if(path.sprite.fit) {

			this.ctx.clip();

			var box = BoundingBox.fromShape(path);

			h = img.height * (box.w/img.width);

			this.renderSpriteByCenter(path.sprite.src,centroid.x,centroid.y,box.w,h,0);
		}

		else if(typeof path.sprite.w === "number" && typeof path.sprite.h === "number") {

			this.ctx.clip();

			w = path.sprite.w || img.width;
			h = path.sprite.h || img.height;

			this.renderSpriteByCenter(path.sprite.src,0,0,w,h,0);

		}

		else if(typeof path.sprite.w !== "number" && typeof path.sprite.h === "number") {
			
		    this.ctx.clip();

			let w = (img.width/img.height) * path.sprite.h;

			this.renderSpriteByCenter(path.sprite.src,0,0,w,path.sprite.h,0);
		}

		else if(typeof path.sprite.w === "number" && typeof path.sprite.h !== "number") {
			
			this.ctx.clip();

			let h = (img.height/img.width) * path.sprite.w;
			
			this.renderSpriteByCenter(path.sprite.src,0,0,path.sprite.w,h,0);
		}

		else if(typeof path.sprite.w !== "number" && typeof path.sprite.h !== "number") {
			this.ctx.clip();
			this.renderSpriteByCenter(path.sprite.src,0,0,img.width,img.height,0);
		}

	}

	this.ctx.restore();	

	this.unsetCtx();
	
}

/**
 * 
 * Render sprite by center
 * 
 * @function
 * @param {String} url - URL of object loaded in PhRender.prototype.spriteImgObj
 * @param {Number} x - x-coordinate
 * @param {Number} y - y-coordinate
 * @param {Number} w - width
 * @param {Number} h - height
 * @param {Number} a - angle
 */

PhRender.prototype.renderSpriteByCenter = function(url,x,y,w,h,a) {

	var spriteImg = this.spriteImgObj[url];

	this.ctx.save();
	this.ctx.translate(x,y)
	this.ctx.rotate(a)
	
	if(h === null) {
		this.ctx.drawImage(spriteImg,0,0,spriteImg.width,spriteImg.height,-w * 0.5 , -h * 0.5,w);
	}

	else {
		this.ctx.drawImage(spriteImg,0,0,spriteImg.width,spriteImg.height,-w * 0.5 , -h * 0.5,w,h);
	}

	this.ctx.restore();
}


/**
 * 
 * Render circle
 * 
 * @function
 * @param {PhSim.Static.Circle} circle 
 */

PhRender.prototype.renderCircle = function (circle) {

	var w;
	var h;
	
	this.setCtx(circle);

	this.ctx.beginPath();
	this.ctx.arc(circle.x, circle.y, circle.radius, 0, 2*Math.PI)
	this.ctx.closePath();

	this.ctx.fill();
	this.ctx.stroke();

	if(circle.gradient) {
		this.ctx.save();
		this.ctx.translate(circle.x,circle.y);
		this.ctx.rotate(circle.cycle);
		this.ctx.fillStyle = Gradients.extractGradient(this.ctx,circle.gradient);
		this.ctx.arc(0,0,circle.radius,0,2*Math.PI);
		this.ctx.fill();
		this.ctx.restore();	

	}

	if(circle.sprite && circle.sprite.src) {

		/**
		 * 
		 * @type {HTMLImageElement}
		 */

		var img = this.spriteImgObj[circle.sprite.src];

		this.ctx.imageSmoothingEnabled = circle.sprite.smooth;

		this.ctx.save();
		this.ctx.translate(circle.x,circle.y);
		this.ctx.rotate(circle.cycle);
		this.ctx.arc(0,0,circle.radius,0,2*Math.PI);

		if(circle.sprite.repeat) {
			var pattern = this.ctx.createPattern(img,"repeat");
			this.ctx.fillStyle = pattern;
			this.ctx.fill();	
		}

		else if(circle.sprite.fit) {

			this.ctx.clip();
			var box = BoundingBox.fromShape(circle);

			h = img.height * (box.w/img.width);

			this.renderSpriteByCenter(circle.sprite.src,0,0,box.w,h);
			this.ctx.restore();	
		}

		else if(typeof circle.sprite.w === "number" && typeof circle.sprite.h === "number") {

			w = circle.sprite.w || img.width;
			h = circle.sprite.h || img.height;

			this.ctx.clip(); 
			this.renderSpriteByCenter(circle.sprite.src,0,0,w,h,0);
		}

		else if(typeof circle.sprite.w !== "number" && typeof circle.sprite.h === "number") {
			
		    this.ctx.clip();

			let w = (img.width/img.height) * circle.sprite.h;

			this.renderSpriteByCenter(circle.sprite.src,0,0,w,circle.sprite.h,0);
		}

		else if(typeof circle.sprite.w === "number" && typeof circle.sprite.h !== "number") {
			
			this.ctx.clip();

			let h = (img.height/img.width) * circle.sprite.w;
			
			this.renderSpriteByCenter(circle.sprite.src,0,0,circle.sprite.w,h,0);
		}

		else if(typeof circle.sprite.w !== "number" && typeof circle.sprite.h !== "number") {
			this.ctx.clip();
			this.renderSpriteByCenter(circle.sprite.src,0,0,img.width,img.height,0);
		}

	}

	this.ctx.restore();

	this.unsetCtx();

}


/**
 * 
 * Render rectangle
 * 
 * @function
 * @param {PhSim.Static.Rectangle} rectangle - Rectangle object
 * @param rectangle.sprite - Sprite Object
 */

PhRender.prototype.renderRectangle = function(rectangle) {

	var c = Centroid.rectangle(rectangle);

	var x = -rectangle.w * 0.5;
	var y = -rectangle.h * 0.5;
	
	this.setCtx(rectangle);
	this.ctx.translate(c.x,c.y);
	this.ctx.rotate(rectangle.cycle);
	this.ctx.beginPath();
	this.ctx.rect(x,y,rectangle.w,rectangle.h);
	this.ctx.closePath();
	this.ctx.stroke();
	this.ctx.fill();

	if(rectangle.widgets) {
		for(let i = 0; i < rectangle.widgets.length; i++) {
			if(rectangle.widgets[i].type === "rectText") {
				this.rectText(rectangle.widgets[i],x,y,rectangle.w,rectangle.h,0);
			}
		}
	}

	this.ctx.rotate(-rectangle.cycle);
	this.ctx.translate(-c.x,-c.y);


	if(typeof rectangle.sprite === "object" && typeof rectangle.sprite.src === "string") {

		var img = this.spriteImgObj[rectangle.sprite.src];

		this.ctx.imageSmoothingEnabled = rectangle.sprite.smooth;

		this.ctx.save();
		this.ctx.translate(c.x,c.y);
		this.ctx.rotate(rectangle.cycle);
		this.ctx.rect(-rectangle.w * 0.5,-rectangle.h * 0.5,rectangle.w,rectangle.h);

		if(rectangle.sprite.repeat) {
	
			var pattern = this.ctx.createPattern(img,"repeat");
			this.ctx.fillStyle = pattern;
			this.ctx.fill();
			this.ctx.restore();	
		}

		else if(rectangle.sprite.fit) {

			this.ctx.clip();

			let h = img.height * (rectangle.w/img.width);

			this.renderSpriteByCenter(rectangle.sprite.src,0,0,rectangle.w,h,0);
		}

		else if(typeof rectangle.sprite.w === "number" && typeof rectangle.sprite.h === "number") { 
			
			this.ctx.clip();
			this.renderSpriteByCenter(rectangle.sprite.src,0,0,rectangle.sprite.w,rectangle.sprite.h,0);
		}

		else if(typeof rectangle.sprite.w !== "number" && typeof rectangle.sprite.h === "number") {
			
			this.ctx.clip();

			let w = (img.width/img.height) * rectangle.sprite.h;

			this.renderSpriteByCenter(rectangle.sprite.src,0,0,w,rectangle.sprite.h,0);
		}

		else if(typeof rectangle.sprite.w === "number" && typeof rectangle.sprite.h !== "number") {
			
			this.ctx.clip();

			let h = (img.height/img.width) * rectangle.sprite.w;
			
			this.renderSpriteByCenter(rectangle.sprite.src,0,0,rectangle.sprite.w,h,0);
		}

		else if(typeof rectangle.sprite.w !== "number" && typeof rectangle.sprite.h !== "number") {
			this.ctx.clip();
			this.renderSpriteByCenter(rectangle.sprite.src,0,0,img.width,img.height,0);
		}

	}

	this.ctx.restore();	
	this.unsetCtx();

}

// Draw text

/**
 * @function
 * @param {*} text
 * @param {String} text.fill - Text Fill Style
 * @param {Number} text.lineWidth - Text border line width
 * @param {String} text.borderColor - Text border color
 * @param {Number} text.size - Text size
 * @param {String} text.font - Text font
 * @param {}
 * @param {Number} x 
 * @param {Number} y 
 * @param {Number} w 
 * @param {Number} h 
 * @param {Number} a 
 */

PhRender.prototype.rectText = function(text,x,y,w,h,a) {
	this.ctx.save();
	this.ctx.translate(x,y);
	this.ctx.rotate(a);
	this.ctx.beginPath();
	this.ctx.rect(0,0,w,h);
	this.ctx.clip();
	this.ctx.textAlign = "left";
	this.ctx.fillStyle = text.fill || "#000000";

	// Reset Line Width

	this.ctx.lineWidth = undefined;

	if(text.lineWidth) {
		this.ctx.lineWidth = text.lineWidth;
	}

	this.ctx.strokeStyle = text.borderColor || "#000000";
	this.ctx.font = text.size + "px " + text.font;
	this.ctx.textBaseline = "top";
	var content = text.content;

	if(this.dynSim) {
		content = this.dynSim.processVar(content);
	}

	var lineHeight = text.lineHeight || this.ctx.measureText("M").width;

	var lines = content.split("\n");

	for(let i = 0; i < lines.length; i++) {

		this.ctx.fillText(lines[i],0,lineHeight * i);

		if(text.lineWidth) {
			this.ctx.strokeText(lines[i],0,lineHeight * i);
		}

	}

	this.ctx.restore();
}

// Draw a regular polygon

/**
 * @function
 * @param {PhSim.Static.RegPolygon} regPolygon 
 */

PhRender.prototype.renderRegPolygon = function(regPolygon) {

	var vertSet = Vertices.regPolygon(regPolygon);
	
	this.setCtx(regPolygon);

	this.ctx.beginPath();

	this.ctx.moveTo(vertSet[0].x, vertSet[0].y);

	for(let j = 0; j < vertSet.length; j++) {
		this.ctx.lineTo(vertSet[j].x, vertSet[j].y);
	}

	this.ctx.closePath();
	
	this.ctx.stroke();

	this.ctx.globalAlpha = regPolygon.fillAlpha;

	this.ctx.fill();

	if(regPolygon.sprite && regPolygon.sprite.src) {

		var img = this.spriteImgObj[regPolygon.sprite.src];

		this.ctx.imageSmoothingEnabled = regPolygon.sprite.smooth;

		this.ctx.save();

		this.ctx.beginPath();

		this.ctx.moveTo(vertSet[0].x, vertSet[0].y);
	
		for(let j = 0; j < vertSet.length; j++) {
			this.ctx.lineTo(vertSet[j].x, vertSet[j].y);
		}
	
		this.ctx.closePath();

		this.ctx.translate(regPolygon.x,regPolygon.y);
		this.ctx.rotate(regPolygon.cycle);

		if(regPolygon.sprite.repeat) {
			var pattern = this.ctx.createPattern(img,"repeat");
			this.ctx.fillStyle = pattern;
			this.ctx.fill();
		}

		if(regPolygon.sprite.fit) {

			this.ctx.clip();

			let box = BoundingBox.fromShape(regPolygon);

			let h = img.height * (box.w/img.width);

			this.renderSpriteByCenter(regPolygon.sprite.src,0,0,box.w,h,0);

		}

		else if(typeof regPolygon.sprite.w === "number" && typeof regPolygon.sprite.h === "number") {
			
			this.ctx.clip();

			let w = regPolygon.sprite.w;
			let h = regPolygon.sprite.h;

			this.renderSpriteByCenter(regPolygon.sprite.src,0,0,w,h,0);
		}

        else if(typeof regPolygon.sprite.w !== "number" && typeof regPolygon.sprite.h === "number") {
			
		    this.ctx.clip();

			let w = (img.width/img.height) * regPolygon.sprite.h;

			this.renderSpriteByCenter(regPolygon.sprite.src,0,0,w,regPolygon.sprite.h,0);
		}

		else if(typeof regPolygon.sprite.w === "number" && typeof regPolygon.sprite.h !== "number") {
			
			this.ctx.clip();

			let h = (img.height/img.width) * regPolygon.sprite.w;
			
			this.renderSpriteByCenter(regPolygon.sprite.src,0,0,regPolygon.sprite.w,h,0);
		}

		else if(typeof regPolygon.sprite.w !== "number" && typeof regPolygon.sprite.h !== "number") {
			this.ctx.clip();
			this.renderSpriteByCenter(regPolygon.sprite.src,0,0,img.width,img.height,0);
		}

	}

	this.ctx.restore();	

	this.unsetCtx();

}

// Draw Static object

/**
 * @function
 * @param {PhSimObject} obj 
 */

PhRender.prototype.renderStatic = function(obj) {
				
	if (obj.shape === "polygon")  {
		this.renderPolygon(obj);
	}
	
	if( obj.shape === "circle") {
		this.renderCircle(obj); 
	}

	if( obj.shape === "rectangle") {
		this.renderRectangle(obj);
	}

	if( obj.shape === "regPolygon") {
		this.renderRegPolygon(obj);
	}

	if( obj.shape === "composite") {
		for(var i = 0; i < obj.parts.length; i++) {
			this.renderStatic(obj.parts[i]);
		}
	}

}

// Draws a layer

/**
 * @function
 * @param {*} layer 
 */

PhRender.prototype.renderStaticLayer = function(layer) {

	for(var i = 0; i < layer.objUniverse.length; i++) {

			this.renderStatic(layer.objUniverse[i])
			
	}	
}

/**
 * @function
 * @param {*} simulation 
 */

PhRender.prototype.simulation = function(simulation) {

	for(var i = 0; i < simulation.layers.length; i++) {
		if(!simulation.layers[i].hidden) {
			this.layer(simulation.layers[i])
		}
	}
}

/**
 * @function
 * @param {*} object 
 */

PhRender.prototype.dynamicSkeleton = function(object) {

	if(object.static.shape === "polygon") {
		
		this.ctx.beginPath();

		this.ctx.moveTo(object.skinmesh[0].x, object.skinmesh[0].y);

		for(var j = 0; j < object.skinmesh.length; j++) {
			this.ctx.lineTo(object.skinmesh[j].x, object.skinmesh[j].y);
		}
	
	}

	else {

		this.ctx.beginPath();

		this.ctx.moveTo(object.matter.vertices.x, object.matter.vertices.y);

		for(let j = 0; j < object.matter.vertices.length; j++) {
			this.ctx.lineTo(object.matter.vertices[j].x, object.matter.vertices[j].y);
		}


	}

}

/**
 * @function
 * @param {*} object 
 */

PhRender.prototype.dynamicSkeleton_center = function(object) {

	if(object.static.shape === "polygon") {
		
		this.ctx.beginPath();

		this.ctx.moveTo(object.skinmesh[0].x - object.matter.position.x, object.skinmesh[0].y - object.matter.position.y);

		for(var j = 0; j < object.skinmesh.length; j++) {
			this.ctx.lineTo(object.skinmesh[j].x - object.matter.position.x, object.skinmesh[j].y - object.matter.position.y);
		}
	
	}

	else {

		this.ctx.beginPath();

		this.ctx.moveTo(object.matter.vertices.x - object.matter.position.x, object.matter.vertices.y - object.matter.position.y);

		for(let j = 0; j < object.matter.vertices.length; j++) {
			this.ctx.lineTo(object.matter.vertices[j].x - object.matter.position.x, object.matter.vertices[j].y - object.matter.position.y);
		}


	}

}

/**
 * @function
 * @param {*} object 
 */

PhRender.prototype.drawDynamicSkeleton = function (object) {

	this.dynamicSkeleton(object);
	this.ctx.closePath();
	this.ctx.stroke();

}

/**
 * @function
 * @param {*} dynObject 
 */

PhRender.prototype.dynamicRenderDraw = function (dynObject) {

	this.ctx.lineWidth = dynObject.lineWidth;
	this.ctx.fillStyle = dynObject.fillStyle;
	this.ctx.strokeStyle = dynObject.strokeStyle;

	
	if(dynObject.shape === "polygon") {
		
		this.drawDynamicSkeleton(dynObject);
		
		this.ctx.fill();

		if(dynObject.sprite && dynObject.sprite.src) {

			var img = this.spriteImgObj[dynObject.sprite.src];

			this.ctx.imageSmoothingEnabled = dynObject.sprite.smooth;

			if(dynObject.sprite.repeat) {

				this.ctx.save();

				this.dynamicSkeleton(dynObject);
				this.ctx.translate(dynObject.matter.position.x,dynObject.matter.position.y);
				this.ctx.rotate(dynObject.matter.angle);
				this.ctx.scale(dynObject.sprite.scale,dynObject.sprite.scale);
		

				var pattern = this.ctx.createPattern(img,"repeat");
				this.ctx.fillStyle = pattern;
				this.ctx.fill();

				this.ctx.restore();
			}

			else if(dynObject.sprite.fit) {

				this.ctx.save();
	
				this.ctx.beginPath();
	
				this.ctx.moveTo(dynObject.verts[0].x, dynObject.verts[0].y);
	
				for(var j = 0; j < dynObject.verts.length; j++) {
					this.ctx.lineTo(dynObject.verts[j].x, dynObject.verts[j].y);
				}
	
				this.ctx.closePath();
	
				this.ctx.clip();
	
				var box = BoundingBox.fromShape(dynObject);
	
				var h = img.height * (box.w/img.width);
	
				this.renderSpriteByCenter(dynObject.sprite.src,dynObject.matter.position.x,dynObject.matter.position.y,box.w,h,dynObject.matter.angle);
	
				this.ctx.restore();	

				//

			}
	
			else {
				this.renderSpriteByCenter(dynObject.sprite.src,dynObject.matter.position.x,dynObject.matter.position.y,img.width,img.height,dynObject.matter.angle);
			}

			//this.ctx.restore();	
	
		}
		
	}

	if(dynObject.shape === "circle") {
		this.renderCircle(dynObject);	
	}
	
	if(dynObject.shape === "regPolygon") {
		this.renderRegPolygon(dynObject);		
	}

	if(dynObject.shape === "rectangle") {
		this.renderRectangle(dynObject);		
	}

	if(dynObject.shape === "composite") {
		for(var i = 1; i < dynObject.parts.length; i++) {
			this.dynamicRenderDraw(dynObject.parts[i]);
		}
	}

}

/**
 * @function
 * @param {*} L 
 */

PhRender.prototype.dynamicDrawLayer = function(L,sim,simulationI) {
	
	for(let i = 0; i < sim.simulations[simulationI].layers[L].length; i++) {
		this.dynamicRenderDraw(L,i);
	}

}

module.exports = PhRender;