Other Websites

  • SeaCloud9 Interactive
  • SeaCloud9 Commercial Development
  • i-os
  • stArcade9

Brendon Smith Social Networks

  • On Linkedin
  • Bookmarks
  • On Twitter
  • On Facebook
Open ↓ Close ↑
  • Home
  • Subscribe

i-create | therefore-i am

i-create | therefore i-am | a blog about opensource technology and rich internet applications

 

Away3D Bullet Physics and MD2 and Waves

By Brendon Smith on January 8, 2012

 

Away3D is the weapon of choice for 3D content on the Stage3D platform. Away3D offers a great deal of support for many model formats and integrates well with many other as3 libraries.  You will find that it is actually easy to integrate Away3D with several popular physics libraries including but not limited to Bullet and JigLib.  Away3D supports Bullet physics by default by utilizing their own port from C++ via Alchemy port.  If you are familiar with JigLib it also supports Away3D through the use of a plugin.  Bullet and JigLib have been around for awhile and are available in several languages.  They allow developers to add physics to their models this makes it possible for us to create racing games, first person shooters, marble games, angry bird tossing etc.  Their are many game engines available to developers for example Unity, Unreal Engine, Shiva, Cooper Cube, CubeEngine,  etc..  These engines are very powerful and do make it trivial to make most video games although I enjoy knowing what goes on in the background these engines are also really expensive except for CubeEngine and CooperCube:) In this demo I will show you how to render an MD2 model, use the triangle mesh, utilize some basic physics, have a wave mesh not to mention texture use.  

Check Out the Triangle Mesh Demo
Takes a few seconds to load no loader

Get the Code

Multiple Collision Demo

