Playing with Citrus, Cadet Editor 3D and AwayPhysics

Talking about 3D, Actionscript 3, Flash and Users contributions.

Exactly a month ago I showed you the new Citrus engine which now supports Away3D. On the other hand, Away3D supports its own Physics engine called awayphysics, and I showed an example of what you can do with with a quick 3D physics with Away3D 4.1 and Bullet Physics Engine demonstration.

Aymeric Lamboley, the mind (and fingers) behind the Citrus Engine, pushed the thing further and gave me the permission to publish his way to quickly generate 3D physics scenes with Citrus Engine and Away3D + awayphysics.

« Now that 3D views and physics are supported, it’s time to give a look on which tool we can use as a 3D game Level Editor. At first, I thought to Prefab. This is the best tool to create a scene with Away3D, importing assets, add lights… But too complex for a simple level design, I mean : hey the Citrus Engine is not a concurrent to Unity3D (which I started to learn thanks to this awesome tutorial). Its 3D physics part is just here to create basic 3D game / puzzle. Also the level editor has to support object creation (physics object), this isn’t obvious with Prefab.
Then I gave a look to Cadet Editor, the 3D editor is very easy to handle. You can create quickly sphere, cube and plane objects, add lights… That’s the right tool to see what can be done!

I really enjoy the design of Cadet Editor, however it should manage right click with camera rotation instead of displaying a simple menu with a link to Away3D.
In Cadet3D project are saved into a format close to MXML. See our examples and its code:

