Skip to content
This repository was archived by the owner on Sep 16, 2019. It is now read-only.

Commit beb0315

Browse files
committed
Decal rendering #1: Geometry (no clipping)
1 parent c84d874 commit beb0315

File tree

9 files changed

+310
-20
lines changed

9 files changed

+310
-20
lines changed

Sledge.DataStructures.Rendering/ArrayManager.cs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,27 @@ namespace Sledge.DataStructures.Rendering
1616
public class ArrayManager
1717
{
1818
private readonly SolidVertexArray _array;
19+
private readonly DecalFaceVertexArray _decalArray;
20+
1921
public ArrayManager(Map map)
2022
{
21-
_array = new SolidVertexArray(map.WorldSpawn.FindAll());
22-
Update(map);
23+
var all = map.WorldSpawn.FindAll();
24+
_array = new SolidVertexArray(all);
25+
_decalArray = new DecalFaceVertexArray(all);
26+
// Update(map);
2327
}
2428

2529
public void Update(Map map)
2630
{
27-
_array.Update(map.WorldSpawn.FindAll());
31+
var all = map.WorldSpawn.FindAll();
32+
_array.Update(all);
33+
_decalArray.Update(all);
34+
}
35+
36+
public void UpdateDecals(Map map)
37+
{
38+
var all = map.WorldSpawn.FindAll();
39+
_decalArray.Update(all);
2840
}
2941

3042
public void UpdatePartial(IEnumerable<MapObject> objects)
@@ -39,6 +51,7 @@ public void UpdatePartial(IEnumerable<Face> faces)
3951

4052
public void DrawTextured(object context, ShaderProgram program)
4153
{
54+
// Todo abstract this out a bit, repeated code all over the place
4255
_array.Bind(context, 0);
4356
foreach (var subset in _array.TextureSubsets)
4457
{
@@ -49,6 +62,16 @@ public void DrawTextured(object context, ShaderProgram program)
4962
_array.Array.DrawElements(0, subset.Start, subset.Count);
5063
}
5164
_array.Unbind();
65+
_decalArray.Bind(context, 0);
66+
foreach (var subset in _decalArray.TextureSubsets)
67+
{
68+
var tex = subset.Instance;
69+
if (tex != null) tex.Bind();
70+
else TextureHelper.Unbind();
71+
program.Set("isTextured", tex != null);
72+
_array.Array.DrawElements(0, subset.Start, subset.Count);
73+
}
74+
_decalArray.Unbind();
5275
}
5376

5477
public void DrawWireframe(object context, ShaderProgram program)
@@ -59,6 +82,12 @@ public void DrawWireframe(object context, ShaderProgram program)
5982
_array.Array.DrawElements(1, subset.Start, subset.Count);
6083
}
6184
_array.Unbind();
85+
_decalArray.Bind(context, 1);
86+
foreach (var subset in _decalArray.WireframeSubsets)
87+
{
88+
_decalArray.Array.DrawElements(1, subset.Start, subset.Count);
89+
}
90+
_decalArray.Unbind();
6291
}
6392
}
6493
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using OpenTK.Graphics.OpenGL;
4+
using Sledge.Common;
5+
using Sledge.DataStructures.MapObjects;
6+
using Sledge.Graphics.Arrays;
7+
8+
namespace Sledge.DataStructures.Rendering
9+
{
10+
/// <summary>
11+
/// A decal face vertex array collects and stores a VBO for all decal faces in the map.
12+
/// Faces are grouped by texture and then split into subsets for optimised rendering later on.
13+
/// Decals are separated from the solid vertex array because extra decal faces can be added by
14+
/// simple translations, which would break partial updating.
15+
/// </summary>
16+
public class DecalFaceVertexArray
17+
{
18+
private static readonly BeginMode[] Modes;
19+
private static readonly ArraySpecification Specification;
20+
private static readonly int SpecSize;
21+
22+
static DecalFaceVertexArray()
23+
{
24+
Modes = new[] { BeginMode.Triangles, BeginMode.Lines};
25+
Specification = new ArraySpecification(
26+
ArrayIndex.Vector3("Position"),
27+
ArrayIndex.Vector3("Normal"),
28+
ArrayIndex.Vector2("Texture"),
29+
ArrayIndex.Vector3("Colour"),
30+
ArrayIndex.Float("Selected"));
31+
SpecSize = Specification.Indices.Sum(x => x.Length);
32+
}
33+
34+
public VertexBuffer<float> Array { get; private set; }
35+
public List<VertexArraySubset<ITexture>> TextureSubsets { get; private set; }
36+
public List<VertexArraySubset<object>> WireframeSubsets { get; private set; }
37+
private readonly Dictionary<object, VertexArray<float>> _arrays;
38+
39+
public void Bind(object context, int index)
40+
{
41+
if (!_arrays.ContainsKey(context))
42+
{
43+
_arrays.Add(context, new VertexArray<float>(Array));
44+
}
45+
_arrays[context].Bind(index);
46+
}
47+
48+
public void Unbind()
49+
{
50+
VertexArray<float>.Unbind();
51+
}
52+
53+
/// <summary>
54+
/// Create a new vertex array for a solid.
55+
/// </summary>
56+
/// <param name="objects">The array objects</param>
57+
public DecalFaceVertexArray(IEnumerable<MapObject> objects)
58+
{
59+
_arrays = new Dictionary<object, VertexArray<float>>();
60+
61+
float[] array;
62+
uint[] indices;
63+
uint[] wireframeIndices;
64+
int count;
65+
TextureSubsets = new List<VertexArraySubset<ITexture>>();
66+
WireframeSubsets = new List<VertexArraySubset<object>>();
67+
GetArrayData(objects, out count, out array, out indices, out wireframeIndices, TextureSubsets, WireframeSubsets);
68+
69+
Array = new VertexBuffer<float>(Specification, Modes, count, sizeof(float), array, new[] { indices, wireframeIndices});
70+
}
71+
72+
/// <summary>
73+
/// Update the array with new data.
74+
/// </summary>
75+
/// <param name="objects">List containing the data to update</param>
76+
public void Update(IEnumerable<MapObject> objects)
77+
{
78+
_arrays.Clear();
79+
float[] array;
80+
uint[] indices;
81+
uint[] wireframeIndices;
82+
int count;
83+
TextureSubsets.Clear();
84+
WireframeSubsets.Clear();
85+
GetArrayData(objects, out count, out array, out indices, out wireframeIndices, TextureSubsets, WireframeSubsets);
86+
87+
Array.Update(count, array, new[] {indices, wireframeIndices});
88+
}
89+
90+
/// <summary>
91+
/// Does a loop around the map objects and calculates array data and the subsets
92+
/// </summary>
93+
/// <param name="objects">The objects in the array</param>
94+
/// <param name="count">Outputs the number of verts in the array</param>
95+
/// <param name="array">Outputs the array data</param>
96+
/// <param name="indices">Outputs the triangle drawing indices</param>
97+
/// <param name="wireframeIndices">Outputs the line drawing indices</param>
98+
/// <param name="subsets">The collection of textured subsets to populate</param>
99+
/// <param name="wireframeSubsets">The collection of wireframe subsets to populate</param>
100+
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)
101+
{
102+
var obj = objects.Where(x => !x.IsVisgroupHidden && !x.IsCodeHidden).ToList();
103+
var faces = obj.OfType<Entity>().SelectMany(x => x.GetTexturedFaces()).ToList();
104+
var indexList = new List<uint>();
105+
var wireframeIndexList = new List<uint>();
106+
uint index = 0;
107+
var idx = 0;
108+
var numVerts = faces.Sum(x => x.Vertices.Count);
109+
array = new float[SpecSize * numVerts];
110+
var subsetStart = 0;
111+
var wireframeSubsetStart = 0;
112+
foreach (var group in faces.GroupBy(x => new { x.Texture.Texture }))
113+
{
114+
foreach (var face in group)
115+
{
116+
idx = WriteFace(array, idx, face);
117+
for (uint i = 1; i < face.Vertices.Count - 1; i++)
118+
{
119+
indexList.Add(index);
120+
indexList.Add(index + i);
121+
indexList.Add(index + i + 1);
122+
}
123+
for (uint i = 0; i < face.Vertices.Count; i++)
124+
{
125+
var ni = (uint) ((i + 1) % face.Vertices.Count);
126+
wireframeIndexList.Add(index + i);
127+
wireframeIndexList.Add(index + ni);
128+
}
129+
index += (uint) face.Vertices.Count;
130+
}
131+
132+
subsets.Add(new VertexArraySubset<ITexture>(group.Key.Texture, subsetStart, indexList.Count - subsetStart));
133+
subsetStart = indexList.Count;
134+
135+
wireframeSubsets.Add(new VertexArraySubset<object>(null, wireframeSubsetStart, wireframeIndexList.Count - wireframeSubsetStart));
136+
wireframeSubsetStart = wireframeIndexList.Count;
137+
}
138+
indices = indexList.ToArray();
139+
wireframeIndices = wireframeIndexList.ToArray();
140+
count = indices.Length;
141+
}
142+
143+
private static int WriteFace(float[] array, int idx, Face face)
144+
{
145+
float nx = (float) face.Plane.Normal.DX,
146+
ny = (float) face.Plane.Normal.DY,
147+
nz = (float) face.Plane.Normal.DZ;
148+
float r = face.Colour.R / 255f,
149+
g = face.Colour.G / 255f,
150+
b = face.Colour.B / 255f;
151+
foreach (var vert in face.Vertices)
152+
{
153+
array[idx++] = ((float) vert.Location.DX);
154+
array[idx++] = ((float) vert.Location.DY);
155+
array[idx++] = ((float) vert.Location.DZ);
156+
array[idx++] = (nx);
157+
array[idx++] = (ny);
158+
array[idx++] = (nz);
159+
array[idx++] = ((float) vert.TextureU);
160+
array[idx++] = ((float) vert.TextureV);
161+
array[idx++] = (r);
162+
array[idx++] = (g);
163+
array[idx++] = (b);
164+
array[idx++] = (face.IsSelected || (face.Parent != null && face.Parent.IsSelected) ? 1 : 0);
165+
}
166+
return idx;
167+
}
168+
}
169+
}

Sledge.DataStructures.Rendering/Sledge.DataStructures.Rendering.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
</ItemGroup>
4545
<ItemGroup>
4646
<Compile Include="ArrayManager.cs" />
47+
<Compile Include="DecalFaceVertexArray.cs" />
4748
<Compile Include="ModelRenderable.cs" />
4849
<Compile Include="Properties\AssemblyInfo.cs" />
4950
<Compile Include="Rendering.cs" />

Sledge.DataStructures.Rendering/SolidVertexArray.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public void UpdatePartial(IEnumerable<Entity> entities)
119119
if (!EntityOffsets.ContainsKey(entity)) continue;
120120
var offset = EntityOffsets[entity];
121121
var idx = 0;
122-
foreach (var face in entity.GetFaces())
122+
foreach (var face in entity.GetBoxFaces())
123123
{
124124
idx = WriteFace(list, idx, face);
125125
}
@@ -182,7 +182,7 @@ private static void GetArrayData(IEnumerable<MapObject> objects, out int count,
182182
foreach (var entity in entities)
183183
{
184184
entityOffsets.Add(entity, idx);
185-
foreach (var face in entity.GetFaces())
185+
foreach (var face in entity.GetBoxFaces())
186186
{
187187
idx = WriteFace(array, idx, face);
188188
if (entity.Sprite == null) // Don't draw the faces if the entity has a sprite

Sledge.DataStructures/MapObjects/Entity.cs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class Entity : MapObject
1414
public EntityData EntityData { get; set; }
1515
public Coordinate Origin { get; set; }
1616
public ITexture Sprite { get; set; }
17+
public ITexture Decal { get; set; }
1718

1819
public Entity(long id) : base(id)
1920
{
@@ -60,6 +61,11 @@ public override void UpdateBoundingBox(bool cascadeToParent = true)
6061
sub = behav.GetCoordinate(0);
6162
add = behav.GetCoordinate(1);
6263
}
64+
else if (GameData.Name == "infodecal")
65+
{
66+
sub = Coordinate.One * -4;
67+
add = Coordinate.One * 4;
68+
}
6369
BoundingBox = new Box(Origin + sub, Origin + add);
6470
}
6571
else if (Children.Any())
@@ -90,7 +96,7 @@ public override void UpdateBoundingBox(bool cascadeToParent = true)
9096
set { base.Colour = value; }
9197
}
9298

93-
public IEnumerable<Face> GetFaces()
99+
public IEnumerable<Face> GetBoxFaces()
94100
{
95101
var faces = new List<Face>();
96102
if (Children.Any()) return faces;
@@ -111,6 +117,63 @@ public IEnumerable<Face> GetFaces()
111117
return faces;
112118
}
113119

120+
private List<Face> _decalGeometry;
121+
122+
public IEnumerable<Face> GetTexturedFaces()
123+
{
124+
return _decalGeometry ?? (_decalGeometry = new List<Face>());
125+
}
126+
127+
public void CalculateDecalGeometry()
128+
{
129+
_decalGeometry = new List<Face>();
130+
if (Decal == null) return; // Texture not found
131+
132+
var boxRadius = Coordinate.One * 4;
133+
// Decals apply to all faces that intersect within an 8x8x8 bounding box
134+
// centered at the origin of the decal
135+
var box = new Box(Origin - boxRadius, Origin + boxRadius);
136+
var root = GetRoot(Parent);
137+
// Get the faces that intersect with the decal's radius
138+
var faces = root.GetAllNodesIntersectingWith(box).OfType<Solid>()
139+
.SelectMany(x => x.Faces).Where(x => x.IntersectsWithBox(box));
140+
foreach (var face in faces)
141+
{
142+
// Project the decal onto the face
143+
var center = face.Plane.Project(Origin);
144+
var decalFace = new Face(int.MinValue) // Use a dummy ID
145+
{
146+
Colour = Colour,
147+
IsSelected = IsSelected,
148+
IsHidden = IsCodeHidden,
149+
Plane = face.Plane,
150+
Texture =
151+
{
152+
Name = Decal.Name,
153+
Texture = Decal,
154+
UAxis = face.Texture.UAxis,
155+
VAxis = face.Texture.VAxis,
156+
XScale = face.Texture.XScale,
157+
YScale = face.Texture.YScale,
158+
XShift = -Decal.Width / 2m,
159+
YShift = -Decal.Height / 2m
160+
}
161+
};
162+
// Re-project the vertices in case the texture axes are not on the face plane
163+
// Also add a tiny bit to the normal axis to ensure the decal is rendered in front of the face
164+
var normalAdd = face.Plane.Normal * 0.2m;
165+
var xShift = face.Texture.UAxis * face.Texture.XScale * Decal.Width / 2;
166+
var yShift = face.Texture.VAxis * face.Texture.YScale * Decal.Height / 2;
167+
decalFace.Vertices.Add(new Vertex(face.Plane.Project(center + xShift - yShift) + normalAdd, decalFace)); // Bottom Right
168+
decalFace.Vertices.Add(new Vertex(face.Plane.Project(center + xShift + yShift) + normalAdd, decalFace)); // Top Right
169+
decalFace.Vertices.Add(new Vertex(face.Plane.Project(center - xShift + yShift) + normalAdd, decalFace)); // Top Left
170+
decalFace.Vertices.Add(new Vertex(face.Plane.Project(center - xShift - yShift) + normalAdd, decalFace)); // Bottom Left
171+
// TODO: verify this covers all situations and I don't have to manually calculate the texture coordinates
172+
decalFace.FitTextureToPointCloud(new Cloud(decalFace.Vertices.Select(x => x.Location)));
173+
_decalGeometry.Add(decalFace);
174+
}
175+
}
176+
114177
public override void Transform(IUnitTransformation transform)
115178
{
116179
Origin = transform.Transform(Origin);
@@ -124,7 +187,9 @@ public override void Transform(IUnitTransformation transform)
124187
/// <returns>The closest intersecting point, or null if the line doesn't intersect.</returns>
125188
public override Coordinate GetIntersectionPoint(Line line)
126189
{
127-
return GetFaces().Select(x => x.GetIntersectionPoint(line))
190+
var faces = GetBoxFaces();
191+
if (_decalGeometry != null) faces = faces.Union(_decalGeometry);
192+
return faces.Select(x => x.GetIntersectionPoint(line))
128193
.Where(x => x != null)
129194
.OrderBy(x => (x - line.Start).VectorMagnitude())
130195
.FirstOrDefault();

Sledge.DataStructures/MapObjects/Face.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,18 @@ public virtual Coordinate GetIntersectionPoint(Line line)
345345
/// <returns>True if one of the face's edges intersects with the box.</returns>
346346
public bool IntersectsWithLine(Box box)
347347
{
348-
return GetLines().Any(box.IntersectsWith);
348+
// Shortcut through the bounding box to avoid the line computations if they aren't needed
349+
return BoundingBox.IntersectsWith(box) && GetLines().Any(box.IntersectsWith);
350+
}
351+
352+
/// <summary>
353+
/// Test this face to see if the given bounding box intersects with it
354+
/// </summary>
355+
/// <param name="box">The box to test against</param>
356+
/// <returns>True if the box intersects</returns>
357+
public bool IntersectsWithBox(Box box)
358+
{
359+
return box.GetBoxLines().Any(x => GetIntersectionPoint(x) != null);
349360
}
350361

351362
/// <summary>

0 commit comments

Comments
 (0)