[font='comic sans ms']GUI Advanced[/font]
At first glance, things haven't changed a lot since my last entry.
Thing is, there is a lot more stuff happening in the background now.
[font='comic sans ms']Ridges, it is always the ridges...[/font]
Random ridge generation is thrown outside the house and now you can draw the ridges by hand with various parameters you can tweak. Just click where the ridge begins, then click where it ends, and that's it.
Since the previous ridge generation is out, the new one has different tweakable parameters:
Amplitude: Now ridges behave more like a wave, and the "amplitude" controls how much a ridge can divert from the center line traced from the begining point to the end point. Remember, this is a "maximum" so the ridge could be displaced anywhere from +amplitude to -amplitude.
Deviation: This part hasn't changed. The deviation controls how rapidly ridges lower their height at their extreme points.
Divisions: This parameter controls how many points along the ridge will be affected by random amplitude variations. Many divisions = noisy ridge.
Max Height: I decided it might be good for this parameter to be tweakable, before it was hard coded. This controls the maximum height of the ridge you're about to draw. Ridges height will go from this value to 0, so now you can have various ridges at different heights.
There will be probably more settings for the subridges that need to be spawned.
Undo function works perfectly for ridges. Though I'm not sure if implementing a redo function is worth it. Ridge objects are pretty much self contained so it shouldn't be too much work.
One thing that I haven't redone is checking ridge collision, it might or might not be added in the future.
[font='comic sans ms']The beans and the peas[/font]
I had a little scuffle with the GUI and passing data to the generators. For now I was just adding methods as I go, "drawRidge(ridgeData)" and so on, but it seemed kinda convoluted so I did a little google search for GUI design.
Ended up with the term "data binding". Data binding is the process of binding the data in your view with the data on your model, and given that Java is such an "enterprise" platform (and "enterprise" drools all over MVC pattern), there was a library ready for binding stuff from Swing to JavaBeans, specifically, "beansbinding-1.2.1.jar".
[font='comic sans ms']beansbinding-1.2.1.jar[/font]
You can download it from here
(btw, I got Eclipse's dark theme here)
This is an "abandoned" library. Its the reference implementation of JSR-295 (Java Specification Request), the last version is from 2008 or 2009 I believe. JSR-295 specifies a way to bind properties among components, usually, a Swing widget with a JavaBean.
In any case, works pretty well. If you import the library, you get a new tab in WindowBuilder for your Swing widget to make the bindings among your components. You can do stuff like, read property from A and update B, vice versa, or keep both A and B in sync each time one of them is modified.
Have in mind that you need this library if you're using Swing. SWT comes with its own data binding library, its already included in WindowBuilder and Eclipse should auto import them when you create a SWT project. Just create a new SWT project, and the bindings tab should appear in your WindowBuilder editor window. Also, I think JavaFX has data binding built in though I'm not sure.
[font='comic sans ms']Beans?[/font]
Yep, beans, Java ones. A JavaBean isn't more than an object with a default constructor with no parameters, getters/setters for each of its attributes and implements the Serializable interface. Makes for a predictable public interface and for easy initialization.
[font='comic sans ms']Mirrors and reflections[/font]
beansbinding uses the reflection API to know what new data it has to instance so it can pass it around. It was problematic since I didn't knew the JFormattedText components would hold Doubles or Longs depending on what formatting rules they had, by the time those values reached my bean, I would get a ClassCastException because beansbindings would try to cast the Double/Long object into the float/int attributes I had in my bean.
In the end I set up the bean to hold Number references (superclass of every boxed primitive pretty much), so whatever the JFormattedText outputted, the bean would capture it and then I could use Number's handy methods like num.floatValue() or num.intValue(), no matter what type they actually represented.
[font='comic sans ms']Now ridges, tomorrow the world![/font]
Anyway, now I need to implement river "drawing". For that I need to remake the river generator and organize things a little bit, for example, I need to implement a way to switch what tool you have currently selected, as it is right now, you always draw ridges no matter what you selected.
The idea is that when I select "Blur", I can configure how much ridge lines get blurred for the river generation process, that is sort of done, there is the blur settings panel and the bounded bean.
Then, when you select "River", the heightmap should be blurred according to whatever is set in the blur settings, the image should be updated and it should let you place river starting particles wherever you want. That means, place river, calculate path, update heightmap, redraw it, place another river, calculate path, and so on.
That is pretty straightforward but it's because my river implementation sucks Proper river tracing should acknowledge river collisions which affect the volume of the rivers and their paths. That means that for each subsequent river particle added, I should check for collisions and change other river paths accordingly.
[font='comic sans ms']Optimization thoughts[/font]
If changing to "River" placement changes the image to the blurred heightmap, then changing back to "Ridge" should show the empty ridges. This is no problem since all the ridge particles and all the positions they cover are stored (and now I'm thinking I could "cull" unneeded ridge positions), but I think it could be a performance concern.
I'd need to reset the heightmap (that means, heightmap, particle map and state map) and redraw all the ridges. For 512x512 maps this is pretty fast but it starts to bog down at 1k x 1k or 2k x 2k (nevermind 4k x 4k).
As I probably mentioned in previous entries, once I get the major features set up, I'll probably have to consider multithreading the passes I can. From what I've profiled, the generator spends most of it's time writing the heightmap to a BufferedImage.
I have a switch to deal with the different colors that each pixel can have depending on its state (river, ridge, land, etc). I could probably get rid of it with some thought.
In any case, its something I'll have to look up in the near future.
[font='comic sans ms']And they lived...[/font]
That's it for now, bye people! Have a nice week!