Skip to content

Instantly share code, notes, and snippets.

@rojepp
Created March 14, 2012 00:02
Show Gist options
  • Save rojepp/2032834 to your computer and use it in GitHub Desktop.
Save rojepp/2032834 to your computer and use it in GitHub Desktop.
F# Translation for Miguel. I have no idea if this works, and it is pretty much a verbatim translation, only some F# flare added. I didn't dare to do too much refactoring without having a way to test properly. Original: https://github.com/xamarin/monotouch
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
Copy link

interesting this is a textual translation c# to imperative f#..but this has almost 100 less code...

@rojepp
Copy link
Author

rojepp commented Mar 14, 2012

@cocodrino - Yes, that is a nice feature of F#. I wasn't even trying to compress loc.

@luajalla
Copy link

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 ...

@rojepp
Copy link
Author

rojepp commented Mar 15, 2012

@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. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment