Virtual Reality Blog

Open Source WebXR A-frame For The Browser

Metaverse Server for VR headset on your home network with a Raspberry Pi Model 4 8GB.


As I finish up this blog in 2021 and the last articles, I sure hope you enjoy it as much as I did writing, and coding it. Be sure to check out a long technically detailed article.





Welcome VR Coders and Creators,

   You've come to the right place! I really put the polish on this blog. It represents my four years work learning and coding the latest in browser based VR, now WebXR compatible. However, many things are changing very fast in this space, and I can't guarantee everything will keep working because of all the changes in WebXR specifications, the browsers, A-Frame, Three.js, hardware, new features, etc. It's harder to hit than a moving target . . .



I've included plenty of helpful links to external content; all I can really hope for is that it helps you. After you've digested this content, please be sure to checkout my VR sandbox server, and follow me on twitter and Medium.

Over the next decade I intuit massive changes in the VR and AR landscape, hardware and its intersection with the internet global community. Remember that "open", and open source really is always better for everyone. Please help to build the metaverse! Even though some don't believe this is a goal for the WebXR spec, I personally believe WebXR can be used to start building it.

UPDATES: I'd like to emphasize some recent articles I've written about NFTs and a Metaverse Server for your VR headset using the home network with a Raspberry Pi Model 4 8GB near the very end of this blog. I've arranged things chronologically, as my skills and understanding of WebXR has evolved, all while writing about it.

The Pi is a great development environment. It has a very established “Maker Community” with a large following, and support. Also, Network-Aframe, which is a multi-user version of VR talk with WebXR, and WebRTC, enabled in a browser, allows you to communicate inside a VR headset. So setting that up with the Pi on a home network provides for “no additional cost hosting” (since you already pay for your internet connection anyway), and no latency response over wi-fi (say with an Oculus Quest 2) locally. But still, all accessible over the internet too. Providing for an alternative method of development and testing to Glitch or Cloud. More like Edge, as in "Edge Computing". Or bleeding edge, Hahah!

Still, I believe it to be an elegant affordable solution for someone on a budget, or who just likes to tinker, as well as a great learning tool for those wanting to code VR for the metaverse.

Ultimately, this little metaverse server on a Pi has the potential of opening up and combining VR with IoT for the home, and possibly even AI, if paired with something like a Google’s Coral, or integrate with Nvidia’s Jetson or Xavier in future articles.

Personally, to me this all feels much like the Dot.com boom and bust era, the beginnings of the commercialization of the internet and it's technologies. Because, I've been around for a while, seen a few things . . . Peace.

- Michael McAnally March 16, 2022




WHAT IS WEBXR? (Be sure to read my articles on Medium, which go further into details for the beginner basics.)



LATEST TRENDS


Vitual Reality Hardware:
Oculus Quest 2 Video Review

Where to begin with VR in a browser?


If you are just starting out, you should learn the basics for animation, shadows, tracks, environment, 3D modeling, audio and video.

FREE TUTORIALS:
You can find most of what you need in my latest article on Medium - March 24, 2021 (follow me there for all my current writings):


Do I Need A VR Headset?

For a primer on current Virtual Reality in general, this article is very informative.

It is now the end of 2020 and VR for the web in a browser is quickly maturing. First, there was WebVR and now there is WebXR. These specifications allow for coding of VR and AR applications inside an internet browser, which are then viewed with a connected VR headset.

Open source repositories like A-Frame provides for easy VR coding in HTML5 and JavaScript. This is a very exciting time, and I encourage you all to explore and try coding VR for the web, in whatever tools or platforms you choose!

Unfortunately aside from serious gamers, engineers, scientists and some computer enthusiasts like myself, most people at this time don't own a VR Headset! So this blog may or may not be very useful to you unless you buy one or can get access to one.

Tethered Oculus Rift, HTC Vive and Windows Mixed Reality VR headsets should all work with code on this website as well as the standalone Oculus Quest 2. If you are developing VR, I still recommend a powerful VR compatible computer with a discrete graphics card; in fact, having a variety of different types of vendor VR headsets to test on, tethered and standalone, is highly recommended to insure cross vender product compatibility and a consistent user experience, as well as doing thorough cross browser testing.

Given some time (probably in the mid-to-late 2020's) I truly believe VR headsets and AR ones will merge and become as indespensible as the mouse is for 2D screens. No more sticking your silly smart phones into cardboard, that time has come and gone.

Using A Browser To Access Virtual Reality


Make sure you allow virtual reality inside your browser. Audio too, etc. The interface will be different for each browser vendor (Chrome desktop shown).

HOW TO VISIT VR FROM THIS WEBSITE ADDRESS ( https://rocketvirtual.com ):

1. Click on the images below to visit each VR example.

2. Please wait patiently for them to load.


3. Then click the VR button in the bottom right corner of the screen when it appears.

4. Finally put on your VR headset and enjoy.
For those who are already wearing a VR Headset and viewing this page from inside a WebXR enabled browser, you can skip this step.

For use of the (2) 6DoF controllers while in VR: Right controller trigger selects, Left controller trigger teleports and one of the Thumbsticks or Pad (depending on your controllers vendor) moves you around.


Space Carousel

Simple VR game in a browser

A Space Carousel ride experience in WebXR populated with flying objects. The theme of the game experience is a merry-go-round or carousel. An ancient contraption enjoyed by children of all ages, with looping movement up and down. I instead reversed things so that the player goes up and down and things go around and around them. I believe it is a simple and good example using mostly Vanilla JavaScript.

Source Code For Space Carousel: 600 lines, commented.

 
         
            

<!DOCTYPE>
<html>
  <head>
    <meta charset="utf-8">
    <title>Space Carousel (loading . . . may make u dizzy)</title>
    <meta name="description" content="A Yo-yo Space Carousel ride experience in WebXR populated with flying objects. Coded By Michael McAnally, August 13, 2018 using A-Frame, updated to A-Frame 1.0.4 master on August 9, 2020."></meta>
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="gray-translucent" />

    <!-- A-frame component libraries, look them up on Github -->
    <script src="aframe-master/dist/aframe-v1.0.4.min.js"></script>
    <script src="aframe-extras-master/dist/aframe-extras.min.js"></script>
    <script src="aframe-environment-component-master/dist/aframe-environment-component.min.js"></script>
    <script src="superframe-master/components/thumb-controls/dist/aframe-thumb-controls-component.min.js"></script>
<!-- Removed teleport for now    <script src="aframe-teleport-controls-master/dist/aframe-teleport-controls.js"></script>   -->
    <script src="superframe-master/components/text-geometry/dist/aframe-text-geometry-component.min.js"></script>
    <script src="aframe-alongpath-component-master/dist/aframe-alongpath-component.min.js" ></script>
    <script src="aframe-curve-component-master/dist/aframe-curve-component.min.js"></script>
<!-- Removed lensflare for now       <script src="aframe-lensflare-component-master/dist/aframe-lensflare-component.min.js"></script>  -->
<!-- Add rotating advertisement  -->
    <script src="aframe-multisrc-component-master/dist/aframe-multisrc-component.js"></script>

    <script type="text/javascript">

// Yes, I'm using some global variables here.

var numberOfArrayDimension = 9;   //IMPORTANT: set to max number of first index of array dimension when adding a new array

var planets = ["forest", "yavapai", "contact", "egypt", "checkerboard", "goaland", "goldmine", "threetowers", "poison", "arches", "tron", "dream", "starry", "osiris"];

var m = 1;  //counter to visit another planet  
var k = 15;  //counter to reset 18 objects visible again

var indexElement = 0;  //reset to array index

var counttx = 0, countup = true;

var nIntervId;

// Sound effects for objects loaded
var squawk = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/duck.mp3');
var brea = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/horse.mp3');
var thrust = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/rocket.mp3');
var zap = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/raygun.mp3');
var flyscr = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/saucer.mp3');
var troar = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/trex.mp3');
var croar = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/ceratops.mp3');
var keysnd = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/keys.mp3');
var heartsnd = new Audio('https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/magical_sparkle.mp3');

// originally was part of a point system, now changed to informational notes when objects are shot.
function startPoints() {
    //nIntervId = setInterval(fadePoints, 600);
}
 
function fadePoints()
{
  if (countup)
  {
    counttx = counttx + .10;
    if (counttx >= 1)
      countup = false;
  }
  else
  {
    counttx = counttx - .10;
    if (counttx <= 0)
      countup = true;
  }
  
  document.getElementById('Points').setAttribute('material', 'opacity: ' + counttx.toString());

}
 
function stopPoints() {

      //document.getElementById('Points').setAttribute('material', 'opacity: 0');
      //clearInterval(nIntervId);
}
  

function changePlanet() {
    m = m + 1;
    if (m > planets.length)  { 
        m = 0;  //reset to array beginning
    }
    // change our environment, may pause play for a moment and redraw
    document.getElementById('onplanet').setAttribute('environment', 'preset: ' + planets[m]);
}


// royalty free sounds
function shoot_raygun(carousel_object) {

    if (indexElement > 5)  { 
        indexElement = 1;  //reset to array index
    }


  switch(carousel_object) {
    case "horse":
          //startPoints();
          brea.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[0][indexElement]);
        break;
    case "duck":
          //startPoints();
          squawk.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[1][indexElement]);
        break;
    case "rocket":
          //startPoints();
          thrust.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[2][indexElement]);
        break;
    case "raygun":
          startPoints();
          zap.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[3][indexElement]);
        break;
    case "saucer":
          //startPoints();
          flyscr.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[4][indexElement]);
        break;
    case "trex":
          //startPoints();
          troar.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[5][indexElement]);
        break;
    case "ceratops":
          //startPoints();
          croar.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[6][indexElement]);
        break;
    case "heart":
          //startPoints();
          heartsnd.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[7][indexElement]);
        break;
    case "key":
          //startPoints();
          keysnd.play();
          //document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[8][indexElement]);
        break;
    default:

    }

    indexElement = indexElement + 1;


}


