Virtual Reality and Druxt

2022.05.02
Druxt

Virtual Reality and new technologies have always been a passion of mine, and I have experimented with A-Frame on many occasions in my career.

Lately I have had aspirations of building VR interfaces as a frontend for a Drupal / Druxt applications, from immersive gallery for my 360 drone photography, to multi-user interactive experiences.

So, in todays post I will of course be experimenting with Druxt and A-Frame.

A-Frame - Make WebVR

A web framework for building 3D/AR/VR experiences

A-Frame is a javascript framework for building web based 3D, Augmented and Virtual Reality scenes.

As an open source web developer wanting to build VR, A-Frame ticks all the right boxes; It's an open source web framework, it uses HTML like elements, it's well documented and easy to use.

I've personally been using A-Frame for many years. I worked on the Drupal A-Frame module, and demonstrated some of the potentials at DrupalSouth 2017, Decoupled Drupal and Responsive VR.

Since then while my focus has been on building the best Decoupled Drupal framework, the A-Frame community has continued to build the best VR framework, with new features like hand tracking.

A-Frame can be developed from a plain HTML file without having to install anything.

Getting started with A-Frame from https://aframe.io/docs

<html>
  <head>
    <script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
      <a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
      <a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
      <a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
      <a-sky color="#ECECEC"></a-sky>
    </a-scene>
  </body>
</html>

As you can see, the simple A-Frame Scene renders inline in your web browser, be it a mobile device, desktop or virtual reality headset.

A-Frame and Nuxt.js

A-Frame provides a NPM package, which means that implementing in Nuxt SHOULD be simple enough. And as evidenced above, the two can be integrated.

But there are a couple of gotchas:

  • Vue.js expects to process unknown HTML elements as Vue components, including A-Frame elements.
  • Nuxt renders both serverside and clientside javascript, but A-Frame is a clientside only framework.

Thankfully, these are relatively simple issues to resolve.

Vue can be configured to ignore specific elements using the ignoredElements option.

This can be used in the nuxt.config.js file to ignore all a- prefixed elements, as so:

nuxt.config.js
export default {
  vue: {
    config: {
      // Ignore A-Frame elements.
      ignoredElements: [/^a-/]
    }
  }
}

A far more common "issue" when using Nuxt and third-party javascript libraries; client-side vs server-side javascript.

While the simplest solution may be to run Nuxt with Server Side Rendering disabled, it's a simple enough process to only attach the A-Frame library and elements client-side only using a process.client check and the ClientOnly component, like so:

<template>
  <div>
    <ClientOnly>
      <a-scene>
        <!-- Add A-Frame elements here. -->
      </a-scene>
    </ClientOnly>
  </div>
</template>

<script>
if (process.client) require('aframe')
</script>

So in theory, and practice, A-Frame can be integrated with Nuxt.js, and a front-end developer can build scenes at will.

And while that may be enough, an important part of Druxt and Decoupling Drupal is enabling content creators to create without the need of a developer, so let's take a look at some ways A-Frame can be integrated with Druxt:

Experiment 1 - A Scene paragraph

The first experiment is relatively standard approach for me; Paragraphs.

Creating an A-Frame scene paragraph with a Textarea for content entry allows the content author to create (or copy and paste) A-Frame scenes anywhere within their content, and is simple to implement:

In Drupal:

  1. Create a new paragraph type, e.g., aframe-scene/admin/structure/paragraphs_type
  2. Add a Text (plain, long) field to the paragraph, e.g., field_scene.
  3. Ensure the paragraph type is added to any required paragraph fields
  4. Ensure the paragraph type permissions are configured appropriately

* Note: Requires the Paragraphs module.

In Nuxt:

  1. Create a Paragraph entity theme component, e.g., ~/components/druxt/entity/paragraph/aframe-scene/Default.vue:
~/components/druxt/entity/paragraph/aframe-scene/Default.vue
<template>
  <div>
    <ClientOnly>
      <a-scene embedded v-html="scene" />
    </ClientOnly>
  </div>
</template>

<script>
import { DruxtEntityMixin } from 'druxt-entity'

// Import A-Frame library client-side only.
if (process.client) {
  require('aframe')
}

export default {
  mixins: [DruxtEntityMixin],

  computed: {
    scene: ({ entity }) => entity.attributes.field_scene,
  }
}
</script>

I made this integration specifically for this blog post, as a way to demonstrate the "Hello World" example (the embedded A-Frame scene above). Being a plain textfield provides a lot of flexibility, any scene can be built, but that comes at the cost of having to code the scenes.

Alternatively, the paragraph could be architected with other fields, checkboxes, integers and even other entity references and paragraphs, allowing for a simpler authoring experience.

Experiment 2 - Spheres and 360s

360° panoramic drone photography is a passion of mine, but it's a much harder medium to share. Being able to immerse yourself in the photo, looking anywhere from the Drones vantage point is a pretty amazing thing.

The a-sky element provides an easy way to map an image to the "sky", which can be used for this exact scenario.

In Drupal:

  1. Create a new Media type: Sphere
  2. Setup media type as per Image type (field_media_image) or as required.

In Nuxt:

  1. Create a Media entity theme component, e.g., ~/components/druxt/entity/media/sphere/Default.vue:
<template>
  <div>
    <ClientOnly>
      <a-scene embedded>
        <a-sky :src="src" />
      </a-scene>
    </ClientOnly>
  </div>
</template>

<script>
import { DruxtEntityMixin } from 'druxt-entity'

// Import A-Frame library client-side only.
if (process.client) require('aframe')

export default {
  mixins: [DruxtEntityMixin],

  computed: {
    // Find the file resource.
    file: ({ entity }) => entity.included.find((o) => o.type === 'file--file'),
    // Get the URL from the file.
    src: ({ file }) => file.attributes.uri.url,
  },

  druxt: {
    query: {
      // Include the image file resource.
      include: ['field_media_image'],
    }
  }
}
</script>

Full disclosure, I ran into an issue on this experiment; the proof of concept was easy enough, but as this blog is serverless and the component is dealing with images, it didn't initially work on Netlify.

In a serverless Druxt build, the Nuxt Image module is used to download images from Drupals to the Nuxt generated output (see What, no images? for context) but Nuxt generate doesn't process anything inside the ClientOnly component.

The solution turned out to be rather simple; The ClientOnly component has a placeholder template, which I used to render a NuxtImage, ensuring the image was present during generation and is correctly processed and accessible to the as an a-asset:

<ClientOnly>
  <a-scene embedded>
    <a-assets>
      <NuxtImg :id="file.id" :src="src" />
    </a-assets>
    <a-sky :src="`#${file.id}`" />
  </a-scene>
  <template #placeholder>
    <NuxtImg :id="file.id" :src="src" />
  </template>
</ClientOnly>

The Next Experiment

The above examples are enough to prove the concept; A-Frame can be run within Nuxt using data provided by Drupal and consumed by Druxt.

But this is only scratching the surface of the potential of VR Druxt integrations. Both examples are simple demonstration of taking a Drupal field and wrapping a small embedded scene to present the field data.

The next example, to truly test the possibilities of A-Frame, would be to create a fully immersive VR interface to navigate through multiples scenes worth of full entity data. Some of the options I've considered so far:

  • UmamiVR; A fully VR responsive frontend for the Drupal Umami demonstration profile.
  • SphereMe; A self-hostable, sphere photography service to create and share virtual sphere galleries.
  • CommerceVR; A fully VR shopping experience using the Commerce Kickstart demo.

Unfortunately, an example of that size takes more time than I currently have availability at the moment. But if you're interested in getting involved and helping build something truly amazing, find me @ https://discord.druxtjs.org, I'm always happy to help others Experiment with Druxt!