PageRenderTime 51ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/Racer2D/CSharp/Resources/Scripts/Terrain.cs

https://gitlab.com/Teo-Mirror/AtomicExamples
C# | 273 lines | 197 code | 35 blank | 41 comment | 17 complexity | 92904bd34d482d238ca92083223e2b50 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using AtomicEngine;
  7. using OpenSimplex;
  8. public class Terrain
  9. {
  10. private readonly Scene _scene;
  11. private readonly Material _surfaceMaterial;
  12. private readonly Material _chunkMaterial;
  13. private readonly DecorationLibrary<Sprite2D> _decorationLib;
  14. private readonly List<CollisionChain2D> _chunks = new List<CollisionChain2D>();
  15. // Generator configuration
  16. private const int Chunksize = 20;
  17. private const int Chunkheight = -100;
  18. private int chunksToGenerate = 200;
  19. private const float NoiseScaleX = .05f;
  20. private const float NoiseScaleY = 6;
  21. private const int SurfaceRepeatPerChunk = 6;
  22. private const float SurfaceSegmentSize = 0.5f;
  23. private const int PercentageOfChunksWithCrates = 4;
  24. // Generation working variables
  25. private OpenSimplexNoise _noise = new OpenSimplexNoise();
  26. private Random _rng = new Random();
  27. private Vector3 _lastSurfaceExtrusion = Vector3.Right*Chunksize;
  28. private int _discard = 0;
  29. private class DecorationLibrary<T>
  30. {
  31. private T[][] _groups;
  32. private Random _rng = new Random();
  33. public DecorationLibrary(params T[][] spritesGroups)
  34. {
  35. _groups = spritesGroups;
  36. }
  37. public T GetRandomFromGroup(int group)
  38. {
  39. if (group >= _groups.Length)
  40. throw new Exception($"There's no group with index {group} in this library");
  41. return _groups[group][_rng.Next(_groups[group].Length)];
  42. }
  43. public List<T> GetAllResources()
  44. {
  45. List <T> allList = new List<T>();
  46. foreach (T[] group in _groups)
  47. {
  48. allList = allList.Concat(group).ToList();
  49. }
  50. return allList;
  51. }
  52. }
  53. public Terrain(Scene scene)
  54. {
  55. _scene = scene;
  56. // We load the materials, the ones in this example only use built in techniques, we use diffuse for the ground for performance
  57. _surfaceMaterial = Cache.Get<Material>("Materials/UnlitAlpha.xml");
  58. _surfaceMaterial.SetTexture(0, Cache.Get<Texture2D>("Scenarios/grasslands/surface.png"));
  59. _chunkMaterial = Cache.Get<Material>("Materials/Unlit.xml");
  60. _chunkMaterial.SetTexture(0,Cache.Get<Texture2D>("Scenarios/grasslands/ground.png"));
  61. // We create and populate a library with the decorations
  62. Func<string, Sprite2D> GetSprite = file => Cache.Get<Sprite2D>($"Scenarios/grasslands/Object/{file}");
  63. _decorationLib = new DecorationLibrary<Sprite2D>(
  64. new [] {GetSprite("Bush (1).png"), GetSprite("Bush (2).png"), GetSprite("Bush (3).png"), GetSprite("Bush (4).png")},
  65. new [] {GetSprite("Mushroom_1.png"), GetSprite("Mushroom_2.png"), GetSprite("Stone.png"), GetSprite("Sign_2.png")},
  66. new [] {GetSprite("Tree_1.png"), GetSprite("Tree_2.png"), GetSprite("Tree_3.png")}
  67. );
  68. // We setup the hotstop/origin of each sprite to be next to its bottom
  69. foreach (Sprite2D sprite in _decorationLib.GetAllResources())
  70. {
  71. sprite.SetHotSpot(new Vector2(0.5f, 0.1f));
  72. }
  73. // We generate the chunks and add some boxes randomly
  74. Sprite2D crateSprite = Cache.Get<Sprite2D>("Scenarios/grasslands/Object/Crate.png");
  75. for (int i = 0; i < Chunksize*chunksToGenerate; i+=Chunksize)
  76. {
  77. GenerateChunk(i);
  78. // Crates
  79. if (_rng.Next(100) < PercentageOfChunksWithCrates)
  80. {
  81. Node crateNode = AtomicMain.CreateSpriteNode(crateSprite, 3);
  82. crateNode.SetPosition(new Vector3(i + _rng.Next(8), 20, -5));
  83. CollisionBox2D crateCollider = crateNode.CreateComponent<CollisionBox2D>();
  84. crateCollider.SetSize(0.76f, 0.76f);
  85. crateCollider.SetDensity(1.0f);
  86. crateCollider.SetRestitution(0.6f);
  87. crateCollider.SetFriction(0.4f);
  88. }
  89. }
  90. }
  91. public float SampleSurface(float posX)
  92. {
  93. return (float)_noise.Evaluate(posX*NoiseScaleX, 0)*NoiseScaleY;
  94. }
  95. void GenerateChunk(int startx)
  96. {
  97. // We create a node and position where the chunk starts
  98. Node node = _scene.CreateChild();
  99. node.SetPosition2D(startx, 0);
  100. // We create components to render the geometries of the surface and the ground
  101. var groundComponent = node.CreateComponent<CustomGeometry>();
  102. groundComponent.SetMaterial(_chunkMaterial);
  103. groundComponent.BeginGeometry(0, PrimitiveType.TRIANGLE_LIST);
  104. var surfaceComponent = node.CreateComponent<CustomGeometry>();
  105. surfaceComponent.SetMaterial(_surfaceMaterial);
  106. surfaceComponent.BeginGeometry(0, PrimitiveType.TRIANGLE_LIST);
  107. // We initialize and add a single entry to the surface collider points list
  108. List<Vector2> surfacePoints = new List<Vector2>() {new Vector2(0,
  109. (float)_noise.Evaluate(startx*NoiseScaleX, 0)*NoiseScaleY
  110. )
  111. };
  112. // We translate the last surface extrusion point so it's local relative to the chunk we're creating
  113. _lastSurfaceExtrusion += Vector3.Left*Chunksize;
  114. // We loop all the segments in this chunk
  115. float incr = SurfaceSegmentSize;
  116. for (float x = 0; x < Chunksize-float.Epsilon*8; x+=incr)
  117. {
  118. // We store vars for the position for the current segment end point x position and for the y position of the 4 points
  119. float xEnd = x+incr;
  120. float tlY = SampleSurface(startx + x);
  121. float trY = SampleSurface(startx + xEnd);
  122. float blY = tlY + Chunkheight;
  123. float brY = trY + Chunkheight;
  124. // We create vectors that represent
  125. Vector3 bl = new Vector3(x, blY, -10);
  126. Vector3 tl = new Vector3(x, tlY, -10);
  127. Vector3 br = new Vector3(xEnd, brY, -10);
  128. Vector3 tr = new Vector3(xEnd, trY, -10);
  129. // We add the top right point to the surface points list (remember we added the first point in the list init)
  130. surfacePoints.Add(new Vector2(tr));
  131. // We call the CreateDecor function passing the global position of the current segment end point and the segment angle
  132. CreateDecor(tr+Vector3.Right*startx, tl-tr);
  133. // We create the geometry of the surface (UV os oriented according to the surface)
  134. Vector2 startV = Vector2.UnitX*(x/Chunksize)*SurfaceRepeatPerChunk;
  135. Vector2 endV = Vector2.UnitX*(xEnd/Chunksize*SurfaceRepeatPerChunk);
  136. //bl
  137. surfaceComponent.DefineVertex(_lastSurfaceExtrusion);
  138. surfaceComponent.DefineTexCoord(startV);
  139. //tl
  140. surfaceComponent.DefineVertex(tl);
  141. surfaceComponent.DefineTexCoord(startV-Vector2.UnitY);
  142. //tr
  143. surfaceComponent.DefineVertex(tr);
  144. surfaceComponent.DefineTexCoord(-Vector2.UnitY+endV);
  145. //bl
  146. surfaceComponent.DefineVertex(_lastSurfaceExtrusion);
  147. surfaceComponent.DefineTexCoord(startV);
  148. //tr
  149. surfaceComponent.DefineVertex(tr);
  150. surfaceComponent.DefineTexCoord(-Vector2.UnitY+endV);
  151. //br - We store the last point to use for continuity
  152. _lastSurfaceExtrusion = tr + Quaternion.FromAxisAngle(Vector3.Back, 90)*Vector3.NormalizeFast(tr - tl);
  153. surfaceComponent.DefineVertex(_lastSurfaceExtrusion);
  154. surfaceComponent.DefineTexCoord(endV);
  155. // We create the geometry of the ground (UV is in world coordinates)
  156. //bl
  157. groundComponent.DefineVertex(bl);
  158. groundComponent.DefineTexCoord(new Vector2(bl/Chunksize));
  159. //tl
  160. groundComponent.DefineVertex(tl);
  161. groundComponent.DefineTexCoord(new Vector2(tl/Chunksize));
  162. //tr
  163. groundComponent.DefineVertex(tr);
  164. groundComponent.DefineTexCoord(new Vector2(tr/Chunksize));
  165. //bl
  166. groundComponent.DefineVertex(bl);
  167. groundComponent.DefineTexCoord(new Vector2(bl/Chunksize));
  168. //tr
  169. groundComponent.DefineVertex(tr);
  170. groundComponent.DefineTexCoord(new Vector2(tr/Chunksize));
  171. //br
  172. groundComponent.DefineVertex(br);
  173. groundComponent.DefineTexCoord(new Vector2(br/Chunksize));
  174. }
  175. // We commit the geometry data we just created
  176. surfaceComponent.Commit();
  177. groundComponent.Commit();
  178. // We create the collider component for the chunk surface
  179. CollisionChain2D surfaceCollider = node.CreateComponent<CollisionChain2D>();
  180. surfaceCollider.SetLoop(false);
  181. surfaceCollider.SetFriction(1);
  182. surfaceCollider.SetVertexCount((uint) surfacePoints.Count+1);
  183. _chunks.Add(surfaceCollider);
  184. // We add a small overlapping segment with a bit of negative offset in y so the wheel passes smoothly over chunk seams
  185. Vector2 smoother = new Vector2(-incr*.5f, (float) _noise.Evaluate((startx-incr*.5f)*NoiseScaleX, 0)*NoiseScaleY-.005f);
  186. surfaceCollider.SetVertex(0,smoother);
  187. // Finally, we set the vertex of the surface collider
  188. for (int c = 0; c < surfacePoints.Count; c++)
  189. {
  190. Vector2 surfacePoint = surfacePoints[c];
  191. surfaceCollider.SetVertex((uint)c+1, surfacePoint);
  192. }
  193. // A collider must have a rigid body to interact with other bodies, even if static which is the case
  194. node.CreateComponent<RigidBody2D>().SetBodyType(BodyType2D.BT_STATIC);
  195. }
  196. void CreateDecor(Vector3 position, Vector3 leftVector)
  197. {
  198. // We discard a few calls
  199. _discard++;
  200. if (_discard % 3 != 0)
  201. return;
  202. // We use the simplex noise to evaluate chances with smooth transition
  203. double chance = _noise.Evaluate(position.X * 0.2f, 0)+1;
  204. Node node = null;
  205. bool isTree = false;
  206. // Common decorations (grasses)
  207. if (chance < 1.2f)
  208. {
  209. // We discard a few of these too
  210. if (_rng.Next(6) < 4)
  211. node = AtomicMain.CreateSpriteNode(_decorationLib.GetRandomFromGroup(0), 3, false);
  212. }
  213. else if (chance < 1.3f)
  214. node = AtomicMain.CreateSpriteNode(_decorationLib.GetRandomFromGroup(1), 4, false);
  215. else if (chance < 1.7f)
  216. {
  217. // We only spawn trees if the surface isn't too steep
  218. if (Math.Abs(leftVector.Y) < 0.1f)
  219. {
  220. node = AtomicMain.CreateSpriteNode(_decorationLib.GetRandomFromGroup(2), 4, false);
  221. node.Scale2D = node.Scale2D *= 1 + _rng.Next(30)/100f;
  222. isTree = true;
  223. }
  224. }
  225. if (node != null)
  226. {
  227. // We position the node on the surface, if the node is a tree we don't rotate it AND offset it a bit
  228. node.SetPosition(position+Vector3.Forward*15);
  229. if (!isTree)
  230. {
  231. Quaternion transformation = Quaternion.FromRotationTo(Vector3.Left, leftVector);
  232. node.SetRotation(transformation);
  233. }
  234. else
  235. node.Translate(-node.Up*0.1f);
  236. }
  237. }
  238. }