Wednesday, 30 June 2010

Keyframe madness

Behold, the promised post! It promises promising developments, developments which seek to fulfil the promise of a promising new keyframe system. There we go, now the word 'promise' looks strange to me. If it doesn't to you, try re-reading the last two sentences again. It's a pretty silly word, isn't it? But I digress.

You may recall from the last post that I've been working on a brand new keyframing system for PyIgnition. Indeed, the more astute amongst you may recall from the above paragraph that developments of a promising nature have occurred in the aforementioned area. Well, I'm glad to say that both observations are indeed in line with the most glorious reality about to unfold before you. That is, the new keyframe system is fully implemented and working rather well.

However, the coding process most certainly hasn't been without challenge. Indeed, it was rather horrific. Essentially, the new keyframe system introduced a somewhat more complicated InterpolateKeyFrames() function which took a little bit longer to complete. Shouldn't really be a problem, no? Well, when this function is running every frame on several hundred particles, each with several keyframes and variables to loop through, the slight difference becomes a seriously massive difference. As a result, even the simplest of scripts like the original fire demo would grind to a near-halt within seconds. And unfortunately no amount of ridiculous over-optimisation of the interpolation function yielded any real improvement.

But wait! All is not lost! For once all hope seemed to have dropped away (much like PyIgnition's frame rates), a guiding light of truth shone through the muck of malformed code and illuminated a speedy solution, bright and gleaming, the Excalibur of my code. Well, to be fair it wasn't quite as romanticised as that. In reality I spent many an age trawling the web for better ways to interpolate between frames, whilst simultaneously trying every optimisation technique I could find on the existing code - literally to the point of trying to use local variables over global variables because they're apparently faster, and avoiding using object.function() (preferring a simple function() ) because it's supposedly slower. The latter saw no avail, regrettably, but the former took me on a most interesting tour of the interwebs. And, I'm pleased to say, I came upon a solution!

You see, the particles belonging to one source are all spawned with exactly the same keyframes - so the same interpolation calculations are being done for every particle, just at different times. This is obviously wasteful. So instead of doing these same calculations, every single time, the program now pre-calculates what the variables of a particle should be for every frame of its life and saves them all to a big old lookup-table-esque array thing. This is stored in the particle source object, and is accessed from created particles through a reference to their parent source. When a particle keyframe is created with the source via the CreateParticleKeyframe() method, the source immediately updates this lookup-table-esque array using its PreCalculateParticles() function.

You'll probably have noticed that there's a definite space-time tradeoff here. Obviously the particle keyframes no longer pose any problems whatsoever, as the particle update method now simply sets each of the particle's variables to the ones stored by the parent source for the current frame. This means that the program is now actually slightly faster than it was before the new system was implemented. However, the thought of storing every variable for every frame of a particle's life is slightly unnerving, as one would assume that it would mean massive memory consumption. However, this is actually not as significant as you might think.

Here's an example. We have a fairly typical particle effect with keyframed particles, which emits 20 particles per frame with a particle lifespan of 600 (both of these are probably larger than you'd ever need to use in practice, but I'm using exceptionally large values just to show how little memory the particle caching actually requires). Now, at present, a particle object can keyframe the following variables:

  • colour - 3*int = 12 bytes
  • radius - 1*float = 8 bytes
  • length - 1*float = 8 bytes

This gives us a total size of 28 bytes for a single set of stored variables. Now, I haven't the foggiest idea how Python stores dictionary objects (which is how these variables are stored in the lookup-table-esque array thing), so just for laughs we'll double this value to 56 bytes, a size it will probably not come close to reaching in reality. Again, this is just to show how little space it actually requires. So, to store keyframed values for every frame of a particle's life, we would need to store 600 of these variable sets. That gives us a final memory usage of 33, 600 bytes or 33.6 kilobytes. Not bad for an overly large value you're unlikely to ever reach in practice, eh? So really, all things considered, this business of pre-calculating keyframed particles is almost certainly the way forward. It's definitely made things run faster anyway.

And that's where I am now. Well, actually, I also added setter methods for keyframeable variables - simply setting them in the 'object.variable = newvalue' fashion simply won't work now as it would be overridden by keyframe interpolation, so you would now use 'object.SetVariable(newvalue)', which creates a keyframe for 'variable' at the supplied value on the current frame. Next up is giving the user the opportunity to select interpolation modes (followed by a bit of bugfixing of course), and the next release - which has been upgraded to Beta 1, incidentally - will be ready!

Friday, 25 June 2010

...And the post frequency meter was striking thirteen!

Yes, I know - two posts in as many days! Utter madness! The post frequency meter is indeed frazzled. But this is merely a swift news-deposit before I head off to undertake the elaborate job of implementing the new keyframing system in PyIgnition for the Alpha 4 ('controlled eruption') release. Incidentally, on that topic, I've just this morning sorted out completely how I'm going to implement the new system. It's all scribbled down on assorted scraps of paper (many of which lie within my own head), and just needs to be coded now. However, as mentioned a few sentences ago, it'll be a long and difficult process. Updates will follow when it's underway!

So, to return to the planned purpose of this present posting, I've done a bit of dabbling in wxPython today for the first time in ages and I must say I'm addicted all over again. It's simply brilliant. And, I'm pleased to announce, fully compatible with Pygame after a little bit of fiddling around. Here for your viewing is a screenshot of 'Wind.py' running on a wxPanel, completely under the command of wxPython:


The window is even resizeable, and the particle effect display scales with it. Incidentally, 'Obsidian' is a name that we were originally considering for what is now PyIgnition, and at the moment is probably going to be the name of the particle effects editor. More incidentally, the more astute amongst you may have noticed that the window looks a bit different to those seen in screenshots I've posted in the past. Why, you ask? Well, I've finally upgraded from the old XP machine (which is probably getting near its sixth birthday now), and with the new one came a new OS: Windows 7. It's rather good actually; I fully agree with those saying it's a vast improvement over Vista. But I digress.

Using pygame in wxPython applications is actually not too complicated a process, although it was incredibly difficult to find anything about it on the Internet when I looked so I'll post my findings here in the hope that the next person who tries it will find them through a Google search. There's only really one source I could find that explains it, and that's the one at http://wiki.wxpython.org/IntegratingPyGame. However, it's a bit iffy in its explanation. In case you don't notice immediately, they miss the part where they actually give the wxFrame a child to use as a pygame display - you'll need to remember to create this yourself. I just used a simple wxPanel.

The code to look at is line 10 - this makes SDL see your widget (or wxWindow, to be terminologically accurate) as the display. From there you can simply use pygame.display.init() as shown, and get a handle to the screen with self.screen = pygame.display.set_mode(). Normally in Pygame one would supply screen dimensions to that function, but here it's probably a good idea to leave them out. However I haven't tried supplying them so I don't know whether it would work or not, so feel free to experiment and see what happens. Now, although the link says you shouldn't import pygame until after setting the SDL_WINDOWID parameter, this is actually a pain because you then don't have access to pygame's functions anywhere else. I actually found that it works perfectly well if you do import pygame at the top of the file, so there's probably nothing wrong with doing so any more. That said, I'm just going with what seems to work so feel free to correct me if I'm wrong and there are hidden issues with doing so.

With that done, you're free to do things like self.screen.fill((0, 0, 0)) and pygame.draw.aaline(screen, [blah...]) to draw to the pygame surface, so all is well from there on. But is it? As a matter of fact, you'll face problems when you try to exit - neither pygame nor wxPython wants to close first, and you'll get errors either way. However, after a bit of messing around I've managed to find a solution to this. Simply bind the following function to WX_EVT_CLOSE:

____def Kill(self, event):
________self.Unbind(event = wx.EVT_PAINT, handler = [your drawing function])
________pygame.quit()
________self.Destroy()

See, the problem that arose in making Pygame quit first was that wxPython would for some reason try to draw again before it exited. This naturally causes problems when your drawing function contains Pygame draw functions, as they will be operating on a surface which has been disabled on Pygame's exit. Unbinding the draw function avoids this issue, and lets pygame exit first with wxPython smoothly following suit afterwards.

And that's pretty much it. So far I haven't found any more issues with the two working together, but I'll update if I do. I hope this proved somewhat helpful. So, back to keyframes!
Goodbye!

Thursday, 24 June 2010

It was a bright cold day in April...

You asked me once, what was in Post 101. I told you that you knew the answer already. Everyone knows it. The thing that is in Post 101 is... well, pretty cool. All cultural references aside, the content of this amusingly-numbered post is in fact rather spiffy, and comes in the manifestation of an update on PyIgnition's progress. To summarise, it's been going rather spiffily. So spiffiliy indeed that I decided it warranted a post. So! To make haste t'wards the very tip of this post's point, I shall cast in your general direction some video footage of PyIgnition, post mutatio:

PyIgnition alpha 3 from Animatinator on Vimeo.



So as you can see, the promised physics system is very much in place. Particles can now deflect off objects thanks to a combination of what is effectively an inverse gravity (but using an inverse cube law instead of inverse square to ensure objects aren't visibly repelled from a distance), and a preceding intersection resolution stage. The latter is a fairly simple process which firstly works out whether any particles are inside obstacles and then shifts them back out to appropriate places. The two procedures are defined as functions for collision obstacles (as InsideObject() and GetResolved() respectively), so the code handling the overall process is just a couple of lines:

if (not obstacle.OutOfRange(particle.pos)) and (obstacle.InsideObject(particle.pos)):
____particle.pos = obstacle.GetResolved(particle.pos)

(The first part of the if statement is just checking if the particle is within reasonable range of the obstacle before it checks for an intersection.) I also added a 'bounce' multiplier to the repulsive force which is mainly set to a value below 1.0, as the force often tends to be a bit big and results in particles gently drifting towards an obstacle only to be propelled away so quickly that the program would need to take the effects of special relativity into account in order to process them correctly. In all of the demos shown this was set to a standard value of 0.2, which causes only a slight bounce when particles collide with obstacles.

So, that concludes this decade's blog update. See you next time, when I'll hopefully be bringing news about the work-in-progress new-and-vastly-improved keyframe system!

Saturday, 12 June 2010

Happy 100th post!

Good day to you all again, my few-and-far-between readers! Welcome, one and all, to what you may already have ascertained is in fact the hundredth discrete outpouring of textual miscellany to grace the monitors of its unfortunate readers. Those of you who have already managed to stumble upon this site before and are (amazingly) visiting again may have noticed that I've decided to mark this occasion with a complete visual redesign. This is in no way linked to Blogger's new, more advanced 'Template designer' page being released today. I hope you'll agree that it looks much nicer than the dull, flat and lifeless look it once bore. And if you don't, well, bollocks to you. It's my blog.

So! What lies on the programme for this milestone of a post? Well, remember that ultra-secret project I alluded to in the last post? The project so secret I daren't even mention it within this particular paragraph?


</p> <p>


Introducing PyIgnition!


PyIgnition alpha 2 from Animatinator on Vimeo.



This is a project I've been working on for about a week now. It's essentially an advanced particle effects engine for use in pygame, and it supports all sorts of nifty features. Most significantly, almost every parameter available is fully keyframeable, which lets you create fairly complex animations. Also handy:
  • (Theoretically) infinite number of particle sources
  • (Theoretically) infinite number of gravities (used for influencing particles' paths), which can be either point gravity (following Newton's law of gravitation) or constant gravity in one direction
  • Several particle drawtypes, from the standard points and images to more exotic types like bubbles
  • Simple interface which also allows plenty of freedom - if you don't like its keyframing system, you could even write your own outwith the library and use it to control parameters of library objects. Anything's possible with a bit of faffing around
  • Physics system (currently under development) to allow you to insert obstacles for particles to collide with and bounce off
Basically, it makes the difficult and arduous task of implementing real-time, interactive particle effects quick and simple. So, this shall be the new focus of this blog's output as we stride onwards into what I hope will be a second hundred posts. Accordingly, I've updated the information on the right-hand side of the page with links to the project's Launchpad page (where you can find its source code and track its progress without relying on me getting around to writing about it here) its screenshot gallery (where, funnily enough, I'll post screenshots of it in action) and its pygame project page. Check them out! As they say. So, I suppose all that remains now is to sign off this milestone post with a grandiose rendering of its number: