knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
The core of r2vr
is 3 classes that map to A-Frame entities, assets, and
scenes. A scene may contain many child entities which may themselves contain
many child entities and assets. Entities may have many components added to define their behaviour.
Objects of these classes are defined using functions: a_entity()
, a_asset()
, a_scene()
.
Given a structure of these objects, r2vr
does the work of:
Rendering them as A-Frame HTML and combining into a selected HTML template.
Collecting their assets and required javascript sources and placing them in the appropriate HTML sections.
* Serving the HTML scene and asset files.
In the A-Frame entity-component architecture a scene is composed of entities. These are defined in HTML like:
<a-entity geometry="primitive: box" material="color: red"></a-entity>
Entities are customised using HTML element properties that map configuration to appearance and behaviour. A-Frame provides many 'convenience' tags to simplify the definition of commonly used entity configurations. For example the above entity can also be defined:
<a-box color="red">
r2vr
respects these same definition types. It provides the added convenience of defining component configuration as R lists. The above can be defined in R any of:
a_entity(geometry = "primitive: box", material = "colour: red") a_entity(geometry = list(primitve = "box"), material = list(color = "red")) a_entity(.tag = "box", color = "red")
Notice that entity components are attached using ...
arguments to the
a_entity()
function. An important convention regarding arguments to a_
functions is now worth discussing:
Arguments prefixed with a .
, ie. .tag
in the above example, are arguments
that will not appear literally in the generated HTML. You can think of them as
driving some 'under the hood' behaviour to do with the way the HTML is structured
or served. This is discriminates them from arguments without .
prefixes like
geometry
above. These are how you represent components. These will appear
literally as attributes of HTML elements in the final output.
For components with multi-word-names in A-Frame, the convention is to separate
with -
, e.g. wasd-controls
, when attaching these in r2vr
you swap the -
for _
since the dash is not legal as a bare symbol in R. So take this HTML:
<a-camera wasd-controls="fly: true;"></a-camera>
Possible r2vr
equivalents are:
a_entity(.tag="camera", wasd_controls = list(fly = TRUE)) a_entity(.tag="camera", wasd_controls = "fly: true;")
One current issue is that you cannot supply an component without configuration
neatly. So to use wasd-controls
with default configuration we must write
either:
a_entity(.tag="camera", wasd_controls="") ## or a_entity(.tag="camera", wasd_controls=NULL)
To require otherwise would mean cracking open the Non-Standard Evaluation can of worms, a step not to be undertaken lightly.
In A-Frame entities can have child entities nested within them. This is often extremely convenient to do since nested entities inherit the position and rotation of their parent. So in this HTML:
<a-box position="1 1 1", rotation="45 45 45" color="blue"> <a-sphere position="1 0 0" color="green"></a-sphere> </a-box>
The green sphere inherits position
and rotation
from its blue box parent.
It's own position is interpreted as offset from its parent. So the sphere's
absolute position is 2 1 1
.
In r2vr
child entities are nested as a list supplied to the parent's
.children
argument. So the above pair are defined:
a_entity(.tag = "box", position = c(1, 1, 1), rotation = c(45, 45, 45), color="blue", .children = list( a_entity(.tag = "sphere", positon = c(2, 1, 1)) ))
Because the nested a_entity
call returns an object it's possible to break up
the nesting a bit, if it aids readability:
sphere <- a_entity(.tag = "sphere", positon = c(2, 1, 1)) box <- a_entity(.tag = "box", position = c(1, 1, 1), rotation = c(45, 45, 45), children = list(sphere))
To use a custom component with an entity, a list of links to the component's
javascript sources can be passed to a_entity
using the .js_sources
argument.
For an example let's crack open the source of r2vr::a_json_model
, a
convenience entity for defining JSON models, which do not have native A-Frame
support:
.extras_model_loader <- "https://cdn.rawgit.com/donmccurdy/aframe-extras/v4.0.2/dist/aframe-extras.loaders.js" a_json_model <- function(src_asset, .js_sources = list(.extras_model_loader), ...){ a_entity(json_model = list(src = src_asset), .js_sources = .js_sources, ...) }
So every a_json_model()
is not much more than an a_entity()
with a json_model
component attached along with a reference to the javascript that powers the
component. You do not have do worry about duplicate .js_sources
being added to
a scene. r2vr
ensures only one reference to each unique javascript file makes
it into the final HTML.
Creating your own wrapper entities in this manner is also a way to simplify scene specification.
A scene is the outer container for all entities. It's also an entity itself. Its special powers are that it understands how to collate and serve a nested structure of entity and asset objects. So just like a regular entity a scene can have component configuration and nested children.
For example:
<a-scene fog stats> <a-box position="1 1 1", rotation="45 45 45" color="blue"> <a-sphere position="1 0 0" color="green"></a-sphere> </a-box> </a-scene>
is defined in r2vr
as:
my_scene <- a_scene(fog="", stats="", .children = list( a_entity(.tag = "box", position = c(1, 1, 1), rotation = c(45, 45, 45), .children = list( a_entity(.tag = "sphere", positon = c(2, 1, 1))))))
In r2vr
scenes work with a HTML templating system. This allows you to reduce
the complexity of scenes in R, by creating a template for the static parts that
are not likely to change, E.g. ground, sky, lighting etc. Templates are selected
using the .template
argument. Built-in templates at the moment are:
template name | description
---|---
"empty"
| An empty scene. Will use default A-Frame lights and camera inserted by A-Frame. Defaults are removed if added to the scene configuration.
"basic"
| Has a grid ground added. Will use default A-Frame lights and camera. Defaults are removed if added to scene configuration.
"basic_map"
| Has a large grid ground, high point light source (A Sun) and high camera start position.V
A custom HTML file can be supplied as a .template
. The following placeholders are populated by the scene object:
placeholder | function
---|---
${title}
| The title of the HTML page.
${description}
| The meta description of the HTML page.
${js_sources}
| Location in HTML <head>
to place a list of <script>
tags linking javascript sources files attached to scene children.
${assets}
| Location to place a list of <a-asset-item>
tags, one per line, generated from a_assets
attached to scene children.
${entities}
| Location to place a list of entity tags, one per line, indented as appropriate, generated from child entities.
${scene_compnents}
| The space after the start of the <a-scene >
tag to inject components attached to a_scene()
.
A scene object can be called upon to render itself to HTML or serve itself to allow viewing in WebVR. Scenes are served using the Fiery webserver framework.
Asumming my_scene
is a reference to a scene object returned by a_scene()
then the following actions are possible:
call | effect
---|---
my_scene$serve(host = "127.0.0.1", port = 8080)
| Serve the Scene HTML as the root, "/", at the supplied IP address and port.
my_scene$stop()
| Stop serving the scene.
my_scene$render()
| A test/validaton helper: Return a string containing the complete scene HTML.
my_scene$write("file.html")
| A test/validation helper: write the complete scene HTML to a file.
Assests and Javascript sources are attached as arguments to components on entities or the scene itself. The scene collects the id
fields and sources of all assets, and the links to all JS files and performs a de-duplication before rendering them in HTML.
It creates routes in the server for all unique assets. All local assets must be below the working directory from which `serve()`` is called.
Assets are media like models, images, videos, sounds etc associated with A-Frame entities. There are two ways to define assets, depending on whether the scene rendering should be delayed until they are loaded or not.
Assets are created with the a_asset()
function. The src
argument references
the asset file and can point to either a local file or a URI for a remote file.
Only local files will be served by r2vr
with a scene. The user's browser will fetch
the remote files from their URIs.
In A-Frame HTML Assets that should be fetched before the scene is allowed to
render are declared in a special <a-assets>
HTML block like this:
<a-scene > <a-assets> <a-asset-item id="cube" src="./cube.json"></a-asset-item> <a-asset-item id="kangaroo" src="./Kangaroo_01.gltf"></a-asset-item> </a-assets> ...
The id
property is referenced in entity component configuration where the asset is to be used like these two entities that render 3D models:
<a-entity json-model="src: #cube;" position="0 0 -2" scale="0.2 0.2 0"></a-entity> <a-gltf-model position="2 2 -3" src="#kangaroo"></a-gltf-model>
When using r2vr
you don't have to worry about declaring assets in the
a-assets
block. That is taken care of automatically. The only concern is
passing the r2vr
asset object returned by a_asset()
to the appropriate
entity component, usually as src
. For example:
cube_model <- a_asset(id = "cube", src = "./cube.json") cube <- a_json_model(src = cube_model, position = c(0, 0, -2), scale = c(0.2, 0.2, 0.2)) my_scene <- a_scene(children = list(cube))
Passing cube_model
to cube
sets up the link between entity and asset in the
underlying HTML using the id "#cube", as well as adding the "cube.json" file to
the assets block.
It is possible the you may wish to declare an asset that is not initially
assigned in the src
of any entity when the scene initially renders. The
intention may be for this asset to be used in response to some event. Entities
and scenes have a .assets
argument for declaring assets of this type. See
vignette 'Communicating between R and WebVR sessions' for an example.
In general is recommended that all assets are loaded before the scene is rendered. This is because when an asset is loaded it can cause the scene to stutter which can contribute to VR sickness symptoms in users.
It is possible to define assets in inline mode that does not delay scene rendering. This can be useful when changing the asset (e.g. texture) associated with some entity in response to a user action. If that action is unlikely, than speculatively fetching the asset may be inappropriate.
Inline mode is just enabled with an argument to a_asset()
:
cube_model <- a_asset(id = "cube", src = "./cube.json", .inline = TRUE) cube <- a_json_model(src = cube_model, position = c(0, 0, -2), scale = c(0.2, 0.2, 0.2)) my_scene <- a_scene(.children = list(cube))
To define a scene containing JSON model and a glTF model, you can pass the asset objects to entities with json model and gltf model components:
library(r2vr) ## Assest cube <- a_asset(id = "cube", src = "./cube.json") kangaroo <- a_asset(id = "kangaroo", src = "./Kangaroo_01.gltf", .parts = "./Kangaroo_01.bin") ## Scene structure my_scene <- a_scene(.template = "empty", title = "A kangaroo and a cube.", .children = list( a_json_model(src = cube, position = c(-2,1,-5), scale = c(0.3, 0.3, 0.3)), a_entity(.tag = "gltf-model", src = kangaroo, position = c(2, -1, -5), scale = c(0.3,0.3,0.3), rotation = c(0, 180, 0)) ))
that will allow you to serve HTML that looks like this:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>A kangaroo and a cube.</title> <meta name="description" content= "A kangaroo and a cube."> <script crossorigin src="https://aframe.io/releases/0.8.0/aframe.min.js"></script> <script crossorigin src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v4.0.2/dist/aframe-extras.loaders.js"></script> </head> <body> <a-scene > <a-assets> <a-asset-item id="cube" src="./cube.json"></a-asset-item> <a-asset-item id="kangaroo" src="./Kangaroo_01.gltf"></a-asset-item> </a-assets> <!-- Entities added in R --> <a-entity json-model="src: #cube;" position="-2 1 -5" scale="0.3 0.3 0.3"></a-entity> <a-gltf-model src="#kangaroo" position="2 -1 -5" scale="0.3 0.3 0.3" rotation="0 180 0"></a-gltf-model> </a-scene> </body> </html>
And renders like this:
Adding images as textures is another useful application. Image assets use the plain HTML <img>
tag instead of <a-asset-item>
. Here's an example that builds a VR scene around an R plot image:
## libs library(r2vr) library(visdat) library(ggplot2) ## making an image ## powers of 2 are good since they need less rescaling. png("VRisdat.png", width = 512, height = 512) p <- vis_dat(airquality) p + theme_light(base_size = 13) dev.off() ## creating a thematic background using 'environment' ## https://github.com/feiss/aframe-environment-component backdrop <- a_entity(environment = list(preset= "tron", dressingAmount = 40, playArea = 0), .js_sources = "https://rawgit.com/feiss/aframe-environment-component/master/dist/aframe-environment-component.min.js") ## rendering the image on a canvas visdat_plot <- a_asset(.tag = "img", id = "vrisdat", src = "VRisdat.png") canvas <- a_entity(.tag = "plane", position = c(0, 10, -10), height = 20, width = 20, src = visdat_plot) my_scene <- a_scene(.title = "A VR ggplot", .template = "empty", .children = list(backdrop, canvas)) my_scene$serve() ## my_scene$stop()
Which creates this scene:
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.