三维可视化软件开发——点云的打开与八叉树管理

准备三维开发环境

新建C#窗体应用程序

  • 新建项目,版本.NET Framework 4.5.2
  • MenuStrip,添加标准项
  • SplitContainer
    三维可视化软件开发——点云的打开与八叉树管理_第1张图片

添加OpenTK(单机方式不联网)

  • 右键单击“引用”,“添加引用”
  • 找到本地 OpenTK.dll和OpenTK.GLControl.dll(3.1.0版本)文件,选中,确定
    三维可视化软件开发——点云的打开与八叉树管理_第2张图片

在窗体里添加三维显示控件

  • 工具箱,右键单击空白处,选择项,浏览,找到OpenTK.GLControl.dll并选中
    工具箱
  • 将GLControl控件拖入Form窗体
  • 控件属性,Anchor,上下左右都选
    三维可视化软件开发——点云的打开与八叉树管理_第3张图片

添加名称空间

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using OpenTK.Platform;

第一个OpenTK程序

初始化OpenGL环境

  • 添加函数InitialGL()和SetupViewport()
        private void InitialGL()
        {
            GL.ShadeModel(ShadingModel.Smooth); // 启用平滑渲染。默认
            GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 黑色背景。默认
            GL.ClearDepth(1.0f); // 设置深度缓存。默认1
            GL.Enable(EnableCap.DepthTest); // 启用深度测试。默认关闭
            SetupViewport();
        }
        private void SetupViewport()
        {
            int w = glControl1.ClientSize.Width;
            int h = glControl1.ClientSize.Height;
            
            GL.MatrixMode(MatrixMode.Projection); // 后面将对投影做操作
            GL.LoadIdentity();
            
            GL.Ortho(-1, 1, -1, 1, -1, 1); // 创建正交平行的视景体。默认
            GL.Viewport(0, 0, w, h); //像素单位,指定OpenGL视口在窗口中的大小
        }
  • 在Form1_Load函数中调用InitialGL()

编写绘制函数并调用

  • 编写绘图函数DrawTriangle()
        private void Drawtriangle()
        {
            /*新旧版OpenTK语法有差别(设置要画的是什么物体,这里是线构成的环状线条,
             * Begin和end一起出现)*/
            GL.Begin(PrimitiveType.Triangles);
            GL.Color4(Color4.Yellow);
            GL.Vertex3(0, 0, 0);
            GL.Color4(Color4.Red);
            GL.Vertex3(0.9, 0, 0);
            GL.Color4(Color4.Green);
            GL.Vertex3(0.9, 0.9, 0);
            GL.End();
        }
  • 编写绘制函数Render()
        private void Render()//(绘图)
        {
            glControl1.MakeCurrent(); //后续OpenGL显示操作在当前控件窗口内进行
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            //清除当前帧已有内容,清除深度测试缓冲区

            Drawtriangle();

            glControl1.SwapBuffers(); /*交换缓冲区。双缓冲绘制时,所有的绘制都是绘制到后台缓冲区里,如果不交换缓冲区,就看不到绘制内容。OpenTK 默认双缓冲(不让屏幕产生晃动,先画到内存缓冲区,再到屏幕)*/
        }
  • Form1窗体添加Paint事件
  • Form1_Paint函数中调用Render
  • 添加类成员变量,确保OpenGL已经初始化
        //添加类成员变量,确保OpenGL已经初始化
        bool bOpenGLInitial = false;
  • InitialGL中添加语句
            bOpenGLInitial = true;
  • Form窗体添加Resize事件
  • Form1_Resize()中,添加语句
            if (bOpenGLInitial)
            {
                SetupViewport();
                Invalidate();//刷新 如果是glControl的事件,应为glControl1.Invalidate();
            }

OpenGL基础

投影

  • 添加like_gluPerspective(double fovy, double aspect, double near, double far)函数
        public void like_gluPerspective(double fovy, double aspect, double near, double far)
        {
            const double DEG2RAD = 3.14159265 / 180.0;
            double tangent = Math.Tan(fovy / 2 * DEG2RAD);
            double height = near * tangent;
            double width = height * aspect;
            GL.Frustum(-width, width, -height, height, near, far);
        }
  • 创建类成员变量fov和perspective_projection,会在后续的扩展功能中使用
        float fov = (float)Math.PI / 3.0f; //视角
        bool perspective_projection = false; //选择投影方式,默认正交
  • 修改SetupViewport()函数,选择投影方式
        private void SetupViewport()
        {
            int w = glControl1.ClientSize.Width;
            int h = glControl1.ClientSize.Height;

            GL.MatrixMode(MatrixMode.Projection); // 后面将对投影做操作
            GL.LoadIdentity(); //调用单位矩阵,回到默认状态

            double aspect;

            if(perspective_projection) //透视投影
            {
                aspect = w / (double)h;
                like_gluPerspective(fov, aspect, 0.001, 10);
                GL.Viewport(0, 0, w, h);
            }
            else //正交投影
            {
                aspect = (w >= h) ? (1.0 * w / h) : (1.0 * h / w);
                if (w <= h)
                    GL.Ortho(-1, 1, -aspect, aspect, -1, 1); //宽小于高,扩大Y
                else
                    GL.Ortho(-aspect, aspect, -1, 1, -1, 1); //宽大于高,扩大X
                GL.Viewport(0, 0, w, h);
            }
        }

平移与旋转

  • 创建类成员变量transX,transY,angleX,angleY并赋初值为零
        double transX = 0; //平移尺寸
        double transY = 0;

        double angleX = 0; //旋转角度
        double angleY = 0;
  • Render()中增添绘图矩阵和平移旋转
            //绘图矩阵
            GL.MatrixMode(MatrixMode.Projection); // 后面将对投影做操作(矩阵操作对投影)
            GL.LoadIdentity();//(调用单位矩阵)

            //图形变换
            GL.Translate(transX, transY, 0); //平移
            GL.Rotate(angleY, 1, 0, 0); //旋转
            GL.Rotate(angleX, 0, 1, 0);
        }
  • 增添类成员变量记录鼠标状态
  • glControl1_MouseDown中添加代码
            if (e.Button == MouseButtons.Left)
            {
                bLeftButtonPushed = true;
                leftButtonPosition = e.Location;
            }
            else if (e.Button == MouseButtons.Right)
            {
                bRightButtonPushed = true;
                RightButtonPosition = e.Location;
            }
  • glControl1_MouseUp中添加代码
            bLeftButtonPushed = false;
            bRightButtonPushed = false;
  • glControl1_MouseMove中添加代码
            if (bLeftButtonPushed)//左键控制平移物体
            {
                transX += (e.Location.X - leftButtonPosition.X) / 120.0; //比例自己试
                transY += -(e.Location.Y - leftButtonPosition.Y) / 120.0;
                leftButtonPosition = e.Location;
                Invalidate();
            }
            if (bRightButtonPushed)//右键控制旋转物体
            {
                angleX += (e.Location.X - RightButtonPosition.X) / 10.0;
                angleY += -(e.Location.Y - RightButtonPosition.Y) / 10.0;
                RightButtonPosition = e.Location;
                Invalidate();
            }

缩放

  • 增添全局变量scaling
        float scaling = 1.0f;//图形大小
  • Render()中调用GL.Scale(scaling, scaling, scaling)
        private void Render()//(绘图)
        {
            glControl1.MakeCurrent(); //后续OpenGL显示操作在当前控件窗口内进行
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
            //清除当前帧已有内容,清除深度测试缓冲区

            //绘图矩阵
            GL.MatrixMode(MatrixMode.Projection); // 后面将对投影做操作(矩阵操作对投影)
            GL.LoadIdentity();//(调用单位矩阵)

            //图形变换
            GL.Translate(transX, transY, 0); //平移
            GL.Rotate(angleY, 1, 0, 0); //旋转
            GL.Rotate(angleX, 0, 1, 0);
            GL.Scale(scaling, scaling, scaling);//缩放

            //绘图函数
            Drawtriangle();

            glControl1.SwapBuffers(); /*交换缓冲区。双缓冲绘制时,所有的绘制都是绘制到后台缓冲区里,如果不交换缓冲区,就看不到绘制内容。OpenTK 默认双缓冲(不让屏幕产生晃动,先画到内存缓冲区,再到屏幕)*/
        }
  • 添加函数glControl1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        private void glControl1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Delta > 0) //e.delta表示鼠标前后滚
            {
                scaling += 0.1f;
            }
            else if (e.Delta < 0)
            {
                scaling -= 0.1f;
            }
            SetupViewport();
            Invalidate(); 
        }
  • Form1()中声明MouseWheel
            glControl1.MouseWheel += new MouseEventHandler(glControl1_MouseWheel);

