lixiaojun
2025-03-21 d03e8c5d165426f820c10e0c876e5c6af14d270b
完善构件拾取
已删除4个文件
已重命名10个文件
已修改2个文件
已添加7个文件
1431 ■■■■■ 文件已修改
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/BoundingBox3.cs 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/BoundingBox3CacheHelper.cs 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/BoundingBox3Helper.cs 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Draw2dHelper.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Network2dExtensions.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/OrthoCamera2dHelper.cs 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/OrthoTransformHelper.cs 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Ray3.cs 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Ray32dCaster.cs 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Vector3CacheHelper.cs 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/ViewPort3.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-control/OrthoDrawer2d.Designer.cs 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-control/OrthoDrawer2d.cs 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-control/OrthoDrawer2d.resx 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox32dCacheHelper.cs 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Ray3.cs 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Ray3Caster.cs 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Vector32dCacheHelper.cs 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/Yw.WinFrmUI.Hydro.L3d.Core.csproj.user 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Test.Core/TestForm.Designer.cs 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Test.Core/TestForm.cs 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Test.Core/TestForm.resx 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Test.Core/Yw.WinFrmUI.Test.Core.csproj.user 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/BoundingBox3.cs
ÎļþÃû´Ó Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox3.cs ÐÞ¸Ä
@@ -10,6 +10,11 @@
        /// <summary>
        /// 
        /// </summary>
        public BoundingBox3() { }
        /// <summary>
        ///
        /// </summary>
        public BoundingBox3(Vector3 min, Vector3 max)
        {
            this.Min = min;
@@ -44,34 +49,47 @@
        public Vector3 Max { get; set; }
        /// <summary>
        /// èŽ·å–ä¸­å¿ƒ
        /// ä¸­å¿ƒç‚¹
        /// </summary>
        public Vector3 GetCenter()
        {
            return new Vector3()
            {
                X = (this.Max.X + this.Min.X) / 2f,
                Y = (this.Max.Y + this.Min.Y) / 2f,
                Z = (this.Max.Z + this.Min.Z) / 2f
            };
        }
        public Vector3 Center => (Min + Max) / 2f;
        /// <summary>
        /// èŽ·å–è§’
        /// å°ºå¯¸
        /// </summary>
        public List<Vector3> GetCorners()
        public Vector3 Size => Max - Min;
        /// <summary>
        /// æ˜¯å¦åŒ…含
        /// </summary>
        public bool Contains(Vector3 pt)
        {
            return new List<Vector3>
            if (pt.X > this.Max.X || pt.X < this.Min.X)
            {
                new Vector3(this.Min.X, this.Min.Y, this.Min.Z),
                new Vector3(this.Max.X, this.Min.Y, this.Min.Z),
                new Vector3(this.Min.X, this.Max.Y, this.Min.Z),
                new Vector3(this.Max.X, this.Max.Y, this.Min.Z),
                new Vector3(this.Min.X, this.Min.Y, this.Max.Z),
                new Vector3(this.Max.X, this.Min.Y, this.Max.Z),
                new Vector3(this.Min.X, this.Max.Y, this.Max.Z),
                new Vector3(this.Max.X, this.Max.Y, this.Max.Z)
            };
                return false;
            }
            if (pt.Y > this.Max.Y || pt.Y < this.Min.Y)
            {
                return false;
            }
            if (pt.Z > this.Max.Z || pt.Z < this.Min.Z)
            {
                return false;
            }
            return true;
        }
        /// <summary>
        /// è®¡ç®—
        /// </summary>
        public static BoundingBox3 Calculate(List<Vector3> pts)
        {
            if (pts == null || pts.Count < 1)
            {
                return default;
            }
            return new BoundingBox3(pts);
        }
        /// <summary>