package
{
	//Away3D lib
	import away3d.animators.VertexAnimator;
	import away3d.cameras.Camera3D;
	import away3d.cameras.lenses.PerspectiveLens;
	import away3d.containers.ObjectContainer3D;
	import away3d.containers.View3D;
	import away3d.core.base.Object3D;
	import away3d.core.base.SubGeometry;
	import away3d.debug.AwayStats;
	import away3d.entities.Mesh;
	import away3d.events.AssetEvent;
	import away3d.events.LoaderEvent;
	import away3d.extrusions.Elevation;
	import away3d.extrusions.SkinExtrude;
	import away3d.library.AssetLibrary;
	import away3d.library.assets.AssetType;
	import away3d.lights.PointLight;
	import away3d.loaders.Loader3D;
	import away3d.loaders.misc.AssetLoaderContext;
	import away3d.loaders.parsers.MD2Parser;
	import away3d.loaders.parsers.OBJParser;
	import away3d.materials.BitmapFileMaterial;
	import away3d.materials.BitmapMaterial;
	import away3d.materials.ColorMaterial;
	import away3d.materials.DefaultMaterialBase;
	import away3d.materials.methods.EnvMapDiffuseMethod;
	import away3d.materials.methods.FogMethod;
	import away3d.materials.utils.CubeMap;
	import away3d.primitives.Cube;
	import away3d.primitives.Plane;
	import away3d.primitives.SkyBox;
	import away3d.primitives.Sphere;
 
	// physics lib 
	import awayphysics.collision.dispatch.AWPCollisionObject;
	import awayphysics.collision.shapes.*;
	import awayphysics.collision.shapes.AWPSphereShape;
	import awayphysics.data.AWPCollisionFlags;
	import awayphysics.debug.AWPDebugDraw;
	import awayphysics.dynamics.*;
	import awayphysics.dynamics.vehicle.*;
	import awayphysics.events.AWPEvent;
 
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	import flash.net.URLRequest;
	import flash.ui.Keyboard;
 
 
 
 
 
	[SWF(width="1280", height="720", frameRate="60", backgroundColor="0x000000")]
	public class LoaderOBJTest extends Sprite
	{
		private var _view : View3D;
		private var _loader : Loader3D;
		private var _light : PointLight;
		private var _mesh : Mesh;
		public var camera:Camera3D;
		public var plane:Plane;
		public var waveCycle:Number =5;
		public var threeSixtyRads:Number = 360 * (Math.PI / 180);
		private var _speed:Number = 1;
		public var extrude:SkinExtrude;
		private var isRenderable:Boolean = false;
		[Embed(source="assets/shipwreck/water.jpg")] private var WaterImage:Class;
		[Embed(source="assets/shipwreck/waterM.jpg")] private var WaterImageM:Class;
		[Embed(source="assets/test/Ratamahatta.jpg")] private var Oger:Class;
		private var oger:Bitmap = new Oger();
		private var waterBitmap:Bitmap = new WaterImage();
		private var waterBitmapM:Bitmap = new WaterImageM();
		private var wave:Number = 0;
		//private var _camController : HoverDragController;
		private var _count : Number = 0;
		protected static const MOVESPEED:Number = 100;
		protected static const TURNSPEED:Number = 90;
		protected var forward:Boolean = false;
		protected var backward:Boolean = false;
		protected var turnLeft:Boolean = false;
		protected var turnRight:Boolean = false;
		public var elevate:Elevation;
		private var _cubeMap : CubeMap;
		[Embed(source="assets/skybox/px.jpg")]
		private var EnvPosX:Class;
		[Embed(source="assets/skybox/py.jpg")]
		private var EnvPosY:Class;
		[Embed(source="assets/skybox/pz.jpg")]
		private var EnvPosZ:Class;
		[Embed(source="assets/skybox/nx.jpg")]
		private var EnvNegX:Class;
		[Embed(source="assets/skybox/ny.jpg")]
		private var EnvNegY:Class;
		[Embed(source="assets/skybox/nz.jpg")]
		private var EnvNegZ:Class;
 
		private var island:ObjectContainer3D;
		private var mylight:PointLight;
		private var _timeScale : Number = 3;
		private var sphereContainer:ObjectContainer3D;
		private var sphereCollider:Mesh;
		private var physicsWorld : AWPDynamicsWorld;
		private var debugDraw:AWPDebugDraw;
		private var _sphereShape : AWPSphereShape;
		public var sphereArr:Array = new Array();
 
		// speed up or slow down physics
		private var timeStep : Number = 0.2 / 60;
		//private var timeStep : Number = 1 / 60;
 
 
		public function LoaderOBJTest()
		{
			_view = new View3D();
			_view.antiAlias = 4;
			this.addChild(_view);
			_light = new PointLight();
			_light.x = 15000;
			_light.z = 15000;
			_light.color = 0xffddbb;
			_view.scene.addChild(_light);
			_view.camera.x = 800;
			_view.camera.y = 220;
			_view.camera.rotationY = -90;
			_view.camera.z = 8;
			camera = _view.camera;
			camera.lens = new PerspectiveLens();
			camera.lens.far = 5000;
 
 
			addChild(new AwayStats(_view));
			initMesh();
 
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			_view.mouseEnabled = true;
			this.addEventListener(Event.ENTER_FRAME, handleEnterFrame);
			this.stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
			this.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;
			stage.addEventListener(Event.RESIZE, onStageResize);
 
			_cubeMap = new CubeMap( new EnvPosX().bitmapData, new EnvNegX().bitmapData,
				new EnvPosY().bitmapData, new EnvNegY().bitmapData,
				new EnvPosZ().bitmapData, new EnvNegZ().bitmapData);
 
			_view.scene.addChild(new SkyBox(_cubeMap));
 
			var tex : BitmapData = new BitmapData(512, 512, false, 0);
			tex.perlinNoise(25, 25, 8, 1, false, true, 7, true);
			var fog : FogMethod = new FogMethod(1500, 0x808080);
 
 
 
 
 
 
		}
 
		private function onStageResize(event : Event) : void
		{
			_view.width = stage.stageWidth;
			_view.height = stage.stageHeight;
		}
 
		private function initMesh() : void
		{
			Loader3D.enableParser(OBJParser);
			_loader = new Loader3D();
			_loader.load(new URLRequest('assets/shipwreck/shipwreck.obj'), new OBJParser());
			_loader.addEventListener(LoaderEvent.RESOURCE_COMPLETE, onSceneResourceComplete);
 
			var waterMaterial:BitmapMaterial = new BitmapMaterial(waterBitmap.bitmapData);
			waterMaterial.bothSides = true;
			plane = new Plane(waterMaterial, 7000, 7000, 100, 100);
			plane.y = 135;
			plane.rotationX = 90;
			plane.segmentsW = 100;
			plane.segmentsH = 100;
 
			// init the physics world
			physicsWorld = AWPDynamicsWorld.getInstance();
			physicsWorld.initWithDbvtBroadphase();
			physicsWorld.collisionCallbackOn = true;
 
 
			_view.scene.addChild(plane);
 
 
		}
 
		private function onAssetRetrieved(event : AssetEvent) : void
		{
			if (event.asset.assetType == AssetType.MESH) {
				var pmaterial : DefaultMaterialBase;
 
 
				_mesh = Mesh(event.asset);
				_mesh.scaleX = 0.4;
				_mesh.scaleY = 0.4;
				_mesh.scaleZ = 0.4;
				_mesh.y = 185;
				_mesh.x = 65;
				_mesh.z = 8;
				_view.scene.addChild(_mesh);
 
				pmaterial = DefaultMaterialBase(_mesh.material);
				var ogerMaterial:BitmapMaterial = new BitmapMaterial(new Oger().bitmapData);
				_mesh.material = ogerMaterial;
 
 
			}
			else if (event.asset.assetType == AssetType.ANIMATOR) {
				var controller : VertexAnimator = VertexAnimator(event.asset);
				controller.play("stand");
				controller.timeScale = _timeScale;
			}
			//debugDraw = new AWPDebugDraw(_view, physicsWorld); 
			//debugDraw.debugMode = AWPDebugDraw.DBG_DrawCollisionShapes;
 
		}
 
		private function onSceneResourceComplete(event : LoaderEvent) : void
		{
			island=ObjectContainer3D(event.target);
 
			island.y = -180;
			island.scaleX = 2;
			island.scaleY = 2;
			island.scaleZ = 2;
			var mesh:Mesh=Mesh(island.getChildAt(0));
			_view.scene.addChild(island);
 
			mylight = new PointLight();
			_view.scene.addChild(mylight);
			mylight.color = 0xffffff;
			mylight.y = 2000;
			mylight.z = -1000;
 
			var materia:ColorMaterial = new ColorMaterial(0xeeee00);
			materia.lights=[mylight];
			materia.bothSides = true;
			var sceneShape : AWPBvhTriangleMeshShape = new AWPBvhTriangleMeshShape(mesh.geometry);
			var sceneBody : AWPRigidBody = new AWPRigidBody(sceneShape, mesh, 0);
 
			// you can debug the collision event below
			sceneBody.addEventListener(AWPEvent.COLLISION_ADDED, worldCollisionAdded);
			physicsWorld.addRigidBody(sceneBody);
 
			//create rigid bogies
			//loop through and try different tests 
			for(var i:int = 0; i < 1; i++){
 
				/*_sphereShape = new AWPSphereShape(40);
				var sphere : Sphere = new Sphere(materia, 40);
				sphere.position = camera.position;
 
				var body : AWPRigidBody = new AWPRigidBody(_sphereShape, sphere, 2);
				//body.position = camera.position;		
				body.ccdSweptSphereRadius = 0.5;
				body.ccdMotionThreshold = 0.5;
				body.gravity = new Vector3D(0.005, 0.005, 0.005, 0.005);
				sphereArr[i] = body;
				body.position = camera.position;
				body.x = -140; //- 10  + (i*5);
				body.y = 400;
				body.z =  (i * 10 + 80) + 100;
 
 
				_view.scene.addChild(sphere);
				//body.addEventListener(AWPEvent.COLLISION_ADDED, worldCollisionAdded);
				physicsWorld.addRigidBody(sphereArr[i]);*/
 
				var boxShape : AWPBoxShape = new AWPBoxShape(600, 600, 600);
				var cube:Cube = new Cube(materia, 200, 200, 200);
 
				var body:AWPRigidBody = new AWPRigidBody(boxShape, cube, 1);
				body.friction = .9;
				//body.position = new Vector3D(-140, 500, (i * 10 + 80) + 100);
				body.x = -140; //- 10  + (i*5);
				body.y = 800;
				body.z =  (i * 10 + 80) + 100;
				_view.scene.addChild(cube);
				physicsWorld.addRigidBody(body);
 
 
 
				//trace(body.position.x);
				//trace(camera.position.x);
			}
 
 
 
			AssetLibrary.enableParser(MD2Parser);
			AssetLibrary.addEventListener(AssetEvent.ASSET_COMPLETE, onAssetRetrieved);
			AssetLibrary.load(new URLRequest('assets/test/tris.md2'));
 
		}
 
		private function worldCollisionAdded(event : AWPEvent) : void {
			trace(event);
		}
 
		private function keyDownHandler(event:KeyboardEvent):void {
			if (event.keyCode == 38)
			{
				this.forward = true;
			}
			else if (event.keyCode == 40)
			{
				this.backward = true;
			}
			else if (event.keyCode == 39)
			{
				this.turnRight = true;
			}
			else if (event.keyCode == 37)
			{
				this.turnLeft = true;
			}
 
		}
 
		private function keyUpHandler(event:KeyboardEvent):void {
			if (event.keyCode == 38)
			{
				this.forward = false;
			}
			else if (event.keyCode == 40)
			{
				this.backward = false;
			}
			else if (event.keyCode == 39)
			{
				this.turnRight = false;
			}
			else if (event.keyCode == 37)
			{
				this.turnLeft = false;
			}
 
		}
 
		private function incrementWave():void {
			waveCycle += _speed; // angle in radians
			var v:Vector.<Number> = SubGeometry(plane.geometry.subGeometries[0]).vertexData;
			var i:int;
			// manipulate the Z component of each vertex
			for (i = 2; i < v.length; i += 3) {
				// multiply by the sine of the x coordinate, plus offset for animation (normalized to be 0-360 degrees)
				v[i] = Math.sin(((v[i - 2] + waveCycle) / 100) * (threeSixtyRads * 1) )  * 2;
				// add harmonic from Y frequency
				v[i] += Math.sin(((v[i - 1] + waveCycle) / 100) * (threeSixtyRads * 1) )  * 1;
			}
 
			SubGeometry(plane.geometry.subGeometries[0].updateVertexData(v));
 
		}
 
		private function handleEnterFrame(e : Event) : void
		{	
 
 
			incrementWave();
 
			if (this.forward)
				_view.camera.moveForward(MOVESPEED * .05);
			else if (this.backward)
				_view.camera.moveBackward(MOVESPEED * .05);
 
			if (this.turnLeft)
				_view.camera.rotationY -= TURNSPEED * .05;
			else if (this.turnRight)
				_view.camera.rotationY += TURNSPEED * .05;
 
 
				isRenderable = true;
 
 
 
		}		
 
		private function onEnterFrame(ev : Event) : void
		{
			_count += .003;
			_light.x = Math.sin(_count) * 150000;
			_light.y = 1000;
			_light.z = Math.cos(_count) * 150000;
			physicsWorld.step(timeStep);
			try{
			//debugDraw.debugDrawWorld();
			}catch(e:Error){
 
				trace(e);
			}
			_view.render();
		}
	}
}

The triangle mesh you find is perfectly suitable for terrains or other complex geometries.  You can consider this demo an advance stress test.  Lets punish the browser, donkey punch it, make it do extreme calculation at a high frame rate.  If you go to the unity blog documentation you will find their recommendations for polygon count.

Unity’s Recommended Polygon Count:

How many polygons you should use depends on the quality you require and the platform you are targeting. Anything between 300-1500 triangles on Mobile Platforms and 500-6000 triangles on Desktop Platforms is reasonable. If you want lots of characters on screen or want the game to run on old machines, you will have to reduce the polygon count for each character. As an example: Half Life 2 characters used 2500-5000 triangles per character. Next-gen AAA games running on PS3 or Xbox 360 usually have characters with 5000-7000 triangles. – http://unity3d.com/support/documentation/printable.html

FTIC Demos of Stage3D on the iPhone!

