mar 2 2010

Airpaper - parte 2

Mais um dia sem muita demanda no trabalho, muita coisa sujeita à aprovação. Então… Vamos à mais uma parte do nosso jogo.

SEGUNDA PARTE. Cenário criado! E como surgiu a curiosidade de usar anáglifo - aquilo que chamam de 3d nos cinemas, novamente popularizado pelo filme Avatar - em Flash, decidi implementar para dar uma maior noção de pespectiva. Nada complicado e nada que não seja entendido depois de uma busca no google por “como fazer anáglifo”.
Usem óculos red-cyan! =P

NOTA 1. Nada refatorado, nada documentado, postergar isso sempre dá problema. #ficadica.

Exemplo funcionando.

Código fonte para download.


package {

	// TWEENER
	import caurina.transitions.Tweener;
	import caurina.transitions.properties.CurveModifiers;

	// SANDY
	import sandy.core.data.Point3D;
	import sandy.core.Scene3D;
	import sandy.core.scenegraph.Camera3D;
	import sandy.core.scenegraph.Group;
	import sandy.core.scenegraph.Shape3D;
	import sandy.core.scenegraph.TransformGroup;
	import sandy.events.QueueEvent;
	import sandy.events.SandyEvent;
	import sandy.materials.Appearance;
	import sandy.materials.attributes.CelShadeAttributes;
	import sandy.materials.attributes.LightAttributes;
	import sandy.materials.attributes.MaterialAttributes;
	import sandy.materials.attributes.PhongAttributes;
	import sandy.materials.BitmapMaterial;
	import sandy.parser.IParser;
	import sandy.parser.Parser;
	import sandy.parser.ParserStack;
	import sandy.util.LoaderQueue;
	import sandy.view.ViewPort;

	// DEBUGGER
	import nl.demonsters.debugger.MonsterDebugger;

	// MIC
	import flash.media.Microphone;
	import flash.system.Security;
	import flash.system.SecurityPanel;

	// OTHERS
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.errors.IllegalOperationError;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	// ANAGLYPH
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.BlendMode;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.ColorTransform;

	import sandy.core.scenegraph.Camera3D;
	import sandy.core.scenegraph.Group;
	import sandy.materials.Appearance;
	import sandy.materials.BitmapMaterial;
	import sandy.primitive.Box;
	import sandy.primitive.Plane3D;
	import sandy.materials.ColorMaterial;

	/**
	 *	...
	 *	@author contato [at] andrecaribe [dot] com [dot] br
	 *	@since 2010-02-25
	 *	@release 0
	 *	@revision 2
	 */
	public class Airplane extends MovieClip {

		// Public properties
		public var d:MonsterDebugger = new MonsterDebugger(this);

		// Private properties
		private var _mic:Microphone;
		private var _timer:Timer;
		private var _scene:Scene3D;
		private var _camera:Camera3D;
		private var _propeller:Shape3D;
		private var _airplane:Shape3D;
		private var _plane1:Plane3D;
		private var _plane2:Plane3D;
		private var _cloud1:Plane3D;
		private var _cloud2:Plane3D;
		private var _parserStack:ParserStack = null;
		private var _queue:LoaderQueue = null;
		private var _transformGroup:TransformGroup;
		private var _airplanePosition:Object = { x:0, y:0, z:-200,
												rotateX:0, rotateY:180, rotateZ:0,
												roll:0, tilt:0, time:0 };

		private var bmScreen:Bitmap = new Bitmap (new BitmapData (500, 500, true, 0));
		private var mcBuffer:Sprite = new Sprite ();
		private var ctLeft:ColorTransform = new ColorTransform  (1, 0, 0);
		private var ctRight:ColorTransform = new ColorTransform  (0, 1 - 0.125, 1 - 0.05);

		/**
		 *	...
		 */
		public function Airplane():void {
			MonsterDebugger.trace(trace, "Airplane");

			// MIC
			Security.showSettings(SecurityPanel.MICROPHONE);
			_mic = Microphone.getMicrophone();
			_mic.setLoopBack(true);
			_mic.setUseEchoSuppression(true);

			CurveModifiers.init();
			loadModels();
		}

		/**
		 *	...
		 */
		private function loadModels():void {
			MonsterDebugger.trace(trace, "loadModels");

			var parserPropeller:IParser = Parser.create("helice.ASE", Parser.ASE);
			var parserAirplane:IParser = Parser.create("aviao.ASE", Parser.ASE);

			_parserStack = new ParserStack();
			_parserStack.add("propeller", parserPropeller);
			_parserStack.add("airplane", parserAirplane);
			_parserStack.addEventListener(ParserStack.COMPLETE, parserComplete);
			_parserStack.start();
		}

		/**
		 *	...
		 *	@param event:Event
		 */
		private function parserComplete(event:Event):void {
			MonsterDebugger.trace(trace, "parserComplete");

			_parserStack.removeEventListener(ParserStack.COMPLETE, parserComplete);

			_propeller = _parserStack.getGroupByName("propeller").children[0] as Shape3D;
			_airplane = _parserStack.getGroupByName("airplane").children[0] as Shape3D;
			loadTextures();
		}

		/**
		 *	...
		 */
		private function loadTextures():void {
			MonsterDebugger.trace(trace, "loadTextures");

			_queue = new LoaderQueue();
			_queue.add("propellerTexture", new URLRequest("helice.png") );
			_queue.add("airplaneTexture", new URLRequest("aviao.png") );
			_queue.addEventListener(SandyEvent.QUEUE_COMPLETE, loadComplete);
			_queue.start();
		}

		/**
		 *	...
		 *	@param event:QueueEvent
		 */
		private function loadComplete(event:QueueEvent):void {
			MonsterDebugger.trace(trace, "loadComplete");

			_queue.removeEventListener(SandyEvent.QUEUE_COMPLETE, loadComplete);

			_camera = new Camera3D(500,500, 45, 50, 10000);
			_camera.viewport = new ViewPort(500, 500);

			var root:Group = createScene();
			var canvas:Sprite = new Sprite();
			this.addChild(canvas);
			_scene = new Scene3D("scene", canvas, _camera, root);

			_scene.container = mcBuffer;

			// CLICK TO START
			addEventListener(MouseEvent.CLICK, clickToStart);
			reset();
		}

		/**
		 *	...
		 *	@param event:MouseEvent
		 */
		function clickToStart(event:MouseEvent):void {
			removeEventListener(MouseEvent.CLICK, clickToStart);
			addEventListener(MouseEvent.CLICK, clickToStop);
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);

			_timer = new Timer(5000);
			_timer.addEventListener(TimerEvent.TIMER, moveToSide);
			_timer.start();
			moveToSide();
		}

		/**
		 *	...
		 *	@param event:MouseEvent
		 */
		function clickToStop(event:MouseEvent):void {
			addEventListener(MouseEvent.CLICK, clickToStart);
			removeEventListener(MouseEvent.CLICK, clickToStop);
			removeEventListener(Event.ENTER_FRAME, enterFrameHandler);

			_timer.stop();
			_timer.removeEventListener(TimerEvent.TIMER, moveToSide);
			_timer = null;
		}

		/**
		 *	...
		 *	@return Group
		 *
		 *	@see sandy.core.scenegraph.Group
		 */
		private function createScene():Group {
			MonsterDebugger.trace(this, "createScene");

			var g:Group = new Group();

			_plane1 = new Plane3D( "back", 500, 500, 1, 1 );
			var planeApp1:Appearance = new Appearance (
				new BitmapMaterial (new BackgroundBack(1, 1), null, 0));
				//new ColorMaterial(0x7CBEF2, 1, null));
				//
			_plane1.appearance = planeApp1;
			_plane1.z = 200;

			g.addChild(_plane1);

			_plane2 = new Plane3D( "front", 500, 500, 1, 1 );
			var planeApp2:Appearance = new Appearance (
				new BitmapMaterial (new BackgroundFront(1, 1), null, 0));
				//new ColorMaterial(0x7CBEF2, 1, null));
			_plane2.appearance = planeApp2;
			_plane2.y = 100;
			_plane2.z = 0;

			g.addChild(_plane2);

			_cloud1 = new Plane3D( "cloud1", 70, 140, 1, 1 );
			var _cloudApp1:Appearance = new Appearance (
				new BitmapMaterial (new Cloud_PNG(1, 1), null, 0));
				//new ColorMaterial(0x7CBEF2, 1, null));
			_cloud1.appearance = _cloudApp1;
			_cloud1.x = 30
			_cloud1.y = -30;
			_cloud1.z = 200;

			g.addChild(_cloud1);

			_cloud2 = new Plane3D( "cloud2", 70, 140, 1, 1 );
			_cloud2.appearance = _cloudApp1;
			_cloud2.x = -40;
			_cloud2.y = 40;
			_cloud2.z = 0;

			g.addChild(_cloud2);

			// ILUMINATION
			// Cel-Shader
			//var ilumination:CelShadeAttributes = new CelShadeAttributes();

			// Phong
			//var ilumination:PhongAttributes = new PhongAttributes (true, 0.2, 15);
			//ilumination.diffuse = .8;
			//ilumination.specular = .8;
			//ilumination.gloss = 100;

			// Normal
			var ilumination:LightAttributes = new LightAttributes (true, 0.3);
			ilumination.diffuse = 0.5;
			ilumination.specular = 1;
			ilumination.gloss = 5;

			// TEXTURES
			var propellerMaterial:BitmapMaterial = new BitmapMaterial(_queue.data["propellerTexture"].bitmapData);
			var propellerApp:Appearance = new Appearance(propellerMaterial);
			_propeller.appearance = propellerApp;

			var airplaneMaterial:BitmapMaterial = new BitmapMaterial(_queue.data["airplaneTexture"].bitmapData, new MaterialAttributes(ilumination));
			var airplaneApp:Appearance = new Appearance( airplaneMaterial );
			airplaneApp.lightingEnable = true;
			_airplane.appearance = airplaneApp;

			// GROUP
			_transformGroup = new TransformGroup('airplaneGroup');
			_transformGroup.addChild(_propeller);
			_transformGroup.addChild(_airplane);
			_transformGroup.enableBackFaceCulling = true;

			g.addChild(_transformGroup);

			// ANAGLYPH
			this.addChild (bmScreen);

			return g;
		}

		/**
		 *	...
		 */
		private function reset():void {
			MonsterDebugger.trace(this, "reset");

			_transformGroup.resetCoords();

			Tweener.addTween(_transformGroup, _airplanePosition);
			enterFrameHandler(null);
		}

		/**
		 *	...
		 *	@param event:Event = null
		 */
		private function enterFrameHandler(event:Event = null):void {
			// Airplane and propeller
			_propeller.rotateZ += 50;
			_transformGroup.y += (_mic.activityLevel-20)/32;	

			// Plane
			_plane1.z -= .03;
			_plane2.y -= .05;
			_plane2.z -= .05;

			// Cloud
			_cloud1.z -= 10;
			if(_cloud1.z < -300) {
				_cloud1.z = 0;
			}

			_cloud2.z -= 8;
			if(_cloud2.z < -300) {
				_cloud2.z = 0;
			}

			// Screen
			bmScreen.bitmapData.fillRect (bmScreen.bitmapData.rect, 0);
			_scene.camera.moveSideways(+4);
			_scene.render();
			bmScreen.bitmapData.draw (mcBuffer, null, ctRight, BlendMode.ADD);
			_scene.camera.moveSideways(-4);
			_scene.render();
			bmScreen.bitmapData.draw (mcBuffer, null, ctLeft,  BlendMode.ADD);
		}

		/**
		 *	...
		 *	@param event:TimerEvent
		 */
		private function moveToSide(event:TimerEvent = null):void {
			if(_timer.currentCount % 2 == 0) {
				Tweener.addTween(_transformGroup, {x:5, roll:-5, time:5, transition:"easeOutInQuad"} );
			} else {
				Tweener.addTween(_transformGroup, {x:-5, roll:5, time:5, transition:"easeOutInQuad"} );
			}

			if(_timer.currentCount % 4 == 0) {
				Tweener.addTween(_transformGroup, {z:-210, time:5, transition:"easeOutInQuad"} );
			} else {
				Tweener.addTween(_transformGroup, {z:-190, time:5, transition:"easeOutInQuad"} );
			}
		}
	}
}

