unity---Mesh网格编程(六)

news2024/11/25 11:34:46

目录

1.模型切割

2.代码


1.模型切割

如图,对3D模型的Mesh网格进行切割,会经过若干个三角面。而切割后,将会产生新的面来组成左右两边的物体。

 要记录每个顶点与顶点下标,新的面要顺时针绘制,

 

 

2.代码

using System.Collections.Generic;
using UnityEngine;

public class CutMode : MonoBehaviour
{
    private Material cutMaterial;
    private Vector3 _startPos;
    private Vector3 _endPos;
    private Vector3 _hitPos;
    private Vector3 _dir, _upDir, _planeNormal;

    private Mesh _mesh;
    private Transform _hitTrans;
    private MeshFilter _leftMeshFilter;

    /// 三角形三个顶点的坐标信息缓存(世界坐标)
    private Vector3[] _triangleTemp = new Vector3[3];
    /// 三角形三个点乘结果的缓存
    private float[] _resultTemp = new float[3];

    //左侧(和平面法向量同侧)模型数据
    private List<Vector3> _leftVertices = new List<Vector3>();
    private List<int> _leftTriangles = new List<int>();
    private List<Vector3> _leftNormals = new List<Vector3>();
    public List<Vector2> uvs_left;
    /// key:原模型顶点下标  value:现模型的顶点下标
    private Dictionary<int, int> _leftIndexMapping = new Dictionary<int, int>();

