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