Flake

Drawing Images onto a PIXI.Graphics obj

Recommended Posts

Hey guys!

 

I've got one main question, with a few follow ups.. here goes :)

 

I was wondering if there was a way draw an image onto the PIXI.Graphics (object thingy)?

I'm aware I can add sprites to the stage and other containers. But I'm currently drawing polygons, and images (with a dynamic render order), So this seems like a good way to do that.

 

 

Is there a way of doing this, similar to the plain/vanilla canvas way:

var canvas = Dom.get('canvas');var context = canvas.getContext('2d');context.drawImage(source, x, y, w, h, ...);
 I've tried:
 var context = pixiRenderer.context;

.. but this only returns the following: CanvasRenderingContext2D {}

 

And now for the follow ups:

Is the context (2d), unique to the Canvas? Would drawing imaged to the graphics object limit me to the CanvasRenderer, or could I still use PIXI.autoDetectRenderer and PIXI.WebGLRenderer ?

 

Thanks in advance!

Share this post


Link to post
Share on other sites

Sounds like what you need is to have a sprite child for that graphics object.  You see, technically, the entire canvas (whether 2d or webgl, doesn't matter) is one big image.  PIXI just draws different parts of the image separately to create the whole.  So when you render a container, you will draw each child of that container (whether sprites, graphics, or something else) and all of its descendants (children, children of children, etc) in order from top (root) to bottom (leaves).  The different parts may overlap, but at the end of the day the entire tree is reduced to an image.
 
So do you want an image loaded from file to be in the middle of your graphics object?  Easy.  Just position the image object (sprite) so that it is in the middle of your graphics object.  The easiest way to do that is to make the sprite a child of the graphics object, so that its position (x and y coordinates) are relative to the top-left corner of the graphics object.  Parent/child relationships are not required, mind you, it is just a convenient way to get relative positioning (and also guarantee that the child is drawn after the parent, possibly overlapping).
 
Does the whole paradigm of drawing position and order make more sense now?  With all that in mind, I'll answer each question specifically:
 
1) Is there a way of doing this, similar to the plain/vanilla canvas way:
 

// untested// 2d or webgl, doesn't matter.var renderer = PIXI.autoDetectRenderer(300,300);var canvas = renderer.view;var stage = new PIXI.Container();var sprite = PIXI.Sprite.fromImage(source);var graphics = new PIXI.Graphics();stage.addChild(graphics);graphics.addChild(sprite);// assuming graphics object is 100w x 100h// graphics position is centered horizontally and vertically relative to stage/canvas.// absolute position of the graphics object is 100 points right and 100 points down from the top-left corner of container (parent).graphics.position = new PIXI.Point(100,100);// assuming sprite object is 50w x 50h// sprite position is centered horizontally and vertically relative to graphics object.// absolute position of the sprite object is 125 points right and 125 points down from the top-left corner of container (grandparent).sprite.position = new PIXI.Point(25,25);// draw some lines n stuff on the graphics object.// Only reason I'm putting it in the ticker is just in case the image hasn't loaded yet.// We'll render 60 frames per second until it does... and after it does!PIXI.ticker.shared.add(function () {  renderer.render(stage);});// put the canvas somewhere in the DOM.

 
 
2) Is the context (2d), unique to the Canvas?
 
Yes.  2d is a context.  webgl is a context.  PIXI calls the 2d context renderer "Canvas".  Both contexts are built on a "canvas" dom element.  Confusing?  Yes.
 
3) Would drawing imaged[sic] to the graphics object limit me to the CanvasRenderer...
 
