/// <summary> /// Utility method, extract info from the given VertexData /// </summary> public void ExtractFrom(VertexData sourceData) { // Release old buffer copies first HardwareBufferManager mgr = HardwareBufferManager.Instance; if (destPositionBuffer != null) { mgr.ReleaseVertexBufferCopy(destPositionBuffer); Debug.Assert(destPositionBuffer == null); } if (destNormalBuffer != null) { mgr.ReleaseVertexBufferCopy(destNormalBuffer); Debug.Assert(destNormalBuffer == null); } VertexDeclaration decl = sourceData.vertexDeclaration; VertexBufferBinding bind = sourceData.vertexBufferBinding; VertexElement posElem = decl.FindElementBySemantic(VertexElementSemantic.Position); VertexElement normElem = decl.FindElementBySemantic(VertexElementSemantic.Normal); VertexElement tanElem = decl.FindElementBySemantic(VertexElementSemantic.Tangent); VertexElement binormElem = decl.FindElementBySemantic(VertexElementSemantic.Binormal); Debug.Assert(posElem != null, "Positions are required"); posBindIndex = posElem.Source; srcPositionBuffer = bind.GetBuffer(posBindIndex); if (normElem == null) { posNormalShareBuffer = false; srcNormalBuffer = null; } else { normBindIndex = normElem.Source; if (normBindIndex == posBindIndex) { posNormalShareBuffer = true; srcNormalBuffer = null; } else { posNormalShareBuffer = false; srcNormalBuffer = bind.GetBuffer(normBindIndex); } } if (tanElem == null) { srcTangentBuffer = null; } else { tanBindIndex = tanElem.Source; srcTangentBuffer = bind.GetBuffer(tanBindIndex); } if (binormElem == null) { srcBinormalBuffer = null; } else { binormBindIndex = binormElem.Source; srcBinormalBuffer = bind.GetBuffer(binormBindIndex); } }
/// <summary> /// Sets up the surface by defining it's control points, type and initial subdivision level. /// </summary> /// <remarks> /// This method initialises the surface by passing it a set of control points. The type of curves to be used /// are also defined here, although the only supported option currently is a bezier patch. You can also /// specify a global subdivision level here if you like, although it is recommended that the parameter /// is left as AUTO_LEVEL, which means the system decides how much subdivision is required (based on the /// curvature of the surface). /// </remarks> /// <param name="controlPointArray"> /// An array containing the vertex data which define control points of the curves /// rather than actual vertices. Note that you are expected to provide not /// just position information, but potentially normals and texture coordinates too. /// The array is internally treated as a contiguous memory buffer without any gaps between the elements. /// The format of the buffer is defined in the VertexDeclaration parameter. /// </param> /// <param name="declaration"> /// VertexDeclaration describing the contents of the buffer. /// Note this declaration must _only_ draw on buffer source 0! /// </param> /// <param name="width">Specifies the width of the patch in control points.</param> /// <param name="height">Specifies the height of the patch in control points.</param> /// <param name="type">The type of surface.</param> /// <param name="uMaxSubdivisionLevel"> /// If you want to manually set the top level of subdivision, /// do it here, otherwise let the system decide. /// </param> /// <param name="vMaxSubdivisionLevel"> /// If you want to manually set the top level of subdivision, /// do it here, otherwise let the system decide. /// </param> /// <param name="visibleSide">Determines which side of the patch (or both) triangles are generated for.</param> public void DefineSurface( Array controlPointArray, VertexDeclaration declaration, int width, int height, PatchSurfaceType type, int uMaxSubdivisionLevel, int vMaxSubdivisionLevel, VisibleSide visibleSide ) { if ( height == 0 || width == 0 ) { return; // Do nothing - garbage } //pin the input to have a pointer Memory.UnpinObject( controlPointArray ); this.controlPointBuffer = Memory.PinObject( controlPointArray ); this.type = type; this.controlWidth = width; this.controlHeight = height; this.controlCount = width*height; this.declaration = declaration; // Copy positions into Vector3 vector this.controlPoints.Clear(); var elem = declaration.FindElementBySemantic( VertexElementSemantic.Position ); var vertSize = declaration.GetVertexSize( 0 ); #if !AXIOM_SAFE_ONLY unsafe #endif { var pVert = this.controlPointBuffer; for ( var i = 0; i < this.controlCount; i++ ) { var pReal = ( pVert + ( ( i*vertSize ) + elem.Offset ) ).ToFloatPointer(); this.controlPoints.Add( new Vector3( pReal[ 0 ], pReal[ 1 ], pReal[ 2 ] ) ); } } this.side = visibleSide; // Determine max level // Initialize to 100% detail this.subdivisionFactor = 1.0f; if ( uMaxSubdivisionLevel == AUTO_LEVEL ) { this.uLevel = this.maxULevel = GetAutoULevel(); } else { this.uLevel = this.maxULevel = uMaxSubdivisionLevel; } if ( vMaxSubdivisionLevel == AUTO_LEVEL ) { this.vLevel = this.maxVLevel = GetAutoVLevel(); } else { this.vLevel = this.maxVLevel = vMaxSubdivisionLevel; } // Derive mesh width / height this.meshWidth = ( LevelWidth( this.maxULevel ) - 1 )*( ( this.controlWidth - 1 )/2 ) + 1; this.meshHeight = ( LevelWidth( this.maxVLevel ) - 1 )*( ( this.controlHeight - 1 )/2 ) + 1; // Calculate number of required vertices / indexes at max resolution this.requiredVertexCount = this.meshWidth*this.meshHeight; var iterations = ( this.side == VisibleSide.Both ) ? 2 : 1; this.requiredIndexCount = ( this.meshWidth - 1 )*( this.meshHeight - 1 )*2*iterations*3; // Calculate bounds based on control points var min = Vector3.Zero; var max = Vector3.Zero; var maxSqRadius = 0.0f; var first = true; for ( var i = 0; i < this.controlPoints.Count; i++ ) { var vec = this.controlPoints[ i ]; if ( first ) { min = max = vec; maxSqRadius = vec.LengthSquared; first = false; } else { min.Floor( vec ); max.Ceil( vec ); maxSqRadius = Utility.Max( vec.LengthSquared, maxSqRadius ); } } // set the bounds of the patch this.aabb.SetExtents( min, max ); this.boundingSphereRadius = Utility.Sqrt( maxSqRadius ); }
/// <summary> /// Sets up the surface by defining it's control points, type and initial subdivision level. /// </summary> /// <remarks> /// This method initialises the surface by passing it a set of control points. The type of curves to be used /// are also defined here, although the only supported option currently is a bezier patch. You can also /// specify a global subdivision level here if you like, although it is recommended that the parameter /// is left as AUTO_LEVEL, which means the system decides how much subdivision is required (based on the /// curvature of the surface). /// </remarks> /// <param name="controlPoints"> /// A pointer to a buffer containing the vertex data which defines control points /// of the curves rather than actual vertices. Note that you are expected to provide not /// just position information, but potentially normals and texture coordinates too. The /// format of the buffer is defined in the VertexDeclaration parameter. /// </param> /// <param name="decl"> /// VertexDeclaration describing the contents of the buffer. /// Note this declaration must _only_ draw on buffer source 0! /// </param> /// <param name="width">Specifies the width of the patch in control points.</param> /// <param name="height">Specifies the height of the patch in control points.</param> /// <param name="type">The type of surface.</param> /// <param name="uMaxSubdivisionLevel"> /// If you want to manually set the top level of subdivision, /// do it here, otherwise let the system decide. /// </param> /// <param name="vMaxSubdivisionLevel"> /// If you want to manually set the top level of subdivision, /// do it here, otherwise let the system decide. /// </param> /// <param name="side">Determines which side of the patch (or both) triangles are generated for.</param> public unsafe void DefineSurface(System.Array controlPointBuffer, VertexDeclaration declaration, int width, int height, PatchSurfaceType type, int uMaxSubdivisionLevel, int vMaxSubdivisionLevel, VisibleSide visibleSide) { if (height == 0 || width == 0) { return; // Do nothing - garbage } this.type = type; this.controlWidth = width; this.controlHeight = height; this.controlCount = width * height; this.controlPointBuffer = controlPointBuffer; this.declaration = declaration; // Copy positions into Vector3 vector controlPoints.Clear(); VertexElement elem = declaration.FindElementBySemantic(VertexElementSemantic.Position); int vertSize = declaration.GetVertexSize(0); byte *pVert = (byte*)Marshal.UnsafeAddrOfPinnedArrayElement(controlPointBuffer, 0); float* pReal = null; for (int i = 0; i < controlCount; i++) { pReal = (float*)(pVert + elem.Offset); controlPoints.Add(new Vector3(pReal[0], pReal[1], pReal[2])); pVert += vertSize; } this.side = visibleSide; // Determine max level // Initialise to 100% detail subdivisionFactor = 1.0f; if (uMaxSubdivisionLevel == AUTO_LEVEL) { uLevel = maxULevel = GetAutoULevel(); } else { uLevel = maxULevel = uMaxSubdivisionLevel; } if (vMaxSubdivisionLevel == AUTO_LEVEL) { vLevel = maxVLevel = GetAutoVLevel(); } else { vLevel = maxVLevel = vMaxSubdivisionLevel; } // Derive mesh width / height meshWidth = (LevelWidth(maxULevel) - 1) * ((controlWidth-1) / 2) + 1; meshHeight = (LevelWidth(maxVLevel) - 1) * ((controlHeight-1) / 2) + 1; // Calculate number of required vertices / indexes at max resolution requiredVertexCount = meshWidth * meshHeight; int iterations = (side == VisibleSide.Both)? 2 : 1; requiredIndexCount = (meshWidth-1) * (meshHeight - 1) * 2 * iterations * 3; // Calculate bounds based on control points Vector3 min = Vector3.Zero; Vector3 max = Vector3.Zero; float maxSqRadius = 0.0f; bool first = true; for(int i = 0; i < controlPoints.Count; i++) { Vector3 vec = controlPoints[i]; if (first) { min = max = vec; maxSqRadius = vec.LengthSquared; first = false; } else { min.Floor(vec); max.Ceil(vec); maxSqRadius = MathUtil.Max(vec.LengthSquared, maxSqRadius); } } // set the bounds of the patch aabb.SetExtents(min, max); boundingSphereRadius = MathUtil.Sqrt(maxSqRadius); }
/// <summary> /// Modifies the vertex data to be suitable for use for rendering shadow geometry. /// </summary> /// <remarks> /// <para> /// Preparing vertex data to generate a shadow volume involves firstly ensuring that the /// vertex buffer containing the positions is a standalone vertex buffer, /// with no other components in it. This method will therefore break apart any existing /// vertex buffers if position is sharing a vertex buffer. /// Secondly, it will double the size of this vertex buffer so that there are 2 copies of /// the position data for the mesh. The first half is used for the original, and the second /// half is used for the 'extruded' version. The vertex count used to render will remain /// the same though, so as not to add any overhead to regular rendering of the object. /// Both copies of the position are required in one buffer because shadow volumes stretch /// from the original mesh to the extruded version. /// </para> /// <para> /// It's important to appreciate that this method can fundamentally change the structure of your /// vertex buffers, although in reality they will be new buffers. As it happens, if other /// objects are using the original buffers then they will be unaffected because the reference /// counting will keep them intact. However, if you have made any assumptions about the /// structure of the vertex data in the buffers of this object, you may have to rethink them. /// </para> /// </remarks> // TODO: Step through and test public void PrepareForShadowVolume() { /* NOTE * Sinbad would dearly, dearly love to just use a 4D position buffer in order to * store the extra 'w' value I need to differentiate between extruded and * non-extruded sections of the buffer, so that vertex programs could use that. * Hey, it works fine for GL. However, D3D9 in it's infinite stupidity, does not * support 4d position vertices in the fixed-function pipeline. If you use them, * you just see nothing. Since we can't know whether the application is going to use * fixed function or vertex programs, we have to stick to 3d position vertices and * store the 'w' in a separate 1D texture coordinate buffer, which is only used * when rendering the shadow. */ // Upfront, lets check whether we have vertex program capability RenderSystem renderSystem = Root.Instance.RenderSystem; bool useVertexPrograms = false; if (renderSystem != null && renderSystem.Caps.CheckCap(Capabilities.VertexPrograms)) { useVertexPrograms = true; } // Look for a position element VertexElement posElem = vertexDeclaration.FindElementBySemantic(VertexElementSemantic.Position); if (posElem != null) { ushort posOldSource = posElem.Source; HardwareVertexBuffer vbuf = vertexBufferBinding.GetBuffer(posOldSource); bool wasSharedBuffer = false; // Are there other elements in the buffer except for the position? if (vbuf.VertexSize > posElem.Size) { // We need to create another buffer to contain the remaining elements // Most drivers don't like gaps in the declaration, and in any case it's waste wasSharedBuffer = true; } HardwareVertexBuffer newPosBuffer = null, newRemainderBuffer = null; if (wasSharedBuffer) { newRemainderBuffer = HardwareBufferManager.Instance.CreateVertexBuffer( vbuf.VertexSize - posElem.Size, vbuf.VertexCount, vbuf.Usage, vbuf.HasShadowBuffer); } // Allocate new position buffer, will be FLOAT3 and 2x the size int oldVertexCount = vbuf.VertexCount; int newVertexCount = oldVertexCount * 2; newPosBuffer = HardwareBufferManager.Instance.CreateVertexBuffer( VertexElement.GetTypeSize(VertexElementType.Float3), newVertexCount, vbuf.Usage, vbuf.HasShadowBuffer); // Iterate over the old buffer, copying the appropriate elements and initializing the rest IntPtr baseSrcPtr = vbuf.Lock(BufferLocking.ReadOnly); // Point first destination pointer at the start of the new position buffer, // the other one half way along IntPtr destPtr = newPosBuffer.Lock(BufferLocking.Discard); // oldVertexCount * 3 * 4, since we are dealing with byte offsets here IntPtr dest2Ptr = new IntPtr(destPtr.ToInt32() + (oldVertexCount * 12)); int prePosVertexSize = 0; int postPosVertexSize = 0; int postPosVertexOffset = 0; if (wasSharedBuffer) { // Precalculate any dimensions of vertex areas outside the position prePosVertexSize = posElem.Offset; postPosVertexOffset = prePosVertexSize + posElem.Size; postPosVertexSize = vbuf.VertexSize - postPosVertexOffset; // the 2 separate bits together should be the same size as the remainder buffer vertex Debug.Assert(newRemainderBuffer.VertexSize == (prePosVertexSize + postPosVertexSize)); IntPtr baseDestRemPtr = newRemainderBuffer.Lock(BufferLocking.Discard); int baseSrcOffset = 0; int baseDestRemOffset = 0; unsafe { float *pDest = (float *)destPtr.ToPointer(); float *pDest2 = (float *)dest2Ptr.ToPointer(); int destCount = 0, dest2Count = 0; // Iterate over the vertices for (int v = 0; v < oldVertexCount; v++) { float *pSrc = (float *)((byte *)baseSrcPtr.ToPointer() + posElem.Offset + baseSrcOffset); // Copy position, into both buffers pDest[destCount++] = pDest2[dest2Count++] = pSrc[0]; pDest[destCount++] = pDest2[dest2Count++] = pSrc[1]; pDest[destCount++] = pDest2[dest2Count++] = pSrc[2]; // now deal with any other elements // Basically we just memcpy the vertex excluding the position if (prePosVertexSize > 0) { Memory.Copy( baseSrcPtr, baseDestRemPtr, baseSrcOffset, baseDestRemOffset, prePosVertexSize); } if (postPosVertexSize > 0) { Memory.Copy( baseSrcPtr, baseDestRemPtr, baseSrcOffset + postPosVertexOffset, baseDestRemOffset + prePosVertexSize, postPosVertexSize); } // increment the pointer offsets baseDestRemOffset += newRemainderBuffer.VertexSize; baseSrcOffset += vbuf.VertexSize; } // next vertex } // unsafe } else { // copy the data directly Memory.Copy(baseSrcPtr, destPtr, vbuf.Size); Memory.Copy(baseSrcPtr, dest2Ptr, vbuf.Size); } vbuf.Unlock(); newPosBuffer.Unlock(); if (wasSharedBuffer) { newRemainderBuffer.Unlock(); } // At this stage, he original vertex buffer is going to be destroyed // So we should force the deallocation of any temporary copies HardwareBufferManager.Instance.ForceReleaseBufferCopies(vbuf); if (useVertexPrograms) { unsafe { // Now it's time to set up the w buffer hardwareShadowVolWBuffer = HardwareBufferManager.Instance.CreateVertexBuffer( sizeof(float), newVertexCount, BufferUsage.StaticWriteOnly, false); // Fill the first half with 1.0, second half with 0.0 IntPtr wPtr = hardwareShadowVolWBuffer.Lock(BufferLocking.Discard); float *pDest = (float *)wPtr.ToPointer(); int destCount = 0; for (int v = 0; v < oldVertexCount; v++) { pDest[destCount++] = 1.0f; } for (int v = 0; v < oldVertexCount; v++) { pDest[destCount++] = 0.0f; } } // unsafe hardwareShadowVolWBuffer.Unlock(); } // if vertexPrograms ushort newPosBufferSource = 0; if (wasSharedBuffer) { // Get the a new buffer binding index newPosBufferSource = vertexBufferBinding.NextIndex; // Re-bind the old index to the remainder buffer vertexBufferBinding.SetBinding(posOldSource, newRemainderBuffer); } else { // We can just re-use the same source idex for the new position buffer newPosBufferSource = posOldSource; } // Bind the new position buffer vertexBufferBinding.SetBinding(newPosBufferSource, newPosBuffer); // Now, alter the vertex declaration to change the position source // and the offsets of elements using the same buffer for (int i = 0; i < vertexDeclaration.ElementCount; i++) { VertexElement element = vertexDeclaration.GetElement(i); if (element.Semantic == VertexElementSemantic.Position) { // Modify position to point at new position buffer vertexDeclaration.ModifyElement( i, newPosBufferSource, // new source buffer 0, // no offset now VertexElementType.Float3, VertexElementSemantic.Position); } else if (wasSharedBuffer && element.Source == posOldSource && element.Offset > prePosVertexSize) { // This element came after position, remove the position's // size vertexDeclaration.ModifyElement( i, posOldSource, // same old source element.Offset - posElem.Size, // less offset now element.Type, element.Semantic, element.Index); } } } // if posElem != null }