Showing posts with label f#. Show all posts
Showing posts with label f#. Show all posts

Friday, 5 June 2015

Particle revamp (Part 1)

I did not post for a little while, been attending several events in the mean time:
  • Kinecthack London: Worked on network Kinect stream and real time point cloud alignment
  • Revision : Did my first production in a demoparty (ranked 8th in demo category)
  • Node15: Workshops and some quick sneaky DX12 presentation (Note: I'm not part of Early Access Program, I just figured the API myself and managed to get some render system up and running fighting with drivers, so I'm not on NDA ;)


Ok all those events were great fun, I should explain some technical parts about it, will do at some point, but for now let's go into some other technical parts.


I wanted to revamp my particle system for a while, at some point I was thinking maybe a scratch rewrite would be fine since it's not an insane code base, but as usual my sensible way of doing comes back and I decided to just improve and refactor, which is always a better decision ;)


So I got a lot of nice features already, tons of emitters (Position, Distance Field, Texture, Mesh, Kinect2, Point clouds...), some nice interaction parts, including advanced effectors like Sph,(accelerated with spatial grids), plenty of various force fields, and collider system (mostly distance field based, after all, a plane collider is just a distance field check with a specific function).


Many effectors can also be controlled via "micro curves" (which is basically a 1d texture rendered from a track in my timeline, and driven by particle age.


All the simulation part is entirely manager in my GPU (compute shader), and is pretty fast so I did not feel that part needed a major rewrite (but will have improvements, that's for next post).


Particle counter is basically a ring buffer, and is currently managed in CPU, which is not a big deal for effectors, but a real problem for emitters, and since now most of my machines are fully dx11.1 enabled (eg: I got access to UAV at every stage feature), this becomes quite a blocker (as it opens a lot of possibilities that I'll explain later).


So I decided to revamp (here understand : improve, not rewrite) this part, but first let's explain the problem.


So we have a small structure that maintains the emission counters, like:

Code Snippet
  1. [StructLayout(LayoutKind.Sequential)]
  2. public struct EmitData
  3. {
  4.     public uint EmitCount;
  5.     public uint MaxParticles;
  6.     public uint EmitOffset;
  7.     public uint ThisFrameCount;
  8.     public uint ThisFrameStartOffset;
  9.     private Vector3 dummy;
  10. }


As you see it's just an evil mutable struct (it's only used to copy to constant buffer, don't worry ;)

Now emitter just implements a simple interface:

Code Snippet
  1. public interface IParticleEmitterObject : IParticleEffectObject
  2. {
  3.     int Emit(RenderContext context, ParticleSystem particles);
  4. }

As you see, emitters returns how many particles they did emit, then the particle system, after each emitter has been processed, updates the counters structures no next emitter starts in the right location.

This works really well for simple emitters (32 particles randomly placed), but now I also have emitters that take data from GPU.

So let's take another example, emit from texture.
This is done in the following steps:

  • Create a buffer (pos+color), with an Append flag
  • Dispatch a compute shader that will go read each pixel
  • If pixel satisfies a condition (luminance for example, but can be anything else), append the pixel position + color in the Append buffer. 
  • Copy buffer counter.
  • Dispatch N particles (where N is in CPU), get a random pixel that did satisfy the condition and push it to the particles buffers.
Now as you can clearly see that creates a problem, the amount of particles to emit is set in CPU, but we have no idea how many pixels did satisfy the condition. So we can have 3 edgy (and one can be nasty) cases (consider our emit count is 32)

  • We can have 20000 pixels that pass the test in frame 1, and emit 32 particles from that, int the next frame, I can emit 32 particles from a 200 pixel buffer. This means we have poor coverage control, I'd like to emit more particles if more pixels pass the test.
  • Less than 32 pixels passed the test (like 10), so some element get emitted several times.
  • Worst case of all, no pixels at all pass the test, so result can be... unpredictable.

So to handle those cases, we have several options:
  • In the compute shader emitter, if thread ID is > amount of elements that pass the test, push a "degenerate particle" (like position = float.maxvalue). This is of course ugly, needs to be repeated in every shader that take a gpu counter, but at least it works (even tho it could provide problems at simulation level).
  • Get the counter back in CPU: This is simple (just copy the counter back in a small staging buffer and read it back into cpu, then choose what you do), but it creates a stall as we need to flush and process the command buffer, and wait for it to be fully executed before to get back our 4 precious bytes. So again, not ideal.
  • Do it properly :) Move the counter data in GPU, and code to maintain that data to compute shader, and profit :)
So first thing, since counter data is in a constant buffer, let's not change all the shaders and the logic, so we use a small structured buffer which contains an exact copy of the data (so we process in structured buffer, and then use CopyResource to copy from StructuredBuffer to Constant Buffer

Code Snippet
  1. //Matches the cbuffer layout, so we can use copyresource
  2. struct sParticleEmitInfo
  3. {
  4.     uint emitCount;
  5.     uint maxParticles;
  6.     uint emitOffset;
  7.     uint thisFrameCount;
  8.     uint thisFrameStartOffset;
  9.     float3 dummy; //to match cbuffer padding
  10. };
  11.  
  12. StructuredBuffer<sParticleEmitInfo> ParticleEmitBuffer : PARTICLEEMITBUFFER;
  13. RWStructuredBuffer<sParticleEmitInfo> RWParticleEmitBuffer : RWPARTICLEEMITBUFFER;


Now we need to modify our emitter, so amount of particles emitted can be either provided as int (like before), or using a location in our graphics card.

So for this we model a union type, I would gladly prefer that in f# but for now don't want to move all my codebase to it :)

Code Snippet
  1. public class ParticleEmitterResult
  2. {
  3.     private StaticResult staticResult;
  4.     private DidNotRunResult didNotRunResult;
  5.     private UnorderedAccessViewResult uavResult;
  6.     private BufferResult bufferResult;
  7.     private ResultType resultType;
  8.  
  9.     private ParticleEmitterResult()
  10.     {
  11.  
  12.     }
  13.  
  14.     public static ParticleEmitterResult DidNotRun()
  15.     {
  16.         ParticleEmitterResult result = new ParticleEmitterResult();
  17.         result.didNotRunResult = new DidNotRunResult();
  18.         result.resultType = ResultType.DidNotRun;
  19.         return result;
  20.     }
  21.  
  22.     public static ParticleEmitterResult Static(uint elementCount)
  23.     {
  24.         ParticleEmitterResult result = new ParticleEmitterResult();
  25.         result.staticResult = new StaticResult(elementCount);
  26.         result.resultType = ResultType.Static;
  27.         return result;
  28.     }
  29.  
  30.     public static ParticleEmitterResult UnorderedView(UnorderedAccessView view)
  31.     {
  32.         if (view == null)
  33.             throw new ArgumentNullException("view");
  34.  
  35.         ParticleEmitterResult result = new ParticleEmitterResult();
  36.         result.uavResult = new UnorderedAccessViewResult(view);
  37.         result.resultType = ResultType.Uav;
  38.         return result;
  39.     }
  40.  
  41.     public static ParticleEmitterResult Buffer(SharpDX.Direct3D11.Buffer buffer, int offset)
  42.     {
  43.         if (buffer == null)
  44.             throw new ArgumentNullException("buffer");
  45.         if (offset < 0)
  46.             throw new ArgumentOutOfRangeException("offset", "offset must be greater than 0");
  47.  
  48.         ParticleEmitterResult result = new ParticleEmitterResult();
  49.         result.bufferResult = new BufferResult(buffer, offset);
  50.         result.resultType = ResultType.Buffer;
  51.         return result;
  52.     }
  53.  
  54.     internal void Handle(RenderContext context, IParticleEmitterResultHandler handler)
  55.     {
  56.         switch(this.resultType)
  57.         {
  58.             case ResultType.DidNotRun:
  59.                 handler.HandleDidNotRun(context);
  60.                 break;
  61.             case ResultType.Static:
  62.                 handler.HandleStaticResult(context, this.staticResult.ElementCount);
  63.                 break;
  64.             case ResultType.Uav:
  65.                 handler.HandleUavResult(context, this.uavResult.UnorderedView);
  66.                 break;
  67.             case ResultType.Buffer:
  68.                 handler.HandleBuffer(context, this.bufferResult.Buffer, this.bufferResult.Offset);
  69.                 break;
  70.         }
  71.     }
  72.  
  73.     public enum ResultType { DidNotRun, Static, Uav, Buffer }
  74.  
  75.     public sealed class DidNotRunResult
  76.     {
  77.  
  78.     }
  79.  
  80.     public sealed class StaticResult
  81.     {
  82.         private readonly uint elementCount;
  83.  
  84.         public uint ElementCount
  85.         {
  86.             get { return this.elementCount; }
  87.         }
  88.  
  89.         public StaticResult(uint elementCount)
  90.         {
  91.             this.elementCount = elementCount;
  92.         }
  93.     }
  94.  
  95.     public sealed class UnorderedAccessViewResult
  96.     {
  97.         private readonly UnorderedAccessView view;
  98.         
  99.         public UnorderedAccessView UnorderedView
  100.         {
  101.             get { return this.view; }
  102.         }
  103.  
  104.         public UnorderedAccessViewResult(UnorderedAccessView view)
  105.         {
  106.             this.view = view;
  107.         }
  108.     }
  109.  
  110.     public sealed class BufferResult
  111.     {
  112.         private readonly SharpDX.Direct3D11.Buffer buffer;
  113.         private readonly int offset;
  114.  
  115.         public SharpDX.Direct3D11.Buffer Buffer
  116.         {
  117.             get { return this.buffer; }
  118.         }
  119.  
  120.         public int Offset
  121.         {
  122.             get { return this.offset; }
  123.         }
  124.  
  125.         public BufferResult(SharpDX.Direct3D11.Buffer buffer, int offset)
  126.         {
  127.             this.buffer = buffer;
  128.             this.offset = offset;
  129.         }
  130.     }
  131. }


This is a bit cumbersome, but well it's pretty safe to use.
As a side note in f# this would look like this:


Code Snippet
  1. open SharpDX.Direct3D11
  2. open System
  3.  
  4. type ParticleSystemArgs(maxElements:int) =
  5.     member x.maxElements = maxElements
  6.  
  7. type StaticEmitResult(elementCount:int) =
  8.     member x.elementCount = if elementCount < 0 then raise(ArgumentOutOfRangeException("elementCount","Muse be greater or equalthan 0")) else elementCount
  9.  
  10. type EmitUnorderedViewResult(view:UnorderedAccessView) =
  11.     member x.view = if view = null then raise(ArgumentNullException("view")) else view
  12.  
  13. type EmitBufferResult(buffer:Buffer,offset:int) =
  14.     member x.buffer = if buffer = null then raise(ArgumentNullException("buffer")) else buffer
  15.     member x.offset = if offset < 0 then raise(ArgumentOutOfRangeException("offset","Offset should be greater or equal than 0")) else offset
  16.  
  17. type ParticleEmitResult =
  18.     | DidNotEmit of unit
  19.     | StaticResult of StaticEmitResult
  20.     | UavResult of EmitUnorderedViewResult
  21.     | BufferResult of EmitBufferResult
  22.  
  23. type IParticleEmitter =
  24.    // abstract method
  25.    abstract member Emit: ParticleSystemArgs -> ParticleEmitResult
  26.  
  27. type IParticleEmitHandler =  
  28.     abstract member HandleNoEmit : unit -> unit
  29.     abstract member HandleStatic : int -> unit
  30.     abstract member HandleUav : UnorderedAccessView -> unit
  31.     abstract member HandleBuffer : EmitBufferResult -> unit
  32.  
  33.  
  34. module ParticleFunctions =
  35.  
  36.     let ApplyHandler (x:ParticleEmitResult, handler: IParticleEmitHandler)=
  37.         match x with
  38.             | DidNotEmit d -> handler.HandleNoEmit()
  39.             | StaticResult sr -> handler.HandleStatic(sr.elementCount)
  40.             | UavResult ur -> handler.HandleUav(ur.view)
  41.             | BufferResult br -> handler.HandleBuffer(br)
  42.  
  43.     let DoEmit(emitter:IParticleEmitter,args:ParticleSystemArgs, handler : IParticleEmitHandler) =
  44.         ApplyHandler(emitter.Emit(args), handler)
Much more concise and the pattern matching is much safer in there, but whatever :)

