上一篇文章我们已经将地图的信息转换成了模型需要的Meh信息,但是生成的是一个平面的效果,所以接下来就要通过其他的方法生成3D的墙面,将上一篇生成的地图的边界衍生出垂直的墙面。

本篇本章的内容的视频链接https://www.youtube.com/watch?v=AsR0-wCTJl8。

再接着上篇文章的内容之前,视频作者对上一个视频的一个错误的代码进行了修改,因为要通过边界来生成墙面,那么边界的点的顺序必然是需要有统一的规定,比如这个图(截自视频),如果此时我们的只有3的这个四方的顶点是active = true,或者说地图信息中的值是等于1的。那么如果只想生成一个面的话只要是顺时针的存储三角形的顶点都能够生成,但是如果AC是做为边界的话,ABC三角就是点A到点C,而BCA三角形的就是点C到点A,所以,为了统一边界的边的方向,视频作者对代码做了简单的修改,对TriangulateSquare函数中的Switch中的1,2,4的点的顺序做了修改。对于上一篇文章这个地方的代码就能发现区别。

            case 1:MeshFromPoints(square.centreLeft, square.centreBottom, square.bottomLeft);break;case 2:MeshFromPoints(square.bottomRight, square.centreBottom, square.centreRight);break;case 4:MeshFromPoints(square.topRight, square.centreRight, square.centreTop);

好了,接下来进入正题,将修改后生成的平面地图立体起来!

首先我们得知道一条边在满足什么条件的情况是一个边界。举个例子

 (截自视频)这个图中的白色的边,显然不是一个边界,边上边的那个点被AB两个三角形公用,下面的点被ABC三个三角形公用,此时有AB两个三角形是两个点都存在。换个例子

 (截自视频)这个途中的白色的边,显然是这个多边形的一个边界,左边的点只有A三角形使用,而右边的点由ABC三个三角形公用,此时只有A三角形是两个点都存在,此时得出的边界的定义:当边的两个点只公用一个三角形的时候,这条边就是多边形的边界。(我想网上应该有更多关于这个的解释,如果认为笔者讲的不好的可以上网搜搜)。

既然得出这个结论,那么我们就通过这个结论去实现我们需要的东西。

首先,我们先给之前我们的地图信息包个外框,修改MapGenerator脚本的GenerateMap函数:

    /// <summary>/// 生成地图/// </summary>void GenerateMap(){map = new int[width, height];RandomFillMap();for (int i = 0; i < 5; i++){SmoothMap();}//创建出边界,这里的边界是地图的最外圈让其有宽度为borderSize的墙,这个值可以随便修改int borderSize = 1;int[,] borderedMap = new int[width + borderSize * 2, height + borderSize * 2];  //乘2的原因是边界有左右,上下for (int x = 0; x < borderedMap.GetLength(0); x++){for (int y = 0; y < borderedMap.GetLength(1); y++){//如果不是新添加的边界,那么就是原来的地图的信息,将原来的数组的数据映射到新的边界地图数据上,如果是边界就直接赋值1if (x >= borderSize && x < width + borderSize && y >= borderSize && y < height + borderSize){//减边界大小是为了让地图生成在中心borderedMap[x, y] = map[x - borderSize, y - borderSize];}else{borderedMap[x, y] = 1;}}}MeshGenerator meshGen = GetComponent<MeshGenerator>();//传进新的地图信息meshGen.GenerateMesh(borderedMap, 1);}

代码的解释,在注释中详细的说明了。

然后回到MeshGenerator脚本,为了能够对于顶点的三角形公用的关系,我们就需要为这些信息定义一个点与三角形的关系的结构体Triangle,其中只需要三个顶点的信息(在所有顶点列表中的索引)就可以了。然后为结构体写一个便于访问一些信息的函数,定义全局字典变量Dictionary<int, List<Triangle>> triangleDictionary,Key是顶点在所有顶点列表中的索引,Value是该顶点被公用的三角形的信息。这样我们就可以通过顶点的索引来获取三角形的关系,然后去对比两个顶点的三角形的公用数来判断是否是边界。所以在之前的CreateTriangle函数的基础上我们做出的修改就是讲传进来的三角面的三个点再拿去创建一个Triangle的结构(这里的Triangle出现的非常的频繁,希望大家不要混淆了,CreateTriangle在上一篇是服务于四方的信息,而这片文章中的Triangle结构体是本章新创建的结构体struct,服务于顶点和三角形的相对关系),并且将顶点和创建出来的三角形结构体存进triangleDictionary字典中。这里我们还需要两个变量:

List<List<int>> outlines = new List<List<int>>();        HashSet<int> checkedVertices = new HashSet<int>();

outlines存的是顶点索引列表的列表(有点拗口),里面的List<int>存的点的索引是该边界的环上的所有的线所使用的到的点(比方说:ABCDEFGA这几个点形成了一个边界,那么就存有ABCDEFG这几个点,AB,BC...GA都是边界线)。

checkedVertices 存的是被检查过的顶点,如果一个顶点被判断过是边界上的点或者不是,都会被存进哈希表中,这样就不会重复的去判断一个点是不是边界上的点。

所以接下来我们就去遍历所的顶点,拿到没有被检查过的一个点A,获取到该点被公用的所有三角形的列表,然后拿到每个三角形的其他的几个点,这几个点和点A都能形成一条边,然后遍历这几个点拿到这几个点被公用的三角形,这样就可以知道相同三角形的数量,根据之前的结论,如果相同的数量是1那么就是边界。这样就能拿到形成边界的另外一个点,将这个点存进checkedVertices中,然后通过递归不断去获取下个与这个新的点形成边界的不存在checkedVertices中的点,直接找不到说明已经找到其中一个边界,直接遍历完所有的点,我们就能拿到所有的边界了。

拿到边界的点就容易了,新建一个子对象(“”Walls“”),挂上MeshRenderer和MeshFilter,脚本中创建新的Mesh,遍历outlines,拿到其中的一个边界的列表outline,两个两个点的去遍历outline中点,向下延伸出新的两个点,高度自定义,但是要注意的是,形成墙的mesh信息的triagles时要注意,因为墙是在内部生成的,所以triangles的添加顺序要变成是逆时针。

完整代码:

MapGenerator的修改上面已经修改了。

MeshGenerator:

public class MeshGenerator : MonoBehaviour {public SquareGrid squareGrid;   //四方网格public MeshFilter walls;        //墙壁的meshfilter组件,新建一个子对象挂在MeshRenderer和MeshFilter,拖拽给该变量赋值List<Vector3> vertces;          //地图信息的顶点的列表List<int> triangles;            //地图信息的所有三角面信息的列表Dictionary<int, List<Triangle>> triangleDictionary = new Dictionary<int, List<Triangle>>();     //包含顶点的三角形的列表和顶点的关系字典List<List<int>> outlines = new List<List<int>>();       //所有边界的列表,储存的是顶点的索引HashSet<int> checkedVertices = new HashSet<int>();      //已经检查过边界的顶点的哈希表,哈希表的特点可以去网上找到很多信息/// <summary>/// 创建生成网格/// </summary>/// <param name="map"></param>/// <param name="squareSize"></param>public void GenerateMesh(int[,] map, float squareSize){triangleDictionary.Clear();outlines.Clear();checkedVertices.Clear();squareGrid = new SquareGrid(map, squareSize);vertces = new List<Vector3>();triangles = new List<int>();for (int x = 0; x < squareGrid.squares.GetLength(0); x++){for (int y = 0; y < squareGrid.squares.GetLength(1); y++){TriangulateSquare(squareGrid.squares[x, y]);}}Mesh mesh = new Mesh();GetComponent<MeshFilter>().mesh = mesh;mesh.vertices = vertces.ToArray();mesh.triangles = triangles.ToArray();mesh.RecalculateNormals();CreateWallMesh();}void CreateWallMesh(){//计算获得所有的边界顶点CalculateMeshOutlines();List<Vector3> wallVertices = new List<Vector3>();        //墙mesh的顶点列表List<int> wallTriangles = new List<int>();              //墙的mesh的三角列表Mesh mesh = new Mesh();float wallHeight = 5;                               //墙的高度foreach (List<int> outline in outlines){for (int i = 0; i < outline.Count - 1; i++){int startIndex = wallVertices.Count;wallVertices.Add(vertces[outline[i]]); //leftwallVertices.Add(vertces[outline[i + 1]]); //rightwallVertices.Add(vertces[outline[i]] - Vector3.up * wallHeight); //bottom left          ,墙向下延伸wallVertices.Add(vertces[outline[i + 1]] - Vector3.up * wallHeight); //bottom right      /** left     right* * * * bottom   bottom* left     right* * left -> bottomLeft -> bottomRight ,  bottomRight -> right -> left* 由于墙是要从内向外看,所以三角形的点的顺序要变成逆时针来存进去*/wallTriangles.Add(startIndex + 0);wallTriangles.Add(startIndex + 2);wallTriangles.Add(startIndex + 3);wallTriangles.Add(startIndex + 3);wallTriangles.Add(startIndex + 1);wallTriangles.Add(startIndex + 0);}}mesh.vertices = wallVertices.ToArray();mesh.triangles = wallTriangles.ToArray();walls.mesh = mesh;}/// <summary>/// 将方形的信息转换成三角信息/// </summary>/// <param name="square"></param>void TriangulateSquare(Square square){switch (square.configuration){case 0:break;// 只有一个点激活的情况case 1:MeshFromPoints(square.centreLeft, square.centreBottom, square.bottomLeft);break;case 2:MeshFromPoints(square.bottomRight, square.centreBottom, square.centreRight);break;case 4:MeshFromPoints(square.topRight, square.centreRight, square.centreTop);break;case 8:MeshFromPoints(square.topLeft, square.centreTop, square.centreLeft);break;// 两个点激活的情况case 3:MeshFromPoints(square.centreRight,square.bottomRight, square.bottomLeft, square.centreLeft);break;case 6:MeshFromPoints(square.centreTop, square.topRight, square.bottomRight, square.centreBottom);break;case 9:MeshFromPoints(square.topLeft, square.centreTop, square.centreBottom, square.bottomLeft);break;case 12:MeshFromPoints(square.topLeft, square.topRight, square.centreRight, square.centreLeft);break;case 5:MeshFromPoints(square.centreTop, square.topRight, square.centreRight, square.centreBottom, square.bottomLeft, square.centreLeft);break;case 10:MeshFromPoints(square.topLeft, square.centreTop, square.centreRight, square.bottomRight, square.centreBottom, square.centreLeft);break;// 三个点激活的情况case 7:MeshFromPoints(square.centreTop, square.topRight, square.bottomRight, square.bottomLeft, square.centreLeft);break;case 11:MeshFromPoints(square.topLeft, square.centreTop, square.centreRight, square.bottomRight, square.bottomLeft);break;case 13:MeshFromPoints(square.topLeft, square.topRight, square.centreRight, square.centreBottom, square.bottomLeft);break;case 14:MeshFromPoints(square.topLeft, square.topRight, square.bottomRight, square.centreBottom, square.centreLeft);break;// 四个点激活的情况case 15:MeshFromPoints(square.topLeft, square.topRight, square.bottomRight, square.bottomLeft);checkedVertices.Add(square.topLeft.vertexIndex);checkedVertices.Add(square.topRight.vertexIndex);checkedVertices.Add(square.bottomRight.vertexIndex);checkedVertices.Add(square.bottomLeft.vertexIndex);break;}}/// <summary>/// 通过顶点来生成Mesh的相关信息/// </summary>/// <param name="points"></param>void MeshFromPoints(params Node[] points){AssignVertices(points);//这里我一开始也是看不明白为什么要这么写,然后发现这么写实在是太巧妙了。//从起始点出发,如果只有三个点就只需要画一个三角形,如果是四个点就会进入第二个判断,这两就能画出两个三角形,一次类推。//相当于三个点就就是012, 如果是四个点就是012,023, 五个点就是画012,023,034if (points.Length >= 3)CreateTriangle(points[0], points[1], points[2]);if (points.Length >= 4)CreateTriangle(points[0], points[2], points[3]);if (points.Length >= 5)CreateTriangle(points[0], points[3], points[4]);if (points.Length >= 6)CreateTriangle(points[0], points[4], points[5]);}/// <summary>/// 分配顶点/// </summary>/// <param name="points"></param>void AssignVertices(Node[] points){for (int i = 0; i < points.Length; i++){if (points[i].vertexIndex == -1){//让index = count 就能够做到从0到最大值-1points[i].vertexIndex = vertces.Count;vertces.Add(points[i].position);}}}/// <summary>/// 根据三点创建三角面/// </summary>/// <param name="a"></param>/// <param name="b"></param>/// <param name="c"></param>void CreateTriangle(Node a, Node b, Node c){///将顶点的关系存进三角信息列表中triangles.Add(a.vertexIndex);triangles.Add(b.vertexIndex);triangles.Add(c.vertexIndex);//创建顶点与三角形的关系的结构体Triangle triangle = new Triangle(a.vertexIndex, b.vertexIndex, c.vertexIndex);AddTrianleToDictionary(triangle.vertexIndexA, triangle);AddTrianleToDictionary(triangle.vertexIndexB, triangle);AddTrianleToDictionary(triangle.vertexIndexC, triangle);}/// <summary>/// 将三角信息存进字典中/// </summary>/// <param name="vertexIndex"></param>/// <param name="triangle"></param>void AddTrianleToDictionary(int vertexIndex, Triangle triangle){if (triangleDictionary.ContainsKey(vertexIndex)){triangleDictionary[vertexIndex].Add(triangle);}else{triangleDictionary.Add(vertexIndex, new List<Triangle>() { triangle });}}/// <summary>/// 获取到相连接的能形成边界的一条边上的另一个店/// </summary>/// <param name="vertexIndex"></param>/// <returns></returns>int GetConnectedOutlineVertex(int vertexIndex){List<Triangle> triangleContainingVertex = triangleDictionary[vertexIndex];for (int i = 0; i < triangleContainingVertex.Count; i++){Triangle triangle = triangleContainingVertex[i];for (int j = 0; j < 3; j++){int vertexB = triangle[j];if (vertexB != vertexIndex && !checkedVertices.Contains(vertexB)){if (IsOutLineEdge(vertexIndex, vertexB)){return vertexB;}}}}return -1;}/// <summary>/// 计算出所有的边界/// </summary>void CalculateMeshOutlines(){for (int vertexIndex = 0; vertexIndex < vertces.Count; vertexIndex++){if (!checkedVertices.Contains(vertexIndex)){int newOutlineVertex = GetConnectedOutlineVertex(vertexIndex);if (newOutlineVertex != -1){//未检查过的点存在边界点checkedVertices.Add(vertexIndex);//创建新的边界列表然后通过递归将所有的边界点拿到List<int> newOutline = new List<int>();newOutline.Add(vertexIndex);outlines.Add(newOutline);FollowOutline(newOutlineVertex, outlines.Count - 1);outlines[outlines.Count - 1].Add(vertexIndex);}}}}/// <summary>/// 递归获取到同一个边界上所有边界点的信息/// </summary>/// <param name="vertexIndex"></param>/// <param name="outlineIndex"></param>void FollowOutline(int vertexIndex, int outlineIndex){outlines[outlineIndex].Add(vertexIndex);checkedVertices.Add(vertexIndex);int nextVertexIndex = GetConnectedOutlineVertex(vertexIndex);if (nextVertexIndex != -1){FollowOutline(nextVertexIndex, outlineIndex);}}/// <summary>/// 通过一条边两个顶点判断该边是否是边界/// </summary>/// <param name="vertexA"></param>/// <param name="vertexB"></param>/// <returns></returns>bool IsOutLineEdge(int vertexA, int vertexB){List<Triangle> triangleContianingVertexA = triangleDictionary[vertexA];int sharedTriangleCount = 0;for (int i = 0; i < triangleContianingVertexA.Count; i++){if (triangleContianingVertexA[i].Contains(vertexB)){sharedTriangleCount++;if (sharedTriangleCount > 1){break;}}}return sharedTriangleCount == 1;}/// <summary>/// 三角信息结构体/// </summary>struct Triangle{public int vertexIndexA;public int vertexIndexB;public int vertexIndexC;int[] verteces;public Triangle(int a, int b, int c){vertexIndexA = a;vertexIndexB = b;vertexIndexC = c;verteces = new int[] { vertexIndexA, vertexIndexB, vertexIndexC };}//让结构体能像索引一直通过index访问三角中的顶点public int this[int i]{get { return verteces[i]; }}public bool Contains(int vertexIndex){return vertexIndex == vertexIndexA || vertexIndex == vertexIndexB || vertexIndex == vertexIndexC;}}/// <summary>/// 方形网格/// </summary>public class SquareGrid{public Square[,] squares;public SquareGrid(int[,] map, float squareSize){//计算出控制点的x方向的数量和y方向的数量,其实就是二维数组的长宽int nodeCountX = map.GetLength(0);int nodeCountY = map.GetLength(1);//根据传进来的方形的边长计算出所有的点构成的面长和高(这里的长指的是x方向的长度,高指的是z方向的纵深)float mapWidth = nodeCountX * squareSize;float mapHeight = nodeCountY * squareSize;//创建控制点的二维数组ControlNode[,] controlNodes = new ControlNode[nodeCountX, nodeCountY];for (int x = 0; x < nodeCountX; x++){for (int y = 0; y < nodeCountY; y++){//计算控制点的位置,并且做了偏移,让整体的中心在原点Vector3 pos = new Vector3(-mapWidth * .5f + x * squareSize + squareSize * .5f, 0, -mapHeight * .5f  + y * squareSize + squareSize * .5f);//如果是1说明是墙,就将active设置成true,不是则为falsecontrolNodes[x, y] = new ControlNode(pos, map[x, y] == 1, squareSize);}}//由于一个四边是有四个点构成,square的总量是nodeCountX - 1 * nodeCoungY - 1, 这里在纸上点出几个点再画四边形就能理解啦。squares = new Square[nodeCountX - 1, nodeCountY - 1];for (int x = 0; x < nodeCountX -1; x++){for (int y = 0; y < nodeCountY - 1; y++){//初始化要按顺序传入topLeft, topRight, _bottomLeft, _bottomRight;squares[x, y] = new Square(controlNodes[x, y + 1], controlNodes[x + 1, y + 1], controlNodes[x, y], controlNodes[x + 1, y]);}}}}/// <summary>/// 方形区域,记录包块四个角的控制点,和每条边的中心的点/// </summary>public class Square{public ControlNode topLeft, topRight, bottomRight, bottomLeft;public Node centreTop, centreRight, centreBottom, centreLeft;public int configuration;public Square(ControlNode _topLeft, ControlNode _topRight, ControlNode _bottomLeft, ControlNode _bottomRight){topLeft = _topLeft;topRight = _topRight;bottomLeft = _bottomLeft;bottomRight = _bottomRight;centreTop = topLeft.right;centreLeft = bottomLeft.above;centreBottom = bottomLeft.right;centreRight = bottomRight.above;if (topLeft.active)configuration += 8;if (topRight.active)configuration += 4;if (bottomRight.active)configuration += 2;if (bottomLeft.active)configuration += 1;}}/// <summary>/// 点的基础类/// </summary>public class Node{public Vector3 position;public int vertexIndex = -1;public Node(Vector3 _position){position = _position;}}/// <summary>/// 控制点(一个方形区域的四角)/// </summary>public class ControlNode : Node{public bool active;public Node above, right;public ControlNode(Vector3 _position, bool _active, float squareSize) : base(_position){active = _active;above = new Node(position + Vector3.forward * squareSize * .5f);right = new Node(position + Vector3.right * squareSize * .5f);}}
}

