3 <title>Tilerender</title>
5 </head><body onload="script()" style="width: 100%; max-height: 600px; overflow: hidden; padding: 0 auto;" >
6 <canvas id="view" width="1280" height="600" style="padding: 0 auto; max-height: 600px;">
7 Canvas not supported<br/>
8 <img id="maptiles" src="tileset.png" width="64" style="overflow: hidden;" />
10 <tile position="0" name="air" fluid="1"></tile>
11 <tile position="1" name="cursor" animated=".125"></tile>
12 <tile position="2" name="grass" animated=".0625"></tile>
13 <tile position="3" name="parquet" elevation=".125"></tile>
14 <tile position="4" name="sand"></tile>
15 <tile position="5" name="dirt" blocking="1"></tile>
16 <tile position="6" name="brick" blocking="1"></tile>
17 <tile position="7" name="water" animated="1" fluid=".5"></tile>
19 <img id="character" src="character.png" speed="4"/>
20 <div id="map" columns="32" rows="32">
21 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
22 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
23 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
24 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
25 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
26 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
27 6, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
28 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
29 6, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
30 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
31 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
32 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
33 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
34 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
35 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
36 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
37 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
38 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
39 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
40 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
41 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
42 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
43 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
44 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
45 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
46 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
47 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
48 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
49 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
50 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
51 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5,
52 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
54 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
55 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
56 2, 2, 2, 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
57 2, 2, 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
58 2, 2, 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
59 2, 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
60 2, 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
61 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
62 7, 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
63 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
64 7, 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, 4, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
65 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, 4, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
66 7, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 4, 4, 4, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
67 2, 2, 2, 2, 7, 2, 2, 2, 2, 2, 2, 2, 6, 3, 4, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
68 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
69 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
70 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 6, 4, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
71 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
72 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
73 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
74 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
75 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
76 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
77 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
78 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
79 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
80 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
81 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
82 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
83 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
84 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
85 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
89 var gravity = 9.81, fps = 30;
90 view = document.getElementById("view");
91 canvas = document.getElementById("view").getContext("2d");
94 rows: document.getElementById("map").getAttribute("rows") * 1 || 1,
95 cols: document.getElementById("map").getAttribute("columns") * 1 || 1,
96 data: document.getElementById("map").textContent.split(',') || [],
97 tiles: document.getElementById("maptiles"),
99 default_props: {animated: false, elevation: 0, blocking: false, fluid: false},
101 tw: 32, th: 16, sw: 16, sh: 8,
102 offset_x: 0, offset_y: 0,
105 this.tw = this.tiles.getAttribute("width") * 1 || 32;
106 this.th = this.tiles.getAttribute("height") * 1 || this.tw / 2;
107 this.sw = this.tw / 2;
108 this.sh = this.th / 2;
110 this.gravity = gravity / fps / Math.sqrt(this.sw * this.sw + this.sh * this.sh) * this.sh;
114 for (i in this.data) this.data[i] = this.data[i] * 1 || 0; // make numeric
116 for (prop of document.querySelectorAll("tiles > tile")) {
117 i = prop.getAttribute("position") * 1;
118 if (i || i == 0) this.tileprops[i] = {
119 animated: prop.getAttribute("animated") || this.default_props.animated,
120 elevation: prop.getAttribute("elevation") * 1 || this.default_props.elevation,
121 blocking: prop.getAttribute("blocking") || this.default_props.blocking,
122 fluid: prop.getAttribute("fluid") || this.default_props.fluid
124 if (!this.tileprops[0]) this.tileprops[0] = { animated: false, elevation: 0, blocking: false, fluid: 1 }
128 props_of: function(n) { return this.tileprops[this.data[n]] || this.tileprops[0]; },
130 tile_at: function(x, y, z) {
131 nx = (y - 48) / 32 + (x) / 64 |0;
132 ny = (y - 48) / 32 - (x) / 64 |0;
134 return ny * this.cols + nx + (z|0) * this.rows * this.cols;
138 tile_at: function(x, y, z) {
139 var my = (y / this.sh |0);
140 var mx = (x / this.sw |0);
142 switch(((my % 2) << 1) + mx % 2) {
143 case 0: my += o = (y % this.sh * 2 > this.sw - x % this.sw); break;
144 case 1: my += o = (y % this.sh * 2 > x % this.sw); mx -= o; break;
145 case 2: my += o = (y % this.sh * 2 > x % this.sw); break;
146 case 3: my += o = (y % this.sh * 2 > this.sw - x % this.sw); mx += o - 1; break;
148 mx -= x / this.tw |0;
150 return this.cols * my + mx + (z |0) * this.rows * this.cols;
155 var px = player.n % this.cols;
156 var x, y, z = 0, n, tile, props;
158 for ( y = 0; y < this.cols * 2; ++y) {
159 xw = (y < this.cols) ? y : this.cols * 2 - y;
160 for (z = 0; z < this.data.length / (this.rows * this.cols); ++z)
161 for ( x = 0; x < xw; ++x) {
162 n = (y <= this.cols) ? x + (y - x) * map.cols : (this.rows - x) * this.cols + (y - this.cols) + x;
163 n = n - this.cols + z * this.cols * this.rows;
164 tile = ( n == ptr ) ? 1 : this.data[n];
165 props = this.tileprops[tile] || this.default_props;
167 canvas.drawImage( this.tiles,
168 this.tw * tile, 2 * this.th * ((props.animated * frame |0) % 8),
169 this.tw, 2 * this.th,
170 - xw * this.sw + x * this.tw - this.offset_x,
171 y * this.sh - this.offset_y - z * this.th,
174 if (player.n == n) player.draw();
179 draw: function(){ // map.draw
180 var py = player.n / this.cols % this.rows |0;
181 var px = player.n % this.cols;
182 var x, y, z, n, tile, props;
184 for (y = 0; y < this.rows; ++y) {
185 for (z = 0; z < this.data.length / (this.rows * this.cols); ++z)
186 for (x = 0; x < this.cols; ++x) {
187 n = x + y * map.cols + z * map.rows * map.cols;
188 tile = ( n == ptr ) ? 1 : this.data[n];
189 props = this.tileprops[tile] || this.default_props;
191 canvas.globalAlpha = ( y > py &&
192 (z > player.z + 1 || z == (player.z |0) && props.blocking) &&
199 canvas.drawImage( this.tiles,
200 this.tw * tile, 2 * this.th * ((props.animated * frame |0) % 8),
201 this.tw, 2 * this.th,
202 x * this.tw + y % 2 * this.sw - this.sw - this.offset_x,
203 y * this.sh - z * this.th - 3 * this.sh - this.offset_y,
216 sprite: document.getElementById("character"),
219 this.w = this.sprite.getAttribute("width") * 1 || map.tw || 32;
220 this.h = this.sprite.getAttribute("height") * 1 || map.sh * 6 || this.w * 1.5;
221 this.o = this.sprite.getAttribute("offset") * 1 || map.sh || this.w / 4;
222 this.ospeed = this.speed = this.sprite.getAttribute("speed") * 1|| this.w / 16;
223 this.n = map.tile_at(this.x, this.y, this.z);
224 map.offset_x = this.x - view.clientWidth / 2 |0;
225 map.offset_y = this.y - view.clientHeight / 2 |0;
228 move: function(){ // player.move
232 case 'n': dy = -this.speed; break;
233 case 's': dy = this.speed; break;
234 case 'w': dx = -this.speed; break;
235 case 'e': dx = this.speed; break;
236 case 'nw': dy = -.45 * this.speed; dx = -.9 * this.speed; break;
237 case 'ne': dy = -.45 * this.speed; dx = .9 * this.speed; break;
238 case 'sw': dy = .45 * this.speed; dx = -.9 * this.speed; break;
239 case 'se': dy = .45 * this.speed; dx = .9 * this.speed; break;
242 if (player.frame % 6 > 2) { dx *= 1.375; dy *= 1.375; }
243 else { dx *= .625; dy *= .625; }
245 var n = map.tile_at(this.x + dx, this.y + dy, this.z);
246 var prop = map.props_of(n), head = map.props_of(n + map.rows * map.cols);
248 if (prop.fluid) { dx *= prop.fluid; dy *= prop.fluid; }
249 else if (head.fluid) { dx *= head.fluid; dy *= head.fluid; }
251 if (!prop.blocking && head.fluid) {
252 this.x += dx; this.y += dy; this.n = n;
255 this.frame += this.frame < 255 ? 1 : -127;
257 map.offset_x = this.x - view.clientWidth / 2 |0;
258 map.offset_y = this.y - view.clientHeight / 2 |0;
259 }, // end player.move
263 var props = map.props_of(this.n);
264 var bottom = map.props_of(this.n - map.rows * map.cols);
265 var zbase = (this.n / (map.rows * map.cols) |0) + props.elevation;
267 this.z += this.zspeed -= map.gravity;
269 if (this.z > zbase) {
270 this.speed = this.ospeed * 2;
271 } else if (props.fluid && !bottom.blocking) {
272 this.n -= map.rows * map.cols;
274 this.speed = this.ospeed;
279 }, // end player.fall
282 draw: function(){ // player.draw
283 var face = 0, state = 0;
286 case 's': face = 0; break;
287 case 'sw': face = 1; break;
288 case 'w': face = 2; break;
289 case 'nw': face = 3; break;
290 case 'n': face = 4; break;
291 case 'ne': face = 5; break;
292 case 'e': face = 6; break;
293 case 'se': face = 7; break;
297 (this.frame > 11) ? frame / 16 % 2 |0 :
298 (this.frame < 12) ? this.frame / 3 + 2 |0 :
301 canvas.drawImage( this.sprite, this.w * face, this.h * state,
303 this.x - this.w / 2 - map.offset_x,
304 this.y - this.h + this.o - map.offset_y - this.z * map.th,
315 function get_input(){
317 if ( keys[87] || keys[38] ) d = 'n';
318 if ( keys[83] || keys[40] ) d = 's';
319 if ( keys[65] || keys[37] ) d += 'w';
320 if ( keys[68] || keys[39] ) d += 'e';
322 if ( d == '' ) player.frame = 12
325 if ( player.frame > 11 ) player.frame = 0;
330 if (keys[32] && player.zspeed == 0) {player.zspeed += .75; player.z +=.0001;}
337 setInterval(function(){
341 frame += frame < 255 ? 1 : -255;
344 window.addEventListener('keydown', function(x){ keys[x.keyCode] = true; }, false );
345 window.addEventListener('keyup', function(x){ keys[x.keyCode] = false;}, false );
347 window.addEventListener('mousemove', function(e){
348 px = view.width * (e.clientX - view.offsetLeft) / view.clientWidth |0;
349 py = view.height * (e.clientY - view.offsetTop) / view.clientHeight |0;
351 ptr = map.tile_at(px + map.offset_x, py + map.offset_y, 0);