Add chunk mesh generation and basic rendering

Woot!
This commit is contained in:
Drew DeVault 2015-05-13 23:09:49 -06:00
parent 0149af3ba2
commit 9385568ab7
10 changed files with 254 additions and 14 deletions

@ -88,6 +88,12 @@ smaller changes like lighting improvements and bow usage mechanics.
Finally, if we've got a nice mature project and a good community going, modding
support would be great.
## Textures
TrueCraft currently distributes
[ProgrammerArt](https://github.com/deathcap/ProgrammerArt) as the default
texture pack.
## Blah blah blah
TrueCraft is not associated with Mojang or Minecraft in any sort of official

@ -2,6 +2,15 @@
using System.Collections.Concurrent;
using System.Threading;
using System.Linq;
using TrueCraft.Client.Linux.Rendering;
using TrueCraft.Core.World;
using TrueCraft.API;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Vector2 = Microsoft.Xna.Framework.Vector2;
using Vector3 = Microsoft.Xna.Framework.Vector3;
using TrueCraft.API.Logic;
namespace TrueCraft.Client.Linux
{
@ -11,13 +20,20 @@ namespace TrueCraft.Client.Linux
/// </summary>
public class ChunkConverter
{
public delegate void ChunkConsumer(Mesh mesh);
private ConcurrentQueue<ReadOnlyChunk> ChunkQueue { get; set; }
private Thread ChunkWorker { get; set; }
private GraphicsDevice Graphics { get; set; }
private IBlockRepository BlockRepository { get; set; }
private ChunkConsumer Consumer { get; set; }
public ChunkConverter()
public ChunkConverter(GraphicsDevice graphics, IBlockRepository blockRepository)
{
ChunkQueue = new ConcurrentQueue<ReadOnlyChunk>();
ChunkWorker = new Thread(new ThreadStart(DoChunks));
BlockRepository = blockRepository;
Graphics = graphics;
}
public void QueueChunk(ReadOnlyChunk chunk)
@ -25,8 +41,9 @@ namespace TrueCraft.Client.Linux
ChunkQueue.Enqueue(chunk);
}
public void Start()
public void Start(ChunkConsumer consumer)
{
Consumer = consumer;
ChunkWorker.Start();
}
@ -43,13 +60,75 @@ namespace TrueCraft.Client.Linux
ReadOnlyChunk chunk;
if (ChunkQueue.Any())
{
while (!ChunkQueue.TryDequeue(out chunk)) { }
// TODO: Create verticies from chunk
Console.WriteLine("Chunk worker received chunk at {0}, {1}", chunk.X, chunk.Z);
while (!ChunkQueue.TryDequeue(out chunk))
{
}
var mesh = ProcessChunk(chunk);
Consumer(mesh);
}
if (idle)
Thread.Sleep(100);
}
}
private Mesh ProcessChunk(ReadOnlyChunk chunk)
{
var verticies = new List<VertexPositionNormalTexture>();
var indicies = new List<int>();
for (byte x = 0; x < Chunk.Width; x++)
{
for (byte z = 0; z < Chunk.Depth; z++)
{
//var height = chunk.Chunk.GetHeight(x, z);
for (byte y = 0; y < Chunk.Height; y++)
{
var coords = new Coordinates3D(x, y, z);
var id = chunk.GetBlockId(coords);
var provider = BlockRepository.GetBlockProvider(id);
var textureMap = provider.GetTextureMap(chunk.GetMetadata(coords));
if (textureMap == null)
{
// TODO: handle this better
textureMap = new Tuple<int, int>(0, 0);
}
if (id != 0)
{
int[] i;
var v = CreateUniformCube(new Vector3(chunk.X * Chunk.Width + x, y, chunk.Z * Chunk.Depth + z),
textureMap, indicies.Count, out i);
verticies.AddRange(v);
indicies.AddRange(i);
}
}
}
}
Console.WriteLine("Created mesh for {0}, {0}", chunk.X, chunk.Z);
return new Mesh(Graphics, verticies.ToArray(), indicies.ToArray());
}
private VertexPositionNormalTexture[] CreateUniformCube(Vector3 offset, Tuple<int, int> textureMap, int indiciesOffset, out int[] indicies)
{
var texCoords = new Vector2(textureMap.Item1, textureMap.Item2);
var texture = new[]
{
texCoords + Vector2.UnitX + Vector2.UnitY,
texCoords + Vector2.UnitY,
texCoords,
texCoords + Vector2.UnitX
};
for (int i = 0; i < texture.Length; i++)
texture[i] *= new Vector2(16f / 256f);
indicies = new int[6 * 6];
var verticies = new VertexPositionNormalTexture[4 * 6];
int[] _indicies;
for (int _side = 0; _side < 6; _side++)
{
var side = (Mesh.CubeFace)_side;
var quad = Mesh.CreateQuad(side, offset, texture, indiciesOffset, out _indicies);
Array.Copy(quad, 0, verticies, _side * 4, 4);
Array.Copy(_indicies, 0, indicies, _side * 6, 6);
}
return verticies;
}
}
}