The above may sound like quite a few triangles but you will eat these up quickly when developing.  We will pugh the polygon count on purpose. This way we can really see how it performs.  So in short this demo might not be perfect for every browser.  Everything you see here is a demo based off of alpha software and a beta sdk from adobe these are all constantly changing.  So this is bleeding edge not cutting edge technology.   You can setup gravity, collisions, etc..  One thing to keep in mind is that even though this demo is for the web who’s to say you couldn’t modify it a bit and run it on a mobile device with AIR?  AIR supports Stage3D so you could put together a first person shooter or a marble physics game off of this code example if you wanted to spend a little bit of time.  I will also be showing you how to use JigLib and Bullet(AmmoJS) with threeJS soon as well.  The code for this project will show you how to utilize both JigLib and Bullet.  I have also made it easy for people to utilize this code by placing in on github so if you can do whatever you wish with it possibly use it as a template. 

I also want to point out a few case studies.  I would like to focus on a few new 3d multi player roleplaying games for Android.  Case study one is Earth and Legend by dvidearts.  This roleplaying game is a really fun interactive roleplaying game that reminds me of Fable.  If you look into it from a technical standpoint you will notice a few things besides the obvious.  Obvious being great game play for a mobile device, clever use of the android game credit api, engaging quests, great usability combined with joyful gameplay, and beautifully rendered scenes. The not so obvious being the following scene usage, low poly count, clever model creation.  DvideArts break their scenes up so they can have several animated monsters per scene.  The breaks in scenes use loaders.  These loaders provide the user with a subtle picture while they do garbage collection in the background.  Also take note of the camera / user player they have a view that never goes above approximately 180 degrees in the main view port.  This strategy helps them overcome the need for complex geometry and textures above the view port at the same time saving real time rendering time.  Their trees rarely have branches and the terrain ( triangle meshes have a low polycount).  This adds to the game play with a scenes that are crafted well you can provide superior game play with a high frame count.  Your models need to be animated.  You will also have to add physics with a UI that makes sense for the inpatient user.  Personally I like their user controls a virtual joy stick for rotation and ui swipe for scene direction. To do these things properly you will ultimately have to exploit various tool sets and know the devices limitations.  To properly know any limitation you will need to push that limitation to know what various tools are capable of performing.

I have noticed that the triangle mesh for JigLib and Bullet may appear to be a little buggy.  Keep in mind though with a higher polygon count and tighter mesh would probably solve this but to do so also increases scene overhead and memory. The Away3D team has but a considerable amount of effort behind Bullet and it shows.  The bullet library has better support for Away3D and the libraries for threeJS jiglib vs. ammoJS are debatable.   

How could you use this code? Well if you wanted to take it a little further you could.  Create a capsule and make an MD2 model a child.  Capsules are great for collisions even though you have access to triangle mesh you wouldn’t use it for your MD2 model collisions you would want use it sparingly because it is a bit of a resource hog but naturally it would be because of the amount of calculations it makes every second of your application.  I will be demoing similar mobile examples soon.

Stage3D is great for developers!  We can use the tools we know and love to create mobile applications quickly.  To be honest why would developers care if the flash player is supported on mobile devices?  How can we montenize content on the web quickly?  I personally would rather create an application that is downloaded 100,000 times over and charge ¢99 for it.  Adobes new version of AIR is great full 3D many new additions that make it considerably easier to develop apps! It is also worth your time to check out launch pad because it will basically scaffold out many application types and then you can build up form it and learn from the code samples it is extremely helpful!

Two Exceptional Stage3D refrences on Physics:

http://blog.muzerly.com/
http://savagelook.com/blog/

Engines:

http://www.ambiera.com/coppercube/
http://unity3d.com/
http://www.stonetrip.com/
http://www.unrealengine.com/
http://cubeengine.com/

 

Links:

http://www.fitc.ca/ -> its for the cool kids
https://github.com/away3d/away3d-core-fp11
https://github.com/away3d/awayphysics-core-fp11
http://www.jiglibflash.com/blog/
http://blog.muzerly.com/?tag=jiglib 
http://www.youtube.com/watch?v=c1w_5ghL91o&hd=1
http://flashdaily.net/post/13740826093/fully-interactive-adobe-flash-stage3d-content-running-on 
http://labs.adobe.com/technologies/airlaunchpad/
https://www.youtube.com/watch?v=c1w_5ghL91o&feature=player_embedded

Share

Posted in 3d, ActionScript, Adobe, Air, Flash, Game Creation, i-create | Tagged 3D computer graphics, Away3D, Experiment, Flash, MD2, Stage3D | Leave a response

 

Garden of Joy webGL Social Experiment

By Brendon Smith on August 28, 2011

 

What is this? It is a simple webGL social experiment. It allows you to share ideas and passions with your friends on facebook by doing so you help build the garden. You can also view other flowers by using your arrow keys to move with the mouse and clicking on other flowers to view what other people enjoy. This experiment was built with threeJS, toxiclibs, and content from Katalabs. This is an alpha as in I am still testing it. So you should expect improvements. I have also thought of several other ideas along the way. I will continue to roll out social applications, desktop, and mobile. The idea of a virtual garden appealed to me because I love gardens and nature in general. I get 99% of my inspiration from nature so the subject matter makes sense to me. A garden grows because of love, care and tending to it. This idea was one of the more complicated implementations of a social experiment I could think of and that is why I choose it. While I do not have a green thumb this is something I can appreciate due to my upbringing and family. If I had more time I would certainly take it up. I like to be challenged and it is important to never stop challenging your thinking and changing your environment. If you do Java try C#, If you do JavaScript do some ActionScript get it change it up and be fearless. I knew that it would challenge me. Making image sliders, rss feeds, accordions, custom scroll bars, etc is trivial simple basic math.

I was originally thinking about creating a twitter virtual garden but became restless with the idea of doing another search driven twitter app. I have actually invested in several books that are dedicated to data visualization and I would like to create more engaging applications that utilize data in a unique way. Data doesn’t have to be boring! I also wanted to make another social application but also use oAuth to do so. oAuth is really shaping up to be the login choice of the masses which is cool to see it is after all about 7 years in the making. oAuth is now being utilized by Twitter and Facebook. I am also anxious to get my hands on the google+ api (hint hint let me have at it google) although it is not available. So I decided to create another Facebook application. They have considerably improved their api since I last utilized it! It was a pleasant surprise. The garden of joy was built utilizing several javascript libraries:

View the Garden Click Here

  • jQuery
  • threeJS
  • toxiclibs
  • TweenJS

