ParticleEffets例子效果

clipboard.png
实现效果:

  1. 界面上的按钮为两种,一是点击会爆发出微粒,二是点击会出现环形圆圈警告模样。
  2. 按钮之间的碰撞及与边界、移动距离及速度的关系受滚动条参数的控制,

代码组织:
clipboard.png
类关系图

文件解释:

clipboard.png

  1. ParticleEffectExamples.xaml实现以上界面的文件,包括调参数Slider控件、包含按钮的自定义FireworkEffect、SonicEffect控件及容器MagnitismCanvas控件
  2. OverlayRenderDecoratorOverlayVisual.cs继承DrawingVisual类,实现管理是否需要当前Visual类的呈现执行命中测试
  3. OverlayRenderDecorator.cs中 重载呈现装饰类OverlayRenderDecorator继承框架类FrameworkElement,聚集上面的OverlayRenderDecoratorOverlayVisual字段、可视化集合类VisualCollection、作为子内容UIElement类的引用。实现添加逻辑内容按钮并点击时会呈现由子类实现的具体呈现内容,如爆发微粒,警告模样呈现。
  4. FireworkEffect.cs文件继承以上装饰类OverlayRenderDecorator实现爆发微粒绘图的每帧呈现,由鼠标点击触发及爆发微粒的每帧可视化呈现 来实现。其中包含微粒的生成、数量、随机出现下落位置、消失及更新。
  5. SonicEffect.cs文件同上,触发圆环的绘制,包含圆环的数量、大小等。
  6. MagnitismCanvas.cs文件继承画布Canvas,设置三个参数:摇摆幅度Drag、边界作用力BorderForce、子项间的相互作用力ChildForce
  7. ParticleEffectsTimeTracker.cs文件提供基本的时间增量驱动,对MagnitismCanvas来说,获取每帧的时间间隔与内容项的位移参数;对FireworkEffect来说也提供帧间时间值,判断是否大于存在时间;对SonicEffet来说暂时未确定,因为更改附件属性RingDelay未起作用。

界面xaml:

<Canvas x:Name="PageCanvas">
  <ae:MagnitismCanvas x:Name="MagCanvas" Drag="0.8" BorderForce="1000" ChildForce="100" 
            DataContext="{Binding ElementName=PageCanvas}" Width="{Binding Path=ActualWidth}" Height="{Binding Path=ActualHeight}">
    <ae:FireworkEffect Canvas.Top="100" Canvas.Left="100">
      <ae:FireworkEffect.Child>
        <Button>Click For Fireworks</Button>
      </ae:FireworkEffect.Child>
    </ae:FireworkEffect>
    <ae:SonicEffect Canvas.Top="200" Canvas.Left="200" RingThickness="10" RingRadius="15"  RingDelay="0:0:2" RingColor="#22553366">
      <ae:SonicEffect.Child>
        <Button>Click For Sonic burst</Button>
      </ae:SonicEffect.Child>
    </ae:SonicEffect>

OverlayRenderDecoratorOverlayVisual:

/// <summary>
///     This class is used internally to delegate it's rendering to the parent OverlayRenderDecorator.
/// </summary>
internal class OverlayRenderDecoratorOverlayVisual : DrawingVisual
{
    internal bool IsHitTestVisible { get; set; } = false;
    //dont hit test, these are just overlay graphics
    protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters)
    {
        if (IsHitTestVisible)
            return base.HitTestCore(hitTestParameters);
        return null;
    }

    //dont hit test, these are just overlay graphics
    protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
    {
        if (IsHitTestVisible)
            return base.HitTestCore(hitTestParameters);
        return null;
    }
}
/// <summary>
    ///     OverlayRenderDecorator provides a simple overlay graphics mechanism similar
    ///     to OnRender called OnOverlayRender
    /// </summary>
    [ContentProperty("Child")]
    public class OverlayRenderDecorator : FrameworkElement
    {
        //this will be a visual child, but not a logical child.  as the last child, it can 'overlay' graphics
        // by calling back to us with OnOverlayRender
        private readonly OverlayRenderDecoratorOverlayVisual _overlayVisual;
        private readonly VisualCollection _vc;
        //the only logical child
        private UIElement _child;
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------

        #region Constructors

        /// <summary>
        ///     Default constructor
        /// </summary>
        public OverlayRenderDecorator()
        {
            _overlayVisual = new OverlayRenderDecoratorOverlayVisual();

            _vc = new VisualCollection(this) {_overlayVisual};
            
            //insert the overlay element into the visual tree
        }

        #endregion

        //-------------------------------------------------------------------
        //
        //  Public Properties
        //
        //-------------------------------------------------------------------

        #region Public Properties

        /// <summary>
        ///     Enables/Disables hit testing on the overlay visual
        /// </summary>
        public bool IsOverlayHitTestVisible
        {
            get
            {
                if (_overlayVisual != null)
                    return _overlayVisual.IsHitTestVisible;
                return false;
            }
            set
            {
                if (_overlayVisual != null)
                    _overlayVisual.IsHitTestVisible = value;
            }
        }

        /// <summary>
        ///     The single child of an <see cref="System.Windows.Media.Animation.OverlayRenderDecorator" />
        /// </summary>
        [DefaultValue(null)]
        public virtual UIElement Child
        {
            get { return _child; }
            set
            {
                if (_child != value)
                {
                    //need to remove old element from logical tree
                    if (_child != null)
                    {
                        OnDetachChild(_child);
                        RemoveLogicalChild(_child);
                    }

                    _vc.Clear();

                    if (value != null)
                    {
                        //add to visual
                        _vc.Add(value);
                        //add to logical
                        AddLogicalChild(value);
                    }

                    //always add the overlay child back into the visual tree if its set
                    if (_overlayVisual != null)
                        _vc.Add(_overlayVisual);

                    //cache the new child
                    _child = value;

                    //notify derived types of the new child
                    if (value != null)
                        OnAttachChild(_child);

                    InvalidateMeasure();
                }
            }
        }

        /// <summary>
        ///     Returns enumerator to logical children.
        /// </summary>
        protected override IEnumerator LogicalChildren
        {
            get
            {
                if (_child == null)
                {
                    return new List<UIElement>().GetEnumerator();
                }
                var l = new List<UIElement> {_child};
                return l.GetEnumerator();
            }
        }

        #endregion

        //-------------------------------------------------------------------
        //
        //  Protected Methods
        //
        //-------------------------------------------------------------------

        #region Protected Methods

        /// <summary>
        ///     Updates DesiredSize of the OverlayRenderDecorator.  Called by parent UIElement.  This is the first pass of layout.
        /// </summary>
        /// <remarks>
        ///     OverlayRenderDecorator determines a desired size it needs from the child's sizing properties, margin, and requested
        ///     size.
        /// </remarks>
        /// <param name="constraint">Constraint size is an "upper limit" that the return value should not exceed.</param>
        /// <returns>The OverlayRenderDecorator's desired size.</returns>
        protected override Size MeasureOverride(Size constraint)
        {
            var child = Child;
            if (child != null)
            {
                child.Measure(constraint);
                return (child.DesiredSize);
            }
            return (new Size());
        }

        /// <summary>
        ///     OverlayRenderDecorator computes the position of its single child inside child's Margin and calls Arrange
        ///     on the child.
        /// </summary>
        /// <param name="arrangeSize">Size the OverlayRenderDecorator will assume.</param>
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            var child = Child;
            child?.Arrange(new Rect(arrangeSize));

            //Our OnRender gets called in Arrange, but
            //  we dont have access to that, so update the 
            //  overlay here.
            if (_overlayVisual != null)
            {
                using (var dc = _overlayVisual.RenderOpen())
                {
                    //delegate to derived types
                    OnOverlayRender(dc);
                }
            }

            return (arrangeSize);
        }

        /// <summary>
        ///     render method for overlay graphics.
        /// </summary>
        /// <param name="dc"></param>
        protected virtual void OnOverlayRender(DrawingContext dc)
        {
        }

        /// <summary>
        ///     gives derives types a simple way to respond to a new child being added
        /// </summary>
        /// <param name="child"></param>
        protected virtual void OnAttachChild(UIElement child)
        {
        }

        /// <summary>
        ///     gives derives types a simple way to respond to a child being removed
        /// </summary>
        /// <param name="child"></param>
        protected virtual void OnDetachChild(UIElement child)
        {
        }

        protected override int VisualChildrenCount => _vc.Count;

        protected override Visual GetVisualChild(int index) => _vc[index];

        #endregion
    }

FireworkEffect:

//uielement
//visual operations
//animation effect stuff
//LayoutOverrideDecorator

public class FireworkEffect : OverlayRenderDecorator
{
    private readonly Vector _gravity = new Vector(0.0, 10.0);
    private readonly List<Particle> _particles = new List<Particle>();
    private Point _lastMousePosition = new Point(0, 0);
    private double _secondsUntilDrop;
    private ParticleEffectsTimeTracker _timeTracker;
    protected Random Random = new Random();

    protected override void OnAttachChild(UIElement child)
    {
        CompositionTarget.Rendering += OnFrameCallback;

        child.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
        child.PreviewMouseMove += OnMouseMove;

        _timeTracker = new ParticleEffectsTimeTracker();
    }

    protected override void OnDetachChild(UIElement child)
    {
        CompositionTarget.Rendering -= OnFrameCallback;

        child.PreviewMouseLeftButtonUp -= OnMouseLeftButtonUp;
        child.PreviewMouseMove -= OnMouseMove;

        _timeTracker = null;
    }

    protected void OnFrameCallback(object sender, EventArgs e)
    {
        UpdateParticles(_timeTracker.Update());
    }

    protected virtual void UpdateParticles(double deltatime)
    {
        //drop particles from mouse position
        if (MouseDropsParticles && IsMouseOver)
        {
            _secondsUntilDrop -= deltatime;
            if (_secondsUntilDrop < 0.0)
            {
                AddRandomBurst(_lastMousePosition - new Vector(Radius/2.0, Radius/2.0), 1);
                _secondsUntilDrop = MouseDropDelay + (Random.NextDouble()*2.0 - 1.0)*MouseDropDelayVariation;
            }
        }

        if (_particles.Count > 0)
            InvalidateVisual();

        //update all particles
        for (var i = 0; i < _particles.Count;)
        {
            //_particles[i]
            var p = _particles[i];

            if (GravitateToMouse)
            {
                p.Velocity += (_lastMousePosition - p.Location)*GravitateScale;
            }
            else
            {
                p.Velocity += _gravity;
            }
            p.Location += p.Velocity*deltatime;

            if (BounceOffContainer)
            {
                var radius = p.Diameter/2.0;
                if (p.Location.X - radius < 0.0)
                {
                    p.Location.X = radius;
                    p.Velocity.X *= -0.9;
                }
                else if (p.Location.X > ActualWidth - radius)
                {
                    p.Location.X = ActualWidth - radius;
                    p.Velocity.X *= -0.9;
                }
                if (p.Location.Y - radius < 0.0)
                {
                    p.Location.Y = radius;
                    p.Velocity.Y *= -0.9;
                }
                else if (p.Location.Y > ActualHeight - radius)
                {
                    p.Location.Y = ActualHeight - radius;
                    p.Velocity.Y *= -0.9;
                }
            }

            //only increment counter for live particles
            if (_timeTracker.ElapsedTime > p.DeathTime)
                _particles.RemoveAt(i);
            else
                i++;
        }
    }

    protected override void OnOverlayRender(DrawingContext drawingContext)
    {
        foreach (var p in _particles)
        {
            //figure out where in the particles life we are
            var particlelife = (_timeTracker.ElapsedTime - p.LifeTime).TotalSeconds/
                               (p.DeathTime - p.LifeTime).TotalSeconds;
            var currentcolor = Color.Multiply(p.StartColor, (float) (1.0 - particlelife)) +
                               Color.Multiply(p.EndColor, (float) particlelife);
            Brush brush = new RadialGradientBrush(currentcolor,
                Color.FromArgb(0, currentcolor.R, currentcolor.G, currentcolor.B));

            var rect =
                new RectangleGeometry(
                    new Rect(new Point(p.Location.X - p.Diameter/2.0, p.Location.Y - p.Diameter/2.0),
                        new Size(p.Diameter, p.Diameter)));
            drawingContext.DrawGeometry(brush, null, rect);
        }
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        _lastMousePosition = e.GetPosition(this);
    }

    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        AddRandomBurst(e.GetPosition(this));
    }

    public void AddRandomBurst()
    {
        var point = new Point(Random.NextDouble()*ActualWidth, Random.NextDouble()*ActualHeight);
        AddRandomBurst(point, ClickBurstSize);
    }

    public void AddRandomBurst(Point location)
    {
        AddRandomBurst(location, ClickBurstSize);
    }

    public void AddRandomBurst(Point location, int count)
    {
        for (var i = 0; i < count; i++)
        {
            var p = new Particle();

            var radius = Radius + (Random.NextDouble()*2.0 - 1.0)*RadiusVariation;
            var lifetime = Random.NextDouble()*3.0 + 1.0;

            p.Location = location;
            p.Velocity = new Vector(Random.NextDouble()*200.0 - 100.0, Random.NextDouble()*-200 + 100.0);
            p.LifeTime = _timeTracker.ElapsedTime;
            p.DeathTime = _timeTracker.ElapsedTime + TimeSpan.FromSeconds(lifetime);
            p.Diameter = 2.0*radius;

            var startColor = StartColor;
            var startColorVariation = StartColorVariation;
            var endColor = EndColor;
            var endColorVariation = EndColorVariation;

            var startRandColor = Color.FromScRgb(startColorVariation.ScA*(float) (Random.NextDouble()*2.0 - 1.0),
                startColorVariation.ScR*(float) (Random.NextDouble()*2.0 - 1.0),
                startColorVariation.ScG*(float) (Random.NextDouble()*2.0 - 1.0),
                startColorVariation.ScB*(float) (Random.NextDouble()*2.0 - 1.0));
            var endRandColor = Color.FromScRgb(endColorVariation.ScA*(float) (Random.NextDouble()*2.0 - 1.0),
                endColorVariation.ScR*(float) (Random.NextDouble()*2.0 - 1.0),
                endColorVariation.ScG*(float) (Random.NextDouble()*2.0 - 1.0),
                endColorVariation.ScB*(float) (Random.NextDouble()*2.0 - 1.0));

            p.StartColor = startColor + startRandColor;
            p.EndColor = endColor + endRandColor;
            _particles.Add(p);
        }
    }

    #region Dependency Properties

    public static readonly DependencyProperty RadiusProperty =
        DependencyProperty.Register(
            "RadiusBase",
            typeof (double),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(15.0)
            );

    public static readonly DependencyProperty RadiusVariationProperty =
        DependencyProperty.Register(
            "RadiusVariation",
            typeof (double),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(5.0)
            );

    public static readonly DependencyProperty StartColorProperty =
        DependencyProperty.Register(
            "StartColor",
            typeof (Color),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(Color.FromArgb(245, 200, 50, 20))
            );

    public static readonly DependencyProperty EndColorProperty =
        DependencyProperty.Register(
            "EndColor",
            typeof (Color),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(Color.FromArgb(100, 200, 255, 20))
            );

    public static readonly DependencyProperty StartColorVariationProperty =
        DependencyProperty.Register(
            "StartColorVariation",
            typeof (Color),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(Color.FromArgb(10, 55, 50, 20))
            );

    public static readonly DependencyProperty EndColorVariationProperty =
        DependencyProperty.Register(
            "EndColorVariation",
            typeof (Color),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(Color.FromArgb(50, 20, 100, 20))
            );

    public static readonly DependencyProperty MouseDropDelayProperty =
        DependencyProperty.Register(
            "MouseDropDelay",
            typeof (double),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(0.1)
            );

    public static readonly DependencyProperty MouseDropDelayVariationProperty =
        DependencyProperty.Register(
            "MouseDropDelayVariation",
            typeof (double),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(0.05)
            );

    public static readonly DependencyProperty ClickBurstSizeProperty =
        DependencyProperty.Register(
            "ClickBurstSize",
            typeof (int),
            typeof (FireworkEffect),
            new FrameworkPropertyMetadata(30)
            );

    #endregion

    #region Properties

    public double Radius
    {
        get { return (double) GetValue(RadiusProperty); }
        set { SetValue(RadiusProperty, value); }
    }

    public double RadiusVariation
    {
        get { return (double) GetValue(RadiusVariationProperty); }
        set { SetValue(RadiusVariationProperty, value); }
    }

    public Color StartColor
    {
        get { return (Color) GetValue(StartColorProperty); }
        set { SetValue(StartColorProperty, value); }
    }

    public Color EndColor
    {
        get { return (Color) GetValue(EndColorProperty); }
        set { SetValue(EndColorProperty, value); }
    }

    public Color StartColorVariation
    {
        get { return (Color) GetValue(StartColorVariationProperty); }
        set { SetValue(StartColorVariationProperty, value); }
    }

    public Color EndColorVariation
    {
        get { return (Color) GetValue(EndColorVariationProperty); }
        set { SetValue(EndColorVariationProperty, value); }
    }


    public bool BounceOffContainer { get; set; } = false;

    public bool GravitateToMouse { get; set; } = false;

    public double GravitateScale { get; set; } = 0.1;

    public bool MouseDropsParticles { get; set; } = false;

    public double MouseDropDelay
    {
        get { return (double) GetValue(MouseDropDelayProperty); }
        set { SetValue(MouseDropDelayProperty, value); }
    }

    public double MouseDropDelayVariation
    {
        get { return (double) GetValue(MouseDropDelayVariationProperty); }
        set { SetValue(MouseDropDelayVariationProperty, value); }
    }

    public int ClickBurstSize
    {
        get { return (int) GetValue(ClickBurstSizeProperty); }
        set { SetValue(ClickBurstSizeProperty, value); }
    }

    #endregion
}

