Compare commits

...

12 Commits

Author SHA1 Message Date
maxwes08
66f26bc2e3 removed build error 2025-12-12 10:31:35 +01:00
maxwes08
2e72dd564e started on sprite support 2025-12-09 09:49:10 +01:00
maxwes08
35bc49c0f8 cleaned up 2025-11-04 09:12:24 +01:00
maxwes08
cad22d3c64 removed sprinting 2025-11-04 09:08:28 +01:00
maxwes08
7835ade2c1 fixed movement 2025-10-07 10:37:46 +02:00
bd2c87ddd1 player and entity physics improvements 2025-10-01 00:40:47 +02:00
maxwes08
11f76ca429 player added 2025-09-30 10:58:12 +02:00
maxwes08
38dccf0a84 better world gen 2025-09-29 10:55:37 +02:00
maxwes08
9a61dfd74c chunbk face culling 2025-09-22 10:56:55 +02:00
81ef6d8a29 fix render offset 2025-09-21 22:55:22 +02:00
maxwes08
e1bb0b3683 raycasting changes 2025-09-16 10:06:01 +02:00
maxwes08
40dd5c3a9e peak 2025-09-15 10:48:45 +02:00
18 changed files with 3578 additions and 146 deletions

41
AABB.cs Normal file
View File

@@ -0,0 +1,41 @@
using OpenTK.Mathematics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Voxel
{
public struct AABB
{
public Vector3 Min;
public Vector3 Max;
public AABB(Vector3 min, Vector3 max)
{
Min = min;
Max = max;
}
public static AABB FromCenter(Vector3 center, float width, float height, float depth)
{
Vector3 half = new Vector3(width / 2f, 0, depth / 2f);
return new AABB(center - half, center + new Vector3(half.X, height, half.Z));
}
public bool Intersects(AABB other)
{
return (Min.X <= other.Max.X && Max.X >= other.Min.X) &&
(Min.Y <= other.Max.Y && Max.Y >= other.Min.Y) &&
(Min.Z <= other.Max.Z && Max.Z >= other.Min.Z);
}
public bool Contains(Vector3 point)
{
return (point.X >= Min.X && point.X <= Max.X) &&
(point.Y >= Min.Y && point.Y <= Max.Y) &&
(point.Z >= Min.Z && point.Z <= Max.Z);
}
}
}

View File

@@ -1,7 +1,12 @@
namespace Voxel using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Voxel
{ {
public class BlockData public class BlockData
{ {
} }
} }

View File

@@ -7,7 +7,8 @@
Dirt, Dirt,
OakPlanks, OakPlanks,
Grass, Grass,
Bedrock Bedrock,
Sand
} }
public enum Orientation : byte public enum Orientation : byte
@@ -74,6 +75,10 @@
Voxel.Blocks.Bedrock, Textures.Bedrock Voxel.Blocks.Bedrock, Textures.Bedrock
)}, )},
{Voxel.Blocks.Sand, new BlockDefinition(
Voxel.Blocks.Sand, Textures.Sand
)},
{ Voxel.Blocks.Grass, new BlockDefinition( { Voxel.Blocks.Grass, new BlockDefinition(
Voxel.Blocks.Grass, Voxel.Blocks.Grass,
Voxel.Textures.GrassSide, // West Voxel.Textures.GrassSide, // West

View File

@@ -7,11 +7,16 @@ namespace Voxel
public static Vector3 Position = new Vector3(-8, 16, -8); public static Vector3 Position = new Vector3(-8, 16, -8);
public static float Pitch = -22.5f; public static float Pitch = -22.5f;
public static float Yaw = 45f; public static float Yaw = 0f;
public static float FOV = 60f; public static float FOV = 60f;
public static float TargetFOV = FOV;
public static float FOVLerpSpeed = 10f;
public static float Speed = 5f; public static float Speed = 5f;
public static float ShiftSpeed = 20f; public static float ShiftSpeed = 20f;
private static int _width;
private static int _height;
public static Matrix4 view => public static Matrix4 view =>
Matrix4.LookAt(Position, Position + Front, Vector3.UnitY); Matrix4.LookAt(Position, Position + Front, Vector3.UnitY);
@@ -21,39 +26,15 @@ namespace Voxel
{ {
get get
{ {
float yawOffset = Yaw - 90f;
Vector3 front; Vector3 front;
front.X = MathF.Cos(MathHelper.DegreesToRadians(Yaw)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch)); front.X = MathF.Cos(MathHelper.DegreesToRadians(yawOffset)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch));
front.Y = MathF.Sin(MathHelper.DegreesToRadians(Pitch)); front.Y = MathF.Sin(MathHelper.DegreesToRadians(Pitch));
front.Z = MathF.Sin(MathHelper.DegreesToRadians(Yaw)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch)); front.Z = MathF.Sin(MathHelper.DegreesToRadians(yawOffset)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch));
return front.Normalized(); return front.Normalized();
} }
} }
public static void Update(float time)
{
float moveSpeed = Speed * time;
if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.LeftShift))
{
moveSpeed = ShiftSpeed * time;
}
if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.W))
{
Position += Front * moveSpeed;
}
if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.S))
{
Position += Front * -moveSpeed;
}
if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.A))
{
Position += Vector3.Cross(Front, Vector3.UnitY).Normalized() * -moveSpeed;
}
if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.D))
{
Position += Vector3.Cross(Front, Vector3.UnitY).Normalized() * moveSpeed;
}
}
public static void UpdateMouse(Vector2 delta) public static void UpdateMouse(Vector2 delta)
{ {
float sensitivity = 0.1f; float sensitivity = 0.1f;
@@ -63,14 +44,28 @@ namespace Voxel
Pitch = MathHelper.Clamp(Pitch, -89f, 89f); Pitch = MathHelper.Clamp(Pitch, -89f, 89f);
} }
public static void UpdateProjection(int width, int height) public static void UpdateProjection()
{ {
float fov = MathHelper.DegreesToRadians(FOV); float fov = MathHelper.DegreesToRadians(FOV);
float aspectRatio = width / (float)height; float aspectRatio = _width / (float)_height;
float near = 0.1f; float near = 0.1f;
float far = 1000f; float far = 1000f;
projection = Matrix4.CreatePerspectiveFieldOfView(fov, aspectRatio, near, far); projection = Matrix4.CreatePerspectiveFieldOfView(fov, aspectRatio, near, far);
} }
public static void UpdateSize(int width, int height)
{
_width = width;
_height = height;
UpdateProjection();
}
public static void UpdateFOV(float deltaTime, float alpha)
{
float currentFOV = MathHelper.Lerp(FOV, TargetFOV, FOVLerpSpeed * deltaTime);
Camera.FOV = currentFOV;
Camera.UpdateProjection();
}
} }
} }

View File

@@ -10,6 +10,7 @@
private Dictionary<ushort, BlockData> _blockData; private Dictionary<ushort, BlockData> _blockData;
private Blocks[] _blocks; private Blocks[] _blocks;
public Dictionary<Orientation, Chunk> Neighbors = new();
public Chunk(int x, int y) public Chunk(int x, int y)
{ {
@@ -23,14 +24,17 @@
Initialize(); Initialize();
} }
public void SetBlock(int x, int y, int z, Blocks block) public void SetBlock(int x, int y, int z, Blocks block, bool updateMesh = true)
{ {
int i = GetBlockIndex(x, y, z); int i = GetBlockIndex(x, y, z);
if (i == -1) return; if (i == -1) return;
_blocks[i] = block; _blocks[i] = block;
UpdateChunkMesh();
Renderer.MarkBuffersDirty(); if (updateMesh)
{
UpdateChunkMesh();
Renderer.MarkBuffersDirty();
}
} }
public void SetBlockIndex(int i, Blocks block) public void SetBlockIndex(int i, Blocks block)
@@ -52,12 +56,20 @@
private void Initialize() private void Initialize()
{ {
for (int i = 0; i < Size * Size; i++) for (int x = 0; x < Size; x++)
_blocks[i] = Blocks.Bedrock; {
for (int i = Size * Size * 1; i < Size * Size * 15; i++) for (int z = 0; z < Size; z++)
_blocks[i] = Blocks.Stone; {
for (int i = Size * Size * 15; i < Size * Size * 16; i++) var position = GetWorldCoordinates(x, 0, z);
_blocks[i] = Blocks.Grass; int height = Worldgen.GetHeight(position.x, position.z);
for (int y = 0; y < 256; y++)
{
Blocks block = Worldgen.GetBlock(y, height);
SetBlock(x, y, z, block, false);
}
}
}
UpdateChunkMesh(); UpdateChunkMesh();
} }
@@ -71,6 +83,13 @@
return (x, y, z); return (x, y, z);
} }
public (int x, int y, int z) GetWorldCoordinates(int x, int y, int z)
{
x += (Size * X);
z += (Size * Y);
return (x, y, z);
}
private int GetBlockIndex(int x, int y, int z) private int GetBlockIndex(int x, int y, int z)
{ {
if (x < 0 || x > 15 || y < 0 || y > 255 || z < 0 || z > 15) if (x < 0 || x > 15 || y < 0 || y > 255 || z < 0 || z > 15)
@@ -89,7 +108,7 @@
( 0, 0, -1) // -Z ( 0, 0, -1) // -Z
}; };
private void UpdateChunkMesh() public void UpdateChunkMesh()
{ {
List<FaceData> faces = new List<FaceData>(Size * Size * Height / 2); List<FaceData> faces = new List<FaceData>(Size * Size * Height / 2);
@@ -129,6 +148,21 @@
int ni = GetBlockIndex(nx, ny, nz); int ni = GetBlockIndex(nx, ny, nz);
if (GetBlockIndex(nx, ny, nz) == -1) if (GetBlockIndex(nx, ny, nz) == -1)
{ {
if (Neighbors.TryGetValue((Orientation)face, out Chunk neighbor) && neighbor != null)
{
int localX = nx;
int localZ = nz;
if (nx < 0) localX = nx + Size;
if (nx >= Size) localX = nx - Size;
if (nz < 0) localZ = nz + Size;
if (nz >= Size) localZ = nz - Size;
Blocks neighborBlock = neighbor.GetBlock(localX, y, localZ);
if (neighborBlock != Blocks.Air)
continue;
}
AddFace(); AddFace();
continue; continue;
} }

449
Entity.cs Normal file
View File

@@ -0,0 +1,449 @@
using OpenTK.Mathematics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Voxel
{
public class Entity
{
public Vector3 Position;
public Vector3 Velocity;
public bool OnGround;
public float Rotation;
public float Width;
public float Height;
private float _gravity = 0.08f;
private float _terminalVelocity = -3.92f;
private float _airMultiplier = 0.91f;
private float _groundMultiplier = 0.6f;
private const float COLLISION_EPSILON = 0.01f;
protected World _world;
public Entity(Vector3 position, float width, float height, World world)
{
Position = position;
Width = width;
Height = height;
_world = world;
}
public void Tick()
{
Vector3 desiredMovement = Velocity;
Vector3 newPosition = Position;
CheckAndResolveCollisions(ref newPosition, desiredMovement);
Position = newPosition;
if (!OnGround)
{
Vector3 acceleration = new Vector3(0, -_gravity, 0);
Velocity += acceleration;
Velocity *= _airMultiplier;
if (Velocity.Y < _terminalVelocity)
{
Velocity.Y = _terminalVelocity;
}
}
else
{
Velocity = new Vector3(Velocity.X * _groundMultiplier, 0f, Velocity.Z * _groundMultiplier);
}
UpdateOnGround();
}
public void CheckAndResolveCollisions(ref Vector3 position, Vector3 movement)
{
Vector3 originalPosition = position;
// Try full movement first
AABB futureBox = GetBoundingBoxAt(originalPosition + movement);
if (!HasCollision(futureBox))
{
// No collision, apply full movement
position = originalPosition + movement;
return;
}
// Collision detected, resolve each axis separately but independently
position = originalPosition;
// Resolve Y collision (always first for ground detection)
ResolveYCollisionIndependent(ref position, movement.Y);
// Resolve X and Z collisions independently of each other
Vector3 tempPosX = new Vector3(position.X + movement.X, position.Y, position.Z);
if (!HasCollision(GetBoundingBoxAt(tempPosX)))
{
position.X = tempPosX.X;
}
else
{
ResolveXCollisionIndependent(ref position, movement.X);
}
Vector3 tempPosZ = new Vector3(position.X, position.Y, position.Z + movement.Z);
if (!HasCollision(GetBoundingBoxAt(tempPosZ)))
{
position.Z = tempPosZ.Z;
}
else
{
ResolveZCollisionIndependent(ref position, movement.Z);
}
}
private void ResolveYCollisionIndependent(ref Vector3 position, float velocityY)
{
if (velocityY == 0) return;
Vector3 testPos = new Vector3(position.X, position.Y + velocityY, position.Z);
AABB testBox = GetBoundingBoxAt(testPos);
if (HasCollision(testBox))
{
if (velocityY > 0) // Hitting ceiling
{
float ceilingY = GetCeilingHeight(testBox);
position.Y = ceilingY - (Height / 2) - COLLISION_EPSILON;
Velocity.Y = 0;
}
else // Hitting floor
{
float floorY = GetFloorHeight(testBox);
position.Y = floorY + (Height / 2) + COLLISION_EPSILON;
Velocity.Y = 0;
OnGround = true;
}
}
else
{
position.Y += velocityY;
}
}
private void ResolveXCollisionIndependent(ref Vector3 position, float velocityX)
{
if (velocityX == 0) return;
// Check if we're already inside a block at current position
AABB currentBox = GetBoundingBoxAt(new Vector3(position.X, position.Y, position.Z));
if (HasCollision(currentBox))
{
// Already inside a block, allow movement to escape
position.X += velocityX;
return;
}
float direction = Math.Sign(velocityX);
float checkDistance = Math.Abs(velocityX) + COLLISION_EPSILON;
for (float offset = 0; offset <= checkDistance; offset += 0.01f)
{
float testX = position.X + (offset * direction);
AABB testBox = GetBoundingBoxAt(new Vector3(testX, position.Y, position.Z));
if (HasCollision(testBox))
{
if (direction > 0) // Moving right
{
position.X = GetRightWallPosition(testBox) - (Width / 2) - COLLISION_EPSILON;
}
else // Moving left
{
position.X = GetLeftWallPosition(testBox) + (Width / 2) + COLLISION_EPSILON;
}
Velocity.X = 0;
return;
}
}
// No collision found, apply full movement
position.X += velocityX;
}
private void ResolveZCollisionIndependent(ref Vector3 position, float velocityZ)
{
if (velocityZ == 0) return;
// Check if we're already inside a block at current position
AABB currentBox = GetBoundingBoxAt(new Vector3(position.X, position.Y, position.Z));
if (HasCollision(currentBox))
{
// Already inside a block, allow movement to escape
position.Z += velocityZ;
return;
}
float direction = Math.Sign(velocityZ);
float checkDistance = Math.Abs(velocityZ) + COLLISION_EPSILON;
for (float offset = 0; offset <= checkDistance; offset += 0.01f)
{
float testZ = position.Z + (offset * direction);
AABB testBox = GetBoundingBoxAt(new Vector3(position.X, position.Y, testZ));
if (HasCollision(testBox))
{
if (direction > 0) // Moving forward
{
position.Z = GetFrontWallPosition(testBox) - (Width / 2) - COLLISION_EPSILON;
}
else // Moving backward
{
position.Z = GetBackWallPosition(testBox) + (Width / 2) + COLLISION_EPSILON;
}
Velocity.Z = 0;
return;
}
}
// No collision found, apply full movement
position.Z += velocityZ;
}
private float GetFloorHeight(AABB box)
{
int minX = (int)MathF.Floor(box.Min.X);
int maxX = (int)MathF.Floor(box.Max.X);
int minZ = (int)MathF.Floor(box.Min.Z);
int maxZ = (int)MathF.Floor(box.Max.Z);
int checkY = (int)MathF.Floor(box.Min.Y);
float highestFloor = float.MinValue;
for (int x = minX; x <= maxX; x++)
{
for (int z = minZ; z <= maxZ; z++)
{
Blocks block = _world.GetBlock(x, checkY, z);
if (block != Blocks.Air)
{
highestFloor = Math.Max(highestFloor, checkY + 1); // Top of the block
}
}
}
return highestFloor != float.MinValue ? highestFloor : box.Min.Y;
}
private float GetCeilingHeight(AABB box)
{
int minX = (int)MathF.Floor(box.Min.X);
int maxX = (int)MathF.Floor(box.Max.X);
int minZ = (int)MathF.Floor(box.Min.Z);
int maxZ = (int)MathF.Floor(box.Max.Z);
int checkY = (int)MathF.Floor(box.Max.Y);
float lowestCeiling = float.MaxValue;
for (int x = minX; x <= maxX; x++)
{
for (int z = minZ; z <= maxZ; z++)
{
Blocks block = _world.GetBlock(x, checkY, z);
if (block != Blocks.Air)
{
lowestCeiling = Math.Min(lowestCeiling, checkY); // Bottom of the block
}
}
}
return lowestCeiling != float.MaxValue ? lowestCeiling : box.Max.Y;
}
private float GetLeftWallPosition(AABB box)
{
int minY = (int)MathF.Floor(box.Min.Y);
int maxY = (int)MathF.Floor(box.Max.Y);
int minZ = (int)MathF.Floor(box.Min.Z);
int maxZ = (int)MathF.Floor(box.Max.Z);
int checkX = (int)MathF.Floor(box.Min.X);
float rightmostWall = float.MinValue;
for (int y = minY; y <= maxY; y++)
{
for (int z = minZ; z <= maxZ; z++)
{
Blocks block = _world.GetBlock(checkX, y, z);
if (block != Blocks.Air)
{
rightmostWall = Math.Max(rightmostWall, checkX + 1); // Right side of the block
}
}
}
return rightmostWall != float.MinValue ? rightmostWall : box.Min.X;
}
private float GetRightWallPosition(AABB box)
{
int minY = (int)MathF.Floor(box.Min.Y);
int maxY = (int)MathF.Floor(box.Max.Y);
int minZ = (int)MathF.Floor(box.Min.Z);
int maxZ = (int)MathF.Floor(box.Max.Z);
int checkX = (int)MathF.Floor(box.Max.X);
float leftmostWall = float.MaxValue;
for (int y = minY; y <= maxY; y++)
{
for (int z = minZ; z <= maxZ; z++)
{
Blocks block = _world.GetBlock(checkX, y, z);
if (block != Blocks.Air)
{
leftmostWall = Math.Min(leftmostWall, checkX); // Left side of the block
}
}
}
return leftmostWall != float.MaxValue ? leftmostWall : box.Max.X;
}
private float GetBackWallPosition(AABB box)
{
int minX = (int)MathF.Floor(box.Min.X);
int maxX = (int)MathF.Floor(box.Max.X);
int minY = (int)MathF.Floor(box.Min.Y);
int maxY = (int)MathF.Floor(box.Max.Y);
int checkZ = (int)MathF.Floor(box.Min.Z);
float frontmostWall = float.MinValue;
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
Blocks block = _world.GetBlock(x, y, checkZ);
if (block != Blocks.Air)
{
frontmostWall = Math.Max(frontmostWall, checkZ + 1); // Front side of the block
}
}
}
return frontmostWall != float.MinValue ? frontmostWall : box.Min.Z;
}
private float GetFrontWallPosition(AABB box)
{
int minX = (int)MathF.Floor(box.Min.X);
int maxX = (int)MathF.Floor(box.Max.X);
int minY = (int)MathF.Floor(box.Min.Y);
int maxY = (int)MathF.Floor(box.Max.Y);
int checkZ = (int)MathF.Floor(box.Max.Z);
float backmostWall = float.MaxValue;
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
Blocks block = _world.GetBlock(x, y, checkZ);
if (block != Blocks.Air)
{
backmostWall = Math.Min(backmostWall, checkZ); // Back side of the block
}
}
}
return backmostWall != float.MaxValue ? backmostWall : box.Max.Z;
}
public bool HasCollision(AABB box)
{
int minX = (int)MathF.Floor(box.Min.X);
int maxX = (int)MathF.Floor(box.Max.X);
int minY = (int)MathF.Floor(box.Min.Y);
int maxY = (int)MathF.Floor(box.Max.Y);
int minZ = (int)MathF.Floor(box.Min.Z);
int maxZ = (int)MathF.Floor(box.Max.Z);
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
for (int z = minZ; z <= maxZ; z++)
{
Blocks block = _world.GetBlock(x, y, z);
if (block != Blocks.Air)
{
return true;
}
}
}
}
return false;
}
public void ApplyImpulse(Vector3 force)
{
Velocity += force;
}
public AABB GetBoundingBox()
{
return GetBoundingBoxAt(Position);
}
public AABB GetBoundingBoxAt(Vector3 position)
{
float halfWidth = Width / 2;
float halfHeight = Height / 2;
Vector3 min = new Vector3(
position.X - halfWidth,
position.Y - halfHeight, // Center Y minus half height
position.Z - halfWidth
);
Vector3 max = new Vector3(
position.X + halfWidth,
position.Y + halfHeight, // Center Y plus half height
position.Z + halfWidth
);
return new AABB(min, max);
}
public void UpdateOnGround()
{
AABB box = GetBoundingBox();
float yCheck = box.Min.Y - 0.05f;
int minX = (int)MathF.Floor(box.Min.X);
int maxX = (int)MathF.Floor(box.Max.X);
int minZ = (int)MathF.Floor(box.Min.Z);
int maxZ = (int)MathF.Floor(box.Max.Z);
OnGround = false;
for (int x = minX; x <= maxX; x++)
{
for (int z = minZ; z <= maxZ; z++)
{
Blocks block = _world.GetBlock(x, (int)MathF.Floor(yCheck), z);
if (block != Blocks.Air)
{
OnGround = true;
return;
}
}
}
}
}
}