No.  PIXI abstracts the WebGL nonsense (it's pretty arcane) away from your eyes so that you can create sprite objects that can be rendered magically using webgl context or rendered boringly using 2d context.

Share this post


Link to post
Share on other sites

Hi @pongstylin, thx for the reply.

Your way works if you want to draw over(or under) the sprite. I'm trying to mix my render order like this.
 

function render (){ graphics.clear(); draw poly draw rect draw image dra line draw rect draw image *about 300 more...}

The list chnages dynamically in length, and the positions shift too. That's why I'd like to render the spirte/texture to canvas, instead of adding it as a child.

Share this post


Link to post
Share on other sites

Graphics do not draw images, so that won't happen.

 

Instead if this thing doesn't change often once drawn, you can draw it all to a canvas and use that as a texture. You could also put multiple graphics and sprites into a container to build the object you want and render that to render texture and use that as a sprite, or just use the container itself in the scene.

Share this post


Link to post
Share on other sites

Graphics do not draw images, so that won't happen.

 

Feared as much. And yup, it changes basically every frame.

I guess I'll stick to the native vanilla canvas for this project.

 

Thanks again @pongstylin @xerver!

Share this post


Link to post
Share on other sites

Flake, I'm pretty sure you can still get 'er done the way you like in WebGL.  Reminder, I said that parent/child relationships are OPTIONAL.  So if they make things awkward, you don't have to use them.  For example, using your latest example:

function render (){  var graphics,sprite;  // Remove all objects we rendered last time so that we can add a new set.  stage.removeChildren();  graphics = new PIXI.Graphics();  // draw poly  stage.addChild(graphics);  graphics = new PIXI.Graphics();  // draw rect  stage.addChild(graphics);  sprite = PIXI.Sprite.fromImage(source);  stage.addChild(sprite);  graphics = new PIXI.Graphics();  // draw line  stage.addChild(graphics);  graphics = new PIXI.Graphics();  // draw rect  stage.addChild(graphics);  sprite = PIXI.Sprite.fromImage(source2);  stage.addChild(sprite);  // about 300 more}

Notice that I'm creating a new graphics object for every shape.  I'm creating a new sprite object for every image.  I add them to the stage in order so that later objects layer on top of earlier objects.  Every object is positioned using absolute coordinates since we're not using parent/child relationships.  This should more closely match what you're doing already for the canvas.  Regardless of whether you are working with 2d canvas manually, or if you are using PIXI, you are drawing the final product layer-by-layer.  So what you can do manually, PIXI can do for you on 2d or webgl context.

 

You see, you can't draw images on a graphics object.  But you don't NEED to.  You just gotta stop thinking of the graphics object as if it were your entire canvas.  It's just a layer.  You may draw more than one shape on a graphics object to be in that layer, but I fragmented the layers above just in case your code is similarly fragmented.

Share this post


Link to post
Share on other sites
PIXI.ticker.shared.add(function () {  renderer.render(stage);});

 

Wow, you understand how Pixi's ticker works!

 

Could you please explain briefly what that code above does and why you chose to use it?

And why use  the ticker instead of `requestAnimationFrame`?

I can't make sense of how to use the ticker from Pixi's docs (and I would never have guesses to use `ticker.shared`.)

Any tips would be most appreciated! 

Share this post


Link to post
Share on other sites

Could you please explain briefly what that code above does and why you chose to use it?

And why use  the ticker instead of `requestAnimationFrame`?

 

The Ticker documentation is here:

http://pixijs.github.io/docs/PIXI.ticker.Ticker.html

 

And here: (It makes reference to using PIXI.ticker.shared)

http://pixijs.github.io/docs/PIXI.ticker.html

 

Although, I tend to prefer to use the code as my documentation:

http://pixijs.github.io/docs/core_ticker_Ticker.js.html

 

Essentially, PIXI.ticker.shared is an instance of the PIXI.ticker.Ticker class.  This instance is used by the Interaction Manager, although I'm trying to figure out why since it seems unnecessary (nobody has responded to my post yet).  Of course, you are free to use the same instance, but keep in mind if you modify how it behaves, anything internal to PIXI that uses it may be affected.

 

The PIXI Ticker is based on requestAnimationFrame, so in function, it is little different from running your render loop manually using requestAnimationFrame.  But the object's event interface may be more attractive to use and look at since it involves less code.  Beyond this basic function, it offers these features:

 

1) Usability: You can start and stop it easily.

 

2) Efficiency: It auto-starts (you can disable this) when there are listeners/callbacks, and auto-stops when there aren't any.

 

3) Throttling: The requestAnimationFrame callback is passed the current time as an argument.  But the ticker callback is the number of frames that should have been rendered since the last call, depending on your target frames per second.  By default, PIXI.CONST.TARGET_FPMS (target frames per millisecond) is set to 0.06, which means you are targeting 60 frames per second.  In a perfect world, it would always say 1.  But if the browser lagged really bad, it might say 60, if it has been a whole second since the last call.  One possible use of this argument is to skip frames if it has a value >= 2.  Or, let's say you have a sprite moving from A to B.  It is supposed to move 2 points per frame, but the callback handed you 1.5 (which, if consistent, equals 40FPS).  That means you can move the sprite 3 points this frame instead of 2 to maintain the desired speed.

 

4) Throttling: The number of frames passed to the callback can be capped to a maximum value.  So to use the sprite movement example from #3, you can make sure the sprite doesn't jump too-large a distance in a single frame - even if the browser is lagging badly.

 

