From 92d7bd0ef49c6e32a7981c69c617e1f5879dd648 Mon Sep 17 00:00:00 2001 From: Funny_Silkie Date: Thu, 8 Oct 2020 18:39:34 +0900 Subject: [PATCH 1/4] implement TileMapNode --- Engine/Node/TileMapNode.cs | 739 +++++++++++++++++++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 Engine/Node/TileMapNode.cs diff --git a/Engine/Node/TileMapNode.cs b/Engine/Node/TileMapNode.cs new file mode 100644 index 00000000..a83a2142 --- /dev/null +++ b/Engine/Node/TileMapNode.cs @@ -0,0 +1,739 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Altseed2 +{ + /// + /// 大量描画を行うノード + /// + [Serializable] + public class TileMapNode : TransformNode, IDrawn, ICullableDrawn + { + private readonly MapChipList chips; + private readonly RenderedPolygon renderedPolygon; + private bool requireUpdateVertexes = true; + + /// + /// 格納されているを取得します。 + /// + public ReadOnlyCollection Chips => _chips ??= new ReadOnlyCollection(chips); + [NonSerialized] + private ReadOnlyCollection _chips; + + /// + /// の新しいインスタンスを生成します。 + /// + /// Graphics昨日が初期化されていない + public TileMapNode() + { + if (!Engine.Config.EnabledCoreModules.HasFlag(CoreModules.Graphics)) throw new InvalidOperationException("Graphics機能が初期化されていません。"); + chips = new MapChipList(); + renderedPolygon = RenderedPolygon.Create(); + } + + #region IDrawn + Rendered ICullableDrawn.Rendered => renderedPolygon; + + void IDrawn.Draw() + { + Engine.Renderer.DrawPolygon(renderedPolygon); + } + + int ICullableDrawn.CullingId => renderedPolygon.Id; + + /// + /// カメラグループを取得または設定します。 + /// + public ulong CameraGroup + { + get => _CameraGroup; + set + { + if (_CameraGroup == value) return; + var old = _CameraGroup; + _CameraGroup = value; + + if (IsRegistered) + Engine.UpdateDrawnCameraGroup(this, old); + } + } + private ulong _CameraGroup; + + /// + /// 描画時の重ね順を取得または設定します。 + /// + public int ZOrder + { + get => _ZOrder; + set + { + if (value == _ZOrder) return; + + var old = _ZOrder; + _ZOrder = value; + + if (IsRegistered) + Engine.UpdateDrawnZOrder(this, old); + } + } + private int _ZOrder; + + /// + /// このノードを描画するかどうかを取得または設定します。 + /// + public bool IsDrawn + { + get => _IsDrawn; set + { + if (_IsDrawn == value) return; + _IsDrawn = value; + this.UpdateIsDrawnActuallyOfDescendants(); + + } + } + private bool _IsDrawn = true; + + /// + /// 先祖のを考慮して、このノードを描画するかどうかを取得します。 + /// + public bool IsDrawnActually => (this as ICullableDrawn).IsDrawnActually; + bool ICullableDrawn.IsDrawnActually { get; set; } = true; + #endregion + + #region Node + internal override void Registered() + { + base.Registered(); + Engine.RegisterDrawn(this); + Engine.CullingSystem.Register(renderedPolygon); + } + + internal override void Unregistered() + { + base.Unregistered(); + Engine.UnregisterDrawn(this); + Engine.CullingSystem.Unregister(renderedPolygon); + } + + internal override void Update() + { + if (requireUpdateVertexes) + { + UpdateVertexes(); + requireUpdateVertexes = false; + } + + base.Update(); + } + #endregion + + #region RenderedPolygon + /// + /// アルファブレンドを取得または設定します。 + /// + public AlphaBlend AlphaBlend + { + get => renderedPolygon.AlphaBlend; + set + { + if (renderedPolygon.AlphaBlend == value) return; + renderedPolygon.AlphaBlend = value; + } + } + + /// + /// 使用するマテリアルを取得または設定します。 + /// + public Material Material + { + get => renderedPolygon.Material; + set + { + if (renderedPolygon.Material == value) return; + renderedPolygon.Material = value; + } + } + + /// + /// 使用するテクスチャを取得または設定します。 + /// + public TextureBase Texture + { + get => renderedPolygon.Texture; + set + { + if (renderedPolygon.Texture == value) return; + renderedPolygon.Texture = value; + renderedPolygon.Src = new RectF(default, value?.Size ?? new Vector2I(1, 1)); + foreach (var chip in chips) chip.Src = new RectF(default, value?.Size ?? new Vector2I(1, 1)); + } + } + + /// + public sealed override Matrix44F InheritedTransform + { + get => base.InheritedTransform; + private protected set + { + base.InheritedTransform = value; + AbsoluteTransform = value * Matrix44F.GetTranslation2D(-CenterPosition); + renderedPolygon.Transform = AbsoluteTransform; + } + } + #endregion + + /// + public sealed override Vector2F ContentSize + { + get + { + MathHelper.GetMinMax(out var min, out var max, renderedPolygon.Vertexes); + return max - min; + } + } + + /// + /// を登録します。 + /// + /// 登録するのインスタンス + /// がnull + /// を追加出来たらtrue,それ以外でそれ以外でfalse + public bool AddMapChip(MapChip chip) + { + if (!chips.Add(chip)) return false; + chip.Owner = this; + chip.OnAdded(); + requireUpdateVertexes = true; + return true; + } + + /// + /// 登録されているを全て削除します。 + /// + public void ClearMapChips() + { + for (int i = 0; i < chips.Count; i++) chips[i].Owner = null; + chips.Clear(); + requireUpdateVertexes = true; + } + + /// + /// を削除します。 + /// + /// 削除するのインスタンス + /// がnull + /// を削除出来たらtrue,それ以外でそれ以外でfalse + public bool RemoveChip(MapChip chip) + { + if (!chips.Remove(chip)) return false; + chip.Owner = null; + requireUpdateVertexes = true; + return true; + } + + /// + /// 頂点の更新をリクエストします。 + /// + internal void RequestUpdateVertexes() + { + requireUpdateVertexes = true; + } + + private void UpdateVertexes() + { + chips.SortIfRequired(); + + var size = Texture?.Size ?? new Vector2I(1, 1); + var vertexes = new Vertex[4 * chips.Count]; + var basicVertexes = new[] + { + new Vector3F(0f, 0f, 0.5f), + new Vector3F(size.X, 0f, 0.5f), + new Vector3F(size.X, size.Y, 0.5f), + new Vector3F(0f, size.Y, 0.5f), + }; + + var buffers = new int[chips.Count * 6]; + + for (int i = 0; i < chips.Count; i++) + { + var chip = chips[i]; + var uv0 = chip.Src.Position / size; + var uvSize = chip.Src.Size / size; + + var i4 = i * 4; + vertexes[i4] = new Vertex(chip.Transform.Transform3D(basicVertexes[0]), chip.Color, uv0, default); + vertexes[i4 + 1] = new Vertex(chip.Transform.Transform3D(basicVertexes[1]), chip.Color, new Vector2F(uv0.X + uvSize.X, uv0.Y), default); + vertexes[i4 + 2] = new Vertex(chip.Transform.Transform3D(basicVertexes[2]), chip.Color, uv0 + uvSize, default); + vertexes[i4 + 3] = new Vertex(chip.Transform.Transform3D(basicVertexes[3]), chip.Color, new Vector2F(uv0.X, uv0.Y + uvSize.Y), default); + + for (int j = 0; j < 2; j++) + { + buffers[i * 6 + j * 3] = i4; + buffers[i * 6 + j * 3 + 1] = i4 + j + 1; + buffers[i * 6 + j * 3 + 2] = i4 + j + 2; + } + } + + renderedPolygon.Vertexes.FromSpan(vertexes); + renderedPolygon.Vertexes = renderedPolygon.Vertexes; + + renderedPolygon.Buffers.FromSpan(buffers); + renderedPolygon.Buffers = renderedPolygon.Buffers; + } + } + + /// + /// 大量描画に使用するマップチップ + /// + [Serializable] + public sealed class MapChip + { + private bool requireCalcTransform = true; + + internal MapChipList List { get; set; } + + internal TileMapNode Owner { get; set; } + + /// + /// 回転角度を取得または設定します。 + /// + public float Angle + { + get => _angle; + set + { + if (_angle == value) return; + _angle = value; + requireCalcTransform = true; + Owner?.RequestUpdateVertexes(); + } + } + private float _angle; + + /// + /// 中心座標を取得または設定します。 + /// + public Vector2F CenterPosition + { + get => _centerPosition; + set + { + if (_centerPosition == value) return; + _centerPosition = value; + requireCalcTransform = true; + Owner?.RequestUpdateVertexes(); + } + } + private Vector2F _centerPosition; + + /// + /// 色を取得または設定します。 + /// + public Color Color + { + get => _color; + set + { + if (_color == value) return; + _color = value; + Owner?.RequestUpdateVertexes(); + } + } + private Color _color = new Color(255, 255, 255); + + /// + /// 左右を反転するかどうかを取得または設定します。 + /// + public bool HorizontalFlip + { + get => _horizontalFlip; + set + { + if (_horizontalFlip == value) return; + _horizontalFlip = value; + requireCalcTransform = true; + } + } + private bool _horizontalFlip = false; + + /// + /// 座標を取得または設定します。 + /// + public Vector2F Position + { + get => _position; + set + { + if (_position == value) return; + _position = value; + requireCalcTransform = true; + Owner?.RequestUpdateVertexes(); + } + } + private Vector2F _position; + + /// + /// 拡大率を取得または設定します。 + /// + public Vector2F Scale + { + get => _scale; + set + { + if (_scale == value) return; + _scale = value; + requireCalcTransform = true; + Owner?.RequestUpdateVertexes(); + } + } + private Vector2F _scale = Vector2F.One; + + /// + /// テクスチャの描画範囲を取得または設定します。 + /// + public RectF Src + { + get => _src; + set + { + if (_src == value) return; + _src = value; + Owner?.RequestUpdateVertexes(); + } + } + private RectF _src; + + /// + /// 変形行列を取得します。 + /// + internal Matrix44F Transform + { + get + { + if (requireCalcTransform) + { + var scale = Scale; + if (HorizontalFlip) scale.X *= -1; + if (VerticalFlip) scale.Y *= -1; + _transform = MathHelper.CalcTransform(Position, MathHelper.DegreeToRadian(Angle), scale) * Matrix44F.GetTranslation2D(-CenterPosition); + requireCalcTransform = false; + } + return _transform; + } + } + private Matrix44F _transform = Matrix44F.Identity; + + /// + /// 上下を反転するかどうかを取得または設定します。 + /// + public bool VerticalFlip + { + get => _verticalFlip; + set + { + if (_verticalFlip == value) return; + _verticalFlip = value; + requireCalcTransform = true; + } + } + private bool _verticalFlip = false; + + /// + /// 重ね順を取得または設定します。 + /// + public int ZOrder + { + get => _zOrder; + set + { + if (_zOrder == value) return; + _zOrder = value; + List?.RequestSort(); + Owner?.RequestUpdateVertexes(); + } + } + private int _zOrder; + + /// + /// の新しいインスタンスを生成します。 + /// + public MapChip() { } + + internal void OnAdded() + { + Src = new RectF(default, Owner.Texture?.Size ?? new Vector2I(1, 1)); + } + } + + /// + /// を格納するコレクション + /// + [Serializable] + internal sealed class MapChipList : IList, IReadOnlyList + { + private static readonly MapChipComparer comparer = new MapChipComparer(); + private MapChip[] items; + private bool requireSorted; + private int version; + + /// + /// 格納されている要素数を取得します。 + /// + public int Count { get; private set; } + + bool ICollection.IsReadOnly => false; + + /// + /// の新しいインスタンスを生成します。 + /// + public MapChipList() + { + items = Array.Empty(); + } + + /// + /// 指定したインデックスの要素を取得します。 + /// + /// 検索する要素のインデックス + /// が範囲外 + /// に対応する要素 + public MapChip this[int index] + { + get + { + if (index < 0 || Count <= index) throw new ArgumentOutOfRangeException(nameof(index), "インデックスが範囲外です"); + return items[index]; + } + } + MapChip IList.this[int index] + { + get => this[index]; + set => throw new NotSupportedException("この操作はサポートされていません"); + } + + /// + /// を追加します。 + /// + /// 追加するのインスタンス + /// がnull + /// を追加出来たらtrue,それ以外でfalse + public bool Add(MapChip item) + { + if (item == null) throw new ArgumentNullException(nameof(item), "引数がnullです"); + if (item.List != null) return false; + var index = Array.BinarySearch(items, 0, Count, item, comparer); + if (index < 0) InsertPrivate(~index, item); + else InsertPrivate(index, item); + return true; + } + + /// + /// の新しいインスタンスを生成します。 + /// + /// ソートするかどうか + /// の新しいインスタンス + public Enumerator GetEnumerator(bool sort) + { + if (sort) SortIfRequired(); + return new Enumerator(this); + } + + private void InsertPrivate(int index, MapChip item) + { + if (items.Length < Count + 1) ReSize(Count + 1); + if (index < Count) Array.Copy(items, index, items, index + 1, Count - index); + items[index] = item; + version++; + Count++; + item.List = this; + } + + /// + /// ソートをリクエストします。 + /// + public void RequestSort() + { + requireSorted = true; + } + + private void ReSize(int min) + { + if (min <= items.Length) return; + var size = items.Length == 0 ? 4 : items.Length * 2; + if ((uint)size > int.MaxValue) size = int.MaxValue; + if (size < min) size = min; + Array.Resize(ref items, size); + } + + /// + /// ソートを実行します。 + /// + public void Sort() + { + Array.Sort(items, comparer); + requireSorted = false; + version++; + } + + /// + /// 必要がある場合にソートを実行します。 + /// + public void SortIfRequired() + { + if (requireSorted) Sort(); + } + + #region IList + void ICollection.Add(MapChip item) => Add(item); + + /// + /// 全ての要素を削除します。 + /// + public void Clear() + { + if (Count == 0) return; + for (int i = 0; i < Count; i++) items[i].List = null; + Array.Clear(items, 0, Count); + Count = 0; + version++; + } + + /// + /// 指定したが格納されているかどうかを取得します。 + /// + /// 検索するのインスタンス + /// が格納されていたらtrue,それ以外でfalse + public bool Contains(MapChip item) => item != null && item.List == this; + + void ICollection.CopyTo(MapChip[] array, int arrayIndex) + { + if (array == null) throw new ArgumentNullException(nameof(array), "引数がnullです"); + if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), "値が0未満です"); + if (array.Length < Count + arrayIndex) throw new ArgumentException("サイズが足りません", nameof(array)); + for (int i = 0; i < Count; i++) array[arrayIndex++] = items[i]; + } + + /// + /// の新しいインスタンスを生成します。 + /// + /// の新しいインスタンス + public Enumerator GetEnumerator() => GetEnumerator(false); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// 指定したのインデックスを取得します。 + /// + /// 検索するのインスタンス + /// のインデックス 格納されていない場合は-1 + public int IndexOf(MapChip item) => Contains(item) ? Array.IndexOf(items, item) : -1; + + void IList.Insert(int index, MapChip item) => throw new NotSupportedException("この操作はサポートされていません"); + + /// + /// を削除します。 + /// + /// 削除するのインスタンス + /// がnull + /// を追加出来たらtrue,それ以外でfalse + public bool Remove(MapChip item) + { + if (item == null) throw new ArgumentNullException(nameof(item), "引数がnullです"); + var index = IndexOf(item); + if (index < 0) return false; + RemoveAt(index); + return true; + } + + /// + /// 指定したインデックスの要素を削除する + /// + /// 削除する要素のインデックス + /// が範囲外 + public void RemoveAt(int index) + { + if (index < 0 || Count - 1 < index) throw new ArgumentOutOfRangeException(nameof(index), "インデックスが範囲外です"); + var chip = items[index]; + if (index < --Count) Array.Copy(items, index + 1, items, index, Count - index); + items[index] = default; + chip.List = null; + version++; + } + #endregion + + /// + /// 列挙を補助する構造体 + /// + [Serializable] + public struct Enumerator : IEnumerator + { + private readonly MapChipList list; + private int index; + private readonly int version; + + /// + /// 現在列挙されている要素を取得します。 + /// + public MapChip Current { get; private set; } + readonly object IEnumerator.Current + { + get + { + if (index == 0 || list.Count + 1 == index) throw new InvalidOperationException("要素の取得に失敗しました"); + return Current; + } + } + + internal Enumerator(MapChipList list) + { + this.list = list; + Current = default; + index = 0; + version = list.version; + } + + /// + /// このインスタンスを破棄します。 + /// + public void Dispose() { } + + /// + /// 列挙を次に進めます。 + /// + /// 列挙中にコレクションが変更された + /// 列挙を次に進められたらtrue,それ以外でfalse + public bool MoveNext() + { + if (version != list.version) throw new InvalidOperationException("列挙中にコレクションが変更されました"); + if ((uint)index < (uint)list.Count) + { + Current = list[index++]; + return true; + } + Current = default; + index = list.Count + 1; + return false; + } + + void IEnumerator.Reset() + { + if (version != list.version) throw new InvalidOperationException("列挙中にコレクションが変更されました"); + Current = default; + index = 0; + } + } + + private sealed class MapChipComparer : IComparer + { + public int Compare(MapChip x, MapChip y) + { + if (x == null) return y == null ? 0 : -1; + if (y == null) return 1; + return x.ZOrder.CompareTo(y.ZOrder); + } + } + } +} From c95a9ca0d895f1ccafb01970bf2f11a4c90f064c Mon Sep 17 00:00:00 2001 From: Funny_Silkie Date: Thu, 8 Oct 2020 19:09:35 +0900 Subject: [PATCH 2/4] fix call methods --- Engine/Node/TileMapNode.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Engine/Node/TileMapNode.cs b/Engine/Node/TileMapNode.cs index a83a2142..119fa5e3 100644 --- a/Engine/Node/TileMapNode.cs +++ b/Engine/Node/TileMapNode.cs @@ -355,6 +355,7 @@ public bool HorizontalFlip if (_horizontalFlip == value) return; _horizontalFlip = value; requireCalcTransform = true; + Owner?.RequestUpdateVertexes(); } } private bool _horizontalFlip = false; @@ -437,6 +438,7 @@ public bool VerticalFlip if (_verticalFlip == value) return; _verticalFlip = value; requireCalcTransform = true; + Owner?.RequestUpdateVertexes(); } } private bool _verticalFlip = false; From 608c5be8503439e2e0b2991a3826f249e9030ef6 Mon Sep 17 00:00:00 2001 From: Funny_Silkie Date: Thu, 8 Oct 2020 19:09:45 +0900 Subject: [PATCH 3/4] add test --- Test/DrawnNode.cs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Test/DrawnNode.cs b/Test/DrawnNode.cs index 23c2dd38..cb3a4906 100644 --- a/Test/DrawnNode.cs +++ b/Test/DrawnNode.cs @@ -399,6 +399,51 @@ public void IBPolygonNodeWithTexture() tc.End(); } + [Test, Apartment(ApartmentState.STA)] + public void TileMapNode() + { + var tc = new TestCore(); + tc.Init(); + + var texture = Texture2D.Load(@"TestData/IO/AltseedPink.png"); + Assert.NotNull(texture); + + var node = new TileMapNode() + { + Position = new Vector2F(250, 250), + Texture = texture + }; + Engine.AddNode(node); + + Assert.True(node.AddMapChip(new MapChip() + { + Angle = 30f, + CenterPosition = texture.Size / 2, + Color = new Color(255, 100, 100), + Position = new Vector2F(100f, 100f), + ZOrder = 0 + })); + Assert.True(node.AddMapChip(new MapChip() + { + CenterPosition = texture.Size / 2, + Color = new Color(100, 100, 255), + Position = new Vector2F(-50f, 0f), + ZOrder = 1 + })); + Assert.True(node.AddMapChip(new MapChip() + { + CenterPosition = texture.Size / 2, + Color = new Color(100, 255, 100), + Position = new Vector2F(0f, 0f), + Scale = new Vector2F(0.5f, 0.5f), + ZOrder = 2 + })); + + tc.LoopBody(null, null); + + tc.End(); + } + [Test, Apartment(ApartmentState.STA)] public void CenterPosition() { From 9b635648e8a26ec5f57c2e4ebb7e3909d560e1b6 Mon Sep 17 00:00:00 2001 From: Funny_Silkie Date: Thu, 8 Oct 2020 19:10:49 +0900 Subject: [PATCH 4/4] update SerializationTest --- Test/ReflectionSources.cs | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/Test/ReflectionSources.cs b/Test/ReflectionSources.cs index d8c4b76c..ede966f1 100644 --- a/Test/ReflectionSources.cs +++ b/Test/ReflectionSources.cs @@ -1198,6 +1198,71 @@ private static Dictionary GetInfo() }; result.Add(textNode.Type, textNode); + // Altseed2.TileMapNode + var tileMapNode = ReflectionInfo.Create(new TileMapNode() + { + AlphaBlend = AlphaBlend.Normal, + Angle = 30f, + CameraGroup = 1, + CenterPosition = new Vector2F(50f, 50f), + HorizontalFlip = false, + IsDrawn = false, + Material = Material.Create(), + Position = new Vector2F(30f, 30f), + Scale = new Vector2F(3f, 2f), + Texture = Texture2D.LoadStrict("TestData/IO/AltseedPink.png"), + VerticalFlip = false, + ZOrder = 10, + }); + tileMapNode.PropertyInfos = new[] + { + tileMapNode.GetProperty("AlphaBlend"), + tileMapNode.GetProperty("Angle"), + tileMapNode.GetProperty("CameraGroup"), + tileMapNode.GetProperty("CenterPosition"), + tileMapNode.GetProperty("ContentSize"), + tileMapNode.GetProperty("HorizontalFlip"), + tileMapNode.GetProperty("IsDrawn"), + tileMapNode.GetProperty("IsRegistered"), + tileMapNode.GetProperty("IsUpdated"), + tileMapNode.GetProperty("IsUpdatedActually"), + tileMapNode.GetProperty("Material"), + tileMapNode.GetProperty("Position"), + tileMapNode.GetProperty("Scale"), + tileMapNode.GetProperty("Status"), + tileMapNode.GetProperty("Texture"), + tileMapNode.GetProperty("VerticalFlip"), + tileMapNode.GetProperty("ZOrder"), + }; + result.Add(tileMapNode.Type, tileMapNode); + + // Altseed2.MapChip + var mapChip = ReflectionInfo.Create(new MapChip() + { + Angle = 30f, + CenterPosition = new Vector2F(50f, 50f), + Color = new Color(255, 100, 100), + HorizontalFlip = true, + Position = new Vector2F(100f, 100f), + Scale = new Vector2F(3f, 2f), + Src = new RectF(50f, 50f, 100f, 100f), + VerticalFlip = false, + ZOrder = 3, + }); + mapChip.PropertyInfos = new[] + { + mapChip.GetProperty("Angle"), + mapChip.GetProperty("CenterPosition"), + mapChip.GetProperty("Color"), + mapChip.GetProperty("HorizontalFlip"), + mapChip.GetProperty("Position"), + mapChip.GetProperty("Scale"), + mapChip.GetProperty("Src"), + mapChip.GetProperty("VerticalFlip"), + mapChip.GetProperty("ZOrder"), + }; + result.Add(mapChip.Type, mapChip); + // Altseed2.TransformerNode var transformerNode = ReflectionInfo.Create(new TransformerNode() {