Source: query.js

const { ObjLoops } = require(".");
const Vector = require("./tools/vector");
const PhSim = require("./index");

var Matter;

if(typeof window === "object") {
	Matter = window.Matter;
}

else {
	Matter = require("matter-js");
}
	
/**
 * @namespace
 * @memberof PhSim
 */

PhSim.Query = {}

/**
 * 
 * Get the special points of a rectangle
 * 
 * @function
 * @param {Object} rectangle 
 */

PhSim.Query.getSpecialRectanglePoints = function(rectangle) {
	
	var o = {

		"center": {
			"rel": {
				"x": 0.5 * rectangle.w,
				"y": 0.5 * rectangle.h
			},
	
			"abs": {
				"x": rectangle.x + o.center.rel.x,
				"y": rectangle.h + o.center.rel.y
			},
		},

		"sidepoint": {

			"rel": {
				"x": rectangle.w,
				"y": 0.5 * rectangle.h
			},

			"abs": {
				"x": o.sidepoint.rel.x + rectangle.x,
				"y": o.sidepoint.rel.y + rectangle.y 
			}

		},

	}

	return o;

}

/**
 * Get the status string of the the dynamic simulation
 * @function
 */

PhSim.prototype.getStatusStr = function() {
	return PhSim.Query.getStatusStr(this);
}

/**
 * Get the status string of a PhSim instance.
 * @param {PhSim} dynObject 
 */

PhSim.Query.getStatusStr = function(phSim) {
	return PhSim.statusStruct[phSim.status];
}

/**
 * 
 * Get collision classes
 * 
 * @function
 * @param {PhSim.DynObject} dynObject - Dynamic Object
 * @returns {String[]}
 * 
 */

PhSim.Query.getCollisionClasses = function(dynObject) {

	if(dynObject.collisionClass) {
		var a = dynObject.collisionClass;
		var b = a.split(" ");
		return b;
	}

	else {
		return [];
	}
}

/**
 * @function
 */

PhSim.prototype.getUniversalObjArray = function() {
	
	var array = []
	
	for(let i = 0; i < this.matterJSWorld.composites.length; i++) {
		
		var a = this.matterJSWorld.composites[i].bodies;

		for(let j = 0; j < a.length; j++) {
			array.push(a[j]);
		}

	}

	for(let i = 0; i < this.matterJSWorld.bodies.length; i++) {
		array.push(this.matterJSWorld.bodies[i]);
	}

	return array;

}

/**
 * Check widget type and return the widget type
 * @param {WidgetOptions} widget 
 */

PhSim.Query.chkWidgetType = function(widget) {
	
	for(var i = 0; i < PhSim.boolKey_lc.length; i++) {
		if(widget[PhSim.boolKey_lc[i]]) {
			return PhSim.boolKey_lc[i];
		}
	}

	return "unknown_widget";

}

/**
 * Get static object of a dynamic object
 * @param {PhSim.DynObject} dynObject - The dynamic object
 */

PhSim.prototype.getStatic = function(dynObject) {
	
	if(dynObject.static || dynObject.static) {
		return null;
	}

	else {
		return dynObject.static;
	}
}

/**
 * Get object by name in PhSim simulation
 * 
 * @function
 * @param {String} str - String for the name
 * @returns {PhSimObject|null} -  Returns the object if found, but returns "null" object if not.
 */

PhSim.prototype.getObjectByName = function(str) {

	for(var i = 0; i < this.objUniverse.length; i++) {
		if(this.objUniverse[i].name === str) {
			return this.objUniverse[i];
		}
	}

}

/**
 * Get Object By Name
 * @param {Simulation|Layer|PhSimObject[]} o 
 * @param {string} str - Name of Object
 */

PhSim.Query.getObjectByName = function(o,str) {

	var x;
	
	if(Array.isArray(o)) {
		for(var i = 0; i < o.length; i++) {
			if(o.name === str) {
				return o[i];
			}
		}
	}
	
	// Get object by name in static composite simulation object

	else if(Array.isArray(o.simulations)) {

		ObjLoops.global(o,function(p){
			if(p.name === str) {
				x = p;
			}
		}) 

		return x;
	}

	// Get object by name in simulation object with layers


	else if(Array.isArray(o.layers)) {

		ObjLoops.layer(o,function(p){
			if(p.name === str) {
				x = p;
			}
		}) 

		return x;

	}

	// Get object by name in simulation object with objUniverse

	else if(Array.isArray(o.objUniverse)) {
		PhSim.Query.getObjectByName(o.objUniverse,str); 
	}

}