View File

@@ -1,10 +1,24 @@
using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Voxel namespace Voxel
{ {
public static class Input public static class Input
{ {
private static Dictionary<Keys, bool> _keystates = new Dictionary<Keys, bool>(); private static Dictionary<Keys, bool> _keystates = new Dictionary<Keys, bool>();
private static Dictionary<MouseButton, bool> _mouseButtonStates = new Dictionary<MouseButton, bool>();
public static Action<MouseWheelEventArgs> OnMouseWheel;
public static bool GetMouseButton(MouseButton button)
{
return _mouseButtonStates.TryGetValue(button, out bool pressed) && pressed;
}
public static void SetMouseButton(MouseButton button, bool pressed)
{
_mouseButtonStates[button] = pressed;
}
public static bool GetKey(Keys key) public static bool GetKey(Keys key)
{ {
@@ -15,5 +29,10 @@ namespace Voxel
{ {
_keystates[key] = pressed; _keystates[key] = pressed;
} }
public static void MouseWheel(MouseWheelEventArgs e)
{
OnMouseWheel.Invoke(e);
}
} }
} }

2506
Noise/FastNoiseLite.cs Normal file

File diff suppressed because it is too large Load Diff

141
Player.cs
View File

@@ -1,39 +1,162 @@
using OpenTK.Mathematics; using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.GraphicsLibraryFramework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Voxel namespace Voxel
{ {
public class Player public class Player : Entity
{ {
public Vector3 Position; public double lastClick = 0;
public readonly float mouseCooldown = 0.2f;
private World _world; private int _blockIndex = 0;
private double _tickTime = 0;
public Player(World world, Vector3 startPos) private Blocks _selectedBlock = Blocks.OakPlanks;
private Vector3 previousPosition;
public Player(Vector3 startPos, World world) : base(startPos, 0.5f, 1.8f, world)
{ {
_world = world; Input.OnMouseWheel += SwitchBlock;
}
public void PlaceBlock()
{
var (success, hit, x, y, z, normal) = _world.Raycast(Camera.Position, Camera.Front.Normalized(), 8);
if (!success) return;
x += normal.X;
y += normal.Y;
z += normal.Z;
_world.SetBlock(x, y, z, _selectedBlock);
} }
public void BreakBlock() public void BreakBlock()
{ {
var (success, hit, x, y, z) = _world.Raycast(Camera.Position, Camera.Front.Normalized(), 10); var (success, hit, x, y, z, normal) = _world.Raycast(Camera.Position, Camera.Front.Normalized(), 8);
if (!success) return; if (!success) return;
_world.SetBlock(x, y, z, Blocks.Air); _world.SetBlock(x, y, z, Blocks.Air);
} }
public void Update(float deltaTime) public new void Tick()
{ {
Camera.Update(deltaTime); previousPosition = Position;
if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.Space)) float forwards = 0;
float sidewards = 0;
if (Input.GetKey(Keys.W))
forwards = -1;
if (Input.GetKey(Keys.S))
forwards = 1;
if (Input.GetKey(Keys.A))
sidewards = -1;
if (Input.GetKey(Keys.D))
sidewards = 1;
if (Input.GetKey(Keys.Space) && OnGround)
{ {
Console.WriteLine("Jump");
Velocity = new Vector3(Velocity.X, 0.42f, Velocity.Z);
OnGround = false;
}
if (Input.GetMouseButton(MouseButton.Right) && lastClick == 0)
{
lastClick = mouseCooldown;
PlaceBlock();
}
if (Input.GetMouseButton(MouseButton.Left) && lastClick == 0)
{
lastClick = mouseCooldown;
BreakBlock(); BreakBlock();
} }
ApplyWalkSpeed(sidewards, forwards);
base.Tick();
}
public void Update(float deltaTime, float alpha)
{
Camera.Position = Vector3.Lerp(previousPosition, Position, alpha) + Vector3.UnitY * 0.62f;
Rotation = Camera.Yaw;
if (lastClick > 0)
{
lastClick -= deltaTime;
if (lastClick < 0) lastClick = 0;
}
if (!Input.GetMouseButton(MouseButton.Right) && !Input.GetMouseButton(MouseButton.Left))
{
lastClick = 0;
}
}
public void ApplyWalkSpeed(float x, float z)
{
Vector3 inputDir = new Vector3(x, 0, z);
if (inputDir.LengthSquared > 0)
inputDir = Vector3.Normalize(inputDir);
float yawRad = MathHelper.DegreesToRadians(Rotation);
float cos = MathF.Cos(yawRad);
float sin = MathF.Sin(yawRad);
Vector3 worldDir = new Vector3(
inputDir.X * cos - inputDir.Z * sin,
0,
inputDir.X * sin + inputDir.Z * cos
);
float M_t = 1.4f;
float groundMultiplier = 0.6f;
if (!OnGround)
{
Vector3 airAccel = worldDir * 0.02f * M_t;
Velocity = new Vector3(Velocity.X + airAccel.X, Velocity.Y, Velocity.Z + airAccel.Z);
}
else
{
Vector3 groundAccel = worldDir * 0.1f * M_t;
Velocity = new Vector3(Velocity.X * groundMultiplier + groundAccel.X,
Velocity.Y,
Velocity.Z * groundMultiplier + groundAccel.Z);
}
}
public void SwitchBlock(MouseWheelEventArgs e)
{
var keys = BlockDefinitions.Blocks.Keys.ToList();
bool inverted = false;
if (e.OffsetY < 0)
inverted = true;
if (inverted)
if (_blockIndex == 0)
_blockIndex = keys.Count -1;
else
_blockIndex -= 1;
else
_blockIndex += 1;
_blockIndex = _blockIndex % keys.Count;
_selectedBlock = keys[_blockIndex];
Console.WriteLine(_selectedBlock);
} }
} }
} }