Code Snippet
  1. public interface IParticleEmitterObject : IParticleEffectObject
  2. {
  3.     ParticleEmitterResult Emit(RenderContext context, ParticleSystem particles);
  4. }


Pretty simple, now we can return different data types that can contain data.

And now we have another interface to handle result, as :


Code Snippet
  1. public interface IParticleEmitterResultHandler
  2. {
  3.     void HandleDidNotRun(RenderContext context);
  4.     void HandleStaticResult(RenderContext context, uint elementCount);
  5.     void HandleUavResult(RenderContext context, UnorderedAccessView uav);
  6.     void HandleBuffer(RenderContext context, SharpDX.Direct3D11.Buffer buffer, int offset);
  7. }


As you can see, there's 4 cases, let's first explain those:

  • Did not run: Emitter did not run at all, I decided to have it as a case instead of returning 0 (so you can also explain why it did not run.
  • Static : This is the same case as our previous cases
  • UnorderedView : Counter is located in UAV, which is the case we we use Emit/Counter buffers to push particles. So for example if we want to emit every pixel that did pass the test in our previous case, we do an indirect dispatch and return the view (which contains the counter)
  • Buffer : This is in a GPU buffer (we also need to provide location in that case). This is very useful for coverage based emitters (for example, we could say, emit 50% of the elements that passed the test every frame, in that case we need to process the counter in a small compute shader to generate a custom dispatch call).
So from there, we can easily update our structured buffer above (using compute shader, but actually kept the readback version for debug purposes)

Now the only small difference is when processing effectors, we don't know the particle count anymore, so instead of using Dispatch we use DispatchIndirect (which is trivial to implement), and use Indirect buffers for drawing as well so, DrawIndirect to draw as sprite, and DrawIndexedInstancedIndirect to render particles as geometry.

So here we go, from there we have a fully fledged counter system in our graphics card, which also mean, for any type of emitters where we want to push every element that pass the test, we can now do it in a single pass (no more need for intermediate buffer, use a CounterBuffer or use InterlockedAdd).

And now since got access to UAV at every stage, it's possible to load balance using tessellation/domain shader

Here is an example of an hybrid particle emitter (which doesn't draw anything on screen but just push adaptive amount of particles depending on triangle size)

Declaration:


Code Snippet
  1. cbuffer cbemitParams : register(b0)
  2. {
  3.     float MinSize = 0.1f;
  4.     float MaxSize = 20.0f;
  5.     float MinimumTessel = 1.0f;
  6.     float MaximumTessel = 12.0f;
  7.     float VelocityScale = 1.0f;
  8. };
  9.  
  10. struct vsInput
  11. {
  12.     float3 p : POSITION;
  13.     float3 n : NORMAL;
  14. };
  15.  
  16. struct hsConstOutput
  17. {
  18.     float edges[3]        : SV_TessFactor;
  19.     float inside[1]       : SV_InsideTessFactor;
  20. };


Our hardcore vertex and hull shaders:


Code Snippet
  1. vsInput VS(vsInput input)
  2. {
  3.     return input;
  4. }
  5.  
  6. [domain("tri")]
  7. [partitioning("fractional_even")]
  8. [outputtopology("triangle_cw")]
  9. [outputcontrolpoints(3)]
  10. [patchconstantfunc("HSConst")]
  11. vsInput HS(InputPatch<vsInput, 3> input, uint id : SV_OutputControlPointID)
  12. {
  13.     return input[id];
  14. }


Now the hull constant function, which defines tesselation factor based on triangle size:

Code Snippet
  1. hsConstOutput HSConst(InputPatch<vsInput, 3> patch)
  2. {
  3.     hsConstOutput output;
  4.     
  5.     float3 p1 = patch[0].p;
  6.     float3 p2 = patch[1].p;
  7.     float3 p3 = patch[2].p;
  8.     
  9.     float v = length(cross(p2-p1,p3-p1));
  10.     
  11.     float r = MaxSize - MinSize;
  12.     float n = (v - MinSize) / r;
  13.     float f = MinimumTessel + n * (MaximumTessel - MinimumTessel);
  14.     f = clamp(f,MinimumTessel,MaximumTessel);
  15.     
  16.     output.edges[0] = f;
  17.     output.edges[1] = f;
  18.     output.edges[2] = f;
  19.     output.inside[0] =f;
  20.     
  21.     return output;
  22. }


And the domain shader, which performs the emission:

Code Snippet
  1. [domain("tri")]
  2. void DS(hsConstOutput input, OutputPatch<vsInput, 3> op, float3 dl : SV_DomainLocation)
  3. {
  4.     uint vid = RWPositionBuffer.IncrementCounter();
  5.  
  6.     float3 p = uv.x * op[0].p
  7.         + uv.y * op[1].p
  8.         + uv.z * op[2].p;
  9.  
  10.     float3 n = uv.x * op[0].n
  11.         + uv.y * op[1].n
  12.         + uv.z * op[2].n;
  13.  
  14.     n = normalize(n) * VelocityScale;
  15.  
  16.     uint particleid = (vid + EmitOffset) % MaxParticles;
  17.  
  18.     RWPositionBuffer[particleid] = p;
  19.     RWInitialPositionBuffer[particleid] = p;
  20.     RWVelocityBuffer[particleid] = n;
  21. }

As you see we perform increment counter on Particle position buffer (flag is set to 0 before to run the shader).

Then we just return the position buffer UAV as a result, which contains the total amount of particles that got emitted.

Make sure to disable pixelshader and geometry shader, set a dummy viewport (otherwise it will not run), and disable depth state, and profit :)

Next part, demoing various emitters, and explain some other new features that this did open.




Wednesday, 16 July 2014

OpenCV, Compute and immutability

I know some of my friends use rather regularly the very nice OpenCV contribution from Elliot Woods.

Most times people use the Camera/Projector calibration tool. This works pretty well (could do with some ui improvemements), but most times I wanted to look at it I always end up with the same problem, you need to download the whole Image pack.

This is a great pack of course, but in that scenario, downloading a 500 megs bulk of dlls (which can also depend of version) is let's call "not ideal". Ok in our times with super fast internet you would think it's ok, but well, my hard drive doesn't like it (so I don't either). All that to call a single opencv function!

