<commit_before><!DOCTYPE html>
<html lang="en">
    <head>
        <title>Reconstruction Viewer</title>
        <meta charset="utf-8">
        <style>
            body {
                margin: 0px;
            }
            #info {
                position: absolute;
                top: 0px;
                left: 10px;
                background-color: #000;
            }
            #image {
                width: 320px;
                height: 240px;
            }
            #text {
                text-align: center;
                font-family: sans-serif;
                color: #FFF;
            }
            #navigation {
                position: absolute;
                top: 0px;
                left: 10px;
                background-color: #AAA;
                display: none;
                font-family: sans-serif;
                font-size: 30px;
            }
        </style>
    </head>
    <body>
        <div id="ThreeJS" target="_blank"></div>
        <div id="info">
            <img id="image">
            <div id="text"></div>
        </div>
        <div id="navigation">
            <table>
                <tr>
                    <td id="nav-turn-left">
                        <button onclick="walkOneStep('TURN_LEFT')">
                            &#8624;
                        </button>
                    </td>
                    <td id="nav-forward">
                        <button onclick="walkOneStep('STEP_FORWARD')">
                            &#8593;
                        </button>
                    </td>
                    <td id="nav-turn-right">
                        <button onclick="walkOneStep('TURN_RIGHT')">
                            &#8625;
                        </button>
                    </td>
                </tr>
                <tr>
                    <td id="nav-left">
                        <button onclick="walkOneStep('STEP_LEFT')">
                            &#8592;
                        </button>
                    </td>
                    <td id="nav-u-turn">
                        <button onclick="walkOneStep('TURN_U')">
                            &#8630;
                        </button>
                    </td>
                    <td id="nav-right">
                        <button onclick="walkOneStep('STEP_RIGHT')">
                            &#8594;
                        </button>
                    </td>
                </tr>
                <tr>
                    <td>
                    </td>
                    <td id="nav-backward">
                        <button onclick="walkOneStep('STEP_BACKWARD')">
                            &#8595;
                        </button>
                    </td>
                    <td>
                        <button onclick="setMovingMode('orbit')">
                            fly
                        </button>
                    </td>
                </tr>
            </table>
        </div>

        <script src="js/jquery.js"></script>
        <script src="js/three.js"></script>
        <script src="js/OrbitControls.js"></script>
        <script src="js/TrackballControls.js"></script>
        <script src="js/dat.gui.js"></script>

        <!-- Shaders -->
        <script type="x-shader/x-vertex" id="vertexshader">

            // switch on high precision floats
            #ifdef GL_ES
            precision highp float;
            #endif

            varying vec4 vRstq;
            uniform mat4 projectorMat;

            void main()
            {
                vRstq = projectorMat * vec4(position, 1.0);
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
            }

        </script>

        <script type="x-shader/x-fragment" id="fragmentshader">

            #ifdef GL_ES
            precision highp float;
            #endif

            varying vec4 vRstq;
            uniform sampler2D projectorTex;
            uniform float opacity;

            void main()
            {
                float x = vRstq.x / vRstq.w + 0.5;
                float y = vRstq.y / vRstq.w + 0.5;
                vec4 baseColor = texture2D(projectorTex, vec2(x, y));
                baseColor.a = opacity;
                gl_FragColor = baseColor;
            }

        </script>

        <script>
            var container, camera, controls, scene, renderer;
            var mouse = new THREE.Vector2();
            var hoverCamera, raycaster, parentTransform;
            var selectedCamera;
            var imagePlane, imageMaterial, imagePlaneCamera;
            var imagePlaneOld, imageMaterialOld, imagePlaneCameraOld;
            var scene_group, grid_group;
            var pointCloudMaterial;
            var reconstructions;
            var reconstruction_visibles = [];
            var reconstruction_groups = [];
            var camera_lines = [];
            var gps_lines = [];
            var imagePlanes = [];
            var imageMaterial = [];
            var imagePlaneCameras = [];
            var imageMaterials = [];
            var num_preview_plane = 5;
            var moveSpeed = 0.2;
            var turnSpeed = 0.1;
            var previousShot = undefined;
            var validMoves;
            var movingMode = 'orbit';
            var savedOptions = {};


            var options = {
                cameraSize: 0.9,
                pointSize: 0.7,
                imagePlaneSize: 50,
                showThumbnail: true,
                drawGrid: true,
                drawGPS: false,
                imagePlaneOpacity: 1,
                cameraColor: new THREE.Color(0xFFFFFF),
                hoverCameraColor: new THREE.Color(0xFF8888),
                selectedCameraColor: new THREE.Color(0xFFFF88),
                reconstruction_visibles: {},
                allNone: function () {
                    var someone = false;
                    for (var r = 0; r < reconstructions.length; ++r) {
                        if (options.reconstruction_visibles[r]) {
                            someone = true;
                            break;
                        }
                    }
                    for (var r = 0; r < reconstructions.length; ++r) {
                        options.reconstruction_visibles[r] = !someone;
                        reconstruction_groups[r].traverse(function (object) {
                            object.visible = !someone;
                        });
                    }
                    render();
                }
            };

            getData();

            function addDatGui(){
                var gui = new dat.GUI();

                var f1 = gui.addFolder('Options');
                f1.add(options, 'pointSize', 0, 2)
                    .listen()
                    .onChange(setPointSize);
                f1.add(options, 'cameraSize', 0, 2)
                    .listen()
                    .onChange(setCameraSize);
                f1.add(options, 'imagePlaneSize', 1, 200)
                    .onChange(function(value) {
                        options.imagePlaneSize *= 1.5;
                        imagePlane.geometry = imagePlaneGeo(imagePlaneCameraOld.reconstruction, imagePlaneCameraOld.shot_id);
                        options.imagePlaneSize /= 1.5;
                        imagePlane.geometry = imagePlaneGeo(imagePlaneCamera.reconstruction, imagePlaneCamera.shot_id);
                        render();
                    });
                f1.add(options, 'showThumbnail')
                    .listen()
                    .onChange(setShowThumbnail);
                f1.add(options, 'drawGrid')
                    .listen()
                    .onChange(setDrawGrid);
                f1.add(options, 'drawGPS')
                    .onChange(function(value) {
                        for (var i = 0; i < gps_lines.length; ++i) {
                            gps_lines[i].visible = value;
                        }
                        render();
                    });
                f1.open();

                var f3 = gui.addFolder('Reconstructions')
                f3.add(options, 'allNone');
                options.reconstruction_visibles = [];
                for (var r = 0; r < reconstructions.length; ++r) {
                    options.reconstruction_visibles[r] = true;
                    f3.add(options.reconstruction_visibles, r, true)
                        .onChange(
                            (function(rr) {
                                return function (value) {
                                    reconstruction_groups[rr].traverse(
                                        function (object) { object.visible = value; } );
                                    render();
                                }
                            })(r)
                        ).listen();
                }
                f3.close();
            }

            function setPointSize(value) {
                options.pointSize = value;
                pointCloudMaterial.size = value;
                render();
            }

            function setCameraSize(value) {
                options.cameraSize = value;
                for (var r = 0; r < reconstructions.length; ++r) {
                    updateCameraLines(reconstructions[r]);
                }
                render();
            }

            function setShowThumbnail(value) {
                options.showThumbnail = value;
                $('#info').css('visibility', value ? 'visible' : 'hidden');
            }

            function setDrawGrid(value) {
                options.drawGrid = value;
                grid_group.visible = value;
                render();
            }

            function setMovingMode(mode) {
                if (mode != movingMode) {
                    movingMode = mode;
                    if (mode == 'orbit') {
                        setPointSize(savedOptions.pointSize);
                        setCameraSize(savedOptions.cameraSize);
                        setShowThumbnail(savedOptions.showThumbnail);
                        setDrawGrid(savedOptions.drawGrid);
                        controls.noRotate = false;
                        controls.noLookAround = false;
                        controls.noPan = false;
                        controls.noZoom = false;
                        controls.noKeys = false;
                        controls.animationPosition.z += 10;
                        imagePlane.visible = false;
                        imagePlaneOld.visible = false;
                        $('#navigation').hide();
                    } else if (mode == 'walk') {
                        savedOptions.pointSize = options.pointSize;
                        savedOptions.cameraSize = options.cameraSize;
                        savedOptions.showThumbnail = options.showThumbnail;
                        savedOptions.drawGrid = options.drawGrid;
                        setPointSize(0);
                        setCameraSize(0.3);
                        setShowThumbnail(false);
                        setDrawGrid(false);
                        controls.noRotate = true;
                        controls.noLookAround = true;
                        controls.noPan = true;
                        controls.noZoom = true;
                        controls.noKeys = true;
                        $('#navigation').show();
                    }
                }
            }

            function getData() {
                jQuery.getJSON(window.location.hash.replace('#',''), function(data) {
                    if ('cameras' in data) {
                        reconstructions = [data];
                    } else {
                        reconstructions = data;
                    }
                    init();
                    animate();
                });
            }

            function rotate(vector, angleaxis) {
                var v = new THREE.Vector3(vector[0], vector[1], vector[2]);
                var axis = new THREE.Vector3(angleaxis[0],
                                             angleaxis[1],
                                             angleaxis[2]);
                var angle = axis.length();
                axis.normalize();
                var matrix = new THREE.Matrix4().makeRotationAxis(axis, angle);
                v.applyMatrix4(matrix);
                return v;
            }

            function opticalCenter(shot) {
                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];
                var Rt = rotate(shot.translation, angleaxis);
                Rt.negate();
                return Rt;
            }

            function viewingDirection(shot) {
                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];
                return rotate([0,0,1], angleaxis);
            }

            function pixelToVertex(cam, shot, u, v, scale) {
                // Projection model:
                // xc = R * x + t
                // u = focal * xc / zc + width / 2
                // v = focal * yc / zc + height / 2

                var zc = scale;
                var xc = (u - cam.width / 2) / cam.focal * zc;
                var yc = (v - cam.height / 2) / cam.focal * zc;

                var xct = [xc - shot.translation[0],
                           yc - shot.translation[1],
                           zc - shot.translation[2]];


                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];

                return rotate(xct, angleaxis);
            }

            function initCameraLines(reconstruction) {
                var lines = []
                for (var shot_id in reconstruction.shots) {
                    if (reconstruction.shots.hasOwnProperty(shot_id)) {
                        var lineMaterial = new THREE.LineBasicMaterial({size: 0.1 })
                        lineMaterial.color = options.cameraColor;
                        var linegeo = cameraLineGeo(reconstruction, shot_id);
                        var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                        line.reconstruction = reconstruction;
                        line.shot_id = shot_id;
                        lines.push(line);
                    }
                }
                return lines;
            }

            function updateCameraLines() {
                for (var i = 0; i < camera_lines.length; ++i) {
                    var linegeo = cameraLineGeo(camera_lines[i].reconstruction, camera_lines[i].shot_id);
                    camera_lines[i].geometry.vertices = linegeo.vertices;
                    camera_lines[i].geometry.verticesNeedUpdate = true;
                }
            }

            function cameraLineGeo(reconstruction, shot_id) {
                var shot = reconstruction.shots[shot_id];
                var cam = reconstruction.cameras[shot.camera];
                var ocenter = opticalCenter(shot);
                var top_left = pixelToVertex(cam, shot, 0, 0, options.cameraSize);
                var top_right = pixelToVertex(cam, shot, cam.width, 0, options.cameraSize);
                var bottom_right = pixelToVertex(cam, shot, cam.width, cam.height, options.cameraSize);
                var bottom_left = pixelToVertex(cam, shot, 0, cam.height, options.cameraSize);
                var linegeo = new THREE.Geometry();
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(top_left);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(bottom_right);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(top_left);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(bottom_right);
                linegeo.vertices.push(bottom_right);
                linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(top_left);
                return linegeo;
            }

            function imagePlaneGeo(reconstruction, shot_id) {
                var shot = reconstruction.shots[shot_id];
                var cam = reconstruction.cameras[shot.camera];
                var geometry = new THREE.Geometry();

                if ('vertices' in shot) {
                    for (var i = 0; i < shot['vertices'].length; ++i) {
                        geometry.vertices.push(
                            new THREE.Vector3(
                                shot['vertices'][i][0],
                                shot['vertices'][i][1],
                                shot['vertices'][i][2]
                            )
                        );
                    }
                    for (var i = 0; i < shot['faces'].length; ++i) {
                        var v0 = shot['faces'][i][0];
                        var v1 = shot['faces'][i][1];
                        var v2 = shot['faces'][i][2];

                        geometry.faces.push(new THREE.Face3(v0, v1, v2));
                    }
                } else {
                    var top_left = pixelToVertex(cam, shot, 0, 0, options.imagePlaneSize);
                    var top_right = pixelToVertex(cam, shot, cam.width, 0, options.imagePlaneSize);
                    var bottom_right = pixelToVertex(cam, shot, cam.width, cam.height, options.imagePlaneSize);
                    var bottom_left = pixelToVertex(cam, shot, 0, cam.height, options.imagePlaneSize);

                    geometry.vertices.push(
                        top_left,
                        bottom_left,
                        bottom_right,
                        top_right
                    );
                    geometry.faces.push(
                        new THREE.Face3(0, 1, 3),
                        new THREE.Face3(1, 2, 3)
                    );
                }

                return geometry;
            }

            function projectorCameraMatrix(cam, shot) {
                var angleaxis = shot.rotation;
                var axis = new THREE.Vector3(angleaxis[0],
                                             angleaxis[1],
                                             angleaxis[2]);
                var angle = axis.length();
                axis.normalize();
                var rotation = new THREE.Matrix4().makeRotationAxis(axis, angle);
                var t = shot.translation;
                var translation = new THREE.Vector3(t[0], t[1], t[2]);
                rotation.setPosition(translation);
                var dx = cam.width / cam.focal;
                var dy = cam.height / cam.focal;
                var projection = new THREE.Matrix4().makeFrustum(-dx, +dx, +dy, -dy, -1, -1000);
                return projection.multiply(rotation);
            }


            function init() {
                raycaster = new THREE.Raycaster();
                raycaster.precision = 0.01;

                renderer = new THREE.WebGLRenderer();
                renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.setClearColor( 0x202020, 0.0);

                container = document.getElementById( 'ThreeJS' );
                container.appendChild(renderer.domElement);

                camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.3, 1000);
                camera.position.x = 50;
                camera.position.y = 50;
                camera.position.z = 50;
                camera.up = new THREE.Vector3(0,0,1);

                controls = new THREE.OrbitControls(camera, renderer.domElement);
                controls.addEventListener('change', render);

                window.addEventListener('resize', onWindowResize, false);
                renderer.domElement.addEventListener('mousemove', onDocumentMouseMove, false);
                renderer.domElement.addEventListener('mousedown', onDocumentMouseDown, false);
                window.addEventListener( 'keydown', onKeyDown, false );

                scene_group = new THREE.Object3D();
                pointCloudMaterial = new THREE.PointCloudMaterial({
                      size: options.pointSize,
                      vertexColors: true,
                    });
                for (var r = 0; r < reconstructions.length; ++r) {
                    var reconstruction = reconstructions[r];
                    reconstruction_groups[r] = new THREE.Object3D();
                    var group = reconstruction_groups[r];

                    // Points.
                    var points = new THREE.Geometry();
                    for (var point_id in reconstruction.points) {
                        if (reconstruction.points.hasOwnProperty(point_id)) {
                            var p = reconstruction.points[point_id].coordinates;
                            var c = reconstruction.points[point_id].color;
                            var color = new THREE.Color();
                            color.setRGB(c[0] / 255., c[1] / 255., c[2] / 255.)
                            points.vertices.push(new THREE.Vector3(p[0], p[1], p[2]));
                            points.colors.push(color);
                        }
                    }
                    group.add(new THREE.PointCloud(points, pointCloudMaterial))

                    // Cameras.
                    var lines = initCameraLines(reconstruction);
                    for (var i = 0; i < lines.length; ++i) {
                        group.add(lines[i]);
                        camera_lines.push(lines[i]);
                    }

                    // GPS positions
                    for (var shot_id in reconstruction.shots) {
                        if (reconstruction.shots.hasOwnProperty(shot_id)) {
                            var shot = reconstruction.shots[shot_id];
                            var ocenter = opticalCenter(shot);
                            var gps = shot.gps_position;

                            var linegeo = new THREE.Geometry();
                            linegeo.vertices.push(
                                ocenter,
                                new THREE.Vector3(gps[0], gps[1], gps[2])
                            );
                            var lineMaterial = new THREE.LineBasicMaterial({ color: 0xff00ff });
                            var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                            line.visible = options.drawGPS;
                            group.add(line);
                            gps_lines.push(line);
                        }
                    }

                    scene_group.add(group);
                }


                // Image plane
                imagePlaneCamera = camera_lines[0];
                var shot = imagePlaneCamera.reconstruction.shots[imagePlaneCamera.shot_id];
                var cam = imagePlaneCamera.reconstruction.cameras[shot.camera];
                var imageTexture = THREE.ImageUtils.loadTexture(imageURL(shot_id));
                imageMaterial = new THREE.ShaderMaterial({
                    // map: imageTexture,
                    side: THREE.DoubleSide,
                    transparent: true,
                    opacity: 1.0,
                    depthWrite: false,
                    uniforms: {
                        projectorMat: {
                             type: 'm4',
                             value: projectorCameraMatrix(cam, shot)
                        },
                        projectorTex: {
                            type: 't',
                            value: imageTexture
                        },
                        opacity: {
                            type: 'f',
                            value: options.imagePlaneOpacity
                        }
                    },
                    vertexShader:   $('#vertexshader').text(),
                    fragmentShader: $('#fragmentshader').text()
                });
                imagePlane = new THREE.Mesh(imagePlaneGeo(imagePlaneCamera.reconstruction,
                                                          imagePlaneCamera.shot_id),
                                            imageMaterial);
                imagePlane.visible = false;
                scene_group.add(imagePlane);

                imagePlaneCameraOld = camera_lines[0];
                var imageTextureOld = THREE.ImageUtils.loadTexture(imageURL(shot_id));

                imageMaterialOld = new THREE.ShaderMaterial({
                    // map: imageTexture,
                    side: THREE.DoubleSide,
                    transparent: true,
                    opacity: 0.5,
                    depthWrite: false,
                    uniforms: {
                        projectorMat: {
                             type: 'm4',
                             value: projectorCameraMatrix(cam, shot)
                        },
                        projectorTex: {
                            type: 't',
                            value: imageTextureOld
                        },
                        opacity: {
                            type: 'f',
                            value: options.imagePlaneOpacity
                        }
                    },
                    vertexShader:   $('#vertexshader').text(),
                    fragmentShader: $('#fragmentshader').text()
                });
                imagePlaneOld = new THREE.Mesh(imagePlaneGeo(imagePlaneCameraOld.reconstruction,
                                                          imagePlaneCameraOld.shot_id),
                                            imageMaterialOld);
                imagePlaneOld.visible = false;

                scene_group.add(imagePlaneOld);

                // for (var i=0; i < num_preview_plane; ++i) {
                //     imagePlaneCameras.push(camera_lines[0]);
                //     var imageTexture = THREE.ImageUtils.loadTexture(imageURL(shot_id));
                //     imageMaterials.push(new THREE.MeshBasicMaterial({
                //         map : imageTexture,
                //         transparent: true,
                //         opacity: options.imagePlaneOpacity
                //     }));
                //     imagePlanes.push(new THREE.Mesh(imagePlaneGeo(imagePlaneCameras[i].reconstruction,
                //                                               imagePlaneCameras[i].shot_id),
                //                                 imageMaterials[i]));
                //     imagePlanes[i].visible = false;
                //     scene_group.add(imagePlanes[i]);
                // }

                // Axis
                grid_group = new THREE.Object3D();
                var linegeo = new THREE.Geometry();
                linegeo.vertices = [
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(1, 0, 0),
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(0, 1, 0),
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(0, 0, 1)
                ];
                linegeo.colors = [
                    new THREE.Color( 0xff0000 ),
                    new THREE.Color( 0xff0000 ),
                    new THREE.Color( 0x00ff00 ),
                    new THREE.Color( 0x00ff00 ),
                    new THREE.Color( 0x0000ff ),
                    new THREE.Color( 0x0000ff )
                ];
                var lineMaterial = new THREE.LineBasicMaterial({
                    color: 0xffffff,
                    vertexColors: THREE.VertexColors
                });
                var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                grid_group.add(line);

                // Ground grid
                {
                    var linegeo = new THREE.Geometry();
                    var N = 20;
                    var scale = 2;
                    for (var i = 0; i <= 2 * N; ++i) {
                        linegeo.vertices.push(
                            new THREE.Vector3(scale * (i - N), scale * (-N), 0),
                            new THREE.Vector3(scale * (i - N), scale * ( N), 0),
                            new THREE.Vector3(scale * (-N), scale * (i - N), 0),
                            new THREE.Vector3(scale * ( N), scale * (i - N), 0)
                        );
                    }
                    var lineMaterial = new THREE.LineBasicMaterial({color: 0x555555});
                    var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                    grid_group.add(line);
                }
                scene_group.add(grid_group);

                scene = new THREE.Scene();
                scene.add(scene_group);

                addDatGui();
                render();
            }

            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
                render();
            }

            function onDocumentMouseMove(event) {
                event.preventDefault();
                mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
                mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
                render();
            }

            function imageURL(shot_id) {
                var hash = window.location.hash.replace('#','');
                var slash = hash.lastIndexOf('/');
                return hash.substring(0, slash) + '/images/' + shot_id;
            }

            function reconstruction_of_shot(reconstructions, shot_id) {
                for (var r = 0; r < reconstructions.length; ++r) {
                    if (shot_id in reconstructions[r]['shots']) {
                        return reconstructions[r];
                    }
                }
                return undefined;
            }

            function reconstruction_id_of_shot(reconstructions, shot_id) {
                for (var r = 0; r < reconstructions.length; ++r) {
                    if (shot_id in reconstructions[r]['shots']) {
                        return r;
                    }
                }
                return undefined;
            }

            function setSelectedCamera(cameraObject) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var image_url = imageURL(shot_id);
                if (selectedCamera !== undefined) {
                    selectedCamera.material.linewidth = 1;
                    selectedCamera.material.color = options.cameraColor;
                }
                selectedCamera = cameraObject;
                selectedCamera.material.linewidth = 5;
                selectedCamera.material.color = options.selectedCameraColor;
                var image_tag = document.getElementById('image');
                image_tag.src = image_url;
                var text = document.getElementById('text');
                text.innerHTML = shot_id;
            }

            function setImagePlaneCamera(cameraObject) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var cam = r['cameras'][shot['camera']];

                if (previousShot !== cameraObject.shot_id) {
                    previousShot = cameraObject.shot_id
                    var image_url = imageURL(shot_id);
                    if (selectedCamera !== cameraObject) {
                        setSelectedCamera(cameraObject);
                    }

                    if (imagePlaneCamera !== undefined) {
                        imagePlaneCameraOld = imagePlaneCamera;
                        imageMaterialOld.uniforms.projectorTex.value = imageMaterial.uniforms.projectorTex.value;
                        imageMaterialOld.uniforms.projectorMat.value = imageMaterial.uniforms.projectorMat.value;

                        imagePlaneOld.geometry = imagePlaneGeo(imagePlaneCameraOld.reconstruction, imagePlaneCameraOld.shot_id);
                        options.imagePlaneOpacity = 1;
                        imagePlaneOld.visible = true;
                    }

                    imagePlaneCamera = cameraObject;
                    imageMaterial.uniforms.projectorTex.value = THREE.ImageUtils.loadTexture(image_url, null, render);
                    imageMaterial.uniforms.projectorMat.value = projectorCameraMatrix(cam, shot)
                    imagePlane.geometry = imagePlaneGeo(r, shot_id);
                    imagePlane.visible = true;
                }
                setMovingMode('walk');
                controls.goto_shot(cam, shot);
            }

            function setImagePlaneCameraList(cameraObject, id) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var cam = r['cameras'][shot['camera']];
                var image_url = imageURL(shot_id);

                imagePlaneCameras[id] = cameraObject;
                imageMaterials[id].map = THREE.ImageUtils.loadTexture(image_url, null, render);
                imageMaterials[id].opacity = 1.0
                imagePlanes[id].geometry = imagePlaneGeo(r, shot_id);
                imagePlanes[id].visible = true;
            }

            function onDocumentMouseDown(event) {
                if (hoverCamera !== undefined) {

                    if (selectedCamera !== hoverCamera) {
                        setSelectedCamera(hoverCamera);
                    } else {
                        setImagePlaneCamera(selectedCamera, true);
                    }

                    // hideImagePlanesList()

                    render();
                }
            }

            function hideImagePlanesList(){
                for (var i =0; i < num_preview_plane; ++i) {
                    imagePlanes[i].visible = false;
                }
            }

            function angleBetweenVector2(x1, y1, x2, y2) {
                var a = Math.atan2(y2, x2) - Math.atan2(y1, x1);
                if (a > Math.PI) return a - 2 * Math.PI;
                else if (a < -Math.PI) return a + 2 * Math.PI;
                else return a;
            }

            function computeValidMoves() {
                var currentPosition = controls.animationPosition;
                var currentTarget = controls.animationTarget;
                var currentDir = currentTarget.clone().sub(currentPosition);
                var turnAngle = undefined;

                var wantedMotionDirs = {
                    STEP_LEFT: new THREE.Vector3(-currentDir.y, currentDir.x, 0),
                    STEP_RIGHT: new THREE.Vector3(currentDir.y, -currentDir.x, 0),
                    STEP_FORWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_BACKWARD: new THREE.Vector3(-currentDir.x, -currentDir.y, 0),
                    TURN_LEFT: new THREE.Vector3(0, 0, 0),
                    TURN_RIGHT: new THREE.Vector3(0, 0, 0),
                    TURN_U: new THREE.Vector3(0, 0, 0)
                }

                var wantedDirs = {
                    STEP_LEFT: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_RIGHT: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_FORWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_BACKWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    TURN_LEFT: new THREE.Vector3(-currentDir.y, currentDir.x, 0),
                    TURN_RIGHT: new THREE.Vector3(currentDir.y, -currentDir.x, 0),
                    TURN_U: new THREE.Vector3(-currentDir.x, -currentDir.y, 0)
                }

                var min_d = {};
                var closest_line = {};
                for (var k in wantedMotionDirs) {
                    if (wantedMotionDirs.hasOwnProperty(k)) {
                        min_d[k] = 999999999999;
                        closest_line[k] = undefined;
                    }
                }

                for (var i = 0; i < camera_lines.length; ++i) {
                    var line = camera_lines[i];
                    var r = line.reconstruction;
                    var shot_id = line.shot_id;
                    var shot = r['shots'][shot_id];
                    var oc = opticalCenter(shot);
                    var dir = viewingDirection(shot);
                    var motion = oc.clone().sub(currentPosition);
                    var d = currentPosition.distanceTo(oc);
                    var rid = reconstruction_id_of_shot(reconstructions, shot_id);
                    var visible = options.reconstruction_visibles[rid];
                    if (!visible) continue;

                    for (var k in wantedMotionDirs) {
                        if (wantedMotionDirs.hasOwnProperty(k)) {
                            var turn = angleBetweenVector2(wantedDirs[k].x, wantedDirs[k].y, dir.x, dir.y);
                            var drift = wantedMotionDirs[k].angleTo(new THREE.Vector3(motion.x, motion.y, 0));
                            if (k.lastIndexOf('STEP', 0) === 0) {
                                if (Math.abs(turn) < 0.5 && Math.abs(drift) < 0.5 && d > 0.01 && d < 20) {
                                    if (d < min_d[k]) {
                                        min_d[k] = d;
                                        closest_line[k] = line;
                                    }
                                }
                            } else if (k.lastIndexOf('TURN', 0) === 0) {
                                if (Math.abs(turn) < 0.7 && d < 15) {
                                    if (d < min_d[k]) {
                                        min_d[k] = d;
                                        closest_line[k] = line;
                                    }
                                }
                            }
                        }
                    }
                }
                return closest_line;
            }

            function walkOneStep(motion_type) {
                var line = validMoves[motion_type];
                if (line !== undefined) {
                    setImagePlaneCamera(line, true);
                }
            }

            function onKeyDown(event) {
                if (movingMode == 'walk') {
                    var validKey = true;

                    switch (event.keyCode) {
                        case 37: // left arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_LEFT');
                            } else {
                                walkOneStep('STEP_LEFT');
                            }
                            break;
                        case 38: // up arrow
                            walkOneStep('STEP_FORWARD');
                            break;
                        case 39: // right arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_RIGHT');
                            } else {
                                walkOneStep('STEP_RIGHT');
                            }
                            break;
                        case 40: // down arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_U');
                            } else {
                                walkOneStep('STEP_BACKWARD');
                            }
                            break;
                        case 27: // ESC
                            setMovingMode('orbit');
                            break;
                        default:
                            validKey = false;
                            break;
                    }

                    if (validKey) {
                        event.preventDefault();
                    }
                }
            }

            function preloadValidMoves() {
                for (var k in validMoves) {
                    if (validMoves.hasOwnProperty(k)) {
                        var line = validMoves[k];
                        if (line !== undefined) {
                            var shot_id = line.shot_id;
                            var image_url = imageURL(shot_id);
                            var temp_img = new Image();
                            temp_img.src = image_url;
                        }
                    }
                }
            }

            function updateValidMovesWidget() {
                $('#nav-left').css('visibility',
                    (validMoves.STEP_LEFT === undefined) ? 'hidden':'visible');
                $('#nav-right').css('visibility',
                    (validMoves.STEP_RIGHT === undefined) ? 'hidden':'visible');
                $('#nav-forward').css('visibility',
                    (validMoves.STEP_FORWARD === undefined) ? 'hidden':'visible');
                $('#nav-backward').css('visibility',
                    (validMoves.STEP_BACKWARD === undefined) ? 'hidden':'visible');
                $('#nav-turn-left').css('visibility',
                    (validMoves.TURN_LEFT === undefined) ? 'hidden':'visible');
                $('#nav-turn-right').css('visibility',
                    (validMoves.TURN_RIGHT === undefined) ? 'hidden':'visible');
                $('#nav-u-turn').css('visibility',
                    (validMoves.TURN_U === undefined) ? 'hidden':'visible');
            }

            function animate() {
                requestAnimationFrame(animate);
                imageMaterial.uniforms.opacity.value = 1 - options.imagePlaneOpacity;
                imageMaterialOld.uniforms.opacity.value = 1;//options.imagePlaneOpacity;
                options.imagePlaneOpacity *= 0.90;
                controls.update();
            }

            function render() {
                validMoves = computeValidMoves();
                updateValidMovesWidget();
                preloadValidMoves();

                // Handle camera selection.
                if (hoverCamera !== undefined && hoverCamera !== selectedCamera) {
                    hoverCamera.material.linewidth = 1;
                    hoverCamera.material.color = options.cameraColor;
                }
                var vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);
                raycaster.set(camera.position, vector.sub(camera.position).normalize());
                var intersects = raycaster.intersectObjects(camera_lines, true);
                hoverCamera = undefined;
                for (var i = 0; i < intersects.length; ++i) {
                    if (intersects[i].distance > 1.5 * options.cameraSize
                        && intersects[i].object.visible) {
                        hoverCamera = intersects[i].object;
                        if (hoverCamera !== selectedCamera) {
                            hoverCamera.material.linewidth = 2;
                            hoverCamera.material.color = options.hoverCameraColor;
                        }
                        break;
                    }
                }

                // Render.
                renderer.render(scene, camera);
            }

        </script>

    </body>
