remove separate tile indexes array and use a direct tilekey->texture array instead
[lantea.git] / js / map.js
index 12767698fa5060cf9b0b849ad3a91db20eb6ebd3..caa457c6f79a93fa8bbd3c1963ad2a4e89e30230 100644 (file)
--- a/js/map.js
+++ b/js/map.js
@@ -203,7 +203,6 @@ var gMap = {
   glResolutionAttr: null,
   glMapTexture: null,
   glTextures: {},
-  glTextureKeys: {},
 
   activeMap: "osm_mapnik",
   tileSize: 256,
@@ -292,7 +291,7 @@ var gMap = {
       gMap.gl.enableVertexAttribArray(gMap.glTextureCoordAttr);
       gMap.gl.vertexAttribPointer(gMap.glTextureCoordAttr, 2, gMap.gl.FLOAT, false, 0, 0);
 
-      gMap.loadImageToTexture(gLoadingTile, 0, "loading::0,0,0");
+      gMap.loadImageToTexture(gLoadingTile, getTileKey("loading", {x: 0, y: 0, z: 0}));
 
       gMap.gl.uniform2f(gMap.glResolutionAttr, gGLMapCanvas.width, gGLMapCanvas.height);
 
@@ -307,23 +306,13 @@ var gMap = {
     gAction.dispatchEvent(throwEv);
   },
 
-  draw: function(aPixels, aOverdraw) {
-    gMap.drawGL(aPixels, aOverdraw);
+  draw: function() {
+    gMap.assembleGL();
     drawTrack();
   },
 
-  drawGL: function(aPixels, aOverdraw) {
+  assembleGL: function() {
     if (!gMap.gl) { return; }
-    // aPixels is an object with left/right/top/bottom members telling how many
-    //   pixels on the borders should actually be drawn.
-    // aOverdraw is a bool that tells if we should draw placeholders or draw
-    //   straight over the existing content.
-    // XXX: Both those optimizations are OFF for GL right now!
-    //if (!aPixels)
-      aPixels = {left: gMap.gl.drawingBufferWidth, right: gMap.gl.drawingBufferWidth,
-                 top: gMap.gl.drawingBufferHeight, bottom: gMap.gl.drawingBufferHeight};
-    if (!aOverdraw)
-      aOverdraw = false;
 
     document.getElementById("zoomLevel").textContent = gMap.pos.z;
     gMap.zoomFactor = Math.pow(2, gMap.maxZoom - gMap.pos.z);
@@ -339,59 +328,61 @@ var gMap = {
     if (gMapPrefsLoaded && mainDB)
       gPrefs.set("position", gMap.pos);
 
-    var tiles = {left: Math.ceil((xMin + aPixels.left * gMap.zoomFactor) / size) -
-                                 (aPixels.left ? 0 : 1),
-                 right: Math.floor((xMax - aPixels.right * gMap.zoomFactor) / size) -
-                                   (aPixels.right ? 1 : 0),
-                 top: Math.ceil((yMin + aPixels.top * gMap.zoomFactor) / size) -
-                                (aPixels.top ? 0 : 1),
-                 bottom: Math.floor((yMax - aPixels.bottom * gMap.zoomFactor) / size) -
-                                    (aPixels.bottom ? 1 : 0)};
-
     // Go through all the tiles in the map, find out if to draw them and do so.
     for (var x = Math.floor(xMin / size); x < Math.ceil(xMax / size); x++) {
       for (var y = Math.floor(yMin / size); y < Math.ceil(yMax / size); y++) { // slow script warnings on the tablet appear here!
-        // Only go to the drawing step if we need to draw this tile.
-        if (x < tiles.left || x > tiles.right ||
-            y < tiles.top || y > tiles.bottom) {
-          // Round here is **CRUCIAL** otherwise the images are filtered
-          // and the performance sucks (more than expected).
-          var xoff = Math.round((x * size - xMin) / gMap.zoomFactor);
-          var yoff = Math.round((y * size - yMin) / gMap.zoomFactor);
-          // Draw placeholder tile unless we overdraw.
-          if (!aOverdraw &&
-              (x < tiles.left -1  || x > tiles.right + 1 ||
-              y < tiles.top -1 || y > tiles.bottom + 1)) {
-            gMap.drawTileGL(xoff, yoff, 0);
-          }
+        // Only go to loading step if we haven't loaded the texture.
+        var coords = {x: x, y: y, z: gMap.pos.z};
+        var tileKey = getTileKey(gMap.activeMap, normalizeCoords(coords));
+        if (!gMap.glTextures[tileKey]) {
           // Initiate loading/drawing of the actual tile.
-          gTileService.get(gMap.activeMap, {x: x, y: y, z: gMap.pos.z},
-                          function(aImage, aStyle, aCoords, aTileKey) {
-            // Only draw if this applies for the current view.
+          gTileService.get(gMap.activeMap, coords,
+                           function(aImage, aStyle, aCoords, aTileKey) {
+            // Only actually load if this still applies for the current view.
             if ((aStyle == gMap.activeMap) && (aCoords.z == gMap.pos.z)) {
-              var ixMin = gMap.pos.x - wid / 2;
-              var iyMin = gMap.pos.y - ht / 2;
-              var ixoff = Math.round((aCoords.x * size - ixMin) / gMap.zoomFactor);
-              var iyoff = Math.round((aCoords.y * size - iyMin) / gMap.zoomFactor);
               var URL = window.URL;
               var imgURL = URL.createObjectURL(aImage);
               var imgObj = new Image();
-              imgObj.src = imgURL;
               imgObj.onload = function() {
-                // TODO: Do actually paint in a separate function, called on requestAnimationFrame.
-                var txIndex = gMap.glTextureKeys[aTileKey];
-                if (!txIndex) {
-                  txIndex = Object.keys(gMap.glTextureKeys).length;
-                  gMap.loadImageToTexture(imgObj, txIndex, aTileKey);
-                }
-                gMap.drawTileGL(ixoff, iyoff, txIndex);
+                gMap.loadImageToTexture(imgObj, aTileKey);
+                requestAnimationFrame(function(aTimestamp) { gMap.drawGL() });
                 URL.revokeObjectURL(imgURL);
               }
+              imgObj.src = imgURL;
             }
           });
         }
       }
     }
+    requestAnimationFrame(function(aTimestamp) { gMap.drawGL() });
+  },
+
+  drawGL: function() {
+    var wid = gMap.gl.drawingBufferWidth * gMap.zoomFactor; // Width in level 18 pixels.
+    var ht = gMap.gl.drawingBufferHeight * gMap.zoomFactor; // Height in level 18 pixels.
+    var size = gMap.tileSize * gMap.zoomFactor; // Tile size in level 18 pixels.
+
+    var xMin = gMap.pos.x - wid / 2; // Corners of the window in level 18 pixels.
+    var yMin = gMap.pos.y - ht / 2;
+    var xMax = gMap.pos.x + wid / 2;
+    var yMax = gMap.pos.y + ht / 2;
+
+    // Go through all the tiles in the map, find out if to draw them and do so.
+    for (var x = Math.floor(xMin / size); x < Math.ceil(xMax / size); x++) {
+      for (var y = Math.floor(yMin / size); y < Math.ceil(yMax / size); y++) { // slow script warnings on the tablet appear here!
+        // Round here is **CRUCIAL** otherwise the images are filtered
+        // and the performance sucks (more than expected).
+        var xoff = Math.round((x * size - xMin) / gMap.zoomFactor);
+        var yoff = Math.round((y * size - yMin) / gMap.zoomFactor);
+        // Draw the tile, first find out the actual texture to use.
+        var norm = normalizeCoords({x: x, y: y, z: gMap.pos.z});
+        var tileKey = getTileKey(gMap.activeMap, norm);
+        if (!gMap.glTextures[tileKey]) {
+          tileKey = getTileKey("loading", {x: 0, y: 0, z: 0});
+        }
+        gMap.drawTileGL(xoff, yoff, tileKey);
+      }
+    }
   },
 
   resizeAndDraw: function() {
@@ -415,9 +406,11 @@ var gMap = {
     }
   },
 
-  drawTileGL: function(aLeft, aRight, aTextureIndex) {
-    gMap.gl.activeTexture(gMap.gl.TEXTURE0 + aTextureIndex);
-    gMap.gl.bindTexture(gMap.gl.TEXTURE_2D, gMap.glTextures[aTextureIndex]);
+  drawTileGL: function(aLeft, aRight, aTileKey) {
+    gMap.gl.activeTexture(gMap.gl.TEXTURE0);
+    gMap.gl.bindTexture(gMap.gl.TEXTURE_2D, gMap.glTextures[aTileKey]);
+    // Set uImage to refer to TEXTURE0
+    gMap.gl.uniform1i(gMap.gl.getUniformLocation(gMap.glShaderProgram, "uImage"), 0);
     var x_start = aLeft;
     var i_width = gMap.tileSize;
     var y_start = aRight;
@@ -436,13 +429,11 @@ var gMap = {
     gMap.gl.drawArrays(gMap.gl.TRIANGLES, 0, 6);
   },
 
-  loadImageToTexture: function(aImage, aTextureIndex, aTileKey) {
-    gMap.glTextureKeys[aTileKey] = aTextureIndex;
+  loadImageToTexture: function(aImage, aTileKey) {
+    // TODO: Get rid of old textures.
     // Create and bind texture.
-    gMap.glTextures[aTextureIndex] = gMap.gl.createTexture();
-    gMap.gl.activeTexture(gMap.gl.TEXTURE0 + aTextureIndex);
-    gMap.gl.bindTexture(gMap.gl.TEXTURE_2D, gMap.glTextures[aTextureIndex]);
-    gMap.gl.uniform1i(gMap.gl.getUniformLocation(gMap.glShaderProgram, "uImage"), 0);
+    gMap.glTextures[aTileKey] = gMap.gl.createTexture();
+    gMap.gl.bindTexture(gMap.gl.TEXTURE_2D, gMap.glTextures[aTileKey]);
     // Set params for how the texture minifies and magnifies (wrap params are not needed as we're power-of-two).
     gMap.gl.texParameteri(gMap.gl.TEXTURE_2D, gMap.gl.TEXTURE_MIN_FILTER, gMap.gl.NEAREST);
     gMap.gl.texParameteri(gMap.gl.TEXTURE_2D, gMap.gl.TEXTURE_MAG_FILTER, gMap.gl.NEAREST);
@@ -518,6 +509,13 @@ function normalizeCoords(aCoords) {
           z: aCoords.z};
 }
 
+function getTileKey(aStyle, aNormalizedCoords) {
+  return aStyle + "::" +
+         aNormalizedCoords.x + "," +
+         aNormalizedCoords.y + "," +
+         aNormalizedCoords.z;
+}
+
 // Returns true if the tile is outside the current view.
 function isOutsideWindow(t) {
   var pos = decodeIndex(t);
@@ -734,22 +732,7 @@ var mapEvHandler = {
           var dY = y - gLastMouseY;
           gMap.pos.x -= dX * gMap.zoomFactor;
           gMap.pos.y -= dY * gMap.zoomFactor;
-          if (false) { // use optimized path
-            /* TODO: investigate optimized path for GL - code below was 2D.
-            var mapData = gMapContext.getImageData(0, 0,
-                                                   gMapCanvas.width,
-                                                   gMapCanvas.height);
-            gMapContext.clearRect(0, 0, gMapCanvas.width, gMapCanvas.height);
-            gMapContext.putImageData(mapData, dX, dY);
-            gMap.draw({left: (dX > 0) ? dX : 0,
-                     right: (dX < 0) ? -dX : 0,
-                     top: (dY > 0) ? dY : 0,
-                     bottom: (dY < 0) ? -dY : 0});
-            */
-          }
-          else {
-            gMap.draw(false, true);
-          }
+          gMap.draw();
           showUI();
         }
         gLastMouseX = x;
@@ -870,22 +853,7 @@ var mapEvHandler = {
         if (dX || dY) {
           gMap.pos.x -= dX * gMap.zoomFactor;
           gMap.pos.y -= dY * gMap.zoomFactor;
-          if (false) { // use optimized path
-            /* TODO: investigate optimized path for GL - code below was 2D.
-            var mapData = gMapContext.getImageData(0, 0,
-                                                   gMapCanvas.width,
-                                                   gMapCanvas.height);
-            gMapContext.clearRect(0, 0, gMapCanvas.width, gMapCanvas.height);
-            gMapContext.putImageData(mapData, dX, dY);
-            gMap.draw({left: (dX > 0) ? dX : 0,
-                     right: (dX < 0) ? -dX : 0,
-                     top: (dY > 0) ? dY : 0,
-                     bottom: (dY < 0) ? -dY : 0});
-            */
-          }
-          else {
-            gMap.draw(false, true);
-          }
+          gMap.draw();
         }
         break;
     }
@@ -1016,7 +984,7 @@ var gTileService = {
 
   get: function(aStyle, aCoords, aCallback) {
     var norm = normalizeCoords(aCoords);
-    var dbkey = aStyle + "::" + norm.x + "," + norm.y + "," + norm.z;
+    var dbkey = getTileKey(aStyle, norm);
     this.getDBCache(dbkey, function(aResult, aEvent) {
       if (aResult) {
         // We did get a cached object.