So just wrote a small P/Invoke dll (and use static library linking instead of dynamic), one 20 lines of dynamic plugin to call the function and here we go, 1 megabyte dll which doesn't need any other external. I like minimalism ;)

I remember I wanted to do this for a while, and considering the amount of (no) time it took I fell very embarrassed )



Now after this there's a (few) things I wanted to add/change in that tool.

First the point selection is nice in some cases, but not so nice in others. Basically current technique renders object space coordinates in a texture, then you just sample that texture.

If you have a model crammed with small polygons it's fine enough, but what I would simply like to do in general is just get closest vertex from a triangle raycast. Since I don't want to blow up 100k rays in cpu, and 3d model is already in GPU (as obviously we want to render it), let's do a little bit of compute shader ;)

So first let's load the model into a big fat buffer (to avoid subsets annoyance, simple prefix sum on indices), then we have the following data structures:

Code Snippet
  1. StructuredBuffer<float3> PositionBuffer : POSITIONBUFFER;
  2. StructuredBuffer<uint3> IndexBuffer : INDEXBUFFER;
  3.  
  4. AppendStructuredBuffer<float3> AppendVertexHitBuffer : APPENDVERTEXHITBUFFER;
  5.  
  6. float3 raypos : RAYPOSITION;
  7. float3 raydir : RAYDIRECTION;
  8.  
  9. int FaceCount : FACECOUNT;
  10.  
  11. float eps : EPSILON = 0.000001f;

