vasturiano/three-globe

Support video texture

Closed this issue ยท 6 comments

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

@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

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

@lslzl3000 nice! Do you have a demo you could share? ๐Ÿ˜ƒ

@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

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)

1.mp4

@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