绘制一个球体

  • 添加函数DrawSphere()
        private void DrawSphere()
        {
            const double radius = 0.5;
            const int step = 5;
            int xWidth = 360 / step + 1;
            int zHeight = 180 / step + 1;
            int halfZHeight = (zHeight - 1) / 2;
            int v = 0;
            double xx, yy, zz;

            GL.Begin(PrimitiveType.Points);
            GL.Color4(Color4.Yellow);
            for (int z = -halfZHeight; z <= halfZHeight; z++)
            {
                var d = 0;
                for (int x = 0; x < xWidth; x++)
                {
                    xx = radius * Math.Cos(x * step * Math.PI / 180)
                        * Math.Cos(z * step * Math.PI / 180.0);
                    zz = radius * Math.Sin(x * step * Math.PI / 180) 
                        * Math.Cos(z * step * Math.PI / 180.0);
                    yy = radius * Math.Sin(z * step * Math.PI / 180);
                    GL.Vertex3(xx, yy, zz);
                }
            }
            GL.End();
        }
  • Render()中调用DrawSphere()

准备las点云文件导入环境(laszip)

添加类库(联网)

  • 工具,NuGet包管理器,管理解决方案的NuGet程序包
  • 搜索laszip
  • 安装Unofficial.laszip.net(2.2.0版本)
  • 添加名称空间laszip.net
using laszip.net;

读取las文件代码

  • Form1窗体中文件菜单的子菜单“打开”,添加子菜单“las点云文件”三维可视化软件开发——点云的打开与八叉树管理_第4张图片
  • 跳转到las点云文件ToolStripMenuItem_Click(object sender, EventArgs e)函数,函数内添加代码
			//新建一个文件对话框
            OpenFileDialog pOpenFileDialog = new OpenFileDialog();

            //设置对话框标题
            pOpenFileDialog.Title = "打开las点云文件";

            //设置打开文件类型
            pOpenFileDialog.Filter = "las文件(*.las)|*.las";

            //监测文件是否存在
            pOpenFileDialog.CheckFileExists = true;

            //文件打开后执行以下程序
            if (pOpenFileDialog.ShowDialog() == DialogResult.OK)
            {
                //MessageBox.Show(pOpenFileDialog.FileName);
                points = ReadLas(pOpenFileDialog.FileName);//传入文件路径,读出点云文件
                Invalidate();
            }
  • 创建成员变量points
        List<Vector3d> points;
  • 创建ReadLas(string fileName)函数
		private List<Vector3d> ReadLas(string fileName)
        {
            var lazReader = new laszip_dll();
            var compressed = true;
            lazReader.laszip_open_reader(fileName, ref compressed); //FileName要给定
            var numberOfPoints = lazReader.header.number_of_point_records;
            
            // las文件中三维点的范围
            double minx = lazReader.header.min_x;
            double miny = lazReader.header.min_y;
            double minz = lazReader.header.min_z;
            double maxx = lazReader.header.max_x;
            double maxy = lazReader.header.max_y;
            double maxz = lazReader.header.max_z;

            double centx = (minx + maxx) / 2;
            double centy = (miny + maxy) / 2;
            double centz = (minz + maxz) / 2;

            double scale = Math.Max(Math.Max(maxx - minx, maxy - miny), (maxz - minz));

            int classification = 0;
            var coordArray = new double[3];//自己考虑是否需要double float
            Vector3d point = new Vector3d();//vector3d是double vector is float
            List<Vector3d> points = new List<Vector3d>((int)numberOfPoints);

            //循环读取每个点
            for (int pointIndex = 0; pointIndex < numberOfPoints; pointIndex++)
            {
                // 读点
                lazReader.laszip_read_point();
                // 得到坐标值
                lazReader.laszip_get_coordinates(coordArray);

                point.X = (coordArray[0] - centx) / scale; //归一化
                point.Y = (coordArray[1] - centy) / scale;
                point.Z = (coordArray[2] - centz) / scale;

                points.Add(point);
                classification = lazReader.point.classification;
            }
            // 关闭
            lazReader.laszip_close_reader();

            return points;
        }

绘制点云

  • 创建DrawPointCloud()函数
        private void DrawPointCloud()
        {
            if (points == null)
                return;

            GL.Begin(PrimitiveType.Points);

            foreach(var v in points)
            {
                GL.Color3(1.0, 1.0, 1.0);
                GL.Vertex3(v.X, v.Y, v.Z);
            }

            GL.End();
        }
  • Render()中调用DrawPointCloud()

点云显示进阶

点云着色

  • 定义类成员变量colors
        List<Vector3d> colors;
  • 修改DrawPointCloud()函数
        private void DrawPointClout()
        {
            if (points == null)
                return;

            GL.Begin(PrimitiveType.Points);

            for (int i = 0; i < points.Count; i++)
            {
                Vector3d cr = colors[i];
                Vector3d v = points[i];

                GL.Color3(cr.X, cr.Y, cr.Z);
                GL.Vertex3(v.X, v.Y, v.Z);
            }

            GL.End();
        }
  • 修改ReadLas函数
       private List<Vector3d> ReadLas(string fileName)
        {
            var lazReader = new laszip_dll();
            var compressed = true;
            lazReader.laszip_open_reader(fileName, ref compressed); //FileName要给定
            var numberOfPoints = lazReader.header.number_of_point_records;
            
            // las文件中三维点的范围
            double minx = lazReader.header.min_x;
            double miny = lazReader.header.min_y;
            double minz = lazReader.header.min_z;
            double maxx = lazReader.header.max_x;
            double maxy = lazReader.header.max_y;
            double maxz = lazReader.header.max_z;

            double centx = (minx + maxx) / 2;
            double centy = (miny + maxy) / 2;
            double centz = (minz + maxz) / 2;

            double scale = Math.Max(Math.Max(maxx - minx, maxy - miny), (maxz - minz));

            int classification = 0;
            var coordArray = new double[3];//自己考虑是否需要double float
            Vector3d point = new Vector3d();//vector3d是double vector is float
            Vector3d cr = new Vector3d(); //着色
            List<Vector3d> points = new List<Vector3d>((int)numberOfPoints);

            colors = new List<Vector3d>((int)numberOfPoints); //着色

            //定义最高点和最低点的颜色
            Vector3d cr1 = new Vector3d(1, 0, 0);
            Vector3d cr2 = new Vector3d(0, 1, 1);

            //循环读取每个点
            for (int pointIndex = 0; pointIndex < numberOfPoints; pointIndex++)
            {
                // 读点
                lazReader.laszip_read_point();
                // 得到坐标值
                lazReader.laszip_get_coordinates(coordArray);

                //每个点根据坐标着色
                cr.X = (coordArray[2] - minz) / (maxz - minz) * (cr2.X - cr1.X) + cr1.X;
                cr.Y = (coordArray[2] - minz) / (maxz - minz) * (cr2.Y - cr1.Y) + cr1.Y;
                cr.Z = (coordArray[2] - minz) / (maxz - minz) * (cr2.Z - cr1.Z) + cr1.Z;
                colors.Add(cr);

                point.X = (coordArray[0] - centx) / scale; //归一化
                point.Y = (coordArray[1] - centy) / scale;
                point.Z = (coordArray[2] - centz) / scale;

                points.Add(point);
                classification = lazReader.point.classification;
            }
            // 关闭
            lazReader.laszip_close_reader();

            return points;
        }
  • 将点云渲染为彩虹色