/**
 * Check if two objects are colliding
 * @function
 * @param {PhSim.DynObject} dynObjectA 
 * @param {PhSim.DynObject} dynObjectB
 * @returns {Boolean} 
 */

PhSim.prototype.collided = function(dynObjectA,dynObjectB) {
	return Matter.SAT.collides(dynObjectA.matter,dynObjectB.matter).collided;
}

/**
 * Check if an object is in a collision
 * @function
 * @param {PhSim.DynObject} dynObject 
 * @returns {Boolean}
 */

PhSim.prototype.isInCollision = function(dynObject) {

	var self = this;

	var returnValue = false;

	this.forAllObjects(function(object) {
		var a = self.collided(dynObject,object);
		if(a === true) {
			returnValue = a;
			return false;
		}	
	});

	return returnValue;
}

/**
 * See if point is contained in shape defined by vertices set.
 * @function
 * @param {Vector[]} a - Set of vertices
 * @param {Vector} v - The vertex to be checked.
 * @return {Boolean} - Returns true if `v` is contained in the shape defined by `a` and false if otherwise.
 */

PhSim.Query.pointInVerts = function(a,v) {

	var canvas = document.createElement("canvas");
	var ctx = canvas.getContext("2d");

	ctx.beginPath();
	ctx.moveTo(a[0].x,a[0].y);

	for(var i = 0; i < a.length; i++) {
		ctx.lineTo(a[i].x,a[i].y);
	}

	ctx.closePath();
	var p = ctx.isPointInPath(v.x,v.y);
	ctx.stroke();


	return p;


}

/**
 * 
 * See if point is in vertices border
 * 
 * @function
 * @param {Vector[]} a - Vertices to check
 * @param {Vector} v - Point to check.
 * @param {Number} width - Width of vertices border
 * @returns {Boolean} - Returns `true` if `v` is in the border of the 
 * polygon defined by `a` and false otherwise.
 */

PhSim.Query.pointInVertsBorder = function(a,v,width) {

	if(width) {
		var canvas = document.createElement("canvas");
		var ctx = canvas.getContext("2d");
		ctx.lineWidth = width;
		ctx.beginPath();
		ctx.lineTo(a[0].x,a[0].y);

		for(var i = 0; i < a.length; i++) {
			ctx.moveTo(a[i].x,a[i].y);
		}

		ctx.closePath();
		var p = ctx.isPointInPath(v.x,v.y);
		ctx.stroke();


		return p;		
	}

	else {
		return false;
	}

}

/**
 * Deep clone a JavaScript object.
 * @param {Object} o 
 */

PhSim.Query.deepClone = function(o) {
	return JSON.parse(JSON.stringify(o));
}

/**
 * @function
 * @param {*} o 
 * @param {*} x 
 * @param {*} y 
 */

PhSim.Query.pointInRectangle = function(o,x,y) {
	var a = PhSim.Vertices.rectangle(o);
	return PhSim.Query.pointInVerts(a,new Vector(x,y));
}

/**
 * 
 * Check if a point (x,y) is in a dynamic object
 * 
 * @function
 * @param {PhSim.DynObject} dynObject - Dynamic Object
 * @param {Number} x - x-coordinate
 * @param {Number} y - y-coordinate
 * @returns {Boolean}
 */

PhSim.Query.pointInObject = function(o,x,y) {

	if(o.shape === "rectangle") {
		return PhSim.Query.pointInRectangle(o,x,y);
	}

	else if(o.shape === "circle") {

		var d = Vector.distance(o,new Vector(x,y));
		
		if(d < o.radius) {
			return true;
		}

		else {
			return false;
		}

	}  
	
	else if(o.shape === "regPolygon") {
		var a = PhSim.Vertices.regPolygon(o);
		return PhSim.Query.pointInVerts(a,new Vector(x,y));
	}

	else if(o.shape === "polygon") {
		return PhSim.Query.pointInVerts(o.verts,new Vector(x,y));
	}
}