Front End Code to view all of it just go to the application and view source

    // resets the camera after it has been viewing a single flower for a bit
	function newCamera(position){
		camera = new THREE.FirstPersonCameraModified( {
					fov: 60, aspect: window.innerWidth / window.innerHeight, near: 1, far: 100000,
					movementSpeed: 200, lookSpeed: 0.03, noFly: false, lookVertical: false
				} );
			if(position != undefined){
				camera.position = position;
			}
	}
	// initiates the garden after the user has accepted the application
	function init() {
				$('#shareBarContainer').removeClass('display0');
				$('body').removeClass('gardenBackground');
				loader = new THREE.JSONLoader();
				container = document.getElementById( 'container' );
				newCamera();
				camera.position.y = 60;
				curentTarget = camera.target;
				scene = new THREE.Scene();
				toxiToThreeSupport = new toxi.THREE.ToxiclibsSupport(scene);
				threeMesh = undefined; 
				threeLight = new THREE.Light(0xe40d4f);
				scene.addChild(threeLight);
				material = new THREE.MeshNormalMaterial({color: 0xe40d4f, opacity: 0.7, blending: THREE.AdditiveBlending, doubleSided: true });
				material2 = new THREE.MeshBasicMaterial( { color: 0xe40d4f,opacity: 0.5, blending: THREE.AdditiveBlending, doubleSided: true  } );
				// uses a service I built to retun the user data in JSON for the garden
				$.get('http://gardenofjoy.seacloud9.org/index.php/server/garden', function(data){
				gardenData = data;
				console.log(data);
                // Right now I am using SphericalHarmonics from Toxiclibs to build the flower although this will change
					//var m = new Array(0.0,3.0,6.0,0.0,1.0,5.0,5.0,5.0);
				var m = new Array(4.0,2.0,5.0,3.0,1.0,8.0,3.0,3.0);
						//var m = new Array(2.0,1.0,6.0,1.0,2.0,7.0,0.0,7.0);
				var sh = new toxi.SphericalHarmonics(m)
						// Loops through the data and randomly places the flowers in the garden
						SurfaceMeshBuilder = new toxi.SurfaceMeshBuilder(sh);
						for(var i=0; i< data.length; i++){
							var toxiMesh = SurfaceMeshBuilder.createMesh(new toxi.TriangleMesh(),50,1,true);
							threeMesh = toxiToThreeSupport.addMesh(toxiMesh,[material, material2,  new THREE.MeshBasicMaterial( { color: 0x3ff507,opacity: 0.2, blending: THREE.AdditiveBlending } )]);
							threeMesh.scale = new THREE.Vector3(5,5,5);
							threeMesh.name = i;
							threeMesh.doubleSided = true;
							threeMesh.position.x = i*Math.floor(Math.random()*400);
							threeMesh.position.z = i*Math.floor(Math.random()*400);
							threeMesh.position.y = 20;
							threeMesh.doubleSided = true;
							objects.push(threeMesh);
							scene.addObject(threeMesh);
						}
					});
				// Loads the Katalabs model for the garden
				loader.load( { model: "/models/garden.js", callback: function(geo){ 
					try{
						var mesh2 =  new THREE.Mesh( geo, new THREE.MeshFaceMaterial({blending: THREE.AdditiveBlending, doubleSided: true, transparent:true, depthTest: false}) );
						mesh2.scale.x = mesh2.scale.y = mesh2.scale.z = 800;
						mesh2.doubleSided = true;
						scene.addObject(mesh2);
						var mc = THREE.CollisionUtils.MeshColliderWBox(mesh2);
						THREE.Collisions.colliders.push( mc );
					}catch(e){
						console.log('fail' + e);
					}
				 } });
 
				renderer = new THREE.WebGLRenderer( { antialias: false } );
				renderer.setSize( window.innerWidth, window.innerHeight );
				container.innerHTML = "";
				container.appendChild( renderer.domElement );
				stats = new Stats();
				stats.domElement.style.position = 'absolute';
				stats.domElement.style.top = '0px';
				container.appendChild( stats.domElement );
                // looks at the url to determine if it is a user story if so it loads that flower in the garden first
					if(window.location.hash != undefined && window.location.hash != ''){
						var hashStr = window.location.hash;
						hashStr = hashStr.replace(/#/i, '');
						console.log(hashStr);
						var getUrl = 'http://gardenofjoy.seacloud9.org/index.php/build_the_garden/thingsilove_getUser/' + hashStr;
						$.get(getUrl, function (data) {
						  $('.formContent').html('');
						  $('.formContent').html(data);
						  $('#whatYoulove').css({
							  position: 'absolute',
							  right: '20px',
							  top: '10px'
						  });
						  $('#whatYoulove').css('z-index', '20');
						  $('#whatYoulove').fadeIn('fast');
						  var m = new Array(4.0, 2.0, 5.0, 3.0, 1.0, 8.0, 3.0, 3.0);
						  var sh = new toxi.SphericalHarmonics(m)
						  SurfaceMeshBuilder = new toxi.SurfaceMeshBuilder(sh);
						  var toxiMesh = SurfaceMeshBuilder.createMesh(new toxi.TriangleMesh(), 50, 1, true);
						  threeMesh = toxiToThreeSupport.addMesh(toxiMesh, [material, material2, new THREE.MeshBasicMaterial({
							  color: 0x3ff507,
							  opacity: 0.2,
							  blending: THREE.AdditiveBlending
						  })]);
						  threeMesh.scale = new THREE.Vector3(5, 5, 5);
						  threeMesh.name = data.length + 1;
						  threeMesh.doubleSided = true;
						  threeMesh.position.x = Math.floor(Math.random() * 400);
						  threeMesh.position.z = Math.floor(Math.random() * 400);
						  threeMesh.position.y = 20;
						  var currentPosition = threeMesh.position;
						  threeMesh.doubleSided = true;
						  objects.push(threeMesh);
						  scene.addObject(threeMesh);
						  cameraTween = new TWEEN.Tween(camera.position).to({
							  x: currentPosition.x - 300,
							  y: currentPosition.y,
							  z: currentPosition.z
						  }, 2000).easing(TWEEN.Easing.Elastic.EaseOut).start();
						  cameraTween.onComplete(function () {
							  camera.target.position = currentPosition;
							  setTimeout('removeTarget()', 5000);
						  });
					  });
					}
 
 
			}
		// prevents normal mouse down event and allows for the user to click and zoom in on a flower
		function onDocumentMouseDown( event ) {
				event.preventDefault();
				var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
				projector.unprojectVector( vector, camera );
				var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
				var intersects = ray.intersectObjects( objects );
 
				if ( intersects.length > 0 ) {
					console.log(intersects[ 0 ].object.position.x);
                    // tween camera with the intersecting object
					cameraTween = new TWEEN.Tween( camera.position ).to( {
						x: intersects[ 0 ].object.position.x - 300,
						y: intersects[ 0 ].object.position.y,
						z: intersects[ 0 ].object.position.z}, 2000 )
					.easing( TWEEN.Easing.Elastic.EaseOut).start();
					obj = intersects[ 0 ].object;
					cameraTween.onComplete(function(){
					try{
					camera.target.position = obj.position;
					flID = parseInt(obj.name);
					console.log(flID);
					var userData = '<div class="floatLeft"><img src="https://graph.facebook.com/'+ gardenData[flID].userid +'/picture"></div><div class="floatLeft">'+ gardenData[flID].username +'</div><div class="floatLeft">'+ gardenData[flID].love +'</div>';
					}catch(e){
						console.log(e);
					}
					$('.formContent').html('');
					$('.formContent').html(userData);
					$('#whatYoulove').css({position:'absolute', right:'20px', top:'10px'});
					$('#whatYoulove').css('z-index','20');
					$('#whatYoulove').fadeIn('fast');
                    // removes the target and resets the camera
					setTimeout('removeTarget()', 5000);
					});
 
				}
		}
 
 
			function removeTarget(){
			newCamera(camera.position);
			}
 
			function animate() {
				requestAnimationFrame( animate );
				render();
				stats.update();
 
			}
 
			function render() {
				TWEEN.update();
				var time = new Date().getTime() * 0.01;				
				renderer.render(scene, camera);
			}

It was a tremendous amount of fun to play around with all of the libraries they all work well together and I am anxious to use them in future projects. TweenJS makes it ridiculously easy to create your animated motion tweens even in 3-D. The tweens give the applications that organic fluid feel. Tweens are great for creating motion. If you have used tweener for actionscript then you know exactly what I am talking about. The tweener engine utilizes many of Robert Penner’s easing equations. Robert Penner wrote a wonderful book about animation “Programming Flash MX” it came out several years ago but still holds its weight in gold. This book is a gem it is useful for anyone wanting to do animation efficiently I highly recommend this book and it has helped me out for a number of years now. ThreeJS is always a pleasure to use when creating 3d environments it is easy to import models into it. I also wanted to use toxiclibs because I have only played with it a little within Processing. Now that it is JavaScript I believe this opens up a number of opportunities it works concurrently with other libraries with ease. I created the original flowers that populate the virtual garden with toxiclibs. I would also like to devote a little time to create an algorithm that uses an L-System possibly in combination with a golden spiral to build random flowers. I have been using processing more and more and it is easy to utilize and algorithm in it and then export the model out as an obj file. Processing is a really handy tool it allows you to easily export content out in a number of useful formats. This of course is a daunting task and I don’t know if I can devote that much time to it. I can make static models but 3d models are time consuming to make especially with open source products.

To create the models I utilized blender specifically the Katalabs OurBricks model library. They have developed a plugin for Blender that makes importing a model a one click operation. It was a cinch. I was able to easily import the models into Blender and then prepare them for threeJS. The Blender user interface has been improved greatly and is finally proving itself as a really useful tool todo 3d modeling. I actually donated money to the Blender foundation about 10 years ago to open source it. It is really great to see it making so many advancements. I have had to recently invest more money in books on Blender to keep up with all the new cool features it offers. It has always been a really cool tool although the learning curve has traditionally been exceptionally brutal it appears to be lowering and becoming the default choice for open source 3d development. I would like become more efficient when creating 3d models. I believe using blender3d will make using every other tool easy. I just need to become more clever in the way I go about creating content.

I really enjoy doing webGL work and I can’t wait until it is fully embraced by mobile which I believe will happen in the not so distant future. Web design and development have been sped up it is funny to me hearing people talk about html5 taking 10 years to implement. These predictions were most likely made when browsers were being released on a two year release cycle that is clearly not the case anymore. Google has set the bar with 3 month release cycles and is forcing all other browsers to play catch up.

Firefox has also sped up considerably and has many features that are not found in their competition right now like the web audio api! Firefox wants to enable web developers to compete with native applications head on. With technologies like webGL and the Audio api this goal is not a far stretch. I have also noticed that the overall quality of Firefox and webGL is really high I have noticed recently that it handles transparency better than Chrome currently does. It will also be interesting to see what Microsofts Windows 8 has to offer developers.

Take some time and vote for webGL to be demoed at SXSX!

Share

Posted in art, i-create, JavaScript | Tagged Blender, Experiment, JavaScript, JQuery, threeJS, toxiclibs, webGL | 4 Responses

 

BSP Map in threeJS and AWAY3D using Molehill and webGL

By Brendon Smith on June 12, 2011

 

First off I know have the JSONObj parser working with shaders and materials so! So in short it has I believe all of the current functionality of the OBJParser that is currently in the AWAY3D broomstick build it may even have more functionality. I have been testing it rigorously and will need to re-factor some of the code. I did a get recently on their broomstick build so it should work with their current implementation of Broomstick (It’s a moving target). This example shows sharing a model between threeJS and Away3d. I chose to grab a BSP map and convert it to and OBJ and then exported it with the threeJS python converter.

This demo requires FlashPlayer 11 stage3D


Flash Version requiring stage3D plugin
View Flash Source
webGL Version
Download all the Code

So what is a BSP map? In short it is a Quake map. You can find sites dedicated to Moders who have made their own maps for games. You can not use these maps for commercial use although these maps are not actually that difficult to produce. Maps on these sites that are downloaded download as a .pk3 this file is really a zip file. So all you have to do is change the extension to a zip file and unzip the file. You will find a map directory and in that directory you will find a .BSP file. This file is the map. You will need to convert this .BSP map to a more useful format like an OBJ file. This way you will be able to edit the file and convert it. Many of the textures in the texture directory will be TARGA(.tga) files you will have to convert these to files that can be used on the internet or within flash. You will want to make files that have black space on them transparent PNG files and the rest of the files can be .JPG. You will also want to be careful to make sure that all files dimensions are a Power of 2. Sometimes the images are not a power of 2 and this will break your Flash and JavaScript renderings of the model so look at each file and make sure it is a Power of 2. In Away3d I have disabled mipmapping by default. I have done this to make it easier on you but you may have situations where you will want to enable mipmapping. With the JSONObj format I have also enabled all textures by default to be dual sided you can also of course change this if you want.

The JSONObj appears to be working pretty good now! Here are some things you will want to be aware of if you embed the model you will also have to embed its textures. I believe this is the way the current OBJ parser is working as well. I could be wrong although I don’t believe I am. The easiest way to use it is place the model and its textures in the asset folder and make sure your model is referencing the materials in the correct location. Make sure all textures are a power of 2 and it should work! The example that I have provided is running at about 60fps for me and the webGL version is at about 30fps. I am still looking into ways to improve the webGL version because I know I can get the frame rate even higher! I have tested this version of the JSONObj parser with rather large models. Some of these models were over 26+ mb and they all work so I am confident you will have a minimum amount of problems.

Latest Version of the JSONObj parser for Away3D

package away3d.loaders.parsers
{
	import away3d.arcane;
	import away3d.core.base.Geometry;
	import away3d.core.base.SubGeometry;
	import away3d.core.base.data.UV;
	import away3d.core.base.data.Vertex;
	import away3d.entities.Mesh;
	import away3d.library.assets.BitmapDataAsset;
	import away3d.loaders.misc.ResourceDependency;
	import away3d.materials.BitmapMaterial;
	import away3d.materials.ColorMaterial;
	import away3d.materials.methods.BasicSpecularMethod;
 
	import flash.display.BitmapData;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
 
	use namespace arcane;
 
	/**
	 * JSONOBJParser provides a parser for the JSONOBJ data type.
	 */
	public class JSONOBJParser extends ParserBase
	{
		private var _startedParsing : Boolean;
		private const LIMIT:uint = 64998;
		private var _currentObject : ObjectGroup;
		private var _currentGroup : Group;
		private var _currentMaterialGroup : MaterialGroup;
		private var _objects : Vector.<ObjectGroup>;
		private var _materialIDs : Vector.<String>;
		private var _materialLoaded : Vector.<LoadedMaterial>;
		private var _materialSpecularData : Vector.<SpecularData>;
		private var _meshes : Vector.<Mesh>;
		private var _lastMtlID:String;
		private var _objectIndex : uint;
		private var _realIndices : Array;
		private var _vertexIndex : uint;
		private var jsonOBJ:Object;
		private var _mtlLib : Boolean;
		private var _mtlLibLoaded : Boolean = true;
		private var isQuad: Boolean = false;
		private var _idCount : uint;
		private var _activeMaterialID:String = "";
 
		private var _vertices : Vector.<Vertex>;
		private var _vertexNormals : Vector.<Vertex>;
		private var _uvs : Vector.<UV>;
 
		private var _scale : Number;
 
		/**
		 * Creates a new JSONOBJParser object.
		 * @param uri The url or id of the data or file to be parsed.
		 * @param extra The holder for extra contextual data that the parser might need.
		 */
		public function JSONOBJParser(scale:Number = 1)
		{
			super(ParserDataFormat.PLAIN_TEXT);
			_scale = scale;
		}
 
		/**
		 * Scaling factor applied directly to vertices data
		 * @param value The scaling factor.
		 */
		public function set scale(value:Number):void
		{
			_scale = value;
		}
 
		/**
		 * Indicates whether or not a given file extension is supported by the parser.
		 * @param extension The file extension of a potential file to be parsed.
		 * @return Whether or not the given file type is supported.
		 */
		public static function supportsType(extension : String) : Boolean
		{
			extension = extension.toLowerCase();
			return extension == "js";
		}
 
		/**
		 * Tests whether a data block can be parsed by the parser.
		 * @param data The data block to potentially be parsed.
		 * @return Whether or not the given data is supported.
		 */
		public static function supportsData(data : *) : Boolean
		{
			var content : String = String(data);
			var hasModel : Boolean = content.indexOf("model") != -1;
			return hasModel;
		}
 
 
		/**
		 * @inheritDoc
		 */
		override arcane function resolveDependency(resourceDependency:ResourceDependency):void
		{
			if (resourceDependency.assets.length != 1)
				return;
 
			var asset:BitmapDataAsset = resourceDependency.assets[0] as BitmapDataAsset;
 
			if (asset){
				var lm:LoadedMaterial = new LoadedMaterial();
				lm.materialID = resourceDependency.id;
				lm.bitmapData = isBitmapDataValid(asset.bitmapData)? asset.bitmapData : defaultBitmapData ;
				_materialLoaded.push(lm);
 
				if(_meshes.length>0)
					applyMaterial(lm);
			}
		}
 
		/**
		* @inheritDoc
		*/
		override arcane function resolveDependencyFailure(resourceDependency:ResourceDependency):void
		{
			var lm:LoadedMaterial = new LoadedMaterial();
			lm.materialID = resourceDependency.id;
			lm.bitmapData = defaultBitmapData;
 
			_materialLoaded.push(lm);
		}
 
		public function trimJSON( s:String ):String
		{
			var trim:RegExp = /var model = /gi;
			s = s.replace(trim, '{"model":');
			trim = /var model=/gi;
			s = s.replace(trim, '{"model":');
			trim = /postMessage\( model \);/gi;
			s = s.replace(trim, '');
			trim = /postMessage\(model\);/gi;
			s = s.replace(trim, '');
			trim = /close\(\);/gi;
			s = s.replace(trim, '');
			trim = /};/gi;
			s = s.replace(trim, '}}');
			return s.replace( /^([\s|\t|\n]+)?(.*)([\s|\t|\n]+)?$/gm, "$2" );
		}
 
 
		/**
		* @inheritDoc
		*/
		override protected function proceedParsing() : Boolean
		{
			var line:String;
			var creturn:String = String.fromCharCode(10);
			var trunk:Array;
 
			if(_textData.indexOf(creturn) == -1)
				creturn = String.fromCharCode(13);
 
			if(!_startedParsing){
				_startedParsing = true;
				_vertices = new Vector.<Vertex>();
				_vertexNormals = new Vector.<Vertex>();
				_materialIDs = new Vector.<String>();
				_materialLoaded = new Vector.<LoadedMaterial>();
				_meshes = new Vector.<Mesh>();
				_uvs = new Vector.<UV>();
				jsonOBJ = JSON.parse(trimJSON(_textData));
				_objects = new Vector.<ObjectGroup>();
				_objectIndex = 0;
			}
 
			createObject();
			createGroup();
			parseFace();
			parseVertex();
			parseUV() ;
			parseVertexNormal();
			translate();
			parseMaterial();
			return PARSING_DONE;
		}
 
 
		/**
		* Converts the parsed data into an Away3D scenegraph structure
		*/
		private function translate() :void
		{
			var groups : Vector.<Group> = _objects[_objectIndex].groups;
			var numGroups : uint = groups.length;
			var materialGroups : Vector.<MaterialGroup>;
			var numMaterialGroups : uint;
			var geometry : Geometry;
			var mesh : Mesh;
			var meshid:uint;
 
			var m : uint;
			var sm : uint;
			var bmMaterial:BitmapMaterial;
 
			for (var g : uint = 0; g < numGroups; ++g) {
				geometry = new Geometry();
				materialGroups = groups[g].materialGroups;
				numMaterialGroups = materialGroups.length;
				translateMaterialGroup(materialGroups[g], geometry);
				bmMaterial = new BitmapMaterial(defaultBitmapData);
				mesh = new Mesh(bmMaterial, geometry);
				meshid = _meshes.length;
				mesh.name = "jsonObj"+meshid;
				_meshes[meshid] = mesh;
 
				if(groups[g].materialID != ""){
					bmMaterial.name = groups[g].materialID+"~"+mesh.name;
				} else {
					bmMaterial.name = _lastMtlID+"~"+mesh.name;
				}
 
				if(mesh.subMeshes.length >1){
					for (sm = 1; sm<mesh.subMeshes.length; ++sm)
						mesh.subMeshes[sm].material = bmMaterial;
				}
 
				finalizeAsset(mesh);
			}
		}
 
		/* If no uv's are found (often seen case with obj format) parser generates a new set of default uv's */
		private function addDefaultUVs(vertices : Vector.<Number>, uvs: Vector.<Number>) :Vector.<Number>
		{
			var j:uint = 0;
			for (var i :uint = 0; i<vertices.length; i+=3){
				if(j == 0){
					uvs.push(0, 1);
				} else if(j == 1){
					uvs.push(.5, 0);
				} else{
					uvs.push(1, 1);
				}
 
				j = (j+1>2)? 0 : j++;
			}
 
			return uvs;
		}
 
		/**
		 * Translates an obj's material group to a subgeometry.
		 * @param materialGroup The material group data to convert.
		 * @param geometry The Geometry to contain the converted SubGeometry.
		 */
		private function translateMaterialGroup(materialGroup : MaterialGroup, geometry : Geometry) : void
		{
			var faces : Vector.<FaceData> = materialGroup.faces;
			var face : FaceData;
			var numFaces : uint = faces.length;
			var numVerts : uint;
 
			var vertices:Vector.<Number> = new Vector.<Number>();
			var uvs:Vector.<Number> = new Vector.<Number>();
			var normals:Vector.<Number> = new Vector.<Number>();
			var indices:Vector.<uint> = new Vector.<uint>();
 
			_realIndices = [];
			_vertexIndex = 0;
 
			var j:uint;
			try{
			for (var i : uint = 0; i < numFaces; ++i) {
				face = faces[i];
				numVerts = face.indexIds.length - 1;
				for (j = 1; j < numVerts; ++j) {
					translateVertexData(face, j, vertices, uvs, indices, normals);
					translateVertexData(face, 0, vertices, uvs, indices, normals);
					translateVertexData(face, j+1, vertices, uvs, indices, normals);
				}
			}
			}catch(e:Error){
				trace(e);
 
			}
 
			var vlength:uint = vertices.length;
 
			if(vlength > 0){
 
				if(vlength <= LIMIT){
 
					buildSubGeometry(geometry, vertices, uvs, indices, normals);
 
				} else {
 
					var nvertices:Vector.<Number> = new Vector.<Number>();
					var nuvs:Vector.<Number> = new Vector.<Number>();
					var nnormals:Vector.<Number> = new Vector.<Number>();
					var nindices:Vector.<uint> = new Vector.<uint>();
 
					var ind:uint;
					var vind:uint;
					var uvind:uint;
 
					vlength = 0;
 
					for (i = 0; i < indices.length; ++i) {
 
						if(vlength+3 > LIMIT){
							vlength = 0;
							buildSubGeometry(geometry, nvertices, nuvs, nindices, nnormals);
							nvertices = new Vector.<Number>();
							nuvs = new Vector.<Number>();
							nnormals = new Vector.<Number>();
							nindices = new Vector.<uint>();
						}
 
						ind = indices[i];
						vind = ind*3;
						uvind = ind*2;
						nindices.push(nvertices.length/3);
						nvertices.push(vertices[vind], vertices[vind+1], vertices[vind+2]);
						try{
						nuvs.push(uvs[uvind], uvs[uvind+1]);
						}catch(e:Error){
							trace('no uv data');
						}
						if(normals[vind]) nnormals.push(normals[vind], normals[vind+1], normals[vind+2]);
 
						vlength+=3;
 
 
					}
 
					buildSubGeometry(geometry, nvertices, nuvs, nindices, nnormals);
 
				}
			}
		}
 
		private function buildSubGeometry(geometry:Geometry, vertices:Vector.<Number>, uvs:Vector.<Number>, indices:Vector.<uint>, normals:Vector.<Number>):void
		{
			if(vertices.length == 0) return;
 
			var subGeom : SubGeometry = new SubGeometry();
			subGeom.autoDeriveVertexTangents = true;
 
			if(uvs.length == 0 && vertices.length > 0)
				uvs = addDefaultUVs(vertices, uvs);
 
			subGeom.updateVertexData(vertices);
			subGeom.updateIndexData(indices);
			subGeom.updateUVData(uvs);
 
			var deriveVN:Boolean = normals.length>0? true :false;
			subGeom.autoDeriveVertexNormals = deriveVN;
 
			if(deriveVN) subGeom.updateVertexNormalData(normals);
 
			geometry.addSubGeometry(subGeom);
		}
 
		private function translateVertexData(face : FaceData, vertexIndex : int, vertices:Vector.<Number>, uvs:Vector.<Number>, indices:Vector.<uint>, normals:Vector.<Number>) : void
		{
			var index : uint;
			var vertex : Vertex;
			var vertexNormal : Vertex;
			var uv : UV;
 
			if (!_realIndices[face.indexIds[vertexIndex]]) {
				index = _vertexIndex;
				_realIndices[face.indexIds[vertexIndex]] = ++_vertexIndex;
				vertex = _vertices[face.vertexIndices[vertexIndex]-1];
				vertices.push(vertex.x * _scale, vertex.y * _scale, vertex.z * _scale);
				if (face.normalIndices.length > 0) {
					try{
					vertexNormal = _vertexNormals[face.normalIndices[vertexIndex]-1];
					normals.push(vertexNormal.x, vertexNormal.y, vertexNormal.z);
					}catch(e:Error){
						trace(e);
					}
				}
 
				if (face.uvIndices.length > 0 ){
 
					try {
						uv = _uvs[face.uvIndices[vertexIndex]-1];
						uvs.push(uv.u, uv.v);
 
					} catch(e:Error) {
 
						switch(vertexIndex){
							case 0:
								uvs.push(0, 1);
								break;
							case 1:
								uvs.push(.5, 0);
								break;
							case 2:
								uvs.push(1, 1);
						}
					}
 
				}
 
			} else {
				index = _realIndices[face.indexIds[vertexIndex]] - 1;
			}
			indices.push(index);
		}
 
 
		/**
		 * Creates a new object group.
		 * @param trunk The data block containing the object tag and its parameters
		 */
		private function createObject() : void
		{
			_currentGroup = null;
			_currentMaterialGroup = null;
			_objects.push(_currentObject = new ObjectGroup());
			_currentObject.name = 'jsonOBJ';
			//if (trunk) _currentObject.name = trunk[1];
		}
 
		/**
		 * Creates a new group.
		 * @param trunk The data block containing the group tag and its parameters
		 */
		private function createGroup() : void
		{
			_currentGroup = new Group();
			for(var i:uint=0; i<jsonOBJ.model.materials.length; i++){
 
			_currentGroup.materialID = 'g' + i;
			_currentGroup.name = 'g' + i;
			_currentObject.groups.push(_currentGroup);
			createMaterialGroup(null);
			}
		}
 
		/**
		 * Creates a new material group.
		 * @param trunk The data block containing the material tag and its parameters
		 */
		private function createMaterialGroup(trunk : Array) : void
		{
			_currentMaterialGroup = new MaterialGroup();
			//if (trunk) _currentMaterialGroup.url = trunk[1];
			_currentGroup.materialGroups.push(_currentMaterialGroup);
		}
 
		/**
		 * Reads the next vertex coordinates.
		 * @param trunk The data block containing the vertex tag and its parameters
		 */
		private function parseVertex() : void
		{
			var currentVert:uint =0;
			for(var i:uint = 0; i < jsonOBJ.model.vertices.length; i++){
			 if(currentVert == i){
				 _vertices.push(new Vertex(parseFloat(jsonOBJ.model.vertices[i]), parseFloat(jsonOBJ.model.vertices[i+1]), -parseFloat(jsonOBJ.model.vertices[i+2])));
				 currentVert = i+3;
			 }
			}
		}
 
		/**
		 * Reads the next uv coordinates.
		 * @param trunk The data block containing the uv tag and its parameters
		 */
		private function parseUV() : void
		{
			var currentVert:uint =0;
 
			for(var i:uint = 0; i < jsonOBJ.model.uvs[0].length; i++){
				if(currentVert == i){
					_uvs.push(new UV(parseFloat(jsonOBJ.model.uvs[0][i]),parseFloat(jsonOBJ.model.uvs[0][i+1])));
					currentVert = i+2;
				}
			}
		}
 
		/**
		 * Reads the next vertex normal coordinates.
		 * @param trunk The data block containing the vertex normal tag and its parameters
		 */
		private function parseVertexNormal() : void
		{
			var currentVert:uint =0;
			for(var i:uint = 0; i < jsonOBJ.model.normals.length; i++){
				if(currentVert == i){
					_vertexNormals.push(new Vertex(parseFloat(jsonOBJ.model.normals[i]), parseFloat(jsonOBJ.model.normals[i+1]), -parseFloat(jsonOBJ.model.normals[i+2])));
					currentVert = i+3;
				}
			}
		}
 
		/**
		 * Reads the next face's indices.
		 * @param trunk The data block containing the face tag and its parameters
		 */
		private function parseFace() : void
		{	
			var currentVert:uint =1;
			for(var i:uint = 0; i < jsonOBJ.model.faces.length; i++){
				var id:Number = jsonOBJ.model.faces[0];
				var face : FaceData = new FaceData();
				var num1:int;
				var num2:int;
				var num3:int;
 
				var num4:int;
				var num5:int;
				var num6:int;
 
				var num7:int;
				var num8:int;
				var num9:int;
 
				var grp:int;
				if(id === jsonOBJ.model.faces[11]){
				if(currentVert == i){
					num1 = parseInt(jsonOBJ.model.faces[i])+1;
					num2 = parseInt(jsonOBJ.model.faces[i+1])+1;
					num3 = parseInt(jsonOBJ.model.faces[i+2])+1;
 
					num4 = parseInt(jsonOBJ.model.faces[i+4])+1;
					num5 = parseInt(jsonOBJ.model.faces[i+5])+1;
					num6 = parseInt(jsonOBJ.model.faces[i+6])+1;
 
					num7 = parseInt(jsonOBJ.model.faces[i+7])+1;
					num8 = parseInt(jsonOBJ.model.faces[i+8])+1;
					num9 = parseInt(jsonOBJ.model.faces[i+9])+1;
 
					face.vertexIndices.push(num1, num2,num3);
					face.uvIndices.push(num4,num5,num6)
					face.normalIndices.push(num7, num8, num9)
					face.indexIds.push(num1+'/'+num4+'/'+num7,num2+'/'+num5+'/'+num8,num3+'/'+num6+'/'+num9);
					grp = parseInt(jsonOBJ.model.faces[i+3]);
					_currentGroup.materialGroups[grp].faces.push(face);
					//_currentMaterialGroup.faces.push(face);
					currentVert = i+11;
				}
				}else{
					isQuad = true;
					// converts Quad data to triangles //
					if(currentVert == i){
					num1 = parseInt(jsonOBJ.model.faces[i])+1;
					num2 = parseInt(jsonOBJ.model.faces[i+1])+1;
					num3 = parseInt(jsonOBJ.model.faces[i+2])+1;
					num4 = parseInt(jsonOBJ.model.faces[i+3])+1;
 
					num5 = parseInt(jsonOBJ.model.faces[i+5])+1;
					num6 = parseInt(jsonOBJ.model.faces[i+6])+1;
					num7 = parseInt(jsonOBJ.model.faces[i+7])+1;
					num8 = parseInt(jsonOBJ.model.faces[i+8])+1;
 
					face.vertexIndices.push(num1, num2,num3);
					face.vertexIndices.push(num1, num3,num4);
					face.normalIndices.push(num5, num6, num7);
					face.normalIndices.push(num5, num7, num8);
 
					face.indexIds.push(num1+'/'+'/'+num5,num2+'/'+'/'+num6,num3+'/'+'/'+num7);
					face.indexIds.push(num1+'/'+'/'+num5,num3+'/'+'/'+num7,num4+'/'+'/'+num8);
 
					grp = parseInt(jsonOBJ.model.faces[i+4]);
 
					_currentGroup.materialGroups[grp].faces.push(face);
					//_currentMaterialGroup.faces.push(face);
					currentVert = i+10;
					}
				}
			}
 
		}
 
		private function parseMaterial():void{
			//var materialDefinitions:Array = data.split('newmtl');
			var lines:Array;
			var trunk:Array;
			var j:uint;
			var mMaterial:BitmapMaterial;
 
			var basicSpecularMethod:BasicSpecularMethod;
			var useSpecular:Boolean;
			var useColor:Boolean;
			var diffuseColor:uint;
			var ambientColor:uint;
			var specularColor:uint;
			var specular:Number;
			var alpha:Number;
			var mapkd:String;
 
			diffuseColor = ambientColor = specularColor = 0xFFFFFF;
			specular = 0;
			useSpecular = false;
			useColor = false;
			alpha = 1;
			mapkd = "";
			var matArray:Array = new Array();
			var matBitArray:Array = new Array();
			var matClArray:Array = new Array();
 
			for(var i:uint = 0; i < jsonOBJ.model.materials.length; i++){
				var lm:LoadedMaterial = new LoadedMaterial();
				var s:String = jsonOBJ.model.materials[i].DbgName;
 
				if(s.charAt(0) != "#"){
 
					try{
					ambientColor =	jsonOBJ.model.materials[i].colorAmbient[0]*255 << 16 | jsonOBJ.model.materials[i].colorAmbient[1]*255 << 8 || jsonOBJ.model.materials[i].colorAmbient[2]*255 ; 
					lm.ambientColor = ambientColor;
					}catch(e:Error){
						trace(e);
					}
 
					try{
					specularColor = jsonOBJ.model.materials[i].colorSpecular[0]*255 << 16 | jsonOBJ.model.materials[i].colorSpecular[1]*255 << 8 || jsonOBJ.model.materials[i].colorSpecular[2]*255 ;
					basicSpecularMethod = new BasicSpecularMethod();
					basicSpecularMethod.specularColor = specularColor;
					basicSpecularMethod.specular = specular;
					lm.specularMethod = basicSpecularMethod;
					}catch(e:Error){
						trace(e);
					}
 
					try{
					diffuseColor = jsonOBJ.model.materials[i].colorDiffuse[0]*255 << 16 | jsonOBJ.model.materials[i].colorDiffuse[1]*255 << 8 || jsonOBJ.model.materials[i].colorDiffuse[2]*255 ;
					lm.bitmapData = new BitmapData(256, 256, false, diffuseColor);
					}catch(e:Error){
						trace(e);
					}
					matArray.push(lm);
					addDependency('mat'+i, new URLRequest(jsonOBJ.model.materials[i].mapDiffuse))
 
				}else{
					var clMat:ColorMaterial;
					var sLen:int = s.length + 1;
					s = s.slice(1, sLen);
					s = '0x'+s;
					var clr:int = int(s);
					lm.bitmapData = new BitmapData(256, 256, false, clr);
					clMat = new ColorMaterial(clr);
					clMat.bothSides = true;
					matArray.push(lm);
					matClArray.push(clMat);
				}
			}
 
			var mesh:Mesh;
			var mat:BitmapMaterial;
			for(var f:uint = 0; f <_meshes.length;++f){
				mesh = _meshes[f];
				mesh.material.name = 'mat'+f;
				if(matClArray.length <= 0){
					mat = BitmapMaterial(mesh.material);
					try{
						mat.bitmapData = matArray[f].bitmapData;
						mat.ambientColor = matArray[f].ambientColor;
						mat = matBitArray[f];
					}catch(e:Error){
						trace(e);
					}
				}else{
					mesh.material = matClArray[f];
				}
			}
		}
 
		private function applyMaterial(lm:LoadedMaterial) : void
		{
			var meshID:String;
			var decomposeID:Array;
			var mesh:Mesh;
			var mat:BitmapMaterial;
			var j:uint;
			var specularData:SpecularData;
 
			for(var i:uint = 0; i <_meshes.length;++i){
				mesh = _meshes[i];
				decomposeID = mesh.material.name.split("~");
 
				if(decomposeID[0] == lm.materialID){
					mesh.material.name = decomposeID[1];
					mat = BitmapMaterial(mesh.material);
					mat.bitmapData = lm.bitmapData;
					mat.ambientColor = lm.ambientColor;
					mat.mipmap = false;
					mat.bothSides = true;
					if(lm.specularMethod){
						mat.specularMethod = lm.specularMethod;
					} else if(_materialSpecularData){
						for(j = 0;j<_materialSpecularData.length;++j){
							specularData = _materialSpecularData[j];
							if(specularData.materialID == lm.materialID){
								mat.specularMethod = specularData.basicSpecularMethod;
								mat.ambientColor = specularData.ambientColor;
								_materialSpecularData.splice(j,1);
								break;
							}
						}
					}
					_meshes.splice(i, 1);
					--i;
				}
			}
		}
 
		private function applyMaterials() : void
		{
			if(_materialLoaded.length == 0)
				return;
 
			for(var i:uint = 0; i <_materialLoaded.length;++i)
				applyMaterial(_materialLoaded[i]);
		}		
 
 
	}
}
 
