Ok, I'm now thinking of a more radical change. Instead of having member functions (getDensity(), getMeterial(), etc), how about those functions get moved outside the type. We could have free functions which can then be applied to primitive types and user defined ones.
The concept of 'densities' arose because this is what the Marching Cubes algorithm is based on, but it doesn't make sense in other algorithms. The CubicExtractor doesn't need it, for example. And the copying/resampling functions don't need to care (usually) what the voxel data represents - just that it can be copied and interpolated.
The various volume classes can already hold primitive and user defined types (as well as Material and Density types) and this can sometimes be useful. I think it should be possible to allow the Marching Cubes algoithm to execute on a volume of floats/ints rather than forcing the use to provide a class with a 'getDensity()' method.
Of course, the marching cubes extractor does still need to work on densities, and it needs to be able to get a density from the underlying data type. But this is possible by providing free functions. Within the algorithm , it would no longer call:
... = someVoxeType.getDensity();
... = asDensity(someVoxelType);
PolyVox would include 'asDensity()' functions for all the primitive types, and actually this concept can still work with the Density/Material classes because asDensity() could just call through to 'getDensity()' for that type.
Looking at the CubicSurfaceExtractor, we could have something similar e.g. 'asMaterial()'. Better still, we can realise that the CubicSurfaceExtractor doesn't actually care about materials - all it really needs to know is whether a given pair of voxels need a quad to be generated between them. So internally it could call something like:
bool result= isQuadNeeded(voxel1, voxel2);
where 'isQuadNeeded()' is a free function in the same way as 'asDensity()'. Again, PolyVox would provide a version of this function for built in types, and users could provide their own version for their own types. This also provides a natural way of handling transparency for the CubicSurfaceExtractor without having to deal with callback functions (which I believe are slow when called millions of times).
So what are the drawbacks? Well, if I am going to provide implementations of asDensity(), isQuadNeeded(), etc for the built in types then the user will not be able to override these. My version of isQuadNeeded(int, int) will probably still use the comparison to zero (just solid/empty voxels and no transparancy). If the user provided their own isQuadNeeded(int, int) function then it would conflict with mine (compile error) rather than replacing it. Functions can only be overloaded based on their parameter types, so if you want a custom 'isQuadNeeded()' function you would also need to define a new voxel type. This could just be a wrapper around a int though.
I may be able to provide default implementations of these funtions via template functions, and then users could override then via specialization... but I'm not really sure about this yet. I'll have to see how it goes.