2020-12-17 12:22:58 +00:00
|
|
|
import { Flywheel } from "./effects.js";
|
2020-12-17 11:09:22 +00:00
|
|
|
import { getPathShapeBetweenPoints } from "./svg.js";
|
|
|
|
|
2020-12-17 17:27:44 +00:00
|
|
|
export class Game {
|
|
|
|
|
|
|
|
constructor(svgEl, crosshairXEl, crosshairYEl) {
|
|
|
|
this.flywheel = new Flywheel();
|
|
|
|
this.svgEl = svgEl;
|
|
|
|
|
|
|
|
// canvas position relative to mouse/screen
|
|
|
|
// TODO: update on resize:
|
|
|
|
svgEl.setAttribute('viewBox', '0 0 ' + window.innerWidth + ' ' + window.innerHeight);
|
|
|
|
this.rect = svgEl.getBoundingClientRect();
|
|
|
|
|
|
|
|
// move crosshairs
|
|
|
|
window.addEventListener('mousemove', function (mousemoveEv) {
|
|
|
|
let x = mousemoveEv.clientX - this.rect.left;
|
|
|
|
let y = mousemoveEv.clientY - this.rect.top;
|
|
|
|
|
|
|
|
crosshairXEl.setAttribute('x1', x)
|
|
|
|
crosshairXEl.setAttribute('x2', x)
|
|
|
|
crosshairYEl.setAttribute('y1', y)
|
|
|
|
crosshairYEl.setAttribute('y2', y)
|
|
|
|
}.bind(this));
|
|
|
|
this.loadAnnotation();
|
|
|
|
}
|
|
|
|
|
|
|
|
loadAnnotation() {
|
2020-12-22 13:31:20 +00:00
|
|
|
let json_request = new Request('/annotation.json?min_area=500');
|
2020-12-17 17:27:44 +00:00
|
|
|
|
|
|
|
fetch(json_request).then(function (response) {
|
|
|
|
return response.json();
|
|
|
|
}).then((annotation) => {
|
|
|
|
let svg_request = new Request('/annotation.svg?id='+annotation.id);
|
|
|
|
|
|
|
|
fetch(svg_request).then(function (response) {
|
|
|
|
return response.text()
|
|
|
|
}).then((text) => {
|
|
|
|
this.addAnnotation(annotation, text);
|
|
|
|
})
|
|
|
|
}).catch((e) => console.error(e));
|
|
|
|
}
|
|
|
|
|
|
|
|
addAnnotation(annotation, svgText) {
|
|
|
|
|
|
|
|
//determine position
|
|
|
|
const maxX = this.rect.width - annotation.bbox[2];
|
|
|
|
const maxY = this.rect.height - annotation.bbox[3];
|
|
|
|
const posX = Math.random() * maxX;
|
|
|
|
const posY = Math.random() * maxY;
|
|
|
|
|
|
|
|
let imgEl = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
|
|
imgEl.innerHTML = svgText;
|
|
|
|
imgEl.classList.add('img');
|
|
|
|
imgEl.setAttribute('transform', `translate(${posX} ${posY})`);
|
|
|
|
this.svgEl.appendChild(imgEl);
|
|
|
|
|
|
|
|
let startTrace = function (mousedownEv) {
|
|
|
|
// create shape to draw
|
|
|
|
let shapeEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
|
|
shapeEl.classList.add('rect');
|
|
|
|
let p1 = { 'x': mousedownEv.clientX - this.rect.left, 'y': mousedownEv.clientY - this.rect.top }
|
|
|
|
let corners = this.flywheel.effects.shape.getAngles();
|
|
|
|
let d = getPathShapeBetweenPoints(p1, p1, corners);
|
|
|
|
this.svgEl.appendChild(shapeEl);
|
2020-12-17 11:09:22 +00:00
|
|
|
shapeEl.setAttribute('d', d);
|
2020-12-17 17:27:44 +00:00
|
|
|
|
2021-01-28 12:03:16 +00:00
|
|
|
let mouseMoveEv = function (mousemoveE) {
|
|
|
|
let p2 = { 'x': mousemoveE.clientX - this.rect.left, 'y': mousemoveE.clientY - this.rect.top }
|
2020-12-17 17:27:44 +00:00
|
|
|
let d = getPathShapeBetweenPoints(p1, p2, corners);
|
|
|
|
shapeEl.setAttribute('d', d);
|
|
|
|
}.bind(this);
|
2021-01-28 12:03:16 +00:00
|
|
|
let mouseUpEv = function (mouseupE) {
|
|
|
|
let p2 = { 'x': mouseupE.clientX - this.rect.left, 'y': mouseupE.clientY - this.rect.top }
|
|
|
|
let d = getPathShapeBetweenPoints(p1, p2, corners);
|
|
|
|
shapeEl.setAttribute('d', d);
|
|
|
|
|
2020-12-17 17:27:44 +00:00
|
|
|
console.log('up');
|
|
|
|
window.removeEventListener('mousemove', mouseMoveEv); // remove itself.
|
|
|
|
window.removeEventListener('mouseup', mouseUpEv); // remove itself.
|
|
|
|
this.svgEl.removeEventListener('mousedown', startTrace); // remove itself.
|
|
|
|
shapeEl.classList.add('locked');
|
|
|
|
//TODO: calculate points
|
2021-01-28 12:03:16 +00:00
|
|
|
|
|
|
|
const points = getPathShapeBetweenPoints(p1, p2, corners, true);
|
|
|
|
this.calculateOverlap(annotation, posX, posY, points);
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-12-17 17:27:44 +00:00
|
|
|
this.flywheel.add();
|
|
|
|
this.loadAnnotation();
|
|
|
|
|
|
|
|
// fade out box
|
2020-12-17 11:09:22 +00:00
|
|
|
setTimeout(function () {
|
2020-12-17 17:27:44 +00:00
|
|
|
shapeEl.classList.add('hide');
|
|
|
|
// remove shape
|
|
|
|
setTimeout(function () {
|
|
|
|
shapeEl.parentNode.removeChild(shapeEl);
|
|
|
|
}, 1000);
|
2020-12-17 11:09:22 +00:00
|
|
|
}, 1000);
|
2020-12-17 17:27:44 +00:00
|
|
|
// fade out img
|
|
|
|
setTimeout(function () {
|
|
|
|
imgEl.classList.add('hide');
|
|
|
|
// remove shape
|
|
|
|
setTimeout(function () {
|
|
|
|
imgEl.parentNode.removeChild(imgEl);
|
|
|
|
}, 500);
|
|
|
|
}, 500);
|
|
|
|
}.bind(this);
|
|
|
|
window.addEventListener('mousemove', mouseMoveEv);
|
|
|
|
window.addEventListener('mouseup', mouseUpEv);
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
this.svgEl.addEventListener('mousedown', startTrace)
|
2020-12-17 16:13:38 +00:00
|
|
|
|
2020-12-17 17:27:44 +00:00
|
|
|
}
|
2021-01-28 12:03:16 +00:00
|
|
|
|
|
|
|
calculateOverlap(annotation, annotationDx, annotationDy, points){
|
|
|
|
const subj_paths = annotation.segments.map((seg) => {
|
|
|
|
return seg.map((point) => {
|
|
|
|
return {X: point[0] + annotationDx - annotation.bbox[0], Y: point[1] + annotationDy - annotation.bbox[1]}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
// apparently, we need a deep copy if we pass it to findDifferencePaths/Clipper
|
|
|
|
const subj_paths2 = JSON.parse(JSON.stringify(subj_paths));
|
|
|
|
const subj_paths3 = JSON.parse(JSON.stringify(subj_paths));
|
|
|
|
// const subj_paths4 = JSON.parse(JSON.stringify(subj_paths));
|
|
|
|
|
|
|
|
const drawn_paths = [points.map((point) => {
|
|
|
|
return {X: point[0], Y: point[1]}
|
|
|
|
})];
|
|
|
|
const drawn_paths2 = [points.map((point) => {
|
|
|
|
return {X: point[0], Y: point[1]}
|
|
|
|
})];
|
|
|
|
// const drawn_paths3 = [points.map((point) => {
|
|
|
|
// return {X: point[0], Y: point[1]}
|
|
|
|
// })];
|
|
|
|
const optimal_bbox_paths = [[
|
|
|
|
{X: annotationDx, Y: annotationDy},
|
|
|
|
{X: annotationDx + annotation.bbox[2], Y: annotationDy},
|
|
|
|
{X: annotationDx + annotation.bbox[2], Y: annotationDy + annotation.bbox[3]},
|
|
|
|
{X: annotationDx, Y: annotationDy + annotation.bbox[3]},
|
|
|
|
]];
|
|
|
|
|
|
|
|
// console.log(subj_paths, drawn_paths, optimal_bbox_paths);
|
|
|
|
|
|
|
|
const scale = 100;
|
|
|
|
let minimalErrorPaths = this.findDifferencePaths(subj_paths, optimal_bbox_paths, scale);
|
|
|
|
let drawnErrorPaths = this.findDifferencePaths(drawn_paths, subj_paths2, scale, ClipperLib.ClipType.ctDifference); // only error outside of shape
|
|
|
|
let drawnSuccessPaths = this.findDifferencePaths(subj_paths3, drawn_paths2, scale, ClipperLib.ClipType.ctIntersection);
|
|
|
|
// let drawnMissPaths = this.findDifferencePaths(subj_paths4, drawn_paths3, scale, ClipperLib.ClipType.ctDifference);
|
|
|
|
// let drawnSolutionPaths = this.findIntersectionPaths(subj_paths2, drawn_paths, scale);
|
|
|
|
|
|
|
|
// const drawnArea = ClipperLib.JS.AreaOfPolygons(clip_paths) / (100*100));
|
|
|
|
const shapeArea = Math.abs(ClipperLib.JS.AreaOfPolygons(subj_paths) / (100*100));
|
|
|
|
// const shapeArea = ClipperLib.JS.AreaOfPolygons(subj_paths) / (100*100));
|
|
|
|
// const minimalErrorArea = ClipperLib.JS.AreaOfPolygons(minimalErrorPaths) / (100*100);
|
|
|
|
const successArea = Math.abs(ClipperLib.JS.AreaOfPolygons(drawnSuccessPaths) / (100*100));
|
|
|
|
// const missArea = Math.abs(ClipperLib.JS.AreaOfPolygons(drawnMissPaths) / (100*100));
|
|
|
|
const minimalErrorArea = Math.abs(ClipperLib.JS.AreaOfPolygons(minimalErrorPaths) / (100*100));
|
|
|
|
const drawnErrorArea = Math.abs(ClipperLib.JS.AreaOfPolygons(drawnErrorPaths) / (100*100));
|
|
|
|
// const error = ((drawnErrorArea+1)/(minimalErrorArea+1));
|
|
|
|
// TODO different? We expected 20% of selection to be error (use minimalErrorArea/drawnArea), it was 25% of error (drawnErrorArea/drawnArea)
|
|
|
|
// is dat drawnErrorArea/minimalErrorArea
|
|
|
|
const simpleScore = (successArea / shapeArea);// TODO: Math.pow() for harder punishment of missed areas?
|
|
|
|
const errorScore = (drawnErrorArea - minimalErrorArea) / shapeArea; // it can get negative, which is what we want for the odd shapes.
|
|
|
|
const score = simpleScore - Math.min(1, errorScore) / 2; // cap error at 1, and make things easier for the player, so errors count less (/2)
|
|
|
|
console.log(successArea, shapeArea, simpleScore, errorScore, score);
|
|
|
|
|
|
|
|
|
|
|
|
// Scale down coordinates and draw ...
|
|
|
|
|
|
|
|
if(window.location.search == "?debug") {
|
|
|
|
let shapeEl2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
|
|
shapeEl2.classList.add('test');
|
|
|
|
this.svgEl.appendChild(shapeEl2);
|
|
|
|
shapeEl2.setAttribute('d', paths2string(minimalErrorPaths, scale));
|
|
|
|
shapeEl2.setAttribute('stroke', 'green');
|
|
|
|
shapeEl2.setAttribute('stroke-width', '2');
|
|
|
|
shapeEl2.setAttribute('fill', 'red');
|
|
|
|
let shapeEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
|
|
shapeEl.classList.add('test');
|
|
|
|
this.svgEl.appendChild(shapeEl);
|
|
|
|
shapeEl.setAttribute('d', paths2string(drawnErrorPaths, scale));
|
|
|
|
shapeEl.setAttribute('stroke', 'yellow');
|
|
|
|
shapeEl.setAttribute('stroke-width', '2');
|
|
|
|
shapeEl.setAttribute('fill', 'orange');
|
|
|
|
let shapeEl3 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
|
|
shapeEl3.classList.add('test');
|
|
|
|
this.svgEl.appendChild(shapeEl3);
|
|
|
|
shapeEl3.setAttribute('d', paths2string(drawnSuccessPaths, scale));
|
|
|
|
shapeEl3.setAttribute('stroke-width', '2');
|
|
|
|
shapeEl3.setAttribute('fill', 'green');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Converts Paths to SVG path string
|
|
|
|
// and scales down the coordinates
|
|
|
|
function paths2string (paths, scale) {
|
|
|
|
var svgpath = "", i, j;
|
|
|
|
if (!scale) scale = 1;
|
|
|
|
for(i = 0; i < paths.length; i++) {
|
|
|
|
for(j = 0; j < paths[i].length; j++){
|
|
|
|
if (!j) svgpath += "M";
|
|
|
|
else svgpath += "L";
|
|
|
|
svgpath += (paths[i][j].X / scale) + ", " + (paths[i][j].Y / scale);
|
|
|
|
}
|
|
|
|
svgpath += "Z";
|
|
|
|
}
|
|
|
|
if (svgpath=="") svgpath = "M0,0";
|
|
|
|
return svgpath;
|
|
|
|
}
|
|
|
|
// var paths = [[{"X":10,"Y":10},{"X":110,"Y":10},{"X":110,"Y":110},{"X":10,"Y":110}]];
|
|
|
|
// console.log(JSON.stringify(paths));
|
|
|
|
// ClipperLib.Clipper.ReversePaths(paths);
|
|
|
|
// console.log(JSON.stringify(paths));
|
|
|
|
}
|
|
|
|
|
|
|
|
findDifferencePaths(subj_paths, clip_paths, scale, overrideClipType) {
|
|
|
|
let cpr = new ClipperLib.Clipper();
|
|
|
|
|
|
|
|
ClipperLib.JS.ScaleUpPaths(subj_paths, scale);
|
|
|
|
ClipperLib.JS.ScaleUpPaths(clip_paths, scale);
|
|
|
|
|
|
|
|
cpr.AddPaths(subj_paths, ClipperLib.PolyType.ptSubject, true); // true means closed path
|
|
|
|
cpr.AddPaths(clip_paths, ClipperLib.PolyType.ptClip, true);
|
|
|
|
|
|
|
|
let solution_paths = new ClipperLib.Paths();
|
|
|
|
// const clipType = ClipperLib.ClipType.ctIntersection;
|
|
|
|
// const clipType = ClipperLib.ClipType.ctDifference; // we like to know everything that is wrong.
|
|
|
|
const clipType = overrideClipType ?? ClipperLib.ClipType.ctXor; // we like to know everything that is wrong.
|
|
|
|
const subject_fillType = ClipperLib.PolyFillType.pftNonZero;
|
|
|
|
const clip_fillType = ClipperLib.PolyFillType.pftNonZero;
|
|
|
|
const succeeded = cpr.Execute(clipType, solution_paths, subject_fillType, clip_fillType);
|
|
|
|
return solution_paths;
|
|
|
|
}
|
2020-12-17 11:09:22 +00:00
|
|
|
}
|