IntelXDK, Crosswalk Runtime and WebGL

Introduction

This article is about developing android apps using Intel XDK and three.js.It will give an overview on how to develop GUI based app for Android architecture using this wonderful tool. I have taken help from the article while explaining Three.js and the full documentation of Three.js gives lot of information to work with.

Pretty new to Android Platform

For last 15 months I have been developing apps for Windows Desktop and so I am very new to Android platform. So this experience will be new for me as I explore the unknown world (for me) of Android. The things that I cover might not be new but I have given it a try.

Why I have chosen Intel XDK

I have little knowledge of HTML and I intend to use an IDE where I can implement my HTML skills. The whole IDE experience was a new one for me because I have not used Intel XDK once. It's a cloud based IDE which requires you to be always connected to the internet when you go through the entire process of creating a package for distribution. Pluses for Intel XDK would be that you don't have to configure Android ADT bundle and it has got an inbuilt emulator to test your app. Here you have the options to choose from different form factors such as Google Nexus 4 and Google Nexus 7,Lenovo K900 etc. Minuses I found would be that the IDE used to freeze at times if you used to work for on it for a long time. At these times i had to restart the IDE and then start my work again. Overall my experience using Intel XDK was good one because I had little trouble developing my app.

Exploring Three.js

Three.js!! In fact it came to my liking as I was searching some processing based examples on net. Essentially another Creative Coders delight (http://threejs.org/) it has got lot of options powered by WebGL it is essentially helpful in creating great looking GUI apps and have fun with. Being Open Sourced with lot of examples to work with. Now one catch as you are developing for Android not all browsers support WebGL in that case what you need to do is use Canvas renderer and you are on your way.

What is Intel XDK?

The Intel XDK is cross platform IDE for developing solid HTML 5 in the developing environment and you can update your code being connected to internet. After you build the app you can distribute it to different platforms. Android apps can be created by the same way and in the build option you can create the apk. This IDE has the ability to code once and distribute it to different platforms. In the new updated XDK there is CROSSWALK build option for Android that is in Beta phase right now it helps in porting your native capabilities html 5, JavaScript and CSS 3 apps. During Development phase you can test the app for different form factors using the emulator. All in all it's a great platform to develop HTML 5 apps and distribute it.

Download Link

Step by step process of downloading Intel XDK with figures.

The next step will detect your OS.

Save the File and the exe will be saved. Follow the steps as mentioned below to install and start the exe.

The project lifecycle of Intel XDK and Android Project shown below.

When you open up Intel XDK you will be presented with an option to Start a new project. Here you can start a fresh with a blank template or reuse any demo and modify it. The options that are available are:

  • i) Start with a Blank Project.
  • ii) Work with a Demo.
  • iii) Import an existing app :-here you can port old apps made with the XDK's, PhoneGap apps, AppMobapps, HTML 5 api based apps but cannot port Java apps.
  • iv) Use App Starter It uses App Framework 2.0. Full details are available here http://app-framework-software.intel.com/.
  • v) Start with App Designer App Designer allows you to get going with the project using App Framework, BootStrap API, jQuery Mobile or Top Coat.

As we are targeting Three.js we will use work with a demo that too the CrossWalk Demo and modify the Demo inserting additional codes in the index.html file and adding the required three.js files. There is a great information explaining and giving an overview into CrossWalk runtime in the Intel Website http://software.intel.com/en-us/html5/articles/crosswalk-application-runtime.

What is Three.js?

Three.js is a library that makes WebGL - 3D in the browser - very easy. While a simple cube in raw WebGL would turn out hundreds of lines of JavaScript and shader code, a Three.js equivalent is only a fraction of that. Three.js is a lightweight cross-browser JavaScript library/API used to create and display animated 3D computer graphics on a Web browser. Three.js scripts may be used in conjunction with the HTML5 canvas element, SVG or WebGL.

Starting A fresh

Decoding one of the examples and creating a new apk from the GITHUB. The Founders of Three.js have done an excellent job with all credits to them I am using one of the examples to get going. https://github.com/mrdoob/three.js/blob/master/examples/canvas_interactive_voxelpainter.html.

  1. Open Intel XDK
  2. Click on Project
  3. Click on Start a new Project

Click on Work with a demo.

Select CrossWalk and click on next.

Click on Create. As the project is created you will get a Congratulation message.

According to your liking change the index.html page as it will reflect the main changes in the app and also add the JavaScript required.

According to your liking change the index.html page as it will reflect the main changes in the app and also add the Three.js JavaScript required.

A Close look at the index.html page

Lets see the flow of the index.html file within Intel XDK.

