Code Three.js in ActionScript with Randori Compiler
Live Demo
Live Demo of three.js html/js generated by ActionScript 3 using the Randori compiler – http://renaun.com/html5/as3threejs/
What is Randori?
Randori is a project with various parts and can be found at http://randoriframework.com/. For my example I am using a subset of the Randori project’s capabilities, which is fine as the Randori project is put together in such a way that it allows for this. You can find the various Randori repos here: https://github.com/RandoriAS
Repos:
- randori-sdk: This repo contains all the Randori framework bits you need for both the ActionScript 3 and JavaScript side.
- randori-compiler: This repo contains the ActionScript 3 to JavaScript compiler source files. Precompiled version is located here
- randori-framework & randori-guice-framework: These repos contains the source files for the Randori framework.
- randori-libraries: This repo contains source files for ActionScript 3 classes that stubbed out JavaScript libraries. For example to get three.js code hinting in ActionScript there are stubbed out ActionScript 3 classes that map to three.js classes.
- randori-plugin-intellij: This repo contains an IntelliJ plugin for creating Randori framework based apps and an integrated compiler.
- randori-demos-bundle: This repo contains demo Randori applications.
- randori-tools: This repo contains the Randori Toolset for ActionScript. These tools help to generate the JavaScript interop libraries.
The ActionScript three.js example in this post makes use of the randori-compiler, randori-libraries, and randori-sdk repos.
Setup Randori for Compiling ActionScript
Here are the steps I used to set up my development space:
- Create a folder called RandoriRepos somewhere on your hard drive, and keep track of this folder path for later use.
- Download http://randoriframework.com/nightly-builds/ to the RandoriRepos folder. Unzip the folder, you should see the RandoriRepos/randori-compiler-latest/ folder now.
- Use a git client to clone the randori-libraries repo into the RandoriRepos folder. Or on the command line navigate to the RandoriRepos folder and execute:
git clone git://github.com/RandoriAS/randori-libraries.git
- Use a git client to clone the randori-sdk repo into the RandoriRepos folder. Or on the command line navigate to the RandoriRepos folder and execute:
git clone git://github.com/RandoriAS/randori-sdk.git
The compiler is located at RandoriRepos/randori-compiler-latest/randori.jar. Next we’ll compile a simple ActionScript 3 class into JavaScript with the compiler.
Compiling ActionScript to JavaScript with the Randori Compiler
The sample files that I used to create the three.js example in ActionScript 3 are located at https://github.com/renaun/RandoriExamples.
git clone https://github.com/renaun/RandoriExamples.git
We want RandoriRepos and RandoriExamples folders to have the same parent folder.
-library-path /path/to/RandoriRepos/randori-sdk/randori-framework/bin/swc/builtin.swc \
-source-path ./ \
-js-classes-as-files=true \
-output ./randori-output
You should see a HelloWorld.js file in the RandoriExamples/SimpleClass/randori-output/ folder. The contents should look like this:
HelloWorld = function() {
};
HelloWorld.prototype.toString = function() {
return "Hello Randori!";
};
HelloWorld.className = "HelloWorld";
HelloWorld.getClassDependencies = function(t) {
var p;
return [];
};
HelloWorld.injectionPoints = function(t) {
return [];
};
To see the HelloWorld.js class in action take a look at RandoriExamples/SimpleClass/index.html.
Well compiling a simple class is not that impressive. Lets get to the real example and show how to use ActionScript 3 and the Randori compiler to create a three.js demo
NOTE: The Randori compiler will not magically convert any of the Flash Player APIs in ActionScript. Whatever you are coding in ActionScript 3 has to be available on the JavaScript side. Meaning we are writing ActionScript 3 code that will map to three.js APIs.
Creating the three.js demo in ActionScript 3
The first thing we need is the ability to reference some basic HTML DOM APIs and the three.js library in ActionScript 3. This is done with stub classes in the randori-libraries repo. But to use them with the Randori compiler we need to create a SWC version of the libraries we want to use. The SWC also allows for code hinting in your IDE, for example in Adobe Flash Builder.
Here are the steps to setup the ActionScript Library Project in Adobe Flash Builder for the randori-libraries:
- In Adobe Flash Builder click on File -> New -> ActionScript Library Project
- Give it a project name of: randori-libraries
- Set the Folder location to: /path/to/RandoriRepos/randori-libraries
- Click Finish
- Open the randori-libraries project properties by right clicking on the project -> Properties
- Open ActionScript Library Build Path -> Library Path and remove the AIR SDK path library
- In Library Path click on “Add SWC…”, enter the following path for builtin.swc and click Ok:
${PARENT-1-PROJECT_LOC}/randori-sdk/randori-framework/bin/swc/builtin.swc
- Open up ActionScript Library Build Path -> Source Path and click on Add Folder, type in the following path and click OK:
HTMLCoreLib/src
- Repeat the above step for:
ThreeJs/src
NOTE: The randori-sdk repo does contain compiled SWC versions for some of the randori-libraries. If you look at https://github.com/RandoriAS/randori-sdk/tree/master/randori-framework/bin/swc you can see it contains: HTMLCoreLib.swc, JQuery.swc, builtin.swc, randori-framework.swc, randori-guice-framework.swc.
Now that we have the libraries for our stub classes we'll use this in the Randori compiler's -library-path arguments.
You might be able to import the ActionScriptThreeJS Adobe Flash Builder project as is; the project is located in the RandoriExamples folder. The steps below can be skipped if the import works; these steps show how to create a new ActionScript project in Adobe Flash Builder and use the Randori stub libraries.
- In Adobe Flash Builder click File -> New -> ActionScript Project
- Give it a project name of: ActionScriptThreeJS
- Set the Folder location to: /path/to/RandoriExamples/ActionScriptThreeJS
- Click Finish
- Open the ActionScriptThreeJS project properties by right clicking on the project -> Properties
- Open ActionScript Build Path -> Library Path and remove the AIR SDK path library
- In Library Path click on "Add Project...", select the "randori-libraries" project and click OK
- Open ActionScript Compiler and uncheck the "Generate HTML wrapper file" checkbox
- Open the src/ActionScriptThreeJS.as and you should see code hinting for Three class as well as others.
Here is the code for ActionScriptThreeJS.as:
* Author: Renaun Erickson (renaun.com - @renaun - github.com/renaun)
*
* Attribution: Leonard Souza - https://github.com/leonardsouza/RandoriAS-ThreeJS
*/
package
{
import com.mrdoob.stats.Stats;
import com.mrdoob.three.Three;
import com.mrdoob.three.cameras.PerspectiveCamera;
import com.mrdoob.three.extras.geometries.CubeGeometry;
import com.mrdoob.three.extras.geometries.PlaneGeometry;
import com.mrdoob.three.loaders.ImageUtils;
import com.mrdoob.three.materials.MeshBasicMaterial;
import com.mrdoob.three.math.Matrix4;
import com.mrdoob.three.math.Plane;
import com.mrdoob.three.objects.Mesh;
import com.mrdoob.three.renderers.WebGLRenderer;
import com.mrdoob.three.scenes.Scene;
import com.mrdoob.three.textures.Texture;
import randori.webkit.dom.DomEvent;
import randori.webkit.dom.Element;
import randori.webkit.dom.MouseEvent;
import randori.webkit.dom.TouchEvent;
import randori.webkit.html.HTMLDivElement;
import randori.webkit.html.HTMLElement;
import randori.webkit.page.Window;
import utils.RequestAnimationFrame;
public class ActionScriptThreeJS
{
protected var container:Element;
protected var stats:Stats;
protected var camera:PerspectiveCamera;
protected var scene:Scene;
protected var renderer:WebGLRenderer;
protected var cube:Mesh;
protected var plane:Plane;
protected var targetRotation:Number = 0;
protected var targetRotationOnMouseDown:Number = 0;
protected var mouseX:Number = 0;
protected var mouseXOnMouseDown:Number = 0;
protected var windowHalfX:Number = window.innerWidth / 2;
protected var windowHalfY:Number = window.innerHeight / 2;
[Inject]
public var animation:RequestAnimationFrame;
public function main():void
{
container = new HTMLDivElement();
window.document.body.appendChild(container);
var info:HTMLElement = new HTMLDivElement();
info.style.position ="absolute;"
info.style.top ="10px";
info.style.width ="100%";
info.style.textAlign ="center";
info.innerHTML ="Drag to spin the cube";
container.appendChild(info);
camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
camera.position.y = 150;
camera.position.z = 600;
scene = new Scene();
// FLOOR
var floorTexture:Texture = ImageUtils.loadTexture("images/checkerboard.jpg");
floorTexture.wrapS = floorTexture.wrapT = Three.RepeatWrapping;
floorTexture.repeat.set(5, 5);
var floorMaterial:MeshBasicMaterial = new MeshBasicMaterial({ map: floorTexture, side: Three.DoubleSide });
var floorGeometry:PlaneGeometry = new PlaneGeometry(1200, 1200, 20, 20);
var floor:Mesh = new Mesh(floorGeometry, floorMaterial);
floor.position.y = -0.5;
floor.rotation.x = Math.PI / 2;
floor.position.z = -10;
scene.add(floor);
// Cube
var geometry:CubeGeometry = new CubeGeometry(200, 200, 200);
for (var i:int = 0; i < geometry.faces.length; i ++)
geometry.faces[i].color.setHex(Math.random() * 0xffffff);
var material:MeshBasicMaterial = new MeshBasicMaterial({ vertexColors: Three.FaceColors });
cube = new Mesh(geometry, material);
cube.position.y = 150;
scene.add(cube);
// Plane
var geometry2:PlaneGeometry = new PlaneGeometry(200, 200);
geometry2.applyMatrix(new Matrix4().makeRotationX(- Math.PI / 2 ));
material = new MeshBasicMaterial({ color: 0xe0e0e0 });
plane = new Mesh(geometry2, material) as Plane;
scene.add(plane);
renderer = new WebGLRenderer( {antialias:true} );
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);
window.document.addEventListener("mousedown", onDocumentMouseDown, false);
window.document.addEventListener("touchstart", onDocumentTouchStart, false);
window.document.addEventListener("touchmove", onDocumentTouchMove, false);
window.addEventListener("resize", onWindowResize, false);
startRender();
}
private function onDocumentMouseDown(event:MouseEvent):void
{
event.preventDefault();
window.console.log("down");
window.document.addEventListener("mousemove", onDocumentMouseMove, false);
window.document.addEventListener("mouseup", onDocumentMouseUp, false);
window.document.addEventListener("mouseout", onDocumentMouseOut, false);
mouseXOnMouseDown = event.clientX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
}
private function onDocumentTouchStart(event:TouchEvent):void
{
if (event.touches.length === 1) {
event.preventDefault();
mouseXOnMouseDown = event.touches[0].pageX - windowHalfX;
targetRotationOnMouseDown = targetRotation;
}
}
private function onDocumentTouchMove(event:TouchEvent):void
{
if (event.touches.length === 1) {
event.preventDefault();
mouseX = event.touches[0].pageX - windowHalfX;
targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05;
window.console.log("touchMove");
}
}
private function onDocumentMouseMove(event:MouseEvent):void
{
mouseX = event.clientX - windowHalfX;
targetRotation = targetRotationOnMouseDown + (mouseX - mouseXOnMouseDown ) * 0.02;
}
private function onDocumentMouseUp(event:MouseEvent):void
{
window.console.log("up");
window.document.removeEventListener("mousemove", onDocumentMouseMove, false);
window.document.removeEventListener("mouseup", onDocumentMouseUp, false);
window.document.removeEventListener("mouseout", onDocumentMouseOut, false);
}
private function onDocumentMouseOut(event:MouseEvent):void
{
window.console.log("out");
window.document.removeEventListener("mousemove", onDocumentMouseMove, false);
window.document.removeEventListener("mouseup", onDocumentMouseUp, false);
window.document.removeEventListener("mouseout", onDocumentMouseOut, false);
}
private function onWindowResize(event:DomEvent):void
{
windowHalfX = window.innerWidth / 2;
windowHalfY = window.innerHeight / 2;
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
private function startRender():void
{
animation.request(startRender);
render();
stats.update();
}
private function render():void
{
plane.rotation.y = cube.rotation.y += (targetRotation - cube.rotation.y) * 0.05;
renderer.render(scene, camera);
}
}
}
The class contains a main() method that is called by the GuiceInjectorBootstrap class once the class has been instantiated. We'll talk about the bootstrap class in a bit. In the main() method you can see three.js like classes. For example the "//Floor" code was copied straight out of a JavaScript example with minor changes to make it reference class names directly without the JavaScript THREE. prefix (ie: ImageUtils instead of THREE.ImageUtils) and strong typing declarations.
The ActionScript class in the randori-libraries/ThreeJS folder are not guaranteed to be complete and are based on r57 of three.js. But it is enough to show you how to mock up a 3rd party JS library and use it in ActionScript 3 with randori-compiler. It was easy to add missing stuff too, I added the ImageUtils class in a jiffy and had it working with r58 of three.js easily.
Well enough talk we need to compile the ActionScript into JavaScript and see it run. Lets take a look at the compile arguments for this project. There is a shell script that shows the compiler arguments, if you are on a Mac you can easily run it in terminal. Here is the full command; this needs to be run in the command line from the RandoriExamples/ActionScriptThreeJS/src/ folder:
-sdk-path=/path/to/RandoriRepos/randori-sdk/ \
-library-path /path/to/RandoriRepos/randori-libraries/bin/randori-libraries.swc \
-source-path ./ \
-js-classes-as-files=true \
-output ../js-randori
The -sdk-path will tell the compiler where the randori-sdk is and will copy over the required randori js files. The -sdk-path also will link in the swc files from the randori-sdk location, which means we don't have to use the -library-path argument to add builtin.swc, HTMLCoreLib.swc, or the randori-guice-framework.swc (Used by the GuiceInjectorBootstrap.as class). The -library-path references the needed SWC's to compile the ActionScript files. The rest should be straightforward, but here is a link to the documentation for the randori compiler arguments.
The code generated by the Randori compiler is found in RandoriExamples/ActionScriptThreeJS/js-randori. It includes the following files:
- GuiceInjectorBootstrap.js
- ActionScriptThreeJS.js
- randori-framework-min.js
- randori-framework.js
- randori-guice-framework-min.js
- randori-guice-framework.js
- utils/RequestAnimationFrame.js
The current Randori compiler doesn't create a wrapper HTML file but I have put in a request for it to create a basic one. Either way we still have to hook up the three.js library and other application assets. These files are located in the RandoriExamples/ActionScriptThreeJS folder as the following:
- index.html - The three.js example index html file.
- images/checkerboard.jpg - Asset file used in the three.js demo.
- js/ - contains the three.js and stats.js libraries that are included in the index.html
Here is what the index.html file looks like:
<html>
<head>
<title>ActionScript to ThreeJS Example</title>
<script src="js/three.min.js"></script>
<script src="js/stats.min.js"></script>
<script src="js-randori/randori-guice-framework.js"></script>
<script src="js-randori/GuiceInjectorBootstrap.js"></script>
</head>
<body>
<script>
onload = function() {
var bootstrap = new GuiceInjectorBootstrap( "ActionScriptThreeJS", "js-randori/" );
bootstrap.launch();
}
</script>
</body>
</html>
So here is where some of the Randori magic happens. The Randori compiler outputs code that the randori-guice-framework.js can do stuff with. What kind of stuff? Well, like dependency injection and lazy loading compiled JavaScript classes when they are needed. The GuiceInjectorBootstrap.as file contains Randori guice classes to handle the loading and injection. All we have to do is pass in the main class file and output folder where the generated JavaScript is located. In this example this is done by the var bootstrap = new GuiceInjectorBootstrap( "ActionScriptThreeJS", "js-randori/" ); code. It loads the js-randori/ActionScriptThreeJS.js file. When it finds that ActionScriptThreeJS is looking for utils/RequestAnimationFrame.js from the [Inject] metadata used in the ActionScriptThreeJS.as file it will go load that class file too. To demonstrate this, run the index.html in the browser and check the network calls in Chrome Developer Tools. It should look something like this:
You can see as it loads three.js, stats.js, randori-guice-framework.js and GuiceInjectorBootstrap.js they are parallel calls. Then it loads the class files, first the ActionScriptThreeJS.js file (which is not included in the index.html script tags). The Randori guice framework then notices that ActionScriptThreeJS injects the utils.RequestAnimationFrame classes and goes and fetches that JavaScript class file. So no worrying about what classes are required to run your code just give it the main class and let Randori guice does the rest.
This is actually where the full Randori framework is pretty cool. The full Randori stack is geared for large applications making use of MVC patterns and dependency injection for some great features. A great place to learn more about the Randori framework for large application development is the getting started section here.
Randori is a fairly new project but it was really fun to play with and something I'll be exploring more.
Links Used in the Post
RandoriAS Project Github - https://github.com/RandoriAS
My ActionScript 3 Three.js Example Code - https://github.com/renaun/RandoriExamples
My ActionScript 3 Three.js Live Demo - http://renaun.com/html5/as3threejs/
A thank you goes out to Leonard Souza for providing the ThreeJS+Randori example - https://github.com/leonardsouza/RandoriAS-ThreeJS
Pingback: Philippe.me » Type annotations for the CreateJS Toolkit
Pingback: randori?ActionScript?JavaScript????????framework « ????TV??