dcels are hard

This commit is contained in:
ajuvercr 2019-09-29 21:56:04 +02:00
parent e281a1683d
commit b63b3099f3
3 changed files with 277 additions and 39 deletions

149
frontend/www/dcel.ts Normal file
View file

@ -0,0 +1,149 @@
import { create } from "domain";
export class Vertex {
coords: [number, number];
incident_edge: HalfEdge;
constructor(x: number, y: number) {
this.coords = [x, y];
}
get_all(): HalfEdge[] {
const out = [];
let current = this.incident_edge;
do {
out.push(current);
current = current.twin.next;
} while (current != this.incident_edge);
return out;
}
}
export class Face {
attributes: any;
outer_component: HalfEdge;
inner_components: HalfEdge[];
constructor(attributes?: any) {
this.attributes = attributes;
}
loop(): [number, number][] {
const out = [];
let iter = 0;
let current = this.outer_component;
do {
if (iter > 100) {
throw new Error("Fuck off");
}
iter += 1;
console.log(current.id, current.face.attributes, current.origin.coords);
out.push(current.origin.coords);
current = current.next;
} while (current != this.outer_component);
return out;
}
}
var id = 0;
function next_id(): number {
id += 1;
return id;
}
export class HalfEdge {
origin: Vertex;
// destination = twin.origin
next: HalfEdge;
prev: HalfEdge;
twin: HalfEdge;
face: Face;
id: number;
constructor(origin: Vertex, f1: Face, f2?: Face) {
this.id = next_id();
this.origin = origin;
this.next = this;
this.prev = this;
if (f2) {
this.twin = new HalfEdge(origin, f2);
} else {
this.twin = this;
}
this.face = f1;
}
insert(at: Vertex, update_twin = true): HalfEdge {
const new_edge = new HalfEdge(at, this.face);
new_edge.next = this.next;
new_edge.prev = this;
new_edge.twin = this.twin;
this.next.prev = new_edge;
this.next = new_edge;
if (update_twin) {
this.twin = this.twin.insert(at, false);
}
return new_edge;
}
split(to: Vertex) {
const e_to = new HalfEdge(this.origin, this.face);
const e_from = new HalfEdge(to, this.face);
e_to.twin = e_from;
e_from.twin = e_to;
e_to.prev = this.prev;
e_to.next = e_from;
e_from.next = this;
e_from.prev = e_to;
this.prev.next = e_to;
this.prev = e_from;
}
add_face() {
}
to_string(): string {
return `Halfedge from ${this.origin ? this.origin.coords : undefined} face1 ${this.face ? this.face.attributes : undefined}`;
}
}
export function test() {
const f1 = new Face("Face 1");
const f2 = new Face("Face 2");
const v1 = new Vertex(0, 0);
const v2 = new Vertex(1, 1);
const v3 = new Vertex(-1, 0);
const v4 = new Vertex(2, 0);
const e1 = new HalfEdge(v1, f1, f2);
f1.outer_component = e1;
const e2 = e1.insert(v2);
const e3 = e2.split(v3);
const e4 = e2.insert(v4);
e1.twin.next = e4.twin;
f2.outer_component = e4.twin;
// const e3 = e1.insert(v3);
console.log(f1.loop());
console.log(f2.loop());
}

View file

@ -367,13 +367,17 @@ set_loading(true);
import { voronoi, Point } from './voronoi' import { voronoi, Point } from './voronoi'
function test() { function test() {
const points = [ const points = [
new Point(5,5), new Point(14, 6),
new Point(1, 8), new Point(13, 11),
new Point(8,8), new Point(8, 7.5),
new Point(10,2), new Point(7, 4),
new Point(4, 11),
]; ];
console.log(voronoi(points)); console.log(voronoi(points));
} }
import { test as dcelt_test } from './dcel';
// dcelt_test();
test(); test();

View file

