Introduction

This design workshop explores the way in which physical places may intersect, digitally. VR seems the obvious platform for an immersion of this sort, however designing for specific (yet broadly geographic) intersections (for example, the Armenian diaspora) would seem to require a closer analysis of our relationship (the “viewer”’s relationship, that is) to place and perspective.

This two week design seminar focuses on a theory of place in the 21st century met with an approach to designing meaningful immersive, digital experiences. Students will select local and (globally) remote sites that they want to connect in the context of the Armenian diaspora and create physical-virtual experiences to achieve this end. For example, a public park in Yerevan may have affinitive physical, emotional, cultural, digital or even aural touchpoints with a street corner in East Hollywood, CA.

Exercise 1: Hello World

A-Frame uses standard HTML with the a-frame.js library. Here’s a quick example to get us started.

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Hello VR World!</title>
    <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
</head>

<body>
    <a-scene background="color: #FAFAFA">
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" shadow></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" shadow></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" shadow></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
    </a-scene>
</body>

</html>

Additional Readings

This workshop draws upon historical works and theory presented by various media and fine artists including Robert Smithson, Andy Goldsworth, James Turell and Kit Galloway and Sherrie Rabinowitz:

“Public Communications Structures”
https://iphiahenry.wordpress.com/2011/09/20/hole-in-space-1980-installation-by-kit-galloway-and-sherrie-rabinowitz

“Site / Non-site Dialectic”
https://www.robertsmithson.com/essays/provisional.htm

“Sensing Spaces”
https://www.artinamericamagazine.com/news-features/news/space-conditioning-james-turrell-and-las-vegas/

“Ephemerality and collaborations with Nature”
http://text-relations.blogspot.com/2010/12/andy-goldsworthys-transient-art.html

Exercise 2: 360 image

Now let’s bring in an image as a backdrop. Background images must be in a specific image projection called Equirectangular, here’s what can be found at Flickr when you search for Equirectangular

Download these background images for examples:
city.jpg
rep-square.jpg
opera.jpg
hamalir.jpg

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>360° Image</title>
    <meta name="description" content="360° Image Gallery - A-Frame">
    <script src="https://aframe.io/releases/0.9.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-assets>
        <img id="opera" crossorigin="anonymous" src="images/opera.jpg">
      </a-assets>

      <!-- 360-degree image. -->
      <a-sky id="image-360" radius="10" src="#opera"></a-sky>
      <!-- Camera + cursor. -->
      <a-entity id="camera" camera look-controls>
        <a-cursor id="cursor"></a-cursor>
      </a-entity>
    </a-scene>
  </body>
</html>

Interesting A-Frame projects

Networked A-Frame project
https://github.com/networked-aframe/networked-aframe

Gunters of Oasis (Portal Game)
https://github.com/supermedium/gunters-of-oasis

Norman
James Paterson’s open-source Web VR tool built to create frame-by-frame animations in 3D. https://experiments.withgoogle.com/norman

Futuristic Interface
https://github.com/aframevr/aframe/tree/v0.9.0/examples/showcase/anime-UI

A-Painter
https://github.com/aframevr/a-painter/

Environment Component
https://blog.mozvr.com/aframe-environment-component/

Exercise 3: Adding 3D Models

A-Frame allows use to import 3D Models into our environment. The models for your environment can be build from scratch in a modeling program like Blender, Maya or you could use an existing model in the following formats: glft, obj

Where to Find Models

Places to find 3D models include:

Download this ant model and forest background for this example.

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Exercise-3</title>
    <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
</head>

<body>
    <a-scene>
        <a-assets>
            <a-asset-item id="trex" src="../images/trex.obj"></a-asset-item>
            <img id="forest" crossorigin="anonymous" src="../images/forest.jpg">
        </a-assets>
        <a-sky id="image-360" radius="10" src="#forest"></a-sky>
        <a-plane position="0 -6 0" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" shadow></a-plane>
        <a-entity obj-model="obj: #trex;" scale="0.008 0.008 0.008" position="-3 -4 -5" material="color: brown" rotatation=""></a-entity>
    </a-scene>
