39public class SceneLayer : IEnumerable<SceneLayerTile>, IDisposable
136 #region private fields
138 private int _tileWidth;
139 private int _tileHeight;
140 private bool _visible;
141 private string _defaultTileCollisionProfile = CollisionProfileNames.World;
142 private bool _fixedGridRenderEligibilityDirty;
143 private bool _fixedGridRenderEligible =
true;
144 private bool _sceneLayerTileArrayExternallyExposed;
147 internal float EffectOpacity {
get;
set; } = 1f;
150 internal float EffectReveal {
get;
set; } = 1f;
156 internal PointF EffectOffsetFactor {
get;
set; } = PointF.Empty;
159 internal PointF EffectOffsetPx {
get;
set; } = PointF.Empty;
161 #endregion private fields
163 #region constructors / finalizer
175 protected internal SceneLayer(
int columnCount,
182 var tileArray =
new SceneLayerTile[columnCount, rowCount];
184 InitValues(tileArray, width, height, parallax, coordinateSystem);
191 private void OnDeserialized(StreamingContext context)
195 bool fastPathEligible =
true;
196 int? fixedZOrder =
null;
201 var tile = _sceneLayerTileArray[x, y] ??=
new SceneLayerTile(
this);
202 tile.parentSceneLayer =
this;
203 if (tile.Transform !=
TileTransform.Identity) TransformedTiles.Add(tile);
204 tile.sceneLayerCoordinates =
new Point(x, y);
206 if (tile.GetType() != typeof(SceneLayerTile) || tile.EnableFog)
207 fastPathEligible =
false;
209 if (fixedZOrder.HasValue && fixedZOrder.Value != tile.ZOrder)
210 fastPathEligible =
false;
212 fixedZOrder ??= tile.ZOrder;
215 _fixedGridRenderEligible = fastPathEligible;
216 _fixedGridRenderEligibilityDirty =
false;
217 _sceneLayerTileArrayExternallyExposed =
false;
218 BuildTileColliders();
235 #endregion constructors / finalizer
254 internal ISceneLayerCoordinates CoordinateSystem {
get;
private set; } =
new OrthogonalCoordinates();
285 return CoordinateSystem
switch
294 _ =>
throw new InvalidOperationException($
"Unknown coordinate system type: {CoordinateSystem.GetType().Name}")
299 CoordinateSystem = value
switch
308 _ =>
throw new ArgumentOutOfRangeException(nameof(value), $
"Unknown coordinate system type: {value}")
323 public string ID {
get;
protected internal set; } = Guid.NewGuid().ToString();
338 internal RefreshQueue RefreshQueue {
get;
set; }
340 private int _zOrder = 0;
367 private float _parallax = 1f;
392 get {
return _parallax; }
418 get {
return _tileHeight; }
444 get {
return _tileWidth; }
470 get {
return _visible; }
479 internal HashSet<SceneLayerTile> TransformedTiles {
get; } =
new();
481 [JsonProperty(
"SceneLayerTileArray")]
482 [JsonConverter(typeof(SceneLayerTileArrayConverter))]
483 private SceneLayerTile[,] _sceneLayerTileArray;
512 _sceneLayerTileArrayExternallyExposed =
true;
513 return _sceneLayerTileArray;
539 [JsonProperty(
"WrapHorizontally")]
540 private bool _wrapHoriz =
false;
559 get {
return _wrapHoriz; }
567 [JsonProperty(
"WrapVertically")]
568 private bool _wrapVerti =
false;
587 get {
return _wrapVerti; }
595 [JsonProperty(
"ShowGridLines")]
596 private bool showGridLines =
false;
614 get {
return showGridLines; }
617 showGridLines = value;
622 [JsonProperty(
"ShowCollisionBoxes")]
623 private bool showCollisionBoxes =
false;
642 get {
return showCollisionBoxes; }
645 showCollisionBoxes = value;
652 [JsonProperty(
"OriginPx")]
653 private Point _originPx = Point.Empty;
676 if (_originPx == value)
return;
695 internal CollisionResolver CollisionResolver {
get;
private set; } =
null!;
714 get => _defaultTileCollisionProfile;
717 if (
string.IsNullOrWhiteSpace(value))
719 throw new ArgumentException(
720 "Default tile collision profile cannot be empty.",
724 if (
Scene is not
null)
726 var profile =
Scene.CollisionProfiles.Get(value);
727 _ = profile.ResolveCollisionGroup(
Scene.CollisionGroups);
728 _ = profile.ResolveCollidesWith(
Scene.CollisionGroups);
731 _defaultTileCollisionProfile = value;
733 if (
Scene is not
null && _sceneLayerTileArray is not
null)
734 ApplyDefaultTileCollisionProfile();
738 #endregion properties
740 #region public methods
760 _tileWidth = newWidth;
761 _tileHeight = newHeight;
769 internal void ApplyDefaultTileCollisionProfile()
771 if (
Scene is
null || _sceneLayerTileArray is
null)
778 foreach (var tile
in _sceneLayerTileArray)
779 tile?.SetCollisionProfile(_defaultTileCollisionProfile);
789 public PointF
GridToWorldPx(PointF grid) => CoordinateSystem.GetAnchorPixelAtSceneLayerCoordinates(
this, grid);
798 public PointF
WorldPxToGrid(PointF worldPx) => CoordinateSystem.GetSceneLayerCoordinatesAtPixel(
this, worldPx);
824 if (!
float.IsFinite(grid.X) || !
float.IsFinite(grid.Y))
825 throw new ArgumentOutOfRangeException(nameof(grid));
843 static int Mod(
int value,
int period) {
int remainder = value % period;
return remainder < 0 ? remainder + period : remainder; }
861 return GetPeriod().Offsets(contentBounds, queryBounds);
863 return contentBounds.IntersectsWith(queryBounds) ?
new[] { PointF.Empty } : Array.Empty<PointF>();
866 internal LayerPeriod GetPeriod() => CoordinateSystem.GetWrapPeriod(
this);
878 return RectangleF.Empty;
892 RectangleF result = RectangleF.Empty;
893 bool hasBounds =
false;
895 foreach (var tile
in corners)
897 var px = CoordinateSystem.GetPixelRangeForTile(tile, includeOverhang:
true);
898 var rf = RectangleF.FromLTRB(px.Left, px.Top, px.Right, px.Bottom);
907 result = RectangleF.Union(result, rf);
912 foreach (var tile
in TransformedTiles)
913 result = RectangleF.Union(result, tile.DrawLocationWorld);
918 #endregion public methods
920 #region private methods
924 _sceneLayerTileArray = tileArray;
925 _parallax = parallax;
927 _tileHeight = height;
930 _originPx = Point.Empty;
934 EffectOffsetFactor = PointF.Empty;
935 EffectOffsetPx = PointF.Empty;
943 SaveGridCoordinatesToSceneLayerTiles();
944 _fixedGridRenderEligible =
true;
945 _fixedGridRenderEligibilityDirty =
false;
946 _sceneLayerTileArrayExternallyExposed =
false;
947 BuildTileColliders();
950 RefreshQueue =
new RefreshQueue(() =>
Scene?.UsesDirtyRegionRendering ??
true);
953 private void SaveGridCoordinatesToSceneLayerTiles()
956 for (
int X = 0; X <= _sceneLayerTileArray.GetUpperBound(0); X++)
958 for (
int Y = 0; Y <= _sceneLayerTileArray.GetUpperBound(1); Y++)
960 _sceneLayerTileArray[X, Y] =
new SceneLayerTile(
this);
961 _sceneLayerTileArray[X, Y].sceneLayerCoordinates =
new Point(X, Y);
966 private void BuildTileColliders()
968 foreach (var tile
in _sceneLayerTileArray)
973 tile.EnsureCollider();
977 internal bool IsFixedGridSnapshotFastPathEligible
981 if (_sceneLayerTileArrayExternallyExposed)
984 if (_fixedGridRenderEligibilityDirty)
985 RecalculateFixedGridRenderEligibility();
987 return _fixedGridRenderEligible;
991 internal void InvalidateFixedGridRenderEligibility() =>
992 _fixedGridRenderEligibilityDirty =
true;
994 private void RecalculateFixedGridRenderEligibility()
996 bool eligible =
true;
997 int? fixedZOrder =
null;
999 foreach (var tile
in _sceneLayerTileArray)
1002 tile.GetType() != typeof(SceneLayerTile) ||
1003 !ReferenceEquals(tile.SceneLayer,
this) ||
1010 if (fixedZOrder.HasValue && fixedZOrder.Value != tile.ZOrder)
1016 fixedZOrder ??= tile.ZOrder;
1019 _fixedGridRenderEligible = eligible;
1020 _fixedGridRenderEligibilityDirty =
false;
1023 internal virtual List<IDrawable> GetDrawablesInWorldRect(Rectangle worldRect,
bool includeOverhang =
true) =>
1024 GetDrawablesInWorldRectCore(
1027 collectSortDiagnostics:
false,
1030 internal virtual List<IDrawable> GetDrawablesInWorldRectWithDiagnostics(
1031 Rectangle worldRect,
1033 bool includeOverhang =
true) =>
1034 GetDrawablesInWorldRectCore(
1037 collectSortDiagnostics:
true,
1040 private List<IDrawable> GetDrawablesInWorldRectCore(
1041 Rectangle worldRect,
1042 bool includeOverhang,
1043 bool collectSortDiagnostics,
1047 return GetWrappedDrawables(worldRect, collectSortDiagnostics, out sortTicks);
1051 var queryRect = worldRect;
1053 queryRect.Inflate(1, 1);
1057 var renderTiles = CoordinateSystem.GetSceneLayerTilesForRendering(
1061 var sceneLayerTiles = renderTiles.Tiles;
1063 var list =
new List<IDrawable>(sceneLayerTiles.Count);
1064 bool uniformGridZOrder =
true;
1065 int? gridZOrder =
null;
1067 for (
int i = 0; i < sceneLayerTiles.Count; i++)
1069 var tile = sceneLayerTiles[i];
1071 if (!tile.Visible || !HasRenderableTileContent(tile))
1074 int tileZ = tile.ZOrder;
1075 if (gridZOrder.HasValue && gridZOrder.Value != tileZ)
1076 uniformGridZOrder =
false;
1078 gridZOrder ??= tileZ;
1083 bool hasNonGridDrawables =
false;
1086 var sprites = SpriteManager.Instance.GetSpritesInWorldRectRange(
1089 fullEnclosures:
false);
1091 for (
int i = 0; i < sprites.Count; i++)
1093 var sprite = sprites[i];
1098 if (!sprite.VisualBoundsWorld.IntersectsWith(queryRect))
1102 hasNonGridDrawables =
true;
1106 var drawings = DirectDrawingManager.Instance.GetDrawingsForLayer(
this);
1108 for (
int i = 0; i < drawings.Count; i++)
1110 var drawing = drawings[i];
1112 if (!drawing.Visible)
1118 if (!drawing.WorldBounds.IntersectsWith(worldRect))
1122 hasNonGridDrawables =
true;
1128 bool gridOrderGuaranteed =
1129 renderTiles.IsRenderOrdered &&
1132 long sortStartTick = collectSortDiagnostics ? HighResTimer.GetCurrentTick() : 0;
1133 if (hasNonGridDrawables)
1135 list.Sort(CompareDrawables);
1137 else if (!gridOrderGuaranteed && !IsSorted(list, CompareDrawables))
1139 list.Sort(CompareDrawables);
1142 sortTicks = collectSortDiagnostics
1143 ? Math.Max(0, HighResTimer.GetCurrentTick() - sortStartTick)
1149 private List<IDrawable> GetWrappedDrawables(
1150 Rectangle worldRect,
1151 bool collectSortDiagnostics,
1154 var period = GetPeriod();
1155 var instances =
new List<IDrawable>();
1156 void Add(IDrawable drawable, RectangleF bounds)
1158 if (!drawable.
Visible)
return;
1159 foreach (var offset
in period.Offsets(bounds, worldRect))
1161 if (instances.Count >= 1_000_000)
1162 throw new InvalidOperationException(
"Too many visible wrapped instances.");
1163 instances.Add(
new WrappedDrawable(drawable,
this, offset));
1169 foreach (var tile
in _sceneLayerTileArray)
1170 if (tile is not
null) Add(tile, tile.DrawLocationWorld);
1171 foreach (var sprite
in SpriteManager.Instance.AllSprites)
1172 if (ReferenceEquals(sprite.SceneLayer,
this)) Add(sprite, sprite.VisualBoundsWorld);
1173 foreach (var drawing
in DirectDrawingManager.Instance.GetDrawingsForLayer(
this))
1174 if (drawing.Mode ==
DirectDrawingMode.SceneLayer) Add(drawing, drawing.WorldBounds);
1176 long sortStartTick = collectSortDiagnostics ? HighResTimer.GetCurrentTick() : 0;
1177 instances.Sort(CompareWrappedDrawables);
1178 sortTicks = collectSortDiagnostics
1179 ? Math.Max(0, HighResTimer.GetCurrentTick() - sortStartTick)
1185 private bool HasRenderableTileContent(SceneLayerTile tile)
1188 if (tile.GetType() != typeof(SceneLayerTile))
1191 if (tile.CurrentFrame.Tilesheet is not
null)
1196 return tile.EnableFog ||
1201 private static bool IsSorted(
1202 List<IDrawable> drawables,
1203 Comparison<IDrawable> comparison)
1205 for (
int i = 1; i < drawables.Count; i++)
1207 if (comparison(drawables[i - 1], drawables[i]) > 0)
1214 private static int CompareWrappedDrawables(IDrawable a, IDrawable b)
1216 var x = (WrappedDrawable)a;
1217 var y = (WrappedDrawable)b;
1219 if (z != 0)
return z;
1221 if (x.Owner is Tile ta && y.Owner is Tile tb)
1223 if (ta.IsPositionFixed != tb.IsPositionFixed)
1224 return ta.IsPositionFixed ? -1 : 1;
1228 ? ta.DrawLocationWorld.Top + ta.Overhang.Top
1229 : ta.DrawLocationWorld.Bottom - ta.Overhang.Bottom - 1) +
1233 ? tb.DrawLocationWorld.Top + tb.Overhang.Top
1234 : tb.DrawLocationWorld.Bottom - tb.Overhang.Bottom - 1) +
1236 int depth = ay.CompareTo(by);
1237 if (depth != 0)
return depth;
1240 (ta.DrawLocationWorld.Left + x.Offset.X)
1241 .CompareTo(tb.DrawLocationWorld.Left + y.Offset.X);
1242 if (horizontal != 0)
return horizontal;
1245 int identity = a.
Id.CompareTo(b.
Id);
1246 return identity != 0
1248 : (x.Offset.Y, x.Offset.X).CompareTo((y.Offset.Y, y.Offset.X));
1251 private static int CompareDrawables(IDrawable a, IDrawable b)
1257 if (a is Tile ta && b is Tile tb)
1258 return ta.CompareTo(tb);
1260 return a.
Id.CompareTo(b.
Id);
1263 #endregion private methods
1317 if (x > _sceneLayerTileArray.GetUpperBound(0)
1318 || y > _sceneLayerTileArray.GetUpperBound(1)
1323 return _sceneLayerTileArray[x, y];
1328 #region IEnumerable Members
1351 IEnumerator<SceneLayerTile> IEnumerable<SceneLayerTile>.GetEnumerator()
1353 for (
int x = 0; x <= _sceneLayerTileArray.GetUpperBound(0); x++)
1355 for (
int y = 0; y <= _sceneLayerTileArray.GetUpperBound(1); y++)
1357 yield
return _sceneLayerTileArray[x, y];
1362 #endregion IEnumerable Members
1364 #region IDisposable Members
1390 GC.SuppressFinalize(
this);
1404 #endregion IDisposable Members
1406 #region empty SceneLayer
1431 public static SceneLayer
Empty {
get; } =
new EmptySceneLayer();
1433 private sealed class EmptySceneLayer :
SceneLayer
1435 internal EmptySceneLayer()
1436 : base(columnCount: 0, rowCount: 0, width: 1, height: 1)
1444 #endregion empty SceneLayer