MSTerrain.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. using System.Collections.Generic;
  2. using Microsoft.Xna.Framework;
  3. using Microsoft.Xna.Framework.Graphics;
  4. using FarseerPhysics.Dynamics;
  5. using FarseerPhysics.Collision;
  6. using FarseerPhysics.Factories;
  7. namespace FarseerPhysics.Common
  8. {
  9. public enum Decomposer
  10. {
  11. Bayazit,
  12. CDT,
  13. Earclip,
  14. Flipcode,
  15. Seidel,
  16. }
  17. /// <summary>
  18. /// Return true if the specified color is inside the terrain.
  19. /// </summary>
  20. public delegate bool TerrainTester(Color color);
  21. /// <summary>
  22. /// Simple class to maintain a terrain.
  23. /// </summary>
  24. public class MSTerrain
  25. {
  26. /// <summary>
  27. /// World to manage terrain in.
  28. /// </summary>
  29. public World World;
  30. /// <summary>
  31. /// Center of terrain in world units.
  32. /// </summary>
  33. public Vector2 Center;
  34. /// <summary>
  35. /// Width of terrain in world units.
  36. /// </summary>
  37. public float Width;
  38. /// <summary>
  39. /// Height of terrain in world units.
  40. /// </summary>
  41. public float Height;
  42. /// <summary>
  43. /// Points per each world unit used to define the terrain in the point cloud.
  44. /// </summary>
  45. public int PointsPerUnit;
  46. /// <summary>
  47. /// Points per cell.
  48. /// </summary>
  49. public int CellSize;
  50. /// <summary>
  51. /// Points per sub cell.
  52. /// </summary>
  53. public int SubCellSize;
  54. /// <summary>
  55. /// Number of iterations to perform in the Marching Squares algorithm.
  56. /// Note: More then 3 has almost no effect on quality.
  57. /// </summary>
  58. public int Iterations = 2;
  59. /// <summary>
  60. /// Decomposer to use when regenerating terrain. Can be changed on the fly without consequence.
  61. /// Note: Some decomposerers are unstable.
  62. /// </summary>
  63. public Decomposer Decomposer;
  64. /// <summary>
  65. /// Point cloud defining the terrain.
  66. /// </summary>
  67. private sbyte[,] _terrainMap;
  68. /// <summary>
  69. /// Generated bodies.
  70. /// </summary>
  71. private List<Body>[,] _bodyMap;
  72. private float _localWidth;
  73. private float _localHeight;
  74. private int _xnum;
  75. private int _ynum;
  76. private AABB _dirtyArea;
  77. private Vector2 _topLeft;
  78. public MSTerrain(World world, AABB area)
  79. {
  80. World = world;
  81. Width = area.Extents.X * 2;
  82. Height = area.Extents.Y * 2;
  83. Center = area.Center;
  84. }
  85. /// <summary>
  86. /// Initialize the terrain for use.
  87. /// </summary>
  88. public void Initialize()
  89. {
  90. // find top left of terrain in world space
  91. _topLeft = new Vector2(Center.X - (Width * 0.5f), Center.Y - (-Height * 0.5f));
  92. // convert the terrains size to a point cloud size
  93. _localWidth = Width * PointsPerUnit;
  94. _localHeight = Height * PointsPerUnit;
  95. _terrainMap = new sbyte[(int)_localWidth + 1, (int)_localHeight + 1];
  96. for (int x = 0; x < _localWidth; x++)
  97. {
  98. for (int y = 0; y < _localHeight; y++)
  99. {
  100. _terrainMap[x, y] = 1;
  101. }
  102. }
  103. _xnum = (int)(_localWidth / CellSize);
  104. _ynum = (int)(_localHeight / CellSize);
  105. _bodyMap = new List<Body>[_xnum, _ynum];
  106. // make sure to mark the dirty area to an infinitely small box
  107. _dirtyArea = new AABB(new Vector2(float.MaxValue, float.MaxValue), new Vector2(float.MinValue, float.MinValue));
  108. }
  109. /// <summary>
  110. /// Apply a texture to the terrain using the specified TerrainTester.
  111. /// </summary>
  112. /// <param name="texture">Texture to apply.</param>
  113. /// <param name="position">Top left position of the texture relative to the terrain.</param>
  114. /// <param name="tester">Delegate method used to determine what colors should be included in the terrain.</param>
  115. public void ApplyTexture(Texture2D texture, Vector2 position, TerrainTester tester)
  116. {
  117. Color[] colorData = new Color[texture.Width * texture.Height];
  118. texture.GetData(colorData);
  119. for (int y = (int)position.Y; y < texture.Height + (int)position.Y; y++)
  120. {
  121. for (int x = (int)position.X; x < texture.Width + (int)position.X; x++)
  122. {
  123. if (x >= 0 && x < _localWidth && y >= 0 && y < _localHeight)
  124. {
  125. bool inside = tester(colorData[((y - (int)position.Y) * texture.Width) + (x - (int)position.X)]);
  126. if (!inside)
  127. _terrainMap[x, y] = 1;
  128. else
  129. _terrainMap[x, y] = -1;
  130. }
  131. }
  132. }
  133. // generate terrain
  134. for (int gy = 0; gy < _ynum; gy++)
  135. {
  136. for (int gx = 0; gx < _xnum; gx++)
  137. {
  138. //remove old terrain object at grid cell
  139. if (_bodyMap[gx, gy] != null)
  140. {
  141. for (int i = 0; i < _bodyMap[gx, gy].Count; i++)
  142. {
  143. World.RemoveBody(_bodyMap[gx, gy][i]);
  144. }
  145. }
  146. _bodyMap[gx, gy] = null;
  147. //generate new one
  148. GenerateTerrain(gx, gy);
  149. }
  150. }
  151. }
  152. /// <summary>
  153. /// Apply a texture to the terrain using the specified TerrainTester.
  154. /// </summary>
  155. /// <param name="position">Top left position of the texture relative to the terrain.</param>
  156. public void ApplyData(sbyte[,] data, Vector2 position)
  157. {
  158. for (int y = (int)position.Y; y < data.GetUpperBound(1) + (int)position.Y; y++)
  159. {
  160. for (int x = (int)position.X; x < data.GetUpperBound(0) + (int)position.X; x++)
  161. {
  162. if (x >= 0 && x < _localWidth && y >= 0 && y < _localHeight)
  163. {
  164. _terrainMap[x, y] = data[x, y];
  165. }
  166. }
  167. }
  168. // generate terrain
  169. for (int gy = 0; gy < _ynum; gy++)
  170. {
  171. for (int gx = 0; gx < _xnum; gx++)
  172. {
  173. //remove old terrain object at grid cell
  174. if (_bodyMap[gx, gy] != null)
  175. {
  176. for (int i = 0; i < _bodyMap[gx, gy].Count; i++)
  177. {
  178. World.RemoveBody(_bodyMap[gx, gy][i]);
  179. }
  180. }
  181. _bodyMap[gx, gy] = null;
  182. //generate new one
  183. GenerateTerrain(gx, gy);
  184. }
  185. }
  186. }
  187. /// <summary>
  188. /// Convert a texture to an sbtye array compatible with ApplyData().
  189. /// </summary>
  190. /// <param name="texture">Texture to convert.</param>
  191. /// <param name="tester"></param>
  192. /// <returns></returns>
  193. public static sbyte[,] ConvertTextureToData(Texture2D texture, TerrainTester tester)
  194. {
  195. sbyte[,] data = new sbyte[texture.Width, texture.Height];
  196. Color[] colorData = new Color[texture.Width * texture.Height];
  197. texture.GetData(colorData);
  198. for (int y = 0; y < texture.Height; y++)
  199. {
  200. for (int x = 0; x < texture.Width; x++)
  201. {
  202. bool inside = tester(colorData[(y * texture.Width) + x]);
  203. if (!inside)
  204. data[x, y] = 1;
  205. else
  206. data[x, y] = -1;
  207. }
  208. }
  209. return data;
  210. }
  211. /// <summary>
  212. /// Modify a single point in the terrain.
  213. /// </summary>
  214. /// <param name="location">World location to modify. Automatically clipped.</param>
  215. /// <param name="value">-1 = inside terrain, 1 = outside terrain</param>
  216. public void ModifyTerrain(Vector2 location, sbyte value)
  217. {
  218. // find local position
  219. // make position local to map space
  220. Vector2 p = location - _topLeft;
  221. // find map position for each axis
  222. p.X = p.X * _localWidth / Width;
  223. p.Y = p.Y * -_localHeight / Height;
  224. if (p.X >= 0 && p.X < _localWidth && p.Y >= 0 && p.Y < _localHeight)
  225. {
  226. _terrainMap[(int)p.X, (int)p.Y] = value;
  227. // expand dirty area
  228. if (p.X < _dirtyArea.LowerBound.X) _dirtyArea.LowerBound.X = p.X;
  229. if (p.X > _dirtyArea.UpperBound.X) _dirtyArea.UpperBound.X = p.X;
  230. if (p.Y < _dirtyArea.LowerBound.Y) _dirtyArea.LowerBound.Y = p.Y;
  231. if (p.Y > _dirtyArea.UpperBound.Y) _dirtyArea.UpperBound.Y = p.Y;
  232. }
  233. }
  234. /// <summary>
  235. /// Regenerate the terrain.
  236. /// </summary>
  237. public void RegenerateTerrain()
  238. {
  239. //iterate effected cells
  240. var gx0 = (int)(_dirtyArea.LowerBound.X / CellSize);
  241. var gx1 = (int)(_dirtyArea.UpperBound.X / CellSize) + 1;
  242. if (gx0 < 0) gx0 = 0;
  243. if (gx1 > _xnum) gx1 = _xnum;
  244. var gy0 = (int)(_dirtyArea.LowerBound.Y / CellSize);
  245. var gy1 = (int)(_dirtyArea.UpperBound.Y / CellSize) + 1;
  246. if (gy0 < 0) gy0 = 0;
  247. if (gy1 > _ynum) gy1 = _ynum;
  248. for (int gx = gx0; gx < gx1; gx++)
  249. {
  250. for (int gy = gy0; gy < gy1; gy++)
  251. {
  252. //remove old terrain object at grid cell
  253. if (_bodyMap[gx, gy] != null)
  254. {
  255. for (int i = 0; i < _bodyMap[gx, gy].Count; i++)
  256. {
  257. World.RemoveBody(_bodyMap[gx, gy][i]);
  258. }
  259. }
  260. _bodyMap[gx, gy] = null;
  261. //generate new one
  262. GenerateTerrain(gx, gy);
  263. }
  264. }
  265. _dirtyArea = new AABB(new Vector2(float.MaxValue, float.MaxValue), new Vector2(float.MinValue, float.MinValue));
  266. }
  267. private void GenerateTerrain(int gx, int gy)
  268. {
  269. float ax = gx * CellSize;
  270. float ay = gy * CellSize;
  271. List<Vertices> polys = MarchingSquares.DetectSquares(new AABB(new Vector2(ax, ay), new Vector2(ax + CellSize, ay + CellSize)), SubCellSize, SubCellSize, _terrainMap, Iterations, true);
  272. if (polys.Count == 0) return;
  273. _bodyMap[gx, gy] = new List<Body>();
  274. // create the scale vector
  275. Vector2 scale = new Vector2(1f / PointsPerUnit, 1f / -PointsPerUnit);
  276. // create physics object for this grid cell
  277. foreach (var item in polys)
  278. {
  279. // does this need to be negative?
  280. item.Scale(ref scale);
  281. item.Translate(ref _topLeft);
  282. item.ForceCounterClockWise();
  283. Vertices p = FarseerPhysics.Common.PolygonManipulation.SimplifyTools.CollinearSimplify(item);
  284. List<Vertices> decompPolys = new List<Vertices>();
  285. switch (Decomposer)
  286. {
  287. case Decomposer.Bayazit:
  288. decompPolys = Decomposition.BayazitDecomposer.ConvexPartition(p);
  289. break;
  290. case Decomposer.CDT:
  291. decompPolys = Decomposition.CDTDecomposer.ConvexPartition(p);
  292. break;
  293. case Decomposer.Earclip:
  294. decompPolys = Decomposition.EarclipDecomposer.ConvexPartition(p);
  295. break;
  296. case Decomposer.Flipcode:
  297. decompPolys = Decomposition.FlipcodeDecomposer.ConvexPartition(p);
  298. break;
  299. case Decomposer.Seidel:
  300. decompPolys = Decomposition.SeidelDecomposer.ConvexPartition(p, 0.001f);
  301. break;
  302. default:
  303. break;
  304. }
  305. foreach (Vertices poly in decompPolys)
  306. {
  307. if (poly.Count > 2)
  308. _bodyMap[gx, gy].Add(BodyFactory.CreatePolygon(World, poly, 1));
  309. }
  310. }
  311. }
  312. }
  313. }