[英]How can I connect two parallel 2d polygons to create a seamless 3d mesh?
假设我有两个多边形,一个位于另一个之上,如下所示:
我想连接他们的顶点,以围绕其周边的三角形创建一个3d网格。 这张图显示了您可以执行此操作的一种方式(橙色线代表三角形边缘):
这类事情可以由人类直观地完成,但是在将其转换为有效的算法方面确实遇到了麻烦。
多边形存储为List<Vector2>
。 它们将总是简单的,并且可能是凹形的。
我会这样做:
在每个多边形中找到两个最接近的点
这将用作起点
从这两个起点重新排列顶点
具有相同的缠绕规则 ,例如图像上的CW
找到每个多边形的“中心”点
例如所有顶点的平均值或边界框的中点之类的值。 它不需要很精确,但是在复杂的凹形上,错误选择的中心位置会导致输出网格的错误。
为每个顶点添加角度信息
角度很容易使用
a=atan2(vertex-center)
毕竟,它看起来应该像这样:
角度角度[deg]
index: 0 1 2 3 4
green: 355 085 135 170 230
blue: 020 135 255
现在,您只需将两个最接近的顶点从一个多边形匹配到另一个多边形
不要忘记角度差最大为+/-180 deg
,如果使用弧度,则将常数180,360
转换为PI,2.0*PI
da=ang1-ang0;
while (da<-180.0) da+=360.0;
while (da>+180.0) da-=360.0;
if (da<0.0) da=-da;
下一文本line(i,j)
将意味着i-th
从绿色和顶点j-th
从蓝色多边形顶点
现在加入:
加入最近的顶点
只需处理所有绿色顶点并将它们连接到最接近的蓝色顶点(图像上的黑线)
line(0,0) line(1,1) line(2,1) line(3,1) line(4,2)
填补漏洞
网格三角剖分每个顶点至少需要2个关节,因此连接较少的处理点:
index: 0 1 2 3 4 green connections: 1 1 1 1 1 blue connections: 1 3 1
因此现在处理少于2次的蓝色顶点(0,2)
,并将它们连接到最接近的绿色顶点(图像上的黄线),而忽略已使用的连接(在这种情况下,选择下一个)
line(1,0) line(3,2)
所以:
index: 0 1 2 3 4 green connections: 1 2 1 2 1 blue connections: 2 3 2
现在处理剩余的绿色(少于2个使用过的顶点,再加入少于3倍的使用蓝色顶点)。 如果连接少于3个,则选择已使用的连接的下一个点;如果连接大于1个,则选择最接近的一个(图像上的红线)。
line(0,2)
绿色0连接到蓝色0,因此可以从蓝色1,2中进行选择(太用了1,所以2)
line(2,-)
绿色2连接到蓝色1,使用了3次,因此忽略此顶点
line(4,-)
绿色4连接到蓝色2,蓝色2使用了3次,因此忽略此顶点
index: 0 1 2 3 4
green connections: 2 2 1 2 1
blue connections: 2 3 3
仅此而已。 现在,您应该像这样完成镶嵌处理:
[笔记]
[Edit1]一种新方法,不易受凹度和点密度的影响
它的速度较慢,但看起来可以用于更复杂的形状。 例如,我从评论中选择了修饰的星号和加号。
在每个多边形中找到两个最接近的点
这将用作起点
从这两个起点重新排列顶点
具有相同的缠绕规则,例如图像上的CW
准备边缘清单
我们将需要一个结构来保存多边形之间的所有连接。 我决定要这样:
List< List<int> > edg0,edg1;
其中edg0[point0][i]
保持连接到point0 i-th
point1
。 当心代码数组中的索引是点索引,数组的实际值n是全局点表pnt
中的点索引,我可以在两者之间进行如下转换:
point0 = (pnt_index - ix0)/3 point1 = (pnt_index - ix1)/3 pnt_index = ix0 + 3*point0 pnt_index = ix1 + 3*point1
其中ix0,ix1
是每个多边形的pnt
起始索引。
加入关闭点
我没有加入最接近的点,而是添加了阈值,因此点距离必须小于阈值minll
。 该阈值是两个起点的距离(最小距离)的乘积。 在代码中:
minll=2.5*l0;
但请注意,我正在比较distance^2
的速度,所以如果您获得距离,它将是
minl=sqrt(2.5)*l0;
乘数系数可以调整...
如您所见,由于阈值设置,恒星上的远点未连接。
填充点中未使用的孔
只需找到未使用点的序列即可,然后从连接的第一个邻居开始从头到尾插入连接。 因此,如果点0 i0 .. i1
未连接并且它们的最近邻居已连接,则取它们最近的点point1 j0 .. j1
并简单地将点i
与j
线性连接,其中:
i = i0 + t*(i1-i0) j = j0 + t*(j1-j0)
其中t
是遍历所有“整数”点索引的参数t=<0.0,1.0>
。 (使用DDA)。
填充点中未使用的孔
它与#5相同,但要在另一个多边形中查找未连接的点1。
查找并连接单个连接线
两个端点仅使用一次。 因此,只需找到该边缘并将其连接到相邻点即可。
找到形成QUAD孔的奇点poinst0
并对其进行三角剖分
因此找到仅连接一次的point0
,然后测试其邻居是否已连接回第一个连接的point1
。 如果没有,则找到QUAD孔,然后对其进行三角测量。
现在只需从edg0,edg1
提取三角形和直线
line很简单,因为它们已经直接编码,对于三角形,只需搜索相邻的point0
以连接到同一point1
。 如果发现形成三角形。 三角形也应形成于另一方的边缘列表中,以便搜索相邻的point1
连接到同一个point0
了。
GL / C ++示例
List<double> pnt;
List<int> lin,tri;
int iy0=0,iy1=0,iy2=0,iy3=0;// pol0<iy0,iy1),pol1<iy1,iy2),merge<iy2,iy3)
int ix0=0,ix1=0,ix2=0; // points1<ix0,ix1), points2<ix1,ix2)
//---------------------------------------------------------------------------
void obj_init()
{
// input data
const double d=0.5; // distance between polygons
const double pol0[]={0.0,2.0, 1.0,2.0, 1.0,3.0, 2.00,3.0, 2.0,2.0, 3.00,2.0, 3.0,1.0, 2.0,1.0, 2.0,0.0, 1.0,0.0, 1.0,1.0, 0.0,1.0, 0.0,2.0};
// const double pol1[]={0.0,0.0, 1.0,1.0, 0.0,2.0, 1.25,1.5, 1.5,2.5, 1.75,1.5, 3.0,2.0, 2.0,1.0, 3.0,0.0, 1.5,0.7};
const double pol1[]={0.0,0.0, 1.0,1.0, 0.0,2.0, 1.25,1.5, 1.5,5.0, 1.75,1.5, 3.0,2.0, 2.0,1.0, 3.0,0.0, 1.5,0.7};
// ***
// variables
List<double> tmp;
List< List<int> > edg0,edg1; // edge table
double minll; // max distance to points that will join automatically
double p[3],l0,l;
int i,i0,i1,ii,ii0,ii1,di;
int j,j0,j1,jj,jj0,jj1,dj;
int k,n0,n1,e,de;
pnt.num=0;
lin.num=0;
// copy pol0 to point list pnt[]
ix0=pnt.num;
n0=sizeof(pol0)/sizeof(pol0[0]);
for (j=pnt.num,i=0;i<n0;)
{
pnt.add(pol0[i]); i++;
pnt.add(pol0[i]); i++;
pnt.add(-d);
} ix1=pnt.num; n0/=2;
// copy pol1 to point list pnt[]
n1=sizeof(pol1)/sizeof(pol1[0]);
for (j=pnt.num,i=0;i<n1;)
{
pnt.add(pol1[i]); i++;
pnt.add(pol1[i]); i++;
pnt.add(+d);
} ix2=pnt.num; n1/=2;
// add polygon edges
iy0=lin.num;
for (j=ix1-3,i=ix0;i<ix1;j=i,i+=3){ lin.add(j); lin.add(i); } iy1=lin.num;
for (j=ix2-3,i=ix1;i<ix2;j=i,i+=3){ lin.add(j); lin.add(i); } iy2=lin.num;
// find closest points -> start points i0,j0
i0=-1; j0=-1; l0=0.0; minll=0.0;
for (i=ix0;i<ix1;i+=3)
for (j=ix1;j<ix2;j+=3)
{
vector_sub(p,pnt.dat+i,pnt.dat+j);
l=vector_len2(p);
if ((i0<0)||(l<l0)){ l0=l; i0=i; j0=j; }
} minll=2.5*l0;
// reorder points so closest ones are first
if (i0!=ix0)
{
tmp.num=0; for (i=ix0;i<ix1;i++) tmp.add(pnt.dat[i]); // copy to temp
for (j=i0,i=ix0;i<ix1;i++,j++,(j>=ix1)?j=ix0:j=j) pnt.dat[i]=tmp.dat[j-ix0]; // reorder
}
if (j0!=ix1)
{
tmp.num=0; for (i=ix1;i<ix2;i++) tmp.add(pnt.dat[i]); // copy to temp
for (j=j0,i=ix1;i<ix2;i++,j++,(j>=ix2)?j=ix1:j=j) pnt.dat[i]=tmp.dat[j-ix1]; // reorder
}
// init edge lists
edg0.allocate(n0); edg0.num=n0; for (i=0;i<n0;i++) edg0[i].num=0;
edg1.allocate(n1); edg1.num=n1; for (i=0;i<n1;i++) edg1[i].num=0;
// join close points
for (ii=0,i=ix0;i<ix1;i+=3,ii++)
{
j0=-1; jj0=-1; l0=0.0;
for (jj=0,j=ix1;j<ix2;j+=3,jj++)
{
vector_sub(p,pnt.dat+i,pnt.dat+j);
l=vector_len2(p);
if ((j0<0)||(l<l0)){ l0=l; j0=j; jj0=jj; }
}
if ((j0>=0)&&(l0<minll))
{
edg0.dat[ii ].add(j0);
edg1.dat[jj0].add(i);
}
}
// fill unused holes in points0
for (e=1,i=0;e;)
{
e=0;
// find last used point0 -> i0
i0=-1; i1=-1;
for (j=0;j<n0;j++,i++,(i==n0)?i=0:i=i) if (edg0.dat[i].num) i0=i; else if (i0>=0) break;
// find next used point0 -> i1
if (i0>=0) if (edg0.dat[i].num==0)
for (j=0;j<n0;j++,i++,(i==n0)?i=0:i=i) if (edg0.dat[i].num){ i1=i; break; }
if (i1>=0)
{
// find last used point1 joined to i0 -> j0
j0=-1; jj0=-1; l0=0.0;
i=i0+1; if (i>=n0) i=0; ii=ix0+i+i+i;
for (j=0;j<edg0.dat[i0].num;j++)
{
jj=edg0.dat[i0].dat[j];
vector_sub(p,pnt.dat+ii,pnt.dat+jj);
l=vector_len2(p);
if ((j0<0)||(l<l0)){ l0=l; j0=(jj-ix1)/3; jj0=jj; }
} i0=i;
// find next used point1 joined to i1 -> j1
j1=-1; jj1=-1; l0=0.0;
i=i1-1; if (i<0) i=n0-1; ii=ix0+i+i+i;
for (j=0;j<edg0.dat[i1].num;j++)
{
jj=edg0.dat[i1].dat[j];
vector_sub(p,pnt.dat+ii,pnt.dat+jj);
l=vector_len2(p);
if ((j1<0)||(l<l0)){ l0=l; j1=(jj-ix1)/3; jj1=jj; }
} i1=i;
// join i0..i1 <-> j0..j1
di=i1-i0; if (di<0) di+=n0; // point0 to join
dj=j1-j0; if (dj<0) dj+=n1; // point1 to join
de=di; if (de<dj) de=dj; // max(points0,point1)
for (e=0;e<=de;e++)
{
i=i0+((e*di)/de); if (i>=n0) i-=n0; ii=ix0+i+i+i;
j=j0+((e*di)/de); if (j>=n1) j-=n1; jj=ix1+j+j+j;
for (k=0;k<edg0.dat[i].num;k++) // avoid duplicate edges
if (edg0.dat[i].dat[k]==jj)
{ k=-1; break; }
if (k>=0)
{
edg0.dat[i].add(jj);
edg1.dat[j].add(ii);
}
}
e=1;
}
}
// fill unused holes in points1
for (e=1,i=0;e;)
{
e=0;
// find last used point1 -> i0
i0=-1; i1=-1;
for (j=0;j<n1;j++,i++,(i==n1)?i=0:i=i) if (edg1.dat[i].num) i0=i; else if (i0>=0) break;
// find next used point1 -> i1
if (i0>=0) if (edg1.dat[i].num==0)
for (j=0;j<n1;j++,i++,(i==n1)?i=0:i=i) if (edg1.dat[i].num){ i1=i; break; }
if (i1>=0)
{
// find last used point0 joined to i0 -> j0
j0=-1; jj0=-1; l0=0.0;
i=i0+1; if (i>=n1) i=0; ii=ix1+i+i+i;
for (j=0;j<edg1.dat[i0].num;j++)
{
jj=edg1.dat[i0].dat[j];
vector_sub(p,pnt.dat+ii,pnt.dat+jj);
l=vector_len2(p);
if ((j0<0)||(l<l0)){ l0=l; j0=(jj-ix0)/3; jj0=jj; }
} i0=i;
// find next used point0 joined to i1 -> j1
j1=-1; jj1=-1; l0=0.0;
i=i1-1; if (i<0) i=n1-1; ii=ix1+i+i+i;
for (j=0;j<edg1.dat[i1].num;j++)
{
jj=edg1.dat[i1].dat[j];
vector_sub(p,pnt.dat+ii,pnt.dat+jj);
l=vector_len2(p);
if ((j1<0)||(l<l0)){ l0=l; j1=(jj-ix0)/3; jj1=jj; }
} i1=i;
// join i0..i1 <-> j0..j1
di=i1-i0; if (di<0) di+=n1; // point1 to join
dj=j1-j0; if (dj<0) dj+=n0; // point0 to join
de=di; if (de<dj) de=dj; // max(points0,point1)
for (e=0;e<=de;e++)
{
i=i0+((e*di)/de); if (i>=n1) i-=n1; ii=ix1+i+i+i;
j=j0+((e*di)/de); if (j>=n0) j-=n0; jj=ix0+j+j+j;
for (k=0;k<edg1.dat[i].num;k++) // avoid duplicate edges
if (edg1.dat[i].dat[k]==jj)
{ k=-1; break; }
if (k>=0)
{
edg1.dat[i].add(jj);
edg0.dat[j].add(ii);
}
}
e=1;
}
}
// find&connect singular connection lines (both endpoints are used just once)
for (i=0;i<n0;i++)
if (edg0.dat[i].num==1) // point0 used once
{
jj=edg0.dat[i].dat[0]; // connected to
j=(jj-ix1)/3;
if (edg1.dat[j].num==1) // point1 used once
{
i0=i-1; if (i< 0) i+=n0; // point0 neighbors
i1=i+1; if (i>=n0) i-=n0;
ii =ix0+i +i +i;
ii0=ix0+i0+i0+i0;
ii1=ix0+i1+i1+i1;
if (edg1.dat[j].dat[0]!=ii0) // avoid duplicate edges
{
edg1.dat[j].add(ii0);
edg0.dat[i0].add(jj);
}
if (edg1.dat[j].dat[0]!=ii1) // avoid duplicate edges
{
edg1.dat[j].add(ii1);
edg0.dat[i1].add(jj);
}
}
}
// find singular poinst0 that form QUAD hole and triangulate it
for (i=0;i<n0;i++)
if (edg0.dat[i].num==1) // point0 used once
{
jj=edg0.dat[i].dat[0]; // connected to
j=(jj-ix1)/3;
i0=i-1; if (i< 0) i+=n0; // point0 neighbors
i1=i+1; if (i>=n0) i-=n0;
j0=j-1; if (j< 0) j+=n1; // point1 neighbors
j1=j+1; if (j>=n1) j-=n1;
ii =ix0+i +i +i;
ii0=ix0+i0+i0+i0;
ii1=ix0+i1+i1+i1;
jj0=ix1+j0+j0+j0;
jj1=ix1+j1+j1+j1;
for (k=0;k<edg1.dat[j].num;k++) // avoid duplicate edges
{
if (edg1.dat[j].dat[k]==ii0) i0=-1;
if (edg1.dat[j].dat[k]==ii1) i1=-1;
}
if (i0>=0)
{
edg1.dat[j ].add(ii0);
edg0.dat[i0].add(jj );
}
if (i1>=0)
{
edg1.dat[j ].add(ii1);
edg0.dat[i1].add(jj );
}
}
// extract merge triangles from edge0
for (i0=0,i1=1;i0<n0;i0++,i1++,(i1>=n0)?i1=0:i1=i1)
{
ii0=ix0+i0+i0+i0;
ii1=ix0+i1+i1+i1;
// find point1 joined to both points0 i0,i1
for (i=0;i<edg0.dat[i0].num;i++)
for (j=0;j<edg0.dat[i1].num;j++)
if (edg0.dat[i0].dat[i]==edg0.dat[i1].dat[j])
{
jj=edg0.dat[i0].dat[i];
tri.add(ii0);
tri.add(ii1);
tri.add(jj);
}
}
// extract merge triangles from edge1
for (i0=0,i1=1;i0<n1;i0++,i1++,(i1>=n1)?i1=0:i1=i1)
{
ii0=ix1+i0+i0+i0;
ii1=ix1+i1+i1+i1;
// find point1 joined to both points0 i0,i1
for (i=0;i<edg1.dat[i0].num;i++)
for (j=0;j<edg1.dat[i1].num;j++)
if (edg1.dat[i0].dat[i]==edg1.dat[i1].dat[j])
{
jj=edg1.dat[i0].dat[i];
tri.add(ii0);
tri.add(ii1);
tri.add(jj);
}
}
// extract merge edges
for (i=ix0,ii=0;ii<n0;ii++,i+=3)
for (j=0;j<edg0.dat[ii].num;j++)
{
lin.add(i);
lin.add(edg0.dat[ii].dat[j]);
}
iy3=lin.num;
/*
// [debug]
AnsiString txt="";
for (txt+="[edg0]\r\n",i=0;i<n0;i++,txt+="\r\n")
for (txt+=AnsiString().sprintf("%2i:",i),j=0;j<edg0.dat[i].num;j++)
txt+=AnsiString().sprintf("%2i ",(edg0.dat[i].dat[j]-ix1)/3);
for (txt+="\r\n[edg1]\r\n",i=0;i<n1;i++,txt+="\r\n")
for (txt+=AnsiString().sprintf("%2i:",i),j=0;j<edg1.dat[i].num;j++)
txt+=AnsiString().sprintf("%2i ",(edg1.dat[i].dat[j]-ix0)/3);
e=FileCreate("debug.txt");
FileWrite(e,txt.c_str(),txt.Length());
FileClose(e);
*/
}
//---------------------------------------------------------------------------
void obj_draw()
{
int i,j;
glLineWidth(2.0);
glColor3f(0.1,0.1,0.1); glBegin(GL_LINES); for (i=0;(i<iy0)&&(i<lin.num);i++) glVertex3dv(pnt.dat+lin.dat[i]); glEnd(); // aux
glColor3f(0.1,0.1,0.9); glBegin(GL_LINES); for ( ;(i<iy1)&&(i<lin.num);i++) glVertex3dv(pnt.dat+lin.dat[i]); glEnd(); // polygon 0
glColor3f(0.9,0.1,0.1); glBegin(GL_LINES); for ( ;(i<iy2)&&(i<lin.num);i++) glVertex3dv(pnt.dat+lin.dat[i]); glEnd(); // polygon 1
glColor3f(0.1,0.9,0.1); glBegin(GL_LINES); for ( ;(i<iy3)&&(i<lin.num);i++) glVertex3dv(pnt.dat+lin.dat[i]); glEnd(); // merge
glColor3f(0.1,0.1,0.1); glBegin(GL_LINES); for ( ; (i<lin.num);i++) glVertex3dv(pnt.dat+lin.dat[i]); glEnd(); // aux
glLineWidth(1.0);
glPointSize(5.0);
glColor3f(0.9,0.9,0.1); glBegin(GL_POINTS); glVertex3dv(pnt.dat+ix0+0); glVertex3dv(pnt.dat+ix1+0); glEnd(); // 1st points
glColor3f(0.1,0.9,0.9); glBegin(GL_POINTS); glVertex3dv(pnt.dat+ix0+3); glVertex3dv(pnt.dat+ix1+3); glEnd(); // 2nd points
glColor3f(0.3,0.3,0.3); glBegin(GL_POINTS); for (i=0;i<pnt.num;i+=3) glVertex3dv(pnt.dat+i); glEnd(); // points
glPointSize(1.0);
glDisable(GL_CULL_FACE);
glColor3f(0.2,0.2,0.2); glBegin(GL_TRIANGLES); for (i=0;i<tri.num;i++) glVertex3dv(pnt.dat+tri.dat[i]); glEnd(); // faces
}
//---------------------------------------------------------------------------
我还使用了我的动态列表模板,因此:
List<double> xxx;
与double xxx[];
相同double xxx[];
xxx.add(5);
在列表末尾加5
xxx[7]
访问数组元素(安全)
xxx.dat[7]
访问数组元素(不安全但快速的直接访问)
xxx.num
是数组的实际使用大小
xxx.reset()
清除数组并设置xxx.num=0
xxx.allocate(100)
为100
项目预分配空间
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.