fev 25 2010

Airpaper - Parte 1

É, não consigo mesmo ficar uma tarde de bobeira no trabalho. Dessa vez resolvi fazer um pequeno jogo utilizando Sandy3d e a interface de microfone que o Flash disponibiliza. Essa é apenas a primeira parte, as outras partes virão quando eu tiver mais tempo livre durante o trabalho.

O JOGO. Um pequeno avião - agradecimentos a Felipe Assis por ter modelado em dois minutos pra mim para um trampo antigo que não foi realizado - em 3d irá “flutuar” a medida que o usuário assoprar/rir/fazer zuada/etc no microfone. Assim o avião deslizará sobre o palco para adquirir os itens e concluir a fase. Ele perderá se voar muito baixo, ou se não pegar um número insuficiente de itens durante o percurso. Tratatemos também sobre sons quando falarmos de colisão.

PRIMEIRA PARTE. Esqueceremos o cenário e integração com outros itens, ou seja, sem colisões por em quanto, apenas o avião para responder aos “comandos” do microfone. Por falar nisso, quem topa implementar comandos de voz em ActionScript 3 comigo?

NOTA 1. Como só fiz colocar o dedão no teclado, escrevendo tudo no péssimo editor de código do Flash CS4, o código nem de longe não está perfeito, não está bem documentado e, enfim… Melhor ver o resultado final e o código fonte.

