A node in a quad tree used to store a patch of terrain.
Algorithm overview: Our goal is to perform traditional chunked LOD with geomorphing. But, instead of just dividing the terrain into tiles, we will divide them into a hierarchy of tiles, a quadtree, where any level of the quadtree can be a rendered tile (to the exclusion of its children). The idea is to collect together children into a larger batch with their siblings as LOD decreases, to improve performance. The minBatchSize and maxBatchSize parameters on Terrain a key to defining this behaviour. Both values are expressed in vertices down one axis. maxBatchSize determines the number of tiles on one side of the terrain, which is numTiles = (terrainSize-1) / (maxBatchSize-1). This in turn determines the depth of the quad tree, which is sqrt(numTiles). The minBatchSize determines the 'floor' of how low the number of vertices can go in a tile before it has to be grouped together with its siblings to drop any lower. We also do not group a tile with its siblings unless all of them are at this minimum batch size, rather than trying to group them when they all end up on the same 'middle' LOD; this is for several reasons; firstly, tiles hitting the same 'middle' LOD is less likely and more transient if they have different levels of 'roughness', and secondly since we're sharing a vertex / index pool between all tiles, only grouping at the min level means that the number of combinations of buffer sizes for any one tile is greatly simplified, making it easier to pool data. To be more specific, any tile / quadtree node can only have log2(maxBatchSize-1) - log2(minBatchSize-1) + 1 LOD levels (and if you set them to the same value, LOD can only change by going up/down the quadtree). The numbers of vertices / indices in each of these levels is constant for the same (relative) LOD index no matter where you are in the tree, therefore buffers can potentially be reused more easily.
Inheritance: IDisposable
		public TerrainQuadTreeNode( Terrain terrain, TerrainQuadTreeNode parent, ushort xOff, ushort yOff, ushort size,
		                            ushort lod, ushort depth, ushort quadrant )
			: base()
		{
			this.mTerrain = terrain;
			this.mParent = parent;
			this.mOffsetX = xOff;
			this.mOffsetY = yOff;
			this.mBoundaryX = (ushort)( xOff + size );
			this.mBoundaryY = (ushort)( yOff + size );
			this.mSize = size;
			this.mBaseLod = lod;
			this.mDepth = depth;
			this.mQuadrant = quadrant;
			this.mBoundingRadius = 0;
			this.mCurrentLod = -1;
			this.mMaterialLodIndex = 0;
			this.mLodTransition = 0;
			this.mChildWithMaxHeightDelta = null;
			this.mSelfOrChildRendered = false;
			this.mNodeWithVertexData = null;
			this.mAABB = new AxisAlignedBox();
			if ( this.mTerrain.MaxBatchSize < size )
			{
				var childSize = (ushort)( ( ( size - 1 )*0.5f ) + 1 );
				var childOff = (ushort)( childSize - 1 );
				var childLod = (ushort)( lod - 1 ); // LOD levels decrease down the tree (higher detail)
				var childDepth = (ushort)( depth + 1 );
				// create children
				this.mChildren[ 0 ] = new TerrainQuadTreeNode( this.mTerrain, this, xOff, yOff, childSize, childLod, childDepth, 0 );
				this.mChildren[ 1 ] = new TerrainQuadTreeNode( this.mTerrain, this, (ushort)( xOff + childOff ), yOff, childSize,
				                                               childLod,
				                                               childDepth, 1 );
				this.mChildren[ 2 ] = new TerrainQuadTreeNode( this.mTerrain, this, xOff, (ushort)( yOff + childOff ), childSize,
				                                               childLod,
				                                               childDepth, 2 );
				this.mChildren[ 3 ] = new TerrainQuadTreeNode( this.mTerrain, this, (ushort)( xOff + childOff ),
				                                               (ushort)( yOff + childOff ),
				                                               childSize, childLod, childDepth, 3 );

				var ll = new LodLevel();
				// non-leaf nodes always render with minBatchSize vertices
				ll.BatchSize = this.mTerrain.MinBatchSize;
				ll.MaxHeightDelta = 0;
				ll.CalcMaxHeightDelta = 0;
				this.mLodLevels.Add( ll );
			}
			else
			{
				//no children
				Array.Clear( this.mChildren, 0, this.mChildren.Length );
				// this is a leaf node and may have internal LODs of its own
				var ownLod = this.mTerrain.NumLodLevelsPerLeaf;

				Debug.Assert( lod == ( ownLod - 1 ), "The lod passed in should reflect the number of lods in a leaf" );
				// leaf nodes always have a base LOD of 0, because they're always handling
				// the highest level of detail
				this.mBaseLod = 0;
				var sz = this.mTerrain.MaxBatchSize;

				while ( ownLod-- != 0 )
				{
					var ll = new LodLevel();
					ll.BatchSize = sz;
					ll.MaxHeightDelta = 0;
					ll.CalcMaxHeightDelta = 0;
					this.mLodLevels.Add( ll );
					if ( ownLod != 0 )
					{
						sz = (ushort)( ( ( sz - 1 )*0.5 ) + 1 );
					}
				}
				Debug.Assert( sz == this.mTerrain.MinBatchSize );
			}

			// local centre calculation
			// because of pow2 +1 there is always a middle point
			var midoffset = (ushort)( ( size - 1 )/2 );
			var midpointX = (ushort)( this.mOffsetX + midoffset );
			var midpointY = (ushort)( this.mOffsetY + midoffset );

			//derive the local centry, but give it a height if 0
			//TODO: - what if we actually centred this at the terrain height at this point?
			//would this be better?
			this.mTerrain.GetPoint( midpointX, midpointY, 0, ref this.mLocalCentre );
			this.mMovable = new Movable( this );
			this.mRend = new Rend( this );
		}
		public void PostDeltaCalculation( Rectangle rect )
		{
			if ( rect.Left <= this.mBoundaryX || rect.Right > this.mOffsetX || rect.Top <= this.mBoundaryY ||
			     rect.Bottom > this.mOffsetY )
			{
				// relevant to this node (overlaps)

				// each non-leaf node should know which of its children transitions
				// to the lower LOD level last, because this is the one which controls
				// when the parent takes over
				if ( !IsLeaf )
				{
					float maxChildDelta = -1;
					TerrainQuadTreeNode childWithMaxHeightDelta = null;
					for ( int i = 0; i < 4; i++ )
					{
						TerrainQuadTreeNode child = this.mChildren[ i ];
						child.PostDeltaCalculation( rect );
						float childData = child.GetLodLevel( (ushort)( child.LodCount - 1 ) ).CalcMaxHeightDelta;

						if ( childData > maxChildDelta )
						{
							childWithMaxHeightDelta = child;
							maxChildDelta = childData;
						}
					}

					// make sure that our highest delta value is greater than all children's
					// otherwise we could have some crossover problems
					// for a non-leaf, there is only one LOD level
					LodLevel tmp = this.mLodLevels[ 0 ];
					tmp.CalcMaxHeightDelta = System.Math.Max( tmp.CalcMaxHeightDelta, maxChildDelta*(Real)1.05 );
					this.mChildWithMaxHeightDelta = childWithMaxHeightDelta;
				}
				else
				{
					// make sure own LOD levels delta values ascend
					for ( int i = 0; i < this.mLodLevels.Count - 1; i++ )
					{
						// the next LOD after this one should have a higher delta
						// otherwise it won't come into affect further back like it should!
						LodLevel tmp = this.mLodLevels[ i ];
						LodLevel tmpPlus = this.mLodLevels[ i + 1 ];
						tmpPlus.CalcMaxHeightDelta = System.Math.Max( tmpPlus.CalcMaxHeightDelta, tmp.CalcMaxHeightDelta*(Real)1.05 );
					}
				}
			}
		}
			public Movable( TerrainQuadTreeNode parent )
				: base()
			{
				this.mParent = parent;
			}
			public Rend( TerrainQuadTreeNode parent )
			{
				this.mParent = parent;
			}
		public void AssignVertexData( ushort treeDepthStart, ushort treeDepthEnd, ushort resolution, ushort sz )
		{
			Debug.Assert( treeDepthStart >= this.mDepth, "Should not be calling this" );

			if ( this.mDepth == treeDepthStart )
			{
				//we own this vertex data
				this.mNodeWithVertexData = this;
				this.mVertexDataRecord = new VertexDataRecord( resolution, sz, (ushort)( treeDepthEnd - treeDepthStart ) );

				CreateCpuVertexData();

				//pass on to children
				if ( !IsLeaf && treeDepthEnd > ( this.mDepth + 1 ) ) // treeDepthEnd is exclusive, and this is children
				{
					for ( int i = 0; i < 4; ++i )
					{
						this.mChildren[ i ].UseAncestorVertexData( this, treeDepthEnd, resolution );
					}
				}
			}
			else
			{
				Debug.Assert( !IsLeaf, "No more levels below this!" );

				for ( int i = 0; i < 4; ++i )
				{
					this.mChildren[ i ].AssignVertexData( treeDepthStart, treeDepthEnd, resolution, sz );
				}
			}
		}
		public void UseAncestorVertexData( TerrainQuadTreeNode owner, int treeDepthEnd, int resolution )
		{
			this.mNodeWithVertexData = owner;
			this.mVertexDataRecord = null;

			if ( !IsLeaf && treeDepthEnd > ( this.mDepth + 1 ) ) // treeDepthEnd is exclusive, and this is children
			{
				for ( int i = 0; i < 4; i++ )
				{
					this.mChildren[ i ].UseAncestorVertexData( owner, treeDepthEnd, resolution );
				}
			}
		}
Example #7
0
		public bool Prepare( ImportData importData )
		{
			FreeTemporaryResources();
			FreeCPUResources();

			CopyGlobalOptions();

			//validate
			if (
				!( Bitwise.IsPow2( importData.TerrainSize - 1 ) && Bitwise.IsPow2( importData.MinBatchSize - 1 ) &&
				   Bitwise.IsPow2( importData.MaxBatchSize - 1 ) ) )
			{
				throw new AxiomException( "terrainSize, minBatchSize and maxBatchSize must all be n^2 + 1. Terrain.Prepare" );
			}

			if ( importData.MinBatchSize > importData.MaxBatchSize )
			{
				throw new AxiomException( "MinBatchSize must be less then or equal to MaxBatchSize. Terrain.Prepare" );
			}

			if ( importData.MaxBatchSize > TERRAIN_MAX_BATCH_SIZE )
			{
				throw new AxiomException( "MaxBatchSize must be not larger then {0} . Terrain.Prepare", TERRAIN_MAX_BATCH_SIZE );
			}

			Alignment = importData.TerrainAlign;
			this.mSize = importData.TerrainSize;
			this.mWorldSize = importData.WorldSize;
			this.mLayerDecl = importData.LayerDeclaration;
			CheckDeclaration();
			this.mLayers = importData.LayerList;
			CheckLayers( false );
			DeriveUVMultipliers();
			this.mMaxBatchSize = importData.MaxBatchSize;
			this.mMinBatchSize = importData.MinBatchSize;
			this.mPos = importData.Pos;
			UpdateBaseScale();
			DetermineLodLevels();

			int numVertices = this.mSize*this.mSize;
			this.mHeightData = new float[numVertices];

			if ( importData.InputFloat != null )
			{
				if ( Utility.RealEqual( importData.InputBias, 0.0f ) && Utility.RealEqual( importData.InputScale, 1.0f ) )
				{
					//straigt copy
					this.mHeightData = new float[numVertices];
					Array.Copy( importData.InputFloat, this.mHeightData, this.mHeightData.Length );
				}
				else
				{
					// scale & bias, lets do it unsafe, should be faster :)
					var src = importData.InputFloat;
					for ( var i = 0; i < numVertices; ++i )
					{
						this.mHeightData[ i ] = ( src[ i ]*importData.InputScale ) + importData.InputBias;
					}
				}
			}
			else if ( importData.InputImage != null )
			{
				var img = importData.InputImage;
				if ( img.Width != this.mSize || img.Height != this.mSize )
				{
					img.Resize( this.mSize, this.mSize );
				}

				// convert image data to floats
				// Do this on a row-by-row basis, because we describe the terrain in
				// a bottom-up fashion (ie ascending world coords), while Image is top-down
				var pSrcBaseF = BufferBase.Wrap( img.Data );
				var pHeightDataF = BufferBase.Wrap( this.mHeightData, mHeightData.Length * sizeof(float) );
				for ( var i = 0; i < this.mSize; ++i )
				{
					var srcy = this.mSize - i - 1;
					using ( var pSrc = pSrcBaseF + srcy*img.RowSpan )
					{
						using ( var pDest = pHeightDataF + i*this.mSize*sizeof ( float ) )
						{
							PixelConverter.BulkPixelConversion( pSrc, img.Format, pDest, PixelFormat.FLOAT32_R, this.mSize );
						}
					}
				}

				pSrcBaseF.Dispose();
				pHeightDataF.Dispose();

				if ( !Utility.RealEqual( importData.InputBias, 0.0f ) || !Utility.RealEqual( importData.InputScale, 1.0f ) )
				{
					for ( int i = 0; i < numVertices; ++i )
					{
						this.mHeightData[ i ] = ( this.mHeightData[ i ]*importData.InputScale ) + importData.InputBias;
					}
				}
			}
			else
			{
				// start with flat terrain
				this.mHeightData = new float[this.mSize*this.mSize];
			}

			var deltaData = new float[numVertices];

			this.mHeightDataPtr = BufferBase.Wrap( this.mHeightData,mHeightData.Length * sizeof(float) );
			this.mDeltaDataPtr = BufferBase.Wrap( deltaData, deltaData.Length * sizeof(float) );

			var numLevel = (ushort)(int)( NumLodLevels - 1 );
			QuadTree = new TerrainQuadTreeNode( this, null, 0, 0, this.mSize, (ushort)( NumLodLevels - 1 ), 0, 0 );
			QuadTree.Prepare();

			//calculate entire terrain
			var rect = new Rectangle();
			rect.Top = 0;
			rect.Bottom = this.mSize;
			rect.Left = 0;
			rect.Right = this.mSize;
			CalculateHeightDeltas( rect );
			FinalizeHeightDeltas( rect, true );

			DistributeVertexData();

			// Imported data is treated as modified because it's not saved
			IsModified = true;
			IsHeightDataModified = true;

			return true;
		}
