Site icon Learn, Tinker, Build

Display and Interact with a Panorama

This is part two of the in depth guide for how I built the Virtual Easter Egg Hunt. We will be taking the panorama created in the previous post and displaying it in a basic webpage. The second part of this guide will look at some of the basic interactions that can be enabled with the panorama.

Note: If you have cloned the code for the Virtual Easter Egg Hunt you will notice some discrepancies between this guide and the code. Part of that is improvements I am making in this guide that have not been made in the code. Another reason for differences are updates to the PSV library.

Basic Webpage

Since we are only building a simple page you can get by with just a text editor. If you would like to use something that will provide some syntax highlighting and auto-completion I would recommend VS Code. Beyond an editor you will also need the Photo Sphere Viewer library. Most of the heavy lifting for displaying and allowing interactions are handled in PSV.

You’ll start with a basic folder structure. For this guide we just need an index.html file and a folder for the panorama to be stored in.

Now we need to build out the basic page structure in the index file. Since most of the display work is handled by PSV we can get by with a very basic page layout.

<!DOCTYPE html>
<html lang="en">
<body>
    
</body>
</html>

You can check results at each step by opening the HTML file in your browser. In the case of this first step it will just be a blank page.

Adding the Photo Sphere Viewer Code

First is to load in the needed stylesheets and javascript code. This will be done in a head section, but can also be done in the body.

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photo-sphere-viewer@4/dist/photo-sphere-viewer.min.css"/>

    <script src="https://cdn.jsdelivr.net/npm/three/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/uevent@2/browser.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/photo-sphere-viewer@4/dist/photo-sphere-viewer.min.js"></script>
</head>
<body>
    
</body>
</html>

There are multiple options for adding the PSV libraries including installing and referencing them locally. The above code references the libraries from a CDN. This means that the code will be pulled from another location instead of your server on load.

Now we need to add the container and basic display scripts for the panorama. Since I am using a cropped image I also need to pass some information about the size of the image to PSV. All of this is handled with the basic viewer initialization code.

<body>
    <div id="viewer"></div>

    <style>
        /* the viewer container must have a defined size */
        #viewer {
          width: 100vw;
          height: 80vh;
        }
    </style>
      
    <script>
        var viewer = new PhotoSphereViewer.Viewer({
          container: document.querySelector('#viewer'),
          panorama: 'images/courtyard.jpg',
          panoData: {
            fullWidth: 14440,
            fullHeight: 7220,
            croppedWidth: 14440,
            croppedHeight: 3299,
            croppedX: 0,
            croppedY: 1961,
            poseHeading: 0, // 0 to 360
            posePitch: 0, // -90 to 90
            poseRoll: 0, // -180 to 180
          }
        });
    </script>
</body>

The values for the cropped images settings can be gathered by using the playground from PSV.

The panorama should now be visible and able to be moved around in when you open in the browser. If all you want to do is display a panorama then at this point you are basically done. You can adjust the size of the viewer window by editing the style for the #viewer div.

Basic Interactions

Now that we can display and move about an image we can add interactions. The major interactions for the Easter Egg Hunt are detecting double clicks, alerting if the double click is on an egg, and marking the egg. Detecting and marking are fairly easy with the PSV library. Knowing if the click is on an egg is where the complexity comes in. A lot of that complexity is just around locating the eggs in the image to get their coordinates.

Detecting Double Click

The PSV library provides a large number of events that can be hooked into. One of those events is dblclick. When fired the even provides a data object with the latitude and longitude of the double click. To start with, since we don’t know where the eggs are, we are just going to write the click locations to the console. Those locations can be used in the next step for setting where the eggs are.

<script>
    function registerClick(data) {
        console.log("Double Click");
        console.log(`Lat: ${data.latitude}`);
        console.log(`Long: ${data.longitude}`);
    }

    var viewer = new PhotoSphereViewer.Viewer({
      container: document.querySelector('#viewer'),
      panorama: 'images/courtyard.jpg',
      panoData: {
        fullWidth: 14440,
        fullHeight: 7220,
        croppedWidth: 14440,
        croppedHeight: 3299,
        croppedX: 0,
        croppedY: 1961,
        poseHeading: 0, // 0 to 360
        posePitch: 0, // -90 to 90
        poseRoll: 0, // -180 to 180
      }
    });

    viewer.on('dblclick', function(e, data){
      registerClick(data);
    });
</script>

Now you’ll see the coordinates printed to the console like below.

At this point you can spend some time locating all the eggs so that you have their coordinates. This is a task best done by multiple people and with periodic breaks if there is a large number of eggs with some difficult to find.

Alerting on a Found Egg