NOTA 2. Fico devendo uma imagem com preview, meu “print screen” no Mac não quer funcionar.

Exemplo funcionando.

Código fonte para download.


package {

	// TWEENER
	import caurina.transitions.Tweener;
	import caurina.transitions.properties.CurveModifiers;

	// SANDY
	import sandy.core.data.Point3D;
	import sandy.core.Scene3D;
	import sandy.core.scenegraph.Camera3D;
	import sandy.core.scenegraph.Group;
	import sandy.core.scenegraph.Shape3D;
	import sandy.core.scenegraph.TransformGroup;
	import sandy.events.QueueEvent;
	import sandy.events.SandyEvent;
	import sandy.materials.Appearance;
	import sandy.materials.attributes.CelShadeAttributes;
	import sandy.materials.attributes.LightAttributes;
	import sandy.materials.attributes.MaterialAttributes;
	import sandy.materials.attributes.PhongAttributes;
	import sandy.materials.BitmapMaterial;
	import sandy.parser.IParser;
	import sandy.parser.Parser;
	import sandy.parser.ParserStack;
	import sandy.util.LoaderQueue;
	import sandy.view.ViewPort;

	// DEBUGGER
	import nl.demonsters.debugger.MonsterDebugger;

	// MIC
	import flash.media.Microphone;
	import flash.system.Security;
	import flash.system.SecurityPanel;

	// OTHERS
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.errors.IllegalOperationError;
	import flash.events.Event;
	import flash.net.URLRequest;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	/**
	 *	...
	 *	@author contato [at] andrecaribe [dot] com [dot] br
	 *	@since 2010-02-25
	 *	@release 0
	 *	@version 1
	 */
	public class Airplane extends MovieClip {

		// Public properties
		public var d:MonsterDebugger = new MonsterDebugger(this);

		// Private properties
		private var _mic:Microphone;
		private var _timer:Timer;
		private var _scene:Scene3D;
		private var _camera:Camera3D;
		private var _propeller:Shape3D;
		private var _airplane:Shape3D;
		private var _parserStack:ParserStack = null;
		private var _queue:LoaderQueue = null;
		private var _transformGroup:TransformGroup;
		private var _rollRightSide:Boolean = true;
		private var _airplanePosition:Object = { x:0, y:0, z:-200,
												rotateX:0, rotateY:180, rotateZ:0,
												roll:0, tilt:0, time:0 };

		/**
		 *	...
		 */
		public function Airplane():void {
			MonsterDebugger.trace(trace, "Airplane");

			// MIC
			Security.showSettings(SecurityPanel.MICROPHONE);
			_mic = Microphone.getMicrophone();
			_mic.setLoopBack(true);
			_mic.setUseEchoSuppression(true);

			CurveModifiers.init();
			loadModels();
		}

		/**
		 *	...
		 */
		private function loadModels():void {
			MonsterDebugger.trace(trace, "loadModels");

			var parserPropeller:IParser = Parser.create("helice.ASE", Parser.ASE);
			var parserAirplane:IParser = Parser.create("aviao.ASE", Parser.ASE);

			_parserStack = new ParserStack();
			_parserStack.add("propeller", parserPropeller);
			_parserStack.add("airplane", parserAirplane);
			_parserStack.addEventListener(ParserStack.COMPLETE, parserComplete);
			_parserStack.start();
		}

		/**
		 *	...
		 *	@param event:Event
		 */
		private function parserComplete(event:Event):void {
			MonsterDebugger.trace(trace, "parserComplete");

			_parserStack.removeEventListener(ParserStack.COMPLETE, parserComplete);

			_propeller = _parserStack.getGroupByName("propeller").children[0] as Shape3D;
			_airplane = _parserStack.getGroupByName("airplane").children[0] as Shape3D;
			loadTextures();
		}

		/**
		 *	...
		 */
		private function loadTextures():void {
			MonsterDebugger.trace(trace, "loadTextures");

			_queue = new LoaderQueue();
			_queue.add("propellerTexture", new URLRequest("helice.png") );
			_queue.add("airplaneTexture", new URLRequest("aviao.png") );
			_queue.addEventListener(SandyEvent.QUEUE_COMPLETE, loadComplete);
			_queue.start();
		}

		/**
		 *	...
		 *	@param event:QueueEvent
		 */
		private function loadComplete(event:QueueEvent):void {
			MonsterDebugger.trace(trace, "loadComplete");

			_queue.removeEventListener(SandyEvent.QUEUE_COMPLETE, loadComplete);

			_camera = new Camera3D(500,500, 45, 50, 10000);
			_camera.viewport = new ViewPort(500, 500);

			var root:Group = createScene();
			var canvas:Sprite = new Sprite();
			this.addChild(canvas);
			_scene = new Scene3D("scene", canvas, _camera, root);

			// CLICK TO START
			addEventListener(MouseEvent.CLICK, clickToStart);
			reset();
		}

		/**
		 *	...
		 *	@param event:MouseEvent
		 */
		function clickToStart(event:MouseEvent):void {
			removeEventListener(MouseEvent.CLICK, clickToStart);
			addEventListener(MouseEvent.CLICK, clickToStop);
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);

			_timer = new Timer(5000);
			_timer.addEventListener(TimerEvent.TIMER, moveToSide);
			_timer.start();
		}

		/**
		 *	...
		 *	@param event:MouseEvent
		 */
		function clickToStop(event:MouseEvent):void {
			addEventListener(MouseEvent.CLICK, clickToStart);
			removeEventListener(MouseEvent.CLICK, clickToStop);
			removeEventListener(Event.ENTER_FRAME, enterFrameHandler);

			_timer.stop();
			_timer.removeEventListener(TimerEvent.TIMER, moveToSide);
			_timer = null;
		}

		/**
		 *	...
		 *	@return Group
		 *
		 *	@see sandy.core.scenegraph.Group
		 */
		private function createScene():Group {
			MonsterDebugger.trace(this, "createScene");

			// ILUMINATION
			// Cel-Shader
			//var ilumination:CelShadeAttributes = new CelShadeAttributes();

			// Phong
			var ilumination:PhongAttributes = new PhongAttributes (true, 0.2, 15);
			ilumination.diffuse = .8;
			ilumination.specular = .8;
			ilumination.gloss = 100;

			// Normal
			//var ilumination:LightAttributes = new LightAttributes (true, 0.2);
			//ilumination.diffuse = 0.5;
			//ilumination.specular = 1;
			//ilumination.gloss = 5;

			// TEXTURES
			var propellerMaterial:BitmapMaterial = new BitmapMaterial(_queue.data["propellerTexture"].bitmapData);
			var propellerApp:Appearance = new Appearance(propellerMaterial);
			_propeller.appearance = propellerApp;

			var airplaneMaterial:BitmapMaterial = new BitmapMaterial(_queue.data["airplaneTexture"].bitmapData, new MaterialAttributes(ilumination));
			var airplaneApp:Appearance = new Appearance( airplaneMaterial );
			airplaneApp.lightingEnable = true;
			_airplane.appearance = airplaneApp;

			// GROUP
			_transformGroup = new TransformGroup('airplaneGroup');
			_transformGroup.addChild(_propeller);
			_transformGroup.addChild(_airplane);

			var g:Group = new Group();
			g.addChild(_transformGroup);

			return g;
		}

		/**
		 *	...
		 */
		private function reset():void {
			MonsterDebugger.trace(this, "reset");

			_transformGroup.resetCoords();

			Tweener.addTween(_transformGroup, _airplanePosition);
			enterFrameHandler(null);
		}

		/**
		 *	...
		 *	@param event:Event = null
		 */
		private function enterFrameHandler(event:Event = null):void {
			// Airplane and propeller
			_propeller.rotateZ += 50;

			var lim:Number = _mic.activityLevel;

			// Gravity y
			_airplanePosition.y += (lim-20)/32;

			// Render
			Tweener.addTween(_transformGroup, {y:_airplanePosition.y} );
			_scene.render();
		}

		/**
		 *	...
		 *	@param event:TimerEvent
		 */
		private function moveToSide(event:TimerEvent):void {
			if(_timer.currentCount % 2 == 0) {
				Tweener.addTween(_transformGroup, {x:5, roll:-5, time:5, transition:"easeOutInQuad"} );
			} else {
				Tweener.addTween(_transformGroup, {x:-5, roll:5, time:5, transition:"easeOutInQuad"} );
			}
		}
	}
}