// Creates a multi-dimensional array

function createArray(length) {
    var arr = new Array(length || 0),
        i = length;

    if (arguments.length > 1) {
        var args = Array.prototype.slice.call(arguments, 1);
        while(i--) arr[length-1 - i] = createArray.apply(this, args);
    }

    return arr;
}


// create a large 2 dimensional global array
funFacts = createArray((numberOfArrayDimension + 1), 45);  // Note: first index in createArray is +1 more than the numberOfArrayDimension

// load array values
// Note: the 0 element is the object name

// horse
funFacts[0][0] = "Equus caballus: common name is\n Horse.";
funFacts[0][1] = "Horses can sleep both lying\n down and standing up.";
funFacts[0][2] = "Horses can run shortly after\n birth.";
funFacts[0][3] = "Horses have been domesticated\n for over 5000 years.";
funFacts[0][4] = "Horses have bigger eyes than\n any other mammal on land.";
funFacts[0][5] = "Horses have evolved from much\n smaller ancestors.";

// duck
funFacts[1][0] = "Anas platyrhynchos: common name\n is Duck.";
funFacts[1][1] = "A yellow rubber duck has achieved\n iconic status in western pop culture.";
funFacts[1][2] = "The rubber duck is symbolically\n linked with bathing.";
funFacts[1][3] = " ";
funFacts[1][4] = " ";
funFacts[1][5] = " ";

// rocket
funFacts[2][0] = "Rocket";
funFacts[2][1] = "The first rocket was invented by\n the Chinese around year 1200.";
funFacts[2][2] = "Rockets were originally used for\n fireworks and for rescuing people at sea.";
funFacts[2][3] = "Robert Goddard is considered the father\n of modern rocketry. He built the first\n liquid-fueled rocket in 1926.";
funFacts[2][4] = "A typical rocket produces more than a\n million pounds of thrust.";
funFacts[2][5] = "Launch rockets are actually several\n rockets linked together.";

// raygun
funFacts[3][0] = "Raygun";
funFacts[3][1] = "A raygun is a science fiction particle-beam\n weapon that fires what is usually destructive energy.";
funFacts[3][2] = "They have various alternate names:\n ray gun, death ray, beam gun, blaster,\n laser gun, laser pistol, phaser, zap gun, etc.";
funFacts[3][3] = "A very early example of a raygun is\n the Heat-Ray featured in H. G. Wells'\n novel The War of the Worlds (1898).";
funFacts[3][4] = "Science fiction during the 1920s\n described death rays.";
funFacts[3][5] = " ";

// saucer
funFacts[4][0] = "Flying Saucer";
funFacts[4][1] = "The term UFO for Unidentified Flying\n Object was coined by US Air Force officer\n Edward Ruppelt in 1952.";
funFacts[4][2] = "Between 1947 and 1969, 12,618 UFO\n sightings were reported to Project\n Blue Book.";
funFacts[4][3] = "In Roswell, New Mexico on July 8th of 1947,\n an information office publicized the recovery\n of a crashed flying disc.";
funFacts[4][4] = "The book Flying Saucers from Outer Space,\n written by author Donald E. Keyhoe in\n 1953, used the term UFO.";
funFacts[4][5] = " ";

// trex
funFacts[5][0] = "Tyrannosaurus";
funFacts[5][1] = "Tyrannosaurus commonly refered to\n as T-Rex";
funFacts[5][2] = "A person who studies dinosaurs\n is called a paleontologist.";
funFacts[5][3] = "Dinosaurs lived on earth until\n 65 million years ago.";
funFacts[5][4] = " ";
funFacts[5][5] = " ";
        
// ceratops
funFacts[6][0] = "Triceratops";
funFacts[6][1] = "Modern birds descended from a group\n of two-legged dinosaurs known as theropods.";
funFacts[6][2] = "Scientist believe dinosaurs became\n extinct after a large metior impacted\n the earth.";
funFacts[6][3] = " ";
funFacts[6][4] = " ";
funFacts[6][5] = " ";

// Heart
funFacts[7][0] = "heart";
funFacts[7][1] = " ";
funFacts[7][2] = " ";
funFacts[7][3] = " ";
funFacts[7][4] = " ";
funFacts[7][5] = " ";

// key
funFacts[8][0] = "key";
funFacts[8][1] = " ";
funFacts[8][2] = " ";
funFacts[8][3] = " ";
funFacts[8][4] = " ";
funFacts[8][5] = " ";



// Component to do on click.
AFRAME.registerComponent('cursor-listener', {
  init: function () {

    this.el.addEventListener('click', function (evt) {
    //count down the opbjects
    k = k - 1;

    if (k < 1)  { 
        k = 15;  //reset to beginning

        //return all 15 objects to view
        document.getElementById('sittingDuckA').setAttribute('visible', true);
        document.getElementById('sittingDuckB').setAttribute('visible', true);
        document.getElementById('sittingDuckC').setAttribute('visible', true);
        document.getElementById('sittingDuckD').setAttribute('visible', true);
        document.getElementById('sittingDuckE').setAttribute('visible', true);
        document.getElementById('sittingDuckF').setAttribute('visible', true);
        document.getElementById('sittingDuckG').setAttribute('visible', true);
        document.getElementById('sittingDuckH').setAttribute('visible', true);
        document.getElementById('sittingDuckI').setAttribute('visible', true);
        // spinning hearts
        document.getElementById('sittingDuckK').setAttribute('visible', true);
        document.getElementById('sittingDuckL').setAttribute('visible', true);
        document.getElementById('sittingDuckM').setAttribute('visible', true);

        document.getElementById('sittingDuckO').setAttribute('visible', true);
        document.getElementById('sittingDuckP').setAttribute('visible', true);
        document.getElementById('sittingDuckQ').setAttribute('visible', true);

        //visit another planet
        m = m + 1;
        if (m > planets.length)  { 
            m = 0;  //reset to array beginning
        }
        // change our environment, may pause play for a moment and redraw
        document.getElementById('onplanet').setAttribute('environment', 'preset: ' + planets[m]);

    } else {

        // remove clicked object from view
        this.setAttribute('visible', false);

    }

    });
  }
});

//   Solves Google mute of audio problem (and it is...) https://stackoverflow.com/questions/47921013/play-sound-on-click-in-a-frame?answertab=active#tab-top

AFRAME.registerComponent('audiohandler', {
  init:function() {
     let playing = false;
     let audio = document.querySelector("#playAudio");
     this.el.addEventListener('click', () => {

          if(!playing) {
              audio.play();
           } else {
              audio.pause();
              audio.currentTime = 0;
           }
           playing = !playing;
     });
  }
})

function playSound() {

//alert("TEST Sound playing functional!!!");
}

  </script>

  </head>
<body>
    <button id="playButton" type="button">Play Music</button>
    <!-- I like the sound of this fun, joyful Carnival Banna tune for game: this "Royalty Purchase" for 10,000 downloads -->
    <audio id="playAudio" autoplay loop>
        <source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/Carnival_Banana.mp3" type="audio/mpeg">
    </audio>
    
