Tuesday, 21 September 2010

Spiff: The competition

In the intervening years between the first inception of Spiff (yes, I'm going camel case, upper case everywhere just won't do) and it's subsequent revival (and re-revival), a couple of competitors have appeared in the same space.

Closest in spirit to Spiff is Preon. It even states it's aim "to be to binary encoded data what Hibernate is to relational databases, and JAXB to XML", which pretty much sums up what I want with Spiff. Where Preon differs is in it's extensive use of annotations to do what Spiff does in it's format definition file (which Spiff calls an .adf file - Arbitrary Data Format). Preon will examine your classes and derive the data format from the order and types of annotated fields in the class. It also uses annotations to derive looping and conditional logic.

I'll admit that I've not used Preon - partly through fear of polluting my ideas about what Spiff could and should do, and partly in case I decided it was better than Spiff and just decided to call the whole thing off. So any discussion of it's merits and drawbacks are truly superficial. My general impression is that it's reliance on annotations are a little hairy - when you're expressing logic in annotations, things look a little awkward. Spiff trades off compactness (having everything described in the code) for readability and also portability. The event dispatching and class binding mechanism means that one .adf file can be used to populate classes of any shape without needing to respecify the file format. This also highlights the fact that it looks like Preon expects the classes to fully describe the file format, which is rarely what you want in the code. One of the use cases that led to me starting to write Spiff was wanting to get little pieces of the data without having to worry about the rest of the file format. And thus were .jump and .skip begat.

On the other side, the Google lads are also in the frame with protobuf. Protobuf is interesting in that it uses something analogous to the .adf file to describe the format. Where it differs is that it will generate classes for you to serialize and deserialize the format. That's something that Spiff might be capable of one day, but I like the idea of being able to write arbitrary POJOs and map the data onto them, rather than having objects in my code whose sole purpose is as marshallers. Also, it's largely oriented towards message-passing, that is, describing a message that will be passed between two systems, such as in RPC, where the user is in control of both ends of the transaction. To that end, the .proto definitions are reliant on using the underlying protobuf grammar for the message, for instance to recognise repeated blocks of data, and don't have some of the flexibility to express more complicated relationships between parts of the file.

I see a couple of strong points in Spiff from this. Portability of .adf files is possibly the biggest. Once someone has defined an .adf for, say, a .bmp file, or an ItunesDB file, anyone else can take that and use it to bind all or part of that data to their own classes. The other is flexibility, in hopefully being able to express all the things that can make binary file formats tricky to work with. I guess first step is to have a working product...

Spiff!

Ok, I'm back on Spiff. I mean it this time. Repeat after me - "I will ship software, I will ship software, I will ship software".

Coming back to code after a little time is an interesting experience. Almost every time you can guarantee a few nuggets of insight that hadn't occurred previously.

Today's lesson: if it's difficult writing a unit test for (usual suspects being the FileNotFoundExceptions and if(x == null)) conditions), it's probably not worth having in the code.

I'm not normally one for striving for 100% code coverage with tests. Like all things that fall under the agile/XP umbrella, if you're doing it by the book, you're doing it wrong. There isn't a book that tells you how you should be working on your projects.

However, in this instance, I thought it would be an interesting exercise to try and get up to 100%. By getting OCD on the unit tests, I found at least two conditions in my code that couldn't actually occur:
  • a null check on an object right after it's constructor was called, and
  • an exception thrown from code where I was using dynamic assignment where static assignment was sufficient.
In the latter case, I had
try {
lib = Class.forName("java.lang.Math");
} catch (ClassNotFoundException e) {
//how do I get here?
}
Can you write a test that exercises the catch block? Nope. This was a remnant of old code that hadn't been cleaned up. What I should have been doing was
lib = java.lang.Math.class;
which doesn't throw any exception.

It's good to remember that adding test cases is not the only way to get closer to 100% code coverage - deleting code does just as well.

How final is final?

One interesting tidbit from Spiff development. How final is
final int x = 1
? Answer: Not very, if you're using reflection. Field.setAccessible(true) will soon get you round any awkward encapsulation issues.