Pretty simple, mouse position is converted back to ray, and we use append buffer to get potential candidates (since we might hit several triangles).

Now here is our ray shader (I omit the ray formula, which is the same as in http://www.geometrictools.com/ )

Code Snippet
  1. [numthreads(64,1,1)]
  2. void CS_RayTriangle(uint3 dtid : SV_DispatchThreadID)
  3. {
  4.     if (dtid.x >= FaceCount) { return; }
  5.     
  6.     uint3 face = IndexBuffer[dtid.x];
  7.     
  8.     float3 p1 = PositionBuffer[face.x];
  9.     float3 p2 = PositionBuffer[face.y];
  10.     float3 p3 = PositionBuffer[face.z];
  11.     
  12.     float3 diff = raypos - p1;
  13.     float3 e1 = p2 - p1;
  14.     float3 e2 = p3 - p1;
  15.     float3 n = normalize(cross(e1,e2));
  16.     
  17.     float DdN = dot(raydir,n);
  18.     float fsign;
  19.     
  20.     bool hit = true;
  21.  
  22.     //Do you rayhit
  23.     
  24.     if (hit)
  25.     {
  26.         AppendVertexHitBuffer.Append(p1);
  27.         AppendVertexHitBuffer.Append(p2);
  28.         AppendVertexHitBuffer.Append(p3);
  29.     }
  30. }

Now when we hit a triangle, we append 3 vertices as "candidates", we now need to find the one closest to us. We could readback filtered data (using CopyResourceRegion), and finish computation on CPU, but that's not fun, so let's continue ;)

