vasturiano / three-globe

WebGL Globe Data Visualization as a ThreeJS reusable 3D object
https://vasturiano.github.io/three-globe/example/links/
MIT License
1.22k stars 147 forks source link

Support video texture #10

Closed lslzl3000 closed 4 years ago

lslzl3000 commented 4 years ago

Is your feature request related to a problem? Please describe. My project requires very complex dynamic effects on the globe surface, it's hard to code in three.js, but we cloud make a 2:1 globe video to simulate all effects. Three.js has a video texture demo, so is there possible to render video texture on the globe?

Describe the solution you'd like Like the demo, we cloud customize a video texture in globeMaterial()

In addition, like #9, support image/video content on the globe with particular location would be a nice feature

vasturiano commented 4 years ago

@lslzl3000 thanks for reaching out.

That's an interesting request. Is this something you could potentially achieve by manipulating the globe wrapping material, accessible via .globeMaterial()?

Alternatively you could create a custom layer that renders another sphere (or fraction of a sphere) with a video texture on.

Globe material example: https://github.com/vasturiano/three-globe/blob/master/example/custom-material/index.html#L23-L27

Custom layer example: https://github.com/vasturiano/three-globe/blob/master/example/custom/index.html#L24-L34

lslzl3000 commented 4 years ago

Thanks for your reply. I've successfully created surface image/video labels in the custom layer. So I'll close this issue

vasturiano commented 4 years ago

@lslzl3000 nice! Do you have a demo you could share? 😃

lslzl3000 commented 4 years ago

@vasturiano here is a simple demo, hope to add image/video with given lat/lng and given size.

// create a blank sphere with given w&h
function addSurfacePanel(lat, lng, alt, width, height) {
    let R = 100 * (1 + alt)
    let geometry = new THREE.SphereBufferGeometry(R, 160, 160, Math.PI / 180 * (90 - width / 2), Math.PI / 180 * width, Math.PI / 180 * (90 - height / 2), Math.PI / 180 * height)
    let material = new THREE.MeshBasicMaterial()
    let sphere = new THREE.Mesh(geometry, material)

        // added in a group, easy rotation
        let group = new THREE.Group()
        group.add(sphere)
        // I don't use lat/lng here, cause we need to update location in customThreeObjectUpdate anyway
    return group
}

// load texture
function loadTexture(url, onProgress) {
    return new Promise((resolve, reject) => {
        new THREE.TextureLoader().load(url, resolve, onProgress, reject)
    })
}

// e.g. load a image at (0,0), a video at(0,180)
const customLayerData = [
    { type: 'image', id: 't1', lat: 0, lng: 0, alt:0.01, width: 60, height: 40, url: '/image/earth-day.jpg' },
    { type: 'video', id: 't2', lat: 0, lng: 180,  alt:0.01, width: 60, height: 40, url: '/video/demo.mp4', autoplay: false }
]

globe.customLayerData(customLayerData)
    .customThreeObject(d => {
        switch (d.type) {
            case 'image':
                return addSurfacePanel(d.lat, d.lng, d.alt, d.width, d.height)
                break
            case 'video':
                // for video, create a video element first
                let video = document.createElement('video')
                video.id = d.id
                video.src = d.url
                video.style.display = 'none'
                document.body.appendChild(video)
                // this will be easier when use Vue/React/.. to manage dom

                return addSurfacePanel(d.lat, d.lng,  d.alt, d.width, d.height)
                break
            // other types
        }
    }).customThreeObjectUpdate(async (group, d) => {
                 // first, update lat/lng, I used group here, maybe you can come up with a better solution
                let obj = group.children[0]
                 // make sure group is at origin
        group.position.set(0, 0, 0)
                 // rotate sphere along with x as lat
        obj.rotation.x = Math.PI / 180 * -d.lat
                 // then rotate group along with y as lng
        group.rotation.y = Math.PI / 180 * d.lng

                // then update texture
        let texture
        switch (d.type) {
            case 'image':
                texture = await loadTexture(d.url)
                obj.material.map = texture
                obj.material.needsUpdate = true
                break
            case 'video':
                let video = document.querySelector('video#'+d.id) // could access easily in Vue/React...
                if (d.autoplay)
                    video.play()
                else
                    video.currentTime = 1

                // replace texture with videoTexture
                texture = new THREE.VideoTexture(video)
                texture.minFilter = THREE.LinearFilter
                texture.magFilter = THREE.LinearFilter
                texture.format = THREE.RGBFormat
                obj.material.map = texture
                obj.material.needsUpdate = true
                break
                          case 'text': // could also make curved text label
                                ...
                                break
        }
    }).onCustomLayerClick(obj => {
        if (obj.type == 'video') {
            let video = document.querySelector('video#'+d.id) // could access easily in Vue/React...
            video.paused ? video.play() : video.pause()
        }
    })

It works fine: e.g. Static image Screen Shot 2020-07-16 at 20 01 07

e.g Live video play Screen Shot 2020-07-16 at 20 01 25

Maybe we could make new image/video layers in this project. I think it would be a good feature

bumbeishvili commented 2 years ago

The video layer is potentially very useful.

It can be used to visualize different kinds of animated data from

http://nomads.ncep.noaa.gov

Here is a water vapor example (it uses video overlay but on top of a 2d map)

https://user-images.githubusercontent.com/6873202/152752302-339eb1d7-5e7a-479f-a621-58c500803b6d.mp4

vasturiano commented 2 years ago

@bumbeishvili thanks for your pointer.

Also worth noting that this can now more easily be achieved with the tiles layer. See docs here: https://github.com/vasturiano/three-globe#tiles-layer

Essentially each custom positioned tile can be setup with its own tileMaterial which can be linked to a video, using Three's VideoTexture.

Alternatively the same thing could potentially be achieved in the same manner as the clouds example, by extending the existing scene with a globe wide object showing the water vapor. That is even a more flexible approach for some custom use cases. You can see the relevant code here: https://github.com/vasturiano/globe.gl/blob/3e3140fdf9595439d15ebbd3a198948dd071af59/example/clouds/index.html#L29-L40