nikolay-k
11/20/2015 - 3:51 PM

360 Panorama image in spherical object.

360 Panorama image in spherical object.

// sphere panorama control
window.addEventListener("load", function () {
	"use strict";
	
	// prepare screen
	var supportOrientation = 'orientation' in screen || 'orientation' in window;
	var forceSphericalTexture = false;
	var forceGyroscope = false;
	var renderer = new THREE.WebGLRenderer();
	var view = document.getElementById("sphericalTexture");
	var w = view.offsetWidth, h = view.offsetHeight;
	renderer.setSize(w, h);

	var onPointerDownPointerX = 0,
		onPointerDownPointerY = 0,
		onPointerDownLon = 0,
		onPointerDownLat = 0;
	
	// prepare camera
	var camera = new THREE.PerspectiveCamera(75, w / h, 1, 1000);
	camera.position.set(0, 0, 0);
	camera.up.set(0, 1, 0);
	camera.lookAt(new THREE.Vector3(0, 0, -1));

	// load texture
	// touch devices
	view.addEventListener('touchstart', loadSphericalTexture, false);

	// load texture
	// mouse
	view.addEventListener('click', loadSphericalTexture, false);

	// load texture function
	function loadSphericalTexture() {
		if (!forceSphericalTexture) {
			view.appendChild(renderer.domElement);
			forceSphericalTexture = true;
		};

		if (supportOrientation && window.DeviceOrientationEvent) {
			view.classList.add('gyroscope');
			elementEnterFullScreen();
		};
	};

	view.addEventListener("touchstart", function (event) {
		onPointerDownPointerX = event.changedTouches[0].pageX;
		onPointerDownPointerY = event.changedTouches[0].pageY;
		onPointerDownLon = lon;
		onPointerDownLat = lat;
		view.addEventListener("touchmove", changedTouches, false);
	}, false);

	view.addEventListener("touchend", function (event) {
		view.removeEventListener("touchmove", changedTouches, false);
	}, false);
	

	// force gyroscope orientation
	if (supportOrientation && window.DeviceOrientationEvent) {
		document.getElementById('toggleGyroscope').addEventListener('touchstart', toggleGyroscope, false);
	};
	
	view.addEventListener("mousedown", function (event) {
		view.addEventListener("mousemove", gyroMouse, false);
	}, false);
	view.addEventListener("mouseup", function (event) {
		view.removeEventListener("mousemove", gyroMouse, false);
	}, false);

	// resize
	window.addEventListener('resize', onWindowResized, false);

	function onWindowResized(event) {
		renderer.setSize(view.offsetWidth, view.offsetHeight);
	}

	// enter fullscreen
	function elementEnterFullScreen() {
		screen.orientation.lock('landscape');

		if (!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement ) {
			if (view.requestFullscreen) {
				view.requestFullscreen();
			} else if (view.msRequestFullscreen) {
				view.msRequestFullscreen();
			} else if (view.mozRequestFullScreen) {
				view.mozRequestFullScreen();
			} else if (view.webkitRequestFullscreen) {
				view.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
			}

			view.classList.add('fullscreen');
		}
	}

	document.getElementById('sphericalFullscreenClose').addEventListener('touchstart', elementExitFullScreen, false);

	function elementExitFullScreen(event) {
		event.preventDefault();
		event.stopPropagation();

		screen.orientation.unlock();

		if (document.exitFullscreen) {
			document.exitFullscreen();
		} else if (document.msExitFullscreen) {
			document.msExitFullscreen();
		} else if (document.mozCancelFullScreen) {
			document.mozCancelFullScreen();
		} else if (document.webkitExitFullscreen) {
			document.webkitExitFullscreen();
		}

		view.classList.remove('fullscreen');
	}

	// fullscreenchange
	view.addEventListener("mozfullscreenchange", function(event) {
		onFullScreenExit();
	});

	view.addEventListener("webkitfullscreenchange", function(event) {
		onFullScreenExit();
	});

	view.addEventListener("fullscreenchange", function(event) {
		onFullScreenExit();
	});

	function onFullScreenExit() {
		if ( (document.fullscreenEnabled || document.mozFullscreenEnabled || document.webkitIsFullScreen) === false ) {
			screen.orientation.unlock();
			view.classList.remove('fullscreen');
		};
	}

	function toggleGyroscope(event) {
		event.preventDefault();
		event.stopPropagation();

		if (!forceGyroscope) {
			forceGyroscope = true;
			window.addEventListener("deviceorientation", gyroSensor, false);
		} else {
			forceGyroscope = false;
			window.removeEventListener("deviceorientation", gyroSensor, false);
		};
	}
	
	// camera rotation by mouse
	var lon = 0;
	var lat = 0;
	var gyroMouse = function (event) {
		var mx = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
		var my = event.movementY || event.mozMovementY || event.webkitMovementY || 0;

		lat = Math.min(Math.max(-Math.PI / 2, lat - my * 0.01), Math.PI / 2);
		lon = lon - mx * 0.01;
		
		var rotm = new THREE.Quaternion().setFromEuler(new THREE.Euler(lat, lon, 0, "YXZ"));
		camera.quaternion.copy(rotm);
	};
		var onPointerDownLon = lon;
		var onPointerDownLat = lat;
	var changedTouches = function (event) {
		event.preventDefault();

		lon = (event.changedTouches[0].pageX - onPointerDownPointerX) * -0.01 + onPointerDownLon;
		lat = (event.changedTouches[0].pageY - onPointerDownPointerY) * -0.01 + onPointerDownLat;
		
		var rotm = new THREE.Quaternion().setFromEuler(new THREE.Euler(lat, lon, 0, "YXZ"));
		camera.quaternion.copy(rotm);
	};

	
	// camera rotation by direct gyro sensor angles on tablets
	// see: http://mdn.io/Detecting_device_orientation
	// (work on android firefox and iOS Safari)
	var eyem = new THREE.Quaternion().setFromEuler(
		new THREE.Euler(-Math.PI / 2, 0, 0));
	var d2r = Math.PI / 180;
	var getOrientation = function () {
		// W3C DeviceOrientation Event Specification (Draft)
		if (window.screen.orientation) return window.screen.orientation.angle;
		// Safari
		if (typeof window.orientation === "number") return window.orientation;
		// workaround for android firefox
		if (window.screen.mozOrientation) return {
			"portrait-primary": 0,
			"portrait-secondary": 180,
			"landscape-primary": 90,
			"landscape-secondary": 270,
		}[window.screen.mozOrientation];
		// otherwise
		return 0;
	};
	var gyroSensor = function (event) {
		event.preventDefault();
		var angle = getOrientation();
		var alpha = event.alpha || 0;
		var beta = event.beta || 0;
		var gamma = event.gamma || 0;
		if (alpha === 0 && beta === 0 && gamma === 0) return;
		// on android chrome: bug as beta may become NaN
		
		// device rot axis order Z-X-Y as alpha, beta, gamma
		// portrait mode Z=rear->front(screen), X=left->right, Y=near->far(cam)
		// => map Z-X-Y to 3D world axes as:
		// - portrait  => y-x-z
		// - landscape => y-z-x
		// var rotType = (angle === 0 || angle === 180) ? "YXZ" : "YZX";
		var rotType = 'YXZ';
		var rotm = new THREE.Quaternion().setFromEuler(new THREE.Euler(beta * d2r, alpha * d2r, -gamma * d2r, rotType));
		var devm = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, -angle * d2r, 0));
		rotm.multiply(devm).multiply(eyem); //rot = (rot x dev) x eye
		camera.quaternion.copy(rotm);
	};
	
	// panorama image texture
	var img = document.createElement("img");
	img.crossOrigin = "anonymous";
	var tex = new THREE.Texture(img);
	img.addEventListener("load", function () {
		tex.needsUpdate = true;
	}, false);
	var mat = new THREE.MeshBasicMaterial({map: tex});
	var geom = new THREE.SphereGeometry(500, 64, 32); // sphere type
	geom.applyMatrix(new THREE.Matrix4().makeScale(1, 1, -1)); //surface inside
	var obj = new THREE.Mesh(geom, mat);
	
	// create scene
	var scene = new THREE.Scene();
	scene.add(obj);

	var replaceTexture = function () {
		var source = view.dataset.image;
		var img = document.createElement("img");
		img.crossOrigin = "anonymous";
		img.src = source;
		var tex = new THREE.Texture(img);
		mat.map = tex;
		img.addEventListener("load", function () {
			tex.needsUpdate = true;
			mat.map.needsUpdate = true; // important for replacing textures
		}, false);
	};
	replaceTexture();
	
	// play animation
	var loop = function loop() {
		requestAnimationFrame(loop);
		renderer.clear();
		renderer.render(scene, camera);
	};
	loop();
}, false);