Azure-Samples / AzureMapsCodeSamples

A set of code samples for the Azure Maps web control.
https://samples.azuremaps.com
MIT License
310 stars 436 forks source link

Unable to interact with WebGLLayer #101

Closed ApoorvaBhagchandani closed 1 year ago

ApoorvaBhagchandani commented 1 year ago

I am currently working on rendering a WebGLLayer using BabylonJS in Typescript similar to the sample given. As there is no option or method for interacting with the layer such as hovering over it or clicking on it, I wanted to ask if anyone had any advice for achieving the same and whether it is on the roadmap for future releases.

Thanks!

rbrundritt commented 1 year ago

The map doesn't expose any events for custom WebGLLayers as it is completely unaware of what is rendered and what/how to interact with the scene. The WebGLLayer is basically an image that is updated every time the map moves. That said, you can interact with it but have to do a little extra work. Here is what I did to get this to work:

  1. Make sure to have access to the scene in the event callback (could be a global variable).
  2. Get the pixel coordinate of the point you are interested within the map. This could be where the mouse pointer clicked, or a geo-position converted to a pixel.
  3. Use the scene's multiPick with the pixel to get all meshes that intersect. Depending on your model, you may want filter out some meshes that you don't want to interact with (e.g. backgrounds).

Here is a simple example that adds support for clicking a model within the layer:

<!DOCTYPE html>
<html lang="en">
<head>
    <title></title>

    <meta charset="utf-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

    <!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
    <link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" rel="stylesheet" />
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>

    <!-- Babylon.js is a powerful, simple, real-time 3D and open game and rendering engine packed into a friendly framework, which Microsoft initially developed. -->
    <script src="https://cdn.babylonjs.com/babylon.js"></script>
    <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>

    <script>
        var map, layer;

        var scene;

        //A list of the mesh names in the model that we want to limit interaction to when "picking" the scene, as some models will include items we to exclude, such as the background.
        var interactiveMeshes = ['dish', 'dish_center', 'quad', 'truss_dish'];

        // Create a renderer that implements atlas.WebGLRenderer
        var renderer = {
            renderingMode: "3d",

            // Method called when the layer is added to the map
            onAdd: (map, gl) => {
                this.map = map;

                // Initialize the Babylon.js engine.
                const engine = new BABYLON.Engine(gl, true, { useHighPrecisionMatrix: true }, true);

                this.scene = new BABYLON.Scene(engine);
                this.scene.autoClear = false;
                this.scene.detachControl();
                this.scene.beforeRender = function () {
                    engine.wipeCaches(true);
                };

                //Store a reference to the scene so we can pick from it later.
                scene = this.scene;

                this.camera = new BABYLON.Camera("camera", new BABYLON.Vector3(), this.scene);
                const light = new BABYLON.HemisphericLight("light", BABYLON.Vector3.One(), this.scene);

                BABYLON.SceneLoader.Append(
                    window.location.href.replace(/babylon.html/gi, '')+ "34m_17/",
                    "34M_17.gltf",
                    this.scene
                )

                // parameters to ensure the model is georeferenced correctly on the map
                const modelOrigin = [148.9819, -35.39847];
                const modelAltitude = 0;
                const modelRotate = [Math.PI / 2, 0, 0];
                const modelCoords = atlas.data.MercatorPoint.fromPosition([...modelOrigin, modelAltitude]);
                const modelScale = 2 * atlas.data.MercatorPoint.meterInMercatorUnits(-35.39847);

                this.modelMatrix = BABYLON.Matrix.Compose(
                    new BABYLON.Vector3(modelScale, modelScale, modelScale),
                    BABYLON.Quaternion.FromEulerAngles(modelRotate[0], modelRotate[1], modelRotate[2]),
                    new BABYLON.Vector3(modelCoords[0], modelCoords[1], modelCoords[2])
                );
            },

            // Method called on each animation frame
            render: (gl, matrix) => {
                // projection & view matrix
                const cameraMatrix = BABYLON.Matrix.FromArray(matrix);
                const mvpMatrix = this.modelMatrix.multiply(cameraMatrix);
                this.camera.freezeProjectionMatrix(mvpMatrix);

                this.scene.render(false);
                this.map.triggerRepaint();
            }
        };

        function GetMap() {
            map = new atlas.Map("map", {
                zoom: 18,
                pitch: 60,
                center: [148.9819, -35.3981],
                style: "satellite_road_labels",
                antialias: true,

                // Add authentication details for connecting to Azure Maps.
                authOptions: {
                    // Use an Azure Maps key. Get an Azure Maps key at https://azuremaps.com/. NOTE: The primary key should be used as the key.
                    authType: 'subscriptionKey',
                    subscriptionKey: '<Your Azure Maps Key>'
                }
            });

            // Wait until the map resources are ready
            map.events.add("ready", function () {
                // Create a WebGL layer
                layer = new atlas.layer.WebGLLayer("babylon", { renderer });
                // Add the layer to the map
                map.layers.add(layer);

                //Add click event to the map.
                map.events.add('click', mapClicked);
            });
        }

        function mapClicked(e) {
            //Get the pixel location that was clicked.
            var pixel = e.pixel;

            //Alternatively, if you can get a pixel for a geo-position and use that.
            //var pixel = map.positionsToPixels([e.position])[0];

            //Pick all meshes that intersect in the scene.
            var pickResults = scene.multiPick(pixel[0], pixel[1]);

            //Loop through and check to see if one of our prefered meshes was picked.
            pickResults.forEach(pickResult => {
                //Check that the pick has a hit and is one of the meshes we want to interact with.
                if (pickResult.hit && interactiveMeshes.indexOf(pickResult.pickedMesh.name) !== -1) {
                    //A mesh we allow to be interacted with in the model has been clicked!
                    console.log(`The ${pickResult.pickedMesh.name} mesh of the model has been clicked!`);                     
                }
            })
        }
    </script>
</head>
<body onload="GetMap()">
    <div id="map" style="position:relative;width:100%;height:600px;"></div>
</body>
</html>
rbrundritt commented 1 year ago

I have a PR that adds this sample to the project.