So, newly armed with that knowledge, what's printed out here?
public class HowFinal {
private final int x = 1;

public static void main(String[] args) throws Exception {
HowFinal howFinal = new HowFinal();
Field f = howFinal.getClass().getDeclaredField("x");
f.setAccessible(true);
f.set(howFinal,2);
System.out.println(howFinal.getX());
System.out.println(f.get(howFinal));
}

public int getX() {
return x;
}
}

The answer, unexpectedly, is
1
2

Er, so x was final after all? Sort of. The compiler inlines constants at compile time, so as far as the runtime JVM is concerned, getX() contains the code return 1;. Querying the field via reflection shows it's true value of 2.

Is there any question whose answer doesn't start with "it depends"?

Spiff on Github

Spiff is now available to download/fork/whatever at GitHub. The GitHub site also has a wiki with some instructions for getting started.

Current state is pre-pre-pre-pre-alpha. That is, it doesn't really work, I'm rebuilding some of the core parts, but it's there for anyone who wants to snoop. Ship early, ship often!

Sunday, 10 January 2010

In (Re-)Development: SPIFF

Shipping is a Feature. A feature that, to be fair, most (all) of my personal software projects have dropped from scope at some point along the way.

One such project was SPIFF. Originally the less pronounceable "SPADF" (Simple Parser for Aribtrary Data Formats), and developed about 4 years ago, the aim was to have a simple way to get data out of those nasty binary file formats that employ obscure things like, y'know, bytes. I mean, why store your integer in 4 bytes when you could wrap it in an xml file?
<?xml version="1.0" encoding="utf-8"?>
<root>
<data type="integer">1</data>
</root>

At the time, I was trying to parse out id3 tags from MP3 files, and compare them to data in an ITunesDB file, both of which employ such ruthless, egregious efficiency. In trying to implement parsers for both these formats, the alarm bells told me that there had to be an easier way. What I wanted to do was define the data format and it's rules in a simple way, and then have some standard bit of code do the hard work. At that time, my desire was to have something like a SAX parser, to which I could listen for events and do work appropriately when the data I needed popped out.

At the heart of it was a JavaCC generated parser, which would read a file that defined a data format:
int                     headerSize
short version
byte flags
int stringLength
string(stringLength) description

and spit out a sequence of Instruction objects that knew how to parse each of these things, and fire events as necessary. Of course, file formats aren't that simple. Even in the basic example above, there's a need to evaluate one element based on the value of another - the description field has a length defined in the int that comes before it. Likewise, in any non-trivial file format, there's a need for conditionals, loops and jumps, almost always based on a value from elsewhere in the file. SPIFF lets users define these these using a dot in front of keywords:
.if(version==1.0) {
byte flags
} .else {
short biggerFlags
}

Jel is employed under the covers to deal with evaluation of expressions. Any values that have been defined previously in the file can be used in an expression, and the ampersand can be used to reference the position of that value in the file. For instance, in a 24-bit bitmap, each "row" of pixels (defined by 3 bytes) is padded to a boundary on a multiple of 4. So it's necessary to do things like:
.repeat(pixelHeight) {
.mark(startOfRow)
.repeat(pixelWidth) {
ubyte rgbBlue
ubyte rgbGreen
ubyte rgbRed
}
.skip (&rgbRed - &startOfRow - 1) % 4
}

You could also insert arbitrary groupings into the format, which would fire an event in the parser that you could use to change state, pretty much like nested elements in an XML file.

One feature that didn't make it into SPIFF was that old "shipping". The code sat in my svn repo doing precisely nothing (I never did get back to that project with the ITunesDB either). Spurred on by the realisation that I never actually finish anything, I recently picked up SPIFF again, with the aim of turning it into something more like JAXB, where the parser can populate (or even generate) an annotated object graph.

Hence, consider this the first in a series of posts covering aspects of the ongoing development of SPIFF, and my attempt to at least get it in the public domain.

Thursday, 5 November 2009

Irkonomics

