vasturiano / globe.gl

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

Best way to render 3D markers #90

Closed danman335 closed 2 years ago

danman335 commented 2 years ago

Hi,

I've setup a globe and styled etc - looking now at the best way to render 3D map markers that rise off the earth perpendicular to its surface. Is there anyway to change the column that is generated by the pointsData or would I better adding custom objects with objectsData in the objects later.

Example of below of what I'm trying to emulate:

Screenshot 2022-02-27 at 08 34 10
vasturiano commented 2 years ago

@danman335 thanks for reaching out.

I'd recommend using the Objects Layer for this. That will give you maximum flexibility on how to customize those pin objects.

danman335 commented 2 years ago

Thanks @vasturiano I got it mostly working, only thing I've hit a blocker on is that if I have multiple locations in my objectsData but only one object is rendered onto the globe, I think its to do with me misunderstanding how gtlf loaders and scenes work but is there anything obvious to be done differently here:

initGlobe() {
      const globeHolder = document.querySelector("#globe");
      const myGlobe = Globe({
        animateIn: true,
      });
      myGlobe(globeHolder)
        .globeImageUrl(require("../assets/full-map.jpg"))
        .backgroundColor("#000000")
        .atmosphereColor("#FFFFFF")
        .objectLat("lat")
        .objectLng("lng")
        .objectAltitude("alt")
        .objectLabel("name");

      const loader = new GLTFLoader();
      const self = this;

      loader.load(
        "/mappin.glb",
        function (gltf) {
          self.locations.forEach(() => {
            let scene = new THREE.Scene();
            gltf.scene.scale.set(30, 30, 30);
            scene.add(gltf.scene)
            myGlobe.objectThreeObject(() => scene);
          });
        },
        undefined,
        function (error) {
          console.error(error);
        }
      );

      myGlobe.objectsData(this.locations);
    },

This is my data and the result:

locations: [ { lat: 57.47753490825867, lng: -4.224291359173809, name: "Scotland", alt: 0, }, { lat: 46.227638, lng: 2.213749, name: "France", alt: 0, }, ],

Screenshot 2022-02-28 at 07 06 22
vasturiano commented 2 years ago

@danman335 it should suffice to do myGlobe.objectThreeObject(gltf.scene).

If you use the accessor function (() => scene) you're essentially using the exact same gltf object3d instance for every item in your data, which is what's happening I believe. You see a single object that gets moved around between all the coordinates.

Leaving out the accessor function cause the module to clone the object for each item.

danman335 commented 2 years ago

@vasturiano thanks, I got that - working now thanks. Final question (I promise) - trying to alter the individual rotations of the objectThreeObjects, at the moment they all share the same rotation properties.

If I log myGlobe.objectsData() I can see my individual objects with a reference to the underlying __threeObj but I can't see how to update the rotation of each point individually. Any pointers would be amazing thanks.

vasturiano commented 2 years ago

@danman335 glad you got the markers to work.

In order to rotate the objects you need to manipulate the rotation attribute in each of them: https://threejs.org/docs/#api/en/core/Object3D.rotation

In principle you should be able to use each object's lat,lng coordinates to calculate what the rotation angle of each object should be.

danman335 commented 2 years ago

Hey @vasturiano yeah I found the docs on rotation - what I'm not understanding is how to manipulate the rotate for each individually. I can't access the __threeObj on each individual object, and updating within my loader applies the properties to each object

loader.load(
        "/mappin.glb",
        function (marker) {
          marker.scene.scale.set(30, 30, 30);
          myGlobe.objectThreeObject(marker.scene);
        },
        function (xhr) {
          console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
        },
        function (error) {
          console.log("An error happened", error);
        }

i.e. whats the best way to access the Object3D properties/methods for the objects created passed in via objectsData?

Thanks

vasturiano commented 2 years ago

You can use the objectThreeObject in function accessor mode (like you had originally, but cloning the objects each time). Something like this:

myGlobe.objectThreeObject(item => {
  const obj = marker.scene.clone();
  obj.rotation = /* compute based on item */;
  return obj;
})
danman335 commented 2 years ago

Thanks thats exactly what I was trying to do amazing @vasturiano!

Last question (promise) as each object is a child of a group I cannot get them to lookAt the centre of the globe. Setting lookAt to 0, 0, 0 for each child just looks at the origin of the group that its within. The origin of the globe is 0, 0, 0 so thats why it isn't working, can we move the meshes out of the groups into the scene to do this or would that not work? I'm trying to get the objects to stand perpendicular to the surface of the sphere similar to how the columns do in the points layer.

vasturiano commented 2 years ago

@danman335 that's more of a question on the scope of ThreeJS itself, but I'm guessing you need to the rotation of the object in the world context, not local.

Perhaps you can get the current object rotation in world units and propagate that to your own, via getWorldQuaternion. Actually lookAt should already be doing that in world context, hmmm.

Maybe you need to convert the globe center to local coordinates before using it, like:

const globeCenter = myGlobe.scene().localToWorld(new THREE.Vector3(0, 0, 0));
...
obj.lookAt(globeCenter);