    //右侧(和平面法向量反向)模型数据
    private List<Vector3> _rightVertices = new List<Vector3>();
    private List<int> _rightTriangles = new List<int>();
    private List<Vector3> _rightNormals = new List<Vector3>();
    public List<Vector2> uvs_right;
    /// key:原模型顶点下标  value:现模型的顶点下标
    private Dictionary<int, int> _rightIndexMapping = new Dictionary<int, int>();
    /// 切面上新生成的顶点
    private List<Vector3> _rectionVertexs = new List<Vector3>();

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            _startPos = Input.mousePosition;
        }

        if (Input.GetMouseButtonUp(0))
        {
            _endPos = Input.mousePosition;
            Ray();
        }
    }

    private void Ray()
    {
        //两点间的中心点
        var center = (_endPos + _startPos) * 0.5f;
        var ray = Camera.main.ScreenPointToRay(center);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit))
        {
            _hitTrans = hit.transform;
            if (_hitTrans.tag.Equals("cutObj"))
            {
                Debug.Log("我切到了" + _hitTrans.name);
                cutMaterial = _hitTrans.GetComponent<MeshRenderer>().materials[0];
            }
            else
            {
                return;
            }

            _hitPos = hit.point;
            _leftMeshFilter = _hitTrans.GetComponent<MeshFilter>();
            _mesh = hit.transform.GetComponent<MeshFilter>().mesh;

            //相机到物体的方向向量   Vector3.normalized归一化向量
            _dir = (hit.point - Camera.main.transform.position).normalized;
            //垂直于_dir的方向向量  Vector3.Dot(v1,v2)点乘--->计算v1在v2上的投影长度(标量)--法向量
            _upDir = (-_dir * Vector3.Dot(Vector3.up, _dir) + Vector3.up).normalized;
            //平面        Vector3.Cross叉乘
            _planeNormal = Vector3.Cross(_dir, _upDir);

            //计算滑动方向与角度
            Vector3 sildeDir = _endPos - _startPos;
            Vector3 baseDir = sildeDir.y < 0 ? -Vector3.up : Vector3.up;
            float angle = Vector3.Angle(sildeDir, baseDir);

            if (sildeDir.y < 0)
            {
                angle = sildeDir.x > 0 ? angle : -angle;
            }
            else
            {
                angle = sildeDir.x > 0 ? -angle : angle;
            }
            //角度转弧度
            angle *= Mathf.Deg2Rad;
            //sin  cos 需传入的参数为弧度
            _upDir = _upDir * Mathf.Cos(angle) + _planeNormal * Mathf.Sin(angle);
            _planeNormal = Vector3.Cross(_dir, _upDir);

            Cut();
        }
        else
        {
            _hitPos = Vector3.zero;
            _mesh = null;
        }
    }

    private void Cut()
    {
        if (_mesh == null)
            return;
        ClearData();
        CalculateVertexInfo();
        GenerateSectionInfo();
        GenerateMesh();
    }

    private void ClearData()
    {
        _leftVertices.Clear();
        _leftTriangles.Clear();
        _leftNormals.Clear();
        _leftIndexMapping.Clear();
        _rightNormals.Clear();
        _rightTriangles.Clear();
        _rightVertices.Clear();
        _rightIndexMapping.Clear();
        _rectionVertexs.Clear();
    }

    private void GenerateMesh()
    {
        GenerateLeftMesh();
        GenerateRightMesh();
    }

    private void GenerateLeftMesh()
    {
        Mesh mesh = new Mesh();
        mesh.name = "leftMesh";
        mesh.vertices = _leftVertices.ToArray();
        mesh.triangles = _leftTriangles.ToArray();
        mesh.normals = _leftNormals.ToArray();
        //ToDo mesh.uv = 
        _leftMeshFilter.mesh = mesh;
    }

    private int newObjNum = 0;
    private void GenerateRightMesh()
    {
        Mesh mesh = new Mesh();
        mesh.name = "rightMesh";
        mesh.vertices = _rightVertices.ToArray();
        mesh.normals = _rightNormals.ToArray();
        mesh.triangles = _rightTriangles.ToArray();
        //ToDo mesh.uv = 
        GameObject newGo = new GameObject();
        newGo.name = "newObj" + newObjNum;
        newGo.transform.tag = "cutObj";
        newGo.transform.position = _hitTrans.position;
        newGo.transform.rotation = _hitTrans.rotation;

        newGo.AddComponent<MeshFilter>().mesh = mesh;
        //newGo.AddComponent<MeshRenderer>().material = _hitTrans.GetComponent<MeshRenderer>().material;
        newGo.AddComponent<MeshRenderer>().material = cutMaterial;
        newGo.AddComponent<Rigidbody>();
        newGo.AddComponent<MeshCollider>().convex = true;
        newObjNum++;
    }

    /// <summary>
    /// 分别计算并存储切开的两个部分的顶点信息
    /// </summary>
    private void CalculateVertexInfo()
    {
        var triangles = _mesh.triangles;

        for (int i = 0; i < triangles.Length; i += 3)
        {
            //三个顶点在原triangles中下标是 i i+1 i+2
            GetDotResult(i, triangles);

            if (_resultTemp[0] >= 0 && _resultTemp[1] >= 0 && _resultTemp[2] >= 0)
            {
                //左侧
                SaveOldVertex(i, true);
            }
            else if (_resultTemp[0] <= 0 && _resultTemp[1] <= 0 && _resultTemp[2] <= 0)
            {
                //右侧
                SaveOldVertex(i, false);
            }
            else
            {
                //被切割的三角形部分
                int differentIndex = GetDifferentSidePointIndex();
                //当前点在triangles的下标
                int p0_Index = i + differentIndex;
                int p1_index = (differentIndex + 1) % 3 + i;
                //先算出c1点进行存储
                SavePointOnSection(_mesh.triangles[p0_Index], _mesh.triangles[p1_index]);
                int p2_index = (differentIndex + 2) % 3 + i;
                //再算出c2点进行存储
                SavePointOnSection(_mesh.triangles[p0_Index], _mesh.triangles[p2_index]);

                SaveCutTriangleVertex(_resultTemp[differentIndex], p0_Index, p1_index, p2_index);
            }
        }
    }

    private void SaveCutTriangleVertex(float result, int p0, int p1, int p2)
    {
        if (result >= 0)
        {
            SaveOldVertex(p0, _leftVertices, _leftNormals, _leftIndexMapping);
            SaveSectionVertexWithOnePoint(p0, _leftTriangles, _leftVertices, _leftNormals, _leftIndexMapping);

            SaveOldVertex(p1, _rightVertices, _rightNormals, _rightIndexMapping);
            SaveOldVertex(p2, _rightVertices, _rightNormals, _rightIndexMapping);
            SaveSectionVertexWithTwoPoint(p1, p2, _rightTriangles, _rightVertices, _rightNormals, _rightIndexMapping);
        }
        else
        {
            SaveOldVertex(p0, _rightVertices, _rightNormals, _rightIndexMapping);
            SaveSectionVertexWithOnePoint(p0, _rightTriangles, _rightVertices, _rightNormals, _rightIndexMapping);

            SaveOldVertex(p1, _leftVertices, _leftNormals, _leftIndexMapping);
            SaveOldVertex(p2, _leftVertices, _leftNormals, _leftIndexMapping);
            SaveSectionVertexWithTwoPoint(p1, p2, _leftTriangles, _leftVertices, _leftNormals, _leftIndexMapping);
        }
    }

    private void SaveSectionVertexWithOnePoint(
        int index,
        List<int> curTriangles,
        List<Vector3> curVertices,
        List<Vector3> curNormals,
        Dictionary<int, int> indexMapping)
    {
        int vertexIndex = _mesh.triangles[index];

        //存储c1
        curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 2]);
        //存储c2
        curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 1]);

        curNormals.Add(_mesh.normals[vertexIndex]);
        curNormals.Add(_mesh.normals[vertexIndex]);

        curTriangles.Add(indexMapping[vertexIndex]);
        curTriangles.Add(curVertices.Count - 2);
        curTriangles.Add(curVertices.Count - 1);
    }

    private void SaveSectionVertexWithTwoPoint(
        int index1,
        int index2,
        List<int> curTriangles,
        List<Vector3> curVertices,
        List<Vector3> curNormals,
        Dictionary<int, int> indexMapping)
    {
        int vertexIndex1 = _mesh.triangles[index1];
        int vertexIndex2 = _mesh.triangles[index2];

        //存储c1
        curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 2]);
        //存储c2
        curVertices.Add(_rectionVertexs[_rectionVertexs.Count - 1]);

        curNormals.Add(_mesh.normals[vertexIndex1]);
        curNormals.Add(_mesh.normals[vertexIndex2]);

        //c1-p1-p2
        curTriangles.Add(curVertices.Count - 2);
        curTriangles.Add(indexMapping[vertexIndex1]);
        curTriangles.Add(indexMapping[vertexIndex2]);

        //p2-c2-c1
        curTriangles.Add(indexMapping[vertexIndex2]);
        curTriangles.Add(curVertices.Count - 1);
        curTriangles.Add(curVertices.Count - 2);
    }

    /// <summary>
    /// 返回值是对应点在_resultTemp中的下标
    /// </summary>
    private int GetDifferentSidePointIndex()
    {
        List<int> temp1 = new List<int>(2);
        List<int> temp2 = new List<int>(2);
        for (int i = 0; i < _resultTemp.Length; i++)
        {
            if (_resultTemp[i] > 0)
            {
                temp1.Add(i);
            }
            else
            {
                temp2.Add(i);
            }
        }

        if (temp1.Count == 1)
        {
            return temp1[0];
        }
        else
        {
            return temp2[0];
        }
    }
    //参数是 原模型vertices下标
    private void SavePointOnSection(int index1, int index2)
    {
        Vector3 side = _mesh.vertices[index2] - _mesh.vertices[index1];
        //方向向量 --- 本地坐标系转世界坐标系
        Vector3 dir = _hitTrans.TransformDirection(side.normalized);
        Vector3 startPos = _hitTrans.TransformPoint(_mesh.vertices[index1]);
        float lengthOnNormal = Vector3.Dot(_hitPos, _planeNormal) - Vector3.Dot(startPos, _planeNormal);
        float length = lengthOnNormal / Vector3.Dot(dir, _planeNormal);
        Vector3 target = startPos + dir * length;
        _rectionVertexs.Add(_hitTrans.InverseTransformPoint(target));
    }

    private void GetDotResult(int index, int[] triangles)
    {
        for (int i = 0; i < _triangleTemp.Length; i++)
        {
            _triangleTemp[i] = _hitTrans.TransformPoint(_mesh.vertices[triangles[index + i]]);

            _resultTemp[i] = Vector3.Dot(_planeNormal, _triangleTemp[i] - _hitPos);
        }
    }

    private void SaveOldVertex(int index, bool isLeft)
    {
        if (isLeft)
        {
            SaveTriangleVertex(index, _leftTriangles, _leftVertices, _leftNormals, _leftIndexMapping);
        }
        else
        {
            SaveTriangleVertex(index, _rightTriangles, _rightVertices, _rightNormals, _rightIndexMapping);
        }
    }

    private void SaveTriangleVertex(
        int index,
        List<int> curTriangles,
        List<Vector3> curVertices,
        List<Vector3> curNormals,
        Dictionary<int, int> indexMapping)
    {
        for (int i = 0; i < 3; i++)
        {
            SaveOldVertex(index + i, curVertices, curNormals, indexMapping);
            curTriangles.Add(indexMapping[_mesh.triangles[index + i]]);
        }
    }

    private void SaveOldVertex(
        int index,
        List<Vector3> curVertices,
        List<Vector3> curNormals,
        Dictionary<int, int> indexMapping)
    {
        int vertexIndex = _mesh.triangles[index];

        if (!indexMapping.ContainsKey(vertexIndex))
        {
            curVertices.Add(_mesh.vertices[vertexIndex]);
            curNormals.Add(_mesh.normals[vertexIndex]);
            indexMapping.Add(vertexIndex, curVertices.Count - 1);
        }
    }

    //生成切面信息
    private void GenerateSectionInfo()
    {
        Vector3 center = (_rectionVertexs[0] + _rectionVertexs[_rectionVertexs.Count / 2]) * 0.5f;
        Vector3 centerNormal = _hitTrans.InverseTransformDirection(_planeNormal);

        SaveSectionCenter(center, centerNormal);
        int leftCenterIndex = _leftVertices.Count - 1;
        int rightCenterIndex = _rightVertices.Count - 1;

        for (int i = 0; i < _rectionVertexs.Count; i += 2)
        {
            Vector3 v1 = _rectionVertexs[i];
            Vector3 v2 = _rectionVertexs[i + 1];
            Vector3 normal = Vector3.Cross(v1 - center, v2 - center);

            SaveSectionVertexInfo(i, -centerNormal, _leftVertices, _leftNormals);
            SaveLeftSectionTriangle(_planeNormal, normal, leftCenterIndex, _leftTriangles, _leftVertices);

            SaveSectionVertexInfo(i, centerNormal, _rightVertices, _rightNormals);
            SaveRightSectionTriangle(_planeNormal, normal, rightCenterIndex, _rightTriangles, _rightVertices);
        }
    }

    private void SaveLeftSectionTriangle(Vector3 planeNormal, Vector3 normal, int centerIndex, List<int> triangles, List<Vector3> vertices)
    {
        if (Vector3.Dot(planeNormal, normal) < 0)
        {
            //左侧切面 三角形法向量方向和planeNormal方向相反,才能正常显示
            // 0 1 2
            triangles.Add(centerIndex);
            triangles.Add(vertices.Count - 2);
            triangles.Add(vertices.Count - 1);
        }
        else
        {
            // 0 2 1
            triangles.Add(centerIndex);
            triangles.Add(vertices.Count - 1);
            triangles.Add(vertices.Count - 2);
        }
    }

    private void SaveRightSectionTriangle(Vector3 planeNormal, Vector3 normal, int centerIndex, List<int> triangles, List<Vector3> vertices)
    {
        if (Vector3.Dot(planeNormal, normal) > 0)
        {
            //右侧切面 三角形法向量方向和planeNormal方向相同,才能正常显示
            // 0 1 2
            triangles.Add(centerIndex);
            triangles.Add(vertices.Count - 2);
            triangles.Add(vertices.Count - 1);
        }
        else
        {
            // 0 2 1
            triangles.Add(centerIndex);
            triangles.Add(vertices.Count - 1);
            triangles.Add(vertices.Count - 2);
        }
    }


    private void SaveSectionVertexInfo(int index, Vector3 normal, List<Vector3> vertices, List<Vector3> normals)
    {
        vertices.Add(_rectionVertexs[index]);
        vertices.Add(_rectionVertexs[index + 1]);
        normals.Add(normal);
        normals.Add(normal);
    }

    private void SaveSectionCenter(Vector3 center, Vector3 normal)
    {
        _leftVertices.Add(center);
        _leftNormals.Add(-normal);

        _rightVertices.Add(center);
        _rightNormals.Add(normal);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/52378.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker+nginx 安装部署修改资源目录配置文件和容器端口信息

查看docker镜像 可以先查看docker下是否存在nginx镜像&#xff0c;使用如下这些命令查看&#xff1a; docker images: 列出所有镜像。docker images nginx: 列出所有nginx镜像&#xff0c;不同版本等等。docker search nginx: 搜索查看所有nginx镜像信息。 拉取安装nginx镜像…

Java8 函数式编程【基础篇】

Java 8是Java在保持向后兼容的前提下首次迈出重要一步&#xff0c;相比之前&#xff0c;不再是只对类库的改良&#xff0c;在编写复杂的集合处理、并行化执行、代码简洁度等方面都有颠覆性的提升。本文将探索和理解函数式编程的含义&#xff0c;以及它在Java 8中的实现。 一、…

RESTful API是什么?看完你整个人都通透了

要弄清楚什么是RESTful API&#xff0c;首先要弄清楚什么是REST&#xff1f; 01 REST REpresentational State Transfer&#xff0c;英语的直译就是“表现层状态转移”。如果看这个概念&#xff0c;估计没几个人能明白是什么意思。那下面就让我来用一句话解释一下什么是RESTf…

低代码搭建质量管理解决方案,为企业管理提速降本

市场竞争的越来越卷&#xff0c;越来越多的制造企业认识到质量管理的重要性。尤其随着全球经济化与信息化的到来&#xff0c;质量管理已经成为企业管理的关键环节。然而与国际上具有先进技术和管理水平的企业相比&#xff0c;我国企业的质量管理较为薄弱&#xff0c;存在着质量…

【MATLAB】羽状图

目录 羽状图 羽状图 h0figure(toolbar,none,...position,[200 150 450 350],...name,实例28);subplot(2,1,1)alpha90:-10:0;rones(size(alpha));malpha*pi/180;nr*10;[u,v]pol2cart(m,n);feather(u,v)title(羽状图)axis([0 20 0 10])subplot(2,1,2)t0:0.5:10;x0.05i;yexp(-x*t…

R语言解释生存分析中危险率和风险率的变化

危险率函数 让我们模拟R中的一些数据&#xff1a; n < - 10000 h < - 0.5 t < - -log&#xff08;runif&#xff08;n&#xff09;&#xff09;/ h 该代码模拟了危险函数的存活时间&#xff0c;即常数。 视频&#xff1a;R语言生存分析原理与晚期肺癌患者分析案…

Ajax学习:jQuery发送ajax请求 通用方法$.ajax

app.all(/jQuery,(requset,response)>{response.setHeader(Access-Control-Allow-Origin,*);const data{name:张三};let strJSON.stringify(data);//需要转换称为json 否则传递的任然是对象response.send(str);//3s之后返回给客户端 }) $(button).eq(2).click(function() {/…

MySQL和Oracle JDBC驱动包下载步骤

MySQL官网&#xff1a;https://www.mysql.com/ 步骤如下&#xff1a; 1.点击DOWNLOADS 2.往下滑&#xff0c;找到MySQL Community&#xff08;GPL&#xff09;Downloands并点击 3.点击Connector/J 4.当前页面展示的是最新版本&#xff0c;要下载历史版本点击Archives 5.选择…

15 【登录鉴权】

15 【登录鉴权-Cookie】 1.什么是认证&#xff08;Authentication&#xff09; 通俗地讲就是验证当前用户的身份&#xff0c;证明“你是你自己”&#xff08;比如&#xff1a;你每天上下班打卡&#xff0c;都需要通过指纹打卡&#xff0c;当你的指纹和系统里录入的指纹相匹配…

细数APDL中的流程控制命令

作者&#xff1a;水哥ANSYS&#xff0c;获授权转载 一、概述 有过其他编程语言经验的同学都知道&#xff0c;流程控制类语言命令在编程中是必须掌握的一门技巧&#xff0c;这类命令能大幅提高我们的编程效率&#xff0c;增加程序可读性。类似地&#xff0c;在APDL中也有很多的…

R语言Poisson回归的拟合优度检验

在这篇文章中&#xff0c;我们将看一下Poisson回归的拟合优度测试与个体计数数据。 最近我们被客户要求撰写关于Poisson回归的研究报告&#xff0c;包括一些图形和统计输出。许多软件包在拟合Poisson回归模型时在输出中提供此测试&#xff0c;或者在拟合此类模型&#xff08;例…

不刷题,PMP考试可以通过吗?

不能&#xff0c;除非你的项目管理经验很厉害&#xff0c;但这么厉害也不需要PMP这个证书了&#xff0c;做一个比喻&#xff0c;学习项目管理知识是读兵书&#xff0c;做题就是“纸上谈兵”&#xff0c;获得PMP证书就是证明你有做指挥的资格&#xff0c;做项目是上战场指挥打仗…

提交代码出现error Empty block statement no-empty,代码却没报错?

开开心心写完代码&#xff0c;commit一下&#xff0c;发现可能控制台报错了&#xff1a; 看了代码却没发现有报错的&#xff0c;后来发现是开了eslint校验&#xff01; 因为存在空的if体&#xff0c;如下&#xff1a; 解决&#xff1a;保证if体不为空即可 参考&#xff1a;…

excel转换成pdf格式怎么操作?这3招教你Excel怎么转PDF

在我们日常办公中&#xff0c;经常会需要用到Excel表格&#xff0c;这类文件格式可以帮助我们日常记录统计数据&#xff0c;有效的提升办公效率。当我们需要将文件发送给别人&#xff0c;为了避免被改数据内容&#xff0c;很多时候都会将Excel转换为PDF格式。那么&#xff0c;E…

flask入门教程之数据库保存

计算机操作数据时&#xff0c;一般是在内存中对数据进行处理&#xff0c;但是计算机的内存空间有限&#xff0c;服务器操作大量数据时&#xff0c;容易造成内存不足&#xff0c;且一旦计算机关机&#xff0c;则内存数据就丢失。所以我们需要将数据进行存储。 持久化&#xff0…

HTTP协议发展史

简介 Hyper Text Transfer Protocol&#xff08;超文本传输协议&#xff09;,是用于从万维网&#xff08;WWW:World Wide Web &#xff09;服务器传输超文本到本地浏览器的传送协议。是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件都必须遵守这个标准。 超文本传输 …

地图数据设计(四):地图比例尺

前言 比例尺作为地图数学基础的组成部分之一&#xff0c;表示了地图的空间尺度&#xff0c;是地图精度和内容详尽程度的决定因素。今天的文章将从比例尺的基本概念、常见比例尺参数以及SuperMap iDesktopX中比例尺设置三个方面展开。 1 比例尺的基本概念 1.1 定义 一般来说&am…

Day16-购物车页面-商品列表修改购物车商品的勾选状态

提纲挈领&#xff1a; 我的操作&#xff1a; 1》当用户点击 radio 组件&#xff0c;希望修改当前商品的勾选状态&#xff0c;此时用户可以为 my-goods 组件绑定 radio-change 事件&#xff0c;从而获取当前商品的 goods_id 和 goods_state&#xff1a; 定义 radioChangeHandle…

达梦数据库备份策略

文章目录一、达梦数据库备份策略1.1 开启归档模式1.1.1 联机配置本地归档1.1.2 脱机配置本地归档1.2 启动DMAP服务1.2.1 启动DMAP1.3 物理备份1.3.1 联机备份(1) 数据库备份(2) 表空间备份(3) 表备份(4) 备份归档1.3.2 脱机备份(1) 启动DMRMAN(2) 备份数据库1.4 物理备份还原1.…

【Linux】命令

常用命令 帮助&#xff08;Manual Pages&#xff0c;Manual&#xff1a;手册&#xff0c;特指参考文件&#xff09; man man <command_name> 打开目录&#xff08;change directory&#xff09; cd /etc/ cd /home 查看当前所在目录 pwd 创建一个名为 file 的文件&…