<!-- to embed uncomment this and style a-scene above    <a-scene embedded>  -->
    <a-scene cursor="rayOrigin: mouse; fuse: false" raycaster="objects: .raycastable, .clickable, a-link">


     <a-assets>

        <img crossorigin="anonymous" id="music-image_on" src="assets/img/music.png">
        <img crossorigin="anonymous" id="planet_cntl-image" src="assets/img/planets.png">
        <img crossorigin="anonymous" id="game_logo-image" src="assets/img/game_logo2.png">

        <a-asset-item id="horse" src="assets/gltf/Horse_Free.glb" shadow="receive: false"></a-asset-item>
        <a-asset-item id="rocket" src="assets/gltf/rocket4.glb" shadow="receive: false"></a-asset-item>
        <a-asset-item id="raygun" src="assets/gltf/raygun4/raygun.glb" shadow="receive: false"></a-asset-item>
        <a-asset-item id="flyingsaucer" src="assets/gltf/FlyingSaucer/fsaucer.glb" shadow="receive: false"></a-asset-item>
        <a-asset-item id="trex" src="assets/gltf/Trex_base_mesh.glb" shadow="receive: false"></a-asset-item>
        <a-asset-item id="triceratops" src="assets/gltf/Triceratops_base_mesh.glb" shadow="receive: false"></a-asset-item>
        <a-asset-item id="duck" src="assets/gltf/Duck.glb" shadow="receive: false"></a-asset-item>
        <a-asset-item id="key" src="assets/gltf/Key.glb" shadow="receive: false"></a-asset-item>
        <a-asset-item id="heart" src="assets/obj/heart_lp.obj/heart_lp.obj" shadow="receive: false"></a-asset-item>

        <img crossorigin="anonymous" id="right" src="assets/img/box-logo-right.png">
        <img crossorigin="anonymous" id="left" src="assets/img/box-logo-left.png">
        <img crossorigin="anonymous" id="top" src="assets/img/box-logo-top.png">
        <img crossorigin="anonymous" id="bottom" src="assets/img/box-logo-bottom.png">
        <img crossorigin="anonymous" id="front" src="assets/img/box-logo3-front.png">
        <img crossorigin="anonymous" id="back" src="assets/img/box-logo-back.png">

        <img crossorigin="anonymous" id="HelloIsland" src="assets/img/GrayNexus.png">

        <img crossorigin="anonymous" id="blue" src="assets/img/fontcolor.png" />

        <!-- Our Font -->
        <a-asset-item id="optimer_bold" src="assets/fonts/optimer_bold.typeface.json"></a-asset-item>
    </a-assets>

    <!-- Zoom and move the camera in for dramatic effects in browser window -->
    <a-entity id="camera_yoyo" animation__second="property: position;
                                         dir: alternate;
                                         dur: 12000;
                                         easing: easeInSine;
                                         loop: true;
                                         from: 0 1.6 0;
                                         to: 0 30 0" begin="20000" >

                   <a-camera look-controls wasd-controls mouse-cursor id="camera_zoom" position="0 1.6 0" user-height="1.6"
                                animation="property: position;
                                         dur: 15000;
                                         from: 0 0 50;
                                         to: 0 1.6 0;
                                         easing: easeInOutSine;
                                         loop: false;
                                         direction: normal" >

                                            <!-- This is where we output text   --> 
                                            <a-entity id="Points" text-geometry="value: ; font: #optimer_bold; bevelEnabled: false; curveSegments: 12; size: .75; height: 0;" material="color:blue; metalness:1; roughness: 0; transparent: false; opacity: 1" position="-.4 -.1 -.4" scale=".03 .03 .03" ></a-entity>

                    </a-camera>


        <a-entity class="leftController" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" visible="true"></a-entity>
         
        <a-entity class="rightController" hand-controls="hand: right; handModelStyle: lowPoly; color: #15ACCF" laser-controls raycaster="showLine: true; far: 40; interval: 0; objects: .clickable, a-link;" line="color: lawngreen; opacity: 0.5" visible="true"></a-entity>

                    
    </a-entity>

        <a-box id="planetButton" class="clickable" position = "0 26 -14" material="src: #planet_cntl-image" scale="1.25 1.25 .25" onclick="changePlanet();" 
                  animation="property: position;
                                dir: alternate;
                                    dur: 20000;
                            easing: easeInSine;
                                    loop: true;
                                  to: 0 5 -17" begin="8000"></a-box>

        <a-box id="playButton" class="clickable" position = "-2 26 -14" material="src: #music-image_on" scale="1.25 1.25 .25" onclick="playSound();" audiohandler ></a-box>

        <a-box id="gameLogo" position="0 29.5 -18" scale="5.48 4.76 5.36" multisrc="srcs:#right,#left,#top,#bottom,#front,#back" animation="property: rotation; dur: 50000; fill: forward; to: 0 360 0; repeat: indefinite"  opacity="1"></a-box>

    <!-- Setup tracs A-I for the carousel.  Not a true circle, but it works...  Varying the y-axis for height in air of object following trac.  Could be more complex tracks in the future. -->

    <a-curve id="tracA" >
        <a-curve-point position="0 1 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="5 1 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="7 1 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="5 1 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 1 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 1 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-8 1 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 1 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 1 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>


    <a-curve id="tracB" curve="">
        <a-curve-point position="0 7 2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 7 4" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-8 7 10" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 7 15" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 7 17" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 7 14" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="7 7 10" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 7 5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 7 2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>


    <a-curve id="tracC" curve="">
        <a-curve-point position="12 5 11" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="14 5 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="10 5 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 5 -1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 5 2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-2 5 7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="1 5 12" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 5 14" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="12 5 11" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>

    <a-curve id="tracD" curve="">
        <a-curve-point position="9 20 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="3 20 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="1 20 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="5 20 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="9 20 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="14 20 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="16 20 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="14 20 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="9 20 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>


    <a-curve id="tracE" curve="">
        <a-curve-point position="0 1 -12" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-2 1 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="0 1 -3" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 1 1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="9 1 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="13 1 -4" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="13 1 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="8 1 -14" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 1 -12" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>


    <a-curve id="tracF" curve="">
        <a-curve-point position="7 14 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="5 14 -15" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="0 14 -17" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 14 -15" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-8 14 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-4 14 -3" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="3 14 -3" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="7 14 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>

    <a-curve id="tracG" curve="">
        <a-curve-point position="1 24 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-3 24 -13" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-11 24 -13" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-14 24 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-13 24 -2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-7 24 1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-3 24 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 24 -3" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="1 24 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>


    <a-curve id="tracH" curve="">
        <a-curve-point position="-2 10 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-4 10 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-10 10 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-15 10 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-17 10 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-15 10 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-10 10 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-4 10 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-2 10 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>


    <a-curve id="tracI" curve="">
        <a-curve-point position="-12 1 1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-14 1 9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-9 1 14" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-1 1 12" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="1 1 7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-1 1 2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-6 1 -1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-12 1 1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>



    <!-- objects following the trac.  Letter A-I corresponds to trac curve above.  Collada and glTF objects loaded here... Note: All objects are royalty free and/or created by me. -->


    <a-entity id="sittingDuckA" class="clickable" gltf-model="#horse" alongpath="curve:#tracA;loop:true;dur:12000;rotate:true" scale="" position="-4.27 1 -6" shadow="receive:false" rotation="-24 -90 90" onclick="stopPoints();shoot_raygun('horse');" cursor-listener></a-entity>

    <a-entity id="sittingDuckB" class="clickable" gltf-model="#duck" alongpath="curve:#tracB;loop:true;dur:14000;rotate:true" position="4.29 1 15.43" shadow="receive:false" rotation="0 261.96 0" scale="1 1 1" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('duck');" cursor-listener></a-entity>

    <a-entity id="sittingDuckC" class="clickable"  gltf-model="#key" alongpath="curve:#tracC;loop:true;dur:12000;rotate:true" position="-1.77 1 5.17" shadow="receive:false" rotation="-7.28 -89.07 82.71" scale="1.3 1.3 1.3" onclick="stopPoints();shoot_raygun('key');" cursor-listener></a-entity>

    <a-entity id="sittingDuckD" class="clickable" gltf-model="#flyingsaucer" alongpath="curve:#tracD;loop:true;dur:12000;rotate:true" position="12.44 1 7.12" shadow="receive:false" rotation="0 261.96 0" scale="4.5 4.5 4.5" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('saucer');" cursor-listener></a-entity>

    <a-entity id="sittingDuckE" class="clickable" gltf-model="#horse" scale="" alongpath="curve:#tracE;loop:true;dur:12000;rotate:true" position="11.90 1 -2.37" shadow="receive:false" rotation="0 138.07 0" onclick="stopPoints();shoot_raygun('horse');" cursor-listener></a-entity>

    <a-entity id="sittingDuckF" class="clickable" gltf-model="#trex" alongpath="curve:#tracF;loop:true;dur:9000;rotate:true" position="-7.92 1 -8.56" shadow="receive:false" rotation="0 13.05 0" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" scale="1 1 1" onclick="stopPoints();shoot_raygun('trex');" cursor-listener></a-entity>

    <a-entity id="sittingDuckG" class="clickable" gltf-model="#triceratops" alongpath="curve:#tracG;loop:true;dur:12000;rotate:true" position="-9.22 1 0.45" shadow="receive:false" rotation="0 70.51 0" scale="0.35 0.35 0.35" onclick="stopPoints();shoot_raygun('ceratops');" cursor-listener></a-entity>

    <a-entity id="sittingDuckH" class="clickable" gltf-model="#rocket" alongpath="curve:#tracH;loop:true;dur:14000;rotate:true" position="-16.13 1 4.16" shadow="receive:false" rotation="0 23.64 0" scale="3.5 3.5 3.5" onclick="stopPoints();shoot_raygun('rocket');" cursor-listener></a-entity>

    <a-entity id="sittingDuckI" class="clickable" gltf-model="#raygun" scale="" alongpath="curve:#tracI;loop:true;dur:12000;rotate:true" position="0.98 1 6.65" shadow="receive:false" rotation="0 -175.74 0" onclick="stopPoints();shoot_raygun('raygun');" cursor-listener></a-entity>

<!-- stationary hearts -->


    <a-obj-model id="sittingDuckK" class="clickable" position="2.5 3.6 -8" scale="0.02 0.02 0.02" src="#heart" material="color: #F585D3"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" shadow="receive:false" onclick="stopPoints();shoot_raygun('heart');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckL" class="clickable" position="1.5 3.6 -8" scale="0.03 0.03 0.03" src="#heart" material="color: #B876F6"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" shadow="receive:false" onclick="stopPoints();shoot_raygun('heart');" cursor-listener></a-obj-model>   
    <a-obj-model id="sittingDuckM" class="clickable" position="0.1 3.6 -8" scale="0.04 0.04 0.04" src="#heart" material="color: #E9EDF8"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" shadow="receive:false" onclick="stopPoints();shoot_raygun('heart');" cursor-listener></a-obj-model>

    <a-obj-model id="sittingDuckO" class="clickable" position="2.5 8.6 -6" scale="0.02 0.02 0.02" src="#heart" material="color: #F44268"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" shadow="receive:false" onclick="stopPoints();shoot_raygun('heart');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckP" class="clickable" position="1.5 8.6 -6" scale="0.03 0.03 0.03" src="#heart" material="color: #F44268"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" shadow="receive:false" onclick="stopPoints();shoot_raygun('heart');" cursor-listener></a-obj-model>   
    <a-obj-model id="sittingDuckQ" class="clickable" position="0.1 8.6 -6" scale="0.04 0.04 0.04" src="#heart" material="color: #F44268"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" shadow="receive:false" onclick="stopPoints();shoot_raygun('heart');" cursor-listener></a-obj-model>

  

    <!-- Tune the world environment to the desired settings  -->

    <a-entity id="onplanet" environment="preset: forest; shadow: true; dressingAmount: 150;fog:0.65"></a-entity>
 
    <a-entity class="environment" position="-1.2 2.94 3.03" light="intensity:0.5;castShadow:true;shadowCameraLeft:-20;shadowCameraBottom:-20;shadowCameraRight:20;shadowCameraTop:20" visible=""></a-entity>

    <a-entity class="environmentDressing environment" visible="false"></a-entity>



