* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-var gMapCanvas, gMapContext, gTrackCanvas, gTrackContext, gGeolocation;
+var gMapCanvas, gMapContext, gGLMapCanvas, gTrackCanvas, gTrackContext, gGeolocation;
var gDebug = false;
var gTileSize = 256;
var gMapPrefsLoaded = false;
var gDragging = false;
-var gDragTouchID;
+var gDragTouchID, gPinchStartWidth;
var gGeoWatchID;
var gTrack = [];
function initMap() {
gGeolocation = navigator.geolocation;
+ // Set up canvas contexts. TODO: Remove 2D map once GL support works.
gMapCanvas = document.getElementById("map");
gMapContext = gMapCanvas.getContext("2d");
+ gGLMapCanvas = document.getElementById("glmap");
+ try {
+ // Try to grab the standard context. If it fails, fallback to experimental.
+ // We also try to tell it we do not need a depth buffer.
+ gMap.gl = gGLMapCanvas.getContext("webgl", {depth: false}) ||
+ gGLMapCanvas.getContext("experimental-webgl", {depth: false});
+ }
+ catch(e) {}
+ // If we don't have a GL context, give up now
+ if (!gMap.gl) {
+ showGLWarningDialog();
+ gMap.gl = null;
+ }
gTrackCanvas = document.getElementById("track");
gTrackContext = gTrackCanvas.getContext("2d");
if (!gActiveMap)
}
}
+ gAction.addEventListener("prefload-done", gMap.initGL, false);
+
console.log("map vars set, loading prefs...");
loadPrefs();
}
gLoadingTile = new Image();
gLoadingTile.src = "style/loading.png";
gLoadingTile.onload = function() {
- var throwEv = new CustomEvent("mapinit-done");
+ var throwEv = new CustomEvent("prefload-done");
gAction.dispatchEvent(throwEv);
};
}
gPrefs.get("position", function(aValue) {
if (aValue) {
gPos = aValue;
- gWaitCounter--;
}
+ gWaitCounter--;
+ var throwEv = new CustomEvent("prefs-step");
+ gAction.dispatchEvent(throwEv);
});
gWaitCounter++;
gPrefs.get("center_map", function(aValue) {
gAction.dispatchEvent(throwEv);
});
gWaitCounter++;
- gTrackStore.getList(function(aTPoints) {
- if (gDebug)
- console.log(aTPoints.length + " points loaded.");
- if (aTPoints.length) {
- gTrack = aTPoints;
+ var trackLoadStarted = false;
+ var redrawBase = 100;
+ gTrackStore.getListStepped(function(aTPoint) {
+ if (aTPoint) {
+ // Add in front and return new length.
+ var tracklen = gTrack.unshift(aTPoint);
+ // Redraw track periodically, larger distance the longer it gets.
+ // Initial paint will do initial track drawing.
+ if (tracklen % redrawBase == 0) {
+ drawTrack();
+ redrawBase = tracklen;
+ }
+ }
+ else {
+ // Last point received.
+ drawTrack();
+ }
+ if (!trackLoadStarted) {
+ // We have the most recent point, if present, rest will load async.
+ trackLoadStarted = true;
+ gWaitCounter--;
+ var throwEv = new CustomEvent("prefs-step");
+ gAction.dispatchEvent(throwEv);
}
- gWaitCounter--;
- var throwEv = new CustomEvent("prefs-step");
- gAction.dispatchEvent(throwEv);
});
}
}
+var gMap = {
+ gl: null,
+ glShaderProgram: null,
+ glVertexPositionAttr: null,
+ glTextureCoordAttr: null,
+ glResolutionAttr: null,
+ glMapTexture: null,
+
+ getVertShaderSource: function() {
+ return 'attribute vec2 aVertexPosition;\n' +
+ 'attribute vec2 aTextureCoord;\n\n' +
+ 'uniform vec2 uResolution;\n\n' +
+ 'varying highp vec2 vTextureCoord;\n\n' +
+ 'void main(void) {\n' +
+ // convert the rectangle from pixels to -1.0 to +1.0 (clipspace) 0.0 to 1.0
+ ' vec2 clipSpace = aVertexPosition * 2.0 / uResolution - 1.0;\n' +
+ ' gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);\n' +
+ ' vTextureCoord = aTextureCoord;\n' +
+ '}'; },
+ getFragShaderSource:function() {
+ return 'varying highp vec2 vTextureCoord;\n\n' +
+ 'uniform sampler2D uImage;\n\n' +
+ 'void main(void) {\n' +
+ ' gl_FragColor = texture2D(uImage, vTextureCoord);\n' +
+ '}'; },
+
+ initGL: function() {
+ // When called from the event listener, the "this" reference doesn't work, so use the object name.
+ if (gMap.gl) {
+ gMap.gl.viewport(0, 0, gMap.gl.drawingBufferWidth, gMap.gl.drawingBufferHeight);
+ gMap.gl.clearColor(0.0, 0.0, 0.0, 0.5); // Set clear color to black, fully opaque.
+ gMap.gl.clear(gMap.gl.COLOR_BUFFER_BIT|gMap.gl.DEPTH_BUFFER_BIT); // Clear the color.
+
+ // Create and initialize the shaders.
+ var vertShader = gMap.gl.createShader(gMap.gl.VERTEX_SHADER);
+ var fragShader = gMap.gl.createShader(gMap.gl.FRAGMENT_SHADER);
+ gMap.gl.shaderSource(vertShader, gMap.getVertShaderSource());
+ // Compile the shader program.
+ gMap.gl.compileShader(vertShader);
+ // See if it compiled successfully.
+ if (!gMap.gl.getShaderParameter(vertShader, gMap.gl.COMPILE_STATUS)) {
+ console.log("An error occurred compiling the vertex shader: " + gMap.gl.getShaderInfoLog(vertShader));
+ return null;
+ }
+ gMap.gl.shaderSource(fragShader, gMap.getFragShaderSource());
+ // Compile the shader program.
+ gMap.gl.compileShader(fragShader);
+ // See if it compiled successfully.
+ if (!gMap.gl.getShaderParameter(fragShader, gMap.gl.COMPILE_STATUS)) {
+ console.log("An error occurred compiling the fragment shader: " + gMap.gl.getShaderInfoLog(fragShader));
+ return null;
+ }
+
+ gMap.glShaderProgram = gMap.gl.createProgram();
+ gMap.gl.attachShader(gMap.glShaderProgram, vertShader);
+ gMap.gl.attachShader(gMap.glShaderProgram, fragShader);
+ gMap.gl.linkProgram(gMap.glShaderProgram);
+ // If creating the shader program failed, alert
+ if (!gMap.gl.getProgramParameter(gMap.glShaderProgram, gMap.gl.LINK_STATUS)) {
+ alert("Unable to initialize the shader program.");
+ }
+ gMap.gl.useProgram(gMap.glShaderProgram);
+ // Get locations of the attributes.
+ gMap.glVertexPositionAttr = gMap.gl.getAttribLocation(gMap.glShaderProgram, "aVertexPosition");
+ gMap.glTextureCoordAttr = gMap.gl.getAttribLocation(gMap.glShaderProgram, "aTextureCoord");
+ gMap.glResolutionAttr = gMap.gl.getUniformLocation(gMap.glShaderProgram, "uResolution");
+
+ var tileVerticesBuffer = gMap.gl.createBuffer();
+ gMap.gl.bindBuffer(gMap.gl.ARRAY_BUFFER, tileVerticesBuffer);
+ // The vertices are the coordinates of the corner points of the square.
+ var vertices = [
+ 0.0, 0.0,
+ 1.0, 0.0,
+ 0.0, 1.0,
+ 0.0, 1.0,
+ 1.0, 0.0,
+ 1.0, 1.0,
+ ];
+ gMap.gl.bufferData(gMap.gl.ARRAY_BUFFER, new Float32Array(vertices), gMap.gl.STATIC_DRAW);
+ gMap.gl.enableVertexAttribArray(gMap.glTextureCoordAttr);
+ gMap.gl.vertexAttribPointer(gMap.glTextureCoordAttr, 2, gMap.gl.FLOAT, false, 0, 0);
+
+ // Map Texture
+ gMap.glMapTexture = gMap.gl.createTexture();
+ gMap.gl.activeTexture(gMap.gl.TEXTURE0);
+ gMap.gl.bindTexture(gMap.gl.TEXTURE_2D, gMap.glMapTexture);
+ gMap.gl.uniform1i(gMap.gl.getUniformLocation(gMap.glShaderProgram, "uImage"), 0);
+ // 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);
+ // Upload the image into the texture.
+ gMap.gl.texImage2D(gMap.gl.TEXTURE_2D, 0, gMap.gl.RGBA, gMap.gl.RGBA, gMap.gl.UNSIGNED_BYTE, gLoadingTile);
+
+ gMap.gl.uniform2f(gMap.glResolutionAttr, gGLMapCanvas.width, gGLMapCanvas.height);
+
+ // Create a buffer for the position of the rectangle corners.
+ var mapVerticesTextureCoordBuffer = gMap.gl.createBuffer();
+ gMap.gl.bindBuffer(gMap.gl.ARRAY_BUFFER, mapVerticesTextureCoordBuffer);
+ gMap.gl.enableVertexAttribArray(gMap.glVertexPositionAttr);
+ gMap.gl.vertexAttribPointer(gMap.glVertexPositionAttr, 2, gMap.gl.FLOAT, false, 0, 0);
+ }
+
+ var throwEv = new CustomEvent("mapinit-done");
+ gAction.dispatchEvent(throwEv);
+ },
+
+ drawGL: function() {
+ if (!gMap.gl) { return; }
+
+ var x_start = 0;
+ var i_width = 256;
+ var y_start = 10;
+ var i_height = 256;
+ var textureCoordinates = [
+ x_start, y_start,
+ x_start + i_width, y_start,
+ x_start, y_start + i_height,
+ x_start, y_start + i_height,
+ x_start + i_width, y_start,
+ x_start + i_width, y_start + i_height,
+ ];
+ gMap.gl.bufferData(gMap.gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gMap.gl.STATIC_DRAW);
+
+ // There are 6 indices in textureCoordinates.
+ gMap.gl.drawArrays(gMap.gl.TRIANGLES, 0, 6);
+
+ var x_start = 400;
+ var i_width = 256;
+ var y_start = 10;
+ var i_height = 256;
+ var textureCoordinates = [
+ x_start, y_start,
+ x_start + i_width, y_start,
+ x_start, y_start + i_height,
+ x_start, y_start + i_height,
+ x_start + i_width, y_start,
+ x_start + i_width, y_start + i_height,
+ ];
+ gMap.gl.bufferData(gMap.gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gMap.gl.STATIC_DRAW);
+ // gMap.gl.bindTexture(gMap.gl.TEXTURE_2D, gMap.glMapTexture);
+
+ // There are 6 indices in textureCoordinates.
+ gMap.gl.drawArrays(gMap.gl.TRIANGLES, 0, 6);
+
+ },
+
+ resizeAndDrawGL: function() {
+ if (!gMap.gl) { return; }
+
+ gMap.gl.viewport(0, 0, gMap.gl.drawingBufferWidth, gMap.gl.drawingBufferHeight);
+ gMap.gl.clear(gMap.gl.COLOR_BUFFER_BIT); // Clear the color.
+ gMap.gl.uniform2f(gMap.glResolutionAttr, gGLMapCanvas.width, gGLMapCanvas.height);
+ gMap.drawGL();
+ },
+}
+
function resizeAndDraw() {
var viewportWidth = Math.min(window.innerWidth, window.outerWidth);
var viewportHeight = Math.min(window.innerHeight, window.outerHeight);
- if (gMapCanvas && gTrackCanvas) {
+ if (gMapCanvas && gGLMapCanvas && gTrackCanvas) {
gMapCanvas.width = viewportWidth;
gMapCanvas.height = viewportHeight;
+ gGLMapCanvas.width = viewportWidth;
+ gGLMapCanvas.height = viewportHeight;
gTrackCanvas.width = viewportWidth;
gTrackCanvas.height = viewportHeight;
drawMap();
+ gMap.resizeAndDrawGL();
showUI();
}
}
}
}
}
+ drawTrack();
+}
+
+function drawTrack() {
gLastDrawnPoint = null;
gCurPosMapCache = undefined;
gTrackContext.clearRect(0, 0, gTrackCanvas.width, gTrackCanvas.height);
handleEvent: function(aEvent) {
var touchEvent = aEvent.type.indexOf('touch') != -1;
+ if (touchEvent) {
+ aEvent.stopPropagation();
+ }
+
// Bail out if the event is happening on an input.
if (aEvent.target.tagName.toLowerCase() == "input")
return;
case "mousedown":
case "touchstart":
if (touchEvent) {
+ if (aEvent.targetTouches.length == 2) {
+ gPinchStartWidth = Math.sqrt(
+ Math.pow(aEvent.targetTouches.item(1).clientX -
+ aEvent.targetTouches.item(0).clientX, 2) +
+ Math.pow(aEvent.targetTouches.item(1).clientY -
+ aEvent.targetTouches.item(0).clientY, 2)
+ );
+ }
gDragTouchID = aEvent.changedTouches.item(0).identifier;
coordObj = aEvent.changedTouches.identifiedTouch(gDragTouchID);
}
break;
case "mousemove":
case "touchmove":
+ if (touchEvent && aEvent.targetTouches.length == 2) {
+ curPinchStartWidth = Math.sqrt(
+ Math.pow(aEvent.targetTouches.item(1).clientX -
+ aEvent.targetTouches.item(0).clientX, 2) +
+ Math.pow(aEvent.targetTouches.item(1).clientY -
+ aEvent.targetTouches.item(0).clientY, 2)
+ );
+ if (!gPinchStartWidth)
+ gPinchStartWidth = curPinchStartWidth;
+
+ if (gPinchStartWidth / curPinchStartWidth > 1.7 ||
+ gPinchStartWidth / curPinchStartWidth < 0.6) {
+ var newZoomLevel = gPos.z + (gPinchStartWidth < curPinchStartWidth ? 1 : -1);
+ if ((newZoomLevel >= 0) && (newZoomLevel <= gMaxZoom)) {
+ // Calculate new center of the map - preserve middle of pinch.
+ // This means that pixel distance between old center and middle
+ // must equal pixel distance of new center and middle.
+ var x = (aEvent.targetTouches.item(1).clientX +
+ aEvent.targetTouches.item(0).clientX) / 2 -
+ gMapCanvas.offsetLeft;
+ var y = (aEvent.targetTouches.item(1).clientY +
+ aEvent.targetTouches.item(0).clientY) / 2 -
+ gMapCanvas.offsetTop;
+
+ // Zoom factor after this action.
+ var newZoomFactor = Math.pow(2, gMaxZoom - newZoomLevel);
+ gPos.x -= (x - gMapCanvas.width / 2) * (newZoomFactor - gZoomFactor);
+ gPos.y -= (y - gMapCanvas.height / 2) * (newZoomFactor - gZoomFactor);
+
+ if (gPinchStartWidth < curPinchStartWidth)
+ zoomIn();
+ else
+ zoomOut();
+
+ // Reset pinch start width and start another pinch gesture.
+ gPinchStartWidth = null;
+ }
+ }
+ // If we are in a pinch, do not drag.
+ break;
+ }
var x = coordObj.clientX - gMapCanvas.offsetLeft;
var y = coordObj.clientY - gMapCanvas.offsetTop;
if (gDragging === true) {
break;
case "mouseup":
case "touchend":
+ gPinchStartWidth = null;
gDragging = false;
showUI();
break;
function clearTrack() {
gTrack = [];
gTrackStore.clear();
- drawMap({left: 0, right: 0, top: 0, bottom: 0});
+ drawTrack();
}
var gTileService = {