View File

@@ -11,24 +11,44 @@ internal class Program
World world = new World(); World world = new World();
Window window = new Window(sizeX, sizeY, title); Window window = new Window(sizeX, sizeY, title);
Player player = new Player(world, Vector3.Zero);
window.Player = player; Console.WriteLine("Generating map...");
for (int x = 0; x < 4; x++) int worldSizeX = 8;
int worldSizeY = 8;
float maxI = worldSizeX * worldSizeY;
int i = 0;
int lastPercentage = 0;
for (int x = 0; x < worldSizeX; x++)
{ {
for (int y = 0; y < 4; y++) for (int y = 0; y < worldSizeY; y++)
{ {
i++;
Chunk chunk = new Chunk(x, y); Chunk chunk = new Chunk(x, y);
world.AddChunk(chunk); world.AddChunk(chunk);
int percentage = (int)((i / maxI) * 100);
if (percentage > lastPercentage)
{
lastPercentage = percentage;
Console.WriteLine((percentage).ToString() + "%");
}
} }
} }
// xmax ymin gör bugg Console.WriteLine("Generated " + maxI.ToString() + " chunks");
Renderer.SetWorld(world); Renderer.SetWorld(world);
Vector3 startPos = new Vector3(15, 64, 15);
Player player = new Player(startPos, world);
window.Tick += player.Tick;
window.Update += player.Update;
window.Update += Camera.UpdateFOV;
window.Run(); window.Run();
} }
} }