After the changes in the index.html file and adding the required js files in the threejs folder (You need to access the files from the Windows directory structure of the project and then add the files manually. In my case I add the files manually over to the main project folder E:\IntelXDK_Projects\eXAMPLE2\threejs). You need to click on emulate (you can choose from the many emulators available to check the project).

The Magic of Intel XDK, Crosswalk to bring the effect of WebGL

Extending the Crosswalk demo with Intel XDK helps you bring WebGL to Android.As per the discussion in this topic the role of Crosswalk with Intel XDK is specified here:

Crosswalk can be thought of as an alternate runtime for Android devices. It is only compatible with Android 4.0 and higher devices, so cannot be applied to older Android 2.x and 3.x devices. It is in a preliminary (alpha) release state right now, I do not when it will be released to beta or final release. When it does become available there will be documentation describing in more detail what Crosswalk offers in comparison to using the builtin webview on Android 4.x devices.

As I had discussion with Bob Duffy I found out that,

Crosswalk with the Intel XDK are replacing the default webview that Android doen't support WebGL.Intel XDK is providing you Chromium and WebGL on pre 4.4 devices.

So the key here in building a new project is extending the index.html page which has already Crosswalk runtime associated.The important files in the project are manifest.json.Taking help from this documentation we see the application structure contains manifest.json in the root directory.The main entry point is then referenced from this manifest file.

The file format

{
	       "name": "WebGL Sample",
	       "manifest_version": 1,
	       "version": "0.0.0.1",
	       "app": {
	                   "launch":{
	                               "local_path": "index.html"
	                   }
	       }
	}

The Crosswalk project is in beta phase and is undergoing changes but you can certainly experiment and learn more. As per the discussion of Crosswalk it is:

At the heart of the Crosswalk web runtime is the Blink* rendering and layout engine. Blink provides the same HTML5 features and capabilities found in today's modern web ecosystem, such as WebGL* and Web Audio*. Crosswalk enables state-of-the-art HTML5-based applications that make the most of today's leading edge mobile devices.

Crosswalk with Intel XDK provides access to WebGL API.

The Build process

Here lies the main action where the apk's are created. The Build menu has all the options to distribute the apps in multiple platforms. Here you can edit Asset as well as images that you want to add to the app.For Android there are two options.

  • i) Android :- you create the normal APK's that you can distribute...
  • ii) CrossWalk for Android (it's in Beta phase) :- this is a build that creates a CrossWalk Runtime Android APK where you have the options to build it for ARM based devices or X86 architecture.

The Build process with figures.

You will see that the build is about to be created. You need to click on build app now.

The next figure shows the build process.

You will get a message that build is successful.

The whole process of changing the app development process happens at the index.html page. Any update here reflects the change and whole flow changes. Make changes to the index.html pages and include the necessary Three.js files. Tweaking the code from the GITHUB will help you explore. There is also a CROSSWALK build which allows to create the package in x86 or ARM architecture it's in beta phase but you can try this build.

The anatomy of the index.html page (Creating a new Project)

Any change made to the index.html actually reflects how the app will look like finally.So we need include necassry Three.js files as well as the whole logic needs to be implemented here. I dug deep in to the three.js GITHUB repository and check which are the examples that can be worked upon and bring it to Intel XDK and finally make the apk out of it. So what I have done is broken down the index.html page and its modification to give the proper view of the project. In context of learning I have taken help from here. It's very useful in exploring the three.js. The primary contributor to this library is Mr Droob and theo-armour. Due respect to these people (They have done excellent job in what Three.js is now) I am exploring these repositories to learn, share and contribute.

let's Start

To be more compatible with different mobile platforms we need to declare viewport with device- width height.

The device width allows adjustment according to the changing devices be it a tablet or different mode phones.

The declaration

 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0"> 

This also implies that when we change the orientation of the device it gives a proper access of the app.

The Style Tag

The Style Tag allows how the app is rendered to the devices.Here is the modification that shows how the app will look like. So now for this project we modify the Style tag accordingly.


body {
				font-family: Monospace;
				background-color: #f0f0f0;
				margin: 0px;
				overflow: hidden;
			}



for reloading to make easier so that we have to reload the pages again and again we include three.min.js within the head tag of the html including the script in the script tag.

 <script></script> 

As we include the three.js script tag in the body we allow important actions to execute within the three.min.js script. Here in lies the logic of implementing the three.js and hence we need to include it to the head tag.

Now comes the turn for initialization of the variables or getting to implement the way the 3D GUI structure will behave we implement animations such as object movements interactions getting in closer to the objects or moving out we start by calling the init() method.

In the entry point for three.js script we need to append Element and the chid behaviors. For getting Geometry to work we need to implement variables and their implementation logic here.

As we come across Three.js Script we see that it is essentially a 3D gui depiction involving:

  • i) Scenes
  • ii) Cameras
  • iii) Projectors
  • iv) Renderers and Objects.

