当前位置: 首页 > news >正文

【Unity】自动生成围绕模型的路径点

思路:根据mesh获取模型[minY,maxY]  路径点数量=50,则高度均分为50层,每层取1个点作为路径点,每层利用射线检测获取到该层的中心点,中心点=围绕该层点旋转360度由外到内射线撞击点总和/撞击点数量,将中心点作为内部点,将内部点沿着正X轴外移并围绕Y轴旋转angle角度获取外部点,angle数据是每次取外部点都会自增固定角度并会angle%360保证不超出360,从外部点到内部点进行射线检测即可得到撞击模型点,将它作为路径点。

优化:为了获取到的撞击点与上次撞击点不在同一个平面上,使用了新方法,代码注释:新方法开始->新方法结束代码进行保证取到一个能够获取不同平面的撞击点的角度。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;[ExecuteInEditMode]
public class ModelEditor : EditorWindow
{private static ModelEditor window;[MenuItem("Tools/模型编辑器")]public static void ShowWindow(){window = EditorWindow.CreateWindow<ModelEditor>();window.minSize = new Vector2(500, 500);window.Show(true);}private GameObject posRootGo;private GameObject redLineRootGo;private int generatePointCount = 30;private float angleDelta = 30f;private void OnGUI(){generatePointCount = EditorGUILayout.IntField("路径点数量", generatePointCount);angleDelta = EditorGUILayout.FloatField("角度", angleDelta);if (GUILayout.Button("围绕模型Y轴由外到内射线生成路径点")){ClearConsole();Physics.SyncTransforms();GameObject go = Selection.activeGameObject;if (go != null){//上个点所在平面的法线Vector3 lastNormal = Vector3.zero;Transform goTrans = go.transform;Mesh mesh = go.GetComponent<MeshFilter>().sharedMesh;float minY = float.MaxValue;float maxY = -float.MaxValue;List<Vector3> worldVertexList = new List<Vector3>();foreach (var v in mesh.vertices){worldVertexList.Add(goTrans.TransformPoint(v));}foreach (var v in worldVertexList){float y = v.y;if (y < minY){minY = y;}if (y > maxY){maxY = y;}}var meshRenderer = go.GetComponent<MeshRenderer>();var bounds = mesh.bounds; //meshRenderer.bounds;// minY = bounds.min.y;// maxY = bounds.max.y;float offset = 0.0008f;minY += offset;maxY -= offset;Debug.Log("模型bounds:" + bounds.min.y + ", " + bounds.max.y);GameObject.DestroyImmediate(posRootGo);posRootGo = new GameObject("PosRoot");Transform posRoot = posRootGo.transform;Debug.Log("模型y范围:"+ minY + " -> " + maxY);Vector3 modelRootPos = go.transform.position;float modelHeight = maxY - minY;float deltaY = modelHeight / generatePointCount;float curY = minY;float angle = 0f;int posID = 0;List<Vector3> pointList = new List<Vector3>();while (curY < (maxY + offset)){Vector3 insidePos = new Vector3(modelRootPos.x, curY, modelRootPos.z);//以初定insidePos中心进行围绕360度 计算出在curY高度时的所有与模型碰撞点Vector3 centerPos = Vector3.zero;int posCnt = 0;for (int i = 0; i <= 360; i += 1){Quaternion tempRotation = Quaternion.AngleAxis(i, Vector3.up);Vector3 tempOutsidePos = insidePos + tempRotation * Vector3.right * 1;Vector3 tempDir = (insidePos - tempOutsidePos).normalized;RaycastHit tempHit;if (Physics.Raycast(tempOutsidePos, tempDir, out tempHit)){centerPos += tempHit.point;posCnt++;}}if (posCnt > 0){centerPos /= posCnt;insidePos = centerPos;   }//新方法开始#region 新方法//新方法依旧是每次先转angleDelta角度,再从此基础上找不一样的法线的撞击点angle += angleDelta;angle %= 360f;//新方法:从angle开始转一圈找到一个点 与上个点不在同一个平面(即法线不一样)for (int i = 0; i <= 360; i += 1){float tempAngle = angle + i;tempAngle %= 360;Quaternion tempRotation = Quaternion.AngleAxis(tempAngle, Vector3.up);Vector3 tempOutsidePos = insidePos + tempRotation * Vector3.right * 1;Vector3 tempDir = (insidePos - tempOutsidePos).normalized;RaycastHit tempHit;if (Physics.Raycast(tempOutsidePos, tempDir, out tempHit)){if (lastNormal != Vector3.zero){//撞击点法线Vector3 hitNormal = tempHit.normal;//下面公式://cos(p) = a*b/|a*b| ;  p = Acos(cos(p)) = Acos(a*b/|a*b|) = Acos(dot(a,b)/|a*b|) float normalAngle = Mathf.Acos(Vector3.Dot(hitNormal, lastNormal) /(hitNormal.magnitude * lastNormal.magnitude));//求出2个法线的夹角弧度 > 18度弧度时认定不一样法线 (可自由调整 阈值)if (normalAngle > 0.1f * Mathf.PI) //18度{Debug.Log($"{posID}点 原角度:{angle} , 新角度:{tempAngle}");lastNormal = hitNormal; //记录上次新点的法线angle = tempAngle; //设定新的角度  这个角度能获取到新的法线的路径点break;}}}}#endregion//新方法结束//旧方法:每次旋转angleDelta度找下一个路径点 [可注释上面新方法代码块,并去除注释以下2行对angle的递增 恢复回旧方法] // angle += angleDelta;// angle %= 360f;Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.up);Vector3 outsidePos = insidePos + rotation * Vector3.right * 1;posID++;var insideGo = GameObject.CreatePrimitive(PrimitiveType.Cube);insideGo.GetComponent<Collider>().enabled = false;insideGo.name = "insidePos_" + posID;insideGo.transform.localScale = Vector3.one * 0.025f;insideGo.transform.parent = posRoot;insideGo.transform.position = insidePos;var outsideGo = GameObject.CreatePrimitive(PrimitiveType.Cube);outsideGo.GetComponent<Collider>().enabled = false;outsideGo.name = "outsidePos_" + posID;outsideGo.transform.localScale = Vector3.one * 0.025f;outsideGo.transform.parent = posRoot;outsideGo.transform.position = outsidePos;Physics.SyncTransforms();//outside -> inside 射线检测Vector3 dir = (insidePos - outsidePos).normalized;RaycastHit hit;if (Physics.Raycast(outsidePos, dir, out hit)){//一次成功: 外点->撞击点线段CreateLine(posRoot, new List<Vector3>(){outsidePos, hit.point}, "Line" + posID, Color.cyan);Debug.Log(posID + "点, 射线检测到模型:" + hit.collider.gameObject.name);pointList.Add(hit.point);lastNormal = hit.normal;}else{CreateLine(posRoot, new List<Vector3>(){outsidePos, insidePos}, "Line" + posID, Color.yellow);Debug.LogError(posID + "点, 无法正常检测到模型");//逐个角度调整 直至检测到为止float rawAngle = angle;bool isOk = false;int whileCnt = 0;while (angle + 1 != rawAngle){whileCnt++;if (whileCnt > 360){Debug.LogError("死循环.");break;}// GameObject.DestroyImmediate(outsideGo);//调整outside点angle++;angle %= 360f;rotation = Quaternion.AngleAxis(angle, Vector3.up);outsidePos = insidePos + rotation * Vector3.right * 1;outsideGo = GameObject.CreatePrimitive(PrimitiveType.Cube);outsideGo.GetComponent<Collider>().enabled = false;outsideGo.name = "outsidePos_" + posID;outsideGo.transform.localScale = Vector3.one * 0.025f;outsideGo.transform.parent = posRoot;outsideGo.transform.position = outsidePos;//外点->内点线条CreateLine(posRoot, new List<Vector3>(){outsidePos, insidePos}, "Line" + posID, Color.blue);//outside -> inside 射线检测dir = (insidePos - outsidePos).normalized;if (Physics.Raycast(outsidePos, dir, out hit)){//内点->撞击点线条CreateLine(posRoot, new List<Vector3>(){insidePos, hit.point}, "撞击_Line" + posID, Color.red);Debug.Log(posID + $"点, 从角度{rawAngle} -> {angle}调整后, 射线检测到模型:" + hit.collider.gameObject.name + ", 撞击点:" + hit.point);pointList.Add(hit.point);lastNormal = hit.normal;isOk = true;break;}}if (!isOk){Debug.LogError($"{posID} 点异常 无法正常射线检测到物体,已经绕了360度了 可能需调小每次调整角度 目前是1°");   }}curY += deltaY;}GameObject.DestroyImmediate(redLineRootGo);redLineRootGo = new GameObject("RedLineRoot");Transform rootTrans = redLineRootGo.transform;for (int i = 0; i < pointList.Count; i++){Vector3 vertex = pointList[i];GameObject vGo = GameObject.CreatePrimitive(PrimitiveType.Cube);vGo.GetComponent<Collider>().enabled = false;vGo.gameObject.name = i.ToString();vGo.transform.parent = rootTrans;vGo.transform.localScale = Vector3.one * 0.01f;vGo.transform.position = vertex;}CreateLine(rootTrans, pointList, "Line", Color.red);}else{Debug.LogError("必须选中一个模型物体");}}}public static void ClearConsole(){
#if UNITY_EDITORSystem.Reflection.Assembly assembly = System.Reflection.Assembly.GetAssembly(typeof(UnityEditor.SceneView));System.Type logEntries = assembly.GetType("UnityEditor.LogEntries");System.Reflection.MethodInfo clearConsoleMethod = logEntries.GetMethod("Clear");clearConsoleMethod.Invoke(new object(), null);
#endif}static void CreateLine(Transform parent, List<Vector3> pointList, string name, Color color, float width = 0.005f){GameObject lineGo = new GameObject(name);lineGo.transform.parent = parent;LineRenderer lineRenderer = lineGo.AddComponent<LineRenderer>();lineRenderer.positionCount = pointList.Count;lineRenderer.SetPositions(pointList.ToArray());lineRenderer.startColor = color;lineRenderer.endColor = color;lineRenderer.startWidth = width;lineRenderer.endWidth = width;lineRenderer.material = new Material(Shader.Find("Sprites/Default"));}
}

http://www.xdnf.cn/news/701371.html

相关文章:

  • 企业应如何构建用户画像系统
  • C语言Day9:C语言类型转换规则
  • Linux Crash工具全解:内核崩溃分析的一切
  • shell脚本总结11
  • 华为OD机试真题——矩形绘制(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • 数据库表与实体类设计
  • 中望CAD与AutoCAD的SWOT对比分析(基于2025线上发布会观察与行业数据)
  • 阿里云云效对接SDK获取流水线制品
  • C++模板语法大全
  • Rust 的Hello World
  • 在qt中使用c++实现与Twincat3 PLC变量通信
  • 知行之桥如何将消息推送到钉钉群?
  • 前端面经 hook 获取dom元素
  • Cookie与Session简介-笔记
  • 代谢测定试剂盒_生化制剂_Sigma-Aldrich®实验室用品及生产材料
  • FastApi学习
  • AMBA-AHB的控制信号
  • jenkins部署slave动态节点
  • java 开发中 nps的内网穿透 再git 远程访问 以及第三放支付接口本地调试中的作用
  • 使用 find 遍历软链接目录时,为什么必须加 -L
  • 华为OD最新机试真题-按单词下标区间翻转文章内容-OD统一考试(B卷)
  • 【案例95】“小”问题引发的“大”发现---记一次环境修复
  • 十六进制数据转换为对应的字符串
  • Python 如何让自动驾驶的“眼睛”和“大脑”真正融合?——传感器数据融合的关键技术解析
  • Java+POI+EXCEL导出柱形图(多列和单列柱形图)
  • 外骨骼驾驶舱HOMIE——3500元让人形机器人1:1复刻人类动作:类似Mobile ALOHA主从臂的主从分离版
  • 深度学习入门:从零搭建你的第一个神经网络
  • Vue3对接deepseek实现ai对话
  • 系统性学习C语言-第十讲-操作符详讲
  • javascript中运算符的优先级