View File

@@ -1,4 +1,5 @@
using OpenTK.Graphics.OpenGL4; using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using static System.Runtime.InteropServices.JavaScript.JSType; using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Voxel namespace Voxel
@@ -10,7 +11,6 @@ namespace Voxel
private static bool _buffersDirty; private static bool _buffersDirty;
private static Dictionary<(int, int), int> _chunkBufferSizes = new Dictionary<(int, int), int>(); private static Dictionary<(int, int), int> _chunkBufferSizes = new Dictionary<(int, int), int>();
private static List<ChunkMesh> _chunkMeshes = new List<ChunkMesh>();
private static Shader _shader; private static Shader _shader;
private static readonly Texture _texture; private static readonly Texture _texture;
private static World? _world; private static World? _world;
@@ -32,7 +32,7 @@ namespace Voxel
GL.BindVertexArray(_vao); GL.BindVertexArray(_vao);
GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo); GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo);
GL.BufferData(BufferTarget.ShaderStorageBuffer, 1024 * 1024 * 2, IntPtr.Zero, BufferUsageHint.DynamicDraw); GL.BufferData(BufferTarget.ShaderStorageBuffer, 1024 * 1024 * 128, IntPtr.Zero, BufferUsageHint.DynamicDraw);
GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, 0, _ssbo); GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, 0, _ssbo);
} }
@@ -43,6 +43,7 @@ namespace Voxel
_shader.Use(); _shader.Use();
_shader.SetMatrix4("view", Camera.view); _shader.SetMatrix4("view", Camera.view);
_shader.SetVector3("cameraPosition", Camera.Position);
_shader.SetMatrix4("projection", Camera.projection); _shader.SetMatrix4("projection", Camera.projection);
if (_buffersDirty) if (_buffersDirty)
@@ -52,6 +53,7 @@ namespace Voxel
} }
RenderWorld(); RenderWorld();
RenderUi();
} }
private static void UpdateAllChunksBuffer() private static void UpdateAllChunksBuffer()
@@ -74,6 +76,32 @@ namespace Voxel
} }
} }
private static void RenderUi()
{
GL.Disable(EnableCap.DepthTest);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
//_uiShader.Use();
//Matrix4 projection = Matrix4.CreateOrthographicOffCenter(
// 0, screenWidth, screenHeight, 0, -1, 1);
//_uiShader.SetMatrix4("projection", projection);
// Bind UI texture atlas
//_uiTexture.Bind();
// Draw all UI sprites (batch by texture for efficiency)
//foreach (var sprite in _uiSprites)
//{
//sprite.Draw();
//}
// Restore 3D settings
GL.Disable(EnableCap.Blend);
GL.Enable(EnableCap.DepthTest);
}
private static void RenderWorld() private static void RenderWorld()
{ {
if (_world == null) return; if (_world == null) return;

View File

@@ -67,6 +67,17 @@ namespace Voxel
GL.UniformMatrix4(location, false, ref matrix); GL.UniformMatrix4(location, false, ref matrix);
} }
public void SetVector3(string name, Vector3 vector3)
{
int location = GL.GetUniformLocation(_handle, name);
if (location == -1)
{
Console.WriteLine($"Uniform '{name}' not found in shader.");
return;
}
GL.Uniform3(location, ref vector3);
}
public void SetInt(string name, int value) public void SetInt(string name, int value)
{ {
int location = GL.GetUniformLocation(_handle, name); int location = GL.GetUniformLocation(_handle, name);

View File

@@ -2,12 +2,24 @@
out vec4 FragColor; out vec4 FragColor;
in vec2 fragUV; in vec2 fragUV;
in vec3 fragPos;
in float lighting; in float lighting;
uniform sampler2D uTexture; uniform sampler2D uTexture;
uniform vec3 cameraPosition;
void main() void main()
{ {
vec4 texColor = texture(uTexture, fragUV); float fogEnd = 512;
FragColor = vec4(texColor.rgb * lighting, texColor.a); float fogStart = 32;
float dist = length(cameraPosition - fragPos);
float fogFactor = (fogEnd - dist) / (fogEnd - fogStart);
fogFactor = clamp(fogFactor, 0, 1);
vec4 fogColor = vec4(0.8,0.8,0.8,1);
vec4 texColor = texture(uTexture, fragUV) * lighting;
vec4 color = mix(fogColor, texColor, fogFactor);
FragColor = vec4(color.rgb, texColor.a);
} }

View File

@@ -20,57 +20,58 @@ uniform int chunkY;
out vec2 fragUV; out vec2 fragUV;
out float lighting; out float lighting;
out vec3 fragPos;
const float lightMult[6] = float[6](0.6, 0.6, 1.0, 0.5, 0.8, 0.8); const float lightMult[6] = float[6](0.6, 0.6, 1.0, 0.5, 0.8, 0.8);
const vec3 offsets[6][6] = vec3[6][6]( const vec3 offsets[6][6] = vec3[6][6](
vec3[6]( // +X vec3[6]( // +X
vec3(0.5, -0.5, -0.5), vec3(1, 0, 0),
vec3(0.5, 0.5, 0.5), vec3(1, 1, 1),
vec3(0.5, -0.5, 0.5), vec3(1, 0, 1),
vec3(0.5, 0.5, 0.5), vec3(1, 1, 1),
vec3(0.5, -0.5, -0.5), vec3(1, 0, 0),
vec3(0.5, 0.5, -0.5) vec3(1, 1, 0)
), ),
vec3[6]( // -X vec3[6]( // -X
vec3(-0.5, -0.5, 0.5), vec3(0, 0, 1),
vec3(-0.5, 0.5, -0.5), vec3(0, 1, 0),
vec3(-0.5, -0.5, -0.5), vec3(0, 0, 0),
vec3(-0.5, 0.5, -0.5), vec3(0, 1, 0),
vec3(-0.5, -0.5, 0.5), vec3(0, 0, 1),
vec3(-0.5, 0.5, 0.5) vec3(0, 1, 1)
), ),
vec3[6]( // +Y vec3[6]( // +Y
vec3(-0.5, 0.5, -0.5), vec3(0, 1, 0),
vec3( 0.5, 0.5, 0.5), vec3(1, 1, 1),
vec3( 0.5, 0.5, -0.5), vec3(1, 1, 0),
vec3( 0.5, 0.5, 0.5), vec3(1, 1, 1),
vec3(-0.5, 0.5, -0.5), vec3(0, 1, 0),
vec3(-0.5, 0.5, 0.5) vec3(0, 1, 1)
), ),
vec3[6]( vec3[6]( // -Y
vec3(-0.5, -0.5, 0.5), vec3(0, 0, 1),
vec3( 0.5, -0.5, -0.5), vec3(1, 0, 0),
vec3( 0.5, -0.5, 0.5), vec3(1, 0, 1),
vec3( 0.5, -0.5, -0.5), vec3(1, 0, 0),
vec3(-0.5, -0.5, 0.5), vec3(0, 0, 1),
vec3(-0.5, -0.5, -0.5) vec3(0, 0, 0)
), ),
vec3[6]( // +Z vec3[6]( // +Z
vec3( 0.5, -0.5, 0.5), vec3(1, 0, 1),
vec3(-0.5, 0.5, 0.5), vec3(0, 1, 1),
vec3(-0.5, -0.5, 0.5), vec3(0, 0, 1),
vec3(-0.5, 0.5, 0.5), vec3(0, 1, 1),
vec3( 0.5, -0.5, 0.5), vec3(1, 0, 1),
vec3( 0.5, 0.5, 0.5) vec3(1, 1, 1)
), ),
vec3[6]( // -Z vec3[6]( // -Z
vec3(-0.5, -0.5, -0.5), vec3(0, 0, 0),
vec3( 0.5, 0.5, -0.5), vec3(1, 1, 0),
vec3( 0.5, -0.5, -0.5), vec3(1, 0, 0),
vec3( 0.5, 0.5, -0.5), vec3(1, 1, 0),
vec3(-0.5, -0.5, -0.5), vec3(0, 0, 0),
vec3(-0.5, 0.5, -0.5) vec3(0, 1, 0)
) )
); );
@@ -111,5 +112,6 @@ void main()
fragUV = uv; fragUV = uv;
lighting = lightMult[facing]; lighting = lightMult[facing];
fragPos = vec3(worldPos.x, worldPos.y, worldPos.z);
gl_Position = projection * view * worldPos; gl_Position = projection * view * worldPos;
} }