SonicEffect:

//uielement
//visual operations

public class SonicEffect : OverlayRenderDecorator
{
    protected override void OnAttachChild(UIElement child)
    {
        child.PreviewMouseLeftButtonUp += OnMouseLeftButtonUp;
    }

    protected override void OnDetachChild(UIElement child)
    {
        CompositionTarget.Rendering -= OnFrameCallback;

        child.PreviewMouseLeftButtonUp -= OnMouseLeftButtonUp;
        _timeTracker = null;
    }

    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (_timeTracker != null)
        {
            _timeTracker.TimerFired -= OnTimerFired;
            _timeTracker = null;
        }

        CompositionTarget.Rendering += OnFrameCallback;
        _timeTracker = new ParticleEffectsTimeTracker {TimerInterval = _ringDelayInSeconds};
        _timeTracker.TimerFired += OnTimerFired;
        _lowerRing = _upperRing = 0;
        _clickPosition = e.GetPosition(this);
    }

    private void OnFrameCallback(object sender, EventArgs e)
    {
        if (_timeTracker != null)
        {
            _timeTracker.Update();
            InvalidateVisual();
        }
    }

    private void OnTimerFired(object sender, EventArgs e)
    {
        if (_upperRing < RingCount)
        {
            _upperRing++;
        }
        else
        {
            _lowerRing++;
            if (_lowerRing >= _upperRing)
            {
                _timeTracker.TimerFired -= OnTimerFired;
                _timeTracker = null;
                CompositionTarget.Rendering -= OnFrameCallback;
            }
        }
    }

    protected override void OnOverlayRender(DrawingContext dc)
    {
        if (_timeTracker != null)
        {
            for (var i = _lowerRing; i < _upperRing; i++)
            {
                var radius = RingRadius*(i + 1);
                dc.DrawEllipse(Brushes.Transparent, new Pen(new SolidColorBrush(RingColor), RingThickness),
                    _clickPosition, radius, radius);
            }
        }
    }

    #region Private Members

    private ParticleEffectsTimeTracker _timeTracker;
    private double _ringDelayInSeconds = 3;
    private Point _clickPosition;
    private int _lowerRing, _upperRing;

    #endregion

    #region Properties

    public int RingCount { get; set; } = 5;

    public TimeSpan RingDelay
    {
        get { return TimeSpan.FromSeconds(_ringDelayInSeconds); }
        set { _ringDelayInSeconds = value.TotalSeconds; }
    }

    public double RingRadius { get; set; } = 7.0;

    public double RingThickness { get; set; } = 5.0;

    public Color RingColor { get; set; } = Color.FromArgb(128, 128, 128, 128);

    #endregion
}

MagnitismCanvas:

public class MagnitismCanvas : Canvas
{
    public MagnitismCanvas()
    {
        // suppress movement in the visual studio designer.
        if (Process.GetCurrentProcess().ProcessName != "devenv")
            CompositionTarget.Rendering += UpdateChildren;
        _timeTracker = new ParticleEffectsTimeTracker();
    }

