Today I’ve been working on adding many different A-Frame components to the panoramic image I shot of my studio. First, I created a new .pug file combining the terrain-model component by Ben Pyrik with the studio panorama:
doctype html html head meta(charset='utf-8') title A-Frame Terrain Model Component with Panoramic background meta(name='description', content='A-Frame Terrain Model Component with Panoramic background') script(src='build.js') link(href="style.css", rel="stylesheet", media="all") body a-scene(antialias='true') //Assets a-assets img(id='sky' src='firstPanorama/2016_10_18_FirstPanorama.JPG') //Camera a-entity(position='0 0 0' rotation='0 0 0') a-entity(camera look-controls wasd-controls) //Terrain a-entity(position='0 100 0' terrain-model='DEM: url(aframe-terrain-model-component/docs/Olympic-Peninsula/data/clipped-envi.bin); texture: url(aframe-terrain-model-component/docs/Olympic-Peninsula/data/olympic-texture.jpg); planeWidth: 287; planeHeight: 151; segmentsWidth: 287; segmentsHeight: 151; zPosition: 50;') //Sky a-sky(src='#sky') //Light a-entity(light='type: ambient; color: #0xeeeeee')
I added the demo my Github pages and took the following screenshot:
I knew that I wanted the landscape to float in the sky above user’s heads, so I used the A-Frame Inspector to reposition the terrain model in the sky. Unfortunately, this had the side-effect of adding some glitches to the drawing of the terrain:
I suspect this is because A-Frame is expecting the terrain 3D model to be viewed from the other side. I decided to keep going with adding the other components I had found that could be good candidates for the interactive demo that I want to test with users in India.
The next component to try was Ocean, part of A-Frame Extras by Don McCurdy. I installed it via NPM as normal:
npm install aframe-extras --save
Then I added the component to main.js:
require('aframe'); require('aframe-terrain-model-component'); require('aframe-extras');
I then created a new .pug file, aFrameOceanComponentWithPanorama.pug:
doctype html html head meta(charset='utf-8') title A-Frame Ocean Component with Panoramic background meta(name='description', content='A-Frame Ocean Component with Panoramic background') script(src='build.js') link(href="style.css", rel="stylesheet", media="all") body a-scene(antialias='true') //Assets a-assets img(id='sky' src='firstPanorama/2016_10_18_FirstPanorama.JPG') //Camera a-entity(position='0 0 0' rotation='0 0 0') a-entity(camera look-controls wasd-controls) //Ocean a-ocean(width='50' depth='50' density='40') //Sky a-sky(src='#sky') //Light a-entity(light='type: ambient; color: #0xeeeeee')
That didn’t display correctly, so I decided to build back up from the static demo’s that Don provided with the component, before re-adding them to the panoramic image. After downloading them, I managed to get them working on my local server by editing the following lines of his Water demo:
<script src="https://aframe.io/releases/0.3.0/aframe.min.js"></script> <script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v2.6.0/dist/aframe-extras.js"></script>
But while isn’t my inclusion of the same component into main.js via Browserify working? I soon found the error, after re-reading Don’s readme.md. I had to add the following lines to register the component within main.js:
var extras = require('aframe-extras'); // Register everything. extras.registerAll();
I added the demo to my GitHub, and took the following screengrab, with a similar glitch as before:
Finally, I tried the Mountain component by Kevin Ngo. Again installing via NPM:
npm install aframe-mountain-component --save
First duplicated the demo to local system, which worked fine, so I created a new pug file:
doctype html html head meta(charset='utf-8') title A-Frame Mountain Component with Panoramic background meta(name='description', content='A-Frame Mountain Component with Panoramic background') script(src='build.js') link(href="style.css", rel="stylesheet", media="all") body a-scene(antialias='true') //Assets a-assets img(id='sky' src='firstPanorama/2016_10_18_FirstPanorama.JPG') //Camera a-entity(position='0 0 0' rotation='0 0 0') a-entity(camera look-controls wasd-controls) //Mountain, seems to be on a large scale! a-mountain(position='0 2000 0') //Sky a-sky(src='#sky') //Light a-entity(light='type: ambient; color: #0xeeeeee')
And added the following line to main.js:
require(‘aframe-mountain-component’);
To make sure that Browserify included the necessary JS files to make the webpage work. This resulted in the following demo and screenshot:
After discussing my bug(s) on the fantastic A-Frame Slack it became clear that I need to edit the properties of the A-Frame Material which makes each entity an appearance. Specifically, I need to make the side property equal to double to render both sides of the mesh.
Bryvik helpfully made a new branch for his terrain-model component, that exposed the side property of the material, via the side property of the original three.js material that it uses.
As well as asking about my problem on the A-Frame Slack, I also browsed Stackoverflow for questions tagged with A-Frame. There I found a post about how to use JavaScript directly with A-Frame. The answer was written by the very helpful Kevin Ngo, but was written for the 0.2.0 release of A-Frame, not the latest stable 0.3.0. After consulting the docs for the 0.3.0 Entity I realised I also needed to work out how to write inline Javascript with Pug. Stackoverflow came to the rescue again – (Jade was the previous name of Pug) with a link to a Pug example of inline Javascript.
doctype html html head meta(charset='utf-8') title A-Frame Terrain Model Component with Panoramic background meta(name='description', content='A-Frame Terrain Model Component with Panoramic background') script(src='build.js') link(href="style.css", rel="stylesheet", media="all") body a-scene(antialias='true' stats) //Assets a-assets img(id='sky' src='firstPanorama/2016_10_18_FirstPanorama.JPG') //Camera a-entity(position='0 0 0' rotation='0 0 0') a-entity(camera look-controls wasd-controls) //Terrain a-entity(id='landscape' position='0 100 0' terrain-model='DEM: url(aframe-terrain-model-component/docs/Olympic-Peninsula/data/clipped-envi.bin); texture: url(aframe-terrain-model-component/docs/Olympic-Peninsula/data/olympic-texture.jpg); planeWidth: 287; planeHeight: 151; segmentsWidth: 287; segmentsHeight: 151; zPosition: 50;') //Sky a-sky(src='#sky') //Light a-entity(light='type: ambient; color: #0xeeeeee') script. console.log("Hello world"); var landscapeEl = document.querySelector('#landscape'); console.log(landscapeEl); console.log(landscapeEl.getAttribute('terrain-model'));
By looking in the Javascript console I could see that I was successfully printing “Hello World”, and setting the Landscape component to the variable landscapeEl. I could even make the Landscape visible or not by typing:
landscapeEl.setAttribute('visible', false);
or
landscapeEl.setAttribute('visible', true);
Into the console. I could also see that I was successfully displaying the public attributes of the Landscape component:
<a-entity id="landscape" position="0 100 0" terrain-model="DEM: url(aframe-terrain-model-component/docs/Olympic-Peninsula/data/clipped-envi.bin); texture: url(aframe-terrain-model-component/docs/Olympic-Peninsula/data/olympic-texture.jpg); planeWidth: 287; planeHeight: 151; segmentsWidth: 287; segmentsHeight: 151; zPosition: 50;" rotation="" scale="" visible=""></a-entity>
As well as the attributes of terrain-model attribute itself:
DEM: url(aframe-terrain-model-component/docs/Olympic-Peninsula/data/clipped-envi.bin); texture: url(aframe-terrain-model-component/docs/Olympic-Peninsula/data/olympic-texture.jpg); planeWidth: 287; planeHeight: 151; segmentsWidth: 287; segmentsHeight: 151; zPosition: 50;
– and side wasn’t one of them (I wasn’t using the branch of the Component that bryvik had made as I was trying to find a general method for changing the side property of any material in A-Frame. I realised that I needed to manipulate the three.js that underlies a-frame directly, which I could do by accessing Entity’s object3D property – which is a native three.js object. By adding the following
var landscapeObject3D = landscapeEl.object3D; console.log(landscapeObject3D.parent); console.log(landscapeObject3D.children);
Via the console, I could then manipulate the THREE.MeshLambertMaterial side property (via THREE.mesh) directly, to get the mesh to draw correctly:
The next step is to do this programatically, but I’m running into a bug where I can type the command on the console to see the result, even trying different methods:
var landscapeEl = document.querySelector('#landscape'); // Gaining access to the internal three.js object that the landscape component contains var landscapeObject3D = landscapeEl.object3D; console.log(landscapeObject3D.parent); console.log(landscapeObject3D.children); for(var i in landscapeObject3D.children) { //for all the children of the landscapeObject3D, change the material side property to THREE.DoubleSide aka 2 landscapeObject3D.children[i].material.side = THREE.DoubleSide; } //this works when typed into the console, but not here programmatically, found here: http://stackoverflow.com/questions/18613295/how-to-set-a-texture-on-a-object3d-child-mesh landscapeObject3D.children[0].material.side = THREE.DoubleSide; //this works when typed into console, but not here programmatically, found here: https://threejs.org/docs/#Reference/Materials/Material landscapeObject3D.traverse( function( node ) { if( node.material ) { node.material.side = THREE.DoubleSide; } }); //this also works when typed into the console, but not programatically, found here: http://stackoverflow.com/questions/16027131/three-js-how-to-make-double-sided-object
The A-Frame Slack came to the rescue, with pookage pointing out that I needed to call the code above only after the rest of the page had loaded, via the global JavaScript event handler: onLoad(). He also recommended the following the FunFunFunction YouTube Channel for general JavaScript learning.
function changeMaterialSide(){ //testing that I can print to the console console.log("A-Frame and the rest have loaded"); //Gaining access to the landscape element via it's ID var landscapeEl = document.querySelector('#landscape'); // Gaining access to the internal three.js object that the landscape component contains var landscapeObject3D = landscapeEl.object3D; //console.log(landscapeObject3D.parent); //console.log(landscapeObject3D.children); for(var i in landscapeObject3D.children) { //for all the children of the landscapeObject3D, change the material side property to THREE.DoubleSide aka 2 landscapeObject3D.children[i].material.side = THREE.DoubleSide; } //this works when typed into the console, but not here programmatically, found here: http://stackoverflow.com/questions/18613295/how-to-set-a-texture-on-a-object3d-child-mesh landscapeObject3D.children[0].material.side = THREE.DoubleSide; //this works when typed into console, but not here programmatically, found here: https://threejs.org/docs/#Reference/Materials/Material landscapeObject3D.traverse( function( node ) { if( node.material ) { node.material.side = THREE.DoubleSide; } }); //this also works when typed into the console, but not programatically, found here: http://stackoverflow.com/questions/16027131/three-js-how-to-make-double-sided-object } //Thanks to @pookage (http://www.beardeddevelopment.co.uk/) on the A-Frame Slack for pointing me in the correct direction:https://developer.mozilla.org/en/docs/Web/API/GlobalEventHandlers/onload window.onload = changeMaterialSide;
With this new knowledge, I added the material changing functionality to the other two component demo’s I had made before, making a total of new three pug files, which compiled to the following HTML/A-Frame pages:
While 3. worked fine, after uploading, I tested 1. and 2. and found that the meshes would not render properly – even though 2. was rendering properly via my local server. I tried adding a call to Material.needsUpdate() after finding a Stackoverflow page alluding to it, but to no avail, even after changing the Material properties to just BackSide sides.
Finally, I posted a Stack Overflow question about it, where the ever helpful Kevin Ngo advised writing a component for the functionality, which I’m going to do next.