Example #8
0
		public bool Prepare( StreamSerializer stream )
		{
			FreeTemporaryResources();
			FreeCPUResources();

			CopyGlobalOptions();

			if ( stream.ReadChunkBegin( TERRAIN_CHUNK_ID, TERRAIN_CHUNK_VERSION ) == null )
			{
				return false;
			}

			byte align;
			stream.Read( out align );
			Alignment = (Alignment)align;
			stream.Read( out this.mSize );
			stream.Read( out this.mWorldSize );

			stream.Read( out this.mMaxBatchSize );
			stream.Read( out this.mMinBatchSize );
			stream.Read( out this.mPos );
			RootSceneNode.Position = this.mPos;
			UpdateBaseScale();
			DetermineLodLevels();

			int numVertices = this.mSize*this.mSize;
			this.mHeightData = new float[numVertices];
			stream.Read( out this.mHeightData );

			// layer declaration
			if ( !ReadLayerDeclaration( ref stream, ref this.mLayerDecl ) )
			{
				return false;
			}

			CheckDeclaration();

			// Layers
			if ( !ReadLayerInstanceList( ref stream, this.mLayerDecl.Elements.Count, ref this.mLayers ) )
			{
				return false;
			}

			DeriveUVMultipliers();

			// Packed layer blend data
			var numLayers = (byte)this.mLayers.Count;
			stream.Read( out this.mLayerBlendMapSize );
			this.mLayerBlendSizeActual = this.mLayerBlendMapSize; // for now, until we check
			//load packed CPU data
			var numBlendTex = GetBlendTextureCount( numLayers );
			for ( var i = 0; i < numBlendTex; ++i )
			{
				var fmt = GetBlendTextureFormat( (byte)i, numLayers );
				var channels = PixelUtil.GetNumElemBytes( fmt );
				var dataSz = channels*this.mLayerBlendMapSize*this.mLayerBlendMapSize;
				var data = new byte[dataSz];
				stream.Read( out data );
				this.mCpuBlendMapStorage.AddRange( data );
			}

			//derived data
			while ( !stream.IsEndOfChunk( TERRAIN_CHUNK_ID ) && stream.NextChunkId == TERRAINDERIVEDDATA_CHUNK_ID )
			{
				stream.ReadChunkBegin( TERRAINDERIVEDDATA_CHUNK_ID, TERRAINDERIVEDDATA_CHUNK_VERSION );
				//name
				var name = string.Empty;
				stream.Read( out name );
				ushort sz;
				stream.Read( out sz );
				if ( name == "normalmap" )
				{
					this.mNormalMapRequired = true;
					var data = new byte[sz*sz*3];
					stream.Read( out data );
					using ( var pDataF = BufferBase.Wrap( data ) )
					{
						this.mCpuTerrainNormalMap = new PixelBox( sz, sz, 1, PixelFormat.BYTE_RGB, pDataF );
					}
				}
				else if ( name == "colormap" )
				{
					IsGlobalColorMapEnabled = true;
					GlobalColorMapSize = sz;
					this.mCpuColorMapStorage = new byte[sz*sz*3];
					stream.Read( out this.mCpuColorMapStorage );
				}
				else if ( name == "lightmap" )
				{
					this.mLightMapRequired = true;
					LightMapSize = sz;
					this.mCpuLightmapStorage = new byte[sz*sz];
					stream.Read( out this.mCpuLightmapStorage );
				}
				else if ( name == "compositemap" )
				{
					this.mCompositeMapRequired = true;
					this.mCompositeMapSize = sz;
					this.mCpuCompositeMapStorage = new byte[sz*sz*4];
					stream.Read( out this.mCpuCompositeMapStorage );
				}

				stream.ReadChunkEnd( TERRAINDERIVEDDATA_CHUNK_ID );
			}

			//Load delta data
			var deltaData = new byte[ sizeof( float ) * numVertices ];
			stream.Read( out deltaData );
			this.mDeltaDataPtr = BufferBase.Wrap( deltaData );

			//Create and load quadtree
			QuadTree = new TerrainQuadTreeNode( this, null, 0, 0, this.mSize, (ushort)( NumLodLevels - 1 ), 0, 0 );
			QuadTree.Prepare();

			stream.ReadChunkEnd( TERRAIN_CHUNK_ID );

			DistributeVertexData();

			IsModified = false;
			IsHeightDataModified = false;

			return true;
		}