Binary file not shown.

After

(image error) Size: 48 KiB

Binary file not shown.

After

(image error) Size: 3.1 KiB

Binary file not shown.

After

(image error) Size: 42 KiB

@ -160,6 +160,9 @@ namespace TrueCraft.Client.Linux
Position = newPosition;
}
public float Yaw { get; set; }
public float Pitch { get; set; }
internal Vector3 _Position;
public Vector3 Position
{
@ -170,7 +173,7 @@ namespace TrueCraft.Client.Linux
set
{
if (_Position != value)
QueuePacket(new PlayerPositionAndLookPacket(value.X, value.Y, value.Y + Height, value.Z, 0, 0, false));
QueuePacket(new PlayerPositionAndLookPacket(value.X, value.Y, value.Y + Height, value.Z, Yaw, Pitch, false));
_Position = value;
}
}

@ -21,7 +21,7 @@ namespace TrueCraft.Client.Linux
UnloadChunks = true;
}
public short GetBlockID(Coordinates3D coordinates)
public byte GetBlockID(Coordinates3D coordinates)
{
return World.GetBlockID(coordinates);
}
@ -89,7 +89,7 @@ namespace TrueCraft.Client.Linux
Chunk = chunk;
}
public short GetBlockId(Coordinates3D coordinates)
public byte GetBlockId(Coordinates3D coordinates)
{
return Chunk.GetBlockID(coordinates);
}

