public AutoSplatHeightAngleRange AddHeight(long heightMM, AutoSplatHeightAngleRange slopesTemplate)
        {
            AutoSplatHeightAngleRange range = new AutoSplatHeightAngleRange(heightMM, slopesTemplate);

            InternalAddHeightAngleRange(range);
            return(range);
        }
 protected void FireHeightRangeMoved(AutoSplatHeightAngleRange range)
 {
     if (null != HeightRangeMoved)
     {
         HeightRangeMoved(this, range);
     }
 }
        public AutoSplatHeightAngleRange AddHeight(long heightMM, int textureIndex)
        {
            AutoSplatHeightAngleRange range = new AutoSplatHeightAngleRange(heightMM, textureIndex, textureIndex);

            InternalAddHeightAngleRange(range);
            return(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);
                }
            }
        }
        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
        }
        private void InternalRemoveHeightAngleRange(Guid rangeId)
        {
            int index = m_RangeList.IndexOf(m_IdToRange[rangeId]);

            // Design constraints require that you always have a min and max
            // range, so we are not allowed to remove the extremes. Otherwise,
            // go crazy!
            if (0 != index && (m_RangeList.Count - 1) != index)
            {
                AutoSplatHeightAngleRange range = m_IdToRange[rangeId];

                m_RangeList.Remove(range);
                m_IdToRange.Remove(rangeId);

                FireHeightRangeRemoved(range);
            }
        }
        public float[] GetAutoSplatSampleNormalized(long heightMM, Vector3 normal)
        {
            AutoSplatHeightAngleRange lowerRange  = null;
            AutoSplatHeightAngleRange higherRange = null;

            // We assume the ranges are sorted in increasing order by height
            foreach (AutoSplatHeightAngleRange range in m_RangeList)
            {
                if (range.HeightMM == heightMM)
                {
                    lowerRange  = range;
                    higherRange = range;
                    break;
                }

                if (range.HeightMM < heightMM)
                {
                    lowerRange = range;
                    continue;
                }

                if (range.HeightMM > heightMM)
                {
                    higherRange = range;
                    // We should have both the lower & upper bounds now, so break;
                    break;
                }
            }

            if (lowerRange == null)
            {
                lowerRange = m_RangeList[0];   // allows us to continue
            }
            if (higherRange == null)
            {
                higherRange = m_RangeList[m_RangeList.Count - 1];   // allows us to continue
            }

            // We want the angle of the normal relative to the XZ plane, so
            // we first use the dot product to get the angle to the Y-axis
            // which is perpendicular to the XZ plane, convert it to degress,
            // and then subtract it from 90.
            float angleRadians = normal.Dot(Vector3.UnitY);
            float angleDegrees = Convert.ToSingle(90 - RadiansToDegrees(angleRadians));

            if (lowerRange == higherRange)
            {
                // No need to do any weighting since we at the exact height
                return(lowerRange.GetAutoSplatSampleNormalized(angleDegrees));
            }

            // Compute the gradiant weighting for the lower & higher angled textures
            float lowerWeight;
            float higherWeight;

            long heightDiff = higherRange.HeightMM - lowerRange.HeightMM;

            if (heightDiff == 0)
            {
                // Give equal weighting to both samples.
                // This covers the case when we have two ranges at the
                // same height....this really shouldn't happen due to the
                // way we choose the lower/higher ranges.
                lowerWeight  = 0.5f;
                higherWeight = 0.5f;
            }
            else
            {
                // How close is the angle to the higher/lower angle?  Normalize
                // that distance from 0..1 and use that as the gradient weights
                higherWeight = ((float)(heightMM - lowerRange.HeightMM)) / heightDiff;
                lowerWeight  = 1f - higherWeight;
            }


            float[] lowerNormalizedSample  = lowerRange.GetAutoSplatSampleNormalized(angleDegrees);
            float[] higherNormalizedSample = higherRange.GetAutoSplatSampleNormalized(angleDegrees);

            float[] normalizedSample = new float[AlphaSplatTerrainConfig.MAX_LAYER_TEXTURES];
            for (int i = 0; i < AlphaSplatTerrainConfig.MAX_LAYER_TEXTURES; i++)
            {
                normalizedSample[i] = lowerNormalizedSample[i] * lowerWeight +
                                      higherNormalizedSample[i] * higherWeight;
            }
            return(normalizedSample);
        }
 public void UndoRemoveHeightRange(AutoSplatHeightAngleRange range)
 {
     InternalAddHeightAngleRange(range);
 }