In this tutorial, we’ll see how to create a VR interface in WebVR. We’ll be using PlaneGeometry and Raycaster to create the VR interface menu.

VR Interface in WebVR

We’ll use PlaneGeometry to create the menu interface in VR. We’ll load the file names for thumbnail and panorama from a JSON file.

This is part of #DaysInVR series. View All VR Projects. Yesterday, we learnt how to create bubble effect in VR. Today we’ll see how to create a VR interface in WebVR.

Join 6000+ Students Upgrade your programming skills to include Virtual Reality. A premium step-by-step training course to get you building real world Virtual Reality apps and website components. You’ll have access to 40+ WebVR & Unity lessons along with their source code. Start Learning Now 150$ for all 3 courses (SAVE 70%)

var listOfPanos = [ { "thumb": "pano2_thumb.jpg", "pano": "/common/pano2.jpg" }, { "thumb": "pano3_thumb.jpg", "pano": "/common/pano3.jpg" }, { "thumb": "pano1_thumb.jpg", "pano": "/common/pano1.jpg" }, { "thumb": "pano4_thumb.jpg", "pano": "/common/snow_pano.jpg" } ]; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var listOfPanos = [ { "thumb" : "pano2_thumb.jpg" , "pano" : "/common/pano2.jpg" } , { "thumb" : "pano3_thumb.jpg" , "pano" : "/common/pano3.jpg" } , { "thumb" : "pano1_thumb.jpg" , "pano" : "/common/pano1.jpg" } , { "thumb" : "pano4_thumb.jpg" , "pano" : "/common/snow_pano.jpg" } ] ;

We’ll use Reticulum for creating our gaze based interface.

Reticulum.init(camera, { proximity: false, clickevents: true, near: null, //near factor of the raycaster (shouldn't be negative and should be smaller than the far property) far: null, //far factor of the raycaster (shouldn't be negative and should be larger than the near property) reticle: { visible: true, restPoint: 1000, //Defines the reticle's resting point when no object has been targeted color: 0xcc0000, innerRadius: 0.0001, outerRadius: 0.003, hover: { color: 0xcc0000, innerRadius: 0.009, outerRadius: 0.011, speed: 5, vibrate: [] //Set to 0 or [] to disable } }, fuse: { visible: true, duration: 1.5, color: 0x00fff6, innerRadius: 0.035, outerRadius: 0.04, vibrate: [], //Set to 0 or [] to disable clickCancelFuse: false //If users clicks on targeted object fuse is canceled } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Reticulum . init ( camera , { proximity : false , clickevents : true , near : null , //near factor of the raycaster (shouldn't be negative and should be smaller than the far property) far : null , //far factor of the raycaster (shouldn't be negative and should be larger than the near property) reticle : { visible : true , restPoint : 1000 , //Defines the reticle's resting point when no object has been targeted color : 0xcc0000 , innerRadius : 0.0001 , outerRadius : 0.003 , hover : { color : 0xcc0000 , innerRadius : 0.009 , outerRadius : 0.011 , speed : 5 , vibrate : [ ] //Set to 0 or [] to disable } } , fuse : { visible : true , duration : 1.5 , color : 0x00fff6 , innerRadius : 0.035 , outerRadius : 0.04 , vibrate : [ ] , //Set to 0 or [] to disable clickCancelFuse : false //If users clicks on targeted object fuse is canceled } } ) ;

Once we initialize it, we’ll create the pano items for the menu. Each pano in the list is added. The thumbnail is loaded as the texture of the plane. The url of the panorama is added in userData object. When the user gazes at the thumbnail for the set duration, the pano is fetched and loaded.

for (var i = 0; i < listOfPanos.length; i++) { var planeGeometry = new THREE.PlaneGeometry(3, 1); var planeMaterial = new THREE.MeshBasicMaterial({ map: loader.load(listOfPanos[i].thumb), side: THREE.DoubleSide }) var plane = new THREE.Mesh(planeGeometry, planeMaterial); plane.position.y = positionY; positionY += 1.5; plane.userData = { pano: listOfPanos[i].pano } plane.position.z = -10; plane.rotation.x = Math.PI / 180 * 20; scene.add(plane); Reticulum.add( plane, { clickCancelFuse: true, // Overrides global setting for fuse's clickCancelFuse reticleHoverColor: 0x00fff6, // Overrides global reticle hover color fuseVisible: true, // Overrides global fuse visibility fuseDuration: 1.5, // Overrides global fuse duration fuseColor: 0xffffcc, // Overrides global fuse color onGazeOver: function(){ // do something when user targets object this.scale.set(1.1, 1.1, 1.1, 1.1); }, onGazeOut: function(){ // do something when user moves reticle off targeted object this.scale.set(1, 1, 1); }, onGazeLong: function(){ // do something user targetes object for specific time // this.material.emissive.setHex( 0x0000cc ); var userPano = this.userData.pano; loader.load(userPano, function(texture) { if (userPano !== lastLoadedPano) { material.map = texture; } lastLoadedPano = userPano; }); }, onGazeClick: function(){ // have the object react when user clicks / taps on targeted object } }); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 for ( var i = 0 ; i < listOfPanos . length ; i ++ ) { var planeGeometry = new THREE . PlaneGeometry ( 3 , 1 ) ; var planeMaterial = new THREE . MeshBasicMaterial ( { map : loader . load ( listOfPanos [ i ] . thumb ) , side : THREE . DoubleSide } ) var plane = new THREE . Mesh ( planeGeometry , planeMaterial ) ; plane . position . y = positionY ; positionY += 1.5 ; plane . userData = { pano : listOfPanos [ i ] . pano } plane . position . z = - 10 ; plane . rotation . x = Math . PI / 180 * 20 ; scene . add ( plane ) ; Reticulum . add ( plane , { clickCancelFuse : true , // Overrides global setting for fuse's clickCancelFuse reticleHoverColor : 0x00fff6 , // Overrides global reticle hover color fuseVisible : true , // Overrides global fuse visibility fuseDuration : 1.5 , // Overrides global fuse duration fuseColor : 0xffffcc , // Overrides global fuse color onGazeOver : function ( ) { // do something when user targets object this . scale . set ( 1.1 , 1.1 , 1.1 , 1.1 ) ; } , onGazeOut : function ( ) { // do something when user moves reticle off targeted object this . scale . set ( 1 , 1 , 1 ) ; } , onGazeLong : function ( ) { // do something user targetes object for specific time // this.material.emissive.setHex( 0x0000cc ); var userPano = this . userData . pano ; loader . load ( userPano , function ( texture ) { if ( userPano !== lastLoadedPano ) { material . map = texture ; } lastLoadedPano = userPano ; } ) ; } , onGazeClick : function ( ) { // have the object react when user clicks / taps on targeted object } } ) ; }

Its your turn

Add more panoramas to the list and create a horizontal menu. Don’t forget to share what you made in the comments.