lixiaojun
2025-03-20 5066a3d90ab19f8380dbb4159d57b0b8e3f17649
L3d基础功能完善
已删除4个文件
已修改9个文件
已添加15个文件
2125 ■■■■ 文件已修改
Yw.WinFrmUI.Hydro.L3d.Core/00-core/PointL3d.cs 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/00-core/NetworkL3d_Method.cs 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/02-visual/VisualL3d.cs 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/02-visual/eVisualState.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/03-node/00-core/NodeL3d.cs 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/04-link/00-core/LinkL3d.cs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/00-core/Style2dL3d.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/00-core/StyleLink2dL3d.cs 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/00-core/StyleNode2dL3d.cs 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/01-point/StylePoint2dL3d.cs 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/02-line/StyleLine2dL3d.cs 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-draw/DrawLine2dHelper.cs 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/02-draw/DrawPoint2dHelper.cs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox3.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox32dCacheHelper.cs 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox3Helper.cs 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Draw2dHelper.cs 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Network2dExtensions.cs 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/OrthoCamera2dHelper.cs 313 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/OrthoCameraHelper.cs 314 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/OrthoTransformHelper.cs 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Ray3.cs 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Ray3Caster.cs 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Vector32dCacheHelper.cs 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/ViewPort3.cs 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/04-control/OrthoDrawer2d.cs 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Test.Core/MainForm.Designer.cs 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Test.Core/MainForm.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Yw.WinFrmUI.Hydro.L3d.Core/00-core/PointL3d.cs
@@ -117,36 +117,37 @@
        }
        /// <summary>
        ///
        /// é•¿åº¦
        /// </summary>
        public float LengthSquared()
        {
            return X * X + Y * Y + Z * Z;
        }
        public float Length => MathF.Sqrt(X * X + Y * Y + Z * Z);
        /// <summary>
        ///
        /// </summary>
        public float Length()
        {
            return (float)Math.Sqrt(LengthSquared());
        }
        /// <summary>
        ///
        /// ç¼©æ”¾åˆ°å•位长度
        /// </summary>
        public void Normalize()
        {
            float length = Length();
            var length = Length;
            if (length > 0)
            {
                X /= length;
                Y /= length;
                Z /= length;
                float num = 1f / Length;
                X *= num;
                Y *= num;
                Z *= num;
            }
        }
        /// <summary>
        /// ç¼©æ”¾åˆ°å•位长度
        /// </summary>
        public PointL3d Normalized()
        {
            var result = this;
            result.Normalize();
            return result;
        }
        /// <summary>
        /// - è¿ç®—
        /// </summary>
        public static PointL3d operator -(PointL3d a, PointL3d b)
Yw.WinFrmUI.Hydro.L3d.Core/01-network/00-core/NetworkL3d_Method.cs
@@ -1,7 +1,4 @@

using OpenTK.Mathematics;
namespace Yw.WinFrmUI.Hydro
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// 
@@ -17,55 +14,6 @@
            return _parters.Exists(x => x.Id == id);
        }
        /// <summary>
        /// èŽ·å–åŒ…å›´ç›’
        /// </summary>
        public BoundingBoxL3d GetBoundingBox()
        {
            var minX = float.MaxValue;
            var minY = float.MaxValue;
            var minZ = float.MaxValue;
            var maxX = float.MinValue;
            var maxY = float.MinValue;
            var maxZ = float.MinValue;
            foreach (var node in this.Nodes)
            {
                minX = Math.Min(minX, node.Position.X);
                minY = Math.Min(minY, node.Position.Y);
                minZ = Math.Min(minZ, node.Position.Z);
                maxX = Math.Max(maxX, node.Position.X);
                maxY = Math.Max(maxY, node.Position.Y);
                maxZ = Math.Max(maxZ, node.Position.Z);
            }
            return new BoundingBoxL3d()
            {
                Min = new PointL3d(minX, minY, minZ),
                Max = new PointL3d(maxX, maxY, maxZ)
            };
        }
        /// <summary>
        /// èŽ·å–ä¸­å¿ƒç‚¹
        /// </summary>
        public PointL3d GetCenter(BoundingBoxL3d boundingBox)
        {
            if (boundingBox == null)
            {
                boundingBox = GetBoundingBox();
            }
            return boundingBox.GetCenter();
        }
        /// <summary>
        /// ç»˜åˆ¶2d
        /// </summary>
        public void Draw2d()
        {
            this.Links?.ForEach(x => x.Draw2d());
            this.Nodes?.ForEach(x => x.Draw2d());
        }
Yw.WinFrmUI.Hydro.L3d.Core/01-network/02-visual/VisualL3d.cs
@@ -16,14 +16,21 @@
        public VisualL3d(VisualL3d rhs) : base(rhs) { }
        /// <summary>
        /// çŠ¶æ€
        /// æ˜¯å¦æ‚¬åœ
        /// </summary>
        internal eVisualState State { get; set; }
        public bool IsHovered { get; set; }
        /// <summary>
        /// 2d绘制
        /// æ˜¯å¦é€‰æ‹©
        /// </summary>
        public abstract void Draw2d();
        public bool IsSelected { get; set; }
        /// <summary>
        /// èŽ·å–ä½ç½®
        /// </summary>
        public abstract List<PointL3d> GetPositions();
Yw.WinFrmUI.Hydro.L3d.Core/01-network/02-visual/eVisualState.cs
ÎļþÒÑɾ³ý
Yw.WinFrmUI.Hydro.L3d.Core/01-network/03-node/00-core/NodeL3d.cs
@@ -1,4 +1,5 @@
namespace Yw.WinFrmUI.Hydro

namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// èŠ‚ç‚¹
@@ -8,7 +9,15 @@
        /// <summary>
        /// 
        /// </summary>
        public NodeL3d() { }
        public NodeL3d()
        {
            this.Style2d = new StyleNode2dL3d()
            {
                Normal = new StylePoint2dL3d(3f, Color.Red),
                Hovered = new StylePoint2dL3d(6f, "#00BFFF"),
                Selected = new StylePoint2dL3d(4f, "#008000")
            };
        }
        /// <summary>
        /// 
@@ -16,6 +25,7 @@
        public NodeL3d(NodeL3d rhs) : base(rhs)
        {
            this.Position = new PointL3d(rhs.Position);
            this.Style2d = new StyleNode2dL3d(rhs.Style2d);
        }
        /// <summary>