</body>

</html>

Exercise

Audio in Networked A-Frame

https://glitch.com/edit/#!/networked-aframe-audio

<html>
  <head>
    <meta charset="utf-8">
    <title>Networked Audio Example — Networked-Aframe</title>
    <meta name="description" content="etworked Audio Example — Networked-Aframe">

    <script src="https://aframe.io/releases/0.8.0/aframe.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.min.js"></script>
    <script src="easyrtc/easyrtc.js"></script>
    <script src="https://unpkg.com/networked-aframe@^0.6.0/dist/networked-aframe.min.js"></script>

    <script src="https://unpkg.com/aframe-randomizer-components@^3.0.1/dist/aframe-randomizer-components.min.js"></script>
    <script src="https://unpkg.com/aframe-particle-system-component@1.0.5/dist/aframe-particle-system-component.min.js"></script>
    <script src="/js/spawn-in-circle.component.js"></script>
  </head>
  <body>
    <a-scene networked-scene="
      room: audio;
      adapter: easyrtc;
      audio: true;
      debug: true;
    ">
      <a-assets>

        <img id="grid" src="https://img.gs/bbdkhfbzkk/stretch/https://i.imgur.com/25P1geh.png" crossorigin="anonymous">
        <img id="sky" src="https://img.gs/bbdkhfbzkk/2048x2048,stretch/http://i.imgur.com/WqlqEkq.jpg" crossorigin="anonymous" />

        <!-- Templates -->

        <!-- Avatar -->
        <template id="avatar-template">
          <a-entity class="avatar" networked-audio-source>
            <a-sphere class="head"
              color="#5985ff"
              scale="0.45 0.5 0.4"
              random-color
            ></a-sphere>
            <a-entity class="face"
              position="0 0.05 0"
            >
              <a-sphere class="eye"
                color="#efefef"
                position="0.16 0.1 -0.35"
                scale="0.12 0.12 0.12"
              >
                <a-sphere class="pupil"
                  color="#000"
                  position="0 0 -1"
                  scale="0.2 0.2 0.2"
                ></a-sphere>
              </a-sphere>
              <a-sphere class="eye"
                color="#efefef"
                position="-0.16 0.1 -0.35"
                scale="0.12 0.12 0.12"
              >
                <a-sphere class="pupil"
                  color="#000"
                  position="0 0 -1"
                  scale="0.2 0.2 0.2"
                ></a-sphere>
              </a-sphere>
            </a-entity>
          </a-entity>
        </template>

        <!-- /Templates -->
      </a-assets>

      <a-entity id="player" networked="template:#avatar-template;attachTemplateToLocal:false;" camera spawn-in-circle="radius:3;" position="0 1.3 0" wasd-controls look-controls>
        <a-sphere class="head"
          visible="false"
          random-color
        ></a-sphere>
      </a-entity>

      <a-entity position="0 0 0"
        geometry="primitive: plane; width: 10000; height: 10000;" rotation="-90 0 0"
        material="src: #grid; repeat: 10000 10000; transparent: true; metalness:0.6; roughness: 0.4; sphericalEnvMap: #sky;"></a-entity>

      <a-entity light="color: #ccccff; intensity: 1; type: ambient;" visible=""></a-entity>
      <a-entity light="color: #ffaaff; intensity: 1.5" position="5 5 5"></a-entity>

      <a-sky src="#sky" rotation="0 -90 0"></a-sky>
      <a-entity id="particles" particle-system="preset: snow"></a-entity>
    </a-scene>

    <script>
      // On mobile remove elements that are resource heavy
      var isMobile = AFRAME.utils.device.isMobile();

      if (isMobile) {
        var particles = document.getElementById('particles');
        particles.parentNode.removeChild(particles);
      }
      
      // Define custom schema for syncing avatar color, set by random-color
      NAF.schemas.add({
        template: '#avatar-template',
        components: [
          'position',
          'rotation',
          {
            selector: '.head',
            component: 'material',
            property: 'color'
          }
        ]
      });
    </script>
  </body>
