diff --git a/TrueCraft.Client/Rendering/Camera.cs b/TrueCraft.Client/Rendering/Camera.cs
index 9cd06d2..30f6967 100644
--- a/TrueCraft.Client/Rendering/Camera.cs
+++ b/TrueCraft.Client/Rendering/Camera.cs
@@ -136,6 +136,20 @@ namespace TrueCraft.Client.Rendering
effect.World = Matrix.Identity;
}
+ ///
+ /// Applies this camera to the specified effect.
+ ///
+ /// The effect to apply this camera to.
+ public void ApplyTo(AlphaTestEffect effect)
+ {
+ if (_isDirty)
+ Recalculate();
+
+ effect.View = _view;
+ effect.Projection = _projection;
+ effect.World = Matrix.Identity;
+ }
+
///
/// Returns the bounding frustum calculated for this camera.
///
diff --git a/TrueCraft.Client/Rendering/ChunkMesh.cs b/TrueCraft.Client/Rendering/ChunkMesh.cs
index 26b934b..3ed766a 100644
--- a/TrueCraft.Client/Rendering/ChunkMesh.cs
+++ b/TrueCraft.Client/Rendering/ChunkMesh.cs
@@ -29,6 +29,23 @@ namespace TrueCraft.Client.Rendering
Vertices = vertices;
SetSubmesh(0, indices);
}
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public ChunkMesh(ReadOnlyChunk chunk, GraphicsDevice device, VertexPositionNormalTexture[] vertices, int[] opaqueIndices, int[] transparentIndices)
+ : base(device, 2, true)
+ {
+ Chunk = chunk;
+ Vertices = vertices;
+ SetSubmesh(0, opaqueIndices);
+ SetSubmesh(1, transparentIndices);
+ }
///
///
diff --git a/TrueCraft.Client/Rendering/ChunkRenderer.cs b/TrueCraft.Client/Rendering/ChunkRenderer.cs
index accd367..69e1259 100644
--- a/TrueCraft.Client/Rendering/ChunkRenderer.cs
+++ b/TrueCraft.Client/Rendering/ChunkRenderer.cs
@@ -19,7 +19,7 @@ namespace TrueCraft.Client.Rendering
/// A daemon of sorts that creates meshes from chunks.
/// Passing meshes back is NOT thread-safe.
///
- public class ChunkRenderer
+ public class ChunkRenderer : Renderer
{
public class ChunkSorter : Comparer
{
@@ -39,85 +39,16 @@ namespace TrueCraft.Client.Rendering
}
}
- public class MeshGeneratedEventArgs : EventArgs
- {
- public bool Transparent { get; set; }
- public ChunkMesh Mesh { get; set; }
-
- public MeshGeneratedEventArgs(ChunkMesh mesh, bool transparent)
- {
- Transparent = transparent;
- Mesh = mesh;
- }
- }
-
- public event EventHandler MeshGenerated;
-
- private ConcurrentQueue ChunkQueue { get; set; }
private GraphicsDevice GraphicsDevice { get; set; }
private IBlockRepository BlockRepository { get; set; }
- private List> RenderThreads { get; set; }
public ChunkRenderer(GraphicsDevice graphics, IBlockRepository blockRepository)
+ : base()
{
- ChunkQueue = new ConcurrentQueue();
- RenderThreads = new List>();
- var threads = Environment.ProcessorCount - 2;
- if (threads < 1)
- threads = 1;
- for (int i = 0; i < threads; i++)
- {
- var token = new CancellationTokenSource();
- var worker = new Thread(() => DoChunks(token));
- worker.IsBackground = true;
- RenderThreads.Add(new Tuple(worker, token));
- }
-
BlockRepository = blockRepository;
GraphicsDevice = graphics;
}
- public void QueueChunk(ReadOnlyChunk chunk)
- {
- ChunkQueue.Enqueue(chunk);
- }
-
- public void QueueHighPriorityChunk(ReadOnlyChunk chunk)
- {
- // TODO: Overwrite existing chunks
- Task.Factory.StartNew(() =>
- {
- ProcessChunk(chunk, new RenderState());
- });
- }
-
- public void Start()
- {
- RenderThreads.ForEach(t => t.Item1.Start());
- }
-
- public void Stop()
- {
- RenderThreads.ForEach(t => t.Item2.Cancel());
- }
-
- private void DoChunks(CancellationTokenSource token)
- {
- var state = new RenderState();
- while (true)
- {
- if (token.Token.IsCancellationRequested)
- return;
-
- ReadOnlyChunk chunk;
- if (ChunkQueue.TryDequeue(out chunk))
- {
- ProcessChunk(chunk, state);
- }
- Thread.Sleep(1);
- }
- }
-
private static readonly Coordinates3D[] AdjacentCoordinates =
{
Coordinates3D.Up,
@@ -128,20 +59,30 @@ namespace TrueCraft.Client.Rendering
Coordinates3D.West
};
+ protected override bool TryRender(ReadOnlyChunk item, out Mesh result)
+ {
+ var state = new RenderState();
+ ProcessChunk(item, state);
+
+ result = new ChunkMesh(item, GraphicsDevice, state.Verticies.ToArray(),
+ state.OpaqueIndicies.ToArray(), state.TransparentIndicies.ToArray());
+
+ return (result != null);
+
+ }
+
private class RenderState
{
- public readonly List OpaqueVerticies = new List();
+ public readonly List Verticies = new List();
public readonly List OpaqueIndicies = new List();
- public readonly List TransparentVerticies = new List();
public readonly List TransparentIndicies = new List();
public readonly HashSet DrawableCoordinates = new HashSet();
}
private void ProcessChunk(ReadOnlyChunk chunk, RenderState state)
{
- state.OpaqueVerticies.Clear();
+ state.Verticies.Clear();
state.OpaqueIndicies.Clear();
- state.TransparentVerticies.Clear();
state.TransparentIndicies.Clear();
state.DrawableCoordinates.Clear();
@@ -198,8 +139,8 @@ namespace TrueCraft.Client.Rendering
int[] i;
var v = BlockRenderer.RenderBlock(provider, descriptor,
new Vector3(chunk.X * Chunk.Width + coords.X, coords.Y, chunk.Z * Chunk.Depth + coords.Z),
- state.OpaqueVerticies.Count, out i);
- state.OpaqueVerticies.AddRange(v);
+ state.Verticies.Count, out i);
+ state.Verticies.AddRange(v);
state.OpaqueIndicies.AddRange(i);
}
else
@@ -207,18 +148,11 @@ namespace TrueCraft.Client.Rendering
int[] i;
var v = BlockRenderer.RenderBlock(provider, descriptor,
new Vector3(chunk.X * Chunk.Width + coords.X, coords.Y, chunk.Z * Chunk.Depth + coords.Z),
- state.TransparentVerticies.Count, out i);
- state.TransparentVerticies.AddRange(v);
+ state.Verticies.Count, out i);
+ state.Verticies.AddRange(v);
state.TransparentIndicies.AddRange(i);
}
}
- var opaque = new ChunkMesh(chunk, GraphicsDevice, state.OpaqueVerticies.ToArray(), state.OpaqueIndicies.ToArray());
- var transparent = new ChunkMesh(chunk, GraphicsDevice, state.TransparentVerticies.ToArray(), state.TransparentIndicies.ToArray());
- if (MeshGenerated != null)
- {
- MeshGenerated(this, new MeshGeneratedEventArgs(opaque, false));
- MeshGenerated(this, new MeshGeneratedEventArgs(transparent, true));
- }
}
}
}
diff --git a/TrueCraft.Client/Rendering/Mesh.cs b/TrueCraft.Client/Rendering/Mesh.cs
index 43d8cd8..9e937ca 100644
--- a/TrueCraft.Client/Rendering/Mesh.cs
+++ b/TrueCraft.Client/Rendering/Mesh.cs
@@ -150,7 +150,7 @@ namespace TrueCraft.Client.Rendering
if ((index < 0) || (index > _indices.Length))
throw new ArgumentOutOfRangeException();
- if (_vertices == null || _vertices.IsDisposed || _indices[index] == null || _indices[index].IsDisposed)
+ if (_vertices == null || _vertices.IsDisposed || _indices[index] == null || _indices[index].IsDisposed || _indices[index].IndexCount < 3)
return; // Invalid state for rendering, just return.
effect.GraphicsDevice.SetVertexBuffer(_vertices);
diff --git a/TrueCraft.Client/Rendering/Renderer.cs b/TrueCraft.Client/Rendering/Renderer.cs
new file mode 100644
index 0000000..8591742
--- /dev/null
+++ b/TrueCraft.Client/Rendering/Renderer.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Threading;
+using System.Collections.Concurrent;
+
+namespace TrueCraft.Client.Rendering
+{
+ ///
+ /// Abstract base class for renderers of meshes.
+ ///
+ /// The object to render into a mesh.
+ public abstract class Renderer : IDisposable
+ {
+ private readonly object _syncLock =
+ new object();
+
+ ///
+ ///
+ ///
+ public event EventHandler> MeshCompleted;
+
+ private volatile bool _isRunning;
+ private Thread _rendererThread;
+ private ConcurrentQueue _items, _priorityItems;
+ private volatile bool _isDisposed;
+
+ ///
+ /// Gets whether this renderer is running.
+ ///
+ public bool IsRunning
+ {
+ get
+ {
+ if (_isDisposed)
+ throw new ObjectDisposedException(GetType().Name);
+ return _isRunning;
+ }
+ }
+
+ ///
+ /// Gets whether this renderer is disposed of.
+ ///
+ public bool IsDisposed
+ {
+ get { return _isDisposed; }
+ }
+
+ ///
+ ///
+ ///
+ protected Renderer()
+ {
+ lock (_syncLock)
+ {
+ _isRunning = false;
+ _rendererThread = new Thread(DoRendering);
+ _items = new ConcurrentQueue(); _priorityItems = new ConcurrentQueue();
+ _isDisposed = false;
+ }
+ }
+
+ ///
+ /// Starts this renderer.
+ ///
+ public void Start()
+ {
+ if (_isDisposed)
+ throw new ObjectDisposedException(GetType().Name);
+
+ if (_isRunning) return;
+ lock (_syncLock)
+ {
+ _isRunning = true;
+ _rendererThread.Start(null);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ private void DoRendering(object obj)
+ {
+ while (_isRunning)
+ {
+ var item = default(T);
+ var result = default(Mesh);
+
+ lock (_syncLock)
+ {
+ if (_priorityItems.TryDequeue(out item) && TryRender(item, out result))
+ {
+ var args = new RendererEventArgs(item, result, true);
+ if (MeshCompleted != null)
+ MeshCompleted(this, args);
+ }
+ else if (_items.TryDequeue(out item) && TryRender(item, out result))
+ {
+ var args = new RendererEventArgs(item, result, false);
+ if (MeshCompleted != null)
+ MeshCompleted(this, args);
+ }
+ }
+
+ if (item == null) // We don't have any work, so sleep for a bit.
+ Thread.Sleep(100);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected abstract bool TryRender(T item, out Mesh result);
+
+ ///
+ /// Stops this renderer.
+ ///
+ public void Stop()
+ {
+ if (_isDisposed)
+ throw new ObjectDisposedException(GetType().Name);
+
+ if (!_isRunning) return;
+ lock (_syncLock)
+ {
+ _isRunning = false;
+ _rendererThread.Join();
+ }
+ }
+
+ ///
+ /// Enqueues an item to this renderer for rendering.
+ ///
+ ///
+ ///
+ public void Enqueue(T item, bool hasPriority = false)
+ {
+ if (_isDisposed)
+ throw new ObjectDisposedException(GetType().Name);
+
+ if (!_isRunning) return;
+ lock (_syncLock)
+ {
+ if (hasPriority)
+ _priorityItems.Enqueue(item);
+ else
+ _items.Enqueue(item);
+ }
+ }
+
+ ///
+ /// Disposes of this renderer.
+ ///
+ public void Dispose()
+ {
+ if (_isDisposed)
+ return;
+
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Disposes of this renderer.
+ ///
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Stop();
+ lock (_syncLock)
+ {
+ _rendererThread = null;
+ _items = null; _priorityItems = null;
+ _isDisposed = true;
+ }
+ }
+ else
+ throw new NotSupportedException(); // We should 'encourage' developers to dispose of renderers properly.
+ }
+
+ ///
+ /// Finalizes this renderer.
+ ///
+ ~Renderer()
+ {
+ Dispose(false);
+ }
+ }
+}
diff --git a/TrueCraft.Client/Rendering/RendererEventArgs.cs b/TrueCraft.Client/Rendering/RendererEventArgs.cs
new file mode 100644
index 0000000..f14ff60
--- /dev/null
+++ b/TrueCraft.Client/Rendering/RendererEventArgs.cs
@@ -0,0 +1,39 @@
+using System;
+
+namespace TrueCraft.Client.Rendering
+{
+ ///
+ ///
+ ///
+ ///
+ public sealed class RendererEventArgs : EventArgs
+ {
+ ///
+ ///
+ ///
+ public T Item { get; private set; }
+
+ ///
+ ///
+ ///
+ public Mesh Result { get; private set; }
+
+ ///
+ ///
+ ///
+ public bool IsPriority { get; private set; }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public RendererEventArgs(T item, Mesh result, bool isPriority)
+ {
+ Item = item;
+ Result = result;
+ IsPriority = isPriority;
+ }
+ }
+}
diff --git a/TrueCraft.Client/TrueCraft.Client.csproj b/TrueCraft.Client/TrueCraft.Client.csproj
index b274693..f389d45 100644
--- a/TrueCraft.Client/TrueCraft.Client.csproj
+++ b/TrueCraft.Client/TrueCraft.Client.csproj
@@ -68,6 +68,8 @@
+
+
diff --git a/TrueCraft.Client/TrueCraftGame.cs b/TrueCraft.Client/TrueCraftGame.cs
index b747325..6cc10aa 100644
--- a/TrueCraft.Client/TrueCraftGame.cs
+++ b/TrueCraft.Client/TrueCraftGame.cs
@@ -28,11 +28,9 @@ namespace TrueCraft.Client
private IPEndPoint EndPoint { get; set; }
private ChunkRenderer ChunkConverter { get; set; }
private DateTime NextPhysicsUpdate { get; set; }
- private List ChunkMeshes { get; set; }
+ private List ChunkMeshes { get; set; }
private ConcurrentBag PendingMainThreadActions { get; set; }
- private ConcurrentBag IncomingChunks { get; set; }
- private ConcurrentBag IncomingTransparentChunks { get; set; }
- private List TransparentChunkMeshes { get; set; }
+ private ConcurrentBag IncomingChunks { get; set; }
public ChatInterface ChatInterface { get; set; }
private RenderTarget2D RenderTarget;
private BoundingFrustum CameraView;
@@ -44,23 +42,23 @@ namespace TrueCraft.Client
private Microsoft.Xna.Framework.Vector3 Delta { get; set; }
private TextureMapper TextureMapper { get; set; }
- private BasicEffect OpaqueEffect, TransparentEffect;
+ private BasicEffect OpaqueEffect;
+ private AlphaTestEffect TransparentEffect;
public TrueCraftGame(MultiplayerClient client, IPEndPoint endPoint)
{
Window.Title = "TrueCraft";
Content.RootDirectory = "Content";
Graphics = new GraphicsDeviceManager(this);
+ Graphics.SynchronizeWithVerticalRetrace = false;
Graphics.IsFullScreen = UserSettings.Local.IsFullscreen;
Graphics.PreferredBackBufferWidth = UserSettings.Local.WindowResolution.Width;
Graphics.PreferredBackBufferHeight = UserSettings.Local.WindowResolution.Height;
Client = client;
EndPoint = endPoint;
NextPhysicsUpdate = DateTime.MinValue;
- ChunkMeshes = new List();
- TransparentChunkMeshes = new List();
- IncomingChunks = new ConcurrentBag();
- IncomingTransparentChunks = new ConcurrentBag();
+ ChunkMeshes = new List();
+ IncomingChunks = new ConcurrentBag();
PendingMainThreadActions = new ConcurrentBag();
MouseCaptured = true;
@@ -79,16 +77,16 @@ namespace TrueCraft.Client
SpriteBatch = new SpriteBatch(GraphicsDevice);
base.Initialize(); // (calls LoadContent)
ChunkConverter = new ChunkRenderer(Graphics.GraphicsDevice, Client.World.World.BlockRepository);
- Client.ChunkLoaded += (sender, e) => ChunkConverter.QueueChunk(e.Chunk);
- Client.ChunkModified += (sender, e) => ChunkConverter.QueueHighPriorityChunk(e.Chunk);
- ChunkConverter.MeshGenerated += ChunkConverter_MeshGenerated;
+ Client.ChunkLoaded += (sender, e) => ChunkConverter.Enqueue(e.Chunk);
+ Client.ChunkModified += (sender, e) => ChunkConverter.Enqueue(e.Chunk, true);
+ ChunkConverter.MeshCompleted += ChunkConverter_MeshGenerated;
ChunkConverter.Start();
Client.PropertyChanged += HandleClientPropertyChanged;
Client.Connect(EndPoint);
var centerX = GraphicsDevice.Viewport.Width / 2;
var centerY = GraphicsDevice.Viewport.Height / 2;
Mouse.SetPosition(centerX, centerY);
- Camera = new Camera(GraphicsDevice.Viewport.AspectRatio, 70.0f, 0.1f, 1000.0f);
+ Camera = new Camera(GraphicsDevice.Viewport.AspectRatio, 70.0f, 1.0f, 1000.0f);
UpdateCamera();
Window.ClientSizeChanged += (sender, e) => CreateRenderTarget();
MouseComponent.Move += OnMouseComponentMove;
@@ -103,12 +101,9 @@ namespace TrueCraft.Client
false, GraphicsDevice.PresentationParameters.BackBufferFormat, DepthFormat.Depth24);
}
- void ChunkConverter_MeshGenerated(object sender, ChunkRenderer.MeshGeneratedEventArgs e)
+ void ChunkConverter_MeshGenerated(object sender, RendererEventArgs e)
{
- if (e.Transparent)
- IncomingTransparentChunks.Add(e.Mesh);
- else
- IncomingChunks.Add(e.Mesh);
+ IncomingChunks.Add(e.Result);
}
void HandleClientPropertyChanged(object sender, PropertyChangedEventArgs e)
@@ -119,7 +114,7 @@ namespace TrueCraft.Client
UpdateCamera();
var sorter = new ChunkRenderer.ChunkSorter(new Coordinates3D(
(int)Client.Position.X, 0, (int)Client.Position.Z));
- PendingMainThreadActions.Add(() => TransparentChunkMeshes.Sort(sorter));
+ PendingMainThreadActions.Add(() => ChunkMeshes.Sort(sorter));
break;
}
}
@@ -154,8 +149,9 @@ namespace TrueCraft.Client
OpaqueEffect.FogEnd = 1000f;
OpaqueEffect.FogColor = Color.CornflowerBlue.ToVector3();
- TransparentEffect = new BasicEffect(GraphicsDevice);
- TransparentEffect.TextureEnabled = true;
+ TransparentEffect = new AlphaTestEffect(GraphicsDevice);
+ TransparentEffect.AlphaFunction = CompareFunction.Greater;
+ TransparentEffect.ReferenceAlpha = 127;
TransparentEffect.Texture = TextureMapper.GetTexture("terrain.png");
base.LoadContent();
@@ -287,16 +283,11 @@ namespace TrueCraft.Client
i.Update(gameTime);
}
- ChunkMesh mesh;
+ Mesh mesh;
if (IncomingChunks.TryTake(out mesh))
{
- var existing = ChunkMeshes.SingleOrDefault(m => m.Chunk.Chunk.Coordinates == mesh.Chunk.Chunk.Coordinates);
- if (existing != null)
- ChunkMeshes.Remove(existing);
ChunkMeshes.Add(mesh);
}
- if (IncomingTransparentChunks.TryTake(out mesh)) // TODO: re-render transparent meshes
- TransparentChunkMeshes.Add(mesh);
Action action;
if (PendingMainThreadActions.TryTake(out action))
action();
@@ -347,37 +338,57 @@ namespace TrueCraft.Client
Camera.ApplyTo(TransparentEffect);
}
+ // Used for faking the disabling of color buffer writing.
+ private static readonly BlendState ColorWriteDisable = new BlendState()
+ {
+ ColorSourceBlend = Blend.Zero,
+ AlphaSourceBlend = Blend.Zero,
+ ColorDestinationBlend = Blend.One,
+ AlphaDestinationBlend = Blend.One
+ };
+
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.SetRenderTarget(RenderTarget);
+ GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
Graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp;
- GraphicsDevice.BlendState = BlendState.NonPremultiplied;
int verticies = 0, chunks = 0;
- GraphicsDevice.DepthStencilState = new DepthStencilState { DepthBufferEnable = true };
+ GraphicsDevice.DepthStencilState = DepthStencilState.Default;
for (int i = 0; i < ChunkMeshes.Count; i++)
{
if (CameraView.Intersects(ChunkMeshes[i].BoundingBox))
{
verticies += ChunkMeshes[i].GetTotalVertices();
chunks++;
- ChunkMeshes[i].Draw(OpaqueEffect);
+ ChunkMeshes[i].Draw(OpaqueEffect, 0);
}
}
- GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
- for (int i = 0; i < TransparentChunkMeshes.Count; i++)
+
+ GraphicsDevice.BlendState = ColorWriteDisable;
+ for (int i = 0; i < ChunkMeshes.Count; i++)
{
- if (CameraView.Intersects(TransparentChunkMeshes[i].BoundingBox))
+ if (CameraView.Intersects(ChunkMeshes[i].BoundingBox))
{
- if (!TransparentChunkMeshes[i].IsDisposed)
- verticies += TransparentChunkMeshes[i].GetTotalVertices();
- TransparentChunkMeshes[i].Draw(TransparentEffect);
+ if (!ChunkMeshes[i].IsDisposed)
+ verticies += ChunkMeshes[i].GetTotalVertices();
+ ChunkMeshes[i].Draw(TransparentEffect, 1);
+ }
+ }
+
+ GraphicsDevice.BlendState = BlendState.NonPremultiplied;
+ for (int i = 0; i < ChunkMeshes.Count; i++)
+ {
+ if (CameraView.Intersects(ChunkMeshes[i].BoundingBox))
+ {
+ if (!ChunkMeshes[i].IsDisposed)
+ verticies += ChunkMeshes[i].GetTotalVertices();
+ ChunkMeshes[i].Draw(TransparentEffect, 1);
}
}
- GraphicsDevice.DepthStencilState = DepthStencilState.Default;
SpriteBatch.Begin();
for (int i = 0; i < Interfaces.Count; i++)