<cadet:CadetScene x:id="0" framerate="30" name="Component" timeScale="1" xmlns:cadet="www.unwrong.com/cadet/1.0.0" xmlns:x="org.bonesframework.core.serialization.Serializer" xmlns:bones="www.bonesframwork.org/bones/1.0.0" xmlns:cadetAway3D4="www.unwrong.com/cadetAway3D4/1.0.0" xmlns:ns0="cadetAway3D4.components.cameras" xmlns:ns1="cadetAway3D4.components.materials" xmlns:ns2="cadetAway3D4.components.geom" xmlns:ns3="cadetAway3D4.components.lights">
  <bones:ArrayCollection x:name="children" x:id="1">
    <cadetAway3D4:RendererAway3D x:name="0" x:id="2" name="Away3D 4 Renderer">
      <ns0:CameraComponent x:name="cameraComponent" x:id="3" transform="0.9833181500434875,-8.754431490842762e-8,-0.18189382553100586,0,0.12057413905858994,0.7487242221832275,0.6518235206604004,0,0.13618825376033783,-0.66288161277771,0.7362341284751892,0,-191.17271423339844,590.8668212890625,-854.3065185546875,1" name="Camera">
        <bones:ArrayCollection x:name="children" x:id="4"/>
      </ns0:CameraComponent>
    </cadetAway3D4:RendererAway3D>
    <cadetAway3D4:MeshComponent x:name="1" x:id="5" transform="1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1" name="Plane">
      <ns1:ColorMaterialComponent x:name="materialComponent" x:id="6" gloss="50" specularStrength="1" color="333722" name="Blue Material" depthCompareMode="less"/>
      <bones:ArrayCollection x:name="children" x:id="7">
        <ns2:PlaneGeometryComponent x:name="0" x:id="8" height="1000" segmentsH="1" segmentsW="1" name="a platform" width="1000"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="8"/>
    </cadetAway3D4:MeshComponent>
    <ns3:DirectionalLightComponent x:name="2" x:id="9" color="16777215" specular="1" ambient="0.1" transform="0.9063077569007874,0,-0.4226183295249939,0,0.3830223083496094,0.42261824011802673,0.8213937878608704,0,0.17860621213912964,-0.9063078165054321,0.3830221891403198,0,0,0,0,1" ambientColor="16777215" diffuse="1" name="Directional Light">
      <bones:ArrayCollection x:name="children" x:id="10"/>
    </ns3:DirectionalLightComponent>
    <x:Ref x:name="3" x:id="3"/>
    <cadetAway3D4:MeshComponent x:name="4" x:id="11" transform="1,0,0,0,0,1,0,0,0,0,1,0,-58.91594696044922,146.62379455566406,-32.241188049316406,1" name="Cube">
      <ns1:ColorMaterialComponent x:name="materialComponent" x:id="12" gloss="50" specularStrength="1" color="489736" name="Green Material" depthCompareMode="less"/>
      <bones:ArrayCollection x:name="children" x:id="13">
        <ns2:CubeGeometryComponent x:name="0" x:id="14" height="180.85" segmentsH="1" tile6="1" depth="180.85" segmentsW="1" segmentsD="1" name="a cube" width="180.85"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="14"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="5" x:id="15" transform="1,0,0,0,0,1,0,0,0,0,1,0,-140.8660888671875,340.4476318359375,-54.04615783691406,1" name="Sphere">
      <ns1:ColorMaterialComponent x:name="materialComponent" x:id="16" gloss="50" specularStrength="1" color="12320768" name="Red Material" depthCompareMode="less"/>
      <bones:ArrayCollection x:name="children" x:id="17">
        <ns2:SphereGeometryComponent x:name="0" x:id="18" segmentsH="12" segmentsW="16" radius="84.13898408877864" name="a sphere"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="18"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="6" x:id="19" transform="1,0,0,0,0,1,0,0,0,0,1,0,151.02638244628906,146.50900268554688,127.5979995727539,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="20">
        <ns2:SphereGeometryComponent x:name="0" x:id="21" segmentsH="12" segmentsW="16" radius="52.35" name="Sphere Geometry"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="21"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="7" x:id="22" transform="1,0,0,0,0,1,0,0,0,0,1,0,164.13088989257813,348.98486328125,102.31562805175781,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="23">
        <ns2:CubeGeometryComponent x:name="0" x:id="24" height="76.87" segmentsH="1" tile6="1" depth="76.87" segmentsW="1" segmentsD="1" name="Cube Geometry" width="76.87064711138314"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="24"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="8" x:id="25" transform="1,0,0,0,0,1,0,0,0,0,1,0,-161.5980987548828,37.646751403808594,291.46575927734375,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="26">
        <ns2:CubeGeometryComponent x:name="0" x:id="27" height="75.29" segmentsH="1" tile6="1" depth="75.29" segmentsW="1" segmentsD="1" name="Cube Geometry" width="75.29350445797198"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="27"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="9" x:id="28" transform="1,0,0,0,0,1,0,0,0,0,1,0,-10.505144119262695,181.75933837890625,69.79844665527344,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="29">
        <ns2:CubeGeometryComponent x:name="0" x:id="30" height="66.29" segmentsH="1" tile6="1" depth="66.29" segmentsW="1" segmentsD="1" name="Cube Geometry" width="66.29740645095558"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="30"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="10" x:id="31" transform="1,0,0,0,0,1,0,0,0,0,1,0,-393.7727966308594,113.91818237304688,-41.75886917114258,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="32">
        <ns2:CubeGeometryComponent x:name="0" x:id="33" height="84.85" segmentsH="1" tile6="1" depth="84.85" segmentsW="1" segmentsD="1" name="Cube Geometry" width="84.85060333420417"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="33"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="11" x:id="34" transform="1,0,0,0,0,1,0,0,0,0,1,0,110.92161560058594,25.127405166625977,230.1426239013672,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="35">
        <ns2:CubeGeometryComponent x:name="0" x:id="36" height="50.25" segmentsH="1" tile6="1" depth="50.25" segmentsW="1" segmentsD="1" name="Cube Geometry" width="50.25481013821924"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="36"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="12" x:id="37" transform="1,0,0,0,0,1,0,0,0,0,1,0,156.6885223388672,99.52238464355469,51.9654655456543,1" name="Cube">
      <x:Ref x:name="materialComponent" x:id="12"/>
      <bones:ArrayCollection x:name="children" x:id="38">
        <ns2:CubeGeometryComponent x:name="0" x:id="39" height="49.76" segmentsH="1" tile6="1" depth="49.76" segmentsW="1" segmentsD="1" name="Cube Geometry" width="49.76054139091827"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="39"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="13" x:id="40" transform="1,0,0,0,0,1,0,0,0,0,1,0,-241.41665649414063,106.39049530029297,-15.305731773376465,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="41">
        <ns2:SphereGeometryComponent x:name="0" x:id="42" segmentsH="12" segmentsW="16" radius="81.08778158664182" name="Sphere Geometry"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="42"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="14" x:id="43" transform="1,0,0,0,0,1,0,0,0,0,1,0,240.3887176513672,66.32697296142578,28.3767147064209,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="44">
        <ns2:SphereGeometryComponent x:name="0" x:id="45" segmentsH="12" segmentsW="16" radius="42.73389288130251" name="Sphere Geometry"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="45"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="15" x:id="46" transform="1,0,0,0,0,1,0,0,0,0,1,0,77.10282897949219,82.25804901123047,-173.88815307617188,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="47">
        <ns2:SphereGeometryComponent x:name="0" x:id="48" segmentsH="12" segmentsW="16" radius="44.53863475379832" name="Sphere Geometry"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="48"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="16" x:id="49" transform="1,0,0,0,0,1,0,0,0,0,1,0,-8.887575149536133,77.42305755615234,325.1551513671875,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="50">
        <ns2:SphereGeometryComponent x:name="0" x:id="51" segmentsH="12" segmentsW="16" radius="26.87288819730761" name="Sphere Geometry"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="51"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="17" x:id="52" transform="1,0,0,0,0,1,0,0,0,0,1,0,-111.85294342041016,150.3166961669922,314.55419921875,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="53">
        <ns2:SphereGeometryComponent x:name="0" x:id="54" segmentsH="12" segmentsW="16" radius="36.59617847826625" name="Sphere Geometry"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="54"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="18" x:id="55" transform="1,0,0,0,0,1,0,0,0,0,1,0,194.72483825683594,66.20823669433594,249.91229248046875,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="56">
        <ns2:SphereGeometryComponent x:name="0" x:id="57" segmentsH="12" segmentsW="16" radius="34.06102262376309" name="Sphere Geometry"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="57"/>
    </cadetAway3D4:MeshComponent>
    <cadetAway3D4:MeshComponent x:name="19" x:id="58" transform="1,0,0,0,0,1,0,0,0,0,1,0,153.58782958984375,59.29662322998047,139.8775634765625,1" name="Sphere">
      <x:Ref x:name="materialComponent" x:id="16"/>
      <bones:ArrayCollection x:name="children" x:id="59">
        <ns2:SphereGeometryComponent x:name="0" x:id="60" segmentsH="12" segmentsW="16" radius="27.704758141615848" name="Sphere Geometry"/>
      </bones:ArrayCollection>
      <x:Ref x:name="geometryComponent" x:id="60"/>
    </cadetAway3D4:MeshComponent>
    <x:Ref x:name="20" x:id="16"/>
    <x:Ref x:name="21" x:id="6"/>
    <x:Ref x:name="22" x:id="12"/>
  </bones:ArrayCollection>
  <Object x:name="userData" x:id="61"/>
  <bones:DependencyManager x:name="dependencyManager" x:id="62">
    <bones:ArrayCollection x:name="dependencyNodes" x:id="63"/>
  </bones:DependencyManager>
