commit
f966206486
9 changed files with 11850 additions and 0 deletions
@ -0,0 +1,50 @@
|
||||
<!-- |
||||
Copyright 2016 Harmen de Weerd |
||||
Copyright 2017 Johannes Keyser |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
--> |
||||
|
||||
<html> |
||||
<head> |
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8"> |
||||
<title>Lemmings</title> |
||||
<script language="JavaScript" src="pathseg.js"></script> |
||||
<script language="JavaScript" src="mouse.js"></script> |
||||
<script language="JavaScript" src="graphics.js"></script> |
||||
<script language="JavaScript" src="lemming_sim_student.js"></script> |
||||
<script language="JavaScript" src="matter.js"></script> |
||||
<script language="JavaScript" src="jquery-1.11.0.min.js"></script> |
||||
<script language="JavaScript" src="decomp.js"></script> |
||||
</head> |
||||
<body onload="init();"> |
||||
<object id="robotbodySVG" type="image/svg+xml" data="robotbody.svg" width=0 ></object> |
||||
<canvas id="arenaLemming" width="450" height="450" style="cursor: initial;"></canvas> |
||||
<br> |
||||
<table border="0"> |
||||
<tbody> |
||||
<tr><th colspan="2" align="center">Simulation properties</th><td></td></tr> |
||||
<tr><td>Simulation steps: <span id="SimStepLabel"></span></td></tr> |
||||
</tr> |
||||
<tr><br></tr> |
||||
<tr><th colspan="2" align="center">Selected robot info</th></tr> |
||||
<tr> |
||||
<td colspan="1" align="center"> |
||||
<canvas id="bayLemming" width="110" height="110" style="border:solid 2px black;"></canvas> |
||||
</td> |
||||
<td>Sensor values<span id="SensorLabel"></span></td> |
||||
</tr> |
||||
</tbody></table> |
||||
</body> |
||||
</html> |
@ -0,0 +1,670 @@
|
||||
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.decomp=e():"undefined"!=typeof global?global.decomp=e():"undefined"!=typeof self&&(self.decomp=e())}(function(){var define,module,exports; |
||||
return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ |
||||
var Scalar = require('./Scalar'); |
||||
|
||||
module.exports = Line; |
||||
|
||||
/** |
||||
* Container for line-related functions |
||||
* @class Line |
||||
*/ |
||||
function Line(){}; |
||||
|
||||
/** |
||||
* Compute the intersection between two lines. |
||||
* @static |
||||
* @method lineInt |
||||
* @param {Array} l1 Line vector 1 |
||||
* @param {Array} l2 Line vector 2 |
||||
* @param {Number} precision Precision to use when checking if the lines are parallel |
||||
* @return {Array} The intersection point. |
||||
*/ |
||||
Line.lineInt = function(l1,l2,precision){ |
||||
precision = precision || 0; |
||||
var i = [0,0]; // point
|
||||
var a1, b1, c1, a2, b2, c2, det; // scalars
|
||||
a1 = l1[1][1] - l1[0][1]; |
||||
b1 = l1[0][0] - l1[1][0]; |
||||
c1 = a1 * l1[0][0] + b1 * l1[0][1]; |
||||
a2 = l2[1][1] - l2[0][1]; |
||||
b2 = l2[0][0] - l2[1][0]; |
||||
c2 = a2 * l2[0][0] + b2 * l2[0][1]; |
||||
det = a1 * b2 - a2*b1; |
||||
if (!Scalar.eq(det, 0, precision)) { // lines are not parallel
|
||||
i[0] = (b2 * c1 - b1 * c2) / det; |
||||
i[1] = (a1 * c2 - a2 * c1) / det; |
||||
} |
||||
return i; |
||||
}; |
||||
|
||||
/** |
||||
* Checks if two line segments intersects. |
||||
* @method segmentsIntersect |
||||
* @param {Array} p1 The start vertex of the first line segment. |
||||
* @param {Array} p2 The end vertex of the first line segment. |
||||
* @param {Array} q1 The start vertex of the second line segment. |
||||
* @param {Array} q2 The end vertex of the second line segment. |
||||
* @return {Boolean} True if the two line segments intersect |
||||
*/ |
||||
Line.segmentsIntersect = function(p1, p2, q1, q2){ |
||||
var dx = p2[0] - p1[0]; |
||||
var dy = p2[1] - p1[1]; |
||||
var da = q2[0] - q1[0]; |
||||
var db = q2[1] - q1[1]; |
||||
|
||||
// segments are parallel
|
||||
if(da*dy - db*dx == 0) |
||||
return false; |
||||
|
||||
var s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx) |
||||
var t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy) |
||||
|
||||
return (s>=0 && s<=1 && t>=0 && t<=1); |
||||
}; |
||||
|
||||
|
||||
},{"./Scalar":4}],2:[function(require,module,exports){ |
||||
module.exports = Point; |
||||
|
||||
/** |
||||
* Point related functions |
||||
* @class Point |
||||
*/ |
||||
function Point(){}; |
||||
|
||||
/** |
||||
* Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order. |
||||
* @static |
||||
* @method area |
||||
* @param {Array} a |
||||
* @param {Array} b |
||||
* @param {Array} c |
||||
* @return {Number} |
||||
*/ |
||||
Point.area = function(a,b,c){ |
||||
return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))); |
||||
}; |
||||
|
||||
Point.left = function(a,b,c){ |
||||
return Point.area(a,b,c) > 0; |
||||
}; |
||||
|
||||
Point.leftOn = function(a,b,c) { |
||||
return Point.area(a, b, c) >= 0; |
||||
}; |
||||
|
||||
Point.right = function(a,b,c) { |
||||
return Point.area(a, b, c) < 0; |
||||
}; |
||||
|
||||
Point.rightOn = function(a,b,c) { |
||||
return Point.area(a, b, c) <= 0; |
||||
}; |
||||
|
||||
var tmpPoint1 = [], |
||||
tmpPoint2 = []; |
||||
|
||||
/** |
||||
* Check if three points are collinear |
||||
* @method collinear |
||||
* @param {Array} a |
||||
* @param {Array} b |
||||
* @param {Array} c |
||||
* @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision. |
||||
* @return {Boolean} |
||||
*/ |
||||
Point.collinear = function(a,b,c,thresholdAngle) { |
||||
if(!thresholdAngle) |
||||
return Point.area(a, b, c) == 0; |
||||
else { |
||||
var ab = tmpPoint1, |
||||
bc = tmpPoint2; |
||||
|
||||
ab[0] = b[0]-a[0]; |
||||
ab[1] = b[1]-a[1]; |
||||
bc[0] = c[0]-b[0]; |
||||
bc[1] = c[1]-b[1]; |
||||
|
||||
var dot = ab[0]*bc[0] + ab[1]*bc[1], |
||||
magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]), |
||||
magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]), |
||||
angle = Math.acos(dot/(magA*magB)); |
||||
return angle < thresholdAngle; |
||||
} |
||||
}; |
||||
|
||||
Point.sqdist = function(a,b){ |
||||
var dx = b[0] - a[0]; |
||||
var dy = b[1] - a[1]; |
||||
return dx * dx + dy * dy; |
||||
}; |
||||
|
||||
},{}],3:[function(require,module,exports){ |
||||
var Line = require("./Line") |
||||
, Point = require("./Point") |
||||
, Scalar = require("./Scalar") |
||||
|
||||
module.exports = Polygon; |
||||
|
||||
/** |
||||
* Polygon class. |
||||
* @class Polygon |
||||
* @constructor |
||||
*/ |
||||
function Polygon(){ |
||||
|
||||
/** |
||||
* Vertices that this polygon consists of. An array of array of numbers, example: [[0,0],[1,0],..] |
||||
* @property vertices |
||||
* @type {Array} |
||||
*/ |
||||
this.vertices = []; |
||||
} |
||||
|
||||
/** |
||||
* Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. |
||||
* @method at |
||||
* @param {Number} i |
||||
* @return {Array} |
||||
*/ |
||||
Polygon.prototype.at = function(i){ |
||||
var v = this.vertices, |
||||
s = v.length; |
||||
return v[i < 0 ? i % s + s : i % s]; |
||||
}; |
||||
|
||||
/** |
||||
* Get first vertex |
||||
* @method first |
||||
* @return {Array} |
||||
*/ |
||||
Polygon.prototype.first = function(){ |
||||
return this.vertices[0]; |
||||
}; |
||||
|
||||
/** |
||||
* Get last vertex |
||||
* @method last |
||||
* @return {Array} |
||||
*/ |
||||
Polygon.prototype.last = function(){ |
||||
return this.vertices[this.vertices.length-1]; |
||||
}; |
||||
|
||||
/** |
||||
* Clear the polygon data |
||||
* @method clear |
||||
* @return {Array} |
||||
*/ |
||||
Polygon.prototype.clear = function(){ |
||||
this.vertices.length = 0; |
||||
}; |
||||
|
||||
/** |
||||
* Append points "from" to "to"-1 from an other polygon "poly" onto this one. |
||||
* @method append |
||||
* @param {Polygon} poly The polygon to get points from. |
||||
* @param {Number} from The vertex index in "poly". |
||||
* @param {Number} to The end vertex index in "poly". Note that this vertex is NOT included when appending. |
||||
* @return {Array} |
||||
*/ |
||||
Polygon.prototype.append = function(poly,from,to){ |
||||
if(typeof(from) == "undefined") throw new Error("From is not given!"); |
||||
if(typeof(to) == "undefined") throw new Error("To is not given!"); |
||||
|
||||
if(to-1 < from) throw new Error("lol1"); |
||||
if(to > poly.vertices.length) throw new Error("lol2"); |
||||
if(from < 0) throw new Error("lol3"); |
||||
|
||||
for(var i=from; i<to; i++){ |
||||
this.vertices.push(poly.vertices[i]); |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Make sure that the polygon vertices are ordered counter-clockwise. |
||||
* @method makeCCW |
||||
*/ |
||||
Polygon.prototype.makeCCW = function(){ |
||||
var br = 0, |
||||
v = this.vertices; |
||||
|
||||
// find bottom right point
|
||||
for (var i = 1; i < this.vertices.length; ++i) { |
||||
if (v[i][1] < v[br][1] || (v[i][1] == v[br][1] && v[i][0] > v[br][0])) { |
||||
br = i; |
||||
} |
||||
} |
||||
|
||||
// reverse poly if clockwise
|
||||
if (!Point.left(this.at(br - 1), this.at(br), this.at(br + 1))) { |
||||
this.reverse(); |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Reverse the vertices in the polygon |
||||
* @method reverse |
||||
*/ |
||||
Polygon.prototype.reverse = function(){ |
||||
var tmp = []; |
||||
for(var i=0, N=this.vertices.length; i!==N; i++){ |
||||
tmp.push(this.vertices.pop()); |
||||
} |
||||
this.vertices = tmp; |
||||
}; |
||||
|
||||
/** |
||||
* Check if a point in the polygon is a reflex point |
||||
* @method isReflex |
||||
* @param {Number} i |
||||
* @return {Boolean} |
||||
*/ |
||||
Polygon.prototype.isReflex = function(i){ |
||||
return Point.right(this.at(i - 1), this.at(i), this.at(i + 1)); |
||||
}; |
||||
|
||||
var tmpLine1=[], |
||||
tmpLine2=[]; |
||||
|
||||
/** |
||||
* Check if two vertices in the polygon can see each other |
||||
* @method canSee |
||||
* @param {Number} a Vertex index 1 |
||||
* @param {Number} b Vertex index 2 |
||||
* @return {Boolean} |
||||
*/ |
||||
Polygon.prototype.canSee = function(a,b) { |
||||
var p, dist, l1=tmpLine1, l2=tmpLine2; |
||||
|
||||
if (Point.leftOn(this.at(a + 1), this.at(a), this.at(b)) && Point.rightOn(this.at(a - 1), this.at(a), this.at(b))) { |
||||
return false; |
||||
} |
||||
dist = Point.sqdist(this.at(a), this.at(b)); |
||||
for (var i = 0; i !== this.vertices.length; ++i) { // for each edge
|
||||
if ((i + 1) % this.vertices.length === a || i === a) // ignore incident edges
|
||||
continue; |
||||
if (Point.leftOn(this.at(a), this.at(b), this.at(i + 1)) && Point.rightOn(this.at(a), this.at(b), this.at(i))) { // if diag intersects an edge
|
||||
l1[0] = this.at(a); |
||||
l1[1] = this.at(b); |
||||
l2[0] = this.at(i); |
||||
l2[1] = this.at(i + 1); |
||||
p = Line.lineInt(l1,l2); |
||||
if (Point.sqdist(this.at(a), p) < dist) { // if edge is blocking visibility to b
|
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
}; |
||||
|
||||
/** |
||||
* Copy the polygon from vertex i to vertex j. |
||||
* @method copy |
||||
* @param {Number} i |
||||
* @param {Number} j |
||||
* @param {Polygon} [targetPoly] Optional target polygon to save in. |
||||
* @return {Polygon} The resulting copy. |
||||
*/ |
||||
Polygon.prototype.copy = function(i,j,targetPoly){ |
||||
var p = targetPoly || new Polygon(); |
||||
p.clear(); |
||||
if (i < j) { |
||||
// Insert all vertices from i to j
|
||||
for(var k=i; k<=j; k++) |
||||
p.vertices.push(this.vertices[k]); |
||||
|
||||
} else { |
||||
|
||||
// Insert vertices 0 to j
|
||||
for(var k=0; k<=j; k++) |
||||
p.vertices.push(this.vertices[k]); |
||||
|
||||
// Insert vertices i to end
|
||||
for(var k=i; k<this.vertices.length; k++) |
||||
p.vertices.push(this.vertices[k]); |
||||
} |
||||
|
||||
return p; |
||||
}; |
||||
|
||||
/** |
||||
* Decomposes the polygon into convex pieces. Returns a list of edges [[p1,p2],[p2,p3],...] that cuts the polygon. |
||||
* Note that this algorithm has complexity O(N^4) and will be very slow for polygons with many vertices. |
||||
* @method getCutEdges |
||||
* @return {Array} |
||||
*/ |
||||
Polygon.prototype.getCutEdges = function() { |
||||
var min=[], tmp1=[], tmp2=[], tmpPoly = new Polygon(); |
||||
var nDiags = Number.MAX_VALUE; |
||||
|
||||
for (var i = 0; i < this.vertices.length; ++i) { |
||||
if (this.isReflex(i)) { |
||||
for (var j = 0; j < this.vertices.length; ++j) { |
||||
if (this.canSee(i, j)) { |
||||
tmp1 = this.copy(i, j, tmpPoly).getCutEdges(); |
||||
tmp2 = this.copy(j, i, tmpPoly).getCutEdges(); |
||||
|
||||
for(var k=0; k<tmp2.length; k++) |
||||
tmp1.push(tmp2[k]); |
||||
|
||||
if (tmp1.length < nDiags) { |
||||
min = tmp1; |
||||
nDiags = tmp1.length; |
||||
min.push([this.at(i), this.at(j)]); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return min; |
||||
}; |
||||
|
||||
/** |
||||
* Decomposes the polygon into one or more convex sub-Polygons. |
||||
* @method decomp |
||||
* @return {Array} An array or Polygon objects. |
||||
*/ |
||||
Polygon.prototype.decomp = function(){ |
||||
var edges = this.getCutEdges(); |
||||
if(edges.length > 0) |
||||
return this.slice(edges); |
||||
else |
||||
return [this]; |
||||
}; |
||||
|
||||
/** |
||||
* Slices the polygon given one or more cut edges. If given one, this function will return two polygons (false on failure). If many, an array of polygons. |
||||
* @method slice |
||||
* @param {Array} cutEdges A list of edges, as returned by .getCutEdges() |
||||
* @return {Array} |
||||
*/ |
||||
Polygon.prototype.slice = function(cutEdges){ |
||||
if(cutEdges.length == 0) return [this]; |
||||
if(cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length==2 && cutEdges[0][0] instanceof Array){ |
||||
|
||||
var polys = [this]; |
||||
|
||||
for(var i=0; i<cutEdges.length; i++){ |
||||
var cutEdge = cutEdges[i]; |
||||
// Cut all polys
|
||||
for(var j=0; j<polys.length; j++){ |
||||
var poly = polys[j]; |
||||
var result = poly.slice(cutEdge); |
||||
if(result){ |
||||
// Found poly! Cut and quit
|
||||
polys.splice(j,1); |
||||
polys.push(result[0],result[1]); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return polys; |
||||
} else { |
||||
|
||||
// Was given one edge
|
||||
var cutEdge = cutEdges; |
||||
var i = this.vertices.indexOf(cutEdge[0]); |
||||
var j = this.vertices.indexOf(cutEdge[1]); |
||||
|
||||
if(i != -1 && j != -1){ |
||||
return [this.copy(i,j), |
||||
this.copy(j,i)]; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Checks that the line segments of this polygon do not intersect each other. |
||||
* @method isSimple |
||||
* @param {Array} path An array of vertices e.g. [[0,0],[0,1],...] |
||||
* @return {Boolean} |
||||
* @todo Should it check all segments with all others? |
||||
*/ |
||||
Polygon.prototype.isSimple = function(){ |
||||
var path = this.vertices; |
||||
// Check
|
||||
for(var i=0; i<path.length-1; i++){ |
||||
for(var j=0; j<i-1; j++){ |
||||
if(Line.segmentsIntersect(path[i], path[i+1], path[j], path[j+1] )){ |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Check the segment between the last and the first point to all others
|
||||
for(var i=1; i<path.length-2; i++){ |
||||
if(Line.segmentsIntersect(path[0], path[path.length-1], path[i], path[i+1] )){ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
}; |
||||
|
||||
function getIntersectionPoint(p1, p2, q1, q2, delta){ |
||||
delta = delta || 0; |
||||
var a1 = p2[1] - p1[1]; |
||||
var b1 = p1[0] - p2[0]; |
||||
var c1 = (a1 * p1[0]) + (b1 * p1[1]); |
||||
var a2 = q2[1] - q1[1]; |
||||
var b2 = q1[0] - q2[0]; |
||||
var c2 = (a2 * q1[0]) + (b2 * q1[1]); |
||||
var det = (a1 * b2) - (a2 * b1); |
||||
|
||||
if(!Scalar.eq(det,0,delta)) |
||||
return [((b2 * c1) - (b1 * c2)) / det, ((a1 * c2) - (a2 * c1)) / det] |
||||
else |
||||
return [0,0] |
||||
} |
||||
|
||||
/** |
||||
* Quickly decompose the Polygon into convex sub-polygons. |
||||
* @method quickDecomp |
||||
* @param {Array} result |
||||
* @param {Array} [reflexVertices] |
||||
* @param {Array} [steinerPoints] |
||||
* @param {Number} [delta] |
||||
* @param {Number} [maxlevel] |
||||
* @param {Number} [level] |
||||
* @return {Array} |
||||
*/ |
||||
Polygon.prototype.quickDecomp = function(result,reflexVertices,steinerPoints,delta,maxlevel,level){ |
||||
maxlevel = maxlevel || 100; |
||||
level = level || 0; |
||||
delta = delta || 25; |
||||
result = typeof(result)!="undefined" ? result : []; |
||||
reflexVertices = reflexVertices || []; |
||||
steinerPoints = steinerPoints || []; |
||||
|
||||
var upperInt=[0,0], lowerInt=[0,0], p=[0,0]; // Points
|
||||
var upperDist=0, lowerDist=0, d=0, closestDist=0; // scalars
|
||||
var upperIndex=0, lowerIndex=0, closestIndex=0; // Integers
|
||||
var lowerPoly=new Polygon(), upperPoly=new Polygon(); // polygons
|
||||
var poly = this, |
||||
v = this.vertices; |
||||
|
||||
if(v.length < 3) return result; |
||||
|
||||
level++; |
||||
if(level > maxlevel){ |
||||
console.warn("quickDecomp: max level ("+maxlevel+") reached."); |
||||
return result; |
||||
} |
||||
|
||||
for (var i = 0; i < this.vertices.length; ++i) { |
||||
if (poly.isReflex(i)) { |
||||
reflexVertices.push(poly.vertices[i]); |
||||
upperDist = lowerDist = Number.MAX_VALUE; |
||||
|
||||
|
||||
for (var j = 0; j < this.vertices.length; ++j) { |
||||
if (Point.left(poly.at(i - 1), poly.at(i), poly.at(j)) |
||||
&& Point.rightOn(poly.at(i - 1), poly.at(i), poly.at(j - 1))) { // if line intersects with an edge
|
||||
p = getIntersectionPoint(poly.at(i - 1), poly.at(i), poly.at(j), poly.at(j - 1)); // find the point of intersection
|
||||
if (Point.right(poly.at(i + 1), poly.at(i), p)) { // make sure it's inside the poly
|
||||
d = Point.sqdist(poly.vertices[i], p); |
||||
if (d < lowerDist) { // keep only the closest intersection
|
||||
lowerDist = d; |
||||
lowerInt = p; |
||||
lowerIndex = j; |
||||
} |
||||
} |
||||
} |
||||
if (Point.left(poly.at(i + 1), poly.at(i), poly.at(j + 1)) |
||||
&& Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { |
||||
p = getIntersectionPoint(poly.at(i + 1), poly.at(i), poly.at(j), poly.at(j + 1)); |
||||
if (Point.left(poly.at(i - 1), poly.at(i), p)) { |
||||
d = Point.sqdist(poly.vertices[i], p); |
||||
if (d < upperDist) { |
||||
upperDist = d; |
||||
upperInt = p; |
||||
upperIndex = j; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// if there are no vertices to connect to, choose a point in the middle
|
||||
if (lowerIndex == (upperIndex + 1) % this.vertices.length) { |
||||
//console.log("Case 1: Vertex("+i+"), lowerIndex("+lowerIndex+"), upperIndex("+upperIndex+"), poly.size("+this.vertices.length+")");
|
||||
p[0] = (lowerInt[0] + upperInt[0]) / 2; |
||||
p[1] = (lowerInt[1] + upperInt[1]) / 2; |
||||
steinerPoints.push(p); |
||||
|
||||
if (i < upperIndex) { |
||||
//lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1);
|
||||
lowerPoly.append(poly, i, upperIndex+1); |
||||
lowerPoly.vertices.push(p); |
||||
upperPoly.vertices.push(p); |
||||
if (lowerIndex != 0){ |
||||
//upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end());
|
||||
upperPoly.append(poly,lowerIndex,poly.vertices.length); |
||||
} |
||||
//upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1);
|
||||
upperPoly.append(poly,0,i+1); |
||||
} else { |
||||
if (i != 0){ |
||||
//lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end());
|
||||
lowerPoly.append(poly,i,poly.vertices.length); |
||||
} |
||||
//lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1);
|
||||
lowerPoly.append(poly,0,upperIndex+1); |
||||
lowerPoly.vertices.push(p); |
||||
upperPoly.vertices.push(p); |
||||
//upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1);
|
||||
upperPoly.append(poly,lowerIndex,i+1); |
||||
} |
||||
} else { |
||||
// connect to the closest point within the triangle
|
||||
//console.log("Case 2: Vertex("+i+"), closestIndex("+closestIndex+"), poly.size("+this.vertices.length+")\n");
|
||||
|
||||
if (lowerIndex > upperIndex) { |
||||
upperIndex += this.vertices.length; |
||||
} |
||||
closestDist = Number.MAX_VALUE; |
||||
|
||||
if(upperIndex < lowerIndex){ |
||||
return result; |
||||
} |
||||
|
||||
for (var j = lowerIndex; j <= upperIndex; ++j) { |
||||
if (Point.leftOn(poly.at(i - 1), poly.at(i), poly.at(j)) |
||||
&& Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { |
||||
d = Point.sqdist(poly.at(i), poly.at(j)); |
||||
if (d < closestDist) { |
||||
closestDist = d; |
||||
closestIndex = j % this.vertices.length; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (i < closestIndex) { |
||||
lowerPoly.append(poly,i,closestIndex+1); |
||||
if (closestIndex != 0){ |
||||
upperPoly.append(poly,closestIndex,v.length); |
||||
} |
||||
upperPoly.append(poly,0,i+1); |
||||
} else { |
||||
if (i != 0){ |
||||
lowerPoly.append(poly,i,v.length); |
||||
} |
||||
lowerPoly.append(poly,0,closestIndex+1); |
||||
upperPoly.append(poly,closestIndex,i+1); |
||||
} |
||||
} |
||||
|
||||
// solve smallest poly first
|
||||
if (lowerPoly.vertices.length < upperPoly.vertices.length) { |
||||
lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); |
||||
upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); |
||||
} else { |
||||
upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); |
||||
lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
} |
||||
result.push(this); |
||||
|
||||
return result; |
||||
}; |
||||
|
||||
/** |
||||
* Remove collinear points in the polygon. |
||||
* @method removeCollinearPoints |
||||
* @param {Number} [precision] The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision. |
||||
* @return {Number} The number of points removed |
||||
*/ |
||||
Polygon.prototype.removeCollinearPoints = function(precision){ |
||||
var num = 0; |
||||
for(var i=this.vertices.length-1; this.vertices.length>3 && i>=0; --i){ |
||||
if(Point.collinear(this.at(i-1),this.at(i),this.at(i+1),precision)){ |
||||
// Remove the middle point
|
||||
this.vertices.splice(i%this.vertices.length,1); |
||||
i--; // Jump one point forward. Otherwise we may get a chain removal
|
||||
num++; |
||||
} |
||||
} |
||||
return num; |
||||
}; |
||||
|
||||
},{"./Line":1,"./Point":2,"./Scalar":4}],4:[function(require,module,exports){ |
||||
module.exports = Scalar; |
||||
|
||||
/** |
||||
* Scalar functions |
||||
* @class Scalar |
||||
*/ |
||||
function Scalar(){} |
||||
|
||||
/** |
||||
* Check if two scalars are equal |
||||
* @static |
||||
* @method eq |
||||
* @param {Number} a |
||||
* @param {Number} b |
||||
* @param {Number} [precision] |
||||
* @return {Boolean} |
||||
*/ |
||||
Scalar.eq = function(a,b,precision){ |
||||
precision = precision || 0; |
||||
return Math.abs(a-b) < precision; |
||||
}; |
||||
|
||||
},{}],5:[function(require,module,exports){ |
||||
module.exports = { |
||||
Polygon : require("./Polygon"), |
||||
Point : require("./Point"), |
||||
}; |
||||
|
||||
},{"./Point":2,"./Polygon":3}]},{},[5]) |
||||
(5) |
||||
}); |
||||
; |
@ -0,0 +1,205 @@
|
||||
/* Interactive elements |
||||
* Model script |
||||
* Copyright 2016 Harmen de Weerd |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
showGraphicsWarnings = true; |
||||
GraphicsConstants = { |
||||
RESIZE_AREA: 5, |
||||
ACTION_NONE: 0, |
||||
ACTION_DRAG: 1, |
||||
|
||||
ACTION_E_RESIZE: 2, |
||||
ACTION_W_RESIZE: 4, |
||||
ACTION_S_RESIZE: 8, |
||||
ACTION_N_RESIZE: 16, |
||||
|
||||
ACTION_NE_RESIZE: 18, |
||||
ACTION_NW_RESIZE: 20, |
||||
ACTION_SE_RESIZE: 10, |
||||
ACTION_SW_RESIZE: 12 |
||||
}; |
||||
|
||||
function ImageElement(img) { |
||||
this.image = img; |
||||
this.x = 0; |
||||
this.y = 0; |
||||
this.rotation = 0; |
||||
this.getWidth = function() { |
||||
return this.image.width; |
||||
}; |
||||
this.getHeight = function() { |
||||
return this.image.height; |
||||
}; |
||||
this.draw = function(ctx, x = this.x, y = this.y) { |
||||
var centerX, centerY; |
||||
centerX = Math.floor(this.image.width/2); |
||||
centerY = Math.floor(this.image.height/2); |
||||
ctx.save(); |
||||
ctx.translate(x + centerX, y + centerY); |
||||
ctx.rotate(this.rotation); |
||||
ctx.translate(-centerX, -centerY); |
||||
ctx.drawImage(this.image, 0, 0); |
||||
ctx.restore(); |
||||
}; |
||||
} |
||||
|
||||
function makeInteractiveElement(graphicsElem, container) { |
||||
graphicsElem.repertoire = GraphicsConstants.ACTION_DRAG; |
||||
graphicsElem.isDraggable = function() { |
||||
return (this.repertoire & GraphicsConstants.ACTION_DRAG) > 0; |
||||
}; |
||||
graphicsElem.isResizable = function() { |
||||
return (this.repertoire & GraphicsConstants.ACTION_RESIZE) > 0; |
||||
}; |
||||
graphicsElem.currentAction = GraphicsConstants.ACTION_NONE; |
||||
/* graphicsElem.onResize = null; |
||||
graphicsElem.onDrag = null; |
||||
graphicsElem.onDragging = null; |
||||
graphicsElem.onDrop = null;*/ |
||||
graphicsElem.startAction = function(event) { |
||||
this.reference = {x: event.target.mouse.x, y: event.target.mouse.y}; |
||||
this.originalDimensions = {x: this.x, |
||||
y: this.y, |
||||
width: this.getWidth(), |
||||
height: this.getHeight()}; |
||||
this.currentAction = this.getLocalAction(event.target.mouse.x - this.x, |
||||
event.target.mouse.y - this.y); |
||||
switch (this.currentAction) { |
||||
case GraphicsConstants.ACTION_DRAG: |
||||
if (this.onDrag && !this.onDrag(this, event)) { |
||||
this.currentAction = GraphicsConstants.ACTION_NONE; |
||||
} |
||||
break; |
||||
case GraphicsConstants.ACTION_SE_RESIZE: |
||||
case GraphicsConstants.ACTION_SW_RESIZE: |
||||
case GraphicsConstants.ACTION_S_RESIZE: |
||||
case GraphicsConstants.ACTION_NW_RESIZE: |
||||
case GraphicsConstants.ACTION_NE_RESIZE: |
||||
case GraphicsConstants.ACTION_W_RESIZE: |
||||
case GraphicsConstants.ACTION_S_RESIZE: |
||||
case GraphicsConstants.ACTION_N_RESIZE: |
||||
case GraphicsConstants.ACTION_E_RESIZE: |
||||
if (this.onResize && !this.onResize(this, event, this.currentAction)) { |
||||
this.currentAction = GraphicsConstants.ACTION_NONE; |
||||
} |
||||
break; |
||||
} |
||||
}; |
||||
graphicsElem.endAction = function(event) { |
||||
if (this.currentAction == GraphicsConstants.ACTION_DRAG && this.onDrop) { |
||||
this.onDrop(this, event); |
||||
} |
||||
this.currentAction = GraphicsConstants.ACTION_NONE; |
||||
}; |
||||
graphicsElem.continueAction = function(event) { |
||||
var x, y; |
||||
x = event.target.mouse.x; |
||||
y = event.target.mouse.y; |
||||
if (this.constraints != null) { |
||||
x = Math.min(this.constraints.right + this.reference.x, |
||||
Math.max(this.constraints.left + this.reference.x, x)); |
||||
y = Math.min(this.constraints.bottom + this.reference.y, |
||||
Math.max(this.constraints.top + this.reference.y, y)); |
||||
} |
||||
if ((this.currentAction & GraphicsConstants.ACTION_DRAG) > 0) { |
||||
this.x = this.originalDimensions.x + x - this.reference.x; |
||||
this.y = this.originalDimensions.y + y - this.reference.y; |
||||
} else { |
||||
if ((this.currentAction & GraphicsConstants.ACTION_S_RESIZE) > 0) { |
||||
this.elem.setHeight(this.originalDimensions.height + y - this.reference.y); |
||||
} |
||||
if ((this.currentAction & GraphicsConstants.ACTION_E_RESIZE) > 0) { |
||||
this.elem.setWidth(this.originalDimensions.width + x - this.reference.x); |
||||
} |
||||
if ((this.currentAction & GraphicsConstants.ACTION_N_RESIZE) > 0) { |
||||
this.y = this.originalDimensions.y + y - this.reference.y; |
||||
this.elem.setHeight(this.originalDimensions.height - y + this.reference.y); |
||||
} |
||||
if ((this.currentAction & GraphicsConstants.ACTION_W_RESIZE) > 0) { |
||||
this.x = this.originalDimensions.x + x - this.reference.x; |
||||
this.elem.setWidth(this.originalDimensions.width - x + this.reference.x); |
||||
} |
||||
} |
||||
if (this.onDragging) { |
||||
this.onDragging(this, event.target); |
||||
} |
||||
}; |
||||
graphicsElem.getActionLabel = function(x, y) { |
||||
switch (this.getLocalAction(x,y)) { |
||||
case GraphicsConstants.ACTION_W_RESIZE: |
||||
case GraphicsConstants.ACTION_E_RESIZE: |
||||
return "col-resize"; |
||||
case GraphicsConstants.ACTION_S_RESIZE: |
||||
case GraphicsConstants.ACTION_N_RESIZE: |
||||
return "row-resize"; |
||||
case GraphicsConstants.ACTION_NW_RESIZE: |
||||
return "nw-resize"; |
||||
case GraphicsConstants.ACTION_NE_RESIZE: |
||||
return "ne-resize"; |
||||
case GraphicsConstants.ACTION_SE_RESIZE: |
||||
return "se-resize"; |
||||
case GraphicsConstants.ACTION_SW_RESIZE: |
||||
return "sw-resize"; |
||||
case GraphicsConstants.ACTION_DRAG: |
||||
return "pointer"; |
||||
default: |
||||
return undefined; |
||||
} |
||||
}; |
||||
|
||||
graphicsElem.getLocalAction = function(x, y) { |
||||
if ((this.repertoire & GraphicsConstants.ACTION_W_RESIZE) > 0 && |
||||
x < GraphicsConstants.RESIZE_AREA) { |
||||
if ((this.repertoire & GraphicsConstants.ACTION_N_RESIZE) > 0 && |
||||
y < GraphicsConstants.RESIZE_AREA) { |
||||
return GraphicsConstants.ACTION_NW_RESIZE; |
||||
} else if ((this.repertoire & GraphicsConstants.ACTION_S_RESIZE) > 0 && |
||||
y > this.getHeight() - GraphicsConstants.RESIZE_AREA) { |
||||
return GraphicsConstants.ACTION_SW_RESIZE; |
||||
} |
||||
return GraphicsConstants.ACTION_W_RESIZE; |
||||
} else if ((this.repertoire & GraphicsConstants.ACTION_E_RESIZE) > 0 && |
||||
x > this.getWidth() - GraphicsConstants.RESIZE_AREA) { |
||||
if ((this.repertoire & GraphicsConstants.ACTION_N_RESIZE) > 0 && |
||||
y < GraphicsConstants.RESIZE_AREA) { |
||||
return GraphicsConstants.ACTION_NE_RESIZE; |
||||
} else if ((this.repertoire & GraphicsConstants.ACTION_S_RESIZE) > 0 && |
||||
y > this.getHeight() - GraphicsConstants.RESIZE_AREA) { |
||||
return GraphicsConstants.ACTION_SE_RESIZE; |
||||
} |
||||
return GraphicsConstants.ACTION_E_RESIZE; |
||||
} else if ((this.repertoire & GraphicsConstants.ACTION_N_RESIZE) > 0 && |
||||
y < GraphicsConstants.RESIZE_AREA) { |
||||
return GraphicsConstants.ACTION_N_RESIZE; |
||||
} else if ((this.repertoire & GraphicsConstants.ACTION_S_RESIZE) > 0 && |
||||
y > this.getHeight() - GraphicsConstants.RESIZE_AREA) { |
||||
return GraphicsConstants.ACTION_S_RESIZE; |
||||
} |
||||
if (this.isDraggable) { |
||||
return GraphicsConstants.ACTION_DRAG; |
||||
} |
||||
return undefined; |
||||
}; |
||||
|
||||
if (container.addInteractiveElement) { |
||||
container.addInteractiveElement(graphicsElem); |
||||
} else if (showGraphicsWarnings) { |
||||
console.warn("Container does not support mouse tracking: element will not be interactive."); |
||||
} |
||||
return graphicsElem; |
||||
} |
||||
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,663 @@
|
||||
/* Lemmings - robot and GUI script. |
||||
* |
||||
* Copyright 2016 Harmen de Weerd |
||||
* Copyright 2017 Johannes Keyser, James Cooke, George Kachergis |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
|
||||
// Description of robot(s), and attached sensor(s) used by InstantiateRobot()
|
||||
RobotInfo = [ |
||||
{body: null, // for MatterJS body, added by InstantiateRobot()
|
||||
color: [255, 255, 255], // color of the robot shape
|
||||
init: {x: 50, y: 50, angle: 0}, // initial position and orientation
|
||||
sensors: [ // define an array of sensors on the robot
|
||||
// define one sensor
|
||||
{sense: senseDistance, // function handle, determines type of sensor
|
||||
minVal: 0, // minimum detectable distance, in pixels
|
||||
maxVal: 50, // maximum detectable distance, in pixels
|
||||
attachAngle: Math.PI/4, // where the sensor is mounted on robot body
|
||||
lookAngle: 0, // direction the sensor is looking (relative to center-out)
|
||||
id: 'distR', // a unique, arbitrary ID of the sensor, for printing/debugging
|
||||
color: [150, 0, 0], // sensor color [in RGB], to distinguish them
|
||||
parent: null, // robot object the sensor is attached to, added by InstantiateRobot
|
||||
value: null // sensor value, i.e. distance in pixels; updated by sense() function
|
||||
}, |
||||
// define another sensor
|
||||
{sense: senseDistance, minVal: 0, maxVal: 50, attachAngle: -Math.PI/4, |
||||
lookAngle: 0, id: 'distL', color: [0, 150, 0], parent: null, value: null |
||||
} |
||||
] |
||||
} |
||||
]; |
||||
|
||||
// Simulation settings; please change anything that you think makes sense.
|
||||
simInfo = { |
||||
maxSteps: 50000, // maximal number of simulation steps to run
|
||||
airDrag: 0.1, // "air" friction of enviroment; 0 is vacuum, 0.9 is molasses
|
||||
boxFric: 0.005, // friction between boxes during collisions
|
||||
boxMass: 0.01, // mass of boxes
|
||||
boxSize: 20, // size of the boxes, in pixels
|
||||
robotSize: 13, // approximate robot radius, in pixels (note the SVG gets scaled down)
|
||||
robotMass: 0.4, // robot mass (a.u)
|
||||
gravity: 0, // constant acceleration in Y-direction
|
||||
bayRobot: null, // currently selected robot
|
||||
baySensor: null, // currently selected sensor
|
||||
bayScale: 3, // scale within 2nd, inset canvas showing robot in it's "bay"
|
||||
doContinue: true, // whether to continue simulation, set in HTML
|
||||
debugSensors: false, // plot sensor rays and mark detected objects
|
||||
debugMouse: false, // allow dragging any object with the mouse
|
||||
engine: null, // MatterJS 2D physics engine
|
||||
world: null, // world object (composite of all objects in MatterJS engine)
|
||||
runner: null, // object for running MatterJS engine
|
||||
height: null, // set in HTML file; height of arena (world canvas), in pixels
|
||||
width: null, // set in HTML file; width of arena (world canvas), in pixels
|
||||
curSteps: 0 // increased by simStep()
|
||||
}; |
||||
|
||||
robots = new Array(); |
||||
sensors = new Array(); |
||||
|
||||
function init() { // called once when loading HTML file
|
||||
const robotBay = document.getElementById("bayLemming"), |
||||
arena = document.getElementById("arenaLemming"), |
||||
height = arena.height, |
||||
width = arena.width; |
||||
simInfo.height = height; |
||||
simInfo.width = width; |
||||
|
||||
/* Create a MatterJS engine and world. */ |
||||
simInfo.engine = Matter.Engine.create(); |
||||
simInfo.world = simInfo.engine.world; |
||||
simInfo.world.gravity.y = simInfo.gravity; |
||||
simInfo.engine.timing.timeScale = 1; |
||||
|
||||
/* Create walls and boxes, and add them to the world. */ |
||||
// note that "roles" are custom properties for rendering (not from MatterJS)
|
||||
function getWall(x, y, width, height) { |
||||
return Matter.Bodies.rectangle(x, y, width, height, |
||||
{isStatic: true, role: 'wall', |
||||
color:[150, 150, 150]}); |
||||
}; |
||||
const wall_lo = getWall(width/2, height-5, width-5, 5), |
||||
wall_hi = getWall(width/2, 5, width-5, 5), |
||||
wall_le = getWall(5, height/2, 5, height-15), |
||||
wall_ri = getWall(width-5, height/2, 5, height-15); |
||||
Matter.World.add(simInfo.world, [wall_lo, wall_hi, wall_le, wall_ri]); |
||||
|
||||
/* Add a bunch of boxes in a neat grid. */ |
||||
function getBox(x, y) { |
||||
// flip coin for red vs blue and add rgb
|
||||
colFlag = Math.round(Math.random()); // random 0,1 variable for box color
|
||||
if (colFlag == 1 ){ |
||||
color = [0, 0, 200]; |
||||
} |
||||
else { |
||||
color = [200, 0, 0]; |
||||
} |
||||
box = Matter.Bodies.rectangle(x, y, simInfo.boxSize, simInfo.boxSize, |
||||
{frictionAir: simInfo.airDrag, |
||||
friction: simInfo.boxFric, |
||||
mass: simInfo.boxMass, |
||||
role: 'box', |
||||
color: color}); |
||||
return box; |
||||
}; |
||||
|
||||
const startX = 100, startY = 100, |
||||
nBoxX = 5, nBoxY = 5, |
||||
gapX = 40, gapY = 30, |
||||
stack = Matter.Composites.stack(startX, startY, |
||||
nBoxX, nBoxY, |
||||
gapX, gapY, getBox); |
||||
Matter.World.add(simInfo.world, stack); |
||||
|
||||
/* Add debugging mouse control for dragging objects. */ |
||||
if (simInfo.debugMouse){ |
||||
const mouseConstraint = Matter.MouseConstraint.create(simInfo.engine, |
||||
{mouse: Matter.Mouse.create(arena), |
||||
// spring stiffness mouse ~ object
|
||||
constraint: {stiffness: 0.5}}); |
||||
Matter.World.add(simInfo.world, mouseConstraint); |
||||
} |
||||
// Add the tracker functions from mouse.js
|
||||
addMouseTracker(arena); |
||||
addMouseTracker(robotBay); |
||||
|
||||
/* Running the MatterJS physics engine (without rendering). */ |
||||
simInfo.runner = Matter.Runner.create({fps: 60, isFixed: false}); |
||||
Matter.Runner.start(simInfo.runner, simInfo.engine); |
||||
// register function simStep() as callback to MatterJS's engine events
|
||||
Matter.Events.on(simInfo.engine, 'tick', simStep); |
||||
|
||||
/* Create robot(s). */ |
||||
setRobotNumber(1); // requires defined simInfo.world
|
||||
loadBay(robots[0]); |
||||
|
||||
}; |
||||
|
||||
function convrgb(values) { |
||||
return 'rgb(' + values.join(', ') + ')'; |
||||
}; |
||||
|
||||
|
||||
function rotate(robot, torque=0) { |
||||
/* Apply a torque to the robot to rotate it. |
||||
* |
||||
* Parameters |
||||
* torque - rotational force to apply to the body. |
||||
* Try values around +/- 0.005. |
||||
*/ |
||||
robot.body.torque = torque; |
||||
}; |
||||
|
||||
function drive(robot, force=0) { |
||||
/* Apply a force to the robot to move it. |
||||
* |
||||
* Parameters |
||||
* force - force to apply to the body. |
||||
* Try values around +/- 0.0005. |
||||
*/ |
||||
const orientation = robot.body.angle, |
||||
force_vec = Matter.Vector.create(force, 0), |
||||
move_vec = Matter.Vector.rotate(force_vec, orientation); |
||||
Matter.Body.applyForce(robot.body, robot.body.position , move_vec); |
||||
}; |
||||
|
||||
|
||||
function senseDistance() { |
||||
/* Distance sensor simulation based on ray casting. Called from sensor |
||||
* object, returns nothing, updates a new reading into this.value. |
||||
* |
||||
* Idea: Cast a ray with a certain length from the sensor, and check |
||||
* via collision detection if objects intersect with the ray. |
||||
* To determine distance, run a Binary search on ray length. |
||||
* Note: Sensor ray needs to ignore robot (parts), or start outside of it. |
||||
* The latter is easy with the current circular shape of the robots. |
||||
* Note: Order of tests are optimized by starting with max ray length, and |
||||
* then only testing the maximal number of initially resulting objects. |
||||
* Note: The sensor's "ray" could have any other (convex) shape; |
||||
* currently it's just a very thin rectangle. |
||||
*/ |
||||
|
||||
const context = document.getElementById('arenaLemming').getContext('2d'); |
||||
var bodies = Matter.Composite.allBodies(simInfo.engine.world); |
||||
|
||||
const robotAngle = this.parent.body.angle, |
||||
attachAngle = this.attachAngle, |
||||
rayAngle = robotAngle + attachAngle + this.lookAngle; |
||||
|
||||
const rPos = this.parent.body.position, |
||||
rSize = simInfo.robotSize, |
||||
startPoint = {x: rPos.x + (rSize+1) * Math.cos(robotAngle + attachAngle), |
||||
y: rPos.y + (rSize+1) * Math.sin(robotAngle + attachAngle)}; |
||||
|
||||
function getEndpoint(rayLength) { |
||||
return {x: rPos.x + (rSize + rayLength) * Math.cos(rayAngle), |
||||
y: rPos.y + (rSize + rayLength) * Math.sin(rayAngle)}; |
||||
}; |
||||
|
||||
function sensorRay(bodies, rayLength) { |
||||
// Cast ray of supplied length and return the bodies that collide with it.
|
||||
const rayWidth = 1e-100, |
||||
endPoint = getEndpoint(rayLength); |
||||
rayX = (endPoint.x + startPoint.x) / 2, |
||||
rayY = (endPoint.y + startPoint.y) / 2, |
||||
rayRect = Matter.Bodies.rectangle(rayX, rayY, rayLength, rayWidth, |
||||
{isSensor: true, isStatic: true, |
||||
angle: rayAngle, role: 'sensor'}); |
||||
|
||||
var collidedBodies = []; |
||||
for (var bb = 0; bb < bodies.length; bb++) { |
||||
var body = bodies[bb]; |
||||
// coarse check on body boundaries, to increase performance:
|
||||
if (Matter.Bounds.overlaps(body.bounds, rayRect.bounds)) { |
||||
for (var pp = body.parts.length === 1 ? 0 : 1; pp < body.parts.length; pp++) { |
||||
var part = body.parts[pp]; |
||||
// finer, more costly check on actual geometry:
|
||||
if (Matter.Bounds.overlaps(part.bounds, rayRect.bounds)) { |
||||
const collision = Matter.SAT.collides(part, rayRect); |
||||
if (collision.collided) { |
||||
collidedBodies.push(body); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return collidedBodies; |
||||
}; |
||||
|
||||
// call 1x with full length, and check all bodies in the world;
|
||||
// in subsequent calls, only check the bodies resulting here
|
||||
var rayLength = this.maxVal; |
||||
bodies = sensorRay(bodies, rayLength); |
||||
// if some collided, search for maximal ray length without collisions
|
||||
if (bodies.length > 0) { |
||||
var lo = 0, |
||||
hi = rayLength; |
||||
while (lo < rayLength) { |
||||
if (sensorRay(bodies, rayLength).length > 0) { |
||||
hi = rayLength; |
||||
} |
||||
else { |
||||
lo = rayLength; |
||||
} |
||||
rayLength = Math.floor(lo + (hi-lo)/2); |
||||
} |
||||
} |
||||
// increase length to (barely) touch closest body (if any)
|
||||
rayLength += 1; |
||||
bodies = sensorRay(bodies, rayLength); |
||||
|
||||
if (simInfo.debugSensors) { // if invisible, check order of object drawing
|
||||
// draw the resulting ray
|
||||
endPoint = getEndpoint(rayLength); |
||||
context.beginPath(); |
||||
context.moveTo(startPoint.x, startPoint.y); |
||||
context.lineTo(endPoint.x, endPoint.y); |
||||
context.strokeStyle = this.parent.info.color; |
||||
context.lineWidth = 0.5; |
||||
context.stroke(); |
||||
// mark all objects's lines intersecting with the ray
|
||||
for (var bb = 0; bb < bodies.length; bb++) { |
||||
var vertices = bodies[bb].vertices; |
||||
context.moveTo(vertices[0].x, vertices[0].y); |
||||
for (var vv = 1; vv < vertices.length; vv += 1) { |
||||
context.lineTo(vertices[vv].x, vertices[vv].y); |
||||
} |
||||
context.closePath(); |
||||
} |
||||
context.stroke(); |
||||
} |
||||
|
||||
// indicate if the sensor exceeded its maximum length by returning infinity
|
||||
if (rayLength > this.maxVal) { |
||||
rayLength = Infinity; |
||||
} |
||||
else { |
||||
// apply mild noise on the sensor reading, and clamp between valid values
|
||||
function gaussNoise(sigma=1) { |
||||
const x0 = 1.0 - Math.random(); |
||||
const x1 = 1.0 - Math.random(); |
||||
return sigma * Math.sqrt(-2 * Math.log(x0)) * Math.cos(2 * Math.PI * x1); |
||||
}; |
||||
rayLength = Math.floor(rayLength + gaussNoise(3)); |
||||
rayLength = Matter.Common.clamp(rayLength, this.minVal, this.maxVal); |
||||
} |
||||
|
||||
this.value = rayLength; |
||||
}; |
||||
|
||||
function dragSensor(sensor, event) { |
||||
const robotBay = document.getElementById('bayLemming'), |
||||
bCenter = {x: robotBay.width/2, |
||||
y: robotBay.height/2}, |
||||
rSize = simInfo.robotSize, |
||||
bScale = simInfo.bayScale, |
||||
sSize = sensor.getWidth(), |
||||
mAngle = Math.atan2( event.mouse.x - bCenter.x, |
||||
-(event.mouse.y - bCenter.y)); |
||||
sensor.info.attachAngle = mAngle; |
||||
sensor.x = bCenter.x - sSize - bScale * rSize * Math.sin(-mAngle); |
||||
sensor.y = bCenter.y - sSize - bScale * rSize * Math.cos( mAngle); |
||||
repaintBay(); |
||||
} |
||||
|
||||
function loadSensor(sensor, event) { |
||||
loadSensorInfo(sensor.sensor); |
||||
} |
||||
|
||||
function loadSensorInfo(sensorInfo) { |
||||
simInfo.baySensor = sensorInfo; |
||||
} |
||||
|
||||
function loadBay(robot) { |
||||
simInfo.bayRobot = robot; |
||||
sensors = new Array(); |
||||
const robotBay = document.getElementById("bayLemming"); |
||||
const bCenter = {x: robotBay.width/2, |
||||
y: robotBay.height/2}, |
||||
rSize = simInfo.robotSize, |
||||
bScale = simInfo.bayScale; |
||||
|
||||
for (var ss = 0; ss < robot.info.sensors.length; ++ss) { |
||||
const curSensor = robot.sensors[ss], |
||||
attachAngle = curSensor.attachAngle; |
||||
// put current sensor into global variable, make mouse-interactive
|
||||
sensors[ss] = makeInteractiveElement(new SensorGraphics(curSensor), |
||||
document.getElementById("bayLemming")); |
||||
const sSize = sensors[ss].getWidth(); |
||||
sensors[ss].x = bCenter.x - sSize - bScale * rSize * Math.sin(-attachAngle); |
||||
sensors[ss].y = bCenter.y - sSize - bScale * rSize * Math.cos( attachAngle); |
||||
sensors[ss].onDragging = dragSensor; |
||||
sensors[ss].onDrag = loadSensor; |
||||
} |
||||
repaintBay(); |
||||
} |
||||
|
||||
function SensorGraphics(sensorInfo) { |
||||
this.info = sensorInfo; |
||||
this.plotSensor = plotSensor; |
||||
// add functions getWidth/getHeight for graphics.js & mouse.js,
|
||||
// to enable dragging the sensor in the robot bay
|
||||
this.getWidth = function() { return 6; }; |
||||
this.getHeight = function() { return 6; }; |
||||
} |
||||
|
||||
functio |