jan 30 2010

Disposição circular de objetos

Fiz um código bem rápido hoje antes de sair do trabalho inspirado nas conversas da semana com os designers sobre cáculo de uma circunferência de um círculo e uma maneira de visualizar as informações através de um círculo.

Círculo

Está ai a lembrancinha.


package br.com.andrecaribe {

	/**
	 *	Realiza o cálculo para posicionar objetos de
	 *	forma circular
	 *
	 *	@author contato [at] andrecaribe [dot] com [dot] br
	 *	@since 2010-01-29
	 *	@version 0
	 *	@revsion 0
	 *	@flashplayer 9
	 */

	import flash.display.Sprite;
	import flash.display.Shape;
	import flash.events.MouseEvent;

	public class Main extends Sprite {

		private var conteiner:Sprite;

		/**
		 *	Método construtor
		 */
		public function Main():void {
			// Altere os valores para obter
			// diferentes resultados.
			create(8, 50, 20);
		}

		/**
		 *	Configura variáveis para a criação dos objetos
		 *	e os adiciona ao palco.
		 *
		 *	@param u:uint número de objetos a serem criados
		 *	@param w:Number largura do objeto criado
		 *	@param h:Number altura do objeto criado
		 */
		private function create(u:uint, w:Number, h:Number):void {
			/// Ângulo que cada objeto deverá ter.
			var degress:Number = (360 / u);
			/// Raio da circunferência
			var r:Number = w * u / (Math.PI< &amp;lt;1);

			conteiner = new Sprite();
			addChild(conteiner);

			for(var i:int = 0; i < u; ++i) {
				conteiner.addChild(draw(r, w, h, degress*i));
			}
			conteiner.x = conteiner.y = r<&amp;lt;1;

			//addEventListener(MouseEvent.CLICK, mouseWheelHandler);
		}

		/**
		 *	Cria objetos.
		 *
		 *	@param r:Number
		 *	@param w:Number
		 *	@param h:Number
		 *	@param d:Number
		 *
		 *	@return Shape
		 */
		private function draw(r:Number, w:Number, h:Number, d:Number):Shape {
			var shape:Shape = new Shape();
			shape.graphics.beginFill(0xFF0000);
			shape.graphics.drawRect(-w>>1, r, w, h);
			shape.graphics.endFill();
			shape.rotation = d;

			this.addChild(shape);

			return shape;
		}

		/**
		 *	...
		 *	@param event:MouseEvent
		 */
		private function mouseWheelHandler(event:MouseEvent):void {
			// TODO
		}
	}
}

