From 8ad7be144f913cdf0c1f2a653dc0c88722a54d53 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Mon, 31 Dec 2018 12:16:34 +0100 Subject: [PATCH 01/18] WIP --- client/package-lock.json | 36 + client/package.json | 3 + client/src/core/utils.ts | 8 + client/src/game/layers/fowplayers.ts | 83 ++- client/src/game/shapes/baserect.ts | 14 + client/src/game/shapes/circle.ts | 5 + client/src/game/shapes/polygon.ts | 6 + client/src/game/shapes/shape.ts | 2 + client/src/game/store.ts | 2 +- client/src/game/units.ts | 3 + client/src/game/{bvh => visibility}/bvh.ts | 5 +- client/src/game/visibility/cdel.ts | 170 +++++ client/src/game/visibility/earcut.d.ts | 54 ++ client/src/game/visibility/earcut.js | 683 ++++++++++++++++++ client/src/game/{bvh => visibility}/node.ts | 0 client/src/game/visibility/oldvis.ts | 63 ++ .../src/game/visibility/polygon-clipping.d.ts | 3 + client/src/game/visibility/triangulate.ts | 252 +++++++ 18 files changed, 1360 insertions(+), 32 deletions(-) create mode 100644 client/src/game/shapes/polygon.ts rename client/src/game/{bvh => visibility}/bvh.ts (97%) create mode 100644 client/src/game/visibility/cdel.ts create mode 100644 client/src/game/visibility/earcut.d.ts create mode 100644 client/src/game/visibility/earcut.js rename client/src/game/{bvh => visibility}/node.ts (100%) create mode 100644 client/src/game/visibility/oldvis.ts create mode 100644 client/src/game/visibility/polygon-clipping.d.ts create mode 100644 client/src/game/visibility/triangulate.ts diff --git a/client/package-lock.json b/client/package-lock.json index f075cef19..3a1ed041a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -31,6 +31,12 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, + "@types/earcut": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.0.tgz", + "integrity": "sha512-PKu2suACS92cLQb2DZ6ytgnQm9PKPQRCD9jPqgMUTMVEuUqLTfCRHuaOXKmZTyVpL7zqJX+ZSZoyUr4e5muSNg==", + "dev": true + }, "@types/lodash": { "version": "4.14.118", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.118.tgz", @@ -939,6 +945,11 @@ } } }, + "babel-plugin-add-module-exports": { + "version": "0.2.1", + "resolved": "http://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", + "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=" + }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -2678,6 +2689,11 @@ "stream-shift": "^1.0.0" } }, + "earcut": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-ttRjmPD5oaTtXOoxhFp9aZvMB14kBjapYaiBuzBB1elOgSLU9P2Ev86G2OClBg+uspUXERsIzXKpUWweH2K4Xg==" + }, "easy-stack": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.0.tgz", @@ -6745,6 +6761,16 @@ "find-up": "^2.1.0" } }, + "polygon-clipping": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.9.2.tgz", + "integrity": "sha512-UO+53kvxmuyMpO6RqepUTj3lQPcFJ6NbqJbfwxcOZv3xTuWUQd2q12JiyT5Sd2NAHgxzHH2ht2BQSuclJaEyDQ==", + "requires": { + "babel-plugin-add-module-exports": "^0.2.1", + "splaytree": "^2.0.2", + "tinyqueue": "^1.2.3" + } + }, "portfinder": { "version": "1.0.19", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.19.tgz", @@ -8750,6 +8776,11 @@ "wbuf": "^1.7.2" } }, + "splaytree": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-2.0.3.tgz", + "integrity": "sha512-IziTvWQv9F1EiKq9XveosQRGTLrdUW0jLokpmAXz0+hnLgBZitvU0j4gUvCGASKwUQvCZaofhff1H8OmE2LRdA==" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -9254,6 +9285,11 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" }, + "tinyqueue": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz", + "integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==" + }, "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", diff --git a/client/package.json b/client/package.json index bda5bb07e..0b27114fe 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,9 @@ "homepage": "https://github.com/Kruptein/PlanarAlly#readme", "dependencies": { "axios": "^0.18.0", + "earcut": "^2.1.4", "lodash.throttle": "^4.1.1", + "polygon-clipping": "^0.9.2", "socket.io-client": "^2.1.1", "tinycolor2": "^1.4.1", "vue": "^2.5.17", @@ -30,6 +32,7 @@ "vuex": "^3.0.1" }, "devDependencies": { + "@types/earcut": "^2.1.0", "@types/lodash": "^4.14.118", "@types/socket.io-client": "^1.4.32", "@types/tinycolor2": "^1.4.1", diff --git a/client/src/core/utils.ts b/client/src/core/utils.ts index f8d75fc08..4715ea709 100644 --- a/client/src/core/utils.ts +++ b/client/src/core/utils.ts @@ -64,6 +64,11 @@ export function fixedEncodeURIComponent(str: string) { export class OrderedMap { keys: K[] = []; values: V[] = []; + + get length() { + return this.keys.length; + } + get(key: K) { return this.values[this.keys.indexOf(key)]; } @@ -77,6 +82,9 @@ export class OrderedMap { this.keys.push(key); this.values.push(value); } + has(key: K) { + return this.indexOf(key) >= 0; + } indexOf(element: K) { return this.keys.indexOf(element); } diff --git a/client/src/game/layers/fowplayers.ts b/client/src/game/layers/fowplayers.ts index a8d02291a..45ff19178 100644 --- a/client/src/game/layers/fowplayers.ts +++ b/client/src/game/layers/fowplayers.ts @@ -4,12 +4,15 @@ import { layerManager } from "@/game/layers/manager"; import { Settings } from "@/game/settings"; import { gameStore } from "@/game/store"; import { g2l, g2lx, g2ly } from "@/game/units"; +import { computeVisibility } from "../visibility/triangulate"; export class FOWPlayersLayer extends Layer { isVisionLayer: boolean = true; + mode = "bvh"; draw(): void { if (!this.valid) { + console.time("VI"); const ctx = this.ctx; if (!gameStore.fowLOS || Settings.skipPlayerFOW) { @@ -20,6 +23,9 @@ export class FOWPlayersLayer extends Layer { ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + // const drctx = layerManager.getLayer("draw")!.ctx; + // drctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + const originalOperation = ctx.globalCompositeOperation; ctx.fillStyle = "rgba(0, 0, 0, 1)"; @@ -30,44 +36,60 @@ export class FOWPlayersLayer extends Layer { // Then cut out all the player vision auras const maxLength = ctx.canvas.width + ctx.canvas.height; + for (const tokenId of gameStore.ownedtokens) { - ctx.beginPath(); - let lastArcAngle = -1; const token = layerManager.UUIDMap.get(tokenId); if (token === undefined) continue; - const center = token.center(); - const lcenter = g2l(center); + if (this.mode === "bvh") { + ctx.beginPath(); + let lastArcAngle = -1; + const center = token.center(); + const lcenter = g2l(center); - for (let angle = 0; angle < 2 * Math.PI; angle += (Settings.angleSteps / 2 / 180) * Math.PI) { - const cos = Math.cos(angle); - const sin = Math.sin(angle); - // Check if there is a hit with one of the nearby light blockers. - const lightRay = new Ray(center, new Vector(cos, sin)); - const hitResult = gameStore.BV.intersect(lightRay); + for (let angle = 0; angle < 2 * Math.PI; angle += (Settings.angleSteps / 2 / 180) * Math.PI) { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + // Check if there is a hit with one of the nearby light blockers. + const lightRay = new Ray(center, new Vector(cos, sin)); + const hitResult = gameStore.BV.intersect(lightRay); - // We can move on to the next angle if nothing was hit. - if (!hitResult.hit) { - // If an earlier hit caused the aura to leave the arc, we need to go back to the arc - if (lastArcAngle === -1) { - // Draw a line from the last non arc location back to the arc - ctx.lineTo(lcenter.x + maxLength * cos, lcenter.y + maxLength * sin); - // Set the start of a new arc beginning at the current angle - lastArcAngle = angle; + // We can move on to the next angle if nothing was hit. + if (!hitResult.hit) { + // If an earlier hit caused the aura to leave the arc, we need to go back to the arc + if (lastArcAngle === -1) { + // Draw a line from the last non arc location back to the arc + ctx.lineTo(lcenter.x + maxLength * cos, lcenter.y + maxLength * sin); + // Set the start of a new arc beginning at the current angle + lastArcAngle = angle; + } + continue; } - continue; - } - // If hit , first finish any ongoing arc, then move to the intersection point - if (lastArcAngle !== -1) { - ctx.arc(lcenter.x, lcenter.y, maxLength, lastArcAngle, angle); - lastArcAngle = -1; + // If hit , first finish any ongoing arc, then move to the intersection point + if (lastArcAngle !== -1) { + ctx.arc(lcenter.x, lcenter.y, maxLength, lastArcAngle, angle); + lastArcAngle = -1; + } + ctx.lineTo(g2lx(hitResult.intersect.x), g2ly(hitResult.intersect.y)); } - ctx.lineTo(g2lx(hitResult.intersect.x), g2ly(hitResult.intersect.y)); - } - // Finish the final arc. - if (lastArcAngle !== -1) ctx.arc(lcenter.x, lcenter.y, maxLength, lastArcAngle, 2 * Math.PI); - else ctx.closePath(); - ctx.fill(); + // Finish the final arc. + if (lastArcAngle !== -1) ctx.arc(lcenter.x, lcenter.y, maxLength, lastArcAngle, 2 * Math.PI); + else ctx.closePath(); + ctx.fill(); + } else { + // const canvas = document.createElement("canvas"); + // canvas.width = window.innerWidth; + // canvas.height = window.innerHeight; + // const pctx = canvas.getContext("2d")!; + const polygon = computeVisibility(token.center()); + // pctx.globalCompositeOperation = "source-in"; + ctx.beginPath(); + ctx.moveTo(g2lx(polygon[0][0]), g2ly(polygon[0][1])); + for (const point of polygon) ctx.lineTo(g2lx(point[0]), g2ly(point[1])); + ctx.closePath(); + ctx.fill(); + // ctx.drawImage(canvas, 0, 0); + } } // For the players this is done at the beginning of this function. TODO: why the split up ??? @@ -75,6 +97,7 @@ export class FOWPlayersLayer extends Layer { if (gameStore.IS_DM) super.draw(!gameStore.fullFOW); ctx.globalCompositeOperation = originalOperation; + console.timeEnd("VI"); } } } diff --git a/client/src/game/shapes/baserect.ts b/client/src/game/shapes/baserect.ts index 05ca657ea..f0b40bf66 100644 --- a/client/src/game/shapes/baserect.ts +++ b/client/src/game/shapes/baserect.ts @@ -22,6 +22,20 @@ export abstract class BaseRect extends Shape { getBoundingBox() { return new BoundingRect(this.refPoint, this.w, this.h); } + + get points() { + if (this.w === 0 || this.h === 0) return [[this.refPoint.x, this.refPoint.y]]; + const topright = this.refPoint.add(new Vector(0, this.h)); + const botright = this.refPoint.add(new Vector(this.w, this.h)); + const botleft = this.refPoint.add(new Vector(this.w, 0)); + return [ + [this.refPoint.x, this.refPoint.y], + [topright.x, topright.y], + [botright.x, botright.y], + [botleft.x, botleft.y], + ]; + } + contains(point: GlobalPoint): boolean { return ( this.refPoint.x <= point.x && diff --git a/client/src/game/shapes/circle.ts b/client/src/game/shapes/circle.ts index 683fb8b40..20e01172e 100644 --- a/client/src/game/shapes/circle.ts +++ b/client/src/game/shapes/circle.ts @@ -34,6 +34,11 @@ export class Circle extends Shape { this.r * 2, ); } + + get points() { + return []; + } + draw(ctx: CanvasRenderingContext2D) { super.draw(ctx); ctx.beginPath(); diff --git a/client/src/game/shapes/polygon.ts b/client/src/game/shapes/polygon.ts new file mode 100644 index 000000000..24fca8c99 --- /dev/null +++ b/client/src/game/shapes/polygon.ts @@ -0,0 +1,6 @@ +import { GlobalPoint } from "../geom"; + +export class Polygon { + points: GlobalPoint[] = []; + overlapsWith(other: Polygon) {} +} diff --git a/client/src/game/shapes/shape.ts b/client/src/game/shapes/shape.ts index 1462eb64c..aaa02f60a 100644 --- a/client/src/game/shapes/shape.ts +++ b/client/src/game/shapes/shape.ts @@ -82,6 +82,8 @@ export abstract class Shape { abstract resizeToGrid(): void; abstract resize(resizeDir: string, point: LocalPoint): void; + abstract get points(): number[][]; + invalidate(skipLightUpdate: boolean) { const l = layerManager.getLayer(this.layer); if (l) l.invalidate(skipLightUpdate); diff --git a/client/src/game/store.ts b/client/src/game/store.ts index 71971761a..fe69fa1cd 100644 --- a/client/src/game/store.ts +++ b/client/src/game/store.ts @@ -4,11 +4,11 @@ import { Action, getModule, Module, Mutation, VuexModule } from "vuex-module-dec import { AssetList } from "@/core/comm/types"; import { socket } from "@/game/api/socket"; import { sendClientOptions } from "@/game/api/utils"; -import { BoundingVolume } from "@/game/bvh/bvh"; import { Note } from "@/game/comm/types/general"; import { GlobalPoint } from "@/game/geom"; import { layerManager } from "@/game/layers/manager"; import { g2l, l2g } from "@/game/units"; +import { BoundingVolume } from "@/game/visibility/bvh"; import { rootStore } from "@/store"; export interface GameState { diff --git a/client/src/game/units.ts b/client/src/game/units.ts index 18c4adf4a..e9fbdb3d2 100644 --- a/client/src/game/units.ts +++ b/client/src/game/units.ts @@ -56,3 +56,6 @@ export function l2gz(z: number) { export function l2gr(r: number) { return l2gz(getUnitDistance(r)); } + +(window).g2lx = g2lx; +(window).g2ly = g2ly; diff --git a/client/src/game/bvh/bvh.ts b/client/src/game/visibility/bvh.ts similarity index 97% rename from client/src/game/bvh/bvh.ts rename to client/src/game/visibility/bvh.ts index 2eb3b5b0e..af6a64d10 100644 --- a/client/src/game/bvh/bvh.ts +++ b/client/src/game/visibility/bvh.ts @@ -1,11 +1,13 @@ import { partition } from "@/core/utils"; -import { BoundingNode, InteriorNode, LeafNode } from "@/game/bvh/node"; import { GlobalPoint, Ray } from "@/game/geom"; import { layerManager } from "@/game/layers/manager"; import { BoundingRect } from "@/game/shapes/boundingrect"; import { g2lx, g2ly, g2lz } from "@/game/units"; +import { BoundingNode, InteriorNode, LeafNode } from "@/game/visibility/node"; import { gameStore } from "../store"; +import * as tr from "./triangulate"; + interface BuildInfo { index: number; bbox: BoundingRect; @@ -34,6 +36,7 @@ export class BoundingVolume { offset = 0; constructor(shapes: string[]) { + tr.triangulate(shapes); this.shapes = shapes; if (this.shapes.length === 0) { this.root = null; diff --git a/client/src/game/visibility/cdel.ts b/client/src/game/visibility/cdel.ts new file mode 100644 index 000000000..74931ad2f --- /dev/null +++ b/client/src/game/visibility/cdel.ts @@ -0,0 +1,170 @@ +import { OrderedMap } from "@/core/utils"; +import { GlobalPoint } from "../geom"; +import { layerManager } from "../layers/manager"; +import { g2lx, g2ly } from "../units"; + +export let cdel: CDEL; + +export class Edge { + from: number; + to: number; + triangleIndex: number = -1; + constrained = false; + + constructor(from: number, to: number) { + this.from = from; + this.to = to; + } + + get opposite() { + return cdel.edges.get(`${this.to}-${this.from}`); + } + + get triangle() { + return cdel.triangles[this.triangleIndex]; + } + + get toVertex() { + return [cdel.vertices[2 * this.to], cdel.vertices[2 * this.to + 1]]; + } + + get fromVertex() { + return [cdel.vertices[2 * this.from], cdel.vertices[2 * this.from + 1]]; + } + + draw(colour = "black") { + const ctx = layerManager.getLayer("draw")!.ctx; + ctx.beginPath(); + ctx.strokeStyle = colour; + ctx.moveTo(g2lx(cdel.vertices[2 * this.from]), g2ly(cdel.vertices[2 * this.from + 1])); + ctx.lineTo(g2lx(cdel.vertices[2 * this.to]), g2ly(cdel.vertices[2 * this.to + 1])); + + ctx.closePath(); + ctx.stroke(); + } +} + +export class Triangle { + edges: number[] = []; + constructor(edges: number[]) { + this.edges = edges; + } + + getEdges(): Edge[] { + return this.edges.map(e => cdel.edges.getIndexValue(e)); + } + + edge(index: number): Edge { + return cdel.edges.getIndexValue(this.edges[index]); + } + + vertex(index: number): [number, number] { + const idx = this.edge(this.ccw(index)).from; + return [cdel.vertices[2 * idx], cdel.vertices[2 * idx + 1]]; + } + + index(edge: Edge) { + for (let i = 0; i < this.edges.length; i++) { + if (cdel.edges.getIndexValue(this.edges[i]) === edge) return i; + } + return -1; + } + + ccw(edge: number) { + return (edge + 2) % 3; + } + + cw(edge: number) { + return (edge + 1) % 3; + } + + neighbour(index: number) { + return this.edge(index).opposite.triangle; + } + + contains(point: GlobalPoint) { + const A = + -this.vertex(1)[1] * this.vertex(2)[0] + + this.vertex(0)[1] * (-this.vertex(1)[0] + this.vertex(2)[0]) + + this.vertex(0)[0] * (this.vertex(1)[1] - this.vertex(2)[1]) + + this.vertex(1)[0] * this.vertex(2)[1]; + const sign = A < 0 ? -1 : 1; + const s = + (this.vertex(0)[1] * this.vertex(2)[0] - + this.vertex(0)[0] * this.vertex(2)[1] + + (this.vertex(2)[1] - this.vertex(0)[1]) * point.x + + (this.vertex(0)[0] - this.vertex(2)[0]) * point.y) * + sign; + if (s < 0) return false; + const t = + (this.vertex(0)[0] * this.vertex(1)[1] - + this.vertex(0)[1] * this.vertex(1)[0] + + (this.vertex(0)[1] - this.vertex(1)[1]) * point.x + + (this.vertex(1)[0] - this.vertex(0)[0]) * point.y) * + sign; + + return t > 0 && s + t < A * sign; + } + + fill(colour = "rgba(0, 0, 0, 0.25)") { + const ctx = layerManager.getLayer("draw")!.ctx; + ctx.beginPath(); + ctx.strokeStyle = "rgba(0, 0, 0, 1)"; + ctx.fillStyle = colour; + ctx.lineJoin = "round"; + ctx.moveTo(g2lx(this.vertex(0)[0]), g2ly(this.vertex(0)[1])); + ctx.lineTo(g2lx(this.vertex(1)[0]), g2ly(this.vertex(1)[1])); + ctx.lineTo(g2lx(this.vertex(2)[0]), g2ly(this.vertex(2)[1])); + ctx.lineTo(g2lx(this.vertex(0)[0]), g2ly(this.vertex(0)[1])); + + ctx.closePath(); + ctx.fill(); + } +} + +export class CDEL { + vertices: number[]; + holes: number[]; + triangles: Triangle[] = []; + edges = new OrderedMap(); + isolatedVertices: number[] = []; + constructor(vertices: number[], holes: number[] = []) { + this.vertices = vertices; + this.holes = holes; + } + add_edge(edge: Edge) { + const key = `${edge.from}-${edge.to}`; + const revKey = `${edge.to}-${edge.from}`; + this.edges.set(key, edge); + } + add_triangle(triangle: Triangle) { + this.triangles.push(triangle); + for (const edge of triangle.edges) this.edges.getIndexValue(edge).triangleIndex = this.triangles.length - 1; + } + + locate(point: GlobalPoint): Triangle | undefined { + for (const triangle of this.triangles) { + if (triangle.contains(point)) return triangle; + } + } + + checkConstraints() { + if (this.holes.length === 0) return; + this.holes.splice(0, 0, 0); + for (let h = 0; h < this.holes.length; h++) { + const i = this.holes[h]; + const j = h + 1 === this.holes.length ? this.vertices.length / 2 : this.holes[h + 1]; + for (let k = i + 1; k < j; k++) { + this.edges.get(`${k - 1}-${k}`).constrained = true; + } + this.edges.get(`${j - 1}-${i}`).constrained = true; + } + this.holes.splice(0, 0); + } +} + +export function createCDEL(vertices: number[], holes: number[] = []) { + cdel = new CDEL(vertices, holes); +} + +createCDEL([]); diff --git a/client/src/game/visibility/earcut.d.ts b/client/src/game/visibility/earcut.d.ts new file mode 100644 index 000000000..c1d770753 --- /dev/null +++ b/client/src/game/visibility/earcut.d.ts @@ -0,0 +1,54 @@ +// Type definitions for earcut 2.1 +// Project: https://github.com/mapbox/earcut#readme +// Definitions by: Adrian Leonhard +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// Duplicated export mirrors actual source: https://github.com/mapbox/earcut/blob/master/src/earcut.js#L3-L4 + +interface EarcutStatic { + /** + * Triangulate an outline. + * + * @param vertices A flat array of vertice coordinates like [x0,y0, x1,y1, x2,y2, ...]. + * @param holes An array of hole indices if any (e.g. [5, 8] for a 12-vertice input would mean one hole with vertices 5–7 and another with 8–11). + * @param dimensions The number of coordinates per vertice in the input array (2 by default). + * @return A flat array with each group of three numbers indexing a triangle in the `vertices` array. + * @example earcut([10,0, 0,50, 60,60, 70,10]); // returns [1,0,3, 3,2,1] + * @example with a hole: earcut([0,0, 100,0, 100,100, 0,100, 20,20, 80,20, 80,80, 20,80], [4]); // [3,0,4, 5,4,0, 3,4,7, 5,0,1, 2,3,7, 6,5,1, 2,7,6, 6,1,2] + * @example with 3d coords: earcut([10,0,1, 0,50,2, 60,60,3, 70,10,4], null, 3); // [1,0,3, 3,2,1] + */ + (vertices: ArrayLike, holes?: ArrayLike, dimensions?: number): number[]; + + /** + * Transforms multi-dimensional array (e.g. GeoJSON Polygon) into the format expected by earcut. + * @example Transforming GeoJSON data. + * const data = earcut.flatten(geojson.geometry.coordinates); + * const triangles = earcut(data.vertices, data.holes, data.dimensions); + * @example Transforming simple triangle with hole: + * const data = earcut.flatten([[[0, 0], [100, 0], [0, 100]], [[10, 10], [0, 10], [10, 0]]]); + * const triangles = earcut(data.vertices, data.holes, data.dimensions); + * @param data Arrays of rings, with the first being the outline and the rest holes. A ring is an array points, each point being an array of numbers. + */ + flatten(data: ArrayLike>>): { vertices: number[]; holes: number[]; dimensions: number }; + + /** + * Returns the relative difference between the total area of triangles and the area of the input polygon. 0 means the triangulation is fully correct. + * @param vertices same as earcut + * @param holes same as earcut + * @param dimensions same as earcut + * @param triangles see return value of earcut + * @example + * const triangles = earcut(vertices, holes, dimensions); + * const deviation = earcut.deviation(vertices, holes, dimensions, triangles); + */ + deviation( + vertices: ArrayLike, + holes: ArrayLike | undefined, + dimensions: number, + triangles: ArrayLike, + ): number; + + default: EarcutStatic; +} +declare const exports: EarcutStatic; +export = exports; +export as namespace earcut; diff --git a/client/src/game/visibility/earcut.js b/client/src/game/visibility/earcut.js new file mode 100644 index 000000000..363b142e8 --- /dev/null +++ b/client/src/game/visibility/earcut.js @@ -0,0 +1,683 @@ +"use strict"; + +module.exports = earcut; +module.exports.default = earcut; + +function earcut(data, holeIndices, dim) { + dim = dim || 2; + + var hasHoles = holeIndices && holeIndices.length, + outerLen = hasHoles ? holeIndices[0] * dim : data.length, + outerNode = linkedList(data, 0, outerLen, dim, true), + triangles = []; + + if (!outerNode || outerNode.next === outerNode.prev) return triangles; + + var minX, minY, maxX, maxY, x, y, invSize; + + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length > 80 * dim) { + minX = maxX = data[0]; + minY = maxY = data[1]; + + for (var i = dim; i < outerLen; i += dim) { + x = data[i]; + y = data[i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } + + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + invSize = Math.max(maxX - minX, maxY - minY); + invSize = invSize !== 0 ? 1 / invSize : 0; + } + + earcutLinked(outerNode, triangles, dim, minX, minY, invSize); + + return triangles; +} + +// create a circular doubly linked list from polygon points in the specified winding order +function linkedList(data, start, end, dim, clockwise) { + var i, last; + + if (clockwise === signedArea(data, start, end, dim) > 0) { + for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last); + } else { + for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); + } + + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + + return last; +} + +// eliminate colinear or duplicate points +function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; + + var p = start, + again; + do { + again = false; + + if (!p.steiner && equals(p, p.next)) { + // if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) break; + again = true; + } else { + p = p.next; + } + } while (again || p !== end); + + return end; +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) +function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && invSize) indexCurve(ear, minX, minY, invSize); + + var stop = ear, + prev, + next; + + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + prev = ear.prev; + next = ear.next; + + if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { + // cut off the triangle + triangles.push(prev.i / dim); + triangles.push(ear.i / dim); + triangles.push(next.i / dim); + + removeNode(ear); + + // skipping the next vertex leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); + + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(ear, triangles, dim); + earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, invSize); + } + + break; + } + } +} + +// check whether a polygon node forms a valid ear with adjacent nodes +function isEar(ear) { + var a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + var p = ear.next.next; + + while (p !== ear.prev) { + if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.next; + } + + return true; +} + +function isEarHashed(ear, minX, minY, invSize) { + var a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : b.x < c.x ? b.x : c.x, + minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : b.y < c.y ? b.y : c.y, + maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : b.x > c.x ? b.x : c.x, + maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : b.y > c.y ? b.y : c.y; + + // z-order range for the current triangle bbox; + var minZ = zOrder(minTX, minTY, minX, minY, invSize), + maxZ = zOrder(maxTX, maxTY, minX, minY, invSize); + + var p = ear.prevZ, + n = ear.nextZ; + + // look for points inside the triangle in both directions + while (p && p.z >= minZ && n && n.z <= maxZ) { + if ( + p !== ear.prev && + p !== ear.next && + pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && + area(p.prev, p, p.next) >= 0 + ) + return false; + p = p.prevZ; + + if ( + n !== ear.prev && + n !== ear.next && + pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) && + area(n.prev, n, n.next) >= 0 + ) + return false; + n = n.nextZ; + } + + // look for remaining points in decreasing z-order + while (p && p.z >= minZ) { + if ( + p !== ear.prev && + p !== ear.next && + pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && + area(p.prev, p, p.next) >= 0 + ) + return false; + p = p.prevZ; + } + + // look for remaining points in increasing z-order + while (n && n.z <= maxZ) { + if ( + n !== ear.prev && + n !== ear.next && + pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) && + area(n.prev, n, n.next) >= 0 + ) + return false; + n = n.nextZ; + } + + return true; +} + +// go through all polygon nodes and cure small local self-intersections +function cureLocalIntersections(start, triangles, dim) { + var p = start; + do { + var a = p.prev, + b = p.next.next; + + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { + triangles.push(a.i / dim); + triangles.push(p.i / dim); + triangles.push(b.i / dim); + + // remove two nodes involved + removeNode(p); + removeNode(p.next); + + p = start = b; + } + p = p.next; + } while (p !== start); + + return p; +} + +// try splitting polygon into two and triangulate them independently +function splitEarcut(start, triangles, dim, minX, minY, invSize) { + // look for a valid diagonal that divides the polygon into two + var a = start; + do { + var b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + var c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, invSize); + earcutLinked(c, triangles, dim, minX, minY, invSize); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); +} + +// link every hole into the outer loop, producing a single-ring polygon without holes +function eliminateHoles(data, holeIndices, outerNode, dim) { + var queue = [], + i, + len, + start, + end, + list; + + for (i = 0, len = holeIndices.length; i < len; i++) { + start = holeIndices[i] * dim; + end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + list = linkedList(data, start, end, dim, false); + if (list === list.next) list.steiner = true; + queue.push(getLeftmost(list)); + } + + queue.sort(compareX); + + // process holes from left to right + for (i = 0; i < queue.length; i++) { + eliminateHole(queue[i], outerNode); + outerNode = filterPoints(outerNode, outerNode.next); + } + + return outerNode; +} + +function compareX(a, b) { + return a.x - b.x; +} + +// find a bridge between vertices that connects hole with an outer ring and and link it +function eliminateHole(hole, outerNode) { + outerNode = findHoleBridge(hole, outerNode); + if (outerNode) { + var b = splitPolygon(outerNode, hole); + filterPoints(b, b.next); + } +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon +function findHoleBridge(hole, outerNode) { + var p = outerNode, + hx = hole.x, + hy = hole.y, + qx = -Infinity, + m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + do { + if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { + var x = p.x + ((hy - p.y) * (p.next.x - p.x)) / (p.next.y - p.y); + if (x <= hx && x > qx) { + qx = x; + if (x === hx) { + if (hy === p.y) return p; + if (hy === p.next.y) return p.next; + } + m = p.x < p.next.x ? p : p.next; + } + } + p = p.next; + } while (p !== outerNode); + + if (!m) return null; + + if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + var stop = m, + mx = m.x, + my = m.y, + tanMin = Infinity, + tan; + + p = m.next; + + while (p !== stop) { + if ( + hx >= p.x && + p.x >= mx && + hx !== p.x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y) + ) { + tan = Math.abs(hy - p.y) / (hx - p.x); // tangential + + if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) { + m = p; + tanMin = tan; + } + } + + p = p.next; + } + + return m; +} + +// interlink polygon nodes in z-order +function indexCurve(start, minX, minY, invSize) { + var p = start; + do { + if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +function sortLinked(list) { + var i, + p, + q, + e, + tail, + numMerges, + pSize, + qSize, + inSize = 1; + + do { + p = list; + list = null; + tail = null; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i < inSize; i++) { + pSize++; + q = q.nextZ; + if (!q) break; + } + qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + } + + p = q; + } + + tail.nextZ = null; + inSize *= 2; + } while (numMerges > 1); + + return list; +} + +// z-order of a point given coords and inverse of the longer side of data bbox +function zOrder(x, y, minX, minY, invSize) { + // coords are transformed into non-negative 15-bit integer range + x = 32767 * (x - minX) * invSize; + y = 32767 * (y - minY) * invSize; + + x = (x | (x << 8)) & 0x00ff00ff; + x = (x | (x << 4)) & 0x0f0f0f0f; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00ff00ff; + y = (y | (y << 4)) & 0x0f0f0f0f; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +// find the leftmost node of a polygon ring +function getLeftmost(start) { + var p = start, + leftmost = start; + do { + if (p.x < leftmost.x) leftmost = p; + p = p.next; + } while (p !== start); + + return leftmost; +} + +// check if a point lies within a convex triangle +function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { + return ( + (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && + (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && + (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0 + ); +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +function isValidDiagonal(a, b) { + return ( + a.next.i !== b.i && + a.prev.i !== b.i && + !intersectsPolygon(a, b) && + locallyInside(a, b) && + locallyInside(b, a) && + middleInside(a, b) + ); +} + +// signed area of a triangle +function area(p, q, r) { + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); +} + +// check if two points are equal +function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; +} + +// check if two segments intersect +function intersects(p1, q1, p2, q2) { + if ((equals(p1, q1) && equals(p2, q2)) || (equals(p1, q2) && equals(p2, q1))) return true; + return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 && area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0; +} + +// check if a polygon diagonal intersects any polygon segments +function intersectsPolygon(a, b) { + var p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) + return true; + p = p.next; + } while (p !== a); + + return false; +} + +// check if a polygon diagonal is locally inside the polygon +function locallyInside(a, b) { + return area(a.prev, a, a.next) < 0 + ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 + : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; +} + +// check if the middle point of a polygon diagonal is inside the polygon +function middleInside(a, b) { + var p = a, + inside = false, + px = (a.x + b.x) / 2, + py = (a.y + b.y) / 2; + do { + if ( + p.y > py !== p.next.y > py && + p.next.y !== p.y && + px < ((p.next.x - p.x) * (py - p.y)) / (p.next.y - p.y) + p.x + ) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; +// if one belongs to the outer ring and another to a hole, it merges it into a single ring +function splitPolygon(a, b) { + var a2 = new Node(a.i, a.x, a.y), + b2 = new Node(b.i, b.x, b.y), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; +} + +// create a node and optionally link it with previous one (in a circular doubly linked list) +function insertNode(i, x, y, last) { + var p = new Node(i, x, y); + + if (!last) { + p.prev = p; + p.next = p; + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; +} + +function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; + + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; +} + +function Node(i, x, y) { + // vertex index in coordinates array + this.i = i; + + // vertex coordinates + this.x = x; + this.y = y; + + // previous and next vertex nodes in a polygon ring + this.prev = null; + this.next = null; + + // z-order curve value + this.z = null; + + // previous and next nodes in z-order + this.prevZ = null; + this.nextZ = null; + + // indicates whether this is a steiner point + this.steiner = false; +} + +// return a percentage difference between the polygon area and its triangulation area; +// used to verify correctness of triangulation +earcut.deviation = function(data, holeIndices, dim, triangles) { + var hasHoles = holeIndices && holeIndices.length; + var outerLen = hasHoles ? holeIndices[0] * dim : data.length; + + var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); + if (hasHoles) { + for (var i = 0, len = holeIndices.length; i < len; i++) { + var start = holeIndices[i] * dim; + var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + polygonArea -= Math.abs(signedArea(data, start, end, dim)); + } + } + + var trianglesArea = 0; + for (i = 0; i < triangles.length; i += 3) { + var a = triangles[i] * dim; + var b = triangles[i + 1] * dim; + var c = triangles[i + 2] * dim; + trianglesArea += Math.abs( + (data[a] - data[c]) * (data[b + 1] - data[a + 1]) - (data[a] - data[b]) * (data[c + 1] - data[a + 1]), + ); + } + + return polygonArea === 0 && trianglesArea === 0 ? 0 : Math.abs((trianglesArea - polygonArea) / polygonArea); +}; + +function signedArea(data, start, end, dim) { + var sum = 0; + for (var i = start, j = end - dim; i < end; i += dim) { + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); + j = i; + } + return sum; +} + +// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts +earcut.flatten = function(data) { + var dim = data[0][0].length, + result = { vertices: [], holes: [], dimensions: dim }, + holeIndex = 0; + + for (var i = 0; i < data.length; i++) { + for (var j = 0; j < data[i].length; j++) { + for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]); + } + if (i > 0) { + holeIndex += data[i - 1].length; + result.holes.push(holeIndex); + } + } + return result; +}; diff --git a/client/src/game/bvh/node.ts b/client/src/game/visibility/node.ts similarity index 100% rename from client/src/game/bvh/node.ts rename to client/src/game/visibility/node.ts diff --git a/client/src/game/visibility/oldvis.ts b/client/src/game/visibility/oldvis.ts new file mode 100644 index 000000000..cc80ed507 --- /dev/null +++ b/client/src/game/visibility/oldvis.ts @@ -0,0 +1,63 @@ +import earcut from "earcut"; + +import { GlobalPoint } from "../geom"; +import { layerManager } from "../layers/manager"; +import { g2lx, g2ly } from "../units"; +import { generate } from "./triangulate"; + +export function draw(vertices: number[], triangles: number[]) { + const ctx = layerManager.getLayer("draw")!.ctx; + ctx.beginPath(); + ctx.strokeStyle = "rgba(0, 0, 0, 1)"; + ctx.lineJoin = "round"; + ctx.moveTo(g2lx(vertices[triangles[0]]), g2ly(vertices[triangles[0] + 1])); + for (let t = 0; t < triangles.length; t += 3) { + ctx.moveTo(g2lx(vertices[2 * triangles[t]]), g2ly(vertices[2 * triangles[t] + 1])); + ctx.lineTo(g2lx(vertices[2 * triangles[t + 1]]), g2ly(vertices[2 * triangles[t + 1] + 1])); + ctx.lineTo(g2lx(vertices[2 * triangles[t + 2]]), g2ly(vertices[2 * triangles[t + 2] + 1])); + ctx.lineTo(g2lx(vertices[2 * triangles[t]]), g2ly(vertices[2 * triangles[t] + 1])); + } + + ctx.closePath(); + ctx.stroke(); +} + +export function x(dd = []) { + const g = generate(dd); + console.log(g); + const e = earcut(g.vertices, g.holes); + console.log(e); + draw(g.vertices, e); + console.log(getTrig(layerManager.getLayer()!.selection[0].refPoint, g.vertices, e)); +} + +function inTrig(point: GlobalPoint, trig: number[]) { + console.log(`Testing ${trig}`); + const A = -trig[3] * trig[4] + trig[1] * (-trig[2] + trig[4]) + trig[0] * (trig[3] - trig[5]) + trig[2] * trig[5]; + const sign = A < 0 ? -1 : 1; + const s = + (trig[1] * trig[4] - trig[0] * trig[5] + (trig[5] - trig[1]) * point.x + (trig[0] - trig[4]) * point.y) * sign; + if (s < 0) return false; + const t = + (trig[0] * trig[3] - trig[1] * trig[2] + (trig[1] - trig[3]) * point.x + (trig[2] - trig[0]) * point.y) * sign; + + return s + t < A * sign; +} + +function getTrig(p: GlobalPoint, vertices: number[], triangles: number[]) { + for (let t = 0; t < triangles.length; t += 3) { + if ( + inTrig(p, [ + vertices[2 * triangles[t]], + vertices[2 * triangles[t] + 1], + vertices[2 * triangles[t + 1]], + vertices[2 * triangles[t + 1] + 1], + vertices[2 * triangles[t + 2]], + vertices[2 * triangles[t + 2] + 1], + ]) + ) { + return t; + } + } + return -1; +} diff --git a/client/src/game/visibility/polygon-clipping.d.ts b/client/src/game/visibility/polygon-clipping.d.ts new file mode 100644 index 000000000..7de8c3c77 --- /dev/null +++ b/client/src/game/visibility/polygon-clipping.d.ts @@ -0,0 +1,3 @@ +declare module "polygon-clipping" { + export function union(...shapes: number[][][][]): number[][][][]; +} diff --git a/client/src/game/visibility/triangulate.ts b/client/src/game/visibility/triangulate.ts new file mode 100644 index 000000000..cb75e4ebf --- /dev/null +++ b/client/src/game/visibility/triangulate.ts @@ -0,0 +1,252 @@ +// import earcut from "earcut"; +import polygon from "polygon-clipping"; +import earcut from "./earcut.js"; + +import { GlobalPoint, Vector } from "../geom"; +import { layerManager } from "../layers/manager"; +import { g2lx, g2ly } from "../units"; +import { cdel, createCDEL, Edge, Triangle } from "./cdel"; + +export function triangulate(shapes: string[]) { + const g = generate(reduce(shapes)); + return triag(g.vertices, g.holes); +} + +(window).p = polygon; + +function reduce(shapes: string[]) { + // const reduced: number[][] = []; + // for (const shapeId of shapes) { + // const shape = layerManager.UUIDMap.get(shapeId)!; + // for (let i = reduced.length - 1; i >= 0; i--) { + // const un = polygon.union([reduced[i]], shape.points); + // if (un.length === 1) { + // reduced[i] = + // } + // } + // } + const points: number[][][][] = []; + for (const shape of shapes) { + const p = layerManager.UUIDMap.get(shape)!.points; + console.log(p); + if (p.length > 0) points.push([p]); + } + console.log(points); + return polygon.union(...points); +} + +function generate(shapes: number[][][][]) { + const vertices = [0, 0, 0, 0, 0, 0, 0, 0]; + let minX = 0; + let minY = 0; + let maxX = 0; + let maxY = 0; + const holes = []; + for (const polygon of shapes) { + holes.push(vertices.length / 2); + // vertices.push( + // rect.refPoint.x, + // rect.refPoint.y, + // topright.x, + // topright.y, + // botright.x, + // botright.y, + // botleft.x, + // botleft.y, + // ); + // if (minX > rect.refPoint.x) minX = rect.refPoint.x; + // if (minY > rect.refPoint.y) minY = rect.refPoint.y; + // if (maxX < botright.x) maxX = botright.x; + // if (maxY < botright.y) maxY = botright.y; + } + minX -= 1e6; + minY -= 1e6; + maxX += 1e6; + maxY += 1e6; + vertices.splice(0, 8, minX, minY, maxX, minY, maxX, maxY, minX, maxY); + return { vertices, holes }; +} + +function triag(vertices: number[], holes: number[]) { + createCDEL(vertices, holes); + const triangles = earcut(vertices, holes); + for (let t = 0; t < triangles.length; t += 3) { + cdel.add_edge(new Edge(triangles[t], triangles[t + 1])); + cdel.add_edge(new Edge(triangles[t + 1], triangles[t + 2])); + cdel.add_edge(new Edge(triangles[t + 2], triangles[t])); + const tl = cdel.edges.length; + cdel.add_triangle(new Triangle([tl - 3, tl - 2, tl - 1])); + } + cdel.checkConstraints(); + return cdel; +} + +function draw() { + const ctx = layerManager.getLayer("draw")!.ctx; + ctx.beginPath(); + ctx.strokeStyle = "rgba(0, 0, 0, 0.25)"; + ctx.lineJoin = "round"; + for (const triangle of cdel.triangles) { + ctx.moveTo(g2lx(triangle.vertex(0)[0]), g2ly(triangle.vertex(0)[1])); + ctx.lineTo(g2lx(triangle.vertex(1)[0]), g2ly(triangle.vertex(1)[1])); + ctx.lineTo(g2lx(triangle.vertex(2)[0]), g2ly(triangle.vertex(2)[1])); + ctx.lineTo(g2lx(triangle.vertex(0)[0]), g2ly(triangle.vertex(0)[1])); + } + + for (let i = 0; i < cdel.vertices.length; i += 2) { + ctx.fillText(`${i / 2}`, g2lx(cdel.vertices[i]), g2ly(cdel.vertices[i + 1])); + } + + ctx.closePath(); + ctx.stroke(); +} + +export function computeVisibility(q: GlobalPoint, it = 0): number[][] { + const rawOutput: number[][] = []; + const triangle = cdel.locate(q); + if (triangle === undefined) { + if (it > 10) { + console.error("Triangle not found"); + return []; + } + return computeVisibility(q.add(new Vector(0.001, 0.001)), it++); + } + // triangle.fill(); + for (const [index, edge] of triangle.getEdges().entries()) { + rawOutput.push(edge.fromVertex); + if (!edge.constrained) expandEdge(q, edge.fromVertex, edge.toVertex, triangle, index, rawOutput); + } + + // const outArr = output(rawOutput); + + // const ctx = layerManager.getLayer("draw")!.ctx; + // // ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + // ctx.beginPath(); + // ctx.strokeStyle = `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`; + // ctx.lineJoin = "round"; + // ctx.moveTo(g2lx(rawOutput[0][0]), g2ly(rawOutput[0][1])); + // for (const point of rawOutput.slice(1)) { + // ctx.lineTo(g2lx(point[0]), g2ly(point[1])); + // } + // ctx.closePath(); + // ctx.stroke(); + + // return outArr; + return rawOutput; +} + +function expandEdge( + q: GlobalPoint, + left: number[], + right: number[], + fh: Triangle, + index: number, + rawOutput: number[][], +): void { + // fh.edge(index).draw(); + const nfh = fh.neighbour(index); + // nfh.fill("rgba(255, 0, 0, 0.25)"); + const nIndex = nfh.index(fh.edge(index).opposite); + const lIndex = nfh.cw(nIndex); + const rIndex = nfh.ccw(nIndex); + const nvh = nfh.vertex(nIndex); + const lvh = nfh.vertex(rIndex); + const rvh = nfh.vertex(lIndex); + + const le = nfh.edge(lIndex); + const re = nfh.edge(rIndex); + + const lo = orientation(q, left, nvh); + const ro = orientation(q, right, nvh); + + // const ctx = layerManager.getLayer("draw")!.ctx; + // ctx.beginPath(); + // ctx.strokeStyle = "red"; + // ctx.lineJoin = "round"; + // ctx.moveTo(g2lx(q.x), g2ly(q.y)); + // ctx.lineTo(g2lx(left[0]), g2ly(left[1])); + // ctx.moveTo(g2lx(q.x), g2ly(q.y)); + // ctx.lineTo(g2lx(nvh[0]), g2ly(nvh[1])); + // ctx.strokeStyle = "blue"; + // ctx.moveTo(g2lx(q.x), g2ly(q.y)); + // ctx.lineTo(g2lx(right[0]), g2ly(right[1])); + // ctx.moveTo(g2lx(q.x), g2ly(q.y)); + // ctx.lineTo(g2lx(nvh[0]), g2ly(nvh[1])); + // ctx.closePath(); + // ctx.stroke(); + + if (lo < 0) { + if (le.constrained) { + if (left[0] !== lvh[0] || left[1] !== lvh[1]) { + rawOutput.push(raySegIntersection(q, left, nvh, lvh)); + } + if (ro < 0) { + rawOutput.push(raySegIntersection(q, right, nvh, lvh)); + } + // return; + } else { + if (ro < 0) { + return expandEdge(q, left, right, nfh, lIndex, rawOutput); + } else { + expandEdge(q, left, nvh, nfh, lIndex, rawOutput); + } + } + } + + if (ro >= 0 && lo <= 0) { + rawOutput.push(nvh); + } + + // Right edge is seen if the new vertex is ccw of the right ray + if (ro > 0) { + if (re.constrained) { + if (lo > 0) { + rawOutput.push(raySegIntersection(q, left, nvh, rvh)); + } + // See if current right ray is rvh + if (right[0] !== rvh[0] || right[1] !== rvh[1]) { + rawOutput.push(raySegIntersection(q, right, nvh, rvh)); + } + return; + } else { + if (lo > 0) { + return expandEdge(q, left, right, nfh, rIndex, rawOutput); + } else { + return expandEdge(q, nvh, right, nfh, rIndex, rawOutput); + } + } + } +} + +function raySegIntersection(q: GlobalPoint, b: number[], s: number[], t: number[]) { + const denominator = (t[1] - s[1]) * (b[0] - q.x) - (t[0] - s[0]) * (b[1] - q.y); + const ua = ((t[0] - s[0]) * (q.y - s[1]) - (t[1] - s[1]) * (q.x - s[0])) / denominator; + // const ub = ((b[0] - q.x) * (q.y - s[1]) - (b[1] - q.y) * (q.x - s[0])) / denominator; + const x = q.x + ua * (b[0] - q.x); + const y = q.y + ua * (b[1] - q.y); + + return [x, y]; +} + +// // ~Shoelace formula +// // > 0 CCW < 0 CW +// function orientation(point: GlobalPoint, from: number[], to: number[]) { +// return ( +// point.x * from[1] + from[0] * to[1] + to[0] * point.y - from[0] * point.y - to[0] * from[1] - point.x * to[1] +// ); +// } + +// > 0 CCW < 0 CW +function orientation(a: GlobalPoint, b: number[], c: number[]) { + const dAx = b[0] - a.x; + const dAy = b[1] - a.y; + const dBx = c[0] - a.x; + const dBy = c[1] - a.y; + return -Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy); +} + +(window).D = draw; +(window).G = generate; +(window).E = earcut; +(window).T = triag; +(window).CV = computeVisibility; From 6072c10c659e10069436e2ade1f318201adae49a Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Tue, 1 Jan 2019 11:38:21 +0100 Subject: [PATCH 02/18] WIP 2 - earcut mostly works - inner rings problem --- client/src/game/shapes/line.ts | 3 + client/src/game/shapes/multiline.ts | 15 +-- client/src/game/shapes/text.ts | 3 + client/src/game/ui/tools/draw.vue | 2 +- client/src/game/visibility/cdel.ts | 2 +- client/src/game/visibility/oldvis.ts | 112 +++++++++++----------- client/src/game/visibility/triangulate.ts | 56 ++++++----- 7 files changed, 106 insertions(+), 87 deletions(-) diff --git a/client/src/game/shapes/line.ts b/client/src/game/shapes/line.ts index c20db7b9c..2a97fbee6 100644 --- a/client/src/game/shapes/line.ts +++ b/client/src/game/shapes/line.ts @@ -25,6 +25,9 @@ export class Line extends Shape { line_width: this.lineWidth, }); } + get points() { + return [[this.refPoint.x, this.refPoint.y], [this.endPoint.x, this.endPoint.y]]; + } getBoundingBox(): BoundingRect { return new BoundingRect( new GlobalPoint(Math.min(this.refPoint.x, this.endPoint.x), Math.min(this.refPoint.x, this.endPoint.y)), diff --git a/client/src/game/shapes/multiline.ts b/client/src/game/shapes/multiline.ts index ced5e44dc..572b9d8b4 100644 --- a/client/src/game/shapes/multiline.ts +++ b/client/src/game/shapes/multiline.ts @@ -6,7 +6,7 @@ import { getFogColour } from "@/game/utils"; export class MultiLine extends Shape { type = "multiline"; - points: GlobalPoint[] = []; + _points: GlobalPoint[] = []; lineWidth: number; constructor( startPoint: GlobalPoint, @@ -16,21 +16,24 @@ export class MultiLine extends Shape { uuid?: string, ) { super(startPoint, "rgba(0, 0, 0, 0)", strokeColour || "#000", uuid); - this.points = points || []; + this._points = points || []; this.lineWidth = lineWidth || 3; } asDict() { return Object.assign(this.getBaseDict(), { line_width: this.lineWidth, - points: this.points.map(p => ({ x: p.x, y: p.y })), + points: this._points.map(p => ({ x: p.x, y: p.y })), }); } + get points() { + return this._points.map(point => [point.x, point.y]); + } getBoundingBox(): BoundingRect { let minx: number = this.refPoint.x; let maxx: number = this.refPoint.y; let miny: number = this.refPoint.x; let maxy: number = this.refPoint.y; - for (const p of this.points) { + for (const p of this._points) { if (p.x < minx) minx = p.x; if (p.x > maxx) maxx = p.x; if (p.y < miny) miny = p.y; @@ -44,14 +47,14 @@ export class MultiLine extends Shape { ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.moveTo(g2lx(this.refPoint.x), g2ly(this.refPoint.y)); - for (const p of this.points) ctx.lineTo(g2lx(p.x), g2ly(p.y)); + for (const p of this._points) ctx.lineTo(g2lx(p.x), g2ly(p.y)); if (this.strokeColour === "fog") ctx.strokeStyle = getFogColour(); else ctx.strokeStyle = this.strokeColour; ctx.lineWidth = g2lz(this.lineWidth); ctx.stroke(); } contains(point: GlobalPoint): boolean { - return this.points.includes(point); + return this._points.includes(point); } center(): GlobalPoint; diff --git a/client/src/game/shapes/text.ts b/client/src/game/shapes/text.ts index 7f5dd0413..52f49fe77 100644 --- a/client/src/game/shapes/text.ts +++ b/client/src/game/shapes/text.ts @@ -29,6 +29,9 @@ export class Text extends Shape { angle: this.angle, }); } + get points() { + return [[this.refPoint.x, this.refPoint.y]]; + } getBoundingBox(): BoundingRect { return new BoundingRect(this.refPoint, 5, 5); // TODO: fix this bounding box } diff --git a/client/src/game/ui/tools/draw.vue b/client/src/game/ui/tools/draw.vue index 23e35577e..2a1fcae1b 100644 --- a/client/src/game/ui/tools/draw.vue +++ b/client/src/game/ui/tools/draw.vue @@ -205,7 +205,7 @@ export default class DrawTool extends Tool { } else if (this.shapeSelect === "circle") { (this.shape).r = endPoint.subtract(this.startPoint).length(); } else if (this.shapeSelect === "paint-brush") { - (this.shape).points.push(endPoint); + (this.shape)._points.push(endPoint); } socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: false }); if (this.shape.visionObstruction) gameStore.recalculateBV(); diff --git a/client/src/game/visibility/cdel.ts b/client/src/game/visibility/cdel.ts index 74931ad2f..13009a7f1 100644 --- a/client/src/game/visibility/cdel.ts +++ b/client/src/game/visibility/cdel.ts @@ -127,7 +127,7 @@ export class CDEL { holes: number[]; triangles: Triangle[] = []; edges = new OrderedMap(); - isolatedVertices: number[] = []; + isolatedVertices: number[][][][] = []; constructor(vertices: number[], holes: number[] = []) { this.vertices = vertices; this.holes = holes; diff --git a/client/src/game/visibility/oldvis.ts b/client/src/game/visibility/oldvis.ts index cc80ed507..377818f1f 100644 --- a/client/src/game/visibility/oldvis.ts +++ b/client/src/game/visibility/oldvis.ts @@ -1,63 +1,63 @@ -import earcut from "earcut"; +// import earcut from "earcut"; -import { GlobalPoint } from "../geom"; -import { layerManager } from "../layers/manager"; -import { g2lx, g2ly } from "../units"; -import { generate } from "./triangulate"; +// import { GlobalPoint } from "../geom"; +// import { layerManager } from "../layers/manager"; +// import { g2lx, g2ly } from "../units"; +// import { generate } from "./triangulate"; -export function draw(vertices: number[], triangles: number[]) { - const ctx = layerManager.getLayer("draw")!.ctx; - ctx.beginPath(); - ctx.strokeStyle = "rgba(0, 0, 0, 1)"; - ctx.lineJoin = "round"; - ctx.moveTo(g2lx(vertices[triangles[0]]), g2ly(vertices[triangles[0] + 1])); - for (let t = 0; t < triangles.length; t += 3) { - ctx.moveTo(g2lx(vertices[2 * triangles[t]]), g2ly(vertices[2 * triangles[t] + 1])); - ctx.lineTo(g2lx(vertices[2 * triangles[t + 1]]), g2ly(vertices[2 * triangles[t + 1] + 1])); - ctx.lineTo(g2lx(vertices[2 * triangles[t + 2]]), g2ly(vertices[2 * triangles[t + 2] + 1])); - ctx.lineTo(g2lx(vertices[2 * triangles[t]]), g2ly(vertices[2 * triangles[t] + 1])); - } +// export function draw(vertices: number[], triangles: number[]) { +// const ctx = layerManager.getLayer("draw")!.ctx; +// ctx.beginPath(); +// ctx.strokeStyle = "rgba(0, 0, 0, 1)"; +// ctx.lineJoin = "round"; +// ctx.moveTo(g2lx(vertices[triangles[0]]), g2ly(vertices[triangles[0] + 1])); +// for (let t = 0; t < triangles.length; t += 3) { +// ctx.moveTo(g2lx(vertices[2 * triangles[t]]), g2ly(vertices[2 * triangles[t] + 1])); +// ctx.lineTo(g2lx(vertices[2 * triangles[t + 1]]), g2ly(vertices[2 * triangles[t + 1] + 1])); +// ctx.lineTo(g2lx(vertices[2 * triangles[t + 2]]), g2ly(vertices[2 * triangles[t + 2] + 1])); +// ctx.lineTo(g2lx(vertices[2 * triangles[t]]), g2ly(vertices[2 * triangles[t] + 1])); +// } - ctx.closePath(); - ctx.stroke(); -} +// ctx.closePath(); +// ctx.stroke(); +// } -export function x(dd = []) { - const g = generate(dd); - console.log(g); - const e = earcut(g.vertices, g.holes); - console.log(e); - draw(g.vertices, e); - console.log(getTrig(layerManager.getLayer()!.selection[0].refPoint, g.vertices, e)); -} +// export function x(dd = []) { +// const g = generate(dd); +// console.log(g); +// const e = earcut(g.vertices, g.holes); +// console.log(e); +// draw(g.vertices, e); +// console.log(getTrig(layerManager.getLayer()!.selection[0].refPoint, g.vertices, e)); +// } -function inTrig(point: GlobalPoint, trig: number[]) { - console.log(`Testing ${trig}`); - const A = -trig[3] * trig[4] + trig[1] * (-trig[2] + trig[4]) + trig[0] * (trig[3] - trig[5]) + trig[2] * trig[5]; - const sign = A < 0 ? -1 : 1; - const s = - (trig[1] * trig[4] - trig[0] * trig[5] + (trig[5] - trig[1]) * point.x + (trig[0] - trig[4]) * point.y) * sign; - if (s < 0) return false; - const t = - (trig[0] * trig[3] - trig[1] * trig[2] + (trig[1] - trig[3]) * point.x + (trig[2] - trig[0]) * point.y) * sign; +// function inTrig(point: GlobalPoint, trig: number[]) { +// console.log(`Testing ${trig}`); +// const A = -trig[3] * trig[4] + trig[1] * (-trig[2] + trig[4]) + trig[0] * (trig[3] - trig[5]) + trig[2] * trig[5]; +// const sign = A < 0 ? -1 : 1; +// const s = +// (trig[1] * trig[4] - trig[0] * trig[5] + (trig[5] - trig[1]) * point.x + (trig[0] - trig[4]) * point.y) * sign; +// if (s < 0) return false; +// const t = +// (trig[0] * trig[3] - trig[1] * trig[2] + (trig[1] - trig[3]) * point.x + (trig[2] - trig[0]) * point.y) * sign; - return s + t < A * sign; -} +// return s + t < A * sign; +// } -function getTrig(p: GlobalPoint, vertices: number[], triangles: number[]) { - for (let t = 0; t < triangles.length; t += 3) { - if ( - inTrig(p, [ - vertices[2 * triangles[t]], - vertices[2 * triangles[t] + 1], - vertices[2 * triangles[t + 1]], - vertices[2 * triangles[t + 1] + 1], - vertices[2 * triangles[t + 2]], - vertices[2 * triangles[t + 2] + 1], - ]) - ) { - return t; - } - } - return -1; -} +// function getTrig(p: GlobalPoint, vertices: number[], triangles: number[]) { +// for (let t = 0; t < triangles.length; t += 3) { +// if ( +// inTrig(p, [ +// vertices[2 * triangles[t]], +// vertices[2 * triangles[t] + 1], +// vertices[2 * triangles[t + 1]], +// vertices[2 * triangles[t + 1] + 1], +// vertices[2 * triangles[t + 2]], +// vertices[2 * triangles[t + 2] + 1], +// ]) +// ) { +// return t; +// } +// } +// return -1; +// } diff --git a/client/src/game/visibility/triangulate.ts b/client/src/game/visibility/triangulate.ts index cb75e4ebf..7092f4baf 100644 --- a/client/src/game/visibility/triangulate.ts +++ b/client/src/game/visibility/triangulate.ts @@ -5,11 +5,25 @@ import earcut from "./earcut.js"; import { GlobalPoint, Vector } from "../geom"; import { layerManager } from "../layers/manager"; import { g2lx, g2ly } from "../units"; -import { cdel, createCDEL, Edge, Triangle } from "./cdel"; +import { cdel, CDEL, createCDEL, Edge, Triangle } from "./cdel"; + +/* +Triangle expansion algorithm +============================= +Based upon https://arxiv.org/pdf/1403.3905.pdf and the CGAL implementation. + +The polygon-clipping library returns polygons in CCW order and always adds the first point +as the last point again to form a complete polygon. + +The earcut library expects a flat list of vertices followed by a list of indices that points +to the holes in the vertices list. +*/ export function triangulate(shapes: string[]) { - const g = generate(reduce(shapes)); - return triag(g.vertices, g.holes); + if (shapes.length === 0) return new CDEL([]); + const sh = reduce(shapes); + const g = generate(sh); + return triag(g.vertices, g.holes, sh); } (window).p = polygon; @@ -28,10 +42,8 @@ function reduce(shapes: string[]) { const points: number[][][][] = []; for (const shape of shapes) { const p = layerManager.UUIDMap.get(shape)!.points; - console.log(p); if (p.length > 0) points.push([p]); } - console.log(points); return polygon.union(...points); } @@ -42,22 +54,19 @@ function generate(shapes: number[][][][]) { let maxX = 0; let maxY = 0; const holes = []; - for (const polygon of shapes) { + for (const shape of shapes) { holes.push(vertices.length / 2); - // vertices.push( - // rect.refPoint.x, - // rect.refPoint.y, - // topright.x, - // topright.y, - // botright.x, - // botright.y, - // botleft.x, - // botleft.y, - // ); - // if (minX > rect.refPoint.x) minX = rect.refPoint.x; - // if (minY > rect.refPoint.y) minY = rect.refPoint.y; - // if (maxX < botright.x) maxX = botright.x; - // if (maxY < botright.y) maxY = botright.y; + for (const sha of shape) { + for (const [idx, point] of sha.reverse().entries()) { + if (idx === shape[0].length - 1) continue; + vertices.push(point[0], point[1]); + + if (minX > point[0]) minX = point[0]; + if (minY > point[1]) minY = point[1]; + if (maxX < point[0]) maxX = point[0]; + if (maxY < point[1]) maxY = point[1]; + } + } } minX -= 1e6; minY -= 1e6; @@ -67,7 +76,7 @@ function generate(shapes: number[][][][]) { return { vertices, holes }; } -function triag(vertices: number[], holes: number[]) { +function triag(vertices: number[], holes: number[], shapes: number[][][][]) { createCDEL(vertices, holes); const triangles = earcut(vertices, holes); for (let t = 0; t < triangles.length; t += 3) { @@ -77,7 +86,8 @@ function triag(vertices: number[], holes: number[]) { const tl = cdel.edges.length; cdel.add_triangle(new Triangle([tl - 3, tl - 2, tl - 1])); } - cdel.checkConstraints(); + draw(); + // cdel.checkConstraints(); return cdel; } @@ -248,5 +258,5 @@ function orientation(a: GlobalPoint, b: number[], c: number[]) { (window).D = draw; (window).G = generate; (window).E = earcut; -(window).T = triag; +(window).T = triangulate; (window).CV = computeVisibility; From 3fdc9d8f355e82ac1acd6c2517ca0a01e088bdc5 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Tue, 1 Jan 2019 22:11:34 +0100 Subject: [PATCH 03/18] Reduce :psyboom: --- client/src/game/game.vue | 2 +- client/src/game/layers/fowplayers.ts | 30 +-- client/src/game/store.ts | 2 +- client/src/game/ui/tools/draw.vue | 7 +- client/src/game/visibility/bvh.ts | 2 +- .../src/game/visibility/{cdel.ts => dcel.ts} | 47 ++-- .../src/game/visibility/polygon-clipping.d.ts | 2 + client/src/game/visibility/te/draw.ts | 22 ++ client/src/game/visibility/triangulate.ts | 230 ++++++++++++------ 9 files changed, 226 insertions(+), 118 deletions(-) rename client/src/game/visibility/{cdel.ts => dcel.ts} (76%) create mode 100644 client/src/game/visibility/te/draw.ts diff --git a/client/src/game/game.vue b/client/src/game/game.vue index 183862de6..053ffe8a9 100644 --- a/client/src/game/game.vue +++ b/client/src/game/game.vue @@ -37,7 +37,7 @@ v-model="zoomFactor" :height="6" :width="200" - :min="0.1" + :min="0.01" :max="5.0" :interval="0.1" :dot-width="8" diff --git a/client/src/game/layers/fowplayers.ts b/client/src/game/layers/fowplayers.ts index 45ff19178..a8f1edbb3 100644 --- a/client/src/game/layers/fowplayers.ts +++ b/client/src/game/layers/fowplayers.ts @@ -8,11 +8,11 @@ import { computeVisibility } from "../visibility/triangulate"; export class FOWPlayersLayer extends Layer { isVisionLayer: boolean = true; - mode = "bvh"; + mode = ""; draw(): void { if (!this.valid) { - console.time("VI"); + // console.time("VI"); const ctx = this.ctx; if (!gameStore.fowLOS || Settings.skipPlayerFOW) { @@ -23,8 +23,8 @@ export class FOWPlayersLayer extends Layer { ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - // const drctx = layerManager.getLayer("draw")!.ctx; - // drctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + const drctx = layerManager.getLayer("draw")!.ctx; + drctx.clearRect(0, 0, window.innerWidth, window.innerHeight); const originalOperation = ctx.globalCompositeOperation; @@ -77,18 +77,14 @@ export class FOWPlayersLayer extends Layer { else ctx.closePath(); ctx.fill(); } else { - // const canvas = document.createElement("canvas"); - // canvas.width = window.innerWidth; - // canvas.height = window.innerHeight; - // const pctx = canvas.getContext("2d")!; - const polygon = computeVisibility(token.center()); - // pctx.globalCompositeOperation = "source-in"; - ctx.beginPath(); - ctx.moveTo(g2lx(polygon[0][0]), g2ly(polygon[0][1])); - for (const point of polygon) ctx.lineTo(g2lx(point[0]), g2ly(point[1])); - ctx.closePath(); - ctx.fill(); - // ctx.drawImage(canvas, 0, 0); + try { + const polygon = computeVisibility(token.center()); + ctx.beginPath(); + ctx.moveTo(g2lx(polygon[0][0]), g2ly(polygon[0][1])); + for (const point of polygon) ctx.lineTo(g2lx(point[0]), g2ly(point[1])); + ctx.closePath(); + ctx.fill(); + } catch {} } } @@ -97,7 +93,7 @@ export class FOWPlayersLayer extends Layer { if (gameStore.IS_DM) super.draw(!gameStore.fullFOW); ctx.globalCompositeOperation = originalOperation; - console.timeEnd("VI"); + // console.timeEnd("VI"); } } } diff --git a/client/src/game/store.ts b/client/src/game/store.ts index fe69fa1cd..65e06f8a4 100644 --- a/client/src/game/store.ts +++ b/client/src/game/store.ts @@ -152,7 +152,7 @@ class GameStore extends VuexModule { @Mutation updateZoom(data: { newZoomValue: number; zoomLocation: GlobalPoint }) { if (data.newZoomValue === this.zoomFactor) return; - if (data.newZoomValue < 0.1) data.newZoomValue = 0.1; + if (data.newZoomValue < 0.1) data.newZoomValue = 0.01; if (data.newZoomValue > 5) data.newZoomValue = 5; const oldLoc = g2l(data.zoomLocation); diff --git a/client/src/game/ui/tools/draw.vue b/client/src/game/ui/tools/draw.vue index 2a1fcae1b..962c5dfe7 100644 --- a/client/src/game/ui/tools/draw.vue +++ b/client/src/game/ui/tools/draw.vue @@ -207,15 +207,16 @@ export default class DrawTool extends Tool { } else if (this.shapeSelect === "paint-brush") { (this.shape)._points.push(endPoint); } - socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: false }); + socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: true }); if (this.shape.visionObstruction) gameStore.recalculateBV(); layer.invalidate(false); } onMouseUp(event: MouseEvent) { - if (this.active && this.shape !== null && !event.altKey && this.useGrid) { + if (!this.active || this.shape === null) return; + if (!event.altKey && this.useGrid) { this.shape.resizeToGrid(); - socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: false }); } + socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: false }); this.active = false; } onSelect() { diff --git a/client/src/game/visibility/bvh.ts b/client/src/game/visibility/bvh.ts index af6a64d10..840990bd6 100644 --- a/client/src/game/visibility/bvh.ts +++ b/client/src/game/visibility/bvh.ts @@ -36,7 +36,7 @@ export class BoundingVolume { offset = 0; constructor(shapes: string[]) { - tr.triangulate(shapes); + tr.triangulate(shapes, true); this.shapes = shapes; if (this.shapes.length === 0) { this.root = null; diff --git a/client/src/game/visibility/cdel.ts b/client/src/game/visibility/dcel.ts similarity index 76% rename from client/src/game/visibility/cdel.ts rename to client/src/game/visibility/dcel.ts index 13009a7f1..bcef85315 100644 --- a/client/src/game/visibility/cdel.ts +++ b/client/src/game/visibility/dcel.ts @@ -3,7 +3,7 @@ import { GlobalPoint } from "../geom"; import { layerManager } from "../layers/manager"; import { g2lx, g2ly } from "../units"; -export let cdel: CDEL; +export let dcel: DCEL; export class Edge { from: number; @@ -17,27 +17,27 @@ export class Edge { } get opposite() { - return cdel.edges.get(`${this.to}-${this.from}`); + return dcel.edges.get(`${this.to}-${this.from}`); } get triangle() { - return cdel.triangles[this.triangleIndex]; + return dcel.triangles[this.triangleIndex]; } get toVertex() { - return [cdel.vertices[2 * this.to], cdel.vertices[2 * this.to + 1]]; + return [dcel.vertices[2 * this.to], dcel.vertices[2 * this.to + 1]]; } get fromVertex() { - return [cdel.vertices[2 * this.from], cdel.vertices[2 * this.from + 1]]; + return [dcel.vertices[2 * this.from], dcel.vertices[2 * this.from + 1]]; } draw(colour = "black") { const ctx = layerManager.getLayer("draw")!.ctx; ctx.beginPath(); ctx.strokeStyle = colour; - ctx.moveTo(g2lx(cdel.vertices[2 * this.from]), g2ly(cdel.vertices[2 * this.from + 1])); - ctx.lineTo(g2lx(cdel.vertices[2 * this.to]), g2ly(cdel.vertices[2 * this.to + 1])); + ctx.moveTo(g2lx(dcel.vertices[2 * this.from]), g2ly(dcel.vertices[2 * this.from + 1])); + ctx.lineTo(g2lx(dcel.vertices[2 * this.to]), g2ly(dcel.vertices[2 * this.to + 1])); ctx.closePath(); ctx.stroke(); @@ -51,21 +51,21 @@ export class Triangle { } getEdges(): Edge[] { - return this.edges.map(e => cdel.edges.getIndexValue(e)); + return this.edges.map(e => dcel.edges.getIndexValue(e)); } edge(index: number): Edge { - return cdel.edges.getIndexValue(this.edges[index]); + return dcel.edges.getIndexValue(this.edges[index]); } vertex(index: number): [number, number] { const idx = this.edge(this.ccw(index)).from; - return [cdel.vertices[2 * idx], cdel.vertices[2 * idx + 1]]; + return [dcel.vertices[2 * idx], dcel.vertices[2 * idx + 1]]; } index(edge: Edge) { for (let i = 0; i < this.edges.length; i++) { - if (cdel.edges.getIndexValue(this.edges[i]) === edge) return i; + if (dcel.edges.getIndexValue(this.edges[i]) === edge) return i; } return -1; } @@ -122,15 +122,13 @@ export class Triangle { } } -export class CDEL { +export class DCEL { vertices: number[]; - holes: number[]; triangles: Triangle[] = []; edges = new OrderedMap(); isolatedVertices: number[][][][] = []; - constructor(vertices: number[], holes: number[] = []) { + constructor(vertices: number[] = [], holes: number[] = []) { this.vertices = vertices; - this.holes = holes; } add_edge(edge: Edge) { const key = `${edge.from}-${edge.to}`; @@ -148,23 +146,22 @@ export class CDEL { } } - checkConstraints() { - if (this.holes.length === 0) return; - this.holes.splice(0, 0, 0); - for (let h = 0; h < this.holes.length; h++) { - const i = this.holes[h]; - const j = h + 1 === this.holes.length ? this.vertices.length / 2 : this.holes[h + 1]; + checkConstraints(start: number, end: number, holes: number[]) { + // if (holes.length === 0) return; + holes.unshift(0); + for (let h = 0; h < holes.length; h++) { + const i = start + holes[h]; + const j = h + 1 === holes.length ? end : start + holes[h + 1]; for (let k = i + 1; k < j; k++) { this.edges.get(`${k - 1}-${k}`).constrained = true; } this.edges.get(`${j - 1}-${i}`).constrained = true; } - this.holes.splice(0, 0); } } -export function createCDEL(vertices: number[], holes: number[] = []) { - cdel = new CDEL(vertices, holes); +export function createDCEL(vertices: number[] = []) { + dcel = new DCEL(vertices); } -createCDEL([]); +createDCEL([]); diff --git a/client/src/game/visibility/polygon-clipping.d.ts b/client/src/game/visibility/polygon-clipping.d.ts index 7de8c3c77..2e6f69437 100644 --- a/client/src/game/visibility/polygon-clipping.d.ts +++ b/client/src/game/visibility/polygon-clipping.d.ts @@ -1,3 +1,5 @@ declare module "polygon-clipping" { export function union(...shapes: number[][][][]): number[][][][]; + export function intersection(...shapes: number[][][][]): number[][][][]; + export function xor(...shapes: number[][][][]): number[][][][]; } diff --git a/client/src/game/visibility/te/draw.ts b/client/src/game/visibility/te/draw.ts new file mode 100644 index 000000000..deda6f68e --- /dev/null +++ b/client/src/game/visibility/te/draw.ts @@ -0,0 +1,22 @@ +import { layerManager } from "@/game/layers/manager"; +import { g2lx, g2ly } from "@/game/units"; + +export function drawPolygon(polygon: number[][], colour?: string) { + const dl = layerManager.getLayer("draw"); + if (dl === undefined) return; + const ctx = dl.ctx; + ctx.lineJoin = "round"; + // ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + ctx.lineJoin = "round"; + ctx.beginPath(); + ctx.strokeStyle = + colour === undefined ? `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})` : colour; + ctx.moveTo(g2lx(polygon[0][0]), g2ly(polygon[0][1])); + for (const point of polygon) { + ctx.lineTo(g2lx(point[0]), g2ly(point[1])); + } + ctx.closePath(); + ctx.stroke(); +} + +(window).DP = drawPolygon; diff --git a/client/src/game/visibility/triangulate.ts b/client/src/game/visibility/triangulate.ts index 7092f4baf..ebeecbba6 100644 --- a/client/src/game/visibility/triangulate.ts +++ b/client/src/game/visibility/triangulate.ts @@ -5,7 +5,8 @@ import earcut from "./earcut.js"; import { GlobalPoint, Vector } from "../geom"; import { layerManager } from "../layers/manager"; import { g2lx, g2ly } from "../units"; -import { cdel, CDEL, createCDEL, Edge, Triangle } from "./cdel"; +import { createDCEL, dcel, Edge, Triangle } from "./dcel"; +import { drawPolygon } from "./te/draw"; /* Triangle expansion algorithm @@ -19,76 +20,176 @@ The earcut library expects a flat list of vertices followed by a list of indices to the holes in the vertices list. */ -export function triangulate(shapes: string[]) { - if (shapes.length === 0) return new CDEL([]); - const sh = reduce(shapes); - const g = generate(sh); - return triag(g.vertices, g.holes, sh); +export function triangulate(shapes: string[], drawt = false) { + createDCEL(); + if (shapes.length === 0) return dcel; + console.time("reduce"); + const ringData = reduce(shapes); + (window).RD = ringData; + if (drawt) { + for (const ringshape of ringData.ringshapes) drawPolygon(ringshape); + } + console.timeEnd("reduce"); + for (let r = 0; r < ringData.rings.length; r++) { + const g = generate(ringData.ringshapes[r], ringData.rings[r]); + triag(g.vertices, g.holes); + } } (window).p = polygon; -function reduce(shapes: string[]) { - // const reduced: number[][] = []; - // for (const shapeId of shapes) { - // const shape = layerManager.UUIDMap.get(shapeId)!; - // for (let i = reduced.length - 1; i >= 0; i--) { - // const un = polygon.union([reduced[i]], shape.points); - // if (un.length === 1) { - // reduced[i] = - // } - // } +function inside(poly: number[][], ring: number[][]): boolean { + const intersect = polygon.intersection([poly], [ring]); + if (intersect.length !== 1 || intersect[0].length !== 1) return false; + // for (const point of intersect[0][0]) { + // const eq = (p: number[]) => p[0] === point[0] && p[1] === point[1]; + // if (!poly.some(eq) && !ring.some(eq)) return false; // } - const points: number[][][][] = []; + return true; +} + +function matchingXors(xors: number[][][][], poly: number[][]) { + const matches = []; + for (const xorE of xors) { + // If 2 points are found matching, it is a ring + // otherwise its an inner block cross-section + let pointFound = false; + for (const xorP of xorE[0]) { + if (poly.some(p => p[0] === xorP[0] && p[1] === xorP[1])) { + if (pointFound) { + matches.push(xorE); + break; + } else { + pointFound = true; + } + } + } + } + return matches; +} + +function reduce(shapes: string[]) { + const world = [[1e10, 1e10], [-1e10, 1e10], [-1e10, -1e10], [1e10, -1e10]]; + const ringshapes: number[][][] = [world]; + const rings: number[][][][] = [[]]; + const queue: [number[][], number[]][] = []; for (const shape of shapes) { const p = layerManager.UUIDMap.get(shape)!.points; - if (p.length > 0) points.push([p]); + if (p.length === 0) continue; + queue.push([p.reverse(), []]); } - return polygon.union(...points); + while (queue.length > 0) { + try { + const drctx = layerManager.getLayer("draw")!.ctx; + drctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + } catch {} + const [q, ringHint] = queue.shift()!; + drawPolygon(q, "red"); + for (let r = rings.length - 1; r >= 0; r--) { + if (ringHint.length > 0 && !ringHint.includes(r)) continue; + if (!inside(q, ringshapes[r])) continue; + // Check if it further expands the inner boundary + const xor = polygon.xor([q], [ringshapes[r]]); + + if (xor.length === 1) { + // touching + if (xor[0].length === 1) { + ringshapes[r] = xor[0][0]; + continue; + // one shape is inside the other + } else { + } + // overlapping + } else if (xor.length === 2) { + ringshapes[r] = matchingXors(xor, ringshapes[r])[0][0]; + continue; + } else if (xor.length > 2) { + // queue.push(...rings[r]); + const hints = [r]; + const ringshape = ringshapes[r]; + ringshapes.splice(r, 1); + const oldRingData = rings.splice(r, 1); + for (const match of matchingXors(xor, ringshape)) { + ringshapes.push(match[0]); + rings.push([]); + hints.push(rings.length - 1); + } + for (const ro of oldRingData[0]) { + queue.unshift([ro, hints]); + } + continue; + } + + if (rings[r].length === 0) { + rings[r].push(q); + break; + } + let result = q; + let ringMerge = false; + for (let re = rings[r].length - 1; re >= 0; re--) { + const ringEl = rings[r][re]; + const union = polygon.union([result], [ringEl]); + // no overlap + if (union.length === 2) { + continue; + // merge does not create ring + } else if (union.length === 1 && union[0].length === 1) { + rings[r].splice(re, 1); + result = union[0][0]; + // merge creates ring + } else if (union.length === 1 && union[0].length === 2) { + ringMerge = true; + rings[r].splice(re, 1); + ringshapes.push(union[0][1].reverse()); + rings.push([]); + // queue.push(...rings[r]); + for (const ro of rings[r]) { + queue.unshift([ro, [r, rings.length - 1]]); + } + rings[r] = [union[0][0]]; + break; + } else { + console.log(union); + console.error("Check dit"); + } + } + if (!ringMerge) rings[r].push(result); + break; + } + } + return { ringshapes, rings }; } -function generate(shapes: number[][][][]) { - const vertices = [0, 0, 0, 0, 0, 0, 0, 0]; - let minX = 0; - let minY = 0; - let maxX = 0; - let maxY = 0; +function generate(boundary: number[][], shapes: number[][][]) { + const vertices: number[] = boundary.reduce((acc, val) => acc.concat(val)); + const end = vertices.length; + if (vertices[0] === vertices[end - 2] && vertices[1] === vertices[end - 1]) vertices.splice(0, 2); const holes = []; for (const shape of shapes) { holes.push(vertices.length / 2); - for (const sha of shape) { - for (const [idx, point] of sha.reverse().entries()) { - if (idx === shape[0].length - 1) continue; - vertices.push(point[0], point[1]); - - if (minX > point[0]) minX = point[0]; - if (minY > point[1]) minY = point[1]; - if (maxX < point[0]) maxX = point[0]; - if (maxY < point[1]) maxY = point[1]; - } + for (const [idx, point] of shape.reverse().entries()) { + if (idx > 0 && point[0] === shape[0][0] && point[1] === shape[0][1]) continue; + vertices.push(point[0], point[1]); } } - minX -= 1e6; - minY -= 1e6; - maxX += 1e6; - maxY += 1e6; - vertices.splice(0, 8, minX, minY, maxX, minY, maxX, maxY, minX, maxY); return { vertices, holes }; } -function triag(vertices: number[], holes: number[], shapes: number[][][][]) { - createCDEL(vertices, holes); +function triag(vertices: number[], holes: number[]) { + const start = dcel.vertices.length / 2; + dcel.vertices.push(...vertices); const triangles = earcut(vertices, holes); for (let t = 0; t < triangles.length; t += 3) { - cdel.add_edge(new Edge(triangles[t], triangles[t + 1])); - cdel.add_edge(new Edge(triangles[t + 1], triangles[t + 2])); - cdel.add_edge(new Edge(triangles[t + 2], triangles[t])); - const tl = cdel.edges.length; - cdel.add_triangle(new Triangle([tl - 3, tl - 2, tl - 1])); + dcel.add_edge(new Edge(start + triangles[t], start + triangles[t + 1])); + dcel.add_edge(new Edge(start + triangles[t + 1], start + triangles[t + 2])); + dcel.add_edge(new Edge(start + triangles[t + 2], start + triangles[t])); + const tl = dcel.edges.length; + dcel.add_triangle(new Triangle([tl - 3, tl - 2, tl - 1])); } - draw(); - // cdel.checkConstraints(); - return cdel; + // draw(); + dcel.checkConstraints(start, dcel.vertices.length / 2, holes); + + return dcel; } function draw() { @@ -96,52 +197,41 @@ function draw() { ctx.beginPath(); ctx.strokeStyle = "rgba(0, 0, 0, 0.25)"; ctx.lineJoin = "round"; - for (const triangle of cdel.triangles) { + for (const triangle of dcel.triangles) { ctx.moveTo(g2lx(triangle.vertex(0)[0]), g2ly(triangle.vertex(0)[1])); ctx.lineTo(g2lx(triangle.vertex(1)[0]), g2ly(triangle.vertex(1)[1])); ctx.lineTo(g2lx(triangle.vertex(2)[0]), g2ly(triangle.vertex(2)[1])); ctx.lineTo(g2lx(triangle.vertex(0)[0]), g2ly(triangle.vertex(0)[1])); } - for (let i = 0; i < cdel.vertices.length; i += 2) { - ctx.fillText(`${i / 2}`, g2lx(cdel.vertices[i]), g2ly(cdel.vertices[i + 1])); + for (let i = 0; i < dcel.vertices.length; i += 2) { + ctx.fillText(`${i / 2}`, g2lx(dcel.vertices[i]), g2ly(dcel.vertices[i + 1])); } ctx.closePath(); ctx.stroke(); } -export function computeVisibility(q: GlobalPoint, it = 0): number[][] { +export function computeVisibility(q: GlobalPoint, it = 0, drawt = false): number[][] { + console.time("CV"); const rawOutput: number[][] = []; - const triangle = cdel.locate(q); + const triangle = dcel.locate(q); if (triangle === undefined) { if (it > 10) { console.error("Triangle not found"); return []; } - return computeVisibility(q.add(new Vector(0.001, 0.001)), it++); + return computeVisibility(q.add(new Vector(0.001, 0.001)), ++it); } // triangle.fill(); for (const [index, edge] of triangle.getEdges().entries()) { rawOutput.push(edge.fromVertex); if (!edge.constrained) expandEdge(q, edge.fromVertex, edge.toVertex, triangle, index, rawOutput); } + console.timeEnd("CV"); - // const outArr = output(rawOutput); - - // const ctx = layerManager.getLayer("draw")!.ctx; - // // ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); - // ctx.beginPath(); - // ctx.strokeStyle = `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`; - // ctx.lineJoin = "round"; - // ctx.moveTo(g2lx(rawOutput[0][0]), g2ly(rawOutput[0][1])); - // for (const point of rawOutput.slice(1)) { - // ctx.lineTo(g2lx(point[0]), g2ly(point[1])); - // } - // ctx.closePath(); - // ctx.stroke(); + if (drawt) drawPolygon(rawOutput); - // return outArr; return rawOutput; } From cb5edb8d085702977243d7269c258f26a9585690 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Tue, 1 Jan 2019 22:34:08 +0100 Subject: [PATCH 04/18] Let circle behave as a rect for now --- client/src/game/layers/fowplayers.ts | 4 ++-- client/src/game/shapes/baserect.ts | 1 + client/src/game/shapes/boundingrect.ts | 10 ++++++++++ client/src/game/shapes/circle.ts | 2 +- client/src/game/visibility/triangulate.ts | 4 ++-- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/client/src/game/layers/fowplayers.ts b/client/src/game/layers/fowplayers.ts index a8f1edbb3..acc05396e 100644 --- a/client/src/game/layers/fowplayers.ts +++ b/client/src/game/layers/fowplayers.ts @@ -12,7 +12,7 @@ export class FOWPlayersLayer extends Layer { draw(): void { if (!this.valid) { - // console.time("VI"); + console.time("VI"); const ctx = this.ctx; if (!gameStore.fowLOS || Settings.skipPlayerFOW) { @@ -93,7 +93,7 @@ export class FOWPlayersLayer extends Layer { if (gameStore.IS_DM) super.draw(!gameStore.fullFOW); ctx.globalCompositeOperation = originalOperation; - // console.timeEnd("VI"); + console.timeEnd("VI"); } } } diff --git a/client/src/game/shapes/baserect.ts b/client/src/game/shapes/baserect.ts index f0b40bf66..ad6a396b6 100644 --- a/client/src/game/shapes/baserect.ts +++ b/client/src/game/shapes/baserect.ts @@ -25,6 +25,7 @@ export abstract class BaseRect extends Shape { get points() { if (this.w === 0 || this.h === 0) return [[this.refPoint.x, this.refPoint.y]]; + // note to self: topright and botleft are swapped because I'm retarded. const topright = this.refPoint.add(new Vector(0, this.h)); const botright = this.refPoint.add(new Vector(this.w, this.h)); const botleft = this.refPoint.add(new Vector(this.w, 0)); diff --git a/client/src/game/shapes/boundingrect.ts b/client/src/game/shapes/boundingrect.ts index d59b9ad42..d46b5f7ad 100644 --- a/client/src/game/shapes/boundingrect.ts +++ b/client/src/game/shapes/boundingrect.ts @@ -26,6 +26,16 @@ export class BoundingRect { ); } + get points() { + if (this.w === 0 || this.h === 0) return [[this.topLeft.x, this.topLeft.y]]; + return [ + [this.topLeft.x, this.topLeft.y], + [this.botLeft.x, this.botLeft.y], + [this.botRight.x, this.botRight.y], + [this.topRight.x, this.topRight.y], + ]; + } + offset(vector: Vector): BoundingRect { return new BoundingRect(this.topLeft.add(vector), this.w, this.h); } diff --git a/client/src/game/shapes/circle.ts b/client/src/game/shapes/circle.ts index 20e01172e..d429d6975 100644 --- a/client/src/game/shapes/circle.ts +++ b/client/src/game/shapes/circle.ts @@ -36,7 +36,7 @@ export class Circle extends Shape { } get points() { - return []; + return this.getBoundingBox().points; } draw(ctx: CanvasRenderingContext2D) { diff --git a/client/src/game/visibility/triangulate.ts b/client/src/game/visibility/triangulate.ts index ebeecbba6..678dc5ca6 100644 --- a/client/src/game/visibility/triangulate.ts +++ b/client/src/game/visibility/triangulate.ts @@ -213,7 +213,7 @@ function draw() { } export function computeVisibility(q: GlobalPoint, it = 0, drawt = false): number[][] { - console.time("CV"); + // console.time("CV"); const rawOutput: number[][] = []; const triangle = dcel.locate(q); if (triangle === undefined) { @@ -228,7 +228,7 @@ export function computeVisibility(q: GlobalPoint, it = 0, drawt = false): number rawOutput.push(edge.fromVertex); if (!edge.constrained) expandEdge(q, edge.fromVertex, edge.toVertex, triangle, index, rawOutput); } - console.timeEnd("CV"); + // console.timeEnd("CV"); if (drawt) drawPolygon(rawOutput); From 8de348d04972252359f5677e6c53f425325e10a9 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Wed, 2 Jan 2019 13:22:13 +0100 Subject: [PATCH 05/18] Fix bug in boundary update --- client/src/game/visibility/triangulate.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/src/game/visibility/triangulate.ts b/client/src/game/visibility/triangulate.ts index 678dc5ca6..045789b41 100644 --- a/client/src/game/visibility/triangulate.ts +++ b/client/src/game/visibility/triangulate.ts @@ -102,6 +102,10 @@ function reduce(shapes: string[]) { // overlapping } else if (xor.length === 2) { ringshapes[r] = matchingXors(xor, ringshapes[r])[0][0]; + // it's possible for shapes in the ring now to be part of the ring + // todo: look into not continue'ing in this case but continuing merge detection below. + for (const ro of rings[r]) queue.unshift([ro, [r]]); + rings[r] = []; continue; } else if (xor.length > 2) { // queue.push(...rings[r]); @@ -114,9 +118,7 @@ function reduce(shapes: string[]) { rings.push([]); hints.push(rings.length - 1); } - for (const ro of oldRingData[0]) { - queue.unshift([ro, hints]); - } + for (const ro of oldRingData[0]) queue.unshift([ro, hints]); continue; } @@ -212,7 +214,7 @@ function draw() { ctx.stroke(); } -export function computeVisibility(q: GlobalPoint, it = 0, drawt = false): number[][] { +export function computeVisibility(q: GlobalPoint, it = 0, drawt = true): number[][] { // console.time("CV"); const rawOutput: number[][] = []; const triangle = dcel.locate(q); @@ -230,7 +232,7 @@ export function computeVisibility(q: GlobalPoint, it = 0, drawt = false): number } // console.timeEnd("CV"); - if (drawt) drawPolygon(rawOutput); + if (drawt) drawPolygon(rawOutput, "red"); return rawOutput; } From 72685ed69993beb0f38307de06bcaf51e57a77ea Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 6 Jan 2019 02:38:41 +0100 Subject: [PATCH 06/18] Start CGAL triangulation port --- client/package-lock.json | 162 +++++++++------ client/package.json | 1 + client/src/game/layers/fowplayers.ts | 4 +- client/src/game/visibility/te/cdt.ts | 122 ++++++++++++ client/src/game/visibility/te/draw.ts | 19 ++ client/src/game/visibility/te/tds.ts | 228 ++++++++++++++++++++++ client/src/game/visibility/te/test.ts | 4 + client/src/game/visibility/te/triag.ts | 17 ++ client/src/game/visibility/triangulate.ts | 7 + client/tslint.json | 3 +- 10 files changed, 502 insertions(+), 65 deletions(-) create mode 100644 client/src/game/visibility/te/cdt.ts create mode 100644 client/src/game/visibility/te/tds.ts create mode 100644 client/src/game/visibility/te/test.ts create mode 100644 client/src/game/visibility/te/triag.ts diff --git a/client/package-lock.json b/client/package-lock.json index 3a1ed041a..f30d98135 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -558,9 +558,9 @@ "dev": true }, "ansi-colors": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.1.tgz", - "integrity": "sha512-Xt+zb6nqgvV9SWAVp0EG3lRsHcbq5DDgqjPPz6pwgtj6RKz65zGXMNa82oJfOSBA/to6GmRP7Dr+6o+kbApTzQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", "dev": true }, "ansi-html": { @@ -1133,9 +1133,9 @@ }, "dependencies": { "array-flatten": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", - "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", "dev": true } } @@ -4253,9 +4253,9 @@ } }, "handle-thing": { - "version": "1.2.5", - "resolved": "http://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", - "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", + "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", "dev": true }, "handlebars": { @@ -4784,9 +4784,9 @@ } }, "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -5613,6 +5613,22 @@ "object-visit": "^1.0.0" } }, + "martinez-polygon-clipping": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/martinez-polygon-clipping/-/martinez-polygon-clipping-0.5.0.tgz", + "integrity": "sha512-FvHURxJTieS4LEHL+aJmWbqCUBk2GrYU5zolgXNAQLJsjnAff7Hqqk5MM1Kl91FnPOcyiAs/cVey11bb0D7qug==", + "requires": { + "splaytree": "^0.1.4", + "tinyqueue": "^1.2.0" + }, + "dependencies": { + "splaytree": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-0.1.4.tgz", + "integrity": "sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ==" + } + } + }, "material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", @@ -6481,37 +6497,14 @@ "dev": true }, "os-locale": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", - "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { - "execa": "^0.10.0", + "execa": "^1.0.0", "lcid": "^2.0.0", "mem": "^4.0.0" - }, - "dependencies": { - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - } } }, "os-tmpdir": { @@ -8748,32 +8741,75 @@ "dev": true }, "spdy": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", - "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", + "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", "dev": true, "requires": { - "debug": "^2.6.8", - "handle-thing": "^1.2.5", + "debug": "^4.1.0", + "handle-thing": "^2.0.0", "http-deceiver": "^1.2.7", - "safe-buffer": "^5.0.1", "select-hose": "^2.0.0", - "spdy-transport": "^2.0.18" + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } } }, "spdy-transport": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.1.tgz", - "integrity": "sha512-q7D8c148escoB3Z7ySCASadkegMmUZW8Wb/Q1u0/XBgDKMO880rLQDj8Twiew/tYi7ghemKUi/whSYOwE17f5Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "dev": true, "requires": { - "debug": "^2.6.8", - "detect-node": "^2.0.3", + "debug": "^4.1.0", + "detect-node": "^2.0.4", "hpack.js": "^2.1.6", - "obuf": "^1.1.1", - "readable-stream": "^2.2.9", - "safe-buffer": "^5.0.1", - "wbuf": "^1.7.2" + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } } }, "splaytree": { @@ -10048,9 +10084,9 @@ } }, "webpack-dev-server": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.10.tgz", - "integrity": "sha512-RqOAVjfqZJtQcB0LmrzJ5y4Jp78lv9CK0MZ1YJDTaTmedMZ9PU9FLMQNrMCfVu8hHzaVLVOJKBlGEHMN10z+ww==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", + "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", "dev": true, "requires": { "ansi-html": "0.0.7", @@ -10072,12 +10108,14 @@ "portfinder": "^1.0.9", "schema-utils": "^1.0.0", "selfsigned": "^1.9.1", + "semver": "^5.6.0", "serve-index": "^1.7.2", "sockjs": "0.3.19", "sockjs-client": "1.3.0", - "spdy": "^3.4.1", + "spdy": "^4.0.0", "strip-ansi": "^3.0.0", "supports-color": "^5.1.0", + "url": "^0.11.0", "webpack-dev-middleware": "3.4.0", "webpack-log": "^2.0.0", "yargs": "12.0.2" @@ -10349,9 +10387,9 @@ } }, "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" diff --git a/client/package.json b/client/package.json index 0b27114fe..8114e4e19 100644 --- a/client/package.json +++ b/client/package.json @@ -20,6 +20,7 @@ "axios": "^0.18.0", "earcut": "^2.1.4", "lodash.throttle": "^4.1.1", + "martinez-polygon-clipping": "^0.5.0", "polygon-clipping": "^0.9.2", "socket.io-client": "^2.1.1", "tinycolor2": "^1.4.1", diff --git a/client/src/game/layers/fowplayers.ts b/client/src/game/layers/fowplayers.ts index acc05396e..a8f1edbb3 100644 --- a/client/src/game/layers/fowplayers.ts +++ b/client/src/game/layers/fowplayers.ts @@ -12,7 +12,7 @@ export class FOWPlayersLayer extends Layer { draw(): void { if (!this.valid) { - console.time("VI"); + // console.time("VI"); const ctx = this.ctx; if (!gameStore.fowLOS || Settings.skipPlayerFOW) { @@ -93,7 +93,7 @@ export class FOWPlayersLayer extends Layer { if (gameStore.IS_DM) super.draw(!gameStore.fullFOW); ctx.globalCompositeOperation = originalOperation; - console.timeEnd("VI"); + // console.timeEnd("VI"); } } } diff --git a/client/src/game/visibility/te/cdt.ts b/client/src/game/visibility/te/cdt.ts new file mode 100644 index 000000000..b8beb9a7f --- /dev/null +++ b/client/src/game/visibility/te/cdt.ts @@ -0,0 +1,122 @@ +import { EdgeCirculator, LocateType, Point, TDS, Triangle, Vertex } from "./tds"; +import { edgeInfo } from "./triag"; + +export class CDT { + tds: TDS; + constructor() { + this.tds = new TDS(); + (window).TDS = this.tds; + } + insertConstraint(a: Point, b: Point) { + const va = this.insert(a, new Triangle()); + const vb = this.insert(b, new Triangle()); + if (va !== vb) this.insertConstraintV(va, vb); + } + + insertConstraintV(va: Vertex, vb: Vertex) { + const stack = [[va, vb]]; + while (stack.length > 0) { + const v = stack.pop()!; + const info = edgeInfo(v[0], v[1]); + if (info.includes) { + this.markConstraint(info.fr!, info.i!); + if (info.vi! !== v[1]) { + stack.push([info.vi!, v[1]]); + } + continue; + } + } + } + + markConstraint(t: Triangle, i: number) { + if (this.tds.dimension === 1) t.constraints[2] = true; + else { + t.constraints[i] = true; + t.neighbours[i]!.constraints[this.tds.mirrorIndex(t, i)] = true; + } + } + + insert(p: Point, start: Triangle) { + const locateInfo = this.locate(p, this.iLocate(p, start)); + const va = this.insertb(p, locateInfo.loc, locateInfo.lt, locateInfo.li); + this.flipAround(va); + return va; + } + + flipAround(v: Vertex) { + if (this.tds.dimension <= 1) return; + } + + insertb(a: Point, loc: Triangle, lt: LocateType, li: number): Vertex { + let insertInConstrainedEdge = false; + // & loc.is_constrained(li) + if (lt === LocateType.EDGE && loc.isConstrained(li)) { + insertInConstrainedEdge = true; + } + const va = this.insertc(a, loc, lt, li); + if (insertInConstrainedEdge) console.log(0); + else if (lt !== LocateType.VERTEX) this.clearConstraintsIncident(va); + if (this.tds.dimension === 2) console.log(1); + return va; + } + + clearConstraintsIncident(v: Vertex) { + const ec = new EdgeCirculator(v, null); + if (ec.valid) { + do { + const t = ec.t!; + const indf = ec.ri; + t.constraints[indf] = false; + if (this.tds.dimension === 2) t.neighbours[indf]!.constraints[this.tds.mirrorIndex(t, indf)] = false; + } while (ec.next()); + } + } + + insertc(p: Point, loc: Triangle, lt: LocateType, li: number): Vertex { + if (this.tds.vertices.length === 1) { + return this.insertFirst(p); + } else if (this.tds.vertices.length === 2) { + if (lt === LocateType.VERTEX) return this.tds.finiteVertex; + else return this.insertSecond(p); + } + throw new Error("insertc"); + } + + insertFirst(p: Point): Vertex { + const v = this.tds.insertDimUp(); + v.point = p; + return v; + } + + insertSecond(p: Point): Vertex { + const v = this.tds.insertDimUp(this.tds._infinite, true); + v.point = p; + return v; + } + + locate(p: Point, start: Triangle) { + let lt = 0; + let li = 0; + if (this.tds.dimension < 0) { + lt = LocateType.OUTSIDE_AFFINE_HULL; + li = 4; + } else if (this.tds.dimension === 0) { + if (this.xyEqual(p, this.tds.finiteVertex.triangle!.vertices[0].point!)) { + lt = LocateType.VERTEX; + } else { + lt = LocateType.OUTSIDE_AFFINE_HULL; + } + li = 4; + } + return { loc: new Triangle(), lt, li }; + } + + iLocate(p: Point, start: Triangle) { + if (this.tds.dimension < 2) return start; + return start; + } + + xyEqual(p: Point, q: Point) { + return p[0] === q[0] && p[1] === q[1]; + } +} diff --git a/client/src/game/visibility/te/draw.ts b/client/src/game/visibility/te/draw.ts index deda6f68e..8cb4b4986 100644 --- a/client/src/game/visibility/te/draw.ts +++ b/client/src/game/visibility/te/draw.ts @@ -19,4 +19,23 @@ export function drawPolygon(polygon: number[][], colour?: string) { ctx.stroke(); } +export function drawPolygonL(polygon: number[][], colour?: string) { + const dl = layerManager.getLayer("draw"); + if (dl === undefined) return; + const ctx = dl.ctx; + ctx.lineJoin = "round"; + // ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + ctx.lineJoin = "round"; + ctx.beginPath(); + ctx.strokeStyle = + colour === undefined ? `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})` : colour; + ctx.moveTo(polygon[0][0], polygon[0][1]); + for (const point of polygon) { + ctx.lineTo(point[0], point[1]); + } + ctx.closePath(); + ctx.stroke(); +} + (window).DP = drawPolygon; +(window).DPL = drawPolygonL; diff --git a/client/src/game/visibility/te/tds.ts b/client/src/game/visibility/te/tds.ts new file mode 100644 index 000000000..6e140afe0 --- /dev/null +++ b/client/src/game/visibility/te/tds.ts @@ -0,0 +1,228 @@ +export type Point = number[]; + +function newPoint(): Point { + return [-7e-310, -7e-310]; +} + +export class Triangle { + vertices: Vertex[] = []; + neighbours: (Triangle | null)[] = [null, null, null]; + constraints = [false, false, false]; + + constructor(...vertices: Vertex[]) { + this.vertices = vertices; + } + + get dimension() { + return this.vertices.length - 1; + } + + addVertex(vertex: Vertex) { + if (vertex === undefined) { + console.log("UNDEFINED HIERE"); + } + this.vertices.push(vertex); + vertex.triangle = this; + } + + isConstrained(index: number): boolean { + return false; + } + + setNeighbour(index: number, neighbour: Triangle) { + this.neighbours[index] = neighbour; + } + + reorient() { + // If certain indices do not exist yet thay will append faulty undefined's, thus we slice them + this.vertices = [this.vertices[1], this.vertices[0], this.vertices[2]].slice(0, this.vertices.length); + this.neighbours = [this.neighbours[1], this.neighbours[0], this.neighbours[2]]; + this.constraints = [this.constraints[1], this.constraints[0], this.constraints[2]]; + } + + index(v: Vertex): number { + return this.vertices.indexOf(v); + } +} + +export class Vertex { + infinite = false; + _point: Point | undefined; + triangle: Triangle | undefined; + + constructor(point?: Point) { + this._point = point; + } + + get point(): Point | undefined { + return this._point; + } + + set point(point: Point | undefined) { + this._point = point; + this.infinite = false; + } +} + +export class EdgeCirculator { + ri: number; + v: Vertex | null; + t: Triangle | null; + _ri: number; + _v: Vertex | null; + _t: Triangle | null; + + constructor(v: Vertex | null, t: Triangle | null) { + this.v = v; + this.t = t; + if (v === null) { + this.t = null; + } else if (t === null) { + this.t = v.triangle!; + } + if (this.t == null || this.t.dimension < 1) { + this.ri = 0; + this.v = null; + this.t = null; + } else { + const i = this.t.index(v!); + if (this.t.dimension === 2) this.ri = 0; + // this.ccw(i); + else this.ri = 2; + } + this._ri = this.ri; + this._v = this.v; + this._t = this.t; + } + + get valid() { + return this.t !== null && this.v !== null; + } + + next(): boolean { + let i = this.t!.index(this.v!); + if (this.t!.dimension === 1) { + this.t = this.t!.neighbours[i === 0 ? 1 : 0]; + } else { + // this.t = this.t!.neighbours[] ccw + i = this.t!.index(this.v!); + // ri = ccw(i) + } + return this.ri !== this._ri || this.v !== this._v || this.t !== this._t; + } +} + +export enum LocateType { + VERTEX, + EDGE, + FACE, + OUTSIDE_CONVEX_HULL, + OUTSIDE_AFFINE_HULL, +} + +export class TDS { + dimension = -1; + vertices: Vertex[] = []; + triangles: Triangle[] = []; + _infinite: Vertex; + + constructor() { + this._infinite = this.createVertex(); + const t = new Triangle(); + t.addVertex(this._infinite); + this.triangles.push(t); + } + + createVertex(): Vertex { + const v = this.infiniteVertex; + if (v === undefined) { + console.log("UNDEFINED HIERE"); + } + this.vertices.push(v); + return v; + } + + setAdjacency(t0: Triangle, i0: number, t1: Triangle, i1: number) { + t0.setNeighbour(i0, t1); + t1.setNeighbour(i1, t0); + } + + get finiteVertex(): Vertex { + return this.vertices[1]; + } + + get infiniteVertex(): Vertex { + const v = new Vertex(newPoint()); + v.infinite = true; + return v; + } + + insertDimUp(w: Vertex = new Vertex(), orient: boolean = true): Vertex { + const v = this.createVertex(); + this.dimension++; + let t1: Triangle; + let t2: Triangle; + switch (this.dimension) { + case 0: { + t1 = this.triangles[0]; + t2 = new Triangle(v); + this.triangles.push(t2); + this.setAdjacency(t1, 0, t2, 0); + v.triangle = t2; + break; + } + case 1: + case 2: { + const deleteList: Triangle[] = []; + const triangles = this.triangles.slice(0, this.triangles.length); + for (const trig of triangles) { + const copy = new Triangle(...trig.vertices); + this.triangles.push(copy); + trig.vertices[this.dimension] = v; + copy.vertices[this.dimension] = w; + this.setAdjacency(trig, this.dimension, copy, this.dimension); + if (trig.vertices.includes(w)) deleteList.push(copy); + } + for (const trig of triangles) { + const neighbour = trig.neighbours[this.dimension]; + for (let j = 0; j < this.dimension; ++j) { + neighbour!.neighbours[j] = trig.neighbours[j]!.neighbours[this.dimension]; + } + } + + let lfit = 0; + if (this.dimension === 1) { + if (orient) { + triangles[lfit].reorient(); + lfit++; + triangles[lfit].neighbours[1]!.reorient(); + } + } + for (const trig of deleteList) { + let j = 1; + if (trig.vertices[0] === w) j = 0; + t1 = trig.neighbours[this.dimension]!; + const i1 = this.mirrorIndex(trig, this.dimension); + t2 = trig.neighbours[j]!; + const i2 = this.mirrorIndex(trig, j); + this.setAdjacency(t1, i1, t2, i2); + this.triangles = this.triangles.filter(t => t !== trig); + } + v.triangle = triangles[0]; + break; + } + default: { + throw new Error("Dimension unknown"); + } + } + return v; + } + + mirrorIndex(t: Triangle, i: number): number { + if (t.dimension === 1) { + const j = t.neighbours[i]!.index(t.vertices[i === 0 ? 1 : 0]); + return j === 0 ? 1 : 0; + } + return 0; + } +} diff --git a/client/src/game/visibility/te/test.ts b/client/src/game/visibility/te/test.ts new file mode 100644 index 000000000..bc4509ee9 --- /dev/null +++ b/client/src/game/visibility/te/test.ts @@ -0,0 +1,4 @@ +import { CDT } from "./cdt"; + +const cdt = new CDT(); +cdt.insertConstraint([50, 50], [50, 100]); diff --git a/client/src/game/visibility/te/triag.ts b/client/src/game/visibility/te/triag.ts new file mode 100644 index 000000000..cd5f5e1a6 --- /dev/null +++ b/client/src/game/visibility/te/triag.ts @@ -0,0 +1,17 @@ +import { EdgeCirculator, Triangle, Vertex } from "./tds"; + +export function edgeInfo(va: Vertex, vb: Vertex) { + const ec = new EdgeCirculator(va, null); + if (ec.valid) { + do { + const indv = 3 - ec.t!.index(va) - ec.ri; + const v = ec.t!.vertices[indv]; + if (!v.infinite) { + if (v === vb) { + return { includes: true, vi: vb, fr: ec.t!, i: ec.ri }; + } + } + } while (ec.next()); + } + return { includes: false }; +} diff --git a/client/src/game/visibility/triangulate.ts b/client/src/game/visibility/triangulate.ts index 045789b41..3ac0800a8 100644 --- a/client/src/game/visibility/triangulate.ts +++ b/client/src/game/visibility/triangulate.ts @@ -1,4 +1,5 @@ // import earcut from "earcut"; +import * as martinez from "martinez-polygon-clipping"; import polygon from "polygon-clipping"; import earcut from "./earcut.js"; @@ -8,6 +9,11 @@ import { g2lx, g2ly } from "../units"; import { createDCEL, dcel, Edge, Triangle } from "./dcel"; import { drawPolygon } from "./te/draw"; +import { CDT } from "./te/cdt"; + +const cdt = new CDT(); +cdt.insertConstraint([50, 50], [50, 100]); + /* Triangle expansion algorithm ============================= @@ -37,6 +43,7 @@ export function triangulate(shapes: string[], drawt = false) { } (window).p = polygon; +(window).m = martinez; function inside(poly: number[][], ring: number[][]): boolean { const intersect = polygon.intersection([poly], [ring]); diff --git a/client/tslint.json b/client/tslint.json index fb71eb7ca..201a9de7a 100644 --- a/client/tslint.json +++ b/client/tslint.json @@ -23,6 +23,7 @@ "no-bitwise": false, "no-console": false, "no-empty": false, - "object-literal-sort-keys": false + "object-literal-sort-keys": false, + "variable-name": [false] } } \ No newline at end of file From f129a07e6647a651392a7eaafd4aa8aa64524cfa Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Mon, 7 Jan 2019 14:29:02 +0100 Subject: [PATCH 07/18] Further work of triangulate port --- client/src/game/visibility/te/cdt.ts | 377 +++++++++++++++++++++- client/src/game/visibility/te/tds.ts | 236 ++++++++++++-- client/src/game/visibility/te/test.ts | 4 - client/src/game/visibility/te/triag.ts | 77 ++++- client/src/game/visibility/triangulate.ts | 3 + 5 files changed, 657 insertions(+), 40 deletions(-) delete mode 100644 client/src/game/visibility/te/test.ts diff --git a/client/src/game/visibility/te/cdt.ts b/client/src/game/visibility/te/cdt.ts index b8beb9a7f..c37e8e9f0 100644 --- a/client/src/game/visibility/te/cdt.ts +++ b/client/src/game/visibility/te/cdt.ts @@ -1,5 +1,5 @@ -import { EdgeCirculator, LocateType, Point, TDS, Triangle, Vertex } from "./tds"; -import { edgeInfo } from "./triag"; +import { EdgeCirculator, LocateType, Point, Sign, TDS, Triangle, Vertex, FaceCirculator } from "./tds"; +import { collinearBetween, edgeInfo, ccw, orientation, hasInexactNegativeOrientation, cw } from "./triag"; export class CDT { tds: TDS; @@ -8,8 +8,8 @@ export class CDT { (window).TDS = this.tds; } insertConstraint(a: Point, b: Point) { - const va = this.insert(a, new Triangle()); - const vb = this.insert(b, new Triangle()); + const va = this.insert(a); + const vb = this.insert(b); if (va !== vb) this.insertConstraintV(va, vb); } @@ -28,6 +28,18 @@ export class CDT { } } + updateConstraintsOpposite(v: Vertex) { + let t = v.triangle!; + const start = t; + let indf: number; + do { + indf = t.indexV(v); + if (t.neighbours[indf]!.constraints[this.tds.mirrorIndex(t, indf)]) t.constraints[indf] = true; + else t.constraints[indf] = false; + t = t.neighbours[ccw(indf)]!; + } while (t !== start); + } + markConstraint(t: Triangle, i: number) { if (this.tds.dimension === 1) t.constraints[2] = true; else { @@ -36,7 +48,7 @@ export class CDT { } } - insert(p: Point, start: Triangle) { + insert(p: Point, start: Triangle | null = null) { const locateInfo = this.locate(p, this.iLocate(p, start)); const va = this.insertb(p, locateInfo.loc, locateInfo.lt, locateInfo.li); this.flipAround(va); @@ -45,18 +57,38 @@ export class CDT { flipAround(v: Vertex) { if (this.tds.dimension <= 1) return; + let t = v.triangle!; + let i: number; + let next: Triangle; + const start = t; + do { + i = t.indexV(v); + next = t.neighbours[ccw(i)]!; + this.propagatingFlip(t, i); + t = next; + } while (next !== start); + } + + propagatingFlip(t: Triangle, i: number, depth = 0) { + if (!this.isFlipable(t, i)) return; } - insertb(a: Point, loc: Triangle, lt: LocateType, li: number): Vertex { + isFlipable(t: Triangle, i: number, perturb = true) { + const ni = t.neighbours[i]!; + if (t.isInfinite() || ni.isInfinite()) return false; + if (t.constraints[i]) return false; + throw new Error("Circle stuff"); + } + + insertb(a: Point, loc: Triangle | null, lt: LocateType, li: number): Vertex { let insertInConstrainedEdge = false; - // & loc.is_constrained(li) - if (lt === LocateType.EDGE && loc.isConstrained(li)) { + if (lt === LocateType.EDGE && loc!.isConstrained(li)) { insertInConstrainedEdge = true; } const va = this.insertc(a, loc, lt, li); if (insertInConstrainedEdge) console.log(0); else if (lt !== LocateType.VERTEX) this.clearConstraintsIncident(va); - if (this.tds.dimension === 2) console.log(1); + if (this.tds.dimension === 2) this.updateConstraintsOpposite(va); return va; } @@ -72,14 +104,26 @@ export class CDT { } } - insertc(p: Point, loc: Triangle, lt: LocateType, li: number): Vertex { + insertc(p: Point, loc: Triangle | null, lt: LocateType, li: number): Vertex { if (this.tds.vertices.length === 1) { return this.insertFirst(p); } else if (this.tds.vertices.length === 2) { if (lt === LocateType.VERTEX) return this.tds.finiteVertex; else return this.insertSecond(p); } - throw new Error("insertc"); + switch (lt) { + case LocateType.VERTEX: { + return loc!.vertices[li]; + } + case LocateType.OUTSIDE_AFFINE_HULL: { + return this.insertOutsideAffineHull(p); + } + case LocateType.OUTSIDE_CONVEX_HULL: { + return this.insertOutsideConvexHull(p, loc!); + } + } + throw new Error("qwe"); + return new Vertex(); } insertFirst(p: Point): Vertex { @@ -94,12 +138,83 @@ export class CDT { return v; } - locate(p: Point, start: Triangle) { + insertOutsideAffineHull(p: Point): Vertex { + let conform = false; + if (this.tds.dimension === 1) { + const t = this.tds.finiteEdge.first!; + const orient = orientation(t.vertices[0].point!, t.vertices[1].point!, p); + conform = orient === Sign.COUNTERCLOCKWISE; + } + const v = this.tds.insertDimUp(this.tds._infinite, conform); + v.point = p; + return v; + } + + insertOutsideConvexHull(p: Point, t: Triangle): Vertex { + let v: Vertex; + if (this.tds.dimension === 1) { + throw new Error("sdfasdasd"); + } else { + v = this.insertOutsideConvexHull2(p, t); + } + v.point = p; + return v; + } + + insertOutsideConvexHull2(p: Point, t: Triangle): Vertex { + let li = t.indexV(this.tds._infinite); + const ccwlist: Triangle[] = []; + const cwlist: Triangle[] = []; + let fc = new FaceCirculator(this.tds._infinite, t); + let done = false; + while (!done) { + fc.prev(); + li = fc.t!.indexV(this.tds._infinite); + const q = fc.t!.vertices[ccw(li)].point!; + const r = fc.t!.vertices[cw(li)].point!; + if (orientation(p, q, r) === Sign.LEFT_TURN) ccwlist.push(fc.t!); + else done = true; + } + fc = new FaceCirculator(this.tds._infinite, t); + done = false; + while (!done) { + fc.next(); + li = fc.t!.indexV(this.tds._infinite); + const q = fc.t!.vertices[ccw(li)].point!; + const r = fc.t!.vertices[cw(li)].point!; + if (orientation(p, q, r) === Sign.LEFT_TURN) cwlist.push(fc.t!); + else done = true; + } + const v = this.tds.insertInFace(t); + v.point = p; + let th; + while (ccwlist.length > 0) { + th = ccwlist[0]; + li = ccw(th.indexV(this.tds._infinite)); + throw new Error("flipi"); + // this.tds.flip(th, li); + ccwlist.shift(); + } + while (cwlist.length > 0) { + th = cwlist[0]; + li = cw(th.indexV(this.tds._infinite)); + throw new Error("flipi"); + // this.tds.flip(th, li); + cwlist.shift(); + } + fc = new FaceCirculator(v, null); + while (!fc.t!.isInfinite()) fc.next(); + this.tds._infinite.triangle = fc.t!; + return v; + } + + locate(p: Point, start: Triangle | null) { let lt = 0; let li = 0; if (this.tds.dimension < 0) { lt = LocateType.OUTSIDE_AFFINE_HULL; li = 4; + return { loc: null, lt, li }; } else if (this.tds.dimension === 0) { if (this.xyEqual(p, this.tds.finiteVertex.triangle!.vertices[0].point!)) { lt = LocateType.VERTEX; @@ -107,13 +222,245 @@ export class CDT { lt = LocateType.OUTSIDE_AFFINE_HULL; } li = 4; + return { loc: null, lt, li }; + } else if (this.tds.dimension === 1) { + return this.marchLocate1D(p); + } + if (start === null) { + const t = this.tds._infinite.triangle!; + start = t.neighbours[t.indexV(this.tds._infinite)]!; + } else if (start.isInfinite()) { + start = start.neighbours[start.indexV(this.tds._infinite)]!; + } + return this.marchLocate2D(start, p); + } + + marchLocate1D(p: Point) { + const ff = this.tds._infinite.triangle!; + const iv = ff.indexV(this.tds._infinite); + const t = ff.neighbours[iv]!; + const pqt = orientation(t.vertices[0].point!, t.vertices[1].point!, p); + if (pqt === Sign.RIGHT_TURN || pqt === Sign.LEFT_TURN) { + return { loc: new Triangle(), lt: LocateType.OUTSIDE_AFFINE_HULL, li: 4 }; + } + const i = t.indexT(ff); + if (collinearBetween(p, t.vertices[1 - i].point!, t.vertices[i].point!)) + return { loc: ff, lt: LocateType.OUTSIDE_CONVEX_HULL, li: iv }; + + if (this.xyEqual(p, t.vertices[1 - i].point!)) return { loc: t, lt: LocateType.VERTEX, li: 1 - i }; + throw new Error("sdfsdf"); + } + + marchLocate2D(c: Triangle, p: Point) { + let prev = null; + let first = true; + let lt: LocateType | undefined; + let li: number | undefined; + while (true) { + if (c.isInfinite()) { + return { loc: c, lt: LocateType.OUTSIDE_CONVEX_HULL, li: c.indexV(this.tds._infinite) }; + } + const leftFirst = Math.round(Math.random()); + const p0 = c.vertices[0].point!; + const p1 = c.vertices[1].point!; + const p2 = c.vertices[2].point!; + let o0: Sign; + let o1: Sign; + let o2: Sign; + if (first) { + prev = c; + first = false; + o0 = orientation(p0, p1, p); + if (o0 === Sign.NEGATIVE) { + c = c.neighbours[2]!; + continue; + } + o1 = orientation(p1, p2, p); + if (o1 === Sign.NEGATIVE) { + c = c.neighbours[0]!; + continue; + } + o2 = orientation(p2, p0, p); + if (o2 === Sign.NEGATIVE) { + c = c.neighbours[1]!; + continue; + } + } else if (leftFirst) { + if (c.neighbours[0]! === prev) { + prev = c; + o0 = orientation(p0, p1, p); + if (o0 === Sign.NEGATIVE) { + c = c.neighbours[2]!; + continue; + } + o2 = orientation(p2, p0, p); + if (o2 === Sign.NEGATIVE) { + c = c.neighbours[1]!; + continue; + } + o1 = Sign.POSITIVE; + } else if (c.neighbours[1]! === prev) { + prev = c; + o1 = orientation(p1, p2, p); + if (o1 === Sign.NEGATIVE) { + c = c.neighbours[0]!; + continue; + } + o0 = orientation(p0, p1, p); + if (o0 === Sign.NEGATIVE) { + c = c.neighbours[2]!; + continue; + } + o2 = Sign.POSITIVE; + } else { + prev = c; + o2 = orientation(p2, p0, p); + if (o2 === Sign.NEGATIVE) { + c = c.neighbours[1]!; + continue; + } + o1 = orientation(p1, p2, p); + if (o1 === Sign.NEGATIVE) { + c = c.neighbours[0]!; + continue; + } + o0 = Sign.POSITIVE; + } + } else { + if (c.neighbours[0] === prev) { + prev = c; + o2 = orientation(p2, p0, p); + if (o2 === Sign.NEGATIVE) { + c = c.neighbours[1]!; + continue; + } + o0 = orientation(p0, p1, p); + if (o0 === Sign.NEGATIVE) { + c = c.neighbours[2]!; + continue; + } + o1 = Sign.POSITIVE; + } else if (c.neighbours[1] === prev) { + prev = c; + o0 = orientation(p0, p1, p); + if (o0 === Sign.NEGATIVE) { + c = c.neighbours[2]!; + continue; + } + o1 = orientation(p1, p2, p); + if (o1 === Sign.NEGATIVE) { + c = c.neighbours[0]!; + continue; + } + o2 = Sign.POSITIVE; + } else { + prev = c; + o1 = orientation(p1, p2, p); + if (o1 === Sign.NEGATIVE) { + c = c.neighbours[0]!; + continue; + } + o2 = orientation(p2, p1, p); + if (o2 === Sign.NEGATIVE) { + c = c.neighbours[1]!; + continue; + } + o0 = Sign.POSITIVE; + } + } + const sum = + (o0 === Sign.COLLINEAR ? 1 : 0) + (o1 === Sign.COLLINEAR ? 1 : 0) + (o2 === Sign.COLLINEAR ? 1 : 0); + switch (sum) { + case 0: { + lt = LocateType.FACE; + li = 4; + break; + } + case 1: { + lt = LocateType.EDGE; + li = o0 === Sign.COLLINEAR ? 2 : o1 === Sign.COLLINEAR ? 0 : 1; + break; + } + case 2: { + lt = LocateType.VERTEX; + li = o0 !== Sign.COLLINEAR ? 2 : o1 !== Sign.COLLINEAR ? 0 : 1; + break; + } + } + if (lt === undefined || li === undefined) throw new Error("ert"); + return { loc: c, lt, li }; } - return { loc: new Triangle(), lt, li }; } - iLocate(p: Point, start: Triangle) { + iLocate(p: Point, start: Triangle | null) { if (this.tds.dimension < 2) return start; - return start; + if (start === null) { + const t = this.tds._infinite.triangle!; + start = t.neighbours[t.indexV(this.tds._infinite)]; + } else if (start.isInfinite()) { + start = start.neighbours[start.indexV(this.tds._infinite)]; + } + let prev = null; + let c = start!; + let first = true; + let nTurns = 2500; + while (true) { + if (!nTurns--) return c; + if (c.isInfinite()) return c; + const p0 = c.vertices[0].point!; + const p1 = c.vertices[1].point!; + const p2 = c.vertices[2].point!; + if (first) { + prev = c; + first = false; + if (hasInexactNegativeOrientation(p0, p1, p)) { + c = c.neighbours[2]!; + continue; + } + if (hasInexactNegativeOrientation(p1, p2, p)) { + c = c.neighbours[0]!; + continue; + } + if (hasInexactNegativeOrientation(p2, p0, p)) { + c = c.neighbours[1]!; + continue; + } + } else { + if (c.neighbours[0] === prev) { + prev = c; + if (hasInexactNegativeOrientation(p0, p1, p)) { + c = c.neighbours[2]!; + continue; + } + if (hasInexactNegativeOrientation(p2, p0, p)) { + c = c.neighbours[1]!; + continue; + } + } else if (c.neighbours[1] === prev) { + prev = c; + if (hasInexactNegativeOrientation(p0, p1, p)) { + c = c.neighbours[2]!; + continue; + } + if (hasInexactNegativeOrientation(p1, p2, p)) { + c = c.neighbours[0]!; + continue; + } + } else { + prev = c; + if (hasInexactNegativeOrientation(p2, p0, p)) { + c = c.neighbours[1]!; + continue; + } + if (hasInexactNegativeOrientation(p1, p2, p)) { + c = c.neighbours[0]!; + continue; + } + } + } + break; + } + return c; } xyEqual(p: Point, q: Point) { diff --git a/client/src/game/visibility/te/tds.ts b/client/src/game/visibility/te/tds.ts index 6e140afe0..cc18d3fd7 100644 --- a/client/src/game/visibility/te/tds.ts +++ b/client/src/game/visibility/te/tds.ts @@ -1,18 +1,57 @@ +import { uuidv4 } from "@/core/utils"; +import { ccw, cw } from "./triag"; + export type Point = number[]; +export const INFINITE = -7e310; + +let _INFINITE_VERTEX: Vertex; + +export enum Sign { + NEGATIVE = -1, + ZERO = 0, + POSITIVE = 1, + + RIGHT_TURN = -1, + LEFT_TURN = 1, + + SMALLER = -1, + EQUAL = 0, + LARGER = 1, + + CLOCKWISE = -1, + COUNTERCLOCKWISE = 1, + + COLLINEAR = 0, + COPLANAR = 0, + DEGENERATE = 0, + + ON_NEGATIVE_SIDE = -1, + ON_ORIENTED_BOUNDARY = 0, + ON_POSITIVE_SIDE = 1, +} + function newPoint(): Point { - return [-7e-310, -7e-310]; + return [INFINITE, INFINITE]; } export class Triangle { vertices: Vertex[] = []; neighbours: (Triangle | null)[] = [null, null, null]; constraints = [false, false, false]; + uuid = uuidv4(); constructor(...vertices: Vertex[]) { this.vertices = vertices; } + from(t: Triangle): this { + this.vertices = t.vertices.slice(0, t.vertices.length); + this.neighbours = t.neighbours.slice(0, t.neighbours.length); + this.constraints = t.constraints.slice(0, t.constraints.length); + return this; + } + get dimension() { return this.vertices.length - 1; } @@ -29,10 +68,6 @@ export class Triangle { return false; } - setNeighbour(index: number, neighbour: Triangle) { - this.neighbours[index] = neighbour; - } - reorient() { // If certain indices do not exist yet thay will append faulty undefined's, thus we slice them this.vertices = [this.vertices[1], this.vertices[0], this.vertices[2]].slice(0, this.vertices.length); @@ -40,15 +75,28 @@ export class Triangle { this.constraints = [this.constraints[1], this.constraints[0], this.constraints[2]]; } - index(v: Vertex): number { + indexV(v: Vertex): number { return this.vertices.indexOf(v); } + + indexT(t: Triangle): number { + return this.neighbours.indexOf(t); + } + + isInfinite(index?: number): boolean { + if (index === undefined) { + return this.vertices.includes(_INFINITE_VERTEX); + } else { + return this.vertices[ccw(index)].infinite || this.vertices[cw(index)].infinite; + } + } } export class Vertex { infinite = false; - _point: Point | undefined; + private _point: Point | undefined; triangle: Triangle | undefined; + uuid = uuidv4(); constructor(point?: Point) { this._point = point; @@ -85,9 +133,8 @@ export class EdgeCirculator { this.v = null; this.t = null; } else { - const i = this.t.index(v!); - if (this.t.dimension === 2) this.ri = 0; - // this.ccw(i); + const i = this.t.indexV(v!); + if (this.t.dimension === 2) this.ri = ccw(i); else this.ri = 2; } this._ri = this.ri; @@ -100,18 +147,116 @@ export class EdgeCirculator { } next(): boolean { - let i = this.t!.index(this.v!); + let i = this.t!.indexV(this.v!); if (this.t!.dimension === 1) { this.t = this.t!.neighbours[i === 0 ? 1 : 0]; } else { - // this.t = this.t!.neighbours[] ccw - i = this.t!.index(this.v!); - // ri = ccw(i) + this.t = this.t!.neighbours[ccw(i)]; + i = this.t!.indexV(this.v!); + this.ri = ccw(i); } return this.ri !== this._ri || this.v !== this._v || this.t !== this._t; } } +class EdgeIterator { + private i = 0; + pos: Triangle | null; + edge: Edge = new Edge(); + tds: TDS; + _es = 0; + constructor(tds: TDS) { + this.tds = tds; + this.edge.second = 0; + if (tds.dimension <= 0) { + this.pos = null; + return; + } + this.pos = tds.triangles[0]; + if (tds.dimension === 1) this.edge.second = 2; + while (this.pos !== null && !this.associatedEdge()) { + throw new Error("[poi"); + } + + if (tds.dimension === 1) this._es = 2; + } + + get valid(): boolean { + return (this.pos !== null || this._es !== this.edge.second) && this.pos!.isInfinite(this.edge.second); + } + + next() { + do { + this.increment(); + } while (this.pos !== null && !this.associatedEdge()); + } + + collect(): Edge { + this.edge.first = this.pos; + return this.edge; + } + + associatedEdge(): boolean { + if (this.tds.dimension === 1) return true; + throw new Error("sdf"); + } + + increment() { + if (this.tds.dimension === 1) { + this.i++; + this.pos = this.tds.triangles[this.i]; + } else if (this.edge.second === 2) { + this.edge.second = 0; + this.i++; + this.pos = this.tds.triangles[this.i]; + } else { + this.edge.second++; + } + } +} + +export class FaceCirculator { + v: Vertex | null; + t: Triangle | null; + _v: Vertex | null; + _t: Triangle | null; + + constructor(v: Vertex | null, t: Triangle | null) { + this.v = v; + this.t = t; + if (v === null) { + this.t = null; + } else if (t === null) { + this.t = v.triangle!; + } + if (this.t == null || this.t.dimension < 2) { + this.v = null; + this.t = null; + } + this._v = this.v; + this._t = this.t; + } + + get valid() { + return this.t !== null && this.v !== null; + } + + prev() { + const i = this.t!.indexV(this.v!); + this.t = this.t!.neighbours[cw(i)]; + } + + next() { + const i = this.t!.indexV(this.v!); + this.t = this.t!.neighbours[ccw(i)]; + } +} + +class Edge { + first: Triangle | null = null; + second: number = 0; +} + export enum LocateType { VERTEX, EDGE, @@ -128,6 +273,7 @@ export class TDS { constructor() { this._infinite = this.createVertex(); + _INFINITE_VERTEX = this._infinite; const t = new Triangle(); t.addVertex(this._infinite); this.triangles.push(t); @@ -142,9 +288,18 @@ export class TDS { return v; } + createTriangle(v0: Vertex, v1: Vertex, v2: Vertex, n0: Triangle | null, n1: Triangle | null, n2: Triangle | null) { + const t = new Triangle(v0, v1, v2); + t.neighbours[0] = n0; + t.neighbours[1] = n1; + t.neighbours[2] = n2; + this.triangles.push(t); + return t; + } + setAdjacency(t0: Triangle, i0: number, t1: Triangle, i1: number) { - t0.setNeighbour(i0, t1); - t1.setNeighbour(i1, t0); + t0.neighbours[i0] = t1; + t1.neighbours[i1] = t0; } get finiteVertex(): Vertex { @@ -157,6 +312,13 @@ export class TDS { return v; } + get finiteEdge(): Edge { + if (this.dimension < 1) throw new Error("aspo"); + const ei = new EdgeIterator(this); + while (ei.valid) ei.next(); + return ei.collect(); + } + insertDimUp(w: Vertex = new Vertex(), orient: boolean = true): Vertex { const v = this.createVertex(); this.dimension++; @@ -176,7 +338,7 @@ export class TDS { const deleteList: Triangle[] = []; const triangles = this.triangles.slice(0, this.triangles.length); for (const trig of triangles) { - const copy = new Triangle(...trig.vertices); + const copy = new Triangle().from(trig); this.triangles.push(copy); trig.vertices[this.dimension] = v; copy.vertices[this.dimension] = w; @@ -196,8 +358,18 @@ export class TDS { triangles[lfit].reorient(); lfit++; triangles[lfit].neighbours[1]!.reorient(); + } else { + triangles[lfit].neighbours[1]!.reorient(); + lfit++; + triangles[lfit].reorient(); + } + } else { + for (const trig of triangles) { + if (orient) trig.neighbours[2]!.reorient(); + else trig.reorient(); } } + for (const trig of deleteList) { let j = 1; if (trig.vertices[0] === w) j = 0; @@ -220,9 +392,35 @@ export class TDS { mirrorIndex(t: Triangle, i: number): number { if (t.dimension === 1) { - const j = t.neighbours[i]!.index(t.vertices[i === 0 ? 1 : 0]); + const j = t.neighbours[i]!.indexV(t.vertices[i === 0 ? 1 : 0]); return j === 0 ? 1 : 0; } - return 0; + return ccw(t.neighbours[i]!.indexV(t.vertices[ccw(i)])); + } + + insertInFace(t: Triangle) { + const v = this.createVertex(); + const v0 = t.vertices[0]; + const v1 = t.vertices[1]; + const v2 = t.vertices[2]; + const n1 = t.neighbours[1]; + const n2 = t.neighbours[2]; + const t1 = this.createTriangle(v0, v, v2, t, n1, null); + const t2 = this.createTriangle(v0, v1, v, t, null, n2); + this.setAdjacency(t1, 2, t2, 1); + if (n1 !== null) { + const i1 = this.mirrorIndex(t, 1); + n1.neighbours[i1] = t1; + } + if (n2 !== null) { + const i2 = this.mirrorIndex(t, 2); + n2.neighbours[i2] = t2; + } + t.vertices[0] = v; + t.neighbours[1] = t1; + t.neighbours[2] = t2; + if (v0.triangle === t) v0.triangle = t2; + v.triangle = t; + return v; } } diff --git a/client/src/game/visibility/te/test.ts b/client/src/game/visibility/te/test.ts deleted file mode 100644 index bc4509ee9..000000000 --- a/client/src/game/visibility/te/test.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { CDT } from "./cdt"; - -const cdt = new CDT(); -cdt.insertConstraint([50, 50], [50, 100]); diff --git a/client/src/game/visibility/te/triag.ts b/client/src/game/visibility/te/triag.ts index cd5f5e1a6..792b899ef 100644 --- a/client/src/game/visibility/te/triag.ts +++ b/client/src/game/visibility/te/triag.ts @@ -1,17 +1,90 @@ -import { EdgeCirculator, Triangle, Vertex } from "./tds"; +import { EdgeCirculator, Point, Sign, Vertex } from "./tds"; + +export function cw(index: number) { + return (index + 2) % 3; +} + +export function ccw(index: number) { + return (index + 1) % 3; +} export function edgeInfo(va: Vertex, vb: Vertex) { const ec = new EdgeCirculator(va, null); if (ec.valid) { do { - const indv = 3 - ec.t!.index(va) - ec.ri; + const indv = 3 - ec.t!.indexV(va) - ec.ri; const v = ec.t!.vertices[indv]; if (!v.infinite) { if (v === vb) { return { includes: true, vi: vb, fr: ec.t!, i: ec.ri }; + } else { + const orient = orientation(va.point!, vb.point!, v.point!); + if (orient === Sign.COLLINEAR && collinearBetween(va.point!, v.point!, vb.point!)) { + return { includes: true, vi: v, fr: ec.t!, i: ec.ri }; + } } } } while (ec.next()); } return { includes: false }; } + +export function collinearBetween(p: Point, q: Point, r: Point): boolean { + let cPQ: Sign; + let cQR: Sign; + if (compare(0, p, r) === Sign.EQUAL) { + cPQ = compare(1, p, q); + cQR = compare(1, q, r); + } else { + cPQ = compare(0, p, q); + cQR = compare(0, q, r); + } + return (cPQ === Sign.SMALLER && cQR === Sign.SMALLER) || (cPQ === Sign.LARGER && cQR === Sign.LARGER); +} + +function compare(index: number, a: Point, b: Point): Sign { + if (a[index] < b[index]) return Sign.SMALLER; + if (a[index] > b[index]) return Sign.LARGER; + return Sign.EQUAL; +} + +export function orientation(p: Point, q: Point, r: Point) { + const px = p[0]; + const py = p[1]; + const qx = q[0]; + const qy = q[1]; + const rx = r[0]; + const ry = r[1]; + const pqx = qx - px; + const pqy = qy - py; + const prx = rx - px; + const pry = ry - py; + const det = determinant(pqx, pqy, prx, pry); + let maxX = Math.abs(pqx); + let maxY = Math.abs(pqy); + const aprX = Math.abs(prx); + const aprY = Math.abs(pry); + if (maxX < aprX) maxX = aprX; + if (maxY < aprY) maxY = aprY; + if (maxX > maxY) [maxX, maxY] = [maxY, maxX]; + // underflow proection in eps computation?? Math.sqrt(Number.MIN_VALUE, Number.EPSILON) + if (maxX < 2e-162 && maxX === 0) { + return Sign.ZERO; + } + // overflow protection in det computation + if (maxY < 1e153) { + const eps = Number.EPSILON * maxX * maxY; + if (det > eps) return Sign.POSITIVE; + if (det < -eps) return Sign.NEGATIVE; + } + console.error("CHECK DEZE SHIT???"); + return Sign.ZERO; +} + +export function determinant(a00: number, a01: number, a10: number, a11: number) { + return a00 * a11 - a01 * a10; +} + +export function hasInexactNegativeOrientation(p: Point, q: Point, r: Point) { + return determinant(q[0] - p[0], q[1] - p[1], r[0] - p[0], r[1] - p[1]) < 0; +} diff --git a/client/src/game/visibility/triangulate.ts b/client/src/game/visibility/triangulate.ts index 3ac0800a8..174c3ab48 100644 --- a/client/src/game/visibility/triangulate.ts +++ b/client/src/game/visibility/triangulate.ts @@ -13,6 +13,9 @@ import { CDT } from "./te/cdt"; const cdt = new CDT(); cdt.insertConstraint([50, 50], [50, 100]); +cdt.insertConstraint([50, 100], [250, 100]); +cdt.insertConstraint([250, 100], [250, 50]); +cdt.insertConstraint([250, 50], [50, 50]); /* Triangle expansion algorithm From afa5059433e10ee1e5d3423d15db4aaef1fabb40 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 19:34:35 +0100 Subject: [PATCH 08/18] Working triangulate ! --- client/src/game/layers/fowplayers.ts | 2 +- client/src/game/store.ts | 38 +- client/src/game/ui/tools/draw.vue | 3 +- client/src/game/ui/tools/select.vue | 4 +- client/src/game/visibility/{ => bvh}/bvh.ts | 7 +- client/src/game/visibility/{ => bvh}/node.ts | 0 client/src/game/visibility/dcel.ts | 167 ----- client/src/game/visibility/earcut.d.ts | 54 -- client/src/game/visibility/earcut.js | 683 ------------------ client/src/game/visibility/oldvis.ts | 63 -- .../src/game/visibility/polygon-clipping.d.ts | 5 - client/src/game/visibility/te/cdt.ts | 408 ++++++++++- client/src/game/visibility/te/draw.ts | 104 +++ client/src/game/visibility/te/pa.ts | 19 + client/src/game/visibility/te/tds.ts | 225 +++++- client/src/game/visibility/te/te.ts | 118 +++ client/src/game/visibility/te/triag.ts | 342 ++++++++- client/src/game/visibility/triangulate.ts | 364 ---------- 18 files changed, 1193 insertions(+), 1413 deletions(-) rename client/src/game/visibility/{ => bvh}/bvh.ts (98%) rename client/src/game/visibility/{ => bvh}/node.ts (100%) delete mode 100644 client/src/game/visibility/dcel.ts delete mode 100644 client/src/game/visibility/earcut.d.ts delete mode 100644 client/src/game/visibility/earcut.js delete mode 100644 client/src/game/visibility/oldvis.ts delete mode 100644 client/src/game/visibility/polygon-clipping.d.ts create mode 100644 client/src/game/visibility/te/pa.ts create mode 100644 client/src/game/visibility/te/te.ts delete mode 100644 client/src/game/visibility/triangulate.ts diff --git a/client/src/game/layers/fowplayers.ts b/client/src/game/layers/fowplayers.ts index a8f1edbb3..f2a2341f7 100644 --- a/client/src/game/layers/fowplayers.ts +++ b/client/src/game/layers/fowplayers.ts @@ -4,7 +4,7 @@ import { layerManager } from "@/game/layers/manager"; import { Settings } from "@/game/settings"; import { gameStore } from "@/game/store"; import { g2l, g2lx, g2ly } from "@/game/units"; -import { computeVisibility } from "../visibility/triangulate"; +import { computeVisibility } from "../visibility/te/te"; export class FOWPlayersLayer extends Layer { isVisionLayer: boolean = true; diff --git a/client/src/game/store.ts b/client/src/game/store.ts index 65e06f8a4..da73b05fc 100644 --- a/client/src/game/store.ts +++ b/client/src/game/store.ts @@ -8,8 +8,9 @@ import { Note } from "@/game/comm/types/general"; import { GlobalPoint } from "@/game/geom"; import { layerManager } from "@/game/layers/manager"; import { g2l, l2g } from "@/game/units"; -import { BoundingVolume } from "@/game/visibility/bvh"; +import { BoundingVolume } from "@/game/visibility/bvh/bvh"; import { rootStore } from "@/store"; +import { triangulate } from "./visibility/te/pa"; export interface GameState { boardInitialized: boolean; @@ -128,24 +129,27 @@ class GameStore extends VuexModule { } @Mutation - recalculateBV() { + recalculateBV(partial = false) { // TODO: This needs to be cleaned up.. if (this.boardInitialized) { - let success = false; - let tries = 0; - while (!success) { - success = true; - try { - this.BV = Object.freeze(new BoundingVolume(this.visionBlockers)); - } catch (error) { - success = false; - tries++; - if (tries > 10) { - console.error(error); - return; - } - } - } + console.time("BV"); + triangulate(partial); + console.timeEnd("BV"); + // let success = false; + // let tries = 0; + // while (!success) { + // success = true; + // try { + // this.BV = Object.freeze(new BoundingVolume(this.visionBlockers)); + // } catch (error) { + // success = false; + // tries++; + // if (tries > 10) { + // console.error(error); + // return; + // } + // } + // } } } diff --git a/client/src/game/ui/tools/draw.vue b/client/src/game/ui/tools/draw.vue index 962c5dfe7..46bfe61d3 100644 --- a/client/src/game/ui/tools/draw.vue +++ b/client/src/game/ui/tools/draw.vue @@ -208,7 +208,7 @@ export default class DrawTool extends Tool { (this.shape)._points.push(endPoint); } socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: true }); - if (this.shape.visionObstruction) gameStore.recalculateBV(); + if (this.shape.visionObstruction) gameStore.recalculateBV(true); layer.invalidate(false); } onMouseUp(event: MouseEvent) { @@ -216,6 +216,7 @@ export default class DrawTool extends Tool { if (!event.altKey && this.useGrid) { this.shape.resizeToGrid(); } + if (this.shape.visionObstruction) gameStore.recalculateBV(); socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: false }); this.active = false; } diff --git a/client/src/game/ui/tools/select.vue b/client/src/game/ui/tools/select.vue index 9015d39e5..3a6fcf885 100644 --- a/client/src/game/ui/tools/select.vue +++ b/client/src/game/ui/tools/select.vue @@ -156,7 +156,7 @@ export default class SelectTool extends Tool { for (const sel of layer.selection) { sel.refPoint = sel.refPoint.add(delta); if (sel !== this.selectionHelper) { - if (sel.visionObstruction) gameStore.recalculateBV(); + if (sel.visionObstruction) gameStore.recalculateBV(true); socket.emit("Shape.Update", { shape: sel.asDict(), redraw: true, temporary: true }); } } @@ -165,7 +165,7 @@ export default class SelectTool extends Tool { for (const sel of layer.selection) { sel.resize(this.resizeDirection, mouse); if (sel !== this.selectionHelper) { - if (sel.visionObstruction) gameStore.recalculateBV(); + if (sel.visionObstruction) gameStore.recalculateBV(true); socket.emit("Shape.Update", { shape: sel.asDict(), redraw: true, temporary: true }); } layer.invalidate(false); diff --git a/client/src/game/visibility/bvh.ts b/client/src/game/visibility/bvh/bvh.ts similarity index 98% rename from client/src/game/visibility/bvh.ts rename to client/src/game/visibility/bvh/bvh.ts index 840990bd6..8cd89c009 100644 --- a/client/src/game/visibility/bvh.ts +++ b/client/src/game/visibility/bvh/bvh.ts @@ -2,11 +2,9 @@ import { partition } from "@/core/utils"; import { GlobalPoint, Ray } from "@/game/geom"; import { layerManager } from "@/game/layers/manager"; import { BoundingRect } from "@/game/shapes/boundingrect"; +import { gameStore } from "@/game/store"; import { g2lx, g2ly, g2lz } from "@/game/units"; -import { BoundingNode, InteriorNode, LeafNode } from "@/game/visibility/node"; -import { gameStore } from "../store"; - -import * as tr from "./triangulate"; +import { BoundingNode, InteriorNode, LeafNode } from "@/game/visibility/bvh/node"; interface BuildInfo { index: number; @@ -36,7 +34,6 @@ export class BoundingVolume { offset = 0; constructor(shapes: string[]) { - tr.triangulate(shapes, true); this.shapes = shapes; if (this.shapes.length === 0) { this.root = null; diff --git a/client/src/game/visibility/node.ts b/client/src/game/visibility/bvh/node.ts similarity index 100% rename from client/src/game/visibility/node.ts rename to client/src/game/visibility/bvh/node.ts diff --git a/client/src/game/visibility/dcel.ts b/client/src/game/visibility/dcel.ts deleted file mode 100644 index bcef85315..000000000 --- a/client/src/game/visibility/dcel.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { OrderedMap } from "@/core/utils"; -import { GlobalPoint } from "../geom"; -import { layerManager } from "../layers/manager"; -import { g2lx, g2ly } from "../units"; - -export let dcel: DCEL; - -export class Edge { - from: number; - to: number; - triangleIndex: number = -1; - constrained = false; - - constructor(from: number, to: number) { - this.from = from; - this.to = to; - } - - get opposite() { - return dcel.edges.get(`${this.to}-${this.from}`); - } - - get triangle() { - return dcel.triangles[this.triangleIndex]; - } - - get toVertex() { - return [dcel.vertices[2 * this.to], dcel.vertices[2 * this.to + 1]]; - } - - get fromVertex() { - return [dcel.vertices[2 * this.from], dcel.vertices[2 * this.from + 1]]; - } - - draw(colour = "black") { - const ctx = layerManager.getLayer("draw")!.ctx; - ctx.beginPath(); - ctx.strokeStyle = colour; - ctx.moveTo(g2lx(dcel.vertices[2 * this.from]), g2ly(dcel.vertices[2 * this.from + 1])); - ctx.lineTo(g2lx(dcel.vertices[2 * this.to]), g2ly(dcel.vertices[2 * this.to + 1])); - - ctx.closePath(); - ctx.stroke(); - } -} - -export class Triangle { - edges: number[] = []; - constructor(edges: number[]) { - this.edges = edges; - } - - getEdges(): Edge[] { - return this.edges.map(e => dcel.edges.getIndexValue(e)); - } - - edge(index: number): Edge { - return dcel.edges.getIndexValue(this.edges[index]); - } - - vertex(index: number): [number, number] { - const idx = this.edge(this.ccw(index)).from; - return [dcel.vertices[2 * idx], dcel.vertices[2 * idx + 1]]; - } - - index(edge: Edge) { - for (let i = 0; i < this.edges.length; i++) { - if (dcel.edges.getIndexValue(this.edges[i]) === edge) return i; - } - return -1; - } - - ccw(edge: number) { - return (edge + 2) % 3; - } - - cw(edge: number) { - return (edge + 1) % 3; - } - - neighbour(index: number) { - return this.edge(index).opposite.triangle; - } - - contains(point: GlobalPoint) { - const A = - -this.vertex(1)[1] * this.vertex(2)[0] + - this.vertex(0)[1] * (-this.vertex(1)[0] + this.vertex(2)[0]) + - this.vertex(0)[0] * (this.vertex(1)[1] - this.vertex(2)[1]) + - this.vertex(1)[0] * this.vertex(2)[1]; - const sign = A < 0 ? -1 : 1; - const s = - (this.vertex(0)[1] * this.vertex(2)[0] - - this.vertex(0)[0] * this.vertex(2)[1] + - (this.vertex(2)[1] - this.vertex(0)[1]) * point.x + - (this.vertex(0)[0] - this.vertex(2)[0]) * point.y) * - sign; - if (s < 0) return false; - const t = - (this.vertex(0)[0] * this.vertex(1)[1] - - this.vertex(0)[1] * this.vertex(1)[0] + - (this.vertex(0)[1] - this.vertex(1)[1]) * point.x + - (this.vertex(1)[0] - this.vertex(0)[0]) * point.y) * - sign; - - return t > 0 && s + t < A * sign; - } - - fill(colour = "rgba(0, 0, 0, 0.25)") { - const ctx = layerManager.getLayer("draw")!.ctx; - ctx.beginPath(); - ctx.strokeStyle = "rgba(0, 0, 0, 1)"; - ctx.fillStyle = colour; - ctx.lineJoin = "round"; - ctx.moveTo(g2lx(this.vertex(0)[0]), g2ly(this.vertex(0)[1])); - ctx.lineTo(g2lx(this.vertex(1)[0]), g2ly(this.vertex(1)[1])); - ctx.lineTo(g2lx(this.vertex(2)[0]), g2ly(this.vertex(2)[1])); - ctx.lineTo(g2lx(this.vertex(0)[0]), g2ly(this.vertex(0)[1])); - - ctx.closePath(); - ctx.fill(); - } -} - -export class DCEL { - vertices: number[]; - triangles: Triangle[] = []; - edges = new OrderedMap(); - isolatedVertices: number[][][][] = []; - constructor(vertices: number[] = [], holes: number[] = []) { - this.vertices = vertices; - } - add_edge(edge: Edge) { - const key = `${edge.from}-${edge.to}`; - const revKey = `${edge.to}-${edge.from}`; - this.edges.set(key, edge); - } - add_triangle(triangle: Triangle) { - this.triangles.push(triangle); - for (const edge of triangle.edges) this.edges.getIndexValue(edge).triangleIndex = this.triangles.length - 1; - } - - locate(point: GlobalPoint): Triangle | undefined { - for (const triangle of this.triangles) { - if (triangle.contains(point)) return triangle; - } - } - - checkConstraints(start: number, end: number, holes: number[]) { - // if (holes.length === 0) return; - holes.unshift(0); - for (let h = 0; h < holes.length; h++) { - const i = start + holes[h]; - const j = h + 1 === holes.length ? end : start + holes[h + 1]; - for (let k = i + 1; k < j; k++) { - this.edges.get(`${k - 1}-${k}`).constrained = true; - } - this.edges.get(`${j - 1}-${i}`).constrained = true; - } - } -} - -export function createDCEL(vertices: number[] = []) { - dcel = new DCEL(vertices); -} - -createDCEL([]); diff --git a/client/src/game/visibility/earcut.d.ts b/client/src/game/visibility/earcut.d.ts deleted file mode 100644 index c1d770753..000000000 --- a/client/src/game/visibility/earcut.d.ts +++ /dev/null @@ -1,54 +0,0 @@ -// Type definitions for earcut 2.1 -// Project: https://github.com/mapbox/earcut#readme -// Definitions by: Adrian Leonhard -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped -// Duplicated export mirrors actual source: https://github.com/mapbox/earcut/blob/master/src/earcut.js#L3-L4 - -interface EarcutStatic { - /** - * Triangulate an outline. - * - * @param vertices A flat array of vertice coordinates like [x0,y0, x1,y1, x2,y2, ...]. - * @param holes An array of hole indices if any (e.g. [5, 8] for a 12-vertice input would mean one hole with vertices 5–7 and another with 8–11). - * @param dimensions The number of coordinates per vertice in the input array (2 by default). - * @return A flat array with each group of three numbers indexing a triangle in the `vertices` array. - * @example earcut([10,0, 0,50, 60,60, 70,10]); // returns [1,0,3, 3,2,1] - * @example with a hole: earcut([0,0, 100,0, 100,100, 0,100, 20,20, 80,20, 80,80, 20,80], [4]); // [3,0,4, 5,4,0, 3,4,7, 5,0,1, 2,3,7, 6,5,1, 2,7,6, 6,1,2] - * @example with 3d coords: earcut([10,0,1, 0,50,2, 60,60,3, 70,10,4], null, 3); // [1,0,3, 3,2,1] - */ - (vertices: ArrayLike, holes?: ArrayLike, dimensions?: number): number[]; - - /** - * Transforms multi-dimensional array (e.g. GeoJSON Polygon) into the format expected by earcut. - * @example Transforming GeoJSON data. - * const data = earcut.flatten(geojson.geometry.coordinates); - * const triangles = earcut(data.vertices, data.holes, data.dimensions); - * @example Transforming simple triangle with hole: - * const data = earcut.flatten([[[0, 0], [100, 0], [0, 100]], [[10, 10], [0, 10], [10, 0]]]); - * const triangles = earcut(data.vertices, data.holes, data.dimensions); - * @param data Arrays of rings, with the first being the outline and the rest holes. A ring is an array points, each point being an array of numbers. - */ - flatten(data: ArrayLike>>): { vertices: number[]; holes: number[]; dimensions: number }; - - /** - * Returns the relative difference between the total area of triangles and the area of the input polygon. 0 means the triangulation is fully correct. - * @param vertices same as earcut - * @param holes same as earcut - * @param dimensions same as earcut - * @param triangles see return value of earcut - * @example - * const triangles = earcut(vertices, holes, dimensions); - * const deviation = earcut.deviation(vertices, holes, dimensions, triangles); - */ - deviation( - vertices: ArrayLike, - holes: ArrayLike | undefined, - dimensions: number, - triangles: ArrayLike, - ): number; - - default: EarcutStatic; -} -declare const exports: EarcutStatic; -export = exports; -export as namespace earcut; diff --git a/client/src/game/visibility/earcut.js b/client/src/game/visibility/earcut.js deleted file mode 100644 index 363b142e8..000000000 --- a/client/src/game/visibility/earcut.js +++ /dev/null @@ -1,683 +0,0 @@ -"use strict"; - -module.exports = earcut; -module.exports.default = earcut; - -function earcut(data, holeIndices, dim) { - dim = dim || 2; - - var hasHoles = holeIndices && holeIndices.length, - outerLen = hasHoles ? holeIndices[0] * dim : data.length, - outerNode = linkedList(data, 0, outerLen, dim, true), - triangles = []; - - if (!outerNode || outerNode.next === outerNode.prev) return triangles; - - var minX, minY, maxX, maxY, x, y, invSize; - - if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); - - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - if (data.length > 80 * dim) { - minX = maxX = data[0]; - minY = maxY = data[1]; - - for (var i = dim; i < outerLen; i += dim) { - x = data[i]; - y = data[i + 1]; - if (x < minX) minX = x; - if (y < minY) minY = y; - if (x > maxX) maxX = x; - if (y > maxY) maxY = y; - } - - // minX, minY and invSize are later used to transform coords into integers for z-order calculation - invSize = Math.max(maxX - minX, maxY - minY); - invSize = invSize !== 0 ? 1 / invSize : 0; - } - - earcutLinked(outerNode, triangles, dim, minX, minY, invSize); - - return triangles; -} - -// create a circular doubly linked list from polygon points in the specified winding order -function linkedList(data, start, end, dim, clockwise) { - var i, last; - - if (clockwise === signedArea(data, start, end, dim) > 0) { - for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last); - } else { - for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); - } - - if (last && equals(last, last.next)) { - removeNode(last); - last = last.next; - } - - return last; -} - -// eliminate colinear or duplicate points -function filterPoints(start, end) { - if (!start) return start; - if (!end) end = start; - - var p = start, - again; - do { - again = false; - - if (!p.steiner && equals(p, p.next)) { - // if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { - removeNode(p); - p = end = p.prev; - if (p === p.next) break; - again = true; - } else { - p = p.next; - } - } while (again || p !== end); - - return end; -} - -// main ear slicing loop which triangulates a polygon (given as a linked list) -function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { - if (!ear) return; - - // interlink polygon nodes in z-order - if (!pass && invSize) indexCurve(ear, minX, minY, invSize); - - var stop = ear, - prev, - next; - - // iterate through ears, slicing them one by one - while (ear.prev !== ear.next) { - prev = ear.prev; - next = ear.next; - - if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { - // cut off the triangle - triangles.push(prev.i / dim); - triangles.push(ear.i / dim); - triangles.push(next.i / dim); - - removeNode(ear); - - // skipping the next vertex leads to less sliver triangles - ear = next.next; - stop = next.next; - - continue; - } - - ear = next; - - // if we looped through the whole remaining polygon and can't find any more ears - if (ear === stop) { - // try filtering points and slicing again - if (!pass) { - earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); - - // if this didn't work, try curing all small self-intersections locally - } else if (pass === 1) { - ear = cureLocalIntersections(ear, triangles, dim); - earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); - - // as a last resort, try splitting the remaining polygon into two - } else if (pass === 2) { - splitEarcut(ear, triangles, dim, minX, minY, invSize); - } - - break; - } - } -} - -// check whether a polygon node forms a valid ear with adjacent nodes -function isEar(ear) { - var a = ear.prev, - b = ear, - c = ear.next; - - if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - - // now make sure we don't have other points inside the potential ear - var p = ear.next.next; - - while (p !== ear.prev) { - if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; - p = p.next; - } - - return true; -} - -function isEarHashed(ear, minX, minY, invSize) { - var a = ear.prev, - b = ear, - c = ear.next; - - if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - - // triangle bbox; min & max are calculated like this for speed - var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : b.x < c.x ? b.x : c.x, - minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : b.y < c.y ? b.y : c.y, - maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : b.x > c.x ? b.x : c.x, - maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : b.y > c.y ? b.y : c.y; - - // z-order range for the current triangle bbox; - var minZ = zOrder(minTX, minTY, minX, minY, invSize), - maxZ = zOrder(maxTX, maxTY, minX, minY, invSize); - - var p = ear.prevZ, - n = ear.nextZ; - - // look for points inside the triangle in both directions - while (p && p.z >= minZ && n && n.z <= maxZ) { - if ( - p !== ear.prev && - p !== ear.next && - pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && - area(p.prev, p, p.next) >= 0 - ) - return false; - p = p.prevZ; - - if ( - n !== ear.prev && - n !== ear.next && - pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) && - area(n.prev, n, n.next) >= 0 - ) - return false; - n = n.nextZ; - } - - // look for remaining points in decreasing z-order - while (p && p.z >= minZ) { - if ( - p !== ear.prev && - p !== ear.next && - pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) && - area(p.prev, p, p.next) >= 0 - ) - return false; - p = p.prevZ; - } - - // look for remaining points in increasing z-order - while (n && n.z <= maxZ) { - if ( - n !== ear.prev && - n !== ear.next && - pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) && - area(n.prev, n, n.next) >= 0 - ) - return false; - n = n.nextZ; - } - - return true; -} - -// go through all polygon nodes and cure small local self-intersections -function cureLocalIntersections(start, triangles, dim) { - var p = start; - do { - var a = p.prev, - b = p.next.next; - - if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { - triangles.push(a.i / dim); - triangles.push(p.i / dim); - triangles.push(b.i / dim); - - // remove two nodes involved - removeNode(p); - removeNode(p.next); - - p = start = b; - } - p = p.next; - } while (p !== start); - - return p; -} - -// try splitting polygon into two and triangulate them independently -function splitEarcut(start, triangles, dim, minX, minY, invSize) { - // look for a valid diagonal that divides the polygon into two - var a = start; - do { - var b = a.next.next; - while (b !== a.prev) { - if (a.i !== b.i && isValidDiagonal(a, b)) { - // split the polygon in two by the diagonal - var c = splitPolygon(a, b); - - // filter colinear points around the cuts - a = filterPoints(a, a.next); - c = filterPoints(c, c.next); - - // run earcut on each half - earcutLinked(a, triangles, dim, minX, minY, invSize); - earcutLinked(c, triangles, dim, minX, minY, invSize); - return; - } - b = b.next; - } - a = a.next; - } while (a !== start); -} - -// link every hole into the outer loop, producing a single-ring polygon without holes -function eliminateHoles(data, holeIndices, outerNode, dim) { - var queue = [], - i, - len, - start, - end, - list; - - for (i = 0, len = holeIndices.length; i < len; i++) { - start = holeIndices[i] * dim; - end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; - list = linkedList(data, start, end, dim, false); - if (list === list.next) list.steiner = true; - queue.push(getLeftmost(list)); - } - - queue.sort(compareX); - - // process holes from left to right - for (i = 0; i < queue.length; i++) { - eliminateHole(queue[i], outerNode); - outerNode = filterPoints(outerNode, outerNode.next); - } - - return outerNode; -} - -function compareX(a, b) { - return a.x - b.x; -} - -// find a bridge between vertices that connects hole with an outer ring and and link it -function eliminateHole(hole, outerNode) { - outerNode = findHoleBridge(hole, outerNode); - if (outerNode) { - var b = splitPolygon(outerNode, hole); - filterPoints(b, b.next); - } -} - -// David Eberly's algorithm for finding a bridge between hole and outer polygon -function findHoleBridge(hole, outerNode) { - var p = outerNode, - hx = hole.x, - hy = hole.y, - qx = -Infinity, - m; - - // find a segment intersected by a ray from the hole's leftmost point to the left; - // segment's endpoint with lesser x will be potential connection point - do { - if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { - var x = p.x + ((hy - p.y) * (p.next.x - p.x)) / (p.next.y - p.y); - if (x <= hx && x > qx) { - qx = x; - if (x === hx) { - if (hy === p.y) return p; - if (hy === p.next.y) return p.next; - } - m = p.x < p.next.x ? p : p.next; - } - } - p = p.next; - } while (p !== outerNode); - - if (!m) return null; - - if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint - - // look for points inside the triangle of hole point, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the point of the minimum angle with the ray as connection point - - var stop = m, - mx = m.x, - my = m.y, - tanMin = Infinity, - tan; - - p = m.next; - - while (p !== stop) { - if ( - hx >= p.x && - p.x >= mx && - hx !== p.x && - pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y) - ) { - tan = Math.abs(hy - p.y) / (hx - p.x); // tangential - - if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) { - m = p; - tanMin = tan; - } - } - - p = p.next; - } - - return m; -} - -// interlink polygon nodes in z-order -function indexCurve(start, minX, minY, invSize) { - var p = start; - do { - if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize); - p.prevZ = p.prev; - p.nextZ = p.next; - p = p.next; - } while (p !== start); - - p.prevZ.nextZ = null; - p.prevZ = null; - - sortLinked(p); -} - -// Simon Tatham's linked list merge sort algorithm -// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html -function sortLinked(list) { - var i, - p, - q, - e, - tail, - numMerges, - pSize, - qSize, - inSize = 1; - - do { - p = list; - list = null; - tail = null; - numMerges = 0; - - while (p) { - numMerges++; - q = p; - pSize = 0; - for (i = 0; i < inSize; i++) { - pSize++; - q = q.nextZ; - if (!q) break; - } - qSize = inSize; - - while (pSize > 0 || (qSize > 0 && q)) { - if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { - e = p; - p = p.nextZ; - pSize--; - } else { - e = q; - q = q.nextZ; - qSize--; - } - - if (tail) tail.nextZ = e; - else list = e; - - e.prevZ = tail; - tail = e; - } - - p = q; - } - - tail.nextZ = null; - inSize *= 2; - } while (numMerges > 1); - - return list; -} - -// z-order of a point given coords and inverse of the longer side of data bbox -function zOrder(x, y, minX, minY, invSize) { - // coords are transformed into non-negative 15-bit integer range - x = 32767 * (x - minX) * invSize; - y = 32767 * (y - minY) * invSize; - - x = (x | (x << 8)) & 0x00ff00ff; - x = (x | (x << 4)) & 0x0f0f0f0f; - x = (x | (x << 2)) & 0x33333333; - x = (x | (x << 1)) & 0x55555555; - - y = (y | (y << 8)) & 0x00ff00ff; - y = (y | (y << 4)) & 0x0f0f0f0f; - y = (y | (y << 2)) & 0x33333333; - y = (y | (y << 1)) & 0x55555555; - - return x | (y << 1); -} - -// find the leftmost node of a polygon ring -function getLeftmost(start) { - var p = start, - leftmost = start; - do { - if (p.x < leftmost.x) leftmost = p; - p = p.next; - } while (p !== start); - - return leftmost; -} - -// check if a point lies within a convex triangle -function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { - return ( - (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && - (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && - (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0 - ); -} - -// check if a diagonal between two polygon nodes is valid (lies in polygon interior) -function isValidDiagonal(a, b) { - return ( - a.next.i !== b.i && - a.prev.i !== b.i && - !intersectsPolygon(a, b) && - locallyInside(a, b) && - locallyInside(b, a) && - middleInside(a, b) - ); -} - -// signed area of a triangle -function area(p, q, r) { - return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); -} - -// check if two points are equal -function equals(p1, p2) { - return p1.x === p2.x && p1.y === p2.y; -} - -// check if two segments intersect -function intersects(p1, q1, p2, q2) { - if ((equals(p1, q1) && equals(p2, q2)) || (equals(p1, q2) && equals(p2, q1))) return true; - return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 && area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0; -} - -// check if a polygon diagonal intersects any polygon segments -function intersectsPolygon(a, b) { - var p = a; - do { - if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) - return true; - p = p.next; - } while (p !== a); - - return false; -} - -// check if a polygon diagonal is locally inside the polygon -function locallyInside(a, b) { - return area(a.prev, a, a.next) < 0 - ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 - : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; -} - -// check if the middle point of a polygon diagonal is inside the polygon -function middleInside(a, b) { - var p = a, - inside = false, - px = (a.x + b.x) / 2, - py = (a.y + b.y) / 2; - do { - if ( - p.y > py !== p.next.y > py && - p.next.y !== p.y && - px < ((p.next.x - p.x) * (py - p.y)) / (p.next.y - p.y) + p.x - ) - inside = !inside; - p = p.next; - } while (p !== a); - - return inside; -} - -// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; -// if one belongs to the outer ring and another to a hole, it merges it into a single ring -function splitPolygon(a, b) { - var a2 = new Node(a.i, a.x, a.y), - b2 = new Node(b.i, b.x, b.y), - an = a.next, - bp = b.prev; - - a.next = b; - b.prev = a; - - a2.next = an; - an.prev = a2; - - b2.next = a2; - a2.prev = b2; - - bp.next = b2; - b2.prev = bp; - - return b2; -} - -// create a node and optionally link it with previous one (in a circular doubly linked list) -function insertNode(i, x, y, last) { - var p = new Node(i, x, y); - - if (!last) { - p.prev = p; - p.next = p; - } else { - p.next = last.next; - p.prev = last; - last.next.prev = p; - last.next = p; - } - return p; -} - -function removeNode(p) { - p.next.prev = p.prev; - p.prev.next = p.next; - - if (p.prevZ) p.prevZ.nextZ = p.nextZ; - if (p.nextZ) p.nextZ.prevZ = p.prevZ; -} - -function Node(i, x, y) { - // vertex index in coordinates array - this.i = i; - - // vertex coordinates - this.x = x; - this.y = y; - - // previous and next vertex nodes in a polygon ring - this.prev = null; - this.next = null; - - // z-order curve value - this.z = null; - - // previous and next nodes in z-order - this.prevZ = null; - this.nextZ = null; - - // indicates whether this is a steiner point - this.steiner = false; -} - -// return a percentage difference between the polygon area and its triangulation area; -// used to verify correctness of triangulation -earcut.deviation = function(data, holeIndices, dim, triangles) { - var hasHoles = holeIndices && holeIndices.length; - var outerLen = hasHoles ? holeIndices[0] * dim : data.length; - - var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); - if (hasHoles) { - for (var i = 0, len = holeIndices.length; i < len; i++) { - var start = holeIndices[i] * dim; - var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; - polygonArea -= Math.abs(signedArea(data, start, end, dim)); - } - } - - var trianglesArea = 0; - for (i = 0; i < triangles.length; i += 3) { - var a = triangles[i] * dim; - var b = triangles[i + 1] * dim; - var c = triangles[i + 2] * dim; - trianglesArea += Math.abs( - (data[a] - data[c]) * (data[b + 1] - data[a + 1]) - (data[a] - data[b]) * (data[c + 1] - data[a + 1]), - ); - } - - return polygonArea === 0 && trianglesArea === 0 ? 0 : Math.abs((trianglesArea - polygonArea) / polygonArea); -}; - -function signedArea(data, start, end, dim) { - var sum = 0; - for (var i = start, j = end - dim; i < end; i += dim) { - sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); - j = i; - } - return sum; -} - -// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts -earcut.flatten = function(data) { - var dim = data[0][0].length, - result = { vertices: [], holes: [], dimensions: dim }, - holeIndex = 0; - - for (var i = 0; i < data.length; i++) { - for (var j = 0; j < data[i].length; j++) { - for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]); - } - if (i > 0) { - holeIndex += data[i - 1].length; - result.holes.push(holeIndex); - } - } - return result; -}; diff --git a/client/src/game/visibility/oldvis.ts b/client/src/game/visibility/oldvis.ts deleted file mode 100644 index 377818f1f..000000000 --- a/client/src/game/visibility/oldvis.ts +++ /dev/null @@ -1,63 +0,0 @@ -// import earcut from "earcut"; - -// import { GlobalPoint } from "../geom"; -// import { layerManager } from "../layers/manager"; -// import { g2lx, g2ly } from "../units"; -// import { generate } from "./triangulate"; - -// export function draw(vertices: number[], triangles: number[]) { -// const ctx = layerManager.getLayer("draw")!.ctx; -// ctx.beginPath(); -// ctx.strokeStyle = "rgba(0, 0, 0, 1)"; -// ctx.lineJoin = "round"; -// ctx.moveTo(g2lx(vertices[triangles[0]]), g2ly(vertices[triangles[0] + 1])); -// for (let t = 0; t < triangles.length; t += 3) { -// ctx.moveTo(g2lx(vertices[2 * triangles[t]]), g2ly(vertices[2 * triangles[t] + 1])); -// ctx.lineTo(g2lx(vertices[2 * triangles[t + 1]]), g2ly(vertices[2 * triangles[t + 1] + 1])); -// ctx.lineTo(g2lx(vertices[2 * triangles[t + 2]]), g2ly(vertices[2 * triangles[t + 2] + 1])); -// ctx.lineTo(g2lx(vertices[2 * triangles[t]]), g2ly(vertices[2 * triangles[t] + 1])); -// } - -// ctx.closePath(); -// ctx.stroke(); -// } - -// export function x(dd = []) { -// const g = generate(dd); -// console.log(g); -// const e = earcut(g.vertices, g.holes); -// console.log(e); -// draw(g.vertices, e); -// console.log(getTrig(layerManager.getLayer()!.selection[0].refPoint, g.vertices, e)); -// } - -// function inTrig(point: GlobalPoint, trig: number[]) { -// console.log(`Testing ${trig}`); -// const A = -trig[3] * trig[4] + trig[1] * (-trig[2] + trig[4]) + trig[0] * (trig[3] - trig[5]) + trig[2] * trig[5]; -// const sign = A < 0 ? -1 : 1; -// const s = -// (trig[1] * trig[4] - trig[0] * trig[5] + (trig[5] - trig[1]) * point.x + (trig[0] - trig[4]) * point.y) * sign; -// if (s < 0) return false; -// const t = -// (trig[0] * trig[3] - trig[1] * trig[2] + (trig[1] - trig[3]) * point.x + (trig[2] - trig[0]) * point.y) * sign; - -// return s + t < A * sign; -// } - -// function getTrig(p: GlobalPoint, vertices: number[], triangles: number[]) { -// for (let t = 0; t < triangles.length; t += 3) { -// if ( -// inTrig(p, [ -// vertices[2 * triangles[t]], -// vertices[2 * triangles[t] + 1], -// vertices[2 * triangles[t + 1]], -// vertices[2 * triangles[t + 1] + 1], -// vertices[2 * triangles[t + 2]], -// vertices[2 * triangles[t + 2] + 1], -// ]) -// ) { -// return t; -// } -// } -// return -1; -// } diff --git a/client/src/game/visibility/polygon-clipping.d.ts b/client/src/game/visibility/polygon-clipping.d.ts deleted file mode 100644 index 2e6f69437..000000000 --- a/client/src/game/visibility/polygon-clipping.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module "polygon-clipping" { - export function union(...shapes: number[][][][]): number[][][][]; - export function intersection(...shapes: number[][][][]): number[][][][]; - export function xor(...shapes: number[][][][]): number[][][][]; -} diff --git a/client/src/game/visibility/te/cdt.ts b/client/src/game/visibility/te/cdt.ts index c37e8e9f0..89460c5b1 100644 --- a/client/src/game/visibility/te/cdt.ts +++ b/client/src/game/visibility/te/cdt.ts @@ -1,5 +1,27 @@ -import { EdgeCirculator, LocateType, Point, Sign, TDS, Triangle, Vertex, FaceCirculator } from "./tds"; -import { collinearBetween, edgeInfo, ccw, orientation, hasInexactNegativeOrientation, cw } from "./triag"; +import { + EdgeCirculator, + FaceCirculator, + LineFaceCirculator, + LocateType, + Point, + Sign, + TDS, + Triangle, + Vertex, +} from "./tds"; +import { + ccw, + collinearBetween, + cw, + edgeInfo, + hasInexactNegativeOrientation, + intersection, + orientation, + sideOfOrientedCircle, + xyEqual, +} from "./triag"; + +export type Edge = [Triangle, number]; export class CDT { tds: TDS; @@ -25,9 +47,213 @@ export class CDT { } continue; } + const intersectionInfo = this.findIntersectedFaces(v[0], v[1]); + if (intersectionInfo.found) { + if (intersectionInfo.vi !== v[0] && intersectionInfo.vi !== v[1]) { + stack.push([v[0], intersectionInfo.vi]); + stack.push([intersectionInfo.vi, v[1]]); + } else { + stack.push(v); + } + continue; + } + this.triangulateHole(intersectionInfo.intersectedFaces, intersectionInfo.listAB, intersectionInfo.listBA); + if (intersectionInfo.vi !== v[1]) { + stack.push([intersectionInfo.vi, v[1]]); + } + } + } + + triangulateHole(intersectedFaces: Triangle[], listAB: Edge[], listBA: Edge[]) { + const edges: Edge[] = []; + this.triangulateHole2(intersectedFaces, listAB, listBA, edges); + this.propagatingFlipE(edges); + } + + triangulateHole2(intersectedFaces: Triangle[], listAB: Edge[], listBA: Edge[], edges: Edge[]) { + if (listAB.length > 0) { + this.triangulateHalfHole(listAB, edges); + this.triangulateHalfHole(listBA, edges); + const fl = listAB[0][0]; + const fr = listBA[0][0]; + fl.neighbours[2] = fr; + fr.neighbours[2] = fl; + fl.constraints[2] = true; + fr.constraints[2] = true; + + while (intersectedFaces.length > 0) { + this.tds.deleteTriangle(intersectedFaces.shift()!); + } } } + triangulateHalfHole(conflictBoundaries: Edge[], edges: Edge[]) { + let iC = 0; + let iN: number; + let iT: number; + const current = () => conflictBoundaries[iC]; + const next = () => conflictBoundaries[iN]; + const tempo = () => conflictBoundaries[iT]; + + const va = current()[0].vertices[ccw(current()[1])]!; + iN = iC; + ++iN; + + let n: Triangle; + let n1: Triangle; + let n2: Triangle; + let ind: number; + let ind1: number; + let ind2: number; + do { + n1 = current()[0]; + ind1 = current()[1]; + if (n1.neighbours[ind1] !== null) { + n = n1.neighbours[ind1]!; + ind = cw(n.indexV(n1.vertices[cw(ind1)]!)); + n1 = n.neighbours[ind]!; + ind1 = this.tds.mirrorIndex(n, ind); + } + n2 = next()[0]; + ind2 = next()[1]; + if (n2.neighbours[ind2] !== null) { + n = n2.neighbours[ind2]!; + ind = cw(n.indexV(n2.vertices[cw(ind2)]!)); + n2 = n.neighbours[ind]!; + ind2 = this.tds.mirrorIndex(n, ind); + } + const v0 = n1.vertices[ccw(ind1)]!; + const v1 = n1.vertices[cw(ind1)]!; + const v2 = n2.vertices[cw(ind2)]!; + const orient = orientation(v0.point!, v1.point!, v2.point!); + switch (orient) { + case Sign.RIGHT_TURN: { + const newlf = this.tds.createTriangle(v0, v2, v1, null, null, null); + edges.push([newlf, 2]); + newlf.neighbours[1] = n1; + newlf.neighbours[0] = n2; + n1.neighbours[ind1] = newlf; + n2.neighbours[ind2] = newlf; + if (n1.isConstrained(ind1)) newlf.constraints[1] = true; + if (n2.isConstrained(ind2)) newlf.constraints[0] = true; + v0.triangle = newlf; + v1.triangle = newlf; + v2.triangle = newlf; + iT = iC + 1; + conflictBoundaries.splice(iC, 0, [newlf, 2]); + conflictBoundaries.splice(Math.max(iT, iN), 1); + conflictBoundaries.splice(Math.min(iT, iN), 1); + iN = iC; + if (v0 !== va) --iC; + else ++iN; + break; + } + case Sign.LEFT_TURN: + case Sign.COLLINEAR: { + ++iC; + ++iN; + break; + } + } + } while (iN < conflictBoundaries.length); + } + + findIntersectedFaces(vaa: Vertex, vbb: Vertex) { + const aa = vaa.point!; + const bb = vbb.point!; + const listAB: Edge[] = []; + const listBA: Edge[] = []; + const intersectedFaces: Triangle[] = []; + const lfc = new LineFaceCirculator(vaa, this, bb); + let ind = lfc.pos!.indexV(vaa); + let vi: Vertex; + if (lfc.pos!.isConstrained(ind)) { + vi = this.intersect(lfc.pos!, ind, vaa, vbb); + return { found: true, vi, listAB, listBA, intersectedFaces }; + } + let lf = lfc.pos!.neighbours[ccw(ind)]!; + let rf = lfc.pos!.neighbours[cw(ind)]!; + listAB.push([lf, lf.indexT(lfc.pos!)]); + listBA.unshift([rf, rf.indexT(lfc.pos!)]); + intersectedFaces.unshift(lfc.pos!); + let previousFace = lfc.pos!; + lfc.next(); + ind = lfc.pos!.indexT(previousFace); + let currentVertex = lfc.pos!.vertices[ind]!; + let done = false; + while (currentVertex !== vbb && !done) { + let i1: number; + let i2: number; + const orient = orientation(aa, bb, currentVertex.point!); + switch (orient) { + case Sign.COLLINEAR: { + done = true; + break; + } + case Sign.LEFT_TURN: + case Sign.RIGHT_TURN: { + if (orient === Sign.LEFT_TURN) { + i1 = ccw(ind); + i2 = cw(ind); + } else { + i1 = cw(ind); + i2 = ccw(ind); + } + if (lfc.pos!.isConstrained(i1)) { + vi = this.intersect(lfc.pos!, i1, vaa, vbb); + return { found: true, vi, listAB, listBA, intersectedFaces }; + } else { + lf = lfc.pos!.neighbours[i2]!; + intersectedFaces.unshift(lfc.pos!); + if (orient === Sign.LEFT_TURN) listAB.push([lf, lf.indexT(lfc.pos!)]); + else listBA.unshift([lf, lf.indexT(lfc.pos!)]); + previousFace = lfc.pos!; + lfc.next(); + ind = lfc.pos!.indexT(previousFace); + currentVertex = lfc.pos!.vertices[ind]!; + } + break; + } + } + } + vi = currentVertex; + intersectedFaces.unshift(lfc.pos!); + lf = lfc.pos!.neighbours[cw(ind)]!; + listAB.push([lf, lf.indexT(lfc.pos!)]); + rf = lfc.pos!.neighbours[ccw(ind)]!; + listBA.unshift([rf, rf.indexT(lfc.pos!)]); + return { found: false, vi, listAB, listBA, intersectedFaces }; + } + + intersect(t: Triangle, i: number, vaa: Vertex, vbb: Vertex): Vertex { + const vcc = t.vertices[cw(i)]!; + const vdd = t.vertices[ccw(i)]!; + const pa = vaa.point!; + const pb = vbb.point!; + const pc = vcc.point!; + const pd = vdd.point!; + const pi = intersection(pa, pb, pc, pd); + let vi: Vertex; + if (pi === null) throw new Error("what"); + else { + this.removeConstrainedEdge(t, i); + vi = this.insert(pi, t); + } + + if (vi !== vcc && vi !== vdd) { + this.insertConstraintV(vcc, vi); + this.insertConstraintV(vi, vdd); + } else { + this.insertConstraintV(vcc, vdd); + } + return vi; + } + + removeConstrainedEdge(t: Triangle, i: number) { + t.constraints[i] = false; + if (this.tds.dimension === 2) t.neighbours[i]!.constraints[this.tds.mirrorIndex(t, i)] = false; + } + updateConstraintsOpposite(v: Vertex) { let t = v.triangle!; const start = t; @@ -71,27 +297,139 @@ export class CDT { propagatingFlip(t: Triangle, i: number, depth = 0) { if (!this.isFlipable(t, i)) return; + const maxDepth = 100; + if (depth === maxDepth) { + throw new Error("maxde"); + return; + } + const ni = t.neighbours[i]!; + this.flip(t, i); + this.propagatingFlip(t, i, depth + 1); + i = ni.indexV(t.vertices[i]!); + this.propagatingFlip(ni, i, depth + 1); + } + + lessEdge(e1: Edge, e2: Edge) { + const ind1 = e1[1]; + const ind2 = e2[1]; + /* return( (&(*e1.first) < &(*e2.first)) + || ( (&(*e1.first) == &(*e2.first)) && (ind1 < ind2)));*/ + console.error("This has to be done correctly"); + return ind1 < ind2; + } + + propagatingFlipE(edges: Edge[]) { + let eI = 0; + let t: Triangle; + let i: number; + let eni: Edge; + const edgeSet: Edge[] = []; + while (eI < edges.length) { + t = edges[eI][0]; + i = edges[eI][1]; + if (this.isFlipable(t, i)) { + eni = [t.neighbours[i]!, this.tds.mirrorIndex(t, i)]; + if (this.lessEdge(edges[eI], eni)) edgeSet.push(edges[eI]); + else edgeSet.push(eni); + } + ++eI; + } + let indf: number; + let ni: Triangle; + let indn: number; + let ei: Edge; + const e: (Edge | null)[] = [null, null, null, null]; + while (edgeSet.length > 0) { + t = edgeSet[0][0]; + indf = edgeSet[0][1]; + ni = t.neighbours[indf]!; + indn = this.tds.mirrorIndex(t, indf); + ei = [t, indf]; + edgeSet.splice(edgeSet.findIndex(ed => ed[0] === ei[0] && ed[1] === ei[1]), 1); + e[0] = [t, cw(indf)]; + e[1] = [t, ccw(indf)]; + e[2] = [ni, cw(indn)]; + e[3] = [ni, ccw(indn)]; + + for (const edge of e) { + const tt = edge![0]; + const ii = edge![1]; + if (this.isFlipable(tt, ii)) { + eni = [tt.neighbours[ii]!, this.tds.mirrorIndex(tt, ii)]; + if (this.lessEdge(edge!, eni)) edgeSet.push(edge!); + else edgeSet.push(eni); + } + } + } + } + + flip(t: Triangle, i: number) { + const u = t.neighbours[i]!; + const j = this.tds.mirrorIndex(t, i); + const t1 = t.neighbours[cw(i)]!; + const i1 = this.tds.mirrorIndex(t, cw(i)); + const t2 = t.neighbours[ccw(i)]!; + const i2 = this.tds.mirrorIndex(t, ccw(i)); + const t3 = u.neighbours[cw(j)]!; + const i3 = this.tds.mirrorIndex(u, cw(j)); + const t4 = u.neighbours[ccw(j)]!; + const i4 = this.tds.mirrorIndex(u, ccw(j)); + this.tds.flip(t, i); + t.constraints[t.indexT(u)] = false; + u.constraints[u.indexT(t)] = false; + t1.neighbours[i1]!.constraints[this.tds.mirrorIndex(t1, i1)] = t1.constraints[i1]; + t2.neighbours[i2]!.constraints[this.tds.mirrorIndex(t2, i2)] = t2.constraints[i2]; + t3.neighbours[i3]!.constraints[this.tds.mirrorIndex(t3, i3)] = t3.constraints[i3]; + t4.neighbours[i4]!.constraints[this.tds.mirrorIndex(t4, i4)] = t4.constraints[i4]; } isFlipable(t: Triangle, i: number, perturb = true) { const ni = t.neighbours[i]!; if (t.isInfinite() || ni.isInfinite()) return false; if (t.constraints[i]) return false; - throw new Error("Circle stuff"); + return sideOfOrientedCircle(ni, t.vertices[i]!.point!, perturb) === Sign.ON_POSITIVE_SIDE; } insertb(a: Point, loc: Triangle | null, lt: LocateType, li: number): Vertex { let insertInConstrainedEdge = false; + let v1: Vertex; + let v2: Vertex; if (lt === LocateType.EDGE && loc!.isConstrained(li)) { insertInConstrainedEdge = true; + v1 = loc!.vertices[ccw(li)]!; + v2 = loc!.vertices[cw(li)]!; } const va = this.insertc(a, loc, lt, li); - if (insertInConstrainedEdge) console.log(0); + if (insertInConstrainedEdge) this.updateConstraintsIncident(va, v1!, v2!); else if (lt !== LocateType.VERTEX) this.clearConstraintsIncident(va); if (this.tds.dimension === 2) this.updateConstraintsOpposite(va); return va; } + updateConstraintsIncident(va: Vertex, c1: Vertex, c2: Vertex) { + if (this.tds.dimension === 0) return; + if (this.tds.dimension === 1) { + const ec = new EdgeCirculator(va, null); + do { + ec.t!.constraints[2] = true; + } while (ec.next()); + } else { + const fc = new FaceCirculator(va, null); + do { + const indf = fc.t!.indexV(va); + const cwi = cw(indf); + const ccwi = ccw(indf); + if (fc.t!.vertices[cwi] === c1 || fc.t!.vertices[cwi] === c2) { + fc.t!.constraints[ccwi] = true; + fc.t!.constraints[cwi] = false; + } else { + fc.t!.constraints[ccwi] = false; + fc.t!.constraints[cwi] = true; + } + } while (fc.next()); + } + } + clearConstraintsIncident(v: Vertex) { const ec = new EdgeCirculator(v, null); if (ec.valid) { @@ -113,7 +451,7 @@ export class CDT { } switch (lt) { case LocateType.VERTEX: { - return loc!.vertices[li]; + return loc!.vertices[li]!; } case LocateType.OUTSIDE_AFFINE_HULL: { return this.insertOutsideAffineHull(p); @@ -121,11 +459,29 @@ export class CDT { case LocateType.OUTSIDE_CONVEX_HULL: { return this.insertOutsideConvexHull(p, loc!); } + case LocateType.EDGE: { + return this.insertInEdge(p, loc!, li); + } + case LocateType.FACE: { + return this.insertInFace(p, loc!); + } } throw new Error("qwe"); return new Vertex(); } + insertInEdge(p: Point, loc: Triangle, li: number): Vertex { + const v = this.tds.insertInEdge(loc, li); + v.point = p; + return v; + } + + insertInFace(p: Point, loc: Triangle): Vertex { + const v = this.tds.insertInFace(loc); + v.point = p; + return v; + } + insertFirst(p: Point): Vertex { const v = this.tds.insertDimUp(); v.point = p; @@ -142,7 +498,7 @@ export class CDT { let conform = false; if (this.tds.dimension === 1) { const t = this.tds.finiteEdge.first!; - const orient = orientation(t.vertices[0].point!, t.vertices[1].point!, p); + const orient = orientation(t.vertices[0]!.point!, t.vertices[1]!.point!, p); conform = orient === Sign.COUNTERCLOCKWISE; } const v = this.tds.insertDimUp(this.tds._infinite, conform); @@ -170,8 +526,8 @@ export class CDT { while (!done) { fc.prev(); li = fc.t!.indexV(this.tds._infinite); - const q = fc.t!.vertices[ccw(li)].point!; - const r = fc.t!.vertices[cw(li)].point!; + const q = fc.t!.vertices[ccw(li)]!.point!; + const r = fc.t!.vertices[cw(li)]!.point!; if (orientation(p, q, r) === Sign.LEFT_TURN) ccwlist.push(fc.t!); else done = true; } @@ -180,8 +536,8 @@ export class CDT { while (!done) { fc.next(); li = fc.t!.indexV(this.tds._infinite); - const q = fc.t!.vertices[ccw(li)].point!; - const r = fc.t!.vertices[cw(li)].point!; + const q = fc.t!.vertices[ccw(li)]!.point!; + const r = fc.t!.vertices[cw(li)]!.point!; if (orientation(p, q, r) === Sign.LEFT_TURN) cwlist.push(fc.t!); else done = true; } @@ -191,15 +547,13 @@ export class CDT { while (ccwlist.length > 0) { th = ccwlist[0]; li = ccw(th.indexV(this.tds._infinite)); - throw new Error("flipi"); - // this.tds.flip(th, li); + this.tds.flip(th, li); ccwlist.shift(); } while (cwlist.length > 0) { th = cwlist[0]; li = cw(th.indexV(this.tds._infinite)); - throw new Error("flipi"); - // this.tds.flip(th, li); + this.tds.flip(th, li); cwlist.shift(); } fc = new FaceCirculator(v, null); @@ -216,7 +570,7 @@ export class CDT { li = 4; return { loc: null, lt, li }; } else if (this.tds.dimension === 0) { - if (this.xyEqual(p, this.tds.finiteVertex.triangle!.vertices[0].point!)) { + if (xyEqual(p, this.tds.finiteVertex.triangle!.vertices[0]!.point!)) { lt = LocateType.VERTEX; } else { lt = LocateType.OUTSIDE_AFFINE_HULL; @@ -239,15 +593,15 @@ export class CDT { const ff = this.tds._infinite.triangle!; const iv = ff.indexV(this.tds._infinite); const t = ff.neighbours[iv]!; - const pqt = orientation(t.vertices[0].point!, t.vertices[1].point!, p); + const pqt = orientation(t.vertices[0]!.point!, t.vertices[1]!.point!, p); if (pqt === Sign.RIGHT_TURN || pqt === Sign.LEFT_TURN) { return { loc: new Triangle(), lt: LocateType.OUTSIDE_AFFINE_HULL, li: 4 }; } const i = t.indexT(ff); - if (collinearBetween(p, t.vertices[1 - i].point!, t.vertices[i].point!)) + if (collinearBetween(p, t.vertices[1 - i]!.point!, t.vertices[i]!.point!)) return { loc: ff, lt: LocateType.OUTSIDE_CONVEX_HULL, li: iv }; - if (this.xyEqual(p, t.vertices[1 - i].point!)) return { loc: t, lt: LocateType.VERTEX, li: 1 - i }; + if (xyEqual(p, t.vertices[1 - i]!.point!)) return { loc: t, lt: LocateType.VERTEX, li: 1 - i }; throw new Error("sdfsdf"); } @@ -261,9 +615,9 @@ export class CDT { return { loc: c, lt: LocateType.OUTSIDE_CONVEX_HULL, li: c.indexV(this.tds._infinite) }; } const leftFirst = Math.round(Math.random()); - const p0 = c.vertices[0].point!; - const p1 = c.vertices[1].point!; - const p2 = c.vertices[2].point!; + const p0 = c.vertices[0]!.point!; + const p1 = c.vertices[1]!.point!; + const p2 = c.vertices[2]!.point!; let o0: Sign; let o1: Sign; let o2: Sign; @@ -360,7 +714,7 @@ export class CDT { c = c.neighbours[0]!; continue; } - o2 = orientation(p2, p1, p); + o2 = orientation(p2, p0, p); if (o2 === Sign.NEGATIVE) { c = c.neighbours[1]!; continue; @@ -407,9 +761,9 @@ export class CDT { while (true) { if (!nTurns--) return c; if (c.isInfinite()) return c; - const p0 = c.vertices[0].point!; - const p1 = c.vertices[1].point!; - const p2 = c.vertices[2].point!; + const p0 = c.vertices[0]!.point!; + const p1 = c.vertices[1]!.point!; + const p2 = c.vertices[2]!.point!; if (first) { prev = c; first = false; @@ -462,8 +816,4 @@ export class CDT { } return c; } - - xyEqual(p: Point, q: Point) { - return p[0] === q[0] && p[1] === q[1]; - } } diff --git a/client/src/game/visibility/te/draw.ts b/client/src/game/visibility/te/draw.ts index 8cb4b4986..11394ea4f 100644 --- a/client/src/game/visibility/te/draw.ts +++ b/client/src/game/visibility/te/draw.ts @@ -1,5 +1,7 @@ import { layerManager } from "@/game/layers/manager"; import { g2lx, g2ly } from "@/game/units"; +import { EdgeIterator, TDS } from "./tds"; +import { ccw, cw } from "./triag"; export function drawPolygon(polygon: number[][], colour?: string) { const dl = layerManager.getLayer("draw"); @@ -37,5 +39,107 @@ export function drawPolygonL(polygon: number[][], colour?: string) { ctx.stroke(); } +function x(xx: number, local: boolean) { + if (local) return xx; + else return g2lx(xx); +} + +function y(yy: number, local: boolean) { + if (local) return yy; + else return g2ly(yy); +} + +let I = 0; +let J = 0; + +function drl(ctx: CanvasRenderingContext2D, from: number[], to: number[], constrained: boolean, local: boolean) { + // J++; + // if (constrained) { + // I++; + // console.log("*", from, to); + // } else { + // console.log(" ", from, to); + // } + ctx.beginPath(); + ctx.strokeStyle = constrained ? "rgba(255, 0, 0, 0.30)" : "rgba(0, 0, 0, 0.30)"; + ctx.moveTo(x(from[0], local), y(from[1], local)); + ctx.lineTo(x(to[0], local), y(to[1], local)); + ctx.closePath(); + ctx.stroke(); +} + +function drawPolygonT(tds: TDS, local = true, clear = true) { + I = 0; + J = 0; + let T = 0; + const dl = layerManager.getLayer("draw"); + if (dl === undefined) return; + const ctx = dl.ctx; + if (clear) ctx.clearRect(0, 0, 2000, 1000); + ctx.lineJoin = "round"; + // ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); + ctx.lineJoin = "round"; + const ei = new EdgeIterator(tds); + while (ei.valid) { + ei.next(); + ei.collect(); + } + ei.collect(); + do { + const fromP = ei.edge.first!.vertices[ccw(ei.edge.second)]!.point!; + const toP = ei.edge.first!.vertices[cw(ei.edge.second)]!.point!; + // if (fromP[0] === -Infinity || toP[0] === -Infinity) { + // ei.next(); + // continue; + // } + J++; + // if (ei.edge.first!.constraints[ei.edge.second]) { + // I++; + // console.log(`Edge: (*) ${fromP} > ${toP}`); + // } else console.log(`Edge: ${fromP} > ${toP}`); + do { + ei.next(); + ei.collect(); + } while (ei.valid); + } while (ei.pos !== null); + for (const t of tds.triangles) { + if (t.isInfinite()) continue; + T++; + const po = []; + ctx.fillStyle = "red"; + if (t.vertices[0] !== undefined) { + po.push(t.vertices[0]!.point); + ctx.beginPath(); + ctx.arc(x(t.vertices[0]!.point![0], local), y(t.vertices[0]!.point![1], local), 5, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fill(); + } + if (t.vertices[1] !== undefined) { + po.push(t.vertices[1]!.point); + ctx.arc(x(t.vertices[1]!.point![0], local), y(t.vertices[1]!.point![1], local), 5, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fill(); + } + if (t.vertices[2] !== undefined) { + po.push(t.vertices[2]!.point); + ctx.arc(x(t.vertices[2]!.point![0], local), y(t.vertices[2]!.point![1], local), 5, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fill(); + } + // console.log("[T] ", ...po, t.constraints); + + ctx.moveTo(x(t.vertices[0]!.point![0], local), y(t.vertices[0]!.point![1], local)); + if (t.vertices[0] !== undefined && t.vertices[1] !== undefined) + drl(ctx, t.vertices[0]!.point!, t.vertices[1]!.point!, t.constraints[2], local); + if (t.vertices[1] !== undefined && t.vertices[2] !== undefined) + drl(ctx, t.vertices[1]!.point!, t.vertices[2]!.point!, t.constraints[0], local); + if (t.vertices[2] !== undefined && t.vertices[0] !== undefined) + drl(ctx, t.vertices[2]!.point!, t.vertices[0]!.point!, t.constraints[1], local); + } + console.log(`Edges: ${I}/${J}`); + console.log(`Faces: ${T}`); +} + (window).DP = drawPolygon; (window).DPL = drawPolygonL; +(window).DPT = drawPolygonT; diff --git a/client/src/game/visibility/te/pa.ts b/client/src/game/visibility/te/pa.ts new file mode 100644 index 000000000..3361cdb02 --- /dev/null +++ b/client/src/game/visibility/te/pa.ts @@ -0,0 +1,19 @@ +import { layerManager } from "@/game/layers/manager"; +import { gameStore } from "@/game/store"; + +import { CDT } from "./cdt"; + +export let PA_CDT = new CDT(); + +export function triangulate(partial: boolean = false) { + const cdt = new CDT(); + for (const sh of gameStore.visionBlockers) { + const shape = layerManager.UUIDMap.get(sh)!; + if (partial && !shape.visibleInCanvas(layerManager.getLayer()!.canvas)) continue; + for (let i = 0; i < shape.points.length; i++) { + cdt.insertConstraint(shape.points[i], shape.points[(i + 1) % shape.points.length]); + } + } + PA_CDT = cdt; + (window).CDT = PA_CDT; +} diff --git a/client/src/game/visibility/te/tds.ts b/client/src/game/visibility/te/tds.ts index cc18d3fd7..5077d4d4e 100644 --- a/client/src/game/visibility/te/tds.ts +++ b/client/src/game/visibility/te/tds.ts @@ -1,5 +1,6 @@ import { uuidv4 } from "@/core/utils"; -import { ccw, cw } from "./triag"; +import { CDT } from "./cdt"; +import { ccw, cw, orientation } from "./triag"; export type Point = number[]; @@ -31,17 +32,25 @@ export enum Sign { ON_POSITIVE_SIDE = 1, } +enum LineFaceState { + UNDEFINED = -1, + VERTEX_VERTEX = 0, + VERTEX_EDGE = 1, + EDGE_VERTEX = 2, + EDGE_EDGE = 3, +} + function newPoint(): Point { return [INFINITE, INFINITE]; } export class Triangle { - vertices: Vertex[] = []; + vertices: (Vertex | null)[] = []; neighbours: (Triangle | null)[] = [null, null, null]; constraints = [false, false, false]; uuid = uuidv4(); - constructor(...vertices: Vertex[]) { + constructor(...vertices: (Vertex | null)[]) { this.vertices = vertices; } @@ -65,7 +74,7 @@ export class Triangle { } isConstrained(index: number): boolean { - return false; + return this.constraints[index]; } reorient() { @@ -87,7 +96,7 @@ export class Triangle { if (index === undefined) { return this.vertices.includes(_INFINITE_VERTEX); } else { - return this.vertices[ccw(index)].infinite || this.vertices[cw(index)].infinite; + return this.vertices[ccw(index)]!.infinite || this.vertices[cw(index)]!.infinite; } } } @@ -159,7 +168,7 @@ export class EdgeCirculator { } } -class EdgeIterator { +export class EdgeIterator { private i = 0; pos: Triangle | null; edge: Edge = new Edge(); @@ -198,17 +207,21 @@ class EdgeIterator { associatedEdge(): boolean { if (this.tds.dimension === 1) return true; - throw new Error("sdf"); + return ( + this.tds.triangles.indexOf(this.pos!) < this.tds.triangles.indexOf(this.pos!.neighbours[this.edge.second]!) + ); } increment() { if (this.tds.dimension === 1) { this.i++; - this.pos = this.tds.triangles[this.i]; + if (this.tds.triangles.length <= this.i) this.pos = null; + else this.pos = this.tds.triangles[this.i]; } else if (this.edge.second === 2) { this.edge.second = 0; this.i++; - this.pos = this.tds.triangles[this.i]; + if (this.tds.triangles.length <= this.i) this.pos = null; + else this.pos = this.tds.triangles[this.i]; } else { this.edge.second++; } @@ -246,9 +259,130 @@ export class FaceCirculator { this.t = this.t!.neighbours[cw(i)]; } - next() { + next(): boolean { const i = this.t!.indexV(this.v!); this.t = this.t!.neighbours[ccw(i)]; + return this.v !== this._v || this.t !== this._t; + } +} + +export class LineFaceCirculator { + private i = 0; + pos: Triangle | null = null; + _tr: CDT; + s: LineFaceState = LineFaceState.UNDEFINED; + p: Point; + q: Point; + + constructor(v: Vertex, tr: CDT, dir: Point) { + this._tr = tr; + this.p = v.point!; + this.q = dir; + + const fc = new FaceCirculator(v, null); + let ic = fc.t!.indexV(v); + let vt = fc.t!.vertices[cw(ic)]!; + while (v === _INFINITE_VERTEX || orientation(this.p, this.q, vt.point!) !== Sign.LEFT_TURN) { + fc.next(); + ic = fc.t!.indexV(v); + vt = fc.t!.vertices[cw(ic)]!; + if (!fc.valid) { + return; + } + } + + let vr = fc.t!.vertices[ccw(ic)]!; + let pqr: Sign = Sign.RIGHT_TURN; + // tslint:disable:no-conditional-assignment + while (vr !== _INFINITE_VERTEX && (pqr = orientation(this.p, this.q, vr.point!)) === Sign.LEFT_TURN) { + fc.prev(); + ic = fc.t!.indexV(v); + vr = fc.t!.vertices[ccw(ic)]!; + } + + ic = fc.t!.indexV(v); + vt = fc.t!.vertices[cw(ic)]!; + + if (vr === _INFINITE_VERTEX) { + fc.prev(); + ic = fc.t!.indexV(v); + vr = fc.t!.vertices[ccw(ic)]!; + pqr = orientation(this.p, this.q, vr.point!); + switch (pqr) { + case Sign.RIGHT_TURN: + case Sign.COLLINEAR: { + fc.next(); + ic = fc.t!.indexV(_INFINITE_VERTEX); + this.pos = fc.t!; + this.s = LineFaceState.VERTEX_VERTEX; + this.i = ic; + break; + } + case Sign.LEFT_TURN: { + break; + } + } + } else if (pqr === Sign.COLLINEAR) { + this.pos = fc.t!; + this.s = LineFaceState.VERTEX_VERTEX; + this.i = ccw(ic); + } else { + this.pos = fc.t!; + this.s = LineFaceState.VERTEX_EDGE; + this.i = ic; + } + } + + next() { + this.increment(); + } + + increment() { + let o: Sign; + if (this.s === LineFaceState.VERTEX_VERTEX || this.s === LineFaceState.EDGE_VERTEX) { + do { + const n = this.pos!.neighbours[cw(this.i)]!; + this.i = n.indexT(this.pos!); + this.pos = n; + if (this.pos!.vertices[this.i] === _INFINITE_VERTEX) { + o = Sign.COLLINEAR; + this.i = cw(this.i); + break; + } + o = orientation(this.p, this.q, this.pos!.vertices[this.i]!.point!); + this.i = cw(this.i); + } while (o === Sign.LEFT_TURN); + if (o === Sign.COLLINEAR) { + this.s = LineFaceState.VERTEX_VERTEX; + this.i = ccw(this.i); + } else { + this.s = LineFaceState.VERTEX_EDGE; + } + } else { + const n = this.pos!.neighbours[this.i]!; + const ni = n.indexT(this.pos!); + this.pos = n; + o = + this.pos!.vertices[ni]! === _INFINITE_VERTEX + ? Sign.COLLINEAR + : orientation(this.p, this.q, this.pos!.vertices[ni]!.point!); + switch (o) { + case Sign.LEFT_TURN: { + this.s = LineFaceState.EDGE_EDGE; + this.i = ccw(ni); + break; + } + case Sign.RIGHT_TURN: { + this.s = LineFaceState.EDGE_EDGE; + this.i = cw(ni); + break; + } + default: { + this.s = LineFaceState.EDGE_VERTEX; + this.i = ni; + } + } + } } } @@ -288,7 +422,14 @@ export class TDS { return v; } - createTriangle(v0: Vertex, v1: Vertex, v2: Vertex, n0: Triangle | null, n1: Triangle | null, n2: Triangle | null) { + createTriangle( + v0: Vertex | null, + v1: Vertex | null, + v2: Vertex | null, + n0: Triangle | null, + n1: Triangle | null, + n2: Triangle | null, + ) { const t = new Triangle(v0, v1, v2); t.neighbours[0] = n0; t.neighbours[1] = n1; @@ -297,6 +438,10 @@ export class TDS { return t; } + deleteTriangle(trig: Triangle) { + this.triangles = this.triangles.filter(t => t !== trig); + } + setAdjacency(t0: Triangle, i0: number, t1: Triangle, i1: number) { t0.neighbours[i0] = t1; t1.neighbours[i1] = t0; @@ -378,7 +523,7 @@ export class TDS { t2 = trig.neighbours[j]!; const i2 = this.mirrorIndex(trig, j); this.setAdjacency(t1, i1, t2, i2); - this.triangles = this.triangles.filter(t => t !== trig); + this.deleteTriangle(trig); } v.triangle = triangles[0]; break; @@ -392,19 +537,19 @@ export class TDS { mirrorIndex(t: Triangle, i: number): number { if (t.dimension === 1) { - const j = t.neighbours[i]!.indexV(t.vertices[i === 0 ? 1 : 0]); + const j = t.neighbours[i]!.indexV(t.vertices[i === 0 ? 1 : 0]!); return j === 0 ? 1 : 0; } - return ccw(t.neighbours[i]!.indexV(t.vertices[ccw(i)])); + return ccw(t.neighbours[i]!.indexV(t.vertices[ccw(i)]!)); } insertInFace(t: Triangle) { const v = this.createVertex(); - const v0 = t.vertices[0]; - const v1 = t.vertices[1]; - const v2 = t.vertices[2]; - const n1 = t.neighbours[1]; - const n2 = t.neighbours[2]; + const v0 = t.vertices[0]!; + const v1 = t.vertices[1]!; + const v2 = t.vertices[2]!; + const n1 = t.neighbours[1]!; + const n2 = t.neighbours[2]!; const t1 = this.createTriangle(v0, v, v2, t, n1, null); const t2 = this.createTriangle(v0, v1, v, t, null, n2); this.setAdjacency(t1, 2, t2, 1); @@ -423,4 +568,46 @@ export class TDS { v.triangle = t; return v; } + + flip(t: Triangle, i: number) { + const n = t.neighbours[i]!; + const ni = this.mirrorIndex(t, i); + const vCW = t.vertices[cw(i)]!; + const vCCW = t.vertices[ccw(i)]!; + const tr = t.neighbours[ccw(i)]!; + const tri = this.mirrorIndex(t, ccw(i)); + const bl = n.neighbours[ccw(ni)]!; + const bli = this.mirrorIndex(n, ccw(ni)); + + t.vertices[cw(i)] = n.vertices[ni]!; + n.vertices[cw(ni)] = t.vertices[i]!; + + this.setAdjacency(t, i, bl, bli); + this.setAdjacency(t, ccw(i), n, ccw(ni)); + this.setAdjacency(n, ni, tr, tri); + + if (vCW.triangle! === t) vCW.triangle = n; + if (vCCW.triangle! === n) vCCW.triangle = t; + } + + insertInEdge(t: Triangle, i: number) { + let v: Vertex; + if (this.dimension === 1) { + v = this.createVertex(); + const ff = t.neighbours[0]!; + const vv = t.vertices[1]!; + const g = this.createTriangle(v, vv, null, ff, t, null); + t.vertices[1] = v; + t.neighbours[0] = g; + ff.neighbours[1] = g; + v.triangle = g; + vv.triangle = ff; + } else { + const n = t.neighbours[i]!; + const ni = this.mirrorIndex(t, i); + v = this.insertInFace(t); + this.flip(n, ni); + } + return v; + } } diff --git a/client/src/game/visibility/te/te.ts b/client/src/game/visibility/te/te.ts new file mode 100644 index 000000000..3281c3c2f --- /dev/null +++ b/client/src/game/visibility/te/te.ts @@ -0,0 +1,118 @@ +import { GlobalPoint } from "@/game/geom"; + +import { Edge } from "./cdt"; +import { drawPolygon } from "./draw"; +import { PA_CDT } from "./pa"; +import { Point, Sign, Triangle } from "./tds"; +import { ccw, cw, orientation } from "./triag"; + +export function computeVisibility(q: GlobalPoint, it = 0, drawt = true): number[][] { + // console.time("CV"); + const Q: Point = [q.x, q.y]; + const rawOutput: number[][] = []; + const triangle = PA_CDT.locate(Q, null).loc; + if (triangle === null) { + console.error("Triangle not found"); + return []; + } + // triangle.fill(); + rawOutput.push(triangle.vertices[1]!.point!); + if (!triangle.isConstrained(0)) + expandEdge(Q, triangle.vertices[2]!.point!, triangle.vertices[1]!.point!, triangle, 0, rawOutput); + rawOutput.push(triangle.vertices[2]!.point!); + if (!triangle.isConstrained(1)) + expandEdge(Q, triangle.vertices[0]!.point!, triangle.vertices[2]!.point!, triangle, 1, rawOutput); + rawOutput.push(triangle.vertices[0]!.point!); + if (!triangle.isConstrained(2)) + expandEdge(Q, triangle.vertices[1]!.point!, triangle.vertices[0]!.point!, triangle, 2, rawOutput); + // console.timeEnd("CV"); + + if (drawt) drawPolygon(rawOutput, "red"); + + return rawOutput; +} + +function expandEdge( + q: Point, + left: number[], + right: number[], + fh: Triangle, + index: number, + rawOutput: number[][], +): void { + // fh.edge(index).draw(); + const nfh = fh.neighbours[index]!; + // nfh.fill("rgba(255, 0, 0, 0.25)"); + const nIndex = nfh.indexT(fh); + const rIndex = ccw(nIndex); + const lIndex = cw(nIndex); + const nvh = nfh.vertices[nIndex]!; + const rvh = nfh.vertices[lIndex]!; + const lvh = nfh.vertices[rIndex]!; + + const re: Edge = [nfh, rIndex]; + const le: Edge = [nfh, lIndex]; + + const ro = orientation(q, right, nvh.point!); + const lo = orientation(q, left, nvh.point!); + + // const ctx = layerManager.getLayer("draw")!.ctx; + // ctx.beginPath(); + // ctx.strokeStyle = "red"; + // ctx.lineJoin = "round"; + // ctx.moveTo(g2lx(q.x), g2ly(q.y)); + // ctx.lineTo(g2lx(left[0]), g2ly(left[1])); + // ctx.moveTo(g2lx(q.x), g2ly(q.y)); + // ctx.lineTo(g2lx(nvh[0]), g2ly(nvh[1])); + // ctx.strokeStyle = "blue"; + // ctx.moveTo(g2lx(q.x), g2ly(q.y)); + // ctx.lineTo(g2lx(right[0]), g2ly(right[1])); + // ctx.moveTo(g2lx(q.x), g2ly(q.y)); + // ctx.lineTo(g2lx(nvh[0]), g2ly(nvh[1])); + // ctx.closePath(); + // ctx.stroke(); + + // Right edge is seen if the new vertex is ccw of the right ray + if (ro === Sign.COUNTERCLOCKWISE) { + if (re[0].isConstrained(re[1])) { + // See if current right ray is rvh + if (right !== rvh.point!) rawOutput.push(raySegIntersection(q, right, nvh.point!, rvh.point!)); + if (lo === Sign.COUNTERCLOCKWISE) rawOutput.push(raySegIntersection(q, left, nvh.point!, rvh.point!)); + } else { + if (lo === Sign.COUNTERCLOCKWISE) return expandEdge(q, left, right, nfh, rIndex, rawOutput); + else expandEdge(q, nvh.point!, right, nfh, rIndex, rawOutput); + } + } + + if (ro !== Sign.CLOCKWISE && lo !== Sign.COUNTERCLOCKWISE) { + rawOutput.push(nvh.point!); + } + + if (lo === Sign.CLOCKWISE) { + if (le[0].isConstrained(le[1])) { + if (ro === Sign.CLOCKWISE) { + rawOutput.push(raySegIntersection(q, right, nvh.point!, lvh.point!)); + } + if (left !== lvh.point!) { + rawOutput.push(raySegIntersection(q, left, nvh.point!, lvh.point!)); + } + return; + } else { + if (ro === Sign.CLOCKWISE) { + return expandEdge(q, left, right, nfh, lIndex, rawOutput); + } else { + return expandEdge(q, left, nvh.point!, nfh, lIndex, rawOutput); + } + } + } +} + +function raySegIntersection(q: Point, b: Point, s: Point, t: Point) { + const denominator = (t[1] - s[1]) * (b[0] - q[0]) - (t[0] - s[0]) * (b[1] - q[1]); + const ua = ((t[0] - s[0]) * (q[1] - s[1]) - (t[1] - s[1]) * (q[0] - s[0])) / denominator; + // const ub = ((b[0] - q.x) * (q.y - s[1]) - (b[1] - q.y) * (q.x - s[0])) / denominator; + const x = q[0] + ua * (b[0] - q[0]); + const y = q[1] + ua * (b[1] - q[1]); + + return [x, y]; +} diff --git a/client/src/game/visibility/te/triag.ts b/client/src/game/visibility/te/triag.ts index 792b899ef..d02b4c55f 100644 --- a/client/src/game/visibility/te/triag.ts +++ b/client/src/game/visibility/te/triag.ts @@ -1,4 +1,6 @@ -import { EdgeCirculator, Point, Sign, Vertex } from "./tds"; +import { EdgeCirculator, Point, Sign, Triangle, Vertex } from "./tds"; + +type Line = number[]; export function cw(index: number) { return (index + 2) % 3; @@ -13,7 +15,7 @@ export function edgeInfo(va: Vertex, vb: Vertex) { if (ec.valid) { do { const indv = 3 - ec.t!.indexV(va) - ec.ri; - const v = ec.t!.vertices[indv]; + const v = ec.t!.vertices[indv]!; if (!v.infinite) { if (v === vb) { return { includes: true, vi: vb, fr: ec.t!, i: ec.ri }; @@ -77,7 +79,6 @@ export function orientation(p: Point, q: Point, r: Point) { if (det > eps) return Sign.POSITIVE; if (det < -eps) return Sign.NEGATIVE; } - console.error("CHECK DEZE SHIT???"); return Sign.ZERO; } @@ -88,3 +89,338 @@ export function determinant(a00: number, a01: number, a10: number, a11: number) export function hasInexactNegativeOrientation(p: Point, q: Point, r: Point) { return determinant(q[0] - p[0], q[1] - p[1], r[0] - p[0], r[1] - p[1]) < 0; } + +export function sideOfOrientedCircle(t: Triangle, p: Point, perturb: boolean): Sign { + if (!t.isInfinite()) + return sideOfOrientedCircleP(t.vertices[0]!.point!, t.vertices[1]!.point!, t.vertices[2]!.point!, p, perturb); + throw new Error("SSS"); +} + +function sideOfOrientedCircleP(p0: Point, p1: Point, p2: Point, p: Point, perturb: boolean): Sign { + const os = getOrientedSide(p0, p1, p2, p); + if (os !== Sign.ON_ORIENTED_BOUNDARY || !perturb) return os; + const points = [p0, p1, p2, p]; + points.sort((a, b) => a[0] - b[0] || a[1] - b[1]); + for (const point of points.reverse()) { + if (point === p) return Sign.ON_NEGATIVE_SIDE; + let o = orientation(p0, p1, p); + if (point === p2 && o !== Sign.COLLINEAR) return o; + o = orientation(p0, p, p2); + if (point === p1 && o !== Sign.COLLINEAR) return o; + o = orientation(p, p1, p2); + if (point === p0 && o !== Sign.COLLINEAR) return o; + } + return Sign.ON_NEGATIVE_SIDE; +} + +export function xyEqual(p: Point, q: Point) { + return p[0] === q[0] && p[1] === q[1]; +} + +export function xySmaller(p: Point, q: Point) { + return p[0] <= q[0] && p[1] <= q[1]; +} + +export function xyCompare(p: Point, q: Point) { + if (xySmaller(p, q)) return Sign.SMALLER; + if (xyEqual(p, q)) return Sign.EQUAL; + return Sign.LARGER; +} + +function getOrientedSide(p: Point, q: Point, r: Point, t: Point): Sign { + const qpx = q[0] - p[0]; + const qpy = q[1] - p[1]; + const rpx = r[0] - p[0]; + const rpy = r[1] - p[1]; + const tpx = t[0] - p[0]; + const tpy = t[1] - p[1]; + const tqx = t[0] - q[0]; + const tqy = t[1] - q[1]; + const rqx = r[0] - q[0]; + const rqy = r[1] - q[1]; + + const det = determinant(qpx * tpy - qpy * tpx, tpx * tqx + tpy * tqy, qpx * rpy - qpy * rpx, rpx * rqx + rpy * rqy); + let maxx = Math.abs(qpx); + let maxy = Math.abs(qpy); + const arpx = Math.abs(rpx); + const arpy = Math.abs(rpy); + const atqx = Math.abs(tqx); + const atqy = Math.abs(tqy); + const atpx = Math.abs(tpx); + const atpy = Math.abs(tpy); + const arqx = Math.abs(rqx); + const arqy = Math.abs(rqy); + + if (maxx < arpx) maxx = arpx; + if (maxx < atpx) maxx = atpx; + if (maxx < atqx) maxx = atqx; + if (maxx < arqx) maxx = arqx; + + if (maxy < arpy) maxy = arpy; + if (maxy < atpy) maxy = atpy; + if (maxy < atqy) maxy = atqy; + if (maxy < arqy) maxy = arqy; + + if (maxx > maxy) [maxx, maxy] = [maxy, maxx]; + + if (maxx < 1e-73 && maxx === 0) return Sign.ON_ORIENTED_BOUNDARY; + // sqrt(sqrt(max_double/16 + else if (maxy < 1e76) { + const eps = Number.EPSILON * maxx * maxy * (maxy * maxy); + if (det > eps) return Sign.ON_POSITIVE_SIDE; + if (det < -eps) return Sign.ON_NEGATIVE_SIDE; + } + + return Sign.ZERO; +} + +function segSegDoIntersectCrossing(p1: Point, p2: Point, p3: Point, p4: Point): boolean { + switch (orientation(p1, p2, p3)) { + case Sign.LEFT_TURN: + return orientation(p3, p4, p2) !== Sign.RIGHT_TURN; + case Sign.RIGHT_TURN: + return orientation(p3, p4, p2) !== Sign.LEFT_TURN; + case Sign.COLLINEAR: + return true; + } +} + +function segSegDoIntersectContained(p1: Point, p2: Point, p3: Point, p4: Point): boolean { + switch (orientation(p1, p2, p3)) { + case Sign.LEFT_TURN: + return orientation(p1, p2, p4) !== Sign.LEFT_TURN; + case Sign.RIGHT_TURN: + return orientation(p1, p2, p4) !== Sign.RIGHT_TURN; + case Sign.COLLINEAR: + return true; + } +} + +export function intersection(pa: Point, pb: Point, pc: Point, pd: Point) { + const i = getIntersectionType(pa, pb, pc, pd); + switch (i.intersectionType) { + case IntersectionType.POINT: + return i.point!; + case IntersectionType.NO_INTERSECTION: + return null; + } + throw new Error("sdfgighowen"); +} + +enum IntersectionType { + NO_INTERSECTION, + POINT, + SEGMENT, +} + +function getLine(p0: Point, p1: Point): Line { + if (p0[0] === p1[0]) return [1, 0, -p0[0]]; + if (p0[1] === p1[1]) return [0, 1, -p0[1]]; + const x = p1[0] - p0[0]; + const y = p1[1] - p0[1]; + return [-y, x, -x + y]; +} + +function getIntersectionType(pa: Point, pb: Point, pc: Point, pd: Point) { + if (!doIntersect(pa, pb, pc, pd)) return { intersectionType: IntersectionType.NO_INTERSECTION, point: null }; + const l1 = getLine(pa, pb); + const l2 = getLine(pc, pd); + const info = getIntersectionTypeLine(l1, l2); + switch (info.intersectionType) { + case IntersectionType.POINT: { + return info; + } + } + throw new Error("gzseuihgpib"); +} + +function getIntersectionTypeLine(la: Line, lb: Line) { + const denom = la[0] * lb[1] - lb[0] * la[1]; + const nom1 = la[1] * lb[2] - lb[1] * la[2]; + const nom2 = lb[0] * la[2] - la[0] * lb[2]; + return { + intersectionType: IntersectionType.POINT, + point: [nom1 / denom, nom2 / denom], + }; +} + +function doIntersect(A1: Point, A2: Point, B1: Point, B2: Point): boolean { + if (xySmaller(A1, A2)) { + if (xySmaller(B1, B2)) { + if (xySmaller(A2, B1) || xySmaller(B2, A1)) return false; + } else { + if (xySmaller(A2, B2) || xySmaller(B1, A1)) return false; + } + } else { + if (xySmaller(B1, B2)) { + if (xySmaller(A1, B1) || xySmaller(B2, A2)) return false; + } else { + if (xySmaller(A1, B2) || xySmaller(B1, A2)) return false; + } + } + if (xySmaller(A1, A2)) { + if (xySmaller(B1, B2)) { + switch (xyCompare(A1, B1)) { + case Sign.SMALLER: { + switch (xyCompare(A2, B1)) { + case Sign.SMALLER: + return false; + case Sign.EQUAL: + return true; + default: { + switch (xyCompare(A2, B2)) { + case Sign.SMALLER: + return segSegDoIntersectCrossing(A1, A2, B1, B2); + case Sign.EQUAL: + return true; + default: + return segSegDoIntersectContained(A1, A2, B1, B2); + } + } + } + } + case Sign.EQUAL: + return true; + default: + switch (xyCompare(B2, A1)) { + case Sign.SMALLER: + return false; + case Sign.EQUAL: + return true; + default: { + switch (xyCompare(B2, A2)) { + case Sign.SMALLER: + return segSegDoIntersectCrossing(B1, B2, A1, A2); + case Sign.EQUAL: + return true; + default: + return segSegDoIntersectContained(B1, B2, A1, A2); + } + } + } + } + } else { + switch (xyCompare(A1, B2)) { + case Sign.SMALLER: { + switch (xyCompare(A2, B2)) { + case Sign.SMALLER: + return false; + case Sign.EQUAL: + return true; + default: { + switch (xyCompare(A2, B1)) { + case Sign.SMALLER: + return segSegDoIntersectCrossing(A1, A2, B2, B1); + case Sign.EQUAL: + return true; + default: + return segSegDoIntersectContained(A1, A2, B2, B1); + } + } + } + } + case Sign.EQUAL: + return true; + default: + switch (xyCompare(B1, A1)) { + case Sign.SMALLER: + return false; + case Sign.EQUAL: + return true; + default: { + switch (xyCompare(B1, A2)) { + case Sign.SMALLER: + return segSegDoIntersectCrossing(B2, B1, A1, A2); + case Sign.EQUAL: + return true; + default: + return segSegDoIntersectContained(B2, B1, A1, A2); + } + } + } + } + } + } else { + if (xySmaller(B1, B2)) { + switch (xyCompare(A2, B1)) { + case Sign.SMALLER: { + switch (xyCompare(A1, B1)) { + case Sign.SMALLER: + return false; + case Sign.EQUAL: + return true; + default: { + switch (xyCompare(A1, B2)) { + case Sign.SMALLER: + return segSegDoIntersectCrossing(A2, A1, B1, B2); + case Sign.EQUAL: + return true; + default: + return segSegDoIntersectContained(A2, A1, B1, B2); + } + } + } + } + case Sign.EQUAL: + return true; + default: + switch (xyCompare(B2, A2)) { + case Sign.SMALLER: + return false; + case Sign.EQUAL: + return true; + default: { + switch (xyCompare(B2, A1)) { + case Sign.SMALLER: + return segSegDoIntersectCrossing(B1, B2, A2, A1); + case Sign.EQUAL: + return true; + default: + return segSegDoIntersectContained(B1, B2, A2, A1); + } + } + } + } + } else { + switch (xyCompare(A2, B2)) { + case Sign.SMALLER: { + switch (xyCompare(A1, B2)) { + case Sign.SMALLER: + return false; + case Sign.EQUAL: + return true; + default: { + switch (xyCompare(A1, B1)) { + case Sign.SMALLER: + return segSegDoIntersectCrossing(A2, A1, B2, B1); + case Sign.EQUAL: + return true; + default: + return segSegDoIntersectContained(A2, A1, B2, B1); + } + } + } + } + case Sign.EQUAL: + return true; + default: + switch (xyCompare(B1, A2)) { + case Sign.SMALLER: + return false; + case Sign.EQUAL: + return true; + default: { + switch (xyCompare(B1, A1)) { + case Sign.SMALLER: + return segSegDoIntersectCrossing(B2, B1, A2, A1); + case Sign.EQUAL: + return true; + default: + return segSegDoIntersectContained(B2, B1, A2, A1); + } + } + } + } + } + } +} diff --git a/client/src/game/visibility/triangulate.ts b/client/src/game/visibility/triangulate.ts deleted file mode 100644 index 174c3ab48..000000000 --- a/client/src/game/visibility/triangulate.ts +++ /dev/null @@ -1,364 +0,0 @@ -// import earcut from "earcut"; -import * as martinez from "martinez-polygon-clipping"; -import polygon from "polygon-clipping"; -import earcut from "./earcut.js"; - -import { GlobalPoint, Vector } from "../geom"; -import { layerManager } from "../layers/manager"; -import { g2lx, g2ly } from "../units"; -import { createDCEL, dcel, Edge, Triangle } from "./dcel"; -import { drawPolygon } from "./te/draw"; - -import { CDT } from "./te/cdt"; - -const cdt = new CDT(); -cdt.insertConstraint([50, 50], [50, 100]); -cdt.insertConstraint([50, 100], [250, 100]); -cdt.insertConstraint([250, 100], [250, 50]); -cdt.insertConstraint([250, 50], [50, 50]); - -/* -Triangle expansion algorithm -============================= -Based upon https://arxiv.org/pdf/1403.3905.pdf and the CGAL implementation. - -The polygon-clipping library returns polygons in CCW order and always adds the first point -as the last point again to form a complete polygon. - -The earcut library expects a flat list of vertices followed by a list of indices that points -to the holes in the vertices list. -*/ - -export function triangulate(shapes: string[], drawt = false) { - createDCEL(); - if (shapes.length === 0) return dcel; - console.time("reduce"); - const ringData = reduce(shapes); - (window).RD = ringData; - if (drawt) { - for (const ringshape of ringData.ringshapes) drawPolygon(ringshape); - } - console.timeEnd("reduce"); - for (let r = 0; r < ringData.rings.length; r++) { - const g = generate(ringData.ringshapes[r], ringData.rings[r]); - triag(g.vertices, g.holes); - } -} - -(window).p = polygon; -(window).m = martinez; - -function inside(poly: number[][], ring: number[][]): boolean { - const intersect = polygon.intersection([poly], [ring]); - if (intersect.length !== 1 || intersect[0].length !== 1) return false; - // for (const point of intersect[0][0]) { - // const eq = (p: number[]) => p[0] === point[0] && p[1] === point[1]; - // if (!poly.some(eq) && !ring.some(eq)) return false; - // } - return true; -} - -function matchingXors(xors: number[][][][], poly: number[][]) { - const matches = []; - for (const xorE of xors) { - // If 2 points are found matching, it is a ring - // otherwise its an inner block cross-section - let pointFound = false; - for (const xorP of xorE[0]) { - if (poly.some(p => p[0] === xorP[0] && p[1] === xorP[1])) { - if (pointFound) { - matches.push(xorE); - break; - } else { - pointFound = true; - } - } - } - } - return matches; -} - -function reduce(shapes: string[]) { - const world = [[1e10, 1e10], [-1e10, 1e10], [-1e10, -1e10], [1e10, -1e10]]; - const ringshapes: number[][][] = [world]; - const rings: number[][][][] = [[]]; - const queue: [number[][], number[]][] = []; - for (const shape of shapes) { - const p = layerManager.UUIDMap.get(shape)!.points; - if (p.length === 0) continue; - queue.push([p.reverse(), []]); - } - while (queue.length > 0) { - try { - const drctx = layerManager.getLayer("draw")!.ctx; - drctx.clearRect(0, 0, window.innerWidth, window.innerHeight); - } catch {} - const [q, ringHint] = queue.shift()!; - drawPolygon(q, "red"); - for (let r = rings.length - 1; r >= 0; r--) { - if (ringHint.length > 0 && !ringHint.includes(r)) continue; - if (!inside(q, ringshapes[r])) continue; - // Check if it further expands the inner boundary - const xor = polygon.xor([q], [ringshapes[r]]); - - if (xor.length === 1) { - // touching - if (xor[0].length === 1) { - ringshapes[r] = xor[0][0]; - continue; - // one shape is inside the other - } else { - } - // overlapping - } else if (xor.length === 2) { - ringshapes[r] = matchingXors(xor, ringshapes[r])[0][0]; - // it's possible for shapes in the ring now to be part of the ring - // todo: look into not continue'ing in this case but continuing merge detection below. - for (const ro of rings[r]) queue.unshift([ro, [r]]); - rings[r] = []; - continue; - } else if (xor.length > 2) { - // queue.push(...rings[r]); - const hints = [r]; - const ringshape = ringshapes[r]; - ringshapes.splice(r, 1); - const oldRingData = rings.splice(r, 1); - for (const match of matchingXors(xor, ringshape)) { - ringshapes.push(match[0]); - rings.push([]); - hints.push(rings.length - 1); - } - for (const ro of oldRingData[0]) queue.unshift([ro, hints]); - continue; - } - - if (rings[r].length === 0) { - rings[r].push(q); - break; - } - let result = q; - let ringMerge = false; - for (let re = rings[r].length - 1; re >= 0; re--) { - const ringEl = rings[r][re]; - const union = polygon.union([result], [ringEl]); - // no overlap - if (union.length === 2) { - continue; - // merge does not create ring - } else if (union.length === 1 && union[0].length === 1) { - rings[r].splice(re, 1); - result = union[0][0]; - // merge creates ring - } else if (union.length === 1 && union[0].length === 2) { - ringMerge = true; - rings[r].splice(re, 1); - ringshapes.push(union[0][1].reverse()); - rings.push([]); - // queue.push(...rings[r]); - for (const ro of rings[r]) { - queue.unshift([ro, [r, rings.length - 1]]); - } - rings[r] = [union[0][0]]; - break; - } else { - console.log(union); - console.error("Check dit"); - } - } - if (!ringMerge) rings[r].push(result); - break; - } - } - return { ringshapes, rings }; -} - -function generate(boundary: number[][], shapes: number[][][]) { - const vertices: number[] = boundary.reduce((acc, val) => acc.concat(val)); - const end = vertices.length; - if (vertices[0] === vertices[end - 2] && vertices[1] === vertices[end - 1]) vertices.splice(0, 2); - const holes = []; - for (const shape of shapes) { - holes.push(vertices.length / 2); - for (const [idx, point] of shape.reverse().entries()) { - if (idx > 0 && point[0] === shape[0][0] && point[1] === shape[0][1]) continue; - vertices.push(point[0], point[1]); - } - } - return { vertices, holes }; -} - -function triag(vertices: number[], holes: number[]) { - const start = dcel.vertices.length / 2; - dcel.vertices.push(...vertices); - const triangles = earcut(vertices, holes); - for (let t = 0; t < triangles.length; t += 3) { - dcel.add_edge(new Edge(start + triangles[t], start + triangles[t + 1])); - dcel.add_edge(new Edge(start + triangles[t + 1], start + triangles[t + 2])); - dcel.add_edge(new Edge(start + triangles[t + 2], start + triangles[t])); - const tl = dcel.edges.length; - dcel.add_triangle(new Triangle([tl - 3, tl - 2, tl - 1])); - } - // draw(); - dcel.checkConstraints(start, dcel.vertices.length / 2, holes); - - return dcel; -} - -function draw() { - const ctx = layerManager.getLayer("draw")!.ctx; - ctx.beginPath(); - ctx.strokeStyle = "rgba(0, 0, 0, 0.25)"; - ctx.lineJoin = "round"; - for (const triangle of dcel.triangles) { - ctx.moveTo(g2lx(triangle.vertex(0)[0]), g2ly(triangle.vertex(0)[1])); - ctx.lineTo(g2lx(triangle.vertex(1)[0]), g2ly(triangle.vertex(1)[1])); - ctx.lineTo(g2lx(triangle.vertex(2)[0]), g2ly(triangle.vertex(2)[1])); - ctx.lineTo(g2lx(triangle.vertex(0)[0]), g2ly(triangle.vertex(0)[1])); - } - - for (let i = 0; i < dcel.vertices.length; i += 2) { - ctx.fillText(`${i / 2}`, g2lx(dcel.vertices[i]), g2ly(dcel.vertices[i + 1])); - } - - ctx.closePath(); - ctx.stroke(); -} - -export function computeVisibility(q: GlobalPoint, it = 0, drawt = true): number[][] { - // console.time("CV"); - const rawOutput: number[][] = []; - const triangle = dcel.locate(q); - if (triangle === undefined) { - if (it > 10) { - console.error("Triangle not found"); - return []; - } - return computeVisibility(q.add(new Vector(0.001, 0.001)), ++it); - } - // triangle.fill(); - for (const [index, edge] of triangle.getEdges().entries()) { - rawOutput.push(edge.fromVertex); - if (!edge.constrained) expandEdge(q, edge.fromVertex, edge.toVertex, triangle, index, rawOutput); - } - // console.timeEnd("CV"); - - if (drawt) drawPolygon(rawOutput, "red"); - - return rawOutput; -} - -function expandEdge( - q: GlobalPoint, - left: number[], - right: number[], - fh: Triangle, - index: number, - rawOutput: number[][], -): void { - // fh.edge(index).draw(); - const nfh = fh.neighbour(index); - // nfh.fill("rgba(255, 0, 0, 0.25)"); - const nIndex = nfh.index(fh.edge(index).opposite); - const lIndex = nfh.cw(nIndex); - const rIndex = nfh.ccw(nIndex); - const nvh = nfh.vertex(nIndex); - const lvh = nfh.vertex(rIndex); - const rvh = nfh.vertex(lIndex); - - const le = nfh.edge(lIndex); - const re = nfh.edge(rIndex); - - const lo = orientation(q, left, nvh); - const ro = orientation(q, right, nvh); - - // const ctx = layerManager.getLayer("draw")!.ctx; - // ctx.beginPath(); - // ctx.strokeStyle = "red"; - // ctx.lineJoin = "round"; - // ctx.moveTo(g2lx(q.x), g2ly(q.y)); - // ctx.lineTo(g2lx(left[0]), g2ly(left[1])); - // ctx.moveTo(g2lx(q.x), g2ly(q.y)); - // ctx.lineTo(g2lx(nvh[0]), g2ly(nvh[1])); - // ctx.strokeStyle = "blue"; - // ctx.moveTo(g2lx(q.x), g2ly(q.y)); - // ctx.lineTo(g2lx(right[0]), g2ly(right[1])); - // ctx.moveTo(g2lx(q.x), g2ly(q.y)); - // ctx.lineTo(g2lx(nvh[0]), g2ly(nvh[1])); - // ctx.closePath(); - // ctx.stroke(); - - if (lo < 0) { - if (le.constrained) { - if (left[0] !== lvh[0] || left[1] !== lvh[1]) { - rawOutput.push(raySegIntersection(q, left, nvh, lvh)); - } - if (ro < 0) { - rawOutput.push(raySegIntersection(q, right, nvh, lvh)); - } - // return; - } else { - if (ro < 0) { - return expandEdge(q, left, right, nfh, lIndex, rawOutput); - } else { - expandEdge(q, left, nvh, nfh, lIndex, rawOutput); - } - } - } - - if (ro >= 0 && lo <= 0) { - rawOutput.push(nvh); - } - - // Right edge is seen if the new vertex is ccw of the right ray - if (ro > 0) { - if (re.constrained) { - if (lo > 0) { - rawOutput.push(raySegIntersection(q, left, nvh, rvh)); - } - // See if current right ray is rvh - if (right[0] !== rvh[0] || right[1] !== rvh[1]) { - rawOutput.push(raySegIntersection(q, right, nvh, rvh)); - } - return; - } else { - if (lo > 0) { - return expandEdge(q, left, right, nfh, rIndex, rawOutput); - } else { - return expandEdge(q, nvh, right, nfh, rIndex, rawOutput); - } - } - } -} - -function raySegIntersection(q: GlobalPoint, b: number[], s: number[], t: number[]) { - const denominator = (t[1] - s[1]) * (b[0] - q.x) - (t[0] - s[0]) * (b[1] - q.y); - const ua = ((t[0] - s[0]) * (q.y - s[1]) - (t[1] - s[1]) * (q.x - s[0])) / denominator; - // const ub = ((b[0] - q.x) * (q.y - s[1]) - (b[1] - q.y) * (q.x - s[0])) / denominator; - const x = q.x + ua * (b[0] - q.x); - const y = q.y + ua * (b[1] - q.y); - - return [x, y]; -} - -// // ~Shoelace formula -// // > 0 CCW < 0 CW -// function orientation(point: GlobalPoint, from: number[], to: number[]) { -// return ( -// point.x * from[1] + from[0] * to[1] + to[0] * point.y - from[0] * point.y - to[0] * from[1] - point.x * to[1] -// ); -// } - -// > 0 CCW < 0 CW -function orientation(a: GlobalPoint, b: number[], c: number[]) { - const dAx = b[0] - a.x; - const dAy = b[1] - a.y; - const dBx = c[0] - a.x; - const dBy = c[1] - a.y; - return -Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy); -} - -(window).D = draw; -(window).G = generate; -(window).E = earcut; -(window).T = triangulate; -(window).CV = computeVisibility; From 460b94967d00b8ee098ae97e57341b675b95c735 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 19:42:17 +0100 Subject: [PATCH 09/18] Make draw mousemove temporary again --- client/src/game/ui/tools/draw.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/game/ui/tools/draw.vue b/client/src/game/ui/tools/draw.vue index 23e35577e..4e1b853b7 100644 --- a/client/src/game/ui/tools/draw.vue +++ b/client/src/game/ui/tools/draw.vue @@ -207,7 +207,7 @@ export default class DrawTool extends Tool { } else if (this.shapeSelect === "paint-brush") { (this.shape).points.push(endPoint); } - socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: false }); + socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: true }); if (this.shape.visionObstruction) gameStore.recalculateBV(); layer.invalidate(false); } From 69b6b97636899f6c21506c676088b6bef5aaeddb Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 19:43:13 +0100 Subject: [PATCH 10/18] Fix strange draw mouseUp behaviour --- client/src/game/ui/tools/draw.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/src/game/ui/tools/draw.vue b/client/src/game/ui/tools/draw.vue index 4e1b853b7..a6a3607f4 100644 --- a/client/src/game/ui/tools/draw.vue +++ b/client/src/game/ui/tools/draw.vue @@ -212,10 +212,11 @@ export default class DrawTool extends Tool { layer.invalidate(false); } onMouseUp(event: MouseEvent) { - if (this.active && this.shape !== null && !event.altKey && this.useGrid) { + if (!this.active || this.shape === null) return; + if (!event.altKey && this.useGrid) { this.shape.resizeToGrid(); - socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: false }); } + socket.emit("Shape.Update", { shape: this.shape!.asDict(), redraw: true, temporary: false }); this.active = false; } onSelect() { From 01e477977afab301a6159a5e108d1b2c93901cad Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 19:44:25 +0100 Subject: [PATCH 11/18] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c75f968e..2edb47f69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ All notable changes to this project will be documented in this file. ## Unreleased +### Fixed + +- Draw tool mouseUp behaviour had some strange quirks that are now ironed uit. + - In particular this prevented some shapes of being synced correctly. + ## 0.12.0 ### Added From bc4cc8b1b378383612f0a1be79eb6e2e137735e9 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 19:52:00 +0100 Subject: [PATCH 12/18] Remove test triangulate libs --- client/package-lock.json | 411 --------------------------------------- client/package.json | 5 - 2 files changed, 416 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index f30d98135..0c1bfc0ef 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -31,12 +31,6 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, - "@types/earcut": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.0.tgz", - "integrity": "sha512-PKu2suACS92cLQb2DZ6ytgnQm9PKPQRCD9jPqgMUTMVEuUqLTfCRHuaOXKmZTyVpL7zqJX+ZSZoyUr4e5muSNg==", - "dev": true - }, "@types/lodash": { "version": "4.14.118", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.118.tgz", @@ -495,15 +489,6 @@ } } }, - "acorn-loose": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-6.0.0.tgz", - "integrity": "sha512-gJff4bSdy882CwS6toeHixdBn9+IP8ojffjCW9hXnb2Ly7uVyAMaH2pLehtwS10wj2FIQ9Iw564MTDSsaQW9ng==", - "dev": true, - "requires": { - "acorn": "^6.0.0" - } - }, "acorn-walk": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", @@ -841,36 +826,6 @@ } } }, - "awesome-typescript-loader": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-5.2.1.tgz", - "integrity": "sha512-slv66OAJB8orL+UUaTI3pKlLorwIvS4ARZzYR9iJJyGsEgOqueMfOMdKySWzZ73vIkEe3fcwFgsKMg4d8zyb1g==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^1.1.0", - "lodash": "^4.17.5", - "micromatch": "^3.1.9", - "mkdirp": "^0.5.1", - "source-map-support": "^0.5.3", - "webpack-log": "^1.2.0" - }, - "dependencies": { - "webpack-log": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", - "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", - "dev": true, - "requires": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "loglevelnext": "^1.0.1", - "uuid": "^3.1.0" - } - } - } - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -945,11 +900,6 @@ } } }, - "babel-plugin-add-module-exports": { - "version": "0.2.1", - "resolved": "http://registry.npmjs.org/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz", - "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=" - }, "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", @@ -1609,17 +1559,6 @@ "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", "dev": true }, - "cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "dev": true, - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - } - }, "clipboardy": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", @@ -2275,15 +2214,6 @@ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "dev": true }, - "d": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "^0.10.9" - } - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -2488,48 +2418,6 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, - "dependency-cruiser": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-4.6.3.tgz", - "integrity": "sha512-I6uhAn8xUPnDBS2A5zyDSfBdT3723q8CGOmJjg8lCk3ao0KVXYw4ZUh6YeMcw1ovav1AgHjlK+9QPAuprPlBBg==", - "dev": true, - "requires": { - "acorn": "6.0.4", - "acorn-loose": "6.0.0", - "acorn-walk": "6.1.0", - "ajv": "6.5.5", - "awesome-typescript-loader": "5.2.1", - "chalk": "2.4.1", - "commander": "2.19.0", - "enhanced-resolve": "4.1.0", - "figures": "2.0.0", - "glob": "7.1.3", - "handlebars": "4.0.12", - "lodash": "4.17.11", - "resolve": "1.8.1", - "safe-regex": "2.0.1", - "semver": "5.6.0", - "semver-try-require": "2.0.3", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "acorn-walk": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz", - "integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==", - "dev": true - }, - "safe-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.0.1.tgz", - "integrity": "sha512-4tbOl0xq/cxbhEhdvxKaCZgzwOKeqt2tnHc2OPBkMsVdZ0s0C5oJwI6voRI9XzPSzeN35PECDNDK946x4d/0eA==", - "dev": true, - "requires": { - "regexp-tree": "~0.0.85" - } - } - } - }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", @@ -2689,11 +2577,6 @@ "stream-shift": "^1.0.0" } }, - "earcut": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.4.tgz", - "integrity": "sha512-ttRjmPD5oaTtXOoxhFp9aZvMB14kBjapYaiBuzBB1elOgSLU9P2Ev86G2OClBg+uspUXERsIzXKpUWweH2K4Xg==" - }, "easy-stack": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.0.tgz", @@ -2882,38 +2765,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.46", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", - "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3239,15 +3090,6 @@ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", "dev": true }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, "file-loader": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-2.0.0.tgz", @@ -4258,35 +4100,6 @@ "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", "dev": true }, - "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", - "dev": true, - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "dev": true, - "requires": { - "lodash": "^4.17.10" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -5538,16 +5351,6 @@ "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", "dev": true }, - "loglevelnext": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.5.tgz", - "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", - "dev": true, - "requires": { - "es6-symbol": "^3.1.1", - "object.assign": "^4.1.0" - } - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -5613,22 +5416,6 @@ "object-visit": "^1.0.0" } }, - "martinez-polygon-clipping": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/martinez-polygon-clipping/-/martinez-polygon-clipping-0.5.0.tgz", - "integrity": "sha512-FvHURxJTieS4LEHL+aJmWbqCUBk2GrYU5zolgXNAQLJsjnAff7Hqqk5MM1Kl91FnPOcyiAs/cVey11bb0D7qug==", - "requires": { - "splaytree": "^0.1.4", - "tinyqueue": "^1.2.0" - }, - "dependencies": { - "splaytree": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-0.1.4.tgz", - "integrity": "sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ==" - } - } - }, "material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", @@ -6006,12 +5793,6 @@ "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", "dev": true }, - "next-tick": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -6346,18 +6127,6 @@ "isobject": "^3.0.0" } }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, "object.getownpropertydescriptors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", @@ -6443,24 +6212,6 @@ "is-wsl": "^1.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - } - } - }, "ora": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ora/-/ora-2.1.0.tgz", @@ -6754,16 +6505,6 @@ "find-up": "^2.1.0" } }, - "polygon-clipping": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.9.2.tgz", - "integrity": "sha512-UO+53kvxmuyMpO6RqepUTj3lQPcFJ6NbqJbfwxcOZv3xTuWUQd2q12JiyT5Sd2NAHgxzHH2ht2BQSuclJaEyDQ==", - "requires": { - "babel-plugin-add-module-exports": "^0.2.1", - "splaytree": "^2.0.2", - "tinyqueue": "^1.2.3" - } - }, "portfinder": { "version": "1.0.19", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.19.tgz", @@ -7624,127 +7365,6 @@ "safe-regex": "^1.1.0" } }, - "regexp-tree": { - "version": "0.0.85", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.0.85.tgz", - "integrity": "sha512-KkuweOhL1M00EHljLhq2lARpLoazdKd0BpkfOZKcO55lWhRqeRCIPSPGf9osgMPj1l/0v37FaqDwa9Ks1W+92A==", - "dev": true, - "requires": { - "cli-table3": "^0.5.0", - "colors": "^1.1.2", - "yargs": "^10.0.3" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.1.2.tgz", - "integrity": "sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^8.1.0" - } - }, - "yargs-parser": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", - "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, "regexpu-core": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", @@ -8244,15 +7864,6 @@ "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true }, - "semver-try-require": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/semver-try-require/-/semver-try-require-2.0.3.tgz", - "integrity": "sha512-HbAkPxj3wTUf2i1w1T2I5V2LfyOSOfB+/a0QoNkMghEG9Y60LmWkL1xZVI5Q9nw8hq3J+H7RoYXhluVmXAVeqg==", - "dev": true, - "requires": { - "semver": "5.6.0" - } - }, "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -8812,11 +8423,6 @@ } } }, - "splaytree": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-2.0.3.tgz", - "integrity": "sha512-IziTvWQv9F1EiKq9XveosQRGTLrdUW0jLokpmAXz0+hnLgBZitvU0j4gUvCGASKwUQvCZaofhff1H8OmE2LRdA==" - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -9022,12 +8628,6 @@ "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", "dev": true }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, "stylehacks": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.1.tgz", @@ -9321,11 +8921,6 @@ "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" }, - "tinyqueue": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz", - "integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==" - }, "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", @@ -10241,12 +9836,6 @@ "string-width": "^1.0.2 || 2" } }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - }, "worker-farm": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz", diff --git a/client/package.json b/client/package.json index 8114e4e19..bf0fea2d2 100644 --- a/client/package.json +++ b/client/package.json @@ -18,10 +18,7 @@ "homepage": "https://github.com/Kruptein/PlanarAlly#readme", "dependencies": { "axios": "^0.18.0", - "earcut": "^2.1.4", "lodash.throttle": "^4.1.1", - "martinez-polygon-clipping": "^0.5.0", - "polygon-clipping": "^0.9.2", "socket.io-client": "^2.1.1", "tinycolor2": "^1.4.1", "vue": "^2.5.17", @@ -33,14 +30,12 @@ "vuex": "^3.0.1" }, "devDependencies": { - "@types/earcut": "^2.1.0", "@types/lodash": "^4.14.118", "@types/socket.io-client": "^1.4.32", "@types/tinycolor2": "^1.4.1", "@types/vue-color": "^2.4.1", "@vue/cli-plugin-typescript": "^3.1.1", "@vue/cli-service": "^3.1.4", - "dependency-cruiser": "^4.6.3", "node-sass": "^4.9.0", "prettier": "^1.15.2", "rimraf": "^2.6.2", From b90efb3c9b6b6e0a4ee2736061839118e5b7e0b7 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 20:07:22 +0100 Subject: [PATCH 13/18] Add menu toggle for new vision mode --- client/src/game/layers/fowplayers.ts | 3 +- client/src/game/store.ts | 43 ++++++++++++++++------------ client/src/game/ui/menu/menu.vue | 13 +++++++++ 3 files changed, 39 insertions(+), 20 deletions(-) diff --git a/client/src/game/layers/fowplayers.ts b/client/src/game/layers/fowplayers.ts index f2a2341f7..2897f8cc9 100644 --- a/client/src/game/layers/fowplayers.ts +++ b/client/src/game/layers/fowplayers.ts @@ -8,7 +8,6 @@ import { computeVisibility } from "../visibility/te/te"; export class FOWPlayersLayer extends Layer { isVisionLayer: boolean = true; - mode = ""; draw(): void { if (!this.valid) { @@ -40,7 +39,7 @@ export class FOWPlayersLayer extends Layer { for (const tokenId of gameStore.ownedtokens) { const token = layerManager.UUIDMap.get(tokenId); if (token === undefined) continue; - if (this.mode === "bvh") { + if (gameStore.visionMode === "bvh") { ctx.beginPath(); let lastArcAngle = -1; const center = token.center(); diff --git a/client/src/game/store.ts b/client/src/game/store.ts index da73b05fc..8caf913f8 100644 --- a/client/src/game/store.ts +++ b/client/src/game/store.ts @@ -59,10 +59,17 @@ class GameStore extends VuexModule { BV = Object.freeze(new BoundingVolume([])); + visionMode: "bvh" | "triangle" = "bvh"; + get selectedLayer() { return this.layers[this.selectedLayerIndex]; } + @Mutation + setVisionMode(visionMode: "bvh" | "triangle") { + this.visionMode = visionMode; + } + @Mutation setBoardInitialized(boardInitialized: boolean) { this.boardInitialized = boardInitialized; @@ -132,24 +139,24 @@ class GameStore extends VuexModule { recalculateBV(partial = false) { // TODO: This needs to be cleaned up.. if (this.boardInitialized) { - console.time("BV"); - triangulate(partial); - console.timeEnd("BV"); - // let success = false; - // let tries = 0; - // while (!success) { - // success = true; - // try { - // this.BV = Object.freeze(new BoundingVolume(this.visionBlockers)); - // } catch (error) { - // success = false; - // tries++; - // if (tries > 10) { - // console.error(error); - // return; - // } - // } - // } + if (this.visionMode === "triangle") triangulate(partial); + else { + let success = false; + let tries = 0; + while (!success) { + success = true; + try { + this.BV = Object.freeze(new BoundingVolume(this.visionBlockers)); + } catch (error) { + success = false; + tries++; + if (tries > 10) { + console.error(error); + return; + } + } + } + } } } diff --git a/client/src/game/ui/menu/menu.vue b/client/src/game/ui/menu/menu.vue index 5795dd547..c26776393 100644 --- a/client/src/game/ui/menu/menu.vue +++ b/client/src/game/ui/menu/menu.vue @@ -104,6 +104,11 @@ + + @@ -149,6 +154,7 @@ import { getRef, uuidv4 } from "@/core/utils"; import { socket } from "@/game/api/socket"; import { Note } from "@/game/comm/types/general"; import { gameStore } from "@/game/store"; +import { layerManager } from "@/game/layers/manager"; @Component({ components: { @@ -250,6 +256,13 @@ export default class MenuBar extends Vue { openNote(note: Note) { getRef("note").open(note); } + changeVisionMode(event: { target: HTMLSelectElement }) { + const value = event.target.value.toLowerCase(); + if (value !== "bvh" && value !== "triangle") return; + gameStore.setVisionMode(value); + gameStore.recalculateBV(); + layerManager.invalidate(); + } } From ddb34670874480e1b563516c87de8bb4f36f4d2f Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 20:09:29 +0100 Subject: [PATCH 14/18] Stop showing vision boundary by default for te --- client/src/game/visibility/te/te.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/game/visibility/te/te.ts b/client/src/game/visibility/te/te.ts index 3281c3c2f..bb2d41207 100644 --- a/client/src/game/visibility/te/te.ts +++ b/client/src/game/visibility/te/te.ts @@ -6,7 +6,7 @@ import { PA_CDT } from "./pa"; import { Point, Sign, Triangle } from "./tds"; import { ccw, cw, orientation } from "./triag"; -export function computeVisibility(q: GlobalPoint, it = 0, drawt = true): number[][] { +export function computeVisibility(q: GlobalPoint, it = 0, drawt = false): number[][] { // console.time("CV"); const Q: Point = [q.x, q.y]; const rawOutput: number[][] = []; From 82dcae6f31d1e2bb07e670803adf74aee316f981 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 20:10:37 +0100 Subject: [PATCH 15/18] Silence a console error for now --- client/src/game/visibility/te/cdt.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/game/visibility/te/cdt.ts b/client/src/game/visibility/te/cdt.ts index 89460c5b1..861c4115c 100644 --- a/client/src/game/visibility/te/cdt.ts +++ b/client/src/game/visibility/te/cdt.ts @@ -314,7 +314,8 @@ export class CDT { const ind2 = e2[1]; /* return( (&(*e1.first) < &(*e2.first)) || ( (&(*e1.first) == &(*e2.first)) && (ind1 < ind2)));*/ - console.error("This has to be done correctly"); + // TODO: This is not proper. + // console.error("This has to be done correctly"); return ind1 < ind2; } From 2a18d48ae991dbfc438abda47b0b4ad389c31ad2 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 20:12:59 +0100 Subject: [PATCH 16/18] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2edb47f69..99cd19bbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ All notable changes to this project will be documented in this file. ## Unreleased +### Added + +- A new vision system has been added based on triangulation. + - You can select this new system as a client option + - It is more precise (i.e. exact) than the previous vision system which was a good approximation. + - It can handle any polygon under any angle, so expect some new draw tools in the future! + - It is slightly more expensive to preprocess, but this should be relatively unnoticeable. + ### Fixed - Draw tool mouseUp behaviour had some strange quirks that are now ironed uit. From a8d1369a27c46c89f4b823834d819ada981ca88c Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 20:20:55 +0100 Subject: [PATCH 17/18] Update typescript and vue-cli --- client/package-lock.json | 490 ++++++++++++++++++++++----------------- client/package.json | 4 +- 2 files changed, 282 insertions(+), 212 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 0c1bfc0ef..f39f046dc 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -37,6 +37,12 @@ "integrity": "sha512-iiJbKLZbhSa6FYRip/9ZDX6HXhayXLDGY2Fqws9cOkEQ6XeKfaxB0sC541mowZJueYyMnVUmmG+al5/4fCDrgw==", "dev": true }, + "@types/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz", + "integrity": "sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA==", + "dev": true + }, "@types/socket.io-client": { "version": "1.4.32", "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.32.tgz", @@ -65,9 +71,9 @@ "dev": true }, "@vue/cli-overlay": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@vue/cli-overlay/-/cli-overlay-3.1.0.tgz", - "integrity": "sha512-id6FtCzfbYQ812vRP9AA5qelmQTfhYvYmU+AGm+eZmSbdk8eZqbUtiraFPa5JsqnPN8twUvpPLmvqmPHoK+VEw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/cli-overlay/-/cli-overlay-3.3.0.tgz", + "integrity": "sha512-UyfeuX6txu8sRtfhJOJlPgETzU3KjshKY2qAnC34KJKcS+7oIYRpeOo8jMMLjImVE0g6d8Rn3A1GkXjRiKWW6w==", "dev": true }, "@vue/cli-plugin-typescript": { @@ -85,18 +91,18 @@ } }, "@vue/cli-service": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-3.1.4.tgz", - "integrity": "sha512-dArLh5yd2B2an/jTD+cmkRmyRxKSE+5il9408vhciyqvZGVjkajTN95YjY5VLkBMQvrv/Z+goGSn+7wtnQSOFg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-3.3.0.tgz", + "integrity": "sha512-7PNitKBBCFcGfCvXSiuUGV2GpGA6KEupQcuxQ2xPL5T3UMSAzJLpx7Siw/9++yO4nQEib6yP3YQk2zTfeXWTSA==", "dev": true, "requires": { "@intervolga/optimize-cssnano-plugin": "^1.0.5", - "@vue/cli-overlay": "^3.1.0", - "@vue/cli-shared-utils": "^3.1.1", + "@vue/cli-overlay": "^3.3.0", + "@vue/cli-shared-utils": "^3.3.0", "@vue/preload-webpack-plugin": "^1.1.0", "@vue/web-component-wrapper": "^1.2.0", - "acorn": "^6.0.2", - "acorn-walk": "^6.1.0", + "acorn": "^6.0.4", + "acorn-walk": "^6.1.1", "address": "^1.0.3", "autoprefixer": "^8.6.5", "cache-loader": "^1.2.5", @@ -106,12 +112,12 @@ "cliui": "^4.1.0", "copy-webpack-plugin": "^4.6.0", "css-loader": "^1.0.1", - "cssnano": "^4.1.7", - "debug": "^4.1.0", + "cssnano": "^4.1.8", + "debug": "^4.1.1", "escape-string-regexp": "^1.0.5", "file-loader": "^2.0.0", "friendly-errors-webpack-plugin": "^1.7.0", - "fs-extra": "^7.0.0", + "fs-extra": "^7.0.1", "globby": "^8.0.1", "hash-sum": "^1.0.2", "html-webpack-plugin": "^3.2.0", @@ -119,10 +125,10 @@ "lodash.defaultsdeep": "^4.6.0", "lodash.mapvalues": "^4.6.0", "lodash.transform": "^4.6.0", - "mini-css-extract-plugin": "^0.4.4", + "mini-css-extract-plugin": "^0.5.0", "minimist": "^1.2.0", "ora": "^3.0.0", - "portfinder": "^1.0.19", + "portfinder": "^1.0.20", "postcss-loader": "^3.0.0", "read-pkg": "^4.0.1", "semver": "^5.6.0", @@ -130,27 +136,73 @@ "source-map-url": "^0.4.0", "ssri": "^6.0.1", "string.prototype.padend": "^3.0.0", - "terser-webpack-plugin": "^1.1.0", + "terser-webpack-plugin": "^1.2.1", "thread-loader": "^1.2.0", "url-loader": "^1.1.2", "vue-loader": "^15.4.2", - "webpack": "^4.18.1", + "webpack": "4", "webpack-bundle-analyzer": "^3.0.3", "webpack-chain": "^4.11.0", - "webpack-dev-server": "^3.1.10", - "webpack-merge": "^4.1.4", + "webpack-dev-server": "^3.1.14", + "webpack-merge": "^4.1.5", "yorkie": "^2.0.0" }, "dependencies": { + "@vue/cli-shared-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-3.3.0.tgz", + "integrity": "sha512-V/sU1jc7/jMCAbU8uA5f4j9Yd8lTqdi3I6FEHfLG1nstwhaNi4BU3WKWOAl72NYVWFYG8VuCrYWDn75kMimtuw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "execa": "^1.0.0", + "joi": "^14.3.0", + "launch-editor": "^2.2.1", + "lru-cache": "^5.1.1", + "node-ipc": "^9.1.1", + "opn": "^5.3.0", + "ora": "^3.0.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "semver": "^5.5.0", + "string.prototype.padstart": "^3.0.0" + } + }, "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" } }, + "hoek": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", + "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", + "dev": true + }, + "joi": { + "version": "14.3.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.1.tgz", + "integrity": "sha512-LQDdM+pkOrpAn4Lp+neNIFV3axv1Vna3j38bisbQhETPMANYRbFJFUyOZcOClYvM/hppMhGWuKSFEK9vjrB+bQ==", + "dev": true, + "requires": { + "hoek": "6.x.x", + "isemail": "3.x.x", + "topo": "3.x.x" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -200,57 +252,33 @@ } }, "@vue/component-compiler-utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-2.3.0.tgz", - "integrity": "sha512-4RB1mow8IO2X0/86plKhflMJYSiSn3sWHiqVob8s/LCADFMHSaFRTQp5GdqvjvlGmwjhdn2dzDQik+RLjTx/5g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-2.5.0.tgz", + "integrity": "sha512-mSB8jWmE/ZeYZHPDEx9hNiiRh5P2V1Q0tObxEQWtxxfXtkIAvPnj7oucGm5SO8Y/QwIlDJgAGqHfj5MCjoKoOg==", "dev": true, "requires": { "consolidate": "^0.15.1", "hash-sum": "^1.0.2", "lru-cache": "^4.1.2", "merge-source-map": "^1.1.0", - "postcss": "^6.0.20", - "postcss-selector-parser": "^3.1.1", + "postcss": "^7.0.7", + "postcss-selector-parser": "^5.0.0", "prettier": "1.13.7", - "source-map": "^0.5.6", + "source-map": "^0.7.3", "vue-template-es2015-compiler": "^1.6.0" }, "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", - "dev": true, - "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, "prettier": { "version": "1.13.7", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.7.tgz", "integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==", "dev": true + }, + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true } } }, @@ -467,9 +495,9 @@ } }, "acorn": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", - "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.5.tgz", + "integrity": "sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==", "dev": true }, "acorn-dynamic-import": { @@ -519,9 +547,9 @@ } }, "ajv-errors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz", - "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", "dev": true }, "ajv-keywords": { @@ -727,7 +755,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -750,7 +778,7 @@ }, "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true }, @@ -1143,7 +1171,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -1180,7 +1208,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -1213,19 +1241,19 @@ } }, "browserslist": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.4.tgz", - "integrity": "sha512-u5iz+ijIMUlmV8blX82VGFrB9ecnUg5qEt55CMZ/YJEhha+d8qpBfOFuutJ6F/VKRXjZoD33b6uvarpPxcl3RA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.0.tgz", + "integrity": "sha512-tQkHS8VVxWbrjnNDXgt7/+SuPJ7qDvD0Y2e6bLtoQluR2SPvlmPUcfcU75L1KAalhqULlIFJlJ6BDfnYyJxJsw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000899", - "electron-to-chromium": "^1.3.82", - "node-releases": "^1.0.1" + "caniuse-lite": "^1.0.30000928", + "electron-to-chromium": "^1.3.100", + "node-releases": "^1.1.3" } }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -1272,7 +1300,7 @@ }, "cacache": { "version": "10.0.4", - "resolved": "http://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", "dev": true, "requires": { @@ -1362,7 +1390,7 @@ }, "callsites": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, @@ -1413,9 +1441,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000910", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000910.tgz", - "integrity": "sha512-u/nxtHGAzCGZzIxt3dA/tpSPOcirBZFWKwz1EPz4aaupnBI2XR0Rbr74g0zc6Hzy41OEM4uMoZ38k56TpYAWjQ==", + "version": "1.0.30000928", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000928.tgz", + "integrity": "sha512-aSpMWRXL6ZXNnzm8hgE4QDLibG5pVJ2Ujzsuj3icazlIkxXkPXtL+BWnMx6FBkWmkZgBHGUxPZQvrbRw2ZTxhg==", "dev": true }, "case-sensitive-paths-webpack-plugin": { @@ -1597,7 +1625,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true } @@ -1633,11 +1661,13 @@ } }, "coa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.1.tgz", - "integrity": "sha512-5wfTTO8E2/ja4jFSxePXlG5nRu5bBtL/r1HCIpJW/lzT6yDtKl0u0Z4o/Vpz32IpKmBn7HerheEZQgA9N2DarQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", "dev": true, "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", "q": "^1.1.2" } }, @@ -1694,7 +1724,7 @@ }, "colors": { "version": "1.1.2", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, @@ -1777,9 +1807,9 @@ } }, "connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", "dev": true }, "console-browserify": { @@ -1918,7 +1948,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -1931,7 +1961,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -1977,7 +2007,7 @@ }, "css-color-names": { "version": "0.0.4", - "resolved": "http://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", "dev": true }, @@ -2102,21 +2132,21 @@ "dev": true }, "cssnano": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.7.tgz", - "integrity": "sha512-AiXL90l+MDuQmRNyypG2P7ux7K4XklxYzNNUd5HXZCNcH8/N9bHPcpN97v8tXgRVeFL/Ed8iP8mVmAAu0ZpT7A==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.8.tgz", + "integrity": "sha512-5GIY0VzAHORpbKiL3rMXp4w4M1Ki+XlXgEXyuWXVd3h6hlASb+9Vo76dNP56/elLMVBBsUfusCo1q56uW0UWig==", "dev": true, "requires": { "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.5", + "cssnano-preset-default": "^4.0.6", "is-resolvable": "^1.0.0", "postcss": "^7.0.0" } }, "cssnano-preset-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.5.tgz", - "integrity": "sha512-f1uhya0ZAjPYtDD58QkBB0R+uYdzHPei7cDxJyQQIHt5acdhyGXaSXl2nDLzWHLwGFbZcHxQtkJS8mmNwnxTvw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.6.tgz", + "integrity": "sha512-UPboYbFaJFtDUhJ4fqctThWbbyF4q01/7UhsZbLzp35l+nUxtzh1SifoVlEfyLM3n3Z0htd8B1YlCxy9i+bQvg==", "dev": true, "requires": { "css-declaration-sorter": "^4.0.1", @@ -2129,7 +2159,7 @@ "postcss-discard-duplicates": "^4.0.2", "postcss-discard-empty": "^4.0.1", "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.9", + "postcss-merge-longhand": "^4.0.10", "postcss-merge-rules": "^4.0.2", "postcss-minify-font-values": "^4.0.2", "postcss-minify-gradients": "^4.0.1", @@ -2298,7 +2328,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true } @@ -2379,7 +2409,7 @@ "dependencies": { "globby": { "version": "6.1.0", - "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { @@ -2392,7 +2422,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -2448,7 +2478,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -2513,7 +2543,7 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", "dev": true } @@ -2526,9 +2556,9 @@ "dev": true }, "domelementtype": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.2.1.tgz", - "integrity": "sha512-SQVCLFS2E7G5CRCMdn6K9bIhRj1bS6QBWZfF0TUPh4V/BbqrQ619IdSS3/izn0FZ+9l+uODzaZjb08fjOfablA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", "dev": true }, "domhandler": { @@ -2561,7 +2591,7 @@ }, "duplexer": { "version": "0.1.1", - "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, @@ -2606,9 +2636,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.84", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.84.tgz", - "integrity": "sha512-IYhbzJYOopiTaNWMBp7RjbecUBsbnbDneOP86f3qvS0G0xfzwNSvMJpTrvi5/Y1gU7tg2NAgeg8a8rCYvW9Whw==", + "version": "1.3.102", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.102.tgz", + "integrity": "sha512-2nzZuXw/KBPnI3QX3UOCSRvJiVy7o9+VHRDQ3D/EHCvVc89X6aj/GlNmEgiR2GBIhmSWXIi4W1M5okA5ScSlNg==", "dev": true }, "elliptic": { @@ -2834,7 +2864,7 @@ }, "events": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, @@ -3144,7 +3174,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -3299,7 +3329,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -3312,7 +3342,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -4229,9 +4259,9 @@ "dev": true }, "hash.js": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", - "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -4340,7 +4370,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -4369,7 +4399,7 @@ }, "htmlparser2": { "version": "3.3.0", - "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { @@ -4396,7 +4426,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -4422,7 +4452,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -4451,7 +4481,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -4906,7 +4936,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -5101,7 +5131,7 @@ }, "jsesc": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true }, @@ -5440,7 +5470,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -5558,9 +5588,9 @@ } }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", + "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", "dev": true }, "mime-db": { @@ -5585,9 +5615,9 @@ "dev": true }, "mini-css-extract-plugin": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.5.tgz", - "integrity": "sha512-dqBanNfktnp2hwL2YguV9Jh91PFX7gu7nRLs4TGsbAfAG6WOtlynFRYzwDwmmeSb5uIwHo9nx1ta0f7vAZVp2w==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", + "integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==", "dev": true, "requires": { "loader-utils": "^1.1.0", @@ -5893,9 +5923,9 @@ } }, "node-releases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.4.tgz", - "integrity": "sha512-GqRV9GcHw8JCRDaP/JoeNMNzEGzHAknMvIHqMb2VeTOmg1Cf9+ej8bkV12tHfzWHQMCkQ5zUFgwFUkfraynNCw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz", + "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==", "dev": true, "requires": { "semver": "^5.3.0" @@ -6147,15 +6177,15 @@ } }, "object.values": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", - "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", + "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.12.0", + "function-bind": "^1.1.1", + "has": "^1.0.3" } }, "obuf": { @@ -6288,7 +6318,7 @@ }, "p-is-promise": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", "dev": true }, @@ -6323,9 +6353,9 @@ "dev": true }, "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz", + "integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==", "dev": true }, "parallel-transform": { @@ -6350,7 +6380,7 @@ }, "parse-asn1": { "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { @@ -6506,9 +6536,9 @@ } }, "portfinder": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.19.tgz", - "integrity": "sha512-23aeQKW9KgHe6citUrG3r9HjeX6vls0h713TAa+CwTKZwNIr/pD2ApaxYF4Um3ZZyq4ar+Siv3+fhoHaIwSOSw==", + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", + "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", "dev": true, "requires": { "async": "^1.5.2", @@ -6523,21 +6553,52 @@ "dev": true }, "postcss": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.6.tgz", - "integrity": "sha512-Nq/rNjnHFcKgCDDZYO0lNsl6YWe6U7tTy+ESN+PnLxebL8uBtYX59HZqvrj7YLK5UCyll2hqDsJOo3ndzEW8Ug==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.11.tgz", + "integrity": "sha512-9AXb//5UcjeOEof9T+yPw3XTa5SL207ZOIC/lHYP4mbUTEh4M0rDAQekQpVANCZdwQwKhBtFZCk3i3h3h2hdWg==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "source-map": "^0.6.1", - "supports-color": "^5.5.0" + "supports-color": "^6.1.0" }, "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -6662,9 +6723,9 @@ } }, "postcss-merge-longhand": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.9.tgz", - "integrity": "sha512-UVMXrXF5K/kIwUbK/crPFCytpWbNX2Q3dZSc8+nQUgfOHrCT4+MHncpdxVphUlQeZxlLXUJbDyXc5NBhTnS2tA==", + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.10.tgz", + "integrity": "sha512-hME10s6CSjm9nlVIcO1ukR7Jr5RisTaaC1y83jWCivpuBtPohA3pZE7cGTIVSYjXvLnXozHTiVOkG4dnnl756g==", "dev": true, "requires": { "css-color-names": "0.0.4", @@ -7011,9 +7072,9 @@ } }, "postcss-selector-parser": { - "version": "5.0.0-rc.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0-rc.4.tgz", - "integrity": "sha512-0XvfYuShrKlTk1ooUrVzMCFQRcypsdEIsGqh5IxC5rdtBi4/M/tDAJeSONwC2MTqEFsmPZYAV7Dd4X8rgAfV0A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", "dev": true, "requires": { "cssesc": "^2.0.0", @@ -7378,7 +7439,7 @@ }, "regjsgen": { "version": "0.2.0", - "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", "dev": true }, @@ -7424,7 +7485,7 @@ }, "css-select": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { @@ -7446,7 +7507,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -7894,9 +7955,9 @@ } }, "serialize-javascript": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", - "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", + "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", "dev": true }, "serve-index": { @@ -7969,7 +8030,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -8296,9 +8357,9 @@ } }, "source-map-support": { - "version": "0.5.9", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", - "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -8520,7 +8581,7 @@ }, "stream-browserify": { "version": "2.0.1", - "resolved": "http://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { @@ -8701,9 +8762,9 @@ } }, "terser": { - "version": "3.10.12", - "resolved": "https://registry.npmjs.org/terser/-/terser-3.10.12.tgz", - "integrity": "sha512-3ODPC1eVt25EVNb04s/PkHxOmzKBQUF6bwwuR6h2DbEF8/j265Y1UkwNtOk9am/pRxfJ5HPapOlUlO6c16mKQQ==", + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.14.1.tgz", + "integrity": "sha512-NSo3E99QDbYSMeJaEk9YW2lTg3qS9V0aKGlb+PlOrei1X02r1wSBHCNX/O+yeTRFSWPKPIGj6MqvvdqV4rnVGw==", "dev": true, "requires": { "commander": "~2.17.1", @@ -8726,9 +8787,9 @@ } }, "terser-webpack-plugin": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.1.0.tgz", - "integrity": "sha512-61lV0DSxMAZ8AyZG7/A4a3UPlrbOBo8NIQ4tJzLPAdGOQ+yoNC7l5ijEow27lBAL2humer01KLS6bGIMYQxKoA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.1.tgz", + "integrity": "sha512-GGSt+gbT0oKcMDmPx4SRSfJPE1XaN3kQRWG4ghxKQw9cn5G9x6aCKSsgYdvyM0na9NJ4Drv0RG6jbBByZ5CMjw==", "dev": true, "requires": { "cacache": "^11.0.2", @@ -8742,24 +8803,24 @@ }, "dependencies": { "cacache": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.1.tgz", - "integrity": "sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", "dev": true, "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "figgy-pudding": "^3.1.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.3", + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", "rimraf": "^2.6.2", - "ssri": "^6.0.0", - "unique-filename": "^1.1.0", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", "y18n": "^4.0.0" } }, @@ -8793,6 +8854,15 @@ "path-exists": "^3.0.0" } }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -8812,9 +8882,9 @@ } }, "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", + "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", "dev": true, "requires": { "p-try": "^2.0.0" @@ -9128,9 +9198,9 @@ "dev": true }, "typescript": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.1.tgz", - "integrity": "sha512-jw7P2z/h6aPT4AENXDGjcfHTu5CSqzsbZc6YlUIebTyBAq8XaKp78x7VcSh30xwSCcsu5irZkYZUSFP1MrAMbg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", + "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", "dev": true }, "uglify-js": { @@ -9483,12 +9553,12 @@ "dev": true }, "vue-loader": { - "version": "15.4.2", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.4.2.tgz", - "integrity": "sha512-nVV27GNIA9MeoD8yQ3dkUzwlAaAsWeYSWZHsu/K04KCD339lW0Jv2sJWsjj3721SP7sl2lYdPmjcHgkWQSp5bg==", + "version": "15.5.1", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.5.1.tgz", + "integrity": "sha512-gsTA9xRzu9jGBzkcrAB8my14RkHMzdr5rY/mCFmxgY2tOVsd2Z1MaYCDXHu5nX6PyHAsVK2/hXmarPln/2MiIw==", "dev": true, "requires": { - "@vue/component-compiler-utils": "^2.0.0", + "@vue/component-compiler-utils": "^2.4.0", "hash-sum": "^1.0.2", "loader-utils": "^1.1.0", "vue-hot-reload-api": "^2.3.0", @@ -9535,9 +9605,9 @@ } }, "vue-template-es2015-compiler": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz", - "integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.8.1.tgz", + "integrity": "sha512-mxBBMuSaPG9+NkVMbh28r8gvWQJ8UXxqDxVNeLy2KBUZiSNxZsagjYwLL8gjROb4oaaYtwRv3K8gAmw76I/U7Q==", "dev": true }, "vuedraggable": { @@ -9589,9 +9659,9 @@ } }, "webpack": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.26.0.tgz", - "integrity": "sha512-J/dP9SJIc5OtX2FZ/+U9ikQtd6H6Mcbqt0xeXtmPwYGDKf8nkbOQQA9KL2Y0rJOsN1Al9Pdn+/j63X58ub8gvQ==", + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.28.4.tgz", + "integrity": "sha512-NxjD61WsK/a3JIdwWjtIpimmvE6UrRi3yG54/74Hk9rwNj5FPkA4DJCf1z4ByDWLkvZhTZE+P3C/eh6UD5lDcw==", "dev": true, "requires": { "@webassemblyjs/ast": "1.7.11", @@ -9750,7 +9820,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -9770,9 +9840,9 @@ } }, "webpack-merge": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.4.tgz", - "integrity": "sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.1.tgz", + "integrity": "sha512-4p8WQyS98bUJcCvFMbdGZyZmsKuWjWVnVHnAS3FFg0HDaRVrPbkivx2RYCre8UiemD67RsiFFLfn4JhLAin8Vw==", "dev": true, "requires": { "lodash": "^4.17.5" @@ -10055,7 +10125,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, diff --git a/client/package.json b/client/package.json index bf0fea2d2..e4c0c12a8 100644 --- a/client/package.json +++ b/client/package.json @@ -35,14 +35,14 @@ "@types/tinycolor2": "^1.4.1", "@types/vue-color": "^2.4.1", "@vue/cli-plugin-typescript": "^3.1.1", - "@vue/cli-service": "^3.1.4", + "@vue/cli-service": "^3.3.0", "node-sass": "^4.9.0", "prettier": "^1.15.2", "rimraf": "^2.6.2", "sass-loader": "^7.0.1", "tslint": "^5.11.0", "tslint-config-prettier": "^1.16.0", - "typescript": "^3.2.1", + "typescript": "^3.2.2", "vue-property-decorator": "^7.2.0", "vue-template-compiler": "^2.5.17", "vuex-module-decorators": "^0.9.1" From 7d6c5608331dad1a30d54b29222ef591d7ff68e7 Mon Sep 17 00:00:00 2001 From: Darragh Van Tichelen Date: Sun, 13 Jan 2019 20:23:45 +0100 Subject: [PATCH 18/18] Bump versions --- CHANGELOG.md | 2 ++ client/package.json | 2 +- server/VERSION | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99cd19bbf..9af3d4fb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ All notable changes to this project will be documented in this file. ## Unreleased +## [0.13.0] - 2019-01-13 + ### Added - A new vision system has been added based on triangulation. diff --git a/client/package.json b/client/package.json index e4c0c12a8..f8c9aab22 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "planarally-client", - "version": "0.11.6", + "version": "0.13.0", "description": "A companion tool for when you travel into the planes.", "scripts": { "serve": "vue-cli-service serve", diff --git a/server/VERSION b/server/VERSION index d33c3a212..51de3305b 100644 --- a/server/VERSION +++ b/server/VERSION @@ -1 +1 @@ -0.12.0 \ No newline at end of file +0.13.0 \ No newline at end of file