Tonight, I have done a bad thing. A very bad thing. Not bad evil bad. Or bad naughty bad. But bad depressing bad. I fired up my old laptop, my trusty Acer 5003WLMi. The little fella wasn't packing too much punch any more, especially trying to do Eclipse plugin development, and the battery had gone south, so a couple of months ago he was shunted under the sofa in favour of a new shiny Acer 6930G. Dual core Intel P7450, 4GB RAM, 320GB disk, Nvidia card - the new recruit is a perky number. Finally I can turn on Compiz effects. The wireless is rock solid. I've got more disk that I know what to do with. I certainly got bang for my buck.

But it has a dark side to it. It appears that in a bid to save money, Acer have manufactured the 6930G without any human ever laying a hand on it's sorry little shell. For if they had, they would have found it immediately that the Acer 6930G is probably the most uncomfortable laptop I've ever had the misfortune to use. For the last two months, I've convinced myself that it's not that bad. But getting the old 5003WLMi out again has me weeping into my trackpad with it's smooth curves.

Of course, you don't need melodramatics and hyperbole, so here's the facts:

  • The trackpad. Oh the trackpad. The trackpad is so bad it demands... A SUBLIST....

    • It's left of centre. Fail.

    • It doesn't have a defined edge. It's just kind of sunken. Want edge scrolling? Forget it. Oh, except for there's a little ridge down one part that sort of tells you where the edge scrolling should be. Want tap zones? Really? Not here you don't. Of course, you can have them, just don't expect to find them without tapping 10 times.

    • The sensitivity is terrible

    • It's bumpy. Yeah, I know, you can't even understand what I'm trying to tell you, right? The plastic on the top panel has little raised bumpsdimples, and that texture is also on the trackpad. I'm a guitar player, so the tips of my fingers aren't exactly Fairy Soft, and yet still I get sore fingers.

  • The front edge of the laptop is sharp. You know, that bit where you rest the edge of your hand when you're using the trackpad. "Hey, you know what this laptop needs? Sharp bits where you put your hands! Yeah!"

  • The screen only tilts back to 45 degrees. "Hey you know what users of this laptop need? A sore neck when they're using it standing up! Yeah!"

  • My kids could use the keyboard as a trampoline, it's that bouncy

  • "Hey, you know what programmers will love? The PgUp/PgDown/Home/End keys out the way at the bottom of the number pad! While you're doing that, why not stick the PgUp key right next to the right arrow key. You know, so that you always hit the frickin' thing when you're trying to just move along the line! Yeah!"
So, in summary, you want a great performing laptop for not much money? Buy an Acer 6930G. Just don't ever use another laptop, lest you should remember what you're missing.

Monday, 27 April 2009

Adventures in 3D: Part X - On the move

Yay, so we've finally got to the point where we can move a camera around in our scene, thanks to matrices. In the last post, we used a TransformMatrix to convert the world z-coordinate to something relative to the camera position, albeit with a hard coded view point. Now we just need to start hooking that into something that allows us to change that viewpoint. The ThreeDeePanel class already contains a worldToCamera variable that stores the transform matrix representing the camera view. Let's do things properly now - it's not hard to imagine that a camera is going to have a few different properties, so let's create a Camera class. Let's also represent the position as a Point instead of the raw transform matrix.

public class Camera {

private Point position;

public Camera(Point p) {
position = p;
}

public Point getPosition() {
return position;
}
}

Then we keep an instance of the Camera in the ThreeDeePanel class, and provide a method for other classes to get at it. We also need to calculate the corresponding Matrix from the camera's viewpoint before calling project():

Camera camera = new Camera(new Point(0,0,-300));

public void getCamera(Point p) {
return camera;
}

protected void paintComponent(Graphics g) {
...
TransformMatrix worldToCameraTransform = TransformMatrix.getWorldToCamera(camera.getPosition());
for(Primitive poly : renderScene){
poly.project(worldToCameraTransform);
poly.draw(g2);
}
...
}

In the ThreeDee class, which is doing all the input, I'm going to listen out for the cursor keys (assuming we're in the MOVE_CAMERA state), grab the camera and change it's position:

private int CAMERA_SPEED = 3;