Download dos arquivos aqui.


set 18 2009

Funções para cálculo distância

Uma grande discussão sobre o algoritmo A* (a-estrela) nas aulas sobre inteligência artificial na faculdade me instigou a conhecer o seu perfeito funcionamento e suas aplicabilidades.  Não entrarei no mérito do A* neste momento, farei em outro artigo, mas é importante frisar que ele é composto por duas funções, uma determinística e outra heurística, para assim realizar a sua função obtendo sempre¹ o melhor resultado.

Serão apresentados neste artigo três funções para cálculo de distância entre dois pontos numa malha quadriculada, estes algoritmos dentre outras aplicações realizam a parte heurística do A* e por isso apresento-os primeiro.

Considerando o problema de encontrar o melhor caminho muito presente em jogos - World of Warcraft, Dungeon Siege, Counter Strike e milhares de outros -, creio que seja interessante uma explicação resumida sobre a implementação destas funções em ActionScript 3.0.

DISTÂNCIA EUCLIDIANA

Creio que a maioria dos visitantes deste blog conheçam o Teorema de Pitágoras, pois é, a Distância Euclidiana se baseia neste teorema, em outras palavras, a distância entre dois pontos é igual raiz quadrada dos quadrados das diferenças nos eixos cartesianos. Ficou confuso? Veja a imagem abaixo que demonstra o resultado.