</a-scene>
<script>
//Google Code for un-audio mute
// Existing code unchanged.
window.onload = function() {
  var context = new AudioContext();
  // Setup all nodes

}

// One-liner to resume playback when user interacted with the page.
document.querySelector('button').addEventListener('click', function() {
  context.resume().then(() => {
    console.log('Playback resumed successfully');
  });
});

</script>

</body>
</html>
            
            
         
    

Valentine VR-Card Game

A Lovely Holiday Greetings Experience

Source Code For Valentine VR-Card Game: 463 lines, commented.

 
         
            

<!DOCTYPE>
<html>
  <head>
    <meta charset="utf-8">
    <title>Valentine VR-Card Game</title>
    <meta name="description" content="A Valentines Day virtual reality greeting card game. Coded and Created By Michael McAnally, January 29, 2019.  Credit to Be My Valentine song by Tim McMorris puchase license, standard 10K AudioJungle"></meta>
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="gray-translucent" />

    <!-- A-frame component libraries, look them up on Github -->
    <script src="aframe-master/dist/aframe-v1.0.4.min.js"></script>
    <script src="aframe-extras-master/dist/aframe-extras.min.js"></script>
    <script src="aframe-environment-component-master/dist/aframe-environment-component.min.js"></script>
    <script src="superframe-master/components/thumb-controls/dist/aframe-thumb-controls-component.min.js"></script>
<!-- Removed teleport for now    <script src="aframe-teleport-controls-master/dist/aframe-teleport-controls.js"></script>   -->
    <script src="superframe-master/components/text-geometry/dist/aframe-text-geometry-component.min.js"></script>
    <script src="aframe-alongpath-component-master/dist/aframe-alongpath-component.min.js" ></script>
    <script src="aframe-curve-component-master/dist/aframe-curve-component.min.js"></script>
<!-- Removed lensflare for now       <script src="aframe-lensflare-component-master/dist/aframe-lensflare-component.min.js"></script>  -->
<!-- Add rotating advertisement  -->
    <script src="aframe-multisrc-component-master/dist/0.3/aframe-multisrc-component.js"></script>

    <script type="text/javascript">
var numberOfArrayDimension = 8; 
var playMusicSpeed = 1;
var rotation_speed = 15500;
var planets = ["yavapai", "forest", "goaland", "dream"];
var m = 1;  
var k = 21;
var indexElement = 0;
var counttx = 0, countup = true;
var nIntervId;
var audio2 = new Audio('https://rocketvirtual.com/webvr/mp3/magical_sparkle.mp3');
function startPoints() {
    nIntervId = setInterval(fadePoints, 400);
}
function fadePoints()
{
  if (countup)
  {
    counttx = counttx + .10;
    if (counttx >= 1)
      countup = false;
  }
  else
  {
    counttx = counttx - .10;
    if (counttx <= 0)
      countup = true;
  }
  document.getElementById('Points').setAttribute('material', 'opacity: ' + counttx.toString());
}
function stopPoints() {
      document.getElementById('Points').setAttribute('material', 'opacity: 0');
      clearInterval(nIntervId);
}
function changePlanet() {
    m = m + 1;
    if (m > planets.length)  { 
        m = 0;
    }
    document.getElementById('onplanet').setAttribute('environment', 'preset: ' + planets[m]);
}
function shoot_raygun(carousel_object) {

    if (indexElement > 5)  { 
        indexElement = 1;
    }
    startPoints();
    audio2.play();
    changeSpeed();
  switch(carousel_object) {
    case "funFacts1":
          document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[0][indexElement]);
        break;
    case "funFacts2":
          document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[1][indexElement]);
        break;
    case "funFacts3":
          document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[2][indexElement]);
        break;
    case "funFacts4":
          document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[3][indexElement]);
        break;
    case "funFacts5":
          document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[4][indexElement]);
        break;
    case "funFacts6":
          document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[5][indexElement]);
        break;
    case "funFacts7":
          document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[6][indexElement]);
        break;
    case "funFacts8":
          document.getElementById('Points').setAttribute('text-geometry', 'value: ' + funFacts[7][indexElement]);
        break;
    default:

    }
    indexElement = indexElement + 1;
}
function changeSpeed() {
    if (rotation_speed < 5700) {
        rotation_speed = 15500;
    }
    rotation_speed = rotation_speed - 1100;
    document.getElementById('sittingDuckA').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    document.getElementById('sittingDuckB').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    document.getElementById('sittingDuckC').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    document.getElementById('sittingDuckD').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    document.getElementById('sittingDuckE').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    document.getElementById('sittingDuckF').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    document.getElementById('sittingDuckG').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    document.getElementById('sittingDuckH').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    document.getElementById('sittingDuckI').setAttribute('alongpath', 'dur: ' + rotation_speed.toString());
    playMusicSpeed = playMusicSpeed + 0.035;   
    playSpeed = document.getElementById('playAudio');
    playSpeed.playbackRate = playMusicSpeed;
}
function createArray(length) {
    var arr = new Array(length || 0),
        i = length;

    if (arguments.length > 1) {
        var args = Array.prototype.slice.call(arguments, 1);
        while(i--) arr[length-1 - i] = createArray.apply(this, args);
    }

    return arr;
}
funFacts = createArray((numberOfArrayDimension + 1), 45);
funFacts[0][0] = "DARLING";
funFacts[0][1] = "YOU & ME";
funFacts[0][2] = "MARRY ME";
funFacts[0][3] = "TRUE LOVE";
funFacts[0][4] = "MY LOVE";
funFacts[0][5] = "SAY YES";
funFacts[1][0] = "DATE ME";
funFacts[1][1] = "MY LOVE";
funFacts[1][2] = "LOVE YOU";
funFacts[1][3] = "BE SWEET";
funFacts[1][4] = "HUG ME";
funFacts[1][5] = "MY BABY";
funFacts[2][0] = "CUTIE";
funFacts[2][1] = "LOVE ME";
funFacts[2][2] = "ALL MINE";
funFacts[2][3] = "CRAZY 4U";
funFacts[2][4] = "CUTIE PIE";
funFacts[2][5] = "OH BABY!";
funFacts[3][0] = "CALL ME";
funFacts[3][1] = "HOT STUFF";
funFacts[3][2] = "RED HOT";
funFacts[3][3] = "EZ2 LOVE";
funFacts[3][4] = "LOVE BUG";
funFacts[3][5] = "XOXO";
funFacts[4][0] = "CUTE";
funFacts[4][1] = "KISS ME";
funFacts[4][2] = "SOUL MATE";
funFacts[4][3] = "SWEET HEART";
funFacts[4][4] = "HOT LIPS";
funFacts[4][5] = "LOVE ME";
funFacts[5][0] = "ONLY YOU";
funFacts[5][1] = "BE MINE";
funFacts[5][2] = "I LUV U";
funFacts[5][3] = "BE TRUE";
funFacts[5][4] = "LIKE ME";
funFacts[5][5] = "HOLD ME";
funFacts[6][0] = "BE GOOD";
funFacts[6][1] = "BE TRUE";
funFacts[6][2] = "MISS YOU";
funFacts[6][3] = "4 EVER";
funFacts[6][4] = "REAL LUV";
funFacts[6][5] = "SO FINE";
funFacts[7][0] = "YOU ROCK";
funFacts[7][1] = "LUV YAH";
funFacts[7][2] = "DEAR ONE";
funFacts[7][3] = "TXT ME";
funFacts[7][4] = "TWEET ME";
funFacts[7][5] = "RU SHY?";
AFRAME.registerComponent('cursor-listener', {
  init: function () {
    this.el.addEventListener('click', function (evt) {
    k = k - 1;
    if (k < 1)  { 
        k = 21; 
        playMusicSpeed = 1;
        playSpeed = document.getElementById('playAudio');
        playSpeed.playbackRate = playMusicSpeed;
        document.getElementById('sittingDuckA').setAttribute('visible', true);
        document.getElementById('sittingDuckB').setAttribute('visible', true);
        document.getElementById('sittingDuckC').setAttribute('visible', true);
        document.getElementById('sittingDuckD').setAttribute('visible', true);
        document.getElementById('sittingDuckE').setAttribute('visible', true);
        document.getElementById('sittingDuckF').setAttribute('visible', true);
        document.getElementById('sittingDuckG').setAttribute('visible', true);
        document.getElementById('sittingDuckH').setAttribute('visible', true);
        document.getElementById('sittingDuckI').setAttribute('visible', true);
        document.getElementById('sittingDuckJ').setAttribute('visible', true);
        document.getElementById('sittingDuckK').setAttribute('visible', true);
        document.getElementById('sittingDuckL').setAttribute('visible', true);
        document.getElementById('sittingDuckM').setAttribute('visible', true);
        document.getElementById('sittingDuckN').setAttribute('visible', true);
        document.getElementById('sittingDuckO').setAttribute('visible', true);
        document.getElementById('sittingDuckP').setAttribute('visible', true);
        document.getElementById('sittingDuckQ').setAttribute('visible', true);
        document.getElementById('sittingDuckR').setAttribute('visible', true);
        document.getElementById('sittingDuckS').setAttribute('visible', true);
        document.getElementById('sittingDuckT').setAttribute('visible', true);
        document.getElementById('sittingDuckU').setAttribute('visible', true);
        m = m + 1;
        if (m > planets.length)  { 
            m = 0;  
        }
        document.getElementById('onplanet').setAttribute('environment', 'preset: ' + planets[m]);
    } else {
        this.setAttribute('visible', false);
    }
    });
  }
});
AFRAME.registerComponent('audiohandler', {
  init:function() {
     let playing = false;
     let audio = document.querySelector("#playAudio");
     this.el.addEventListener('click', () => {

          if(!playing) {
              audio.play();
           } else {
              audio.pause();
              audio.currentTime = 0;
           }
           playing = !playing;
     });
  }
})

      function playSound() {
        }

  </script>

  </head>
