One By One Design

Drawing on Stuff in Away3D 4.0

So, Easter Day, I thought I’d sit down and make a little ‘Paint on an Egg and Send it to Your Friend’ app. Unfortunately, I ran into two main technical difficulties. 1. I couldn’t get the UV seams of the texture to align properly to make a nice seamless painting; and 2. the idea is just so damn overdone and lame I really didn’t want to be bothered writing the thing.

So, instead, I thought I’d write up a quick how-to for anyone wanting to do something similar in the latest version of Away3D (4.0.0 beta at the time of this writing).

For anyone unfamiliar with the idea, the basic concept is this: you create a material based on some BitmapData or other, apply that material to some 3D object, “draw on” that BitmapData (using draw(), or setPixel(), or copyPixels(), or whatever method is best for the moment), then ensure that the material is updated to reflect your drawing on the 3D object.

In Away3D 4.0 there are a few gotchas in this process (or, at least, they got me) that this will hopefully help you out with.

First of all, BitmapMaterials are no more. There are now TextureMaterials and BitmapTextures. They’re easy enough to get used to – you just create a TextureMaterial with a BitmapTexture and apply that TextureMaterial to a Mesh instance. It’s a slightly different procedure from previous versions of Away3D, but straight forward enough. David Lenaerts actually talked about these changes back in December, so anyone who’s been using Away3D regularly (unlike myself) is probably already aware of and used to these things.

The first thing I spent too much time on was the fact that it doesn’t seem possible to directly alter the BitmapData of a BitmapTexture object. Instead you have to clone the BitmapData, apply your changes to the clone, then reassign the bitmapData property (and dispose of the old BitmapData, of course). So let’s say you want to set the upper left pixel of a texture to red…

// first get the texture of your working material as a BitmapTexture
var tex:BitmapTexture = mMaterial.texture as BitmapTexture;

// get the bitmapdata of the texture
var oldData:BitmapData = tex.bitmapData;

// clone the bitch
var newData:BitmapData = oldData.clone();

// adjust the pixels
newData.setPixel(0, 0, 0xFF0000);

// reapply
tex.bitmapData = newData;

// out with the old
oldData.dispose();