euclidian

Interessante notar é que este método se aplica em qualquer dimensão, não apenas no universo bidimensional, entretanto é muito custoso a sua execução em computadores por conta da radiciação e exponenciações presentes.

Portanto, apesar de ser muito preciso e fácil de implementar em linguagens de alto nível é muito lento. Não queremos um jogo que trave nas batalhas mais temidas!


public static var size:int = 10;

public static function getDistance(startX:int, startY:int, endX:int, endY:int):Number {
return size * Math.sqrt((startX - endX) * (startX - endX) + (startY - endY) * (startY - endY));
}

DISTÂNCIA MANHATTAN OU POMBALINA

Imagine que você está em Brasília e deseja pegar um táxi do setor dos hotéis para o dos restaurantes, facilmente você poderá saber quanto irá pagar pela corrida, pois a cidade é divida em quarteirões de lado igual. Neste caso não iríamos usar a distância Euclidiana, pois utilizando ela iríamos desconsiderar os prédios e ruas. Pois bem, a Distância Manhattan não despresa, ela realiza mudança de sentido sempre um múltiplos de 90°. Veja a imagem abaixo.

manhattan

Manhattan é fácil de implementar e executa até 2500% mais rápido que a Distância Euclidiana!


public static var size:int = 10;

public static function getDistance(startX:int, startY:int, endX:int, endY:int):int {
return size * (((startX - endX) ^ ((startX - endX) >> 31)) - ((startX - endX) >> 31) + ((startY - endY) ^ ((startY - endY) >> 31)) - ((startY - endY) >> 31));
}