</html>
<commit_msg>Viewer: tweaks to make it work on a small window.<commit_after><!DOCTYPE html>
<html lang="en">
    <head>
        <title>Reconstruction Viewer</title>
        <meta charset="utf-8">
        <style>
            body {
                margin: 0px;
            }
            #info {
                position: absolute;
                top: 0px;
                left: 10px;
                background-color: #000;
            }
            #image {
                width: 160px;
                height: 120px;
            }
            #text {
                text-align: center;
                font-family: sans-serif;
                font-size: 10px;
                color: #FFF;
            }
            #navigation {
                position: absolute;
                top: 0px;
                left: 10px;
                display: none;
                font-family: sans-serif;
                font-size: 30px;
            }
        </style>
    </head>
    <body>
        <div id="ThreeJS" target="_blank"></div>
        <div id="info">
            <img id="image">
            <div id="text"></div>
        </div>
        <div id="navigation">
            <table>
                <tr>
                    <td id="nav-turn-left">
                        <button onclick="walkOneStep('TURN_LEFT')">
                            &#8624;
                        </button>
                    </td>
                    <td id="nav-forward">
                        <button onclick="walkOneStep('STEP_FORWARD')">
                            &#8593;
                        </button>
                    </td>
                    <td id="nav-turn-right">
                        <button onclick="walkOneStep('TURN_RIGHT')">
                            &#8625;
                        </button>
                    </td>
                </tr>
                <tr>
                    <td id="nav-left">
                        <button onclick="walkOneStep('STEP_LEFT')">
                            &#8592;
                        </button>
                    </td>
                    <td id="nav-u-turn">
                        <button onclick="walkOneStep('TURN_U')">
                            &#8630;
                        </button>
                    </td>
                    <td id="nav-right">
                        <button onclick="walkOneStep('STEP_RIGHT')">
                            &#8594;
                        </button>
                    </td>
                </tr>
                <tr>
                    <td>
                    </td>
                    <td id="nav-backward">
                        <button onclick="walkOneStep('STEP_BACKWARD')">
                            &#8595;
                        </button>
                    </td>
                    <td>
                        <button onclick="setMovingMode('orbit')">
                            fly
                        </button>
                    </td>
                </tr>
            </table>
        </div>

        <script src="js/jquery.js"></script>
        <script src="js/three.js"></script>
        <script src="js/OrbitControls.js"></script>
        <script src="js/TrackballControls.js"></script>
        <script src="js/dat.gui.js"></script>

        <!-- Shaders -->
        <script type="x-shader/x-vertex" id="vertexshader">

            // switch on high precision floats
            #ifdef GL_ES
            precision highp float;
            #endif

            varying vec4 vRstq;
            uniform mat4 projectorMat;

            void main()
            {
                vRstq = projectorMat * vec4(position, 1.0);
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
            }

        </script>

        <script type="x-shader/x-fragment" id="fragmentshader">

            #ifdef GL_ES
            precision highp float;
            #endif

            varying vec4 vRstq;
            uniform sampler2D projectorTex;
            uniform float opacity;

            void main()
            {
                float x = vRstq.x / vRstq.w + 0.5;
                float y = vRstq.y / vRstq.w + 0.5;
                vec4 baseColor = texture2D(projectorTex, vec2(x, y));
                baseColor.a = opacity;
                gl_FragColor = baseColor;
            }

        </script>

        <script>
            var container, camera, controls, scene, renderer;
            var mouse = new THREE.Vector2();
            var hoverCamera, raycaster, parentTransform;
            var selectedCamera;
            var imagePlane, imageMaterial, imagePlaneCamera;
            var imagePlaneOld, imageMaterialOld, imagePlaneCameraOld;
            var scene_group, grid_group;
            var pointCloudMaterial;
            var reconstructions;
            var reconstruction_visibles = [];
            var reconstruction_groups = [];
            var camera_lines = [];
            var gps_lines = [];
            var imagePlanes = [];
            var imageMaterial = [];
            var imagePlaneCameras = [];
            var imageMaterials = [];
            var num_preview_plane = 5;
            var moveSpeed = 0.2;
            var turnSpeed = 0.1;
            var previousShot = undefined;
            var validMoves;
            var movingMode = 'orbit';
            var savedOptions = {};


            var options = {
                cameraSize: 0.9,
                pointSize: 0.7,
                imagePlaneSize: 50,
                showThumbnail: true,
                drawGrid: true,
                drawGPS: false,
                imagePlaneOpacity: 1,
                cameraColor: new THREE.Color(0xFFFFFF),
                hoverCameraColor: new THREE.Color(0xFF8888),
                selectedCameraColor: new THREE.Color(0xFFFF88),
                reconstruction_visibles: {},
                allNone: function () {
                    var someone = false;
                    for (var r = 0; r < reconstructions.length; ++r) {
                        if (options.reconstruction_visibles[r]) {
                            someone = true;
                            break;
                        }
                    }
                    for (var r = 0; r < reconstructions.length; ++r) {
                        options.reconstruction_visibles[r] = !someone;
                        reconstruction_groups[r].traverse(function (object) {
                            object.visible = !someone;
                        });
                    }
                    render();
                }
            };

            getData();

            function addDatGui(){
                var gui = new dat.GUI();

                var f1 = gui.addFolder('Options');
                f1.add(options, 'pointSize', 0, 2)
                    .listen()
                    .onChange(setPointSize);
                f1.add(options, 'cameraSize', 0, 2)
                    .listen()
                    .onChange(setCameraSize);
                f1.add(options, 'imagePlaneSize', 1, 200)
                    .onChange(function(value) {
                        options.imagePlaneSize *= 1.5;
                        imagePlane.geometry = imagePlaneGeo(imagePlaneCameraOld.reconstruction, imagePlaneCameraOld.shot_id);
                        options.imagePlaneSize /= 1.5;
                        imagePlane.geometry = imagePlaneGeo(imagePlaneCamera.reconstruction, imagePlaneCamera.shot_id);
                        render();
                    });
                f1.add(options, 'showThumbnail')
                    .listen()
                    .onChange(setShowThumbnail);
                f1.add(options, 'drawGrid')
                    .listen()
                    .onChange(setDrawGrid);
                f1.add(options, 'drawGPS')
                    .onChange(function(value) {
                        for (var i = 0; i < gps_lines.length; ++i) {
                            gps_lines[i].visible = value;
                        }
                        render();
                    });
                f1.open();

                var f3 = gui.addFolder('Reconstructions')
                f3.add(options, 'allNone');
                options.reconstruction_visibles = [];
                for (var r = 0; r < reconstructions.length; ++r) {
                    options.reconstruction_visibles[r] = true;
                    f3.add(options.reconstruction_visibles, r, true)
                        .onChange(
                            (function(rr) {
                                return function (value) {
                                    reconstruction_groups[rr].traverse(
                                        function (object) { object.visible = value; } );
                                    render();
                                }
                            })(r)
                        ).listen();
                }
                f3.close();

                gui.close();
            }

            function setPointSize(value) {
                options.pointSize = value;
                pointCloudMaterial.size = value;
                render();
            }

            function setCameraSize(value) {
                options.cameraSize = value;
                for (var r = 0; r < reconstructions.length; ++r) {
                    updateCameraLines(reconstructions[r]);
                }
                render();
            }

            function setShowThumbnail(value) {
                options.showThumbnail = value;
                $('#info').css('visibility', value ? 'visible' : 'hidden');
            }

            function setDrawGrid(value) {
                options.drawGrid = value;
                grid_group.visible = value;
                render();
            }

            function setMovingMode(mode) {
                if (mode != movingMode) {
                    movingMode = mode;
                    if (mode == 'orbit') {
                        setPointSize(savedOptions.pointSize);
                        setCameraSize(savedOptions.cameraSize);
                        setShowThumbnail(savedOptions.showThumbnail);
                        setDrawGrid(savedOptions.drawGrid);
                        controls.noRotate = false;
                        controls.noLookAround = false;
                        controls.noPan = false;
                        controls.noZoom = false;
                        controls.noKeys = false;
                        controls.animationPosition.z += 10;
                        controls.dollyOut(4);
                        imagePlane.visible = false;
                        imagePlaneOld.visible = false;
                        $('#navigation').hide();
                    } else if (mode == 'walk') {
                        savedOptions.pointSize = options.pointSize;
                        savedOptions.cameraSize = options.cameraSize;
                        savedOptions.showThumbnail = options.showThumbnail;
                        savedOptions.drawGrid = options.drawGrid;
                        setPointSize(0);
                        setCameraSize(0.2);
                        setShowThumbnail(false);
                        setDrawGrid(false);
                        controls.noRotate = true;
                        controls.noLookAround = true;
                        controls.noPan = true;
                        controls.noZoom = true;
                        controls.noKeys = true;
                        $('#navigation').show();
                    }
                }
            }

            function getData() {
                jQuery.getJSON(window.location.hash.replace('#',''), function(data) {
                    if ('cameras' in data) {
                        reconstructions = [data];
                    } else {
                        reconstructions = data;
                    }
                    init();
                    animate();
                });
            }

            function rotate(vector, angleaxis) {
                var v = new THREE.Vector3(vector[0], vector[1], vector[2]);
                var axis = new THREE.Vector3(angleaxis[0],
                                             angleaxis[1],
                                             angleaxis[2]);
                var angle = axis.length();
                axis.normalize();
                var matrix = new THREE.Matrix4().makeRotationAxis(axis, angle);
                v.applyMatrix4(matrix);
                return v;
            }

            function opticalCenter(shot) {
                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];
                var Rt = rotate(shot.translation, angleaxis);
                Rt.negate();
                return Rt;
            }

            function viewingDirection(shot) {
                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];
                return rotate([0,0,1], angleaxis);
            }

            function pixelToVertex(cam, shot, u, v, scale) {
                // Projection model:
                // xc = R * x + t
                // u = focal * xc / zc + width / 2
                // v = focal * yc / zc + height / 2

                var zc = scale;
                var xc = (u - cam.width / 2) / cam.focal * zc;
                var yc = (v - cam.height / 2) / cam.focal * zc;

                var xct = [xc - shot.translation[0],
                           yc - shot.translation[1],
                           zc - shot.translation[2]];


                var angleaxis = [-shot.rotation[0],
                                 -shot.rotation[1],
                                 -shot.rotation[2]];

                return rotate(xct, angleaxis);
            }

            function initCameraLines(reconstruction) {
                var lines = []
                for (var shot_id in reconstruction.shots) {
                    if (reconstruction.shots.hasOwnProperty(shot_id)) {
                        var lineMaterial = new THREE.LineBasicMaterial({size: 0.1 })
                        lineMaterial.color = options.cameraColor;
                        var linegeo = cameraLineGeo(reconstruction, shot_id);
                        var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                        line.reconstruction = reconstruction;
                        line.shot_id = shot_id;
                        lines.push(line);
                    }
                }
                return lines;
            }

            function updateCameraLines() {
                for (var i = 0; i < camera_lines.length; ++i) {
                    var linegeo = cameraLineGeo(camera_lines[i].reconstruction, camera_lines[i].shot_id);
                    camera_lines[i].geometry.vertices = linegeo.vertices;
                    camera_lines[i].geometry.verticesNeedUpdate = true;
                }
            }

            function cameraLineGeo(reconstruction, shot_id) {
                var shot = reconstruction.shots[shot_id];
                var cam = reconstruction.cameras[shot.camera];
                var ocenter = opticalCenter(shot);
                var top_left = pixelToVertex(cam, shot, 0, 0, options.cameraSize);
                var top_right = pixelToVertex(cam, shot, cam.width, 0, options.cameraSize);
                var bottom_right = pixelToVertex(cam, shot, cam.width, cam.height, options.cameraSize);
                var bottom_left = pixelToVertex(cam, shot, 0, cam.height, options.cameraSize);
                var linegeo = new THREE.Geometry();
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(top_left);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(bottom_right);
                linegeo.vertices.push(ocenter);
                linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(top_left);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(top_right);
                linegeo.vertices.push(bottom_right);
                linegeo.vertices.push(bottom_right);
                linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(bottom_left);
                linegeo.vertices.push(top_left);
                return linegeo;
            }

            function imagePlaneGeo(reconstruction, shot_id) {
                var shot = reconstruction.shots[shot_id];
                var cam = reconstruction.cameras[shot.camera];
                var geometry = new THREE.Geometry();

                if ('vertices' in shot) {
                    for (var i = 0; i < shot['vertices'].length; ++i) {
                        geometry.vertices.push(
                            new THREE.Vector3(
                                shot['vertices'][i][0],
                                shot['vertices'][i][1],
                                shot['vertices'][i][2]
                            )
                        );
                    }
                    for (var i = 0; i < shot['faces'].length; ++i) {
                        var v0 = shot['faces'][i][0];
                        var v1 = shot['faces'][i][1];
                        var v2 = shot['faces'][i][2];

                        geometry.faces.push(new THREE.Face3(v0, v1, v2));
                    }
                } else {
                    var top_left = pixelToVertex(cam, shot, 0, 0, options.imagePlaneSize);
                    var top_right = pixelToVertex(cam, shot, cam.width, 0, options.imagePlaneSize);
                    var bottom_right = pixelToVertex(cam, shot, cam.width, cam.height, options.imagePlaneSize);
                    var bottom_left = pixelToVertex(cam, shot, 0, cam.height, options.imagePlaneSize);

                    geometry.vertices.push(
                        top_left,
                        bottom_left,
                        bottom_right,
                        top_right
                    );
                    geometry.faces.push(
                        new THREE.Face3(0, 1, 3),
                        new THREE.Face3(1, 2, 3)
                    );
                }

                return geometry;
            }

            function projectorCameraMatrix(cam, shot) {
                var angleaxis = shot.rotation;
                var axis = new THREE.Vector3(angleaxis[0],
                                             angleaxis[1],
                                             angleaxis[2]);
                var angle = axis.length();
                axis.normalize();
                var rotation = new THREE.Matrix4().makeRotationAxis(axis, angle);
                var t = shot.translation;
                var translation = new THREE.Vector3(t[0], t[1], t[2]);
                rotation.setPosition(translation);
                var dx = cam.width / cam.focal;
                var dy = cam.height / cam.focal;
                var projection = new THREE.Matrix4().makeFrustum(-dx, +dx, +dy, -dy, -1, -1000);
                return projection.multiply(rotation);
            }


            function init() {
                raycaster = new THREE.Raycaster();
                raycaster.precision = 0.01;

                renderer = new THREE.WebGLRenderer();
                renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.setClearColor( 0x202020, 0.0);

                container = document.getElementById( 'ThreeJS' );
                container.appendChild(renderer.domElement);

                camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.3, 1000);
                camera.position.x = 50;
                camera.position.y = 50;
                camera.position.z = 50;
                camera.up = new THREE.Vector3(0,0,1);

                controls = new THREE.OrbitControls(camera, renderer.domElement);
                controls.addEventListener('change', render);

                window.addEventListener('resize', onWindowResize, false);
                renderer.domElement.addEventListener('mousemove', onDocumentMouseMove, false);
                renderer.domElement.addEventListener('mousedown', onDocumentMouseDown, false);
                window.addEventListener( 'keydown', onKeyDown, false );

                scene_group = new THREE.Object3D();
                pointCloudMaterial = new THREE.PointCloudMaterial({
                      size: options.pointSize,
                      vertexColors: true,
                    });
                for (var r = 0; r < reconstructions.length; ++r) {
                    var reconstruction = reconstructions[r];
                    reconstruction_groups[r] = new THREE.Object3D();
                    var group = reconstruction_groups[r];

                    // Points.
                    var points = new THREE.Geometry();
                    for (var point_id in reconstruction.points) {
                        if (reconstruction.points.hasOwnProperty(point_id)) {
                            var p = reconstruction.points[point_id].coordinates;
                            var c = reconstruction.points[point_id].color;
                            var color = new THREE.Color();
                            color.setRGB(c[0] / 255., c[1] / 255., c[2] / 255.)
                            points.vertices.push(new THREE.Vector3(p[0], p[1], p[2]));
                            points.colors.push(color);
                        }
                    }
                    group.add(new THREE.PointCloud(points, pointCloudMaterial))

                    // Cameras.
                    var lines = initCameraLines(reconstruction);
                    for (var i = 0; i < lines.length; ++i) {
                        group.add(lines[i]);
                        camera_lines.push(lines[i]);
                    }

                    // GPS positions
                    for (var shot_id in reconstruction.shots) {
                        if (reconstruction.shots.hasOwnProperty(shot_id)) {
                            var shot = reconstruction.shots[shot_id];
                            var ocenter = opticalCenter(shot);
                            var gps = shot.gps_position;

                            var linegeo = new THREE.Geometry();
                            linegeo.vertices.push(
                                ocenter,
                                new THREE.Vector3(gps[0], gps[1], gps[2])
                            );
                            var lineMaterial = new THREE.LineBasicMaterial({ color: 0xff00ff });
                            var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                            line.visible = options.drawGPS;
                            group.add(line);
                            gps_lines.push(line);
                        }
                    }

                    scene_group.add(group);
                }


                // Image plane
                imagePlaneCamera = camera_lines[0];
                var shot = imagePlaneCamera.reconstruction.shots[imagePlaneCamera.shot_id];
                var cam = imagePlaneCamera.reconstruction.cameras[shot.camera];
                var imageTexture = THREE.ImageUtils.loadTexture(imageURL(shot_id));
                imageMaterial = new THREE.ShaderMaterial({
                    // map: imageTexture,
                    side: THREE.DoubleSide,
                    transparent: true,
                    opacity: 1.0,
                    depthWrite: false,
                    uniforms: {
                        projectorMat: {
                             type: 'm4',
                             value: projectorCameraMatrix(cam, shot)
                        },
                        projectorTex: {
                            type: 't',
                            value: imageTexture
                        },
                        opacity: {
                            type: 'f',
                            value: options.imagePlaneOpacity
                        }
                    },
                    vertexShader:   $('#vertexshader').text(),
                    fragmentShader: $('#fragmentshader').text()
                });
                imagePlane = new THREE.Mesh(imagePlaneGeo(imagePlaneCamera.reconstruction,
                                                          imagePlaneCamera.shot_id),
                                            imageMaterial);
                imagePlane.visible = false;
                scene_group.add(imagePlane);

                imagePlaneCameraOld = camera_lines[0];
                var imageTextureOld = THREE.ImageUtils.loadTexture(imageURL(shot_id));

                imageMaterialOld = new THREE.ShaderMaterial({
                    // map: imageTexture,
                    side: THREE.DoubleSide,
                    transparent: true,
                    opacity: 0.5,
                    depthWrite: false,
                    uniforms: {
                        projectorMat: {
                             type: 'm4',
                             value: projectorCameraMatrix(cam, shot)
                        },
                        projectorTex: {
                            type: 't',
                            value: imageTextureOld
                        },
                        opacity: {
                            type: 'f',
                            value: options.imagePlaneOpacity
                        }
                    },
                    vertexShader:   $('#vertexshader').text(),
                    fragmentShader: $('#fragmentshader').text()
                });
                imagePlaneOld = new THREE.Mesh(imagePlaneGeo(imagePlaneCameraOld.reconstruction,
                                                          imagePlaneCameraOld.shot_id),
                                            imageMaterialOld);
                imagePlaneOld.visible = false;

                scene_group.add(imagePlaneOld);

                // for (var i=0; i < num_preview_plane; ++i) {
                //     imagePlaneCameras.push(camera_lines[0]);
                //     var imageTexture = THREE.ImageUtils.loadTexture(imageURL(shot_id));
                //     imageMaterials.push(new THREE.MeshBasicMaterial({
                //         map : imageTexture,
                //         transparent: true,
                //         opacity: options.imagePlaneOpacity
                //     }));
                //     imagePlanes.push(new THREE.Mesh(imagePlaneGeo(imagePlaneCameras[i].reconstruction,
                //                                               imagePlaneCameras[i].shot_id),
                //                                 imageMaterials[i]));
                //     imagePlanes[i].visible = false;
                //     scene_group.add(imagePlanes[i]);
                // }

                // Axis
                grid_group = new THREE.Object3D();
                var linegeo = new THREE.Geometry();
                linegeo.vertices = [
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(1, 0, 0),
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(0, 1, 0),
                    new THREE.Vector3(0, 0, 0),
                    new THREE.Vector3(0, 0, 1)
                ];
                linegeo.colors = [
                    new THREE.Color( 0xff0000 ),
                    new THREE.Color( 0xff0000 ),
                    new THREE.Color( 0x00ff00 ),
                    new THREE.Color( 0x00ff00 ),
                    new THREE.Color( 0x0000ff ),
                    new THREE.Color( 0x0000ff )
                ];
                var lineMaterial = new THREE.LineBasicMaterial({
                    color: 0xffffff,
                    vertexColors: THREE.VertexColors
                });
                var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                grid_group.add(line);

                // Ground grid
                {
                    var linegeo = new THREE.Geometry();
                    var N = 20;
                    var scale = 2;
                    for (var i = 0; i <= 2 * N; ++i) {
                        linegeo.vertices.push(
                            new THREE.Vector3(scale * (i - N), scale * (-N), 0),
                            new THREE.Vector3(scale * (i - N), scale * ( N), 0),
                            new THREE.Vector3(scale * (-N), scale * (i - N), 0),
                            new THREE.Vector3(scale * ( N), scale * (i - N), 0)
                        );
                    }
                    var lineMaterial = new THREE.LineBasicMaterial({color: 0x555555});
                    var line = new THREE.Line(linegeo, lineMaterial, THREE.LinePieces);
                    grid_group.add(line);
                }
                scene_group.add(grid_group);

                scene = new THREE.Scene();
                scene.add(scene_group);

                addDatGui();
                render();
            }

            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
                render();
            }

            function onDocumentMouseMove(event) {
                event.preventDefault();
                mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
                mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
                render();
            }

            function imageURL(shot_id) {
                var hash = window.location.hash.replace('#','');
                var slash = hash.lastIndexOf('/');
                return hash.substring(0, slash) + '/images/' + shot_id;
            }

            function reconstruction_of_shot(reconstructions, shot_id) {
                for (var r = 0; r < reconstructions.length; ++r) {
                    if (shot_id in reconstructions[r]['shots']) {
                        return reconstructions[r];
                    }
                }
                return undefined;
            }

            function reconstruction_id_of_shot(reconstructions, shot_id) {
                for (var r = 0; r < reconstructions.length; ++r) {
                    if (shot_id in reconstructions[r]['shots']) {
                        return r;
                    }
                }
                return undefined;
            }

            function setSelectedCamera(cameraObject) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var image_url = imageURL(shot_id);
                if (selectedCamera !== undefined) {
                    selectedCamera.material.linewidth = 1;
                    selectedCamera.material.color = options.cameraColor;
                }
                selectedCamera = cameraObject;
                selectedCamera.material.linewidth = 5;
                selectedCamera.material.color = options.selectedCameraColor;
                var image_tag = document.getElementById('image');
                image_tag.src = image_url;
                var text = document.getElementById('text');
                text.innerHTML = shot_id;
            }

            function setImagePlaneCamera(cameraObject) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var cam = r['cameras'][shot['camera']];

                if (previousShot !== cameraObject.shot_id) {
                    previousShot = cameraObject.shot_id
                    var image_url = imageURL(shot_id);
                    if (selectedCamera !== cameraObject) {
                        setSelectedCamera(cameraObject);
                    }

                    if (imagePlaneCamera !== undefined) {
                        imagePlaneCameraOld = imagePlaneCamera;
                        imageMaterialOld.uniforms.projectorTex.value = imageMaterial.uniforms.projectorTex.value;
                        imageMaterialOld.uniforms.projectorMat.value = imageMaterial.uniforms.projectorMat.value;

                        imagePlaneOld.geometry = imagePlaneGeo(imagePlaneCameraOld.reconstruction, imagePlaneCameraOld.shot_id);
                        options.imagePlaneOpacity = 1;
                        imagePlaneOld.visible = true;
                    }

                    imagePlaneCamera = cameraObject;
                    imageMaterial.uniforms.projectorTex.value = THREE.ImageUtils.loadTexture(image_url, null, render);
                    imageMaterial.uniforms.projectorMat.value = projectorCameraMatrix(cam, shot)
                    imagePlane.geometry = imagePlaneGeo(r, shot_id);
                    imagePlane.visible = true;
                }
                setMovingMode('walk');
                controls.goto_shot(cam, shot);
            }

            function setImagePlaneCameraList(cameraObject, id) {
                var r = cameraObject.reconstruction;
                var shot_id = cameraObject.shot_id;
                var shot = r['shots'][shot_id];
                var cam = r['cameras'][shot['camera']];
                var image_url = imageURL(shot_id);

                imagePlaneCameras[id] = cameraObject;
                imageMaterials[id].map = THREE.ImageUtils.loadTexture(image_url, null, render);
                imageMaterials[id].opacity = 1.0
                imagePlanes[id].geometry = imagePlaneGeo(r, shot_id);
                imagePlanes[id].visible = true;
            }

            function onDocumentMouseDown(event) {
                if (hoverCamera !== undefined) {

                    if (selectedCamera !== hoverCamera) {
                        setSelectedCamera(hoverCamera);
                    } else {
                        setImagePlaneCamera(selectedCamera, true);
                    }

                    // hideImagePlanesList()

                    render();
                }
            }

            function hideImagePlanesList(){
                for (var i =0; i < num_preview_plane; ++i) {
                    imagePlanes[i].visible = false;
                }
            }

            function angleBetweenVector2(x1, y1, x2, y2) {
                var a = Math.atan2(y2, x2) - Math.atan2(y1, x1);
                if (a > Math.PI) return a - 2 * Math.PI;
                else if (a < -Math.PI) return a + 2 * Math.PI;
                else return a;
            }

            function computeValidMoves() {
                var currentPosition = controls.animationPosition;
                var currentTarget = controls.animationTarget;
                var currentDir = currentTarget.clone().sub(currentPosition);
                var turnAngle = undefined;

                var wantedMotionDirs = {
                    STEP_LEFT: new THREE.Vector3(-currentDir.y, currentDir.x, 0),
                    STEP_RIGHT: new THREE.Vector3(currentDir.y, -currentDir.x, 0),
                    STEP_FORWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_BACKWARD: new THREE.Vector3(-currentDir.x, -currentDir.y, 0),
                    TURN_LEFT: new THREE.Vector3(0, 0, 0),
                    TURN_RIGHT: new THREE.Vector3(0, 0, 0),
                    TURN_U: new THREE.Vector3(0, 0, 0)
                }

                var wantedDirs = {
                    STEP_LEFT: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_RIGHT: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_FORWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    STEP_BACKWARD: new THREE.Vector3(currentDir.x, currentDir.y, 0),
                    TURN_LEFT: new THREE.Vector3(-currentDir.y, currentDir.x, 0),
                    TURN_RIGHT: new THREE.Vector3(currentDir.y, -currentDir.x, 0),
                    TURN_U: new THREE.Vector3(-currentDir.x, -currentDir.y, 0)
                }

                var min_d = {};
                var closest_line = {};
                for (var k in wantedMotionDirs) {
                    if (wantedMotionDirs.hasOwnProperty(k)) {
                        min_d[k] = 999999999999;
                        closest_line[k] = undefined;
                    }
                }

                for (var i = 0; i < camera_lines.length; ++i) {
                    var line = camera_lines[i];
                    var r = line.reconstruction;
                    var shot_id = line.shot_id;
                    var shot = r['shots'][shot_id];
                    var oc = opticalCenter(shot);
                    var dir = viewingDirection(shot);
                    var motion = oc.clone().sub(currentPosition);
                    var d = currentPosition.distanceTo(oc);
                    var rid = reconstruction_id_of_shot(reconstructions, shot_id);
                    var visible = options.reconstruction_visibles[rid];
                    if (!visible) continue;

                    for (var k in wantedMotionDirs) {
                        if (wantedMotionDirs.hasOwnProperty(k)) {
                            var turn = angleBetweenVector2(wantedDirs[k].x, wantedDirs[k].y, dir.x, dir.y);
                            var drift = wantedMotionDirs[k].angleTo(new THREE.Vector3(motion.x, motion.y, 0));
                            if (k.lastIndexOf('STEP', 0) === 0) {
                                if (Math.abs(turn) < 0.5 && Math.abs(drift) < 0.5 && d > 0.01 && d < 20) {
                                    if (d < min_d[k]) {
                                        min_d[k] = d;
                                        closest_line[k] = line;
                                    }
                                }
                            } else if (k.lastIndexOf('TURN', 0) === 0) {
                                if (Math.abs(turn) < 0.7 && d < 15) {
                                    if (d < min_d[k]) {
                                        min_d[k] = d;
                                        closest_line[k] = line;
                                    }
                                }
                            }
                        }
                    }
                }
                return closest_line;
            }

            function walkOneStep(motion_type) {
                var line = validMoves[motion_type];
                if (line !== undefined) {
                    setImagePlaneCamera(line, true);
                }
            }

            function onKeyDown(event) {
                if (movingMode == 'walk') {
                    var validKey = true;

                    switch (event.keyCode) {
                        case 37: // left arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_LEFT');
                            } else {
                                walkOneStep('STEP_LEFT');
                            }
                            break;
                        case 38: // up arrow
                            walkOneStep('STEP_FORWARD');
                            break;
                        case 39: // right arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_RIGHT');
                            } else {
                                walkOneStep('STEP_RIGHT');
                            }
                            break;
                        case 40: // down arrow
                            if (event.shiftKey) {
                                walkOneStep('TURN_U');
                            } else {
                                walkOneStep('STEP_BACKWARD');
                            }
                            break;
                        case 27: // ESC
                            setMovingMode('orbit');
                            break;
                        default:
                            validKey = false;
                            break;
                    }

                    if (validKey) {
                        event.preventDefault();
                    }
                }
            }

            function preloadValidMoves() {
                for (var k in validMoves) {
                    if (validMoves.hasOwnProperty(k)) {
                        var line = validMoves[k];
                        if (line !== undefined) {
                            var shot_id = line.shot_id;
                            var image_url = imageURL(shot_id);
                            var temp_img = new Image();
                            temp_img.src = image_url;
                        }
                    }
                }
            }

            function updateValidMovesWidget() {
                $('#nav-left').css('visibility',
                    (validMoves.STEP_LEFT === undefined) ? 'hidden':'visible');
                $('#nav-right').css('visibility',
                    (validMoves.STEP_RIGHT === undefined) ? 'hidden':'visible');
                $('#nav-forward').css('visibility',
                    (validMoves.STEP_FORWARD === undefined) ? 'hidden':'visible');
                $('#nav-backward').css('visibility',
                    (validMoves.STEP_BACKWARD === undefined) ? 'hidden':'visible');
                $('#nav-turn-left').css('visibility',
                    (validMoves.TURN_LEFT === undefined) ? 'hidden':'visible');
                $('#nav-turn-right').css('visibility',
                    (validMoves.TURN_RIGHT === undefined) ? 'hidden':'visible');
                $('#nav-u-turn').css('visibility',
                    (validMoves.TURN_U === undefined) ? 'hidden':'visible');
            }

            function animate() {
                requestAnimationFrame(animate);
                imageMaterial.uniforms.opacity.value = 1 - options.imagePlaneOpacity;
                imageMaterialOld.uniforms.opacity.value = 1;//options.imagePlaneOpacity;
                options.imagePlaneOpacity *= 0.90;
                controls.update();
            }

            function render() {
                validMoves = computeValidMoves();
                updateValidMovesWidget();
                preloadValidMoves();

                // Handle camera selection.
                if (hoverCamera !== undefined && hoverCamera !== selectedCamera) {
                    hoverCamera.material.linewidth = 1;
                    hoverCamera.material.color = options.cameraColor;
                }
                var vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);
                raycaster.set(camera.position, vector.sub(camera.position).normalize());
                var intersects = raycaster.intersectObjects(camera_lines, true);
                hoverCamera = undefined;
                for (var i = 0; i < intersects.length; ++i) {
                    if (intersects[i].distance > 1.5 * options.cameraSize
                        && intersects[i].object.visible) {
                        hoverCamera = intersects[i].object;
                        if (hoverCamera !== selectedCamera) {
                            hoverCamera.material.linewidth = 2;
                            hoverCamera.material.color = options.hoverCameraColor;
                        }
                        break;
                    }
                }

                // Render.
                renderer.render(scene, camera);
            }

        </script>

    </body>
</html>

{'fraction_non_alphanumeric': 0.09638705122803821, 'fraction_numerical': 0.010441456110586065, 'mean_word_length': 1.0412792859799183, 'pattern_counts': {'":': 0, '<': 213, '<?xml version=': 0, '>': 169, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 90, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7119912', 'n_tokens_mistral': 22520, 'n_tokens_neox': 20172, 'n_words': 3991}