-
-
Save rojepp/2032834 to your computer and use it in GitHub Desktop.
| namespace GLCameraRipple | |
| open System | |
| open System.Drawing | |
| open System.Runtime.InteropServices | |
| //open MonoTouch.CoreFoundation | |
| open Microsoft.FSharp.NativeInterop | |
| type RippleModel(screenSize : Size, meshFactor: int, touchRadius: int, textureSize: Size) = | |
| do Console.WriteLine ("New RippleModel"); | |
| let poolWidth = screenSize.Width / meshFactor | |
| let poolHeight = screenSize.Height / meshFactor | |
| let texCoordFactorS, texCoordOffsetS, | |
| texCoordFactorT, texCoordOffsetT = | |
| if (float screenSize.Height) / (float screenSize.Width) < (float textureSize.Width) / (float textureSize.Height) then | |
| let t = (textureSize.Height * screenSize.Height |> float) / (screenSize.Width * textureSize.Width |> float) | |
| t , (1.0 - t) / 2.0, | |
| 1.0, 0.0 | |
| else | |
| let t = (screenSize.Width * textureSize.Width |> float) / (textureSize.Height * screenSize.Height |> float) | |
| 1.0, 0.0, | |
| t , (1.0 - t) / 2.0 | |
| let rippleCoeff = Array2D.init (touchRadius * 2 + 1) (touchRadius * 2 + 1) (fun x y -> | |
| let distance = (x - touchRadius) * (x - touchRadius) + (y - touchRadius) * (y - touchRadius) |> float |> sqrt | |
| if distance < (float touchRadius) then | |
| let factor = distance / (float touchRadius) | |
| ((factor * Math.PI |> cos) + 1.0) * 256.0 | |
| else 0.0 | |
| ) | |
| // +2 for padding the border, mutable so we avoid array copy | |
| let mutable rippleSource = Array2D.create (poolWidth + 2) (poolHeight + 2) 0.0 | |
| let mutable rippleDest = Array2D.create (poolWidth + 2) (poolHeight + 2) 0.0 | |
| let poolSize2 = poolWidth * poolHeight * 2 | |
| let rippleVertices = Marshal.AllocHGlobal(poolSize2 * sizeof<float>); | |
| let rippleTexCoords = Marshal.AllocHGlobal(poolSize2 * sizeof<float>); | |
| let rippleIndicies = Marshal.AllocHGlobal((poolHeight - 1) * (poolWidth * 2 + 2) * sizeof<uint16>); | |
| // Use nativeptr internally, expose nativeint | |
| let verts = NativePtr.ofNativeInt<float> rippleVertices | |
| let texcoords = NativePtr.ofNativeInt<float> rippleTexCoords | |
| let indices = NativePtr.ofNativeInt<uint16> rippleIndicies | |
| do // InitMesh() | |
| for i in 0 .. poolHeight - 2 do | |
| for j in 0 .. poolWidth - 1 do | |
| // This is hideous, but can probably be improved with some inlined helper | |
| NativePtr.set verts ((i * poolWidth + j) * 2 + 0) (-1.0 + (float j) * (2.0 / ((float poolWidth) - 1.0))) | |
| NativePtr.set verts ((i * poolWidth + j) * 2 + 1) ( 1.0 - (float i) * (2.0 / ((float poolHeight) - 1.0))) | |
| NativePtr.set texcoords ((i * poolWidth + j) * 2 + 0) ( (float i) / ((float poolHeight) - 1.0) * texCoordFactorS + texCoordOffsetS) | |
| NativePtr.set texcoords ((i * poolWidth + j) * 2 + 1) ( (float i) / ((float poolHeight) - 1.0) * texCoordFactorS + texCoordOffsetS) | |
| let index = ref 0 | |
| let write (v: int) = NativePtr.set indices !index (uint16 v) | |
| incr index | |
| for i in 0 .. poolHeight - 1 do | |
| for j in 0 .. poolWidth - 1 do | |
| if i % 2 = 0 then | |
| if j = 0 then | |
| (i ) * poolWidth + j |> write | |
| (i ) * poolWidth + j |> write | |
| (i + 1) * poolWidth + j |> write | |
| if j = (poolWidth - 1) then | |
| (i + 1) * poolWidth + j |> write | |
| else | |
| if j = 0 then | |
| (i + 1) * poolWidth + j |> write | |
| (i + 1) * poolWidth + j |> write | |
| (i ) * poolWidth + j |> write | |
| if j = (poolWidth - 1) then | |
| (i ) * poolWidth + j |> write | |
| let simulate () = | |
| for y in 0 .. poolHeight - 1 do | |
| for x in 0 .. poolWidth - 1 do | |
| // water update | |
| let a, b, c, d = | |
| rippleSource.[x + 1, y ], | |
| rippleSource.[x + 1, y + 2], | |
| rippleSource.[x , y + 1], | |
| rippleSource.[x + 2, y + 1] | |
| let result = (a + b + c + d) / 2.0 - rippleDest.[x + 1, y + 1] | |
| let diminished = result - result / 32.0 | |
| rippleDest.[x + 1, y + 1] <- diminished | |
| // texcoords | |
| for y in 0 .. poolHeight - 1 do | |
| for x in 0 .. poolWidth - 1 do | |
| let a, b, c, d = | |
| rippleDest.[x + 1, y ], | |
| rippleDest.[x + 1, y + 2], | |
| rippleDest.[x , y + 1], | |
| rippleDest.[x + 2, y + 1] | |
| let inline clamp min max v = if v < min then min elif v > max then max else v | |
| let s_offset = (b - a) / 2048.0 |> clamp -0.5 0.5 | |
| let t_offset = (c - d) / 2048.0 |> clamp -0.5 0.5 | |
| let s_tc = (float y) / (poolHeight - 1 |> float) * texCoordFactorS + texCoordOffsetS | |
| let t_tc = (float x) / (poolWidth - 1 |> float) * texCoordFactorT + texCoordOffsetT | |
| NativePtr.set texcoords ((y * poolWidth + x) * 2 + 0) (s_tc + s_offset) | |
| NativePtr.set texcoords ((y * poolWidth + x) * 2 + 1) (t_tc + t_offset) | |
| let temp = rippleSource | |
| rippleSource <- rippleDest | |
| rippleDest <- temp | |
| let addRipple (l: PointF) = | |
| let xIndex = (int (l.X / (float32 screenSize.Width ))) * poolWidth | |
| let yIndex = (int (l.Y / (float32 screenSize.Height))) * poolHeight | |
| for y in yIndex - touchRadius .. yIndex + touchRadius do | |
| for x in xIndex - touchRadius .. xIndex + touchRadius do | |
| if x >= 0 && x < poolWidth && y >= 0 && y < poolHeight then | |
| rippleSource.[x + 1, y + 1] = rippleSource.[x + 1, y + 1] + rippleCoeff.[y - (yIndex - touchRadius), x - (xIndex - touchRadius)] |> ignore | |
| member this.Vertices with get () = NativePtr.toNativeInt verts | |
| member this.TexCoords with get () = NativePtr.toNativeInt texcoords | |
| member this.Indices with get () = NativePtr.toNativeInt indices | |
| member this.VertexSize with get () = poolWidth * poolHeight * 2 * sizeof<float> | |
| member this.IndexSize with get () = (poolHeight - 1) * (poolWidth * 2 + 2) * sizeof<float> | |
| member this.IndexCount with get () = this.IndexSize / sizeof<uint16> | |
| member this.RunSimulation() = simulate () | |
| member this.InitiateRippleAtLocation(location) = addRipple location |
| using System; | |
| using System.Drawing; | |
| using System.Runtime.InteropServices; | |
| using MonoTouch.CoreFoundation; | |
| namespace GLCameraRipple | |
| { | |
| public class RippleModel | |
| { | |
| Size screenSize; | |
| int poolHeight, poolWidth; | |
| int touchRadius, meshFactor; | |
| float texCoordFactorS; | |
| float texCoordOffsetS; | |
| float texCoordFactorT; | |
| float texCoordOffsetT; | |
| // ripple coefficients | |
| float[,] rippleCoeff; | |
| // ripple simulation buffers | |
| float[,] rippleSource; | |
| float[,] rippleDest; | |
| // data passed to GL | |
| unsafe float* rippleVertices; | |
| unsafe float* rippleTexCoords; | |
| unsafe ushort* rippleIndicies; | |
| public RippleModel(Size screenSize, int meshFactor, int touchRadius, Size textureSize) | |
| { | |
| Console.WriteLine("New RippleModel"); | |
| this.screenSize = screenSize; | |
| this.meshFactor = meshFactor; | |
| this.touchRadius = touchRadius; | |
| poolWidth = screenSize.Width / meshFactor; | |
| poolHeight = screenSize.Height / meshFactor; | |
| if ((float)screenSize.Height / screenSize.Width < (float)textureSize.Width / textureSize.Height) | |
| { | |
| texCoordFactorS = (float)(textureSize.Height * screenSize.Height) / (screenSize.Width * textureSize.Width); | |
| texCoordOffsetS = (1 - texCoordFactorS) / 2f; | |
| texCoordFactorT = 1; | |
| texCoordOffsetT = 0; | |
| } | |
| else | |
| { | |
| texCoordFactorS = 1; | |
| texCoordOffsetS = 0; | |
| texCoordFactorT = (float)(screenSize.Width * textureSize.Width) / (textureSize.Height * screenSize.Height); | |
| texCoordOffsetT = (1 - texCoordFactorT) / 2f; | |
| } | |
| rippleCoeff = new float[touchRadius * 2 + 1, touchRadius * 2 + 1]; | |
| // +2 for padding the border | |
| rippleSource = new float[poolWidth + 2, poolHeight + 2]; | |
| rippleDest = new float[poolWidth + 2, poolHeight + 2]; | |
| unsafe | |
| { | |
| int poolsize2 = poolWidth * poolHeight * 2; | |
| rippleVertices = (float*)Marshal.AllocHGlobal(poolsize2 * sizeof(float)); | |
| rippleTexCoords = (float*)Marshal.AllocHGlobal(poolsize2 * sizeof(float)); | |
| rippleIndicies = (ushort*)Marshal.AllocHGlobal((poolHeight - 1) * (poolWidth * 2 + 2) * sizeof(ushort)); | |
| } | |
| InitRippleCoef(); | |
| InitMesh(); | |
| } | |
| void InitRippleCoef() | |
| { | |
| for (int y = 0; y <= 2 * touchRadius; y++) | |
| { | |
| for (int x = 0; x <= 2 * touchRadius; x++) | |
| { | |
| float distance = (float)Math.Sqrt((x - touchRadius) * (x - touchRadius) + (y - touchRadius) * (y - touchRadius)); | |
| if (distance <= touchRadius) | |
| { | |
| float factor = (distance / touchRadius); | |
| // goes from -512 -> 0 | |
| rippleCoeff[x, y] = -((float)Math.Cos(factor * Math.PI) + 1f) * 256f; | |
| } | |
| else | |
| rippleCoeff[x, y] = 0; | |
| } | |
| } | |
| } | |
| unsafe void InitMesh() | |
| { | |
| for (int i = 0; i < poolHeight; i++) | |
| { | |
| for (int j = 0; j < poolWidth; j++) | |
| { | |
| rippleVertices[(i * poolWidth + j) * 2 + 0] = -1f + j * (2f / (poolWidth - 1)); | |
| rippleVertices[(i * poolWidth + j) * 2 + 1] = 1f - i * (2f / (poolHeight - 1)); | |
| rippleTexCoords[(i * poolWidth + j) * 2 + 0] = (float)i / (poolHeight - 1) * texCoordFactorS + texCoordOffsetS; | |
| rippleTexCoords[(i * poolWidth + j) * 2 + 1] = (1f - (float)j / (poolWidth - 1)) * texCoordFactorT + texCoordFactorT; | |
| } | |
| } | |
| uint index = 0; | |
| for (int i = 0; i < poolHeight - 1; i++) | |
| { | |
| for (int j = 0; j < poolWidth; j++) | |
| { | |
| if (i % 2 == 0) | |
| { | |
| // emit extra index to create degenerate triangle | |
| if (j == 0) | |
| { | |
| rippleIndicies[index] = (ushort)(i * poolWidth + j); | |
| index++; | |
| } | |
| rippleIndicies[index] = (ushort)(i * poolWidth + j); | |
| index++; | |
| rippleIndicies[index] = (ushort)((i + 1) * poolWidth + j); | |
| index++; | |
| // emit extra index to create degenerate triangle | |
| if (j == (poolWidth - 1)) | |
| { | |
| rippleIndicies[index] = (ushort)((i + 1) * poolWidth + j); | |
| index++; | |
| } | |
| } | |
| else | |
| { | |
| // emit extra index to create degenerate triangle | |
| if (j == 0) | |
| { | |
| rippleIndicies[index] = (ushort)((i + 1) * poolWidth + j); | |
| index++; | |
| } | |
| rippleIndicies[index] = (ushort)((i + 1) * poolWidth + j); | |
| index++; | |
| rippleIndicies[index] = (ushort)(i * poolWidth + j); | |
| index++; | |
| // emit extra index to create degenerate triangle | |
| if (j == (poolWidth - 1)) | |
| { | |
| rippleIndicies[index] = (ushort)(i * poolWidth + j); | |
| index++; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| public IntPtr Vertices | |
| { | |
| get { unsafe { return (IntPtr)rippleVertices; } } | |
| } | |
| public IntPtr TexCoords | |
| { | |
| get { unsafe { return (IntPtr)rippleTexCoords; } } | |
| } | |
| public IntPtr Indices | |
| { | |
| get { unsafe { return (IntPtr)rippleIndicies; } } | |
| } | |
| public int VertexSize | |
| { | |
| get { return poolWidth * poolHeight * 2 * sizeof(float); } | |
| } | |
| public int IndexSize | |
| { | |
| get { return (poolHeight - 1) * (poolWidth * 2 + 2) * sizeof(ushort); } | |
| } | |
| public int IndexCount | |
| { | |
| get { return IndexSize / sizeof(ushort); } | |
| } | |
| public unsafe void RunSimulation() | |
| { | |
| for (int y = 0; y < poolHeight; y++) | |
| { | |
| for (int x = 0; x < poolWidth; x++) | |
| { | |
| // * - denotes current pixel | |
| // | |
| // a | |
| // c * d | |
| // b | |
| // +1 to both x/y values because the border is padded | |
| float a = rippleSource[x + 1, y]; | |
| float b = rippleSource[x + 1, y + 2]; | |
| float c = rippleSource[x, y + 1]; | |
| float d = rippleSource[x + 2, y + 1]; | |
| float result = (a + b + c + d) / 2f - rippleDest[x + 1, y + 1]; | |
| result -= result / 32f; | |
| rippleDest[x + 1, y + 1] = result; | |
| } | |
| } | |
| for (int y = 0; y < poolHeight; y++) | |
| { | |
| for (int x = 0; x < poolWidth; x++) | |
| { | |
| // * - denotes current pixel | |
| // | |
| // a | |
| // c * d | |
| // b | |
| // +1 to both x/y values because the border is padded | |
| float a = rippleDest[x + 1, y]; | |
| float b = rippleDest[x + 1, y + 2]; | |
| float c = rippleDest[x, y + 1]; | |
| float d = rippleDest[x + 2, y + 1]; | |
| float s_offset = ((b - a) / 2048f); | |
| float t_offset = ((c - d) / 2048f); | |
| // clamp | |
| s_offset = (s_offset < -0.5f) ? -0.5f : s_offset; | |
| t_offset = (t_offset < -0.5f) ? -0.5f : t_offset; | |
| s_offset = (s_offset > 0.5f) ? 0.5f : s_offset; | |
| t_offset = (t_offset > 0.5f) ? 0.5f : t_offset; | |
| float s_tc = (float)y / (poolHeight - 1) * texCoordFactorS + texCoordOffsetS; | |
| float t_tc = (1f - (float)x / (poolWidth - 1)) * texCoordFactorT + texCoordOffsetT; | |
| rippleTexCoords[(y * poolWidth + x) * 2 + 0] = s_tc + s_offset; | |
| rippleTexCoords[(y * poolWidth + x) * 2 + 1] = t_tc + t_offset; | |
| } | |
| } | |
| var tmp = rippleDest; | |
| rippleSource = rippleDest; | |
| rippleSource = tmp; | |
| } | |
| public void InitiateRippleAtLocation(PointF location) | |
| { | |
| int xIndex = (int)((location.X / screenSize.Width) * poolWidth); | |
| int yIndex = (int)((location.Y / screenSize.Height) * poolHeight); | |
| for (int y = (int)yIndex - (int)touchRadius; y <= (int)yIndex + (int)touchRadius; y++) | |
| for (int x = (int)xIndex - (int)touchRadius; x <= (int)xIndex + (int)touchRadius; x++) | |
| { | |
| if (x >= 0 && x < poolWidth && y >= 0 && y < poolHeight) | |
| // +1 to both x/y values because the border is padded | |
| rippleSource[x + 1, y + 1] += rippleCoeff[(y - (yIndex - touchRadius)), x - (xIndex - touchRadius)]; | |
| } | |
| } | |
| } | |
| } |
@cocodrino - Yes, that is a nice feature of F#. I wasn't even trying to compress loc.
Is it correct to remove the 2nd loop from the simulate() method? after the 1st one rippleDest is changed, so the values are different.
0 a
1 c * d
2 b
For each iteration we need to know the values for at least 3 rows, but the actual ones are there only at the 3nd iteration, not the 1st one
To remove the loop something like this is needed
76 for y in 0 .. poolHeight + 1 do
77 for x in 0 .. poolWidth - 1 do
78 if j < poolHeight then
79 // ...
87 if j >= 2 then
88 let a, b, c, d =
89 rippleDest.[x + 1, y - 2],
90 rippleDest.[x + 1, y],
91 rippleDest.[x , y - 1],
92 rippleDest.[x + 2, y - 1]```
It reduces the array read operations (-1 for each iteration), but adds 2 checks, looks like doesn't worse it
0 a1
1 c1 * d1
2 b1 (=a2)
3 c2 * d2
4 b2 ...
@luajalla No, it is an error to remove the second loop. I remember identifying that problem, but must have then forgotten about it.
It shows that I never actually ran this code. I just typed it out to see what this kind of code (gfx, sim) would look like in F#. :)
I'm slightly ashamed to admit I didn't bother with installing MonoTouch. Obviously I should have. :)
interesting this is a textual translation c# to imperative f#..but this has almost 100 less code...