<head>
<meta charset="utf-8">
<title>Automap</title>
- <style media="screen">.m,body,html{width:100%;height:100%;margin:0;outline:1px dotted #000}.m img{position:absolute;image-rendering:pixelated}.i{width:15em;background-color:rgba(200,200,200,.5);left:0;top:0;font-family:sans-serif;position:absolute;z-index:1}h1{font-size:22px;margin:.6em 1em;text-align:center}.t{margin:.3em auto;width:90%;outline:1px solid #333}.t th{width:30%}.t td{text-align:right}</style>
+ <style>html, body, .map {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ overflow: hidden;
+ outline: 1px dotted black;
+}
+
+.infobox {
+ width: 15em;
+ /* height: 15em; */
+ background-color: rgba(200, 200, 200, 0.5);
+ left: 0;
+ top: 0;
+ font-family: sans-serif;
+ position: absolute;
+ z-index: 1;
+}
+
+h1 {
+ font-size: 22px;
+ margin: .6em 1em;
+ text-align: center;
+}
+
+.infoboxTable {
+ margin: .3em auto;
+ width: 90%;
+ outline: 1px solid #333;
+}
+
+.infoboxTable th {
+ width: 30%;
+}
+
+.infoboxTable td {
+ text-align: right;
+}</style>
</head>
<body>
- <div class="i">
- <h1>Chunk Info</h1>
- <table class="t"></table>
- </div>
- <script type="text/javascript">
- function ViewFrame() {
- this.map = document.createElement("div"), this.map.className = "m", this.infobox = document.getElementsByClassName("t")[0], this.infoboxSlots = new Map, ViewFrame.initInfobox(this.infobox, this.infoboxSlots), document.getElementsByTagName("body")[
- 0].append(this.map), this.loadedChunksByName = new Map, this.loadedChunksByCoords = new Map, this.availableChunks = null, this.chunkScript = null, this.renderOnReload = !0, this.dirty = !1, this.rendering = !1, this.x = null, this.y = null,
- this._zoom = 32, this.updateEdges()
- }
+ <script type="text/javascript">function ViewFrame() {
+ // dom stuff
+ const map = document.createElement('canvas'); // the map we see
+ map.className = 'map';
+ map.height = window.innerHeight;
+ map.width = window.innerWidth;
+ document.getElementsByTagName('body')[0].append(map);
+ // this is the map that we actually draw on
+ this.map = map.getContext('2d', {
+ alpha: false
+ });
+ this.map.imageSmoothingEnabled = false;
+
+ // the info box in the corner
+ this.infobox = document.getElementsByClassName('infoboxTable')[0];
+ this.infoboxSlots = new Array();
+
+ // load the metadata!
+ this.chunkScript = document.createElement('script');
+ this.chunkScript.type = 'text/javascript';
+ this.chunkScript.src = 'Metadata.js';
+ document.getElementsByTagName('body')[0].append(this.chunkScript);
+ this.chunkScript.addEventListener('load', () => {
+ ViewFrame.initInfobox(this.infobox, this.infoboxSlots);
+ this.x = ViewFrame.chunks.startCoords[0];
+ this.y = ViewFrame.chunks.startCoords[1];
+ this.availableChunks = ViewFrame.chunks.chunkMetadata;
+ this.render();
+ }, {
+ once: true
+ });
- function decode(t, e) {
- t.decode().then(() => {
- e(t)
- }).catch(() => {})
+ // Tracks images that have been loaded and are on the map
+ this.loadedChunksByName = new Map();
+ // this is needed because [1, 2] != [1, 2] and thats how we store coords.
+ this.loadedChunksByCoords = new Map();
+ this.availableChunks = null; // the chunks in ./Metadata.js
+ // so that we dont render twice at the same time
+ this.rendering = false;
+
+ this.x = -1;
+ this.y = -1; // can be fractional
+ this.zoom = 32; // pixels wide the shards are to be
+ this.updateEdges();
+}
+// prototypes, some less... notable? methods are
+// in ViewFrameUtils.js
+ViewFrame.prototype.reloadChunkList = function () {
+ if (this.chunkScript) {
+ this.chunkScript.remove();
+ delete this.chunkScript;
+ }
+
+ this.chunkScript = document.createElement('script');
+ this.chunkScript.type = 'text/javascript';
+ this.chunkScript.src = 'Metadata.js';
+ document.getElementsByTagName('body')[0].append(this.chunkScript);
+
+ this.chunkScript.addEventListener('load', () => {
+ this.availableChunks = ViewFrame.chunks.chunkMetadata;
+ this.render();
+ });
+};
+
+ViewFrame.prototype.render = function () {
+ if (!this.availableChunks) return;
+ if (this.rendering) clearInterval(ViewFrame.intervalRef);
+ this.rendering = true;
+ this.updateEdges();
+ this.map.clearRect(0, 0, window.innerWidth, window.innerHeight);
+ // culling
+ this.loadedChunksByCoords
+ .forEach((chunk, coord) => { // check the bounds
+ if (coord[0] < this.eastChunk &&
+ coord[0] >= this.westChunk &&
+ coord[1] <= this.northChunk &&
+ coord[1] >= this.southChunk) {
+
+ this.place(chunk, coord[0], coord[1]);
+ return;
+ }
+ // its out of range!!!
+ // get 'em boys!!!!!
+ this.loadedChunksByCoords.delete(coord);
+ this.loadedChunksByName.delete(coord.join('_'));
+ chunk.remove();
+ });
+
+ // gathering what we need to load
+ const neededChunks = new Set();
+ for (var x = this.westChunk; x < this.eastChunk; x++) {
+ for (var y = this.southChunk; y < this.northChunk; y++) {
+ const chunKey = [x, y]; // chunk + key = chunKey :)
+ const name = chunKey.join('_');
+ // continue if its not available, or it is loaded
+ if (!this.availableChunks.has(name) ||
+ this.loadedChunksByName.has(name)) continue;
+ neededChunks.add(chunKey);
}
- ViewFrame.prototype.reloadChunkList = function() {
- console.log("Reloading chunks!"), this.chunkScript && (this.chunkScript.remove(), delete this.chunkScript), this.chunkScript = document.createElement("script"), this.chunkScript.type = "text/javascript", this.chunkScript.src = "Metadata.js",
- document.getElementsByTagName("body")[0].append(this.chunkScript), this.chunkScript.onload = (() => {
- this.availableChunks = ViewFrame.chunks.chunkMetadata, null !== this.x && null !== this.y || (this.x = ViewFrame.chunks.startCoords[0], this.y = ViewFrame.chunks.startCoords[1]), this.renderOnReload && this.render()
- }), this.dirty = !0
- }, ViewFrame.prototype.render = function() {
- if (!this.availableChunks) return;
- if (this.rendering) return;
- this.rendering = !0, this.dirty && this.updateEdges(), this.loadedChunksByCoords.forEach((t, e) => {
- if (e[0] < this.eastChunk && e[0] >= this.westChunk && e[1] <= this.northChunk && e[1] >= this.southChunk) {
- let [s, i] = this.chunkToScreen(e[0], e[1]);
- t.style.left = s + "px", t.style.top = i + "px";
- const n = this.zoom / 32;
- 1 != n && (t.style.transform = `scale(${n})`)
- } else this.loadedChunksByCoords.delete(e), this.loadedChunksByName.delete(e.join("_")), t.remove()
+ }
+ // iterating over everything we need to load
+ const it = neededChunks.values();
+ ViewFrame.intervalRef = setInterval(() => {
+ let round = it.next();
+ if (!round.done) {
+ // load
+ const img = new Image(32, 32);
+ const name = round.value.join('_');
+
+ img.src = 'Chunks/' + name + '.png';
+
+ decode(img, loadedImage => {
+ this.place(img, round.value[0], round.value[1]);
});
- const t = new Set;
- for (var e = this.westChunk; e < this.eastChunk; e++)
- for (var s = this.southChunk; s < this.northChunk; s++) {
- const i = [e, s],
- n = i.join("_");
- this.availableChunks.has(n) && !this.loadedChunksByName.has(n) && t.add(i)
- }
- const i = t.values();
- let n = setInterval(() => {
- let t = i.next();
- if (t.done) clearInterval(n), this.rendering = !1;
- else {
- const e = new Image(32, 32),
- s = t.value.join("_");
- e.src = s + ".png", e.alt = s, decode(e, e => {
- let [s, i] = this.chunkToScreen(t.value[0], t.value[1]);
- e.style.left = s + "px", e.style.top = i + "px";
- const n = this.zoom / 32;
- 1 != n && (chunk.style.transform = `scale(${n})`), this.map.append(e)
- }), this.loadedChunksByName.set(s, e), this.loadedChunksByCoords.set(t.value, e)
- }
- }, 4)
- }, ViewFrame.prototype.updateInfobox = function(t) {
- const e = this.availableChunks.get(t);
- this.infoboxSlots.forEach((t, s) => {
- t.innerText = e[s]
- })
- }, ViewFrame.initInfobox = function(t, e) {
- ["prettyCoord:Loc.", "chunkAge:Age", "temp:Temp.", "YMax:Y Max", "fert:Fert.", "forestDens:Forest", "rain:Rain", "shrubDens:Shrub", "airBlocks:Air", "nonAirBlocks:Non-Air"].map(t => t.split(":")).forEach(s => {
- const i = s[0],
- n = s[1],
- h = document.createElement("tr"),
- o = document.createElement("th");
- o.innerText = n;
- const r = document.createElement("td");
- r.innerText = "0", e.set(i, r), h.append(o, r), t.append(h)
- })
- }, ViewFrame.prototype.screenToChunk = function(t, e) {
- return [(t - this.width / 2) / this.zoom, (e - this.height / 2) / this.zoom]
- }, ViewFrame.prototype.chunkToScreen = function(t, e) {
- return [(t - this.west) * this.zoom, (e - this.south) * this.zoom]
- }, ViewFrame.prototype.updateEdges = function() {
- if (!this.dirty) return;
- const t = Math.ceil(this.width / this.zoom),
- e = Math.ceil(this.height / this.zoom);
- this.east = this.x + t / 2, this.eastChunk = Math.ceil(this.east), this.west = this.x - t / 2, this.westChunk = Math.floor(this.west), this.north = this.y + e / 2, this.northChunk = Math.ceil(this.north), this.south = this.y - e / 2, this.southChunk =
- Math.floor(this.south), this.dirty = !1
- }, ViewFrame.prototype.moveCenter = function(t, e) {
- let [s, i] = this.screenToChunk(t, e);
- this.x += s, this.y += i, this.dirty = !0
- }, ViewFrame.prototype.clear = function() {
- this.loadedChunksByName.clear(), this.loadedChunksByCoords.clear(), this.chunkScript && this.chunkScript.remove(), delete this.chunkScript, delete ViewFrame.chunks, this.map.innerHTML = ""
- }, Object.defineProperties(ViewFrame.prototype, {
- width: {
- get() {
- return this.map.clientWidth
- }
- },
- height: {
- get() {
- return this.map.clientHeight
- }
- },
- zoom: {
- get() {
- return this._zoom
- },
- set(t) {
- this._zoom = t, this.dirty = !0
- }
- }
+ this.loadedChunksByName.set(name, img);
+ this.loadedChunksByCoords.set(round.value, img);
+ } else {
+ clearInterval(ViewFrame.intervalRef);
+ this.rendering = false;
+ }
+ }, 4);
+};
+
+ViewFrame.prototype.place = function (img, x, y) {
+ x -= this.x;
+ y -= this.y;
+ x *= this.zoom;
+ y *= this.zoom;
+ x += this.width / 2;
+ y += this.height / 2;
+
+ this.map.drawImage(img, Math.floor(x), Math.floor(y), this.zoom, this.zoom);
+};
+
+ViewFrame.prototype.updateInfobox = function (chunkName) {
+ const chunkMeta = this.availableChunks.get(chunkName);
+ this.infoboxSlots.forEach((l, k) => {
+ l.innerText = chunkMeta ? chunkMeta[k] : '0';
+ });
+};</script></script>
+ <script type="text/javascript">ViewFrame.initInfobox = function (ibox, iboxSlots) {
+ // TODO: make faster
+ ViewFrame.chunks
+ .chunkMetadataNames.forEach((item, i) => {
+ const slot = document.createElement('tr');
+ const head = document.createElement('th');
+ head.innerText = item;
+ const row = document.createElement('td');
+ row.innerText = '0';
+ iboxSlots[i] = row;
+ slot.append(head, row);
+ ibox.append(slot);
});
- const vf = new ViewFrame;
- vf.reloadChunkList(),
- function() {
- var t;
- window.addEventListener("resize", () => {
- clearTimeout(t), vf.clear(), t = setTimeout(() => {
- vf.updateEdges(), vf.render()
- }, 500)
- })
- }(), vf.map.addEventListener("mousedown", t => {
- vf.moveCenter(t.x, t.y), setTimeout(() => {
- vf.render()
- }, 250)
- }),
- function() {
- var t;
- vf.map.addEventListener("mousemove", e => {
- e.target instanceof HTMLImageElement && t !== e.target && (t = e.target, vf.updateInfobox(e.target.alt))
- })
- }();
- (function() {
- setInterval(() => {
- vf.reloadChunkList();
- }, 6000);
- }());
- </script>
+};
+
+ViewFrame.prototype.updateEdges = function () {
+ if (this.width != window.innerWidth || this.height != window.innerHeight) {
+ this.width = window.innerWidth;
+ this.map.canvas.width = this.width;
+ this.height = window.innerHeight;
+ this.map.canvas.height = this.height;
+ this.map.imageSmoothingEnabled = false;
+ }
+ const chunksWide = Math.ceil(this.width / this.zoom);
+ const chunksHigh = Math.ceil(this.height / this.zoom);
+
+ this.east = this.x + chunksWide / 2; // this is fractional and is used to keep track of the edges of the window
+ this.eastChunk = Math.ceil(this.east); // this is not and is used to track the chunks that need to load
+ this.west = this.x - chunksWide / 2;
+ this.westChunk = Math.floor(this.west);
+ this.north = this.y + chunksHigh / 2;
+ this.northChunk = Math.ceil(this.north);
+ this.south = this.y - chunksHigh / 2;
+ this.southChunk = Math.floor(this.south);
+};
+
+/**
+* dx and dy are the distances in chunks
+*/
+ViewFrame.prototype.moveCenter = function (dx, dy) {
+ // to pan when we click on the map!
+ this.x += (dx - this.width / 2) / this.zoom;
+ this.y += (dy - this.height / 2) / this.zoom;
+};
+
+/**
+* x and y are positions in chunks
+*/
+ViewFrame.prototype.setCenter = function (x, y) {
+ this.x = x;
+ this.y = y;
+};
+
+/**
+* x and y are positions in BLOCKS!!!!
+*/
+ViewFrame.prototype.setBlockwiseCenter = function (x, y) {
+ this.x = x / this.zoom;
+ this.y = y / this.zoom;
+};
+
+ViewFrame.prototype.clear = function () {
+ this.loadedChunksByName.clear();
+ this.loadedChunksByCoords.clear();
+ if (this.chunkScript) this.chunkScript.remove();
+ delete this.chunkScript;
+ delete ViewFrame.chunks;
+ this.map.clearRect(0, 0, window.innerWidth, window.innerHeight);
+};
+
+function decode(img, cb) {
+ img.decode()
+ .then(() => {
+ cb(img);
+ })
+ .catch(() => {}); // so images arent added on error
+}</script></script>
+ <div class="infobox">
+ <h1>Chunk Info</h1>
+ <table class="infoboxTable">
+ </table>
+ </div>
+ <script type="text/javascript">const vf = new ViewFrame();
+vf.reloadChunkList();
+
+// the event handlers are in iifes so they dont make unneeded globals.
+// resize, delay re-render to reduce lag.
+(function () {
+ var id;
+ window.addEventListener('resize', () => {
+ clearTimeout(id);
+ id = setTimeout(() => {
+ vf.render();
+ }, 500);
+ });
+}());
+
+// panning
+(function () {
+ var id;
+ vf.map.canvas.addEventListener('mousedown', event => {
+ clearTimeout(id);
+ vf.moveCenter(event.pageX, event.pageY);
+ id = setTimeout(() => {
+ vf.render();
+ }, 250);
+ });
+}());
+
+// #### CONTROLS ####
+// hovering
+(function () {
+ var lastX = 0;
+ var lastY = 0;
+ vf.map.canvas.addEventListener('mousemove', event => {
+ // only count if the mouse moved more than a chunk
+ let x = Math.floor(vf.x +
+ (event.clientX - vf.width / 2) / vf.zoom);
+ let y = Math.floor(vf.y +
+ (event.clientY - vf.height / 2) / vf.zoom);
+ if (x == lastX && y == lastY) return;
+ lastX = x;
+ lastY = y;
+ vf.updateInfobox(x + '_' + y);
+ });
+}());
+
+// scroll/zoom
+(function () {
+ var id;
+ vf.map.canvas.addEventListener('wheel', event => {
+ clearTimeout(id);
+ id = setTimeout(() => {
+ vf.render();
+ }, 250);
+ // this makes it so zooming out is faster
+ let dir = -Math.sign(event.deltaY);
+ if (vf.zoom < 16) // arbitrary value
+ vf.zoom += dir;
+ else
+ vf.zoom += dir * 4;
+ if (vf.zoom <= 0)
+ vf.zoom += 1; // make sure it doesnt go to negative values
+ });
+}());
+
+// reload the chunk list every six seconds
+var devBlockReload = false; // disable via command line
+(function () {
+ setInterval(() => {
+ if (devBlockReload) return;
+ vf.reloadChunkList();
+ }, 6000);
+}());</script></script>
+
</body>
</html>
\ No newline at end of file