Example #9
0
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="terrain">The ultimate parent terrain</param>
        /// <param name="parent">ptional parent node (in which case xoff, yoff are 0 and size must be entire terrain)</param>
        /// <param name="xOff">Offsets from the start of the terrain data in 2D</param>
        /// <param name="yOff">Offsets from the start of the terrain data in 2D</param>
        /// <param name="size">The size of the node in vertices at the highest LOD</param>
        /// <param name="lod">The base LOD level</param>
        /// <param name="depth">The depth that this node is at in the tree (or convenience)</param>
        /// <param name="quadrant">The index of the quadrant (0, 1, 2, 3)</param>
        public TerrainQuadTreeNode(Terrain terrain, TerrainQuadTreeNode parent, ushort xOff, ushort yOff,
            ushort size, ushort lod, ushort depth, ushort quadrant)
        {
            mTerrain = terrain;
            mParent = parent;
            mOffsetX = xOff;
            mOffsetY = yOff;
            mBoundaryX = (ushort)(xOff + size);
            mBoundaryY = (ushort)(yOff + size);
            mSize = size;
            mBaseLod = lod;
            mDepth = depth;
            mQuadrant = quadrant;
            mBoundingRadius = 0;
            mCurrentLod = -1;
            mMaterialLodIndex = 0;
            mLodTransition = 0;
            mChildWithMaxHeightDelta = null;
            mSelfOrChildRendered = false;
            mNodeWithVertexData = null;
           // mMovable = null;
            mRend = null;
            mAABB = new AxisAlignedBox();
            if (mTerrain.MaxBatchSize < size)
            {
                ushort childSize = (ushort)(((size - 1) * 0.5f) + 1);
                ushort childOff = (ushort)(childSize - 1);
                ushort childLod = (ushort)(lod - 1);
                ushort childDepth = (ushort)(depth + 1);

                mChildren[0] = new TerrainQuadTreeNode(mTerrain, this, xOff, yOff, childSize, childLod, childDepth, 0);
                mChildren[1] = new TerrainQuadTreeNode(mTerrain, this, (ushort)(xOff + childOff), yOff, childSize, childLod, childDepth, 1);
                mChildren[2] = new TerrainQuadTreeNode(mTerrain, this, xOff, (ushort)(yOff + childOff), childSize, childLod, childDepth, 2);
                mChildren[3] = new TerrainQuadTreeNode(mTerrain, this, (ushort)(xOff + childOff), (ushort)(yOff + childOff), childSize, childLod, childDepth, 3);

                LodLevel ll = new LodLevel();
                // non-leaf nodes always render with minBatchSize vertices
                ll.BatchSize = mTerrain.MinBatchSize;
                ll.MaxHeightDelta = 0;
                ll.CalcMaxHeightDelta = 0;
                mLodLevels.Add(ll);
            }
            else
            {
                //no children
                Array.Clear(mChildren, 0, mChildren.Length);
                // this is a leaf node and may have internal LODs of its own
                ushort ownLod = mTerrain.LodLevelsPerLeafCount;

                Debug.Assert(lod == (ownLod - 1),
                    "The lod passed in should reflect the number of lods in a leaf");
                // leaf nodes always have a base LOD of 0, because they're always handling
                // the highest level of detail
                mBaseLod = 0;
                ushort sz = mTerrain.MaxBatchSize;

                while (ownLod-- != 0)
                {
                    LodLevel ll = new LodLevel();
                    ll.BatchSize = sz;
                    ll.MaxHeightDelta = 0;
                    ll.CalcMaxHeightDelta = 0;
                    mLodLevels.Add(ll);
                    if(ownLod != 0)
                        sz = (ushort)(((sz - 1) * 0.5) + 1);

                }
                Debug.Assert(sz == mTerrain.MinBatchSize);
            }

            // local centre calculation
            // because of pow2 +1 there is always a middle point
            ushort midoffset = (ushort)((size - 1) / 2);
            ushort midpointX = (ushort)(mOffsetX + midoffset);
            ushort midpointY = (ushort)(mOffsetY + midoffset);

            //derive the local centry, but give it a height if 0
            //TODO: - what if we actually centred this at the terrain height at this point?
            //would this be better?
            mTerrain.GetPoint(midpointX, midpointY, 0, ref mLocalCentre);
            /*mRend = new Rend(this);
            mMovable = new Movable(this,mRend);*/
            mRend= new TerrainRendable(this);
            SceneNode sn = mTerrain.RootSceneNode.CreateChildSceneNode(mLocalCentre);
            sn.AttachObject(mRend);
           // sn.AttachObject(mRend);

        }
Example #10
0
 public TerrainRendable(TerrainQuadTreeNode parent)
 {
     mParent = parent;
     this.MovableType = "AxiomTerrainNodeMovable";
 }
Example #11
0
 /// <summary>
 /// 
 /// </summary>
 /// <param name="parent"></param>
 public Movable(TerrainQuadTreeNode parent, Rend renderable)
 {
     mParent = parent;
     mRend = renderable;
 }