axiosengine/axios/Common/TextureTools/MarchingSquares.cs

800 lines
28 KiB
C#

using System.Collections.Generic;
using FarseerPhysics.Collision;
using Microsoft.Xna.Framework;
namespace FarseerPhysics.Common
{
// Ported by Matthew Bettcher - Feb 2011
/*
Copyright (c) 2010, Luca Deltodesco
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions
and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the nape project nor the names of its contributors may be used to endorse
or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
public static class MarchingSquares
{
/// <summary>
/// Marching squares over the given domain using the mesh defined via the dimensions
/// (wid,hei) to build a set of polygons such that f(x,y) less than 0, using the given number
/// 'bin' for recursive linear inteprolation along cell boundaries.
///
/// if 'comb' is true, then the polygons will also be composited into larger possible concave
/// polygons.
/// </summary>
/// <param name="domain"></param>
/// <param name="cellWidth"></param>
/// <param name="cellHeight"></param>
/// <param name="f"></param>
/// <param name="lerpCount"></param>
/// <param name="combine"></param>
/// <returns></returns>
public static List<Vertices> DetectSquares(AABB domain, float cellWidth, float cellHeight, sbyte[,] f,
int lerpCount, bool combine)
{
CxFastList<GeomPoly> ret = new CxFastList<GeomPoly>();
List<Vertices> verticesList = new List<Vertices>();
//NOTE: removed assignments as they were not used.
List<GeomPoly> polyList;
GeomPoly gp;
int xn = (int)(domain.Extents.X * 2 / cellWidth);
bool xp = xn == (domain.Extents.X * 2 / cellWidth);
int yn = (int)(domain.Extents.Y * 2 / cellHeight);
bool yp = yn == (domain.Extents.Y * 2 / cellHeight);
if (!xp) xn++;
if (!yp) yn++;
sbyte[,] fs = new sbyte[xn + 1, yn + 1];
GeomPolyVal[,] ps = new GeomPolyVal[xn + 1, yn + 1];
//populate shared function lookups.
for (int x = 0; x < xn + 1; x++)
{
int x0;
if (x == xn) x0 = (int)domain.UpperBound.X;
else x0 = (int)(x * cellWidth + domain.LowerBound.X);
for (int y = 0; y < yn + 1; y++)
{
int y0;
if (y == yn) y0 = (int)domain.UpperBound.Y;
else y0 = (int)(y * cellHeight + domain.LowerBound.Y);
fs[x, y] = f[x0, y0];
}
}
//generate sub-polys and combine to scan lines
for (int y = 0; y < yn; y++)
{
float y0 = y * cellHeight + domain.LowerBound.Y;
float y1;
if (y == yn - 1) y1 = domain.UpperBound.Y;
else y1 = y0 + cellHeight;
GeomPoly pre = null;
for (int x = 0; x < xn; x++)
{
float x0 = x * cellWidth + domain.LowerBound.X;
float x1;
if (x == xn - 1) x1 = domain.UpperBound.X;
else x1 = x0 + cellWidth;
gp = new GeomPoly();
int key = MarchSquare(f, fs, ref gp, x, y, x0, y0, x1, y1, lerpCount);
if (gp.Length != 0)
{
if (combine && pre != null && (key & 9) != 0)
{
combLeft(ref pre, ref gp);
gp = pre;
}
else
ret.Add(gp);
ps[x, y] = new GeomPolyVal(gp, key);
}
else
gp = null;
pre = gp;
}
}
if (!combine)
{
polyList = ret.GetListOfElements();
foreach (GeomPoly poly in polyList)
{
verticesList.Add(new Vertices(poly.Points.GetListOfElements()));
}
return verticesList;
}
//combine scan lines together
for (int y = 1; y < yn; y++)
{
int x = 0;
while (x < xn)
{
GeomPolyVal p = ps[x, y];
//skip along scan line if no polygon exists at this point
if (p == null)
{
x++;
continue;
}
//skip along if current polygon cannot be combined above.
if ((p.Key & 12) == 0)
{
x++;
continue;
}
//skip along if no polygon exists above.
GeomPolyVal u = ps[x, y - 1];
if (u == null)
{
x++;
continue;
}
//skip along if polygon above cannot be combined with.
if ((u.Key & 3) == 0)
{
x++;
continue;
}
float ax = x * cellWidth + domain.LowerBound.X;
float ay = y * cellHeight + domain.LowerBound.Y;
CxFastList<Vector2> bp = p.GeomP.Points;
CxFastList<Vector2> ap = u.GeomP.Points;
//skip if it's already been combined with above polygon
if (u.GeomP == p.GeomP)
{
x++;
continue;
}
//combine above (but disallow the hole thingies
CxFastListNode<Vector2> bi = bp.Begin();
while (Square(bi.Elem().Y - ay) > Settings.Epsilon || bi.Elem().X < ax) bi = bi.Next();
//NOTE: Unused
//Vector2 b0 = bi.elem();
Vector2 b1 = bi.Next().Elem();
if (Square(b1.Y - ay) > Settings.Epsilon)
{
x++;
continue;
}
bool brk = true;
CxFastListNode<Vector2> ai = ap.Begin();
while (ai != ap.End())
{
if (VecDsq(ai.Elem(), b1) < Settings.Epsilon)
{
brk = false;
break;
}
ai = ai.Next();
}
if (brk)
{
x++;
continue;
}
CxFastListNode<Vector2> bj = bi.Next().Next();
if (bj == bp.End()) bj = bp.Begin();
while (bj != bi)
{
ai = ap.Insert(ai, bj.Elem()); // .clone()
bj = bj.Next();
if (bj == bp.End()) bj = bp.Begin();
u.GeomP.Length++;
}
//u.p.simplify(float.Epsilon,float.Epsilon);
//
ax = x + 1;
while (ax < xn)
{
GeomPolyVal p2 = ps[(int)ax, y];
if (p2 == null || p2.GeomP != p.GeomP)
{
ax++;
continue;
}
p2.GeomP = u.GeomP;
ax++;
}
ax = x - 1;
while (ax >= 0)
{
GeomPolyVal p2 = ps[(int)ax, y];
if (p2 == null || p2.GeomP != p.GeomP)
{
ax--;
continue;
}
p2.GeomP = u.GeomP;
ax--;
}
ret.Remove(p.GeomP);
p.GeomP = u.GeomP;
x = (int)((bi.Next().Elem().X - domain.LowerBound.X) / cellWidth) + 1;
//x++; this was already commented out!
}
}
polyList = ret.GetListOfElements();
foreach (GeomPoly poly in polyList)
{
verticesList.Add(new Vertices(poly.Points.GetListOfElements()));
}
return verticesList;
}
#region Private Methods
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/** Linearly interpolate between (x0 to x1) given a value at these coordinates (v0 and v1)
such as to approximate value(return) = 0
**/
private static int[] _lookMarch = {
0x00, 0xE0, 0x38, 0xD8, 0x0E, 0xEE, 0x36, 0xD6, 0x83, 0x63, 0xBB, 0x5B, 0x8D,
0x6D, 0xB5, 0x55
};
private static float Lerp(float x0, float x1, float v0, float v1)
{
float dv = v0 - v1;
float t;
if (dv * dv < Settings.Epsilon)
t = 0.5f;
else t = v0 / dv;
return x0 + t * (x1 - x0);
}
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/** Recursive linear interpolation for use in marching squares **/
private static float Xlerp(float x0, float x1, float y, float v0, float v1, sbyte[,] f, int c)
{
float xm = Lerp(x0, x1, v0, v1);
if (c == 0)
return xm;
sbyte vm = f[(int)xm, (int)y];
if (v0 * vm < 0)
return Xlerp(x0, xm, y, v0, vm, f, c - 1);
return Xlerp(xm, x1, y, vm, v1, f, c - 1);
}
/** Recursive linear interpolation for use in marching squares **/
private static float Ylerp(float y0, float y1, float x, float v0, float v1, sbyte[,] f, int c)
{
float ym = Lerp(y0, y1, v0, v1);
if (c == 0)
return ym;
sbyte vm = f[(int)x, (int)ym];
if (v0 * vm < 0)
return Ylerp(y0, ym, x, v0, vm, f, c - 1);
return Ylerp(ym, y1, x, vm, v1, f, c - 1);
}
/** Square value for use in marching squares **/
private static float Square(float x)
{
return x * x;
}
private static float VecDsq(Vector2 a, Vector2 b)
{
Vector2 d = a - b;
return d.X * d.X + d.Y * d.Y;
}
private static float VecCross(Vector2 a, Vector2 b)
{
return a.X * b.Y - a.Y * b.X;
}
/** Look-up table to relate polygon key with the vertices that should be used for
the sub polygon in marching squares
**/
/** Perform a single celled marching square for for the given cell defined by (x0,y0) (x1,y1)
using the function f for recursive interpolation, given the look-up table 'fs' of
the values of 'f' at cell vertices with the result to be stored in 'poly' given the actual
coordinates of 'ax' 'ay' in the marching squares mesh.
**/
private static int MarchSquare(sbyte[,] f, sbyte[,] fs, ref GeomPoly poly, int ax, int ay, float x0, float y0,
float x1, float y1, int bin)
{
//key lookup
int key = 0;
sbyte v0 = fs[ax, ay];
if (v0 < 0) key |= 8;
sbyte v1 = fs[ax + 1, ay];
if (v1 < 0) key |= 4;
sbyte v2 = fs[ax + 1, ay + 1];
if (v2 < 0) key |= 2;
sbyte v3 = fs[ax, ay + 1];
if (v3 < 0) key |= 1;
int val = _lookMarch[key];
if (val != 0)
{
CxFastListNode<Vector2> pi = null;
for (int i = 0; i < 8; i++)
{
Vector2 p;
if ((val & (1 << i)) != 0)
{
if (i == 7 && (val & 1) == 0)
poly.Points.Add(p = new Vector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin)));
else
{
if (i == 0) p = new Vector2(x0, y0);
else if (i == 2) p = new Vector2(x1, y0);
else if (i == 4) p = new Vector2(x1, y1);
else if (i == 6) p = new Vector2(x0, y1);
else if (i == 1) p = new Vector2(Xlerp(x0, x1, y0, v0, v1, f, bin), y0);
else if (i == 5) p = new Vector2(Xlerp(x0, x1, y1, v3, v2, f, bin), y1);
else if (i == 3) p = new Vector2(x1, Ylerp(y0, y1, x1, v1, v2, f, bin));
else p = new Vector2(x0, Ylerp(y0, y1, x0, v0, v3, f, bin));
pi = poly.Points.Insert(pi, p);
}
poly.Length++;
}
}
//poly.simplify(float.Epsilon,float.Epsilon);
}
return key;
}
/** Used in polygon composition to composit polygons into scan lines
Combining polya and polyb into one super-polygon stored in polya.
**/
private static void combLeft(ref GeomPoly polya, ref GeomPoly polyb)
{
CxFastList<Vector2> ap = polya.Points;
CxFastList<Vector2> bp = polyb.Points;
CxFastListNode<Vector2> ai = ap.Begin();
CxFastListNode<Vector2> bi = bp.Begin();
Vector2 b = bi.Elem();
CxFastListNode<Vector2> prea = null;
while (ai != ap.End())
{
Vector2 a = ai.Elem();
if (VecDsq(a, b) < Settings.Epsilon)
{
//ignore shared vertex if parallel
if (prea != null)
{
Vector2 a0 = prea.Elem();
b = bi.Next().Elem();
Vector2 u = a - a0;
//vec_new(u); vec_sub(a.p.p, a0.p.p, u);
Vector2 v = b - a;
//vec_new(v); vec_sub(b.p.p, a.p.p, v);
float dot = VecCross(u, v);
if (dot * dot < Settings.Epsilon)
{
ap.Erase(prea, ai);
polya.Length--;
ai = prea;
}
}
//insert polyb into polya
bool fst = true;
CxFastListNode<Vector2> preb = null;
while (!bp.Empty())
{
Vector2 bb = bp.Front();
bp.Pop();
if (!fst && !bp.Empty())
{
ai = ap.Insert(ai, bb);
polya.Length++;
preb = ai;
}
fst = false;
}
//ignore shared vertex if parallel
ai = ai.Next();
Vector2 a1 = ai.Elem();
ai = ai.Next();
if (ai == ap.End()) ai = ap.Begin();
Vector2 a2 = ai.Elem();
Vector2 a00 = preb.Elem();
Vector2 uu = a1 - a00;
//vec_new(u); vec_sub(a1.p, a0.p, u);
Vector2 vv = a2 - a1;
//vec_new(v); vec_sub(a2.p, a1.p, v);
float dot1 = VecCross(uu, vv);
if (dot1 * dot1 < Settings.Epsilon)
{
ap.Erase(preb, preb.Next());
polya.Length--;
}
return;
}
prea = ai;
ai = ai.Next();
}
}
#endregion
#region CxFastList from nape physics
#region Nested type: CxFastList
/// <summary>
/// Designed as a complete port of CxFastList from CxStd.
/// </summary>
internal class CxFastList<T>
{
// first node in the list
private CxFastListNode<T> _head;
private int _count;
/// <summary>
/// Iterator to start of list (O(1))
/// </summary>
public CxFastListNode<T> Begin()
{
return _head;
}
/// <summary>
/// Iterator to end of list (O(1))
/// </summary>
public CxFastListNode<T> End()
{
return null;
}
/// <summary>
/// Returns first element of list (O(1))
/// </summary>
public T Front()
{
return _head.Elem();
}
/// <summary>
/// add object to list (O(1))
/// </summary>
public CxFastListNode<T> Add(T value)
{
CxFastListNode<T> newNode = new CxFastListNode<T>(value);
if (_head == null)
{
newNode._next = null;
_head = newNode;
_count++;
return newNode;
}
newNode._next = _head;
_head = newNode;
_count++;
return newNode;
}
/// <summary>
/// remove object from list, returns true if an element was removed (O(n))
/// </summary>
public bool Remove(T value)
{
CxFastListNode<T> head = _head;
CxFastListNode<T> prev = _head;
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
if (head != null)
{
if (value != null)
{
do
{
// if we are on the value to be removed
if (comparer.Equals(head._elt, value))
{
// then we need to patch the list
// check to see if we are removing the _head
if (head == _head)
{
_head = head._next;
_count--;
return true;
}
else
{
// were not at the head
prev._next = head._next;
_count--;
return true;
}
}
// cache the current as the previous for the next go around
prev = head;
head = head._next;
} while (head != null);
}
}
return false;
}
/// <summary>
/// pop element from head of list (O(1)) Note: this does not return the object popped!
/// There is good reason to this, and it regards the Alloc list variants which guarantee
/// objects are released to the object pool. You do not want to retrieve an element
/// through pop or else that object may suddenly be used by another piece of code which
/// retrieves it from the object pool.
/// </summary>
public CxFastListNode<T> Pop()
{
return Erase(null, _head);
}
/// <summary>
/// insert object after 'node' returning an iterator to the inserted object.
/// </summary>
public CxFastListNode<T> Insert(CxFastListNode<T> node, T value)
{
if (node == null)
{
return Add(value);
}
CxFastListNode<T> newNode = new CxFastListNode<T>(value);
CxFastListNode<T> nextNode = node._next;
newNode._next = nextNode;
node._next = newNode;
_count++;
return newNode;
}
/// <summary>
/// removes the element pointed to by 'node' with 'prev' being the previous iterator,
/// returning an iterator to the element following that of 'node' (O(1))
/// </summary>
public CxFastListNode<T> Erase(CxFastListNode<T> prev, CxFastListNode<T> node)
{
// cache the node after the node to be removed
CxFastListNode<T> nextNode = node._next;
if (prev != null)
prev._next = nextNode;
else if (_head != null)
_head = _head._next;
else
return null;
_count--;
return nextNode;
}
/// <summary>
/// whether the list is empty (O(1))
/// </summary>
public bool Empty()
{
if (_head == null)
return true;
return false;
}
/// <summary>
/// computes size of list (O(n))
/// </summary>
public int Size()
{
CxFastListNode<T> i = Begin();
int count = 0;
do
{
count++;
} while (i.Next() != null);
return count;
}
/// <summary>
/// empty the list (O(1) if CxMixList, O(n) otherwise)
/// </summary>
public void Clear()
{
CxFastListNode<T> head = _head;
while (head != null)
{
CxFastListNode<T> node2 = head;
head = head._next;
node2._next = null;
}
_head = null;
_count = 0;
}
/// <summary>
/// returns true if 'value' is an element of the list (O(n))
/// </summary>
public bool Has(T value)
{
return (Find(value) != null);
}
// Non CxFastList Methods
public CxFastListNode<T> Find(T value)
{
// start at head
CxFastListNode<T> head = _head;
EqualityComparer<T> comparer = EqualityComparer<T>.Default;
if (head != null)
{
if (value != null)
{
do
{
if (comparer.Equals(head._elt, value))
{
return head;
}
head = head._next;
} while (head != _head);
}
else
{
do
{
if (head._elt == null)
{
return head;
}
head = head._next;
} while (head != _head);
}
}
return null;
}
public List<T> GetListOfElements()
{
List<T> list = new List<T>();
CxFastListNode<T> iter = Begin();
if (iter != null)
{
do
{
list.Add(iter._elt);
iter = iter._next;
} while (iter != null);
}
return list;
}
}
#endregion
#region Nested type: CxFastListNode
internal class CxFastListNode<T>
{
internal T _elt;
internal CxFastListNode<T> _next;
public CxFastListNode(T obj)
{
_elt = obj;
}
public T Elem()
{
return _elt;
}
public CxFastListNode<T> Next()
{
return _next;
}
}
#endregion
#endregion
#region Internal Stuff
#region Nested type: GeomPoly
internal class GeomPoly
{
public int Length;
public CxFastList<Vector2> Points;
public GeomPoly()
{
Points = new CxFastList<Vector2>();
Length = 0;
}
}
#endregion
#region Nested type: GeomPolyVal
private class GeomPolyVal
{
/** Associated polygon at coordinate **/
/** Key of original sub-polygon **/
public int Key;
public GeomPoly GeomP;
public GeomPolyVal(GeomPoly geomP, int K)
{
GeomP = geomP;
Key = K;
}
}
#endregion
#endregion
}
}