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;