/// <summary>
/// Generates the indexes required to render a shadow volume into the
/// index buffer which is passed in, and updates shadow renderables to use it.
/// </summary>
/// <param name="edgeData">The edge information to use.</param>
/// <param name="indexBuffer">The buffer into which to write data into; current
/// contents are assumed to be discardable.</param>
/// <param name="light">The light, mainly for type info as silhouette calculations
/// should already have been done in <see cref="UpdateEdgeListLightFacing"/></param>
/// <param name="shadowRenderables">A list of shadow renderables which has
/// already been constructed but will need populating with details of
/// the index ranges to be used.</param>
/// <param name="flags">Additional controller flags, see <see cref="ShadowRenderableFlags"/>.</param>
protected virtual void GenerateShadowVolume(EdgeData edgeData, HardwareIndexBuffer indexBuffer, Light light,
ShadowRenderableList shadowRenderables, int flags)
{
// Edge groups should be 1:1 with shadow renderables
Debug.Assert(edgeData.edgeGroups.Count == shadowRenderables.Count);
LightType lightType = light.Type;
bool extrudeToInfinity = (flags & (int)ShadowRenderableFlags.ExtrudeToInfinity) > 0;
// Lock index buffer for writing
IntPtr idxPtr = indexBuffer.Lock(BufferLocking.Discard);
int indexStart = 0;
unsafe {
// TODO: Will currently cause an overflow for 32 bit indices, revisit
short *pIdx = (short *)idxPtr.ToPointer();
int count = 0;
// Iterate over the groups and form renderables for each based on their
// lightFacing
for (int groupCount = 0; groupCount < edgeData.edgeGroups.Count; groupCount++)
{
EdgeData.EdgeGroup eg = (EdgeData.EdgeGroup)edgeData.edgeGroups[groupCount];
ShadowRenderable si = (ShadowRenderable)shadowRenderables[groupCount];
RenderOperation lightShadOp = null;
// Initialise the index bounds for this shadow renderable
RenderOperation shadOp = si.GetRenderOperationForUpdate();
shadOp.indexData.indexCount = 0;
shadOp.indexData.indexStart = indexStart;
// original number of verts (without extruded copy)
int originalVertexCount = eg.vertexData.vertexCount;
bool firstDarkCapTri = true;
int darkCapStart = 0;
for (int edgeCount = 0; edgeCount < eg.edges.Count; edgeCount++)
{
EdgeData.Edge edge = (EdgeData.Edge)eg.edges[edgeCount];
EdgeData.Triangle t1 = (EdgeData.Triangle)edgeData.triangles[edge.triIndex[0]];
EdgeData.Triangle t2 =
edge.isDegenerate ? (EdgeData.Triangle)edgeData.triangles[edge.triIndex[0]] : (EdgeData.Triangle)edgeData.triangles[edge.triIndex[1]];
if (t1.lightFacing && (edge.isDegenerate || !t2.lightFacing))
{
/* Silhouette edge, first tri facing the light
* Also covers degenerate tris where only tri 1 is valid
* Remember verts run anticlockwise along the edge from
* tri 0 so to point shadow volume tris outward, light cap
* indexes have to be backwards
*
* We emit 2 tris if light is a point light, 1 if light
* is directional, because directional lights cause all
* points to converge to a single point at infinity.
*
* First side tri = near1, near0, far0
* Second tri = far0, far1, near1
*
* 'far' indexes are 'near' index + originalVertexCount
* because 'far' verts are in the second half of the
* buffer
*/
pIdx[count++] = (short)edge.vertIndex[1];
pIdx[count++] = (short)edge.vertIndex[0];
pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount);
shadOp.indexData.indexCount += 3;
if (!(lightType == LightType.Directional && extrudeToInfinity))
{
// additional tri to make quad
pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount);
pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount);
pIdx[count++] = (short)edge.vertIndex[1];
shadOp.indexData.indexCount += 3;
}
// Do dark cap tri
// Use McGuire et al method, a triangle fan covering all silhouette
// edges and one point (taken from the initial tri)
if ((flags & (int)ShadowRenderableFlags.IncludeDarkCap) > 0)
{
if (firstDarkCapTri)
{
darkCapStart = edge.vertIndex[0] + originalVertexCount;
firstDarkCapTri = false;
}
else
{
pIdx[count++] = (short)darkCapStart;
pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount);
pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount);
shadOp.indexData.indexCount += 3;
}
}
}
else if (!t1.lightFacing && (edge.isDegenerate || t2.lightFacing))
{
// Silhouette edge, second tri facing the light
// Note edge indexes inverse of when t1 is light facing
pIdx[count++] = (short)edge.vertIndex[0];
pIdx[count++] = (short)edge.vertIndex[1];
pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount);
shadOp.indexData.indexCount += 3;
if (!(lightType == LightType.Directional && extrudeToInfinity))
{
// additional tri to make quad
pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount);
pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount);
pIdx[count++] = (short)edge.vertIndex[0];
shadOp.indexData.indexCount += 3;
}
// Do dark cap tri
// Use McGuire et al method, a triangle fan covering all silhouette
// edges and one point (taken from the initial tri)
if ((flags & (int)ShadowRenderableFlags.IncludeDarkCap) > 0)
{
if (firstDarkCapTri)
{
darkCapStart = edge.vertIndex[1] + originalVertexCount;
firstDarkCapTri = false;
}
else
{
pIdx[count++] = (short)darkCapStart;
pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount);
pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount);
shadOp.indexData.indexCount += 3;
}
}
}
}
// Do light cap
if ((flags & (int)ShadowRenderableFlags.IncludeLightCap) > 0)
{
ShadowRenderable lightCapRend = null;
if (si.IsLightCapSeperate)
{
// separate light cap
lightCapRend = si.LightCapRenderable;
lightShadOp = lightCapRend.GetRenderOperationForUpdate();
lightShadOp.indexData.indexCount = 0;
// start indexes after the current total
// NB we don't update the total here since that's done below
lightShadOp.indexData.indexStart =
indexStart + shadOp.indexData.indexCount;
}
for (int triCount = 0; triCount < edgeData.triangles.Count; triCount++)
{
EdgeData.Triangle t = (EdgeData.Triangle)edgeData.triangles[triCount];
// Light facing, and vertex set matches
if (t.lightFacing && t.vertexSet == eg.vertexSet)
{
pIdx[count++] = (short)t.vertIndex[0];
pIdx[count++] = (short)t.vertIndex[1];
pIdx[count++] = (short)t.vertIndex[2];
if (lightShadOp != null)
{
lightShadOp.indexData.indexCount += 3;
}
else
{
shadOp.indexData.indexCount += 3;
}
}
}
}
// update next indexStart (all renderables sharing the buffer)
indexStart += shadOp.indexData.indexCount;
// add on the light cap too
if (lightShadOp != null)
{
indexStart += lightShadOp.indexData.indexCount;
}
}
}
// Unlock index buffer
indexBuffer.Unlock();
Debug.Assert(indexStart <= indexBuffer.IndexCount, "Index buffer overrun while generating shadow volume!");
}