private PointCloudOctree ReadLas(string fileName)
        {
            var lazReader = new laszip_dll();
            var compressed = true;
            lazReader.laszip_open_reader(fileName, ref compressed); //FileName要给定
            var numberOfPoints = lazReader.header.number_of_point_records;

            // las文件中三维点的范围
            double minx = lazReader.header.min_x;
            double miny = lazReader.header.min_y;
            double minz = lazReader.header.min_z;
            double maxx = lazReader.header.max_x;
            double maxy = lazReader.header.max_y;
            double maxz = lazReader.header.max_z;

            double centx = (minx + maxx) / 2;
            double centy = (miny + maxy) / 2;
            double centz = (minz + maxz) / 2;

            double scale = Math.Max(Math.Max(maxx - minx, maxy - miny), (maxz - minz));

            int classification = 0;
            var coordArray = new double[3];//自己考虑是否需要double float
            Vector3d point = new Vector3d();//vector3d是double vector is float
            Vector3d cr = new Vector3d(); //着色
            List<Vector3d> points = new List<Vector3d>((int)numberOfPoints);

            colors = new List<Vector3d>((int)numberOfPoints); //着色

            //彩虹色
            Vector3d cr_red = new Vector3d(1, 0, 0);
            Vector3d cr_orange = new Vector3d(1, 0.647, 0);
            Vector3d cr_yellow = new Vector3d(1, 1, 0);
            Vector3d cr_green = new Vector3d(0, 1, 0);
            Vector3d cr_cyan = new Vector3d(0, 0.498, 1);
            Vector3d cr_bule = new Vector3d(0, 0, 1);
            Vector3d cr_purple = new Vector3d(0.545, 0, 1);

            //6等分z轴,用于插值颜色
            double z_1_6 = 1 * (maxz - minz) / 6 + minz;
            double z_2_6 = 2 * (maxz - minz) / 6 + minz;
            double z_3_6 = 3 * (maxz - minz) / 6 + minz;
            double z_4_6 = 4 * (maxz - minz) / 6 + minz;
            double z_5_6 = 5 * (maxz - minz) / 6 + minz;

            //循环读取每个点
            for (int pointIndex = 0; pointIndex < numberOfPoints; pointIndex++)
            {
                // 读点
                lazReader.laszip_read_point();
                // 得到坐标值
                lazReader.laszip_get_coordinates(coordArray);

                //每个点根据坐标着色
                cr.X = (coordArray[2] - minz) / (maxz - minz) * (cr2.X - cr1.X) + cr1.X;
                cr.Y = (coordArray[2] - minz) / (maxz - minz) * (cr2.Y - cr1.Y) + cr1.Y;
                cr.Z = (coordArray[2] - minz) / (maxz - minz) * (cr2.Z - cr1.Z) + cr1.Z;
                colors.Add(cr);

                point.X = (coordArray[0] - centx) / scale; //归一化
                point.Y = (coordArray[1] - centy) / scale;
                point.Z = (coordArray[2] - centz) / scale;

                points.Add(point);
                classification = lazReader.point.classification;
            }
            // 关闭
            lazReader.laszip_close_reader();

            return points;
        }

点云管理(八叉树)

  • 定义类成员变量pco并删除points和colors(所有点都应该放在八叉树里)
        PointCloudOctree pco;//假定有八叉树对象 pco类成员变量
  • las点云文件ToolStripMenuItem_Click(object sender, EventArgs e)函数中,ReadLas(pOpenFileDialog.FileName)返回值为pco
                pco = ReadLas(pOpenFileDialog.FileName);//传入文件路径,读出点云文件
  • 更改Render()中的绘图函数(八叉树应该能把自己画出来)
            if (pco != null)
            {
                pco.Render();//现在还没有,八叉树自己的绘图函数
            }
  • 项目添加类PointCloudOctree
  • PointCloudOctree.cs中创建类PointCloudNode
  • PointCloudOctree.cs中创建结构体ColorPoint
    struct ColorPoint //每个点的坐标和颜色是联系的,都是点的属性
    {
        public Vector3d point;
        public Vector3d color;
    }
  • PointCloudOctree.cs中添加名称空间
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using OpenTK.Platform;
  • 在PointCloudNode中创建类成员变量data,并定义子树
        List<ColorPoint> data;
        PointCloudNode[] child;//将子树定义为数组,不需要写8个名字
  • PointCloudOctree中创建根结点root
        PointCloudNode root;//根节点
  • 构造函数创建八叉树
        public PointCloudOctree(List<ColorPoint> data,
            Vector3d minv, Vector3d maxv)/*构造函数创建类,函数名和类名应该相同 
                                         * 参数1:Las文件读出的点 参数2:最小的坐标值 参数3:最大坐标值*/
        {

        }
  • 在Form.cs中把函数需要的输入参数准备好
  • 修改ReadLas函数返回类型为八叉树
  • ReadLas中定义ColorPoint类型变量color_point,删除colors和cr
            ColorPoint color_point = new ColorPoint();
  • 将着色和归一化的变量改为color_point,不再需要添加cr,points改为添加color_point
               //每个点根据坐标着色
                if (coordArray[2] <= z_1_6)
                {
                    color_point.color.X = (coordArray[2] - minz) / (maxz - minz) * (cr_orange.X - cr_red.X) + cr_red.X;
                    color_point.color.Y = (coordArray[2] - minz) / (maxz - minz) * (cr_orange.Y - cr_red.Y) + cr_red.Y;
                    color_point.color.Z = (coordArray[2] - minz) / (maxz - minz) * (cr_orange.Z - cr_red.Z) + cr_red.Z;
                }
                else if (coordArray[2] <= z_2_6)
                {
                    color_point.color.X = (coordArray[2] - minz) / (maxz - minz) * (cr_yellow.X - cr_orange.X) + cr_orange.X;
                    color_point.color.Y = (coordArray[2] - minz) / (maxz - minz) * (cr_yellow.Y - cr_orange.Y) + cr_orange.Y;
                    color_point.color.Z = (coordArray[2] - minz) / (maxz - minz) * (cr_yellow.Z - cr_orange.Z) + cr_orange.Z;
                }
                else if(coordArray[2] <= z_3_6)
                {
                    color_point.color.X = (coordArray[2] - minz) / (maxz - minz) * (cr_green.X - cr_yellow.X) + cr_yellow.X;
                    color_point.color.Y = (coordArray[2] - minz) / (maxz - minz) * (cr_green.Y - cr_yellow.Y) + cr_yellow.Y;
                    color_point.color.Z = (coordArray[2] - minz) / (maxz - minz) * (cr_green.Z - cr_yellow.Z) + cr_yellow.Z;
                }
                else if (coordArray[2] <= z_4_6)
                {
                    color_point.color.X = (coordArray[2] - minz) / (maxz - minz) * (cr_cyan.X - cr_green.X) + cr_green.X;
                    color_point.color.Y = (coordArray[2] - minz) / (maxz - minz) * (cr_cyan.Y - cr_green.Y) + cr_green.Y;
                    color_point.color.Z = (coordArray[2] - minz) / (maxz - minz) * (cr_cyan.Z - cr_green.Z) + cr_green.Z;
                }
                else if (coordArray[2] <= z_5_6)
                {
                    color_point.color.X = (coordArray[2] - minz) / (maxz - minz) * (cr_bule.X - cr_cyan.X) + cr_cyan.X;
                    color_point.color.Y = (coordArray[2] - minz) / (maxz - minz) * (cr_bule.Y - cr_cyan.Y) + cr_cyan.Y;
                    color_point.color.Z = (coordArray[2] - minz) / (maxz - minz) * (cr_bule.Z - cr_cyan.Z) + cr_cyan.Z;
                }
                else
                {
                    color_point.color.X = (coordArray[2] - minz) / (maxz - minz) * (cr_purple.X - cr_bule.X) + cr_bule.X;
                    color_point.color.Y = (coordArray[2] - minz) / (maxz - minz) * (cr_purple.Y - cr_bule.Y) + cr_bule.Y;
                    color_point.color.Z = (coordArray[2] - minz) / (maxz - minz) * (cr_purple.Z - cr_bule.Z) + cr_bule.Z;
                }

                color_point.point.X = (coordArray[0] - centx) / scale; //归一化
                color_point.point.Y = (coordArray[1] - centy) / scale;
                color_point.point.Z = (coordArray[2] - centz) / scale;

                points.Add(color_point);
  • 修改points类型为ColorPoint
            List<ColorPoint> points = new List<ColorPoint>((int)numberOfPoints);
  • ReadLas读点后添加变量minv和maxv
            Vector3d minv;
            minv.X = (minx - centx) / scale;
            minv.Y = (miny - centy) / scale;
            minv.Z = (minz - centz) / scale;

            Vector3d maxv;
            maxv.X = (maxx - centx) / scale;
            maxv.Y = (maxy - centy) / scale;
            maxv.Z = (maxz - centz) / scale;
  • ReadLas中创建八叉树p,并将函数返回值改为p
            PointCloudOctree p = new PointCloudOctree(points, minv, maxv);

            return p;
  • ReadLas中删除point
  • 删除DrawPointCloud函数
  • PointCloudOctree类中创建属性Count和Render函数
        public int Count
        {
            get { return 0; }
        }

        public void Render()
        {

        }
  • PointCloudNode类中构造函数创建节点
        public PointCloudNode(List<ColorPoint> data, Vector3d minv, Vector3d maxv)
        {
            //先判断传入参数是否有效
            if (data == null)
                return;
            if (data.Count == 0)
                return;
        }
  • PointCloudOctree函数中创建根节点
            if (data == null)
                return;
            if (data.Count == 0)
                return;

            root = new PointCloudNode(data, minv, maxv);//创建八叉树就是创建根节点
  • PointCloudNode函数中定义常量控制节点内点的数量
        public const int max_point_num = 10000;
  • PointCloudNode函数中,如果节点内点的数量小于阈值,不必再分,否则,再次分割节点
            //判断点的数量是否超过阈值
            if (data.Count < max_point_num)
                this.data = data;//后一个是参数,前一个是成员变量
            else
            {
                this.data = null;
                child = new PointCloudNode[8];
            }
  • else中,创建孩子节点的点数据
                List<ColorPoint>[] childData = new List<ColorPoint>[8];
                for (int i = 0; i < 8; i++)
                    childData[i] = new List<ColorPoint>();
  • else中,定义孩子节点坐标最值和切分坐标
                //孩子节点坐标最值
                Vector3d[] minva = new Vector3d[8];
                Vector3d[] maxva = new Vector3d[8];

                //切分坐标
                Vector3d split = (minv + maxv) / 2;
  • else中,切分根节点并标号
				//将根节点切分为8块,分别标号12345678
                foreach (var v in data)
                {
                    if (v.point.Z > split.Z)//1234节点
                    {
                        if (v.point.Y > split.Y)//12
                        {
                            if (v.point.X > split.X)//1
                            {
                                childData[0].Add(v);
                            }
                            else//2
                            {
                                childData[1].Add(v);
                            }
                        }
                        else//34
                        {
                            if (v.point.X > split.X)//3
                            {
                                childData[2].Add(v);
                            }
                            else//4
                            {
                                childData[3].Add(v);
                            }
                        }
                    }
                    else//5678节点
                    {
                        if (v.point.Y > split.Y)//56
                        {
                            if (v.point.X > split.X)//5
                            {
                                childData[4].Add(v);
                            }
                            else//6
                            {
                                childData[5].Add(v);
                            }
                        }
                        else//78
                        {
                            if (v.point.X > split.X)//7
                            {
                                childData[6].Add(v);
                            }
                            else//8
                            {
                                childData[7].Add(v);
                            }
                        }
                    }
                }
  • else中,确定每个子节点的坐标最值
                //重复八次,自己确定最值
                minva[0].X = split.X; minva[0].Y = split.Y; minva[0].Z = split.Z;
                maxva[0].X = maxv.X;  maxva[0].Y = maxv.Y;  maxva[0].Z = maxv.Z;

                minva[1].X = split.X; minva[1].Y = split.Y; minva[1].Z = split.Z;
                maxva[1].X = split.X; maxva[1].Y = maxv.Y;  maxva[1].Z = maxv.Z;

                minva[2].X = split.X; minva[2].Y = minv.Y;  minva[2].Z = split.Z;
                maxva[2].X = maxv.X;  maxva[2].Y = split.Y; maxva[2].Z = maxv.Z;
                
                minva[3].X = minv.X;  minva[3].Y = minv.Y;  minva[3].Z = split.Z;
                maxva[3].X = split.X; maxva[3].Y = split.Y; maxva[3].Z = maxv.Z;

                minva[4].X = split.X; minva[4].Y = split.Y; minva[4].Z = minv.Z;
                maxva[4].X = maxv.X;  maxva[4].Y = maxv.Y;  maxva[4].Z = split.Z;
                
                minva[5].X = minv.X;  minva[5].Y = split.Y; minva[5].Z = minv.Z;
                maxva[5].X = split.X; maxva[5].Y = maxv.Y;  maxva[5].Z = split.Z;
                
                minva[6].X = split.X; minva[6].Y = minv.Y;  minva[6].Z = minv.Z;
                maxva[6].X = maxv.X;  maxva[6].Y = split.Y; maxva[6].Z = split.Z;

                minva[7].X = minv.X;  minva[7].Y = minv.Y;  minva[7].Z = minv.Z;
                maxva[7].X = split.X; maxva[7].Y = split.Y; maxva[7].Z = split.Z;
  • else中,将每个孩子节点定义为新的根节点
                for (int i = 0; i < 8; i++)
                {
                    if (childData[i].Count <= 0)
                        continue;
                    child[i] = new PointCloudNode(childData[i], minva[i], maxva[i]);
                }
  • PointCloudNode类中,定义Render函数
        public void Render()
        {
            if (data != null)
            {
                //画点
                GL.Begin(PrimitiveType.Points);
                foreach (var v in data)
                {
                    GL.Color3(v.color.X, v.color.Y, v.color.Z);
                    GL.Vertex3(v.point.X, v.point.Y, v.point.Z);
                }
                GL.End();
            }

            if (child != null)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (child[i] != null)
                    {
                        child[i].Render();
                    }
                }
            }
        }
  • 完善PointCloudOctree类中Render函数
            if (root != null)
                root.Render()