@ -1,5 +1,6 @@
using System;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
namespace TrueCraft.Client.Linux.Rendering
{
@ -11,9 +12,9 @@ namespace TrueCraft.Client.Linux.Rendering
public Mesh(GraphicsDevice device, VertexPositionNormalTexture[] verticies, int[] indicies)
{
Verticies = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration,
verticies.Length, BufferUsage.None);
verticies.Length, BufferUsage.WriteOnly);
Verticies.SetData(verticies);
Indicies = new IndexBuffer(device, IndexElementSize.ThirtyTwoBits, indicies.Length, BufferUsage.None);
Indicies = new IndexBuffer(device, typeof(int), indicies.Length, BufferUsage.WriteOnly);
Indicies.SetData(indicies);
}
@ -36,5 +37,96 @@ namespace TrueCraft.Client.Linux.Rendering
0, 0, Indicies.IndexCount, 0, Indicies.IndexCount / 3);
}
}
public static VertexPositionNormalTexture[] CreateQuad(CubeFace face, Vector3 offset, Vector2[] texture, int indiciesOffset, out int[] indicies)
{
indicies = new[] { 1, 0, 3, 1, 3, 2 };
for (int i = 0; i < indicies.Length; i++)
indicies[i] += ((int)face * 6) + indiciesOffset;
var quad = new VertexPositionNormalTexture[4];
var unit = CubeMesh[(int)face];
var normal = CubeNormals[(int)face];
for (int i = 0; i < 4; i++)
{
quad[i] = new VertexPositionNormalTexture(offset + unit[i], normal, texture[i]);
}
return quad;
}
public enum CubeFace
{
PositiveZ = 0,
NegativeZ,
PositiveX,
NegativeX,
PositiveY,
NegativeY
}
private static Vector3[][] CubeMesh;
private static readonly Vector3[] CubeNormals =
{
new Vector3(0, 0, 1),
new Vector3(0, 0, -1),
new Vector3(1, 0, 0),
new Vector3(-1, 0, 0),
new Vector3(0, 1, 0),
new Vector3(0, -1, 0)
};
static Mesh()
{
CubeMesh = new Vector3[6][];
CubeMesh[0] = new[] // Positive Z face
{
new Vector3(0.5f, 0.5f, 0.5f),
new Vector3(-0.5f, 0.5f, 0.5f),
new Vector3(-0.5f, -0.5f, 0.5f),
new Vector3(0.5f, -0.5f, 0.5f)
};
CubeMesh[1] = new[] // Negative Z face
{
new Vector3(-0.5f, 0.5f, -0.5f),
new Vector3(0.5f, 0.5f, -0.5f),
new Vector3(0.5f, -0.5f, -0.5f),
new Vector3(-0.5f, -0.5f, -0.5f)
};
CubeMesh[2] = new[] // Positive X face
{
new Vector3(0.5f, 0.5f, -0.5f),
new Vector3(0.5f, 0.5f, 0.5f),
new Vector3(0.5f, -0.5f, 0.5f),
new Vector3(0.5f, -0.5f, -0.5f)
};
CubeMesh[3] = new[] // Negative X face
{
new Vector3(-0.5f, 0.5f, 0.5f),
new Vector3(-0.5f, 0.5f, -0.5f),
new Vector3(-0.5f, -0.5f, -0.5f),
new Vector3(-0.5f, -0.5f, 0.5f)
};
CubeMesh[4] = new[] // Positive Y face
{
new Vector3(-0.5f, 0.5f, -0.5f),
new Vector3(-0.5f, 0.5f, 0.5f),
new Vector3(0.5f, 0.5f, 0.5f),
new Vector3(0.5f, 0.5f, -0.5f)
};
CubeMesh[5] = new[] // Negative Y face
{
new Vector3(0.5f, -0.5f, -0.5f),
new Vector3(0.5f, -0.5f, 0.5f),
new Vector3(-0.5f, -0.5f, 0.5f),
new Vector3(-0.5f, -0.5f, -0.5f)
};
}
}
}

@ -118,5 +118,11 @@
<None Include="Content\dejavu.fnt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Content\items.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Content\terrain.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

