IconSmoothSystem.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. using System.Numerics;
  2. using Content.Shared.IconSmoothing;
  3. using JetBrains.Annotations;
  4. using Robust.Client.GameObjects;
  5. using Robust.Shared.Map;
  6. using Robust.Shared.Map.Components;
  7. using Robust.Shared.Map.Enumerators;
  8. using static Robust.Client.GameObjects.SpriteComponent;
  9. namespace Content.Client.IconSmoothing
  10. {
  11. // TODO: just make this set appearance data?
  12. /// <summary>
  13. /// Entity system implementing the logic for <see cref="IconSmoothComponent"/>
  14. /// </summary>
  15. [UsedImplicitly]
  16. public sealed partial class IconSmoothSystem : EntitySystem
  17. {
  18. [Dependency] private readonly SharedMapSystem _mapSystem = default!;
  19. private readonly Queue<EntityUid> _dirtyEntities = new();
  20. private readonly Queue<EntityUid> _anchorChangedEntities = new();
  21. private int _generation;
  22. public void SetEnabled(EntityUid uid, bool value, IconSmoothComponent? component = null)
  23. {
  24. if (!Resolve(uid, ref component, false) || value == component.Enabled)
  25. return;
  26. component.Enabled = value;
  27. DirtyNeighbours(uid, component);
  28. }
  29. public override void Initialize()
  30. {
  31. base.Initialize();
  32. InitializeEdge();
  33. SubscribeLocalEvent<IconSmoothComponent, AnchorStateChangedEvent>(OnAnchorChanged);
  34. SubscribeLocalEvent<IconSmoothComponent, ComponentShutdown>(OnShutdown);
  35. SubscribeLocalEvent<IconSmoothComponent, ComponentStartup>(OnStartup);
  36. }
  37. private void OnStartup(EntityUid uid, IconSmoothComponent component, ComponentStartup args)
  38. {
  39. var xform = Transform(uid);
  40. if (xform.Anchored)
  41. {
  42. component.LastPosition = TryComp<MapGridComponent>(xform.GridUid, out var grid)
  43. ? (xform.GridUid.Value, _mapSystem.TileIndicesFor(xform.GridUid.Value, grid, xform.Coordinates))
  44. : (null, new Vector2i(0, 0));
  45. DirtyNeighbours(uid, component);
  46. }
  47. if (component.Mode != IconSmoothingMode.Corners || !TryComp(uid, out SpriteComponent? sprite))
  48. return;
  49. SetCornerLayers(sprite, component);
  50. if (component.Shader != null)
  51. {
  52. sprite.LayerSetShader(CornerLayers.SE, component.Shader);
  53. sprite.LayerSetShader(CornerLayers.NE, component.Shader);
  54. sprite.LayerSetShader(CornerLayers.NW, component.Shader);
  55. sprite.LayerSetShader(CornerLayers.SW, component.Shader);
  56. }
  57. }
  58. public void SetStateBase(EntityUid uid, IconSmoothComponent component, string newState)
  59. {
  60. if (!TryComp<SpriteComponent>(uid, out var sprite))
  61. return;
  62. component.StateBase = newState;
  63. SetCornerLayers(sprite, component);
  64. }
  65. private void SetCornerLayers(SpriteComponent sprite, IconSmoothComponent component)
  66. {
  67. sprite.LayerMapRemove(CornerLayers.SE);
  68. sprite.LayerMapRemove(CornerLayers.NE);
  69. sprite.LayerMapRemove(CornerLayers.NW);
  70. sprite.LayerMapRemove(CornerLayers.SW);
  71. var state0 = $"{component.StateBase}0";
  72. sprite.LayerMapSet(CornerLayers.SE, sprite.AddLayerState(state0));
  73. sprite.LayerSetDirOffset(CornerLayers.SE, DirectionOffset.None);
  74. sprite.LayerMapSet(CornerLayers.NE, sprite.AddLayerState(state0));
  75. sprite.LayerSetDirOffset(CornerLayers.NE, DirectionOffset.CounterClockwise);
  76. sprite.LayerMapSet(CornerLayers.NW, sprite.AddLayerState(state0));
  77. sprite.LayerSetDirOffset(CornerLayers.NW, DirectionOffset.Flip);
  78. sprite.LayerMapSet(CornerLayers.SW, sprite.AddLayerState(state0));
  79. sprite.LayerSetDirOffset(CornerLayers.SW, DirectionOffset.Clockwise);
  80. }
  81. private void OnShutdown(EntityUid uid, IconSmoothComponent component, ComponentShutdown args)
  82. {
  83. _dirtyEntities.Enqueue(uid);
  84. DirtyNeighbours(uid, component);
  85. }
  86. public override void FrameUpdate(float frameTime)
  87. {
  88. base.FrameUpdate(frameTime);
  89. var xformQuery = GetEntityQuery<TransformComponent>();
  90. var smoothQuery = GetEntityQuery<IconSmoothComponent>();
  91. // first process anchor state changes.
  92. while (_anchorChangedEntities.TryDequeue(out var uid))
  93. {
  94. if (!xformQuery.TryGetComponent(uid, out var xform))
  95. continue;
  96. if (xform.MapID == MapId.Nullspace)
  97. {
  98. // in null-space. Almost certainly because it left PVS. If something ever gets sent to null-space
  99. // for reasons other than this (or entity deletion), then maybe we still need to update ex-neighbor
  100. // smoothing here.
  101. continue;
  102. }
  103. DirtyNeighbours(uid, comp: null, xform, smoothQuery);
  104. }
  105. // Next, update actual sprites.
  106. if (_dirtyEntities.Count == 0)
  107. return;
  108. _generation += 1;
  109. var spriteQuery = GetEntityQuery<SpriteComponent>();
  110. // Performance: This could be spread over multiple updates, or made parallel.
  111. while (_dirtyEntities.TryDequeue(out var uid))
  112. {
  113. CalculateNewSprite(uid, spriteQuery, smoothQuery, xformQuery);
  114. }
  115. }
  116. public void DirtyNeighbours(EntityUid uid, IconSmoothComponent? comp = null, TransformComponent? transform = null, EntityQuery<IconSmoothComponent>? smoothQuery = null)
  117. {
  118. smoothQuery ??= GetEntityQuery<IconSmoothComponent>();
  119. if (!smoothQuery.Value.Resolve(uid, ref comp) || !comp.Running)
  120. return;
  121. _dirtyEntities.Enqueue(uid);
  122. if (!Resolve(uid, ref transform))
  123. return;
  124. Vector2i pos;
  125. EntityUid entityUid;
  126. if (transform.Anchored && TryComp<MapGridComponent>(transform.GridUid, out var grid))
  127. {
  128. entityUid = transform.GridUid.Value;
  129. pos = _mapSystem.CoordinatesToTile(transform.GridUid.Value, grid, transform.Coordinates);
  130. }
  131. else
  132. {
  133. // Entity is no longer valid, update around the last position it was at.
  134. if (comp.LastPosition is not (EntityUid gridId, Vector2i oldPos))
  135. return;
  136. if (!TryComp(gridId, out grid))
  137. return;
  138. entityUid = gridId;
  139. pos = oldPos;
  140. }
  141. // Yes, we updates ALL smoothing entities surrounding us even if they would never smooth with us.
  142. DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, 0)));
  143. DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, 0)));
  144. DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(0, 1)));
  145. DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(0, -1)));
  146. if (comp.Mode is IconSmoothingMode.Corners or IconSmoothingMode.NoSprite or IconSmoothingMode.Diagonal)
  147. {
  148. DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, 1)));
  149. DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, -1)));
  150. DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(-1, 1)));
  151. DirtyEntities(_mapSystem.GetAnchoredEntitiesEnumerator(entityUid, grid, pos + new Vector2i(1, -1)));
  152. }
  153. }
  154. private void DirtyEntities(AnchoredEntitiesEnumerator entities)
  155. {
  156. // Instead of doing HasComp -> Enqueue -> TryGetComp, we will just enqueue all entities. Generally when
  157. // dealing with walls neighboring anchored entities will also be walls, and in those instances that will
  158. // require one less component fetch/check.
  159. while (entities.MoveNext(out var entity))
  160. {
  161. _dirtyEntities.Enqueue(entity.Value);
  162. }
  163. }
  164. private void OnAnchorChanged(EntityUid uid, IconSmoothComponent component, ref AnchorStateChangedEvent args)
  165. {
  166. if (!args.Detaching)
  167. _anchorChangedEntities.Enqueue(uid);
  168. }
  169. private void CalculateNewSprite(EntityUid uid,
  170. EntityQuery<SpriteComponent> spriteQuery,
  171. EntityQuery<IconSmoothComponent> smoothQuery,
  172. EntityQuery<TransformComponent> xformQuery,
  173. IconSmoothComponent? smooth = null)
  174. {
  175. TransformComponent? xform;
  176. Entity<MapGridComponent>? gridEntity = null;
  177. // The generation check prevents updating an entity multiple times per tick.
  178. // As it stands now, it's totally possible for something to get queued twice.
  179. // Generation on the component is set after an update so we can cull updates that happened this generation.
  180. if (!smoothQuery.Resolve(uid, ref smooth, false)
  181. || smooth.Mode == IconSmoothingMode.NoSprite
  182. || smooth.UpdateGeneration == _generation
  183. || !smooth.Enabled
  184. || !smooth.Running)
  185. {
  186. if (smooth is { Enabled: true } &&
  187. TryComp<SmoothEdgeComponent>(uid, out var edge) &&
  188. xformQuery.TryGetComponent(uid, out xform))
  189. {
  190. var directions = DirectionFlag.None;
  191. if (TryComp(xform.GridUid, out MapGridComponent? grid))
  192. {
  193. var gridUid = xform.GridUid.Value;
  194. var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
  195. gridEntity = (gridUid, grid);
  196. if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery))
  197. directions |= DirectionFlag.North;
  198. if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery))
  199. directions |= DirectionFlag.South;
  200. if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery))
  201. directions |= DirectionFlag.East;
  202. if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery))
  203. directions |= DirectionFlag.West;
  204. }
  205. CalculateEdge(uid, directions, component: edge);
  206. }
  207. return;
  208. }
  209. xform = xformQuery.GetComponent(uid);
  210. smooth.UpdateGeneration = _generation;
  211. if (!spriteQuery.TryGetComponent(uid, out var sprite))
  212. {
  213. Log.Error($"Encountered a icon-smoothing entity without a sprite: {ToPrettyString(uid)}");
  214. RemCompDeferred(uid, smooth);
  215. return;
  216. }
  217. var spriteEnt = (uid, sprite);
  218. if (xform.Anchored)
  219. {
  220. if (TryComp(xform.GridUid, out MapGridComponent? grid))
  221. {
  222. gridEntity = (xform.GridUid.Value, grid);
  223. }
  224. else
  225. {
  226. Log.Error($"Failed to calculate IconSmoothComponent sprite in {uid} because grid {xform.GridUid} was missing.");
  227. return;
  228. }
  229. }
  230. switch (smooth.Mode)
  231. {
  232. case IconSmoothingMode.Corners:
  233. CalculateNewSpriteCorners(gridEntity, smooth, spriteEnt, xform, smoothQuery);
  234. break;
  235. case IconSmoothingMode.CardinalFlags:
  236. CalculateNewSpriteCardinal(gridEntity, smooth, spriteEnt, xform, smoothQuery);
  237. break;
  238. case IconSmoothingMode.Diagonal:
  239. CalculateNewSpriteDiagonal(gridEntity, smooth, spriteEnt, xform, smoothQuery);
  240. break;
  241. default:
  242. throw new ArgumentOutOfRangeException();
  243. }
  244. }
  245. private void CalculateNewSpriteDiagonal(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth,
  246. Entity<SpriteComponent> sprite, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
  247. {
  248. if (gridEntity == null)
  249. {
  250. sprite.Comp.LayerSetState(0, $"{smooth.StateBase}0");
  251. return;
  252. }
  253. var gridUid = gridEntity.Value.Owner;
  254. var grid = gridEntity.Value.Comp;
  255. var neighbors = new Vector2[]
  256. {
  257. new(1, 0),
  258. new(1, -1),
  259. new(0, -1),
  260. };
  261. var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
  262. var rotation = xform.LocalRotation;
  263. var matching = true;
  264. for (var i = 0; i < neighbors.Length; i++)
  265. {
  266. var neighbor = (Vector2i)rotation.RotateVec(neighbors[i]);
  267. matching = matching && MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos + neighbor), smoothQuery);
  268. }
  269. if (matching)
  270. {
  271. sprite.Comp.LayerSetState(0, $"{smooth.StateBase}1");
  272. }
  273. else
  274. {
  275. sprite.Comp.LayerSetState(0, $"{smooth.StateBase}0");
  276. }
  277. }
  278. private void CalculateNewSpriteCardinal(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth, Entity<SpriteComponent> sprite, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
  279. {
  280. var dirs = CardinalConnectDirs.None;
  281. if (gridEntity == null)
  282. {
  283. sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int)dirs}");
  284. return;
  285. }
  286. var gridUid = gridEntity.Value.Owner;
  287. var grid = gridEntity.Value.Comp;
  288. var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
  289. if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery))
  290. dirs |= CardinalConnectDirs.North;
  291. if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery))
  292. dirs |= CardinalConnectDirs.South;
  293. if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery))
  294. dirs |= CardinalConnectDirs.East;
  295. if (MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery))
  296. dirs |= CardinalConnectDirs.West;
  297. sprite.Comp.LayerSetState(0, $"{smooth.StateBase}{(int)dirs}");
  298. var directions = DirectionFlag.None;
  299. if ((dirs & CardinalConnectDirs.South) != 0x0)
  300. directions |= DirectionFlag.South;
  301. if ((dirs & CardinalConnectDirs.East) != 0x0)
  302. directions |= DirectionFlag.East;
  303. if ((dirs & CardinalConnectDirs.North) != 0x0)
  304. directions |= DirectionFlag.North;
  305. if ((dirs & CardinalConnectDirs.West) != 0x0)
  306. directions |= DirectionFlag.West;
  307. CalculateEdge(sprite, directions, sprite);
  308. }
  309. private bool MatchingEntity(IconSmoothComponent smooth, AnchoredEntitiesEnumerator candidates, EntityQuery<IconSmoothComponent> smoothQuery)
  310. {
  311. while (candidates.MoveNext(out var entity))
  312. {
  313. if (smoothQuery.TryGetComponent(entity, out var other) &&
  314. other.SmoothKey != null &&
  315. (other.SmoothKey == smooth.SmoothKey || smooth.AdditionalKeys.Contains(other.SmoothKey)) &&
  316. other.Enabled)
  317. {
  318. return true;
  319. }
  320. }
  321. return false;
  322. }
  323. private void CalculateNewSpriteCorners(Entity<MapGridComponent>? gridEntity, IconSmoothComponent smooth, Entity<SpriteComponent> spriteEnt, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
  324. {
  325. var (cornerNE, cornerNW, cornerSW, cornerSE) = gridEntity == null
  326. ? (CornerFill.None, CornerFill.None, CornerFill.None, CornerFill.None)
  327. : CalculateCornerFill(gridEntity.Value, smooth, xform, smoothQuery);
  328. // TODO figure out a better way to set multiple sprite layers.
  329. // This will currently re-calculate the sprite bounding box 4 times.
  330. // It will also result in 4-8 sprite update events being raised when it only needs to be 1-2.
  331. // At the very least each event currently only queues a sprite for updating.
  332. // Oh god sprite component is a mess.
  333. var sprite = spriteEnt.Comp;
  334. sprite.LayerSetState(CornerLayers.NE, $"{smooth.StateBase}{(int)cornerNE}");
  335. sprite.LayerSetState(CornerLayers.SE, $"{smooth.StateBase}{(int)cornerSE}");
  336. sprite.LayerSetState(CornerLayers.SW, $"{smooth.StateBase}{(int)cornerSW}");
  337. sprite.LayerSetState(CornerLayers.NW, $"{smooth.StateBase}{(int)cornerNW}");
  338. var directions = DirectionFlag.None;
  339. if ((cornerSE & cornerSW) != CornerFill.None)
  340. directions |= DirectionFlag.South;
  341. if ((cornerSE & cornerNE) != CornerFill.None)
  342. directions |= DirectionFlag.East;
  343. if ((cornerNE & cornerNW) != CornerFill.None)
  344. directions |= DirectionFlag.North;
  345. if ((cornerNW & cornerSW) != CornerFill.None)
  346. directions |= DirectionFlag.West;
  347. CalculateEdge(spriteEnt, directions, sprite);
  348. }
  349. private (CornerFill ne, CornerFill nw, CornerFill sw, CornerFill se) CalculateCornerFill(Entity<MapGridComponent> gridEntity, IconSmoothComponent smooth, TransformComponent xform, EntityQuery<IconSmoothComponent> smoothQuery)
  350. {
  351. var gridUid = gridEntity.Owner;
  352. var grid = gridEntity.Comp;
  353. var pos = _mapSystem.TileIndicesFor(gridUid, grid, xform.Coordinates);
  354. var n = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.North)), smoothQuery);
  355. var ne = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthEast)), smoothQuery);
  356. var e = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.East)), smoothQuery);
  357. var se = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthEast)), smoothQuery);
  358. var s = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.South)), smoothQuery);
  359. var sw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.SouthWest)), smoothQuery);
  360. var w = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.West)), smoothQuery);
  361. var nw = MatchingEntity(smooth, _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, pos.Offset(Direction.NorthWest)), smoothQuery);
  362. // ReSharper disable InconsistentNaming
  363. var cornerNE = CornerFill.None;
  364. var cornerSE = CornerFill.None;
  365. var cornerSW = CornerFill.None;
  366. var cornerNW = CornerFill.None;
  367. // ReSharper restore InconsistentNaming
  368. if (n)
  369. {
  370. cornerNE |= CornerFill.CounterClockwise;
  371. cornerNW |= CornerFill.Clockwise;
  372. }
  373. if (ne)
  374. {
  375. cornerNE |= CornerFill.Diagonal;
  376. }
  377. if (e)
  378. {
  379. cornerNE |= CornerFill.Clockwise;
  380. cornerSE |= CornerFill.CounterClockwise;
  381. }
  382. if (se)
  383. {
  384. cornerSE |= CornerFill.Diagonal;
  385. }
  386. if (s)
  387. {
  388. cornerSE |= CornerFill.Clockwise;
  389. cornerSW |= CornerFill.CounterClockwise;
  390. }
  391. if (sw)
  392. {
  393. cornerSW |= CornerFill.Diagonal;
  394. }
  395. if (w)
  396. {
  397. cornerSW |= CornerFill.Clockwise;
  398. cornerNW |= CornerFill.CounterClockwise;
  399. }
  400. if (nw)
  401. {
  402. cornerNW |= CornerFill.Diagonal;
  403. }
  404. // Local is fine as we already know it's parented to the grid (due to the way anchoring works).
  405. switch (xform.LocalRotation.GetCardinalDir())
  406. {
  407. case Direction.North:
  408. return (cornerSW, cornerSE, cornerNE, cornerNW);
  409. case Direction.West:
  410. return (cornerSE, cornerNE, cornerNW, cornerSW);
  411. case Direction.South:
  412. return (cornerNE, cornerNW, cornerSW, cornerSE);
  413. default:
  414. return (cornerNW, cornerSW, cornerSE, cornerNE);
  415. }
  416. }
  417. // TODO consider changing this to use DirectionFlags?
  418. // would require re-labelling all the RSI states.
  419. [Flags]
  420. private enum CardinalConnectDirs : byte
  421. {
  422. None = 0,
  423. North = 1,
  424. South = 2,
  425. East = 4,
  426. West = 8
  427. }
  428. [Flags]
  429. private enum CornerFill : byte
  430. {
  431. // These values are pulled from Baystation12.
  432. // I'm too lazy to convert the state names.
  433. None = 0,
  434. // The cardinal tile counter-clockwise of this corner is filled.
  435. CounterClockwise = 1,
  436. // The diagonal tile in the direction of this corner.
  437. Diagonal = 2,
  438. // The cardinal tile clockwise of this corner is filled.
  439. Clockwise = 4,
  440. }
  441. private enum CornerLayers : byte
  442. {
  443. SE,
  444. NE,
  445. NW,
  446. SW,
  447. }
  448. }
  449. }