123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- using System.Collections.Generic;
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Graphics;
- using FarseerPhysics.Dynamics;
- using FarseerPhysics.Collision;
- using FarseerPhysics.Factories;
- namespace FarseerPhysics.Common
- {
- public enum Decomposer
- {
- Bayazit,
- CDT,
- Earclip,
- Flipcode,
- Seidel,
- }
- /// <summary>
- /// Return true if the specified color is inside the terrain.
- /// </summary>
- public delegate bool TerrainTester(Color color);
- /// <summary>
- /// Simple class to maintain a terrain.
- /// </summary>
- public class MSTerrain
- {
- /// <summary>
- /// World to manage terrain in.
- /// </summary>
- public World World;
- /// <summary>
- /// Center of terrain in world units.
- /// </summary>
- public Vector2 Center;
- /// <summary>
- /// Width of terrain in world units.
- /// </summary>
- public float Width;
- /// <summary>
- /// Height of terrain in world units.
- /// </summary>
- public float Height;
- /// <summary>
- /// Points per each world unit used to define the terrain in the point cloud.
- /// </summary>
- public int PointsPerUnit;
- /// <summary>
- /// Points per cell.
- /// </summary>
- public int CellSize;
- /// <summary>
- /// Points per sub cell.
- /// </summary>
- public int SubCellSize;
- /// <summary>
- /// Number of iterations to perform in the Marching Squares algorithm.
- /// Note: More then 3 has almost no effect on quality.
- /// </summary>
- public int Iterations = 2;
- /// <summary>
- /// Decomposer to use when regenerating terrain. Can be changed on the fly without consequence.
- /// Note: Some decomposerers are unstable.
- /// </summary>
- public Decomposer Decomposer;
- /// <summary>
- /// Point cloud defining the terrain.
- /// </summary>
- private sbyte[,] _terrainMap;
- /// <summary>
- /// Generated bodies.
- /// </summary>
- private List<Body>[,] _bodyMap;
- private float _localWidth;
- private float _localHeight;
- private int _xnum;
- private int _ynum;
- private AABB _dirtyArea;
- private Vector2 _topLeft;
- public MSTerrain(World world, AABB area)
- {
- World = world;
- Width = area.Extents.X * 2;
- Height = area.Extents.Y * 2;
- Center = area.Center;
- }
- /// <summary>
- /// Initialize the terrain for use.
- /// </summary>
- public void Initialize()
- {
- // find top left of terrain in world space
- _topLeft = new Vector2(Center.X - (Width * 0.5f), Center.Y - (-Height * 0.5f));
- // convert the terrains size to a point cloud size
- _localWidth = Width * PointsPerUnit;
- _localHeight = Height * PointsPerUnit;
- _terrainMap = new sbyte[(int)_localWidth + 1, (int)_localHeight + 1];
- for (int x = 0; x < _localWidth; x++)
- {
- for (int y = 0; y < _localHeight; y++)
- {
- _terrainMap[x, y] = 1;
- }
- }
- _xnum = (int)(_localWidth / CellSize);
- _ynum = (int)(_localHeight / CellSize);
- _bodyMap = new List<Body>[_xnum, _ynum];
- // make sure to mark the dirty area to an infinitely small box
- _dirtyArea = new AABB(new Vector2(float.MaxValue, float.MaxValue), new Vector2(float.MinValue, float.MinValue));
- }
- /// <summary>
- /// Apply a texture to the terrain using the specified TerrainTester.
- /// </summary>
- /// <param name="texture">Texture to apply.</param>
- /// <param name="position">Top left position of the texture relative to the terrain.</param>
- /// <param name="tester">Delegate method used to determine what colors should be included in the terrain.</param>
- public void ApplyTexture(Texture2D texture, Vector2 position, TerrainTester tester)
- {
- Color[] colorData = new Color[texture.Width * texture.Height];
- texture.GetData(colorData);
- for (int y = (int)position.Y; y < texture.Height + (int)position.Y; y++)
- {
- for (int x = (int)position.X; x < texture.Width + (int)position.X; x++)
- {
- if (x >= 0 && x < _localWidth && y >= 0 && y < _localHeight)
- {
- bool inside = tester(colorData[((y - (int)position.Y) * texture.Width) + (x - (int)position.X)]);
- if (!inside)
- _terrainMap[x, y] = 1;
- else
- _terrainMap[x, y] = -1;
- }
- }
- }
- // generate terrain
- for (int gy = 0; gy < _ynum; gy++)
- {
- for (int gx = 0; gx < _xnum; gx++)
- {
- //remove old terrain object at grid cell
- if (_bodyMap[gx, gy] != null)
- {
- for (int i = 0; i < _bodyMap[gx, gy].Count; i++)
- {
- World.RemoveBody(_bodyMap[gx, gy][i]);
- }
- }
- _bodyMap[gx, gy] = null;
- //generate new one
- GenerateTerrain(gx, gy);
- }
- }
- }
- /// <summary>
- /// Apply a texture to the terrain using the specified TerrainTester.
- /// </summary>
- /// <param name="position">Top left position of the texture relative to the terrain.</param>
- public void ApplyData(sbyte[,] data, Vector2 position)
- {
- for (int y = (int)position.Y; y < data.GetUpperBound(1) + (int)position.Y; y++)
- {
- for (int x = (int)position.X; x < data.GetUpperBound(0) + (int)position.X; x++)
- {
- if (x >= 0 && x < _localWidth && y >= 0 && y < _localHeight)
- {
- _terrainMap[x, y] = data[x, y];
- }
- }
- }
- // generate terrain
- for (int gy = 0; gy < _ynum; gy++)
- {
- for (int gx = 0; gx < _xnum; gx++)
- {
- //remove old terrain object at grid cell
- if (_bodyMap[gx, gy] != null)
- {
- for (int i = 0; i < _bodyMap[gx, gy].Count; i++)
- {
- World.RemoveBody(_bodyMap[gx, gy][i]);
- }
- }
- _bodyMap[gx, gy] = null;
- //generate new one
- GenerateTerrain(gx, gy);
- }
- }
- }
- /// <summary>
- /// Convert a texture to an sbtye array compatible with ApplyData().
- /// </summary>
- /// <param name="texture">Texture to convert.</param>
- /// <param name="tester"></param>
- /// <returns></returns>
- public static sbyte[,] ConvertTextureToData(Texture2D texture, TerrainTester tester)
- {
- sbyte[,] data = new sbyte[texture.Width, texture.Height];
- Color[] colorData = new Color[texture.Width * texture.Height];
- texture.GetData(colorData);
- for (int y = 0; y < texture.Height; y++)
- {
- for (int x = 0; x < texture.Width; x++)
- {
- bool inside = tester(colorData[(y * texture.Width) + x]);
- if (!inside)
- data[x, y] = 1;
- else
- data[x, y] = -1;
- }
- }
- return data;
- }
- /// <summary>
- /// Modify a single point in the terrain.
- /// </summary>
- /// <param name="location">World location to modify. Automatically clipped.</param>
- /// <param name="value">-1 = inside terrain, 1 = outside terrain</param>
- public void ModifyTerrain(Vector2 location, sbyte value)
- {
- // find local position
- // make position local to map space
- Vector2 p = location - _topLeft;
- // find map position for each axis
- p.X = p.X * _localWidth / Width;
- p.Y = p.Y * -_localHeight / Height;
- if (p.X >= 0 && p.X < _localWidth && p.Y >= 0 && p.Y < _localHeight)
- {
- _terrainMap[(int)p.X, (int)p.Y] = value;
- // expand dirty area
- if (p.X < _dirtyArea.LowerBound.X) _dirtyArea.LowerBound.X = p.X;
- if (p.X > _dirtyArea.UpperBound.X) _dirtyArea.UpperBound.X = p.X;
- if (p.Y < _dirtyArea.LowerBound.Y) _dirtyArea.LowerBound.Y = p.Y;
- if (p.Y > _dirtyArea.UpperBound.Y) _dirtyArea.UpperBound.Y = p.Y;
- }
- }
- /// <summary>
- /// Regenerate the terrain.
- /// </summary>
- public void RegenerateTerrain()
- {
- //iterate effected cells
- var gx0 = (int)(_dirtyArea.LowerBound.X / CellSize);
- var gx1 = (int)(_dirtyArea.UpperBound.X / CellSize) + 1;
- if (gx0 < 0) gx0 = 0;
- if (gx1 > _xnum) gx1 = _xnum;
- var gy0 = (int)(_dirtyArea.LowerBound.Y / CellSize);
- var gy1 = (int)(_dirtyArea.UpperBound.Y / CellSize) + 1;
- if (gy0 < 0) gy0 = 0;
- if (gy1 > _ynum) gy1 = _ynum;
- for (int gx = gx0; gx < gx1; gx++)
- {
- for (int gy = gy0; gy < gy1; gy++)
- {
- //remove old terrain object at grid cell
- if (_bodyMap[gx, gy] != null)
- {
- for (int i = 0; i < _bodyMap[gx, gy].Count; i++)
- {
- World.RemoveBody(_bodyMap[gx, gy][i]);
- }
- }
- _bodyMap[gx, gy] = null;
- //generate new one
- GenerateTerrain(gx, gy);
- }
- }
- _dirtyArea = new AABB(new Vector2(float.MaxValue, float.MaxValue), new Vector2(float.MinValue, float.MinValue));
- }
- private void GenerateTerrain(int gx, int gy)
- {
- float ax = gx * CellSize;
- float ay = gy * CellSize;
- List<Vertices> polys = MarchingSquares.DetectSquares(new AABB(new Vector2(ax, ay), new Vector2(ax + CellSize, ay + CellSize)), SubCellSize, SubCellSize, _terrainMap, Iterations, true);
- if (polys.Count == 0) return;
- _bodyMap[gx, gy] = new List<Body>();
- // create the scale vector
- Vector2 scale = new Vector2(1f / PointsPerUnit, 1f / -PointsPerUnit);
- // create physics object for this grid cell
- foreach (var item in polys)
- {
- // does this need to be negative?
- item.Scale(ref scale);
- item.Translate(ref _topLeft);
- item.ForceCounterClockWise();
- Vertices p = FarseerPhysics.Common.PolygonManipulation.SimplifyTools.CollinearSimplify(item);
- List<Vertices> decompPolys = new List<Vertices>();
- switch (Decomposer)
- {
- case Decomposer.Bayazit:
- decompPolys = Decomposition.BayazitDecomposer.ConvexPartition(p);
- break;
- case Decomposer.CDT:
- decompPolys = Decomposition.CDTDecomposer.ConvexPartition(p);
- break;
- case Decomposer.Earclip:
- decompPolys = Decomposition.EarclipDecomposer.ConvexPartition(p);
- break;
- case Decomposer.Flipcode:
- decompPolys = Decomposition.FlipcodeDecomposer.ConvexPartition(p);
- break;
- case Decomposer.Seidel:
- decompPolys = Decomposition.SeidelDecomposer.ConvexPartition(p, 0.001f);
- break;
- default:
- break;
- }
- foreach (Vertices poly in decompPolys)
- {
- if (poly.Count > 2)
- _bodyMap[gx, gy].Add(BodyFactory.CreatePolygon(World, poly, 1));
- }
- }
- }
- }
- }
|