5) Throttling: The number of frames passed to the callback can be multiplied.  So to use the sprite movement example from #3, you can increase (i.e. multiply by 2) or decrease (i.e. multiply by 0.5) animation speed.

 

6) Usability: You can add callbacks that should be called only once by the ticker as opposed to a constant loop.

 

7) Usability: You can add or remove multiple callbacks at will.  All callbacks will be called in the same frame, so that multiple animations may be managed separately, but rendered in sync.  requestAnimationFrame supports this also, but keeping track of those request IDs can be more painful.

 

8) Usability: You could just stop the ticker and trigger callbacks manually.  For example, you could allow a user to stop the animations and present a button that allows them to step through frame-by-frame.

 

In essence, you got a bunch of little features here that cater to creative situations, but aren't useful in many scenarios.  But the idea of wrapping requestAnimationFrame into an object that may be manipulated carries with it a lot of potential.  Feel free to ignore it or use it, depending on your situation.

Share this post


Link to post
Share on other sites

Thanks pongstylin, epic post!

 

I'm wondering if you could use the ticker to easily set (or throttle?) the animation frame rate?

And whether using the ticker's `deltaTime` would make it easy to implement a sprite interpolation/extrapolation variable render time feature?

 

I'm going to study your extremely helpful post in more detail and report back :)

Share this post


Link to post
Share on other sites

As far as I can tell from the code, the ticker triggers callbacks every time requestAnimationFrame triggers its callback.  So if you're hoping that the ticker can be configured to triggered callbacks 40FPS instead of 60FPS, you can't.  But, just because the callback is triggered doesn't mean you have to render.  For example, the Interaction Manager adds itself to the shared ticker so that it can figure out if your mouse is hovering over something that can be interacted with and change your cursor to a pointer.  But the Interaction Manager only performs this check at 6FPS.  See how it does it below:

 

// ticker callback functionInteractionManager.prototype.update = function (deltaTime){    this._deltaTime += deltaTime;    // this.interactionFrequency is 10, by default    if (this._deltaTime < this.interactionFrequency)    {        return;    }    this._deltaTime = 0;    // manage some interaction stuff}

So it sums up the deltaTime (number of frames to render since last call) until it equals or exceeds 10 (6FPS when PIXI.CONST.TARGET_FPMS is set to 0.06).  So even though the update function gets called 60FPS, it only does its manager stuff 6FPS.  You could say that it is more flexible this way.  You can run some animations at 30FPS and others at 60FPS, using the same ticker object.  The only downside is if your entire app runs at 12FPS (this is what I use) then you got a loop running faster than you will ever need.  But this is the case if you use requestAnimationFrame manually as well.

 

Yes, since the deltaTime is the number of frames to render since last call, it should be easy to use in interpolation/extrapolation.  I kind of already described the example, but here's some code to drive it home.

var ticker,sprite,mover;ticker = new PIXI.ticker.Ticker();sprite = PIXI.Sprite.fromImage(imageUrl);mover = function (numFrames) {  // Move the sprite to the right by 2 points per frame at 60FPS.  // If this callback is called precisely on-time, then numFrames will be 1 and we'll move the prescribed 2 points.  // If this callback is called in double the time (30FPS), then numFrames will be 2 and we'll move 4 points.  // If this callback is called in the middle (40FPS), then numFrames will be 1.5 and we'll move 3 points.  sprite.position.x += numFrames * 2;  // stop the animation once we reach our destination.  if (sprite.position.x >= 100) {    sprite.position.x = 100;    ticker.remove(mover);  }};ticker.add(mover);

I'm not sure if this is interpolation or extrapolation (I'm really not familiar with the theory, I just logically conclude that this might be a normal use-case), but I think it's in the ballpark of what you're talking about.

Share this post


Link to post
Share on other sites

I'm not sure if this is interpolation or extrapolation 

It's extrapolation.

Interpolation would use position values from the previous frame.

 

Thank you so much, this is really useful to know!!!

Share this post


Link to post
Share on other sites
On 6/8/2015 at 4:33 PM, xerver said:

Instead if this thing doesn't change often once drawn, you can draw it all to a canvas and use that as a texture.

are there any examples of how one would do this?

I'm trying to implement simple tiling system so I need to draw thousands and millions of tiles. If I would go and create 1000x1000 sprites I will have decreasing FPS, event if using ParticleContainer. Instead of having each of them as spite object, I suppose to find a way just to draw it once somehow in container.

Share this post


Link to post
Share on other sites
3 hours ago, SET001 said:

are there any examples of how one would do this?