</cadet:CadetScene>

This format isn’t easy to handle! Cadet should really export to XML or JSON format.
Anyway this what I’ve done:

You can remove the AwayPhysics debug view via the console:

set awayPhysics visible false

As usual all the source code is available on the GitHub.

This is the GameState:

package awayphysics.cadet3d {
 
	import away3d.controllers.HoverController;
	import away3d.debug.AwayStats;
 
	import com.citrusengine.core.State;
	import com.citrusengine.physics.awayphysics.AwayPhysics;
	import com.citrusengine.utils.ObjectMaker3D;
	import com.citrusengine.view.CitrusView;
	import com.citrusengine.view.away3dview.Away3DView;
 
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Vector3D;
 
	/**
	 * @author Aymeric
	 */
	public class Cadet3DGameState extends State {
 
		[Embed(source="/../embed/3D/simpletest.away3d4", mimeType="application/octet-stream")]
		private const _CADET_LEVEL:Class;
 
		// navigation variables
		private var _cameraController:HoverController;
 
		private var _move:Boolean = false;
		private var _lastPanAngle:Number;
		private var _lastTiltAngle:Number;
		private var _lastMouseX:Number;
		private var _lastMouseY:Number;
		private var _lookAtPosition:Vector3D = new Vector3D();
 
		public function Cadet3DGameState() {
 
			super();
		}
 
		override public function initialize():void {
 
			super.initialize();
 
			addChild(new AwayStats((view as Away3DView).viewRoot));
 
			var awayPhysics:AwayPhysics = new AwayPhysics("awayPhysics");
			awayPhysics.visible = true;
			add(awayPhysics);
 
			ObjectMaker3D.FromCadetEditor3D(XML(new _CADET_LEVEL()));
 
			_cameraController = new HoverController((view as Away3DView).viewRoot.camera, null, 175, 20, 1000);
 
			stage.addEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
			stage.addEventListener(Event.MOUSE_LEAVE, _onMouseUp);
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
		}
 
		// Make sure and call this override to specify Away3D view.
		override protected function createView():CitrusView {
 
			return new Away3DView(this);
		}
 
		override public function destroy():void {
 
			stage.removeEventListener(MouseEvent.MOUSE_DOWN, _onMouseDown);
			stage.removeEventListener(MouseEvent.MOUSE_UP, _onMouseUp);
			stage.removeEventListener(Event.MOUSE_LEAVE, _onMouseUp);
			stage.removeEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
 
			super.destroy();
		}
 
		override public function update(timeDelta:Number):void {
 
			super.update(timeDelta);
 
			if (_move) {
				_cameraController.panAngle = 0.3 * (stage.mouseX - _lastMouseX) + _lastPanAngle;
				_cameraController.tiltAngle = 0.3 * (stage.mouseY - _lastMouseY) + _lastTiltAngle;
			}
 
			_cameraController.lookAtPosition = _lookAtPosition;
		}
 
		private function _onMouseDown(mEvt:MouseEvent):void {
			_lastPanAngle = _cameraController.panAngle;
			_lastTiltAngle = _cameraController.tiltAngle;
			_lastMouseX = stage.mouseX;
			_lastMouseY = stage.mouseY;
			_move = true;
		}
 
		private function _onMouseUp(evt:Event):void {
			_move = false;
		}
 
		private function _onMouseWheel(mEvt:MouseEvent):void {
 
			_cameraController.distance -= mEvt.delta * 5;
 
			if (_cameraController.distance < 100)
				_cameraController.distance = 100;
			else if (_cameraController.distance > 2000)
				_cameraController.distance = 2000;
		}
 
	}
}