代码的注释写的应该还是比较清楚的吧,希望有所帮助~。

最后再贴一张效果图

程序洞穴生成三(Procedural Cave Generation)相关推荐

  1. 程序洞穴生成六(Procedural Cave Generation)

    这次直接将后面的内容一次性讲完啦.其实本来是想一个一个视频的讲的,但是今天才发现我上次准备好的内容没有写然后没有发,所以今天就一起讲了,其实剩下的视频是有三个的,最后一个视频主要是作者的应用,写了一些 ...

  2. 程序洞穴生成一(Procedural Cave Generation)

    这边博客是我自己学习过程中写的第一篇文章,一方面是想与大家分享好的知识,另一方面是为了监督自己不断的学习,希望能给大家带来好的内容分享. 这篇文章的内容是搬运自Youtube上的Sebastian大佬 ...

  3. Java黑皮书课后题第4章:*4.25(生成车牌号码)假设一个车牌号码由三个大写字母和后面的四个数字组成。编写一个程序,生成一个车牌号码

    *4.25(生成车牌号码)假设一个车牌号码由三个大写字母和后面的四个数字组成.编写一个程序,生成一个车牌号码 题目 题目概述 破题 代码 题目 题目概述 *4.25(生成车牌号码)假设一个车牌号码由三 ...

  4. GPU Pro2 - 3.Procedural Content Generation on the GPU

    GPU Pro2  - 3.Procedural Content Generation on the GPU 这篇文章着重介绍了基于Brownian 噪声和高度图在GPU中实时生成和渲染无限大地形系统 ...

  5. php小程序码生成并保存,小程序中如何生成小程序码

    导语: 小程序是一种不需要下载安装即可使用的应用,它实现了应用"触手可及"的梦想,用户扫一扫或者搜一下即可打开应用.也体现了"用完即走"的理念,用户不用关心是否 ...

  6. poi生成word不可以修改_操作不懂技术就可以做小程序无限生成平台的创业项目实操教程...

    小程序现在是个风口,很多人都涉足这个行业,不过大部分都是站在用户的角度去接触的小程序,作为创业者,我们有没有机会可以给别人做小程序呢?当然是可以的,今天我们就来操作这样一个小程序无限生成的项目,对于零 ...

  7. Android项目实战(二):安卓应用程序退出的三种方法

    现在的APP退出的时候都不是让用户点击了"后退键"就退出.防止用户点错了后退键而造成的用户体检不好. 一年前搞的Demo代码不见了,重新写下就当是复习和以后直接拿来用把 目前流行的 ...

  8. uniapp使用高德地图微信小程序SDK生成地图轨迹

    一文看懂微信小程序生成地图轨迹 一.开发准备 1.下载微信小程序SDK 2.高德控制台申请相关平台key 二.html页面展示地图标签 三.引入sdk及定义数据 四.相关方法(静态生成两个点之间的轨迹 ...

  9. 小程序一键生成系统网站源码

    简介: 小程序一键生成系统网站源码,亲测没有问题,项目一本万利! 搭建部署方式: 测试环境:Linux+宝塔+php5.6+mysql5.5 第一步:修改配置信息 修改指引: 1:修改数据库配置连接路 ...

最新文章

  1. 四级重点高频词汇表_四级为什么自己估分和真实成绩不一样……?
  2. 运行windows live writer时发生“意外错误”
  3. 这一次,腾讯用AI让手语“发声”
  4. word2vec相关
  5. 神圣的傻瓜,善良的杰克·凯鲁亚克:纪念《在路上》50年
  6. Google Guava学习笔记——基础工具类Joiner的使用
  7. 关于udelay(); mdelay(); ndelay(); msleep();
  8. 自动将存储过程转成C#代码的过程[转]
  9. ionicView视图的生命周期
  10. 编程必备基础知识-计算机组成原理-概述篇
  11. 项目进度管理方法——甘特图
  12. PCB布线宽度与 mil与mm转换 等技巧
  13. springsecurity与gateway网关整合配置
  14. Room 使用解析(2.4.2 版本)
  15. 有意思的hand-crafted features based IQA的论文吧2(图像质量评价)
  16. Docker Nginx 如何重新加载配置
  17. windows 没有应用商店,直接安装所需应用的解决方案
  18. 桌面 计算机 网络连接怎么办,电脑显示不安全网络连接失败怎么办
  19. 课设:指纹签到系统-支持PC网页端查看
  20. QSystemTrayIcon退出后系统托盘图标不消失问题

热门文章

  1. 春耕有时,2016触控科技高校认证讲师训练营华丽启动
  2. 微信小程序saveFile,openDocument方法下载、预览pdf文件不能用本地应用打开(不能另存为)的问题
  3. 336(see). Palindrome Pairs 5.(see)
  4. 申请EV https证书前的注意事项
  5. 【趣味】0基础快速掌握区块链服务关键概念
  6. 华为EMUI8/9刷机直通车:一篇文章拿下安卓13
  7. 基于https协议访问SeaTable
  8. J2ME手机蓝牙间谍控制之我所见
  9. matlab实现数值积分 【一】(trapz函数)
  10. 2021年昌聚工程申报奖励及要求,补贴100万