</html>

Exercise 4: 360 Images with Buttons

https://aframe.io/docs/0.9.0/guides/building-a-360-image-gallery.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>360° Image Gallery</title>
    <meta name="description" content="360° Image Gallery - A-Frame">
    <script src="https://aframe.io/releases/0.9.0/aframe.min.js"></script>
    <script src="https://unpkg.com/aframe-event-set-component@5/dist/aframe-event-set-component.min.js"></script>
    <script src="https://unpkg.com/aframe-layout-component@5.3.0/dist/aframe-layout-component.min.js"></script>
    <script src="https://unpkg.com/aframe-template-component@3.2.1/dist/aframe-template-component.min.js"></script>
    <script src="https://unpkg.com/aframe-proxy-event-component@2.1.0/dist/aframe-proxy-event-component.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-assets>
        <img id="city" crossorigin="anonymous" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/city.jpg">
        <img id="city-thumb" crossorigin="anonymous" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-city.jpg">
        <img id="cubes-thumb" crossorigin="anonymous" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-cubes.jpg">
        <img id="sechelt-thumb" crossorigin="anonymous" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/thumb-sechelt.jpg">
        <audio id="click-sound" crossorigin="anonymous" src="https://cdn.aframe.io/360-image-gallery-boilerplate/audio/click.ogg"></audio>
        <img id="cubes" crossorigin="anonymous" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/cubes.jpg">
        <img id="sechelt" crossorigin="anonymous" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/sechelt.jpg">

        <!-- Image link template to be reused. -->
        <script id="link" type="text/html">
          <a-entity class="link"
            geometry="primitive: plane; height: 1; width: 1"
            material="shader: flat; src: ${thumb}"
            event-set__mouseenter="scale: 1.2 1.2 1"
            event-set__mouseleave="scale: 1 1 1"
            event-set__click="_target: #image-360; _delay: 300; material.src: ${src}"
            proxy-event="event: click; to: #image-360; as: fade"
            sound="on: click; src: #click-sound"></a-entity>
        </script>
      </a-assets>

      <!-- 360-degree image. -->
      <a-sky id="image-360" radius="10" src="#city"
             animation__fade="property: components.material.material.color; type: color; from: #FFF; to: #000; dur: 300; startEvents: fade"
             animation__fadeback="property: components.material.material.color; type: color; from: #000; to: #FFF; dur: 300; startEvents: animationcomplete__fade"></a-sky>

      <!-- Image links. -->
      <a-entity id="links" layout="type: line; margin: 1.5" position="0 -1 -4">
        <a-entity template="src: #link" data-src="#cubes" data-thumb="#cubes-thumb"></a-entity>
        <a-entity template="src: #link" data-src="#city" data-thumb="#city-thumb"></a-entity>
        <a-entity template="src: #link" data-src="#sechelt" data-thumb="#sechelt-thumb"></a-entity>
      </a-entity>

      <!-- Camera + cursor. -->
      <a-entity id="camera" camera look-controls>
        <a-cursor
          id="cursor"
          animation__click="property: scale; startEvents: click; from: 0.1 0.1 0.1; to: 1 1 1; dur: 150"
          animation__fusing="property: fusing; startEvents: fusing; from: 1 1 1; to: 0.1 0.1 0.1; dur: 1500"
          event-set__mouseenter="_event: mouseenter; color: springgreen"
          event-set__mouseleave="_event: mouseleave; color: black"
          raycaster="objects: .link"></a-cursor>
      </a-entity>
    </a-scene>
  </body>
</html>