PhSim.prototype.pointInObject = function(o,x,y) {
	return PhSim.Query.pointInObject(o,x,y)
}

/**
 * Get object by ID
 * 
 * @function
 * @param {String} idNum
 * @returns {PhSim.DynObject} 
 * 
 */

PhSim.prototype.getDynObjectByID = function(idNum) {

	var a = this.getUniversalObjArray();

	for(var i = 0; i < a.length; i++) {
		if(a[i].id === idNum) {
			return a[i];
		}
	}

}

/**
 * 
 * Get array of objects that contain a certain point 
 * 
 * @function
 * @param {Number} x - x-coordinate
 * @param {Number} y - y-coordinate
 * @returns {PhSim.DynObject[]}
 * 
 */

PhSim.prototype.pointObjArray = function(x,y) {

	var b = [];

	var a = this.objUniverse || [];

	for(var i = 0; i < a.length; i++) {
		if(this.pointInObject(a[i],x,y)) {
			b.push(a[i]);
		}
	}

	return b;

}

/** 
 * Get the collison pairs that contain a certain object 
 * 
 * @function
 * @param {PhSim.DynObject} dynObject
 * @returns {PhSim.phSimCollision[]}
 * 
 */

PhSim.prototype.getCollisionList = function(dynObject) {

	var a = [];

	this.matterJSEngine.pairs.list.forEach(function(c){
		if(c.bodyA.plugin.dynObject === dynObject || c.bodyB.plugin.dynObject === dynObject) {
			var p = new PhSim.Events.PhSimCollision();
			p.bodyA = c.bodyA.plugin.dynObject;
			p.bodyB = c.bodyA.plugin.dynObject;
			p.matter = c;
			a.push(p);
		}
	});

	return a;

}

PhSim.prototype.getCollidingMatterBodies = function(body) {

	var a = [];

	for(var i = 0; i < this.matterJSEngine.pairs.list.length; i++) {
		
		var o = this.matterJSEngine.pairs.list[i];

		if(o.bodyA === body) {
			a.push(o.bodyB);
		}

		if(o.bodyB === body) {
			a.push(o.bodyA);
		}
	
	}

	return a;

}

/**
 * 
 * Get array of Dynamic Object colliding some specified colliding object.
 * 
 * @function
 * @param {PhSim.DynObject} dynObject - Dynamic Object
 * @returns {PhSim.DynObject[]}
 * 
 */

PhSim.prototype.getCollidingObjects = function(dynObject) {
	
	var a = this.getCollisionList(dynObject);
	var b = []

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

		if(a[i].matter.bodyA.plugin.id === dynObject.id) {
			b.push(a[i].bodyB);
		}

		if(a[i].matter.bodyB.plugin.id === dynObject.id) {
			b.push(a[i].bodyA);		
		}

	}

	return b;

}

/**
 * Get senor classes
 * 
 * @function
 * @param {PhSim.DynObject} dynObject 
 * @returns {String[]}
 */

PhSim.Query.getSensorClasses = function(dynObject) {

	if(dynObject.sensorClass) {
		var a = dynObject.sensorClass;
		var b = a.split(" ");
		return b;
	}

	else {
		return [];
	}
}

/**
 * 
 * Check if two objects share at least one sensor class
 * 
 * @function
 * @param {PhSim.DynObject} dynObjectA 
 * @param {PhSim.DynObject} dynObjectB 
 * @returns {Boolean}
 */

PhSim.Query.sameSensorClasses = function(dynObjectA,dynObjectB) {
	return PhSim.Query.intersectionExists(PhSim.Query.getSensorClasses(dynObjectA),PhSim.Query.getSensorClasses(dynObjectB));
}

/**
 * Sees if `array1` and `array2` share at least one element.
 * 
 * @param {Array} array1 
 * @param {Array} array2
 * @returns {Boolean} 
 */

PhSim.Query.intersectionExists = function(array1,array2) {

	for(var i = 0; i < array1.length; i++) {
		var a = array2.indexOf(array1[i]);
		if(a !== -1) {
			return true;
		}
	}

	return false;
}