Certain modifications we have in Three.js script allows implementing plane Geometry to Face normals.

we use


var normalMatrix= new Three.Matrix3();


For creating a Shadow effect with the camera perspective we use this:

camera= new THREE.perspectiveCamera();

Modifying the custom grid involves changes in the geometry hence we do the following:


	var size = 500, step = 50;

				var geometry = new THREE.Geometry();

				for ( var i = - size; i <= size; i += step ) {

					geometry.vertices.push( new THREE.Vector3( - size, 0, i ) );
					geometry.vertices.push( new THREE.Vector3(   size, 0, i ) );

					geometry.vertices.push( new THREE.Vector3( i, 0, - size ) );
					geometry.vertices.push( new THREE.Vector3( i, 0,   size ) );

				}

				var material = new THREE.LineBasicMaterial( { color: 0x000000, opacity: 0.2 } );

				var line = new THREE.Line( geometry, material );
				line.type = THREE.LinePieces;
				scene.add( line );


We use projector to change the behavior of the objects and also implementing mouse movements and to select certain objects. This also helps in projection in a screen space.

The light reflection as well as ambient light effect is controlled in these lines of code. This also shows how the lighting effect will be.


var ambientLight = new THREE.AmbientLight( 0x606060 );


Taking a look at variable declaration.


target=new THREE.Vector3(0,200,0);


In this declaration above we declare a 3D vector.A 3D vector is in general a geometric quantity that has magnitude and direction.


var normalMatrix=new THREE.Matrix 3();


It's a 3*3 matrix.

For projection purpose we use mouse 2D and mouse 3D.

More modifications and the whole code of the html is shown below it create a grid and you can place the boxes and design it. This is a excerpt modified from the link.