显示加速

显示列表
  • PointCloudNode中定义类成员变量
        int iShowListNum;//显示列表
  • PointCloudNode函数中
			if (data.Count < max_point_num)
            {
                this.data = data;//后一个是参数,前一个是成员变量

                //创建显示列表
                iShowListNum = GL.GenLists(1);
                GL.NewList(iShowListNum, ListMode.Compile);
                GL.Begin(PrimitiveType.Points);
                for (int i = 0; i < data.Count; i++)
                {
                    ColorPoint v = data[i];

                    GL.Color3(v.color.X, v.color.Y, v.color.Z);
                    GL.Vertex3(v.point.X, v.point.Y, v.point.Z);
                }
                GL.End();
                GL.EndList();
            }
  • PointCloudNode的Render函数中
if (data != null)
            {
                //调用显示列表
                GL.CallList(iShowListNum);
            }
视景体切割
  • Form中定义类成员变量
        double[,] mFrustum = new double[6, 4];//视景体
  • 添加函数CalculateFrustum和NormalizePlane
		public void CalculateFrustum()
        {
            Matrix4 projectionMatrix = new Matrix4();
            GL.GetFloat(GetPName.ProjectionMatrix, out projectionMatrix);
            Matrix4 modelViewMatrix = new Matrix4();
            GL.GetFloat(GetPName.ModelviewMatrix, out modelViewMatrix);

            float[] _clipMatrix = new float[16];
            const int RIGHT = 0, LEFT = 1, BOTTOM = 2, TOP = 3, BACK = 4, FRONT = 5;

            _clipMatrix[0] = (modelViewMatrix.M11 * projectionMatrix.M11)
                + (modelViewMatrix.M12 * projectionMatrix.M21) + (modelViewMatrix.M13 * projectionMatrix.M31)
                + (modelViewMatrix.M14 * projectionMatrix.M41);
            _clipMatrix[1] = (modelViewMatrix.M11 * projectionMatrix.M12)
                + (modelViewMatrix.M12 * projectionMatrix.M22) + (modelViewMatrix.M13 * projectionMatrix.M32)
                + (modelViewMatrix.M14 * projectionMatrix.M42);
            _clipMatrix[2] = (modelViewMatrix.M11 * projectionMatrix.M13)
                + (modelViewMatrix.M12 * projectionMatrix.M23) + (modelViewMatrix.M13 * projectionMatrix.M33)
                + (modelViewMatrix.M14 * projectionMatrix.M43);
            _clipMatrix[3] = (modelViewMatrix.M11 * projectionMatrix.M14)
                + (modelViewMatrix.M12 * projectionMatrix.M24) + (modelViewMatrix.M13 * projectionMatrix.M34)
                + (modelViewMatrix.M14 * projectionMatrix.M44);

            _clipMatrix[4] = (modelViewMatrix.M21 * projectionMatrix.M11)
                + (modelViewMatrix.M22 * projectionMatrix.M21) + (modelViewMatrix.M23 * projectionMatrix.M31)
                + (modelViewMatrix.M24 * projectionMatrix.M41);
            _clipMatrix[5] = (modelViewMatrix.M21 * projectionMatrix.M12)
                + (modelViewMatrix.M22 * projectionMatrix.M22) + (modelViewMatrix.M23 * projectionMatrix.M32)
                + (modelViewMatrix.M24 * projectionMatrix.M42);
            _clipMatrix[6] = (modelViewMatrix.M21 * projectionMatrix.M13)
                + (modelViewMatrix.M22 * projectionMatrix.M23) + (modelViewMatrix.M23 * projectionMatrix.M33)
                + (modelViewMatrix.M24 * projectionMatrix.M43);
            _clipMatrix[7] = (modelViewMatrix.M21 * projectionMatrix.M14)
                + (modelViewMatrix.M22 * projectionMatrix.M24) + (modelViewMatrix.M23 * projectionMatrix.M34)
                + (modelViewMatrix.M24 * projectionMatrix.M44);

            _clipMatrix[8] = (modelViewMatrix.M31 * projectionMatrix.M11)
                + (modelViewMatrix.M32 * projectionMatrix.M21) + (modelViewMatrix.M33 * projectionMatrix.M31)
                + (modelViewMatrix.M34 * projectionMatrix.M41);
            _clipMatrix[9] = (modelViewMatrix.M31 * projectionMatrix.M12)
                + (modelViewMatrix.M32 * projectionMatrix.M22) + (modelViewMatrix.M33 * projectionMatrix.M32)
                + (modelViewMatrix.M34 * projectionMatrix.M42);
            _clipMatrix[10] = (modelViewMatrix.M31 * projectionMatrix.M13)
                + (modelViewMatrix.M32 * projectionMatrix.M23) + (modelViewMatrix.M33 * projectionMatrix.M33)
                + (modelViewMatrix.M34 * projectionMatrix.M43);
            _clipMatrix[11] = (modelViewMatrix.M31 * projectionMatrix.M14)
                + (modelViewMatrix.M32 * projectionMatrix.M24) + (modelViewMatrix.M33 * projectionMatrix.M34)
                + (modelViewMatrix.M34 * projectionMatrix.M44);

            _clipMatrix[12] = (modelViewMatrix.M41 * projectionMatrix.M11)
                + (modelViewMatrix.M42 * projectionMatrix.M21) + (modelViewMatrix.M43 * projectionMatrix.M31)
                + (modelViewMatrix.M44 * projectionMatrix.M41);
            _clipMatrix[13] = (modelViewMatrix.M41 * projectionMatrix.M12)
                + (modelViewMatrix.M42 * projectionMatrix.M22) + (modelViewMatrix.M43 * projectionMatrix.M32)
                + (modelViewMatrix.M44 * projectionMatrix.M42);
            _clipMatrix[14] = (modelViewMatrix.M41 * projectionMatrix.M13)
                + (modelViewMatrix.M42 * projectionMatrix.M23) + (modelViewMatrix.M43 * projectionMatrix.M33)
                + (modelViewMatrix.M44 * projectionMatrix.M43);
            _clipMatrix[15] = (modelViewMatrix.M41 * projectionMatrix.M14)
                + (modelViewMatrix.M42 * projectionMatrix.M24) + (modelViewMatrix.M43 * projectionMatrix.M34)
                + (modelViewMatrix.M44 * projectionMatrix.M44);

            mFrustum[RIGHT, 0] = _clipMatrix[3] - _clipMatrix[0];
            mFrustum[RIGHT, 1] = _clipMatrix[7] - _clipMatrix[4];
            mFrustum[RIGHT, 2] = _clipMatrix[11] - _clipMatrix[8];
            mFrustum[RIGHT, 3] = _clipMatrix[15] - _clipMatrix[12];
            NormalizePlane(mFrustum, RIGHT);

            mFrustum[LEFT, 0] = _clipMatrix[3] + _clipMatrix[0];
            mFrustum[LEFT, 1] = _clipMatrix[7] + _clipMatrix[4];
            mFrustum[LEFT, 2] = _clipMatrix[11] + _clipMatrix[8];
            mFrustum[LEFT, 3] = _clipMatrix[15] + _clipMatrix[12];
            NormalizePlane(mFrustum, LEFT);

            mFrustum[BOTTOM, 0] = _clipMatrix[3] + _clipMatrix[1];
            mFrustum[BOTTOM, 1] = _clipMatrix[7] + _clipMatrix[5];
            mFrustum[BOTTOM, 2] = _clipMatrix[11] + _clipMatrix[9];
            mFrustum[BOTTOM, 3] = _clipMatrix[15] + _clipMatrix[13];
            NormalizePlane(mFrustum, BOTTOM);

            mFrustum[TOP, 0] = _clipMatrix[3] - _clipMatrix[1];
            mFrustum[TOP, 1] = _clipMatrix[7] - _clipMatrix[5];
            mFrustum[TOP, 2] = _clipMatrix[11] - _clipMatrix[9];
            mFrustum[TOP, 3] = _clipMatrix[15] - _clipMatrix[13];
            NormalizePlane(mFrustum, TOP);

            mFrustum[BACK, 0] = _clipMatrix[3] - _clipMatrix[2];
            mFrustum[BACK, 1] = _clipMatrix[7] - _clipMatrix[6];
            mFrustum[BACK, 2] = _clipMatrix[11] - _clipMatrix[10];
            mFrustum[BACK, 3] = _clipMatrix[15] - _clipMatrix[14];
            NormalizePlane(mFrustum, BACK);

            mFrustum[FRONT, 0] = _clipMatrix[3] + _clipMatrix[2];
            mFrustum[FRONT, 1] = _clipMatrix[7] + _clipMatrix[6];
            mFrustum[FRONT, 2] = _clipMatrix[11] + _clipMatrix[10];
            mFrustum[FRONT, 3] = _clipMatrix[15] + _clipMatrix[14];
            NormalizePlane(mFrustum, FRONT);
        }
        private void NormalizePlane(double[,] frustum, int side)
        {
            double magnitude = Math.Sqrt((frustum[side, 0] * frustum[side, 0]) +
           (frustum[side, 1] * frustum[side, 1]) + (frustum[side, 2] * frustum[side, 2]));
            frustum[side, 0] /= magnitude;
            frustum[side, 1] /= magnitude;
            frustum[side, 2] /= magnitude;
            frustum[side, 3] /= magnitude;
        }
  • Form1的Render函数中调用CalculateFrustum
  • PointCloudNode中添加类成员变量
        Vector3d min_coordinate, max_coordinate;//坐标最值

        bool OutlineInFrustum;//包围核是否在视景体内
  • PointCloudNode函数中
            if (data.Count < max_point_num)
                min_coordinate = minv;
                max_coordinate = maxv;
  • PointCloudNode函数的Render中
            OutlineInFrustum = VoxelWithinFrustum(frustum, min_coordinate.X, min_coordinate.Y, min_coordinate.Z,
                max_coordinate.X, max_coordinate.Y, max_coordinate.Z);
            if (!OutlineInFrustum)
                return;
  • PointCloudNode中添加函数
        bool VoxelWithinFrustum(double[,] ftum, double minx, double miny, double minz,
            double maxx, double maxy, double maxz)
        {
            double x1 = minx, y1 = miny, z1 = minz;
            double x2 = maxx, y2 = maxy, z2 = maxz;
            for (int i = 0; i < 6; i++)
            {
                if ((ftum[i, 0] * x1 + ftum[i, 1] * y1 + ftum[i, 2] * z1 + ftum[i, 3] <= 0.0F) &&
                (ftum[i, 0] * x2 + ftum[i, 1] * y1 + ftum[i, 2] * z1 + ftum[i, 3] <= 0.0F) &&
                (ftum[i, 0] * x1 + ftum[i, 1] * y2 + ftum[i, 2] * z1 + ftum[i, 3] <= 0.0F) &&
                (ftum[i, 0] * x2 + ftum[i, 1] * y2 + ftum[i, 2] * z1 + ftum[i, 3] <= 0.0F) &&
                (ftum[i, 0] * x1 + ftum[i, 1] * y1 + ftum[i, 2] * z2 + ftum[i, 3] <= 0.0F) &&
                (ftum[i, 0] * x2 + ftum[i, 1] * y1 + ftum[i, 2] * z2 + ftum[i, 3] <= 0.0F) &&
                (ftum[i, 0] * x1 + ftum[i, 1] * y2 + ftum[i, 2] * z2 + ftum[i, 3] <= 0.0F) &&
                (ftum[i, 0] * x2 + ftum[i, 1] * y2 + ftum[i, 2] * z2 + ftum[i, 3] <= 0.0F))
                {
                    return false;
                }
            }
            return true;
        }
  • 修改窗体Render函数,增加mFrustum参数
            if (pco != null)
            {
                pco.Render(mFrustum);//现在还没有,八叉树自己的绘图函数
            }
  • 修改PointCloudOctree的Render函数
        public void Render(double[,] frustum)
        {
            if (root != null)
                root.Render(frustum);
        }
  • 修改PointCloudNode函数,增加double[,] frustum参数
        public void Render(double[,] frustum)
  • 修改孩子节点Render函数
                        child[i].Render(frustum);

