private void LoadLevelFaces(Quake3Level q3lvl) { //----------------------------------------------------------------------- // Faces //----------------------------------------------------------------------- leafFaceGroups = new int[q3lvl.LeafFaces.Length]; Array.Copy(q3lvl.LeafFaces, 0, leafFaceGroups, 0, leafFaceGroups.Length); faceGroups = new BspStaticFaceGroup[q3lvl.Faces.Length]; // Set up index buffer // NB Quake3 indexes are 32-bit // Copy the indexes into a software area for staging numIndexes = q3lvl.NumElements + patchIndexCount; // Create an index buffer manually in system memory, allow space for patches indexes = HardwareBufferManager.Instance.CreateIndexBuffer( IndexType.Size32, numIndexes, BufferUsage.Dynamic ); // Write main indexes indexes.WriteData(0, Marshal.SizeOf(typeof(uint)) * q3lvl.NumElements, q3lvl.Elements, true); }
/// <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; // Initialize 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!" ); }
/// <summary> /// Tells the system to build the mesh relating to the surface into externally created buffers. /// </summary> /// <remarks> /// The VertexDeclaration of the vertex buffer must be identical to the one passed into /// <see cref="DefineSurface"/>. In addition, there must be enough space in the buffer to /// accommodate the patch at full detail level; you should check <see cref="RequiredVertexCount"/> /// and <see cref="RequiredIndexCount"/> to determine this. This method does not create an internal /// mesh for this patch and so GetMesh will return null if you call it after building the /// patch this way. /// </remarks> /// <param name="destVertexBuffer">The destination vertex buffer in which to build the patch.</param> /// <param name="vertexStart">The offset at which to start writing vertices for this patch.</param> /// <param name="destIndexBuffer">The destination index buffer in which to build the patch.</param> /// <param name="indexStart">The offset at which to start writing indexes for this patch.</param> public void Build(HardwareVertexBuffer destVertexBuffer, int vertexStart, HardwareIndexBuffer destIndexBuffer, int indexStart) { if(controlPoints.Count == 0) { return; } vertexBuffer = destVertexBuffer; vertexOffset = vertexStart; indexBuffer = destIndexBuffer; indexOffset = indexStart; // lock just the region we are interested in IntPtr lockedBuffer = vertexBuffer.Lock( vertexOffset * declaration.GetVertexSize(0), requiredVertexCount * declaration.GetVertexSize(0), BufferLocking.NoOverwrite); DistributeControlPoints(lockedBuffer); // subdivide the curves to the max // Do u direction first, so need to step over v levels not done yet int vStep = 1 << maxVLevel; int uStep = 1 << maxULevel; // subdivide this row in u for(int v = 0; v < meshHeight; v += vStep) { SubdivideCurve(lockedBuffer, v * meshWidth, uStep, meshWidth / uStep, uLevel); } // Now subdivide in v direction, this time all the u direction points are there so no step for(int u = 0; u < meshWidth; u++) { SubdivideCurve(lockedBuffer, u, vStep * meshWidth, meshHeight / vStep, vLevel); } // don't forget to unlock! vertexBuffer.Unlock(); // Make triangles from mesh at this current level of detail MakeTriangles(); }
/// <summary> /// Gets an iterator over the renderables required to render the shadow volume. /// </summary> /// <remarks> /// Shadowable geometry should ideally be designed such that there is only one /// ShadowRenderable required to render the the shadow; however this is not a necessary /// limitation and it can be exceeded if required. /// </remarks> /// <param name="technique">The technique being used to generate the shadow.</param> /// <param name="light">The light to generate the shadow from.</param> /// <param name="indexBuffer">The index buffer to build the renderables into, /// the current contents are assumed to be disposable.</param> /// <param name="extrudeVertices">If true, this means this class should extrude /// the vertices of the back of the volume in software. If false, it /// will not be done (a vertex program is assumed).</param> /// <param name="extrusionDistance"></param> /// <param name="flags">Technique-specific flags, see <see cref="ShadowRenderableFlags"/></param> /// <returns>An iterator that will allow iteration over all renderables for the full shadow volume.</returns> public abstract IEnumerator GetShadowVolumeRenderableEnumerator( ShadowTechnique technique, Light light, HardwareIndexBuffer indexBuffer, bool extrudeVertices, float extrusionDistance, int flags );
public IEnumerator GetShadowVolumeRenderableEnumerator( ShadowTechnique technique, Light light, HardwareIndexBuffer indexBuffer, float extrusionDistance, bool extrudeVertices ) { return GetShadowVolumeRenderableEnumerator( technique, light, indexBuffer, extrudeVertices, extrusionDistance, 0 ); }
public EntityShadowRenderable( Entity parent, HardwareIndexBuffer indexBuffer, VertexData vertexData, bool createSeperateLightCap, SubEntity subEntity ) : this( parent, indexBuffer, vertexData, createSeperateLightCap, subEntity, false ) { }
/// <summary> /// Populate the index buffer with the information in the data array /// </summary> /// <param name="idxBuffer">HardwareIndexBuffer to populate</param> /// <param name="indexCount">the number of indices</param> /// <param name="indexType">the type of index (e.g. IndexType.Size16)</param> /// <param name="data">the data to fill the buffer</param> internal void FillBuffer( HardwareIndexBuffer idxBuffer, int indexCount, IndexType indexType, int[ , ] data ) { int faceCount = data.GetLength( 0 ); int count = data.GetLength( 1 ); IntPtr indices = idxBuffer.Lock( BufferLocking.Discard ); if( indexType == IndexType.Size32 ) { // read the ints into the buffer data unsafe { int* pInts = (int*) indices.ToPointer(); for( int i = 0; i < faceCount; ++i ) for( int j = 0; j < count; ++j ) { Debug.Assert( i * count + j < indexCount, "Wrote off end of index buffer" ); pInts[ i * count + j ] = data[ i, j ]; } } } else { // read the shorts into the buffer data unsafe { short* pShorts = (short*) indices.ToPointer(); for( int i = 0; i < faceCount; ++i ) for( int j = 0; j < count; ++j ) { Debug.Assert( i * count + j < indexCount, "Wrote off end of index buffer" ); pShorts[ i * count + j ] = (short) data[ i, j ]; } } } // unlock the buffer to commit idxBuffer.Unlock(); }
private static void ReadBuffer(HardwareIndexBuffer idxBuffer, int maxIndex, IndexType indexType, ref int[,] data) { IntPtr indices = idxBuffer.Lock(BufferLocking.ReadOnly); int faceCount = data.GetLength(0); if (indexType == IndexType.Size32) { // read the ints from the buffer data unsafe { int* pInts = (int*)indices.ToPointer(); for (int i = 0; i < faceCount; ++i) for (int j = 0; j < 3; ++j) { Debug.Assert(i * 3 + j < maxIndex, "Read off end of index buffer"); data[i, j] = pInts[i * 3 + j]; } } } else { // read the shorts from the buffer data unsafe { short* pShorts = (short*)indices.ToPointer(); for (int i = 0; i < faceCount; ++i) for (int j = 0; j < 3; ++j) { Debug.Assert(i * 3 + j < maxIndex, "Read off end of index buffer"); data[i, j] = pShorts[i * 3 + j]; } } } // unlock the buffer idxBuffer.Unlock(); }
/// <summary> /// Gets an iterator over the renderables required to render the shadow volume. /// </summary> /// <remarks> /// Shadowable geometry should ideally be designed such that there is only one /// ShadowRenderable required to render the the shadow; however this is not a necessary /// limitation and it can be exceeded if required. /// </remarks> /// <param name="technique">The technique being used to generate the shadow.</param> /// <param name="light">The light to generate the shadow from.</param> /// <param name="indexBuffer">The index buffer to build the renderables into, /// the current contents are assumed to be disposable.</param> /// <param name="extrudeVertices">If true, this means this class should extrude /// the vertices of the back of the volume in software. If false, it /// will not be done (a vertex program is assumed).</param> /// <param name="flags">Technique-specific flags, see <see cref="ShadowRenderableFlags"/></param> /// <returns>An iterator that will allow iteration over all renderables for the full shadow volume.</returns> public abstract IEnumerator GetShadowVolumeRenderableEnumerator(ShadowTechnique technique, Light light, HardwareIndexBuffer indexBuffer, bool extrudeVertices, float extrusionDistance, int flags);
public RegionShadowRenderable( Region parent, HardwareIndexBuffer indexBuffer, VertexData vertexData, bool createSeparateLightCap ) : this( parent, indexBuffer, vertexData, createSeparateLightCap, false ) { }
public override IEnumerator GetShadowVolumeRenderableEnumerator(ShadowTechnique technique, Light light, HardwareIndexBuffer indexBuffer, bool extrudeVertices, float extrusionDistance, int flags) { return dummyList.GetEnumerator(); }
public RegionShadowRenderable( Region parent, HardwareIndexBuffer indexBuffer, VertexData vertexData, bool createSeparateLightCap, bool isLightCap ) { throw new NotImplementedException(); }
public IEnumerator GetShadowVolumeRenderableIterator( ShadowTechnique shadowTechnique, Light light, HardwareIndexBuffer indexBuffer, bool extrudeVertices, float extrusionDistance, ulong flags ) { Debug.Assert( indexBuffer != null, "Only external index buffers are supported right now" ); Debug.Assert( indexBuffer.Type == IndexType.Size16, "Only 16-bit indexes supported for now" ); // Calculate the object space light details var lightPos = light.GetAs4DVector(); var world2Obj = parentNode.FullTransform.Inverse(); lightPos = world2Obj*lightPos; // We need to search the edge list for silhouette edges if ( this.edgeList == null ) { throw new Exception( "You enabled stencil shadows after the buid process! In " + "Region.GetShadowVolumeRenderableIterator" ); } // Init shadow renderable list if required var init = this.shadowRenderables.Count == 0; RegionShadowRenderable esr = null; //bool updatedSharedGeomNormals = false; for ( var i = 0; i < this.edgeList.EdgeGroups.Count; i++ ) { var group = (EdgeData.EdgeGroup)this.edgeList.EdgeGroups[ i ]; if ( init ) { // Create a new renderable, create a separate light cap if // we're using a vertex program (either for this model, or // for extruding the shadow volume) since otherwise we can // get depth-fighting on the light cap esr = new RegionShadowRenderable( this, indexBuffer, group.vertexData, this.vertexProgramInUse || !extrudeVertices ); this.shadowRenderables.Add( esr ); } else { esr = (RegionShadowRenderable)this.shadowRenderables[ i ]; } // Extrude vertices in software if required if ( extrudeVertices ) { ExtrudeVertices( esr.PositionBuffer, group.vertexData.vertexCount, lightPos, extrusionDistance ); } } return (IEnumerator)this.shadowRenderables; }
/// <summary> /// /** Internal utility function for loading data from Quake3. /// </summary> protected void LoadQuake3Level( Quake3Level q3lvl ) { ResourceGroupManager rgm = ResourceGroupManager.Instance; rgm.notifyWorldGeometryStageStarted( "Parsing entities" ); LoadEntities( q3lvl ); rgm.notifyWorldGeometryStageEnded(); rgm.notifyWorldGeometryStageStarted( "Extracting lightmaps" ); q3lvl.ExtractLightmaps(); rgm.notifyWorldGeometryStageEnded(); //----------------------------------------------------------------------- // Vertices //----------------------------------------------------------------------- // Allocate memory for vertices & copy vertexData = new VertexData(); // Create vertex declaration VertexDeclaration decl = vertexData.vertexDeclaration; int offset = 0; int lightTexOffset = 0; decl.AddElement( 0, offset, VertexElementType.Float3, VertexElementSemantic.Position ); offset += VertexElement.GetTypeSize( VertexElementType.Float3 ); decl.AddElement( 0, offset, VertexElementType.Float3, VertexElementSemantic.Normal ); offset += VertexElement.GetTypeSize( VertexElementType.Float3 ); decl.AddElement( 0, offset, VertexElementType.Float2, VertexElementSemantic.TexCoords, 0 ); offset += VertexElement.GetTypeSize( VertexElementType.Float2 ); decl.AddElement( 0, offset, VertexElementType.Float2, VertexElementSemantic.TexCoords, 1 ); // Build initial patches - we need to know how big the vertex buffer needs to be // to accommodate the subdivision // we don't want to include the elements for texture lighting, so we clone it rgm.notifyWorldGeometryStageStarted( "Initializing patches" ); InitQuake3Patches( q3lvl, (VertexDeclaration)decl.Clone() ); rgm.notifyWorldGeometryStageEnded(); // this is for texture lighting color and alpha decl.AddElement( 1, lightTexOffset, VertexElementType.Color, VertexElementSemantic.Diffuse ); lightTexOffset += VertexElement.GetTypeSize( VertexElementType.Color ); // this is for texture lighting coords decl.AddElement( 1, lightTexOffset, VertexElementType.Float2, VertexElementSemantic.TexCoords, 2 ); rgm.notifyWorldGeometryStageStarted( "Setting up vertex data" ); // Create the vertex buffer, allow space for patches HardwareVertexBuffer vbuf = HardwareBufferManager.Instance.CreateVertexBuffer( decl.Clone(0), q3lvl.NumVertices + patchVertexCount, BufferUsage.StaticWriteOnly /* the vertices will be read often for texture lighting, use shadow buffer */, true ); // Create the vertex buffer for texture lighting, allow space for patches HardwareVertexBuffer texLightBuf = HardwareBufferManager.Instance.CreateVertexBuffer( decl.Clone(1), q3lvl.NumVertices + patchVertexCount, BufferUsage.DynamicWriteOnly, false ); // COPY static vertex data - Note that we can't just block-copy the vertex data because we have to reorder // our vertex elements; this is to ensure compatibility with older cards when using // hardware vertex buffers - Direct3D requires that the buffer format maps onto a // FVF in those older drivers. // Lock just the non-patch area for now. unsafe { BspVertex vert = new BspVertex(); TextureLightMap texLightMap = new TextureLightMap(); // Keep another base pointer for use later in patch building for ( int v = 0; v < q3lvl.NumVertices; v++ ) { QuakeVertexToBspVertex( q3lvl.Vertices[ v ], out vert, out texLightMap ); BspVertex* bvptr = | TextureLightMap* tlptr = &texLightMap; vbuf.WriteData( v * sizeof( BspVertex ), sizeof( BspVertex ), (IntPtr)bvptr ); texLightBuf.WriteData( v * sizeof( TextureLightMap ), sizeof( TextureLightMap ), (IntPtr)tlptr ); } } // Setup binding vertexData.vertexBufferBinding.SetBinding( 0, vbuf ); // Setup texture lighting binding vertexData.vertexBufferBinding.SetBinding( 1, texLightBuf ); // Set other data vertexData.vertexStart = 0; vertexData.vertexCount = q3lvl.NumVertices + patchVertexCount; rgm.notifyWorldGeometryStageEnded(); //----------------------------------------------------------------------- // Faces //----------------------------------------------------------------------- rgm.notifyWorldGeometryStageStarted( "Setting up face data" ); leafFaceGroups = new int[ q3lvl.LeafFaces.Length ]; Array.Copy( q3lvl.LeafFaces, 0, leafFaceGroups, 0, leafFaceGroups.Length ); faceGroups = new BspStaticFaceGroup[ q3lvl.Faces.Length ]; // Set up index buffer // NB Quake3 indexes are 32-bit // Copy the indexes into a software area for staging numIndexes = q3lvl.NumElements + patchIndexCount; // Create an index buffer manually in system memory, allow space for patches indexes = HardwareBufferManager.Instance.CreateIndexBuffer( IndexType.Size32, numIndexes, BufferUsage.Dynamic ); // Write main indexes indexes.WriteData( 0, Marshal.SizeOf( typeof( uint ) ) * q3lvl.NumElements, q3lvl.Elements, true ); rgm.notifyWorldGeometryStageEnded(); // now build patch information rgm.notifyWorldGeometryStageStarted( "Building patches" ); BuildQuake3Patches( q3lvl.NumVertices, q3lvl.NumElements ); rgm.notifyWorldGeometryStageEnded(); //----------------------------------------------------------------------- // Create materials for shaders //----------------------------------------------------------------------- // NB this only works for the 'default' shaders for now // i.e. those that don't have a .shader script and thus default // to just texture + lightmap // TODO: pre-parse all .shader files and create lookup for next stage (use ROGL shader_file_t) // Material names are shadername#lightmapnumber // This is because I like to define materials up front completely // rather than combine lightmap and shader dynamically (it's // more generic). It results in more materials, but they're small // beer anyway. Texture duplication is prevented by infrastructure. // To do this I actually need to parse the faces since they have the // shader/lightmap combo (lightmap number is not in the shader since // it can be used with multiple lightmaps) string shaderName; int face = q3lvl.Faces.Length; int progressCountdown = 100; int progressCount = 0; while ( face-- > 0 ) { // Progress reporting if ( progressCountdown == 100 ) { ++progressCount; String str = String.Format( "Loading materials (phase {0})", progressCount ); rgm.notifyWorldGeometryStageStarted( str ); } else if ( progressCountdown == 0 ) { // stage report rgm.notifyWorldGeometryStageEnded(); progressCountdown = 100 + 1; } // Check to see if existing material // Format shader#lightmap int shadIdx = q3lvl.Faces[ face ].shader; shaderName = String.Format( "{0}#{1}", q3lvl.Shaders[ shadIdx ].name, q3lvl.Faces[ face ].lmTexture ); Material shadMat = (Material)MaterialManager.Instance.GetByName( shaderName ); if ( shadMat == null && !bspOptions.useLightmaps ) { // try the no-lightmap material shaderName = String.Format( "{0}#n", q3lvl.Shaders[ shadIdx ].name ); shadMat = (Material)MaterialManager.Instance.GetByName( shaderName ); } if ( shadMat == null ) { // Color layer // NB no extension in Q3A(doh), have to try shader, .jpg, .tga string tryName = q3lvl.Shaders[ shadIdx ].name; // Try shader first Quake3Shader shader = (Quake3Shader)Quake3ShaderManager.Instance.GetByName( tryName ); if ( shader != null ) { shadMat = shader.CreateAsMaterial( q3lvl.Faces[ face ].lmTexture ); } else { // No shader script, try default type texture shadMat = (Material)MaterialManager.Instance.Create( shaderName, rgm.WorldResourceGroupName ); Pass shadPass = shadMat.GetTechnique( 0 ).GetPass( 0 ); // Try jpg TextureUnitState tex = null; if ( ResourceGroupManager.Instance.ResourceExists( rgm.WorldResourceGroupName, tryName + ".jpg" ) ) { tex = shadPass.CreateTextureUnitState( tryName + ".jpg" ); } if ( ResourceGroupManager.Instance.ResourceExists( rgm.WorldResourceGroupName, tryName + ".tga" ) ) { tex = shadPass.CreateTextureUnitState( tryName + ".tga" ); } if ( tex != null ) { // Set replace on all first layer textures for now tex.SetColorOperation( LayerBlendOperation.Replace ); tex.SetTextureAddressingMode( TextureAddressing.Wrap ); // for ambient lighting tex.ColorBlendMode.source2 = LayerBlendSource.Manual; } if ( bspOptions.useLightmaps && q3lvl.Faces[ face ].lmTexture != -1 ) { // Add lightmap, additive blending tex = shadPass.CreateTextureUnitState( String.Format( "@lightmap{0}", q3lvl.Faces[ face ].lmTexture ) ); // Blend tex.SetColorOperation( LayerBlendOperation.Modulate ); // Use 2nd texture co-ordinate set tex.TextureCoordSet = 1; // Clamp tex.SetTextureAddressingMode( TextureAddressing.Clamp ); } shadMat.CullingMode = CullingMode.None; shadMat.Lighting = false; } } shadMat.Load(); // Copy face data BspStaticFaceGroup dest = new BspStaticFaceGroup(); InternalBspFace src = q3lvl.Faces[ face ]; if ( ( q3lvl.Shaders[ src.shader ].surfaceFlags & SurfaceFlags.Sky ) == SurfaceFlags.Sky ) dest.isSky = true; else dest.isSky = false; dest.materialHandle = shadMat.Handle; dest.elementStart = src.elemStart; dest.numElements = src.elemCount; dest.numVertices = src.vertCount; dest.vertexStart = src.vertStart; dest.plane = new Plane(); if ( Quake3ShaderManager.Instance.GetByName( q3lvl.Shaders[ shadIdx ].name ) != null ) { // it's a quake shader dest.isQuakeShader = true; } if ( src.type == BspFaceType.Normal ) { dest.type = FaceGroup.FaceList; // Assign plane dest.plane.Normal = new Vector3( src.normal[ 0 ], src.normal[ 1 ], src.normal[ 2 ] ); dest.plane.D = -dest.plane.Normal.Dot( new Vector3( src.org[ 0 ], src.org[ 1 ], src.org[ 2 ] ) ); // Don't rebase indexes here - Quake3 re-uses some indexes for multiple vertex // groups eg repeating small details have the same relative vertex data but // use the same index data. } else if ( src.type == BspFaceType.Patch ) { // Seems to be some crap in the Q3 level where vertex count = 0 or num control points = 0? if ( ( dest.numVertices == 0 ) || ( src.meshCtrl[ 0 ] == 0 ) ) { dest.type = FaceGroup.Unknown; } else { // Set up patch surface dest.type = FaceGroup.Patch; // Locate the patch we already built if ( !patches.ContainsKey( face ) ) throw new AxiomException( "Patch not found from previous built state." ); dest.patchSurf = (PatchSurface)patches[ face ]; } } else if ( src.type == BspFaceType.Mesh ) { dest.type = FaceGroup.FaceList; // Assign plane dest.plane.Normal = new Vector3( src.normal[ 0 ], src.normal[ 1 ], src.normal[ 2 ] ); dest.plane.D = -dest.plane.Normal.Dot( new Vector3( src.org[ 0 ], src.org[ 1 ], src.org[ 2 ] ) ); } else { LogManager.Instance.Write( "!!! Unknown face type !!!" ); } faceGroups[ face ] = dest; } //----------------------------------------------------------------------- // Nodes //----------------------------------------------------------------------- // Allocate memory for all nodes (leaves and splitters) nodes = new BspNode[ q3lvl.NumNodes + q3lvl.NumLeaves ]; numLeaves = q3lvl.NumLeaves; leafStart = q3lvl.NumNodes; // Run through and initialize the array so front/back node pointers // aren't null. for ( int i = 0; i < nodes.Length; i++ ) nodes[ i ] = new BspNode(); // Convert nodes // In our array, first q3lvl.NumNodes are non-leaf, others are leaves for ( int i = 0; i < q3lvl.NumNodes; i++ ) { BspNode node = nodes[ i ]; InternalBspNode q3node = q3lvl.Nodes[ i ]; node.IsLeaf = false; node.Owner = this; Plane splitPlane = new Plane(); // Set plane splitPlane.Normal = new Vector3( q3lvl.Planes[ q3node.plane ].normal[ 0 ], q3lvl.Planes[ q3node.plane ].normal[ 1 ], q3lvl.Planes[ q3node.plane ].normal[ 2 ] ); splitPlane.D = -q3lvl.Planes[ q3node.plane ].distance; node.SplittingPlane = splitPlane; // Set bounding box node.BoundingBox = new AxisAlignedBox( new Vector3( q3node.bbox[ 0 ], q3node.bbox[ 1 ], q3node.bbox[ 2 ] ), new Vector3( q3node.bbox[ 3 ], q3node.bbox[ 4 ], q3node.bbox[ 5 ] ) ); // Set back pointer // Negative indexes in Quake3 mean leaves. if ( q3node.back < 0 ) node.BackNode = nodes[ leafStart + ( ~( q3node.back ) ) ]; else node.BackNode = nodes[ q3node.back ]; // Set front pointer // Negative indexes in Quake3 mean leaves if ( q3node.front < 0 ) node.FrontNode = nodes[ leafStart + ( ~( q3node.front ) ) ]; else node.FrontNode = nodes[ q3node.front ]; } //----------------------------------------------------------------------- // Brushes //----------------------------------------------------------------------- // Reserve enough memory for all brushes, solid or not (need to maintain indexes) brushes = new BspBrush[ q3lvl.NumBrushes ]; for ( int i = 0; i < q3lvl.NumBrushes; i++ ) { InternalBspBrush q3brush = q3lvl.Brushes[ i ]; // Create a new OGRE brush BspBrush brush = new BspBrush(); int numBrushSides = q3brush.numSides; int brushSideIdx = q3brush.firstSide; // Iterate over the sides and create plane for each while ( numBrushSides-- > 0 ) { InternalBspPlane side = q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ]; // Notice how we normally invert Q3A plane distances, but here we do not // Because we want plane normals pointing out of solid brushes, not in. Plane brushSide = new Plane( new Vector3( q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ].normal[ 0 ], q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ].normal[ 1 ], q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ].normal[ 2 ] ), q3lvl.Planes[ q3lvl.BrushSides[ brushSideIdx ].planeNum ].distance ); brush.Planes.Add( brushSide ); brushSideIdx++; } // Build world fragment brush.Fragment.FragmentType = WorldFragmentType.PlaneBoundedRegion; brush.Fragment.Planes = brush.Planes; brushes[ i ] = brush; } //----------------------------------------------------------------------- // Leaves //----------------------------------------------------------------------- for ( int i = 0; i < q3lvl.NumLeaves; ++i ) { BspNode node = nodes[ i + this.LeafStart ]; InternalBspLeaf q3leaf = q3lvl.Leaves[ i ]; node.IsLeaf = true; node.Owner = this; // Set bounding box node.BoundingBox.Minimum = new Vector3( q3leaf.bbox[ 0 ], q3leaf.bbox[ 1 ], q3leaf.bbox[ 2 ] ); node.BoundingBox.Maximum = new Vector3( q3leaf.bbox[ 3 ], q3leaf.bbox[ 4 ], q3leaf.bbox[ 5 ] ); // Set faces node.FaceGroupStart = q3leaf.faceStart; node.NumFaceGroups = q3leaf.faceCount; node.VisCluster = q3leaf.cluster; // Load Brushes for this leaf int realBrushIdx = 0, solidIdx = 0; int brushCount = q3leaf.brushCount; int brushIdx = q3leaf.brushStart; node.SolidBrushes = new BspBrush[ brushCount ]; while ( brushCount-- > 0 ) { realBrushIdx = q3lvl.LeafBrushes[ brushIdx ]; InternalBspBrush q3brush = q3lvl.Brushes[ realBrushIdx ]; // Only load solid ones, we don't care about any other types // Shader determines this. InternalBspShader brushShader = q3lvl.Shaders[ q3brush.shaderIndex ]; if ( ( brushShader.contentFlags & ContentFlags.Solid ) == ContentFlags.Solid ) node.SolidBrushes[ solidIdx ] = brushes[ realBrushIdx ]; brushIdx++; solidIdx++; } } // Vis - just copy visData.numClusters = q3lvl.VisData.clusterCount; visData.rowLength = q3lvl.VisData.rowSize; visData.tableData = new byte[ q3lvl.VisData.rowSize * q3lvl.VisData.clusterCount ]; Array.Copy( q3lvl.VisData.data, 0, visData.tableData, 0, visData.tableData.Length ); }
public override void NotifyIndexBufferDestroyed( HardwareIndexBuffer buffer ) { _baseInstance.NotifyIndexBufferDestroyed( buffer ); }
public IEnumerator GetShadowVolumeRenderableEnumerator(ShadowTechnique technique, Light light, HardwareIndexBuffer indexBuffer, float extrusionDistance, bool extrudeVertices) { return(GetShadowVolumeRenderableEnumerator(technique, light, indexBuffer, extrudeVertices, extrusionDistance, 0)); }
public override IEnumerator GetShadowVolumeRenderableEnumerator( ShadowTechnique technique, Light light, HardwareIndexBuffer indexBuffer, bool extrudeVertices, float extrusionDistance, int flags ) { Debug.Assert( indexBuffer != null, "Only external index buffers are supported right now" ); Debug.Assert( indexBuffer.Type == IndexType.Size16, "Only 16-bit indexes supported for now" ); // Potentially delegate to LOD entity if ( this.meshLodIndex > 0 && this.mesh.IsLodManual ) { Debug.Assert( this.meshLodIndex - 1 < this.lodEntityList.Count, "No LOD EntityList - did you build the manual LODs after creating the entity?" ); var lodEnt = this.lodEntityList[ this.meshLodIndex - 1 ]; // index - 1 as we skip index 0 (original LOD) if ( HasSkeleton && lodEnt.HasSkeleton ) { // Copy the animation state set to lod entity, we assume the lod // entity only has a subset animation states CopyAnimationStateSubset( lodEnt.animationState, this.animationState ); } return lodEnt.GetShadowVolumeRenderableEnumerator( technique, light, indexBuffer, extrudeVertices, extrusionDistance, flags ); } // Prep mesh if required // NB This seems to result in memory corruptions, having problems // tracking them down. For now, ensure that shadows are enabled // before any entities are created if ( !this.mesh.IsPreparedForShadowVolumes ) { this.mesh.PrepareForShadowVolume(); // reset frame last updated to force update of buffers this.frameAnimationLastUpdated = 0; // re-prepare buffers PrepareTempBlendedBuffers(); } // Update any animation UpdateAnimation(); // Calculate the object space light details var lightPos = light.GetAs4DVector(); // Only use object-space light if we're not doing transforms // Since when animating the positions are already transformed into // world space so we need world space light position var isAnimated = HasSkeleton || this.mesh.HasVertexAnimation; if ( !isAnimated ) { var world2Obj = parentNode.FullTransform.Inverse(); lightPos = world2Obj*lightPos; } // We need to search the edge list for silhouette edges var edgeList = GetEdgeList(); // Init shadow renderable list if required var init = ( this.shadowRenderables.Count == 0 ); if ( init ) { this.shadowRenderables.Capacity = edgeList.edgeGroups.Count; } var updatedSharedGeomNormals = false; EntityShadowRenderable esr = null; EdgeData.EdgeGroup egi; // note: using capacity for the loop since no items are in the list yet. // capacity is set to how large the collection will be in the end for ( var i = 0; i < this.shadowRenderables.Capacity; i++ ) { egi = (EdgeData.EdgeGroup)edgeList.edgeGroups[ i ]; var data = ( isAnimated ? FindBlendedVertexData( egi.vertexData ) : egi.vertexData ); if ( init ) { // Try to find corresponding SubEntity; this allows the // linkage of visibility between ShadowRenderable and SubEntity var subEntity = FindSubEntityForVertexData( egi.vertexData ); // Create a new renderable, create a separate light cap if // we're using hardware skinning since otherwise we get // depth-fighting on the light cap esr = new EntityShadowRenderable( this, indexBuffer, data, subEntity.VertexProgramInUse || !extrudeVertices, subEntity ); this.shadowRenderables.Add( esr ); } else { esr = (EntityShadowRenderable)this.shadowRenderables[ i ]; if ( HasSkeleton ) { // If we have a skeleton, we have no guarantee that the position // buffer we used last frame is the same one we used last frame // since a temporary buffer is requested each frame // therefore, we need to update the EntityShadowRenderable // with the current position buffer esr.RebindPositionBuffer( data, isAnimated ); } } // For animated entities we need to recalculate the face normals if ( isAnimated ) { if ( egi.vertexData != this.mesh.SharedVertexData || !updatedSharedGeomNormals ) { // recalculate face normals edgeList.UpdateFaceNormals( egi.vertexSet, esr.PositionBuffer ); // If we're not extruding in software we still need to update // the latter part of the buffer (the hardware extruded part) // with the latest animated positions if ( !extrudeVertices ) { var srcPtr = esr.PositionBuffer.Lock( BufferLocking.Normal ); var destPtr = srcPtr + ( egi.vertexData.vertexCount*12 ); // 12 = sizeof(float) * 3 Memory.Copy( srcPtr, destPtr, 12*egi.vertexData.vertexCount ); esr.PositionBuffer.Unlock(); } if ( egi.vertexData == this.mesh.SharedVertexData ) { updatedSharedGeomNormals = true; } } } // Extrude vertices in software if required if ( extrudeVertices ) { ExtrudeVertices( esr.PositionBuffer, egi.vertexData.vertexCount, lightPos, extrusionDistance ); } // Stop suppressing hardware update now, if we were esr.PositionBuffer.SuppressHardwareUpdate( false ); } // Calc triangle light facing UpdateEdgeListLightFacing( edgeList, lightPos ); // Generate indexes and update renderables GenerateShadowVolume( edgeList, indexBuffer, light, this.shadowRenderables, flags ); return this.shadowRenderables.GetEnumerator(); }
/// <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!"); }
public EntityShadowRenderable( Entity parent, HardwareIndexBuffer indexBuffer, VertexData vertexData, bool createSeparateLightCap, SubEntity subEntity, bool isLightCap ) { this.parent = parent; // Save link to vertex data this.currentVertexData = vertexData; // Initialize render op renderOperation.indexData = new IndexData(); renderOperation.indexData.indexBuffer = indexBuffer; renderOperation.indexData.indexStart = 0; // index start and count are sorted out later // Create vertex data which just references position component (and 2 component) renderOperation.vertexData = new VertexData(); renderOperation.vertexData.vertexDeclaration = HardwareBufferManager.Instance.CreateVertexDeclaration(); renderOperation.vertexData.vertexBufferBinding = HardwareBufferManager.Instance.CreateVertexBufferBinding(); // Map in position data renderOperation.vertexData.vertexDeclaration.AddElement( 0, 0, VertexElementType.Float3, VertexElementSemantic.Position ); this.originalPosBufferBinding = vertexData.vertexDeclaration.FindElementBySemantic( VertexElementSemantic.Position ).Source; this.positionBuffer = vertexData.vertexBufferBinding.GetBuffer( this.originalPosBufferBinding ); renderOperation.vertexData.vertexBufferBinding.SetBinding( 0, this.positionBuffer ); // Map in w-coord buffer (if present) if ( vertexData.hardwareShadowVolWBuffer != null ) { renderOperation.vertexData.vertexDeclaration.AddElement( 1, 0, VertexElementType.Float1, VertexElementSemantic.TexCoords, 0 ); this.wBuffer = vertexData.hardwareShadowVolWBuffer; renderOperation.vertexData.vertexBufferBinding.SetBinding( 1, this.wBuffer ); } // Use same vertex start as input renderOperation.vertexData.vertexStart = vertexData.vertexStart; if ( isLightCap ) { // Use original vertex count, no extrusion renderOperation.vertexData.vertexCount = vertexData.vertexCount; } else { // Vertex count must take into account the doubling of the buffer, // because second half of the buffer is the extruded copy renderOperation.vertexData.vertexCount = vertexData.vertexCount*2; if ( createSeparateLightCap ) { // Create child light cap lightCap = new EntityShadowRenderable( parent, indexBuffer, vertexData, false, subEntity, true ); } } }
private void CreateBuffers(ushort[] indices, short[] vertices) { var numIndices = indices.Length; var numVertices = vertices.Length; _vertexDeclaration = HardwareBufferManager.Instance.CreateVertexDeclaration(); _vertexDeclaration.AddElement(0, 0, VertexElementType.Short2, VertexElementSemantic.Position); _ib = HardwareBufferManager.Instance.CreateIndexBuffer(IndexType.Size16, numIndices, BufferUsage.WriteOnly); _vb = HardwareBufferManager.Instance.CreateVertexBuffer(_vertexDeclaration, numVertices, BufferUsage.WriteOnly, false); _ib.WriteData(0, numIndices * sizeof(ushort), indices, true); _vb.WriteData(0, numVertices * sizeof(ushort), vertices, true); var binding = new VertexBufferBinding(); binding.SetBinding(0, _vb); VertexData = new VertexData(); VertexData.vertexDeclaration = _vertexDeclaration; VertexData.vertexBufferBinding = binding; VertexData.vertexCount = numVertices; VertexData.vertexStart = 0; IndexData = new IndexData(); IndexData.indexBuffer = _ib; IndexData.indexCount = numIndices; IndexData.indexStart = 0; }