    private void UpdateChildren(object sender, EventArgs e)
    {
        //update time delta
        _timeTracker.Update();

        foreach (UIElement child in LogicalTreeHelper.GetChildren(this))
        {
            var velocity = _childrenVelocities.ContainsKey(child) ? _childrenVelocities[child] : new Vector(0, 0);

            //compute velocity dampening
            velocity = velocity*Drag;

            var truePosition = GetTruePosition(child);
            var childRect = new Rect(truePosition, child.RenderSize);


            //accumulate forces
            var forces = new Vector(0, 0);

            //add wall repulsion
            forces.X += BorderForce/Math.Max(1, childRect.Left);
            forces.X -= BorderForce/Math.Max(1, RenderSize.Width - childRect.Right);
            forces.Y += BorderForce/Math.Max(1, childRect.Top);
            forces.Y -= BorderForce/Math.Max(1, RenderSize.Height - childRect.Bottom);

            //each other child pushes away based on the square distance
            foreach (UIElement otherchild in LogicalTreeHelper.GetChildren(this))
            {
                //dont push against itself
                if (otherchild == child)
                    continue;

                var otherchildtruePosition = GetTruePosition(otherchild);
                var otherchildRect = new Rect(otherchildtruePosition, otherchild.RenderSize);

                //make sure rects aren't the same
                if (otherchildRect == childRect)
                    continue;

                //ignore children with a size of 0,0
                if (otherchildRect.Width == 0 && otherchildRect.Height == 0 ||
                    childRect.Width == 0 && childRect.Height == 0)
                    continue;

                //vector from current other child to current child
                //are they overlapping?  if so, distance is 0
                var toChild = AreRectsOverlapping(childRect, otherchildRect)
                    ? new Vector(0, 0)
                    : VectorBetweenRects(childRect, otherchildRect);

                var length = toChild.Length;
                if (length < 1)
                {
                    length = 1;
                    var childCenter = GetCenter(childRect);
                    var otherchildCenter = GetCenter(otherchildRect);
                    //compute toChild from the center of both rects
                    toChild = childCenter - otherchildCenter;
                }

                var childpush = ChildForce/length;

                toChild.Normalize();
                forces += toChild*childpush;
            }

            //add forces to velocity and store it for next iteration
            velocity += forces;
            _childrenVelocities[child] = velocity;

            //move the object based on it's velocity
            SetTruePosition(child, truePosition + _timeTracker.DeltaSeconds*velocity);
        }
    }

    private bool AreRectsOverlapping(Rect r1, Rect r2)
    {
        if (r1.Bottom < r2.Top) return false;
        if (r1.Top > r2.Bottom) return false;

        if (r1.Right < r2.Left) return false;
        if (r1.Left > r2.Right) return false;

        return true;
    }

    private Point IntersectInsideRect(Rect r, Point raystart, Vector raydir)
    {
        var xtop = raystart.X + raydir.X*(r.Top - raystart.Y)/raydir.Y;
        var xbottom = raystart.X + raydir.X*(r.Bottom - raystart.Y)/raydir.Y;
        var yleft = raystart.Y + raydir.Y*(r.Left - raystart.X)/raydir.X;
        var yright = raystart.Y + raydir.Y*(r.Right - raystart.X)/raydir.X;
        var top = new Point(xtop, r.Top);
        var bottom = new Point(xbottom, r.Bottom);
        var left = new Point(r.Left, yleft);
        var right = new Point(r.Right, yright);
        var tv = raystart - top;
        var bv = raystart - bottom;
        var lv = raystart - left;
        var rv = raystart - right;
        //classify ray direction
        if (raydir.Y < 0)
        {
            if (raydir.X < 0) //top left
            {
                if (tv.LengthSquared < lv.LengthSquared)
                    return top;
                return left;
            }
            if (tv.LengthSquared < rv.LengthSquared)
                return top;
            return right;
        }
        if (raydir.X < 0) //bottom left
        {
            if (bv.LengthSquared < lv.LengthSquared)
                return bottom;
            return left;
        }
        if (bv.LengthSquared < rv.LengthSquared)
            return bottom;
        return right;
    }

    private Vector VectorBetweenRects(Rect r1, Rect r2)
    {
        //find the edge points and use these to measure the distance
        var r1Center = GetCenter(r1);
        var r2Center = GetCenter(r2);
        var between = (r1Center - r2Center);
        between.Normalize();
        var edge1 = IntersectInsideRect(r1, r1Center, -between);
        var edge2 = IntersectInsideRect(r2, r2Center, between);
        return edge1 - edge2;
    }