鼠标选点

  • 控件添加MouseDoubleClick事件
        private void glControl1_MouseDoubleClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            Point ptClicked = e.Location;
            //射线选择
            Vector3d winxyz;
            winxyz.X = ptClicked.X;
            winxyz.Y = ptClicked.Y;
            winxyz.Z = 0.0f;
            Vector3d nearPoint = new Vector3d(0, 0, 0);
            UnProject(winxyz, ref nearPoint);
            winxyz.Z = 1.0f;
            Vector3d farPoint = new Vector3d(0, 0, 0);
            UnProject(winxyz, ref farPoint);

            Vector3d line;
            line = farPoint - nearPoint;

            Point3DExt close_point = new Point3DExt();
            close_point.flag = 10000;

            if (pco != null)
            {
                pco.FindClosestPoint(mFrustum, nearPoint, farPoint, ref close_point);
                MessageBox.Show("选中点坐标为:\nX坐标:" + close_point.point.X
                        + "\nY坐标:" + close_point.point.Y + "\nZ坐标:" + close_point.point.Z);
                
                Render();
                Invalidate();
                glControl1.Invalidate();
            }
        }
  • 添加函数UnProject、UnProject和like_gluUnProject
        int UnProject(Vector3d win, ref Vector3d obj)
        {
            Matrix4d modelMatrix;
            GL.GetDouble(GetPName.ModelviewMatrix, out modelMatrix);
            Matrix4d projMatrix;
            GL.GetDouble(GetPName.ProjectionMatrix, out projMatrix);
            int[] viewport = new int[4];
            GL.GetInteger(GetPName.Viewport, viewport);
            return UnProject(win, modelMatrix, projMatrix, viewport, ref obj);
        }
        int UnProject(Vector3d win, Matrix4d modelMatrix, Matrix4d projMatrix, int[] viewport, ref Vector3d obj)
        {
            return like_gluUnProject(win.X, win.Y, win.Z, modelMatrix, projMatrix,
            viewport, ref obj.X, ref obj.Y, ref obj.Z);
        }
        int like_gluUnProject(double winx, double winy, double winz,
            Matrix4d modelMatrix, Matrix4d projMatrix, int[] viewport,
            ref double objx, ref double objy, ref double objz)
        {
            Matrix4d finalMatrix;
            Vector4d _in;
            Vector4d _out;
            finalMatrix = Matrix4d.Mult(modelMatrix, projMatrix);
            finalMatrix.Invert();
            _in.X = winx;
            _in.Y = viewport[3] - winy;
            _in.Z = winz;
            _in.W = 1.0f;
            // Map x and y from window coordinates
            _in.X = (_in.X - viewport[0]) / viewport[2];
            _in.Y = (_in.Y - viewport[1]) / viewport[3];
            // Map to range -1 to 1
            _in.X = _in.X * 2 - 1;
            _in.Y = _in.Y * 2 - 1;
            _in.Z = _in.Z * 2 - 1;
            //__gluMultMatrixVecd(finalMatrix, _in, _out);
            // check if this works:
            _out = Vector4d.Transform(_in, finalMatrix);
            if (_out.W == 0.0)
                return (0);
            _out.X /= _out.W;
            _out.Y /= _out.W;
            _out.Z /= _out.W;
            objx = _out.X;
            objy = _out.Y;
            objz = _out.Z;
            return (1);
        }
  • 添加结构体
    struct Point3DExt
    {
        public Vector3d point;
        public double flag;
    }
  • PointCloudOctree中添加函数FindClosestPoint
        //找最近点
        public void FindClosestPoint(double[,] frustum, Vector3d near_point, Vector3d far_point,
            ref Point3DExt closest_point)
        {
            if (root != null)
                root.FindClosestPoint(frustum, near_point, far_point, ref closest_point);
        }
  • PointCloudNode中添加函数FindClosestPoint。注意,节点调用的任何函数,都应在孩子节点中递归调用
        public void FindClosestPoint(double[,] frustum, Vector3d near_point, Vector3d far_point,
            ref Point3DExt closest_point)
        {
            if (!OutlineInFrustum)
                return;

            Vector3d line;
            line = far_point - near_point;

            //本节点里查找
            if (data != null)
            {
                //Vector3d v, vcross;
                double distance;

                for (int i = 0; i < data.Count; i++)
                {
                    distance = CalculateDistance(data[i].point, far_point, near_point);

                    if (closest_point.flag > distance)
                    {
                        closest_point.point = data[i].point;
                        closest_point.flag = distance;
                    }
                }
            }
            if (child != null)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (child[i] != null)
                    {
                        child[i].FindClosestPoint(frustum, near_point, far_point, ref closest_point);
                    }
                }
            }
        }
  • 根据海伦公式求点到线段距离,添加相关函数
        //求三角形面积
        private double CalculateSquare(double lenght1, double lenght2, double lenght3)
        {
            double s;

            double half_circumference = (lenght1 + lenght2 + lenght3) / 2;
            double ss = half_circumference * (half_circumference - lenght1)
                * (half_circumference - lenght2) * (half_circumference - lenght3);
            s = Math.Sqrt(ss);

            return s;
        }
        //求两点间距离
        private double CalculateLength(Vector3d point1, Vector3d point2)
        {
            double x = 100 * (point1.X - point2.X);
            double y = 100 * (point1.Y - point2.Y);
            double z = 100 * (point1.Z - point2.Z);

            double ll = x * x + y * y + z * z;
            double l = Math.Sqrt(ll);

            return l;
        }
        //求点到线段距离
        private double CalculateDistance(Vector3d now_point, Vector3d far_point, Vector3d near_point)
        {
            double now_far_distance = CalculateLength(now_point, far_point);
            double now_near_distance = CalculateLength(now_point, near_point);
            double near_far_distance = CalculateLength(near_point, far_point);

            double triangle_square = CalculateSquare(now_far_distance, now_near_distance, near_far_distance);

            double distance = (2 * triangle_square / 10000) / (near_far_distance / 100);

            return distance;
        }