DISTÂNCIA DIAGONAL

A Distância Diagonal é semelhante à Manhattan, só que é possível andar na diagonal de um quadrado da malha suavizando o movimento. Veja a imagem abaixo.

diagonal

A Distância Diagonal é pouco mais complexa que o Manhattan e um pouco mais lenta. Cabe a você escolher a melhor função para obter o melhor resultado esperado.


public static var size:int = 10;
public static var diagonal:int = 14;

public static function getDistance(startX:int, startY:int, endX:int, endY:int):int {
var distanceX:int = (startX - endX ^ (startX - endX >> 31)) - (startX - endX >> 31);
var distanceY:int = (startY - endY ^ (startY - endY >> 31)) - (startY - endY >> 31);
var h_diagonal:int = distanceX < distanceY ? distanceX : distanceY;
return diagonal * h_diagonal + size * (distanceX + distanceY - (h_diagonal << 1 ));
}


NOTAS

¹: obter o melhor resultado depende das funções que serão aplicadas no A*.


EXEMPLO FINAL

O projeto foi criado no Flash Develop 3 e compilado no Flex SDK 3.
Baixe todos os arquivos fonte e binários aqui!


mai 15 2009

Interfaces gráficas flutuantes utilizando o padrão Observer e Tweener

O modismo é criar interfaces gráficas para sites em tela cheia - fullscreen. Estou trabalhando em um site que exige essa funcionalidade de modo que os elementos na interface gráfica distribuam-se corretamente pela tela do usuário de modo a adequar-se a resolução do monitor ou o tamanho da janela do navegador que o flash está sendo executado.

É bem tranquilo tratar essas informações para um, dois, três elementos… Mas para n elementos, como fazer? Neste site podem ser criadas inúmeras janelas, além do habitual menu e imagem de fundo. Ainda é necessário uma série de protótipos até a entrega final do produto e modificar o código em diversos pontos, diversas vezes sem dúvida não é uma boa prática.

A solução que encontrei foi utilizar um padrão de projeto chamado de Observer. Com ele um objeto Subject poderá enviar mensagens para muitos objetos Observers criando uma relação fracamente acoplada para os Observers que poderão ser reutilizados de maneira simples neste ou em outros projetos.

A classe principal enviará informações sobre o tamanho da tela utilizada pelo usuário e notificará os Observers toda vez que o tamanho da tela for alterado pelo usuário.

A classe abstrata ASubject deve conter alguns métodos específicos para notificar os possíveis subjects, bem como anexar subjects e remove-los. Ela será a classe pai do nosso Subject Main.