The other troublesome thing was finding the coordinates of the BitmapData that correspond to the mouse position on the object in question. You can do this by, first of all, setting the Mesh instance’s mouseEnabled property to true, then setting its mouseHitMethod property to 1 (i.e.¬†MouseHitMethod.MESH_CLOSEST_HIT). After doing these two things, the event object passed to any¬†MouseEvent3D event listener will have a uv property, which is a Point. That point is a x, y ratio of the mouse coordinates on the texture’s BitmapData (in other words you need to multiply those point x and y values by the width and height of the BitmapData to find the actual mouse coordinates.

Sounds trickier than it is, so here’s a full copy / paste / compile example that will let you mouse down and draw on a cube.

/**
 *	Copyright (c) 2012 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package
{
	import away3d.cameras.lenses.PerspectiveLens;
	import away3d.containers.View3D;
	import away3d.core.raycast.MouseHitMethod;
	import away3d.entities.Mesh;
	import away3d.events.MouseEvent3D;
	import away3d.lights.PointLight;
	import away3d.materials.lightpickers.StaticLightPicker;
	import away3d.materials.TextureMaterial;
	import away3d.primitives.CubeGeometry;
	import away3d.textures.BitmapTexture;
	import flash.display.BitmapData;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Vector3D;

	/**
	 * Example of how to 'draw on' a 3D object in Away3D 4.0 Beta.
	 * To use: mouse down on top of the cube and move around...
	 *
	 * @author Devon O.
	 */

	[SWF(width='800', height='600', backgroundColor='#000000', frameRate='60')]
	public class Main extends Sprite
	{
		private var mView:View3D;

		private var mCube:Mesh
		private var mMaterial:TextureMaterial;

		private var mLight1:PointLight;
		private var mLight2:PointLight;

		private var mBrush:BitmapData;

		public function Main():void
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}

		private function init(event:Event = null):void
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);

			initBrush(0x232323);
			initView();
			initLights();
			initCube();
			startRender();
		}

		private function startRender():void
		{
			addEventListener(Event.ENTER_FRAME, onTick);
		}

		private function initBrush(col:uint):void
		{
			if (mBrush) mBrush.dispose();

			var s:Shape = new Shape();
			s.graphics.beginFill(col);
			s.graphics.drawCircle(5, 5, 5);
			s.graphics.endFill();
			mBrush = new BitmapData(10, 10, true, 0x00000000);
			mBrush.draw(s);
		}

		private function initView():void
		{
			mView = new View3D();
			mView.backgroundColor = 0x000000;
			mView.antiAlias = 4;

			mView.camera.position = new Vector3D(100, 75, -150);
			mView.camera.lookAt(new Vector3D(0, 0, 0));
			(mView.camera.lens as PerspectiveLens).fieldOfView = 60;

			addChild(mView);
		}

		private function initLights():void
		{
			mLight1 = new PointLight();
			mLight1.specular = 0.3;
			mLight1.color = 0xFFFFFFFF;
			mLight1.diffuse = 0xFFFFFF;
			mLight1.position = new Vector3D(-550, 550, -2000);
			mView.scene.addChild(mLight1);

			mLight2 = new PointLight();
			mLight2.specular = 0.3;
			mLight2.color = 0xFFFFFF;
			mLight2.diffuse = 0xFFFFFF;
			mLight2.position = new Vector3D(850, -100, -1500);
			mView.scene.addChild(mLight2);
		}

		private function initCube():void
		{
			var dat:BitmapData = new BitmapData(256, 256, false, 0xADADAD);
			mMaterial = new TextureMaterial(new BitmapTexture(dat));

			var lightPicker:StaticLightPicker = new StaticLightPicker([mLight1, mLight2]);
			mMaterial.lightPicker = lightPicker;

			mCube = new Mesh(new CubeGeometry(), mMaterial);
			mCube.mouseEnabled = true;						// self explanatory, but necessary
			mCube.mouseHitMethod = MouseHitMethod.MESH_CLOSEST_HIT;			// will ensure the uv property of the event object is not null
			mCube.addEventListener(MouseEvent3D.MOUSE_DOWN, onCubeDown);
			mCube.addEventListener(MouseEvent3D.MOUSE_UP, onCubeOut);
			mCube.addEventListener(MouseEvent3D.MOUSE_OUT, onCubeOut);

			mView.scene.addChild(mCube);
		}

		private function onCubeDown(event:MouseEvent3D):void
		{
			mCube.addEventListener(MouseEvent3D.MOUSE_MOVE, onCubeMove);
		}

		private function onCubeOut(event:MouseEvent3D):void
		{
			mCube.removeEventListener(MouseEvent3D.MOUSE_MOVE, onCubeMove);
		}

		private function onCubeMove(event:MouseEvent3D):void
		{
			var uvPoint:Point = event.uv;					// will be a ratio
			var tex:BitmapTexture = mMaterial.texture as BitmapTexture;	// get the bitmaptexture
			var oldData:BitmapData = tex.bitmapData;			// get the bitmapdata
			var newData:BitmapData = oldData.clone();			// clone it
			uvPoint.x *= newData.width;					// multiply the point ratios by width and height
			uvPoint.y *= newData.height;
			uvPoint.x -= 5;							// this is half the width of the brush - just to center it
			uvPoint.y -= 5;
			newData.copyPixels(mBrush, mBrush.rect, uvPoint);		// copy the brush into the bitmap data
			tex.bitmapData = newData;					// set the bitmapData property of the texture
			oldData.dispose();						// dispose the old
		}

		private function onTick(event:Event):void
		{
			mView.render();
		}
	}
}

And that’s that. Hope it helps out. And incidentally, here’s my egg painter (eggamajig) thing I was playing around with. After double clicking, mouse down and draw on the egg.

[kml_flashembed publishmethod=”static” fversion=”11.0.0″ movie=”http://blog.onebyonedesign.com/wp-content/uploads/2012/04/eggamajegg.swf” width=”600″ height=”450″ targetclass=”flashmovie” wmode=”direct”]

Get Adobe Flash player

[/kml_flashembed]

If anyone can point out some UV seam fixing tips for a complete 3D newb (as the kids say), I’d appreciate it.

Posted by

Post a comment

Your email address will not be published. Required fields are marked *