Compare commits
14 Commits
50f9d4c0c8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a1cdd6b5de | |||
| e96f78b6b0 | |||
|
|
66f26bc2e3 | ||
|
|
2e72dd564e | ||
|
|
35bc49c0f8 | ||
|
|
cad22d3c64 | ||
|
|
7835ade2c1 | ||
| bd2c87ddd1 | |||
|
|
11f76ca429 | ||
|
|
38dccf0a84 | ||
|
|
9a61dfd74c | ||
| 81ef6d8a29 | |||
|
|
e1bb0b3683 | ||
|
|
40dd5c3a9e |
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
55
Camera.cs
55
Camera.cs
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
230
Chunk.cs
230
Chunk.cs
@@ -1,15 +1,38 @@
|
|||||||
namespace Voxel
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Voxel
|
||||||
{
|
{
|
||||||
public class Chunk
|
public class Chunk
|
||||||
{
|
{
|
||||||
public static int Size = 16;
|
public const int Size = 16;
|
||||||
public static int Height = 256;
|
public const int Height = 256;
|
||||||
|
public const int TotalBlocks = Size * Size * Height;
|
||||||
|
|
||||||
public readonly int X;
|
public readonly int X;
|
||||||
public readonly int Y;
|
public readonly int Y;
|
||||||
private ChunkMesh _chunkMesh;
|
private ChunkMesh _chunkMesh;
|
||||||
|
|
||||||
private Dictionary<ushort, BlockData> _blockData;
|
private Dictionary<ushort, BlockData> _blockData;
|
||||||
private Blocks[] _blocks;
|
private Blocks[] _blocks;
|
||||||
|
public Dictionary<Orientation, Chunk> Neighbors = new();
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Dictionary<int, Orientation> FaceToNeighborMap = new()
|
||||||
|
{
|
||||||
|
{ 0, Orientation.West }, // +X face
|
||||||
|
{ 1, Orientation.East }, // -X face
|
||||||
|
{ 4, Orientation.North }, // +Z face
|
||||||
|
{ 5, Orientation.South } // -Z face
|
||||||
|
};
|
||||||
|
|
||||||
public Chunk(int x, int y)
|
public Chunk(int x, int y)
|
||||||
{
|
{
|
||||||
@@ -23,18 +46,22 @@
|
|||||||
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)
|
||||||
{
|
{
|
||||||
|
if (i < 0 || i >= TotalBlocks) return;
|
||||||
_blocks[i] = block;
|
_blocks[i] = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,102 +74,159 @@
|
|||||||
|
|
||||||
public BlockData GetBlockData(int x, int y, int z)
|
public BlockData GetBlockData(int x, int y, int z)
|
||||||
{
|
{
|
||||||
return new BlockData();
|
return new BlockData(); // TODO: Implement
|
||||||
}
|
}
|
||||||
|
|
||||||
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 < Height; y++)
|
||||||
|
{
|
||||||
|
Blocks block = Worldgen.GetBlock(y, height);
|
||||||
|
SetBlock(x, y, z, block, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
UpdateChunkMesh();
|
UpdateChunkMesh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo
|
|
||||||
public (int x, int y, int z) IndexToPosition(int i)
|
public (int x, int y, int z) IndexToPosition(int i)
|
||||||
{
|
{
|
||||||
int x = 0;
|
if (i < 0 || i >= TotalBlocks)
|
||||||
int y = 0;
|
return (0, 0, 0);
|
||||||
int z = 0;
|
|
||||||
|
int y = i >> 8; // y * 256
|
||||||
|
int remainder = i & 0xFF; // Lower 8 bits: x + z * 16
|
||||||
|
int z = remainder >> 4; // z * 16
|
||||||
|
int x = remainder & 0xF; // x
|
||||||
|
|
||||||
return (x, y, z);
|
return (x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetBlockIndex(int x, int y, int z)
|
public (int x, int y, int z) GetWorldCoordinates(int localX, int localY, int localZ)
|
||||||
{
|
{
|
||||||
if (x < 0 || x > 15 || y < 0 || y > 255 || z < 0 || z > 15)
|
return (localX + (X * Size), localY, localZ + (Y * Size));
|
||||||
return -1;
|
|
||||||
|
|
||||||
return x + z * Size + y * Size * Size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly (int dx, int dy, int dz)[] Offsets = new (int, int, int)[6]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private int GetBlockIndex(int x, int y, int z)
|
||||||
{
|
{
|
||||||
( 1, 0, 0), // +X
|
// bit shifting
|
||||||
(-1, 0, 0), // -X
|
if ((uint)x >= Size || (uint)y >= Height || (uint)z >= Size)
|
||||||
( 0, 1, 0), // +Y
|
return -1;
|
||||||
( 0, -1, 0), // -Y
|
|
||||||
( 0, 0, 1), // +Z
|
|
||||||
( 0, 0, -1) // -Z
|
|
||||||
};
|
|
||||||
|
|
||||||
private void UpdateChunkMesh()
|
return x + (z << 4) + (y << 8); // x * 1, z * 16, y * 256
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateChunkMesh()
|
||||||
{
|
{
|
||||||
List<FaceData> faces = new List<FaceData>(Size * Size * Height / 2);
|
List<FaceData> faces = new List<FaceData>(TotalBlocks / 2); // Approximate capacity
|
||||||
|
GenerateFaces(faces);
|
||||||
|
_chunkMesh.SetFaces(faces);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateFaces(List<FaceData> faces)
|
||||||
|
{
|
||||||
for (int x = 0; x < Size; x++)
|
for (int x = 0; x < Size; x++)
|
||||||
{
|
{
|
||||||
for (int z = 0; z < Size; z++)
|
for (int z = 0; z < Size; z++)
|
||||||
{
|
{
|
||||||
for (int y = 0; y < Height; y++)
|
for (int y = 0; y < Height; y++)
|
||||||
{
|
{
|
||||||
for (byte face = 0; face < 6; face++)
|
ProcessBlockFaces(x, y, z, faces);
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
AddFace();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_blocks[ni] == Blocks.Air)
|
|
||||||
{
|
|
||||||
AddFace();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_chunkMesh.SetFaces(faces);
|
}
|
||||||
|
|
||||||
|
private void ProcessBlockFaces(int x, int y, int z, List<FaceData> faces)
|
||||||
|
{
|
||||||
|
int blockIndex = GetBlockIndex(x, y, z);
|
||||||
|
if (blockIndex == -1) return;
|
||||||
|
|
||||||
|
Blocks block = _blocks[blockIndex];
|
||||||
|
if (block == Blocks.Air) return;
|
||||||
|
|
||||||
|
var blockDef = BlockDefinitions.Blocks[block];
|
||||||
|
|
||||||
|
for (int face = 0; face < 6; face++)
|
||||||
|
{
|
||||||
|
if (ShouldAddFace(x, y, z, face))
|
||||||
|
{
|
||||||
|
AddFace(x, y, z, face, blockDef.FaceTextures[face], faces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldAddFace(int x, int y, int z, int face)
|
||||||
|
{
|
||||||
|
var offset = Offsets[face];
|
||||||
|
int nx = x + offset.dx;
|
||||||
|
int ny = y + offset.dy;
|
||||||
|
int nz = z + offset.dz;
|
||||||
|
|
||||||
|
int neighborIndex = GetBlockIndex(nx, ny, nz);
|
||||||
|
if (neighborIndex != -1)
|
||||||
|
{
|
||||||
|
return _blocks[neighborIndex] == Blocks.Air;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsFaceVisibleAtChunkBoundary(x, y, z, face);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsFaceVisibleAtChunkBoundary(int x, int y, int z, int face)
|
||||||
|
{
|
||||||
|
if (!FaceToNeighborMap.TryGetValue(face, out Orientation neighborOrientation))
|
||||||
|
{
|
||||||
|
// top bottom faces always visible at chunk bounds
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Neighbors.TryGetValue(neighborOrientation, out Chunk neighbor) || neighbor == null)
|
||||||
|
{
|
||||||
|
return true; // no neighbor, face is visible
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate coordinates in neighbor chunk
|
||||||
|
var offset = Offsets[face];
|
||||||
|
int localX = x;
|
||||||
|
int localZ = z;
|
||||||
|
|
||||||
|
if (offset.dx != 0) // east, west face
|
||||||
|
{
|
||||||
|
localX = WrapCoordinate(x + offset.dx, Size);
|
||||||
|
}
|
||||||
|
else if (offset.dz != 0) // north, south face
|
||||||
|
{
|
||||||
|
localZ = WrapCoordinate(z + offset.dz, Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return neighbor.GetBlock(localX, y, localZ) == Blocks.Air;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int WrapCoordinate(int coord, int size)
|
||||||
|
{
|
||||||
|
if (coord < 0) return coord + size;
|
||||||
|
if (coord >= size) return coord - size;
|
||||||
|
return coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFace(int x, int y, int z, int face, Textures texture, List<FaceData> faces)
|
||||||
|
{
|
||||||
|
faces.Add(new FaceData
|
||||||
|
{
|
||||||
|
Facing = (Orientation)face,
|
||||||
|
Texture = texture,
|
||||||
|
X = (byte)x,
|
||||||
|
Y = (byte)y,
|
||||||
|
Z = (byte)z
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChunkMesh GetChunkMesh()
|
public ChunkMesh GetChunkMesh()
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Input.cs
21
Input.cs
@@ -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
2506
Noise/FastNoiseLite.cs
Normal file
File diff suppressed because it is too large
Load Diff
141
Player.cs
141
Player.cs
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
Program.cs
32
Program.cs
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
32
Renderer.cs
32
Renderer.cs
@@ -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;
|
||||||
|
|||||||
11
Shader.cs
11
Shader.cs
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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
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
|
||||||
|
}
|
||||||
|
}
|
||||||
54
Window.cs
54
Window.cs
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -70,9 +90,9 @@ namespace Voxel
|
|||||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
153
World.cs
153
World.cs
@@ -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
|
||||||
{
|
{
|
||||||
@@ -7,6 +8,14 @@ namespace Voxel
|
|||||||
{
|
{
|
||||||
private Dictionary<(int, int), Chunk> _chunks;
|
private Dictionary<(int, int), Chunk> _chunks;
|
||||||
|
|
||||||
|
private static readonly Dictionary<Orientation, (int x, int y)> _neighborOffsets = new()
|
||||||
|
{
|
||||||
|
{ Orientation.West, (1, 0) },
|
||||||
|
{ Orientation.East, (-1, 0) },
|
||||||
|
{ Orientation.North, (0, 1) },
|
||||||
|
{ Orientation.South, (0, -1) }
|
||||||
|
};
|
||||||
|
|
||||||
public World()
|
public World()
|
||||||
{
|
{
|
||||||
_chunks = new Dictionary<(int, int), Chunk>();
|
_chunks = new Dictionary<(int, int), Chunk>();
|
||||||
@@ -21,11 +30,54 @@ namespace Voxel
|
|||||||
public void AddChunk(Chunk chunk)
|
public void AddChunk(Chunk chunk)
|
||||||
{
|
{
|
||||||
_chunks[(chunk.X, chunk.Y)] = chunk;
|
_chunks[(chunk.X, chunk.Y)] = chunk;
|
||||||
|
|
||||||
|
UpdateNeighboringChunks(chunk);
|
||||||
|
chunk.UpdateChunkMesh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveChunk(int chunkX, int chunkZ)
|
public void RemoveChunk(int chunkX, int chunkZ)
|
||||||
{
|
{
|
||||||
_chunks.Remove((chunkX, chunkZ));
|
if (_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk))
|
||||||
|
{
|
||||||
|
var neighbors = GetChunkNeighbors(chunk).ToList();
|
||||||
|
_chunks.Remove((chunkX, chunkZ));
|
||||||
|
|
||||||
|
// 3. For each neighbor, remove reference to this chunk
|
||||||
|
foreach (var (orientation, neighbor) in neighbors)
|
||||||
|
{
|
||||||
|
var opposite = GetOppositeOrientation(orientation);
|
||||||
|
neighbor.Neighbors[opposite] = null;
|
||||||
|
neighbor.UpdateChunkMesh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateNeighboringChunks(Chunk chunk)
|
||||||
|
{
|
||||||
|
chunk.Neighbors[Orientation.West] = null;
|
||||||
|
chunk.Neighbors[Orientation.East] = null;
|
||||||
|
chunk.Neighbors[Orientation.North] = null;
|
||||||
|
chunk.Neighbors[Orientation.South] = null;
|
||||||
|
|
||||||
|
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
|
||||||
|
{
|
||||||
|
Orientation opposite = GetOppositeOrientation(orientation);
|
||||||
|
neighbor.Neighbors[opposite] = chunk;
|
||||||
|
chunk.Neighbors[orientation] = neighbor;
|
||||||
|
neighbor.UpdateChunkMesh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Orientation GetOppositeOrientation(Orientation orientation)
|
||||||
|
{
|
||||||
|
return orientation switch
|
||||||
|
{
|
||||||
|
Orientation.West => Orientation.East,
|
||||||
|
Orientation.East => Orientation.West,
|
||||||
|
Orientation.North => Orientation.South,
|
||||||
|
Orientation.South => Orientation.North,
|
||||||
|
_ => orientation
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Chunk> GetAllChunks()
|
public IEnumerable<Chunk> GetAllChunks()
|
||||||
@@ -46,6 +98,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 +121,33 @@ 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 == Chunk.Size - 1 || localX == 0) || (localZ == Chunk.Size - 1 || 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)
|
||||||
|
{
|
||||||
|
int chunkX = chunk.X;
|
||||||
|
int chunkY = chunk.Y;
|
||||||
|
|
||||||
|
foreach (var kv in _neighborOffsets)
|
||||||
|
{
|
||||||
|
Chunk neighbor = GetChunk(chunkX + kv.Value.x, chunkY + kv.Value.y);
|
||||||
|
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 +161,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
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