最近要跟别人联机打无主之地3,忙里抽闲做点东西,避免大脑萎缩。
Mesh的切割做如下思路:拟定切割的平面
求得新增的切割点
缝合截面
纠正绕序得到新Mesh
拟定切割面
首先看第一个,拟定切割面。其实这个步骤包含两步,第一步是从输入拿到世界坐标空间下的平面,第二步是把平面从世界坐标空间转到各个模型坐标空间。
先规划好数据类型吧:
public struct DividePlane
{
public Vector3 normal, point;
public Vector3 v1, v2, v3;
}
public struct DivideSegment
{
public Vector3 p0, p1;
public bool available { get { return p0 != p1; } }
public DivideSegment(Vector3 v0, Vector3 v1)
{
p0 = v0; p1 = v1;
}
}
DividePlane之所以要这么设计,是因为一个Plane对不同的模型有着不同的转换后的样式……从输出中拿平面是这样的:
private Vector3 pressStart, pressEnd;
public List waitForDivide = new List();
private void Update()
{
if (Input.GetMouseButtonDown(0))
pressStart = Input.mousePosition;
if (Input.GetMouseButtonUp(0))
{
pressEnd = Input.mousePosition;
if (pressEnd == pressStart) return;
DividePlane plane = new DividePlane();
plane.v1 = Camera.main.ScreenToWorldPoint(new Vector3(pressStart.x, pressStart.y, Camera.main.nearClipPlane));
plane.v2 = Camera.main.ScreenToWorldPoint(new Vector3(pressEnd.x, pressEnd.y, Camera.main.nearClipPlane));
plane.v3 = Camera.main.ScreenToWorldPoint(new Vector3(pressEnd.x, pressEnd.y, Camera.main.farClipPlane));
for (int i = waitForDivide.Count - 1; i >= 0; i--)
if (TransformPlane(waitForDivide[i], ref plane))
{
//... }
}
}
然后在转换的过程中,不能先计算法线然后拿去转换,应当把三个点转换好了再Vector3.Cross,这个有必要强调一下,因为以前踩过坑:
/// /// DividePlane先转到模型坐标空间下 /// private bool TransformPlane(GameObject toDivide, ref DividePlane plane)
{
var mf = toDivide.GetComponent();
if (mf == null) return false;
var v1 = toDivide.transform.worldToLocalMatrix.MultiplyPoint(plane.v1);
var v2 = toDivide.transform.worldToLocalMatrix.MultiplyPoint(plane.v2);
var v3 = toDivide.transform.worldToLocalMatrix.MultiplyPoint(plane.v3);
plane.point = v2;
plane.normal = Vector3.Cross(v3 - v2, v1 - v2).normalized;
return true;
}
计算面与线段的交点
当然,这还是可以划分为两个命题,如何判断线段与面相交,和如何计算这个交点,因为用的点法式表达式,我们可以直接用线段的两端到平面上点的向量与法向量的点积来判断。如果相交的话,必然有两个点积符号相异。
更进一步的,要计算该点的话,就要把线段与平面的交点表达为如下形式:
将平面点法式中的点和法线表达为
和
,有:
解该方程可得
,也就得到交点。
private static bool Intersect(DividePlane plane, DivideSegment segment)
{
if (!segment.available) return false;
Vector3 d0 = segment.p0 - plane.point;
Vector3 d1 = segment.p1 - plane.point;
float t0 = Vector3.Dot(d0, plane.normal);
float t1 = Vector3.Dot(d1, plane.normal);
return t0 * t1 < 0;
}
private static void GetCrossPoint(DividePlane plane, DivideSegment segment, out float k, out Vector3 crossPoint)
{
Vector3 d = segment.p1 - segment.p0;
Vector3 n = plane.normal;
Vector3 v = plane.point;
Vector3 v0 = segment.p0;
k = (n.x * v.x - v0.x * n.x
+ n.y * v.y - v0.y * n.y
+ n.z * v.z - v0.z * n.z) /
(n.x * d.x +
n.y * d.y +
n.z * d.z);
k = Mathf.Clamp01(k);
crossPoint = v0 + k * d;
}
剩下的内容就比较枯燥,对三种情况进行检查,然后要做的就是在绕序中把原先的三角形绕序替换成新的三个三角形绕序:
缝合截面
这一块我暂时没想好,因为思索到了三个问题:截面上顶点的顺序问题
凹多边形如何分配绕序
截面可能由多个封闭形状构成
且待我之后再细细思索。
生成新Mesh
准备好两个新Mesh的容器(当然,可以将旧Mesh所使用的容器认为是其中之一),然后将顶点“倾倒并过滤”。
在这里有个小小的优化,我们新增的顶点绝对是在顶点数组末尾的,因此只需要检查前面的顶点就行:
// 检查原Mesh里所有的顶点并分离到左右容器内 for (int i = 0; i < originalVertexCount; i++)
{
Vector3 v = vertices[i];
int flag = IsLeft(plane, v);
if (flag == 1)
{
leftIndexDic.Add(i, vertices_l.Count);
vertices_l.Add(v);
uvs_l.Add(uvs[i]);
}
else if (flag == -1)
{
rightIndexDic.Add(i, vertices_r.Count);
vertices_r.Add(v);
uvs_r.Add(uvs[i]);
}
}
// 对于 i >= origialVertexCount的顶点而言,必然是后续新增的点 // 即,必然是切面上的点,因此两边都要添加 for (int i = originalVertexCount; i < vertices.Count; i++)
{
Vector3 v = vertices[i];
Vector2 u = uvs[i];
leftIndexDic.Add(i, vertices_l.Count);
rightIndexDic.Add(i, vertices_r.Count);
uvs_l.Add(u);
uvs_r.Add(u);
vertices_l.Add(v);
vertices_r.Add(v);
}
// 缝合伤口,顶点已经定了,因此只对finalTriangle做修改就可以了 // TODO // 最后检查triangles,调整绕序里的ID for (int i = 0; i < finalTriangles.Count; i += 3)
{
// 在左边 var vi0 = finalTriangles[i];
var vi1 = finalTriangles[i + 1];
var vi2 = finalTriangles[i + 2];
bool l = leftIndexDic.ContainsKey(vi0) &&
leftIndexDic.ContainsKey(vi1) &&
leftIndexDic.ContainsKey(vi2);
bool r = rightIndexDic.ContainsKey(vi0) &&
rightIndexDic.ContainsKey(vi1) &&
rightIndexDic.ContainsKey(vi2);
if (l && !r)
{
triangles.Add(leftIndexDic[vi0]);
triangles.Add(leftIndexDic[vi1]);
triangles.Add(leftIndexDic[vi2]);
}
if (r && !l)
{
triangles_r.Add(rightIndexDic[vi0]);
triangles_r.Add(rightIndexDic[vi1]);
triangles_r.Add(rightIndexDic[vi2]);
}
if (l && r)
Debug.LogError("this triangle cross the plane");
if (!l && !r)
Debug.LogError("this triangle cannot put into the array");
}
总结
Mesh的切割总的来看还是非常废CPU资源的东西。
首先,得先过滤掉不需要被切割的东西。这需要我们设定切割的范围,并且对所有能被切的东西有个空间管理。这需要一个类似八叉树的空间管理算法来做第一步筛减。
其次,得过滤掉被切分得过多的东西,一个东西被多次切分之后势必会新增巨多的顶点和面数,导致下一次被切割的资源消耗呈指数型上升。这需要所有为切分的物体增加指示器或者是顶点数量和可见性的判断。
最后,切分是一个需要大量生成GameObject和重建Mesh的工作,一个池是跑不掉的。而Mesh的重建则是一个没法很好优化的点,个人的猜想是对距离小于某个值的情况下,对点进行合并,把大量切分时产生的碎屑面数降下来。
目前的效果如下所示:
https://www.zhihu.com/video/1230318190258671616
无主3编剧,真的,我就是抠脚都写不出你这种剧情。真的是屎,屎到让我对自己的神经产生了自我怀疑。跟群友一对,大家都这样认为啊?那没事了。
进展生硬无比,角色无比僵化。玛雅当场去世,莉莉丝原地升天。找一本好莱坞编剧书,照着章节一二三抠着写都比你写得好。
演出手法一塌糊涂,突然给你整个“除非抹掉星球”,然后原地升天。故事结束了,该收尾了,你这时候突兀无比整死个人,还整死个老角色,队友们一个个非常平静就我很上火。EXO ME?意义何在!?
反派塑造空有噱头,毫无内在光彩。就是两个有了力量的巨婴,毫无脑子整天BBBB,然而就这种废物天天骇进来跟你通话,给你玩螳螂捕蝉黄雀在后,想塑造疯狂高智商结果搞得我感觉自己是个弱智。
对,我是弱智,但我弱智是因为给定若干个顶点我无法判断连通性和绕序,而不是拿着秘藏钥匙还被人偷家!
而且我生活中最狂怒的就是别人没办法沟通,没办法理解。问题是这作的艾娃真的是个铁憨憨,给老子当宠物倒了我都懒得拉。
“秘藏猎人,来舰桥和我谈谈。”PTSD发作,请。
我想杀杰克是因为我很愤怒,我想杀泰瑞和特洛伊就是因为爷想速度结束游戏。一想到以后还要打三周目我就想下别人的存档。