    private Point GetRenderTransformOffset(UIElement e)
    {
        //make sure they object's render transform is a translation
        var renderTranslation = e.RenderTransform as TranslateTransform;
        if (renderTranslation == null)
        {
            renderTranslation = new TranslateTransform(0, 0);
            e.RenderTransform = renderTranslation;
        }

        return new Point(renderTranslation.X, renderTranslation.Y);
    }

    private void SetRenderTransformOffset(UIElement e, Point offset)
    {
        //make sure they object's render transform is a translation
        var renderTranslation = e.RenderTransform as TranslateTransform;
        if (renderTranslation == null)
        {
            renderTranslation = new TranslateTransform(0, 0);
            e.RenderTransform = renderTranslation;
        }

        //set new offset
        renderTranslation.X = offset.X;
        renderTranslation.Y = offset.Y;
    }

    private Point GetTruePosition(UIElement e)
    {
        var renderTranslation = GetRenderTransformOffset(e);
        return new Point(GetLeft(e) + renderTranslation.X, GetTop(e) + renderTranslation.Y);
    }

    private void SetTruePosition(UIElement e, Point p)
    {
        var canvasOffset = new Vector(GetLeft(e), GetTop(e));
        var renderTranslation = p - canvasOffset;

        SetRenderTransformOffset(e, renderTranslation);
    }

    private Point GetCenter(Rect r) => new Point((r.Left + r.Right) / 2.0, (r.Top + r.Bottom) / 2.0);

    #region Private Members

    private readonly ParticleEffectsTimeTracker _timeTracker;
    private readonly Dictionary<UIElement, Vector> _childrenVelocities = new Dictionary<UIElement, Vector>();

    #endregion

    #region Properties

    public double BorderForce { get; set; } = 1000.0;

    public double ChildForce { get; set; } = 200.0;

    public double Drag { get; set; } = 0.9;

    #endregion
}

ParticleEffectsTimeTracker:

//uielement
//visual operations

//animation effect stuff

public class ParticleEffectsTimeTracker
{
    #region Constructors

    public ParticleEffectsTimeTracker()
    {
        ElapsedTime = DateTime.Now;
    }

    #endregion

    #region Events

    public event EventHandler TimerFired;

    #endregion

    public double Update()
    {
        var currentTime = DateTime.Now;


        //get the difference in time
        var diffTime = currentTime - ElapsedTime;
        DeltaSeconds = diffTime.TotalSeconds;


        //does the user want a callback on regular intervals?
        if (TimerInterval > 0.0)
        {
            /*
                //compute the intervals for this and previous update
                int currInterval = (int)(currentTime / TimeSpan.FromSeconds(_timerInterval));
                int prevInterval = (int)(_lastTime / TimeSpan.FromSeconds(_timerInterval));

                //has the interval changed since last update?
                if (currInterval != prevInterval)
                {
                    //fire interval event
                    //note that this will only be called once per frame at most
                    // so if they interval is too small, you wont get 2+ fires per frame
                    TimerFired(this, null);
                } */

            if (currentTime != ElapsedTime)
            {
                TimerFired?.Invoke(this, null);
            }
        }


        //cycle old time
        ElapsedTime = currentTime;

        return DeltaSeconds;
    }

    #region Private Memebers

    #endregion

    #region Properties

    public double TimerInterval { get; set; } = -1;

    public DateTime ElapsedTime { get; private set; }

    public double DeltaSeconds { get; private set; }

    #endregion
}

Particle:

internal class Particle
{
    public DateTime DeathTime;
    public double Diameter;
    public Color EndColor;
    public DateTime LifeTime;
    public Point Location;
    public Color StartColor;
    public Vector Velocity;
}

代码解析:

  1. OverlayRenderDecoratorOverlayVisual属性IsHitTestVisible 为什么设置为False?

    1. 当为True时,点击一次出现微粒爆发,此时再点击时会执行微粒上的点击命中并阻止进行下一层visual命中测试,因此按钮不会触发点击事件。
    2. 当为false时,微粒visual层不触发命中测试,直接进行下一层按钮的命中测试,因此会执行按钮点击事件,并会再次爆发出微粒。

3.此属性受继承类的IsOverlayHitTestVisible属性关联开关管理。

后续补充详细---


李志玮
22 声望34 粉丝

求索~~


引用和评论

0 条评论