扩展功能

计算两点间距离

  • Form窗体中,添加GroupBox,修改Text为“扩展功能”
  • GroupBox中,添加CheckBox,修改Text为“计算两点间距离”
    三维可视化软件开发——点云的打开与八叉树管理_第5张图片
  • 双击这个CheckBox,跳转到相应函数,添加代码
            if (calculate_distance_between_points)
                calculate_distance_between_points = false;
            else
                calculate_distance_between_points = true;
  • 添加类成员变量
        bool calculate_distance_between_points = false;//是否计算两点距离
        List<Point3DExt> two_points = new List<Point3DExt>(2);//长度为2,存放要求距离的点
        int num_two_points = -1;//判断选中的点是第几个
  • 修改glControl1_MouseDoubleClick函数
            if (pco != null)
            {
                pco.FindClosestPoint(mFrustum, nearPoint, farPoint, ref close_point);
                if (!calculate_distance_between_points)
                    MessageBox.Show("选中点坐标为:\nX坐标:" + close_point.point.X
                        + "\nY坐标:" + close_point.point.Y + "\nZ坐标:" + close_point.point.Z);
                else
                {
                    if (num_two_points > 0)
                    {
                        num_two_points = 0;
                        two_points.Clear();
                    }
                    else
                        num_two_points += 1;

                    two_points.Add(close_point);
                    if (num_two_points == 1)
                    {
                        double dis_points = calculateDistance(two_points[0], two_points[1]);
                        MessageBox.Show("选取的两点坐标为:\n"
                            + coordinate2string(two_points[0].point) + "\n"
                            + coordinate2string(two_points[1].point) + "\n"
                            + "这两点之间的距离为:\n"
                            + Convert.ToString(dis_points));//distance
                    }
                }
                glControl1.Invalidate();
            }
  • 添加相应函数
        private string coordinate2string(Vector3d v)//将点的坐标转换为字符串以输出
        {
            string string_of_coordinate = "(" + Convert.ToString(v.X) + "," + Convert.ToString(v.Y) + ","
                + Convert.ToString(v.Z) + ")";
            return string_of_coordinate;
        }

        private double calculateDistance(Point3DExt point1, Point3DExt point2)
        {
            double x = point1.point.X - point2.point.X;
            double y = point1.point.Y - point2.point.Y;
            double z = point1.point.Z - point2.point.Z;

            double dd = x * x + y * y + z * z;
            double d = Math.Sqrt(dd);

            return d;
        }