Now that we can detect the double click and get the coordinates we need to compare it to the known egg locations. The route I took is to iterate over all of the eggs and seeing how close the click is. The distance can either be determined by comparing the difference in latitude and longitude separately, or by combining them to get the radial distance. I chose to compare separately just as a personal preference.

The egg locations are stored to an array that contains the latitude, longitude, and if the egg has been found.

var eggs = [
    [-0.12587820712970976, 5.510338553759633, 0],
    [-0.028580325945409157, 5.445477327995103, 0],
    [0.053453344915963985, 5.5273236466781634, 0],
    [-0.26486446083318516, 5.881107170695541, 0],
    [0.007088363198832104, 5.822068560570339, 0],
    [-0.12362573067266247, 0.5002947765628827, 0],
    [-0.15758379816854595, 0.7082625359322156, 0],
    [-0.19612747796709296, 0.9357689626661057, 0],
    [-0.10673168638709907, 0.9813403942572025, 0],
    [0.18049113609768863, 0.6241884176319561, 0]
]

The eggs array will be used in a function with the latitude and longitude of the click to see if it is near an egg. If the click is close enough then an alert will appear and the egg will be flagged as found. If someone clicks on an egg that has already been found the alert will say so.

function checkEggs(lat, long) {
    eggs.forEach(function (egg, index) {
        var latDiff = Math.abs(egg[0] - lat);
        var longDiff = Math.abs(egg[1] - long);

        if((latDiff < 0.02) && (longDiff < 0.015)) {
            if(egg[3] == 1) {
                alert("Egg already found.");
            } else {
                alert("You found an Egg!");
                egg[3] = 1;
            }
        }
    });
}

function registerClick(data) {
    console.log("Double Click");
    console.log(`Lat: ${data.latitude}`);
    console.log(`Long: ${data.longitude}`);

    checkEggs(data.latitude, data.longitude)
}

The pop-up produced is fairly simple, and will also trigger most pop-up blockers after enough occurrences.

Marking the Eggs

The final major piece of building out the basic Easter Egg Hunt is to visually mark eggs that have been found. This is another case where the PSV library provides a simple way to handle this. The marking is done by a PSV plugin imaginatively named markers.

The first step is to load in the markers javascript code and stylesheet. Both will be loaded in the head section.

<head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photo-sphere-viewer@4/dist/photo-sphere-viewer.min.css"/>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photo-sphere-viewer@4/dist/plugins/markers.css"/>

    <script src="https://cdn.jsdelivr.net/npm/three/build/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/uevent@2/browser.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/photo-sphere-viewer@4/dist/photo-sphere-viewer.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/photo-sphere-viewer@4/dist/plugins/markers.js"></script>
</head>

With the plugin code loaded we now need to set the plugin to be used by PSV as part of the viewer. This is done by listing it in the plugin section of the viewer initialization. Just below the viewer initialization we will also extract the plugin out into a variable we can use.

var viewer = new PhotoSphereViewer.Viewer({
  container: document.querySelector('#viewer'),
  panorama: 'images/courtyard.jpg',
  panoData: {
    fullWidth: 14440,
    fullHeight: 7220,
    croppedWidth: 14440,
    croppedHeight: 3299,
    croppedX: 0,
    croppedY: 1961,
    poseHeading: 0, // 0 to 360
    posePitch: 0, // -90 to 90
    poseRoll: 0, // -180 to 180
  },
  plugins: [
    [PhotoSphereViewer.MarkersPlugin, {}]
  ]
});

const markersPlugin = viewer.getPlugin(PhotoSphereViewer.MarkersPlugin);

Now we just update out functions to pass the markerPlugin object through until it reached the checkEggs function. In that function we will add code to add a marker whenever an egg is found. This provides our final visual cue that an egg has already been located.

function checkEggs(lat, long, markers) {
    eggs.forEach(function (egg, index) {
        var latDiff = Math.abs(egg[0] - lat);
        var longDiff = Math.abs(egg[1] - long);

        if((latDiff < 0.02) && (longDiff < 0.015)) {
            if(egg[3] == 1) {
                alert("Egg already found.");
            } else {
                alert("You found an Egg!");
                egg[3] = 1;

                markers.addMarker({
                    id: `egg=${index}`,
                    circle: 20,
                    latitude: egg[0],
                    longitude: egg[1],
                    svgStyle: {
                        opacity: '0.5',
                        stroke: 'blue',
                        strokeWidth: '3px',
                        fill: 'light-blue'
                    }
                });
            }
        }
    });
}

If everything is working correctly you will now see a blue circle around eggs after you have located them like below.

Next Steps

Now you have everything needed to build a very simple single panorama egg hunt. In the next guide I will go over adding instructions on the initial load, better alert pop-ups, and adding more panoramas.

Exit mobile version