/// <summary> /// Setup constructor /// </summary> /// <param name="camera">Camera, used to determine patch error</param> /// <param name="generator">Object used to generate the patch vertices</param> /// <param name="patch">Terrain patch</param> /// <param name="calculatePatchError">If true, the maximum error between the patch geometry and the terrain model is calculated</param> private unsafe TerrainPatchBuildItem( IProjectionCamera camera, ITerrainPatchGenerator generator, ITerrainPatch patch, bool calculatePatchError ) { m_Camera = camera; m_Generator = generator; m_Patch = patch; m_CalculatePatchError = calculatePatchError; }
/// <summary> /// Allocates a terrain patch build item from an internal pool /// </summary> public static TerrainPatchBuildItem Allocate( IProjectionCamera camera, ITerrainPatchGenerator generator, ITerrainPatch patch, bool calculatePatchError ) { if ( s_BuildItems.Count == 0 ) { return new TerrainPatchBuildItem( camera, generator, patch, calculatePatchError ); } TerrainPatchBuildItem item = s_BuildItems[ 0 ]; s_BuildItems.RemoveAt( 0 ); item.m_Camera = camera; item.m_Generator = generator; item.m_Patch = patch; item.m_CalculatePatchError = calculatePatchError; return item; }
/// <summary> /// Determines the distance at which a patch LOD should be increased, from the patch error /// </summary> private static float DistanceFromError( IProjectionCamera camera, float error ) { // Extract frustum from projection matrix: // http://www.opengl.org/resources/faq/technical/transformations.htm // http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=209274 float near = camera.PerspectiveZNear; float top = Functions.Tan( camera.PerspectiveFovDegrees * Constants.DegreesToRadians * 0.5f ) * near; float a = near / top; float t = ( TerrainPatchConstants.ErrorThreshold * 2 ) / Graphics.Renderer.ViewportHeight; float c = a / t; float d = error * c; return d; }
public void Update( IProjectionCamera camera ) { if ( m_Children != null ) { foreach ( QuadPatch childPatch in m_Children ) { childPatch.Update( camera ); } } else { if ( m_BuildVertices ) { m_IncreaseDetailDistance = BuildVertices( camera ); m_BuildVertices = false; } if ( m_BuildIndices ) { BuildIndices( ); m_BuildIndices = false; } } }
private unsafe float BuildVertices( IProjectionCamera camera ) { float maxError = 0; using ( IVertexBufferLock vbLock = m_Vertices.VertexBuffer.Lock( m_VbIndex, PatchArea, false, true ) ) { PatchVertex* curVertex = ( PatchVertex* )vbLock.Bytes; float incX = m_Width / ( PatchResolution - 1 ); float incZ = m_Depth / ( PatchResolution - 1 ); float z = m_Z; for ( int row = 0; row < PatchResolution; ++row, z += incZ ) { float x = m_X; for ( int col = 0; col < PatchResolution; ++col, x += incX ) { float curHeight = m_Terrain.GetHeight( x, z ); float nextHeightX = m_Terrain.GetHeight( x + incX, z ); float nextHeightY = m_Terrain.GetHeight( x, z + incZ ); float estXHeight = ( curHeight + nextHeightX ) / 2; float estYHeight = ( curHeight + nextHeightY ) / 2; float xError = Math.Abs( m_Terrain.GetHeight( x + incX / 2, z ) - estXHeight ); float yError = Math.Abs( m_Terrain.GetHeight( x, z + incZ / 2 ) - estYHeight ); float error = Utils.Max( xError, yError ); maxError = error > maxError ? error : maxError; curVertex->Position = new Point3( x, curHeight, z ); ++curVertex; } } } return DistanceFromError( camera, maxError ); }
private void ReduceDetail( ITerrainPatchGenerator generator, IProjectionCamera camera ) { Build( generator, camera ); }
/// <summary> /// Increases the detail of this paqtch /// </summary> private void IncreaseDetail( ITerrainPatchGenerator generator, IProjectionCamera camera ) { if ( m_CachedChildren != null ) { // Child nodes have already been created - they just need new vertex buffers m_PendingChildren = m_CachedChildren; m_CachedChildren = null; } else { Vector3 uOffset = m_LocalUAxis * 0.5f; Vector3 vOffset = m_LocalVAxis * 0.5f; float error = m_PatchError / 2; // float error = float.MaxValue;// Use this value to force a recalculation of the patch error float uvRes = m_UvRes / 2; Point2 tlUv = m_Uv; Point2 trUv = m_Uv + new Vector2( uvRes, 0 ); Point2 brUv = m_Uv + new Vector2( uvRes, uvRes ); Point2 blUv = m_Uv + new Vector2( 0, uvRes ); TerrainPatch tl = new TerrainPatch( this, m_Vertices, m_LocalOrigin, uOffset, vOffset, error, tlUv,uvRes ); TerrainPatch tr = new TerrainPatch( this, m_Vertices, m_LocalOrigin + uOffset, uOffset, vOffset, error, trUv, uvRes ); TerrainPatch bl = new TerrainPatch( this, m_Vertices, m_LocalOrigin + vOffset, uOffset, vOffset, error, blUv, uvRes ); TerrainPatch br = new TerrainPatch( this, m_Vertices, m_LocalOrigin + uOffset + vOffset, uOffset, vOffset, error, brUv, uvRes ); m_PendingChildren = new TerrainPatch[] { tl, tr, bl, br }; } foreach ( TerrainPatch patch in m_PendingChildren ) { patch.Build( generator, camera ); } }
/// <summary> /// Queues up a work item to builds this patch's vertex and index buffers /// </summary> private void Build( ITerrainPatchGenerator generator, IProjectionCamera camera ) { m_Building = true; TerrainPatchBuildItem builder = TerrainPatchBuildItem.Allocate( camera, generator, this, ( PatchError == float.MaxValue ) ); TerrainPatchBuilder.QueueWork( builder ); }
/// <summary> /// Updates the level of detail of this patch. /// </summary> public void UpdateLod( Point3 cameraPos, ITerrainPatchGenerator generator, IProjectionCamera camera ) { if ( m_IncreaseDetailDistance == float.MaxValue ) { // Detail up distance has not been calculated for this patch yet - exit return; } float distanceToPatch = AccurateDistanceToPatch( cameraPos, PatchCentre, m_Radius ); m_DistToPatch = distanceToPatch; if ( distanceToPatch < m_IncreaseDetailDistance ) { if ( IsLeafNode ) { IncreaseDetail( generator, camera ); } else if ( m_Children != null ) { foreach ( TerrainPatch childPatch in m_Children ) { childPatch.UpdateLod( cameraPos, generator, camera ); } } } else if ( distanceToPatch > m_IncreaseDetailDistance ) { if ( !IsLeafNode && CanReduceDetail ) { ReduceDetail( generator, camera ); } } }
/// <summary> /// Updates this patch /// </summary> public void Update( IProjectionCamera camera, ITerrainPatchGenerator generator ) { if ( m_Children != null ) { foreach ( TerrainPatch childPatch in m_Children ) { childPatch.Update( camera, generator ); } } else { // Bodge to generate vertices (blocking) on the first frame if ( m_Parent == null && m_VbIndex == -1 && m_IncreaseDetailDistance == float.MaxValue ) { TerrainPatchBuildItem builder = TerrainPatchBuildItem.Allocate( camera, generator, this, ( PatchError == float.MaxValue ) ); builder.Build( ); } } }