Permalink
Browse files

Decal rendering #1: Geometry (no clipping)

  • Loading branch information...
LogicAndTrick committed Feb 7, 2013
1 parent c84d874 commit beb031563aac796d52727a36aa13358be4979895
@@ -16,15 +16,27 @@ namespace Sledge.DataStructures.Rendering
public class ArrayManager
{
private readonly SolidVertexArray _array;
private readonly DecalFaceVertexArray _decalArray;
public ArrayManager(Map map)
{
_array = new SolidVertexArray(map.WorldSpawn.FindAll());
Update(map);
var all = map.WorldSpawn.FindAll();
_array = new SolidVertexArray(all);
_decalArray = new DecalFaceVertexArray(all);
// Update(map);
}
public void Update(Map map)
{
_array.Update(map.WorldSpawn.FindAll());
var all = map.WorldSpawn.FindAll();
_array.Update(all);
_decalArray.Update(all);
}
public void UpdateDecals(Map map)
{
var all = map.WorldSpawn.FindAll();
_decalArray.Update(all);
}
public void UpdatePartial(IEnumerable<MapObject> objects)
@@ -39,6 +51,7 @@ public void UpdatePartial(IEnumerable<Face> faces)
public void DrawTextured(object context, ShaderProgram program)
{
// Todo abstract this out a bit, repeated code all over the place
_array.Bind(context, 0);
foreach (var subset in _array.TextureSubsets)
{
@@ -49,6 +62,16 @@ public void DrawTextured(object context, ShaderProgram program)
_array.Array.DrawElements(0, subset.Start, subset.Count);
}
_array.Unbind();
_decalArray.Bind(context, 0);
foreach (var subset in _decalArray.TextureSubsets)
{
var tex = subset.Instance;
if (tex != null) tex.Bind();
else TextureHelper.Unbind();
program.Set("isTextured", tex != null);
_array.Array.DrawElements(0, subset.Start, subset.Count);
}
_decalArray.Unbind();
}
public void DrawWireframe(object context, ShaderProgram program)
@@ -59,6 +82,12 @@ public void DrawWireframe(object context, ShaderProgram program)
_array.Array.DrawElements(1, subset.Start, subset.Count);
}
_array.Unbind();
_decalArray.Bind(context, 1);
foreach (var subset in _decalArray.WireframeSubsets)
{
_decalArray.Array.DrawElements(1, subset.Start, subset.Count);
}
_decalArray.Unbind();
}
}
}
@@ -0,0 +1,169 @@
using System.Collections.Generic;
using System.Linq;
using OpenTK.Graphics.OpenGL;
using Sledge.Common;
using Sledge.DataStructures.MapObjects;
using Sledge.Graphics.Arrays;
namespace Sledge.DataStructures.Rendering
{
/// <summary>
/// A decal face vertex array collects and stores a VBO for all decal faces in the map.
/// Faces are grouped by texture and then split into subsets for optimised rendering later on.
/// Decals are separated from the solid vertex array because extra decal faces can be added by
/// simple translations, which would break partial updating.
/// </summary>
public class DecalFaceVertexArray
{
private static readonly BeginMode[] Modes;
private static readonly ArraySpecification Specification;
private static readonly int SpecSize;
static DecalFaceVertexArray()
{
Modes = new[] { BeginMode.Triangles, BeginMode.Lines};
Specification = new ArraySpecification(
ArrayIndex.Vector3("Position"),
ArrayIndex.Vector3("Normal"),
ArrayIndex.Vector2("Texture"),
ArrayIndex.Vector3("Colour"),
ArrayIndex.Float("Selected"));
SpecSize = Specification.Indices.Sum(x => x.Length);
}
public VertexBuffer<float> Array { get; private set; }
public List<VertexArraySubset<ITexture>> TextureSubsets { get; private set; }
public List<VertexArraySubset<object>> WireframeSubsets { get; private set; }
private readonly Dictionary<object, VertexArray<float>> _arrays;
public void Bind(object context, int index)
{
if (!_arrays.ContainsKey(context))
{
_arrays.Add(context, new VertexArray<float>(Array));
}
_arrays[context].Bind(index);
}
public void Unbind()
{
VertexArray<float>.Unbind();
}
/// <summary>
/// Create a new vertex array for a solid.
/// </summary>
/// <param name="objects">The array objects</param>
public DecalFaceVertexArray(IEnumerable<MapObject> objects)
{
_arrays = new Dictionary<object, VertexArray<float>>();
float[] array;
uint[] indices;
uint[] wireframeIndices;
int count;
TextureSubsets = new List<VertexArraySubset<ITexture>>();
WireframeSubsets = new List<VertexArraySubset<object>>();
GetArrayData(objects, out count, out array, out indices, out wireframeIndices, TextureSubsets, WireframeSubsets);
Array = new VertexBuffer<float>(Specification, Modes, count, sizeof(float), array, new[] { indices, wireframeIndices});
}
/// <summary>
/// Update the array with new data.
/// </summary>
/// <param name="objects">List containing the data to update</param>
public void Update(IEnumerable<MapObject> objects)
{
_arrays.Clear();
float[] array;
uint[] indices;
uint[] wireframeIndices;
int count;
TextureSubsets.Clear();
WireframeSubsets.Clear();
GetArrayData(objects, out count, out array, out indices, out wireframeIndices, TextureSubsets, WireframeSubsets);
Array.Update(count, array, new[] {indices, wireframeIndices});
}
/// <summary>
/// Does a loop around the map objects and calculates array data and the subsets
/// </summary>
/// <param name="objects">The objects in the array</param>
/// <param name="count">Outputs the number of verts in the array</param>
/// <param name="array">Outputs the array data</param>
/// <param name="indices">Outputs the triangle drawing indices</param>
/// <param name="wireframeIndices">Outputs the line drawing indices</param>
/// <param name="subsets">The collection of textured subsets to populate</param>
/// <param name="wireframeSubsets">The collection of wireframe subsets to populate</param>
private static void GetArrayData(IEnumerable<MapObject> objects, out int count, out float[] array, out uint[] indices, out uint[] wireframeIndices, ICollection<VertexArraySubset<ITexture>> subsets, ICollection<VertexArraySubset<object>> wireframeSubsets)
{
var obj = objects.Where(x => !x.IsVisgroupHidden && !x.IsCodeHidden).ToList();
var faces = obj.OfType<Entity>().SelectMany(x => x.GetTexturedFaces()).ToList();
var indexList = new List<uint>();
var wireframeIndexList = new List<uint>();
uint index = 0;
var idx = 0;
var numVerts = faces.Sum(x => x.Vertices.Count);
array = new float[SpecSize * numVerts];
var subsetStart = 0;
var wireframeSubsetStart = 0;
foreach (var group in faces.GroupBy(x => new { x.Texture.Texture }))
{
foreach (var face in group)
{
idx = WriteFace(array, idx, face);
for (uint i = 1; i < face.Vertices.Count - 1; i++)
{
indexList.Add(index);
indexList.Add(index + i);
indexList.Add(index + i + 1);
}
for (uint i = 0; i < face.Vertices.Count; i++)
{
var ni = (uint) ((i + 1) % face.Vertices.Count);
wireframeIndexList.Add(index + i);
wireframeIndexList.Add(index + ni);
}
index += (uint) face.Vertices.Count;
}
subsets.Add(new VertexArraySubset<ITexture>(group.Key.Texture, subsetStart, indexList.Count - subsetStart));
subsetStart = indexList.Count;
wireframeSubsets.Add(new VertexArraySubset<object>(null, wireframeSubsetStart, wireframeIndexList.Count - wireframeSubsetStart));
wireframeSubsetStart = wireframeIndexList.Count;
}
indices = indexList.ToArray();
wireframeIndices = wireframeIndexList.ToArray();
count = indices.Length;
}
private static int WriteFace(float[] array, int idx, Face face)
{
float nx = (float) face.Plane.Normal.DX,
ny = (float) face.Plane.Normal.DY,
nz = (float) face.Plane.Normal.DZ;
float r = face.Colour.R / 255f,
g = face.Colour.G / 255f,
b = face.Colour.B / 255f;
foreach (var vert in face.Vertices)
{
array[idx++] = ((float) vert.Location.DX);
array[idx++] = ((float) vert.Location.DY);
array[idx++] = ((float) vert.Location.DZ);
array[idx++] = (nx);
array[idx++] = (ny);
array[idx++] = (nz);
array[idx++] = ((float) vert.TextureU);
array[idx++] = ((float) vert.TextureV);
array[idx++] = (r);
array[idx++] = (g);
array[idx++] = (b);
array[idx++] = (face.IsSelected || (face.Parent != null && face.Parent.IsSelected) ? 1 : 0);
}
return idx;
}
}
}
@@ -44,6 +44,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ArrayManager.cs" />
<Compile Include="DecalFaceVertexArray.cs" />
<Compile Include="ModelRenderable.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rendering.cs" />
@@ -119,7 +119,7 @@ public void UpdatePartial(IEnumerable<Entity> entities)
if (!EntityOffsets.ContainsKey(entity)) continue;
var offset = EntityOffsets[entity];
var idx = 0;
foreach (var face in entity.GetFaces())
foreach (var face in entity.GetBoxFaces())
{
idx = WriteFace(list, idx, face);
}
@@ -182,7 +182,7 @@ private static void GetArrayData(IEnumerable<MapObject> objects, out int count,
foreach (var entity in entities)
{
entityOffsets.Add(entity, idx);
foreach (var face in entity.GetFaces())
foreach (var face in entity.GetBoxFaces())
{
idx = WriteFace(array, idx, face);
if (entity.Sprite == null) // Don't draw the faces if the entity has a sprite
@@ -14,6 +14,7 @@ public class Entity : MapObject
public EntityData EntityData { get; set; }
public Coordinate Origin { get; set; }
public ITexture Sprite { get; set; }
public ITexture Decal { get; set; }
public Entity(long id) : base(id)
{
@@ -60,6 +61,11 @@ public override void UpdateBoundingBox(bool cascadeToParent = true)
sub = behav.GetCoordinate(0);
add = behav.GetCoordinate(1);
}
else if (GameData.Name == "infodecal")
{
sub = Coordinate.One * -4;
add = Coordinate.One * 4;
}
BoundingBox = new Box(Origin + sub, Origin + add);
}
else if (Children.Any())
@@ -90,7 +96,7 @@ public new Color Colour
set { base.Colour = value; }
}
public IEnumerable<Face> GetFaces()
public IEnumerable<Face> GetBoxFaces()
{
var faces = new List<Face>();
if (Children.Any()) return faces;
@@ -111,6 +117,63 @@ public IEnumerable<Face> GetFaces()
return faces;
}
private List<Face> _decalGeometry;
public IEnumerable<Face> GetTexturedFaces()
{
return _decalGeometry ?? (_decalGeometry = new List<Face>());
}
public void CalculateDecalGeometry()
{
_decalGeometry = new List<Face>();
if (Decal == null) return; // Texture not found
var boxRadius = Coordinate.One * 4;
// Decals apply to all faces that intersect within an 8x8x8 bounding box
// centered at the origin of the decal
var box = new Box(Origin - boxRadius, Origin + boxRadius);
var root = GetRoot(Parent);
// Get the faces that intersect with the decal's radius
var faces = root.GetAllNodesIntersectingWith(box).OfType<Solid>()
.SelectMany(x => x.Faces).Where(x => x.IntersectsWithBox(box));
foreach (var face in faces)
{
// Project the decal onto the face
var center = face.Plane.Project(Origin);
var decalFace = new Face(int.MinValue) // Use a dummy ID
{
Colour = Colour,
IsSelected = IsSelected,
IsHidden = IsCodeHidden,
Plane = face.Plane,
Texture =
{
Name = Decal.Name,
Texture = Decal,
UAxis = face.Texture.UAxis,
VAxis = face.Texture.VAxis,
XScale = face.Texture.XScale,
YScale = face.Texture.YScale,
XShift = -Decal.Width / 2m,
YShift = -Decal.Height / 2m
}
};
// Re-project the vertices in case the texture axes are not on the face plane
// Also add a tiny bit to the normal axis to ensure the decal is rendered in front of the face
var normalAdd = face.Plane.Normal * 0.2m;
var xShift = face.Texture.UAxis * face.Texture.XScale * Decal.Width / 2;
var yShift = face.Texture.VAxis * face.Texture.YScale * Decal.Height / 2;
decalFace.Vertices.Add(new Vertex(face.Plane.Project(center + xShift - yShift) + normalAdd, decalFace)); // Bottom Right
decalFace.Vertices.Add(new Vertex(face.Plane.Project(center + xShift + yShift) + normalAdd, decalFace)); // Top Right
decalFace.Vertices.Add(new Vertex(face.Plane.Project(center - xShift + yShift) + normalAdd, decalFace)); // Top Left
decalFace.Vertices.Add(new Vertex(face.Plane.Project(center - xShift - yShift) + normalAdd, decalFace)); // Bottom Left
// TODO: verify this covers all situations and I don't have to manually calculate the texture coordinates
decalFace.FitTextureToPointCloud(new Cloud(decalFace.Vertices.Select(x => x.Location)));
_decalGeometry.Add(decalFace);
}
}
public override void Transform(IUnitTransformation transform)
{
Origin = transform.Transform(Origin);
@@ -124,7 +187,9 @@ public override void Transform(IUnitTransformation transform)
/// <returns>The closest intersecting point, or null if the line doesn't intersect.</returns>
public override Coordinate GetIntersectionPoint(Line line)
{
return GetFaces().Select(x => x.GetIntersectionPoint(line))
var faces = GetBoxFaces();
if (_decalGeometry != null) faces = faces.Union(_decalGeometry);
return faces.Select(x => x.GetIntersectionPoint(line))
.Where(x => x != null)
.OrderBy(x => (x - line.Start).VectorMagnitude())
.FirstOrDefault();
@@ -345,7 +345,18 @@ public virtual Coordinate GetIntersectionPoint(Line line)
/// <returns>True if one of the face's edges intersects with the box.</returns>
public bool IntersectsWithLine(Box box)
{
return GetLines().Any(box.IntersectsWith);
// Shortcut through the bounding box to avoid the line computations if they aren't needed
return BoundingBox.IntersectsWith(box) && GetLines().Any(box.IntersectsWith);
}
/// <summary>
/// Test this face to see if the given bounding box intersects with it
/// </summary>
/// <param name="box">The box to test against</param>
/// <returns>True if the box intersects</returns>
public bool IntersectsWithBox(Box box)
{
return box.GetBoxLines().Any(x => GetIntersectionPoint(x) != null);
}
/// <summary>
Oops, something went wrong.

0 comments on commit beb0315

Please sign in to comment.