public void MoveAngle(Guid pairId, float angleDegrees)
        {
            AutoSplatAngleTexturePair pair = GetPair(pairId);

            if (!pair.AngleDegrees.Equals(angleDegrees))
            {
                AutoSplatAngleTexturePair clone = null;

                if (pair.IsCriticalAngle)
                {
                    clone = new AutoSplatAngleTexturePair(pair);
                    // We won't need to 'infinitesimally bump' the new angle because
                    // we already tested to establish that the values are not equal.
                }
                else
                {
                    // Guarenteed to be neither first nor last because it's not critical
                    int pairIndex = GetIndex(pairId);

                    float prevAngle = m_AngleSortedPairs[pairIndex - 1].AngleDegrees;
                    float nextAngle = m_AngleSortedPairs[pairIndex + 1].AngleDegrees;

                    if (angleDegrees < prevAngle || angleDegrees > nextAngle)
                    {
                        // Only Add and Remove should change the sorting order
                        throw new ConstraintException(
                                  "MoveAngle would change ordering of pairs;" +
                                  "only Add and Remove should change the sorting order.");
                    }

                    // If the move would make the angle coincide exactly with one of
                    // its neighbors, back it off a teensie-weensie bit.
                    const float infinitesimal = 0.0001f;
                    if (angleDegrees.Equals(prevAngle))
                    {
                        angleDegrees += infinitesimal;
                    }
                    else if (angleDegrees.Equals(nextAngle))
                    {
                        angleDegrees -= infinitesimal;
                    }
                }

                float oldAngle = pair.AngleDegrees;

                pair.AngleDegrees = angleDegrees;

                FireAngleMoved(pairId, oldAngle, angleDegrees);

                if (null != clone)
                {
                    // So we are splitting one of the critical pairs. By waiting until
                    // after we've moved the original pair to add the clone, we assure
                    // the sorting order stays the same for pre-existing pairs. The
                    // Outside World cares about this order.
                    InternalAddAngleTexturePair(clone);
                }
            }
        }
        public AutoSplatAngleTexturePair AddAngleTexturePair(float angleDegrees, int textureIndex)
        {
            AutoSplatAngleTexturePair pair = new AutoSplatAngleTexturePair(angleDegrees, textureIndex);

            InternalAddAngleTexturePair(pair);

            return(pair);
        }
        /// <summary>
        /// This is like an 'add' operation, but you need to restore the id of the
        /// pair literally because further operations in the undo stack will refer
        /// to the pair by id.
        /// </summary>
        /// <param name="angleDegrees">angle of the slope</param>
        /// <param name="textureIndex">texture associated with the slope</param>
        /// <param name="id">unique id by which the slope is accessed</param>
        /// <returns></returns>
        public AutoSplatAngleTexturePair UndoRemoveAngleTexturePair(float angleDegrees, int textureIndex, Guid id)
        {
            AutoSplatAngleTexturePair pair = new AutoSplatAngleTexturePair(angleDegrees, textureIndex, id);

            InternalAddAngleTexturePair(pair);

            return(pair);
        }
        void InternalRemoveAngleTexturePair(AutoSplatAngleTexturePair pair)
        {
            // You are not allowed to remove critical pairs!  We have a design
            // constraint that there are always a horizontal and a vertical pair.
            //
            // This is not as constraining as it might sound. Assume the 'remove'
            // request is the outcome of a GUI action--like dragging to the trash.
            // Even if you attempt to remove a critical pair, the act of moving it
            // to the trash hotspot will move it away from the critical position,
            // which in turn causes a new pair to be inserted to replace it in the
            // critical position.  You could do it all day if that's how you like
            // to spend your time.

            if (!pair.IsCriticalAngle)
            {
                m_IdToPairMap.Remove(pair.Id);

                m_AngleSortedPairs.Remove(pair);

                FireAngleTexturePairRemoved(pair);
            }
        }
        public float[] GetAutoSplatSampleNormalized(float angleDegrees)
        {
            AutoSplatAngleTexturePair lowerPair  = null;
            AutoSplatAngleTexturePair higherPair = null;

            // We assume the pairs are sorted in increasing order by angle
            foreach (AutoSplatAngleTexturePair pair in m_AngleSortedPairs)
            {
                if (pair.AngleDegrees == angleDegrees)
                {
                    lowerPair  = pair;
                    higherPair = pair;
                    break;
                }

                if (pair.AngleDegrees < angleDegrees)
                {
                    lowerPair = pair;
                    continue;
                }

                if (pair.AngleDegrees > angleDegrees)
                {
                    higherPair = pair;
                    // We should have both the lower & upper bounds now, so break;
                    break;
                }
            }

            if (lowerPair == null)
            {
                Debug.Assert(lowerPair != null,
                             "Unable to find lower angle when getting autosplat sample.  Angle=" + angleDegrees + " Range=" +
                             this);
            }
            if (higherPair == null)
            {
                Debug.Assert(higherPair != null,
                             "Unable to find higher angle when getting autosplat sample.  Angle=" + angleDegrees +
                             " Range=" + this);
            }

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

            float angleDiff = higherPair.AngleDegrees - lowerPair.AngleDegrees;

            if (angleDiff == 0 || lowerPair.TextureIndex == higherPair.TextureIndex)
            {
                lowerWeight  = 0f;
                higherWeight = 1f;
            }
            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 = (angleDegrees - lowerPair.AngleDegrees) / angleDiff;
                lowerWeight  = 1f - higherWeight;
            }

            float[] normalizedSample = new float[AlphaSplatTerrainConfig.MAX_LAYER_TEXTURES];

            // It's important that we set higher second because
            // if lower & higher angle are equal the same, we
            // set the higherWeight to 1f above and want to make
            // sure it's set that way here.
            normalizedSample[lowerPair.TextureIndex]  = lowerWeight;
            normalizedSample[higherPair.TextureIndex] = higherWeight;

            return(normalizedSample);
        }