Fork me on GitHub
Not signed in (Sign In)

Welcome, Guest

Want to take part in these discussions? Sign in if you have an account, or apply for one below

    • CommentAuthorericr
    • CommentTimeMar 25th 2008 edited
     
    I'm extremely impressed with the Flint Particle System. So much so that I'm trying to integrate it into a game that I'm working on currently.

    I've run into several performance issues, however, that I spent some time tracking down. Here I'll outline what I've found and what I did to alleviate the issue.

    Issue: The particle system experiences major slowdown with spacially large projects.

    Root of the Cause: BitmapEmitters (and thus the PixelEmitters) use the Stage as their drawspace archetype. Thus if your project size is set to something like 1024x768, all filter effect math is performed on a Bitmap of size 1024x768. Each pixel is recalculated each frame regardless of how big the actual effect is.

    Proposed Fix: Divorce the BitmapEmitter class from all Stage-specific calculations. This requires modification of a single function: BitmapEmitter::addedToStage(). The rewritten addedToStage() function follows:
    private function addedToStage( ev:Event ):void
    {
    if( _bitmap && _bitmap.bitmapData )
    {
    _bitmap.bitmapData.dispose();
    }
    if( !parent || parent.width == 0 )
    {
    _bitmap = null;
    return;
    }

    _bitmap = new Bitmap();
    _bitmap.bitmapData = new BitmapData(parent.width, parent.height, true, 0);
    parent.addChild(_bitmap);

    _offset = new Point(0, 0);
    _bitmap.x = - _offset.x;
    _bitmap.y = - _offset.y;
    }


    Kickass Side Effects:

    1. Current implementations will not change. If you simply call addChild() within the Flash Document's Main class or even in the first frame's Actionscript code (in a CS3 Project) then it continues to use the stage as ever.
    2. If you make a smaller DisplayObjectContainer object (a Sprite object, say), draw an invisible rectangle at size 100x100 (thus setting the Sprite's width and height to 100), and add the Emitter to the Sprite (mySprite.addChild(emitter);) instead of the stage then it will draw in just that 100x100 frame, completely contained!
    3. In order to move the emitter around on the stage, just move the DisplayObjectContainer object!
    4. You can now contain your particle effect (give it boundaries).
    5. You're saving cycles by using a smaller bitmap drawing space.

    I'm very interested in looking into further possible speed enhancements. Any ideas? Anyone see something that this change breaks?
    • CommentAuthorRichard
    • CommentTimeApr 7th 2008
     
    Hi

    Sorry to take a while to respond - I've been away on holiday. Just uploaded version 1.0 and then noticed your post - wish I'd seen it earlier.

    I appreciate the problem but have an issue with your proposed solution - requiring the container to have an invisible object to specify its size doesn't feel logical. It also breaks any existing code where the emitter is placed inside a display object other than the stage.

    I'd suggest a better solution would be to add optional parameters to the BitmapRenderer (which now contains the drawing functionality that was in the BitmapEmitter) to specify the size and location of the bitmap. If no parameter is passed, existing functionality applies. The parameter is probably best as a flash.geom.Rectangle. What do you think?
    • CommentAuthorericr
    • CommentTimeApr 7th 2008 edited
     
    No problem on the response time. Hope you're holiday was relaxing!

    In terms of breaking the code, why would anyone place a Flint object in a non-Stage DisplayObject using the current code-base? All calculations are done based on the Stage itself. The Parent Object is entirely irrelevant. Further, you'll notice in the code above that I removed the "localToGlobal" point translation call (a cleaner solution in general). All rendering is now done based entirely on the parent's local coordinate system. If you added the object to the Stage then it uses the Stage's coordinate system. If you added it to your "avatarDude" object then it uses that space... Am I missing something? My testing shows that I can use my own DisplayObjects or the Stage and both show up where I expect them.

    I currently call my containing objects something like "flintBox"... heh.

    I see where your other issue lies with my solution: I simply add the emitter (0.9.4 code) to a DisplayObject that I manage myself. You would like it to be self contained, correct? How about pushing the controls into the emitter itself?

    I am fine with that solution and agree that it makes better sense.

    The Rectangle class looks like it would work "okay". The only question I have is do you really need an object for that? It's essentially two parameters you're adding: width and height. You already have an X and Y value... The only benefit you'd potentially get would be in some of the BitmapData class functions (as mentioned in the Rectangle class outline). Though I haven't looked into just how much utility that would actually provide....

    Using the Rectangle object would force you to push your X and Y parameters into that object's initialization. I'm not sure that's such a great idea, either.

    What I would do with the new class is allow people to specify a width and height. If they do so then you initialize your new bitmapData based on those parameters. Otherwise you let it default to the parent object's size. It simply doesn't make sense to add an emitter to a non-Stage's Display List and default to using the Stage's drawing space.

    Further, doesn't that actually break objects? If I addChild(theEmitter) to my 10x10 bubble Sprite and then resize the emitter to the Stage's size (because I didn't specify a width and height), won't that expand the containing object's (the bubble's) size to the Stage's size? Not necessarily the bitmap representation of said bubble itself but it's coordinate system (myBubble.width and myBubble.height)? I'm pretty sure that's how Flash works, unfortunately... (I've seen my coordinate systems explode because contained elements went rogue and ran outside of the original size).

    In short, yes, I think it's a good idea to allow the user to set a width and height for the object. I definitely think that all localToGLobal calls and stage-based calculations should be nixed, though.

    Thoughts?

    (Awesome job on the code, by the way! 1.0 looks sweet!)
    • CommentAuthorRichard
    • CommentTimeApr 8th 2008 edited
     
    The BitmapRenderer (formerly BitmapEmitter) doesn't default to the stage's drawing space. The BitmapRenderer draws particles relative to the renderer's coordinate system (the renderer is a sprite). This takes account of the renderer's position, rotation, and scaling. However, to render the particles the BitmapRenderer needs a Bitmap object and an associated BitmapData object. The Bitmap object is placed inside the BitmapRenderer, so all rendering is relative to the BitmapRenderer. There are no circumstances under which the rendering is relative to the stage. However, a decision has to be made on the size and position of this Bitmap object.

    Early versions of the BitmapEmitter created the BitmapData at the minimum size required to draw all the particles. However, calculating this every frame and, when necessary, creating a new larger BitmapData and copying the old image into the new one resulted in a speed hit on the particle system. To counteract this I modified the class to create a BitmapData object that matched the size of the stage, and positioned it within the BitmapEmitter so it was over the stage. This means there's no need to constantly check that the BitmapData is the right size since we can draw all particles that are on-stage and we don't need to draw particles that are off-stage.

    This doesn't affect the fact that the particles' positions are relative to the coordinate system of the renderer, not of the stage.

    When I switched the code to make the bitmap the same size as the stage I was testing on projects that were 600 or so pixels tall and wide - the largest I tested was 800 x 800. It may be that with larger stage sizes the speed hit from the bitmap covering the entire stage is greater than the speed hit from the bitmap size being tested and modified as necessary. Which leaves three choices going forward -

    1. BitmapRenderers use bitmaps that cover the stage
    2. BitmapRenderers constantly calculate the optimum size for the bitmap
    3. The user has to decide in advance what size bitmap they need

    I think that option 3 has its own problems because it relies on prescience on the part of the developer who has to know what is the largest BitmapData object that will be needed to draw the particle system. My ideal choice would be option 2 if only I could make it faster.

    N.B. with option 3 it would be wrong to assume that the bitmap should be placed at 0,0 since that assumes that particles will never have a negative x or y coordinate. That's why I would propose using a rectangle to define both the size and position of the bitmap.

    My preferred choice is either -
    A. Default to option 1 but let the developer override the size and position as per option 3 (as discussed previously).
    B. Use option 2 but let the developer override the initial size and position to minimise the amount of resizing of the bitmap.

    Either can be implemented without breaking backward compatibility (except in as much as they may change the speed of the rendering and hence the frame-rate). I would like to use option B if only it were faster. However, I think the best choice is A.
    • CommentAuthorericr
    • CommentTimeApr 8th 2008 edited
     
    I understand your concerns but I have to ask if you've tried the code that I modified above. In BitmapEmitter land it didn't break backwards compatibility one iota. If you make that modification and re-run the snow tutorial you'll see that nothing changes. This is because the BitmapEmitter (still talking 0.9.4) was added to the Stage directly and thus uses that object's coordinate system for calculations.

    The Bitmap object is placed inside the BitmapRenderer, so all rendering is relative to the BitmapRenderer. There are no circumstances under which the rendering is relative to the stage. However, a decision has to be made on the size and position of this Bitmap object.

    With the BitmapRenderer, all rendering is relative to the Stage:
    private function addedToStage( ev:Event ):void
    {
    if( _bitmap && _bitmap.bitmapData )
    {
    _bitmap.bitmapData.dispose();
    }
    if( !stage || stage.stageWidth == 0 )
    {
    _bitmap = null;
    return;
    }
    _bitmap = new Bitmap( null, "auto", _smoothing);
    _bitmap.bitmapData = new BitmapData( stage.stageWidth, stage.stageHeight, true, 0 );
    addChild( _bitmap );
    _offset = localToGlobal( new Point( 0, 0 ) );
    _bitmap.x = - _offset.x;
    _bitmap.y = - _offset.y;
    }

    Every BitmapRenderer object automatically defines its BitmapData canvas based on the size of the stage. Change the size of the stage in your project and you've changed the canvas size for all BitmapRenderer objects. The user has absolutely no control over the size of the object.

    I took the fire code from the demos and placed it in my 1024x768 project and it slowed to something like 5fps. When I made the modifications I mentioned in my first post and encapsulated it in an object of size 60x120 the thing ran like greased lightning.

    This doesn't affect the fact that the particles' positions are relative to the coordinate system of the renderer, not of the stage.

    Actually I'm not so sure about that. Let's do this line by line:
    _bitmap = new Bitmap( null, "auto", _smoothing);Make a new Bitmap object with mostly default settings.

    _bitmap.bitmapData = new BitmapData( stage.stageWidth, stage.stageHeight, true, 0 );Make a new, empty BitmapData object with width and height set to that of the Stage.

    addChild( _bitmap );Add the bitmap object to the BitmapRenderer (a Sprite). This has the side effect of resizing the BitmapRenderer to the size of the Stage!

    _offset = localToGlobal( new Point( 0, 0 ) );Convert the BitmapRenderer object's (0,0) point [the top-left corner] into Stage coordinates. If you've simply added the BitmapRenderer object to the stage then the BitmapRenderer object's (0,0) is already the Stage's (0,0).

    _bitmap.x = - _offset.x;
    _bitmap.y = - _offset.y;
    Set the Bitmap object's x and y coordinates to the new offset. But this won't work if someone moves the BitmapRenderer! The BitmapRenderer is already the same size as the Stage. If you translate it then your offset will be based on that! The minute you move it and have a particle effect throw a particle to the edge of the stage it won't get there because the canvas has shifted! I just noticed that you attempt to sidestep this issue by overriding the obj.x and obj.y properties. The issue there is that you're using localToGlobal again. If I've added the BitmapRenderer to anything other than the Stage, this code will not work as expected.

    And that's the same problem I came up against at first. :)

    <--snip-->
    • CommentAuthorericr
    • CommentTimeApr 8th 2008 edited
     
    I think that option 3 has its own problems because it relies on prescience on the part of the developer who has to know what is the largest BitmapData object that will be needed to draw the particle system.

    I have actually taken the time to get Flint into a usable situation. What I found was that, yes, it did require prescience. But I would never release something without testing the particle I had coded. When I noticed a hard-edge in the particle, I went back to the code, modified the width and height of the container, and got it to where it was just barely big enough for the effect whilst cutting down on as much CPU as possible (the game I'm building also uses a Physics Engine which happens to also be a huge resource hog).

    If I wanted to add some randomness to the mix and let the player/user go nuts (at their own peril), I would either add the Renderer to the Stage or give it a Framed Sprite (like a window).

    Let's hit the options one by one:
    1. BitmapRenderers use bitmaps that cover the stage
    This has huge performance consequences. Seriously, add my code modifications and then add the Fire particle in your demo to an object of size 60x120 or so (you'll need to adjust the location of the particles within that space to the bottom - something like (30, 115)) and see how much faster it runs. You'll see the benefits immediately.

    2. BitmapRenderers constantly calculate the optimum size for the bitmap
    This will result in wasted calculations once the emitter stabilizes. If there were a way to precompute this I'd say "This is it!"

    3. The user has to decide in advance what size bitmap they need
    Well, with the system we somewhat fleshed out above, they wouldn't actually need to - they have the option to if they would like. The benefit there lies in the huge speed increase potential.

    My preferred choice is either -
    A. Default to option 1 but let the developer override the size and position as per option 3 (as discussed previously).
    B. Use option 2 but let the developer override the initial size and position to minimise the amount of resizing of the bitmap.

    Either can be implemented without breaking backward compatibility (except in as much as they may change the speed of the rendering and hence the frame-rate). I would like to use option B if only it were faster. However, I think the best choice is A.


    I'm totally with you on that. (As somewhat hinted above ;D)

    N.B. with option 3 it would be wrong to assume that the bitmap should be placed at 0,0 since that assumes that particles will never have a negative x or y coordinate. That's why I would propose using a rectangle to define both the size and position of the bitmap.
    N.B.?
    So you would define the rectangle such that (0,0) was in the center of the rectangle? If so, that's not how it's used in the BitmapData class. Further, there's no problem at all with particles going negative: they don't get drawn to the screen! They've essentially left the canvas and Adobe handles that by discarding those calls (I assume).
    • CommentAuthorRichard
    • CommentTimeApr 9th 2008 edited
     
    richard: "This doesn't affect the fact that the particles' positions are relative to the coordinate system of the renderer, not of the stage."

    ericr: "Actually I'm not so sure about that." ( and "With the BitmapRenderer, all rendering is relative to the Stage")

    I am. Try this code, as the document class.

    package
    {
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.geom.Point;

    import org.flintparticles.actions.Move;
    import org.flintparticles.counters.Steady;
    import org.flintparticles.displayObjects.Dot;
    import org.flintparticles.emitters.Emitter;
    import org.flintparticles.initializers.Position;
    import org.flintparticles.initializers.SharedImage;
    import org.flintparticles.initializers.Velocity;
    import org.flintparticles.renderers.BitmapRenderer;
    import org.flintparticles.zones.PointZone;

    [SWF(width='500', height='500', frameRate='61', backgroundColor='#000000')]

    public class main extends Sprite
    {
    private var particleContainer:Sprite;

    public function main()
    {
    particleContainer = new Sprite();
    addChild( particleContainer );

    var emitter:Emitter = new Emitter();
    var renderer:BitmapRenderer = new BitmapRenderer();
    emitter.renderer = renderer;
    particleContainer.addChild( renderer );

    emitter.counter = new Steady( 5 );
    emitter.addInitializer( new SharedImage( new Dot( 3 ) ) );
    emitter.addInitializer( new Position( new PointZone( new Point( 0, 0 ) ) ) );
    emitter.addInitializer( new Velocity( new PointZone( new Point( 100, 100 ) ) ) );

    emitter.addAction( new Move() );

    emitter.start();

    stage.addEventListener( MouseEvent.CLICK, click );
    }

    private function click( ev:MouseEvent ):void
    {
    particleContainer.x = stage.mouseX;
    particleContainer.y = stage.mouseY;
    }
    }
    }


    The renderer is inside particleContainer which is on the stage. If you click anywhere on the stage, particleContainer moves to the location of the click. Because the renderer is inside particleContainer, so does the renderer. And, because the particles are drawn relative to the coordinate system of the renderer, so do the particles. If they were drawn relative to the stage, they'd stay in the same place because the stage hasn't moved. This is also the observed behaviour if you move the renderer rather than particleContainer.

    If I've added the BitmapRenderer to anything other than the Stage, this code will not work as expected.

    The particles are in the renderer and the renderer is in the particleContainer sprite. If I move a DisplayObjectContainer I expect the DisplayObjects inside it to move with it. This is how the code works.

    localToGlobal is only used to decide where to position the bitmap in the renderer. It is not used to decide where to draw the particles. The effect of not positioning the bitmap at (0,0) within the renderer is reversed out when drawing the particles so that the particles are drawn in this bitmap but in positions relative to the renderer's coordinate system and not the bitmap's coordinate system.

    matrix.translate( _offset.x, _offset.y );

    is the key to this.

    Now, if you move the renderer the bitmap may no longer cover the whole stage. That is, I agree, a problem. But that does not imply that the particles are rendered relative to the stage. It merely implies that the default size and position of the bitmap image is not ideal. That is why, as you suggest (and I agree), we need to allow developers to override the default.

    I still think the default (a bitmap that covers the stage given the current location of the renderer) is a good default. This is a compromise but works well in more situations than any other compromise I can think of. Of course developers should be able to override this default behaviour.

    I understand your concerns but I have to ask if you've tried the code that I modified above. In BitmapEmitter land it didn't break backwards compatibility one iota.

    It would break the code above, which will not render anything using your code in the BitmapRenderer or old style BitmapEmitter.
    • CommentAuthorRichard
    • CommentTimeApr 9th 2008 edited
     
    So you would define the rectangle such that (0,0) was in the center of the rectangle? If so, that's not how it's used in the BitmapData class. Further, there's no problem at all with particles going negative: they don't get drawn to the screen! They've essentially left the canvas and Adobe handles that by discarding those calls (I assume).

    When defining a renderer, I don't want developers to have to consider the inner workings of it. Specifically, I don't think that the rectangle parameter should be identified as defining the bitmapData object. Rather, the rectangle should define the area within the renderer that the developer wants available for rendering the particles. And I don't see a problem with that area including negative values.

    A rectangle defines top, left, width and height. That gives us a region within the renderer, and the renderer should then create an appropriate bitmap for drawing in this region, and only this region.

    I think it is worthwhile allowing this freedom, and it helps to make the renderers interchangeable (the DisplayObjectRenderer allows for negative particle coordinates).
    • CommentAuthorRichard
    • CommentTimeApr 9th 2008
     
    Anyway. What I propose is to keep the current behaviour as the default but allow developers to override this using a rectangle to define the area they want available for the particles to render into, relative to the coordinate system of the BitmapRenderer.

    I would also propose to create a debug bitmap renderer that auto-sizes the bitmap and traces out the size and position whenever it grows to accommodate the particles. That could be used to ascertain the optimum size for use in the standard BitmapEmitter.
    • CommentAuthorericr
    • CommentTimeApr 9th 2008
     
    I beg to differ.

    I used your code and did some trace statements of the size of the particleContainer sprite. It is indeed initialized to the width and height of the stage (because the BitmapData object is sized like that -> it forces its container to the same height and it's container, as well). You never once set the width and height of the particleContainer Sprite or drew into it directly. But because you've defined your BitmapData object as you have, it balloons to that size immediately. That is what I meant by 'relative to the stage' - it's something you would not understand without looking at how bitmapRenderer objects are initialized and following through Adobe's not-so-straightforward parent-child system.

    What's worse is that if you resize the Stage while the emitter is running, the particleContainer sprite does not resize. You get the 'cutting off' situation that occurs in the code that I posted to start this thread. I use Adobe Flash CS3. I drew a white box at the border of the particleContainer sprite and then maximized the test window that pops up. Clicking around shows that A) there is a 500x500 box in the screen and B) the particles are contained within that 500x500 box.

    Try my modified version here:
    package
    {
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.geom.Point;

    import org.flintparticles.actions.Move;
    import org.flintparticles.counters.Steady;
    import org.flintparticles.displayObjects.Dot;
    import org.flintparticles.emitters.Emitter;
    import org.flintparticles.initializers.Position;
    import org.flintparticles.initializers.SharedImage;
    import org.flintparticles.initializers.Velocity;
    import org.flintparticles.renderers.BitmapRenderer;
    import org.flintparticles.zones.PointZone;

    [SWF(width='500', height='500', frameRate='61', backgroundColor='#000000')]

    public class main extends Sprite
    {
    private var particleContainer:Sprite;
    private var renderer:BitmapRenderer;

    public function main()
    {
    particleContainer = new Sprite();
    addChild( particleContainer );

    var emitter:Emitter = new Emitter();
    renderer = new BitmapRenderer();
    emitter.renderer = renderer;
    particleContainer.addChild( renderer );

    emitter.counter = new Steady( 5 );
    emitter.addInitializer( new SharedImage( new Dot( 3 ) ) );
    emitter.addInitializer( new Position( new PointZone( new Point( 0, 0 ) ) ) );
    emitter.addInitializer( new Velocity( new PointZone( new Point( 100, 100 ) ) ) );

    emitter.addAction( new Move() );

    emitter.start();

    stage.addEventListener( MouseEvent.CLICK, click );
    particleContainer.graphics.lineStyle(1, 0xFFFFFF);
    particleContainer.graphics.drawRect(0, 0, particleContainer.width, particleContainer.height);
    }

    private function click( ev:MouseEvent ):void
    {
    particleContainer.x = stage.mouseX;
    particleContainer.y = stage.mouseY;
    //renderer.x = 50;
    //renderer.y = 50;
    trace("width: " + particleContainer.width + ", height: " + particleContainer.height);
    trace("X: " + particleContainer.x + ", Y: " + particleContainer.y);
    }
    }
    }

    Try the following:
    1. Start clicking around and things will be alright (except that once you click you'll see the original bounding box drawn in the main() function.
    2. Do as above except maximize the window you're in. Start clicking around. Note that your trace statements are pretty much as expected: something like 500x500 width and height with the x and y at the mouse.
    3. Uncomment the "renderer.x" and "renderer.y" lines. Repeat steps 1 and 2. Watch as your particleContainer's width and height explode and how the drawing gets all funky.

    The problem with the latter part is that your renderer.x and renderer.y functions are strictly relative to the Stage because they use the localToGlobal function calls. This issue would go away and they would be completely relative to their parent object (in this case: the particleContainer object - even the Stage if that's where you added the renderer) if you simply dropped the localToGlobal calls and did everything based on parent width and height.

    This helps explain why some people are running into issues with the clicks, too. If moving the renderer explodes the size of the parent object in this way and the parent object needs the mouseEnabled object set then you've got a serious issue.
    • CommentAuthorericr
    • CommentTimeApr 9th 2008
     
    Now try the following:
    Overwrite BitmapRenderer's addedToStage, set x, and set y functions as follows:
    //...
    /*
    * Create the correct sized bitmap when the renderer is added to the display list.
    */
    private function addedToStage( ev:Event ):void
    {
    if( _bitmap && _bitmap.bitmapData )
    {
    _bitmap.bitmapData.dispose();
    }
    if( !parent || parent.width == 0 )
    {
    _bitmap = null;
    return;
    }
    _bitmap = new Bitmap( null, "auto", _smoothing);
    _bitmap.bitmapData = new BitmapData( parent.width, parent.height, true, 0 );
    addChild( _bitmap );
    _offset = new Point( 0, 0 );
    _bitmap.x = - _offset.x;
    _bitmap.y = - _offset.y;
    }

    /**
    * Override's the default x property of the display object to add additional functionality
    * for managing the bitmap display.
    */
    override public function set x( value:Number ):void
    {
    super.x = value;
    if( _bitmap && stage )
    {
    _offset = new Point( 0, 0 );
    _bitmap.x = - _offset.x;
    _bitmap.y = - _offset.y;
    }
    }

    /**
    * Override's the default y property of the display object to add additional functionality
    * for managing the bitmap display.
    */
    override public function set y( value:Number ):void
    {
    super.y = value;
    if( _bitmap && stage )
    {
    _offset = new Point( 0, 0 );
    _bitmap.x = - _offset.x;
    _bitmap.y = - _offset.y;
    }
    }
    //...

    And using the modified file I posted above as your starting point, erase the following lines:
    particleContainer.graphics.lineStyle(1, 0xFFFFFF);
    particleContainer.graphics.drawRect(0, 0, particleContainer.width, particleContainer.height);

    Add the following lines directly below "addChild( particleContainer );"
    particleContainer.graphics.lineStyle(1, 0xFFFFFF);
    particleContainer.graphics.drawRect(0, 0, 500, 500);

    Run the tests I mentioned in the previous post. You'll notice that the emitter moves more as we'd expect.
    (Changing the renderer's x and y directly still modifies the container object but in a very predictable way.)
    • CommentAuthorericr
    • CommentTimeApr 9th 2008 edited
     
    If you remove the particleContainer and try to add the emitter directly to the "main" class object (by simply calling "addChild(renderer);") you will not see anything. This is because the "main" object is not yet completely initialized and has width 0. This is definitely a problem that would need fixing in my current solution.

    Like all other DisplayObjects, the "main" class (which extends Sprite) doesn't have a width or height until it has a child with a width or height set. Thus it fails right before creating the BitmapData object.

    I never came across that issue because I generate my particles after I have a "gameBoard" specified and added to the "main" object.
    • CommentAuthorericr
    • CommentTimeApr 9th 2008
     
    Also, can you give an example of when a user would experience negative particle values? In my testing the math has always worked out just fine...
    • CommentAuthorericr
    • CommentTimeApr 9th 2008 edited
     
    In fact, what's wrong with making someone specify the width and height of a BitmapRenderer object? You're essentially telling them "How big an area do you want these particles to be simulated in?" If the user wants fullscreen, then by golly let them call:
    renderer:BitmapRenderer = new BitmapRenderer(stage.stageWidth, stage.stageHeight);
    That's the best method, in my estimation! The BitmapRenderer object is already a container (the BitmapData object is a child of the BitmapRenderer object). Use it as such! I made a few further modifications to the BitmapRenderer code. The set x and set y code is unchanged. Only the constructor and the addedToStage() functions got slight modifications.

    Constructor:
    //...
    /**
    * The constructor creates a BitmapRenderer. After creation it should be
    * added to the display list of a DisplayObjectContainer to place it on
    * the stage and should be applied to an Emitter using the Emitter's
    * renderer property.
    *
    * @param width Specify a width of the render area.
    *
    * @param height Specify a height of the render area.
    *
    * @param smoothing Whether to use smoothing when scaling the Bitmap and, if the
    * particles are represented by bitmaps, when drawing the particles.
    * Smoothing removes pixelation when images are scaled and rotated, but it
    * takes longer.
    */
    public function BitmapRenderer( width:int, height:int, smoothing:Boolean = false )
    {
    super();
    _width = width;
    _height = height;
    _smoothing = smoothing;
    _preFilters = new Array();
    _postFilters = new Array();
    addEventListener( Event.ADDED_TO_STAGE, addedToStage, false, 0, true );
    addEventListener( Event.REMOVED_FROM_STAGE, removedFromStage, false, 0, true );
    }
    //...

    addedToStage():
    //...
    /*
    * Create the correct sized bitmap when the renderer is added to the display list.
    */
    private function addedToStage( ev:Event ):void
    {
    if( _bitmap && _bitmap.bitmapData )
    {
    _bitmap.bitmapData.dispose();
    }
    //if( !parent || parent.width == 0 )
    if( !parent || _width <= 0 || _height <= 0 )
    {
    _bitmap = null;
    return;
    }
    _bitmap = new Bitmap( null, "auto", _smoothing);
    _bitmap.bitmapData = new BitmapData( _width, _height, true, 0 );
    addChild( _bitmap );
    _offset = new Point( 0, 0 );
    _bitmap.x = - _offset.x;
    _bitmap.y = - _offset.y;
    }
    //...

    If you use this code and simply specify a width and height in the BitmapRenderer object constructor within the test example I posted above you'll see that even calling "addToStage(renderer)" works just fine. Further, if you modify the click function so that you're moving "renderer" directly you'll see that it moves around the screen perfectly. And if you print out its width and height you'll see that they remain constant. Awesome.

    You don't need to have the user explicitly specify a width/height for the other renderers because they are inherently different beasts. You can't [easily] specify a boundary for Flash's Vector graphics. If the object reaches a parent's boundary the boundary expands to fit it. The cut-off aspect is unique to Bitmap objects. Why should you be forced to abstract that difference away for the user? It's one of the things that separates it from the rest and in fact is one of the things I was specifically looking for when I started out. Calculations get very complex when you can't trust a width or height value.

    For further enhancements I would take a look at possibly removing the _offset code because you only ever set it to a new point of (0, 0). Further, I would take a look at abstracting the "creating a new bitmapData" functionality away to a protected/private utility function that both addedToStage and a width/height setter could use.

    This sidesteps the need for a rectangle, too. You could add a note to both the Documentation and example code that explains how to set the width and height to that of the stage.

    :)
    • CommentAuthorRichard
    • CommentTimeApr 10th 2008 edited
     
    It's something you would not understand without looking at how bitmapRenderer objects are initialized and following through Adobe's not-so-straightforward parent-child system

    Just to be clear, I do understand how BitmapRenderer objects are initialized (I wrote the code) and I fully understand Adobe's parent/child system.

    If you use this code and simply specify a width and height in the BitmapRenderer object constructor

    If you read my first reply on this thread you'll see that I agreed with you that there should be a way for developers to specify the size of the rendering area of the BitmapRenderer. I have never wavered from this view.

    That is what I meant by 'relative to the stage'

    It is clear to me that we have been using the term "relative to the stage" to mean different things and this has lead to our misunderstanding each other and hence to most of the debate above. Specifically, we have used the term "relative" in different ways.

    When I refer to a display object being drawn or rendered "relative to" another, I am referring to the coordinate systems (I even said "relative to the coordinate system of" a number of times).

    Specifically, a particle is drawn "relative to" the renderer because its position and rotation is in relation to the coordinate system of the renderer. The renderer is "relative to" its container because its position and rotation is in the coordinate system of its container. Note also that the bitmap is not the renderer, it is a Bitmap object inside the renderer. Particles are rendered in positions that are "relative to" the renderer, not the bitmap.

    From your most recent posts it becomes clear to me that when you refer to the renderer being "relative to" the stage, you mean that it's size matches that of the stage. If you read my posts above with my understanding of the term "relative to" in mind you'll notice that I've never said that the bitmap created inside the renderer is anything other than the same size as the stage at the time the renderer is added to the stage. I've also acknowledged that this is a problem and that it can be overcome by allowing the developer to specify a different size.

    All of which, I suspect, means that we have been debating at cross purposes and actually agree with each other.

    My view, which I have held all along, is as follows (avoiding the term "relative to"). Please tell me what you disagree with.


    • The position and rotation of particles are, and should be, within the coordinate system of the renderer.

    • The position and rotation of the renderer is, and should be, within the coordinate system of its container, whether that is the stage or some other display object container.

    • It should not be necessary for developers who use the BitmapRenderer to know anything about its inner workings.

    • The BitmapRenderer must, by its nature, limit the area on which the particles can be rendered. Specifically, this is because it uses a BitmapData object and BitmapData objects cannot be of infinite size. I shall refer to this limited area as its canvas.

    • The current BitmapRenderer creates a canvas for the particles to be drawn in that is the same size as the stage at the time that the renderer is added to the stage.

    • The current BitmapRenderer places this canvas within itself such that the canvas exactly covers the stage at the time that the renderer is added to the stage.

    • Creating the canvas the same size as the stage, and placing it to exactly cover the stage, is a limitation.

    • To overcome this limitation, developers should be able to specify the size and position of the canvas.

    • The canvas should exist within the BitmapRenderer such that, when the BitmapRenderer is moved or scaled, its canvas moves and scales with it.

    • It should be possible for a developer to choose to create a canvas on which particles with negative x and/or y coordinates can be drawn (see below for an example).

    • If a BitmapRenderer's canvas is to have a default size and position, the best value for that is a canvas that is the same size as the stage and that exactly covers the stage.

    • This default size and position may no longer be desirable because it causes confusion for some developers

    • Removing the default size and position from the BitmapRenderer will break backward compatibility.

    • When adding new features, if possible one should avoid breaking backward compatibility.

    • CommentAuthorRichard
    • CommentTimeApr 10th 2008 edited
     
    Uncomment the "renderer.x" and "renderer.y" lines. Repeat steps 1 and 2. Watch as your particleContainer's width and height explode and how the drawing gets all funky.

    The particleContainer's width and height reflect it's contents, which are the bitmap plus the outline you have created. If these are not in the same location then the size of the object containing them will naturally increase. I would hardly refer to this as exploding. Also, what do you mean by "get all funky". I see nothing unusual.

    This helps explain why some people are running into issues with the clicks, too. If moving the renderer explodes the size of the parent object in this way and the parent object needs the mouseEnabled object set then you've got a serious issue.

    pyiap's problem was caused by the fact that he was using an emitter (specifically the sparkler example) as a cursor by having it tract the mouse, and he wanted to click through the emitter to the object underneath. This would be a problem with any bitmap size that was capable of rendering the emitter because the emitter is directly beneath the cursor.

    You'll notice that the emitter moves more as we'd expect.

    The emitter moves in exactly the same way in both versions. (The emitter is the source of the particles.)

    If you [...] try to add the emitter directly to the "main" class object you will not see anything. This is definitely a problem that would need fixing in my current solution.

    But not with the solution I proposed in my first post and still intend to implement - to use a Rectangle object to define the area of the renderer's canvas.

    In fact, what's wrong with making someone specify the width and height of a BitmapRenderer object?

    Only that it breaks backward compatibility, which I'd like to avoid if possible.

    I would take a look at possibly removing the _offset code

    This will be required to allow for negative particle positions.

    Also, can you give an example of when a user would experience negative particle values?

    package
    {
    import flash.display.Sprite;
    import flash.geom.Point;

    import org.flintparticles.actions.Move;
    import org.flintparticles.counters.Steady;
    import org.flintparticles.displayObjects.Dot;
    import org.flintparticles.emitters.Emitter;
    import org.flintparticles.initializers.*;
    import org.flintparticles.renderers.BitmapRenderer;
    import org.flintparticles.zones.*;

    [SWF(width='500', height='500', frameRate='61', backgroundColor='#000000')]

    public class Test extends Sprite
    {
    public function Test()
    {
    var emitter : Emitter = new Emitter( );
    var renderer : BitmapRenderer = new BitmapRenderer();
    emitter.renderer = renderer;
    addChild( renderer );
    renderer.x = 250;
    renderer.y = 250;

    emitter.counter = new Steady( 25 );
    emitter.addInitializer( new SharedImage( new Dot( 3 ) ) );
    emitter.addInitializer( new Position( new PointZone( new Point( 0, 0 ) ) ) );
    emitter.addInitializer( new Velocity( new DiscZone( new Point( 0, 0 ), 100, 100 ) ) );

    emitter.addAction( new Move( ) );

    emitter.start( );
    }
    }
    }
    • CommentAuthorericr
    • CommentTimeApr 11th 2008 edited
     
    Before I respond to the meet of your posts, I'd like to point something out about your demo code above.

    Setting renderer.x and renderer.y in that manner certainly produces the undesired simulation given my modifications.

    It is not, however, how users should reposition the particle effects within the canvas. <-- Thrust of this post.

    According to the documentation, the proper way to achieve that effect is to call emitter.x and emitter.y. The documentation for those functions explain this:
    "Indicates the x [/y] coordinate of the Emitter instance relative to the local coordinate system of the Renderer." [Emphasis mine.]

    So, moving the renderer should move the location of the actual canvas. Moving the emitter should move the location of the particles themselves (all of the drawing logic should be handled by the emitter and its actions/etc). That's the abstraction layer as I understand it.

    Using my BitmapRenderer modifications with the following code will show this at work.

    package
    {
    import flash.display.Sprite;
    import flash.geom.Point;

    import org.flintparticles.actions.Move;
    import org.flintparticles.counters.Steady;
    import org.flintparticles.displayObjects.Dot;
    import org.flintparticles.emitters.Emitter;
    import org.flintparticles.initializers.*;
    import org.flintparticles.renderers.BitmapRenderer;
    import org.flintparticles.zones.*;

    [SWF(width='500', height='500', frameRate='61', backgroundColor='#000000')]

    public class Test extends Sprite
    {
    public function Test()
    {
    var emitter : Emitter = new Emitter( );
    //var renderer : BitmapRenderer = new BitmapRenderer();
    var renderer : BitmapRenderer = new BitmapRenderer( 250, 250 );
    emitter.renderer = renderer;
    addChild( renderer );
    renderer.x = 50;
    renderer.y = 50;
    emitter.x = 125;
    emitter.y = 125;

    emitter.counter = new Steady( 25 );
    emitter.addInitializer( new SharedImage( new Dot( 3 ) ) );
    emitter.addInitializer( new Position( new PointZone( new Point( 0, 0 ) ) ) );
    emitter.addInitializer( new Velocity( new DiscZone( new Point( 0, 0 ), 100, 100 ) ) );

    emitter.addAction( new Move( ) );

    emitter.start( );
    }
    }
    }

    What you will see is a 250x250 pixel area where the 125x125 point has particles streaming from it. The entire 250x250 area is shifted over and down 50 pixels from the corners of the 500x500 pixel stage.
    • CommentAuthorericr
    • CommentTimeApr 11th 2008 edited
     
    And now onto the meaty responses :)

    It's something you would not understand without looking at how bitmapRenderer objects are initialized and following through Adobe's not-so-straightforward parent-child system
    Just to be clear, I do understand how BitmapRenderer objects are initialized (I wrote the code) and I fully understand Adobe's parent/child system.

    Ah, sorry. By "you" I didn't mean literally You; I meant the third person "you". Replace that with "It's something a developer would not understand without looking at how BitmapRenderer objects..." Sorry about the vagueness of it. I brought it up because I've seen a lot of obviously confused people on forums grapple with Adobe's system [in even the very simple cases]. If those people try to integrate Flint then the oddities I pointed out above would be a concern.

    From your most recent posts it becomes clear to me that when you refer to the renderer being "relative to" the stage, you mean that it's size matches that of the stage. I usually use the term "same size as" in such circumstances. If you read my posts above with my understanding of the term "relative to" in mind you'll notice that I've never said that the bitmap created inside the renderer is anything other than the same size as the stage at the time the renderer is added to the stage.

    I agree. There was a misunderstanding (d'oh). By the way, I do appreciate the Dictionary reference [<-- Not sarcastic]. I have done similar things myself dealing with people in the forums... (unfortunately it often does not help :[ ). In my case, though, I did in fact mean relative in the same sense as you. You use the localToGlobal function whenever the x and y properties are accessed. This means that calculations relative to the stage are performed behind the magic curtain. The reason I bring this up is because it only works given the current model [I think]. I guess I'm just really opposed to relying on localToGlobal for anything not specifically top-level.

    My view, which I have held all along, is as follows (avoiding the term "relative to"). Please tell me what you disagree with.
    Sweet.

    • The position and rotation of particles are, and should be, within the coordinate system of the renderer.
      Agreed. This is handled by the Emitter object.

    • The position and rotation of the renderer is, and should be, within the coordinate system of its container, whether that is the stage or some other display object container.
      Agreed [mostly].

    • It should not be necessary for developers who use the BitmapRenderer to know anything about its inner workings.
      Absolutely, unquestioningly agreed.

    • The BitmapRenderer must, by its nature, limit the area on which the particles can be rendered. Specifically, this is because it uses a BitmapData object and BitmapData objects cannot be of infinite size. I shall refer to this limited area as its canvas.
      Agreed.

    • The current BitmapRenderer creates a canvas for the particles to be drawn in that is the same size as the stage at the time that the renderer is added to the stage.
      Agreed. Note that I understand "at the time the renderer is added to the stage" as "at the time the renderer is added to the stage's Display List" and not specifically stage.addChild(renderer).

    • The current BitmapRenderer places this canvas within itself such that the canvas exactly covers the stage at the time that the renderer is added to the stage.
      Agreed. [With the same caveat above.]

    • Creating the canvas the same size as the stage, and placing it to exactly cover the stage, is a limitation.
      Agreed.

    • To overcome this limitation, developers should be able to specify the size and position of the canvas.
      Agreed.

    • The canvas should exist within the BitmapRenderer such that, when the BitmapRenderer is moved or scaled, its canvas moves and scales with it.
      Agreed.

    • It should be possible for a developer to choose to create a canvas on which particles with negative x and/or y coordinates can be drawn (see below for an example).
      Disagreed. See my previous post for how to implement this desired effect correctly. [Negative x/y values for the BitmapRenderer don't make sense for me because it is a canvas. It's something drawn to. Negative canvas values are, by definition, off the canvas. The Emitter object should and does handle this.

    • If a BitmapRenderer's canvas is to have a default size and position, the best value for that is a canvas that is the same size as the stage and that exactly covers the stage.
      Agreed. Nothing else really makes any sense.

    • This default size and position may no longer be desirable because it causes confusion for some developers, but removing it will break backward compatibility.
      Absolutely agreed. But we're only a few days out from your 1.0 release and you broke with backwards compatibility there. I minor 1.0.1 release with a small fix like this would be tolerable. When you weigh "code modifications this will require [->1]" against "increased clarity and efficiency [->HUGE]", I think it's a manageable, if undesirable, result.

    • When adding new features, if possible one should avoid breaking backward compatibility.
      Agreed. I don't, however, really see this as a feature-add. It's an enhancement more than anything... it cleans up the code a bit and makes simulations better. Functionality wise, not much changes at all.
    • CommentAuthorericr
    • CommentTimeApr 11th 2008 edited
     
    To sum up my arguments:
    Particle rotation, placement, etc. within the canvas should be the Emitter's job. The Renderer should merely act as a "drawing space".

    Another question arises:
    All that said, is there a reason that BitmapRenderer extends "Sprite" and not "Bitmap"? The "Bitmap" object... kind of makes more sense and has less overhead. The only thing "Sprite" gets you is the "DisplayObjectContainer" chain. My understanding is that the renderer should be the drawing space and that's it. No more. Since developers aren't adding Sprites to this object (as they do with the DisplayObjectRenderer), couldn't we back down on it and use the Bitmap object?

    And a PS:
    I hope you're not taking offense to anything I'm writing. When I emphasize in my text it is to make sure that stuff is not misread, not to vent any kind of anger or to infer some kind of stupidity. I actually appreciate very much that you're taking the time to respond to me and keep the dialog moving. The more I dig into the system the more I respect how well thought-out it is :)
    • CommentAuthorericr
    • CommentTimeApr 11th 2008
     
    Here:
    package org.flintparticles.renderers
    {
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.filters.BitmapFilter;
    import flash.geom.Matrix;
    import flash.geom.Point;

    import org.flintparticles.particles.Particle;

    /**
    * The BitmapRenderer draws particles onto a single Bitmap display object.
    *
    * <p>The image to be used for each particle is the particles image property.
    * This is a DisplayObject, but this
    * DisplayObject is not used directly but is, rather, copied into the
    * bitmap with the various properties of the particle applied.
    * Consequently each particle may be represented by the same DisplayObject
    * instance and the SharedImage initializer can be used with this emitter.</p>
    *
    * <p>The BitmapRenderer allows the use of BitmapFilters to modify the appearance
    * of the bitmap. Every frame, under normal circumstances, the Bitmap used to
    * display the particles is wiped clean before all the particles are redrawn.
    * However, if one or more filters are added to the renderer, the filters are
    * applied to the bitmap instead of wiping it clean. This enables various trail
    * effects by using blur and other filters.</p>
    */
    public class BitmapRenderer extends Sprite implements Renderer
    {
    protected var _bitmap:Bitmap;
    protected var _width:Number;
    protected var _height:Number;
    private var _preFilters:Array;
    private var _postFilters:Array;
    private var _smoothing:Boolean;

    /**
    * The constructor creates a BitmapRenderer. After creation it should be
    * added to the display list of a DisplayObjectContainer to place it on
    * the stage and should be applied to an Emitter using the Emitter's
    * renderer property.
    *
    * @param width Specify a width of the render area. If no width is specified it defaults
    * to the direct parent's width.*
    *
    * @param height Specify a height of the render area. If no height is specified it defaults
    * to the direct parent's height.*
    *
    * * If either the width or height are set to 0 *OR* the direct parent's width or height are
    * set to 0 when the BitmapRenderer object is added to an active Display List then BitmapRenderer
    * will use the width and height of the Stage.
    *
    * @param smoothing Whether to use smoothing when scaling the Bitmap and, if the
    * particles are represented by bitmaps, when drawing the particles.
    * Smoothing removes pixelation when images are scaled and rotated, but it
    * takes longer.
    */
    public function BitmapRenderer( width:Number = 0, height:Number = 0, smoothing:Boolean = false )
    {
    super();
    _width = width;
    _height = height;
    _smoothing = smoothing;
    _preFilters = new Array();
    _postFilters = new Array();
    addEventListener( Event.ADDED_TO_STAGE, addedToStage, false, 0, true );
    addEventListener( Event.REMOVED_FROM_STAGE, removedFromStage, false, 0, true );
    }

    /**
    * The addFilter method adds a BitmapFilter to the renderer. These filters
    * are applied each frame, before or after the new particle positions are drawn, instead
    * of wiping the display clear. Use of a blur filter, for example, will
    * produce a trail behind each particle as the previous images blur and fade
    * more each frame.
    *
    * @param filter The filter to apply
    * @param postRender If false, the filter is applied before drawing the particles
    * in their new positions. If true the filter is applied after drawing the particles.
    */
    public function addFilter( filter:BitmapFilter, postRender:Boolean = false ):void
    {
    if( postRender )
    {
    _postFilters.push( filter );
    }
    else
    {
    _preFilters.push( filter );
    }
    }

    /**
    * Removes a BitmapFilter object from the Renderer.
    *
    * @param filter The BitmapFilter to remove
    *
    * @see addFilter()
    */
    public function removeFilter( filter:BitmapFilter ):void
    {
    for( var i:uint = 0; i < _preFilters.length; ++i )
    {
    if( _preFilters[i] == filter )
    {
    _preFilters.splice( i, 1 );
    return;
    }
    }
    for( i = 0; i < _postFilters.length; ++i )
    {
    if( _postFilters[i] == filter )
    {
    _postFilters.splice( i, 1 );
    return;
    }
    }
    }

    /*
    * Create the correct sized bitmap when the renderer is added to the display list.
    */
    private function addedToStage( ev:Event ):void
    {
    if( _bitmap && _bitmap.bitmapData )
    {
    _bitmap.bitmapData.dispose();
    }
    if( _width <= 0 || _height <= 0 )
    {
    if( !parent )
    {
    _bitmap = null;
    return;
    }
    if( parent.width <= 0 || parent.height <= 0 ) // Can these values even be negative?
    {
    _width = parent.stage.stageWidth;
    _height = parent.stage.stageHeight;
    }
    else
    {
    _width = parent.width;
    _height = parent.height;
    }
    }
    _bitmap = new Bitmap( null, "auto", _smoothing);
    _bitmap.bitmapData = new BitmapData( _width, _height, true, 0 );
    addChild( _bitmap );
    }
    • CommentAuthorericr
    • CommentTimeApr 11th 2008 edited
     
    /**
    * Override's the default x property of the display object to add additional functionality
    * for managing the bitmap display.
    */
    override public function set x( value:Number ):void
    {
    super.x = value;
    }

    /**
    * Override's the default y property of the display object to add additional functionality
    * for managing the bitmap display.
    */
    override public function set y( value:Number ):void
    {
    super.y = value;
    }

    /**
    * Resize the rendering area of the BitmapRenderer.
    */
    public function resize( width:Number, height:Number ):void
    {
    _width = width;
    _height = height;
    addedToStage( null );
    }

    /**
    * Get the current width of the Bitmap object.
    */
    public function get renderWidth():Number
    {
    return _width;
    }

    /**
    * Set the current width of Bitmap object and force a resize.
    */
    public function set renderWidth( value:Number ):void
    {
    _width = value;
    addedToStage(null);
    }

    /**
    * Get the current height of the Bitmap object.
    */
    public function get renderHeight():Number
    {
    return _height;
    }

    /**
    * Set the current height of Bitmap object and force a resize.
    */
    public function set renderHeight( value:Number ):void
    {
    _height = value;
    addedToStage(null);
    }

    /*
    * When the renderer is removed from the stage.
    */
    private function removedFromStage( ev:Event ):void
    {
    dispose();
    }

    /**
    * @inheritDoc
    */
    public function renderParticles( particles:Array ):void
    {
    if( !_bitmap )
    {
    addedToStage( null );
    if( !_bitmap )
    {
    return;
    }
    }
    var i:uint;
    var len:uint;
    _bitmap.bitmapData.lock();
    len = _preFilters.length;
    for( i = 0; i < len; ++i )
    {
    _bitmap.bitmapData.applyFilter( _bitmap.bitmapData, _bitmap.bitmapData.rect, new Point( 0, 0 ), _preFilters[i] );
    }
    if( len == 0 && _postFilters.length == 0 )
    {
    _bitmap.bitmapData.fillRect( _bitmap.bitmapData.rect, 0 );
    }
    len = particles.length;
    if ( len )
    {
    for( i = 0; i < len; ++i )
    {
    drawParticle( particles[i] );
    }
    }
    len = _postFilters.length;
    for( i = 0; i < len; ++i )
    {
    _bitmap.bitmapData.applyFilter( _bitmap.bitmapData, _bitmap.bitmapData.rect, new Point( 0, 0 ), _postFilters[i] );
    }
    _bitmap.bitmapData.unlock();
    }

    /**
    * Used internally here and in derived classes to alter the manner of
    * the particle rendering (e.g. in the PixelRenderer class).
    */
    protected function drawParticle( particle:Particle ):void
    {
    _bitmap.bitmapData.draw( particle.image, particle.matrixTransform, particle.colorTransform, particle.image.blendMode, null, _smoothing );
    }

    /**
    * @inheritDoc
    */
    public function addParticle( particle:Particle ):void
    {
    }

    /**
    * @inheritDoc
    */
    public function removeParticle( particle:Particle ):void
    {
    }

    /**
    * May be called by the user to cause the renderer to dispose of the bitmapData object
    * it is using. This method will automatically be called when the renderer is removed
    * from the display list.
    */
    public function dispose():void
    {
    if( _bitmap && _bitmap.bitmapData )
    {
    _bitmap.bitmapData.dispose();
    }
    }
    }
    }

    Test code:
    package
    {
    import flash.display.Sprite;
    import flash.geom.Point;

    import org.flintparticles.actions.Move;
    import org.flintparticles.counters.Steady;
    import org.flintparticles.displayObjects.Dot;
    import org.flintparticles.emitters.Emitter;
    import org.flintparticles.initializers.*;
    import org.flintparticles.renderers.BitmapRenderer;
    import org.flintparticles.zones.*;

    [SWF(width='500', height='500', frameRate='61', backgroundColor='#000000')]

    public class Test extends Sprite
    {
    public function Test()
    {
    var emitter : Emitter = new Emitter( );
    var renderer : BitmapRenderer = new BitmapRenderer();
    //var renderer : BitmapRenderer = new BitmapRenderer( 250, 250 );
    emitter.renderer = renderer;
    addChild( renderer );
    renderer.x = 50;
    renderer.y = 50;
    emitter.x = 125;
    emitter.y = 125;

    emitter.counter = new Steady( 25 );
    emitter.addInitializer( new SharedImage( new Dot( 3 ) ) );
    emitter.addInitializer( new Position( new PointZone( new Point( 0, 0 ) ) ) );
    emitter.addInitializer( new Velocity( new DiscZone( new Point( 0, 0 ), 100, 100 ) ) );

    emitter.addAction( new Move( ) );

    emitter.start( );
    trace(renderer.renderWidth);
    trace(renderer.renderHeight)
    renderer.renderWidth = 400;;
    renderer.renderHeight = 250;
    trace(renderer.renderWidth);
    trace(renderer.renderHeight);
    trace(renderer.width);
    trace(renderer.height);
    }
    }
    }

    ASDoc compliant (I think). Keeps backwards compatibility (though I think you will agree that it obfuscates the code a bit). Further, it allows dynamic resizing of the canvas. Play around!

    The only caveat I see is that the renderer.width and renderer.height values don't shrink when you call resize or renderWidth/renderHeight setters. This is due to the nature of DisplayObjectContainers.
    • CommentAuthorericr
    • CommentTimeApr 11th 2008
     
    Version where BitmapRenderer extends Bitmap instead of Sprite:
    package org.flintparticles.renderers
    {
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.events.Event;
    import flash.filters.BitmapFilter;
    import flash.geom.Matrix;
    import flash.geom.Point;

    import org.flintparticles.particles.Particle;

    /**
    * The BitmapRenderer draws particles onto a single Bitmap display object.
    *
    * <p>The image to be used for each particle is the particles image property.
    * This is a DisplayObject, but this
    * DisplayObject is not used directly but is, rather, copied into the
    * bitmap with the various properties of the particle applied.
    * Consequently each particle may be represented by the same DisplayObject
    * instance and the SharedImage initializer can be used with this emitter.</p>
    *
    * <p>The BitmapRenderer allows the use of BitmapFilters to modify the appearance
    * of the bitmap. Every frame, under normal circumstances, the Bitmap used to
    * display the particles is wiped clean before all the particles are redrawn.
    * However, if one or more filters are added to the renderer, the filters are
    * applied to the bitmap instead of wiping it clean. This enables various trail
    * effects by using blur and other filters.</p>
    */
    public class BitmapRenderer extends Bitmap implements Renderer
    {
    protected var _width:Number;
    protected var _height:Number;
    private var _preFilters:Array;
    private var _postFilters:Array;

    /**
    * The constructor creates a BitmapRenderer. After creation it should be
    * added to the display list of a DisplayObjectContainer to place it on
    * the stage and should be applied to an Emitter using the Emitter's
    * renderer property.
    *
    * @param width Specify a width of the render area. If no width is specified it defaults
    * to the direct parent's width.*
    *
    * @param height Specify a height of the render area. If no height is specified it defaults
    * to the direct parent's height.*
    *
    * * If either the width or height are set to 0 *OR* the direct parent's width or height are
    * set to 0 when the BitmapRenderer object is added to an active Display List then BitmapRenderer
    * will use the width and height of the Stage.
    *
    * @param smoothing Whether to use smoothing when scaling the Bitmap and, if the
    * particles are represented by bitmaps, when drawing the particles.
    * Smoothing removes pixelation when images are scaled and rotated, but it
    * takes longer.
    */
    public function BitmapRenderer( width:Number = 0, height:Number = 0, smoothing:Boolean = false )
    {
    super( null, "auto", smoothing );
    _width = width;
    _height = height;
    _preFilters = new Array();
    _postFilters = new Array();
    addEventListener( Event.ADDED_TO_STAGE, addedToStage, false, 0, true );
    addEventListener( Event.REMOVED_FROM_STAGE, removedFromStage, false, 0, true );
    }

    /**
    * The addFilter method adds a BitmapFilter to the renderer. These filters
    * are applied each frame, before or after the new particle positions are drawn, instead
    * of wiping the display clear. Use of a blur filter, for example, will
    * produce a trail behind each particle as the previous images blur and fade
    * more each frame.
    *
    * @param filter The filter to apply
    * @param postRender If false, the filter is applied before drawing the particles
    * in their new positions. If true the filter is applied after drawing the particles.
    */
    public function addFilter( filter:BitmapFilter, postRender:Boolean = false ):void
    {
    if( postRender )
    {
    _postFilters.push( filter );
    }
    else
    {
    _preFilters.push( filter );
    }
    }

    /**
    * Removes a BitmapFilter object from the Renderer.
    *
    * @param filter The BitmapFilter to remove
    *
    * @see addFilter()
    */
    public function removeFilter( filter:BitmapFilter ):void
    {
    for( var i:uint = 0; i < _preFilters.length; ++i )
    {
    if( _preFilters[i] == filter )
    {
    _preFilters.splice( i, 1 );
    return;
    }
    }
    for( i = 0; i < _postFilters.length; ++i )
    {
    if( _postFilters[i] == filter )
    {
    _postFilters.splice( i, 1 );
    return;
    }
    }
    }

    /*
    * Create the correct sized bitmap when the renderer is added to the display list.
    */
    private function addedToStage( ev:Event ):void
    {
    if( bitmapData )
    {
    bitmapData.dispose();
    }
    if( _width <= 0 || _height <= 0 )
    {
    if( !parent )
    {
    return;
    }
    if( parent.width <= 0 || parent.height <= 0 ) // Can these values even be negative?
    {
    _width = parent.stage.stageWidth;
    _height = parent.stage.stageHeight;
    }
    else
    {
    _width = parent.width;
    _height = parent.height;
    }
    }
    bitmapData = new BitmapData( _width, _height, true, 0 );
    }
    • CommentAuthorericr
    • CommentTimeApr 11th 2008 edited
     

    /**
    * Override's the default x property of the display object to add additional functionality
    * for managing the bitmap display.
    */
    override public function set x( value:Number ):void
    {
    super.x = value;
    }

    /**
    * Override's the default y property of the display object to add additional functionality
    * for managing the bitmap display.
    */
    override public function set y( value:Number ):void
    {
    super.y = value;
    }

    /**
    * Resize the rendering area of the BitmapRenderer.
    */
    public function resize( width:Number, height:Number ):void
    {
    _width = width;
    _height = height;
    addedToStage( null );
    }

    /**
    * Get the current width of the Bitmap object.
    */
    public function get renderWidth():Number
    {
    return _width;
    }

    /**
    * Set the current width of Bitmap object and force a resize.
    */
    public function set renderWidth( value:Number ):void
    {
    _width = value;
    addedToStage(null);
    }

    /**
    * Get the current height of the Bitmap object.
    */
    public function get renderHeight():Number
    {
    return _height;
    }

    /**
    * Set the current height of Bitmap object and force a resize.
    */
    public function set renderHeight( value:Number ):void
    {
    _height = value;
    addedToStage(null);
    }

    /*
    * When the renderer is removed from the stage.
    */
    private function removedFromStage( ev:Event ):void
    {
    dispose();
    }

    /**
    * @inheritDoc
    */
    public function renderParticles( particles:Array ):void
    {
    if( !bitmapData )
    {
    addedToStage( null );
    if( !bitmapData )
    {
    return;
    }
    }
    var i:uint;
    var len:uint;
    bitmapData.lock();
    len = _preFilters.length;
    for( i = 0; i < len; ++i )
    {
    bitmapData.applyFilter( bitmapData, bitmapData.rect, new Point( 0, 0 ), _preFilters[i] );
    }
    if( len == 0 && _postFilters.length == 0 )
    {
    bitmapData.fillRect( bitmapData.rect, 0 );
    }
    len = particles.length;
    if ( len )
    {
    for( i = 0; i < len; ++i )
    {
    drawParticle( particles[i] );
    }
    }
    len = _postFilters.length;
    for( i = 0; i < len; ++i )
    {
    bitmapData.applyFilter( bitmapData, bitmapData.rect, new Point( 0, 0 ), _postFilters[i] );
    }
    bitmapData.unlock();
    }

    /**
    * Used internally here and in derived classes to alter the manner of
    * the particle rendering (e.g. in the PixelRenderer class).
    */
    protected function drawParticle( particle:Particle ):void
    {
    bitmapData.draw( particle.image, particle.matrixTransform, particle.colorTransform, particle.image.blendMode, null, smoothing );
    }

    /**
    * @inheritDoc
    */
    public function addParticle( particle:Particle ):void
    {
    }

    /**
    * @inheritDoc
    */
    public function removeParticle( particle:Particle ):void
    {
    }

    /**
    * May be called by the user to cause the renderer to dispose of the bitmapData object
    * it is using. This method will automatically be called when the renderer is removed
    * from the display list.
    */
    public function dispose():void
    {
    if( bitmapData )
    {
    bitmapData.dispose();
    }
    }
    }
    }

    Use the same test code as in the previous post.
    • CommentAuthorericr
    • CommentTimeApr 11th 2008 edited
     
    So that's a lot to read through. I've produced two viable solutions that maintain backwards compatibility while removing any Stage-related calculations (except as an ultimate fallback on initialization - for backwards compatibility purposes).

    I'd prefer the latter version, myself. It:
    1. Reduces the overhead both in actual object size (by over half)

    2. Drastically reduces the number of object->member function calls (by removing the need to redirect through a _bitmap member object).

    3. Removes the issue described with the Sprite implementation (where the renderer.width and renderer.height values didn't reflect the changes made to the renderHeight and renderWidth).

    The reason I added the "renderHeight/Width getters and setters is so that the user could still use the default "Bitmap" behavior of getting and setting the width and height of the Bitmap object (which results in scaling).

    Thoughts?

    P.S.- Both versions:

    1. Remove all localToGlobal() calls.

    2. Completely remove _offset variables, including the matrix.translate( _offset.x, _offset.y ); call.

    3. Will obviously require a [very simple] behind-the-scenes update to PixelRenderer. Added benefit here is that users will be able to specify a PixelRenderer area as well (even that slows down with big canvases!).
    • CommentAuthorericr
    • CommentTimeApr 11th 2008 edited
     
    In fact, here you go! Updated PixelRenderer versions!

    Sprite-based BitmapRenderer mode:
    package org.flintparticles.renderers
    {
    import flash.display.Bitmap;
    import flash.display.BitmapData;

    import org.flintparticles.particles.Particle;

    /**
    * The PixelRenderer draws particles as single pixels on a Bitmap display object.
    */
    public class PixelRenderer extends BitmapRenderer
    {
    /**
    * The constructor creates a PixelRenderer. After creation it should be
    * added to the display list of a DisplayObjectContainer to place it on
    * the stage and should be applied to an Emitter using the Emitter's
    * renderer property.
    */
    public function PixelRenderer( width:Number = 0, height:Number = 0 )
    {
    super( width, height );
    }

    /**
    * Used internally to draw the particles.
    */
    override protected function drawParticle( particle:Particle ):void
    {
    _bitmap.bitmapData.setPixel32( Math.round( particle.x ), Math.round( particle.y ), particle.color );
    }
    }
    }


    Bitmap-based BitmapRenderer mode:
    package org.flintparticles.renderers
    {
    import flash.display.Bitmap;
    import flash.display.BitmapData;

    import org.flintparticles.particles.Particle;

    /**
    * The PixelRenderer draws particles as single pixels on a Bitmap display object.
    */
    public class PixelRenderer extends BitmapRenderer
    {
    /**
    * The constructor creates a PixelRenderer. After creation it should be
    * added to the display list of a DisplayObjectContainer to place it on
    * the stage and should be applied to an Emitter using the Emitter's
    * renderer property.
    */
    public function PixelRenderer( width:Number = 0, height:Number = 0 )
    {
    super( width, height );
    }

    /**
    * Used internally to draw the particles.
    */
    override protected function drawParticle( particle:Particle ):void
    {
    bitmapData.setPixel32( Math.round( particle.x ), Math.round( particle.y ), particle.color );
    }
    }
    }


    :D
    • CommentAuthorRichard
    • CommentTimeApr 11th 2008
     
    It's good that we agree on most things.

    Here's my current plan.

    1. Remove the default canvas from the BitmapRenderer.
    2. Remove the x and y overrides too.

    Why? - This behaviour limits the versatility of what should be a basic bitmap renderer with the ability for the developer to use it as he/she wishes. I've changed my mind. Thank you for changing it for me.

    3. Add a Rectangle parameter to the BitmapRenderer for the developer to define the region of the canvas that the renderer can draw on.

    Why? We need to define the draw canvas.

    Why a rectangle? I haven't changed my mind on allowing negative values for the particles' positions. The renderer is a display object. To break it if the user sets its x and y coordinates is simply wrong. Also, the particles move relative to the emitter. If particles have a negative velocity then they will eventually reach a point where they have a negative position. This is also why the BitmapRenderer is a sprite - so the canvas can allow negative positions.

    Negative canvas values are, by definition, off the canvas.

    Only if you define your canvas to not allow negative values. I see no reason to impose that limitation.

    4. Create a new FullStageBitmapRenderer that is a modified version of the current BitmapRenderer that, as far as possible, uses a canvas that will always allow rendering anywhere on the stage. This was the motivation behind the current BitmapRenderer and is a good motivation - it's simple and in many circumstances does it's job of working without developers having to think about it. A lot of developers like this. The current implementation breaks if the developer moves things around in certain ways (as you found) and is too slow when the stage is very large (again, as you found). The new one will break less often (it won't be foolproof - that would require information that the flash player doesn't expose to Actionscript).

    Why? I know this is useful to some developers. It also minimises the backward compatibility impact since developers can switch over to this new renderer if they want the old behaviour.
    • CommentAuthorericr
    • CommentTimeApr 11th 2008 edited
     
    1. 3. Add a Rectangle parameter to the BitmapRenderer for the developer to define the region of the canvas that the renderer can draw on.
      Why? We need to define the draw canvas.

      This is redundant. The BitmapRenderer is the canvas the emitter draws to. That is the only clear way to encapsulate everything.

    2. Why a rectangle? I haven't changed my mind on allowing negative values for the particles' positions. The renderer is a display object. To break it if the user sets its x and y coordinates is simply wrong. Also, the particles move relative to the emitter. If particles have a negative velocity then they will eventually reach a point where they have a negative position. This is also why the BitmapRenderer is a sprite - so the canvas can allow negative positions.
      Did you even test the Bitmap extended version I posted above? This is all handled perfectly by the emitter and renderer. Negative values are a moot point. Try out the test code: it works perfectly. Setting x/y values on the BitmapRenderer moves the canvas (because it is the canvas). Setting x/y values on the emitter moves the location particles are generated at within the canvas, as per the Documentation. Setting emitter.renderer is the exact same thing as setting a "emitter.canvas" because of how you defined what it means to move the emitter. To add a rectangle is to add an unnecessary layer of abstraction.

    3. Negative canvas values are, by definition, off the canvas.
      Only if you define your canvas to not allow negative values. I see no reason to impose that limitation.
      You've already solved this problem and you didn't even need to worry about negative values. Fire up the test code I produced using the BitmapRenderer that extends Bitmap instead of Sprite if you don't trust me. It works. [The only way you'd get negative values in here is if you moved the origin point to the center of the Display Object... which is incongruous with the model Adobe uses and will require constant hacks to get things to work correctly.


    Please try the new Bitmap based BitmapRenderer code using the test code I provided. Then, as you did before, post some code that breaks it. The only thing I've done to your code is simplify it.

    As for the FullStageBitmapRenderer would you propose to register a function with the Stage's resize event? That wouldn't be good because that event relies on the Stage's ScaleMode value. The developer could break this. Let the developer handle the Stage resize event. That's their job, not the particle engine's. With the last two versions I posted above the developer can easily manage that themselves. Further, most developers will develop with a locked stage size in mind. Again, I took care of the default value withe the last version of the code posted above... it defaults as the current version does. I even made the proper modifications to the PixelRenderer class and posted them above.
    • CommentAuthorericr
    • CommentTimeApr 11th 2008
     
    More testing:
    package
    {
    import flash.display.Sprite;
    import flash.geom.Point;

    import org.flintparticles.actions.Move;
    import org.flintparticles.counters.Steady;
    import org.flintparticles.displayObjects.Dot;
    import org.flintparticles.emitters.Emitter;
    import org.flintparticles.initializers.*;
    import org.flintparticles.renderers.BitmapRenderer;
    import org.flintparticles.zones.*;

    [SWF(width='500', height='500', frameRate='61', backgroundColor='#000000')]

    public class Test extends Sprite
    {
    public function Test()
    {
    var emitter : Emitter = new Emitter( );
    var renderer : BitmapRenderer = new BitmapRenderer();
    emitter.renderer = renderer;
    addChild( renderer );
    renderer.x = 50;
    renderer.y = -50;
    emitter.x = -15;
    //emitter.x = renderer.renderWidth + 15;
    emitter.y = 125;

    emitter.counter = new Steady( 25 );
    emitter.addInitializer( new SharedImage( new Dot( 3 ) ) );
    emitter.addInitializer( new Position( new PointZone( new Point( 0, 0 ) ) ) );
    emitter.addInitializer( new Velocity( new DiscZone( new Point( 0, 0 ), 100, 100 ) ) );

    emitter.addAction( new Move( ) );

    emitter.start( );
    trace(renderer.renderWidth);
    trace(renderer.renderHeight);
    renderer.renderWidth = 400;
    renderer.renderHeight = 250;
    trace(renderer.renderWidth);
    trace(renderer.renderHeight);
    trace(renderer.width);
    trace(renderer.height);
    // This accesses the Bitmap.height value, scaling the canvas vertically as per the Bitmap class.
    renderer.height = 500;
    }
    }
    }

    Your negative x and negative y values work just fine.
    • renderer.y = -50; - This moves the BitmapRenderer (the canvas) fifty units north of the stage.

    • emitter.x = -15; - This moves the Emitter fifteen units off of the canvas. You will see that particles still show up as expected (you can tell the origin point is just off the BitmapRenderer (the canvas)!

    • emitter.x = renderer.renderWidth + 15; - Uncomment this and you'll see the exact same thing as above except on the opposite side.


    Note that the above code even shows you how to scale the entire canvas (without having to compute everything on individual particles!). It relies on the build in functionality of the Bitmap class. The last line in the test code does this for us: renderer.height = 500;. You will see that it's 50 pixels off the bottom of the stage. This is because we told the BitmapRenderer to move up 50 pixels earlier in the test code.

    It works, man! It's done! The bugs I found before are gone, the code remains backwards compatible, and it's faster than ever! Less object overhead, less objects, less object creation, less calls to Matrix.transform(), ... The code is clearer than ever, more extensible, and even conforms to Adobe's DisplayObject model.

    The only thing I would consider at this point is to rename "renderWidth" and "renderHeight" to "canvasWidth" and "canvasHeight" respectively ("resize" to "resizeCanvas", too). You know, to better explain exactly what's going on.

    The point should also be made that setting "renderer.height" and "renderer.width" will scale the Canvas according to the current Scale Mode used. And this is easy to document by overriding the properties but not changing them).
    • CommentAuthorRichard
    • CommentTimeApr 11th 2008
     
    Please try the new Bitmap based BitmapRenderer code using the test code I provided. Then, as you did before, post some code that breaks it.

    I did try it. I didn't bother posting a new example because the previous example that I posted breaks both of your versions. But if you want some new code, here's an example in which the renderer's position isn't changed from (0,0) and it still breaks your code.

    package
    {
    import flash.display.Sprite;
    import flash.geom.Point;

    import org.flintparticles.actions.Move;
    import org.flintparticles.counters.Steady;
    import org.flintparticles.displayObjects.Dot;
    import org.flintparticles.emitters.Emitter;
    import org.flintparticles.initializers.*;
    import org.flintparticles.renderers.BitmapRenderer;
    import org.flintparticles.zones.*;

    [SWF(width='500', height='500', frameRate='61', backgroundColor='#000000')]

    public class Test extends Sprite
    {
    public function Test()
    {
    var particleContainer:Sprite = new Sprite();
    addChild( particleContainer );
    particleContainer.x = 250;
    particleContainer.y = 250;

    var emitter : Emitter = new Emitter( );
    var renderer : BitmapRenderer = new BitmapRenderer( 250, 250 );
    emitter.renderer = renderer;
    particleContainer.addChild( renderer );

    emitter.counter = new Steady( 25 );
    emitter.addInitializer( new SharedImage( new Dot( 3 ) ) );
    emitter.addInitializer( new Velocity( new DiscZone( new Point( 0, 0 ), 100, 100 ) ) );

    emitter.addAction( new Move( ) );

    emitter.start( );
    }
    }
    }


    It breaks because some of the particles have a negative x and/or y position.

    Internally, the canvas is a Bitmap object. But you and I both agreed that developers should not have to know about the internal workings of the BitmapRenderer. Try stepping back from the implementation and thinking of the BitmapRenderer as just another renderer...

    Renderers draw particles. Particles exist in 2D space. The Cartesian coordinate system used by Flash allows for both positive and negative values for the x and y coordinates of positions in 2D space. In common with this, the positions of the particles may have both positive and negative values for their x and y coordinates. A renderer draws the particles in this 2D space.

    But, because the BitmapRenderer cannot be designed to render objects over the full infinite region of this 2D space, we have to ask the developer to select a limited region of this space for which the renderer will draw the contents. This region, which we call the canvas, is in the same 2D space as the particles. Because particles can move anywhere within this 2D space, we should try to allow developers to place the canvas anywhere within this 2D space. That requires that the canvas be able to occupy regions of the space where the x and/or y coordinates are negative.

    If you're still not convinced, please consider one other thing - just because you don't use particles with negative values that doesn't mean that no-one else does (I do, for one). Using the Rectangle adds more flexibility, not less. It won't stop you doing what you're doing, but it will let other people do other things that they couldn't otherwise do. I can't think of any reason to be against that.
    • CommentAuthorericr
    • CommentTimeApr 14th 2008 edited
     
    I understand how things work and how they're supposed to work.

    My code is not broken. Your "Test" code above does *not* break it.

    If you want the dots to show up at the middle of the bitmap then you must set the "emitter.x / emitter.y" properties

    I've already linked these but there you are again. Further, here's exactly what I'm pointing out:

    "Emitter.x -> Indicates the x coordinate of the Emitter instance relative to the local coordinate system of the Renderer."
    "Emitter.y -> Indicates the y coordinate of the Emitter instance relative to the local coordinate system of the Renderer."

    I understand your concern. I actually posted 'negative particle' test code above and everything worked as expected. Your test code makes total sense above. I am sorry but moving the Container (and thus moving the renderer) will not change the fact that you defined the emitter point at the (0,0) spot of the renderer; the top-left corner!

    But you already have a way of moving the effect to the middle: Shift the entire emitter in the renderer by setting the emitter.x/y properties I mentioned above. Add the following to the code and you'll see that the emitter spits particles in all directions:

    emitter.x = renderer.width / 2;
    emitter.y = renderer.height / 2;


    If you want the effect to show up in the middle of the screen, change the particleContainer.x/y values to 125 instead of 250. Done.

    I will maintain that changing the BitmapRenderer's position based on a container or the Renderer itself is only moving it around onscreen. If you want to move the Emitter around within the renderer then you should call Emitter.x/y.

    I maintain that this works well because it follows the rest of Flash's design. Putting (0,0) in the middle of an object (a-la 'standard' Cartesian coordinates) will make calculating locations a real pain to keep straight.

    Am I not correct when setting emitter.x/y? If nothing else, try that with the code you posted above. What do you see wrong with this model?
    • CommentAuthorRichard
    • CommentTimeApr 14th 2008
     
    My code is not broken. Your "Test" code above does *not* break it.

    Well, my test code certainly doesn't work. I'd call that a failing in your proposed renderer.

    I actually posted 'negative particle' test code above and everything worked as expected.

    Which do you think is the negative particle test? In all your versions of the BitmapRenderer, particles with negative coordinates are not visible.

    "Emitter.x -> Indicates the x coordinate of the Emitter instance relative to the local coordinate system of the Renderer."
    "Emitter.y -> Indicates the y coordinate of the Emitter instance relative to the local coordinate system of the Renderer."


    Yes. That is correct.

    I could also add

    BitmapRenderer.x -> Indicates the x coordinate of the registration point of the Renderer.
    BitmapRenderer.y -> Indicates the y coordinate of the registration point of the Renderer.

    But since the BitmapRenderer is a DisplayObject, I thought that was obvious.

    Putting (0,0) in the middle of an object will...

    I assume you mean (0,0) of the particle system. I have not placed (0,0) in the middle of the object. (0,0) is at the registration point of the renderer. This is consistent with all display objects within Flash.

    The coordinate space of the particle system is mapped directly to the coordinate space of the renderer. This is logical and is consistent with all other renderers within Flint.

    Also, it's not about placing the emitter in the middle of the renderer. It's about placing the emitter anywhere I like in the particle system, placing the renderer anywhere I like in its parent object, placing that parent object anywhere I like within its parent object, etc.

    And then placing the canvas anywhere I like within the particle system too. Because defining the canvas does not move the emitter and it does not move the particle system within the renderer. Defining the canvas defines the region of the particle system's coordinate space (also the region of the renderer's coordinate space, because they are the same) that is drawn.
    • CommentAuthorericr
    • CommentTimeApr 14th 2008 edited
     
    I am glad that we agree on the (0, 0) point being Flash-consistent. That makes things easier. Nice!

    By "break" I assume you mean that my code, when run without emitter.x/y values being set, displays the particles in the 4th Quadrant only, correct? What you want is to see the emitter blasting particles in all directions. Your test code should produce the same exact effect with a naively defined 'canvas'. Moving the particleContainer simply moves the Renderer's location around the screen: you're shifting the Renderer's (0, 0) point to the center of the screen.

    Desire: Particle emitter that shoots dots out in all directions from the center of the Stage.
    Parameters: 250x250 BitmapRenderer placed within a particleContainer Display Object.

    Steps: [With my code - assumes emitter setup in the test code]
    1. renderer = new BitmapRenderer(250, 250); - Creates new Bitmap with size 250x250.

    2. emitter.x/y = renderer.width/height / 2; - Position the emitter's (0, 0) point to the center of the Renderer/canvas.

    3. particleContainer.x/y = 125; - Moves the BitmapRenderer to the center of the Stage.


    Steps: - [With your code - assumes emitter setup in the test code]
    1. renderer = new BitmapRenderer(new Rectangle(-125, -125, 250, 250); - Creates new Sprite with size 250x250.

    2. particleContainer.x/y = 250; - Moves the BitmapRenderer to the center of the Stage.


    The difference is minimal. Both require foresight by the programmer on desired size and location. Mine, however, is much easier to understand as will be shown below.

    With my code I could resize the canvas by calling resizeWidth/Height. This would resize the width and height of the Bitmap and this was always reflected in the renderer.width/height values.

    With your code, you can resize the canvas by redefining the Rectangle. However, when you do that you can potentially change the width and height of the Sprite and the width/height values will always represent what the width/height's maximum size was throughout the course of its existence. Calling renderer.canvas = new Rectangle(0, 0, 250, 250); with your code changes the width/height of the BitmapRenderer, even though you specify the same 250px width/height as you did before. This also changes the width/height of your particleContainer object.

    The point is that my code results in easier-to-track resizing. With your method you have to keep track of how far you've pushed things in both the positive and negative directions in order to get some semblance of what's going on with those values.

    Finally, your code does not reflect what you see. If I call trace(particleContainer.x); after the steps outlined above with your method, I get "250", the center point of our 500x500 stage. BUT this doesn't reflect what we see on the screen: (0, 0) is supposed to be the top-left corner of Flash Display objects. So if I draw a box around its edges I should see a box around the lower right quadrant of the screen. The particles however, are now being drawn in the center of the Stage, outside of the parent object!

    The hierarchy of your test file looks like this ("->" means "child of"):
    Bitmap (canvas) -> Sprite (BitmapRenderer) -> Sprite (particleContainer) -> Sprite (test) -> Stage.

    Now let's see what's actually happening:
    Output this.stage.x/y and you'll get (0,0).
    Output this.x/y and you'll get (0,0). (That's relative to the parent (stage).
    Output particleContainer.x/y and you'll get (250,250). That's relative to the parent (test).
    Output renderer.x/y and you'll get (0,0). That's relative to the parent (particleContainer).
    Output renderer._bitmap.x/y and you'll get (-125,-125). That's relative to the parent (renderer). <--NO GOOD!

    And that's where confusion WILL arise. You've now broken consistency with Flash containers. There is no way to know where the particles are ACTUALLY APPEARING onscreen after you've set the canvas.

    I attempted to do that last line of code with stock Flint 1.0.1. The _bitmap property is protected (as it should be), meaning that I shouldn't even be able to get access to those values. I DID notice that the SVN code set that as public. And I hope that that's merely for testing purposes as granting direct access to _bitmap has dire consequences.

    Further, if someone wanted to redefine the Rectangle by saying something like "renderer.canvas = new Rectangle(0, 0, 250, 250);" they'd be stretching the renderer AND the particleContainer by 125 pixels in both directions. (This new Rectangle, by the way, works exactly the same way as my setup... except that it requires more Matrix multiplications, object overhead, etc.... and you can still reposition the particles to get the desired effect by moving the Emitter around.)

    The code I posted encapsulates things more nicely, it reduces Object overhead dramatically [especially when compared to your newest version], reduces computation requirements [no more per-particle matrix translations], and you can achieve the same exact effects with less DisplayObject-related side effects.

    What with my explanation do you take issue with?
    • CommentAuthorRichard
    • CommentTimeApr 14th 2008 edited
     
    (0, 0) is supposed to be the top-left corner of Flash Display objects

    No, no, no. (0,0) is supposed to be the registration point of Flash display objects. It seems that, from this simple mistake, many of your errors flow.

    The coordinate system of flash display objects allows for items to exist to the left and above the registration point (0,0). The only exception to this is the Bitmap object (this is an inconsistency in Flash). But renderers, in Flint, are not Bitmap objects, they are Sprites.

    If I call trace(particleContainer.x); after the steps outlined above with your method, I get "250", the center point of our 500x500 stage. BUT this doesn't reflect what we see on the screen.

    It reflects the registration point of the particleContainer, as it is supposed to according to the Flash documentation.

    With your code, you can resize the canvas by redefining the Rectangle. However, when you do that you can potentially change the width and height of the Sprite

    As I pointed out to you elsewhere, this is due to an error in the code. The fix is already in SVN and will be in the next download version.

    Output this.stage.x/y and you'll get (0,0).
    Output this.x/y and you'll get (0,0). (That's relative to the parent (stage).
    Output particleContainer.x/y and you'll get (250,250). That's relative to the parent (test).
    Output renderer.x/y and you'll get (0,0). That's relative to the parent (particleContainer).
    Output renderer._bitmap.x/y and you'll get (-125,-125). That's relative to the parent (renderer).

    You've now broken consistency with Flash containers.


    Again NO. This is entirely consistent with Flash containers.

    Also, consider that the user doesn't know about _bitmap. The renderer draws particles and the particles have x/y coordinates. A particle at position (x,y) will be drawn at position (x,y) relative to the renderer.

    What you want is to see the emitter blasting particles in all directions.

    What I want is the option to define the canvas so I can see particles travelling in all directions from an emitter placed at (0,0). I can implement the emitter your way...


    1. renderer = new BitmapRenderer(new Rectangle( 0, 0, 250, 250) );

    2. emitter.x/y = renderer.width/height / 2;

    3. particleContainer.x/y = 125;


    but I don't want to be forced into that. I prefer to have the option to place the canvas anywhere within the renderer's/particle system's coordinate space.

    What with my explanation do you take issue with?

    Much. But most significantly, I suspect, the fact that you think the (0,0) point in flash display objects is always at the top left corner of the display-object's contents. It is not.

    According to the Flash docs on x/y properties for display objects

    "Indicates the x/y coordinate of the DisplayObject instance relative to the local coordinates of the parent DisplayObjectContainer. If the object is inside a DisplayObjectContainer that has transformations, it is in the local coordinate system of the enclosing DisplayObjectContainer. Thus, for a DisplayObjectContainer rotated 90° counterclockwise, the DisplayObjectContainer's children inherit a coordinate system that is rotated 90° counterclockwise. The object's coordinates refer to the registration point position."

    Try this

    var sprite:Sprite = new Sprite();
    addChild( sprite );
    sprite.x = 100;
    sprite.y = 100;
    sprite.graphics.beginFill( 0xFF0000 );
    sprite.graphics.drawCircle( 0, 0, 100 );
    sprite.graphics.endFill();


    The sprite is at (100,100) but its contents reach back to the top and left of the stage. i.e. the sprite's top left corner is at (-100,-100).

    And I hope that that's merely for testing purposes.

    It was, and shouldn't have been checked in. Thanks for catching it.
    • CommentAuthorericr
    • CommentTimeApr 14th 2008 edited
     
    var sprite:Sprite = new Sprite();
    addChild( sprite );
    sprite.x = 100;
    sprite.y = 100;
    sprite.graphics.beginFill( 0xFF0000 );
    sprite.graphics.drawCircle( 0, 0, 100 );
    sprite.graphics.endFill();

    Well I'll be a monkey's uncle.

    And with that post you have rewritten my understanding of the Flash Display Object system. This raises two questions:

    1. How do we get the 'bounding box' of a Sprite? In the code you posted above (and I copied in this post), what can I do to tell that sprite's bounding box is defined by (0, 0)->(200, 200)?

    2. Why not have a Bitmap version of BitmapRenderer? There are benefits to the code that I posted (major speed enhancements) and my class could easily be rewritten to incorporate the Rectangle method.


    With all the other Renderer's, you're adding DisplayObject children to them. The only thing you get by defining BitmapRenderer as a sprite is the ability to allow people to drop Display Objects into its Display List. For me, the Particles are the icing on the cake: the last thing to go on (especially in a Flash game). What's wrong with also giving people the option to use BitmapRenderer2 or some-such?

    Err... see the next three posts for documented implementations.
    • CommentAuthorericr
    • CommentTimeApr 14th 2008 edited
     
    Bitmap based BitmapRenderer2:
    package org.flintparticles.renderers
    {
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.DisplayObject;
    import flash.filters.BitmapFilter;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;

    import org.flintparticles.particles.Particle;

    /**
    * The BitmapRenderer draws particles onto a single Bitmap display object.
    *
    * <p>New to Flint version 1.0.2, this class differs from the BitmapRenderer
    * class in that it extends Bitmap instead of Sprite. As such it cannot have
    * children like its brother.</p>
    *
    * <p>Other than this difference, BitmapRenderer and BitmapRenderer2 are identical.
    * Use BitmapRenderer2 if you are optimizing for speed and do not need
    * DisplayObjectContainer functionality.</p>
    *
    * <p>The BitmapRenderer2 does not receive mouse events as it is a Bitmap and not a
    * Sprite.</p>
    *
    * @see org.flintparticles.renderers.BitmapRenderer
    */
    public class BitmapRenderer2 extends Bitmap implements Renderer
    {
    private var _preFilters:Array;
    private var _postFilters:Array;
    protected var _smoothing:Boolean;
    protected var _canvas:Rectangle;

    /**
    * The constructor creates a BitmapRenderer. After creation it should be
    * added to the display list of a DisplayObjectContainer to place it on
    * the stage and should be applied to an Emitter using the Emitter's
    * renderer property.
    *
    * @param canvas The area within the renderer on which particles can be drawn.
    * Particles outside this area will not be drawn.
    * @param smoothing Whether to use smoothing when scaling the Bitmap and, if the
    * particles are represented by bitmaps, when drawing the particles.
    * Smoothing removes pixelation when images are scaled and rotated, but it
    * takes longer.
    */
    public function BitmapRenderer2( canvas:Rectangle, smoothing:Boolean = false )
    {
    _smoothing = smoothing;
    _preFilters = new Array();
    _postFilters = new Array();
    _canvas = canvas;
    super( null, "auto", _smoothing );
    createBitmap();
    }

    /**
    * The addFilter method adds a BitmapFilter to the renderer. These filters
    * are applied each frame, before or after the new particle positions are drawn, instead
    * of wiping the display clear. Use of a blur filter, for example, will
    * produce a trail behind each particle as the previous images blur and fade
    * more each frame.
    *
    * @param filter The filter to apply
    * @param postRender If false, the filter is applied before drawing the particles
    * in their new positions. If true the filter is applied after drawing the particles.
    */
    public function addFilter( filter:BitmapFilter, postRender:Boolean = false ):void
    {
    if( postRender )
    {
    _postFilters.push( filter );
    }
    else
    {
    _preFilters.push( filter );
    }
    }

    /**
    * Removes a BitmapFilter object from the Renderer.
    *
    * @param filter The BitmapFilter to remove
    *
    * @see addFilter()
    */
    public function removeFilter( filter:BitmapFilter ):void
    {
    for( var i:uint = 0; i < _preFilters.length; ++i )
    {
    if( _preFilters[i] == filter )
    {
    _preFilters.splice( i, 1 );
    return;
    }
    }
    for( i = 0; i < _postFilters.length; ++i )
    {
    if( _postFilters[i] == filter )
    {
    _postFilters.splice( i, 1 );
    return;
    }
    }
    }

    /*
    * Create the BitmapData object
    */
    private function createBitmap():void
    {
    if( !canvas )
    {
    return;
    }
    if( bitmapData )
    {
    bitmapData.dispose();
    }

    bitmapData = new BitmapData( _canvas.width, _canvas.height, true, 0 );
    x = _canvas.x;
    y = _canvas.y;
    }

    /**
    * The canvas is the area within the renderer on which particles can be drawn.
    * Particles outside this area will not be drawn.
    */
    public function get canvas():Rectangle
    {
    return _canvas;
    }
    public function set canvas( value:Rectangle ):void
    {
    _canvas = value;
    createBitmap();
    }

    /**
    * When the renderer is no longer required, this method must be called by the
    * user to free up memory used by the renderer.
    */
    public function dispose():void
    {
    if( bitmapData )
    {
    bitmapData.dispose();
    }
    }

    /**
    * @inheritDoc
    */
    public function renderParticles( particles:Array ):void
    {
    if( !bitmapData )
    {
    return;
    }
    var i:uint;
    var len:uint;
    bitmapData.lock();
    len = _preFilters.length;
    for( i = 0; i < len; ++i )
    {
    bitmapData.applyFilter( bitmapData, bitmapData.rect, new Point( 0, 0 ), _preFilters[i] );
    }
    if( len == 0 && _postFilters.length == 0 )
    {
    bitmapData.fillRect( bitmapData.rect, 0 );
    }
    len = particles.length;
    if ( len )
    {
    for( i = 0; i < len; ++i )
    {
    drawParticle( particles[i] );
    }
    }
    len = _postFilters.length;
    for( i = 0; i < len; ++i )
    {
    bitmapData.applyFilter( bitmapData, bitmapData.rect, new Point( 0, 0 ), _postFilters[i] );
    }
    bitmapData.unlock();
    }

    <---SNIP--->
    • CommentAuthorericr
    • CommentTimeApr 14th 2008
     
    /**
    * Used internally here and in derived classes to alter the manner of
    * the particle rendering (e.g. in the PixelRenderer class).
    */
    protected function drawParticle( particle:Particle ):void
    {
    if ( _canvas.x != 0 && _canvas.y != 0 )
    {
    var matrix:Matrix;
    matrix = particle.matrixTransform;
    matrix.translate( -_canvas.x, -_canvas.y );
    bitmapData.draw( particle.image, matrix, particle.colorTransform, particle.image.blendMode, null, _smoothing );
    }
    else
    {
    bitmapData.draw( particle.image, particle.matrixTransform, particle.colorTransform, particle.image.blendMode, null, _smoothing );
    }
    }

    /**
    * @inheritDoc
    */
    public function addParticle( particle:Particle ):void
    {
    }

    /**
    * @inheritDoc
    */
    public function removeParticle( particle:Particle ):void
    {
    }
    }
    }
    • CommentAuthorericr
    • CommentTimeApr 14th 2008 edited
     
    Bitmap based PixelRenderer2:
    package org.flintparticles.renderers
    {
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.geom.Rectangle;

    import org.flintparticles.particles.Particle;

    /**
    * The PixelRenderer draws particles as single pixels on a Bitmap display object.
    *
    * <p>New to Flint version 1.0.2, this class differs from the PixelRenderer
    * class in that it extends Bitmap instead of Sprite (through BitmapRenderer2).
    * As such it cannot have children like its brother.</p>
    *
    * @see org.flintparticles.renderers.BitmapRenderer2
    * @see org.flintparticles.renderers.FullStagePixelRenderer
    */
    public class PixelRenderer2 extends BitmapRenderer2
    {
    /**
    * The constructor creates a PixelRenderer. After creation it should be
    * added to the display list of a DisplayObjectContainer to place it on
    * the stage and should be applied to an Emitter using the Emitter's
    * renderer property.
    */
    public function PixelRenderer2( canvas:Rectangle )
    {
    super( canvas );
    }

    /**
    * Used internally to draw the particles.
    */
    override protected function drawParticle( particle:Particle ):void
    {
    bitmapData.setPixel32( Math.round( particle.x - _canvas.x ), Math.round( particle.y - _canvas.y ), particle.color );
    }
    }
    }


    ASDoc info included.

    I did this because I see utility in it: they're faster/smaller than the Sprite-based versions and should behave identically (except for the DisplayObjectContainer aspects... more overhead when I'm just adding them as quick, efficient effects to a game).

    Thoughts on this?

    [Note: I based these classes off the SVN version of the code that included the "private _bitmap:Bitmap;" fix. Refresh->copy->pasted about ten minutes ago.]
    • CommentAuthorRichard
    • CommentTimeApr 15th 2008
     
    How do we get the 'bounding box' of a Sprite?

    sprite.getBounds();

    Why not have a Bitmap version of BitmapRenderer?

    You can't allow negative x/y coordinates without physically moving the bitmap. If the renderer is the bitmap, then you are moving the renderer, which is counter-intuitive - I place the renderer in one place then the renderer decides to move elsewhere without my knowledge. In your code, this is

    x = _canvas.x;
    y = _canvas.y;


    If the renderer is a sprite that contains the bitmap, then the bitmap can be moved without moving the renderer.

    To be clear, the only problem I see with the bitmap being the renderer is this inability to show negative coordinate positions without moving the renderer itself. If one accepts that limitation, I see no other problem with it.

    I wouldn't want to include it in Flint because the speed increase will be minimal and the possibility for confusion between the two very strong. Fortunately, though, Flint is open source and if they program to the Renderer interface, as you have done, developers can make their own optimised renderers for specific purposes.
    • CommentAuthorericr
    • CommentTimeApr 15th 2008
     
    sprite.getBounds();
    Awesome. That really rounds things out quite nicely!

    You can't allow negative x/y coordinates without physically moving the bitmap. If the renderer is the bitmap, then you are moving the renderer, which is counter-intuitive - I place the renderer in one place then the renderer decides to move elsewhere without my knowledge.
    That said, it's the same thing as what you've done. Except that you're allowing the designer direct access to that location instead of handling it internally. In my estimation it's actually a bit more straightforward than the current Flint-based version. Though, it did take some 30 posts for me to fully grasp the whole concept of negative coordinates in Flash... Essentially people get finer control with this (they control the container and the Bitmap within it).

    The speed enhancements are huge for games. I'm running an OSS Physics Engine concurrently with Flint and every little cycle/memory block is super important.

    I wouldn't want to include it in Flint because the speed increase will be minimal and the possibility for confusion between the two very strong. Fortunately, though, Flint is open source and if they program to the Renderer interface, as you have done, developers can make their own optimised renderers for specific purposes.
    True. I am going to continue to use my version as it excels at what I need it to do. As you post version updates I might update my code on the forums to reflect the changes in case anyone else is interested in some slightly optimized code. :)

    Again, great work!