// ==UserScript==
// @name Planets.nu - Territory Map Plugin
// @description Plugin for Planets.nu which allows to overlay various territory maps over the starmap
// @namespace kedalion/territoryMap
// @version 0.31
// @date 2014-12-24
// @author kedalion
// @include http://planets.nu/*
// @include http://play.planets.nu/*
// @include http://test.planets.nu/*
// @resource userscript https://greasyfork.org/en/scripts/7151-planets-nu-territory-map-plugin
// @homepage http://planets.nu/discussion/utility-script-territory-map-plugin
// ==/UserScript==
/* -----------------------------------------------------------------------
Change log:
0.31 Moved script location to greasyfork.org and added support for the sphere addon [2014-12-24]
0.3 Moved script location to monkeyguts.com [2014-05-30]
0.25 Added hotkey 'x' support [2013-07-25]
0.24 Added support for games with 'explore map'[2013-07-25]
0.23 Fix to ensure correct button behavior [2013-07-25]
0.22 Small fix to prevent data leak between games
0.2 New release to work with client version 3 and plugin interface. Drawing on low zoom level missing. Added alliance view. [2013-07-22]
0.1 Initial release for old client version < 3 [2012-05-29]
----------------------------------------------------------------------- */
function wrapper () { // wrapper for injection
if (vgap.version < 3.0) {
console.log("TerritoryMap plugin requires at least NU version 3.0. Plugin disabled." );
return;
}
var plugin_version = 0.31;
console.log("TerritoryMap plugin version: v" + plugin_version );
/*
* Specify your plugin
* You need to have all those methods defined or errors will be thrown.
* I inserted the print-outs in order to demonstrate when each method is
* being called. Just comment them out once you know your way around.
*
* For any access to plugin class variables and methods from inside these
* reserved methods, "vgap.plugins["nameOfMyPlugin"].my_variable" has to be
* used instead of "this.my_variable".
*/
var territoryMapPlugin = {
/*
* processload: executed whenever a turn is loaded: either the current turn or
* an older turn through time machine
*/
processload: function() {
//console.log("ProcessLoad: plugin called.");
//check if plugin data is for current game
if (vgap.plugins["territoryMapPlugin"].gameid != vgap.gameId ) {
vgap.plugins["territoryMapPlugin"].reset();
}
if (vgap.plugins["territoryMapPlugin"].sites.length != vgap.planets.length) {
vgap.plugins["territoryMapPlugin"].clearDiagram();
}
},
/*
* loaddashboard: executed to rebuild the dashboard content after a turn is loaded
*/
loaddashboard: function() {
//console.log("LoadDashboard: plugin called.");
},
/*
* showdashboard: executed when switching from starmap to dashboard
*/
showdashboard: function() {
//console.log("ShowDashboard: plugin called.");
},
/*
* showsummary: executed when returning to the main screen of the dashboard
*/
showsummary: function() {
//console.log("ShowSummary: plugin called.");
},
/*
* loadmap: executed after the first turn has been loaded to create the map
* as far as I can tell not executed again when using time machine
*/
loadmap: function() {
//console.log("LoadMap: plugin called.");
vgap.plugins["territoryMapPlugin"].reset();
vgap.plugins["territoryMapPlugin"].addMapTool("Territory Map", "ShowMinerals", "territorymap", function () {
if (vgap.plugins["territoryMapPlugin"].enabled) {
if (vgap.plugins["territoryMapPlugin"].mode == 0) {
vgap.plugins["territoryMapPlugin"].enabled = false;
} else {
vgap.plugins["territoryMapPlugin"].mode = 0;
}
} else {
vgap.plugins["territoryMapPlugin"].enabled = true;
vgap.plugins["territoryMapPlugin"].mode = 0;
}
vgap.plugins["territoryMapPlugin"].syncButtons();
vgap.map.draw();
});
vgap.plugins["territoryMapPlugin"].addMapTool("Alliance Map", "ShowMinerals", "alliancemap", function () {
if (vgap.plugins["territoryMapPlugin"].enabled) {
if (vgap.plugins["territoryMapPlugin"].mode == 1) {
vgap.plugins["territoryMapPlugin"].enabled = false;
} else {
vgap.plugins["territoryMapPlugin"].mode = 1;
}
} else {
vgap.plugins["territoryMapPlugin"].enabled = true;
vgap.plugins["territoryMapPlugin"].mode = 1;
}
vgap.plugins["territoryMapPlugin"].syncButtons();
vgap.map.draw();
});
},
/*
* showmap: executed when switching from dashboard to starmap
*/
showmap: function() {
//console.log("ShowMap: plugin called.");
},
/*
* draw: executed on any click or drag on the starmap
*/
draw: function() {
//console.log("Draw: plugin called.");
if (vgap.plugins["territoryMapPlugin"].enabled) {
vgap.plugins["territoryMapPlugin"].showTerritory();
}
},
/*
* loadplanet: executed a planet is selected on dashboard or starmap
*/
loadplanet: function() {
//console.log("LoadPlanet: plugin called.");
},
/*
* loadstarbase: executed a planet is selected on dashboard or starmap
*/
loadstarbase: function() {
//console.log("LoadStarbase: plugin called.");
},
/*
* loadship: executed a planet is selected on dashboard or starmap
*/
loadship: function() {
//console.log("LoadShip: plugin called.");
},
version: plugin_version,
gameid: -1,
enabled: false,
mode: 0, //0: each player separate, 1: all planets of alliance partners same color, enemies each alone
voronoi: null,
bbox: [],//{xl: 1800,xr: 2200,yt: 1800,yb: 2200};
diagram: null,
sites: [],
oldClearTools: null,
reset: function () {
//reset plugin settings if different game is loaded
this.gameid = vgap.gameId;
this.enabled = false;
this.mode = 0;
this.voronoi = null;
this.bbox = [];
this.diagram = null;
this.sites = [];
this.syncButtons();
},
clearDiagram: function () {
this.voronoi = null;
this.bbox = [];
this.diagram = null;
this.sites = [];
},
syncButtons: function () {
//make sure the buttons represent the actual plugin state
if (document.getElementById("territorymap") != null) {
var territory_on = (document.getElementById("territorymap").className.indexOf("selectedmaptool") != -1);
if ((territory_on && !this.enabled) || (!territory_on && this.enabled && this.mode == 0) || (territory_on && this.enabled && this.mode == 1)) {
$('#territorymap').toggleClass("selectedmaptool");
}
}
if (document.getElementById("alliancemap") != null) {
var alliance_on = (document.getElementById("alliancemap").className.indexOf("selectedmaptool") != -1);
if ((alliance_on && !this.enabled) || (!alliance_on && this.enabled && this.mode == 1) || (alliance_on && this.enabled && this.mode == 0)) {
$('#alliancemap').toggleClass("selectedmaptool");
}
}
},
addMapTool: function (text, cls, id, onclick) {
$("<li class='" + cls + "' id='" + id + "'>" + text + "</li>").tclick(onclick).appendTo("#MapTools");
},
showTerritory: function () {
// draw territory
//console.log("Drawing territory2");
if (this.diagram == null || this.diagram.gameId != vgap.gameId) {
this.calculateMapVoronoi();
}
if (this.diagram != null && this.diagram.gameId == vgap.gameId) {
var ctx = vgap.map.ctx;
//console.log("diagram exists");
for (var i = 0; i < this.diagram.cells.length; i++) {
var cell = this.diagram.cells[i];
var ownerId = vgap.planetAt(cell.site.x, cell.site.y).ownerid;
var colorsA =["#F0F8FF","#32CD32","#CD5C5C","#FFC0CB","#98FB98","#C0C0C0","#FFFF00","#EE82EE","#D3DFD3","#B0E0E6","#87CEFA","#7B68EE","#F4A460","#D2B48C","#FF6347","#F5DEB3","#F08080","#2F4F4F","#008080","#B22222","#808000","#9370DB","#00FF00","#4B0082","#D2B48C","#9ACD32", "#DAA520","#F0F8FF","#6B8E23","#FF4500"];
// var colorsA2 =["#FFFFFF","#006400","#FF0000","#FF69B4","#00FA9A","#6A5ACD","#FFD700","#9400D3","#808080","#00CED1","#4169E1","#7B68EE","#A0522D","#87CEEB","#FF6347","#F5DEB3","#F08080","#2F4F4F","#008080","#B22222","#808000","#9370DB","#00FF00","#4B0082","#D2B48C","#9ACD32", "#DAA520","#F0F8FF","#6B8E23","#FF4500"];
var alpha = 0.2;
var color = colorsA[0];
if (ownerId != 0) {
if (this.mode == 1 && vgap.allied(ownerId) && vgap.alliedTo(ownerId)) {
color = colorsA[vgap.player.id];
} else {
color = colorsA[ownerId];
}
alpha = 0.5;
}
//check if area is visible
if (!vgap.map.isVisible(cell.site.x, cell.site.y, cell.visibility_radius)) {
continue;
}
var vertexFrom = cell.halfedges[0].edge.va;
var vertexTo = cell.halfedges[0].edge.vb;
if ( (cell.halfedges[0].edge.lSite == null) || (cell.halfedges[0].edge.lSite.voronoiId != cell.site.voronoiId) ) {
vertexFrom = cell.halfedges[0].edge.vb;
vertexTo = cell.halfedges[0].edge.va;
}
ctx.strokeStyle = colorToRGBA(color, 0.4);
ctx.lineWidth = 2;
ctx.fillStyle = colorToRGBA(color, alpha);
ctx.beginPath();
ctx.moveTo(vgap.map.screenX(vertexFrom.x), vgap.map.screenY(vertexFrom.y));
for (var j=0; j < cell.halfedges.length; j++) {
if ( (cell.halfedges[j].edge.lSite == null) || (cell.halfedges[j].edge.lSite.voronoiId != cell.site.voronoiId) ) {
vertexTo = cell.halfedges[j].edge.va;
} else {
vertexTo = cell.halfedges[j].edge.vb;
}
ctx.lineTo(vgap.map.screenX(vertexTo.x), vgap.map.screenY(vertexTo.y));
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
}
},
calculateMapVoronoi: function () {
if (this.diagram != null) {
if (this.gameid == vgap.gameId) {
console.log("Voronoi diagram already calculated for gameid: " + vgap.gameId);
return;
}
}
this.gameid = vgap.gameId;
//add planets as sites
var maxCoord = [-10000,-10000];
var minCoord = [10000,10000];
this.sites = [];
for (var p = 0; p < vgap.planets.length; p++) {
var pl = vgap.planets[p];
//fix for the sphere addon: ignore planets with negative id
if (pl.id < 0)
continue;
this.sites.push({x: pl.x, y: pl.y});
if (pl.x > maxCoord[0])
maxCoord[0] = pl.x;
if (pl.y > maxCoord[1])
maxCoord[1] = pl.y;
if (pl.x < minCoord[0])
minCoord[0] = pl.x;
if (pl.y < minCoord[1])
minCoord[1] = pl.y;
}
minCoord[0] -= 30;
minCoord[1] -= 30;
maxCoord[0] += 30;
maxCoord[1] += 30;
//use map settings instead of min/max planet coordinates
minCoord[0] = 2000 - vgap.settings.mapwidth/2;
minCoord[1] = 2000 - vgap.settings.mapheight/2;
maxCoord[0] = 2000 + vgap.settings.mapwidth/2;
maxCoord[1] = 2000 + vgap.settings.mapheight/2;
//bounding box
this.bbox = {xl: minCoord[0],xr: maxCoord[0],yt: minCoord[1],yb: maxCoord[1]};
// vgap.diagram = null;
this.voronoi = new Voronoi();
this.diagram = this.voronoi.compute(this.sites, this.bbox, this.gameid);
}
};
// register your plugin with NU
vgap.registerPlugin(territoryMapPlugin, "territoryMapPlugin");
/**
* Overload the clearTools function in order to be able to
* disable the territory map through hotkey 'x'
*/
vgap.plugins["territoryMapPlugin"].oldClearTools = vgapMap.prototype.clearTools;
vgapMap.prototype.clearTools = function(result) {
vgap.plugins["territoryMapPlugin"].enabled = false;
vgap.plugins["territoryMapPlugin"].syncButtons();
//execute the normal clearTools function
vgap.plugins["territoryMapPlugin"].oldClearTools.apply(this,arguments);
};
/*!
Author: Raymond Hill ([email protected])
File: rhill-voronoi-core.js
Version: 0.96
Date: May 26, 2011
Description: This is my personal Javascript implementation of
Steven Fortune's algorithm to compute Voronoi diagrams.
Copyright (C) 2010,2011 Raymond Hill
https:github.com/gorhill/Javascript-Voronoi
Licensed under The MIT License
http://en.wikipedia.org/wiki/MIT_License
*****
Portions of this software use, depend, or was inspired by the work of:
"Fortune's algorithm" by Steven J. Fortune: For his clever
algorithm to compute Voronoi diagrams.
http://ect.bell-labs.com/who/sjf/
"The Liang-Barsky line clipping algorithm in a nutshell!" by Daniel White,
to efficiently clip a line within a rectangle.
http://www.skytopia.com/project/articles/compsci/clipping.html
"rbtree" by Franck Bui-Huu
https://github.com/fbuihuu/libtree/blob/master/rb.c
I ported to Javascript the C code of a Red-Black tree implementation by
Franck Bui-Huu, and further altered the code for Javascript efficiency
and to very specifically fit the purpose of holding the beachline (the key
is a variable range rather than an unmutable data point), and unused
code paths have been removed. Each node in the tree is actually a beach
section on the beachline. Using a tree structure for the beachline remove
the need to lookup the beach section in the array at removal time, as
now a circle event can safely hold a reference to its associated
beach section (thus findDeletionPoint() is no longer needed). This
finally take care of nagging finite arithmetic precision issues arising
at lookup time, such that epsilon could be brought down to 1e-9 (from 1e-4).
rhill 2011-05-27: added a 'previous' and 'next' members which keeps track
of previous and next nodes, and remove the need for Beachsection.getPrevious()
and Beachsection.getNext().
*/
function Voronoi() {
this.edges = null;
this.cells = null;
this.beachsectionJunkyard = [];
this.circleEventJunkyard = [];
};
Voronoi.prototype.reset = function() {
if (!this.beachline) {
this.beachline = new this.RBTree();
}
if (this.beachline.root) {
var a = this.beachline.getFirst(this.beachline.root);
while (a) {
this.beachsectionJunkyard.push(a);
a = a.rbNext;
}
}
this.beachline.root = null;
if (!this.circleEvents) {
this.circleEvents = new this.RBTree();
}
this.circleEvents.root = this.firstCircleEvent = null;
this.edges = [];
this.cells = [];
};
Voronoi.prototype.sqrt = Math.sqrt;
Voronoi.prototype.abs = Math.abs;
Voronoi.prototype.EPSILON = 1e-9;
Voronoi.prototype.equalWithEpsilon = function(d, c) {
return this.abs(d - c) < 1e-9;
};
Voronoi.prototype.greaterThanWithEpsilon = function(d, c) {
return d - c > 1e-9;
};
Voronoi.prototype.greaterThanOrEqualWithEpsilon = function(d, c) {
return c - d < 1e-9;
};
Voronoi.prototype.lessThanWithEpsilon = function(d, c) {
return c - d > 1e-9;
};
Voronoi.prototype.lessThanOrEqualWithEpsilon = function(d, c) {
return d - c < 1e-9;
};
Voronoi.prototype.RBTree = function() {
this.root = null;
};
Voronoi.prototype.RBTree.prototype.rbInsertSuccessor = function(e, a) {
var d;
if (e) {
a.rbPrevious = e;
a.rbNext = e.rbNext;
if (e.rbNext) {
e.rbNext.rbPrevious = a;
}
e.rbNext = a;
if (e.rbRight) {
e = e.rbRight;
while (e.rbLeft) {
e = e.rbLeft;
}
e.rbLeft = a;
} else {
e.rbRight = a;
}
d = e;
} else {
if (this.root) {
e = this.getFirst(this.root);
a.rbPrevious = null;
a.rbNext = e;
e.rbPrevious = a;
e.rbLeft = a;
d = e;
} else {
a.rbPrevious = a.rbNext = null;
this.root = a;
d = null;
}
}
a.rbLeft = a.rbRight = null;
a.rbParent = d;
a.rbRed = true;
var c, b;
e = a;
while (d && d.rbRed) {
c = d.rbParent;
if (d === c.rbLeft) {
b = c.rbRight;
if (b && b.rbRed) {
d.rbRed = b.rbRed = false;
c.rbRed = true;
e = c;
} else {
if (e === d.rbRight) {
this.rbRotateLeft(d);
e = d;
d = e.rbParent;
}
d.rbRed = false;
c.rbRed = true;
this.rbRotateRight(c);
}
} else {
b = c.rbLeft;
if (b && b.rbRed) {
d.rbRed = b.rbRed = false;
c.rbRed = true;
e = c;
} else {
if (e === d.rbLeft) {
this.rbRotateRight(d);
e = d;
d = e.rbParent;
}
d.rbRed = false;
c.rbRed = true;
this.rbRotateLeft(c);
}
}
d = e.rbParent;
}
this.root.rbRed = false;
};
Voronoi.prototype.RBTree.prototype.rbRemoveNode = function(f) {
if (f.rbNext) {
f.rbNext.rbPrevious = f.rbPrevious;
}
if (f.rbPrevious) {
f.rbPrevious.rbNext = f.rbNext;
}
f.rbNext = f.rbPrevious = null;
var e = f.rbParent, g = f.rbLeft, b = f.rbRight, d;
if (!g) {
d = b;
} else {
if (!b) {
d = g;
} else {
d = this.getFirst(b);
}
}
if (e) {
if (e.rbLeft === f) {
e.rbLeft = d;
} else {
e.rbRight = d;
}
} else {
this.root = d;
}
var a;
if (g && b) {
a = d.rbRed;
d.rbRed = f.rbRed;
d.rbLeft = g;
g.rbParent = d;
if (d !== b) {
e = d.rbParent;
d.rbParent = f.rbParent;
f = d.rbRight;
e.rbLeft = f;
d.rbRight = b;
b.rbParent = d;
} else {
d.rbParent = e;
e = d;
f = d.rbRight;
}
} else {
a = f.rbRed;
f = d;
}
if (f) {
f.rbParent = e;
}
if (a) {
return
}
if (f && f.rbRed) {
f.rbRed = false;
return
}
var c;
do {
if (f === this.root) {
break;
}
if (f === e.rbLeft) {
c = e.rbRight;
if (c.rbRed) {
c.rbRed = false;
e.rbRed = true;
this.rbRotateLeft(e);
c = e.rbRight;
}
if ((c.rbLeft && c.rbLeft.rbRed) || (c.rbRight && c.rbRight.rbRed)) {
if (!c.rbRight || !c.rbRight.rbRed) {
c.rbLeft.rbRed = false;
c.rbRed = true;
this.rbRotateRight(c);
c = e.rbRight;
}
c.rbRed = e.rbRed;
e.rbRed = c.rbRight.rbRed = false;
this.rbRotateLeft(e);
f = this.root;
break;
}
} else {
c = e.rbLeft;
if (c.rbRed) {
c.rbRed = false;
e.rbRed = true;
this.rbRotateRight(e);
c = e.rbLeft;
}
if ((c.rbLeft && c.rbLeft.rbRed) || (c.rbRight && c.rbRight.rbRed)) {
if (!c.rbLeft || !c.rbLeft.rbRed) {
c.rbRight.rbRed = false;
c.rbRed = true;
this.rbRotateLeft(c);
c = e.rbLeft;
}
c.rbRed = e.rbRed;
e.rbRed = c.rbLeft.rbRed = false;
this.rbRotateRight(e);
f = this.root;
break;
}
}
c.rbRed = true;
f = e;
e = e.rbParent;
} while (!f.rbRed);
if (f) {
f.rbRed = false;
}
};
Voronoi.prototype.RBTree.prototype.rbRotateLeft = function(b) {
var d = b, c = b.rbRight, a = d.rbParent;
if (a) {
if (a.rbLeft === d) {
a.rbLeft = c;
} else {
a.rbRight = c;
}
} else {
this.root = c;
}
c.rbParent = a;
d.rbParent = c;
d.rbRight = c.rbLeft;
if (d.rbRight) {
d.rbRight.rbParent = d;
}
c.rbLeft = d;
};
Voronoi.prototype.RBTree.prototype.rbRotateRight = function(b) {
var d = b, c = b.rbLeft, a = d.rbParent;
if (a) {
if (a.rbLeft === d) {
a.rbLeft = c;
} else {
a.rbRight = c;
}
} else {
this.root = c;
}
c.rbParent = a;
d.rbParent = c;
d.rbLeft = c.rbRight;
if (d.rbLeft) {
d.rbLeft.rbParent = d;
}
c.rbRight = d;
};
Voronoi.prototype.RBTree.prototype.getFirst = function(a) {
while (a.rbLeft) {
a = a.rbLeft;
}
return a;
};
Voronoi.prototype.RBTree.prototype.getLast = function(a) {
while (a.rbRight) {
a = a.rbRight;
}
return a;
};
Voronoi.prototype.Cell = function(a) {
this.site = a;
this.halfedges = [];
};
Voronoi.prototype.Cell.prototype.prepare = function() {
var a = this.halfedges, b = a.length, c;
while (b--) {
c = a[b].edge;
if (!c.vb || !c.va) {
a.splice(b, 1);
}
}
a.sort(function(e, d) {
return d.angle - e.angle;
});
return a.length;
};
Voronoi.prototype.Vertex = function(a, b) {
this.x = a;
this.y = b;
};
Voronoi.prototype.Edge = function(b, a) {
this.lSite = b;
this.rSite = a;
this.va = this.vb = null;
};
Voronoi.prototype.Halfedge = function(d, e, a) {
this.site = e;
this.edge = d;
if (a) {
this.angle = Math.atan2(a.y - e.y, a.x - e.x);
} else {
var c = d.va, b = d.vb;
this.angle = d.lSite === e ? Math.atan2(b.x - c.x, c.y - b.y) : Math.atan2(c.x - b.x, b.y - c.y);
}
};
Voronoi.prototype.Halfedge.prototype.getStartpoint = function() {
return this.edge.lSite === this.site ? this.edge.va : this.edge.vb;
};
Voronoi.prototype.Halfedge.prototype.getEndpoint = function() {
return this.edge.lSite === this.site ? this.edge.vb : this.edge.va;
};
Voronoi.prototype.createEdge = function(e, a, d, b) {
var c = new this.Edge(e, a);
this.edges.push(c);
if (d) {
this.setEdgeStartpoint(c, e, a, d);
}
if (b) {
this.setEdgeEndpoint(c, e, a, b);
}
this.cells[e.voronoiId].halfedges.push(new this.Halfedge(c, e, a));
this.cells[a.voronoiId].halfedges.push(new this.Halfedge(c, a, e));
return c;
};
Voronoi.prototype.createBorderEdge = function(d, c, a) {
var b = new this.Edge(d, null);
b.va = c;
b.vb = a;
this.edges.push(b);
return b;
};
Voronoi.prototype.setEdgeStartpoint = function(b, d, a, c) {
if (!b.va && !b.vb) {
b.va = c;
b.lSite = d;
b.rSite = a;
} else {
if (b.lSite === a) {
b.vb = c;
} else {
b.va = c;
}
}
};
Voronoi.prototype.setEdgeEndpoint = function(b, d, a, c) {
this.setEdgeStartpoint(b, a, d, c);
};
Voronoi.prototype.Beachsection = function(a) {
this.site = a;
};
Voronoi.prototype.createBeachsection = function(a) {
var b = this.beachsectionJunkyard.pop();
if (b) {
b.site = a;
} else {
b = new this.Beachsection(a);
}
return b;
};
Voronoi.prototype.leftBreakPoint = function(e, f) {
var a = e.site, m = a.x, l = a.y, k = l - f;
if (!k) {
return m;
}
var n = e.rbPrevious;
if (!n) {
return -Infinity;
}
a = n.site;
var h = a.x, g = a.y, d = g - f;
if (!d) {
return h;
}
var c = h - m, j = 1 / k - 1 / d, i = c / d;
if (j) {
return (-i + this.sqrt(i * i - 2 * j * (c * c / (-2 * d) - g + d / 2 + l - k / 2))) / j + m;
}
return (m + h) / 2;
};
Voronoi.prototype.rightBreakPoint = function(b, c) {
var d = b.rbNext;
if (d) {
return this.leftBreakPoint(d, c);
}
var a = b.site;
return a.y === c ? a.x : Infinity;
};
Voronoi.prototype.detachBeachsection = function(a) {
this.detachCircleEvent(a);
this.beachline.rbRemoveNode(a);
this.beachsectionJunkyard.push(a);
};
Voronoi.prototype.removeBeachsection = function(b) {
var a = b.circleEvent, j = a.x, h = a.ycenter, e = new this.Vertex(j, h), f = b.rbPrevious, d = b.rbNext, l = [b], g = Math.abs;
this.detachBeachsection(b);
var m = f;
while (m.circleEvent && g(j - m.circleEvent.x) < 1e-9 && g(h - m.circleEvent.ycenter) < 1e-9) {
f = m.rbPrevious;
l.unshift(m);
this.detachBeachsection(m);
m = f;
}
l.unshift(m);
this.detachCircleEvent(m);
var c = d;
while (c.circleEvent && g(j - c.circleEvent.x) < 1e-9 && g(h - c.circleEvent.ycenter) < 1e-9) {
d = c.rbNext;
l.push(c);
this.detachBeachsection(c);
c = d;
}
l.push(c);
this.detachCircleEvent(c);
var k = l.length, i;
for (i = 1; i < k; i++) {
c = l[i];
m = l[i - 1];
this.setEdgeStartpoint(c.edge, m.site, c.site, e);
}
m = l[0];
c = l[k - 1];
c.edge = this.createEdge(m.site, c.site, undefined, e);
this.attachCircleEvent(m);
this.attachCircleEvent(c);
};
Voronoi.prototype.addBeachsection = function(l) {
var j = l.x, n = l.y;
var p, m, v, q, o = this.beachline.root;
while (o) {
v = this.leftBreakPoint(o, n) - j;
if (v > 1e-9) {
o = o.rbLeft;
} else {
q = j - this.rightBreakPoint(o, n);
if (q > 1e-9) {
if (!o.rbRight) {
p = o;
break;
}
o = o.rbRight;
} else {
if (v > -1e-9) {
p = o.rbPrevious;
m = o;
} else {
if (q > -1e-9) {
p = o;
m = o.rbNext;
} else {
p = m = o;
}
}
break;
}
}
}
var e = this.createBeachsection(l);
this.beachline.rbInsertSuccessor(p, e);
if (!p && !m) {
return
}
if (p === m) {
this.detachCircleEvent(p);
m = this.createBeachsection(p.site);
this.beachline.rbInsertSuccessor(e, m);
e.edge = m.edge = this.createEdge(p.site, e.site);
this.attachCircleEvent(p);
this.attachCircleEvent(m);
return
}
if (p && !m) {
e.edge = this.createEdge(p.site, e.site);
return
}
if (p !== m) {
this.detachCircleEvent(p);
this.detachCircleEvent(m);
var h = p.site, k = h.x, i = h.y, t = l.x - k, r = l.y - i, a = m.site, c = a.x - k, b = a.y - i, u = 2 * (t * b - r * c), g = t * t + r * r, f = c * c + b * b, s = new this.Vertex((b * g - r * f) / u + k, (t * f - c * g) / u + i);
this.setEdgeStartpoint(m.edge, h, a, s);
e.edge = this.createEdge(h, l, undefined, s);
m.edge = this.createEdge(l, a, undefined, s);
this.attachCircleEvent(p);
this.attachCircleEvent(m);
return
}
};
Voronoi.prototype.CircleEvent = function() {
};
Voronoi.prototype.attachCircleEvent = function(i) {
var r = i.rbPrevious, o = i.rbNext;
if (!r || !o) {
return
}
var k = r.site, u = i.site, c = o.site;
if (k === c) {
return
}
var t = u.x, s = u.y, n = k.x - t, l = k.y - s, f = c.x - t, e = c.y - s;
var v = 2 * (n * e - l * f);
if (v >= -2e-12) {
return
}
var h = n * n + l * l, g = f * f + e * e, m = (e * h - l * g) / v, j = (n * g - f * h) / v, b = j + s;
var q = this.circleEventJunkyard.pop();
if (!q) {
q = new this.CircleEvent();
}
q.arc = i;
q.site = u;
q.x = m + t;
q.y = b + this.sqrt(m * m + j * j);
q.ycenter = b;
i.circleEvent = q;
var a = null, p = this.circleEvents.root;
while (p) {
if (q.y < p.y || (q.y === p.y && q.x <= p.x)) {
if (p.rbLeft) {
p = p.rbLeft;
} else {
a = p.rbPrevious;
break;
}
} else {
if (p.rbRight) {
p = p.rbRight;
} else {
a = p;
break;
}
}
}
this.circleEvents.rbInsertSuccessor(a, q);
if (!a) {
this.firstCircleEvent = q;
}
};
Voronoi.prototype.detachCircleEvent = function(a) {
var b = a.circleEvent;
if (b) {
if (!b.rbPrevious) {
this.firstCircleEvent = b.rbNext;
}
this.circleEvents.rbRemoveNode(b);
this.circleEventJunkyard.push(b);
a.circleEvent = null;
}
};
Voronoi.prototype.connectEdge = function(l, a) {
var b = l.vb;
if (!!b) {
return true;
}
var c = l.va, p = a.xl, n = a.xr, r = a.yt, d = a.yb, o = l.lSite, e = l.rSite, i = o.x, h = o.y, k = e.x, j = e.y, g = (i + k) / 2, f = (h + j) / 2, m, q;
if (j !== h) {
m = (i - k) / (j - h);
q = f - m * g;
}
if (m === undefined) {
if (g < p || g >= n) {
return false;
}
if (i > k) {
if (!c) {
c = new this.Vertex(g, r);
} else {
if (c.y >= d) {
return false;
}
}
b = new this.Vertex(g, d);
} else {
if (!c) {
c = new this.Vertex(g, d);
} else {
if (c.y < r) {
return false;
}
}
b = new this.Vertex(g, r);
}
} else {
if (m < -1 || m > 1) {
if (i > k) {
if (!c) {
c = new this.Vertex((r - q) / m, r);
} else {
if (c.y >= d) {
return false;
}
}
b = new this.Vertex((d - q) / m, d);
} else {
if (!c) {
c = new this.Vertex((d - q) / m, d);
} else {
if (c.y < r) {
return false;
}
}
b = new this.Vertex((r - q) / m, r);
}
} else {
if (h < j) {
if (!c) {
c = new this.Vertex(p, m * p + q);
} else {
if (c.x >= n) {
return false;
}
}
b = new this.Vertex(n, m * n + q);
} else {
if (!c) {
c = new this.Vertex(n, m * n + q);
} else {
if (c.x < p) {
return false;
}
}
b = new this.Vertex(p, m * p + q);
}
}
}
l.va = c;
l.vb = b;
return true;
};
Voronoi.prototype.clipEdge = function(d, i) {
var b = d.va.x, l = d.va.y, h = d.vb.x, g = d.vb.y, f = 0, e = 1, k = h - b, j = g - l;
var c = b - i.xl;
if (k === 0 && c < 0) {
return false;
}
var a = -c / k;
if (k < 0) {
if (a < f) {
return false;
} else {
if (a < e) {
e = a;
}
}
} else {
if (k > 0) {
if (a > e) {
return false;
} else {
if (a > f) {
f = a;
}
}
}
}
c = i.xr - b;
if (k === 0 && c < 0) {
return false;
}
a = c / k;
if (k < 0) {
if (a > e) {
return false;
} else {
if (a > f) {
f = a;
}
}
} else {
if (k > 0) {
if (a < f) {
return false;
} else {
if (a < e) {
e = a;
}
}
}
}
c = l - i.yt;
if (j === 0 && c < 0) {
return false;
}
a = -c / j;
if (j < 0) {
if (a < f) {
return false;
} else {
if (a < e) {
e = a;
}
}
} else {
if (j > 0) {
if (a > e) {
return false;
} else {
if (a > f) {
f = a;
}
}
}
}
c = i.yb - l;
if (j === 0 && c < 0) {
return false;
}
a = c / j;
if (j < 0) {
if (a > e) {
return false;
} else {
if (a > f) {
f = a;
}
}
} else {
if (j > 0) {
if (a < f) {
return false;
} else {
if (a < e) {
e = a;
}
}
}
}
if (f > 0) {
d.va = new this.Vertex(b + f * k, l + f * j);
}
if (e < 1) {
d.vb = new this.Vertex(b + e * k, l + e * j);
}
return true;
};
Voronoi.prototype.clipEdges = function(e) {
var a = this.edges, d = a.length, c, b = Math.abs;
while (d--) {
c = a[d];
if (!this.connectEdge(c, e) || !this.clipEdge(c, e) || (b(c.va.x - c.vb.x) < 1e-9 && b(c.va.y - c.vb.y) < 1e-9)) {
c.va = c.vb = null;
a.splice(d, 1);
}
}
};
Voronoi.prototype.closeCells = function(a) {
var n = a.xl, m = a.xr, q = a.yt, e = a.yb, g = this.cells, f = g.length, b, h, r, o, p, j, l, i, d, c, k = Math.abs;
while (f--) {
b = g[f];
if (!b.prepare()) {
continue;
}
o = b.halfedges;
p = o.length;
h = 0;
while (h < p) {
r = (h + 1) % p;
i = o[h].getEndpoint();
l = o[r].getStartpoint();
if (k(i.x - l.x) >= 1e-9 || k(i.y - l.y) >= 1e-9) {
d = i;
if (this.equalWithEpsilon(i.x, n) && this.lessThanWithEpsilon(i.y, e)) {
c = new this.Vertex(n, this.equalWithEpsilon(l.x, n) ? l.y : e);
} else {
if (this.equalWithEpsilon(i.y, e) && this.lessThanWithEpsilon(i.x, m)) {
c = new this.Vertex(this.equalWithEpsilon(l.y, e) ? l.x : m, e);
} else {
if (this.equalWithEpsilon(i.x, m) && this.greaterThanWithEpsilon(i.y, q)) {
c = new this.Vertex(m, this.equalWithEpsilon(l.x, m) ? l.y : q);
} else {
if (this.equalWithEpsilon(i.y, q) && this.greaterThanWithEpsilon(i.x, n)) {
c = new this.Vertex(this.equalWithEpsilon(l.y, q) ? l.x : n, q);
}
}
}
}
j = this.createBorderEdge(b.site, d, c);
o.splice(h + 1, 0, new this.Halfedge(j, b.site, null));
p = o.length;
}
h++;
}
}
};
Voronoi.prototype.compute = function(h, i, gameid) {
var d = new Date();
this.reset();
var g = h.slice(0);
g.sort(function(n, m) {
var o = m.y - n.y;
if (o) {
return o;
}
return m.x - n.x;
});
var b = g.pop(), k = 0, f = Number.MIN_VALUE, e = Number.MIN_VALUE, j = this.cells, a;
for (; ; ) {
a = this.firstCircleEvent;
if (b && (!a || b.y < a.y || (b.y === a.y && b.x < a.x))) {
if (b.x !== f || b.y !== e) {
j[k] = new this.Cell(b);
b.voronoiId = k++;
this.addBeachsection(b);
e = b.y;
f = b.x;
}
b = g.pop();
} else {
if (a) {
this.removeBeachsection(a.arc);
} else {
break;
}
}
}
this.clipEdges(i);
this.closeCells(i);
this.computeCellVisibilityRadius();
var c = new Date();
var l = {cells: this.cells, edges: this.edges, execTime: c.getTime() - d.getTime(), gameId: gameid};
this.reset();
return l;
};
Voronoi.prototype.computeCellVisibilityRadius = function() {
//check if area is visible
for (var i = 0; i < this.cells.length; i++) {
var cell = this.cells[i];
var max_dist = 0;
var dx, dy;
for (var j=0; j < cell.halfedges.length; j++) {
dx = cell.site.x - cell.halfedges[j].edge.va.x;
dy = cell.site.y - cell.halfedges[j].edge.va.y;
d = Math.sqrt(dx*dx + dy*dy);
if (d > max_dist) {
max_dist = d;
}
dx = cell.site.x - cell.halfedges[j].edge.vb.x;
dy = cell.site.y - cell.halfedges[j].edge.vb.y;
d = Math.sqrt(dx*dx + dy*dy);
if (d > max_dist) {
max_dist = d;
}
}
cell.visibility_radius = max_dist;
}
};
} //wrapper for injection
var script = document.createElement("script");
script.type = "application/javascript";
script.textContent = "(" + wrapper + ")();";
document.body.appendChild(script);