@@ -85,17 +103,10 @@
            );
        }
        /// <summary>
        /// è®¡ç®—
        /// </summary>
        public static BoundingBox3 Calculate(List<Vector3> pts)
        {
            if (pts == null || pts.Count < 1)
            {
                return default;
            }
            return new BoundingBox3(pts);
        }
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/BoundingBox3CacheHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// åŒ…围盒缓存辅助类
    /// </summary>
    internal class BoundingBox3CacheHelper
    {
        /// <summary>
        ///
        /// </summary>
        public BoundingBox3CacheHelper(NetworkL3d nw, Vector3CacheHelper vc)
        {
            _nw = nw;
            _vc = vc;
            Initial();
        }
        private NetworkL3d _nw = null;
        private Vector3CacheHelper _vc = null;
        private Dictionary<string, BoundingBox3> _dict = new();
        /// <summary>
        /// æ˜¯å¦åˆå§‹åŒ–
        /// </summary>
        public bool Initialized
        {
            get
            {
                if (_nw == null)
                {
                    return false;
                }
                if (_vc == null)
                {
                    return false;
                }
                if (_dict.Count < 1)
                {
                    return false;
                }
                return true;
            }
        }
        //初始化
        private void Initial()
        {
            if (_nw == null)
            {
                return;
            }
            if (_vc == null)
            {
                return;
            }
            _dict.Clear();
            _nw.Nodes.ForEach(x =>
            {
                var pts = _vc.GetById(x.Id);
                if (pts != null && pts.Count > 0)
                {
                    var bx = BoundingBox3Helper.CalcuPointBoundingBox(pts[0], x.Style2d.Normal.Radiu);
                    _dict.Add(x.Id, bx);
                }
            });
            _nw.Links.ForEach((Action<LinkL3d>)(x =>
            {
                var pts = _vc.GetById(x.Id);
                if (pts != null && pts.Count > 1)
                {
                    var bx = BoundingBox3Helper.CalcuLineBoundingBox(pts[0], pts[1], x.Style2d.Normal.Width);
                    _dict.Add(x.Id, bx);
                }
            }));
        }
        /// <summary>
        /// é€šè¿‡ Id èŽ·å–
        /// </summary>
        public BoundingBox3 GetById(string Id)
        {
            if (!Initialized)
            {
                return default;
            }
            if (_dict.ContainsKey(Id))
            {
                return _dict[Id];
            }
            return default;
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/BoundingBox3Helper.cs
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Draw2dHelper.cs
ÎļþÃû´Ó Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Draw2dHelper.cs ÐÞ¸Ä
@@ -8,6 +8,26 @@
    /// </summary>
    internal class Draw2dHelper
    {
        /// <summary>
        /// ç»˜åˆ¶èŠ‚ç‚¹
        /// </summary>
        public static void DrawNode(NodeL3d node)
        {
            var size = node.Style2d.Normal.Radiu * 2;
            var color = node.Style2d.Normal.Color;
            if (node.IsSelected)
            {
                size = node.Style2d.Selected.Radiu * 2;
                color = node.Style2d.Selected.Color;
            }
            if (node.IsHovered)
            {
                size = node.Style2d.Hovered.Radiu * 2;
                color = node.Style2d.Hovered.Color;
            }
            DrawPoint(size, color, node.Position);
        }
        /// <summary>
        /// ç»˜åˆ¶ç‚¹
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Network2dExtensions.cs
ÎļþÃû´Ó Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Network2dExtensions.cs ÐÞ¸Ä
@@ -1,4 +1,7 @@
namespace Yw.WinFrmUI.Hydro
using OpenTK.Mathematics;
using System.Windows.Forms;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// 
@@ -56,10 +59,10 @@
        /// <summary>
        /// æ‚¬åœ2d
        /// </summary>
        public static void Hover2d(this NetworkL3d nw, Ray3 ray, BoundingBox32dCacheHelper bxcache)
        public static void Hover2d(this NetworkL3d nw, Ray3 ray, float zoom, Vector3CacheHelper vecache)
        {
            nw.Visuals.ForEach(x => x.IsHovered = false);
            var visual = ray.CastingClosest2(nw, bxcache);
            var visual = ray.CastingClosest(zoom, nw, vecache);
            if (visual != null)
            {
                visual.IsHovered = true;
@@ -69,10 +72,10 @@
        /// <summary>
        /// é€‰æ‹©2d
        /// </summary>
        public static void Select2d(this NetworkL3d nw, Ray3 ray, BoundingBox32dCacheHelper bxcache)
        public static void Select2d(this NetworkL3d nw, Ray3 ray, float zoom, Vector3CacheHelper vecache)
        {
            nw.Visuals.ForEach(x => x.IsSelected = false);
            var visual = ray.CastingClosest2(nw, bxcache);
            var visual = ray.CastingClosest(zoom, nw, vecache);
            if (visual != null)
            {
                visual.IsSelected = true;
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/OrthoCamera2dHelper.cs
ÎļþÃû´Ó Yw.WinFrmUI.Hydro.L3d.Core/03-helper/OrthoCamera2dHelper.cs ÐÞ¸Ä
@@ -24,10 +24,9 @@
        {
            _gl = gl;
            _nw = nw;
            _veccache = new Vector32dCacheHelper(nw);
            var pts = _veccache.GetPositions();
            _veccache = new Vector3CacheHelper(nw);
            var pts = _veccache.GetAll();
            _bx = new BoundingBox3(pts);
            _bxcache = new BoundingBox32dCacheHelper(nw, _veccache);
            _mc = (_bx.Max + _bx.Min) * 0.5f;
            _radius = (_bx.Max - _bx.Min).Length / 2f;
            UpdateModelMatrix();
@@ -38,9 +37,8 @@
        // åŸºç¡€å‚æ•°
        private GLControl _gl = null;//gl控件
        private NetworkL3d _nw = null;//管网模型
        private Vector32dCacheHelper _veccache = null;//位置辅助类
        private Vector3CacheHelper _veccache = null;//位置辅助类
        private BoundingBox3 _bx = null;//包围盒
        private BoundingBox32dCacheHelper _bxcache = null;//包围盒缓存
        private Vector3 _mc; //模型中心
        private float _radius = 100f;//包围球半径
        private float _pw = 10f;//投影宽度
@@ -184,6 +182,7 @@
                return;
            }
            this.Zoom *= e.Delta > 0 ? 0.9f : 1.1f;
            UpdateModelMatrix();
            UpdateViewMatrix();
            UpdateProjectionMatrix();
            _gl.Invalidate();
@@ -256,7 +255,9 @@
                // æ›´æ–°å››å…ƒæ•°
                _rotation = rotationY * rotationX * _rotation;
                _rotation = Quaternion.Normalize(_rotation);
                UpdateModelMatrix();
                UpdateViewMatrix();
                UpdateProjectionMatrix();
            }
            else if (_isDragging)
            {
@@ -265,22 +266,41 @@
                _translation.X += dx * xratio;
                _translation.Y -= dy * yratio;
                UpdateModelMatrix();
                UpdateViewMatrix();
                UpdateProjectionMatrix();
            }
            else
            //var depth = OrthoTransformHelper.GetScreenPointDepth(e.X, e.Y, _gl.Height);
            //var pos = OrthoTransformHelper.ScreenToWorld(e.X, e.Y, depth, _viewMatrix * _modelMatrix, _projMatrix, _gl.Width, _gl.Height);
            //_nw.Hover2d(pos, this.Zoom, _veccache);
            //var ray = OrthoTransformHelper.CreateRay(e.X, e.Y, depth, _forward, _modelMatrix, _viewMatrix, _projMatrix, _gl.Width, _gl.Height);
            var ray = OrthoTransformHelper.CreateRay2(e.X, e.Y, _modelMatrix, _viewMatrix, _projMatrix, _gl.Width, _gl.Height);
            if (ray != null)
            {
                var depth = OrthoTransformHelper.GetScreenPointDepth(e.X, e.Y, _gl.Height);
                var ray = OrthoTransformHelper.CreateRay(e.X, e.Y, depth, _forward, _modelMatrix, _viewMatrix, _projMatrix, _gl.Width, _gl.Height);
                //var ray = OrthoTransformHelper.CreateRay2(e.X, e.Y, _modelMatrix, _viewMatrix, _projMatrix, _gl.Width, _gl.Height);
                if (ray != null)
                {
                    _nw.Hover2d(ray, _bxcache);
                }
                _nw.Hover2d(ray, this.Zoom, _veccache);
                //_gl.Invalidate();
            }
            _lastMousePos = e.Location;
            _gl.Invalidate();
        }
        public void HandleMouseHover()
        {
            if (!Initialized)
            {
                return;
            }
            //var depth = OrthoTransformHelper.GetScreenPointDepth(e.X, e.Y, _gl.Height);
            //var pos = OrthoTransformHelper.ScreenToWorld(e.X, e.Y, depth, _viewMatrix * _modelMatrix, _projMatrix, _gl.Width, _gl.Height);
            //_nw.Hover2d(pos, this.Zoom, _veccache);
            //var ray = OrthoTransformHelper.CreateRay(e.X, e.Y, depth, _forward, _modelMatrix, _viewMatrix, _projMatrix, _gl.Width, _gl.Height);
            //var ray = OrthoTransformHelper.CreateRay2(_lastMousePos.X, _lastMousePos.Y, _modelMatrix, _viewMatrix, _projMatrix, _gl.Width, _gl.Height);
            //if (ray != null)
            //{
            //    _nw.Hover2d(ray, this.Zoom, _veccache);
            //    _gl.Invalidate();
            //}
        }
        /// <summary>
        /// å¤„理鼠标点击
        /// </summary>
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/OrthoTransformHelper.cs
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Ray3.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
using OpenTK.Mathematics;
using System.Windows.Forms;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// å°„线
    /// </summary>
    internal class Ray3
    {
        /// <summary>
        ///
        /// </summary>
        public Ray3() { }
        /// <summary>
        ///
        /// </summary>
        public Ray3(Vector3 origin, Vector3 direction)
        {
            Origin = origin;
            Direction = direction.Normalized();//确保归一化
        }
        /// <summary>
        /// åŽŸç‚¹
        /// </summary>
        public Vector3 Origin { get; set; }
        /// <summary>
        /// æ–¹å‘
        /// </summary>
        public Vector3 Direction { get; set; }
        /// <summary>
        /// æ£€æµ‹å°„线是否与点相交(基于容差)
        /// </summary>
        /// <param name="point">目标点</param>
        /// <param name="epsilon">容差阈值(点与射线的最大允许距离 é»˜è®¤ 0.01f)</param>
        /// <param name="maxDistance">最大有效距离(射线方向上的范围 é»˜è®¤ Float.MaxValue)</param>
        /// <param name="distance">输出相交距离</param>
        /// <returns>是否相交</returns>
        public bool IntersectsPoint
            (
                Vector3 point,
                float epsilon,
                float maxDistance,
                out float distance
            )
        {
            // è®¡ç®—从起点到目标点的向量
            Vector3 originToPoint = point - Origin;
            // è®¡ç®—点在射线方向上的投影长度(参数t)
            float t = Vector3.Dot(originToPoint, Direction);
            // å¦‚æžœt为负数或超过最大距离,说明点在射线反方向或超出范围
            if (t < 0 || t > maxDistance)
            {
                distance = float.NaN;
                return false;
            }
            // è®¡ç®—投影点位置
            Vector3 projectedPoint = Origin + Direction * t;
            // è®¡ç®—实际点与投影点的距离(最短距离)
            float pointDistance = Vector3.Distance(point, projectedPoint);
            // åˆ¤æ–­æ˜¯å¦åœ¨å®¹å·®èŒƒå›´å†…
            if (pointDistance <= epsilon)
            {
                distance = t; // è¿”回射线起点到投影点的距离(若Direction是单位向量,t即实际距离)
                return true;
            }
            distance = float.NaN;
            return false;
        }
        /// <summary>
        /// æ£€æµ‹å°„线是否与线段相交
        /// </summary>
        /// <param name="start">线段开始点</param>
        /// <param name="end">线段结束点</param>
        /// <param name="epsilon">误差(包含线段的半径)</param>
        /// <param name="maxDistance">射线方向的最大有效距离</param>
        /// <param name="distance">输出射线起点到交点的距离</param>
        /// <returns>是否相交</returns>
        public bool IntersectsLine
            (
                Vector3 segStart,
                Vector3 segEnd,
                float epsilon,
                float maxDistance,
                out float distance
            )
        {
            distance = float.MaxValue;
            Vector3 ab = segEnd - segStart;
            Vector3 ao = Origin - segStart;
            Vector3 n = Vector3.Cross(ab, Direction);
            float denom = n.Length * n.Length;
            if (Math.Abs(denom) < 1e-8)
            {
                return false;
            }
            Vector3 q = Vector3.Cross(ao, Direction);
            float s = Vector3.Dot(q, n) / denom;
            if (s < 0 || s > 1)
            {
                return false;
            }
            Vector3 closestPointOnSegment = segStart + Vector3.Multiply(ab, s);
            Vector3 v = closestPointOnSegment - Origin;
            float t = Vector3.Dot(v, Direction);
            if (t < 0)
            {
                return false;
            }
            Vector3 intersectionPoint = Origin + Vector3.Multiply(Direction, t);
            double distanceToSegment = (intersectionPoint - closestPointOnSegment).Length;
            if (distanceToSegment > epsilon)
            {
                return false;
            }
            distance = t;
            return true;
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Ray32dCaster.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,167 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// å°„线碰撞
    /// </summary>
    internal static class Ray32dCaster
    {
        /// <summary>
        /// æŠ•å°„
        /// </summary>
        /// <param name="ray">射线</param>
        /// <param name="zoom">缩放</param>
        /// <param name="visuals">可见构件列表</param>
        /// <param name="vecache">点缓存</param>
        public static List<VisualL3d> Casting(this Ray3 ray, float zoom, List<VisualL3d> visuals, Vector3CacheHelper vecache)
        {
            if (ray == null)
            {
                return default;
            }
            if (visuals == null || visuals.Count < 1)
            {
                return default;
            }
            if (vecache == null)
            {
                return default;
            }
            var list = new List<VisualL3d>();
            foreach (var visual in visuals)
            {
                if (visual is NodeL3d node)
                {
                    var vec = vecache.GetById(node.Id);
                    var epsilon = node.Style2d.Normal.Radiu * zoom;
                    if (ray.IntersectsPoint(vec[0], epsilon, float.MaxValue, out _))
                    {
                        list.Add(visual);
                    }
                }
                else if (visual is LinkL3d link)
                {
                    var vec = vecache.GetById(link.Id);
                    var epsilon = link.Style2d.Normal.Width / 2f * zoom;
                    if (ray.IntersectsLine(vec[0], vec[1], epsilon, float.MaxValue, out _))
                    {
                        list.Add(visual);
                    }
                }
            }
            return list;
        }
        /// <summary>
        /// æŠ•射最近节点
        /// </summary>
        /// <param name="ray">射线</param>
        /// <param name="zoom">缩放</param>
        /// <param name="nodes">节点</param>
        /// <param name="vecache">点缓存</param>
        /// <returns></returns>
        public static NodeL3d CastingClosest(this Ray3 ray, float zoom, List<NodeL3d> nodes, Vector3CacheHelper vecache)
        {
            if (ray == null)
            {
                return default;
            }
            if (nodes == null || nodes.Count < 1)
            {
                return default;
            }
            if (vecache == null)
            {
                return default;
            }
            NodeL3d closest = null;
            float minDistance = float.MaxValue;
            foreach (var node in nodes)
            {
                var vec = vecache.GetById(node.Id);
                var epsilon = node.Style2d.Normal.Radiu * zoom;
                if (ray.IntersectsPoint(vec[0], epsilon, float.MaxValue, out float distance))
                {
                    if (distance <= minDistance)
                    {
                        minDistance = distance;
                        closest = node;
                    }
                }
            }
            return closest;
        }
        /// <summary>
        /// æŠ•射最近管段
        /// </summary>
        /// <param name="ray">射线</param>
        /// <param name="links">管段</param>
        /// <param name="vecache">点缓存</param>
        /// <returns></returns>
        public static LinkL3d CastingClosest(this Ray3 ray, float zoom, List<LinkL3d> links, Vector3CacheHelper vecache)
        {
            if (ray == null)
            {
                return default;
            }
            if (links == null || links.Count < 1)
            {
                return default;
            }
            if (vecache == null)
            {
                return default;
            }
            LinkL3d closest = null;
            float minDistance = float.MaxValue;
            foreach (var link in links)
            {
                var vec = vecache.GetById(link.Id);
                var epsilon = link.Style2d.Normal.Width * zoom;
                if (ray.IntersectsLine(vec[0], vec[1], epsilon, float.MaxValue, out float distance))
                {
                    if (distance <= minDistance)
                    {
                        minDistance = distance;
                        closest = link;
                    }
                }
            }
            return closest;
        }
        /// <summary>
        /// æŠ•射最近构件
        /// </summary>
        /// <param name="ray">射线</param>
        /// <param name="nw">管网</param>
        /// <param name="vecache">点缓存</param>
        /// <returns></returns>
        public static VisualL3d CastingClosest(this Ray3 ray, float zoom, NetworkL3d nw, Vector3CacheHelper vecache)
        {
            VisualL3d visual = CastingClosest(ray, zoom, nw?.Nodes, vecache);
            if (visual == null)
            {
                visual = CastingClosest(ray, zoom, nw?.Links, vecache);
            }
            return visual;
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/Vector3CacheHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
using OpenTK.Mathematics;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// ä¸‰ç»´ç‚¹ç¼“存辅助类
    /// </summary>
    internal class Vector3CacheHelper
    {
        /// <summary>
        ///
        /// </summary>
        public Vector3CacheHelper(NetworkL3d nw)
        {
            _nw = nw;
            Initial();
        }
        private NetworkL3d _nw = null;
        private Dictionary<string, List<Vector3>> _dict = new();
        /// <summary>
        /// æ˜¯å¦åˆå§‹åŒ–
        /// </summary>
        public bool Initialized
        {
            get
            {
                if (_nw == null)
                {
                    return false;
                }
                if (_dict.Count < 1)
                {
                    return false;
                }
                return true;
            }
        }
        //初始化
        private void Initial()
        {
            if (_nw == null)
            {
                return;
            }
            foreach (var visual in _nw.Visuals)
            {
                var pts = visual.GetPositions();
                var vpts = pts.Select(x => new Vector3(x.X, x.Y, x.Z)).ToList();
                _dict.Add(visual.Id, vpts);
            }
        }
        /// <summary>
        /// èŽ·å–æ‰€æœ‰
        /// </summary>
        /// <returns></returns>
        public List<Vector3> GetAll()
        {
            if (!Initialized)
            {
                return default;
            }
            return _dict.SelectMany(x => x.Value).Distinct().ToList();
        }
        /// <summary>
        /// é€šè¿‡ Id èŽ·å–
        /// </summary>
        public List<Vector3> GetById(string Id)
        {
            if (string.IsNullOrEmpty(Id))
            {
                return default;
            }
            if (_dict.ContainsKey(Id))
            {
                return _dict[Id];
            }
            return default;
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/02-helper/ViewPort3.cs
ÎļþÃû´Ó Yw.WinFrmUI.Hydro.L3d.Core/03-helper/ViewPort3.cs ÐÞ¸Ä
@@ -24,22 +24,22 @@
        /// <summary>
        /// x
        /// </summary>
        public float X { get; set; }
        public int X { get; set; }
        /// <summary>
        /// y
        /// </summary>
        public float Y { get; set; }
        public int Y { get; set; }
        /// <summary>
        /// å®½åº¦
        /// </summary>
        public float Width { get; set; }
        public int Width { get; set; }
        /// <summary>
        /// é«˜åº¦
        /// </summary>
        public float Height { get; set; }
        public int Height { get; set; }
    }
Yw.WinFrmUI.Hydro.L3d.Core/03-control/OrthoDrawer2d.Designer.cs
Yw.WinFrmUI.Hydro.L3d.Core/03-control/OrthoDrawer2d.cs
ÎļþÃû´Ó Yw.WinFrmUI.Hydro.L3d.Core/04-control/OrthoDrawer2d.cs ÐÞ¸Ä
@@ -26,8 +26,11 @@
            this.glControl1.MouseUp += OnMouseUp;
            this.glControl1.MouseMove += OnMouseMove;
            this.glControl1.MouseClick += OnMouseClick;
            this.glControl1.MouseDoubleClick += GlControl1_MouseDoubleClick;
            this.glControl1.MouseDoubleClick += OnMouseDoubleClick;
            this.glControl1.MouseHover += GlControl1_MouseHover;
        }
        private NetworkL3d _nw = null;
        private OrthoCamera2dHelper _orthoHelper = null;
@@ -57,9 +60,10 @@
            GL.ShadeModel(ShadingModel.Smooth);
            GL.Enable(EnableCap.DepthTest);//深度测试
            GL.DepthMask(true); // å…è®¸å†™å…¥æ·±åº¦ç¼“冲区
            GL.DepthMask(true);
            GL.Enable(EnableCap.PointSmooth);//启用点平滑
            GL.Enable(EnableCap.LineSmooth);//启用线平滑
            GL.DepthFunc(DepthFunction.Lequal); // é»˜è®¤å€¼ï¼Œå¯å°è¯•改为 LessEqual
            _orthoHelper.HandleResize();
        }
@@ -114,11 +118,16 @@
        }
        //鼠标双击
        private void GlControl1_MouseDoubleClick(object sender, MouseEventArgs e)
        private void OnMouseDoubleClick(object sender, MouseEventArgs e)
        {
            _orthoHelper.HandleMouseDoubleClick(e);
        }
        //鼠标hover
        private void GlControl1_MouseHover(object sender, EventArgs e)
        {
            _orthoHelper?.HandleMouseHover();
        }
    }
Yw.WinFrmUI.Hydro.L3d.Core/03-control/OrthoDrawer2d.resx
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox32dCacheHelper.cs
ÎļþÒÑɾ³ý
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Ray3.cs
ÎļþÒÑɾ³ý
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Ray3Caster.cs
ÎļþÒÑɾ³ý
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Vector32dCacheHelper.cs
ÎļþÒÑɾ³ý
Yw.WinFrmUI.Hydro.L3d.Core/Yw.WinFrmUI.Hydro.L3d.Core.csproj.user
@@ -2,7 +2,7 @@
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup />
  <ItemGroup>
    <Compile Update="04-control\OrthoDrawer2d.cs">
    <Compile Update="03-control\OrthoDrawer2d.cs">
      <SubType>UserControl</SubType>
    </Compile>
  </ItemGroup>
Yw.WinFrmUI.Test.Core/TestForm.Designer.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
namespace Yw.WinFrmUI
{
    partial class TestForm
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            toolStrip1 = new ToolStrip();
            glControl1 = new OpenTK.GLControl.GLControl();
            SuspendLayout();
            //
            // toolStrip1
            //
            toolStrip1.Location = new Point(0, 0);
            toolStrip1.Name = "toolStrip1";
            toolStrip1.Size = new Size(800, 25);
            toolStrip1.TabIndex = 0;
            toolStrip1.Text = "toolStrip1";
            //
            // glControl1
            //
            glControl1.API = OpenTK.Windowing.Common.ContextAPI.OpenGL;
            glControl1.APIVersion = new Version(3, 3, 0, 0);
            glControl1.Dock = DockStyle.Fill;
            glControl1.Flags = OpenTK.Windowing.Common.ContextFlags.Default;
            glControl1.IsEventDriven = true;
            glControl1.Location = new Point(0, 25);
            glControl1.Name = "glControl1";
            glControl1.Profile = OpenTK.Windowing.Common.ContextProfile.Core;
            glControl1.SharedContext = null;
            glControl1.Size = new Size(800, 425);
            glControl1.TabIndex = 1;
            glControl1.Load += glControl1_Load;
            glControl1.MouseClick += glControl1_MouseClick;
            glControl1.MouseDoubleClick += glControl1_MouseDoubleClick;
            glControl1.MouseDown += glControl1_MouseDown;
            glControl1.MouseEnter += glControl1_MouseEnter;
            glControl1.MouseLeave += glControl1_MouseLeave;
            glControl1.MouseHover += glControl1_MouseHover;
            glControl1.MouseMove += glControl1_MouseMove;
            glControl1.MouseUp += glControl1_MouseUp;
            glControl1.Resize += glControl1_Resize;
            //
            // TestForm
            //
            AutoScaleDimensions = new SizeF(7F, 17F);
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(800, 450);
            Controls.Add(glControl1);
            Controls.Add(toolStrip1);
            Name = "TestForm";
            Text = "TestForm";
            ResumeLayout(false);
            PerformLayout();
        }
        #endregion
        private ToolStrip toolStrip1;
        private OpenTK.GLControl.GLControl glControl1;
    }
}
Yw.WinFrmUI.Test.Core/TestForm.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
using OpenTK.Mathematics;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Yw.WinFrmUI
{
    public partial class TestForm : Form
    {
        public TestForm()
        {
            InitializeComponent();
            this.Load += TestForm_Load;
        }
        private void TestForm_Load(object sender, EventArgs e)
        {
        }
        private void glControl1_MouseClick(object sender, MouseEventArgs e)
        {
        }
        private void glControl1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
        }
        private void glControl1_MouseDown(object sender, MouseEventArgs e)
        {
        }
        private void glControl1_MouseHover(object sender, EventArgs e)
        {
        }
        private void glControl1_MouseEnter(object sender, EventArgs e)
        {
        }
        private void glControl1_MouseLeave(object sender, EventArgs e)
        {
        }
        private void glControl1_MouseMove(object sender, MouseEventArgs e)
        {
        }
        private void glControl1_MouseUp(object sender, MouseEventArgs e)
        {
        }
        private void glControl1_Load(object sender, EventArgs e)
        {
        }
        private void glControl1_Resize(object sender, EventArgs e)
        {
        }
    }
}
Yw.WinFrmUI.Test.Core/TestForm.resx
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
  <!--
    Microsoft ResX Schema
    Version 2.0
    The primary goals of this format is to allow a simple XML format
    that is mostly human readable. The generation and parsing of the
    various data types are done through the TypeConverter classes
    associated with the data types.
    Example:
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
    There are any number of "resheader" rows that contain simple
    name/value pairs.
    Each data row contains a name, and value. The row also contains a
    type or mimetype. Type corresponds to a .NET class that support
    text/value conversion through the TypeConverter architecture.
    Classes that don't support this are serialized and stored with the
    mimetype set.
    The mimetype is used for serialized objects, and tells the
    ResXResourceReader how to depersist the object. This is currently not
    extensible. For a given mimetype the value must be set accordingly:
    Note - application/x-microsoft.net.object.binary.base64 is the format
    that the ResXResourceWriter will generate, however the reader can
    read any of the formats listed below.
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.
    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
    <value>17, 17</value>
  </metadata>
</root>
Yw.WinFrmUI.Test.Core/Yw.WinFrmUI.Test.Core.csproj.user
@@ -4,5 +4,8 @@
    <Compile Update="MainForm.cs">
      <SubType>Form</SubType>
    </Compile>
    <Compile Update="TestForm.cs">
      <SubType>Form</SubType>
    </Compile>
  </ItemGroup>
</Project>