private void ConvertConfigToRules( AutoSplatConfig autoSplatConfig )
        {
            // Initialize textures

            layerTextureNames[ SAND_INDEX ]  = autoSplatConfig.SandTextureName;
            layerTextureNames[ GRASS_INDEX ] = autoSplatConfig.GrassTextureName;
            layerTextureNames[ ROCK_INDEX ]  = autoSplatConfig.RockTextureName;
            layerTextureNames[ SNOW_INDEX ]  = autoSplatConfig.SnowTextureName;

            // Create height angle ranges bottom-up; in all cases we use rock for
            // the vertical -- the steeper the rockier. Actually, more accurately,
            // we don't use full vertical; in the auto splat normal rules, we only
            // go up to one radian.  So each of these is actually a triplet, where
            // we use the same texture for up to 57.2 degrees, then start adding rock.

            AutoSplatHeightAngleRange range;
            float angleStart = 0f;
            float angleMid   = 90f - 57.2957795f;
            float angleEnd   = 90f;

            // Min height - Sand
            range = new AutoSplatHeightAngleRange( Math.Min( MinHeightMM, 0 ) );
            range.AddAngleTexturePair( angleStart, SAND_INDEX );
            range.AddAngleTexturePair( angleMid, SAND_INDEX );
            range.AddAngleTexturePair( angleEnd, ROCK_INDEX );
            InternalAddHeightAngleRange( range );

            // Grass - blend inflection point for grass
            range = new AutoSplatHeightAngleRange( (long) autoSplatConfig.SandToGrassHeight );
            range.AddAngleTexturePair( angleStart, GRASS_INDEX );
            range.AddAngleTexturePair( angleMid, GRASS_INDEX );
            range.AddAngleTexturePair( angleEnd, ROCK_INDEX );
            InternalAddHeightAngleRange( range );

            // Rock - blend inflection point for rock
            range = new AutoSplatHeightAngleRange( (long) autoSplatConfig.GrassToRockHeight );
            range.AddAngleTexturePair( angleStart, ROCK_INDEX );
            range.AddAngleTexturePair( angleEnd, ROCK_INDEX );
            InternalAddHeightAngleRange( range );

            // Snow - blend inflection point for snow
            range = new AutoSplatHeightAngleRange( (long) autoSplatConfig.RockToSnowHeight );
            range.AddAngleTexturePair( angleStart, SNOW_INDEX );
            range.AddAngleTexturePair( angleMid, SNOW_INDEX );
            range.AddAngleTexturePair( angleEnd, ROCK_INDEX );
            InternalAddHeightAngleRange( range );

            // Max height - Snow!
            if( MaxHeightMM > autoSplatConfig.RockToSnowHeight )
            {
                // Max height - Snow!
                range = new AutoSplatHeightAngleRange( MaxHeightMM );
                range.AddAngleTexturePair( angleStart, SNOW_INDEX );
                range.AddAngleTexturePair( angleEnd, SNOW_INDEX );
                InternalAddHeightAngleRange( range );
            }
        }
        private void InternalAddHeightAngleRange( AutoSplatHeightAngleRange range )
        {
            if( range == null )
            {
                throw new NullReferenceException();
            }

            // Make sure the height angle range to be added is not duplicated
            foreach( AutoSplatHeightAngleRange angleRange in m_RangeList )
            {
                if( angleRange.HeightMM == range.HeightMM )
                {
                    throw new ArgumentException( "Duplicate heights are not allowed." + range );
                }
            }

            #if REJECT_OUT_OF_BOUNDS_RANGES
            // A properly configured world will never have a range outside of the
            // height limits, but it is possible that a legacy world will not have
            // saved the AutoSplatConfig, so it uses the default--and the default
            // config might have ranges outside the world's limits.  Rather than
            // try to guess how to fit it, we'll just discard it, and leave it to
            // the user to set up a configuration that they want.
            if( range.HeightMM <= MaxHeightMM &&
                range.HeightMM >= MinHeightMM )
            {
                m_RangeList.Add( range );
                m_RangeList.Sort(); // Keep sorted by height

                m_IdToRange.Add( range.Id, range );

                FireHeightRangeAdded( range );
            }
            #else
            m_RangeList.Add( range );
            m_RangeList.Sort(); // Keep sorted by height

            m_IdToRange.Add( range.Id, range );

            // TODO: This is not right yet, but useful in the short term
            // For legacy worlds, we may have ranges outside the min/max
            // that occurs in the world. We'll stretch the bounds for
            // auto-splatting, but now they are out of sync with the terrain.
            AdjustMinMax();

            FireHeightRangeAdded( range );
            #endif
        }
 protected void FireHeightRangeRemoved( AutoSplatHeightAngleRange range )
 {
     if( null != HeightRangeRemoved )
     {
         HeightRangeRemoved( this, range );
     }
 }
 public void UndoRemoveHeightRange( AutoSplatHeightAngleRange range )
 {
     InternalAddHeightAngleRange( range );
 }
        public void MoveHeight( Guid id, long newHeightMM )
        {
            if( m_IdToRange.ContainsKey( id ) )
            {
                AutoSplatHeightAngleRange targetRange = m_IdToRange[ id ];

                if( newHeightMM.Equals( targetRange.HeightMM ) )
                {
                    // This move isn't going to change anything, so get out early.
                    return;
                }

                int index = m_RangeList.IndexOf( targetRange );

                AutoSplatHeightAngleRange splitRange = null;

                if( 0 == index || (m_RangeList.Count - 1) == index )
                {
                    // This move alters a range at an extreme, which implicitly splits the
                    // range as we must always keep a range at each extreme.
                    splitRange = new AutoSplatHeightAngleRange( targetRange.HeightMM, targetRange );
                }
                else
                {
                    // Guaranteed to be neither the first nor last
                    AutoSplatHeightAngleRange prev = m_RangeList[ index - 1 ];
                    AutoSplatHeightAngleRange next = m_RangeList[ index + 1 ];

                    if( newHeightMM < prev.HeightMM || next.HeightMM < newHeightMM )
                    {
                        throw new ConstraintException(
                            "MoveHeight would change ordering of HeightAngleRange list;" +
                            " only Add and Remove should change the sort ordering." );
                    }

                    // If the move would make the height coincide with one of its
                    // neighbors, back it off slightly to make things sort nicely.
                    if( newHeightMM.Equals( prev.HeightMM ) )
                    {
                        ++newHeightMM;
                    }
                    else if( newHeightMM.Equals( next.HeightMM ) )
                    {
                        --newHeightMM;
                    }
                }

                targetRange.HeightMM = newHeightMM;

                FireHeightRangeMoved( targetRange );

                if( null != splitRange )
                {
                    // Add the result of the split
                    InternalAddHeightAngleRange( splitRange );
                }
            }
        }
 public AutoSplatHeightAngleRange AddHeight( long heightMM, AutoSplatHeightAngleRange slopesTemplate )
 {
     AutoSplatHeightAngleRange range = new AutoSplatHeightAngleRange( heightMM, slopesTemplate );
     InternalAddHeightAngleRange( range );
     return range;
 }
 public AutoSplatHeightAngleRange AddHeight( long heightMM, int textureIndex )
 {
     AutoSplatHeightAngleRange range = new AutoSplatHeightAngleRange( heightMM, textureIndex, textureIndex );
     InternalAddHeightAngleRange( range );
     return range;
 }