First to think option is to sort the data, but that's expensive, we only want the closest element.

So first let's process all elements and write the closest distance into a single buffer:

Code Snippet
  1. [numthreads(64,1,1)]
  2. void CS_MinDistance(uint3 dtid : SV_DispatchThreadID)
  3. {
  4.     if (dtid.x >= VertexHitCountBuffer.Load(0)) { return; }
  5.     
  6.     float3 p = VertexHitBuffer[dtid.x];
  7.     
  8.     float d = distance(raypos,p);
  9.     uint dummy;    
  10.     InterlockedMin(RWMinDistanceBuffer[0],asuint(d),dummy);
  11. }

Now we need to filter closest element:

Code Snippet
  1. [numthreads(64,1,1)]
  2. void CS_StoreIndex(uint3 dtid : SV_DispatchThreadID)
  3. {
  4.     if (dtid.x >= VertexHitCountBuffer.Load(0)) { return; }
  5.     
  6.     float3 p = VertexHitBuffer[dtid.x];
  7.     float d = distance(raypos,p);
  8.     uint ud = asuint(d);
  9.     
  10.     uint mind = MinDistanceBuffer[0];    
  11.     InterlockedCompareStore(RWMinElementBuffer[0], mind,ud);
  12. }

Please note that we don't store position directly since interlocked operations are only allowed on int/uint type. Also we don't handle case if we have more than one candidate, this is easy to replace Store by append (but anyway at some point we need to decide which point we select).

