2019-09-28 21:05:25 +02:00
import { Heap } from 'ts-heap'
2019-09-29 21:56:04 +02:00
import { HalfEdge , Face , Vertex } from './dcel' ;
2019-09-28 21:05:25 +02:00
interface WithPriority {
get_priority ( ) : number ;
}
export class Point {
x : number ;
y : number ;
2019-09-29 21:56:04 +02:00
face? : Face ;
2019-09-28 21:05:25 +02:00
2019-09-29 21:56:04 +02:00
constructor ( x : number , y : number , face? :Face ) {
2019-09-28 21:05:25 +02:00
this . x = x ;
this . y = y ;
2019-09-29 21:56:04 +02:00
this . face = face ;
2019-09-28 21:05:25 +02:00
}
equals ( other : Point ) : boolean {
return Math . abs ( this . x - other . x ) + Math . abs ( this . y - other . y ) < 0.00001 ;
}
2019-09-29 21:56:04 +02:00
toString ( ) : string {
return ` {x: ${ this . x } , y: ${ this . y } } ` ;
}
2019-09-28 21:05:25 +02:00
}
class CircleEvent implements WithPriority {
y : number ;
alive : boolean = true ;
2019-09-29 21:56:04 +02:00
center : Vertex ;
2019-09-28 21:05:25 +02:00
leaf : Leaf ;
from : Point [ ] ;
static
from_sites ( s1 : Point , s2 : Point , s3 : Point , leaf : Leaf ) : CircleEvent {
const a = s1 . x * ( s2 . y - s3 . y ) - s1 . y * ( s2 . x - s3 . x ) + s2 . x * s3 . y - s3 . x * s2 . y ;
const b = ( s1 . x * * 2 + s1 . y * * 2 ) * ( s3 . y - s2 . y ) + ( s2 . x * * 2 + s2 . y * * 2 ) * ( s1 . y - s3 . y ) + ( s3 . x * * 2 + s3 . y * * 2 ) * ( s2 . y - s1 . y ) ;
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 ) ;
2019-09-29 21:56:04 +02:00
const center = new Vertex ( - b / ( 2 . * a ) , - c / ( 2 . * a ) ) ;
2019-09-28 21:05:25 +02:00
const r = Math . sqrt ( ( b * * 2 + c * * 2 - 4 . * a * d ) / ( 4 . * a * * 2 ) ) ;
2019-09-29 21:56:04 +02:00
const y = center . coords [ 1 ] - r ;
2019-09-28 21:05:25 +02:00
const out = new CircleEvent ( ) ;
out . y = y ;
out . center = center ;
out . leaf = leaf ;
out . from = [ s1 , s2 , s3 ] ;
return out ;
}
get_priority ( ) : number {
return this . y ;
}
print() {
2019-09-29 21:56:04 +02:00
console . log ( ` Circle event at ${ this . y } ${ JSON . stringify ( this . center ) } , ${ this . leaf . point . toString ( ) } from ${ JSON . stringify ( this . from . map ( ( e ) = > e . toString ( ) ) ) } ` ) ;
2019-09-28 21:05:25 +02:00
}
}
class SiteEvent implements WithPriority {
2019-09-29 21:56:04 +02:00
face : Face ;
2019-09-28 21:05:25 +02:00
point : Point ;
constructor ( point : Point ) {
2019-09-29 21:56:04 +02:00
this . face = new Face ( point ) ;
2019-09-28 21:05:25 +02:00
this . point = point ;
2019-09-29 21:56:04 +02:00
this . point . face = this . face ;
2019-09-28 21:05:25 +02:00
}
get_priority ( ) : number {
return this . point . y ;
}
print() {
2019-09-29 21:56:04 +02:00
console . log ( ` Site event ${ this . point . toString ( ) } ` ) ;
2019-09-28 21:05:25 +02:00
}
}
function calc_x ( left : Point , right : Point , y : number ) : number {
const [ a1 , b1 , c1 ] = from_focus_vertex ( left , y ) ;
const [ a2 , b2 , c2 ] = from_focus_vertex ( right , y ) ;
2019-09-29 21:56:04 +02:00
if ( Math . abs ( a1 - a2 ) < 0.0001 ) {
return ( left . x + right . x ) / 2 . ;
}
2019-09-28 21:05:25 +02:00
const da = a1 - a2 ;
const db = b1 - b2 ;
const dc = c1 - c2 ;
const d = db * db - 4 . * da * dc ;
2019-09-29 21:56:04 +02:00
if ( d <= 0 . ) {
2019-09-28 21:05:25 +02:00
throw new Error ( ` D is less then 0 ${ d } ` ) ;
}
const dd = Math . sqrt ( d ) ;
const x = ( - db + dd ) / ( 2 . * da ) ;
return x ;
}
function from_focus_vertex ( focus : Point , y : number ) : number [ ] {
const a = ( focus . y - y ) / 2 ;
const h = focus . x ;
const k = focus . y - a ;
return [ 1 / ( 4 . * a ) , - h / ( 2 * a ) , ( h * * 2 / ( 4 * a ) ) + k ]
}
function cmp_event ( e1 : WithPriority , e2 : WithPriority ) {
return e2 . get_priority ( ) - e1 . get_priority ( ) ;
}
type Queue = Heap < WithPriority > ;
type Node = Leaf | Breakpoint ;
type Parent = Breakpoint | State ;
class Leaf {
point : Point ;
event : CircleEvent | undefined ;
left : Leaf | undefined ;
right : Leaf | undefined ;
parent : Parent ;
2019-09-29 21:56:04 +02:00
half_edge : HalfEdge ;
2019-09-28 21:05:25 +02:00
constructor ( point : Point , parent : Parent ) {
this . point = point ;
this . parent = parent ;
2019-09-29 21:56:04 +02:00
this . half_edge = new HalfEdge ( undefined , undefined ) ;
2019-09-28 21:05:25 +02:00
}
false_alarm() {
if ( this . event ) {
console . log ( ` False alarm ${ JSON . stringify ( this . event . center ) } ${ this . event . y } ` ) ;
this . event . alive = false ;
}
}
update_left ( leaf : Leaf ) {
if ( this . left ) {
this . left . right = leaf ;
}
leaf . left = this . left ;
}
update_right ( leaf : Leaf ) {
if ( this . right ) {
this . right . left = leaf ;
}
leaf . right = this . right ;
}
split ( point : Point , events : Queue ) {
this . false_alarm ( ) ;
2019-09-29 21:56:04 +02:00
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 ;
}
2019-09-28 21:05:25 +02:00
const left = new Leaf ( this . point , undefined ) ;
left . left = this . left ;
if ( this . left ) this . left . right = left ;
const right = new Leaf ( this . point , undefined ) ;
right . right = this . right ;
if ( this . right ) this . right . left = right ;
const middle = new Leaf ( point , undefined ) ;
middle . left = left ;
middle . right = right ;
right . left = middle ;
left . right = middle ;
const br1 = new Breakpoint ( [ this . point , left ] , [ point , middle ] , undefined ) ;
const br2 = new Breakpoint ( [ point , br1 ] , [ this . point , right ] , this . parent ) ;
br1 . parent = br2 ;
if ( this . parent instanceof Breakpoint ) {
this . parent . set_me ( this , br2 ) ;
} else {
this . parent . root = br2 ;
}
const maybe_left = left . check_circles ( point . y , events ) ;
2019-09-29 21:56:04 +02:00
if ( maybe_left && maybe_left . center . coords [ 0 ] < middle . point . x ) {
console . log ( ` Adding circle ` ) ;
2019-09-28 21:05:25 +02:00
maybe_left . print ( ) ;
left . event = maybe_left ;
events . add ( maybe_left ) ;
}
const maybe_right = right . check_circles ( point . y , events ) ;
2019-09-29 21:56:04 +02:00
if ( maybe_right && maybe_right . center . coords [ 0 ] >= middle . point . x ) {
console . log ( ` Adding circle ` ) ;
2019-09-28 21:05:25 +02:00
maybe_right . print ( ) ;
right . event = maybe_right ;
events . add ( maybe_right ) ;
}
}
check_circles ( y : number , events : Queue ) : CircleEvent | undefined {
const left = this . left ;
const right = this . right ;
if ( left && right ) {
const circle = CircleEvent . from_sites ( left . point , this . point , right . point , this ) ;
console . log ( ` ${ circle . y } < ${ y } ` ) ;
if ( circle . y < y ) {
return circle ;
}
}
}
2019-09-29 21:56:04 +02:00
delete ( vertex : Vertex ) {
2019-09-28 21:05:25 +02:00
if ( this . parent instanceof Breakpoint ) {
2019-09-29 21:56:04 +02:00
this . parent . remove_me ( this . point , vertex ) ;
2019-09-28 21:05:25 +02:00
} else {
console . error ( "Shouldnt be here" ) ;
}
}
print ( indent : string ) {
2019-09-29 21:56:04 +02:00
console . log ( ` ${ indent } Leaf from ${ this . point . toString ( ) } vertex: ${ this . half_edge . to_string ( ) } ` ) ;
2019-09-28 21:05:25 +02:00
}
}
class Breakpoint {
left : [ Point , Node ] ;
right : [ Point , Node ] ;
parent : Parent ;
2019-09-29 21:56:04 +02:00
half_edge : HalfEdge ;
2019-09-28 21:05:25 +02:00
constructor ( left : [ Point , Node ] , right : [ Point , Node ] , parent : Parent ) {
this . left = left ;
this . right = right ;
this . left [ 1 ] . parent = this ;
this . right [ 1 ] . parent = this ;
this . parent = parent ;
2019-09-29 21:56:04 +02:00
this . half_edge = new HalfEdge ( undefined , left [ 0 ] . face , right [ 0 ] . face ) ;
2019-09-28 21:05:25 +02:00
}
2019-09-29 21:56:04 +02:00
remove_me ( point : Point , vertex : Vertex ) {
const edge = this . half_edge . insert ( vertex ) ;
2019-09-28 21:05:25 +02:00
const other = this . get_other ( point ) ;
if ( this . parent instanceof Breakpoint ) {
this . parent . set_me ( this , other ) ;
2019-09-29 21:56:04 +02:00
this . parent . set_edge ( edge ) ;
2019-09-28 21:05:25 +02:00
} else {
this . parent . root = other ;
}
2019-09-29 21:56:04 +02:00
}
set_edge ( edge : HalfEdge ) {
this . left [ 1 ] . half_edge = edge ;
this . right [ 1 ] . half_edge = edge . split ( edge . origin ) ;
2019-09-28 21:05:25 +02:00
}
set_me ( old_me : Node , new_me : Node ) {
if ( this . left [ 1 ] == old_me ) {
this . left [ 1 ] = new_me ;
} else {
this . right [ 1 ] = new_me ;
}
}
get ( point : Point ) : Leaf {
const { x , y } = point ;
const test_x = calc_x ( this . left [ 0 ] , this . right [ 0 ] , y ) ;
if ( test_x >= x ) {
if ( this . left [ 1 ] instanceof Leaf ) {
return this . left [ 1 ] ;
} else {
return this . left [ 1 ] . get ( point ) ;
}
} else {
if ( this . right [ 1 ] instanceof Leaf ) {
return this . right [ 1 ] ;
} else {
return this . right [ 1 ] . get ( point ) ;
}
}
}
get_other ( point : Point ) : Node {
if ( this . left [ 0 ] . equals ( point ) ) {
return this . right [ 1 ] ;
} else {
return this . left [ 1 ] ;
}
}
print ( indent : string ) {
2019-09-29 21:56:04 +02:00
console . log ( ` ${ indent } vertex: ${ this . half_edge . to_string ( ) } ` ) ;
2019-09-28 21:05:25 +02:00
console . log ( ` ${ indent } left ` ) ;
this . left [ 1 ] . print ( indent + ' ' ) ;
console . log ( ` ${ indent } right ` ) ;
this . right [ 1 ] . print ( indent + ' ' ) ;
}
}
function get_from_node ( root : Node , point : Point ) : Leaf {
if ( root instanceof Leaf ) {
return root ;
} else {
return root . get ( point ) ;
}
}
class State {
root : Node | undefined ;
print() {
if ( this . root ) {
this . root . print ( '' ) ;
} else {
console . log ( "No root no tree" ) ;
}
}
}
export function voronoi ( points : Point [ ] ) : Point [ ] {
const out = [ ] ;
const state = new State ;
const queue = new Heap < WithPriority > ( cmp_event ) ;
2019-09-29 21:56:04 +02:00
2019-09-28 21:05:25 +02:00
for ( let point of points ) {
queue . add ( new SiteEvent ( point ) ) ;
}
let event ;
while ( event = queue . pop ( ) ) {
console . log ( '---------------------------' ) ;
event . print ( ) ;
if ( event instanceof SiteEvent ) {
handle_site_event ( event , queue , state , out ) ;
} else {
if ( ! event . alive ) {
console . log ( "Dead" ) ;
continue ;
}
handle_circle_event ( event , queue , state , out ) ;
}
state . print ( ) ;
console . log ( queue ) ;
print_leaves ( get_from_node ( state . root , new Point ( 0 , 0 ) ) ) ;
}
return out ;
}
function handle_site_event ( event : SiteEvent , queue : Queue , state : State , out : Point [ ] ) {
if ( state . root ) {
const leaf = get_from_node ( state . root , event . point ) ;
leaf . split ( event . point , queue ) ;
} else {
state . root = new Leaf ( event . point , state ) ;
}
}
2019-09-29 21:56:04 +02:00
function handle_circle_event ( event : CircleEvent , queue : Queue , state : State , out : [ number , number ] [ ] ) {
2019-09-28 21:05:25 +02:00
if ( ! event . alive ) return ;
2019-09-29 21:56:04 +02:00
event . leaf . delete ( event . center ) ;
2019-09-28 21:05:25 +02:00
const right = event . leaf . right ;
const left = event . leaf . left ;
if ( right ) {
right . false_alarm ( ) ;
2019-09-29 21:56:04 +02:00
// if (right.right) right.right.false_alarm();
2019-09-28 21:05:25 +02:00
right . left = left ;
const maybe_right = right . check_circles ( event . y , queue ) ;
if ( maybe_right ) {
console . log ( ` Adding circle event ` ) ;
maybe_right . print ( ) ;
right . event = maybe_right ;
queue . add ( maybe_right ) ;
}
}
if ( left ) {
left . false_alarm ( ) ;
2019-09-29 21:56:04 +02:00
// if (left.left) left.left.false_alarm();
2019-09-28 21:05:25 +02:00
left . right = right ;
const maybe_left = left . check_circles ( event . y , queue ) ;
if ( maybe_left ) {
console . log ( ` Adding circle event ` ) ;
maybe_left . print ( ) ;
left . event = maybe_left ;
queue . add ( maybe_left ) ;
}
}
2019-09-29 21:56:04 +02:00
out . push ( event . center . coords ) ;
2019-09-28 21:05:25 +02:00
}
function print_leaves ( start : Leaf ) {
let current = start ;
while ( current . left ) {
current = current . left ;
}
const points = [ current . point ] ;
while ( current . right ) {
current = current . right ;
points . push ( current . point ) ;
}
2019-09-29 21:56:04 +02:00
console . log ( JSON . stringify ( points . map ( ( p ) = > p . toString ( ) ) ) ) ;
2019-09-28 21:05:25 +02:00
}