@ -1,5 +1,5 @@
import { Heap } from 'ts-heap' import { Heap } from 'ts-heap'
import { handle } from './games'; import { HalfEdge, Face, Vertex } from './dcel';
interface WithPriority { interface WithPriority {
get_priority(): number; get_priority(): number;
@ -8,21 +8,27 @@ interface WithPriority {
export class Point { export class Point {
x: number; x: number;
y: number; y: number;
face?: Face;
constructor(x: number, y: number) { constructor(x: number, y: number, face? :Face) {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.face = face;
} }
equals(other: Point): boolean { equals(other: Point): boolean {
return Math.abs(this.x - other.x) + Math.abs(this.y - other.y) < 0.00001; return Math.abs(this.x - other.x) + Math.abs(this.y - other.y) < 0.00001;
} }
toString(): string {
return `{x: ${this.x}, y: ${this.y}}`;
}
} }
class CircleEvent implements WithPriority { class CircleEvent implements WithPriority {
y: number; y: number;
alive: boolean = true; alive: boolean = true;
center: Point; center: Vertex;
leaf: Leaf; leaf: Leaf;
from: Point[]; from: Point[];
@ -34,9 +40,9 @@ class CircleEvent implements WithPriority {
const c = (s1.x ** 2 + s1.y ** 2) * (s2.x - s3.x) + (s2.x ** 2 + s2.y ** 2)*(s3.x - s1.x) + (s3.x ** 2 + s3.y ** 2) * (s1.x - s2.x); const c = (s1.x ** 2 + s1.y ** 2) * (s2.x - s3.x) + (s2.x ** 2 + s2.y ** 2)*(s3.x - s1.x) + (s3.x ** 2 + s3.y ** 2) * (s1.x - s2.x);
const d = (s1.x ** 2 + s1.y ** 2) * (s3.x*s2.y - s2.x*s3.y) + (s2.x ** 2 + s2.y ** 2)*(s1.x*s3.y - s3.x*s1.y) + (s3.x ** 2 + s3.y ** 2) * (s2.x*s1.y - s1.x*s2.y); const d = (s1.x ** 2 + s1.y ** 2) * (s3.x*s2.y - s2.x*s3.y) + (s2.x ** 2 + s2.y ** 2)*(s1.x*s3.y - s3.x*s1.y) + (s3.x ** 2 + s3.y ** 2) * (s2.x*s1.y - s1.x*s2.y);
const center = new Point(-b / (2. * a), -c / (2. * a)); const center = new Vertex(-b / (2. * a), -c / (2. * a));
const r = Math.sqrt((b ** 2 + c ** 2 - 4. * a * d) / (4. * a ** 2)); const r = Math.sqrt((b ** 2 + c ** 2 - 4. * a * d) / (4. * a ** 2));
const y = center.y - r; const y = center.coords[1] - r;
const out = new CircleEvent(); const out = new CircleEvent();
out.y = y; out.y = y;
@ -53,15 +59,18 @@ class CircleEvent implements WithPriority {
} }
print() { print() {
console.log(`Circle event at ${this.y} ${JSON.stringify(this.center)}, ${JSON.stringify(this.leaf.point)} from ${JSON.stringify(this.from)}`); console.log(`Circle event at ${this.y} ${JSON.stringify(this.center)}, ${this.leaf.point.toString()} from ${JSON.stringify(this.from.map((e) => e.toString()))}`);
} }
} }
class SiteEvent implements WithPriority{ class SiteEvent implements WithPriority{
face: Face;
point: Point; point: Point;
constructor(point: Point) { constructor(point: Point) {
this.face = new Face(point);
this.point = point; this.point = point;
this.point.face = this.face;
} }
get_priority(): number { get_priority(): number {
@ -69,7 +78,7 @@ class SiteEvent implements WithPriority{
} }
print() { print() {
console.log(`Site event ${JSON.stringify(this.point)}`); console.log(`Site event ${this.point.toString()}`);
} }
} }
@ -77,13 +86,17 @@ function calc_x(left: Point, right: Point, y: number): number {
const [a1, b1, c1] = from_focus_vertex(left, y); const [a1, b1, c1] = from_focus_vertex(left, y);
const [a2, b2, c2] = from_focus_vertex(right, y); const [a2, b2, c2] = from_focus_vertex(right, y);
if (Math.abs(a1 - a2) < 0.0001) {
return (left.x + right.x) / 2.;
}
const da = a1 - a2; const da = a1 - a2;
const db = b1 - b2; const db = b1 - b2;
const dc = c1 - c2; const dc = c1 - c2;
const d = db * db - 4. * da * dc; const d = db * db - 4. * da * dc;
if (d < 0.) { if (d <= 0.) {
throw new Error(`D is less then 0 ${d}`); throw new Error(`D is less then 0 ${d}`);
} }
@ -120,9 +133,12 @@ class Leaf {
right: Leaf | undefined; right: Leaf | undefined;
parent: Parent; parent: Parent;
half_edge: HalfEdge;
constructor(point: Point, parent: Parent) { constructor(point: Point, parent: Parent) {
this.point = point; this.point = point;
this.parent = parent; this.parent = parent;
this.half_edge = new HalfEdge(undefined, undefined);
} }
false_alarm() { false_alarm() {
@ -149,6 +165,74 @@ class Leaf {
split(point: Point, events: Queue) { split(point: Point, events: Queue) {
this.false_alarm(); this.false_alarm();
if (this.point.y == point.y) {
const middle = new Leaf(point, undefined);
const parent = this.parent;
if (this.point.x > point.x) {
const br = new Breakpoint([point, middle], [this.point, this], this.parent);
if (this.left) this.left.right = middle;
this.left = middle;
middle.right = this;
if (parent instanceof Breakpoint) {
parent.set_me(this, br);
} else {
parent.root = br;
}
const maybe_left = middle.check_circles(point.y, events);
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
console.log(`Adding circle`);
maybe_left.print();
middle.event = maybe_left;
events.add(maybe_left);
}
const maybe_right = this.check_circles(point.y, events);
if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
console.log(`Adding circle`);
maybe_right.print();
this.event = maybe_right;
events.add(maybe_right);
}
} else {
const br = new Breakpoint([this.point, this], [point, middle], this.parent);
if (this.right) this.right.left = middle;
this.right = middle;
middle.left = this;
if (parent instanceof Breakpoint) {
parent.set_me(this, br);
} else {
parent.root = br;
}
const maybe_left = this.check_circles(point.y, events);
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
console.log(`Adding circle`);
maybe_left.print();
this.event = maybe_left;
events.add(maybe_left);
}
const maybe_right = middle.check_circles(point.y, events);
if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
console.log(`Adding circle`);
maybe_right.print();
middle.event = maybe_right;
events.add(maybe_right);
}
}
return;
}
const left = new Leaf(this.point, undefined); const left = new Leaf(this.point, undefined);
left.left = this.left; left.left = this.left;
if (this.left) this.left.right = left; if (this.left) this.left.right = left;
@ -175,17 +259,16 @@ class Leaf {
} }
const maybe_left = left.check_circles(point.y, events); const maybe_left = left.check_circles(point.y, events);
if (maybe_left && maybe_left.center.coords[0] < middle.point.x) {
if (maybe_left && maybe_left.center.x < middle.point.x) { console.log(`Adding circle`);
console.log(`Adding circle event left ${JSON.stringify(maybe_left.leaf.point)}`);
maybe_left.print(); maybe_left.print();
left.event = maybe_left; left.event = maybe_left;
events.add(maybe_left); events.add(maybe_left);
} }
const maybe_right = right.check_circles(point.y, events); const maybe_right = right.check_circles(point.y, events);
if (maybe_right && maybe_right.center.x >= middle.point.x) { if (maybe_right && maybe_right.center.coords[0] >= middle.point.x) {
console.log(`Adding circle event right`); console.log(`Adding circle`);
maybe_right.print(); maybe_right.print();
right.event = maybe_right; right.event = maybe_right;
events.add(maybe_right); events.add(maybe_right);
@ -205,16 +288,16 @@ class Leaf {
} }
} }
delete() { delete(vertex: Vertex) {
if (this.parent instanceof Breakpoint) { if (this.parent instanceof Breakpoint) {
this.parent.remove_me(this.point); this.parent.remove_me(this.point, vertex);
} else { } else {
console.error("Shouldnt be here"); console.error("Shouldnt be here");
} }
} }
print(indent: string) { print(indent: string) {
console.log(`${indent}Leaf from ${JSON.stringify(this.point)}`); console.log(`${indent}Leaf from ${this.point.toString()} vertex: ${this.half_edge.to_string()}`);
} }
} }
@ -222,6 +305,7 @@ class Breakpoint {
left: [Point, Node]; left: [Point, Node];
right: [Point, Node]; right: [Point, Node];
parent: Parent; parent: Parent;
half_edge: HalfEdge;
constructor(left: [Point, Node], right: [Point, Node], parent: Parent) { constructor(left: [Point, Node], right: [Point, Node], parent: Parent) {
this.left = left; this.left = left;
@ -229,26 +313,31 @@ class Breakpoint {
this.left[1].parent = this; this.left[1].parent = this;
this.right[1].parent = this; this.right[1].parent = this;
this.parent = parent; this.parent = parent;
this.half_edge = new HalfEdge(undefined, left[0].face, right[0].face);
} }
remove_me(point: Point) { remove_me(point: Point, vertex: Vertex) {
const edge = this.half_edge.insert(vertex);
const other = this.get_other(point); const other = this.get_other(point);
if (this.parent instanceof Breakpoint) { if (this.parent instanceof Breakpoint) {
console.log("Parent is still breakpoint");
this.parent.set_me(this, other); this.parent.set_me(this, other);
this.parent.set_edge(edge);
} else { } else {
console.log("Parent is root");
this.parent.root = other; this.parent.root = other;
} }
}
set_edge(edge: HalfEdge) {
this.left[1].half_edge = edge;
this.right[1].half_edge = edge.split(edge.origin);
} }
set_me(old_me: Node, new_me: Node) { set_me(old_me: Node, new_me: Node) {
if (this.left[1] == old_me) { if (this.left[1] == old_me) {
console.log("Setting other left");
this.left[1] = new_me; this.left[1] = new_me;
} else { } else {
console.log("Setting other right");
this.right[1] = new_me; this.right[1] = new_me;
} }
} }
@ -257,17 +346,13 @@ class Breakpoint {
const { x, y } = point; const { x, y } = point;
const test_x = calc_x(this.left[0], this.right[0], y); const test_x = calc_x(this.left[0], this.right[0], y);
console.log(`${test_x} >= ${x}`);
if (test_x >= x) { if (test_x >= x) {
console.log("Going left");
if (this.left[1] instanceof Leaf) { if (this.left[1] instanceof Leaf) {
return this.left[1]; return this.left[1];
} else { } else {
return this.left[1].get(point); return this.left[1].get(point);
} }
} else { } else {
console.log("Going right");
if (this.right[1] instanceof Leaf) { if (this.right[1] instanceof Leaf) {
return this.right[1]; return this.right[1];
} else { } else {
@ -278,15 +363,14 @@ class Breakpoint {
get_other(point: Point): Node { get_other(point: Point): Node {
if (this.left[0].equals(point)) { if (this.left[0].equals(point)) {
console.log("Other is right");
return this.right[1]; return this.right[1];
} else { } else {
console.log("Other is left");
return this.left[1]; return this.left[1];
} }
} }
print(indent: string) { print(indent: string) {
console.log(`${indent}vertex: ${this.half_edge.to_string()}`);
console.log(`${indent}left`); console.log(`${indent}left`);
this.left[1].print(indent + ' '); this.left[1].print(indent + ' ');
console.log(`${indent}right`); console.log(`${indent}right`);
@ -318,6 +402,8 @@ export function voronoi(points: Point[]): Point[] {
const out = []; const out = [];
const state = new State; const state = new State;
const queue = new Heap<WithPriority>(cmp_event); const queue = new Heap<WithPriority>(cmp_event);
for (let point of points) { for (let point of points) {
queue.add(new SiteEvent(point)); queue.add(new SiteEvent(point));
} }
@ -347,7 +433,6 @@ export function voronoi(points: Point[]): Point[] {
function handle_site_event(event: SiteEvent, queue: Queue, state: State, out: Point[]) { function handle_site_event(event: SiteEvent, queue: Queue, state: State, out: Point[]) {
if (state.root) { if (state.root) {
const leaf = get_from_node(state.root, event.point); const leaf = get_from_node(state.root, event.point);
console.log(`Splitting leaf from ${JSON.stringify(leaf.point)}`);
leaf.split(event.point, queue); leaf.split(event.point, queue);
} else { } else {
@ -355,16 +440,16 @@ function handle_site_event(event: SiteEvent, queue: Queue, state: State, out: Po
} }
} }
function handle_circle_event(event: CircleEvent, queue: Queue, state: State, out: Point[]) { function handle_circle_event(event: CircleEvent, queue: Queue, state: State, out: [number, number][]) {
if (!event.alive) return; if (!event.alive) return;
event.leaf.delete(); event.leaf.delete(event.center);
const right = event.leaf.right; const right = event.leaf.right;
const left = event.leaf.left; const left = event.leaf.left;
if (right) { if (right) {
right.false_alarm(); right.false_alarm();
if (right.right) right.right.false_alarm(); // if (right.right) right.right.false_alarm();
right.left = left; right.left = left;
const maybe_right = right.check_circles(event.y, queue); const maybe_right = right.check_circles(event.y, queue);
@ -378,7 +463,7 @@ function handle_circle_event(event: CircleEvent, queue: Queue, state: State, out
if (left) { if (left) {
left.false_alarm(); left.false_alarm();
if (left.left) left.left.false_alarm(); // if (left.left) left.left.false_alarm();
left.right = right; left.right = right;
const maybe_left = left.check_circles(event.y, queue); const maybe_left = left.check_circles(event.y, queue);
@ -390,7 +475,7 @@ function handle_circle_event(event: CircleEvent, queue: Queue, state: State, out
} }
} }
out.push(event.center); out.push(event.center.coords);
} }
function print_leaves(start: Leaf) { function print_leaves(start: Leaf) {
@ -407,5 +492,5 @@ function print_leaves(start: Leaf) {
points.push(current.point); points.push(current.point);
} }
console.log(JSON.stringify(points)); console.log(JSON.stringify(points.map((p) => p.toString())));
} }