OSDN Git Service

commit the part that makes the map work
[automap/automap.git] / Automap / assets / automap / config / automap.html
1 <!DOCTYPE html>
2 <html lang="en" dir="ltr">
3
4 <head>
5         <meta charset="utf-8">
6         <title>Automap</title>
7         <style>html, body, .map {
8         width: 100%;
9         height: 100%;
10         margin: 0;
11         overflow: hidden;
12         outline: 1px dotted black;
13 }
14
15 .infobox {
16         width: 15em;
17         /* height: 15em; */
18         background-color: rgba(200, 200, 200, 0.5);
19         left: 0;
20         top: 0;
21         font-family: sans-serif;
22         position: absolute;
23         z-index: 1;
24 }
25
26 h1 {
27         font-size: 22px;
28         margin: .6em 1em;
29         text-align: center;
30 }
31
32 .infoboxTable {
33         margin: .3em auto;
34         width: 90%;
35         outline: 1px solid #333;
36 }
37
38 .infoboxTable th {
39         width: 30%;
40 }
41
42 .infoboxTable td {
43         text-align: right;
44 }</style>
45 </head>
46
47 <body>
48         <script type="text/javascript">function ViewFrame() {
49         // dom stuff
50         const map = document.createElement('canvas'); // the map we see
51         map.className = 'map';
52         map.height = window.innerHeight;
53         map.width = window.innerWidth;
54         document.getElementsByTagName('body')[0].append(map);
55         // this is the map that we actually draw on
56         this.map = map.getContext('2d', {
57                 alpha: false
58         });
59         this.map.imageSmoothingEnabled = false;
60
61         // the info box in the corner
62         this.infobox = document.getElementsByClassName('infoboxTable')[0];
63         this.infoboxSlots = new Array();
64
65         // load the metadata!
66         this.chunkScript = document.createElement('script');
67         this.chunkScript.type = 'text/javascript';
68         this.chunkScript.src = 'Metadata.js';
69         document.getElementsByTagName('body')[0].append(this.chunkScript);
70         this.chunkScript.addEventListener('load', () => {
71                 ViewFrame.initInfobox(this.infobox, this.infoboxSlots);
72                 this.x = ViewFrame.chunks.startCoords[0];
73                 this.y = ViewFrame.chunks.startCoords[1];
74                 this.availableChunks = ViewFrame.chunks.chunkMetadata;
75                 this.render();
76         }, {
77                 once: true
78         });
79
80         // Tracks images that have been loaded and are on the map
81         this.loadedChunksByName = new Map();
82         // this is needed because [1, 2] != [1, 2] and thats how we store coords.
83         this.loadedChunksByCoords = new Map();
84         this.availableChunks = null; // the chunks in ./Metadata.js
85         // so that we dont render twice at the same time
86         this.rendering = false;
87
88         this.x = -1;
89         this.y = -1; // can be fractional
90         this.zoom = 32; // pixels wide the shards are to be
91         this.updateEdges();
92 }
93 // prototypes, some less... notable? methods are
94 // in ViewFrameUtils.js 
95 ViewFrame.prototype.reloadChunkList = function () {
96         if (this.chunkScript) {
97                 this.chunkScript.remove();
98                 delete this.chunkScript;
99         }
100
101         this.chunkScript = document.createElement('script');
102         this.chunkScript.type = 'text/javascript';
103         this.chunkScript.src = 'Metadata.js';
104         document.getElementsByTagName('body')[0].append(this.chunkScript);
105
106         this.chunkScript.addEventListener('load', () => {
107                 this.availableChunks = ViewFrame.chunks.chunkMetadata;
108                 this.render();
109         });
110 };
111
112 ViewFrame.prototype.render = function () {
113         if (!this.availableChunks) return;
114         if (this.rendering) clearInterval(ViewFrame.intervalRef);
115         this.rendering = true;
116         this.updateEdges();
117         this.map.clearRect(0, 0, window.innerWidth, window.innerHeight);
118         // culling
119         this.loadedChunksByCoords
120                 .forEach((chunk, coord) => { // check the bounds
121                         if (coord[0] < this.eastChunk &&
122                                 coord[0] >= this.westChunk &&
123                                 coord[1] <= this.northChunk &&
124                                 coord[1] >= this.southChunk) {
125
126                                 this.place(chunk, coord[0], coord[1]);
127                                 return;
128                         }
129                         // its out of range!!!
130                         // get 'em boys!!!!!
131                         this.loadedChunksByCoords.delete(coord);
132                         this.loadedChunksByName.delete(coord.join('_'));
133                         chunk.remove();
134                 });
135
136         // gathering what we need to load
137         const neededChunks = new Set();
138         for (var x = this.westChunk; x < this.eastChunk; x++) {
139                 for (var y = this.southChunk; y < this.northChunk; y++) {
140                         const chunKey = [x, y]; // chunk + key = chunKey :)
141                         const name = chunKey.join('_');
142                         // continue if its not available, or it is loaded
143                         if (!this.availableChunks.has(name) ||
144                                 this.loadedChunksByName.has(name)) continue;
145                         neededChunks.add(chunKey);
146                 }
147         }
148         // iterating over everything we need to load
149         const it = neededChunks.values();
150         ViewFrame.intervalRef = setInterval(() => {
151                 let round = it.next();
152                 if (!round.done) {
153                         // load
154                         const img = new Image(32, 32);
155                         const name = round.value.join('_');
156
157                         img.src = 'Chunks/' + name + '.png';
158
159                         decode(img, loadedImage => {
160                                 this.place(img, round.value[0], round.value[1]);
161                         });
162                         this.loadedChunksByName.set(name, img);
163                         this.loadedChunksByCoords.set(round.value, img);
164                 } else {
165                         clearInterval(ViewFrame.intervalRef);
166                         this.rendering = false;
167                 }
168         }, 4);
169 };
170
171 ViewFrame.prototype.place = function (img, x, y) {
172         x -= this.x;
173         y -= this.y;
174         x *= this.zoom;
175         y *= this.zoom;
176         x += this.width / 2;
177         y += this.height / 2;
178
179         this.map.drawImage(img, Math.floor(x), Math.floor(y), this.zoom, this.zoom);
180 };
181
182 ViewFrame.prototype.updateInfobox = function (chunkName) {
183         const chunkMeta = this.availableChunks.get(chunkName);
184         this.infoboxSlots.forEach((l, k) => {
185                 l.innerText = chunkMeta ? chunkMeta[k] : '0';
186         });
187 };</script></script>
188         <script type="text/javascript">ViewFrame.initInfobox = function (ibox, iboxSlots) {
189         // TODO: make faster
190         ViewFrame.chunks
191                 .chunkMetadataNames.forEach((item, i) => {
192                         const slot = document.createElement('tr');
193                         const head = document.createElement('th');
194                         head.innerText = item;
195                         const row = document.createElement('td');
196                         row.innerText = '0';
197                         iboxSlots[i] = row;
198                         slot.append(head, row);
199                         ibox.append(slot);
200                 });
201 };
202
203 ViewFrame.prototype.updateEdges = function () {
204         if (this.width != window.innerWidth || this.height != window.innerHeight) {
205                 this.width = window.innerWidth;
206                 this.map.canvas.width = this.width;
207                 this.height = window.innerHeight;
208                 this.map.canvas.height = this.height;
209                 this.map.imageSmoothingEnabled = false;
210         }
211         const chunksWide = Math.ceil(this.width / this.zoom);
212         const chunksHigh = Math.ceil(this.height / this.zoom);
213
214         this.east = this.x + chunksWide / 2; // this is fractional and is used to keep track of the edges of the window
215         this.eastChunk = Math.ceil(this.east); // this is not and is used to track the chunks that need to load
216         this.west = this.x - chunksWide / 2;
217         this.westChunk = Math.floor(this.west);
218         this.north = this.y + chunksHigh / 2;
219         this.northChunk = Math.ceil(this.north);
220         this.south = this.y - chunksHigh / 2;
221         this.southChunk = Math.floor(this.south);
222 };
223
224 /**
225 * dx and dy are the distances in chunks
226 */
227 ViewFrame.prototype.moveCenter = function (dx, dy) {
228         // to pan when we click on the map!
229         this.x += (dx - this.width / 2) / this.zoom;
230         this.y += (dy - this.height / 2) / this.zoom;
231 };
232
233 /**
234 * x and y are positions in chunks
235 */
236 ViewFrame.prototype.setCenter = function (x, y) {
237         this.x = x;
238         this.y = y;
239 };
240
241 /**
242 * x and y are positions in BLOCKS!!!!
243 */
244 ViewFrame.prototype.setBlockwiseCenter = function (x, y) {
245         this.x = x / this.zoom;
246         this.y = y / this.zoom;
247 };
248
249 ViewFrame.prototype.clear = function () {
250         this.loadedChunksByName.clear();
251         this.loadedChunksByCoords.clear();
252         if (this.chunkScript) this.chunkScript.remove();
253         delete this.chunkScript;
254         delete ViewFrame.chunks;
255         this.map.clearRect(0, 0, window.innerWidth, window.innerHeight);
256 };
257
258 function decode(img, cb) {
259         img.decode()
260                 .then(() => {
261                         cb(img);
262                 })
263                 .catch(() => {}); // so images arent added on error
264 }</script></script>
265         <div class="infobox">
266                 <h1>Chunk Info</h1>
267                 <table class="infoboxTable">
268                 </table>
269         </div>
270         <script type="text/javascript">const vf = new ViewFrame();
271 vf.reloadChunkList();
272
273 // the event handlers are in iifes so they dont make unneeded globals.
274 // resize, delay re-render to reduce lag.
275 (function () {
276         var id;
277         window.addEventListener('resize', () => {
278                 clearTimeout(id);
279                 id = setTimeout(() => {
280                         vf.render();
281                 }, 500);
282         });
283 }());
284
285 // panning
286 (function () {
287         var id;
288         vf.map.canvas.addEventListener('mousedown', event => {
289                 clearTimeout(id);
290                 vf.moveCenter(event.pageX, event.pageY);
291                 id = setTimeout(() => {
292                         vf.render();
293                 }, 250);
294         });
295 }());
296
297 // #### CONTROLS ####
298 // hovering
299 (function () {
300         var lastX = 0;
301         var lastY = 0;
302         vf.map.canvas.addEventListener('mousemove', event => {
303                 // only count if the mouse moved more than a chunk
304                 let x = Math.floor(vf.x +
305                         (event.clientX - vf.width / 2) / vf.zoom);
306                 let y = Math.floor(vf.y +
307                         (event.clientY - vf.height / 2) / vf.zoom);
308                 if (x == lastX && y == lastY) return;
309                 lastX = x;
310                 lastY = y;
311                 vf.updateInfobox(x + '_' + y);
312         });
313 }());
314
315 // scroll/zoom
316 (function () {
317         var id;
318         vf.map.canvas.addEventListener('wheel', event => {
319                 clearTimeout(id);
320                 id = setTimeout(() => {
321                         vf.render();
322                 }, 250);
323                 // this makes it so zooming out is faster
324                 let dir = -Math.sign(event.deltaY);
325                 if (vf.zoom < 16) // arbitrary value
326                         vf.zoom += dir;
327                 else
328                         vf.zoom += dir * 4;
329                 if (vf.zoom <= 0)
330                         vf.zoom += 1; // make sure it doesnt go to negative values
331         });
332 }());
333
334 // reload the chunk list every six seconds
335 var devBlockReload = false; // disable via command line
336 (function () {
337         setInterval(() => {
338                 if (devBlockReload) return;
339                 vf.reloadChunkList();
340         }, 6000);
341 }());</script></script>
342
343 </body>
344
345 </html>