vasturiano / globe.gl

UI component for Globe Data Visualization using ThreeJS/WebGL
https://vasturiano.github.io/globe.gl/example/world-population/
MIT License
2.03k stars 301 forks source link

[Feature request] enhance click callbacks #16

Closed lslzl3000 closed 3 years ago

lslzl3000 commented 4 years ago

Is your feature request related to a problem? Please describe.

  1. There is no callback for clicks on the globe in current version, but we need to know the coords when user click on the globe in many cases. e.g add points/label/.. on user clicks.
  2. The callbacks should also give back the coords for a click
  3. There is only one globe switch for pointer event currently, but sometimes we need to ignore pointer for 1/2 layers. e.g a custom layer( such as particles to simulate clouds) with high altitude value will block every other layers. It should not catch pointer event, and pass through to next layer.

Describe the solution you'd like

  1. Enable click callbacks for globe layer itself.
  2. For all click callbacks in all layers, should also give back the coords of the click
  3. Add config in each layer to ignore/pass-through pointer event, and for custom layer, there should be a config for each obj.
lslzl3000 commented 4 years ago

@vasturiano I tried to dig your code, but your structure is complex for me to change. Therefore, I just disabled the enablePointerInteraction(false) and add a new pointer controller in my code. A simple demo:

onClick(x, y) {
    let cursor = new THREE.Vector2();
    cursor.x = x / myGlobe.width() * 2 - 1;
    cursor.y = -(y / myGlobe.height()) * 2 + 1;
    let raycaster = new THREE.Raycaster();
    raycaster.params.Line.threshold = 1
    raycaster.setFromCamera(cursor, myGlobe.camera())
    let intersects = raycaster.intersectObjects(myGlobe.scene().children, true)

    if (intersects.length > 0) {
        let index = 0
        let globeObj = this.getGlobeObj(intersects[index].object)

                 // if globeObj match the types or with __ignore (for custom layer objs)
                 // then pass through to next one
                let ignoreType = /globe|points|arc/ 
            while ((!globeObj || globeObj.__ignore || ignoreType.test(globeObj.__globeObjType)) && index < intersects.length) {
            index++
            globeObj = this.getGlobeObj(intersects[index].object)
        }
        if (!globeObj)
            return

        let type = globeObj.__globeObjType
        let data = globeObj.__data

                // fire custom callbacks with data and coords of the click
                objFn[type](data, myGlobe.toGeoCoords(intersects[index].point))
    }
}

By the way, with coords returned in pointer events for pointsMerge(true)/hexBinMerge(true), we could find out which point/hex has been clicked by looping the lat/lng in each data

vasturiano commented 4 years ago

@lslzl3000 thanks for your suggestions, they're good ideas. 👍

1) new globe pointer events have been added: onGlobeClick and onGlobeRightClick. They include the clicked polar coordinates, as well as the pointer event: onGlobeClick(({ lat, lng }, event) => ...).

2) All the layer pointer click functions now include a second argument which is the pointer event itself. From this event object one can extract the specific viewport clicked coordinates. Further, these viewport coordinates can be converted into the polar coordinates on the globe's surface directly under the clicked location, using toGlobeCoords(x, y).

3) There is a new filter method that allows this to be configured, it's pointerEventsFilter(obj => ...). That lets you custom configure which objects to ignore (f.e. cosmetic layers like fog or so) and trigger the pointer events on deeper objects instead.