截图

  • MenuStrip中添加截图按钮
  • 双击截图按钮,跳转到截图ToolStripMenuItem_Click,添加代码
            int[] vdata = new int[4];
            GL.GetInteger(GetPName.Viewport, vdata);
            int w = vdata[2];
            int h = vdata[3];
            if ((w % 4) != 0)
                w = (w / 4 + 1) * 4;
            byte[] imgBuffer = new byte[w * h * 3];
            GL.ReadPixels(0, 0, w, h, OpenTK.Graphics.OpenGL.PixelFormat.Bgr,
           PixelType.UnsignedByte, imgBuffer);
            FlipHeight(imgBuffer, w, h);
            Bitmap bmp = BytesToImg(imgBuffer, w, h);
            bmp.Save("D:\\opentk.bmp");
            MessageBox.Show("截图成功!");
  • 添加相关函数
        private void FlipHeight(byte[] data, int w, int h)
        {
            int wstep = w * 3;
            byte[] temp = new byte[wstep];
            for (int i = 0; i < h / 2; i++)
            {
                Array.Copy(data, wstep * i, temp, 0, wstep);
                Array.Copy(data, wstep * (h - i - 1), data, wstep * i, wstep);
                Array.Copy(temp, 0, data, wstep * (h - i - 1), wstep);
            }
        }
        private Bitmap BytesToImg(byte[] bytes, int w, int h)
        {
            Bitmap bmp = new Bitmap(w, h);
            BitmapData bd = bmp.LockBits(new Rectangle(0, 0, w, h),
            ImageLockMode.ReadWrite,
            System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            IntPtr ptr = bd.Scan0;
            int bmpLen = bd.Stride * bd.Height;
            Marshal.Copy(bytes, 0, ptr, bmpLen); //using System.Runtime.InteropServices;
            bmp.UnlockBits(bd);
            return bmp;
        }
  • 添加名称空间
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

显示包围盒

  • GroupBox中添加CheckBox,修改Text为“显示八叉树轮廓”
  • 双击CheckBox,跳转到checkBox2_CheckedChanged函数
  • Form1添加类成员变量
        bool show_octree_outline = false;//是否画包围盒
  • Form1的Render函数添加show_octree_outline参量
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Render(show_octree_outline);
        }
        private void Render(bool ShowOctreeOutline)
  • 八叉树和节点的Render也相应地添加参数
            if (pco != null)
            {
                pco.Render(mFrustum, ShowOctreeOutline);//现在还没有,八叉树自己的绘图函数
            }
        public void Render(double[,] frustum, bool ShowOctreeOutline)
        {
            if (root != null)
                root.Render(frustum, ShowOctreeOutline);
        }
            if (data != null)
            {
                //调用显示列表
                GL.CallList(iShowListNum);

                if (ShowOctreeOutline == true)
                {
                    DrawNodeOutline(min_coordinate, max_coordinate);
                }
            }

            if (child != null)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (child[i] != null)
                    {
                        child[i].Render(frustum, ShowOctreeOutline);
                    }
                }
            }
  • PointCloudNode中添加DrawNodeOutline函数
        //画包围盒
        private void DrawNodeOutline(Vector3d min_node_coordinate, Vector3d max_node_coordinate)
        {
            //下面
            GL.Begin(PrimitiveType.Lines);
            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, min_node_coordinate.Y, min_node_coordinate.Z);//(0,0,0)
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, min_node_coordinate.Y, min_node_coordinate.Z);//(1,0,0)

            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, max_node_coordinate.Y, min_node_coordinate.Z);//(0,1,0)
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, max_node_coordinate.Y, min_node_coordinate.Z);//(1,1,0)

            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, min_node_coordinate.Y, min_node_coordinate.Z);//(0,0,0)
            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, max_node_coordinate.Y, min_node_coordinate.Z);//(0,1,0)

            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, min_node_coordinate.Y, min_node_coordinate.Z);//(1,0,0)
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, max_node_coordinate.Y, min_node_coordinate.Z);//(1,1,0)

            GL.End();

            //上面
            GL.Begin(PrimitiveType.Lines);
            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, min_node_coordinate.Y, max_node_coordinate.Z);//(0,0,1)
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, min_node_coordinate.Y, max_node_coordinate.Z);//(1,0,1)

            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, max_node_coordinate.Y, max_node_coordinate.Z);//(0,1,1)
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, max_node_coordinate.Y, max_node_coordinate.Z);//(1,1,1)

            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, min_node_coordinate.Y, max_node_coordinate.Z);//(0,0,1)
            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, max_node_coordinate.Y, max_node_coordinate.Z);//(0,1,1)

            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, min_node_coordinate.Y, max_node_coordinate.Z);//(1,0,1)
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, max_node_coordinate.Y, max_node_coordinate.Z);//(1,1,1)

            GL.End();

            //(0,0,0)-(0,0,1)
            GL.Begin(PrimitiveType.Lines);
            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, min_node_coordinate.Y, min_node_coordinate.Z);
            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, min_node_coordinate.Y, max_node_coordinate.Z);
            GL.End();

            //(1,1,1)-(1,1,0)
            GL.Begin(PrimitiveType.Lines);
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, max_node_coordinate.Y, max_node_coordinate.Z);
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, max_node_coordinate.Y, min_node_coordinate.Z);
            GL.End();

            //(1,0,0)-(1,0,1)
            GL.Begin(PrimitiveType.Lines);
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, min_node_coordinate.Y, min_node_coordinate.Z);
            GL.Color4(Color4.White);
            GL.Vertex3(max_node_coordinate.X, min_node_coordinate.Y, max_node_coordinate.Z);
            GL.End();

            //(0,1,0)-(0,1,1)
            GL.Begin(PrimitiveType.Lines);
            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, max_node_coordinate.Y, min_node_coordinate.Z);
            GL.Color4(Color4.White);
            GL.Vertex3(min_node_coordinate.X, max_node_coordinate.Y, max_node_coordinate.Z);
            GL.End();
        }

初始化点云色系

  • Form1窗体添加GroupBox,修改Text为“选择点云色系”
  • 添加三个RadioButton,修改Text为“彩虹”、“暖色调”、“冷色调”
  • 添加类成员变量
        string point_cloud_color = "rainbow";//选择渲染颜色
  • 双击三个RadioButton,分别对point_cloud_color赋值
        private void radioButton1_CheckedChanged(object sender, EventArgs e)
        {
            point_cloud_color = "rainbow";
            Invalidate();
        }

        private void radioButton2_CheckedChanged(object sender, EventArgs e)
        {
            point_cloud_color = "warm";
            Invalidate();
        }

        private void radioButton3_CheckedChanged(object sender, EventArgs e)
        {
            point_cloud_color = "cold";
            Invalidate();
        }
  • ReadLas读点时,每个点根据坐标着色
                //每个点根据坐标着色
                if (point_cloud_color == "rainbow")
                {
                    if (coordArray[2] <= z_1_6)
                    {
                        color_point.color.X = (coordArray[2] - minz) / (z_1_6 - minz) * (cr_orange.X - cr_red.X) + cr_red.X;
                        color_point.color.Y = (coordArray[2] - minz) / (z_1_6 - minz) * (cr_orange.Y - cr_red.Y) + cr_red.Y;
                        color_point.color.Z = (coordArray[2] - minz) / (z_1_6 - minz) * (cr_orange.Z - cr_red.Z) + cr_red.Z;
                    }
                    else if (coordArray[2] <= z_2_6)
                    {
                        color_point.color.X = (coordArray[2] - z_1_6) / (z_2_6 - z_1_6) * (cr_yellow.X - cr_orange.X) + cr_orange.X;
                        color_point.color.Y = (coordArray[2] - z_1_6) / (z_2_6 - z_1_6) * (cr_yellow.Y - cr_orange.Y) + cr_orange.Y;
                        color_point.color.Z = (coordArray[2] - z_1_6) / (z_2_6 - z_1_6) * (cr_yellow.Z - cr_orange.Z) + cr_orange.Z;
                    }
                    else if (coordArray[2] <= z_3_6)
                    {
                        color_point.color.X = (coordArray[2] - z_2_6) / (z_3_6 - z_2_6) * (cr_green.X - cr_yellow.X) + cr_yellow.X;
                        color_point.color.Y = (coordArray[2] - z_2_6) / (z_3_6 - z_2_6) * (cr_green.Y - cr_yellow.Y) + cr_yellow.Y;
                        color_point.color.Z = (coordArray[2] - z_2_6) / (z_3_6 - z_2_6) * (cr_green.Z - cr_yellow.Z) + cr_yellow.Z;
                    }
                    else if (coordArray[2] <= z_4_6)
                    {
                        color_point.color.X = (coordArray[2] - z_3_6) / (z_4_6 - z_3_6) * (cr_cyan.X - cr_green.X) + cr_green.X;
                        color_point.color.Y = (coordArray[2] - z_3_6) / (z_4_6 - z_3_6) * (cr_cyan.Y - cr_green.Y) + cr_green.Y;
                        color_point.color.Z = (coordArray[2] - z_3_6) / (z_4_6 - z_3_6) * (cr_cyan.Z - cr_green.Z) + cr_green.Z;
                    }
                    else if (coordArray[2] <= z_5_6)
                    {
                        color_point.color.X = (coordArray[2] - z_4_6) / (z_5_6 - z_4_6) * (cr_bule.X - cr_cyan.X) + cr_cyan.X;
                        color_point.color.Y = (coordArray[2] - z_4_6) / (z_5_6 - z_4_6) * (cr_bule.Y - cr_cyan.Y) + cr_cyan.Y;
                        color_point.color.Z = (coordArray[2] - z_4_6) / (z_5_6 - z_4_6) * (cr_bule.Z - cr_cyan.Z) + cr_cyan.Z;
                    }
                    else
                    {
                        color_point.color.X = (coordArray[2] - z_5_6) / (maxz - z_5_6) * (cr_purple.X - cr_bule.X) + cr_bule.X;
                        color_point.color.Y = (coordArray[2] - z_5_6) / (maxz - z_5_6) * (cr_purple.Y - cr_bule.Y) + cr_bule.Y;
                        color_point.color.Z = (coordArray[2] - z_5_6) / (maxz - z_5_6) * (cr_purple.Z - cr_bule.Z) + cr_bule.Z;
                    }
                }

                else if (point_cloud_color == "warm")
                {
                    //2等分z轴,用于插值颜色
                    double z_1_2 = 1 * (maxz - minz) / 2 + minz;

                    if (coordArray[2] <= z_1_2)
                    {
                        color_point.color.X = (coordArray[2] - minz) / (z_1_2 - minz) * (cr_orange.X - cr_red.X) + cr_red.X;
                        color_point.color.Y = (coordArray[2] - minz) / (z_1_2 - minz) * (cr_orange.Y - cr_red.Y) + cr_red.Y;
                        color_point.color.Z = (coordArray[2] - minz) / (z_1_2 - minz) * (cr_orange.Z - cr_red.Z) + cr_red.Z;
                    }
                    else
                    {
                        color_point.color.X = (coordArray[2] - z_1_2) / (maxz - z_1_2) * (cr_yellow.X - cr_orange.X) + cr_orange.X;
                        color_point.color.Y = (coordArray[2] - z_1_2) / (maxz - z_1_2) * (cr_yellow.Y - cr_orange.Y) + cr_orange.Y;
                        color_point.color.Z = (coordArray[2] - z_1_2) / (maxz - z_1_2) * (cr_yellow.Z - cr_orange.Z) + cr_orange.Z;
                    }
                }
                else if (point_cloud_color == "cold")
                {
                    //2等分z轴,用于插值颜色
                    double z_1_2 = 1 * (maxz - minz) / 2 + minz;

                    if (coordArray[2] <= z_1_2)
                    {
                        color_point.color.X = (coordArray[2] - minz) / (z_1_2 - minz) * (cr_cyan.X - cr_green.X) + cr_green.X;
                        color_point.color.Y = (coordArray[2] - minz) / (z_1_2 - minz) * (cr_cyan.Y - cr_green.Y) + cr_green.Y;
                        color_point.color.Z = (coordArray[2] - minz) / (z_1_2 - minz) * (cr_cyan.Z - cr_green.Z) + cr_green.Z;
                    }
                    else
                    {
                        color_point.color.X = (coordArray[2] - z_1_2) / (maxz - z_1_2) * (cr_bule.X - cr_cyan.X) + cr_cyan.X;
                        color_point.color.Y = (coordArray[2] - z_1_2) / (maxz - z_1_2) * (cr_bule.Y - cr_cyan.Y) + cr_cyan.Y;
                        color_point.color.Z = (coordArray[2] - z_1_2) / (maxz - z_1_2) * (cr_bule.Z - cr_cyan.Z) + cr_cyan.Z;
                    }
                }