17
UISprite.cs Normal file
View File

@@ -0,0 +1,17 @@
using OpenTK.Mathematics;
using System.Drawing;
public class UISprite
{
public Vector2 Position; // Screen pixels (not normalized)
public Vector2 Size; // Size in pixels
public Rectangle TextureRegion; // Which part of atlas to use
public Color Tint = Color.White;
public float Rotation = 0f;
public Vector2 Origin = Vector2.Zero; // Rotation/scale origin
public void Draw()
{
// Draw textured quad using TextureRegion coordinates
}
}

View File

@@ -2,6 +2,7 @@
using OpenTK.Windowing.Common; using OpenTK.Windowing.Common;
using OpenTK.Windowing.Common.Input; using OpenTK.Windowing.Common.Input;
using OpenTK.Windowing.Desktop; using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace Voxel namespace Voxel
{ {
@@ -9,22 +10,38 @@ namespace Voxel
{ {
public readonly int Width = width; public readonly int Width = width;
public readonly int Height = height; public readonly int Height = height;
public Player Player;
public uint frames = 0; public uint frames = 0;
public double timeElapsed = 0; public double timeElapsed = 0;
public event Action<float, float> Update;
public event Action Tick;
private double _tickTime;
public const double TICK_LENGTH = 1.0 / 20.0; // 20 TPS
protected override void OnUpdateFrame(FrameEventArgs e) protected override void OnUpdateFrame(FrameEventArgs e)
{ {
base.OnUpdateFrame(e); base.OnUpdateFrame(e);
if (Input.GetKey(OpenTK.Windowing.GraphicsLibraryFramework.Keys.Escape)) _tickTime += e.Time;
while (_tickTime >= TICK_LENGTH)
{
_tickTime -= TICK_LENGTH;
Tick(); // run exactly once per tick
}
if (Input.GetKey(Keys.Escape))
{ {
Close(); Close();
} }
if (Player != null) if (Input.GetKey(Keys.F11))
{ {
Player.Update((float)e.Time); if (!IsFullscreen)
WindowState = WindowState.Fullscreen;
else
WindowState = WindowState.Normal;
} }
} }
@@ -36,6 +53,8 @@ namespace Voxel
frames++; frames++;
timeElapsed += e.Time; timeElapsed += e.Time;
float alpha = (float)(_tickTime / Window.TICK_LENGTH);
float deltaTime = (float)e.Time;
if (timeElapsed >= 1) if (timeElapsed >= 1)
{ {
Console.WriteLine("FPS: " + frames.ToString()); Console.WriteLine("FPS: " + frames.ToString());
@@ -43,7 +62,8 @@ namespace Voxel
frames = 0; frames = 0;
} }
Camera.Update((float)e.Time); Update.Invoke(deltaTime, alpha);
Renderer.Render(); Renderer.Render();
SwapBuffers(); SwapBuffers();
@@ -53,7 +73,7 @@ namespace Voxel
{ {
base.OnFramebufferResize(e); base.OnFramebufferResize(e);
Camera.UpdateProjection(e.Width, e.Height); Camera.UpdateSize(e.Width, e.Height);
GL.Viewport(0, 0, e.Width, e.Height); GL.Viewport(0, 0, e.Width, e.Height);
} }
@@ -72,7 +92,7 @@ namespace Voxel
CursorState = CursorState.Grabbed; CursorState = CursorState.Grabbed;
VSync = VSyncMode.On; VSync = VSyncMode.On;
Camera.UpdateProjection(Width, Height); Camera.UpdateSize(Width, Height);
} }
protected override void OnMouseMove(MouseMoveEventArgs e) protected override void OnMouseMove(MouseMoveEventArgs e)
@@ -93,5 +113,23 @@ namespace Voxel
base.OnKeyDown(e); base.OnKeyDown(e);
Input.SetKey(e.Key, true); Input.SetKey(e.Key, true);
} }
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
Input.SetMouseButton(e.Button, true);
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
Input.SetMouseButton(e.Button, false);
}
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
base.OnMouseWheel(e);
Input.MouseWheel(e);
}
} }
} }

