Compare commits
12 Commits
50f9d4c0c8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
Dirt,
|
||||
OakPlanks,
|
||||
Grass,
|
||||
Bedrock
|
||||
Bedrock,
|
||||
Sand
|
||||
}
|
||||
|
||||
public enum Orientation : byte
|
||||
@@ -74,6 +75,10 @@
|
||||
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
|
||||
|
||||
55
Camera.cs
55
Camera.cs
@@ -7,11 +7,16 @@ namespace Voxel
|
||||
public static Vector3 Position = new Vector3(-8, 16, -8);
|
||||
|
||||
public static float Pitch = -22.5f;
|
||||
public static float Yaw = 45f;
|
||||
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);
|
||||
|
||||
@@ -21,39 +26,15 @@ namespace Voxel
|
||||
{
|
||||
get
|
||||
{
|
||||
float yawOffset = Yaw - 90f;
|
||||
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.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();
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
float sensitivity = 0.1f;
|
||||
@@ -63,14 +44,28 @@ namespace Voxel
|
||||
Pitch = MathHelper.Clamp(Pitch, -89f, 89f);
|
||||
}
|
||||
|
||||
public static void UpdateProjection(int width, int height)
|
||||
public static void UpdateProjection()
|
||||
{
|
||||
float fov = MathHelper.DegreesToRadians(FOV);
|
||||
float aspectRatio = width / (float)height;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
52
Chunk.cs
52
Chunk.cs
@@ -10,6 +10,7 @@
|
||||
|
||||
private Dictionary<ushort, BlockData> _blockData;
|
||||
private Blocks[] _blocks;
|
||||
public Dictionary<Orientation, Chunk> Neighbors = new();
|
||||
|
||||
public Chunk(int x, int y)
|
||||
{
|
||||
@@ -23,15 +24,18 @@
|
||||
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);
|
||||
if (i == -1) return;
|
||||
_blocks[i] = block;
|
||||
UpdateChunkMesh();
|
||||
|
||||
if (updateMesh)
|
||||
{
|
||||
UpdateChunkMesh();
|
||||
Renderer.MarkBuffersDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBlockIndex(int i, Blocks block)
|
||||
{
|
||||
@@ -52,12 +56,20 @@
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
for (int i = 0; i < Size * Size; i++)
|
||||
_blocks[i] = Blocks.Bedrock;
|
||||
for (int i = Size * Size * 1; i < Size * Size * 15; i++)
|
||||
_blocks[i] = Blocks.Stone;
|
||||
for (int i = Size * Size * 15; i < Size * Size * 16; i++)
|
||||
_blocks[i] = Blocks.Grass;
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -71,6 +83,13 @@
|
||||
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)
|
||||
@@ -89,7 +108,7 @@
|
||||
( 0, 0, -1) // -Z
|
||||
};
|
||||
|
||||
private void UpdateChunkMesh()
|
||||
public void UpdateChunkMesh()
|
||||
{
|
||||
List<FaceData> faces = new List<FaceData>(Size * Size * Height / 2);
|
||||
|
||||
@@ -129,6 +148,21 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -15,5 +29,10 @@ namespace Voxel
|
||||
{
|
||||
_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.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
|
||||
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()
|
||||
{
|
||||
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;
|
||||
|
||||
_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();
|
||||
}
|
||||
|
||||
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();
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
32
Renderer.cs
32
Renderer.cs
@@ -1,4 +1,5 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Mathematics;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Voxel
|
||||
@@ -10,7 +11,6 @@ namespace Voxel
|
||||
private static bool _buffersDirty;
|
||||
|
||||
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 readonly Texture _texture;
|
||||
private static World? _world;
|
||||
@@ -32,7 +32,7 @@ namespace Voxel
|
||||
GL.BindVertexArray(_vao);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace Voxel
|
||||
|
||||
_shader.Use();
|
||||
_shader.SetMatrix4("view", Camera.view);
|
||||
_shader.SetVector3("cameraPosition", Camera.Position);
|
||||
_shader.SetMatrix4("projection", Camera.projection);
|
||||
|
||||
if (_buffersDirty)
|
||||
@@ -52,6 +53,7 @@ namespace Voxel
|
||||
}
|
||||
|
||||
RenderWorld();
|
||||
RenderUi();
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (_world == null) return;
|
||||
|
||||
11
Shader.cs
11
Shader.cs
@@ -67,6 +67,17 @@ namespace Voxel
|
||||
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);
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
|
||||
out vec4 FragColor;
|
||||
in vec2 fragUV;
|
||||
in vec3 fragPos;
|
||||
in float lighting;
|
||||
|
||||
uniform sampler2D uTexture;
|
||||
uniform vec3 cameraPosition;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 texColor = texture(uTexture, fragUV);
|
||||
FragColor = vec4(texColor.rgb * lighting, texColor.a);
|
||||
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);
|
||||
}
|
||||
@@ -20,57 +20,58 @@ 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(0.5, -0.5, -0.5),
|
||||
vec3(0.5, 0.5, 0.5),
|
||||
vec3(0.5, -0.5, 0.5),
|
||||
vec3(0.5, 0.5, 0.5),
|
||||
vec3(0.5, -0.5, -0.5),
|
||||
vec3(0.5, 0.5, -0.5)
|
||||
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.5, -0.5, 0.5),
|
||||
vec3(-0.5, 0.5, -0.5),
|
||||
vec3(-0.5, -0.5, -0.5),
|
||||
vec3(-0.5, 0.5, -0.5),
|
||||
vec3(-0.5, -0.5, 0.5),
|
||||
vec3(-0.5, 0.5, 0.5)
|
||||
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.5, 0.5, -0.5),
|
||||
vec3( 0.5, 0.5, 0.5),
|
||||
vec3( 0.5, 0.5, -0.5),
|
||||
vec3( 0.5, 0.5, 0.5),
|
||||
vec3(-0.5, 0.5, -0.5),
|
||||
vec3(-0.5, 0.5, 0.5)
|
||||
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](
|
||||
vec3(-0.5, -0.5, 0.5),
|
||||
vec3( 0.5, -0.5, -0.5),
|
||||
vec3( 0.5, -0.5, 0.5),
|
||||
vec3( 0.5, -0.5, -0.5),
|
||||
vec3(-0.5, -0.5, 0.5),
|
||||
vec3(-0.5, -0.5, -0.5)
|
||||
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( 0.5, -0.5, 0.5),
|
||||
vec3(-0.5, 0.5, 0.5),
|
||||
vec3(-0.5, -0.5, 0.5),
|
||||
vec3(-0.5, 0.5, 0.5),
|
||||
vec3( 0.5, -0.5, 0.5),
|
||||
vec3( 0.5, 0.5, 0.5)
|
||||
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.5, -0.5, -0.5),
|
||||
vec3( 0.5, 0.5, -0.5),
|
||||
vec3( 0.5, -0.5, -0.5),
|
||||
vec3( 0.5, 0.5, -0.5),
|
||||
vec3(-0.5, -0.5, -0.5),
|
||||
vec3(-0.5, 0.5, -0.5)
|
||||
vec3(0, 0, 0),
|
||||
vec3(1, 1, 0),
|
||||
vec3(1, 0, 0),
|
||||
vec3(1, 1, 0),
|
||||
vec3(0, 0, 0),
|
||||
vec3(0, 1, 0)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -111,5 +112,6 @@ void main()
|
||||
fragUV = uv;
|
||||
lighting = lightMult[facing];
|
||||
|
||||
fragPos = vec3(worldPos.x, worldPos.y, worldPos.z);
|
||||
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
|
||||
}
|
||||
}
|
||||
52
Window.cs
52
Window.cs
@@ -2,6 +2,7 @@
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Common.Input;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
@@ -9,22 +10,38 @@ namespace Voxel
|
||||
{
|
||||
public readonly int Width = width;
|
||||
public readonly int Height = height;
|
||||
public Player Player;
|
||||
public uint frames = 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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
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++;
|
||||
timeElapsed += e.Time;
|
||||
float alpha = (float)(_tickTime / Window.TICK_LENGTH);
|
||||
float deltaTime = (float)e.Time;
|
||||
if (timeElapsed >= 1)
|
||||
{
|
||||
Console.WriteLine("FPS: " + frames.ToString());
|
||||
@@ -43,7 +62,8 @@ namespace Voxel
|
||||
frames = 0;
|
||||
}
|
||||
|
||||
Camera.Update((float)e.Time);
|
||||
Update.Invoke(deltaTime, alpha);
|
||||
|
||||
Renderer.Render();
|
||||
|
||||
SwapBuffers();
|
||||
@@ -53,7 +73,7 @@ namespace Voxel
|
||||
{
|
||||
base.OnFramebufferResize(e);
|
||||
|
||||
Camera.UpdateProjection(e.Width, e.Height);
|
||||
Camera.UpdateSize(e.Width, e.Height);
|
||||
|
||||
GL.Viewport(0, 0, e.Width, e.Height);
|
||||
}
|
||||
@@ -72,7 +92,7 @@ namespace Voxel
|
||||
CursorState = CursorState.Grabbed;
|
||||
VSync = VSyncMode.On;
|
||||
|
||||
Camera.UpdateProjection(Width, Height);
|
||||
Camera.UpdateSize(Width, Height);
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseMoveEventArgs e)
|
||||
@@ -93,5 +113,23 @@ namespace Voxel
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
102
World.cs
102
World.cs
@@ -1,5 +1,6 @@
|
||||
using OpenTK.Mathematics;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
@@ -21,6 +22,22 @@ namespace Voxel
|
||||
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)
|
||||
@@ -46,6 +63,18 @@ namespace Voxel
|
||||
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;
|
||||
@@ -57,9 +86,41 @@ namespace Voxel
|
||||
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 (bool success, Blocks block, int x, int y, int z) Raycast(Vector3 origin, Vector3 direction, float maxDistance)
|
||||
public IEnumerable<(Orientation orientation, Chunk neighbor)> GetChunkNeighbors(Chunk chunk)
|
||||
{
|
||||
Dictionary<Orientation, (int x, int y)> offsets = new()
|
||||
{
|
||||
{ Orientation.West, (1, 0) },
|
||||
{ Orientation.East, (-1, 0) },
|
||||
{ Orientation.North, (0, 1) },
|
||||
{ Orientation.South, (0, -1) }
|
||||
};
|
||||
|
||||
foreach (var kv in offsets)
|
||||
{
|
||||
int nx = chunk.X + kv.Value.x;
|
||||
int ny = chunk.Y + kv.Value.y;
|
||||
|
||||
Chunk neighbor = GetChunk(nx, ny);
|
||||
if (neighbor != null)
|
||||
yield return (kv.Key, neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
public (bool success, Blocks block, int x, int y, int z, Vector3i normal) Raycast(Vector3 origin, Vector3 direction, float maxDistance)
|
||||
{
|
||||
int x = (int)MathF.Floor(origin.X);
|
||||
int y = (int)MathF.Floor(origin.Y);
|
||||
@@ -73,60 +134,47 @@ namespace Voxel
|
||||
float tDeltaY = direction.Y != 0 ? MathF.Abs(1 / direction.Y) : float.MaxValue;
|
||||
float tDeltaZ = direction.Z != 0 ? MathF.Abs(1 / direction.Z) : float.MaxValue;
|
||||
|
||||
float tMaxX = direction.X > 0
|
||||
? (MathF.Floor(origin.X) + 1 - origin.X) * tDeltaX
|
||||
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
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
// step to next voxel
|
||||
if (tMaxX < tMaxY)
|
||||
{
|
||||
if (tMaxX < tMaxZ)
|
||||
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
|
||||
{
|
||||
z += stepZ;
|
||||
distance = tMaxZ;
|
||||
tMaxZ += tDeltaZ;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tMaxY < tMaxZ)
|
||||
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);
|
||||
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