auto-accept/www/js/game.js

236 lines
11 KiB
JavaScript

import { Flywheel } from "./effects.js";
import { getPathShapeBetweenPoints } from "./svg.js";
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() {
let json_request = new Request('/annotation.json?min_area=500');
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);
shapeEl.setAttribute('d', d);
let mouseMoveEv = function (mousemoveE) {
let p2 = { 'x': mousemoveE.clientX - this.rect.left, 'y': mousemoveE.clientY - this.rect.top }
let d = getPathShapeBetweenPoints(p1, p2, corners);
shapeEl.setAttribute('d', d);
}.bind(this);
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);
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
const points = getPathShapeBetweenPoints(p1, p2, corners, true);
this.calculateOverlap(annotation, posX, posY, points);
this.flywheel.add();
this.loadAnnotation();
// fade out box
setTimeout(function () {
shapeEl.classList.add('hide');
// remove shape
setTimeout(function () {
shapeEl.parentNode.removeChild(shapeEl);
}, 1000);
}, 1000);
// 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)
}
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;
}
}