<body>
    <button id="playButton" type="button">Play Music</button>
    <audio id="playAudio" autoplay loop>
      <!-- Song standard Licensed 10K purchased from Audio Jungle Be My Valentine by Tim McMorris -->
        <source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/BeMyValentine.mp3" type="audio/mpeg">
    </audio>
    <a-scene cursor="rayOrigin: mouse; fuse: false" raycaster="objects: .raycastable, .clickable, a-link">
     <a-assets>

        <img crossorigin="anonymous" id="right" src="assets/img/box-logo-right.png">
        <img crossorigin="anonymous" id="left" src="assets/img/box-logo-left.png">
        <img crossorigin="anonymous" id="top" src="assets/img/box-logo-top.png">
        <img crossorigin="anonymous" id="bottom" src="assets/img/box-logo-bottom.png">
        <img crossorigin="anonymous" id="front" src="assets/img/box-logo-front.png">
        <img crossorigin="anonymous" id="back" src="assets/img/box-logo-back.png">


        <img crossorigin="anonymous" id="music-image_on" src="/assets/img/music6.png">
        
        <img crossorigin="anonymous" id="brought-to-you-by" src="/assets/img/by.png">

        <a-asset-item id="heart" src="assets/obj/heart_lp.obj/heart_lp.obj" shadow="receive: false"></a-asset-item>
        <!-- Our Font -->
        <a-asset-item id="optimer_bold" src="assets/fonts/optimer_bold.typeface.json"></a-asset-item>
    </a-assets>
    <!-- Zoom and move the camera in for dramatic effects in browser window -->
    <a-entity id="camera_yoyo" animation__second="property: position;
                                         dir: alternate;
                                         dur: 12000;
                                         easing: easeInSine;
                                         loop: true;
                                         from: 0 1.6 0;
                                         to: 0 9 0" begin="20000" >

                   <a-camera look-controls wasd-controls mouse-cursor id="camera_zoom" position="0 1.6 0" user-height="1.6"
                                animation="property: position;
                                         dur: 15000;
                                         from: 0 8 75;
                                         to: 0 1.6 0;
                                         easing: easeInOutSine;
                                         loop: false;
                                         direction: normal" >



                    </a-camera>


        <a-entity class="leftController" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" visible="true"><!-- This is where we output text   --> 
                                            <a-entity id="Points" text-geometry="value: ; font: #optimer_bold; bevelEnabled: false; curveSegments: 12; size: .35; height: 0;" material="color:pink; metalness:1; roughness: 0; transparent: false; opacity: 1" position=".15 0 0" scale=".25 .25 .25" ></a-entity>
        </a-entity>
         
        <a-entity class="rightController" hand-controls="hand: right; handModelStyle: lowPoly; color: #15ACCF" laser-controls raycaster="showLine: true; far: 40; interval: 0; objects: .clickable, a-link;" line="color: lawngreen; opacity: 0.5" visible="true"></a-entity>

        </a-entity>
              

        <a-box id="playButton" class="clickable" position = "-2 6 -19" material="src: #music-image_on" scale="1.25 1.25 .25" onclick="playSound();" audiohandler cursor-listener ></a-box>
        
        <a-box id="to-website" class="clickable" position = "0.8 6 -19" material="src: #brought-to-you-by" scale="3.6 1.29 0.00001" onclick="location.href='https://rocketvirtual.com';" ></a-box>

        <a-box id="gameLogo" position="0 9.5 -23.5" scale="5.48 4.76 5.36" multisrc="srcs:#right,#left,#top,#bottom,#front,#back" opacity="1">

        <a-animation attribute="rotation"
               dur="50000"
               fill="forwards"
               to="0 360 0"
               repeat="indefinite"></a-animation></a-box>

    <a-curve id="tracA" >



        <a-curve-point position="0 4 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="5 4 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="7 4 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="5 4 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 4 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 4 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-8 4 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 4 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 4 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
    <a-curve id="tracB" curve="">
        <a-curve-point position="0 7 2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 7 4" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-8 7 10" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 7 15" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 7 17" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 7 14" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="7 7 10" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 7 5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 7 2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
    <a-curve id="tracC" curve="">
        <a-curve-point position="12 5 11" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="14 5 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="10 5 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 5 -1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 5 2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-2 5 7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="1 5 12" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 5 14" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="12 5 11" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
    <a-curve id="tracD" curve="">
        <a-curve-point position="9 9 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="3 9 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="1 9 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="5 9 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="9 9 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="14 9 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="16 9 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="14 9 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="9 9 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
    <a-curve id="tracE" curve="">
        <a-curve-point position="0 6 -12" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-2 6 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="0 6 -3" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="6 6 1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="9 6 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="13 6 -4" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="13 6 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="8 6 -14" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 6 -12" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
    <a-curve id="tracF" curve="">
        <a-curve-point position="7 8 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="5 8 -15" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="0 8 -17" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-6 8 -15" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-8 8 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-4 8 -3" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="3 8 -3" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="7 8 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
   <a-curve id="tracG" curve="">
        <a-curve-point position="1 3 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-3 3 -13" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-11 3 -13" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-14 3 -9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-13 3 -2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-7 3 1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-3 3 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="0 3 -3" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="1 3 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
    <a-curve id="tracH" curve="">
        <a-curve-point position="-2 2 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-4 2 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-10 2 -7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-15 2 -5" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-17 2 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-15 2 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-10 2 8" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-4 2 6" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-2 2 0" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
    <a-curve id="tracI" curve="">
        <a-curve-point position="-12 1 1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-14 1 9" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-9 1 14" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-1 1 12" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="1 1 7" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-1 1 2" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>  
        <a-curve-point position="-6 1 -1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
        <a-curve-point position="-12 1 1" geometry="height:0.1;width:0.1;depth:0.1" material="color:#ff0000" curve-point="" visible="false"></a-curve-point>
    </a-curve>
    <a-obj-model  id="sittingDuckA" class="clickable" scale="0.05 0.05 0.05" src="#heart" material="color: #F44268" alongpath="curve:#tracA;loop:true;dur:12000;rotate:true" position="-4.27 1 -6" shadow="receive:false" rotation="-24 -90 90" animation__rotate="property:rotation;dur:5000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts1');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckB" class="clickable" scale="0.05 0.05 0.05" src="#heart" material="color: #F8E267" alongpath="curve:#tracB;loop:true;dur:14000;rotate:true" position="4.29 1 15.43" shadow="receive:false" rotation="0 261.96 0" animation__rotate="property:rotation;dur:5000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts2');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckC" class="clickable"  scale="0.05 0.05 0.05" src="#heart" material="color: #F585D3" alongpath="curve:#tracC;loop:true;dur:12000;rotate:true" position="-1.77 1 5.17" shadow="receive:false" rotation="0 261.96 0" animation__rotate="property:rotation;dur:5000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts3');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckD" class="clickable" scale="0.05 0.05 0.05" src="#heart" material="color: #B876F6" alongpath="curve:#tracD;loop:true;dur:12000;rotate:true" position="12.44 1 7.12" shadow="receive:false" rotation="0 261.96 0" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts4');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckE" class="clickable" scale="0.05 0.05 0.05" src="#heart" material="color: #E9EDF8" alongpath="curve:#tracE;loop:true;dur:12000;rotate:true" position="11.90 1 -2.37" shadow="receive:false" rotation="0 138.07 0" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts5');" cursor-listener></a-obj-model>
   <a-obj-model id="sittingDuckF" class="clickable" scale="0.05 0.05 0.05" src="#heart" material="color: #F44268" alongpath="curve:#tracF;loop:true;dur:9000;rotate:true" position="-7.92 1 -8.56" shadow="receive:false" rotation="0 13.05 0" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts6');" cursor-listener></a-obj-model>
   <a-obj-model id="sittingDuckG" class="clickable" scale="0.05 0.05 0.05" src="#heart" material="color: #F8E267" alongpath="curve:#tracG;loop:true;dur:12000;rotate:true" position="-9.22 1 0.45" shadow="receive:false" rotation="0 70.51 0" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts7');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckH" class="clickable" scale="0.05 0.05 0.05" src="#heart" material="color: #F585D3" alongpath="curve:#tracH;loop:true;dur:14000;rotate:true" position="-16.13 1 4.16" shadow="receive:false" rotation="0 23.64 0" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts8');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckI" class="clickable" scale="0.05 0.05 0.05" src="#heart" material="color: #B876F6" alongpath="curve:#tracI;loop:true;dur:12000;rotate:true" position="0.98 1 6.65" shadow="receive:false" rotation="0 -175.74 0" animation__rotate="property:rotation;dur:3000;easing:linear;loop:true;from:0 0 0;to:0 360 0" onclick="stopPoints();shoot_raygun('funFacts1');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckJ" class="clickable" position="1.8 3.6 -8" scale="0.005 0.005 0.005" src="#heart" material="color: #F8E267"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts2');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckK" class="clickable" position="1.5 3.6 -8" scale="0.01 0.01 0.01" src="#heart" material="color: #F585D3"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts3');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckL" class="clickable" position="1 3.6 -8" scale="0.02 0.02 0.02" src="#heart" material="color: #B876F6"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts4');" cursor-listener></a-obj-model>   
    <a-obj-model id="sittingDuckM" class="clickable" position="0.1 3.6 -8" scale="0.03 0.03 0.03" src="#heart" material="color: #E9EDF8"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts5');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckN" class="clickable" position="2.8 8.6 -6" scale="0.005 0.005 0.005" src="#heart" material="color: #F44268"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts6');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckO" class="clickable" position="2.5 8.6 -6" scale="0.01 0.01 0.01" src="#heart" material="color: #F44268"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts7');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckP" class="clickable" position="2 8.6 -6" scale="0.02 0.02 0.02" src="#heart" material="color: #F44268"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts8');" cursor-listener></a-obj-model>   
    <a-obj-model id="sittingDuckQ" class="clickable" position="1.1 8.6 -6" scale="0.03 0.03 0.03" src="#heart" material="color: #F44268"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts1');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckR" class="clickable" position="-5.8 5.6 -3" scale="0.005 0.005 0.005" src="#heart" material="color: #F8E267"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts2');" cursor-listener></a-obj-model>
   <a-obj-model id="sittingDuckS" class="clickable" position="-5.5 5.6 -3" scale="0.01 0.01 0.01" src="#heart" material="color: #F585D3"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts3');" cursor-listener></a-obj-model>
    <a-obj-model id="sittingDuckT" class="clickable" position="-5 5.6 -3" scale="0.02 0.02 0.02" src="#heart" material="color: #B876F6"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts4');" cursor-listener></a-obj-model>   
    <a-obj-model id="sittingDuckU" class="clickable" position="-4.1 5.6 -3" scale="0.03 0.03 0.03" src="#heart" material="color: #E9EDF8"
       animation__rotate="property: rotation; dur: 5000; easing: linear; loop: true; to: 0 360 0" onclick="stopPoints();shoot_raygun('funFacts5');" cursor-listener></a-obj-model>       
    <a-entity id="onplanet" environment="preset: forest; shadow: true"></a-entity>
    <a-entity class="environment" position="-1.2 2.94 3.03" light="intensity:0.5;castShadow:true;shadowCameraLeft:-20;shadowCameraBottom:-20;shadowCameraRight:20;shadowCameraTop:20" visible=""></a-entity>
    <a-entity class="environmentDressing environment" visible="false"></a-entity>