124
World.cs
View File

@@ -1,5 +1,6 @@
using OpenTK.Mathematics; using OpenTK.Mathematics;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
namespace Voxel namespace Voxel
{ {
@@ -21,6 +22,22 @@ namespace Voxel
public void AddChunk(Chunk chunk) public void AddChunk(Chunk chunk)
{ {
_chunks[(chunk.X, chunk.Y)] = chunk; _chunks[(chunk.X, chunk.Y)] = chunk;
Dictionary<Orientation, Orientation> oppositeOrientation = new()
{
{Orientation.West, Orientation.East},
{Orientation.East, Orientation.West},
{Orientation.North, Orientation.South},
{Orientation.South, Orientation.North},
};
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
{
neighbor.Neighbors[oppositeOrientation[orientation]] = chunk;
chunk.Neighbors[orientation] = neighbor;
neighbor.UpdateChunkMesh();
}
chunk.UpdateChunkMesh();
} }
public void RemoveChunk(int chunkX, int chunkZ) public void RemoveChunk(int chunkX, int chunkZ)
@@ -46,6 +63,18 @@ namespace Voxel
return chunk.GetBlock(localX, worldY, localZ); return chunk.GetBlock(localX, worldY, localZ);
} }
List<Orientation> GetEdgeOrientations(int localX, int localZ, int size)
{
var orientations = new List<Orientation>();
if (localX == size - 1) orientations.Add(Orientation.West);
if (localX == 0) orientations.Add(Orientation.East);
if (localZ == size - 1) orientations.Add(Orientation.North);
if (localZ == 0) orientations.Add(Orientation.South);
return orientations;
}
public void SetBlock(int worldX, int worldY, int worldZ, Blocks block) public void SetBlock(int worldX, int worldY, int worldZ, Blocks block)
{ {
int chunkX = worldX / Chunk.Size; int chunkX = worldX / Chunk.Size;
@@ -57,9 +86,41 @@ namespace Voxel
int localZ = worldZ % Chunk.Size; int localZ = worldZ % Chunk.Size;
chunk.SetBlock(localX, worldY, localZ, block); chunk.SetBlock(localX, worldY, localZ, block);
if (block == Blocks.Air && (localX == 15 || localX == 0) || (localZ == 15 || localZ == 0))
{
foreach (var orientation in GetEdgeOrientations(localX, localZ, Chunk.Size))
{
if (chunk.Neighbors.TryGetValue(orientation, out var neighbor) && neighbor != null)
{
neighbor.UpdateChunkMesh();
}
}
}
} }
public (bool success, Blocks block, int x, int y, int z) Raycast(Vector3 origin, Vector3 direction, float maxDistance) public IEnumerable<(Orientation orientation, Chunk neighbor)> GetChunkNeighbors(Chunk chunk)
{
Dictionary<Orientation, (int x, int y)> offsets = new()
{
{ Orientation.West, (1, 0) },
{ Orientation.East, (-1, 0) },
{ Orientation.North, (0, 1) },
{ Orientation.South, (0, -1) }
};
foreach (var kv in offsets)
{
int nx = chunk.X + kv.Value.x;
int ny = chunk.Y + kv.Value.y;
Chunk neighbor = GetChunk(nx, ny);
if (neighbor != null)
yield return (kv.Key, neighbor);
}
}
public (bool success, Blocks block, int x, int y, int z, Vector3i normal) Raycast(Vector3 origin, Vector3 direction, float maxDistance)
{ {
int x = (int)MathF.Floor(origin.X); int x = (int)MathF.Floor(origin.X);
int y = (int)MathF.Floor(origin.Y); int y = (int)MathF.Floor(origin.Y);
@@ -73,60 +134,47 @@ namespace Voxel
float tDeltaY = direction.Y != 0 ? MathF.Abs(1 / direction.Y) : float.MaxValue; float tDeltaY = direction.Y != 0 ? MathF.Abs(1 / direction.Y) : float.MaxValue;
float tDeltaZ = direction.Z != 0 ? MathF.Abs(1 / direction.Z) : float.MaxValue; float tDeltaZ = direction.Z != 0 ? MathF.Abs(1 / direction.Z) : float.MaxValue;
float tMaxX = direction.X > 0 float tMaxX = direction.X > 0 ? (MathF.Floor(origin.X) + 1 - origin.X) * tDeltaX
? (MathF.Floor(origin.X) + 1 - origin.X) * tDeltaX
: (origin.X - MathF.Floor(origin.X)) * tDeltaX; : (origin.X - MathF.Floor(origin.X)) * tDeltaX;
float tMaxY = direction.Y > 0 float tMaxY = direction.Y > 0 ? (MathF.Floor(origin.Y) + 1 - origin.Y) * tDeltaY
? (MathF.Floor(origin.Y) + 1 - origin.Y) * tDeltaY
: (origin.Y - MathF.Floor(origin.Y)) * tDeltaY; : (origin.Y - MathF.Floor(origin.Y)) * tDeltaY;
float tMaxZ = direction.Z > 0 float tMaxZ = direction.Z > 0 ? (MathF.Floor(origin.Z) + 1 - origin.Z) * tDeltaZ
? (MathF.Floor(origin.Z) + 1 - origin.Z) * tDeltaZ
: (origin.Z - MathF.Floor(origin.Z)) * tDeltaZ; : (origin.Z - MathF.Floor(origin.Z)) * tDeltaZ;
float distance = 0f; float distance = 0f;
Vector3i normal = Vector3i.Zero;
while (distance <= maxDistance) while (distance <= maxDistance)
{ {
Blocks block = GetBlock(x, y, z); Blocks block = GetBlock(x, y, z);
if (block != Blocks.Air)
{
return (true, block, x, y, z);
}
// step to next voxel if (block != Blocks.Air)
if (tMaxX < tMaxY) return (true, block, x, y, z, normal);
if (tMaxX < tMaxY && tMaxX < tMaxZ)
{ {
if (tMaxX < tMaxZ) x += stepX;
{ normal = new Vector3i(-stepX, 0, 0);
x += stepX; distance = tMaxX;
distance = tMaxX; tMaxX += tDeltaX;
tMaxX += tDeltaX; }
} else if (tMaxY < tMaxZ)
else {
{ y += stepY;
z += stepZ; normal = new Vector3i(0, -stepY, 0);
distance = tMaxZ; distance = tMaxY;
tMaxZ += tDeltaZ; tMaxY += tDeltaY;
}
} }
else else
{ {
if (tMaxY < tMaxZ) z += stepZ;
{ normal = new Vector3i(0, 0, -stepZ);
y += stepY; distance = tMaxZ;
distance = tMaxY; tMaxZ += tDeltaZ;
tMaxY += tDeltaY;
}
else
{
z += stepZ;
distance = tMaxZ;
tMaxZ += tDeltaZ;
}
} }
} }
return (false, Blocks.Air, 0, 0, 0); return (false, Blocks.Air, 0, 0, 0, Vector3i.Zero);
} }
public void Update() public void Update()