选择绘制图形

  • 添加类成员变量
        string paint_object = "las";//打开文件
  • 打开菜单中添加“三角形”和“球体”两个选项
    三维可视化软件开发——点云的打开与八叉树管理_第6张图片
  • 双击,分别对paint_object赋值
        private void 三角形ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            paint_object = "triangle";
        }
        private void 球体ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            paint_object = "sphere";
        }
  • 修改Form1的Render中绘图函数
            //绘图函数
            if (paint_object == "las")
            {
                if (pco != null)
                {
                    pco.Render(mFrustum, ShowOctreeOutline);//现在还没有,八叉树自己的绘图函数
                }
            }
            else if (paint_object == "sphere")
            {
                DrawSphere();
            }
            else if (paint_object == "triangle")
            {
                DrawTriangle();
            }

选择点的大小

  • 添加类成员变量
        int ptSize = 1;//点的大小
  • Form1窗体添加GroupBox,修改Text为“请输入点的大小,1~10之间的整数”
  • 添加TextBox,双击转到函数textBox1_TextChanged,添加代码
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            if (textBox1.Text == "")
                return;

            int a = Int32.Parse(textBox1.Text);

            if (a <= 0 || a >= 10)
                a = 1;

            ptSize = a;

            Invalidate();
        }
  • DrawSphere函数中,GL.Begin语句前,添加代码
            GL.PointSize(ptSize);
  • Form1的Render函数,添加参数point_size,修改各处Render
        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Render(show_octree_outline, ptSize);
        }
        private void Render(bool ShowOctreeOutline, int point_size)
  • 八叉树和节点的Render函数也添加参数
                    pco.Render(mFrustum, ShowOctreeOutline, point_size);
        public void Render(double[,] frustum, bool ShowOctreeOutline, int point_size)
        {
            if (root != null)
                root.Render(frustum, ShowOctreeOutline, point_size);
        }
        public void Render(double[,] frustum, bool ShowOctreeOutline, int point_size)
        {
            OutlineInFrustum = VoxelWithinFrustum(frustum, min_coordinate.X, min_coordinate.Y, min_coordinate.Z,
                max_coordinate.X, max_coordinate.Y, max_coordinate.Z);

            if (!OutlineInFrustum)
                return;

            if (data != null)
            {
                //画点
                GL.PointSize(point_size);

                //调用显示列表
                GL.CallList(iShowListNum);

                if (ShowOctreeOutline == true)
                    DrawNodeOutline(min_coordinate, max_coordinate);
            }

            if (child != null)
            {
                for (int i = 0; i < 8; i++)
                {
                    if (child[i] != null)
                    {
                        child[i].Render(frustum, ShowOctreeOutline, point_size);
                    }
                }
            }
        }

选择投影方式

  • GroupBox中添加两个RadioButton,修改Text为“平行投影”、“透视投影”
  • 双击RadioButton,分别对perspective_projection赋值
        private void radioButton4_CheckedChanged(object sender, EventArgs e)
        {
            perspective_projection = false;
            Invalidate();
        }

        private void radioButton5_CheckedChanged(object sender, EventArgs e)
        {
            perspective_projection = true;
            Invalidate();
        }
  • Form的Render中,修改平移参数
            if (perspective_projection)
                GL.Translate(transX, transY, -1); //平移
            else
                GL.Translate(transX, transY, 0);

使用说明

  • Form1的MenuStrip添加“使用说明”,双击转到函数
        private void 使用说明ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("登陆后,从打开菜单中选择绘制的图像\n" +
                "鼠标左键可以控制物体平移\n" +
                "鼠标右键可以控制物体旋转\n" +
                "鼠标滚轮可以控制物体缩放\n" +
                "双击鼠标可以选点" +
                "复选框可以选择绘制包围核(默认不绘制),也可以选择计算两点间距离(默认不计算)");
        }

打开新文件与退出

  • Form1窗体添加两个Button,分别修改Text为“打开新文件”、“退出”
  • 双击两个Button,添加代码
        private void button1_Click(object sender, EventArgs e)
        {
            this.Hide();
            Form1 form1 = new Form1();
            form1.Show();
        }

        private void button2_Click(object sender, EventArgs e)//如果“打开新文件”被触发过,必须执行一下语句才能退出程序,否则只会关闭当前窗口,程序仍在运行
        {
            Application.Exit();
        }

设置登录界面

  • 项目添加窗体,命名为InitialForm
  • BackColor设置为白色
  • 添加Label,“三维可视化软件”,Font,隶书,一号
  • 添加两个Label,“用户名”,“密码”,均设置为楷体,三号
  • 添加两个TextBox
  • 添加两个Button,“使用须知”、“登录”
  • 双击Button,分别添加代码
        private void button1_Click(object sender, EventArgs e)//使用须知
        {
            MessageBox.Show("本软件最终解释权归开发者所有,未经授权不得商用!!!");
        }

        private void button2_Click(object sender, EventArgs e)//登录
        {
            if (textBox1.Text == "368" && textBox2.Text == "")//自定义用户名和密码
            {
                DialogResult = DialogResult.OK;
                Dispose();
                Close();
            }
            else
            {
                MessageBox.Show("用户名或密码错误,请重新输入");
            }
        }
  • InitialForm中添加代码从而添加背景图片,将图片存放在项目的bin/Debug文件夹
this.BackgroundImage = Image.FromFile("whu1.jpg");
  • 将InitialForm的Size设置为图片大小
  • 打开Program.cs文件,将
            Application.Run(new Form1());
  • 替换为
            InitialForm f1 = new InitialForm();
            Form1 form1 = new Form1();
            f1.ShowDialog();
            if (f1.DialogResult == DialogResult.OK)
            {
                Application.Run(form1);
            }
  • 效果

美化界面

  • Form1,WindowState,Maximize
  • 将所有panel的BackColor设置为纯白

你可能感兴趣的:(visual,studio,mfc,c#)