I'm trying to draw a 10x10 grid with Actionscript 3 with a vanishing point behind - so each square looks like it's coming towards the screen (each from its own relative perspective).
I've found many tutorials for 3D perspective cubes, but they all revolve around movement. Surely static shapes must be easier, but I'm yet to find any help regarding them.
Is there some way I can use PerspectiveProjection() in my case where it doesn't involve movement? It looks to be exactly what I want, yet seems reliant on movement.
Or are there any other methods for 3D perspective object creation?
I'd prefer to use internal AS3 functions if possible.
The closest I've got was this tutorial , which I could likely apply to my situation, but I want to make sure there's not an easier/cleaner way before attempting it.
Thanks.
Here's the fastest and probably most recommended way to achieve what you're after:
Download the Papervision3D library for AS3.
Once you've done this, create a document class and paste inside it this code that I've created for you to get you started:
package
{
import org.papervision3d.view.BasicView;
import org.papervision3d.objects.primitives.Cube;
import org.papervision3d.materials.utils.MaterialsList;
import org.papervision3d.materials.ColorMaterial;
/**
* Document class.
* #author Marty Wallace.
*/
public class Base extends BasicView
{
/**
* Constructor.
*/
public function Base()
{
// Create an array of faces for your cube.
var faces:Array = [
"front",
"back",
"left",
"right",
"top",
"bottom"
];
// Create a list of materials, which contains a material for each face of the cube.
var list:MaterialsList = new MaterialsList();
// Create a new material for each face.
for each(var i:String in faces)
{
// Define the material.
var material:ColorMaterial = new ColorMaterial(Math.random()*0xFFFFFF);
// Add your material to the face represented by i.
list.addMaterial(material, i);
}
// Create the Cube.
var cube:Cube = new Cube(list, 250, 250, 250);
// Rotate the cube to however required.
cube.rotationX = Math.random()*360;
cube.rotationY = Math.random()*360;
cube.rotationZ = Math.random()*360;
// Add the cube to the scene.
scene.addChild(cube);
// Render the cube.
startRendering();
}
}
}
The majority of the code is pretty self explanatory and there are uint.MAX_VALUE tutorials around for this particular framework.
Enjoy!
Related
FURTHER EDIT: Added second parameter to clarify that TreeGenerator is using pre-created parts from the Sprites passed through the parameter, not generating them.
EDIT: I've attempted to change the code away from using "shapes" and "MovieClips" as that was kind of confusing and obscuring the issue I was having.
I'm trying to create a Sprite by using that parts of other Sprites. I've posted some code that illustrates what I'm trying to do:
public class TreeGenerator extends Sprite
{
private var _leaf:Sprite
private var _branch:Sprite
private var _trunk:Sprite
//these are separately drawn and instantiated in other sprites,
//one of which will be passed through in the parameters
public var thumbnail:Sprite
public function TreeGenerator($preCreatedTreeOne:Sprite, $preCreatedTreeTwo:Sprite)
{
_leaf = $preCreatedTreeOne.leaf;
_branch = $preCreatedTreeTwo.branch;
_trunk = $preCreatedTreeOne.trunk;
thumbnail = $preCreatedTreeOne.leaf;
//just uses the leaf for this example
this.addchild(_leaf);
_leaf.y = 30;
this.addchild(_branch);
_branch.y = 20;
this.addchild(_trunk);
_trunk.y = 10;
//this "puts together" the tree image (though very
//simply with just y for example purposes)
this.addchild(thumbnail);
thumbnail.y = 40;
//this thumbnail is supposed to be a separate object that can also
//be interacted with but this example neglects the event listeners.
//the important thing here is the setting of the y to 40, which
//overwrites the y of 30 for _leaf.
}
}
I'm passing through two Sprites already instantiated to $preCreatedTreeOne and $preCreatedTreeTwo that were created through an assortment of tree parts to choose from (large green leaf, small red leaf, thin branch, thick branch, etc.). Those sprites are drawn images, not images generated in code (say from a .swc library). When the user clicks a button after clicking on two trees on the stage, TreeGenerator will create another image of a tree, complete with leaf, branch, and trunk but this time using the parts from the two pre-created trees (and it would be dynamic, one click would generate a tree with green leaves from tree one, thick branches from tree two, and a thin trunk from tree one, whichever combination of two trees chosen as "one" or "two"). There would also be a separate thumbnail that could be interacted with independently (though that code is omitted in the example).
However, when I run the code, I see that the y coordinate value for _leaf gets overridden by thumbnail, because I now realize that both are now $preCreatedTreeOne.leaf.
How do I "take" additional instances (or copies) of $preCreatedTreeOne.leaf from $preCreatedTreeOne so that I can independently manipulate them when I store them in different variables?
IGraphicsData
You can create objects that correspond to calls to methods of the drawing API. They all implement the above interface. You can then group them in a Vector.<IGraphicsData> and shove them into drawGraphicsData() to get your stuff drawn in a Graphics object.
You are not passing around the meal, but its recipe. You can then cook as many meals according to the recipe as you like.
Since FP 11.6 you can also query Vector.<IGraphicsData> from a Graphics object via readGraphicsData()
disclaimer: I hacked together a modified version of your example code in wonder.fl because I don't have a compiler installed, please implement this properly with separate class files and not internal classes.
My Flash Player is from back in the day when it was still cool to have it and thus a little old (11.2), which means I couldn't actually test the code with readGraphicsData(), which is why I pass the triangle list to all 3 parameters.
package
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.IGraphicsData;
import flash.display.GraphicsPath;
import flash.display.GraphicsSolidFill;
import flash.display.GraphicsEndFill;
public class Main extends Sprite
{
public function Main()
{
// assemble Vector.<IGraphicsData> manually for triangle
var triangle:GraphicsPath = new GraphicsPath();
var triangleHeight:uint = 30;
triangle.moveTo(triangleHeight / 2, 0);
triangle.lineTo(triangleHeight, triangleHeight);
triangle.lineTo(0, triangleHeight);
triangle.lineTo(triangleHeight / 2, 0);
var commands:Vector.<IGraphicsData> = new <IGraphicsData>[new GraphicsSolidFill(0xff0000), triangle, new GraphicsEndFill()];
addChild(new ShapeSet(commands, commands, commands));
// since FP 11.6 the following is also possible
// assemble Vector.<IGraphicsData> from existing Graphics object in Shape
var square:Shape = new Shape();
square.graphics.beginFill(0xff00);
square.graphics.drawRect(0, 0, 30, 30);
square.graphics.endFill();
addChild(new ShapeSet(square.graphics.readGraphicsData(), commands, commands));
}
}
}
import flash.display.Sprite;
import flash.display.Shape;
import flash.display.IGraphicsData;
internal class ShapeSet extends Sprite
{
private var _square:Shape;
private var _triangle:Shape;
private var _thumbnail:Shape;
public function ShapeSet(square:Vector.<IGraphicsData>, triangle:Vector.<IGraphicsData>, thumbnail:Vector.<IGraphicsData>)
{
_square = new Shape();
_triangle = new Shape();
_thumbnail = new Shape();
addChild(_square);
_square.x = 10;
addChild(_triangle);
_triangle.x = 60;
addChild(_thumbnail);
_thumbnail.x = 90;
_square.graphics.drawGraphicsData(square);
_triangle.graphics.drawGraphicsData(triangle);
_thumbnail.graphics.drawGraphicsData(thumbnail);
}
}
If you want to pass only triangles to triangles, you should write a triangle class that encapsulates a Vector.<IGraphicsData> and ensures that it only contains data that represents a triangle. How to do this is out of the scope of this question.
Okay so I'm trying to create a program that reads a map from a csv file and then draw each tile using a tilesheet. Reading in the map works fine and I could draw certain tiles depending on the value read in but only if I embedded the images. Obviously this is impractical when it comes to having >20 different tiles; embedding them all just wouldn't be smart.
This is my code for drawing the tiles from the tilesheet.
package
{
import flash.display.Graphics;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.geom.Point;
public class Tile extends Sprite
{
[Embed(source="../bin/lib/TileSheet.png")]
private var BitmapClass:Class;
private var tileBitmap:Bitmap = new BitmapClass();
var tileSize = 25;
var tileSheetLength = 20;
var sheetColumns:int = tileBitmap.bitmapData.width / tileSize;
var pt:Point = new Point(0, 0);
var bmp:Bitmap = new Bitmap(new BitmapData(tileSize, tileSize, true, 0));
public function Tile(collide:Boolean, id:int)
{
Draw(id);
}
private function Draw(id:int):void
{
var col:int = id % sheetColumns;
var row:int = Math.floor(id / sheetColumns);
var rect:Rectangle = new Rectangle(col * tileSize, row * tileSize, tileSize, tileSize);
bmp.bitmapData.copyPixels (tileBitmap.bitmapData, rect, pt, null, null, true);
this.addChild(bmp);
}
public function Update():void
{
}
}
}
'
So what I need help with is optimising this code so that I can run it around 1,900 times rather than the 910-911 times it can handle right now before just closing without errors. If there is a better way of doing this please let me know and any constructive criticism is always appreciated!
You have a Tile class which has a BitmapClass instance. Perhaps that should be a static property (belonging to the class, not every instance) to begin with. My guess is you're using the memory for the whole tile sheet every since time you instantiate a single tile which you probably don't want to do.
Another thing I'm noticing is you're creating a new BitmapData for each tile, when in fact you probably just need the tile data (it's id/coordinates) so you can copy pixels into the final BitmapData which gets displayed on stage. Perhaps you need to a class to manage resources(embedded bitmaps) and another to manage the different Tile instance(which should hold render data and references to pixels, but shouldn't store the actual data) and copying to the main buffer.
Also, it's a good idea to use BitmapData's lock() and unlock() functions for performance when doing multiple pixel operations on an image.
Have a look at Lee Brimelow's Sprite Sheet tutorials (part 1,2 and especially 3). They're really easy to follow and useful.
Also, it might be worth having a look at the have a look at the GPU accelerated IsoHill
library.
I've used IsoHill for a project before and it's quite fast, but it's best to get comfortable with the basics first, otherwise this might seem a bit much.
I've been trying to find the best way to animate bezier curves with AS3. By this far following has been the best solution:
import flash.display.*;
import flash.display.Sprite;
import flash.geom.*;
import com.greensock.TweenMax;
import com.greensock.easing.*;
public class Waves extends MovieClip
{
public var piste:Number = stage.stageHeight;
public var piste2:Number = 0;
var a:Sprite = new Sprite();
var coord:Vector.<Number> = new Vector.<Number>();
var com:Vector.<int> = new Vector.<int>();
public function Waves()
{
addChild(a);
coord.push(0, 30);
com.push(1);
coord.push(260, piste, stage.stageWidth, 30);
com.push(3);
tweenNumbers();
}
public function tweenNumbers():void {
TweenMax.to(this, 0.45, {piste:piste2, repeat:-1, yoyo:true, immediateRender:true, ease:Expo.easeOut, onUpdate:draw});
}
public function draw():void {
coord[3] = piste;
a.graphics.clear();
a.graphics.lineStyle(1,0x990000,1);
a.graphics.drawPath(com, coord);
}
}
Do I really have to use graphics.clear to animate curves? Is there more efficient way? If I tween faster than 1 second, rendering lags and you can see the previous line, is there way to get rid of it?
Hmm. Perhaps you should post your used version of TweenMax to properly debug the issue. There seem to be several of them, some use asynchronusly dispatched "update" events, some employ an enterframe listener, thus making sure each update routine is called each frame. So, graphics jittering can occur in an asynchronus scenario.
On the other questions:
Yes, you have to redraw the graphics object in question, this involves calling graphics.clear(). See, the Graphics object is a blackbox entity, you can't directly reach a control point of a curve to tween it somehow. So, in order to change a point on a curve, you have to redraw it.
A more efficient way would be emulating a tween on your Sprite directly, via an enterframe listener and a function similar to Strong.easeOut used in tweening to interpolate coordinates. You will then get rid of the all extra framework included in TweenMax library and will get full control of the event and code flow. This, however, is some work to both emulate yoyo behavior, time setting behavior, framerate behavior (you can switch to "time=frame" approach, eliminating one of the issues) and easing behavior. The tweenNumbers will look like this:
var isYoyo:Boolean=false;
var currentFrame:int;
var maxFrame:int;
function easingFunction(frame:int,maxframe:int,a:Number,b:Number):Number {
var x:Number=Number(frame)/maxframe;
return a+(b-a)*(x*x*(3-2*x)); // 3x^2-2x^3, a double-easing Perlin function
// recreate your needed function here!
}
var piste1:Number=0; // storing start coordinate
private function tweenNumbers():void {
maxFrame=Math.round(0.45*stage.frameRate); // from seconds to frames
currentFrame=0;
isYoyo=false;
a.addEventListener(Event.ENTER_FRAME,onUpdate);
}
private function onUpdate(e:Event):void {
if (!isYoyo) {
currentFrame++;
if (currentFrame==maxFrame) isYoyo=true;
} else {
currentFrame--;
if (currentFrame==0) isYoyo=false;
} // advance time
coords[3]=easingFunction(currentFrame,maxFrame,piste1,piste2);
// tween the coords[3] manually
a.graphics.clear();
a.graphics.lineStyle(1,0x990000,1);
a.graphics.drawPath(com, coord);
// draw updated path
}
No guarantee of desynching, though, but will normally work. Also a desynch (seeing previous line) can possibly happen if you have set stage framerate too high, so the video subsystem of a target device can't draw as many frames at once.
Does anyone know of any libraries or tools for importing an SVG file into actionscript on the fly? I need to be able to import an SVG file into flash and convert it to a movieclip or preferably a flat 3d object. The end goal here is to implement a vector file with Augmented reality.
A great library for this can be downloaded here: http://code.google.com/p/as3svgrendererlib/
It is easy to implement and reliable. Note that there are two ways to implement the code. I prefer the following because it gives you more control:
private function loadSVG(url:String):void {
ProcessExecutor.instance.initialize(stage);
ProcessExecutor.instance.percentFrameProcessingTime = 0.9;
loader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.addEventListener(Event.COMPLETE, svgLoaded, false, 0, true);
try {
loader.load(new URLRequest(url));
} catch (error:Error) {
trace(error);
}
}
private function svgLoaded(e:Event):void {
var svgString:String = loader.data as String;
var svgDocument:SVGDocument = new SVGDocument();
svgDocument.parse(svgString);
this.addChild(svgDocument);
}
The other way is shorter and might be the way to go when you only have to load a small SVG or just a few.
public function loadSVG(url:String) {
ProcessExecutor.instance.initialize(stage);
ProcessExecutor.instance.percentFrameProcessingTime = 0.9;
var svgDocument:SVGDocument = new SVGDocument();
svgDocument.load(url);
addChild(svgDocument);
}
Two important notes:
1. I could not seem to be able to capture the width or height of the SVGs and the parent Sprite had a width and height of 0 after loading the SVG. I solved this by making all the SVGs the same size before loading them into AS3. After that I knew the dimensions I used the ScaleX and scaleY to resize the loaded SVGs.
2. The stage has to exist before you can use this code. adding the following code will make sure you won't run into problems:yourDisplayClass.addEventListener(Event.ADDED_TO_STAGE, loadYourSVGs);
Now how you convert this to a flat 3D object depends on your 3D Library. I have worked with Away3D where you can use bitmapmaterial on your Sprite3D. The Sprite3D class would be the object to use. I hope your 3D Library supports the use of MovieClips so that you can add them to your 3D Object. Else you will have to use to extract the bitmapMaterial from the movie clip as i have done in the following example:
public function movieClipToBitmapMaterial(mc:MovieClip):BitmapMaterial {
var bmData:BitmapData = new BitmapData(mc.width, mc.height, true, 0xFFFFFF);
bmData.draw(displayObject);
return new BitmapMaterial(bmData);
}
Your input in the above function will be the movieclip onto wich you have loaded your SVG.
I am not sure of this but I think that by using this function you will loose the ability to scale as much as you like without loosing quality.
I hope my input was of some help!
Good luck
PS: don't forget to add the SWC from the link to your project and to check out the provided examples. Please note as well the excellent comment of shaunhusain on your original question. You might not have to use 3D Library.
Salam,
It is very easy:
Open the "name.svg" file in illustartror.
Save it as "name.ai"
Import it into the stage in flash, that is all.
I am trying to build a portfolio application similar to the used by Whitevoid. I am using Flex 4 and Papervision3D 2. I have everything working except for one issue. When I try to load an external SWF as a material on one of the planes, I can see any native Flex or Flash components in their correct positions, but the papervision objects are not being rendered properly. It looks like the viewport is not being set in the nested swf. I have posted my code for loading the swf below.
private function loadMovie(path:String=""):void
{
loader = new Loader();
request = new URLRequest(path);
loader.contentLoaderInfo.addEventListener(Event.INIT, addMaterial);
loader.load(request);
}
private function addMaterial(e:Event):void
{
movie = new MovieClip();
movie.addChild(e.target.content);
var width:Number = 0;
var height:Number = 0;
width = loader.contentLoaderInfo.width;
height = loader.contentLoaderInfo.height;
//calculate the aspect ratio of the swf
var matAR:Number = width/height;
if (matAR > aspectRatio)
{
plane.scaleY = aspectRatio / matAR;
}
else if (matAR < aspectRatio)
{
plane.scaleX = matAR / aspectRatio;
}
var mat:MovieMaterial = new MovieMaterial(movie, false, true, false, new Rectangle(0, 0, width, height));
mat.interactive = true;
mat.smooth = true;
plane.material = mat;
}
Below I have posted two pictures. The first is a shot of the application running by itself. The second is the application as a MovieMaterial on a Plane. You can see how the button created as a spark object in the mxml stays in the correct position, but papervision sphere (which is rotating) is in the wrong location. Is there something I am missing here?
Man. I haven't seen that site in a while. Still one of the cooler PV projects...
What do you mean by:
I cannot properly see the scene rendered in Papervision
You say you can see the components in their appropriate positions, as in: you have a plane with what looks like the intended file loading up? But I'm guessing that you can't interact with it.
As far as I know, and I've spent a reasonable amount of time trying to make something similar work, the MovieMaterial (which I assume you're using) draws a Bitmap of whatever contents exist in your MovieClip, and if you set it to animated=true, then it will render out a series of bitmaps - equating animation. What it's not doing, is displaying an actual MovieClip (or SWF) on the plane. So you may see your components, but this is how:
MovieMaterial.as line 137
// ______________________________________________________________________ CREATE BITMAP
/**
*
* #param asset
* #return
*/
protected function createBitmapFromSprite( asset:DisplayObject ):BitmapData
{
// Set the new movie reference
movie = asset;
// initialize the bitmap since it's new
initBitmap( movie );
// Draw
drawBitmap();
// Call super.createBitmap to centralize the bitmap specific code.
// Here only MovieClip specific code, all bitmap code (maxUVs, AUTO_MIP_MAP, correctBitmap) in BitmapMaterial.
bitmap = super.createBitmap( bitmap );
return bitmap;
}
Note in the WhiteVoid you never actually interact with a movie until it "lands" = he's very likely swapping in a Movie on top of the bitmap textured plane.
The part that you are interacting with is probably another plane that holds the "button" that simply becomes visible on mouseover.
I think PV1.0 had access to real swfs as a material but this changed in 2.0. Sadly. Hopefully Molehill will.
cheers