I want to manipulate and animate some BitmapData to create a swirl effect such as the one found in the link below:
http://www.flash-filter.net/swirl-effect.phtml
What techniques could I apply?
Btw: Yes, I do know the effect is ugly..
In case you're looking for how a swirl effect is achieved, i.e. the algorithm. Here is a nice step-by-step explanation of how the image is transformed: Image- Twist and Swirl Algorithm
There is a Pixel Bender filter for this. Check out this link:
http://www.adobe.com/cfusion/exchange/index.cfm?event=extensionDetail&extid=1536021
Here is a tutorial on Tweening Pixel Bender filters
http://plasticsturgeon.com/2011/03/pixel-transition-tweening-a-pixel-bender-filter-in-as3/
You may also want to check out the AS3 ImageProcessing library
http://blog.joa-ebert.com/imageprocessing-library/
This should give you a good starting point
Pixel Bender ist krieg!
While I do agree with the previous answers, I'd like to point out, that you can do this kind of effect by using only bitmap filters that are out there for years now: DisplacementMapFilter namely. Create a displacement map that moves pixels in a circular direction and apply this map to an image multiple times. This will get you a swirling transformation.
Here's my simple implementation.
The usage is pretty straightforward (see after the class).
package org.noregret.images
{
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.display.BlendMode;
import flash.display.DisplayObject;
import flash.display.GradientType;
import flash.display.InterpolationMethod;
import flash.display.SpreadMethod;
import flash.display.Sprite;
import flash.filters.DisplacementMapFilter;
import flash.filters.DisplacementMapFilterMode;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* #author Nox Noctis (http://noregret.org)
*/
public class Swirl
{
public var target : DisplayObject;
public var allowCache : Boolean = false;
public var levels : uint;
public var isDestroyed : Boolean;
protected var bitmapMargin : Point;
protected var filter : DisplacementMapFilter;
protected var radius : int;
protected var cache : Object;
protected var map : BitmapData;
protected var targetRect : Rectangle;
protected var mapOffset : Point;
protected var maxLevel : Number = 1;
public function Swirl(_target : DisplayObject, _filterLevels : uint = 10, _allowCache : Boolean = true)
{
target = _target;
allowCache = _allowCache;
levels = _filterLevels;
cache = {};
filter = new DisplacementMapFilter();
filter.componentX = BitmapDataChannel.RED;
filter.componentY = BitmapDataChannel.GREEN;
filter.scaleX = -20;
filter.scaleY = -20;
filter.mapPoint = new Point();
filter.mode = DisplacementMapFilterMode.IGNORE;
}
private function createDisplacementMap() : void
{
targetRect = target.getRect(target);
radius = Math.max(Math.max(targetRect.width, targetRect.height), 100) / 2;
radius = Math.sqrt(2) * radius;
mapOffset = new Point(radius - targetRect.width / 2, radius - targetRect.height / 2);
var mapSprite : Sprite = new Sprite();
var redLayer : Sprite = new Sprite();
var greenLayer : Sprite = new Sprite();
var grayLayer : Sprite = new Sprite();
mapSprite.addChild(redLayer);
mapSprite.addChild(greenLayer);
mapSprite.addChild(grayLayer);
var gradientMatrix : Matrix;
gradientMatrix = new Matrix();
gradientMatrix.createGradientBox(radius * 2, radius * 2, Math.PI / 2, -radius, -radius);
redLayer.graphics.lineStyle(0, 0, 0);
redLayer.graphics.beginGradientFill(GradientType.LINEAR, [0xFF0000, 0], [100, 100], [0, 255], gradientMatrix, SpreadMethod.PAD, InterpolationMethod.RGB);
redLayer.graphics.drawCircle(0, 0, radius);
redLayer.graphics.endFill();
greenLayer.graphics.lineStyle(0, 0, 0);
gradientMatrix.createGradientBox(radius * 2, radius * 2, 0, -radius, -radius);
greenLayer.graphics.beginGradientFill(GradientType.LINEAR, [0x00FF00, 0x00FF00], [0, 100], [10, 245], gradientMatrix, SpreadMethod.PAD, InterpolationMethod.RGB);
greenLayer.graphics.drawCircle(0, 0, radius);
greenLayer.graphics.endFill();
greenLayer.blendMode = BlendMode.ADD;
gradientMatrix = new Matrix();
gradientMatrix.createGradientBox(radius * 2, radius * 2, 0, -radius, -radius);
grayLayer.graphics.lineStyle(0, 0, 0);
grayLayer.graphics.beginGradientFill(GradientType.RADIAL, [0x808080, 0x808080], [0, 100], [0, 0xFF], gradientMatrix, SpreadMethod.PAD, InterpolationMethod.RGB);
grayLayer.graphics.drawCircle(0, 0, radius);
grayLayer.graphics.endFill();
var rect : Rectangle = mapSprite.getRect(mapSprite);
var matrix : Matrix = new Matrix();
matrix.translate(-rect.x, -rect.y);
if (map) {
map.dispose();
}
map = new BitmapData(rect.width, rect.height, false, 0xFF808080);
map.draw(mapSprite, matrix);
filter.mapBitmap = map;
}
public function swirlTo(ratio : Number) : BitmapData
{
if (isDestroyed) {
trace("Swirl: error! Tried to swirl on disposed item.");
return null;
}
if (ratio < 0) {
ratio = 0;
}
var level : uint = Math.round(levels * ratio);
var cacheName : String = getCacheName(level);
if (cache[cacheName]) {
return (cache[cacheName] as BitmapData).clone();
}
var rect : Rectangle = target.getRect(target);
if (!map || rect.width != targetRect.width || rect.height != targetRect.height) {
createDisplacementMap();
flushCache();
}
var point : Point = new Point(-targetRect.x, -targetRect.y);
bitmapMargin = new Point(point.x + mapOffset.x, point.y + mapOffset.y);
var bmp : BitmapData;
if (cache["l" + maxLevel]) {
bmp = cache["l" + maxLevel] as BitmapData;
} else {
bmp = new BitmapData(map.width, map.height, true, 0);
var matrix : Matrix = new Matrix();
matrix.translate(bitmapMargin.x, bitmapMargin.y);
bmp.draw(target, matrix, null, null, null, true);
}
if (level == 0) {
cache[cacheName] = bmp.clone();
return bmp;
}
var destPoint : Point = new Point();
for (var i : Number = maxLevel;i <= level; i++) {
bmp.applyFilter(bmp, bmp.rect, destPoint, filter);
if (allowCache) {
cache["l" + i] = bmp.clone();
}
}
maxLevel = Math.max(maxLevel, level);
return bmp;
}
private function getCacheName(level : uint) : String
{
return "l" + level;
}
public function flushCache() : void
{
for each (var bmp:BitmapData in cache) {
bmp.dispose();
}
cache = {};
}
public function destroy() : void
{
flushCache();
target = null;
map.dispose();
map = null;
isDestroyed = true;
}
}
}
Usage example:
package
{
import flash.display.Sprite;
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.display.Bitmap;
import org.noregret.images.Swirl;
[SWF(width="800",height="600",backgroundColor="#FFFFFF",fps="30")]
public class TestSwirl extends Sprite
{
private const loader:Loader = new Loader();
private const swirlBitmap:Bitmap = new Bitmap();
private var swirl:Swirl;
private var time:Number = 0;
public function TestSwirl()
{
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
var request:URLRequest = new URLRequest("http://i.stack.imgur.com/Vtsvm.gif");
loader.load(request, new LoaderContext(true));
}
protected function onLoadComplete(event:Event):void
{
var original:Bitmap = loader.content as Bitmap;
addChild(original);
swirlBitmap.bitmapData = original.bitmapData.clone();
swirlBitmap.x = original.x + original.width + 10;
addChild(swirlBitmap);
swirl = new Swirl(original,80);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
protected function onEnterFrame(event:Event):void
{
var ratio:Number = Math.abs(Math.sin(time));
// ***
swirlBitmap.bitmapData = swirl.swirlTo(ratio);
// ***
time += .02;
}
}
}
I would suggest using Pixel Bender to create your own bitmap filters.
Related
Context: For a legacy Flex/Actionscript drawing app, I need to add scaling of simple symbols. The app uses the Graffiti lib for drawing and the resulting shape data is stored as GraphicsPathCommands serialized to XML using the Degrafa lib for save and reload. I need to enable the user to scale these graphics and then get updated path data which can be serialized. The symbols are simple but more complicated than simple geometry. For example:
Question: I converted the SVG data for this symbol to Actionscript GraphicsPathCommands and am able to draw it, and of course translation is easy – but I don't know how I would scale it, given a bounding box defined by a user dragging out a marquee rectangle in the app.
Does anyone know of either an Actionscript way of transforming the command data, or a Javascript snippet for scaling SVG which I can port to Actionscript?
For reference, an example of the Actionscript GraphicsPathCommands for drawing a star is below.
public function DrawPathExample()
{
var star_commands:Vector.<int> = new Vector.<int>(5, true);
star_commands[0] = GraphicsPathCommand.MOVE_TO;
star_commands[1] = GraphicsPathCommand.LINE_TO;
star_commands[2] = GraphicsPathCommand.LINE_TO;
star_commands[3] = GraphicsPathCommand.LINE_TO;
star_commands[4] = GraphicsPathCommand.LINE_TO;
var star_coord:Vector.<Number> = new Vector.<Number>(10, true);
star_coord[0] = 66; //x
star_coord[1] = 10; //y
star_coord[2] = 23;
star_coord[3] = 127;
star_coord[4] = 122;
star_coord[5] = 50;
star_coord[6] = 10;
star_coord[7] = 49;
star_coord[8] = 109;
star_coord[9] = 127;
graphics.beginFill(0x003366);
graphics.drawPath(star_commands, star_coord);
}
Solution
A full solution for interactively scaling GraphicsPathCommand data is below. The path data was derived from an SVG put through this SVGParser. It generates path drawing commands in the form of graphics.lineTo(28.4,16.8);. A couple of utility functions separate the data from the commands and store them in Vectors so the data can be serialized. I don't need to use arbitrary SWGs so I just hardcoded the data.
package classes
{
import flash.display.GraphicsPathCommand;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
public class DrawSVG extends Sprite
{
private var startPt:Point = new Point();
private var selectRect:Rectangle = new Rectangle();
private var viewBox:Rectangle = new Rectangle();
protected var commands:Vector.<int> = new Vector.<int>();
protected var drawingData:Vector.<Number> = new Vector.<Number>();
protected var sourceDrawingData:Vector.<Number> = new Vector.<Number>();
public function DrawSVG()
{
super();
this.addEventListener(Event.ADDED_TO_STAGE, setup);
setupWomanData();
}
private function setup(event:Event):void
{
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
private function onMouseDown(event:MouseEvent):void
{
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
this.graphics.clear();
// offset so graphic draws centered on click point
startPt = new Point(event.stageX - (viewBox.width /2), event.stageY - (viewBox.height /2));
selectRect = new Rectangle(startPt.x, startPt.y, viewBox.width, viewBox.height);
var kx:Number = selectRect.width / (viewBox.width);
var ky:Number = selectRect.height / (viewBox.height);
var scaleFactor:Number = kx < ky ? kx : ky;
drawSymbol(scaleFactor);
this.graphics.lineStyle(1, 0x000000);
this.graphics.drawRect(selectRect.x, selectRect.y, selectRect.width, selectRect.height);
}
private function onMouseMove(event:MouseEvent):void
{
selectRect.width = Math.max(viewBox.width, Math.abs(event.stageX - startPt.x));
selectRect.height = Math.max(viewBox.height, Math.abs(event.stageY - startPt.y));
var kx:Number = selectRect.width / (viewBox.width);
var ky:Number = selectRect.height / (viewBox.height);
var scaleFactor:Number = kx < ky ? kx : ky;
this.graphics.clear();
drawSymbol(scaleFactor);
this.graphics.lineStyle(1, 0x000000);
this.graphics.drawRect(selectRect.x, selectRect.y, viewBox.width * scaleFactor, viewBox.height * scaleFactor);
}
private function onMouseUp(event:MouseEvent):void
{
stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
this.graphics.clear();
createSprite(commands, drawingData);
}
private function drawSymbol(toScale:Number):void
{
drawingData.length = 0;
for (var i:int = 0; i < sourceDrawingData.length; i++) {
drawingData[i] = Math.max(sourceDrawingData[i], sourceDrawingData[i] * toScale);
drawingData[i] += i % 2 == 0 ? startPt.x : startPt.y ;
}
this.graphics.clear();
this.graphics.lineStyle();
this.graphics.beginFill(0xff0000);
this.graphics.drawPath(commands, drawingData);
this.graphics.endFill();
}
private function createSprite(command:Vector.<int>, coord:Vector.<Number>):Shape{
var s:Shape = new Shape();
addChild(s);
s.graphics.beginFill(0xff);
s.graphics.drawPath(command, coord);
s.graphics.endFill();
return s;
}
private function setupWomanData():void
{
commands = new Vector.<int>();
drawingData = new Vector.<Number>();
viewBox= new Rectangle(0, 0, 24.629, 52.336);
addMoveToCmd(12.31,10.3);
addCurveToCmd(13.37,10.3,14.3,9.89);
addCurveToCmd(15.24,9.48,15.94,8.78);
addCurveToCmd(16.64,8.08,17.05,7.14);
addCurveToCmd(17.46,6.2,17.46,5.15);
addCurveToCmd(17.46,4.1,17.05,3.16);
addCurveToCmd(16.64,2.23,15.94,1.52);
addCurveToCmd(15.24,0.82,14.3,0.41);
addCurveToCmd(13.37,0,12.31,0);
addCurveToCmd(11.26,0,10.33,0.41);
addCurveToCmd(9.39,0.82,8.69,1.52);
addCurveToCmd(7.98,2.23,7.57,3.16);
addCurveToCmd(7.16,4.1,7.16,5.15);
addCurveToCmd(7.16,6.2,7.57,7.14);
addCurveToCmd(7.98,8.08,8.69,8.78);
addCurveToCmd(9.39,9.48,10.33,9.89);
addCurveToCmd(11.26,10.3,12.31,10.3);
addLineToCmd(12.314,10.304);
addMoveToCmd(24.6,26.36);
addLineToCmd(20.7,12.77);
addCurveToCmd(20.62,12.3,20.39,11.91);
addCurveToCmd(20.15,11.51,19.81,11.23);
addCurveToCmd(19.47,10.94,19.04,10.78);
addCurveToCmd(18.61,10.62,18.14,10.62);
addLineToCmd(6.49,10.62);
addCurveToCmd(6.02,10.62,5.59,10.78);
addCurveToCmd(5.16,10.94,4.82,11.23);
addCurveToCmd(4.48,11.51,4.24,11.91);
addCurveToCmd(4.01,12.3,3.93,12.77);
addLineToCmd(0.03,26.36);
addCurveToCmd(0.01,26.4,0.01,26.45);
addCurveToCmd(-0.01,26.5,-0.01,26.55);
addCurveToCmd(0.01,26.6,0.01,26.65);
addCurveToCmd(0.02,26.69,0.03,26.74);
addCurveToCmd(-0.15,27.95,0.55,28.69);
addCurveToCmd(1.25,29.44,2.2,29.6);
addCurveToCmd(3.15,29.77,4.05,29.3);
addCurveToCmd(4.95,28.84,5.17,27.63);
addLineToCmd(6.85,21.37);
addLineToCmd(4.07,34.88);
addCurveToCmd(3.81,35.51,3.91,36.15);
addCurveToCmd(4,36.78,4.35,37.3);
addCurveToCmd(4.7,37.81,5.26,38.13);
addCurveToCmd(5.81,38.45,6.49,38.45);
addLineToCmd(6.78,38.45);
addLineToCmd(6.78,49.72);
addCurveToCmd(6.78,50.99,7.59,51.62);
addCurveToCmd(8.41,52.25,9.39,52.25);
addCurveToCmd(10.37,52.25,11.19,51.62);
addCurveToCmd(12,50.99,12,49.72);
addLineToCmd(12,38.45);
addLineToCmd(12.63,38.45);
addLineToCmd(12.63,49.72);
addCurveToCmd(12.63,50.99,13.44,51.62);
addCurveToCmd(14.26,52.25,15.24,52.25);
addCurveToCmd(16.22,52.25,17.04,51.62);
addCurveToCmd(17.85,50.99,17.85,49.72);
addLineToCmd(17.85,38.45);
addLineToCmd(18.14,38.45);
addCurveToCmd(18.82,38.45,19.38,38.13);
addCurveToCmd(19.93,37.81,20.28,37.3);
addCurveToCmd(20.63,36.78,20.72,36.14);
addCurveToCmd(20.81,35.51,20.56,34.87);
addLineToCmd(17.78,21.37);
addLineToCmd(19.45,27.58);
addCurveToCmd(19.67,28.79,20.57,29.27);
addCurveToCmd(21.47,29.75,22.43,29.6);
addCurveToCmd(23.38,29.45,24.08,28.7);
addCurveToCmd(24.78,27.96,24.6,26.74);
addCurveToCmd(24.61,26.69,24.62,26.65);
addCurveToCmd(24.63,26.6,24.63,26.55);
addCurveToCmd(24.63,26.5,24.62,26.45);
addCurveToCmd(24.62,26.4,24.6,26.36);
addLineToCmd(24.601,26.356);
}
protected function addCurveToCmd(p1:Number, p2:Number, p3:Number, p4:Number):void
{
commands.push(GraphicsPathCommand.CURVE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
sourceDrawingData.push(p3);
sourceDrawingData.push(p4);
}
protected function addMoveToCmd(p1:Number, p2:Number):void
{
commands.push(GraphicsPathCommand.MOVE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
}
protected function addLineToCmd(p1:Number, p2:Number):void
{
commands.push(GraphicsPathCommand.LINE_TO);
sourceDrawingData.push(p1);
sourceDrawingData.push(p2);
}
}
}
Seems like there is a pretty straightforward way to do this. It looks like the only thing to scale are the coordinates themselves, so you may just apply a scale factor.
Based on your example:
public function ASEntryPoint() {
var star_commands:Vector.<int> = new Vector.<int>(5, true);
star_commands[0] = GraphicsPathCommand.MOVE_TO;
star_commands[1] = GraphicsPathCommand.LINE_TO;
star_commands[2] = GraphicsPathCommand.LINE_TO;
star_commands[3] = GraphicsPathCommand.LINE_TO;
star_commands[4] = GraphicsPathCommand.LINE_TO;
var star_coord:Vector.<Number> = new Vector.<Number>(10, true);
star_coord[0] = 66; //x
star_coord[1] = 10; //y
star_coord[2] = 23;
star_coord[3] = 127;
star_coord[4] = 122;
star_coord[5] = 50;
star_coord[6] = 10;
star_coord[7] = 49;
star_coord[8] = 109;
star_coord[9] = 127;
//reference shape to detect initial size
var s:Shape = shapeInRect(star_commands, star_coord);
var bounds:Rectangle = s.getBounds(s);
s.graphics.lineStyle(1);
s.graphics.drawRect(bounds.x, bounds.y, bounds.width, bounds.height);
addChild(s);
//fit to target
var targetSize:Rectangle = new Rectangle(150, 100, 75, 60);
//detect lesser factor - assuming you need to preserve proportions
var kx:Number = targetSize.width / (bounds.width);
var ky:Number = targetSize.height / (bounds.height);
var toUse:Number = kx < ky ? kx : ky;
//apply to coords
for (var i:int = 0; i < star_coord.length; i++) {
//size
star_coord[i] *= toUse;
//fix initial offset
star_coord[i] -= i % 2 == 0 ? bounds.x * toUse : bounds.y * toUse;
}
//draw
addChild(shapeInRect(star_commands, star_coord, targetSize));
}
private function shapeInRect(command:Vector.<int>, coord:Vector.<Number>, rect:Rectangle = null):Shape{
var s:Shape = new Shape();
addChild(s);
s.graphics.beginFill(0x003366);
s.graphics.drawPath(command, coord);
s.graphics.endFill();
if (rect){
s.graphics.lineStyle(1);
s.graphics.drawRect(0, 0, rect.width, rect.height);
s.x = rect.x;
s.y = rect.y;
}
return s;
}
I have this code that gives the b2Body an impulse towards the cursor when you click on stage.
private function clicked(e:MouseEvent):void
{
var impulse_x:Number = (mouseX / world_scale - mainChar.GetPosition().x);
var impulse_y:Number = (mouseY / world_scale - mainChar.GetPosition().y);
var impulse:b2Vec2 = new b2Vec2(impulse_x, impulse_y);
mainChar.ApplyImpulse(impulse, mainChar.GetPosition());
}
I was trying to use keyboard right arrow key for movement and so in the update function I added the if key[Keyboard.RIGHT]:
private function update(e:Event):void {
world.Step (1 / 30, 10, 10);
if (key[Keyboard.RIGHT]) {
impulse_x = 3;
mainChar.ApplyImpulse(impulse, mainChar.GetPosition());
}
}
private function onKeyUp(e:KeyboardEvent):void
{
key[e.keyCode] = false;
}
private function onKeyDown(e:KeyboardEvent):void
{
key[e.keyCode] = true;
}
But the b2body goes to top left on the screen and stays there and doesn't move. I am changing the impulse value that then goes into the vector that is applied to the body.
Although it doesn't give me any errors.
EDIT: The whole code!
package
{
import Box2D.Collision.Shapes.b2PolygonShape;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2FixtureDef;
import Box2D.Dynamics.b2World;
import Box2D.Dynamics.Contacts.b2ContactEdge;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;
public class Main extends Sprite
{
public var world:b2World = new b2World(new b2Vec2(0, 0), true);
public var world_scale:Number = 30;
public var mainChar:b2Body;
private var ground:b2Body;
public var keys:Array = [];
public var beingPressed:Boolean;
public var impulse_x:Number;
public var impulse_y:Number;
public var impulse:b2Vec2 = new b2Vec2(impulse_x, impulse_y);
public function Main():void
{
createMainCharacter(stage.stageWidth / 2, stage.stageHeight / 2, 50, 100);
var th:uint = 10;
addEventListener(Event.ENTER_FRAME, update);
//stage.addEventListener(MouseEvent.CLICK, clicked);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
/**private function clicked(e:MouseEvent):void
{
var impulse_x:Number = (mouseX / world_scale - mainChar.GetPosition().x);
var impulse_y:Number = (mouseY / world_scale - mainChar.GetPosition().y);
var impulse:b2Vec2 = new b2Vec2(impulse_x, impulse_y);
mainChar.ApplyImpulse(impulse, mainChar.GetPosition());
}**/
private function update(e:Event):void
{
world.Step (1 / 30, 10, 10);
var temp:Sprite;
for (var body:b2Body = world.GetBodyList(); body != null; body = body.GetNext())
{
if (body.GetUserData())
{
temp = body.GetUserData() as Sprite;
temp.x = body.GetPosition().x * world_scale;
temp.y = body.GetPosition().y * world_scale;
temp.rotation = body.GetAngle() * (180 / Math.PI); // radians to degrees
}
}
if (keys[Keyboard.RIGHT])
{
impulse_x = 3;
impulse_y = 0;
mainChar.ApplyImpulse(impulse, mainChar.GetPosition());
} else {
impulse_x = 0;
impulse_y = 0;
}
}
private function createMainCharacter(x:Number, y:Number, width:Number, height:Number):void
{
var mainCharSprite:Sprite = new Sprite();
mainCharSprite.graphics.beginFill(0x000000);
mainCharSprite.graphics.drawRect( -width / 2, -height / 2 , width, height);
mainCharSprite.graphics.endFill;
mainCharSprite.x = x;
mainCharSprite.y = y;
addChild(mainCharSprite);
var mainCharDef:b2BodyDef = new b2BodyDef();
mainCharDef.userData = mainCharSprite;
mainCharDef.type = b2Body.b2_dynamicBody;
mainCharDef.position.Set (x / world_scale, y / world_scale);
mainChar = world.CreateBody(mainCharDef);
var shape:b2PolygonShape = new b2PolygonShape();
shape.SetAsBox((width / 2) / world_scale, (height / 2) / world_scale);
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.shape = shape;
fixtureDef.restitution = 0.1;
fixtureDef.friction = 0.1;
fixtureDef.density = 0.5;
mainChar.CreateFixture(fixtureDef);
}
private function onKeyUp(e:KeyboardEvent):void
{
keys[e.keyCode] = false;
}
private function onKeyDown(e:KeyboardEvent):void
{
keys[e.keyCode] = true;
}
}
}
Changing the values of impulse_x and impulse_y is not going to update the values used by the b2Vec2 impulse. In AS3 primitives are passed by value not by reference. If you want the b2Vec2 object referenced by the variable impulse to change its values you need to change them directly on the object itself:
impulse.x = 3.0;
impulse.y = 0.0;
mainChar.ApplyImpulse(impulse, mainChar.GetPosition());
I have created a soft body circle in nape. And now I'm trying to get it texturized. But I'm having trouble, and I can't find the answer. Thats why I'm turning to you guys.
Im trying to do what he is doing in this objective c tutorial:
http://www.uchidacoonga.com/2012/04/soft-body-physics-with-box2d-and-cocos2d-part-44/
any ideas on how to do this with starling and stage3d?
You have to write a custom display object (see Starling manual). Here's a basic example:
package
{
import com.adobe.utils.AGALMiniAssembler;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.VertexBuffer3D;
import flash.geom.Point;
import flash.utils.ByteArray;
import starling.core.RenderSupport;
import starling.core.Starling;
import starling.display.DisplayObject;
import starling.errors.MissingContextError;
import starling.textures.Texture;
public class Ball extends DisplayObject
{
private static const PROGRAM_NAME:String = "ball";
private var _texture:Texture;
private var _numSides:uint;
private static const data32PerVertex:uint = 4;
private var _vertices:Vector.<Number>;
private var _indices:Vector.<uint>;
private var _vertexBuffer:VertexBuffer3D;
private var _indexBuffer:IndexBuffer3D;
public function Ball(initialX:Number, initialY:Number, initialR:Number, texture:Texture, numSides:uint = 10) {
_texture = texture;
_numSides = numSides;
// if the texture is a SubTexture (i.e. a texture from an atlas), then you need
// to modify these values to match the sub-texture UV bounds.
var minU:Number = 0, minV:Number = 0, maxU:Number = 1, maxV:Number = 1;
setupGeometry(initialX, initialY, initialR, minU, minV, maxU, maxV);
createBuffers();
registerPrograms();
}
private function setupGeometry(initialX:Number, initialY:Number, initialR:Number, uMin:Number, vMin:Number, uMax:Number, vMax:Number):void {
const numVertices:uint = _numSides + 1,
numSideVertices:uint = _numSides,
txtCu:Number = (uMin + uMax) / 2, // center of the circle in UV coords
txtCv:Number = (vMin + vMax) / 2,
txtRu:Number = uMax - txtCu, // radiuses of the circle in UV coords
txtRv:Number = vMax - txtCv;
_vertices = new Vector.<Number>(data32PerVertex * numVertices, true);
_indices = new Vector.<uint>(3 * _numSides, true);
var centerVectexIndex:uint = _numSides;
// side vertices
for (var sideVertexI:uint = 0; sideVertexI < numSideVertices; ++sideVertexI) {
var dataOffset:uint = sideVertexI * data32PerVertex,
angle:Number = 2 * Math.PI * sideVertexI / _numSides,
sinA:Number = Math.sin(angle),
cosA:Number = Math.cos(angle);
_vertices[dataOffset ] = initialX + initialR * cosA; // x
_vertices[dataOffset + 1] = initialY + initialR * sinA; // y
_vertices[dataOffset + 2] = txtCu + txtRu * cosA; // u
_vertices[dataOffset + 3] = txtCv + txtRv * sinA; // v
var indexOffset:uint = 3 * sideVertexI;
_indices[indexOffset ] = centerVectexIndex;
_indices[indexOffset + 1] = sideVertexI;
_indices[indexOffset + 2] = (sideVertexI + 1) % numSideVertices;
}
// center vertex
dataOffset = centerVectexIndex * data32PerVertex;
_vertices[dataOffset ] = initialX; // x
_vertices[dataOffset + 1] = initialY; // y
_vertices[dataOffset + 2] = txtCu; // u
_vertices[dataOffset + 3] = txtCv; // v
}
private function createBuffers():void {
var context:Context3D = Starling.context;
if (context == null) {
throw new MissingContextError();
}
_vertexBuffer && _vertexBuffer.dispose();
_indexBuffer && _indexBuffer.dispose();
const verticesCount:uint = _numSides + 1;
_vertexBuffer = context.createVertexBuffer(verticesCount, 4);
_vertexBuffer.uploadFromVector(_vertices, 0, verticesCount);
const indicesCount:uint = 3 * _numSides; // _numSides triangles, 3 indices per each triangle
_indexBuffer = context.createIndexBuffer(indicesCount);
_indexBuffer.uploadFromVector(_indices, 0, indicesCount);
}
private function registerPrograms():void {
var starling:Starling = Starling.current;
if (starling.hasProgram(PROGRAM_NAME)) {
return;
}
// va0.xy - position
// va1.xy - UV coords
// vc0-vc3 - mvp matrix
var vertexAGAL:String =
"m44 op, va0, vc0 \n" +
"mov v0, va1";
var fragmentAGAL:String =
"tex oc, v0, fs0 <2d, clamp, linear, mipnone> \n"; // just sample texture color
var asm:AGALMiniAssembler = new AGALMiniAssembler(),
vertexBytecode:ByteArray = asm.assemble(Context3DProgramType.VERTEX, vertexAGAL),
fragmentBytecode:ByteArray = asm.assemble(Context3DProgramType.FRAGMENT, fragmentAGAL);
starling.registerProgram(PROGRAM_NAME, vertexBytecode, fragmentBytecode);
}
override public function render(support:RenderSupport, parentAlpha:Number):void {
var context:Context3D = Starling.context;
if (context == null) {
throw new MissingContextError();
}
support.finishQuadBatch();
// setup
support.applyBlendMode(_texture.premultipliedAlpha);
context.setProgram(Starling.current.getProgram(PROGRAM_NAME));
context.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_2); // position, va0
context.setVertexBufferAt(1, _vertexBuffer, 2, Context3DVertexBufferFormat.FLOAT_2); // uv, va1
context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, support.mvpMatrix3D, true); // mvp matrix, vc0-vc3
context.setTextureAt(0, _texture.base); // texture, fs0
// draw
context.drawTriangles(_indexBuffer);
support.raiseDrawCount();
// clean up
context.setVertexBufferAt(0, null);
context.setVertexBufferAt(1, null);
context.setTextureAt(0, null);
}
override public function hitTest(localPoint:Point, forTouch:Boolean = false):DisplayObject {
var isHit:Boolean = false;
// to achieve proper mouse handling, you need to place here the code
// that checks if localPoint is contained inside any of triangles and
// sets isHit flag accorgingly.
return isHit ? this : null;
}
}
}
Usage example:
package
{
import flash.display.BitmapData;
import flash.display.GradientType;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.geom.Matrix;
import starling.display.Sprite;
import starling.textures.Texture;
public class BallExperiment extends starling.display.Sprite
{
public function BallExperiment() {
}
public function start():void {
const numSides:uint = 7;
var txt:Texture = createBallTxt(numSides, true);
var ball:Ball = new Ball(200, 200, 50, txt, numSides);
addChild(ball);
}
private function createBallTxt(numSides:uint, debugFillBcgr:Boolean = false):Texture {
var canvas:flash.display.Sprite = new flash.display.Sprite(),
g:Graphics = canvas.graphics;
// as we don't want to use sub-textures in this simple example, we need this
// number to be a power of two: otherwise Starling will internally create
// a power-of-two-sized texture and return a sub-texture of this bigger texture.
const size:Number = 512;
// we need to make the radius of a ball texture to be smaller than size/2 in order
// to prevent the texture from extending beyond our triangles.
var rScale:Number = Math.cos(Math.PI / numSides),
r:Number = rScale * (size / 2);
g.lineStyle(0, 0, 0);
// draw uniform background to show actual triangulation
if (debugFillBcgr) {
g.beginFill(0xBB4400, 0.2);
g.drawRect(0, 0, size, size);
g.endFill();
}
// draw the ball
g.beginFill(0x0000DD);
g.drawCircle(size / 2, size / 2, r);
var m:Matrix = new Matrix();
m.createGradientBox(size, size);
g.beginGradientFill(GradientType.LINEAR, [0x00DD00, 0x00DD00], [0, 1], [0, 255], m);
g.drawCircle(size / 2, size / 2, r);
g.endFill();
const smallCircleR:Number = r / 10,
smallCircleCR:Number = r - 2 * smallCircleR;
g.beginFill(0xBB0000);
for (var i:uint = 0; i < numSides; ++i) {
var angle:Number = 2 * Math.PI * i / numSides,
cx:Number = size / 2 + smallCircleCR * Math.cos(angle),
cy:Number = size / 2 + smallCircleCR * Math.sin(angle);
g.drawCircle(cx, cy, smallCircleR);
}
g.drawCircle(size / 2, size / 2, smallCircleR);
g.endFill();
// create and return the texture
var bmd:BitmapData = new BitmapData(size, size, true, 0);
bmd.draw(canvas);
return Texture.fromBitmapData(bmd);
}
}
}
Example runner:
package
{
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import starling.core.Starling;
import starling.events.Event;
[SWF(width = 600, height = 500, frameRate = 60)]
public class StarlingTestRunner extends Sprite
{
public function StarlingTestRunner() {
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
Starling.multitouchEnabled = false;
Starling.handleLostContext = false;
var starling:Starling = new Starling(BallExperiment, stage);
starling.showStats = true;
starling.simulateMultitouch = true;
starling.enableErrorChecking = true;
starling.addEventListener(Event.ROOT_CREATED, onTestCreated);
starling.start();
}
private function onTestCreated(e:Event, test:BallExperiment):void {
test.start();
}
}
}
The result:
To distort the ball, just modify those elements of _vertices vector that correspond to x and y coordinates (i.e. elements with indices 4n and 4n + 1, where n = 0 .. numSides) and then re-upload _vertices array to the vertex buffer.
Alternatively, you can implement ball geometry using VertexData helper class, as shown in Starling manual.
I am trying to follow a couple of tutorials on Box2D, but somehow I can't seem to get the gravity to be applied. Below is my code (with the URLs that I read for tutorials), maybe there is something I overlooked? Thanks for your help!
package
{
import Box2D.Collision.Shapes.b2PolygonShape;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2DebugDraw;
import Box2D.Dynamics.b2FixtureDef;
import Box2D.Dynamics.b2World;
import org.osflash.signals.natives.NativeSignal;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
/*
* http://active.tutsplus.com/tutorials/games/introduction-to-box2d-for-flash-and-as3/
* http://blog.allanbishop.com/box2d-2-1a-tutorial-part-1/
* http://plasticsturgeon.com/2010/08/making-an-as3-game-in-box2d-flash-version-2-0-hello-world-box2d/
*/
[SWF(backgroundColor="#cccccc", frameRate="30", width="1280", height="720")]
public class Floor extends Sprite
{
private const WIDTH :uint = 1280;
private const HEIGHT :uint = 720;
private const FPS :uint = 30;
private const NUM_PIXELS_PER_METER :uint = 30;
private const TIMESTEP :Number = 0 / NUM_PIXELS_PER_METER;
private const VELOCITY_ITERATIONS :uint = 6;
private const POSITION_ITERATIONS :uint = 2;
private var _world :b2World;
private var _gravity :b2Vec2;
private var _updated :NativeSignal;
public function Floor()
{
init();
}
private function init():void
{
initStage();
initBox2D();
_updated = new NativeSignal ( this,
Event.ENTER_FRAME,
Event
);
_updated.add(onUpdated);
}
private function initStage():void
{
stage.frameRate = FPS;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
}
private function initBox2D():void
{
_gravity = new b2Vec2(0, 10);
_world = new b2World(_gravity, true);
var vector:b2Vec2 = new b2Vec2();
// floor
vector.x = (WIDTH * 0.5) / NUM_PIXELS_PER_METER;
vector.y = (HEIGHT * 0.75) / NUM_PIXELS_PER_METER;
var floorBodyDef:b2BodyDef = new b2BodyDef();
floorBodyDef.position.Set(vector.x, vector.y);
var floorBody:b2Body = _world.CreateBody(floorBodyDef);
vector.x = (WIDTH * 0.5) / NUM_PIXELS_PER_METER;
vector.y = 32 / NUM_PIXELS_PER_METER;
var floorBox:b2PolygonShape = new b2PolygonShape();
floorBox.SetAsBox(vector.x, vector.y);
var floorFixture:b2FixtureDef = new b2FixtureDef();
floorFixture.shape = floorBox;
floorFixture.density = 1;
floorFixture.friction = 1;
floorBody.CreateFixture(floorFixture);
// box
vector.x = (WIDTH * 0.5) / NUM_PIXELS_PER_METER;
vector.y = (HEIGHT * 0.125) / NUM_PIXELS_PER_METER;
var boxBodyDef:b2BodyDef = new b2BodyDef();
boxBodyDef.type = b2Body.b2_dynamicBody;
boxBodyDef.position.Set(vector.x, vector.y);
var boxBody:b2Body = _world.CreateBody(boxBodyDef);
var box:b2PolygonShape = new b2PolygonShape();
box.SetAsBox(1, 1);
var boxFixtureDef:b2FixtureDef = new b2FixtureDef();
boxFixtureDef.shape = box;
boxFixtureDef.density = 1;
boxFixtureDef.friction = 0.3;
boxFixtureDef.restitution = 0.1;
boxBody.CreateFixture(boxFixtureDef);
// debug
var debug:Sprite = new Sprite();
addChild(debug);
var debugDraw:b2DebugDraw = new b2DebugDraw();
debugDraw.SetSprite(debug);
debugDraw.SetDrawScale(NUM_PIXELS_PER_METER);
debugDraw.SetLineThickness(1.0);
debugDraw.SetAlpha(1);
debugDraw.SetFillAlpha(0.4);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
_world.SetDebugDraw(debugDraw);
}
private function onUpdated(_:Event):void
{
_world.Step ( TIMESTEP,
VELOCITY_ITERATIONS,
POSITION_ITERATIONS
);
_world.ClearForces();
_world.DrawDebugData();
}
}
}
Sorry for being an idiot, when copying the TIMESTEP constant from elsewhere, I got a result of 0... My bad.
I am trying to create a Colorpicker control for my application. I prefer pure actionscript. Does someone know how to create a picker like this: http://parasolarchives.com/tools/colorpicker/
Most interestingly I am interested how to draw the gradient because the gradient has a saturation,
this is a picker i use myself, hope it'll be useful:
package lazylib.ui.generated
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.GradientType;
import flash.events.MouseEvent;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import lazylib.broadcast.evts.ColorEvent;
import lazylib.broadcast.Radio;
/**
* ...
* #author www0z0k
*/
public class Picker extends Sprite
{
private var bmpc: Bitmap;
private var bmp:BitmapData;
private var id:String;
private var w:int;
private var h:int;
public static const COLOR_PICKED_EVT_TYPE: String = 'picked by picker';
public function Picker(_id:String, _w:int = 256, _h:int = 256, _x:int = 0, _y:int = 0)
{
id = _id;
w = _w;
h = _h;
x = _x;
y = _y;
bmp = new BitmapData(w, h);
bmpc = new Bitmap(bmp);
addChild(bmpc);
refillBmp(0x7f7f7f);
}
public function get ID():String { return id; }
private function refillBmp(overColor:int = 0x7f7f7f, alphaStep:Number = 0.006):void {
var rtspr:Sprite = new Sprite();
var spr:Sprite = new Sprite();
var colors:Array = new Array(0xff0000,0xff7f00,0xffff00,0x7fff00,0x00ff00, 0x00ff7f, 0x00ffff,0x007fff,0x0000ff, 0x7f00ff,0xff00ff, 0xff007f, 0xff0000);
var alphas: Array = new Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
var ratios: Array = new Array(10, 30, 50, 70, 90, 110, 130, 150, 170, 190, 210, 230, 250);
var matrix:Matrix = new Matrix();
matrix.createGradientBox(w, h);
spr.graphics.beginGradientFill(GradientType.LINEAR, colors, alphas, ratios, matrix);
spr.graphics.drawRect(0, 0, w, h);
spr.graphics.endFill();
rtspr.addChild(spr);
var spr2:Sprite = new Sprite();
rtspr.addChild(spr2);
var startA: Number = 1;
for (var i:int = 0; startA > 0; i++) {
startA -= alphaStep;//orig 0.004!!!
spr2.graphics.lineStyle(1, overColor, startA);
spr2.graphics.moveTo(0, h - i);
spr2.graphics.lineTo(w, h - i);
}
bmp.draw(rtspr, new Matrix());
graphics.beginFill(0, 0);
graphics.drawRect(0, 0, w, h);
graphics.endFill();
buttonMode = true;
addEventListener(MouseEvent.CLICK, onClick);
}
public function adjustDarkness(percent:int):void {
var colorValue: int = int(255 * percent / 100);
var currentGray:int = colorValue + colorValue << 8 + colorValue << 16;
refillBmp(colorValue);
}
private function onClick(e:MouseEvent):void {
var col: int = bmp.getPixel(e.localX, e.localY);
Radio.broadcast(new ColorEvent(COLOR_PICKED_EVT_TYPE + id, col));
dispatchEvent(new ColorEvent(COLOR_PICKED_EVT_TYPE + id, col));
}
}
}
Radio is a global dispatcher class (posted here if you need it), ColorEvent is just an event with an int color field, adjustDarkness is usually called from an external scrollbar. Let me know if a working example is needed.
I'm not sure this would be too accurate, but I think I can give you a few ideas:
You could grab that same squared image you are showing (or photoshop's for that matter) and change it's hue to get the whole color range.
If you want to draw it, mathematically I don't think it's too hard to do... for the cyan, for instance, the Y axis should control the maximum amount of color in all channels, and the X axis should control the proportion of red in the mix.