#region Copyright & License Information /* * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, * see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenRA.FileFormats; using OpenRA.Primitives; namespace OpenRA.Graphics { public struct VoxelRenderData { public readonly int Start; public readonly int Count; public readonly Sheet Sheet; public VoxelRenderData(int start, int count, Sheet sheet) { Start = start; Count = count; Sheet = sheet; } } public sealed class VoxelLoader : IDisposable { static readonly float[] ChannelSelect = { 0.75f, 0.25f, -0.25f, -0.75f }; readonly List vertices = new List(); readonly Cache, Voxel> voxels; IVertexBuffer vertexBuffer; int totalVertexCount; int cachedVertexCount; SheetBuilder sheetBuilder; static SheetBuilder CreateSheetBuilder() { var allocated = false; Func allocate = () => { if (allocated) throw new SheetOverflowException(""); allocated = true; return SheetBuilder.AllocateSheet(SheetType.DualIndexed, Game.Settings.Graphics.SheetSize); }; return new SheetBuilder(SheetType.DualIndexed, allocate); } public VoxelLoader() { voxels = new Cache, Voxel>(LoadFile); vertices = new List(); totalVertexCount = 0; cachedVertexCount = 0; sheetBuilder = CreateSheetBuilder(); } Vertex[] GenerateSlicePlane(int su, int sv, Func first, Func second, Func coord) { var colors = new byte[su * sv]; var normals = new byte[su * sv]; var c = 0; for (var v = 0; v < sv; v++) for (var u = 0; u < su; u++) { var voxel = first(u, v) ?? second(u, v); colors[c] = voxel == null ? (byte)0 : voxel.Color; normals[c] = voxel == null ? (byte)0 : voxel.Normal; c++; } var s = sheetBuilder.Allocate(new Size(su, sv)); Util.FastCopyIntoChannel(s, 0, colors); Util.FastCopyIntoChannel(s, 1, normals); s.Sheet.CommitBufferedData(); var channelP = ChannelSelect[(int)s.Channel]; var channelC = ChannelSelect[(int)s.Channel + 1]; return new Vertex[6] { new Vertex(coord(0, 0), s.Left, s.Top, channelP, channelC), new Vertex(coord(su, 0), s.Right, s.Top, channelP, channelC), new Vertex(coord(su, sv), s.Right, s.Bottom, channelP, channelC), new Vertex(coord(su, sv), s.Right, s.Bottom, channelP, channelC), new Vertex(coord(0, sv), s.Left, s.Bottom, channelP, channelC), new Vertex(coord(0, 0), s.Left, s.Top, channelP, channelC) }; } IEnumerable GenerateSlicePlanes(VxlLimb l) { Func get = (x, y, z) => { if (x < 0 || y < 0 || z < 0) return null; if (x >= l.Size[0] || y >= l.Size[1] || z >= l.Size[2]) return null; var v = l.VoxelMap[(byte)x, (byte)y]; if (v == null || !v.ContainsKey((byte)z)) return null; return l.VoxelMap[(byte)x, (byte)y][(byte)z]; }; // Cull slices without any visible faces var xPlanes = new bool[l.Size[0] + 1]; var yPlanes = new bool[l.Size[1] + 1]; var zPlanes = new bool[l.Size[2] + 1]; for (var x = 0; x < l.Size[0]; x++) { for (var y = 0; y < l.Size[1]; y++) { for (var z = 0; z < l.Size[2]; z++) { if (get(x, y, z) == null) continue; // Only generate a plane if it is actually visible if (!xPlanes[x] && get(x - 1, y, z) == null) xPlanes[x] = true; if (!xPlanes[x + 1] && get(x + 1, y, z) == null) xPlanes[x + 1] = true; if (!yPlanes[y] && get(x, y - 1, z) == null) yPlanes[y] = true; if (!yPlanes[y + 1] && get(x, y + 1, z) == null) yPlanes[y + 1] = true; if (!zPlanes[z] && get(x, y, z - 1) == null) zPlanes[z] = true; if (!zPlanes[z + 1] && get(x, y, z + 1) == null) zPlanes[z + 1] = true; } } } for (var x = 0; x <= l.Size[0]; x++) if (xPlanes[x]) yield return GenerateSlicePlane(l.Size[1], l.Size[2], (u, v) => get(x, u, v), (u, v) => get(x - 1, u, v), (u, v) => new float[] { x, u, v }); for (var y = 0; y <= l.Size[1]; y++) if (yPlanes[y]) yield return GenerateSlicePlane(l.Size[0], l.Size[2], (u, v) => get(u, y, v), (u, v) => get(u, y - 1, v), (u, v) => new float[] { u, y, v }); for (var z = 0; z <= l.Size[2]; z++) if (zPlanes[z]) yield return GenerateSlicePlane(l.Size[0], l.Size[1], (u, v) => get(u, v, z), (u, v) => get(u, v, z - 1), (u, v) => new float[] { u, v, z }); } public VoxelRenderData GenerateRenderData(VxlLimb l) { Vertex[] v; try { v = GenerateSlicePlanes(l).SelectMany(x => x).ToArray(); } catch (SheetOverflowException) { // Sheet overflow - allocate a new sheet and try once more Log.Write("debug", "Voxel sheet overflow! Generating new sheet"); sheetBuilder.Current.ReleaseBuffer(); sheetBuilder = CreateSheetBuilder(); v = GenerateSlicePlanes(l).SelectMany(x => x).ToArray(); } vertices.Add(v); var start = totalVertexCount; var count = v.Length; totalVertexCount += count; return new VoxelRenderData(start, count, sheetBuilder.Current); } public void RefreshBuffer() { if (vertexBuffer != null) vertexBuffer.Dispose(); vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(totalVertexCount); vertexBuffer.SetData(vertices.SelectMany(v => v).ToArray(), totalVertexCount); cachedVertexCount = totalVertexCount; } public IVertexBuffer VertexBuffer { get { if (cachedVertexCount != totalVertexCount) RefreshBuffer(); return vertexBuffer; } } Voxel LoadFile(Pair files) { VxlReader vxl; HvaReader hva; using (var s = Game.ModData.ModFiles.Open(files.First + ".vxl")) vxl = new VxlReader(s); using (var s = Game.ModData.ModFiles.Open(files.Second + ".hva")) hva = new HvaReader(s, files.Second + ".hva"); return new Voxel(this, vxl, hva); } public Voxel Load(string vxl, string hva) { return voxels[Pair.New(vxl, hva)]; } public void Finish() { sheetBuilder.Current.ReleaseBuffer(); } public void Dispose() { if (vertexBuffer != null) vertexBuffer.Dispose(); sheetBuilder.Dispose(); } } }