diff --git a/Examples/MinecraftClone/Inventory.cs b/Examples/MinecraftClone/Inventory.cs
index ed8b7eb..b195560 100644
--- a/Examples/MinecraftClone/Inventory.cs
+++ b/Examples/MinecraftClone/Inventory.cs
@@ -1,4 +1,5 @@
-using System;
+using Minecraft.Terrain.Block;
+using System;
using System.Linq;
namespace Minecraft;
@@ -31,7 +32,7 @@ public class Inventory
public InventoryItem GetActiveSlotItems(int slotNumber)
=> Toolbar[slotNumber].Items;
- /*public void AddItem(BlockType type)
+ /*public void AddItem(BlockId type)
{
var item = Items.FirstOrDefault(x => x.Type == type);
if (item is not null)
@@ -43,10 +44,10 @@ public InventoryItem GetActiveSlotItems(int slotNumber)
///
/// Adds an item to the toolbar.
///
- /// The type of the block to add to the toolbar.
- public void AddToolbarItem(BlockType blockType)
+ /// The type of the block to add to the toolbar.
+ public void AddToolbarItem(BlockId BlockId)
{
- var slot = Toolbar.FirstOrDefault(i => i.Items.Type == blockType);
+ var slot = Toolbar.FirstOrDefault(i => i.Items.Type == BlockId);
if (slot is not null)
{
slot.Items.Amount += 1;
@@ -57,18 +58,18 @@ public void AddToolbarItem(BlockType blockType)
return;
}
- if (SelectedSlot.Items.Type == BlockType.None)
+ if (SelectedSlot.Items.Type == BlockId.Air)
{
- SelectedSlot.Items = new InventoryItem { Type = blockType, Amount = 1 };
+ SelectedSlot.Items = new InventoryItem { Type = BlockId, Amount = 1 };
Toolbar[SelectedSlotIndex] = SelectedSlot;
return;
}
for (int i = 0; i < Toolbar.Length; i++)
{
- if (Toolbar[i].Items.Type == BlockType.None)
+ if (Toolbar[i].Items.Type == BlockId.Air)
{
- Toolbar[i].Items = new InventoryItem { Type = blockType, Amount = 1 };
+ Toolbar[i].Items = new InventoryItem { Type = BlockId, Amount = 1 };
return;
}
}
@@ -130,7 +131,7 @@ public class InventoryItem
///
/// Gets or sets the type of the block in the inventory.
///
- public BlockType Type { get; set; } = BlockType.None;
+ public BlockId Type { get; set; } = BlockId.Air;
/// Gets or sets the amount of the block.
public int Amount { get; set; }
diff --git a/Examples/MinecraftClone/Minecraft.cs b/Examples/MinecraftClone/Minecraft.cs
index 6819f78..cb637fd 100644
--- a/Examples/MinecraftClone/Minecraft.cs
+++ b/Examples/MinecraftClone/Minecraft.cs
@@ -1,24 +1,17 @@
using ImGuiNET;
-
-using Minecraft.Block;
using Microsoft.Extensions.Logging;
-
+using Minecraft.Terrain.Block;
+using Minecraft.Terrain.Layers;
using SharpEngine.Core;
using SharpEngine.Core.Entities;
-using SharpEngine.Core.Entities.Lights;
-using SharpEngine.Core.Entities.Properties;
using SharpEngine.Core.Entities.UI;
-using SharpEngine.Core.Entities.UI.Layouts;
using SharpEngine.Core.Enums;
using SharpEngine.Core.Interfaces;
+using SharpEngine.Core.Numerics;
using SharpEngine.Core.Scenes;
using SharpEngine.Core.Windowing;
-using SharpEngine.Core.ObjLoader.Loaders.ObjLoader;
-
using Silk.NET.Input;
-
using System;
-using System.Numerics;
namespace Minecraft;
@@ -32,14 +25,14 @@ public class Minecraft : Game
private readonly Scene _scene;
private readonly ILogger _logger;
- private SceneNode _lightsNode;
- private SceneNode _blocksNode;
-
private Input _input;
private readonly Inventory _inventory;
private UIElement _uiElem;
+ private readonly Terrain.TerrainGenerator_New _terrain;
+ private readonly SceneNode _blocksNode;
+
///
/// Gets the main window.
///
@@ -60,6 +53,17 @@ public Minecraft(Scene scene, ISettings settings, ILogger logger)
_inventory = new Inventory();
+ _blocksNode = _scene.AddNode("Blocks");
+ _terrain = new Terrain.TerrainGenerator_New(_scene, _blocksNode,
+ [
+ new HeightMapPass(),
+ new BaseTerrainPass(),
+ new CavePass(),
+ // new OrePass(),
+ // new WaterPass(),
+ // new SurfaceDecorationPass(),
+ // new FoliagePass()
+ ]);
}
///
@@ -72,26 +76,9 @@ public override void Initialize()
_input = new Input(Camera);
_inventory.Initialize();
- _lightsNode = _scene.Root.AddChild("lights");
- _blocksNode = _scene.Root.AddChild("blocks");
-
- // var gridLayout = new GridLayout();
+ _terrain.InitializeWorld();
- // TODO: #89 Fix UI renderer
- _uiElem = new UIElement(Window.GetGL(), "uiElement");
- _scene.UIElements.Add(_uiElem);
-
- var uiElem2 = new UIElement(Window.GetGL(), "uiElement");
- uiElem2.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(0.2f, 0.2f);
- uiElem2.Transform.Position = new SharpEngine.Core.Numerics.Vector2(30, 0);
-
- // gridLayout.AddChild(_uiElem, uiElem2);
- _scene.UIElements.Add(_uiElem);
- _scene.UIElements.Add(uiElem2);
-
- // _scene.UIElements.Add(gridLayout);
-
- InitializeWorld();
+ InitializeUI();
}
catch (Exception ex)
{
@@ -99,6 +86,25 @@ public override void Initialize()
}
}
+ private void InitializeUI()
+ {
+ // var gridLayout = new GridLayout();
+
+ // TODO: #89 Fix UI renderer
+ _uiElem = new UIElement(Window.GetGL(), "uiElement");
+ _scene.UIElements.Add(_uiElem);
+
+ var uiElem2 = new UIElement(Window.GetGL(), "uiElement");
+ uiElem2.Transform.Scale = new Vector2(0.2f, 0.2f);
+ uiElem2.Transform.Position = new Vector2(30, 0);
+
+ // gridLayout.AddChild(_uiElem, uiElem2);
+ _scene.UIElements.Add(_uiElem);
+ _scene.UIElements.Add(uiElem2);
+
+ // _scene.UIElements.Add(gridLayout);
+ }
+
///
/// Handles rendering after the frame is drawn.
///
@@ -133,97 +139,20 @@ public void OnAfterRender(Frame frame)
ImGui.SliderFloat("rotation", ref rotation, 0, 360);
ImGui.Text($"UI Element width: {_uiElem.Width}");
- ImGui.SliderFloat("width", ref width, 0, 100);
+ ImGui.SliderFloat("width", ref width, 0, Window.Width);
ImGui.Text($"UI Element height: {_uiElem.Height}");
- ImGui.SliderFloat("height", ref height, 0, 100);
+ ImGui.SliderFloat("height", ref height, 0, Window.Height);
ImGui.End();
- _uiElem.Transform.Position = new SharpEngine.Core.Numerics.Vector2(x, y);
- _uiElem.Transform.Scale = new SharpEngine.Core.Numerics.Vector2(sx, sy);
+ _uiElem.Transform.Position = new Vector2(x, y);
+ _uiElem.Transform.Scale = new Vector2(sx, sy);
_uiElem.Transform.Rotation.Angle = rotation;
_uiElem.Height = height;
_uiElem.Width = width;
}
-
- private void InitializeWorld()
- {
- InitializeLights();
- InitializeChunks();
-
- // TODO: #2 Does not work yet.
- // var torus = MeshService.Instance.LoadMesh("torus", @"C:\Users\antti\Documents\Untitled2.obj");
-
- var model = ObjLoaderFactory.Load(Window.GetGL(), @"C:\Users\antti\Documents\Untitled2.obj");
- var go = new GameObject(model);
- var go2 = new GameObject(model)
- {
- Transform = new Transform(new SharpEngine.Core.Numerics.Vector3(0, 0, 30))
- };
-
- _scene.Root.AddChild(go);
- _scene.Root.AddChild(go2);
- }
-
- private void InitializeLights()
- {
- _lightsNode.AddChild(new DirectionalLight());
-
- _lightsNode.AddChild(
- new PointLight(new SharpEngine.Core.Numerics.Vector3(0.7f, 0.2f, 2.0f), 0),
- new PointLight(new SharpEngine.Core.Numerics.Vector3(2.3f, -3.3f, -4.0f), 1),
- new PointLight(new SharpEngine.Core.Numerics.Vector3(-4.0f, 2.0f, -12.0f), 2),
- new PointLight(new SharpEngine.Core.Numerics.Vector3(0.0f, 0.0f, -3.0f), 3)
- );
-
- _lightsNode.AddChild(new SpotLight()
- {
- Ambient = new SharpEngine.Core.Numerics.Vector3(0.0f, 0.0f, 0.0f),
- Diffuse = new SharpEngine.Core.Numerics.Vector3(1.0f, 1.0f, 1.0f),
- Specular = new SharpEngine.Core.Numerics.Vector3(1.0f, 1.0f, 1.0f),
- });
- }
-
- private void InitializeChunks()
- {
- // TODO: #88 Generate chunks when player moves
-
- // TODO: #87 Generate chunks using 3d Perlin noise
-
- const int chunkSize = 16;
- const int numChunks = 1;
- // const int numChunks = 3;
-
- for (int i = 0; i < numChunks; i++)
- {
- var chunkPos = new Vector3(i * chunkSize, 0, 0);
- GenerateChunk(chunkSize, chunkPos);
- }
- }
-
- private void GenerateChunk(int chunkSize, Vector3 chunkPos)
- {
- for (int x = 0; x < chunkSize; x++)
- {
- for (int z = 0; z < chunkSize; z++)
- {
- var blockPos = chunkPos + new Vector3(x, 0, z);
-
- var dirt = new Dirt(blockPos, $"Dirt ({x}{z})");
- _blocksNode.AddChild(dirt);
-
- for (int y = 1; y < chunkSize; y++)
- {
- blockPos.Y = -y;
- var stone = new Stone(blockPos, $"Dirt ({x}{z}.{y})");
- _blocksNode.AddChild(stone);
- }
- }
- }
- }
-
///
public override void Update(double deltaTime, IInputContext input)
=> _input.HandleKeyboard(input.Keyboards[0], (float)deltaTime);
@@ -232,7 +161,7 @@ public override void Update(double deltaTime, IInputContext input)
///
public override void HandleKeyboard(IKeyboard input, double deltaTime)
{
- for (int i = 0; i <= 9; i++)
+ for (int i = 1; i <= 9; i++)
{
if (input.IsKeyPressed(Key.Number0 + i))
{
@@ -262,13 +191,13 @@ public override void HandleMouseDown(IMouse mouse, MouseButton button)
{
if (button == MouseButton.Right)
{
- if (_inventory.SelectedSlot.Items.Type != BlockType.None && _inventory.SelectedSlot.Items.Amount > 0)
+ if (_inventory.SelectedSlot.Items.Type != BlockId.Air && _inventory.SelectedSlot.Items.Amount > 0)
{
PlaceBlock();
_inventory.SelectedSlot.Items.Amount -= 1;
if (_inventory.SelectedSlot.Items.Amount < 0)
- _inventory.SelectedSlot.Items.Type = BlockType.None;
+ _inventory.SelectedSlot.Items.Type = BlockId.Air;
}
else
{
@@ -278,26 +207,26 @@ public override void HandleMouseDown(IMouse mouse, MouseButton button)
if (button == MouseButton.Left)
{
- var destroyedBlockType = DestroyBlock();
- if (destroyedBlockType != BlockType.None)
+ var destroyedBlockId = DestroyBlock();
+ if (destroyedBlockId != BlockId.Air)
{
// TODO: #86 The block should be added to the slot so that 0 is the last slot instead of 9.
// TODO: #86 The first block destroyed doesn't seem to be added to the inventory.
- _logger.LogInformation("Block destroyed: {DestroyedBlockType}.", destroyedBlockType);
- _inventory.AddToolbarItem(destroyedBlockType);
+ _logger.LogInformation("Block destroyed: {DestroyedBlockId}.", destroyedBlockId);
+ _inventory.AddToolbarItem(destroyedBlockId);
}
}
}
- private BlockType DestroyBlock()
+ private BlockId DestroyBlock()
{
- if (!Camera.IsInView(_scene, out GameObject? intersectingObject, out Vector3 _, allowedTypes: typeof(BlockBase)))
- return BlockType.None;
+ if (!Camera.IsInView(_scene, out GameObject? intersectingObject, out _, allowedTypes: typeof(BlockBase)))
+ return BlockId.Air;
var block = (BlockBase)intersectingObject!;
_blocksNode.RemoveChild(intersectingObject!);
- return block.BlockType;
+ return block.BlockId;
}
private void PlaceBlock()
@@ -318,7 +247,7 @@ private void PlaceBlock()
private static Vector3 GetNewBlockPosition(Vector3 hitPosition, GameObject intersectingObject)
{
Vector3 normal = Ray.GetClosestFaceNormal(hitPosition, intersectingObject);
- return (System.Numerics.Vector3)intersectingObject.Transform.Position + (normal * (System.Numerics.Vector3)intersectingObject.Transform.Scale);
+ return intersectingObject.Transform.Position + (normal * intersectingObject.Transform.Scale);
}
///
diff --git a/Examples/MinecraftClone/Block/BlockBase.cs b/Examples/MinecraftClone/Terrain/Block/BlockBase.cs
similarity index 93%
rename from Examples/MinecraftClone/Block/BlockBase.cs
rename to Examples/MinecraftClone/Terrain/Block/BlockBase.cs
index e10a295..7461f0e 100644
--- a/Examples/MinecraftClone/Block/BlockBase.cs
+++ b/Examples/MinecraftClone/Terrain/Block/BlockBase.cs
@@ -1,9 +1,8 @@
using SharpEngine.Core.Entities;
+using SharpEngine.Core.Numerics;
using SharpEngine.Core.Primitives;
-using System.Numerics;
-
-namespace Minecraft.Block;
+namespace Minecraft.Terrain.Block;
///
/// Represents a block in the game.
@@ -13,10 +12,10 @@ public class BlockBase : GameObject
///
/// Gets the block type.
///
- public virtual BlockType BlockType { get; }
+ public virtual BlockId BlockId { get; }
/// Gets a value indicating whether the block is solid.
- public virtual bool IsSolid { get; }
+ public virtual bool IsSolid { get; } = true;
/// Gets or sets the size of the block in the inventory.
public int SizeInInventory { get; set; } = 1;
diff --git a/Examples/MinecraftClone/BlockFactory.cs b/Examples/MinecraftClone/Terrain/Block/BlockFactory.cs
similarity index 54%
rename from Examples/MinecraftClone/BlockFactory.cs
rename to Examples/MinecraftClone/Terrain/Block/BlockFactory.cs
index 7991855..44d1deb 100644
--- a/Examples/MinecraftClone/BlockFactory.cs
+++ b/Examples/MinecraftClone/Terrain/Block/BlockFactory.cs
@@ -1,9 +1,7 @@
-using Minecraft.Block;
-
+using SharpEngine.Core.Numerics;
using System;
-using System.Numerics;
-namespace Minecraft
+namespace Minecraft.Terrain.Block
{
///
/// Used to create a block objects.
@@ -18,27 +16,13 @@ public static class BlockFactory
/// The name of the block to be created.
/// The newly created block.
///
- public static BlockBase CreateBlock(BlockType type, Vector3 position, string name)
+ public static BlockBase CreateBlock(BlockId type, Vector3 position, string name)
=> type switch
{
- BlockType.Dirt => new Dirt(position, name),
- BlockType.Stone => new Stone(position, name),
+ BlockId.Dirt => new Dirt(position, name),
+ BlockId.Stone => new Stone(position, name),
+ BlockId.Grass => new Grass(position, name),
_ => throw new ArgumentException("Invalid block type", nameof(type)),
};
}
-
- ///
- /// Represents the type of block.
- ///
- public enum BlockType
- {
- /// Represents no block type.
- None,
-
- /// Represents a dirt block.
- Dirt,
-
- /// Represents a stone block.
- Stone
- }
}
diff --git a/Examples/MinecraftClone/Terrain/Block/BlockId.cs b/Examples/MinecraftClone/Terrain/Block/BlockId.cs
new file mode 100644
index 0000000..34b428a
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/BlockId.cs
@@ -0,0 +1,26 @@
+namespace Minecraft.Terrain.Block;
+
+///
+/// Represents the type of block.
+///
+public enum BlockId : ushort
+{
+ Air = 0,
+
+ Grass = 1,
+ Dirt = 2,
+ Stone = 3,
+
+ Water = 10,
+
+ CoalOre = 20,
+ IronOre = 21,
+ CopperOre = 22,
+
+ Wood = 50,
+
+ TallGrass = 100,
+ Flower = 101,
+ Log = 102,
+ Leaves = 103
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Block/Dirt.cs b/Examples/MinecraftClone/Terrain/Block/Dirt.cs
similarity index 83%
rename from Examples/MinecraftClone/Block/Dirt.cs
rename to Examples/MinecraftClone/Terrain/Block/Dirt.cs
index 183d5d8..d76622e 100644
--- a/Examples/MinecraftClone/Block/Dirt.cs
+++ b/Examples/MinecraftClone/Terrain/Block/Dirt.cs
@@ -1,8 +1,8 @@
using SharpEngine.Core._Resources;
using SharpEngine.Core.Extensions;
-using System.Numerics;
+using SharpEngine.Core.Numerics;
-namespace Minecraft.Block;
+namespace Minecraft.Terrain.Block;
///
/// Represents a dirt block.
@@ -20,8 +20,5 @@ public Dirt(Vector3 position, string name) : base(position, name, PathExtensions
Default.FragmentShader) { }
///
- public override BlockType BlockType => BlockType.Dirt;
-
- ///
- public override bool IsSolid => true;
+ public override BlockId BlockId => BlockId.Dirt;
}
diff --git a/Examples/MinecraftClone/Terrain/Block/Grass.cs b/Examples/MinecraftClone/Terrain/Block/Grass.cs
new file mode 100644
index 0000000..8fb3faa
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/Grass.cs
@@ -0,0 +1,21 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Numerics;
+
+namespace Minecraft.Terrain.Block;
+
+internal class Grass : BlockBase
+{
+ ///
+ /// Initializes a new block.
+ ///
+ /// The position where the block should be initialized.
+ /// The name of the block to initialize.
+ public Grass(Vector3 position, string name) : base(position, name, PathExtensions.GetAssemblyPath("Resources\\grass.jpg"),
+ PathExtensions.GetAssemblyPath("Resources\\container2_specular.png"),
+ Default.VertexShader,
+ Default.FragmentShader) { }
+
+ ///
+ public override BlockId BlockId => BlockId.Grass;
+}
diff --git a/Examples/MinecraftClone/Terrain/Block/Iron.cs b/Examples/MinecraftClone/Terrain/Block/Iron.cs
new file mode 100644
index 0000000..431bf46
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/Iron.cs
@@ -0,0 +1,17 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Numerics;
+
+namespace Minecraft.Terrain.Block;
+
+internal class Iron : BlockBase
+{
+ public Iron(Vector3 position, string name)
+ : base(position, name, DiffuseMap(), SpecularMap(), Default.VertexShader, Default.FragmentShader) { }
+
+ private static string DiffuseMap() => PathExtensions.GetAssemblyPath("Resources\\container2.png");
+ private static string SpecularMap() => PathExtensions.GetAssemblyPath("Resources\\container2_specular.png");
+
+ ///
+ public override BlockId BlockId => BlockId.IronOre;
+}
diff --git a/Examples/MinecraftClone/Terrain/Block/Leaf.cs b/Examples/MinecraftClone/Terrain/Block/Leaf.cs
new file mode 100644
index 0000000..b047da2
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/Leaf.cs
@@ -0,0 +1,17 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Numerics;
+
+namespace Minecraft.Terrain.Block;
+
+internal class Leaf : BlockBase
+{
+ public Leaf(Vector3 position, string name)
+ : base(position, name, DiffuseMap(), SpecularMap(), Default.VertexShader, Default.FragmentShader) { }
+
+ private static string DiffuseMap() => PathExtensions.GetAssemblyPath("Resources\\container2.png");
+ private static string SpecularMap() => PathExtensions.GetAssemblyPath("Resources\\container2_specular.png");
+
+ ///
+ public override BlockId BlockId => BlockId.Leaves;
+}
diff --git a/Examples/MinecraftClone/Block/Stone.cs b/Examples/MinecraftClone/Terrain/Block/Stone.cs
similarity index 50%
rename from Examples/MinecraftClone/Block/Stone.cs
rename to Examples/MinecraftClone/Terrain/Block/Stone.cs
index 13352d3..dcd3b3f 100644
--- a/Examples/MinecraftClone/Block/Stone.cs
+++ b/Examples/MinecraftClone/Terrain/Block/Stone.cs
@@ -1,8 +1,8 @@
using SharpEngine.Core._Resources;
using SharpEngine.Core.Extensions;
-using System.Numerics;
+using SharpEngine.Core.Numerics;
-namespace Minecraft.Block;
+namespace Minecraft.Terrain.Block;
internal class Stone : BlockBase
{
@@ -14,13 +14,14 @@ internal class Stone : BlockBase
///
/// The position where the block is created.
/// The name of the object in the scene.
- public Stone(Vector3 position, string name) : base(position, name, PathExtensions.GetAssemblyPath("Resources\\container2.png"),
- PathExtensions.GetAssemblyPath("Resources\\container2_specular.png"),
- Default.VertexShader,
- Default.FragmentShader) { }
+ public Stone(Vector3 position, string name)
+ : base(position, name, DiffuseMap(), SpecularMap(), Default.VertexShader, Default.FragmentShader) { }
+
+ private static string DiffuseMap() => PathExtensions.GetAssemblyPath("Resources\\container2.png");
+ private static string SpecularMap() => PathExtensions.GetAssemblyPath("Resources\\container2_specular.png");
///
- public override BlockType BlockType => BlockType.Stone;
+ public override BlockId BlockId => BlockId.Stone;
///
public override bool IsSolid => true;
diff --git a/Examples/MinecraftClone/Terrain/Block/Wood.cs b/Examples/MinecraftClone/Terrain/Block/Wood.cs
new file mode 100644
index 0000000..f58ff58
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Block/Wood.cs
@@ -0,0 +1,17 @@
+using SharpEngine.Core._Resources;
+using SharpEngine.Core.Extensions;
+using SharpEngine.Core.Numerics;
+
+namespace Minecraft.Terrain.Block;
+
+internal class Wood : BlockBase
+{
+ public Wood(Vector3 position, string name)
+ : base(position, name, DiffuseMap(), SpecularMap(), Default.VertexShader, Default.FragmentShader) { }
+
+ private static string DiffuseMap() => PathExtensions.GetAssemblyPath("Resources\\container2.png");
+ private static string SpecularMap() => PathExtensions.GetAssemblyPath("Resources\\container2_specular.png");
+
+ ///
+ public override BlockId BlockId => BlockId.Wood;
+}
diff --git a/Examples/MinecraftClone/Terrain/ChunkData.cs b/Examples/MinecraftClone/Terrain/ChunkData.cs
new file mode 100644
index 0000000..63d27c6
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/ChunkData.cs
@@ -0,0 +1,57 @@
+using Minecraft.Terrain.Block;
+
+namespace Minecraft.Terrain;
+
+public sealed class ChunkData
+{
+ private readonly BlockId[,,] _blocks;
+ private readonly int[,] _heightMap;
+
+ public ChunkData(int size, int height)
+ {
+ Size = size;
+ Height = height;
+
+ _blocks = new BlockId[size, height, size];
+ _heightMap = new int[size, size];
+ }
+
+ public int Size { get; }
+
+ public int Height { get; }
+
+ public BlockId GetBlock(int x, int y, int z)
+ {
+ return _blocks[x, y, z];
+ }
+
+ public void SetBlock(int x, int y, int z, BlockId block)
+ {
+ _blocks[x, y, z] = block;
+ }
+
+ public int GetHeight(int x, int z)
+ {
+ return _heightMap[x, z];
+ }
+
+ public void SetHeight(int x, int z, int height)
+ {
+ _heightMap[x, z] = height;
+ }
+
+ public bool IsInside(int x, int y, int z)
+ {
+ return x >= 0 &&
+ y >= 0 &&
+ z >= 0 &&
+ x < Size &&
+ y < Height &&
+ z < Size;
+ }
+
+ public bool IsAir(int x, int y, int z)
+ {
+ return GetBlock(x, y, z) == BlockId.Air;
+ }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/ChunkGenerationContext.cs b/Examples/MinecraftClone/Terrain/ChunkGenerationContext.cs
new file mode 100644
index 0000000..6f19da9
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/ChunkGenerationContext.cs
@@ -0,0 +1,27 @@
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Numerics.Noise;
+
+namespace Minecraft.Terrain;
+
+public sealed class ChunkGenerationContext
+{
+ public required int Seed { get; init; }
+
+ public required Vector3 ChunkPosition { get; init; }
+
+ public required int ChunkSize { get; init; }
+
+ public required int MinY { get; init; }
+
+ public required int MaxY { get; init; }
+
+ public required int WaterLevel { get; init; }
+
+ public required INoiseGenerator TerrainNoise { get; init; }
+
+ public required INoiseGenerator CaveNoise { get; init; }
+
+ public required INoiseGenerator OreNoise { get; init; }
+
+ public required INoiseGenerator FoliageNoise { get; init; }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/IChunkGenerationLayer.cs b/Examples/MinecraftClone/Terrain/IChunkGenerationLayer.cs
new file mode 100644
index 0000000..5980bbd
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/IChunkGenerationLayer.cs
@@ -0,0 +1,6 @@
+namespace Minecraft.Terrain;
+
+public interface IChunkGenerationLayer
+{
+ void Generate(ChunkGenerationContext context, ChunkData chunk);
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/Layers/BaseTerrainPass.cs b/Examples/MinecraftClone/Terrain/Layers/BaseTerrainPass.cs
new file mode 100644
index 0000000..4ad29f1
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Layers/BaseTerrainPass.cs
@@ -0,0 +1,59 @@
+using Minecraft.Terrain;
+using Minecraft.Terrain.Block;
+using System;
+
+namespace Minecraft.Terrain.Layers;
+
+public sealed class BaseTerrainPass : IChunkGenerationLayer
+{
+ private const int DirtDepth = 3;
+
+ public void Generate(ChunkGenerationContext context, ChunkData chunk)
+ {
+ for (int x = 0; x < chunk.Size; x++)
+ {
+ for (int z = 0; z < chunk.Size; z++)
+ {
+ int surfaceWorldY = chunk.GetHeight(x, z);
+ int surfaceLocalY = WorldYToLocalY(context, surfaceWorldY);
+
+ GenerateColumn(chunk, x, z, surfaceLocalY);
+ }
+ }
+ }
+
+ private static void GenerateColumn(ChunkData chunk, int x, int z, int surfaceLocalY)
+ {
+ for (int localY = 0; localY < chunk.Height; localY++)
+ {
+ BlockId block = GetBlockForLayer(localY, surfaceLocalY);
+
+ chunk.SetBlock(x, localY, z, block);
+ }
+ }
+
+ private static BlockId GetBlockForLayer(int localY, int surfaceLocalY)
+ {
+ if (localY > surfaceLocalY)
+ return BlockId.Air;
+
+ if (localY == 0)
+ return BlockId.Stone;
+
+ int depthFromSurface = surfaceLocalY - localY;
+
+ return depthFromSurface switch
+ {
+ 0 => BlockId.Grass,
+
+ > 0 and <= DirtDepth => BlockId.Dirt,
+
+ _ => BlockId.Stone
+ };
+ }
+
+ private static int WorldYToLocalY(ChunkGenerationContext context, int worldY)
+ {
+ return worldY - context.MinY;
+ }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/Layers/CavePass.cs b/Examples/MinecraftClone/Terrain/Layers/CavePass.cs
new file mode 100644
index 0000000..6239107
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Layers/CavePass.cs
@@ -0,0 +1,54 @@
+using Minecraft.Terrain.Block;
+
+namespace Minecraft.Terrain.Layers;
+
+public sealed class CavePass : IChunkGenerationLayer
+{
+ private const float CaveThreshold = 0.68f;
+
+ public void Generate(ChunkGenerationContext context, ChunkData chunk)
+ {
+ for (int x = 0; x < chunk.Size; x++)
+ {
+ for (int y = 0; y < chunk.Height; y++)
+ {
+ for (int z = 0; z < chunk.Size; z++)
+ {
+ TryCarveBlock(context, chunk, x, y, z);
+ }
+ }
+ }
+ }
+
+ private static void TryCarveBlock(
+ ChunkGenerationContext context,
+ ChunkData chunk,
+ int localX,
+ int localY,
+ int localZ)
+ {
+ if (localY == 0)
+ return;
+
+ BlockId block = chunk.GetBlock(localX, localY, localZ);
+
+ if (block != BlockId.Stone)
+ return;
+
+ float worldX = context.ChunkPosition.X + localX;
+ float worldY = LocalYToWorldY(context, localY);
+ float worldZ = context.ChunkPosition.Z + localZ;
+
+ float caveNoise = context.CaveNoise.Sample3D(worldX, worldY, worldZ);
+
+ if (caveNoise <= CaveThreshold)
+ return;
+
+ chunk.SetBlock(localX, localY, localZ, BlockId.Air);
+ }
+
+ private static int LocalYToWorldY(ChunkGenerationContext context, int localY)
+ {
+ return context.MinY + localY;
+ }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/Layers/HeightMapPass.cs b/Examples/MinecraftClone/Terrain/Layers/HeightMapPass.cs
new file mode 100644
index 0000000..cffdedc
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/Layers/HeightMapPass.cs
@@ -0,0 +1,26 @@
+namespace Minecraft.Terrain.Layers;
+
+internal class HeightMapPass : IChunkGenerationLayer
+{
+ public void Generate(ChunkGenerationContext context, ChunkData chunk)
+ {
+ for (int x = 0; x < chunk.Size; x++)
+ {
+ for (int z = 0; z < chunk.Size; z++)
+ {
+ float worldX = context.ChunkPosition.X + x;
+ float worldZ = context.ChunkPosition.Z + z;
+
+ float noise = context.TerrainNoise.Sample2D(worldX, worldZ);
+
+ int minSurfaceY = 0;
+ int maxSurfaceY = 16;
+
+ int surfaceY = minSurfaceY +
+ (int)(noise * (maxSurfaceY - minSurfaceY));
+
+ chunk.SetHeight(x, z, surfaceY);
+ }
+ }
+ }
+}
diff --git a/Examples/MinecraftClone/Terrain/TerrainGenerator.cs b/Examples/MinecraftClone/Terrain/TerrainGenerator.cs
new file mode 100644
index 0000000..b71e3e9
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/TerrainGenerator.cs
@@ -0,0 +1,158 @@
+using Minecraft.Terrain.Block;
+using SharpEngine.Core.Entities;
+using SharpEngine.Core.Entities.Lights;
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Numerics.Noise;
+using SharpEngine.Core.Scenes;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Minecraft.Terrain;
+
+public sealed class TerrainGenerator_New
+{
+ private readonly INoiseGenerator _terrainNoise = new PerlinNoiseGenerator(seed: 1234)
+ {
+ Scale = 32f,
+ Octaves = 5,
+ Persistence = 0.45f,
+ Lacunarity = 2f
+ };
+
+ private readonly INoiseGenerator _caveNoise = new PerlinNoiseGenerator(seed: 5678)
+ {
+ Scale = 18f,
+ Octaves = 3,
+ Persistence = 0.5f,
+ Lacunarity = 2f
+ };
+
+ // TODO: Tweak
+ private readonly INoiseGenerator _oreNoise = new PerlinNoiseGenerator(seed: 1234)
+ {
+ Scale = 32f,
+ Octaves = 5,
+ Persistence = 0.45f,
+ Lacunarity = 2f
+ };
+
+ // TODO: Tweak
+ private readonly INoiseGenerator _foliageNoise = new PerlinNoiseGenerator(seed: 5678)
+ {
+ Scale = 18f,
+ Octaves = 3,
+ Persistence = 0.5f,
+ Lacunarity = 2f
+ };
+
+ private readonly IReadOnlyList _layers;
+
+ private readonly Scene _scene;
+
+ private SceneNode _lightsNode;
+ private SceneNode _blocksNode;
+
+ internal TerrainGenerator_New(Scene scene, SceneNode blocksNode, IEnumerable? layers = null)
+ {
+ _scene = scene;
+ _lightsNode = _scene.AddNode("Lights");
+ _blocksNode = blocksNode;
+ _layers = layers?.ToArray() ?? Array.Empty();
+ }
+
+ internal void InitializeWorld()
+ {
+ InitializeLights();
+ InitializeChunks();
+ }
+
+ private void InitializeLights()
+ {
+ _lightsNode.AddChild(new DirectionalLight());
+
+ _lightsNode.AddChild(
+ new PointLight(new Vector3(0.7f, 0.2f, 2.0f), 0),
+ new PointLight(new Vector3(2.3f, -3.3f, -4.0f), 1),
+ new PointLight(new Vector3(-4.0f, 2.0f, -12.0f), 2),
+ new PointLight(new Vector3(0.0f, 0.0f, -3.0f), 3)
+ );
+
+ _lightsNode.AddChild(new SpotLight()
+ {
+ Ambient = new Vector3(0.0f, 0.0f, 0.0f),
+ Diffuse = new Vector3(1.0f, 1.0f, 1.0f),
+ Specular = new Vector3(1.0f, 1.0f, 1.0f),
+ });
+ }
+
+ private void InitializeChunks()
+ {
+ const int chunkSize = 16;
+ const int numChunks = 5;
+
+ for (int chunkZ = 0; chunkZ < numChunks; chunkZ++)
+ {
+ for (int chunkX = 0; chunkX < numChunks; chunkX++)
+ {
+ var chunkPos = new Vector3(chunkX * chunkSize, 0, chunkZ * chunkSize);
+
+ var context = new ChunkGenerationContext
+ {
+ Seed = 1234,
+ ChunkPosition = chunkPos,
+ ChunkSize = chunkSize,
+ MinY = -16,
+ MaxY = 16,
+ WaterLevel = 0,
+
+ TerrainNoise = _terrainNoise,
+ CaveNoise = _caveNoise,
+ OreNoise = _oreNoise,
+ FoliageNoise = _foliageNoise
+ };
+
+ ChunkData chunk = GenerateChunk(context);
+
+ CreateBlocksFromChunkData(chunk, chunkPos);
+ }
+ }
+ }
+
+ public ChunkData GenerateChunk(ChunkGenerationContext context)
+ {
+ int height = context.MaxY - context.MinY + 1;
+
+ var chunk = new ChunkData(context.ChunkSize, height);
+
+ foreach (IChunkGenerationLayer layer in _layers)
+ {
+ layer.Generate(context, chunk);
+ }
+
+ return chunk;
+ }
+
+ private void CreateBlocksFromChunkData(ChunkData chunk, Vector3 chunkPos)
+ {
+ for (int x = 0; x < chunk.Size; x++)
+ {
+ for (int y = 0; y < chunk.Height; y++)
+ {
+ for (int z = 0; z < chunk.Size; z++)
+ {
+ BlockId block = chunk.GetBlock(x, y, z);
+
+ if (block == BlockId.Air)
+ continue;
+
+ var blockPos = chunkPos + new Vector3(x, y, z);
+
+ var blockObject = BlockFactory.CreateBlock(block, blockPos, $"Block_{blockPos.X}_{blockPos.Y}_{blockPos.Z}");
+
+ _blocksNode.AddChild(blockObject);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Examples/MinecraftClone/Terrain/TerrainGenerator_Old.cs b/Examples/MinecraftClone/Terrain/TerrainGenerator_Old.cs
new file mode 100644
index 0000000..84ee4f0
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/TerrainGenerator_Old.cs
@@ -0,0 +1,145 @@
+using Minecraft.Terrain.Block;
+using SharpEngine.Core.Entities.Lights;
+using SharpEngine.Core.Numerics;
+using SharpEngine.Core.Numerics.Noise;
+using SharpEngine.Core.Scenes;
+
+namespace Minecraft.Terrain;
+
+internal class TerrainGenerator_Old
+{
+ private readonly INoiseGenerator _terrainNoise = new PerlinNoiseGenerator(seed: 1234)
+ {
+ Scale = 32f,
+ Octaves = 5,
+ Persistence = 0.45f,
+ Lacunarity = 2f
+ };
+
+ private readonly INoiseGenerator _caveNoise = new PerlinNoiseGenerator(seed: 5678)
+ {
+ Scale = 18f,
+ Octaves = 3,
+ Persistence = 0.5f,
+ Lacunarity = 2f
+ };
+
+ private readonly Scene _scene;
+
+ private SceneNode _lightsNode;
+ private SceneNode _blocksNode;
+
+ internal TerrainGenerator_Old(Scene scene, SceneNode blocksNode)
+ {
+ _scene = scene;
+ _lightsNode = _scene.AddNode("Lights");
+ _blocksNode = blocksNode;
+ }
+
+ internal void InitializeWorld()
+ {
+ InitializeLights();
+ InitializeChunks();
+ }
+
+ private void InitializeLights()
+ {
+ _lightsNode.AddChild(new DirectionalLight());
+
+ _lightsNode.AddChild(
+ new PointLight(new Vector3(0.7f, 0.2f, 2.0f), 0),
+ new PointLight(new Vector3(2.3f, -3.3f, -4.0f), 1),
+ new PointLight(new Vector3(-4.0f, 2.0f, -12.0f), 2),
+ new PointLight(new Vector3(0.0f, 0.0f, -3.0f), 3)
+ );
+
+ _lightsNode.AddChild(new SpotLight()
+ {
+ Ambient = new Vector3(0.0f, 0.0f, 0.0f),
+ Diffuse = new Vector3(1.0f, 1.0f, 1.0f),
+ Specular = new Vector3(1.0f, 1.0f, 1.0f),
+ });
+ }
+
+ private void InitializeChunks()
+ {
+ // TODO: #88 Generate chunks when player moves
+
+ // TODO: #87 Generate chunks using 3d Perlin noise
+
+ const int chunkSize = 16;
+ const int numChunks = 1;
+ // const int numChunks = 3;
+
+ for (int i = 0; i < numChunks; i++)
+ {
+ var chunkPos = new Vector3(i * chunkSize, 0, 0);
+ GenerateChunk(chunkSize, chunkPos);
+ }
+ }
+
+ private void GenerateChunk(int chunkSize, Vector3 chunkPos)
+ {
+ const int minTerrainHeight = 4;
+ const int maxTerrainHeight = 16;
+
+ const int dirtDepth = 3;
+
+ const int lowestY = -16;
+
+ const float caveThreshold = 0.68f;
+
+ for (int x = 0; x < chunkSize; x++)
+ {
+ for (int z = 0; z < chunkSize; z++)
+ {
+ float worldX = chunkPos.X + x;
+ float worldZ = chunkPos.Z + z;
+
+ float terrainNoise = _terrainNoise.Sample2D(worldX, worldZ);
+
+ int columnHeight = minTerrainHeight +
+ (int)(terrainNoise * (maxTerrainHeight - minTerrainHeight));
+
+ int surfaceY = (int)chunkPos.Y + columnHeight - 1;
+ int bottomY = (int)chunkPos.Y + lowestY;
+
+ for (int worldY = surfaceY; worldY >= bottomY; worldY--)
+ {
+ var blockPos = new Vector3(worldX, worldY, worldZ);
+
+ int depthFromSurface = surfaceY - worldY;
+
+ if (depthFromSurface == 0)
+ {
+ var grass = new Grass(blockPos, $"Grass ({worldX}, {worldY}, {worldZ})");
+ _blocksNode.AddChild(grass);
+
+ continue;
+ }
+
+ if (depthFromSurface <= dirtDepth)
+ {
+ var dirt = new Dirt(blockPos, $"Dirt ({worldX}, {worldY}, {worldZ})");
+ _blocksNode.AddChild(dirt);
+
+ continue;
+ }
+
+ bool isLowestLevel = worldY == bottomY;
+
+ if (!isLowestLevel)
+ {
+ float caveNoise = _caveNoise.Sample3D(worldX, worldY, worldZ);
+
+ if (caveNoise > caveThreshold)
+ continue;
+ }
+
+ var stone = new Stone(blockPos, $"Stone ({worldX}, {worldY}, {worldZ})");
+ _blocksNode.AddChild(stone);
+ }
+ }
+ }
+ }
+}
diff --git a/Examples/MinecraftClone/Terrain/terrain_layering_pipeline.md b/Examples/MinecraftClone/Terrain/terrain_layering_pipeline.md
new file mode 100644
index 0000000..a971e31
--- /dev/null
+++ b/Examples/MinecraftClone/Terrain/terrain_layering_pipeline.md
@@ -0,0 +1,478 @@
+# Terrain Layering and Generation Pipeline
+
+This document describes a scalable terrain layering approach for chunk-based world generation.
+
+The goal is to avoid placing every terrain rule directly inside one large block loop. Instead, terrain generation should be treated as a sequence of independent generation passes.
+
+Each pass has one responsibility and modifies lightweight chunk/block data before the final renderable blocks or meshes are created.
+
+---
+
+## Core Idea
+
+Terrain generation should be split into two main concerns:
+
+```text
+Terrain shape
+ Determines where terrain exists.
+
+Terrain materials
+ Determines what each block should be.
+```
+
+For example:
+
+```text
+Shape:
+ - Column height
+ - Cave positions
+ - Terrain slope
+ - Sea level
+ - Mountain regions
+
+Materials:
+ - Grass
+ - Dirt
+ - Stone
+ - Ore
+ - Water
+ - Foliage
+```
+
+This makes terrain easier to extend later with features such as ores, trees, flowers, water, snow, sand, or biome-specific surfaces.
+
+---
+
+## Recommended Generation Pipeline
+
+```mermaid
+flowchart TD
+ A[Chunk Coordinates] --> B[Height Map Pass]
+
+ B --> C[Base Terrain Pass]
+ C --> C1[Grass Surface Layer]
+ C --> C2[Dirt Subsurface Layer]
+ C --> C3[Stone Underground Layer]
+ C --> C4[Bottom Stone / Bedrock Layer]
+
+ C1 --> D[Chunk Block Data]
+ C2 --> D
+ C3 --> D
+ C4 --> D
+
+ D --> E[Cave Pass]
+ E --> E1[Carve Only Stone]
+ E1 --> F[Ore Pass]
+
+ F --> F1[Replace Stone With Ore]
+ F1 --> G[Water Pass]
+
+ G --> G1[Fill Empty Blocks Below Water Level]
+ G1 --> H[Surface Decoration Pass]
+
+ H --> H1[Grass Tufts]
+ H --> H2[Flowers]
+ H --> H3[Rocks]
+
+ H1 --> I[Foliage Pass]
+ H2 --> I
+ H3 --> I
+
+ I --> I1[Trees]
+ I --> I2[Bushes]
+ I --> I3[Large Plants]
+
+ I1 --> J[Finalize Chunk]
+ I2 --> J
+ I3 --> J
+
+ J --> K[Mesh / GameObject Creation]
+```
+
+---
+
+## Layer Responsibilities
+
+### 1. Height Map Pass
+
+The height map pass decides the surface height for each `x/z` column.
+
+It should answer:
+
+```text
+How tall is the terrain at this world position?
+```
+
+Typical inputs:
+
+- World seed
+- World `x/z` position
+- 2D noise
+- Biome data
+- Terrain settings
+
+Example output:
+
+```text
+Column at x=10, z=4 has surfaceY = 12
+```
+
+The height map pass should not decide whether a block is grass, dirt, stone, ore, or water. It only defines the terrain shape.
+
+---
+
+### 2. Base Terrain Pass
+
+The base terrain pass creates the main solid terrain layers.
+
+Recommended rule:
+
+```text
+Highest solid block = Grass
+3 blocks below surface = Dirt
+Everything below that = Stone
+Lowest level = Stone or Bedrock
+```
+
+Conceptually:
+
+```text
+surfaceY Grass
+surfaceY - 1 Dirt
+surfaceY - 2 Dirt
+surfaceY - 3 Dirt
+surfaceY - 4 Stone
+surfaceY - 5 Stone
+...
+bottomY Stone / Bedrock
+```
+
+This pass should create predictable base terrain before caves, ores, water, or decorations are added.
+
+---
+
+### 3. Cave Pass
+
+The cave pass removes underground blocks based on 3D noise.
+
+Important rule:
+
+```text
+Caves should only carve stone.
+```
+
+This prevents caves from cutting holes through grass and dirt surface layers unless that is intentionally desired.
+
+Recommended cave rules:
+
+```text
+If block is Stone
+And block is not the lowest layer
+And cave noise is above threshold
+Then replace block with Air
+```
+
+The lowest layer should usually remain solid to prevent chunks from having holes through the bottom.
+
+---
+
+### 4. Ore Pass
+
+The ore pass replaces some stone blocks with ore blocks.
+
+Important rule:
+
+```text
+Ores should replace stone, not grass or dirt.
+```
+
+Example rules:
+
+```text
+Coal:
+ Allowed in stone
+ Common
+ Wide Y range
+
+Iron:
+ Allowed in stone
+ Medium rarity
+ Mid/deep Y range
+
+Rare ore:
+ Allowed in stone
+ Low rarity
+ Deep Y range
+```
+
+Ore generation can use:
+
+- 3D noise
+- Random chance
+- Y-level ranges
+- Vein size rules
+- Biome-specific modifiers
+
+---
+
+### 5. Water Pass
+
+The water pass fills empty spaces below a configured water level.
+
+Recommended rule:
+
+```text
+If block is Air
+And y is below or equal to waterLevel
+Then place Water
+```
+
+This should usually happen after caves are carved, because caves below sea level may become flooded.
+
+Water generation may also consider:
+
+- Oceans
+- Lakes
+- Rivers
+- Underground water
+- Biome rules
+
+---
+
+### 6. Surface Decoration Pass
+
+The surface decoration pass places small objects on top of terrain.
+
+Examples:
+
+- Grass tufts
+- Flowers
+- Pebbles
+- Mushrooms
+- Snow layers
+- Fallen sticks
+
+Recommended rule:
+
+```text
+If block is Grass
+And space above is Air
+And decoration noise/random allows it
+Then place decoration above the grass block
+```
+
+Surface decorations should usually not replace the terrain block itself. They usually occupy the air block above the surface.
+
+---
+
+### 7. Foliage Pass
+
+The foliage pass handles larger vegetation.
+
+Examples:
+
+- Trees
+- Bushes
+- Large plants
+- Cacti
+- Reeds
+
+Tree placement usually needs more checks than small decorations:
+
+```text
+If block is Grass
+And enough vertical space exists
+And enough horizontal space exists
+And tree noise/random allows it
+Then place tree
+```
+
+Large foliage should be generated late because it may occupy several blocks.
+
+---
+
+## Recommended Pass Order
+
+A practical order is:
+
+```text
+1. Height Map Pass
+2. Base Terrain Pass
+3. Cave Pass
+4. Bottom Layer Pass
+5. Ore Pass
+6. Water Pass
+7. Surface Decoration Pass
+8. Foliage Pass
+9. Final Mesh / GameObject Creation
+```
+
+A more abstract version:
+
+```text
+Shape
+Material
+Carving
+Replacement
+Fluid
+Decoration
+Finalization
+```
+
+---
+
+## Why Use Passes?
+
+Using generation passes keeps each system focused.
+
+Instead of one huge method containing every rule:
+
+```text
+GenerateChunk()
+ handles grass
+ handles dirt
+ handles stone
+ handles caves
+ handles ores
+ handles water
+ handles trees
+ handles flowers
+```
+
+Prefer:
+
+```text
+HeightMapPass
+BaseTerrainPass
+CavePass
+OrePass
+WaterPass
+DecorationPass
+FoliagePass
+```
+
+This makes the world generator easier to extend, debug, test, and tune.
+
+---
+
+## Recommended Data Flow
+
+```mermaid
+flowchart LR
+ A[World Seed] --> B[Generation Settings]
+ B --> C[Chunk Data]
+ C --> D[Generation Passes]
+ D --> E[Block IDs]
+ E --> F[Chunk Mesh]
+ E --> G[Optional GameObjects]
+```
+
+The generator should ideally operate on lightweight block data first.
+
+For example:
+
+```text
+BlockId.Grass
+BlockId.Dirt
+BlockId.Stone
+BlockId.Air
+BlockId.Water
+BlockId.IronOre
+```
+
+After generation is complete, the engine can convert the chunk data into:
+
+- A mesh
+- Renderable chunk objects
+- Physics colliders
+- Optional `GameObject` instances for interactive blocks
+
+This is usually better than creating one full `GameObject` for every block during terrain generation.
+
+---
+
+## Mental Model: Masks
+
+A useful way to think about terrain features is through masks.
+
+A mask defines where something is allowed to exist.
+
+Examples:
+
+```text
+Grass mask:
+ Only the highest solid block in a column
+
+Dirt mask:
+ Only the first 3 blocks below grass
+
+Stone mask:
+ Everything below dirt
+
+Cave mask:
+ Only stone blocks, excluding the bottom layer
+
+Ore mask:
+ Only stone blocks within allowed Y ranges
+
+Water mask:
+ Empty space below water level
+
+Tree mask:
+ Grass surface, enough empty space above, suitable random/noise value
+```
+
+This avoids messy conditionals and makes each terrain feature easier to reason about.
+
+---
+
+## Suggested Future Interfaces
+
+Eventually, the terrain generator could be modeled around generation passes:
+
+```csharp
+public interface IChunkGenerationPass
+{
+ void Generate(ChunkGenerationContext context, ChunkData chunk);
+}
+```
+
+Example passes:
+
+```csharp
+public sealed class HeightMapPass : IChunkGenerationPass;
+public sealed class BaseTerrainPass : IChunkGenerationPass;
+public sealed class CavePass : IChunkGenerationPass;
+public sealed class OrePass : IChunkGenerationPass;
+public sealed class WaterPass : IChunkGenerationPass;
+public sealed class FoliagePass : IChunkGenerationPass;
+```
+
+The chunk generator then becomes a pipeline:
+
+```csharp
+foreach (IChunkGenerationPass pass in _passes)
+{
+ pass.Generate(context, chunk);
+}
+```
+
+---
+
+## Summary
+
+Use a terrain generation pipeline where each layer or feature is handled by a dedicated pass.
+
+Recommended structure:
+
+```text
+Height map decides shape.
+Base terrain creates grass, dirt and stone.
+Caves carve only stone.
+Ores replace stone.
+Water fills empty air below water level.
+Decorations and foliage are placed last.
+Final chunk data is converted into renderable meshes or objects.
+```
+
+This keeps the system flexible enough to support future terrain features such as foliage, water, ores, biomes, snow, rivers, and structures.
diff --git a/SharpEngine.Core/Abstractions/IGame.cs b/SharpEngine.Core/Abstractions/IGame.cs
index 0fad3ee..73662b9 100644
--- a/SharpEngine.Core/Abstractions/IGame.cs
+++ b/SharpEngine.Core/Abstractions/IGame.cs
@@ -1,7 +1,9 @@
using SharpEngine.Core.Entities.Views;
using SharpEngine.Core.Entities.Views.Settings;
using SharpEngine.Core.Enums;
+using SharpEngine.Core.Numerics;
using SharpEngine.Core.Scenes;
+
using Silk.NET.Input;
namespace SharpEngine.Core.Interfaces;
@@ -21,7 +23,7 @@ public abstract class Game
public Game()
{
Scene = new Scene();
- Camera = new(new System.Numerics.Vector3(0), new DefaultViewSettings());
+ Camera = new(new Vector3(0), new DefaultViewSettings());
}
///
@@ -33,7 +35,7 @@ public Game()
public Game(Scene scene)
{
Scene = scene;
- Camera = new(new System.Numerics.Vector3(0), new DefaultViewSettings());
+ Camera = new(new Vector3(0), new DefaultViewSettings());
}
///
diff --git a/SharpEngine.Core/Entities/Views/CameraView.cs b/SharpEngine.Core/Entities/Views/CameraView.cs
index 16ea0fb..df06475 100644
--- a/SharpEngine.Core/Entities/Views/CameraView.cs
+++ b/SharpEngine.Core/Entities/Views/CameraView.cs
@@ -1,7 +1,9 @@
using SharpEngine.Core.Entities.Views.Settings;
+using SharpEngine.Core.Numerics;
using SharpEngine.Core.Shaders;
-using System.Numerics;
+
using Plane = System.Numerics.Plane;
+using Matrix4x4 = System.Numerics.Matrix4x4;
namespace SharpEngine.Core.Entities.Views;
@@ -103,7 +105,7 @@ public float Fov
///
/// The view matrix.
public override Matrix4x4 GetViewMatrix()
- => Matrix4x4.CreateLookAt(Position, Position + _front, _up);
+ => Matrix4x4.CreateLookAt((System.Numerics.Vector3)Position, (System.Numerics.Vector3)(Position + _front), (System.Numerics.Vector3)_up);
///
/// Gets the projection matrix of the camera.
diff --git a/SharpEngine.Core/Entities/Views/View.cs b/SharpEngine.Core/Entities/Views/View.cs
index b037f4e..aac388b 100644
--- a/SharpEngine.Core/Entities/Views/View.cs
+++ b/SharpEngine.Core/Entities/Views/View.cs
@@ -1,9 +1,10 @@
using SharpEngine.Core.Entities.Views.Settings;
+using SharpEngine.Core.Numerics;
using SharpEngine.Core.Scenes;
using SharpEngine.Core.Shaders;
using System;
-using System.Numerics;
+using Matrix4x4 = System.Numerics.Matrix4x4;
namespace SharpEngine.Core.Entities.Views;
diff --git a/SharpEngine.Core/Ray.cs b/SharpEngine.Core/Ray.cs
index 942346b..1084cfd 100644
--- a/SharpEngine.Core/Ray.cs
+++ b/SharpEngine.Core/Ray.cs
@@ -1,10 +1,10 @@
using SharpEngine.Core.Entities;
+using SharpEngine.Core.Numerics;
using SharpEngine.Core.Scenes;
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Numerics;
namespace SharpEngine.Core;
diff --git a/SharpEngine.Core/Windowing/Window.cs b/SharpEngine.Core/Windowing/Window.cs
index fca661d..5b9e734 100644
--- a/SharpEngine.Core/Windowing/Window.cs
+++ b/SharpEngine.Core/Windowing/Window.cs
@@ -22,7 +22,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Numerics;
+using SharpEngine.Core.Numerics;
namespace SharpEngine.Core.Windowing;
@@ -364,7 +364,7 @@ protected void OnUpdateFrame(Frame frame)
var mouse = Input?.Mice[0];
if (mouse is not null)
{
- Camera.UpdateMousePosition(mouse.Position);
+ Camera.UpdateMousePosition((Vector2)mouse.Position);
OnHandleMouse?.Invoke(mouse);
}