</a-scene>
<script>
window.onload = function() {
  var context = new AudioContext();
}
document.querySelector('button').addEventListener('click', function() {
  context.resume().then(() => {
    console.log('Playback resumed successfully');
  });
});
</script>
</body>
</html> 
            
            
         
    

Mind Palace 360VR

Immersive teleporting user experience

My avatar is a tripod!

360° Cameras are another way to experience VR immersively. Many newer high resolution models are coming to market. So I took it upon myself to develop some sample code for an immersive UI/UX.

A detailed article explaining the code.

Source Code For Mind Palace 360VR: 420 lines, commented.

 
         
            

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width,shrink-to-fit=no,user-scalable=no,maximum-scale=1,minimum-scale=1">
    <title>Mind Palace 360VR (Heavy graphic loading . . . please wait)</title>
    <meta name="description" content="This is a 360 VR Mind Palace structure."></meta>
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="gray-translucent" />
    <!-- A-frame component libraries, look them up on Github -->
    <script src="aframe-master/dist/aframe-v1.1.0.min.js"></script>
    <script src="aframe-rounded-master/dist/aframe-rounded-component.min.js"></script>
    <script src="superframe-master/components/text-geometry/dist/aframe-text-geometry-component.min.js"></script>
    <script src="aframe-extras-master/dist/aframe-extras.min.js"></script>


    <script type="text/javascript">

      var Speech = true;
      var audio1 = new Audio('assets/wav/action.wav');
      var audio2 = new Audio('assets/wav/swoosh.wav');


      function speakInfo(narration) {

        var audio_msg = new SpeechSynthesisUtterance(narration);

        if (Speech === true) {
            window.speechSynthesis.speak(audio_msg);
        }
      }

      function changeOrb(orb_num) {

        // change our orb sky
        document.getElementById('orbSky').setAttribute('material', 'src: #orb' + orb_num.toString());
      }



      //   audio https://stackoverflow.com/questions/47921013/play-sound-on-click-in-a-frame?answertab=active#tab-top
      AFRAME.registerComponent('audiohandler', {
        init:function() {
            let playing = false;
            let audio = document.querySelector("#playAudio");
            this.el.addEventListener('click', () => {

                if(!playing) {
                    audio.play();
                } else {
                    audio.pause();
                    audio.currentTime = 0;
                }
                playing = !playing;
            });
        }
      })

      function playSwoosh() {
        audio2.play();
      }


      function playBlip() {
        audio1.play();
      }


      function playSound() {

        //alert("TEST Sound playing functional!!!");
      }


  </script>

  </head>
  <body>

    <button id="playButton" type="button">Play Music</button>
    <audio id="playAudio" autoplay loop>
        <source src="https://rocketvirtual.com/A-Frame_WebXR/assets/mp3/Romanzeandante.mp3" type="audio/mpeg">
    </audio>

    <a-scene background="color: #FAFAFA">

      <a-assets timeout="30000" >

        <!-- mixin used to animate selected  orbs  -->
        <a-mixin id="marble" scale=".30 .30 .30" material="color: white" animation__rotation="startEvents: mouseenter; pauseEvents: mouseleave; resumeEvents: mouseenter; property: rotation; to: 0 360 0; loop: true; dur: 10000" animation__mouseenter="startEvents: mouseenter; pauseEvents: mouseleave; resumeEvents: mouseenter; property: components.material.material.color; type: color; to: white;  dur: 500; " animation__mouseleave="property: components.material.material.color; type: color; to: gray; startEvents: mouseleave; dur: 500;" shadow ></a-mixin>


        <!-- Replace with eight 360 images of your choosing -->

        <img crossorigin="anonymous" id="orb1" src="assets/360/image/theBeach.jpg">
        <img crossorigin="anonymous" id="orb2" src="assets/360/image/SAM_101_0161.jpg">
        <img crossorigin="anonymous" id="orb3" src="assets/360/image/SAM_101_0346.jpg">
        <img crossorigin="anonymous" id="orb4" src="assets/360/image/SAM_101_0128.jpg">
        <img crossorigin="anonymous" id="orb5" src="assets/360/image/SAM_101_0152.jpg">
        <img crossorigin="anonymous" id="orb6" src="assets/360/image/SAM_101_0370.jpg">
        <img crossorigin="anonymous" id="orb7" src="assets/360/image/360_0425.jpg">
        <img crossorigin="anonymous" id="orb8" src="assets/360/image/SAM_101_0109.jpg">


        <!-- Replace with eight thumbnails corresponding to the 360 images above to be wrapped around orbs  -->
        <img crossorigin="anonymous" id="orbthumb1" src="assets/360/image/thumb/theBeach_thumb.jpg">
        <img crossorigin="anonymous" id="orbthumb2" src="assets/360/image/thumb/SAM_101_0161_thumb.jpg">
        <img crossorigin="anonymous" id="orbthumb3" src="assets/360/image/thumb/SAM_101_0346_thumb.jpg">
        <img crossorigin="anonymous" id="orbthumb4" src="assets/360/image/thumb/SAM_101_0128_thumb.jpg">
        <img crossorigin="anonymous" id="orbthumb5" src="assets/360/image/thumb/SAM_101_0152_thumb.jpg">
        <img crossorigin="anonymous" id="orbthumb6" src="assets/360/image/thumb/SAM_101_0370_thumb.jpg">
        <img crossorigin="anonymous" id="orbthumb7" src="assets/360/image/thumb/360_0425_thumb.jpg">
        <img crossorigin="anonymous" id="orbthumb8" src="assets/360/image/thumb/SAM_101_0109_thumb.jpg">

        <!-- Replace MP4 video with your own  -->
        <video crossorigin="anonymous" id="video-src" src="assets/video/MP_beach.mp4"></video>
        
        <!-- Our font -->
        <a-asset-item id="optimer_bold" src="assets/fonts/optimer_bold.typeface.json"></a-asset-item>

        <!-- Controls for the video player -->
        <img crossorigin="anonymous" src="assets/img/play2.png" id="play" >
        <img crossorigin="anonymous" src="assets/img/pause.png" id="pause" >
        <img crossorigin="anonymous" src="assets/img/volume-normal.png" id="volume-normal" >
        <img crossorigin="anonymous" src="assets/img/volume-mute.png" id="volume-mute" >
        <img crossorigin="anonymous" src="assets/img/seek-back.png" id="seek-back" >

        <img crossorigin="anonymous" id="music-image_on" src="assets/img/music.png">

      </a-assets>

      <a-entity id="mouseCursor" cursor="rayOrigin: mouse"></a-entity>

      <a-sky id="orbSky" material="src: #orb1" rotation="0 -90 0" ></a-sky>

      <!-- Title of the Mind Palace  -->
      <a-entity id="NodeName" position="-1.04751 -1.79712 -2.84702" rotation="-39.4 0 0" text-geometry="value: Mind Palace 360VR; opacity: .5; size: .175; font: #optimer_bold" material="color: #F4A460"></a-entity>


      <!-- Basic movement and selection  -->

      <a-entity id="cameraRig" movement-controls="" position="0 0 5" rotation="0 0 0">
        <!-- camera -->
        <a-entity id="head" camera="active: true" look-controls="" position="0 1.6 0" ></a-entity>

        <a-entity class="leftController" hand-controls="hand: left; handModelStyle: lowPoly; color: #15ACCF" visible="true"></a-entity>
         
        <a-entity class="rightController" hand-controls="hand: right; handModelStyle: lowPoly; color: #15ACCF" laser-controls raycaster="showLine: true; far: 10; interval: 0; objects: .clickable, a-link;" line="color: #7cfc00; opacity: 0.5" visible="true"></a-entity>

      </a-entity>

      <!-- This lets us play music if browser allows it, enable audio  -->
      <a-box id="playButton" class="clickable" position="2.22428 -1.6294 -4.10106" rotation="-27.121 0 0" material="src: #music-image_on" scale="0.25 0.25 0.25" onclick="playBlip();" audiohandler shadow ></a-box>

      <!-- Video Label  -->
      <a-entity id="VideoLabel" class="clickable" position="-0.40113 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Coastline Video; opacity: .5; size: 0.09; font: #optimer_bold" onclick="playBlip();speakInfo('To Play Video Select The Green Play Button. Other Video Controls Will Appear.');" material="color: #F4A460"></a-entity>

      <!-- Replace labels and speak info for the orbs if you want them  -->
      <a-entity id="OrbName_place1" class="clickable" position="-2.25805 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Beach; size: 0.09; font: #optimer_bold" material="color: #F4A460" onclick="playBlip();speakInfo('Beach.');"></a-entity>
      <a-entity id="OrbName_place2" class="clickable" position="-1.252 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Waterfront; size: 0.09; font: #optimer_bold" material="color: #F4A460" onclick="playBlip();speakInfo('Waterfront and Bay Bridge. San Francisco, California');"></a-entity>
      <a-entity id="OrbName_place3" class="clickable" position="0.723 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Castro; size: 0.09; font: #optimer_bold" material="color: #F4A460" onclick="playBlip();speakInfo('The Castro.');"></a-entity>
      <a-entity id="OrbName_place4" class="clickable" position="1.687 -0.598 -4.5" rotation="-24.697 0 0" text-geometry="value: Downtown; size: 0.09; font: #optimer_bold" material="color: #F4A460" onclick="playBlip();speakInfo('Downtown San Francisco.');"></a-entity>
      <a-entity id="OrbName_place5" material="color: #F4A460" class="clickable" position="-1.775 -1.110 -4" rotation="-24.697 0 0" text-geometry="value: Pier; size: 0.09; font: #optimer_bold" onclick="playBlip();speakInfo('A Pier in San Francisco Bay.');"></a-entity>
      <a-entity id="OrbName_place6" material="color: #F4A460" class="clickable" position="-0.775 -1.110 -4" rotation="-24.697 0 0" text-geometry="value: Salesforce; size: 0.09; font: #optimer_bold" onclick="playBlip();speakInfo('Street level and Salesforce Tower.');"></a-entity>
      <a-entity id="OrbName_place7" material="color: #F4A460" class="clickable" position="0.225 -1.110 -4" rotation="-24.697 0 0" text-geometry="value: Academy; size: 0.09; font: #optimer_bold" onclick="playBlip();speakInfo('Academy of Science.');"></a-entity>
      <a-entity id="OrbName_place8" material="color: #F4A460" class="clickable" position="1.192 -1.110 -4" rotation="-24.697 0 0" text-geometry="value: Lands End; size: 0.09; font: #optimer_bold" onclick="playBlip();speakInfo('Lands End and San Francisco Golden Gate Bridge.');"></a-entity>


      <!-- Actual orbs and their placement  -->
      <a-sphere id="orb_place1" class="clickable" mixin="marble" position="-2 -1 -4.5" rotation="0 -125 0" material="src: #orbthumb1" onclick="playSwoosh();changeOrb(1);" ></a-sphere>
      <a-sphere id="orb_place2" class="clickable" mixin="marble" position="-1 -1 -4.5" rotation="0 85 0" material="src: #orbthumb2" onclick="playSwoosh();changeOrb(2);" ></a-sphere>
      <a-sphere id="orb_place4" class="clickable" mixin="marble" position="1 -1 -4.5" rotation="0 -75 0" material="src: #orbthumb3" onclick="playSwoosh();changeOrb(3);" ></a-sphere>
      <a-sphere id="orb_place5" class="clickable" mixin="marble" position="2 -1 -4.5" rotation="0 -90 0" material="src: #orbthumb4" onclick="playSwoosh();changeOrb(4);" ></a-sphere>
      <a-sphere id="orb_place6" class="clickable" mixin="marble" position="-1.5 -1.5 -4" rotation="0 -75 0" material="src: #orbthumb5" onclick="playSwoosh();changeOrb(5);" ></a-sphere>
      <a-sphere id="orb_place7" class="clickable" mixin="marble" position="-.5 -1.5 -4" rotation="0 -75 0" material="src: #orbthumb6" onclick="playSwoosh();changeOrb(6);" ></a-sphere>
      <a-sphere id="orb_place8" class="clickable" mixin="marble" position=".5 -1.5 -4" rotation="0 45 0" material="src: #orbthumb7" onclick="playSwoosh();changeOrb(7);" ></a-sphere>
      <a-sphere id="orb_place9" class="clickable" mixin="marble" position="1.5 -1.5 -4" rotation="0 -85 0" material="src: #orbthumb8" onclick="playSwoosh();changeOrb(8);" ></a-sphere>

      <!-- Translucent base for orbs -->
      <a-rounded radius="0.1" top-left-radius="0.6" top-right-radius="0.6" bottom-left-radius="0.6" bottom-right-radius="0.6" position="-2.667 -2.20165 -4.13398" scale="0.661 0.218 0.00001" rotation="-50 0 0" width="8" height="8" color="#657383" opacity=".35" shadow="" rounded=""></a-rounded>

      <!-- Controls for display of video screen, seems to work nicely, javascript below </scene> tag below -->
     
          <!-- MEDIAS HOLDER -->
          <a-sound id="alert-sound" src="src: url(assets/wav/action.wav)" autoplay="false" position="0 0 0"></a-sound>
          <a-video id="video-screen" src="#video-src" position="0.00193 1.02935 -5.4166" rotation="0 0 0" scale="0.564 0.697 1" width="8" height="4" rotation="0 0 0" visible="false"></a-video>

          <!-- END MEDIAS HOLDER -->

          <!-- CONTROLS -->
          <a-image class="clickable" id="control-back" width="0.4" height="0.4" src="#seek-back" position="-0.37171 -0.92581 -4.49178" rotation="0 0 0" visible="true" scale="0.85 0.85 0.85"></a-image>

          <a-image class="clickable" id="control-play" width="0.4" height="0.4" src="#play" position="0.03166 -0.92581 -4.49178" rotation="0 0 0"></a-image>

          <a-image class="clickable" id="control-volume" width="0.4" height="0.4" src="#volume-mute" position="0.42174 -0.92581 -4.49178" rotation="0 0 0" visible="true" scale="0.75 0.75 0.75"></a-image>

          <!-- END CONTROLS -->

          <!-- PROGRESSBAR -->
          <a-entity id="progress-bar" geometry="primitive:plane;height:0.1;width:4" material="opacity:0;transparent:true;visible:false" position="0.03516 -0.5536 -5.48963" rotation="0 0 0">
            <a-plane id="progress-bar-track" width="4" height="0.1" color="gray" position="" opacity="0.2" visible="false" material="" geometry=""></a-plane>
            <a-plane id="progress-bar-fill" width="3.0772968174269693" height="0.1" color="#7198e5" position="-0.4613515912865154 0 0.01438" geometry="" visible="false" material=""></a-plane>
          </a-entity>
          <!-- END PROGRESSBAR -->


    </a-scene>

    <!-- A Video Player Script (still works, when permissions enabled) -->

    <script type="text/javascript">


