Compare commits
22 Commits
1d062528aa
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66f26bc2e3 | ||
|
|
2e72dd564e | ||
|
|
35bc49c0f8 | ||
|
|
cad22d3c64 | ||
|
|
7835ade2c1 | ||
| bd2c87ddd1 | |||
|
|
11f76ca429 | ||
|
|
38dccf0a84 | ||
|
|
9a61dfd74c | ||
| 81ef6d8a29 | |||
|
|
e1bb0b3683 | ||
|
|
40dd5c3a9e | ||
| 50f9d4c0c8 | |||
|
|
ce456b6b26 | ||
|
|
b6655d71d9 | ||
|
|
3678eaa5f8 | ||
| 94ebc4ace4 | |||
| 00713db79e | |||
| a9cab195b6 | |||
|
|
6fb19c415f | ||
|
|
71c5f3a3aa | ||
| b6f9966eb9 |
41
AABB.cs
Normal file
41
AABB.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
BlockData.cs
Normal file
12
BlockData.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class BlockData
|
||||
{
|
||||
}
|
||||
}
|
||||
94
Blocks.cs
Normal file
94
Blocks.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
namespace Voxel
|
||||
{
|
||||
public enum Blocks : byte
|
||||
{
|
||||
Air,
|
||||
Stone,
|
||||
Dirt,
|
||||
OakPlanks,
|
||||
Grass,
|
||||
Bedrock,
|
||||
Sand
|
||||
}
|
||||
|
||||
public enum Orientation : byte
|
||||
{
|
||||
West = 0, // + X
|
||||
East = 1, // - X
|
||||
Top = 2, // + Y
|
||||
Bottom = 3,// - Y
|
||||
North = 4, // + Z
|
||||
South = 5, // - Z
|
||||
}
|
||||
|
||||
public class BlockDefinition
|
||||
{
|
||||
public Blocks BlockType;
|
||||
public Textures[] FaceTextures;
|
||||
|
||||
public BlockDefinition(Blocks type, Textures singleTexture)
|
||||
{
|
||||
BlockType = type;
|
||||
FaceTextures = new Voxel.Textures[6];
|
||||
for (int i = 0; i < 6; i++) FaceTextures[i] = singleTexture;
|
||||
}
|
||||
|
||||
public BlockDefinition(
|
||||
Blocks type,
|
||||
Textures west,
|
||||
Textures east,
|
||||
Textures top,
|
||||
Textures bottom,
|
||||
Textures north,
|
||||
Textures south
|
||||
)
|
||||
{
|
||||
BlockType = type;
|
||||
FaceTextures = new Textures[]
|
||||
{
|
||||
west, east, top, bottom, north, south
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockDefinitions
|
||||
{
|
||||
public static readonly Dictionary<Blocks, BlockDefinition> Blocks;
|
||||
|
||||
static BlockDefinitions()
|
||||
{
|
||||
Blocks = new Dictionary<Blocks, BlockDefinition>
|
||||
{
|
||||
{Voxel.Blocks.Stone, new BlockDefinition(
|
||||
Voxel.Blocks.Stone, Textures.Stone
|
||||
)},
|
||||
|
||||
{Voxel.Blocks.Dirt, new BlockDefinition(
|
||||
Voxel.Blocks.Dirt, Textures.Dirt
|
||||
)},
|
||||
|
||||
{Voxel.Blocks.OakPlanks, new BlockDefinition(
|
||||
Voxel.Blocks.OakPlanks, Textures.OakPlanks
|
||||
)},
|
||||
|
||||
{Voxel.Blocks.Bedrock, new BlockDefinition(
|
||||
Voxel.Blocks.Bedrock, Textures.Bedrock
|
||||
)},
|
||||
|
||||
{Voxel.Blocks.Sand, new BlockDefinition(
|
||||
Voxel.Blocks.Sand, Textures.Sand
|
||||
)},
|
||||
|
||||
{ Voxel.Blocks.Grass, new BlockDefinition(
|
||||
Voxel.Blocks.Grass,
|
||||
Voxel.Textures.GrassSide, // West
|
||||
Voxel.Textures.GrassSide, // East
|
||||
Voxel.Textures.GrassTop, // Top
|
||||
Voxel.Textures.Dirt, // Bottom
|
||||
Voxel.Textures.GrassSide, // North
|
||||
Voxel.Textures.GrassSide // South
|
||||
)},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
71
Camera.cs
Normal file
71
Camera.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
static class Camera
|
||||
{
|
||||
public static Vector3 Position = new Vector3(-8, 16, -8);
|
||||
|
||||
public static float Pitch = -22.5f;
|
||||
public static float Yaw = 0f;
|
||||
public static float FOV = 60f;
|
||||
public static float TargetFOV = FOV;
|
||||
public static float FOVLerpSpeed = 10f;
|
||||
public static float Speed = 5f;
|
||||
public static float ShiftSpeed = 20f;
|
||||
|
||||
private static int _width;
|
||||
private static int _height;
|
||||
|
||||
public static Matrix4 view =>
|
||||
Matrix4.LookAt(Position, Position + Front, Vector3.UnitY);
|
||||
|
||||
public static Matrix4 projection;
|
||||
|
||||
public static Vector3 Front
|
||||
{
|
||||
get
|
||||
{
|
||||
float yawOffset = Yaw - 90f;
|
||||
Vector3 front;
|
||||
front.X = MathF.Cos(MathHelper.DegreesToRadians(yawOffset)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch));
|
||||
front.Y = MathF.Sin(MathHelper.DegreesToRadians(Pitch));
|
||||
front.Z = MathF.Sin(MathHelper.DegreesToRadians(yawOffset)) * MathF.Cos(MathHelper.DegreesToRadians(Pitch));
|
||||
return front.Normalized();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateMouse(Vector2 delta)
|
||||
{
|
||||
float sensitivity = 0.1f;
|
||||
Yaw += delta.X * sensitivity;
|
||||
Pitch -= delta.Y * sensitivity;
|
||||
|
||||
Pitch = MathHelper.Clamp(Pitch, -89f, 89f);
|
||||
}
|
||||
|
||||
public static void UpdateProjection()
|
||||
{
|
||||
float fov = MathHelper.DegreesToRadians(FOV);
|
||||
float aspectRatio = _width / (float)_height;
|
||||
float near = 0.1f;
|
||||
float far = 1000f;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
187
Chunk.cs
Normal file
187
Chunk.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
namespace Voxel
|
||||
{
|
||||
public class Chunk
|
||||
{
|
||||
public static int Size = 16;
|
||||
public static int Height = 256;
|
||||
public readonly int X;
|
||||
public readonly int Y;
|
||||
private ChunkMesh _chunkMesh;
|
||||
|
||||
private Dictionary<ushort, BlockData> _blockData;
|
||||
private Blocks[] _blocks;
|
||||
public Dictionary<Orientation, Chunk> Neighbors = new();
|
||||
|
||||
public Chunk(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
|
||||
_blockData = new Dictionary<ushort, BlockData>();
|
||||
_blocks = new Blocks[Size * Size * Height];
|
||||
_chunkMesh = new ChunkMesh(X, Y);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void SetBlock(int x, int y, int z, Blocks block, bool updateMesh = true)
|
||||
{
|
||||
int i = GetBlockIndex(x, y, z);
|
||||
if (i == -1) return;
|
||||
_blocks[i] = block;
|
||||
|
||||
if (updateMesh)
|
||||
{
|
||||
UpdateChunkMesh();
|
||||
Renderer.MarkBuffersDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBlockIndex(int i, Blocks block)
|
||||
{
|
||||
_blocks[i] = block;
|
||||
}
|
||||
|
||||
public Blocks GetBlock(int x, int y, int z)
|
||||
{
|
||||
int i = GetBlockIndex(x, y, z);
|
||||
if (i == -1) return Blocks.Air;
|
||||
return _blocks[i];
|
||||
}
|
||||
|
||||
public BlockData GetBlockData(int x, int y, int z)
|
||||
{
|
||||
return new BlockData();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
for (int x = 0; x < Size; x++)
|
||||
{
|
||||
for (int z = 0; z < Size; z++)
|
||||
{
|
||||
var position = GetWorldCoordinates(x, 0, z);
|
||||
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();
|
||||
}
|
||||
|
||||
// todo
|
||||
public (int x, int y, int z) IndexToPosition(int i)
|
||||
{
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int z = 0;
|
||||
|
||||
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)
|
||||
{
|
||||
if (x < 0 || x > 15 || y < 0 || y > 255 || z < 0 || z > 15)
|
||||
return -1;
|
||||
|
||||
return x + z * Size + y * Size * Size;
|
||||
}
|
||||
|
||||
private static readonly (int dx, int dy, int dz)[] Offsets = new (int, int, int)[6]
|
||||
{
|
||||
( 1, 0, 0), // +X
|
||||
(-1, 0, 0), // -X
|
||||
( 0, 1, 0), // +Y
|
||||
( 0, -1, 0), // -Y
|
||||
( 0, 0, 1), // +Z
|
||||
( 0, 0, -1) // -Z
|
||||
};
|
||||
|
||||
public void UpdateChunkMesh()
|
||||
{
|
||||
List<FaceData> faces = new List<FaceData>(Size * Size * Height / 2);
|
||||
|
||||
for (int x = 0; x < Size; x++)
|
||||
{
|
||||
for (int z = 0; z < Size; z++)
|
||||
{
|
||||
for (int y = 0; y < Height; y++)
|
||||
{
|
||||
for (byte face = 0; face < 6; face++)
|
||||
{
|
||||
int indexBase = y * Size * Size + z * Size + x;
|
||||
Blocks block = _blocks[indexBase];
|
||||
|
||||
void AddFace()
|
||||
{
|
||||
FaceData faceData = new FaceData();
|
||||
|
||||
faceData.Facing = (Orientation)face;
|
||||
faceData.Texture = BlockDefinitions.Blocks[block].FaceTextures[face];
|
||||
|
||||
faceData.X = (byte)x;
|
||||
faceData.Y = (byte)y;
|
||||
faceData.Z = (byte)z;
|
||||
|
||||
faces.Add(faceData);
|
||||
}
|
||||
|
||||
if (block == Blocks.Air) continue; // ignore if air
|
||||
|
||||
int nx = x + Offsets[face].dx;
|
||||
int ny = y + Offsets[face].dy;
|
||||
int nz = z + Offsets[face].dz;
|
||||
|
||||
// check neighbor, ignore if at chunk edge
|
||||
|
||||
int ni = GetBlockIndex(nx, ny, nz);
|
||||
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();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_blocks[ni] == Blocks.Air)
|
||||
{
|
||||
AddFace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_chunkMesh.SetFaces(faces);
|
||||
}
|
||||
|
||||
public ChunkMesh GetChunkMesh()
|
||||
{
|
||||
return _chunkMesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
ChunkMesh.cs
Normal file
44
ChunkMesh.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class ChunkMesh
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
private byte[] _packedData;
|
||||
public bool NeedsUpdate = false;
|
||||
public int Size = 0;
|
||||
private List<FaceData> _faces;
|
||||
|
||||
public ChunkMesh(int x, int y)
|
||||
{
|
||||
_packedData = new byte[0];
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
public void SetFaces(List<FaceData> faces)
|
||||
{
|
||||
Size = faces.Count;
|
||||
_faces = faces;
|
||||
NeedsUpdate = true;
|
||||
}
|
||||
|
||||
public byte[] GetPackedData()
|
||||
{
|
||||
if (NeedsUpdate)
|
||||
{
|
||||
_packedData = _faces.SelectMany(f => f.Pack()).ToArray();
|
||||
}
|
||||
|
||||
return _packedData;
|
||||
}
|
||||
}
|
||||
}
|
||||
449
Entity.cs
Normal file
449
Entity.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
FaceData.cs
Normal file
26
FaceData.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public struct FaceData
|
||||
{
|
||||
public Orientation Facing;
|
||||
public byte X, Y, Z;
|
||||
public Textures Texture;
|
||||
public byte LightLevel;
|
||||
|
||||
public byte[] Pack()
|
||||
{
|
||||
return new byte[]
|
||||
{
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
(byte)Facing,
|
||||
(byte)Texture,
|
||||
LightLevel,
|
||||
0,0 // two bits empty
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Input.cs
Normal file
38
Input.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public static class Input
|
||||
{
|
||||
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)
|
||||
{
|
||||
return _keystates.TryGetValue(key, out bool pressed) && pressed;
|
||||
}
|
||||
|
||||
public static void SetKey(Keys key, bool pressed)
|
||||
{
|
||||
_keystates[key] = pressed;
|
||||
}
|
||||
|
||||
public static void MouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
OnMouseWheel.Invoke(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
2506
Noise/FastNoiseLite.cs
Normal file
2506
Noise/FastNoiseLite.cs
Normal file
File diff suppressed because it is too large
Load Diff
162
Player.cs
Normal file
162
Player.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class Player : Entity
|
||||
{
|
||||
public double lastClick = 0;
|
||||
public readonly float mouseCooldown = 0.2f;
|
||||
|
||||
private int _blockIndex = 0;
|
||||
private double _tickTime = 0;
|
||||
|
||||
private Blocks _selectedBlock = Blocks.OakPlanks;
|
||||
private Vector3 previousPosition;
|
||||
|
||||
public Player(Vector3 startPos, World world) : base(startPos, 0.5f, 1.8f, 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()
|
||||
{
|
||||
var (success, hit, x, y, z, normal) = _world.Raycast(Camera.Position, Camera.Front.Normalized(), 8);
|
||||
if (!success) return;
|
||||
|
||||
_world.SetBlock(x, y, z, Blocks.Air);
|
||||
}
|
||||
|
||||
public new void Tick()
|
||||
{
|
||||
previousPosition = Position;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Program.cs
42
Program.cs
@@ -1,4 +1,5 @@
|
||||
using Voxel;
|
||||
using OpenTK.Mathematics;
|
||||
using Voxel;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
@@ -8,7 +9,46 @@ internal class Program
|
||||
int sizeY = 600;
|
||||
string title = "Game";
|
||||
|
||||
World world = new World();
|
||||
Window window = new Window(sizeX, sizeY, title);
|
||||
|
||||
Console.WriteLine("Generating map...");
|
||||
|
||||
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 < worldSizeY; y++)
|
||||
{
|
||||
i++;
|
||||
Chunk chunk = new Chunk(x, y);
|
||||
world.AddChunk(chunk);
|
||||
|
||||
int percentage = (int)((i / maxI) * 100);
|
||||
if (percentage > lastPercentage)
|
||||
{
|
||||
lastPercentage = percentage;
|
||||
Console.WriteLine((percentage).ToString() + "%");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Generated " + maxI.ToString() + " chunks");
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
136
Renderer.cs
Normal file
136
Renderer.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
static class Renderer
|
||||
{
|
||||
private static int _ssbo;
|
||||
private static int _vao;
|
||||
private static bool _buffersDirty;
|
||||
|
||||
private static Dictionary<(int, int), int> _chunkBufferSizes = new Dictionary<(int, int), int>();
|
||||
private static Shader _shader;
|
||||
private static readonly Texture _texture;
|
||||
private static World? _world;
|
||||
|
||||
static Renderer()
|
||||
{
|
||||
string vertexPath = "Shaders/shader.vert";
|
||||
string fragmentPath = "Shaders/shader.frag";
|
||||
string texturePath = "atlas.png";
|
||||
|
||||
_shader = new Shader(vertexPath, fragmentPath);
|
||||
_texture = new Texture(texturePath);
|
||||
|
||||
_shader.SetInt("uTexture", 0);
|
||||
|
||||
_ssbo = GL.GenBuffer();
|
||||
_vao = GL.GenVertexArray();
|
||||
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo);
|
||||
|
||||
GL.BufferData(BufferTarget.ShaderStorageBuffer, 1024 * 1024 * 128, IntPtr.Zero, BufferUsageHint.DynamicDraw);
|
||||
GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, 0, _ssbo);
|
||||
}
|
||||
|
||||
public static void Render()
|
||||
{
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo);
|
||||
|
||||
_shader.Use();
|
||||
_shader.SetMatrix4("view", Camera.view);
|
||||
_shader.SetVector3("cameraPosition", Camera.Position);
|
||||
_shader.SetMatrix4("projection", Camera.projection);
|
||||
|
||||
if (_buffersDirty)
|
||||
{
|
||||
UpdateAllChunksBuffer();
|
||||
_buffersDirty = false;
|
||||
}
|
||||
|
||||
RenderWorld();
|
||||
RenderUi();
|
||||
}
|
||||
|
||||
private static void UpdateAllChunksBuffer()
|
||||
{
|
||||
if (_world == null) return;
|
||||
|
||||
int offset = 0;
|
||||
foreach (Chunk chunk in _world.GetAllChunks())
|
||||
{
|
||||
ChunkMesh chunkMesh = chunk.GetChunkMesh();
|
||||
|
||||
if (chunkMesh.NeedsUpdate)
|
||||
{
|
||||
byte[] data = chunkMesh.GetPackedData();
|
||||
GL.BufferSubData(BufferTarget.ShaderStorageBuffer, (IntPtr)offset * 8, chunkMesh.Size * 8, data);
|
||||
}
|
||||
|
||||
_chunkBufferSizes[(chunk.X, chunk.Y)] = offset;
|
||||
offset += chunkMesh.Size * 8;
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (_world == null) return;
|
||||
|
||||
foreach (Chunk chunk in _world.GetAllChunks())
|
||||
{
|
||||
ChunkMesh chunkMesh = chunk.GetChunkMesh();
|
||||
|
||||
if (chunkMesh.Size == 0) continue;
|
||||
|
||||
if (!_chunkBufferSizes.TryGetValue((chunk.X, chunk.Y), out int offset)) continue;
|
||||
|
||||
_shader.SetInt("chunkX", chunk.X);
|
||||
_shader.SetInt("chunkY", chunk.Y);
|
||||
|
||||
//GL.MemoryBarrier(MemoryBarrierFlags.ShaderStorageBarrierBit);
|
||||
GL.DrawArrays(PrimitiveType.Triangles, offset * 6, chunkMesh.Size * 6);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetWorld(World world)
|
||||
{
|
||||
_world = world;
|
||||
_buffersDirty = true;
|
||||
}
|
||||
|
||||
public static void MarkBuffersDirty()
|
||||
{
|
||||
_buffersDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Shader.cs
62
Shader.cs
@@ -1,16 +1,11 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class Shader
|
||||
{
|
||||
int handle;
|
||||
private int _handle;
|
||||
private bool disposedValue = false;
|
||||
|
||||
public Shader(string vertexPath, string fragmentPath)
|
||||
@@ -41,36 +36,69 @@ namespace Voxel
|
||||
|
||||
// attach
|
||||
|
||||
handle = GL.CreateProgram();
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
GL.AttachShader(handle, vertexShader);
|
||||
GL.AttachShader(handle, fragmentShader);
|
||||
GL.AttachShader(_handle, vertexShader);
|
||||
GL.AttachShader(_handle, fragmentShader);
|
||||
|
||||
GL.LinkProgram(handle);
|
||||
GL.LinkProgram(_handle);
|
||||
|
||||
GL.GetProgram(handle, GetProgramParameterName.LinkStatus, out success);
|
||||
GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out success);
|
||||
if (success == 0)
|
||||
{
|
||||
string infoLog = GL.GetProgramInfoLog(handle);
|
||||
string infoLog = GL.GetProgramInfoLog(_handle);
|
||||
Console.WriteLine(infoLog);
|
||||
}
|
||||
|
||||
GL.DetachShader(handle, vertexShader);
|
||||
GL.DetachShader(handle, fragmentShader);
|
||||
GL.DetachShader(_handle, vertexShader);
|
||||
GL.DetachShader(_handle, fragmentShader);
|
||||
GL.DeleteShader(fragmentShader);
|
||||
GL.DeleteShader(vertexShader);
|
||||
}
|
||||
|
||||
public void SetMatrix4(string name, Matrix4 matrix)
|
||||
{
|
||||
int location = GL.GetUniformLocation(_handle, name);
|
||||
if (location == -1)
|
||||
{
|
||||
Console.WriteLine($"Uniform '{name}' not found in shader.");
|
||||
return;
|
||||
}
|
||||
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)
|
||||
{
|
||||
int location = GL.GetUniformLocation(_handle, name);
|
||||
if (location == -1)
|
||||
{
|
||||
Console.WriteLine($"Uniform '{name}' not found in shader.");
|
||||
return;
|
||||
}
|
||||
GL.Uniform1(location, value);
|
||||
}
|
||||
|
||||
public void Use()
|
||||
{
|
||||
GL.UseProgram(handle);
|
||||
GL.UseProgram(_handle);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
GL.DeleteProgram(handle);
|
||||
GL.DeleteProgram(_handle);
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
#version 330 core
|
||||
out vec4 FragColor;
|
||||
#version 430 core
|
||||
|
||||
in vec4 vertexColor;
|
||||
out vec4 FragColor;
|
||||
in vec2 fragUV;
|
||||
in vec3 fragPos;
|
||||
in float lighting;
|
||||
|
||||
uniform sampler2D uTexture;
|
||||
uniform vec3 cameraPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
FragColor = vertexColor;
|
||||
float fogEnd = 512;
|
||||
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);
|
||||
}
|
||||
@@ -1,10 +1,117 @@
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPosition;
|
||||
#version 430 core
|
||||
|
||||
out vec4 vertexColor;
|
||||
struct FaceData {
|
||||
uint x;
|
||||
uint y;
|
||||
uint z;
|
||||
uint facing; // 0=+X,1=-X,2=+Y,3=-Y,4=+Z,5=-Z
|
||||
uint texture;
|
||||
uint lightLevel;
|
||||
};
|
||||
|
||||
layout(std430, binding = 0) buffer FaceBuffer {
|
||||
uint faces[];
|
||||
};
|
||||
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
uniform int chunkX;
|
||||
uniform int chunkY;
|
||||
|
||||
out vec2 fragUV;
|
||||
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 vec3 offsets[6][6] = vec3[6][6](
|
||||
vec3[6]( // +X
|
||||
vec3(1, 0, 0),
|
||||
vec3(1, 1, 1),
|
||||
vec3(1, 0, 1),
|
||||
vec3(1, 1, 1),
|
||||
vec3(1, 0, 0),
|
||||
vec3(1, 1, 0)
|
||||
),
|
||||
vec3[6]( // -X
|
||||
vec3(0, 0, 1),
|
||||
vec3(0, 1, 0),
|
||||
vec3(0, 0, 0),
|
||||
vec3(0, 1, 0),
|
||||
vec3(0, 0, 1),
|
||||
vec3(0, 1, 1)
|
||||
),
|
||||
vec3[6]( // +Y
|
||||
vec3(0, 1, 0),
|
||||
vec3(1, 1, 1),
|
||||
vec3(1, 1, 0),
|
||||
vec3(1, 1, 1),
|
||||
vec3(0, 1, 0),
|
||||
vec3(0, 1, 1)
|
||||
),
|
||||
vec3[6]( // -Y
|
||||
vec3(0, 0, 1),
|
||||
vec3(1, 0, 0),
|
||||
vec3(1, 0, 1),
|
||||
vec3(1, 0, 0),
|
||||
vec3(0, 0, 1),
|
||||
vec3(0, 0, 0)
|
||||
),
|
||||
vec3[6]( // +Z
|
||||
vec3(1, 0, 1),
|
||||
vec3(0, 1, 1),
|
||||
vec3(0, 0, 1),
|
||||
vec3(0, 1, 1),
|
||||
vec3(1, 0, 1),
|
||||
vec3(1, 1, 1)
|
||||
),
|
||||
vec3[6]( // -Z
|
||||
vec3(0, 0, 0),
|
||||
vec3(1, 1, 0),
|
||||
vec3(1, 0, 0),
|
||||
vec3(1, 1, 0),
|
||||
vec3(0, 0, 0),
|
||||
vec3(0, 1, 0)
|
||||
)
|
||||
);
|
||||
|
||||
const vec2 uvs[6] = vec2[6](vec2(0,0), vec2(1,1), vec2(1,0), vec2(1,1), vec2(0,0), vec2(0,1));
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(aPosition, 1.0);
|
||||
vertexColor = vec4(aPosition + vec3(0.5,0.5,0.5), 1.0);
|
||||
uint faceIndex = gl_VertexID / 6u;
|
||||
uint vertIndex = gl_VertexID % 6u;
|
||||
|
||||
uint start = faceIndex * 2u; // 2 byte per face
|
||||
|
||||
uint u0 = faces[start]; // data in uint 0
|
||||
uint u1 = faces[start + 1]; // data in uint 1
|
||||
|
||||
// extract values from bits
|
||||
uint x = u0 & 0xFFu;
|
||||
uint y = (u0 >> 8) & 0xFFu;
|
||||
uint z = (u0 >> 16) & 0xFFu;
|
||||
uint facing = (u0 >> 24) & 0xFFu;
|
||||
uint texture = u1 & 0xFFu;
|
||||
uint lightLevel = (u1 >> 8) & 0xFFu;
|
||||
|
||||
vec3 basePos = vec3(x + chunkX * 16, y, z + chunkY * 16);
|
||||
vec4 worldPos = vec4(basePos + offsets[facing][vertIndex], 1.0);
|
||||
float light = float(lightLevel) / 255.0; // use later for caves and stuff
|
||||
|
||||
// UV mapping
|
||||
uint col = texture & 15u; // texture % 16
|
||||
uint row = texture >> 4; // texture / 16
|
||||
row = 15u - row; // invert row so 0 is top
|
||||
|
||||
// convert to float after int math, divide by 16
|
||||
vec2 uv = uvs[vertIndex] * 0.0625;
|
||||
uv.x += float(col) * 0.0625;
|
||||
uv.y += float(row) * 0.0625;
|
||||
|
||||
fragUV = uv;
|
||||
lighting = lightMult[facing];
|
||||
|
||||
fragPos = vec3(worldPos.x, worldPos.y, worldPos.z);
|
||||
gl_Position = projection * view * worldPos;
|
||||
}
|
||||
43
Texture.cs
Normal file
43
Texture.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using StbImageSharp;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class Texture
|
||||
{
|
||||
private int _handle;
|
||||
private string _path;
|
||||
|
||||
public Texture(string path)
|
||||
{
|
||||
_handle = GL.GenTexture();
|
||||
_path = path;
|
||||
|
||||
LoadFromFile();
|
||||
}
|
||||
|
||||
private void LoadFromFile()
|
||||
{
|
||||
StbImage.stbi_set_flip_vertically_on_load(1);
|
||||
ImageResult image = ImageResult.FromStream(File.OpenRead(_path), ColorComponents.RedGreenBlueAlpha);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, _handle);
|
||||
GL.TexImage2D(
|
||||
TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
|
||||
image.Width, image.Height, 0,
|
||||
PixelFormat.Rgba, PixelType.UnsignedByte, image.Data
|
||||
);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||
}
|
||||
|
||||
public void Bind(TextureUnit unit = TextureUnit.Texture0)
|
||||
{
|
||||
GL.ActiveTexture(unit);
|
||||
GL.BindTexture(TextureTarget.Texture2D, _handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Textures.cs
Normal file
44
Textures.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
namespace Voxel
|
||||
{
|
||||
public enum Textures : uint
|
||||
{
|
||||
GrassTop,
|
||||
Stone,
|
||||
Dirt,
|
||||
GrassSide,
|
||||
OakPlanks,
|
||||
SmoothStoneSlab,
|
||||
SmoothStone,
|
||||
Bricks,
|
||||
TntSide,
|
||||
TntTop,
|
||||
TntBottom,
|
||||
Cobweb,
|
||||
Rose,
|
||||
Dandelion,
|
||||
Water,
|
||||
OakSapling,
|
||||
Cobblestone,
|
||||
Bedrock,
|
||||
Sand,
|
||||
Gravel,
|
||||
OakSide,
|
||||
OakTop,
|
||||
IronBlock,
|
||||
GoldBlock,
|
||||
DiamondBlock,
|
||||
EmeraldBlock,
|
||||
RedstoneBlock,
|
||||
none0,
|
||||
RedMushroom,
|
||||
BrownMushroom,
|
||||
JungleSapling,
|
||||
none1,
|
||||
GoldOre,
|
||||
IronOre,
|
||||
CoalOre,
|
||||
Bookshelf,
|
||||
MossyCobblestone,
|
||||
Obsidian,
|
||||
}
|
||||
}
|
||||
48
Triangle.cs
48
Triangle.cs
@@ -1,48 +0,0 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
|
||||
public class Triangle
|
||||
{
|
||||
private int _vao;
|
||||
private Shader _shader;
|
||||
|
||||
public Triangle()
|
||||
{
|
||||
string vertexPath = "Shaders/shader.vert";
|
||||
string fragmentPath = "Shaders/shader.frag";
|
||||
_shader = new Shader(vertexPath, fragmentPath);
|
||||
|
||||
float[] vertices =
|
||||
{
|
||||
-0.5f, -0.5f, 0.0f, //top
|
||||
0.5f, -0.5f, 0.0f, // bottom right
|
||||
0.0f, 0.5f, 0.0f // bottom left
|
||||
};
|
||||
|
||||
_vao = GL.GenVertexArray();
|
||||
GL.BindVertexArray(_vao);
|
||||
|
||||
int vbo = GL.GenBuffer();
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
|
||||
|
||||
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
|
||||
_shader.Use();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
GL.BindVertexArray(_vao);
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
UISprite.cs
Normal file
17
UISprite.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK" Version="4.9.4" />
|
||||
<PackageReference Include="StbImageSharp" Version="2.30.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="atlas.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Shaders\shader.frag">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
117
Window.cs
117
Window.cs
@@ -1,30 +1,70 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Common.Input;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class Window(int width, int height, string title) : GameWindow(GameWindowSettings.Default, new NativeWindowSettings() { Size = (width, height), Title = title })
|
||||
public class Window(int width, int height, string title) : GameWindow(GameWindowSettings.Default, new NativeWindowSettings() { ClientSize = (width, height), Title = title })
|
||||
{
|
||||
private Triangle _triangle;
|
||||
public readonly int Width = width;
|
||||
public readonly int Height = height;
|
||||
public uint frames = 0;
|
||||
public double timeElapsed = 0;
|
||||
|
||||
protected override void OnUpdateFrame(FrameEventArgs args)
|
||||
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)
|
||||
{
|
||||
base.OnUpdateFrame(args);
|
||||
base.OnUpdateFrame(e);
|
||||
|
||||
_tickTime += e.Time;
|
||||
|
||||
while (_tickTime >= TICK_LENGTH)
|
||||
{
|
||||
_tickTime -= TICK_LENGTH;
|
||||
Tick(); // run exactly once per tick
|
||||
}
|
||||
|
||||
if (Input.GetKey(Keys.Escape))
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
if (Input.GetKey(Keys.F11))
|
||||
{
|
||||
if (!IsFullscreen)
|
||||
WindowState = WindowState.Fullscreen;
|
||||
else
|
||||
WindowState = WindowState.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRenderFrame(FrameEventArgs args)
|
||||
protected override void OnRenderFrame(FrameEventArgs e)
|
||||
{
|
||||
base.OnRenderFrame(args);
|
||||
base.OnRenderFrame(e);
|
||||
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
_triangle.Draw();
|
||||
frames++;
|
||||
timeElapsed += e.Time;
|
||||
float alpha = (float)(_tickTime / Window.TICK_LENGTH);
|
||||
float deltaTime = (float)e.Time;
|
||||
if (timeElapsed >= 1)
|
||||
{
|
||||
Console.WriteLine("FPS: " + frames.ToString());
|
||||
timeElapsed = 0;
|
||||
frames = 0;
|
||||
}
|
||||
|
||||
Update.Invoke(deltaTime, alpha);
|
||||
|
||||
Renderer.Render();
|
||||
|
||||
SwapBuffers();
|
||||
}
|
||||
@@ -33,6 +73,8 @@ namespace Voxel
|
||||
{
|
||||
base.OnFramebufferResize(e);
|
||||
|
||||
Camera.UpdateSize(e.Width, e.Height);
|
||||
|
||||
GL.Viewport(0, 0, e.Width, e.Height);
|
||||
}
|
||||
|
||||
@@ -40,9 +82,54 @@ namespace Voxel
|
||||
{
|
||||
base.OnLoad();
|
||||
|
||||
_triangle = new Triangle();
|
||||
GL.ClearColor(0.72f, 0.88f, 0.97f, 1f);
|
||||
|
||||
GL.ClearColor(0.5f, 0.5f, 0.5f, 1f);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||
|
||||
CursorState = CursorState.Grabbed;
|
||||
VSync = VSyncMode.On;
|
||||
|
||||
Camera.UpdateSize(Width, Height);
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseMoveEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
|
||||
Camera.UpdateMouse(e.Delta);
|
||||
}
|
||||
|
||||
protected override void OnKeyUp(KeyboardKeyEventArgs e)
|
||||
{
|
||||
base.OnKeyUp(e);
|
||||
Input.SetKey(e.Key, false);
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyboardKeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
185
World.cs
Normal file
185
World.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using OpenTK.Mathematics;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class World
|
||||
{
|
||||
private Dictionary<(int, int), Chunk> _chunks;
|
||||
|
||||
public World()
|
||||
{
|
||||
_chunks = new Dictionary<(int, int), Chunk>();
|
||||
}
|
||||
|
||||
public Chunk GetChunk(int chunkX, int chunkZ)
|
||||
{
|
||||
_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public void AddChunk(Chunk 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)
|
||||
{
|
||||
_chunks.Remove((chunkX, chunkZ));
|
||||
}
|
||||
|
||||
public IEnumerable<Chunk> GetAllChunks()
|
||||
{
|
||||
return _chunks.Values;
|
||||
}
|
||||
|
||||
public Blocks GetBlock(int worldX, int worldY, int worldZ)
|
||||
{
|
||||
int chunkX = worldX / Chunk.Size;
|
||||
int chunkZ = worldZ / Chunk.Size;
|
||||
Chunk chunk = GetChunk(chunkX, chunkZ);
|
||||
if (chunk == null) return 0; // air if chunk not loaded
|
||||
|
||||
int localX = worldX % Chunk.Size;
|
||||
int localZ = worldZ % Chunk.Size;
|
||||
|
||||
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)
|
||||
{
|
||||
int chunkX = worldX / Chunk.Size;
|
||||
int chunkZ = worldZ / Chunk.Size;
|
||||
Chunk chunk = GetChunk(chunkX, chunkZ);
|
||||
if (chunk == null) return;
|
||||
|
||||
int localX = worldX % Chunk.Size;
|
||||
int localZ = worldZ % Chunk.Size;
|
||||
|
||||
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 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 y = (int)MathF.Floor(origin.Y);
|
||||
int z = (int)MathF.Floor(origin.Z);
|
||||
|
||||
int stepX = direction.X > 0 ? 1 : -1;
|
||||
int stepY = direction.Y > 0 ? 1 : -1;
|
||||
int stepZ = direction.Z > 0 ? 1 : -1;
|
||||
|
||||
float tDeltaX = direction.X != 0 ? MathF.Abs(1 / direction.X) : 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 tMaxX = direction.X > 0 ? (MathF.Floor(origin.X) + 1 - origin.X) * tDeltaX
|
||||
: (origin.X - MathF.Floor(origin.X)) * tDeltaX;
|
||||
float tMaxY = direction.Y > 0 ? (MathF.Floor(origin.Y) + 1 - origin.Y) * tDeltaY
|
||||
: (origin.Y - MathF.Floor(origin.Y)) * tDeltaY;
|
||||
float tMaxZ = direction.Z > 0 ? (MathF.Floor(origin.Z) + 1 - origin.Z) * tDeltaZ
|
||||
: (origin.Z - MathF.Floor(origin.Z)) * tDeltaZ;
|
||||
|
||||
float distance = 0f;
|
||||
Vector3i normal = Vector3i.Zero;
|
||||
|
||||
while (distance <= maxDistance)
|
||||
{
|
||||
Blocks block = GetBlock(x, y, z);
|
||||
|
||||
if (block != Blocks.Air)
|
||||
return (true, block, x, y, z, normal);
|
||||
|
||||
if (tMaxX < tMaxY && tMaxX < tMaxZ)
|
||||
{
|
||||
x += stepX;
|
||||
normal = new Vector3i(-stepX, 0, 0);
|
||||
distance = tMaxX;
|
||||
tMaxX += tDeltaX;
|
||||
}
|
||||
else if (tMaxY < tMaxZ)
|
||||
{
|
||||
y += stepY;
|
||||
normal = new Vector3i(0, -stepY, 0);
|
||||
distance = tMaxY;
|
||||
tMaxY += tDeltaY;
|
||||
}
|
||||
else
|
||||
{
|
||||
z += stepZ;
|
||||
normal = new Vector3i(0, 0, -stepZ);
|
||||
distance = tMaxZ;
|
||||
tMaxZ += tDeltaZ;
|
||||
}
|
||||
}
|
||||
|
||||
return (false, Blocks.Air, 0, 0, 0, Vector3i.Zero);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Worldgen.cs
Normal file
79
Worldgen.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user