It’s about time I popped this bubble of silence.
I’ve been working hard on two programming projects this summer, namely my Google Summer of Code project and a fancy upgrade to some molecular dynamics software. In my previous posts, I looked at the back-end of Step and some of the mathematics of smoothed particle hydrodynamics. There are still plenty of outstanding problems in those areas but I’ll address those gradually in the coming weeks. For this post, I’ll give an overview of all the GUI/user interactivity stuff that i’ll have to tackle on my quest to implement fast fluid simulation.
I’ll just enumerate the ways the user can interact with a fluid and trace through exactly what is going on behind the scenes. I know, KDevelop/IDEs are pretty fancy, but I’m oldschool so I tend to just follow the code execution manually. It’s kind of like a grep-based treasure hunt! So, I’ll just give some running commentary with bonus screenshots from the current Gas classes. I doubt many readers of this blog will find this interesting, but my dream is that this post series may someday be useful for a new Step developer =P
Oh, if you’re a Qt newbie just remember that any class with a Q is a Qt class, and don’t forget your slots and signals.
User clicks the fluid object in the item palette and adds it to the Scene.
When any action, such as clicking on a button, is performed by the user, a QAction:triggered signal is emitted by that object. Within the itemPalette class (as pictured graphically above) we deal with this signal by linking it up to an actionTriggered slot.
QObject::connect(_actionGroup, SIGNAL(triggered(QAction*)), this, SLOT(actionTriggered(QAction*)));
This slot then passes over the responsibility of creating a new object from the itemPalette to the WorldScene’s beginAddItem method. The WorldScene handles all things graphical in Step. It inherits QGraphicsScene and provides us with a safe place for visualizing our 2D objects. Some day, when a brave Summer of Code student steps up to the challenge, Step will evolve to 3D and leave QGraphicsScene in the dust.
WorldScene then initializes a new ItemCreator. You can only add one item at a time to the WorldScene, so it checks to see if an itemCreator has already been defined, and if so it deletes it, while emitting an endAddItem signal. Anyway, since we don’t actually know the type of the item being created, we have to pass the item over to the worldFactory class to query our object. Once we know more about the object we call the specific newItemCreator class of that object, that in my case will be FluidCreator. It’s worth mentioning that every derived class form Item has it’s own Creator, GraphicsItem, and MenuHandler classes. I’ll go over these as they come up.
FluidCreator will be a class with the sole purpose of adjusting the initial state of the fluid before it gets placed on the WorldScene. It’s definition should look something like this:
FluidCreator(const QString& className, WorldModel* worldModel, WorldScene* worldScene) : ItemCreator(className, worldModel, worldScene) {}
At this point the user gets a friendly notification to click somewhere to position the start of the fluid. In the Phun physics sandbox you can “liquify” any object to create a fluid. This can be pretty entertaining, but when it comes building a precise simulation, a boring rectangle is best suited!
Any WorldScene clicks get passed to the fluidCreator sceneEvent until the item creation is flagged as being completed. Three different mouse events are handled by the fluidCreator scene event.
- The user clicks the mouse. This creates a fluid object and a fluidForce object that are added to the World. This case also starts a “Macro” for undo purposes later on.
- The user drags the mouse. This updates the measureRectSize and measureRectCenter fluid attributes.
- The user releases the mouse. This finalizes the rectangle information. If the user did not drag the mouse it would create a default rectangle size. Releasing the mouse prompts the user to input detailed attributes for the fluid. For early development all values should be fixed as only certain values may permit stable fluids. Once this information is added, the fluid particles will be created within the fluid and the Undo macro is ended. A nice pop-up confirming the creation of a new fluid will appear and we are left with a rectangle filled with non overlapping fluid particles.
On top of all that, when the user releases the mouse, a menu handler is created. This menuHandler takes care of both the context menu and the creation of new fluid particles. By itself, a fluid is just a framework for keeping track of fluid particles. Separating the creation of fluid particles from creation of a fluid allows the user to re-run the particle creation method until a desired smoothness is achieved. MenuHandler also kicks off a critical chain reaction by adding a FluidParticleList to the WorldModel.
The WorldModel is really the central nervous system of Step. All solvers, constraints, collisions, and general mayhem occurs within this monster of a class. I won’t unnecessarily spill the guts of Step into this post, but suffice to say, things get quite elaborate at this point.
To quote my mentor:
In WorldScene::worldRowsInserted signal handler, which is called when new item is added to the World, the corresponding graphics item gets created and initialized. In WorldScene::worldDataChanged slot, which is called when some (or all) items in the World changes their state, it calls WorldGraphicsItem::worldDataChanged method for each graphics item on the scene so that each item can redraw itself
The question I have to ask is, how should one draw a fluid Particle? I will have to do some hands on experimentation with this later, but it is important that fluid particle spheres overlap to give an illusion of a continuos fluid. The actual drawing of the fluid particle object is coded into the GraphicsItem::paint class.
Users selects a fluid particle
Selecting or hovering over a fluid particle is considered a “stateChange” event and is handled depending on the object. User interactivity with the fluid may be an interesting task, especially in real-time while the simulation is running. However, clicking and dragging a fluid particle should be entirely possible even when pushing against other fluid particles. As with a Gas, I feel that a fluid particle should display a velocity vector (as pictured below). This velocityHandler is created using the Vector2D velocity quantity of our fluid as follows:
_velocityHandler = new ArrowHandlerGraphicsItem(item, worldModel, this, _item->metaObject()->property(“velocity”));
In particular, ArrowHandlerGraphicsItem is described in detail in the WorldGraphics class. One idea I may be interested in implementing in the future, is adding support for displaying “velocity field lines” to show the instantaneous velocity at a fixed grid of points throughout the fluid. Other cool graphical features might be a rainbow color mapping to identify the areas of high pressure/density.
User presses simulate.
The actual simulation process, in order words, the integration algorithm of all our forces in order to go forward one step in time, is briefly outlined in the Introduction to StepCore document here (http://stepcore.sourceforge.net/docs/design_intro.html). But what end user in their right mind is interested in such things?
End users can keep track of two types of measurements for a fluid.
- Exact properties of a selected “fluid particle”.
- Average observables of our system like average velocities, densities and pressures using a rectangular selection.
Where the latter would be the most accurate data to be obtained from the fluid. The difficulty with the first measurement is that fluid particles are an arbitrary representation of a bulk amount of fluid. Nonetheless, for either measurement, the properties browser gets updated with data in a similar way. PropertiesBrowser has slots to catch when an object gets selected or when data changes in the world. Then it updates the browser fields accordingly.
A difficult aspect of simulation will surely be the collision of fluid particles with other objects. In that case, it is imperative that the effective radius of a fluid particle avoid overlapping excessively with other objects. Perhaps by tweaking the smoothing kernel, no overlap will actually occur, but I must pay close attention that the fluid particles don’t appear penetrate though an object when in fact the core particle is somewhere safe. This is a case when dynamic graphical scaling of the radius of a particle might be beneficial.
User deletes the object.
Deleting a fluid is a bit less complex than creating a new fluid and fluid particles from scratch. It’s more or less just a matter of getting your book-keeping straight. Any delete event, for instance, selecting an object and pressing the delete key, will be caught and passed on to the worldModel for annilhation.
That’s just about all the interactions the user has with the fluid. Phew, talk about exhaustive. Next post, hopefully on Thursday or Friday, I’ll touch on some collision detection issues. That’ll be the final topic before I dive into a glorious ocean of code.