/*
* Polygon
* November 16, 2016
*
* GPLv3 --- Copyright (C) 2016 Olivier Pirson
* http://www.opimedia.be/
*
*/
var assert = console.assert; // https://developer.mozilla.org/en-US/docs/Web/API/Console/assert
// var assert = function () {};
/*********
* Model *
*********/
class Point {
constructor(x, y, cartesian=true) {
assert(typeof cartesian === "boolean", cartesian);
// Polar coordinates
// https://en.wikipedia.org/wiki/Polar_coordinate_system#Converting_between_polar_and_Cartesian_coordinates
if (cartesian) {
this._x = x;
this._y = y;
this._r = Math.sqrt(x*x + y*y);
this._phi = (Math.atan2(y, x) + 2*Math.PI) % (2*Math.PI);
}
else {
this._r = x;
this._phi = y;
this._x = this.r*Math.cos(this.phi);
this._y = this.r*Math.sin(this.phi);
}
}
get x() { return this._x; }
get y() { return this._y; }
get r() { return this._r; }
get phi() { return this._phi; }
toString(precision=2, cartesian=true) {
assert(typeof precision === "number", precision);
assert(precision >= 0, precision);
assert(typeof cartesian === "boolean", cartesian);
function format(n) {
var s = (Number.isInteger(n)
? n.toString()
: n.toFixed(precision));
return s;
}
return "(" + format(cartesian
? this.x
: this.r) + "," + format(cartesian
? this.y
: this.phi) + ")";
}
/*
Return a new point that is the sum.
*/
add(p) {
assert(p instanceof Point, p);
return new Point(this.x + p.x, this.y + p.y);
}
/*
Return true iff the point is equal to the point p,
namely iff they are same cartesian coordinates.
*/
isEqual(p) {
assert(p instanceof Point, p);
return (this.x == p.x) && (this.y == p.y);
}
/*
Return a new point that is the difference.
*/
sub(p) {
assert(p instanceof Point, p);
return new Point(this.x - p.x, this.y - p.y);
}
}
class Polygon {
constructor() {
this._points = [];
}
add(p) {
assert(p instanceof Point, p);
// FIXME
// if (this.isPossibleNewVertex(p)) {
this._points.push(p);
// }
}
getVertex(i) {
assert(typeof i === "number", i);
assert(i >= 0, i);
assert(i < this.size());
return this._points[i];
}
/*
Return true iff p is (strictically) in the polygon
*/
isIn(p) {
assert(p instanceof Point, p);
/*
Return true iff the horizontal line of vertical coordonate y
(strictically) intersect the segment (p1, p2).
*/
function isHorizontalIntersectSegment(y, p1, p2) {
assert(typeof y === "number", y);
assert(p1 instanceof Point, p1);
assert(p2 instanceof Point, p2);
return (p1.y > y && p2.y < y) || (p1.y < y && p2.y > y);
}
var i;
var intersections = new Set();
var a;
var b;
// Collect all intersection points with line
for (i = 0; i < this.size(); ++i) {
a = this.getVertex(i);
b = this.getVertex((i + 1) % this.size());
if (a.y === b.y) {
if (p.y === a.y) {
intersections.add(a.x);
intersections.add(b.x);
}
}
else if (isHorizontalIntersectSegment(p.y, a, b)) {
intersections.add((p.y - a.y)*(b.x - a.x)/(b.y - a.y) + a.x);
}
}
// Sort intersection points
intersections = Array.from(intersections);
intersections.sort(function (a, b) {
return a - b;
});
// Search where is p in this intersection points
i = 0;
while ((i < this.size()) && (p.x > intersections[i])) {
++i;
}
return (i % 2) != 0;
}
/*
Return true iff it possible to add a vertex p to the polygon
FIXME
*/
isPossibleNewVertex(p) {
if (this.isVertex(p)) {
return false;
}
if (this.size() > 1) {
var i;
var p0;
var p2;
var q1;
var q2;
p0 = this.getVertex(0);
p2 = this.getVertex(this.size() - 1);
for (i = 0; i < this.size(); ++i) {
q1 = this.getVertex(i);
q2 = this.getVertex((i + 1) % this.size());
if (isSegmentsIntersects(p0, p, q1, q2) || isSegmentsIntersects(p, p2, q1, q2)) {
return false;
}
}
}
return true;
}
/*
Return true iff p is a vertex of the polygon
*/
isVertex(p) {
assert(p instanceof Point, p);
var i;
for (i = 0; i < this.size(); ++i) {
if (p.isEqual(this._points[i])) {
return true;
}
}
return false;
}
size() {
return this._points.length;
}
}
/*
Return true iff segments (a, b) and (c, d) intersect.
"Computational Geometry in C"
(J. O'Rourke/ Cambridge University Press 2nd 1998), pp. 29-32
FIXME
*/
function isSegmentsIntersects(a, b, c, d) {
assert(a instanceof Point, a);
assert(b instanceof Point, b);
assert(c instanceof Point, c);
assert(d instanceof Point, d);
function isBetween(a, b, c) {
if (!isCollinear(a, b, c)) {
return false;
}
else if (a.x !== b.x) {
return ((a.x <= c.x) && (c.x <= b.x)) || ((a.x >= c.x) && (c.x >= b.x));
}
else {
return ((a.y <= c.y) && (c.y <= b.y)) || ((a.y >= c.y) && (c.y >= b.y));
}
}
function isCollinear(a, b, c) {
return orientationDeterminant(a, b, c) === 0;
}
function isIntersectProp(a, b, c, d) {
if (isCollinear(a, b, c) || isCollinear(a, b, d) || isCollinear(c, d, a) || isCollinear(c, d, b)) {
return false;
}
else {
return xor(isLeft(a, b, c), isLeft(a, b, d)) && xor(isLeft(c, d, a), isLeft(c, d, b));
}
}
function isLeft(a, b, c) {
return orientationDeterminant(a, b, c) > 0;
}
function xor(x, y) {
return (x || y) && !(x || y);
}
if (isIntersectProp(a, b, c, d)) {
return true;
}
else {
return isBetween(a, b, c) || isBetween(a, b, d) || isBetween(c, d, a) || isBetween(c, d, b);
}
}
/*
Return the orientation determinant of a, b and c.
*/
function orientationDeterminant(a, b, c) {
assert(a instanceof Point, a);
assert(b instanceof Point, b);
assert(c instanceof Point, c);
return b.x*c.y - a.x*c.y + a.x*b.y - b.y*c.x + a.y*c.x - a.y*b.x;
}
(function () {
"use strict";
/*************
* Constants *
*************/
var SIZE = 500; // radius of the canvas
var MID_SIZE = SIZE/2;
var BACKGROUND_COLOR;
var POINT_COLOR;
/*************
* Variables *
*************/
var polygon = new Polygon();
var p5js; // instance of the p5.js library: http://p5js.org/
/************************
* Controller functions *
************************/
function addPoint(p) {
polygon.add(p);
update();
}
function clear() {
polygon = new Polygon();
p5js.clear();
update();
}
function update() {
p5js.clear();
drawPolygon(polygon, BACKGROUND_COLOR);
drawPoints(polygon, POINT_COLOR);
printPoints();
}
/******************
* View functions *
******************/
function canvasCoordsToPoint(x, y) {
assert(typeof x === "number", x);
assert(typeof y === "number", y);
return new Point(x - MID_SIZE, MID_SIZE - y);
}
function drawPoint(p, label, color) {
assert(p instanceof Point, p);
assert((label === undefined) || (typeof label === "string"), label);
p = pointToCanvasCoordsPoint(p);
p5js.stroke(color);
p5js.fill(color);
p5js.ellipse(p.x, p.y, 5, 5);
if ((label !== undefined) && document.getElementById("labelCheckbox").checked) {
p5js.fill(0, 0, 0);
p5js.strokeWeight(0);
p5js.text(label, p.x + 5, p.y);
}
}
function drawPoints(polygon, color) {
var i;
for (i = 0; i < polygon.size(); ++i) {
drawPoint(polygon.getVertex(i), i.toString(), color);
}
}
function drawPolygon(polygon, color) {
if (polygon.size() > 1) {
var i;
var p;
p5js.fill(color);
p5js.beginShape();
for (i = 0; i < polygon.size(); ++i) {
p = pointToCanvasCoordsPoint(polygon.getVertex(i));
p5js.vertex(p.x, p.y);
}
p5js.endShape(p5js.CLOSE);
}
}
function isInCanvas(p) {
assert(p instanceof Point, p);
return p.r*p.r < MID_SIZE*MID_SIZE;
}
function pointToCanvasCoordsPoint(p) {
assert(p instanceof Point, p);
return new Point(p.x + MID_SIZE, MID_SIZE - p.y);
}
function printInfos(p) {
assert(p instanceof Point, p);
var div = document.getElementById("infos");
if (isInCanvas(p)) {
div.innerHTML = ("Cartesian: "+ p.toString() + " : Polar: " + p.toString(2, false)
+ "
Is " + (polygon.isIn(p)
? ""
: "NOT ") + "in the polygon");
}
else {
div.innerHTML = null;
}
}
function printPoints() {
var div = document.getElementById("pointsList");
var ol = document.createElement("ol");
div.innerHTML = polygon.size() + " node(s):";
var i;
var li;
for (i = 0; i < polygon.size(); ++i) {
li = document.createElement("li");
li.innerHTML = polygon.getVertex(i).toString() + " : " + polygon.getVertex(i).toString(2, false);
if (i === 0) {
li.setAttribute("value", "0");
}
ol.appendChild(li);
}
div.appendChild(ol);
}
/************************************
* Set load callback and init p5.js *
************************************/
window.addEventListener("load", function () {
p5js = new p5(
function (p5js) {
p5js.setup = function () {
p5js.createCanvas(SIZE, SIZE);
};
p5js.mouseClicked = function () {
var p = canvasCoordsToPoint(p5js.mouseX, p5js.mouseY);
if (isInCanvas(p)) {
addPoint(p);
}
};
p5js.mouseMoved = function () {
var p = canvasCoordsToPoint(p5js.mouseX, p5js.mouseY);
// FIXME: infos to debug
if (isInCanvas(p)) {
console.log(polygon.isPossibleNewVertex(p), p);
}
printInfos(p);
};
},
"canvasContainer"
);
BACKGROUND_COLOR = p5js.color(200, 200, 200);
POINT_COLOR = p5js.color(0, 0, 0);
document.getElementById("clearButton").addEventListener("click", clear);
document.getElementById("labelCheckbox").addEventListener("change", update);
update();
});
}());