package {

	import com.adobe.utils.ArrayUtil;
	import flash.display.MovieClip;
	import flash.errors.IllegalOperationError;

	public class ASubject extends MovieClip {

		// Lista com IObservers.
		protected var _observers:Array = new Array();

		// Guarda estado do Subject.
		protected var _state:Object;

		public function get state():Object {
			return _state;
		}

		public function ASubject(c:ASubject):void {
			if (c != this) {
				throw new IllegalOperationError("This class must be overriden");
			}
		}

		/**
		 * Adiciona um objeto IObserver
		 * à lista _observers.
		 * @param o:IObserver
		 */
		public function attach(o:IObserver):void {
			_observers.push(o);
		}

		/**
		 * Remove um objeto IObserver
		 * à lista _observers.
		 * @param o:IObserver
		 */
		public function detach(o:IObserver):void {
			ArrayUtil.removeValueFromArray(_observers, o);
		}

		/**
		 * Notifica objeto IObserver presentes
		 * na lista _observers.
		 * @param s:ASubject
		 */
		public function notify(s:ASubject):void {
			for each(var o in _observers) {
				o.update(this);
			}
		}
	}
}

É necessário criar uma interface para Observer para que o Subject se comunique com ele.


package {

	public interface IObserver {

		/**
		 * Realiza alterações no objeto
		 * que implementa esta interface
		 * e está presente na lista _observers.
		 *
		 * @see ASubject
		 *
		 * @param a:ASubject
		 */
		function update(a:ASubject):void;
	}
}

Criamos um IObserver Menu.


package  {

	import caurina.transitions.*;
	import flash.display.MovieClip;
	import flash.display.Shape;

	public class Menu extends MovieClip implements IObserver {

		private var _background:Shape;

		public function Menu():void {
			draw();
		}

		public function update(a:ASubject):void {
			Tweener.addTween(_background, { x: a.state.width - _background.width - 20,
											y: 20,
											time: 1 } );
		}

		private function draw():void {
			_background = new Shape();
			_background.graphics.beginFill(0x000000);
			_background.graphics.drawRect(0, 0, 180, 250);
			_background.graphics.endFill();
			addChild(_background);
		}
	}
}

Agora anexamos o IObserver Menu ao ASubject Main da nossa aplicação. Note que ASubject é filho de MovieClip, necessário - poderia ser Sprite também - para que nossa classe se torne o Document Class do filme flash.


package {
	import flash.events.Event;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;

	public class Main extends ASubject {

		private var menu:Menu;
		private var footer:Footer;

		public function Main():void {
			super(this);

			menu = new Menu();
			addChild(menu);
			// Adiciona objeto.
			attach(menu);

			stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
			stage.addEventListener(Event.ACTIVATE, activateHandler);
			stage.addEventListener(Event.RESIZE, resizeHandler);
		}

		private function activateHandler(event:Event):void {
			_state = { width: stage.stageWidth, height: stage.stageHeight };
			// Notifica objetos.
			notify(this);
		}

		private function resizeHandler(event:Event):void {
			_state = { width: stage.stageWidth, height: stage.stageHeight };
			// Notifica objetos.
			notify(this);
		}
	}
}

Agora supondo que eu queira adicionar um novo IObject ao meu projeto basta criar uma nova classe que herde a interface de IObject, como por exemplo a classe Footer abaixo.


package  {

	import caurina.transitions.*;
	import flash.display.MovieClip;
	import flash.display.Shape;

	public class Footer extends MovieClip implements IObserver {

		private var _background:Shape;

		public function Footer():void {
			draw();
		}

		public function update(a:ASubject):void {
			Tweener.addTween(_background, { width: a.state.width,
											y: a.state.height -20,
											time: 1} );
		}

		private function draw():void {
			_background = new Shape();
			_background.graphics.beginFill(0xFF0000);
			_background.graphics.drawRect(0, 0, 530, 20);
			_background.graphics.endFill();
			addChild(_background);
		}
	}
}

Agora para integrar o novo objeto ao meu Subject Main basta alterar seu método construtor para:


public function Main():void {
	super(this);

	menu = new Menu();
	addChild(menu);
	// Adiciona objeto.
	attach(menu);

	footer = new Footer();
	addChild(footer);
	// Adiciona objeto.
	attach(footer);

	stage.scaleMode = StageScaleMode.NO_SCALE;
    stage.align = StageAlign.TOP_LEFT;
	stage.addEventListener(Event.ACTIVATE, activateHandler);
	stage.addEventListener(Event.RESIZE, resizeHandler);
}

E sobre o Tweener? Bem, é muito simples de utiliza-lo, existem inúmeros tutorias e uma boa documentação disponível na Internet. Usei ele apenas para deixa-lo mais #cool. Fica aqui como uma dica.

Baixe os arquivos utilizados como exemplo e o resultado final.