@@ -24,20 +34,26 @@
        public PointL3d Position { get; set; }
        /// <summary>
        ///
        /// 2d样式
        /// </summary>
        public override void Draw2d()
        public StyleNode2dL3d Style2d { get; set; }
        /// <summary>
        /// èŽ·å–ä½ç½®
        /// </summary>
        public override List<PointL3d> GetPositions()
        {
            switch (this.State)
            {
                case eVisualState.Normal: DrawPoint2dHelper.Draw(5f, Color.Red, this.Position); break;
                case eVisualState.Hovered: DrawPoint2dHelper.Draw(6f, ColorTranslator.FromHtml("#00BFFF"), this.Position); break;
                case eVisualState.Selected: DrawPoint2dHelper.Draw(7f, ColorTranslator.FromHtml("#008000"), this.Position); break;
                default: break;
            }
            return new List<PointL3d>() { this.Position };
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/01-network/04-link/00-core/LinkL3d.cs
@@ -8,7 +8,15 @@
        /// <summary>
        /// 
        /// </summary>
        public LinkL3d() { }
        public LinkL3d()
        {
            this.Style2d = new StyleLink2dL3d()
            {
                Normal = new StyleLine2dL3d(2f, Color.Blue),
                Hovered = new StyleLine2dL3d(5f, "#00BFFF"),
                Selected = new StyleLine2dL3d(3f, "#800080")
            };
        }
        /// <summary>
        /// 
@@ -17,6 +25,7 @@
        {
            this.StartPosition = new PointL3d(rhs.StartPosition);
            this.EndPosition = new PointL3d(rhs.EndPosition);
            this.Style2d = new StyleLink2dL3d(rhs.Style2d);
        }
        /// <summary>
@@ -30,31 +39,28 @@
        public PointL3d EndPosition { get; set; }
        /// <summary>
        /// ä½ç½®åˆ—表
        /// 2d样式
        /// </summary>
        public virtual List<PointL3d> Positions
        {
            get
            {
                return new List<PointL3d>() { this.StartPosition, this.EndPosition };
            }
        }
        public StyleLink2dL3d Style2d { get; set; }
        /// <summary>
        /// ç»˜åˆ¶
        /// èŽ·å–ä½ç½®
        /// </summary>
        public override void Draw2d()
        public override List<PointL3d> GetPositions()
        {
            switch (this.State)
            {
                case eVisualState.Normal: DrawLine2dHelper.Draw(2f, Color.Blue, this.StartPosition, this.EndPosition); break;
                case eVisualState.Hovered: DrawLine2dHelper.Draw(3f, ColorTranslator.FromHtml("#00BFFF"), this.StartPosition, this.EndPosition); break;
                case eVisualState.Selected: DrawLine2dHelper.Draw(3.5f, ColorTranslator.FromHtml("#800080"), this.StartPosition, this.EndPosition); break;
                default: break;
            }
            return new List<PointL3d>() { this.StartPosition, this.EndPosition };
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/00-core/Style2dL3d.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// 2d样式
    /// </summary>
    public class Style2dL3d
    {
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/00-core/StyleLink2dL3d.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    ///
    /// </summary>
    public class StyleLink2dL3d : Style2dL3d
    {
        /// <summary>
        ///
        /// </summary>
        public StyleLink2dL3d() { }
        /// <summary>
        ///
        /// </summary>
        public StyleLink2dL3d(StyleLink2dL3d rhs)
        {
            this.Normal = new StyleLine2dL3d(rhs.Normal);
            this.Hovered = new StyleLine2dL3d(rhs.Hovered);
            this.Selected = new StyleLine2dL3d(rhs.Selected);
        }
        /// <summary>
        /// æ­£å¸¸
        /// </summary>
        public StyleLine2dL3d Normal { get; set; }
        /// <summary>
        /// æ‚¬åœ
        /// </summary>
        public StyleLine2dL3d Hovered { get; set; }
        /// <summary>
        /// é€‰æ‹©
        /// </summary>
        public StyleLine2dL3d Selected { get; set; }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/00-core/StyleNode2dL3d.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    ///
    /// </summary>
    public class StyleNode2dL3d : Style2dL3d
    {
        /// <summary>
        ///
        /// </summary>
        public StyleNode2dL3d() { }
        /// <summary>
        ///
        /// </summary>
        public StyleNode2dL3d(StyleNode2dL3d rhs)
        {
            this.Normal = new StylePoint2dL3d(rhs.Normal);
            this.Hovered = new StylePoint2dL3d(rhs.Hovered);
            this.Selected = new StylePoint2dL3d(rhs.Selected);
        }
        /// <summary>
        /// æ­£å¸¸
        /// </summary>
        public StylePoint2dL3d Normal { get; set; }
        /// <summary>
        /// æ‚¬åœ
        /// </summary>
        public StylePoint2dL3d Hovered { get; set; }
        /// <summary>
        /// é€‰æ‹©
        /// </summary>
        public StylePoint2dL3d Selected { get; set; }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/01-point/StylePoint2dL3d.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// 2d点样式
    /// </summary>
    public class StylePoint2dL3d
    {
        /// <summary>
        ///
        /// </summary>
        public StylePoint2dL3d() { }
        /// <summary>
        ///
        /// </summary>
        public StylePoint2dL3d(float radiu, Color color)
        {
            this.Radiu = radiu;
            this.Color = color;
        }
        /// <summary>
        ///
        /// </summary>
        public StylePoint2dL3d(float radiu, string htmlColor)
        {
            this.Radiu = radiu;
            this.Color = ColorTranslator.FromHtml(htmlColor);
        }
        /// <summary>
        ///
        /// </summary>
        public StylePoint2dL3d(StylePoint2dL3d rhs)
        {
            this.Radiu = rhs.Radiu;
            this.Color = rhs.Color;
        }
        /// <summary>
        /// åŠå¾„
        /// </summary>
        public float Radiu { get; set; }
        /// <summary>
        /// é¢œè‰²
        /// </summary>
        public Color Color { get; set; }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/01-network/05-style/02-line/StyleLine2dL3d.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    ///
    /// </summary>
    public class StyleLine2dL3d
    {
        /// <summary>
        ///
        /// </summary>
        public StyleLine2dL3d() { }
        /// <summary>
        ///
        /// </summary>
        public StyleLine2dL3d(float width, Color color)
        {
            this.Width = width;
            this.Color = color;
        }
        /// <summary>
        ///
        /// </summary>
        public StyleLine2dL3d(float width, string htmlColor)
        {
            this.Width = width;
            this.Color = ColorTranslator.FromHtml(htmlColor);
        }
        /// <summary>
        ///
        /// </summary>
        public StyleLine2dL3d(StyleLine2dL3d rhs)
        {
            this.Width = rhs.Width;
            this.Color = rhs.Color;
        }
        /// <summary>
        /// çº¿å®½
        /// </summary>
        public float Width { get; set; }
        /// <summary>
        /// çº¿è‰²
        /// </summary>
        public Color Color { get; set; }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/02-draw/DrawLine2dHelper.cs
ÎļþÒÑɾ³ý
Yw.WinFrmUI.Hydro.L3d.Core/02-draw/DrawPoint2dHelper.cs
ÎļþÒÑɾ³ý
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox3.cs
@@ -3,9 +3,9 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    ///
    /// åŒ…å›´ç›’
    /// </summary>
    internal struct BoundingBox3
    internal class BoundingBox3
    {
        /// <summary>
        /// 
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox32dCacheHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// åŒ…围盒缓存辅助类
    /// </summary>
    internal class BoundingBox32dCacheHelper
    {
        /// <summary>
        ///
        /// </summary>
        public BoundingBox32dCacheHelper(NetworkL3d nw, Vector32dCacheHelper vc)
        {
            _nw = nw;
            _vc = vc;
            Initial();
        }
        private NetworkL3d _nw = null;
        private Vector32dCacheHelper _vc = null;
        private Dictionary<string, BoundingBox3> _dict = new();
        //初始化
        private void Initial()
        {
            if (_nw == null)
            {
                return;
            }
            if (_vc == null)
            {
                return;
            }
            _dict.Clear();
            _nw.Nodes.ForEach(x =>
            {
                var pts = _vc.GetPositions(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.GetPositions(x.Id);
                if (pts != null && pts.Count > 0)
                {
                    var bx = BoundingBox3Helper.CalcuLineBoundingBox(pts[0], pts[1], x.Style2d.Normal.Width);
                    _dict.Add(x.Id, bx);
                }
            }));
        }
        /// <summary>
        /// èŽ·å–åŒ…å›´ç›’
        /// </summary>
        public BoundingBox3 GetBoundingBox(string id)
        {
            if (_dict.ContainsKey(id))
            {
                return _dict[id];
            }
            return default;
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/BoundingBox3Helper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
using OpenTK.Mathematics;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// åŒ…围盒辅助类
    /// </summary>
    internal class BoundingBox3Helper
    {
        /// <summary>
        /// è®¡ç®—圆点包围盒
        /// </summary>
        /// <param name="pt">圆点</param>
        /// <param name="radius">半径</param>
        /// <returns></returns>
        public static BoundingBox3 CalcuPointBoundingBox(Vector3 pt, float radius)
        {
            var min = pt - new Vector3(radius);
            var max = pt + new Vector3(radius);
            return new BoundingBox3(min, max);
        }
        /// <summary>
        /// è®¡ç®—圆柱体的轴对齐包围盒(AABB)
        /// </summary>
        public static BoundingBox3 CalcuLineBoundingBox(Vector3 start, Vector3 end, float width)
        {
            var radius = width / 2f;
            Vector3 dir = (end - start).Normalized();
            float dirX = dir.X, dirY = dir.Y, dirZ = dir.Z;
            // è®¡ç®—各轴的端点和扩展量
            float minX = Math.Min(start.X, end.X);
            float maxX = Math.Max(start.X, end.X);
            float extensionX = radius * (float)MathF.Sqrt(1f - dirX * dirX);
            minX -= extensionX;
            maxX += extensionX;
            float minY = Math.Min(start.Y, end.Y);
            float maxY = Math.Max(start.Y, end.Y);
            float extensionY = radius * (float)MathF.Sqrt(1f - dirY * dirY);
            minY -= extensionY;
            maxY += extensionY;
            float minZ = Math.Min(start.Z, end.Z);
            float maxZ = Math.Max(start.Z, end.Z);
            float extensionZ = radius * (float)MathF.Sqrt(1f - dirZ * dirZ);
            minZ -= extensionZ;
            maxZ += extensionZ;
            return new BoundingBox3(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ));
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Draw2dHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// ç»˜åˆ¶2d辅助类
    /// </summary>
    internal class Draw2dHelper
    {
        /// <summary>
        /// ç»˜åˆ¶ç‚¹
        /// </summary>
        /// <param name="size">点大小</param>
        /// <param name="color">点颜色</param>
        /// <param name="pt">点位置</param>
        public static void DrawPoint(float size, Color color, PointL3d pt)
        {
            GL.PointSize(size);
            GL.Begin(PrimitiveType.Points);
            GL.Color3(color);
            GL.Vertex3(pt.X, pt.Y, pt.Z);
            GL.End();
        }
        /// <summary>
        /// ç»˜åˆ¶ç‚¹
        /// </summary>
        /// <param name="size">点大小</param>
        /// <param name="color">点颜色</param>
        /// <param name="pt">点位置</param>
        public static void DrawPoint(float size, Color color, Vector3 pt)
        {
            GL.PointSize(size);
            GL.Begin(PrimitiveType.Points);
            GL.Color3(color);
            GL.Vertex3(pt.X, pt.Y, pt.Z);
            GL.End();
        }
        /// <summary>
        /// ç»˜åˆ¶çº¿
        /// </summary>
        /// <param name="width">线宽</param>
        /// <param name="color">线色</param>
        /// <param name="spt">开始点</param>
        /// <param name="ept">结束点</param>
        public static void DrawLine(float width, Color color, PointL3d spt, PointL3d ept)
        {
            GL.LineWidth(width);
            GL.Begin(PrimitiveType.Lines);
            GL.Color3(color);
            GL.Vertex3(spt.X, spt.Y, spt.Z);
            GL.Vertex3(ept.X, ept.Y, ept.Z);
            GL.End();
        }
        /// <summary>
        /// ç»˜åˆ¶
        /// </summary>
        /// <param name="width">线宽</param>
        /// <param name="color">线色</param>
        /// <param name="spt">开始点</param>
        /// <param name="ept">结束点</param>
        public static void DrawLine(float width, Color color, Vector3 spt, Vector3 ept)
        {
            GL.LineWidth(width);
            GL.Begin(PrimitiveType.Lines);
            GL.Color3(color);
            GL.Vertex3(spt.X, spt.Y, spt.Z);
            GL.Vertex3(ept.X, ept.Y, ept.Z);
            GL.End();
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Network2dExtensions.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    ///
    /// </summary>
    internal static class Network2dExtensions
    {
        /// <summary>
        /// ç»˜åˆ¶2d
        /// </summary>
        public static void Draw2d(this NetworkL3d nw)
        {
            if (nw == null)
            {
                return;
            }
            nw.Links.ForEach(x =>
            {
                if (!x.IsSelected && !x.IsHovered)
                {
                    Draw2dHelper.DrawLine(x.Style2d.Normal.Width, x.Style2d.Normal.Color, x.StartPosition, x.EndPosition);
                }
                else
                {
                    if (x.IsHovered)
                    {
                        Draw2dHelper.DrawLine(x.Style2d.Hovered.Width, x.Style2d.Hovered.Color, x.StartPosition, x.EndPosition);
                    }
                    else
                    {
                        Draw2dHelper.DrawLine(x.Style2d.Selected.Width, x.Style2d.Selected.Color, x.StartPosition, x.EndPosition);
                    }
                }
            });
            nw.Nodes.ForEach(x =>
            {
                if (!x.IsSelected && !x.IsHovered)
                {
                    Draw2dHelper.DrawPoint(x.Style2d.Normal.Radiu * 2, x.Style2d.Normal.Color, x.Position);
                }
                else
                {
                    if (x.IsHovered)
                    {
                        Draw2dHelper.DrawPoint(x.Style2d.Hovered.Radiu * 2, x.Style2d.Hovered.Color, x.Position);
                    }
                    else
                    {
                        Draw2dHelper.DrawPoint(x.Style2d.Selected.Radiu * 2, x.Style2d.Selected.Color, x.Position);
                    }
                }
            });
        }
        /// <summary>
        /// æ‚¬åœ2d
        /// </summary>
        public static void Hover2d(this NetworkL3d nw, Ray3 ray, BoundingBox32dCacheHelper bxcache)
        {
            nw.Visuals.ForEach(x => x.IsHovered = false);
            var visual = ray.CastingClosest(nw, bxcache);
            if (visual != null)
            {
                visual.IsHovered = true;
            }
        }
        /// <summary>
        /// é€‰æ‹©2d
        /// </summary>
        public static void Select2d(this NetworkL3d nw, Ray3 ray, BoundingBox32dCacheHelper bxcache)
        {
            nw.Visuals.ForEach(x => x.IsSelected = false);
            var visual = ray.CastingClosest(nw, bxcache);
            if (visual != null)
            {
                visual.IsSelected = true;
                return;
            }
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/OrthoCamera2dHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,313 @@
using OpenTK.GLControl;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
using System.Transactions;
using System.Windows.Forms;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// æ­£äº¤ç›¸æœºè¾…助类
    /// </summary>
    internal class OrthoCamera2dHelper
    {
        /// <summary>
        /// è¾…助类
        /// </summary>
        public OrthoCamera2dHelper() { }
        /// <summary>
        /// åˆå§‹åŒ–
        /// </summary>
        public void Initial(GLControl gl, NetworkL3d nw)
        {
            _gl = gl;
            _nw = nw;
            _veccache = new Vector32dCacheHelper(nw);
            var pts = _veccache.GetPositions();
            _bx = new BoundingBox3(pts);
            _bxcache = new BoundingBox32dCacheHelper(nw, _veccache);
            _mc = (_bx.Max + _bx.Min) * 0.5f;
            _radius = (_bx.Max - _bx.Min).Length / 2f;
            UpdateModelMatrix();
            UpdateViewMatrix();
            UpdateProjectionMatrix();
        }
        // åŸºç¡€å‚æ•°
        private GLControl _gl = null;//gl控件
        private NetworkL3d _nw = null;//管网模型
        private Vector32dCacheHelper _veccache = null;//位置辅助类
        private BoundingBox3 _bx = null;//包围盒
        private BoundingBox32dCacheHelper _bxcache = null;//包围盒缓存
        private Vector3 _mc; //模型中心
        private float _radius = 100f;//包围球半径
        private float _pw = 10f;//投影宽度
        private float _ph = 10f;//投影高度
        private Quaternion _rotation = Quaternion.Identity;//旋转四元数
        private Vector3 _translation = Vector3.Zero;//平移量
        private Matrix4 _modelMatrix;//模型矩阵
        private Matrix4 _viewMatrix;//视图矩阵
        private Matrix4 _projMatrix;//投影矩阵
        private Vector3 _forward;
        /// <summary>
        /// ç¼©æ”¾
        /// </summary>
        public float Zoom
        {
            get => _zoom;
            set => _zoom = MathHelper.Clamp(value, 0.01f, 100.0f); // é™åˆ¶ç¼©æ”¾èŒƒå›´
        }
        private float _zoom = 1.0f; // å½“前缩放系数(1表示原始大小)
        /// <summary>
        /// æ˜¯å¦åˆå§‹åŒ–
        /// </summary>
        public bool Initialized
        {
            get
            {
                if (_nw == null)
                {
                    return false;
                }
                if (_gl == null)
                {
                    return false;
                }
                return true;
            }
        }
        //更新模型矩阵
        private void UpdateModelMatrix()
        {
            _modelMatrix = Matrix4.CreateTranslation(_translation * this.Zoom);
        }
        //更新视图矩阵
        private void UpdateViewMatrix()
        {
            var eye = new Vector3(_mc.X, _mc.Y, _mc.Z + _radius * 2);
            var target = _mc;
            var up = Vector3.UnitY;
            var matrix = Matrix4.CreateFromQuaternion(_rotation);
            matrix.Invert();
            eye = Vector3.TransformPosition(eye, matrix);
            up = Vector3.TransformPosition(up, matrix);
            _viewMatrix = Matrix4.LookAt(eye, target, up);
            _forward = target - eye;
            _forward.Normalize();
        }
        //更新投影矩阵
        private void UpdateProjectionMatrix()
        {
            if (!Initialized)
            {
                return;
            }
            var aspect = _gl.AspectRatio;
            var size = _radius * 2;
            _pw = size;
            _ph = size;
            if (aspect > 1)
            {
                _pw *= aspect;
            }
            else
            {
                _ph /= aspect;
            }
            _projMatrix = Matrix4.CreateOrthographic(_pw * this.Zoom, _ph * this.Zoom, _radius, _radius * 3);
        }
        #region é¼ æ ‡äº¤äº’
        private bool _isDragging = false;//是否增在拖动
        private bool _isRotating = false;//是否正在旋转
        private Point _lastMousePos;//最近一次鼠标位置
        /// <summary>
        /// å¤„理大小改变
        /// </summary>
        public void HandleResize()
        {
            if (!Initialized)
            {
                return;
            }
            _gl.MakeCurrent();
            GL.Viewport(0, 0, _gl.Width, _gl.Height);
            UpdateProjectionMatrix();
            _gl.Invalidate();
        }
        /// <summary>
        /// å¤„理绘制
        /// </summary>
        public void HandleRender()
        {
            if (!Initialized)
            {
                return;
            }
            _gl.MakeCurrent();
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
            //投影矩阵
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref _projMatrix);
            //视图矩阵
            GL.MatrixMode(MatrixMode.Modelview);
            var viewMatrix = _viewMatrix * _modelMatrix;
            GL.LoadMatrix(ref viewMatrix);
            _nw.Draw2d();
            //var code = GL.GetError();
            _gl.SwapBuffers();
        }
        /// <summary>
        /// å¤„理鼠标滚轮
        /// </summary>
        public void HandleMouseWheel(MouseEventArgs e)
        {
            if (!Initialized)
            {
                return;
            }
            this.Zoom *= e.Delta > 0 ? 0.9f : 1.1f;
            UpdateModelMatrix();
            UpdateViewMatrix();
            UpdateProjectionMatrix();
            _gl.Invalidate();
        }
        /// <summary>
        /// å¤„理鼠标按下
        /// </summary>
        public void HandleMouseDown(MouseEventArgs e)
        {
            if (!Initialized)
            {
                return;
            }
            _lastMousePos = e.Location;
            if (e.Button == MouseButtons.Right)
            {
                _isDragging = true;
                _gl.Cursor = Cursors.SizeAll; // ä¿®æ”¹å…‰æ ‡æ ·å¼
            }
            else if (e.Button == MouseButtons.Left)
            {
                _isRotating = true;
                _gl.Cursor = Cursors.SizeAll; // ä¿®æ”¹å…‰æ ‡æ ·å¼
            }
        }
        /// <summary>
        /// å¤„理鼠标弹起
        /// </summary>
        public void HandleMouseUp(MouseEventArgs e)
        {
            if (!Initialized)
            {
                return;
            }
            _lastMousePos = e.Location;
            if (e.Button == MouseButtons.Right)
            {
                _isDragging = false;
                _gl.Cursor = Cursors.Default;
            }
            else if (e.Button == MouseButtons.Left)
            {
                _isRotating = false;
                _gl.Cursor = Cursors.Default;
            }
            _gl.Invalidate();
        }
        /// <summary>
        /// å¤„理鼠标移动
        /// </summary>
        public void HandleMouseMove(MouseEventArgs e)
        {
            if (!Initialized)
            {
                return;
            }
            float dx = e.X - _lastMousePos.X;
            float dy = e.Y - _lastMousePos.Y;
            if (_isRotating)
            {
                // æ ¹æ®é¼ æ ‡ç§»åŠ¨é‡è®¡ç®—æ—‹è½¬è§’åº¦
                Quaternion rotationX = Quaternion.FromAxisAngle(Vector3.UnitX, dy / _radius);
                Quaternion rotationY = Quaternion.FromAxisAngle(Vector3.UnitY, dx / _radius);
                // æ›´æ–°å››å…ƒæ•°
                _rotation = rotationY * rotationX * _rotation;
                _rotation = Quaternion.Normalize(_rotation);
                UpdateViewMatrix();
            }
            else if (_isDragging)
            {
                float xratio = _pw / _gl.Width;
                float yratio = _ph / _gl.Height;
                _translation.X += dx * xratio;
                _translation.Y -= dy * yratio;
                UpdateModelMatrix();
            }
            else
            {
                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);
                if (ray != null)
                {
                    _nw.Hover2d(ray, _bxcache);
                }
            }
            _lastMousePos = e.Location;
            _gl.Invalidate();
        }
        /// <summary>
        /// å¤„理鼠标点击
        /// </summary>
        public void HandleMouseClick(MouseEventArgs e)
        {
        }
        /// <summary>
        /// å¤„理鼠标双击
        /// </summary>
        public void HandleMouseDoubleClick(MouseEventArgs e)
        {
            var depth = OrthoTransformHelper.GetScreenPointDepth(e.X, e.Y, _gl.Height);
            var world = OrthoTransformHelper.ScreenToWorld(e.X, e.Y, depth, _viewMatrix * _modelMatrix, _projMatrix, _gl.Width, _gl.Height);
            MessageBox.Show($"x:{world.X},y:{world.Y},z:{world.Z}");
        }
        #endregion
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/OrthoCameraHelper.cs
ÎļþÒÑɾ³ý
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/OrthoTransformHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,195 @@
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// æ­£äº¤è½¬æ¢è¾…助类
    /// </summary>
    internal class OrthoTransformHelper
    {
        /// <summary>
        /// å±å¹•坐标 To NDC坐标
        /// </summary>
        /// <param name="screenX">屏幕X坐标(左上角为原点)</param>
        /// <param name="screenY">屏幕Y坐标(左上角为原点)</param>
        /// <param name="depth">深度值(范围[0, 1],可选)</param>
        /// <param name="viewportWidth">视口宽度</param>
        /// <param name="viewportHeight">视口高度</param>
        /// <returns></returns>
        public static Vector3 ScreenToNDC(float screenX, float screenY, float depth, int viewportWidth, int viewportHeight)
        {
            // å½’一化到 [0, 1]
            float normalizedX = screenX / viewportWidth;
            float normalizedY = screenY / viewportHeight;
            // æ˜ å°„到 [-1, 1]
            float ndcX = 2.0f * normalizedX - 1.0f;
            float ndcY = 1.0f - 2.0f * normalizedY; // ç¿»è½¬Yè½´
            float ndcZ = 2.0f * depth - 1.0f;      // æ·±åº¦å€¼è½¬æ¢
            return new Vector3(ndcX, ndcY, ndcZ);
        }
        /// <summary>
        /// NDC坐标 To å±å¹•坐标
        /// </summary>
        /// <param name="ndc">NDC坐标(X,Y âˆˆ [-1, 1], Z âˆˆ [-1, 1])</param>
        /// <param name="viewportWidth">视口宽度</param>
        /// <param name="viewportHeight">视口高度</param>
        /// <returns></returns>
        public static Vector2 NDCToScreen(Vector3 ndc, int viewportWidth, int viewportHeight)
        {
            // X轴:[-1, 1] â†’ [0, viewportWidth]
            float screenX = (ndc.X + 1.0f) * 0.5f * viewportWidth;
            // Y轴:[-1, 1] â†’ [0, viewportHeight](翻转Y轴)
            float screenY = (1.0f - (ndc.Y + 1.0f) * 0.5f) * viewportHeight;
            // Z轴(可选):[-1, 1] â†’ [0, 1]
            float screenZ = (ndc.Z + 1.0f) * 0.5f;
            return new Vector2(screenX, screenY);
        }
        /// <summary>
        /// è¯»å–鼠标位置对应的深度值
        /// </summary>
        /// <param name="mouseX">屏幕坐标X(左上角为原点)</param>
        /// <param name="mouseY">屏幕坐标Y(左上角为原点)</param>
        /// <param name="viewportHeight">视口高度</param>
        /// <returns>深度值(范围[0, 1])</returns>
        public static float GetScreenPointDepth(int mouseX, int mouseY, int viewportHeight)
        {
            // è°ƒæ•´Y坐标:屏幕坐标系(左上原点) â†’ OpenGL视口坐标系(左下原点)
            int glY = viewportHeight - mouseY;
            // è¯»å–深度值
            float[] depth = new float[1];
            GL.ReadPixels(
                mouseX,          // é¼ æ ‡X坐标
                glY,             // è°ƒæ•´åŽçš„Y坐标
                1, 1,            // è¯»å–1x1像素
                PixelFormat.DepthComponent, // åƒç´ æ ¼å¼ä¸ºæ·±åº¦åˆ†é‡
                PixelType.Float, // æ•°æ®ç±»åž‹ä¸ºæµ®ç‚¹æ•°
                depth            // å­˜å‚¨ç»“果的数组
            );
            return depth[0];
        }
        /// <summary>
        /// å±å¹•坐标 To ä¸–界坐标
        /// </summary>
        /// <param name="mouseX">屏幕坐标X(左上角为原点)</param>
        /// <param name="mouseY">屏幕坐标Y(左上角为原点)</param>
        /// <param name="depth">深度值(范围[0, 1],可选)</param>
        /// <param name="mview">视图矩阵</param>
        /// <param name="proj">投影矩阵</param>
        /// <param name="viewportWidth">视口宽度</param>
        /// <param name="viewportHeight">视口高度</param>
        /// <returns></returns>
        public static Vector3 ScreenToWorld(float mouseX, float mouseY, float depth, Matrix4 mview, Matrix4 proj, int viewportWidth, int viewportHeight)
        {
            var ndc = ScreenToNDC(mouseX, mouseY, depth, viewportWidth, viewportHeight);
            var matrix = mview * proj;
            var invert = matrix.Inverted();
            // å°†NDC坐标转换为世界坐标
            var wp = Vector4.TransformRow(new Vector4(ndc, 1f), invert);
            wp /= wp.W; // é€è§†é™¤æ³•
            return new Vector3(wp.X, wp.Y, wp.Z);
        }
        /// <summary>
        /// ä¸–界坐标 To å±å¹•坐标
        /// </summary>
        /// <param name="wp">世界坐标</param>
        /// <param name="mview">视图矩阵</param>
        /// <param name="proj">投影矩阵</param>
        /// <param name="wiewportWidth">视口宽度</param>
        /// <param name="viewportHeight">视口高度</param>
        /// <returns></returns>
        public static Vector2 WorldToScreen(Vector3 wp, Matrix4 mview, Matrix4 proj, int wiewportWidth, int viewportHeight)
        {
            // è½¬æ¢ä¸ºé½æ¬¡åæ ‡
            var wph = new Vector4(wp, 1.0f);
            //应用矩阵
            var mvp = mview * proj;
            //裁剪坐标
            var clip = Vector4.TransformRow(wph, mvp);
            //NDC
            clip /= clip.W;
            return NDCToScreen(clip.Xyz, wiewportWidth, viewportHeight);
        }
        /// <summary>
        /// åˆ›å»ºå°„线
        /// </summary>
        /// <param name="mouseX">屏幕坐标X(左上角为原点)</param>
        /// <param name="mouseY">屏幕坐标Y(左上角为原点)</param>
        /// <param name="depth">深度值(范围[0, 1],可选)</param>
        /// <param name="forward">观察方向</param>
        /// <param name="model">模型矩阵</param>
        /// <param name="view">视图矩阵</param>
        /// <param name="proj">投影矩阵</param>
        /// <param name="viewportWidth">视口宽度</param>
        /// <param name="viewportHeight">视口高度</param>
        public static Ray3 CreateRay
            (
                float mouseX,
                float mouseY,
                float depth,
                Vector3 forward,
                Matrix4 model,
                Matrix4 view,
                Matrix4 proj,
                int viewportWidth,
                int viewportHeight
            )
        {
            var mview = view * model;
            var origins = ScreenToWorld(mouseX, mouseY, depth, mview, proj, viewportWidth, viewportHeight);
            return new Ray3(origins, forward);
        }
        /// <summary>
        /// åˆ›å»ºå°„线
        /// </summary>
        /// <param name="mouseX">屏幕坐标X(左上角为原点)</param>
        /// <param name="mouseY">屏幕坐标Y(左上角为原点)</param>
        /// <param name="model">模型矩阵</param>
        /// <param name="view">视图矩阵</param>
        /// <param name="proj">投影矩阵</param>
        /// <param name="viewportWidth">视口宽度</param>
        /// <param name="viewportHeight">视口高度</param>
        public static Ray3 CreateRay2
            (
                float mouseX,
                float mouseY,
                Matrix4 model,
                Matrix4 view,
                Matrix4 proj,
                int viewportWidth,
                int viewportHeight
            )
        {
            var mview = view * model;
            var near = ScreenToWorld(mouseX, mouseY, 0f, mview, proj, viewportWidth, viewportHeight);
            var far = ScreenToWorld(mouseX, mouseY, 1f, mview, proj, viewportWidth, viewportHeight);
            var direction = (far - near).Normalized();
            return new Ray3(near, direction);
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Ray3.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,132 @@
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="bx">包围盒</param>
        /// <param name="distance">交点与原点的距离</param>
        /// <returns>是否相交</returns>
        public bool Casting(BoundingBox3 bx, out float distance)
        {
            distance = float.MaxValue;
            float tmin = float.MinValue;
            float tmax = float.MaxValue;
            // æ£€æŸ¥ X æ–¹å‘的面
            if (Math.Abs(this.Direction.X) < float.Epsilon)
            {
                if (this.Origin.X < bx.Min.X || this.Origin.X > bx.Max.X)
                {
                    return false;
                }
            }
            else
            {
                float tx1 = (bx.Min.X - this.Origin.X) / this.Direction.X;
                float tx2 = (bx.Max.X - this.Origin.X) / this.Direction.X;
                tmin = Math.Max(tmin, Math.Min(tx1, tx2));
                tmax = Math.Min(tmax, Math.Max(tx1, tx2));
                if (tmax < tmin)
                {
                    return false;
                }
            }
            // æ£€æŸ¥ Y æ–¹å‘的面
            if (Math.Abs(this.Direction.Y) < float.Epsilon)
            {
                if (this.Origin.Y < bx.Min.Y || this.Origin.Y > bx.Max.Y)
                {
                    return false;
                }
            }
            else
            {
                float ty1 = (bx.Min.Y - this.Origin.Y) / this.Direction.Y;
                float ty2 = (bx.Max.Y - this.Origin.Y) / this.Direction.Y;
                tmin = Math.Max(tmin, Math.Min(ty1, ty2));
                tmax = Math.Min(tmax, Math.Max(ty1, ty2));
                if (tmax < tmin)
                {
                    return false;
                }
            }
            // æ£€æŸ¥ Z æ–¹å‘的面
            if (Math.Abs(this.Direction.Z) < float.Epsilon)
            {
                if (this.Origin.Z < bx.Min.Z || this.Origin.Z > bx.Max.Z)
                {
                    return false;
                }
            }
            else
            {
                float tz1 = (bx.Min.Z - this.Origin.Z) / this.Direction.Z;
                float tz2 = (bx.Max.Z - this.Origin.Z) / this.Direction.Z;
                tmin = Math.Max(tmin, Math.Min(tz1, tz2));
                tmax = Math.Min(tmax, Math.Max(tz1, tz2));
                if (tmax < tmin)
                {
                    return false;
                }
            }
            if (tmin >= 0)
            {
                distance = tmin;
            }
            else if (tmax >= 0)
            {
                distance = tmax;
            }
            else
            {
                return false;
            }
            return true;
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Ray3Caster.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
using System.Collections.Generic;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// å°„线碰撞
    /// </summary>
    internal static class Ray3Caster
    {
        /// <summary>
        /// æŠ•å°„
        /// </summary>
        /// <param name="ray">射线</param>
        /// <param name="visuals">可见构件列表</param>
        /// <param name="bxcache">包围盒缓存</param>
        public static List<VisualL3d> Casting(this Ray3 ray, List<VisualL3d> visuals, BoundingBox32dCacheHelper bxcache)
        {
            if (ray == null)
            {
                return default;
            }
            if (visuals == null || visuals.Count < 1)
            {
                return default;
            }
            if (bxcache == null)
            {
                return default;
            }
            var list = new List<VisualL3d>();
            foreach (var visual in visuals)
            {
                var bx = bxcache.GetBoundingBox(visual.Id);
                if (ray.Casting(bx, out float distance))
                {
                    list.Add(visual);
                }
            }
            return list;
        }
        /// <summary>
        /// æŠ•射最近构件
        /// </summary>
        /// <param name="ray">射线</param>
        /// <param name="visuals">可见构件列表</param>
        /// <param name="bxcache">包围盒缓存</param>
        public static VisualL3d CastingClosest(this Ray3 ray, List<VisualL3d> visuals, BoundingBox32dCacheHelper bxcache)
        {
            if (ray == null)
            {
                return default;
            }
            if (visuals == null || visuals.Count < 1)
            {
                return default;
            }
            if (bxcache == null)
            {
                return default;
            }
            VisualL3d closest = null;
            float minDistance = float.MaxValue;
            foreach (var visual in visuals)
            {
                var bx = bxcache.GetBoundingBox(visual.Id);
                if (ray.Casting(bx, out float distance))
                {
                    if (distance <= minDistance)
                    {
                        minDistance = distance;
                        closest = visual;
                    }
                }
            }
            return closest;
        }
        /// <summary>
        /// æŠ•射最近构件
        /// </summary>
        /// <param name="ray">射线</param>
        /// <param name="nw">管网</param>
        /// <param name="bxcache">包围盒缓存</param>
        /// <returns></returns>
        public static VisualL3d CastingClosest(this Ray3 ray, NetworkL3d nw, BoundingBox32dCacheHelper bxcache)
        {
            var visuals = nw?.Visuals;
            return CastingClosest(ray, visuals, bxcache);
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/Vector32dCacheHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
using OpenTK.Mathematics;
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// ä¸‰ç»´ç‚¹ç¼“存辅助类
    /// </summary>
    internal class Vector32dCacheHelper
    {
        /// <summary>
        ///
        /// </summary>
        public Vector32dCacheHelper(NetworkL3d nw)
        {
            _nw = nw;
            Initial();
        }
        private NetworkL3d _nw = null;
        private Dictionary<string, List<Vector3>> _dict = new();
        //初始化
        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>
        public List<Vector3> GetPositions()
        {
            if (_dict.Count < 1)
            {
                return default;
            }
            return _dict.SelectMany(x => x.Value).ToList();
        }
        /// <summary>
        /// èŽ·å–ä½ç½®
        /// </summary>
        public List<Vector3> GetPositions(string id)
        {
            if (_dict.ContainsKey(id))
            {
                return _dict[id];
            }
            return default;
        }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/03-helper/ViewPort3.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
namespace Yw.WinFrmUI.Hydro
{
    /// <summary>
    /// è§†å›¾çª—口
    /// </summary>
    internal class ViewPort3
    {
        /// <summary>
        ///
        /// </summary>
        public ViewPort3() { }
        /// <summary>
        ///
        /// </summary>
        public ViewPort3(int x, int y, int width, int height)
        {
            this.X = x;
            this.Y = y;
            this.Width = width;
            this.Height = height;
        }
        /// <summary>
        /// x
        /// </summary>
        public float X { get; set; }
        /// <summary>
        /// y
        /// </summary>
        public float Y { get; set; }
        /// <summary>
        /// å®½åº¦
        /// </summary>
        public float Width { get; set; }
        /// <summary>
        /// é«˜åº¦
        /// </summary>
        public float Height { get; set; }
    }
}
Yw.WinFrmUI.Hydro.L3d.Core/04-control/OrthoDrawer2d.cs
@@ -19,16 +19,18 @@
        {
            InitializeComponent();
            this.glControl1.Load += glControl_Load;
            this.glControl1.Paint += glControl_Paint;
            this.glControl1.Resize += glControl_Resize;
            this.glControl1.Paint += glControl_Paint;
            this.glControl1.MouseWheel += OnMouseWheel;
            this.glControl1.MouseDown += OnMouseDown;
            this.glControl1.MouseUp += OnMouseUp;
            this.glControl1.MouseMove += OnMouseMove;
            this.glControl1.MouseClick += OnMouseClick;
            this.glControl1.MouseDoubleClick += GlControl1_MouseDoubleClick;
        }
        private NetworkL3d _nw = null;
        private OrthoCameraHelper _orthoHelper = null;
        private OrthoCamera2dHelper _orthoHelper = null;
        /// <summary>
        /// åˆå§‹åŒ–管网
@@ -36,107 +38,87 @@
        public void InitialNetwork(NetworkL3d nw)
        {
            _nw = nw;
            var pts = _nw.Nodes.Select(x => new Vector3(x.Position.X, x.Position.Y, x.Position.Z)).ToList();
            _orthoHelper = new OrthoCameraHelper();
            _orthoHelper.Initial(pts);
            _orthoHelper = new OrthoCamera2dHelper();
            _orthoHelper.Initial(this.glControl1, _nw);
        }
        //加载事件
        private void glControl_Load(object sender, EventArgs e)
        {
            GL.ClearColor(Color.Black); // èƒŒæ™¯é¢œè‰²
            if (_nw == null)
            {
                return;
            }
            if (_orthoHelper == null)
            {
                return;
            }
            GL.ClearColor(Color.Transparent); // èƒŒæ™¯é¢œè‰²
            GL.ShadeModel(ShadingModel.Smooth);
            GL.Enable(EnableCap.DepthTest);//深度测试
            GL.DepthMask(true); // å…è®¸å†™å…¥æ·±åº¦ç¼“冲区
            GL.DepthMask(true);
            GL.Enable(EnableCap.PointSmooth);//启用点平滑
            GL.Hint(HintTarget.PointSmoothHint, HintMode.Nicest);
            GL.Enable(EnableCap.LineSmooth);//启用线平滑
            GL.Hint(HintTarget.LineSmoothHint, HintMode.Nicest);
            GL.PointSize(5);
            GL.LineWidth(3);
            ResizeGL();
            _orthoHelper.HandleResize();
        }
        // å°ºå¯¸æ”¹å˜
        private void glControl_Resize(object sender, EventArgs e)
        {
            ResizeGL();
            if (_nw == null)
            {
                return;
            }
            if (_orthoHelper == null)
            {
                return;
            }
            _orthoHelper.HandleResize();
        }
        // ç»˜åˆ¶
        private void glControl_Paint(object sender, PaintEventArgs e)
        {
            RenderGL();
            _orthoHelper.HandleRender();
        }
        // æ¸²æŸ“
        private void RenderGL()
        {
            if (_nw == null)
            {
                return;
            }
            this.glControl1.MakeCurrent();
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
            //投影矩阵
            GL.MatrixMode(MatrixMode.Projection);
            var projectionMatrix = _orthoHelper.ProjectionMatrix;
            GL.LoadMatrix(ref projectionMatrix);
            //视图矩阵
            GL.MatrixMode(MatrixMode.Modelview);
            var viewMatrix = _orthoHelper.ViewMatrix * _orthoHelper.ModelMatrix;
            GL.LoadMatrix(ref viewMatrix);
            _nw.Draw2d();
            var code = GL.GetError();
            this.glControl1.SwapBuffers();
        }
        // è°ƒæ•´å¤§å°
        private void ResizeGL()
        {
            this.glControl1.MakeCurrent();
            _orthoHelper.UpdateViewport(this.glControl1.Width, this.glControl1.Height);
            this.glControl1.Invalidate();
        }
        #region é¼ æ ‡äº¤äº’
        //鼠标滚轮
        private void OnMouseWheel(object sender, MouseEventArgs e)
        {
            _orthoHelper?.HandleMouseWheel(this.glControl1, e);
            _orthoHelper?.HandleMouseWheel(e);
        }
        // é¼ æ ‡æŒ‰ä¸‹
        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            _orthoHelper?.HandleMouseDown(this.glControl1, e);
            _orthoHelper?.HandleMouseDown(e);
        }
        //鼠标弹起
        private void OnMouseUp(object sender, MouseEventArgs e)
        {
            _orthoHelper?.HandleMouseUp(this.glControl1, e);
            _orthoHelper?.HandleMouseUp(e);
        }
        //鼠标移动
        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            _orthoHelper?.HandleMouseMove(this.glControl1, e);
            _orthoHelper?.HandleMouseMove(e);
        }
        #endregion
        //鼠标点击
        private void OnMouseClick(object sender, MouseEventArgs e)
        {
            _orthoHelper?.HandleMouseClick(e);
        }
        //鼠标双击
        private void GlControl1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            _orthoHelper.HandleMouseDoubleClick(e);
        }
    }
Yw.WinFrmUI.Test.Core/MainForm.Designer.cs
@@ -28,20 +28,74 @@
        /// </summary>
        private void InitializeComponent()
        {
            splitContainer1 = new SplitContainer();
            propertyGrid2 = new PropertyGrid();
            propertyGrid1 = new PropertyGrid();
            propertyGrid3 = new PropertyGrid();
            ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit();
            splitContainer1.Panel1.SuspendLayout();
            splitContainer1.SuspendLayout();
            SuspendLayout();
            //
            // splitContainer1
            //
            splitContainer1.Dock = DockStyle.Fill;
            splitContainer1.Location = new Point(0, 0);
            splitContainer1.Name = "splitContainer1";
            //
            // splitContainer1.Panel1
            //
            splitContainer1.Panel1.Controls.Add(propertyGrid3);
            splitContainer1.Panel1.Controls.Add(propertyGrid2);
            splitContainer1.Panel1.Controls.Add(propertyGrid1);
            splitContainer1.Size = new Size(1084, 674);
            splitContainer1.SplitterDistance = 311;
            splitContainer1.TabIndex = 0;
            //
            // propertyGrid2
            //
            propertyGrid2.Dock = DockStyle.Top;
            propertyGrid2.Location = new Point(0, 223);
            propertyGrid2.Name = "propertyGrid2";
            propertyGrid2.Size = new Size(311, 262);
            propertyGrid2.TabIndex = 1;
            //
            // propertyGrid1
            //
            propertyGrid1.Dock = DockStyle.Top;
            propertyGrid1.Location = new Point(0, 0);
            propertyGrid1.Name = "propertyGrid1";
            propertyGrid1.Size = new Size(311, 223);
            propertyGrid1.TabIndex = 0;
            //
            // propertyGrid3
            //
            propertyGrid3.Dock = DockStyle.Top;
            propertyGrid3.Location = new Point(0, 485);
            propertyGrid3.Name = "propertyGrid3";
            propertyGrid3.Size = new Size(311, 186);
            propertyGrid3.TabIndex = 2;
            // 
            // MainForm
            // 
            AutoScaleDimensions = new SizeF(7F, 17F);
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(800, 450);
            ClientSize = new Size(1084, 674);
            Controls.Add(splitContainer1);
            Name = "MainForm";
            Text = "MainForm";
            splitContainer1.Panel1.ResumeLayout(false);
            ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit();
            splitContainer1.ResumeLayout(false);
            ResumeLayout(false);
        }
        #endregion
        private SplitContainer splitContainer1;
        private PropertyGrid propertyGrid2;
        private PropertyGrid propertyGrid1;
        private PropertyGrid propertyGrid3;
    }
}
Yw.WinFrmUI.Test.Core/MainForm.cs
@@ -15,13 +15,20 @@
        private void MainForm_Load(object sender, EventArgs e)
        {
            var nw = LoadEpaNetwork();
            var tank = nw.Tanks[0];
            this.propertyGrid1.SelectedObject = new PointL3d(tank.Position.X, tank.Position.Y, tank.GetElev());
            var cooling = nw.Junctions.Find(x => x.Id == "452584");
            this.propertyGrid2.SelectedObject = new PointL3d(cooling.Position.X, cooling.Position.Y, cooling.GetElev());
            var nw3d = Get3dNetwork(nw);
            var openGLControl = new OrthoDrawer2d();
            openGLControl.Dock = DockStyle.Fill;
            openGLControl.InitialNetwork(nw3d);
            this.Controls.Add(openGLControl);
            this.splitContainer1.Panel2.Controls.Add(openGLControl);
        }
        private Yw.Epanet.Network LoadEpaNetwork()
        {
            var fileName = "wh.inp";