public ThreeDee() {
...
panel.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
....
} else if (moveState == MoveState.MOVE_CAMERA) {
boolean ctrlDown = ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) == MouseEvent.CTRL_DOWN_MASK);
Camera camera = panel.getCamera();
switch(e.getKeyCode()) {
case(KeyEvent.VK_UP):
if(ctrlDown) {
camera.getPosition().y += CAMERA_SPEED;
} else {
camera.getPosition().z += CAMERA_SPEED;
}
break;
case(KeyEvent.VK_DOWN):
if(ctrlDown) {
camera.getPosition().y -= CAMERA_SPEED;
} else {
camera.getPosition().z -= CAMERA_SPEED;
}
break;
case(KeyEvent.VK_RIGHT):
camera.getPosition().x += CAMERA_SPEED;
break;
case(KeyEvent.VK_LEFT):
camera.getPosition().x -= CAMERA_SPEED;
break;
}
}

Give that a go, and you should have a moving camera! However, move around a bit and you'll probably notice a problem. The canBeCulled() method in the Triangle class is still using a hard coded viewpoint to decide whether a face is away from the viewer, so if you move up alongside the object, you'll see that it's rear end is missing. We just need to adjust that method to take the new viewpoint into account (again, don't forget to change the method signature in the Primitive interface):

public boolean canBeCulled(Point camera) {
if (WIREFRAME) return false;

Vector viewer = camera.vectorTo(getPosition());
double cull = normal.dotProduct(viewer);
return (cull > 0);
}



As funky as this is, there is the small problem of only being able to look straight ahead. With the point where the camera is, we also need to store some information about which way it's pointing. The camera ought to be free to rotate around any of the axes - look left and right, up and down, and roll side to side. These are commonly know as yaw, pitch and roll. The good news is that these are simply rotations, which we already know how to deal with. What's slightly different is that we're no longer rotating around the world origin, but instead treating the camera as the origin. Also, going right back to the discussion in Part I, the rotation of the objects in the world is opposite to the camera rotation - turning the camera 90 degrees right is like rotating the world 90 degrees left.

We'll store the rotation of the camera as three angles, alpha, beta and gamma, which represent the rotation around the X, Y and Z axes respectively. It's worth considering exactly what that means, to save confusion later. Rotation around the X axis is the looking up and down (pitch) - it's easy to see the "X" and immediately think it ought to be side-to-side motion. Likewise, rotation around Y is looking side-to-side (yaw), and around Z is roll. I'll add a rotate() method to the Camera to change those angles:

public void rotate(double da, double db, double dg) {
alpha += da;
beta += db;
gamma += dg;
}

Now we just need to be able to get the appropriate RotationMatrix that represents those angles. Given rotation matrices Rx, Ry and Rz for each of the three axes, the total rotation is simply Rz.(Ry.Rx). Note that we multiply Rx and Ry first, then multiply by Rz. Why? Because applying rotations in different orders gives different results. Imagine you're looking at a point straight ahead. First, look up 45 degrees. Then look right 45 degrees. Then roll over 90 degrees. You're now looking at a point "top right" of where you were originally, and with your head tilted to one side. Start again, but this time do the "roll" first. You'll actually end up (if you've done it properly...) looking at a point "top left" of where you were. The secret is in the fact that "up" and "right" are relative to which way you're already facing. If you're on your feet and tilt your head backwards to look up, it really is "up". If you were lying on your side on the ground and tilted your head back, you'd actually still be looking along the ground. You would actually have to look "right" to look "up".

So, I'll add a new method to RotationMatrix to get an instance that represents alpha,beta,gamma, which we'll do by getting instances for each axis and multiplying them together.

public static RotationMatrix getInstance(double alpha, double beta, double gamma) {
return RotationMatrix.getInstance(gamma, RotationAxis.Z).times
(RotationMatrix.getInstance(beta, RotationAxis.Y).times
(RotationMatrix.getInstance(alpha, RotationAxis.X)));
}