<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js canvas - interactive - voxel painter</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				font-family: Monospace;
				background-color: #f0f0f0;
				margin: 0px;
				overflow: hidden;
			}
		</style>
	</head>
	<body>

		<script src="../build/three.min.js"></script>

		<script src="js/libs/stats.min.js"></script>

		<script>

			var container, stats;
			var camera, scene, renderer;
			var projector, plane;
			var mouse2D, mouse3D, raycaster, theta = 45,
			isShiftDown = false, isCtrlDown = false,
			target = new THREE.Vector3( 0, 200, 0 );
			var normalMatrix = new THREE.Matrix3();
			var ROLLOVERED;

			init();
			animate();

			function init() {

				container = document.createElement( 'div' );
				document.body.appendChild( container );

				var info = document.createElement( 'div' );
				info.style.position = 'absolute';
				info.style.top = '10px';
				info.style.width = '100%';
				info.style.textAlign = 'center';
				info.innerHTML = '<a href="http://threejs.org" target="_blank">three.js</a> - voxel painter<br><strong>click</strong>: add voxel, <strong>control + click</strong>: remove voxel, <strong>shift</strong>: rotate, <a href="javascript:save()">save .png</a>';
				container.appendChild( info );

				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 );
				camera.position.y = 800;

				scene = new THREE.Scene();

				// Grid

				var size = 500, step = 50;

				var geometry = new THREE.Geometry();

				for ( var i = - size; i <= size; i += step ) {

					geometry.vertices.push( new THREE.Vector3( - size, 0, i ) );
					geometry.vertices.push( new THREE.Vector3(   size, 0, i ) );

					geometry.vertices.push( new THREE.Vector3( i, 0, - size ) );
					geometry.vertices.push( new THREE.Vector3( i, 0,   size ) );

				}

				var material = new THREE.LineBasicMaterial( { color: 0x000000, opacity: 0.2 } );

				var line = new THREE.Line( geometry, material );
				line.type = THREE.LinePieces;
				scene.add( line );

				//

				projector = new THREE.Projector();

				plane = new THREE.Mesh( new THREE.PlaneGeometry( 1000, 1000 ), new THREE.MeshBasicMaterial() );
				plane.rotation.x = - Math.PI / 2;
				plane.visible = false;
				scene.add( plane );

				mouse2D = new THREE.Vector3( 0, 10000, 0.5 );

				// Lights

				var ambientLight = new THREE.AmbientLight( 0x606060 );
				scene.add( ambientLight );

				var directionalLight = new THREE.DirectionalLight( 0xffffff );
				directionalLight.position.x = Math.random() - 0.5;
				directionalLight.position.y = Math.random() - 0.5;
				directionalLight.position.z = Math.random() - 0.5;
				directionalLight.position.normalize();
				scene.add( directionalLight );

				var directionalLight = new THREE.DirectionalLight( 0x808080 );
				directionalLight.position.x = Math.random() - 0.5;
				directionalLight.position.y = Math.random() - 0.5;
				directionalLight.position.z = Math.random() - 0.5;
				directionalLight.position.normalize();
				scene.add( directionalLight );

				renderer = new THREE.CanvasRenderer();
				renderer.setSize( window.innerWidth, window.innerHeight );

				container.appendChild(renderer.domElement);

				stats = new Stats();
				stats.domElement.style.position = 'absolute';
				stats.domElement.style.top = '0px';
				container.appendChild( stats.domElement );

				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
				document.addEventListener( 'mousedown', onDocumentMouseDown, false );
				document.addEventListener( 'keydown', onDocumentKeyDown, false );
				document.addEventListener( 'keyup', onDocumentKeyUp, false );

				//

				window.addEventListener( 'resize', onWindowResize, false );

			}

			function onWindowResize() {

				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize( window.innerWidth, window.innerHeight );

			}

			function onDocumentMouseMove( event ) {

				event.preventDefault();

				mouse2D.x = ( event.clientX / window.innerWidth ) * 2 - 1;
				mouse2D.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

				var intersects = raycaster.intersectObjects( scene.children );

				if ( intersects.length > 0 ) {

					if ( ROLLOVERED ) ROLLOVERED.color.setHex( 0x00ff80 );

					ROLLOVERED = intersects[ 0 ].face;
					ROLLOVERED.color.setHex( 0xff8000 )

				}

			}

			function onDocumentMouseDown( event ) {

				event.preventDefault();

				var intersects = raycaster.intersectObjects( scene.children );

				if ( intersects.length > 0 ) {

					var intersect = intersects[ 0 ];

					if ( isCtrlDown ) {

						if ( intersect.object != plane ) {

							scene.remove( intersect.object );

						}

					} else {

						normalMatrix.getNormalMatrix( intersect.object.matrixWorld );

						var normal = intersect.face.normal.clone();
						normal.applyMatrix3( normalMatrix ).normalize();

						var position = new THREE.Vector3().addVectors( intersect.point, normal );

						var geometry = new THREE.CubeGeometry( 50, 50, 50 );

						for ( var i = 0; i < geometry.faces.length; i ++ ) {

							geometry.faces[ i ].color.setHex( 0x00ff80 );

						}

						var material = new THREE.MeshLambertMaterial( { vertexColors: THREE.FaceColors } );

						var voxel = new THREE.Mesh( geometry, material );
						voxel.position.x = Math.floor( position.x / 50 ) * 50 + 25;
						voxel.position.y = Math.floor( position.y / 50 ) * 50 + 25;
						voxel.position.z = Math.floor( position.z / 50 ) * 50 + 25;
						voxel.matrixAutoUpdate = false;
						voxel.updateMatrix();
						scene.add( voxel );

					}

				}
			}

			function onDocumentKeyDown( event ) {

				switch( event.keyCode ) {

					case 16: isShiftDown = true; break;
					case 17: isCtrlDown = true; break;

				}

			}

			function onDocumentKeyUp( event ) {

				switch( event.keyCode ) {

					case 16: isShiftDown = false; break;
					case 17: isCtrlDown = false; break;

				}
			}

			function save() {

				window.open( renderer.domElement.toDataURL('image/png'), 'mywindow' );
				return false;

			}

			//

			function animate() {

				requestAnimationFrame( animate );

				render();
				stats.update();

			}

			function render() {

				if ( isShiftDown ) {

					theta += mouse2D.x * 3;

				}

				camera.position.x = 1400 * Math.sin( theta * Math.PI / 360 );
				camera.position.z = 1400 * Math.cos( theta * Math.PI / 360 );
				camera.lookAt( target );

				raycaster = projector.pickingRay( mouse2D.clone(), camera );

				renderer.render( scene, camera );

			}

		</script>

	</body>
</html>



The project as it looks in the emulator.

After the experiment we see that adding simple modification to the index.html and adding required three.js files gives you some cool GUI effects that yo can use in your projects.

  • The possibilities are endless with three.js as you can also develop games with it.
  • Three.js is an excellent WebGL tool that helps you explore 3D GUI applications in an innovative manner.

Now when you combine the Intel XDK IDE you can get some great APK's created with it.

Nexus7 Emulator Images

This article is an attempt to showcase how Three.js can be develop good GUI based WebGL Android app using Intel XDK IDE. For the entire project process Internet connectivity is required. As I learn more I will try to contribute more. GITHUB repository for Three.js Check the examples and experiment. I had fun tweaking the codes.

Good resources

You will know a lot and get good knowledge out of questions of Three.js at StackOverflow.

Intel XDK Documentation

Three.js documentation

APK Examples and the Code link

For more complete information about compiler optimizations, see our Optimization Notice.