// value objects:
class ObjectGroup
{
	public var name : String;
	public var groups : Vector.<Group> = new Vector.<Group>();
}
 
class Group
{
	public var name : String;
	public var materialID : String;
	public var materialGroups : Vector.<MaterialGroup> = new Vector.<MaterialGroup>();
}
 
class MaterialGroup
{
	public var url : String;
	public var faces : Vector.<FaceData> = new Vector.<FaceData>();
}
 
class SpecularData
{
	import away3d.materials.methods.BasicSpecularMethod;
 
	public var materialID : String;
	public var basicSpecularMethod : BasicSpecularMethod;
	public var ambientColor:uint = 0xFFFFFF;
}
 
class LoadedMaterial
{
	import flash.display.BitmapData;
	import away3d.materials.methods.BasicSpecularMethod;
 
	public var materialID:String;
	public var bitmapData:BitmapData;
 
	public var specularMethod:BasicSpecularMethod;
	public var ambientColor:uint = 0xFFFFFF;
}
 
class FaceData
{
	public var vertexIndices : Vector.<uint> = new Vector.<uint>();
	public var uvIndices : Vector.<uint> = new Vector.<uint>();
	public var normalIndices : Vector.<uint> = new Vector.<uint>();
	public var indexIds : Vector.<String> = new Vector.<String>();	// used for real index lookups
}

