diff --git a/Scripts/Chunk.cs b/Scripts/Chunk.cs new file mode 100644 index 0000000..06c814f --- /dev/null +++ b/Scripts/Chunk.cs @@ -0,0 +1,128 @@ +using System; +using FOU.Scripts.Elements; +using Godot; + +namespace FOU.Scripts; + +public class Chunk { + public Element[,] Elements; + public Chunk NeighborN = null; + public Chunk NeighborE = null; + public Chunk NeighborS = null; + public Chunk NeighborW = null; + + private readonly Image image; + private readonly int sizeX; + private readonly int sizeY; + private int frame; + private Chunk _this; + + public Chunk(int x, int y) { + _this = this; + + sizeX = x; + sizeY = y; + + Elements = new Element[x, y]; + for (int i = 0; i < sizeX; i++) { + for (int j = 0; j < sizeY; j++) { + Elements[i,j] = new Element(i, j, _this); + } + } + + image = Image.Create(sizeX, sizeY, false, Image.Format.Rgb8); + image.Fill(Colors.Black); + } + + public void Update() { + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + if (Elements[x,y] != null) + Elements[x,y].Update(frame); + } + } + frame++; + // TODO: enable rain again + // MakeItRain(_main.RainAmount); + } + + public void WritePixel(int x, int y, int size) { + int halfsize = size/2; + + Type type = typeof(T); + + for (int i = -halfsize; i <= halfsize; i++) { + for (int j = -halfsize; j <= halfsize; j++) { + int X = Mathf.Clamp(x + i, 0, sizeX-1); + int Y = Mathf.Clamp(y + j, 0, sizeY-1); + object o = Activator.CreateInstance(type, X, Y, _this); + + if (Elements[X,Y].GetType() != type) + Elements[X,Y] = o as Element; + } + } + } + + public Image DrawLevel() { + for (int x = 0; x < sizeX; x++) { + for (int y = 0; y < sizeY; y++) { + image.SetPixel(x,y, Elements[x,y].Color); + } + } + return image; + } + + public void Swap(Element what, Vector2I pos) { + Swap(what, Get(pos)); + } + + public Element Get(Vector2I pos) { + // North + if (pos.X < 0) + if (NeighborN != null) return NeighborN.Get(NeighborN.sizeX + pos.X, pos.Y); + + // South + if (pos.X >= sizeX) + if (NeighborS != null) return NeighborS.Get(sizeX - pos.X, pos.Y); + + // West + if (pos.Y < 0) + if (NeighborW != null) return NeighborW.Get(pos.X, NeighborW.sizeY + pos.Y); + + // East + if (pos.Y >= sizeY) + if (NeighborE != null) return NeighborE.Get(pos.X, sizeY - pos.Y); + + return Get(pos.X, pos.Y); + } + + private Element Get(int x, int y) { + if (x < 0 || x >= sizeX) return null; + if (y < 0 || y >= sizeY) return null; + + return Elements[x,y]; + } + + public bool IsEmpty(Vector2I pos) { + return Get(pos)?.GetType() == typeof(Element); + } + + private void Swap(Element what, Element swapTo) { + Element swap = new Element(what); + + if (what == null || swapTo == null) return; + + what.Position = swapTo.Position; + what.Chunk = swapTo.Chunk; + Elements[swapTo.Position.X, swapTo.Position.Y] = what; + + swapTo.Position = swap.Position; + swapTo.Chunk = swap.Chunk; + Elements[swap.Position.X, swap.Position.Y] = swapTo; + + what.Active = true; + swapTo.Active = true; + what.LastMove = frame; + swapTo.LastMove = frame; + } +} diff --git a/Scripts/Elements/Dirt.cs b/Scripts/Elements/Dirt.cs index a288c70..e55b14e 100644 --- a/Scripts/Elements/Dirt.cs +++ b/Scripts/Elements/Dirt.cs @@ -4,7 +4,7 @@ namespace FOU.Scripts.Elements; public class Dirt : Solid { - public Dirt(int x, int y, ref Level level) : base(x, y, ref level) { + public Dirt(int x, int y, ref Chunk chunk) : base(x, y, ref chunk) { Color = AddColorVariance(Colors.Brown); Density = 10; DiffuseSpeed = 50; diff --git a/Scripts/Elements/Element.cs b/Scripts/Elements/Element.cs index 254eed4..856d7bc 100644 --- a/Scripts/Elements/Element.cs +++ b/Scripts/Elements/Element.cs @@ -6,6 +6,7 @@ public class Element { public Color Color = Colors.Black; public Vector2I Position; + public Chunk Chunk; public int Density; public int LastUpdate = -1; public int LastMove = 0; @@ -16,20 +17,19 @@ public class Element { protected const float MAX_COLOR_VARIANCE = 0.1f; protected static readonly Vector2I VERTICAL_OPPOSITE = new Vector2I(-1, 1); - protected readonly Level Level; private bool active = true; private Color originalColor; public Element(Element e) { Position = e.Position; - Level = e.Level; + Chunk = e.Chunk; } - public Element(int x, int y, Level level) { + public Element(int x, int y, Chunk chunk) { Position.X = x; Position.Y = y; - Level = level; + Chunk = chunk; } public bool Active { @@ -65,19 +65,19 @@ public class Element { protected virtual void Tick() { Vector2I randomDirection = RandomDirectionDown(); - if (Level.IsEmpty(Position + Vector2I.Down)) - Level.Swap(this, Position + Vector2I.Down); + if (Chunk.IsEmpty(Position + Vector2I.Down)) + Chunk.Swap(this, Position + Vector2I.Down); - else if (Level.IsEmpty(Position + randomDirection)) - Level.Swap(this, Position + randomDirection); + else if (Chunk.IsEmpty(Position + randomDirection)) + Chunk.Swap(this, Position + randomDirection); - else if (Level.IsEmpty(Position + randomDirection * VERTICAL_OPPOSITE)) - Level.Swap(this, Position + randomDirection * VERTICAL_OPPOSITE); + else if (Chunk.IsEmpty(Position + randomDirection * VERTICAL_OPPOSITE)) + Chunk.Swap(this, Position + randomDirection * VERTICAL_OPPOSITE); if (GD.Randi() % MAX_DIFFUSE_SPEED > DiffuseSpeed) return; // ascend slower - if (Level.Get(Position + randomDirection)?.Density < Density) - Level.Swap(this, Position + Vector2I.Down); + if (Chunk.Get(Position + randomDirection)?.Density < Density) + Chunk.Swap(this, Position + Vector2I.Down); } protected Vector2I RandomDirectionDown() { diff --git a/Scripts/Elements/Liquid.cs b/Scripts/Elements/Liquid.cs index c330c7f..ea2d7a7 100644 --- a/Scripts/Elements/Liquid.cs +++ b/Scripts/Elements/Liquid.cs @@ -5,7 +5,7 @@ namespace FOU.Scripts.Elements; public abstract class Liquid : Element { public const int MAX_VERTICAL_SPREAD = 3; - protected Liquid(int x, int y, ref Level level) : base(x, y, level) { } + protected Liquid(int x, int y, ref Chunk chunk) : base(x, y, chunk) { } public override bool Update(int currentFrame) { if (!base.Update(currentFrame)) return false; @@ -21,13 +21,13 @@ public abstract class Liquid : Element { if (randomDirection.Y != 0) randomDirection *= Vector2I.Left * (int)(GD.Randi() % MAX_VERTICAL_SPREAD); - if (Level.IsEmpty(Position + Vector2I.Down)) - Level.Swap(this, Position + Vector2I.Down); + if (Chunk.IsEmpty(Position + Vector2I.Down)) + Chunk.Swap(this, Position + Vector2I.Down); - else if (Level.IsEmpty(Position + randomDirection)) - Level.Swap(this, Position + Vector2I.Right * randomDirection); + else if (Chunk.IsEmpty(Position + randomDirection)) + Chunk.Swap(this, Position + Vector2I.Right * randomDirection); - else if (Level.IsEmpty(Position + randomDirection)) - Level.Swap(this, Position + Vector2I.Right * randomDirection * VERTICAL_OPPOSITE); + else if (Chunk.IsEmpty(Position + randomDirection)) + Chunk.Swap(this, Position + Vector2I.Right * randomDirection * VERTICAL_OPPOSITE); } } diff --git a/Scripts/Elements/Solid.cs b/Scripts/Elements/Solid.cs index 9ad4913..55b8107 100644 --- a/Scripts/Elements/Solid.cs +++ b/Scripts/Elements/Solid.cs @@ -3,7 +3,7 @@ namespace FOU.Scripts.Elements; public abstract class Solid : Element { - protected Solid(int x, int y, ref Level level) : base(x, y, level) { } + protected Solid(int x, int y, ref Chunk chunk) : base(x, y, chunk) { } public override bool Update(int currentFrame) { if (!base.Update(currentFrame)) return false; diff --git a/Scripts/Elements/Water.cs b/Scripts/Elements/Water.cs index 71cbcf4..200f2b5 100644 --- a/Scripts/Elements/Water.cs +++ b/Scripts/Elements/Water.cs @@ -4,7 +4,7 @@ namespace FOU.Scripts.Elements; public class Water : Liquid { - public Water(int x, int y, ref Level level) : base(x, y, ref level) { + public Water(int x, int y, ref Chunk chunk) : base(x, y, ref chunk) { Color = AddColorVariance(Colors.Blue); Density = 1; } diff --git a/Scripts/Level.cs b/Scripts/Level.cs index 726b0c4..a1aaef0 100644 --- a/Scripts/Level.cs +++ b/Scripts/Level.cs @@ -1,123 +1,84 @@ -using System; -using FOU.Scripts.Elements; -using Godot; +using Godot; namespace FOU.Scripts; public class Level { - public int SizeX; - public int SizeY; - private Image mImage; - private int _frame; - private Element[,] _elements; - private Level _this; - private Main _main; + public Vector2I Resolution; - public Level(int x, int y, Main main) { - GD.Print($"Generating level ({x}:{y})"); - _this = this; - _main = main; + private Chunk[,] chunks; + private Image image; - SizeX = x; - SizeY = y; + private int chunksX; + private int chunksY; - _elements = new Element[x, y]; - for (int i = 0; i < SizeX; i++) { - for (int j = 0; j < SizeY; j++) { - _elements[i,j] = new Element(i, j, _this); + // per chunk: + private int chunkResX; + private int chunkResY; + + public Level(Main main, int sizeX, int sizeY) { + GD.Print($"Generating level ({sizeX}:{sizeY})"); + + Resolution = new Vector2I(sizeX, sizeY); + chunksX = main.ChunksPerAxis; + chunksY = main.ChunksPerAxis; + + chunkResX = sizeX / chunksX; + chunkResY = sizeY / chunksY; + + image = Image.Create(sizeX, sizeY, false, Image.Format.Rgb8); + + chunks = new Chunk[chunksX, chunksY]; + // create all chunks + for (int x = 0; x < chunksX; x++) { + for (int y = 0; y < chunksY; y++) { + chunks[x, y] = new Chunk(chunkResX, chunkResY); } } - mImage = Image.Create(SizeX, SizeY, false, Image.Format.Rgb8); - mImage.Fill(Colors.Black); + // assign neighbors + for (int x = 0; x < chunksX; x++) { + for (int y = 0; y < chunksY; y++) { + + if (x > 0) chunks[x, y].NeighborN = chunks[x-1, y]; + if (x < chunksX-1) chunks[x, y].NeighborS = chunks[x+1, y]; + + if (y > 0) chunks[x, y].NeighborW = chunks[x, y-1]; + if (y < chunksY-1) chunks[x, y].NeighborE = chunks[x, y+1]; + } + } } public void Update() { - for (int x = 0; x < SizeX; x++) { - for (int y = 0; y < SizeY; y++) { - if (_elements[x,y] != null) - _elements[x,y].Update(_frame); - } - } - _frame++; - MakeItRain(_main.RainAmount); + foreach (Chunk c in chunks) + c.Update(); } + // TODO! private void MakeItRain(float rainAmount) { - int rainDrops = (int)Math.Round(rainAmount); - - for (int i = 0; i <= rainDrops; i++) { - if (GD.Randf() < rainAmount) - WritePixel((int)(GD.Randi() % SizeX), 0, 1); - } + // int rainDrops = (int)Math.Round(rainAmount); + // + // for (int i = 0; i <= rainDrops; i++) { + // if (GD.Randf() < rainAmount) + // WritePixel((int)(GD.Randi() % SizeX), 0, 1); + // } } public void WritePixel(int x, int y, int size) { - int halfsize = size/2; - - Type type = typeof(T); - - for (int i = -halfsize; i <= halfsize; i++) { - for (int j = -halfsize; j <= halfsize; j++) { - int X = Mathf.Clamp(x + i, 0, SizeX-1); - int Y = Mathf.Clamp(y + j, 0, SizeY-1); - object o = Activator.CreateInstance(type, X, Y, _this); - - if (_elements[X,Y].GetType() != type) - _elements[X,Y] = o as Element; - } - } + chunks[x/chunkResX, y/chunkResY].WritePixel(x % chunkResX, y % chunkResY, size); } public Image DrawLevel() { - for (int x = 0; x < SizeX; x++) { - for (int y = 0; y < SizeY; y++) { - mImage.SetPixel(x,y, _elements[x,y].Color); + for (int cx = 0; cx < chunksX; cx++) { + for (int cy = 0; cy < chunksY; cy++) { + + for (int x = 0; x < chunkResX; x++) { + for (int y = 0; y < chunkResY; y++) { + image.SetPixel(cx*chunkResX + x, cy*chunkResY + y, chunks[cx,cy].Elements[x, y].Color); + } + } } } - return mImage; - } - - private void Swap(Element what, Element swapTo) { - Element swap = new Element(what); - - if (what == null || swapTo == null) return; - - what.Position = swapTo.Position; - _elements[swapTo.Position.X, swapTo.Position.Y] = what; - - swapTo.Position = swap.Position; - _elements[swap.Position.X, swap.Position.Y] = swapTo; - - what.Active = true; - swapTo.Active = true; - what.LastMove = _frame; - swapTo.LastMove = _frame; - } - - public void Swap(Element what, Vector2I pos) { - Swap(what, Get(pos)); - } - - public Element Get(Vector2I where) { - if (where.X < 0 || where.X >= SizeX) return null; - if (where.Y < 0 || where.Y >= SizeY) return null; - - return _elements[where.X, where.Y]; - } - - public Element Get(int x, int y) { - if (x < 0 || x >= SizeX) return null; - if (y < 0 || y >= SizeY) return null; - - return _elements[x,y]; - } - - public bool IsEmpty(Vector2I pos) { - if (pos.X < 0 || pos.X >= SizeX) return false; - if (pos.Y < 0 || pos.Y >= SizeY) return false; - - return Get(pos).GetType() == typeof(Element); + return image; } } diff --git a/Scripts/Main.cs b/Scripts/Main.cs index 732c10a..62d0ce1 100644 --- a/Scripts/Main.cs +++ b/Scripts/Main.cs @@ -1,3 +1,4 @@ +using System.Reflection; using FOU.Scripts; using FOU.Scripts.Elements; using Godot; @@ -7,6 +8,7 @@ public partial class Main : Node2D { [Export] public int BrushSize = 5; [Export] public float TextureResolution = 0.5f; + [Export] public int ChunksPerAxis = 2; [Export] public float RainAmount = 0.01f; public static Main Instance; @@ -15,16 +17,14 @@ public partial class Main : Node2D { private Level mLevel; public override void _Ready() { - mLevel = new Level((int)(GetViewportRect().Size.X * TextureResolution), - (int)(GetViewportRect().Size.Y * TextureResolution), this - ); + mLevel = new Level(this, (int)(GetViewportRect().Size.X * TextureResolution), + (int)(GetViewportRect().Size.Y * TextureResolution)); mLevelDrawer = GetNode("CanvasLayer/LevelDrawer"); Instance = this; } public override void _Process(double delta) { - mLevel.Update(); mLevelDrawer.Texture = ImageTexture.CreateFromImage(mLevel.DrawLevel()); } @@ -34,8 +34,8 @@ public partial class Main : Node2D { Vector2 mouse = GetViewport().GetMousePosition(); - float mappedX = mouse.X / GetViewportRect().Size.X * mLevel.SizeX; - float mappedY = mouse.Y / GetViewportRect().Size.Y * mLevel.SizeY; + float mappedX = mouse.X / (GetViewportRect().Size.X / mLevel.Resolution.X); + float mappedY = mouse.Y / (GetViewportRect().Size.Y / mLevel.Resolution.Y); mLevel.WritePixel((int)mappedX, (int)mappedY, BrushSize); }