/**
 * 
 * Get objects colliding some object that share the same sensor classes.
 * 
 * @function
 * @param {PhSim} phSim - PhSim instance
 * @param {PhSim.DynObject} dynObject - Object to check for colliding sensor objects
 * @returns {PhSim.DynObject[]} 
 */

PhSim.Query.getCollidingSensorObjects = function(phSim,dynObject) {

	var a = phSim.getCollisionList(dynObject);
	var b = []

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

		var dynCol = a[i]
		var matterCol = dynCol.matter;

		if(matterCol.bodyA.plugin.dynObject.id === dynObject.id && PhSim.Query.sameSensorClasses(dynObject,dynCol.bodyB)) {
			b.push(dynCol.bodyB);
		}

		if(matterCol.bodyB.plugin.dynObject.id === dynObject.id && PhSim.Query.sameSensorClasses(dynObject,dynCol.bodyA)) {
			b.push(dynCol.bodyA);		
		}

	}

	return b;
}

/**
 * 
 * Get objects colliding some object that share the same sensor classes.
 * 
 * 
 * @function
 * @param {PhSim.DynObject} dynObject - Object to check for colliding sensor objects
 * @returns {PhSim.DynObject[]} 
 */

PhSim.prototype.getCollidingSensorObjects = function(dynObject) {
	//return PhSim.Query.getCollidingSensorObjects(this,dynObject)

	var a = this.getCollisionList(dynObject);
	var b = []

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

		var dynCol = a[i]
		var matterCol = dynCol.matter;

		if(matterCol.bodyA.plugin.dynObject.id === dynObject.id && PhSim.Query.sameSensorClasses(dynObject,dynCol.bodyB)) {
			b.push(dynCol.bodyB);
		}

		if(matterCol.bodyB.plugin.dynObject.id === dynObject.id && PhSim.Query.sameSensorClasses(dynObject,dynCol.bodyA)) {
			b.push(dynCol.bodyA);		
		}

	}

	return b;
}

/**
 * @function
 * @param {PhSim.DynObject} dynObject 
 * @returns {Boolean}
 */

PhSim.prototype.inSensorCollision = function(dynObject) {
	
	var a = this.getCollidingSensorObjects(dynObject); 
	
	if(a.length > 0) {
		return true;	
	}

	else {
		return false;
	}
}

/**
 * @function
 * @param {Number} cx - x-coordinate of upper left corner.
 * @param {Number} cy - y-coordinate of upper left corner.
 * @param {Number} cw - width of rectangle
 * @param {Number} ch - height of rectangle
 * @param {Number} px - x-coordinate of point to be checked.
 * @param {Number} py - y-coordinate of point to be checked.
 * @returns {Boolean}
 */

PhSim.Query.isPointInRawRectangle = function(cx,cy,cw,ch,px,py) {
	
	var cond = (cx < px) && (px < cx + cw) && (cy < py) && (py < cy + ch) 

	if(cond) {
		return true;
	}

	else {
		return false;
	}
}

/**
 * 
 * Get object that checks the collision relations between two objects
 * 
 * @function
 * @param {PhSim.DynObject} dynObjectA - The first object
 * @param {PhSim.DynObject} dynObjectB - The second object
 * @returns {PhSim.CollisionReport} - A collision report that updates itself after each update
 */

PhSim.prototype.getCollisionChecker = function(dynObjectA,dynObjectB) {

	var self = this;
	var report = new PhSim.CollisionReport();
	report.before = null;
	report.current = null;
	report.difference = null;
	report.objectA = dynObjectA;
	report.objectB = dynObjectB;

	this.on("beforeupdate",function() {
		report.before = self.collided(dynObjectA,dynObjectB);
	});

	this.on("afterupdate",function() {
		report.current = self.collided(dynObjectA,dynObjectB);
		report.difference = report.current - report.before;
		if(report.difference) {
			var eventObj = new PhSim.Events.PhSimDynEvent();
			eventObj.report = report;
			eventObj.difference = report.difference;
			self.callEventClass("collisionchange",self,eventObj);
		}

	})

	return report;
}