I'm trying to implement simple tiling system so I need to draw thousands and millions of tiles. If I would go and create 1000x1000 sprites I will have decreasing FPS, event if using ParticleContainer. Instead of having each of them as spite object, I suppose to find a way just to draw it once somehow in container.

http://pixijs.io/examples/#/basics/render-texture.js

 

For next time, prefer opening a new thread to reviving a 3 year old one :)

Share this post


Link to post
Share on other sites

I saw that example but being new to pixi.js I can't understand how it work. In this example it will still create as many Sprite objects as tiles on the map, add them to the container, add sprite with texture (which is empty, because rabbit sprites are added to the container) to the stage and then, using ticker (some sort of render loop?) will render container with rabbits, using render texture used previously to the sprite (which added to the stage)... I just don't understand how everything is working here. What actually is causing drawing rabbits on texture?? Why I can't draw it once, why I need those tickers loop?

In my case I just add few containers as a child for some root container. then I add dynamic entities on one container and static entities on another. Everything gets rendered as soon as i call addChild for `app.stage` with rootContainer as argument:

rootContainer = new PIXI.Container()
textureContainer = new PIXI.particles.ParticleContainer(200000)
entitiesContainer = new PIXI.particles.ParticleContainer(200000)

init(store: Store) {
    const resolutionX: number = window.innerWidth
    const resolutionY: number = window.innerHeight
    this.app = new PIXI.Application(resolutionX, resolutionY)
    document.getElementById('app').appendChild(this.app.view)

    const tiles = PIXI.BaseTexture.fromImage(`${config.publicPath}/tiles.png`)
    const tileSize = 32
    const grassTile = new PIXI.Texture(tiles, new PIXI.Rectangle(tileSize * 56, tileSize * 14, tileSize, tileSize))
    for (let i = 0; i < 1000; i++) {
      for (let j = 0; j < 1000; j++) {
        const grass = new PIXI.Sprite(grassTile)
        grass.position.x = i * tileSize
        grass.position.y = j * tileSize
        this.textureContainer.addChild(grass)
      }
    }
    this.rootContainer.addChild(this.textureContainer)
    this.rootContainer.addChild(this.entitiesContainer)
    this.app.stage.addChild(this.rootContainer)

}

Why do I need calling `render` in `tickers` loop?

with this approach i have few drawbacks:

1. the loop that will create 1000 x 1000 = 1 000 000 sprites and this loop is time expensive. Is it possible to somehow optimize this? The only optimization here I can think of is to draw only visible tiles. But at leas this happens once at the and of the game.

2. 1 million sprites will constantly present in system which as I guess, causing FPS drop. The FPS is actually as smaller as many sprites I add, so am I right thinking that sprites amount causing it? How this problem solved in http://pixijs.io/examples/#/basics/render-texture.js ? With that approach I will end up with same 1 million sprites causing FPS drop?

 

 

Share this post


Link to post
Share on other sites

Is it possible to somehow optimize this?

1. Maintain a "window" around your screen, like 2x time screen , fill it with tiles. If camera moves away from it too far, make a new window, fill it with new tiles. 
2. Make some tea
3. If you still dont understand the algorithm,go to 1.


Also, use pixi-tilemap, its faster than ParticleContainer for that cause.

Share this post


Link to post
Share on other sites
4 hours ago, ivan.popelyshev said:

3. If you still dont understand the algorithm,go to 1.

I do not understand why do I need to call `render`

why it should be called inside `ticker` 

I don`t understand how I can draw some static texture on come container

I don't understand how I can I avoid creating million of Sprite objects just to draw 10 static tiles on a texture or at least how to avoid FPS drop causing by that

 

If you can help to put a light to any of this questions - I would be thankfull

Share this post


Link to post
Share on other sites

Render & Ticker: https://github.com/pixijs/pixi.js/wiki/v5-Custom-Application-GameLoop - everything is true except "interaction", its a part of renderer, cant move it away.

static texture to container: 1. generate it with "generateTexture" or manually create RenderTexture and use "renderer.render" like here: https://pixijs.io/examples/#/basics/render-texture.js , then put it inside the sprite

avoid million of sprites: create only sprites that are around the screen in some "window", like 2x of screen. If your screen (0,0,800,600) , try (-400, -300, 1200, 900) . If camera goes too far, you have to refill your container with sprites of new window.

avoid million of sprites#2: use pixi-tilemap, it doesnt create extra objects per tile. Fill it with tiles, its like Graphics but for tiles.

Share this post


Link to post
Share on other sites

thanx, I'll try pixi-tilemap. Even with it I think I would need mange tiles to have only visible on screen (and around) to not load all tiles in the large world there...

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.