Silverlight数学引擎(10)——尺规作图设计

前面的分析被总结为两个字——依赖,我们的设计就从依赖开始。如果将相互依赖的各个元素画成一幅图,这种图就有两种形式,一种是树(像我们前面做的表达式解析),另一种就是网。很明显几何作图各个元素之间的关系是一张网,因此我们很难用一个树状的职能型的组织结构来类比地说明它,或者用人际关系之网来比如更恰当一点。

不少人都会谈网色变,因为即是网就必然存在闭路,我们的网络短路或者程序死循环大多都拜他所赐,所以首先我们就要注意这个问题。举个例子,比如我们屏幕上有两个自由点A和B,以及依赖于它们的线段AB,我们用鼠标可以拖动自由点A,然后线段AB会跟着发生变化。如果我们用鼠标拖动线段,则A也会跟着移动,A的移动又带动着线AB的变化,好,等着翘辫子了。

所以为了防止出现这样的情况,我们就必须严厉禁止鼠标拖动非自由的元素,如线和圆以及交点之类的,如此,相应的接口随之诞生:

复制代码
    public interface IMovable
    {
        void Move(LogicalPoint newPosition);
    }
复制代码

 

我们只允许继承了IMovable的元素才能被鼠标控制,很显然只有FreePointPointOnLine、PointOnCircle有这样的属性,然后我们的鼠标事件只需要这样写就OK了:

复制代码
this.MouseLeftButtonDown
                += (s, e) =>
                       {
                           var item = this.HitTest<Ellipse>(e.GetPosition(null));
                           if (item != null && item.Tag != null && item.Tag is IMovable)
                           {
                               SeletedItem = item.Tag as IMovable;
                           }
                       };
复制代码

有了这一点,我们基本上就从源头把像死循环这样的大bug给办了,就是说我们已经避免了依赖被颠倒的问题,下来就来看看如何处理正常的依赖关系,由于都是坐标系上的图形元素,我们用ICoordinate接口来规范它,首先分析下各个元素共有的一些性质:

  1. 有关联的坐标系,否则逻辑坐标与屏幕坐标无法转化
  2. 有更新显示的行为(UpdateVisual)
  3. 有中心位置(Center)
  4. 有关联的图形界面元素如(Ellipse、Line)
  5. 有依赖(Dependencies)
  6. 有叠放顺序

此外还有颜色、可见性等其他属性,暂时先不考了,按照这些规则定义我们的各个类,为了简化代码,我们还是先定义一个抽象的基类,之后各个具体类从基类扩展即可;此外因为点有很多种且大部分性质相同,我们将其定义为一个抽象类PointShape,具体的点再继承它,由此关系就变得比较清晰:

因为各个元素的共同点是非常多的,所以基本上的功能都在集中在CoordinateBase类中实现,下面主要看看CoordinateBase是如何处理依赖的:

首先是向父元素们注册依赖,比如线AB依赖于A和B,则把AB分别添加到A、B的依赖列表中:

复制代码
        public List<ICoordinate> Dependencies { get; set; }

        public void RegisterDependencies(params ICoordinate[] parents)
        {
            foreach (var parent in parents)
            {
                if (parent.Dependencies == null)
                    parent.Dependencies = new List<ICoordinate>();
                parent.Dependencies.Add(this);
            }
        }
复制代码

再来看看依赖的使用,只发生在UpdateVisual()函数中,任何子类都必须调用基类的该方法,且只能在各自的UpdateVisual()的末尾调用,这样保证了依赖能够按照顺序处理:

复制代码
        public virtual void UpdateVisual()
        {
            if (!Center.Exists())
                Shape.Opacity = 0.1;
            else if (visible)
                Shape.Opacity = Opacity;

            if (Dependencies != null)
                foreach (var dep in Dependencies)
                    dep.UpdateVisual();
        }
复制代码

来看看PointShape的实现就知道了:

复制代码
    public abstract class PointShape : CoordinateBase
    {
        private const double Size = 20;

        public override FrameworkElement CreateShape()
        {
            return new Ellipse { Width = Size, Height = Size, Fill = Fill, Stroke = Brushes.Black, Tag = this };
        }

        public override int ZIndex { get { return (int)MathEngine.ZIndex.FreePoint; } }

        public override void UpdateVisual()
        {
            Shape.CenterTo(Center.ToPhysical(CS).Coordinate);
            base.UpdateVisual();
        }
    }
复制代码

 

具体类的代码实现都是挺简单的,其实我们在分析的时候基本上都实现了,这里只是进行了必要的重构和相关的性能优化。在重构过程中我将各个Demo的代码分开,做到了新的Demo的出现不会覆盖旧的版本,例如本次的Demo就是以一个三角形外接圆示例的实例来展现的,放在单独的静态类中:

复制代码
    //三角形外接圆示例
    public static class TriangleCircumcircleDemo
    {
        public static void Play(CoordinateSystem cs)
        {
            cs.Children.Clear();

            //创建三个自由点A、B、C
            var A = cs.DrawFreePoint(new LogicalPoint(1, 2));
            var B = cs.DrawFreePoint(new LogicalPoint(-2, 3));
            var C = cs.DrawFreePoint(new LogicalPoint(0, 0));

            //创建三角形ABC的三条边
            var AB = cs.DrawLine(A, B);
            var BC = cs.DrawLine(B, C);
            var CA = cs.DrawLine(C, A);

            //创建AB边中垂线
            var O1 = cs.DrawCircle(A, AB);
            var O2 = cs.DrawCircle(B, AB);

            var D = cs.DrawIntersectionPoint(O1, O2, 1);
            var E = cs.DrawIntersectionPoint(O1, O2, 2);

            var DE = cs.DrawLine(D, E);

            //var F = cs.DrawIntersectionPoint(AB, DE);//AB边中点

            //创建BC边中垂线
            var O3 = cs.DrawCircle(B, BC);
            var O4 = cs.DrawCircle(C, BC);

            var G = cs.DrawIntersectionPoint(O3, O4, 1);
            var H = cs.DrawIntersectionPoint(O3, O4, 2);

            var GH = cs.DrawLine(G, H);

            //var I = cs.DrawIntersectionPoint(BC, GH);//BC边中点

            var O = cs.DrawIntersectionPoint(DE, GH);//中垂线交点(外接圆圆心)
            var OA = cs.DrawLine(O, A);//外接圆半径
            var circleABC = cs.DrawCircle(O, OA);//三角形ABC外接圆

            //
            var auxiliaryFigures = new CoordinateBase[] {O1, O2, D, E, DE, O3, O4, G, H, GH, OA};
            foreach (var figure in auxiliaryFigures)
                figure.Opacity = 0.2;
        }
    }
复制代码

从示例中可以看出,除了A、B、C三个点是自由点外,其他的全都是靠依赖产生的,呵呵,三生万物实在不假哦。从三角形的外接圆我有所感触:其实自由是相对的,因为A、B、C名义上是自由点,却始终逃不出自己构造的外接圆,有点意思吧!

【源代码和演示地址】

几何作图的原理就是这些了,除了外接圆,你还可以画出它的内切圆、旁切圆、伪内切圆、九点圆等等等等,总之,可以作茧自缚哈哈!理论结束了下次我们就来点实际有用的吧,如何用鼠标交互地去画图而不是用代码写呢,涉及到界面编程都是有点挑战的,后续章节会给出方案!

posted @ 2012-12-03 20:39  玉减香销  阅读(243)  评论(0编辑  收藏  举报