This demo requires FlashPlayer 11 stage3D


Flash Version requiring stage3D plugin
View Flash Source
webGL Version
Download all the Code

ToDo list:

  • Support Materials
  • Support Multiple Shader Groups
  • Support Binary Data
  • Morphing Targets
  • Better Error Handeling

This was a fun experiment for me and the next time I dive into this project I will be using jigLib to add some physics! I hope you have found this useful and helpful. Hopefully I can re-factor the code a bit and get it accepted within the main branch of Away3D broomstick! Last time I also mis-spoke the .AWD format is a text format not a binary format although I believe the next version will be a binary format.

BSP Converters:
http://www.ambiera.com/copperlicht/index.html
http://fwheel.net/gk3/gk3mod2obj/bsp2obj.html
http://nemesis.thewavelength.net/index.php?p=45
http://blenderartists.org/forum/showthread.php?41306-generic-questions-about-mesh-creation

Share

Posted in 3d, ActionScript, Flex, i-create, JavaScript | Tagged 3D computer graphics, ActionScript, as3, Away3D, BSP, Computer graphics, Experiment, JavaScript, Obj, PK3, Quake, threeJS | 1 Response

Next »
 
 

3d ActionScript Adobe Air Android Apollo Apple art as3 Away3D C# CakePHP CSS Experiment Flash Flash Develop Flex Games Generative Generative Design Google HTML5 hype JavaScript Joshua Davis JQuery JSON Life Linux MashUp Open Source OpenSource PaperVision3D PC History Processing RIA Silverlight Technology/Internet threeJS twitter Web webGL Webware XML Yahoo Pipes

