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...
url copied!
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?
url copied!
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?
url copied!
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.
url copied!
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