let globalPoints = [
{"x":155,"y":395,"n":"A"}/*0*/,
{"x":235,"y":630,"n":"B"}/*1*/,
{"x":0,"y":630,"n":"C"}/*2*/,
{"x":0,"y":380,"n":"D"}/*3*/,
{"x":0,"y":80,"n":"E"}/*4*/,
{"x":405,"y":80,"n":"F"}/*5*/,
{"x":385,"y":355,"n":"G"}/*6*/,
{"x":224,"y":598,"n":"H"}/*7*/,
{"x":755,"y":520,"n":"I"}/*8*/,
{"x":795,"y":80,"n":"J"}/*9*/,
{"x":890,"y":85,"n":"K"}/*10*/,
{"x":890,"y":630,"n":"L"}/*11*/,
{"x":855,"y":630,"n":"M"}/*12*/,
{"x":0,"y":0,"n":"N"}/*13*/,
{"x":890,"y":0,"n":"O"}/*14*/
];
let shapePoints = [
[0,1,2,3],
[3,4,5,6,0],
[0,7,8,9,5,6],
[9,10,11,12,8],
[9,10,11,12,8],
[7,1,12,8],
[4,13,14,10,9,5]
];
let lastClick = null;
class Shape {
constructor(points)
{
this.points = points;
this.filledIn = false;
}
draw(ctx) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
ctx.fillStyle = 'red';
ctx.moveTo(globalPoints[this.points[0]].x,globalPoints[this.points[0]].y);
for (let i =1;i < this.points.length;i++) {
ctx.lineTo(globalPoints[this.points[i]].x,globalPoints[this.points[i]].y);
}
ctx.closePath();
if (this.filledIn) {
ctx.fill();
}
ctx.stroke();
}
toString()
{
let name = "";
for (let i=0;i<this.points.length;i++) {
name += globalPoints[this.points[i]].n;
}
return name;
}
//use the 'alternate method' from this answer
//https://math.stackexchange.com/questions/926559/when-is-a-point-in-the-plane-inside-a-simple-closed-path
calculateMouseClick(mouseX,mouseY)
{
//generate a ray that extends to outside of the shape,
//we know -1,-1 will be outside as all points are positive
let ray = {"p1":{"x":mouseX,"y":mouseY},"p2":{"x":-1,"y":-1}};
let numIntersections = 0;
//calculate all but last lines
for (let i=0;i<this.points.length-1;i++) {
let shapeEdge = {
"p1":{
"x":globalPoints[this.points[i]].x,
"y":globalPoints[this.points[i]].y
},
"p2":{
"x":globalPoints[this.points[i+1]].x,
"y":globalPoints[this.points[i+1]].y
}
};
if (doIntersect(ray.p1,ray.p2,shapeEdge.p1,shapeEdge.p2)) {
numIntersections++;
}
}
//last line wraps around to start of array
let shapeEdge = {
"p1":{
"x":globalPoints[this.points[0]].x,
"y":globalPoints[this.points[0]].y
},
"p2":{
"x":globalPoints[this.points[this.points.length-1]].x,
"y":globalPoints[this.points[this.points.length-1]].y
}
};
if (doIntersect(ray.p1,ray.p2,shapeEdge.p1,shapeEdge.p2)) {
numIntersections++;
}
if (numIntersections == 0 || numIntersections%2 == 0) {
return false;
}
return true;
}
}
let shapes = [];
function init()
{
"use strict";
for (let i = 0;i< globalPoints.length;i++) {
//add 50 to x and y so we don't draw at the very edge of the canvas
globalPoints[i].x += 50;
globalPoints[i].y += 80;
}
for (let i = 0;i < shapePoints.length;i++) {
shapes.push(new Shape(shapePoints[i]));
}
draw();
let canvas = document.getElementById("drawingArea");
canvas.addEventListener("click",canvasClicked);
}
function draw()
{
let canvas = document.getElementById("drawingArea");
let ctx = canvas.getContext('2d');
ctx.clearRect(0,0,canvas.width,canvas.height);
drawShapes(ctx);
drawPoints(ctx);
drawClick(ctx);
}
function drawShapes(ctx)
{
"use strict";
for (let i = 0;i<shapes.length;i++) {
shapes[i].draw(ctx);
}
}
function drawPoints(ctx)
{
"use strict";
ctx.font = "48px sans";
for (let i = 0;i < globalPoints.length;i++) {
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(globalPoints[i].x,globalPoints[i].y,5,0,2*Math.PI);
ctx.fill();
ctx.fillStyle = "#444444";
ctx.fillText(globalPoints[i].n,globalPoints[i].x-40,globalPoints[i].y-25);
}
}
function drawClick(ctx)
{
"use strict";
if (lastClick == null) {
return;
}
ctx.strokeStyle = "#33dd33";
ctx.arc(lastClick.x,lastClick.y,10,0,2*Math.PI);
ctx.fill();
}
function canvasClicked(event)
{
"use strict";
let mouseX = event.offsetX;
let mouseY = event.offsetY;
lastClick = {"x":mouseX,"y":mouseY};
//clear out previous click
for (let i=0;i<shapes.length;i++) {
shapes[i].filledIn = false;
}
let foundShape = false;
let shapeNameElem = document.getElementById("shapeName");
for (let i=0;i<shapes.length;i++) {
let isIn = shapes[i].calculateMouseClick(mouseX,mouseY);
if (isIn) {
shapes[i].filledIn = true;
foundShape = true;
shapeNameElem.innerHTML = "Clicked shape: "+shapes[i].toString();
break;
}
}
if (!foundShape) {
shapeNameElem.innerHTML="";
}
draw();
}
/********************************
onSegment(), orientation(), doIntersect()
https://www.geeksforgeeks.org/check-if-two-given-line-segments-intersect/
license CC BY-SA
https://www.geeksforgeeks.org/copyright-information/
********************************/
// Given three colinear points p, q, r, the function checks if
// point q lies on line segment 'pr'
function onSegment(p, q, r)
{
if (q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) &&
q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y))
return true;
return false;
}
// To find orientation of ordered triplet (p, q, r).
// The function returns following values
// 0 --> p, q and r are colinear
// 1 --> Clockwise
// 2 --> Counterclockwise
function orientation(p, q, r)
{
// See https://www.geeksforgeeks.org/orientation-3-ordered-points/
// for details of below formula.
let val = (q.y - p.y) * (r.x - q.x) -
(q.x - p.x) * (r.y - q.y);
if (val == 0) return 0; // colinear
return (val > 0)? 1: 2; // clock or counterclock wise
}
// The main function that returns true if line segment 'p1q1'
// and 'p2q2' intersect.
function doIntersect(p1, q1, p2, q2)
{
// Find the four orientations needed for general and
// special cases
let o1 = orientation(p1, q1, p2);
let o2 = orientation(p1, q1, q2);
let o3 = orientation(p2, q2, p1);
let o4 = orientation(p2, q2, q1);
// General case
if (o1 != o2 && o3 != o4)
return true;
// Special Cases
// p1, q1 and p2 are colinear and p2 lies on segment p1q1
if (o1 == 0 && onSegment(p1, p2, q1)) return true;
// p1, q1 and q2 are colinear and q2 lies on segment p1q1
if (o2 == 0 && onSegment(p1, q2, q1)) return true;
// p2, q2 and p1 are colinear and p1 lies on segment p2q2
if (o3 == 0 && onSegment(p2, p1, q2)) return true;
// p2, q2 and q1 are colinear and q1 lies on segment p2q2
if (o4 == 0 && onSegment(p2, q1, q2)) return true;
return false; // Doesn't fall in any of the above cases
}
window.addEventListener("load",init);
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
<script src="test.js"></script>
<link rel="stylesheet" href="test.css"/>
</head>
<body>
<p id="shapeName"></p>
<canvas id="drawingArea" width="1000" height="800"></canvas>
</body>
</html>