WP Cumulus Flash tag cloud by Roy Tanck and Luke Morton requires Flash Player 9 or better.

  • Monthly
  • Yearly
  • Links
  • January 2012
  • August 2011
  • June 2011
  • May 2011
  • April 2011
  • December 2010
  • November 2010
  • August 2010
  • June 2010
  • May 2010
  • 2012
  • 2011
  • 2010
  • 2009
  • 2008
  • 2007
  • 2006
  • 2005
  • agit8
  • Away3D
  • Ben Nadel
  • Bit-101
  • Bruce Jawn
  • Causecast
  • D.I.Y.
  • Dr Woohoo
  • draw.logic
  • Flight404
  • Flong
  • generatorX
  • gSkinner
  • haXe
  • Jonathan Snook
  • Joshua Davis
  • Jot
  • Kirupa
  • LifeHacker
  • Make
  • Minor White
  • Mr Doob
  • NihiLogic
  • NurseOnTheRun
  • octane42
  • OpenFrameWorks
  • Processing
  • PV3D
  • Senocular
  • Sephiroth
  • ShineDraw
  • Stroep
  • SWX
  • Tech News
  • Toxi
  • UnitZeroOne
  • World We Live In
  • ZeusLabs

Photos

lisa1

Recent Comments

  • seacloud9 on Molehill Away3D Dancing Fish Demo Part 1
  • William on Molehill Away3D Dancing Fish Demo Part 1

RSS LifeStream

  • seacloud9: RT @rdad: JavaScript dashboard framework jSlate open sourced http://t.co/GSpM0iiB via @zite
  • seacloud9: Activetuts+: An Introduction to the #HTML5 Gamepad API http://t.co/U6bvxwDO
  • seacloud9: #CreativeJS: 3D wood turning http://t.co/2fvJTD8D

Copyright © 2012 i-create | therefore-i am.