using System;
namespace WiiTUIO.Provider
{
///
/// This class is responsible for transforming a 2D-coordinate on a source rectangle onto a that of a destination rectangle.
/// The transformation is linear and will not take into account bent or curved surfaces (the transformations are affine!).
/// This is based on the work done by Johnny Lee and can be found here: http://johnnylee.net/projects/wii/
///
internal class Warper
{
private float[] srcX = new float[4];
private float[] srcY = new float[4];
private float[] dstX = new float[4];
private float[] dstY = new float[4];
private float[] srcMat = new float[16];
private float[] dstMat = new float[16];
private float[] warpMat = new float[16];
private bool dirty;
///
/// Construct a new warper class. Initialise with a perfect mapping.
///
public Warper()
{
setIdentity();
}
public void setIdentity()
{
// Set the source and destination rectangles to be the same.
setSource(0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
setDestination(0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
// Compute the matrix which transforms between the two. Simples.
computeWarp();
}
///
/// Set the source rectangle we are transforming coordinates from.
///
/// Top Left X
/// Top Left Y
/// Top Right X
/// Top Right Y
/// Bottom Left X
/// Bottom Left Y
/// Bottom Right X
/// Bottom Right Y
public void setSource(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3)
{
// Set the data.
srcX[0] = x0;
srcY[0] = y0;
srcX[1] = x1;
srcY[1] = y1;
srcX[2] = x2;
srcY[2] = y2;
srcX[3] = x3;
srcY[3] = y3;
// Flag we need to change.
dirty = true;
}
///
/// Set the destination rectangle we are transforming coordinates onto.
///
/// Top Left X
/// Top Left Y
/// Top Right X
/// Top Right Y
/// Bottom Left X
/// Bottom Left Y
/// Bottom Right X
/// Bottom Right Y
public void setDestination(float x0, float y0,
float x1, float y1,
float x2, float y2,
float x3, float y3)
{
// Set data.
dstX[0] = x0;
dstY[0] = y0;
dstX[1] = x1;
dstY[1] = y1;
dstX[2] = x2;
dstY[2] = y2;
dstX[3] = x3;
dstY[3] = y3;
// Flag we need to change.
dirty = true;
}
///
/// Compute the new 'warp' matrix based on the source and destination rectangles.
///
public void computeWarp()
{
computeQuadToSquare(srcX[0], srcY[0], srcX[1], srcY[1], srcX[2], srcY[2], srcX[3], srcY[3], srcMat);
computeSquareToQuad(dstX[0], dstY[0], dstX[1], dstY[1], dstX[2], dstY[2], dstX[3], dstY[3], dstMat);
multMats(srcMat, dstMat, warpMat);
// Remove our flag so that we are not in need of update again until something changes.
dirty = false;
}
///
/// A helper function to multiply two matracies.
///
/// Source matrix as a 16-float flattened 4x4 matrix.
/// Destination matrix as a 16-float flattened 4x4 matrix.
/// Result matrix as a 16-float flattened 4x4 matrix.
public void multMats(float[] srcMat, float[] dstMat, float[] resMat)
{
// DSTDO/CBB: could be faster, but not called often enough to matter
for (int r = 0; r < 4; r++)
{
int ri = r * 4;
for (int c = 0; c < 4; c++)
{
resMat[ri + c] = (srcMat[ri] * dstMat[c] +
srcMat[ri + 1] * dstMat[c + 4] +
srcMat[ri + 2] * dstMat[c + 8] +
srcMat[ri + 3] * dstMat[c + 12]);
}
}
}
public void computeSquareToQuad(float x0,
float y0,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3,
float[] mat)
{
float dx1 = x1 - x2, dy1 = y1 - y2;
float dx2 = x3 - x2, dy2 = y3 - y2;
float sx = x0 - x1 + x2 - x3;
float sy = y0 - y1 + y2 - y3;
float g = (sx * dy2 - dx2 * sy) / (dx1 * dy2 - dx2 * dy1);
float h = (dx1 * sy - sx * dy1) / (dx1 * dy2 - dx2 * dy1);
float a = x1 - x0 + g * x1;
float b = x3 - x0 + h * x3;
float c = x0;
float d = y1 - y0 + g * y1;
float e = y3 - y0 + h * y3;
float f = y0;
mat[0] = a; mat[1] = d; mat[2] = 0; mat[3] = g;
mat[4] = b; mat[5] = e; mat[6] = 0; mat[7] = h;
mat[8] = 0; mat[9] = 0; mat[10] = 1; mat[11] = 0;
mat[12] = c; mat[13] = f; mat[14] = 0; mat[15] = 1;
}
public void computeQuadToSquare(float x0,
float y0,
float x1,
float y1,
float x2,
float y2,
float x3,
float y3,
float[] mat)
{
computeSquareToQuad(x0, y0, x1, y1, x2, y2, x3, y3, mat);
// invert through adjoint
float a = mat[0], d = mat[1], /* ignore */ g = mat[3];
float b = mat[4], e = mat[5], /* 3rd col*/ h = mat[7];
/* ignore 3rd row */
float c = mat[12], f = mat[13];
float A = e - f * h;
float B = c * h - b;
float C = b * f - c * e;
float D = f * g - d;
float E = a - c * g;
float F = c * d - a * f;
float G = d * h - e * g;
float H = b * g - a * h;
float I = a * e - b * d;
// Probably unnecessary since 'I' is also scaled by the determinant,
// and 'I' scales the homogeneous coordinate, which, in turn,
// scales the X,Y coordinates.
// Determinant = a * (e - f * h) + b * (f * g - d) + c * (d * h - e * g);
float idet = 1.0f / (a * A + b * D + c * G);
mat[0] = A * idet; mat[1] = D * idet; mat[2] = 0; mat[3] = G * idet;
mat[4] = B * idet; mat[5] = E * idet; mat[6] = 0; mat[7] = H * idet;
mat[8] = 0; mat[9] = 0; mat[10] = 1; mat[11] = 0;
mat[12] = C * idet; mat[13] = F * idet; mat[14] = 0; mat[15] = I * idet;
}
///
/// Return a reference to the array which is the warp matrix.
///
/// An array reference of a 4x4 matrix represented as a flattened 16-float array.
public float[] getWarpMatrix()
{
return warpMat;
}
///
/// Transform a point from one rectangle onto another using this class's warp matrix.
///
/// Source point, X coordinate.
/// Source point, Y coordinate.
/// Destination point, X coordinate.
/// Destination point, Y coordinate.
public void warp(float srcX, float srcY, ref float dstX, ref float dstY)
{
// If our matrix is out of date, recompute it.
if (dirty)
computeWarp();
// Compute the coordinate transform.
Warper.warp(warpMat, srcX, srcY, ref dstX, ref dstY);
}
///
/// Use a given matrix to transform a point on one rectangle onto another.
///
/// A reference to the array which describes the matrix we want to use.
/// Source point, X coordinate.
/// Source point, Y coordinate.
/// Destination point, X coordinate.
/// Destination point, Y coordinate.
public static void warp(float[] mat, float srcX, float srcY, ref float dstX, ref float dstY)
{
float[] result = new float[4];
float z = 0;
result[0] = (float)(srcX * mat[0] + srcY * mat[4] + z * mat[8] + 1 * mat[12]);
result[1] = (float)(srcX * mat[1] + srcY * mat[5] + z * mat[9] + 1 * mat[13]);
result[2] = (float)(srcX * mat[2] + srcY * mat[6] + z * mat[10] + 1 * mat[14]);
result[3] = (float)(srcX * mat[3] + srcY * mat[7] + z * mat[11] + 1 * mat[15]);
dstX = result[0] / result[3];
dstY = result[1] / result[3];
}
}
}