//Google Code for un-audio mute
// Existing code unchanged.
window.onload = function() {
  var context = new AudioContext();
  // Setup all nodes

}

// One-liner to resume playback when user interacted with the page.
document.querySelector('button').addEventListener('click', function() {
  context.resume().then(() => {
    console.log('Playback resumed successfully');
  });
});




      var AVideoPlayer = function() {
      // Vals
      this.duration         = 0;
      this.current_progress = 0;
      this.progressWidth    = 4;
      this.paused           = true;
      // Elems
      this.elProgressBar   = null;
      this.elProgressTrack = null;
      this.elProgressFill  = null;
      this.elAlertSound    = null;
      this.elVideo         = null;
      this.elVideoScreen   = null;
      this.elControlBack   = null;
      this.elControlPlay   = null;
      this.elControlVolume = null;
      this._initElements = function() {
        this.elProgressBar   = document.getElementById('progress-bar');
        this.elProgressTrack = document.getElementById('progress-bar-track');
        this.elProgressFill  = document.getElementById('progress-bar-fill');
        this.elAlertSound    = document.getElementById('alert-sound');
        this.elVideo         = document.getElementById('video-src');
        this.elVideoScreen   = document.getElementById('video-screen');
        this.elControlBack   = document.getElementById('control-back');
        this.elControlPlay   = document.getElementById('control-play');
        this.elControlVolume = document.getElementById('control-volume');
      }
      /**
      * PROGRESS
      */
      this.setProgress = function(progress) {
        var new_progress = this.progressWidth*progress;
        this._setProgressWidth(new_progress);
        var progress_coord = this._getProgressCoord();
        if (progress_coord != undefined) {
         progress_coord.x = -(this.progressWidth-new_progress)/2;
         this._setProgressCoord(progress_coord);
        }
      }
      this._getProgressCoord = function() {
        return AFRAME.utils.coordinates.parse(this.elProgressFill.getAttribute("position"))
      }
      this._getProgressWidth = function() {
        return parseFloat(this.elProgressFill.getAttribute("width"));
      }
      this._setProgressCoord = function(coord) {
        this.elProgressFill.setAttribute("position", coord);
      }
      this._setProgressWidth = function(width) {
        this.elProgressFill.setAttribute("width", width);
      }
      /*
      * UI SETTERS
      */
      this.isProgressBarVisible = function(isVisible) {
        this.elProgressTrack.setAttribute("visible", isVisible);
        this.elProgressFill.setAttribute("visible", isVisible);
      }
      this.isControlVisible = function(isVisible) {
        this.elControlBack.setAttribute("visible", isVisible);
        this.elControlVolume.setAttribute("visible", isVisible);
        this.elVideoScreen.setAttribute("visible", isVisible);
      }
      /*
      * EVENTS
      */
      this._addPlayerEvents = function() {
        var that = this;
        this.elVideo.pause();
        this.elVideo.onplay = function() {
          that.duration = this.duration;
        }
        this.elVideo.ontimeupdate = function() {
          if (that.duration > 0) {
            that.current_progress = this.currentTime/that.duration;
          }
          that.setProgress(that.current_progress);
        }
      }
      this._addControlsEvent = function() {
        var that = this;
        this.elControlPlay.addEventListener('click', function () {
          that._playAudioAlert();
          if (that.elVideo.paused) {
            this.setAttribute('src', '#pause');
            that.elVideo.play();
            that.paused = false;
            that.isProgressBarVisible(true);
            that.isControlVisible(true);
          } else {
            this.setAttribute('src', '#play');
            that.elVideo.pause();
            that.paused = true;
            that.isProgressBarVisible(false);
            that.isControlVisible(false);
         }
        });
        this.elControlVolume.addEventListener('click', function () {
          that._playAudioAlert();
          if (that.elVideo.muted) {
            that.elVideo.muted = false;
            this.setAttribute('src', '#volume-normal');
          } else {
            that.elVideo.muted = true;
            this.setAttribute('src', '#volume-mute');
          }
        });
        this.elControlBack.addEventListener('click', function () {
          that._playAudioAlert();
          that.elVideo.currentTime = 0;
        });
      }
      this._addProgressEvent = function() {
        var that = this;
        this.elProgressBar.addEventListener('click', function (e) {
          if (e.detail == undefined || e.detail.intersection == undefined || that.duration === 0) {
            return;
          }
          let seekedPosition = (e.detail.intersection.point.x+(that.progressWidth/2))/that.progressWidth;
          try {
            let seekedTime = seekedPosition*that.duration;
            that.elVideo.currentTime = seekedTime;
          } catch (e) {
          }
        });
      }
      this._playAudioAlert = function() {
        if (this.elAlertSound.components !== undefined && this.elAlertSound.components.sound !== undefined) {
         this.elAlertSound.components.sound.playSound();
        }
      }
      /**
      * MOBILE PATCH TO PLAY VIDEO
      */
      this._mobileFriendly = function() {
        if (AFRAME.utils.device.isMobile()) {
          var that = this;
          let video_permission        = document.getElementById('video-permission');
         let video_permission_button = document.getElementById('video-permission-button');
          video_permission.style.display = 'block';
          video_permission_button.addEventListener("click", function() {
            video_permission.style.display = 'none';
            that.elVideo.play();
            that.elVideo.pause();
          }, false);
        }
      }
      this.init = function() {
        this._initElements();
        //this._determinateProgressWidth();
        this.setProgress(this.current_progress);
        this._addPlayerEvents();
        this._addControlsEvent();
        this._addProgressEvent();
        this._mobileFriendly();
      }
      this.init();
    }
      let scene = document.querySelector('a-scene');
      var cursor = document.querySelector('a-cursor');
      /**
      * CINEMA MODE
      */
      scene.lightOff = function() {
        scene.islightOn = true;
        scene.removeAttribute('animation__fogback');
        scene.setAttribute('animation__fog', "property: fog.color; to: #0c192a; dur: 800; easing: easeInQuad;");
      }
      scene.lightOn = function() {
        scene.islightOn = false;
        scene.removeAttribute('animation__fog');
        scene.setAttribute('animation__fogback', "property: fog.color; to: #dbdedb; dur: 800");
      }
      /**
      * AVideoPlayer
      */
      var videoPlayer = new AVideoPlayer();
      document.querySelector('#control-play').addEventListener('click', function () {
        if (videoPlayer.paused) {
          scene.lightOn()
        } else {
          scene.lightOff();
        }
      });
    </script>
  </body>
</html> 
            
            
         
    

Here are links to the A-Frame GitHub code I use most. I've standardized off of A-frame version 1.0.4 which still works with aframe-extras and the navmesh and movement-controls (later versions break this functionality somewhat, of course unless it has been fixed recently - posted July 2021) . . .

The base for A-frame components
https://github.com/aframevr/aframe

Nice extras
https://github.com/donmccurdy/aframe-extras

Great environment creator
https://github.com/supermedium/aframe-environment-component

More great stuff for Supermedium browser and games
https://github.com/supermedium/superframe

Thumb controls
https://github.com/supermedium/superframe/tree/master/components/thumb-controls/

3D text geometry
https://github.com/supermedium/superframe/tree/master/components/text-geometry/

For teleporting around
https://github.com/fernandojsg/aframe-teleport-controls

Moving objects on a predefined track
https://github.com/protyze/aframe-alongpath-component

Track curve
https://github.com/protyze/aframe-curve-component

Used for a sun flare
https://github.com/mokargas/aframe-lensflare-component

Used for generating navmesh inside the inspector, use with teleport
https://github.com/donmccurdy/aframe-inspector-plugin-recast

Some like using superhands
https://github.com/wmurphyrd/aframe-super-hands-component


Particle system
https://github.com/IdeaSpaceVR/aframe-particle-system-component


Rounded primitive
https://github.com/etiennepinchon/aframe-rounded


Hosting VR On The Server

I host my own code, on servers on the internet. The trick is you must have an SSL Certificate, otherwise an HTTPS setup for it to work. You can get a free one.

In my examples I have copied over the framework component libraries above to my server. For those who don't host their own servers, CDNs are the answer and available for those who want to use something like Glitch to just initially learn A-Frame.


My Opinions Are My Own, But Here Is What I Think!

I would be remiss if I didn't mention the new Oculus Quest 2. So, since I write this blog, here are my personal opinions.

Can you say Walled Garden? I have found this video above to be basically an honest and fair review.

My personal VR Headset is the Samsung HMD Odyssey tethered to a PC with a minimum spec NVIDIA GeForce GTX 1060 graphics card (affordable at this time) on a Windows Mixed Reality Windows 10 PC. It works for me, and the tethered cable gives me much higher performance than my previous Samsung Gear VR, which was compatible with the Oculus Go, way back when. However, with the tethered cable attached the Oculus Quest 2 should perform well for VR games and for developers alike.

If you are looking to monetize on the Horizon platform, perhaps this is reason enough to join. And the recent Workrooms is interesting for enterprise VR play. Recent Update: I have since purchased an Oculus Quest 2 and retuned many of my apps for use with it. If I could afford it, I'd like a Valve Index on the SteamVR platform.

Google's Day Dream is dead on the vine. I'm focusing on VR, because AR is still not ready for prime time consumer adoption. Google Glass was repurposed for the enterprise. Magic Leap took too much venture capital. To be fair, here is a wonderful historic video link to earlier Magic Leap prototypes.

I'm certainly not waiting for Apple to release "a hardware entry" for VR or even AR, they have been rumor promising for years now.

Now HoloLens 2, here is a wonderfully long technical video explaination, worth the time.

Finally, the poorest and yet what I consider the most innovative company in the VR space is Mozilla, who has now suffered a significant layoff and restructuring, very sad. But, there is still hope with HUBS.

Ok, so that's my personal opinions, yours may obviously differ, but whatever you choose, do enjoy it and code for it!



Welcome to my VR Office!

Multi-user access to the same web page in virtual reality is possible with and without a headset (VR hardware). See my Medium Article for details on setting up your own server with NAF.






.