Archive for the ‘overloads’ tag
GLGeom UV generators
Per Dave’s request, I’ve recently added a new function to generate UV coordinates on a glgeom::primitive_buffer. The function name is glgeom::compute_uv_mapping().
The principle is simple: it’s a callback-based function that iterates over all the vertices and calls the callback to generate a UV coordinate. The existing glgeom_extension_mappers module already provides worker functions to use in the callback argument (e.g. mapper_cube, mapper_spherical).
Images
Above is a spherical mapper with a 10x scale applied to the terrain geometry.
Above is a cube mapper with a 10x scale applied to the terrain geometry.
Code
The syntax for generating a planar XY mapping is as follows:
glgeom::compute_uv_mapping(primitive, 0, [](const glgeom::point3f& p, const glgeom::vector3f& n) -> glgeom::point2f { return glgeom::scale( glgeom::mapper_planar_xy(p), glgeom::vector2f(10, 10)); });
Using namespace glgeom, it looks like this:
compute_uv_mapping(primitive, 0, [](const point3f& p, const vector3f& n) -> point2f { return scale( mapper_planar_xy(p), vector2f(10, 10)); });
For anyone living in the dark ages of pre-C++11, the above code reads as follows:
- Compute a UV mapping for “primitive”
- Store the computed UVs in channel 0
- Generate a lambda function as callback…
- …which takes in a 3d position and a normal and returns a 2d point (i.e. the UV coordinate)
- …which uses the GLGeom built-in “mapper_planar_xy” function to generate the UV
- …and lastly applies a scale factor of 10 to both the u and v coordinates
API Design Commentary
I have to admit, I’ve not thoroughly satisfied with the resulting code above. It’s a bit verbose and has a very high syntactical-boilerplate-to-content ratio. However, this is the best compromise I’ve come up with yet.
What I don’t like about this design
- The lamdba notation is heavy-weight: there’s almost as much code to define the lambda signature as there is in the lambda body
- It’s possibly too general: in an ideal design, everything should have an immediately obvious place. Mappers transformations (scaling and rotation) as quite common so I’d be nice to have a “built-in” argument for these rather than relying on the user adding them manually into the lambda (and possibly introducing a typo into the multiplication order)
- Is a UV coordinate a “point”? In theory yes, in practice UVs are often treated as generic tuples, making the glgeom::point2t type occasionally cumbersome
Why not allow function pointers to be passed in directly?
I’d like it better if syntax like this worked:
glgeom::compute_uv_mapping(primitive, glgeom::mapper_cube);
But this doesn’t work well. I want to support mapper functions that require positions and normals (e.g. mapper_cube) as well as those that depend only on position (e.g. mapper_spherical). I also want to support lambda functions for inlined custom mappers. Overloading a compute_uv_mapping template method to automatically choose the right variation did not seem to work (VS2010, at least, doesn’t seem to handle overloading well with lambdas of varying type). The compiler doesn’t seem to automatically resolve to the right template overload in a way that provides the syntax I’d like.
With this approach, I ended up with compiler complaints about ambiguous type resolution.
Why not provide an explicit set of compute_uv_* methods?
The ugly boilerplate syntax would go away if a series of functions where provided:
glgeom::compute_uv_cube_mapping(primitive, uv_transform); glgeom::compute_uv_spherical_mapping(primitive, uv_transform); glgeom::compute_uv_planar_xy_mapping(primitive, uv_transform); ...
I like this approach that (a) syntax doesn’t get in the way and (b) it leads to about as self-documenting code as is possible.
What I don’t like is (a) it requires a mirror function for every mapper defined in the glgeom_extension_mappers and (b) creates a dependency between the glgeom_extension_mappers module and the glgeom_extension_primitive_buffer module.
So…?
I much prefer the principle that a generic function to apply an mapper exists alongside and independent of the set of mappers that can be applied (i.e. something a bit more along the lines of functional programming, thank you Javascript and JQuery for teaching me how nice this approach can be!). With that principle in mind, I’ve ended up with the syntactically verbose lambda based approach.
What’s Next?
Hopefully, generating tangents and bi-tangents. I wrote a bit before about bump mapping and generating those values in the GLSL shader, but it would be nice to compute such data easily on the CPU side as well.
In any case, this will require me to add some functions to compute mesh adjacency information for a primitive buffer – a feature I’ve been meaning to add anyway.
Overloading by Return Value in C++
In C++, a named function or method can be overloaded with different input arguments. However, it cannot be overloaded solely by having a different return value.
Yet occasionally, this seems like it might be useful functionality. This post looks at a way to use C++ to emulate overloading a function by return value.
variant myvec3variant = json_parse(“[ 1, 2, 3 ]“);
variant myvec4variant = json_parse(“[ 1, 2, 3, 1 ]“);vec3f myvec3 = convert(myvec3variant);
vec4f myvec4 = convert(myvec4variant);
Per the disclaimer already mentioned, let’s ignore for the moment whether this is a good idea or not and simply look at the C++ code necessary to make the above code work.
Use an Intermediate Type
The solution to get this to work is actually quite simple.
- Define the function to overload with different return values such that it returns an intermediate class object.
- This class will be a wrapper on the input argument that has a templatized implicit conversion operator.
- The conversion operator will then call an overloaded stand-alone function to do the conversion.
In the core header file:
class variant_implicit_cast
{
public:
inline variant_implicit_cast (variant& v) : m_variant(v) {}template <typename T>
inline operator T ()
{
T t;
detail::convert_from_variant(t, m_variant);
return t;
}
protected:
variant& m_variant;
};inline variant_implicit_cast
convert (variant& v)
{
return variant_implicit_cast(v);
}
Specific conversions can be defined without changing the core definition from the variant header file. The following example conversion could be defined in an entirely different header: there’s no dependency on this base convert function and this specific implementation.
namespace detail {
void convert_from_variant (vec3f& s, variant& v);
}
How It Works
This works because the compiler will see that assignment statement an attempt to implicitly convert from the intermediate class to the l-value class type. This in turn invokes the templatized conversion operator. The templatized conversion operator will then use its template type to invoke the correct overload of the conversion worker function.
These methods are all short, inlined methods. In a release build they should (ideally) not incur any overhead.
The Result?
A single named function that appears to be overload-able by return type.
The single name is a real boon since the client programmer simply knows that calling convert is always supposed to work. It keeps the code concise and straightforward.
Furthermore, it avoids the need to modify the core class (the one being converted from) at all. Defining implicit or explicit conversion operators/methods on the core class would work correctly, but then it would create a unnecessary dependency between two potentially unrelated classes. In this model, the conversion worker routines (in the detail namespace) can be defined externally and independently of the core types.