Like Flash as Level Editor or Tiled Map Editor, we use the ObjectMaker class. This is the template for Cadet Editor 3D:

public static function FromCadetEditor3D(levelData:XML, addToCurrentState:Boolean = true):Array {
 
	var ce:CitrusEngine = CitrusEngine.getInstance();
 
	var params:Object;
 
	var objects:Array = [];
 
	var type:String;
	var radius:Number;
 
	var object:AwayPhysicsObject;
 
	for each (var root:XML in levelData.children()) {
		for each (var parent:XML in root.children()) {
 
			type = parent.@name;
 
			if (type == "Cube" || type == "Plane" || type == "Sphere") {
 
				var transform:Array = parent.@transform.split(",");
 
				params = {};
				params.x = transform[12];
				params.y = transform[13];
				params.z = transform[14];
 
				for each (var child:XML in parent.children()) {
 
					for each (var finalElement:XML in child.children()) {
 
						params.width = finalElement.@width;
						params.height = finalElement.@height;
						params.depth = finalElement.@depth;
						radius = finalElement.@radius;
 
						if (radius)
							params.radius = finalElement.@radius;
 
						if (type == "Plane") {
 
							// the plane seems to use the height as the depth
							params.depth = params.height;
							params.height = 0;
							params.view = new Mesh(new CubeGeometry(params.width, params.height, params.depth), new ColorMaterial(0xFF0000));
							object = new Platform("plane", params);
 
						} else {
 
							if (params.radius) {
 
								params.view = new Mesh(new SphereGeometry(params.radius), new ColorMaterial(0x00FF00));
								object = new AwayPhysicsObject("sphere", params);
 
							} else {
 
								params.view = new Mesh(new CubeGeometry(params.width, params.height, params.depth), new ColorMaterial(0x0000FF));
								object = new AwayPhysicsObject("cube", params);
							}
						}
 
						objects.push(object);
					}
				}
 
			}
		}
	}
 
	if (addToCurrentState)
		for each (object in objects) ce.state.add(object);
 
	return objects;
}

As you can notice, the object maker uses CE prebuilt objects for AwayPhysics but not in a dynamic way.

This is some points that Cadet Editor should allow to be an excellent level editor :
– quickly most of the points are in Tiled Map Editor, checkout Citrus Engine support there.
– an easy to parse exporter format (xml or json).
– adding personal properties so we can precize type, gravity, view for example directly in the level editor. A must have! »

Follow Aymeric Lamboley blog for the latest news.