And just get position:

Code Snippet
  1. [numthreads(1,1,1)]
  2. void CS_ExtractPosition(uint3 dtid : SV_DispatchThreadID)
  3. {
  4.     uint idx = RWMinElementBuffer[0];
  5.     RWPositionBuffer[0] = VertexHitBuffer[idx];
  6. }

Copy those 12 bytes back in your CPU and you have your closest vertex.

One part of the morning well spent )

Now one feature which is always useful for a good editor (since at the end we edit points), if of course some form of undo/redo.

You have three main ways to implement undo:

  • For each action, use one function to update your model and one function to revert it. THis can be really cumbersome and error prone.
  • Serialize the state, and on undo create a new (or part modified) state from serialized data
  • Use immutable state
I have much growing interest into using more immutable in general, this is safer and i like the concept around it (ok it doesn't map well everywhere and can consume memory), but in that case (something like 10 points and couple projectors data), this sounds like a good use case.

So here is a calibration point:

Code Snippet
  1. public class CalibrationPoint
  2. {
  3.     private readonly Vector2 screenPosition;
  4.     private readonly Vector3 objectPosition;
  5.  
  6.     public CalibrationPoint(Vector2 screenPosition, Vector3 objectPosition)
  7.     {
  8.         this.screenPosition = screenPosition;
  9.         this.objectPosition = objectPosition;
  10.     }
  11.  
  12.     public Vector2 ScreenPosition
  13.     {
  14.         get { return this.screenPosition; }
  15.     }
  16.  
  17.     public Vector3 ObjectPosition
  18.     {
  19.         get { return this.objectPosition; }
  20.     }
  21. }

You can see that once we create our point, we can't change properties anymore.

No to update properties, instead of setting data directly, we return a new point. There's a little way to help memory, if property is the same we return the same instance:

Code Snippet
  1. public CalibrationPoint SetScreenPosition(Vector2 screenPosition)
  2. {
  3.     return this.screenPosition == screenPosition ? this : new CalibrationPoint(screenPosition, this.objectPosition);
  4. }
  5.  
  6. public CalibrationPoint SetObjectPosition(Vector3 objectPosition)
  7. {
  8.     return this.objectPosition == objectPosition ? this : new CalibrationPoint(this.screenPosition, objectPosition);
  9. }
  10.  
  11. public CalibrationPoint Set(Vector2 screenPosition, Vector3 objectPosition)
  12. {
  13.     return this.objectPosition == objectPosition &&
  14.         this.screenPosition == screenPosition ? this : new CalibrationPoint(screenPosition, objectPosition);
  15. }

Now do the same for Projector and calibration data:

Code Snippet
  1. public class Projector
  2. {
  3.     private readonly string name;
  4.     private readonly IEnumerable<CalibrationPoint> points;
  5.  
  6.     public Projector(string name, IEnumerable<CalibrationPoint> points)
  7.     {
  8.         if (name == null)
  9.         {
  10.             throw new ArgumentNullException("name");
  11.         }
  12.         if (points == null)
  13.         {
  14.             throw new ArgumentNullException("points");
  15.         }
  16.         this.name = name;
  17.         this.points = points;
  18.     }
  19.  
  20.     public string Name
  21.     {
  22.         get { return this.name; }
  23.     }
  24.  
  25.     public IEnumerable<CalibrationPoint> Points
  26.     {
  27.         get { return this.points; }
  28.     }
  29. }

Some of the functions to modify (create new) state:

Code Snippet
  1. public Projector AddPoint(Vector2 screenPosition, Vector3 objectPosition)
  2. {
  3.     var point = new CalibrationPoint(screenPosition, objectPosition);
  4.  
  5.     return new Projector(this.name, this.points.Concat(new CalibrationPoint[] { point }));
  6. }
  7.  
  8. public Projector RemovePoint(CalibrationPoint point)
  9. {
  10.     return new Projector(this.name, this.points.Where(p => p != point));
  11. }

Calibration class:

Code Snippet
  1. public class Calibration
  2. {
  3.     private readonly IEnumerable<Projector> projectors;
  4.     private readonly CalibrationSettings settings;
  5.  
  6.     public Calibration(CalibrationSettings settings, IEnumerable<Projector> projectors)
  7.     {
  8.         if (settings == null)
  9.         {
  10.             throw new ArgumentNullException("settings");
  11.         }
  12.         if (projectors == null)
  13.         {
  14.             throw new ArgumentNullException("projectors");
  15.         }
  16.         this.settings = settings;
  17.         this.projectors = projectors;
  18.     }
  19. }

And to update projector data:

Code Snippet
  1. public Calibration UpdateProjector(Projector oldProjector, Projector newProjector)
  2. {
  3.     if (oldProjector == newProjector)
  4.     {
  5.         return this;
  6.     }
  7.     else
  8.     {
  9.         var projs = this.projectors.ToList();
  10.         int idx = projs.IndexOf(oldProjector);
  11.  
  12.         if (idx >= 0)
  13.         {
  14.             projs[idx] = newProjector;
  15.             return new Calibration(this.settings, projs);
  16.         }
  17.         else
  18.         {
  19.             throw new ArgumentException("oldProjector", "This projector is not part of this calibration data");
  20.         }
  21.     }
  22. }

I could do argument check first of course, and you can use some "Builder classes" to maintain those updates, but you get the point.

Now someone would say, this is a lot of work for simple classes....

But now once you are done with this (not so bad) boilerplate, here is out undo stack:

Code Snippet
  1. public class CalibrationUndoStack
  2. {
  3.     private readonly Stack<Calibration> undoStack;
  4.  
  5.     public CalibrationUndoStack(Calibration initial)
  6.     {
  7.         this.undoStack = new Stack<Calibration>();
  8.         this.undoStack.Push(initial);
  9.     }
  10.  
  11.     public void Apply(Func<Calibration, Calibration> commandFunc)
  12.     {
  13.         var newState = commandFunc(this.Current);
  14.         if (newState != this.Current)
  15.         {
  16.             this.undoStack.Push(newState);
  17.         }
  18.     }
  19.  
  20.     public Calibration Current
  21.     {
  22.         get { return this.undoStack.Peek(); }
  23.     }
  24.  
  25.     public Calibration Undo()
  26.     {
  27.         return this.CanUndo ? this.undoStack.Pop() : this.undoStack.Peek();
  28.     }
  29.  
  30.     public bool CanUndo
  31.     {
  32.         get { return this.undoStack.Count > 1; }
  33.     }
  34. }

As you can see, since we always return a new state, we pass a lambda to the stack, and if object has been modified (eg: function returns a new state), then we push our new state. That's how easy that is.

To implement update commands becomes as trivial as :

Code Snippet
  1. public static Calibration AddProjector(Calibration c, string name)
  2. {
  3.     return c.AddProjector(name);
  4. }
  5.  
  6. public static Calibration RenameProjector(Calibration state, Projector projector, string newname)
  7. {
  8.     var p = projector.SetName(newname);
  9.     return state.UpdateProjector(projector, p);
  10. }
  11.  
  12. public static Calibration AddPoint(Calibration state, Projector projector, Vector2 screen, Vector3 obj)
  13. {
  14.     var p = projector.AddPoint(screen, obj);
  15.     return state.UpdateProjector(projector, p);
  16. }
  17.  
  18. public static Calibration SetScreenPoint(Calibration state, Projector projector, CalibrationPoint point, Vector2 screen)
  19. {
  20.     var newPoint = point.SetScreenPosition(screen);
  21.     var newProjector = projector.UpdatePoint(point, newPoint);
  22.     return state.UpdateProjector(projector, newProjector);
  23.  
  24. }

And as you notice, this looks pretty verbose, here is how to do the same in f# (I love type inference, amongst may other things)

Code Snippet
  1. module CalibrationCommandsFS =
  2.  
  3.     let addprojector (c:Calibration,n) = c.AddProjector(n);
  4.     
  5.     let renameprojector(c:Calibration,p, n) = c.UpdateProjector(p,p.SetName(n))
  6.  
  7.     let addpoint(c:Calibration,p,s,o) = c.UpdateProjector(p,p.AddPoint(s,o))
  8.  
  9.     let setscreenpoint(c:Calibration,proj,pt,s) = c.UpdateProjector(proj,proj.UpdatePoint(pt,pt.SetScreenPosition(s)))

And to operate on calibration:

Code Snippet
  1. let x = new CalibrationUndoStack()
  2.  
  3. let s = new CalibrationSettings(Matrix.Identity)
  4. let empty  = []
  5. let c = new Calibration(s, [])
  6.  
  7. x.Apply(fun c -> addprojector(c,"hello"))

That's it for now, but likely more f# soon ;)