@ -7,6 +7,7 @@ using TrueCraft.Client.Linux.Interface;
using System.IO;
using System.Net;
using TrueCraft.API;
using TrueCraft.Client.Linux.Rendering;
namespace TrueCraft.Client.Linux
{
@ -20,6 +21,10 @@ namespace TrueCraft.Client.Linux
private IPEndPoint EndPoint { get; set; }
private ChunkConverter ChunkConverter { get; set; }
private DateTime NextPhysicsUpdate { get; set; }
private List<Mesh> ChunkMeshes { get; set; }
private object ChunkMeshesLock = new object();
private BasicEffect effect;
public TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint)
{
@ -31,9 +36,8 @@ namespace TrueCraft.Client.Linux
Graphics.PreferredBackBufferHeight = 720;
Client = client;
EndPoint = endPoint;
ChunkConverter = new ChunkConverter();
Client.ChunkLoaded += (sender, e) => ChunkConverter.QueueChunk(e.Chunk);
NextPhysicsUpdate = DateTime.MinValue;
ChunkMeshes = new List<Mesh>();
}
protected override void Initialize()
@ -41,8 +45,17 @@ namespace TrueCraft.Client.Linux
Interfaces = new List<IGameInterface>();
SpriteBatch = new SpriteBatch(GraphicsDevice);
base.Initialize(); // (calls LoadContent)
ChunkConverter.Start();
ChunkConverter = new ChunkConverter(Graphics.GraphicsDevice, Client.World.World.BlockRepository);
Client.ChunkLoaded += (sender, e) => ChunkConverter.QueueChunk(e.Chunk);
ChunkConverter.Start(mesh =>
{
lock (ChunkMeshesLock)
ChunkMeshes.Add(mesh);
});
Client.Connect(EndPoint);
var centerX = GraphicsDevice.Viewport.Width / 2;
var centerY = GraphicsDevice.Viewport.Height / 2;
Mouse.SetPosition(centerX, centerY);
}
protected override void LoadContent()
@ -53,6 +66,9 @@ namespace TrueCraft.Client.Linux
var fontTexture = Content.Load<Texture2D>("dejavu_0.png");
DejaVu = new FontRenderer(fontFile, fontTexture);
Interfaces.Add(new ChatInterface(Client, DejaVu));
effect = new BasicEffect(GraphicsDevice);
effect.TextureEnabled = true;
effect.Texture = Texture2D.FromStream(GraphicsDevice, File.OpenRead("Content/terrain.png"));
base.LoadContent();
}
@ -66,7 +82,6 @@ namespace TrueCraft.Client.Linux
{
if (state.IsKeyDown(Keys.Escape))
Exit();
// TODO: Handle rotation
// TODO: Rebindable keys
// TODO: Horizontal terrain collisions
TrueCraft.API.Vector3 delta = TrueCraft.API.Vector3.Zero;
@ -79,6 +94,16 @@ namespace TrueCraft.Client.Linux
if (state.IsKeyDown(Keys.Down) || state.IsKeyDown(Keys.S))
delta = TrueCraft.API.Vector3.Backwards;
Client.Position += delta * (gameTime.ElapsedGameTime.TotalSeconds * 4.3717); // Note: 4.3717 is the speed of a Minecraft player in m/s
var centerX = GraphicsDevice.Viewport.Width / 2;
var centerY = GraphicsDevice.Viewport.Height / 2;
var mouse = Mouse.GetState();
var look = new Vector2(centerX - mouse.Position.X, centerY - mouse.Position.Y) * 0.2f; // TODO: fewer magic numbers
Mouse.SetPosition(centerX, centerY);
Client.Yaw += look.X;
Client.Pitch += look.Y;
Client.Yaw %= 360;
Client.Pitch %= 360;
}
protected override void Update(GameTime gameTime)
@ -99,12 +124,41 @@ namespace TrueCraft.Client.Linux
protected override void Draw(GameTime gameTime)
{
// TODO: Move camera logic elsewhere
var player = new Microsoft.Xna.Framework.Vector3(
(float)Client.Position.X,
(float)Client.Position.Y,
(float)Client.Position.Z);
var lookAt = Microsoft.Xna.Framework.Vector3.Transform(
new Microsoft.Xna.Framework.Vector3(0, 0, -1),
Matrix.CreateRotationX(MathHelper.ToRadians(Client.Pitch)) * Matrix.CreateRotationY(MathHelper.ToRadians(Client.Yaw)));
var cameraMatrix = Matrix.CreateLookAt(
player, player + lookAt,
Microsoft.Xna.Framework.Vector3.Up);
var projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(60f), GraphicsDevice.Viewport.AspectRatio, 0.3f, 10000f);
Graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
Graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
effect.View = cameraMatrix;
effect.Projection = projectionMatrix;
effect.World = Matrix.CreateTranslation(Microsoft.Xna.Framework.Vector3.Zero);
lock (ChunkMeshesLock)
{
foreach (var chunk in ChunkMeshes)
chunk.Draw(effect);
}
SpriteBatch.Begin();
foreach (var i in Interfaces)
{
i.DrawSprites(gameTime, SpriteBatch);
}
DejaVu.DrawText(SpriteBatch, 0, 500, string.Format("X: {0}, Y: {1}, Z: {2}", Client.Position.X, Client.Position.Y, Client.Position.Z));
DejaVu.DrawText(SpriteBatch, 0, 530, string.Format("Yaw: {0}, Pitch: {1}", Client.Yaw, Client.Pitch));
SpriteBatch.End();
base.Draw(gameTime);
}