Finally, we implement a getCameraRotation() method on Camera to get that matrix, and multiply it by the position transform matrix we created above to give the final transform from world to camera. Note that the method signature for project changes to take a base Matrix type. Also, we need to change our RotationMatrix class (and any code that calls it) to use homogenous coordinates, so that we can multiply a RotationMatrix and TransformMatrix. We know that this is pretty simple, just add a row and column to each matrix, with a 1 in the bottom right corner (in hindsight, there's no need for RotationMatrix and TransformMatrix to be different types, really we should just use the base Matrix class. Ah well, you live and learn)

public RotationMatrix getCameraTransform() {
Matrix translation = TransformMatrix.getWorldToCamera(position);
return RotationMatrix.getInstance(alpha, beta, gamma).times(translation);
}

and in ThreeDeePanel:
Matrix worldToCameraTransform = camera.getCameraTransform();

This is all very well, but it doesn't actually do much yet. All we have to do though, is wire it up to the input listeners. Whilst in the MOVE_CAMERA state, dragging the mouse will look left,right,up,down, and moving the scroll wheel will roll.

public void mouseDragged(MouseEvent e) {
...
case MOVE_CAMERA:
panel.getCamera().rotate(dy * RADIAN,dx * RADIAN,0.0);
}
}

and the same sort of thing in mouseWheelMoved() for the roll. Note that dx (moving the mouse left and right) affects the Y axis rotation, and vice versa, for the reasons we discussed above.

Run that, and you too can look around the back of your object! Take care to check whether the rotation is what you expect, as it's not always obvious. Matrix multiplication is not commutative - AB is not the same as BA - and if you've got something in the wrong order, it'll generally manifest itself here as the axis of rotation being wrong. For instance, you may find that instead of the object rotating around the camera, the camera rotates around the object.



You may also notice that the scene "stretches" when it's rotated towards the edge of the screen. This is related to the focal length. If the effect looks unnatural, try increasing the focal length - a shorter focal length effectively gives you more of a "fish-eye" look.

Now you've managed to get around the back, you've probably also noticed a few problems with the z-order. Remember that we're sorting, and therefore drawing, the screen by (world) z position, which is fine as long as we're looking in the +ve z direction. As soon as we move elsewhere, that becomes useless - we now need to sort by z order relative to the camera. That means we need to be converting from world space to camera space before doing the sorting.

Let's also take the opportunity to tidy up the Triangle class. We're currently marshalling vertex data between arrays and matrices, whereas we could just keep the vertex data as a matrix and be done with it. For convenience, I'll add methods to the Vector and Point classes to convert to/from matrices.

As well as storing the world coordinates, we'll also keep a matrix of the view coordinates in Triangle. These will be populated in the project() method, which now needs to move in the pipeline to a point before we do the z-order sorting.

public class Triangle extends Primitive {
// vertices of the triangle in world space
Matrix[] vertices = new Matrix[3];
Matrix[] viewVertices = new Matrix[3];

public void project(Matrix worldToCamera) {

for (int i = 0; i < 3; i++) {
viewVertices[i] = persMatrix.times(worldToCamera.times(vertices[i]));

if(viewVertices[i].data[2][0] < 0) {
draw = false;
return;
}

xPoints[i] = (int) (viewVertices[i].data[0][0] / viewVertices[i].data[3][0]);
yPoints[i] = (int) (viewVertices[i].data[1][0] / viewVertices[i].data[3][0]);
}
}
}

The doPipeline() method now consists of backfaceCulling(), project(), sortForZOrder(), lightScene(). All we need to do now is change getZOrder() in Triangle to return the z order from the (view coords) viewVertices array instead of the (world coords) vertices array.

public double getZOrder() {
return getAverage(viewVertices, 2);
}

@Override
public Point getPosition() {
return new Point(getAverage(vertices, 0), getAverage(vertices, 1), getAverage(vertices, 2));
}

private double getAverage(Matrix[] matrices, int index){
return (matrices[0].data[index][0] + matrices[1].data[index][0] + matrices[2].data[index][0])/3;
}

There, all sorted. We're gradually getting there. Unfortunately, it seems that for every bit we add, it throws up another couple of problems to solve. But we love solving problems, right? Right?! Put the kettle on, make a cup of tea, download the source, and let's ponder.