There are bits of the C# language that we don't think about too much when writing websites – and implicit conversions are one of those things. But while I've been messing about with some ray-tracing code in evenings recently, I found a couple of examples they patterns they can be a help with...
So how can I write the code so that it the internals work in the "right" angle units for their work, but I have the option to define things as degrees or as radians to suit my thinking?
public void CalculateSomeAngles(double radians) { ... } var radians = Math.ToRadians(45); CalculateSomeAngles(radians);
That's fine – but it's not really very type safe. Because the method takes a double, you can supply anything. And there's not really anything stopping you accidentally supplying degrees instead of radians.
So what else could you do?
public struct Radians { public double Value { get; private set; } public Radians(double value) { Value = value; } } public void CalculateSomeAngles(Radians radians) { ... } CalculateSomeAngles(new Radians(1.1));
With a wrapper type to be explicit about the units, it's harder to make a mistake. And we can also use this pattern to allow you to pass whatever units you prefer. We can define a struct for degrees too:
public struct Degrees { public double Value { get; private set; } public Degrees(double value) { Value = value; } }
But the magic here is adding some operators to allow the compiler to automatically do some conversions for us. If we add a "to degrees" conversion to the radians struct and the equivalent degrees struct:
public struct Degrees { public static implicit operator Radians(Degrees d) { return new Radians(d.Value * Math.PI / 180); } } public struct Radians { public static implicit operator Degrees(Radians r) { return new Degrees(r.Value * 180 / Math.PI); } }
Then the compiler no longer cares which you pass – it will just convert to the one it needs in any situation...
So you can write:
CalculateSomeAngles(new Radians(1.1)); CalculateSomeAngles(new Degrees(72));
And now I don't have to worry about my units.
The pattern I ended up with for animation was that it needed a mechanism to set which frame is being rendered, and a mechanism to get the value for this point. Something like:
public interface IAnimateable<T> { T Value { get; } void SetFrame(int thisFrame, int frameCount); }
So if we have a material with "shinyness" that can be amimated, we can replace the normal
double
-typed property with something which exposes this interface, and when the material gets initialised for a specific frame we just pass that data down to the animateable properties:
public class PhongMaterial { .... public IAnimateable<double> Shinyness { get; init ; } public void SetFrame(int thisFrame, int frameCount) { .... Shinyness.SetFrame(thisFrame, frameCount); .... } .... }
And we can have a whatever implementations of this animatable double we like. So to move between 1 and 100, we might declare something like:
var someShape = new Sphere() { Material = new PhongMaterial() { Shinyness = new AnimatedRange(1, 100) } };
But the side effect of doing this is that when we don't need an animated value we still have to pass an object for that value:
var someShape = new Sphere() { Material = new PhongMaterial() { Shinyness = new StaticValue(25) } };
But the trick above with implicit conversions can save us some typing here. The definition for the static value can help:
public class StaticValue : IAnimateable<double> { public double Value { get; private set; } public StaticValue(double value) { Value = value; } public void SetFrame(int thisFrame, int frameCount) { } public static implicit operator StaticValue(double value) { return new StaticValue(value); } }
And now a value which doesn't animate can be declared as:
var someShape = new Sphere() { Material = new PhongMaterial() { Shinyness = 25 } };
This pattern has ended up very useful for both simple scene properties like above, and for more complex Matrix properties for transformations.
↑ Back to top