79
Worldgen.cs Normal file
View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Voxel
{
public static class Worldgen
{
private static int baseHeight = 32;
private static float res1 = 2;
private static float res2 = 4;
private static float amplitude1 = 4;
private static float amplitude2 = 2;
private static float elevationMapRes = (float)1/8;
private static float elevationMapAmplitude = 32;
private static Random random = new Random();
private static FastNoiseLite noise = new FastNoiseLite(random.Next());
public static int GetHeight(int x, int z)
{
float map1 = noise.GetNoise(
x * res1,
z * res1
) * amplitude1;
float map2 = noise.GetNoise(x * res2,
z * res2
) * amplitude2;
float elevationMap = (noise.GetNoise(
x * elevationMapRes,
z * elevationMapRes
) + 0.25f) * elevationMapAmplitude;
return baseHeight + (int)(elevationMap + map1 + map2);
}
public static Blocks GetBlock(int y, int maxY)
{
if (y > maxY)
{
return Blocks.Air;
}
if (y == maxY)
{
if (y < baseHeight)
return Blocks.Sand;
else
return Blocks.Grass;
}
if (y < 4)
{
if (y == 0) return Blocks.Bedrock;
float randomValue = random.NextSingle();
if (randomValue < (1f / y))
{
return Blocks.Bedrock;
}
return Blocks.Stone;
}
if (y > maxY - 6)
{
return Blocks.Dirt;
}
return Blocks.Stone;
}
}
}