用于可视化的坐标轴与外包框的网格生成算法
在三维模型显示与交互任务中, 坐标轴和外包框是其基本的辅助工具。相关方法可以分成几何体(网格构造)算法与着色器方法(点精灵方式等),本文仅讨论相关的网格生成。
考虑到坐标轴与外包框可以用柱体与锥体两大基本元素组合生成,基本元素又可进一步划分为由采样点构成的圆周,圆面和锥体斜面都可由采样点圆周与中心点构成扇形三角形区域,圆柱面则由相邻两个灯采样点圆周构成三角形条带。 扇形三角形区域和三角形条带均可用基本共享顶点数据结构表示,处理好三角形面朝向问题,则有如下代码实现:
// 坐标轴网格构建
// 1. 坐标轴的尖顶帽子效果可用等圆周三角形条带和扇形三角形实现
// 2. 坐标轴的交界部分可使用扇形三角形实现
YbMesh::indicesTriMesh<glm::vec3> YbMesh::geometry::make_axes(float height,float r) {
auto v = std::make_shared<std::vector<glm::vec3>>();
auto f = std::make_shared<std::vector<glm::ivec3>>();
// z
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(glm::vec3(2*r*std::sinf(delta), 2*r*std::cosf(delta), height+r*2));
for(float h = 1.0f; h >= 0; h -= 1.0f/4) {
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(glm::vec3(r*std::sinf(delta), r*std::cosf(delta), height*h+r*2));
}
v->emplace_back(glm::vec3(.0f, .0f, height+r*5));
v->emplace_back(glm::vec3(.0f, .0f, 0.0f));
// y
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(glm::vec3(2*r*std::cosf(delta), height+r*2, 2*r*std::sinf(delta)));
for(float h = 1.0f; h >= 0; h -= 1.0f/4) {
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(glm::vec3(r*std::cosf(delta), height*h+r*2, r*std::sinf(delta)));
}
v->emplace_back(glm::vec3(.0f, height+r*5, .0f));
v->emplace_back(glm::vec3(.0f, 0.0f, .0f));
// x
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(glm::vec3(height+r*2, 2*r*std::sinf(delta), 2*r*std::cosf(delta)));
for(float h = 1.0f; h >= 0; h -= 1.0f/4) {
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(glm::vec3(height*h+r*2, r*std::sinf(delta), r*std::cosf(delta)));
}
v->emplace_back(glm::vec3(height+r*5, .0f, .0f));
v->emplace_back(glm::vec3(.0f, 0.0f, .0f));
for(int offset:{0,122,244}){
for(int j = 0; j < 20; j++) {
// top-down faces
f->emplace_back(glm::ivec3(j,(j+1)%20,120)+offset);
f->emplace_back(glm::ivec3(100+(j+1)%20,100+j,121)+offset);
for(int i = 0; i < 5; i++) {
f->emplace_back(glm::ivec3(i*20+(j+1)%20,i*20+j,(1+i)*20+j)+offset);
f->emplace_back(glm::ivec3((i+1)*20+j,(i+1)*20+(j+1)%20,i*20+(j+1)%20)+offset);
}
}
}
return YbMesh::indicesTriMesh<glm::vec3>(v,f);
}
// 外包框
// 1. 棱的非交界部分用等采样点圆周的三角形条带实现
// 2. 棱的交界部分用扇形三角形实现
// 3. 外包框的8个视觉顶点的坐标位置与索引号的编码关系(难点)
YbMesh::indicesTriMesh<glm::vec3> YbMesh::geometry::make_box(float height,float r) {
auto v = std::make_shared<std::vector<glm::vec3>>();
auto f = std::make_shared<std::vector<glm::ivec3>>();
// z directions
for(int e:{0,1,2,3}) {
glm::vec3 offset = 0.5f*height*glm::vec3(e&2?-1.0f:1.0f,e&1?-1.0f:1.0f,-1.0f);
for(float h = 1.0f; h >= 0; h -= 1.0f/4) {
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(offset+glm::vec3(r*std::sinf(delta), r*std::cosf(delta), r+(height-2*r)*h));
}
}
// y
for(int e:{0,1,2,3}) {
glm::vec3 offset = 0.5f*height*glm::vec3(e&2?-1.0f:1.0f,-1.0f,e&1?-1.0f:1.0f);
for(float h = 1.0f; h >= 0; h -= 1.0f/4) {
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(offset+glm::vec3(r*std::cosf(delta), r+(height-2*r)*h, r*std::sinf(delta)));
}
}
// x
for(int e:{0,1,2,3}) {
glm::vec3 offset = 0.5f*height*glm::vec3(-1.0f,e&2?-1.0f:1.0f,e&1?-1.0f:1.0f);
for(float h = 1.0f; h >= 0; h -= 1.0f/4) {
for(float delta = 2*PI; delta >= 0; delta -= PI/10)
v->emplace_back(offset+glm::vec3(r+(height-2*r)*h, r*std::sinf(delta), r*std::cosf(delta)));
}
}
for(int offset = 0; offset < 1200; offset += 100){
for(int j = 0; j < 20; j++) {
for(int i = 0; i < 4; i++) {
f->emplace_back(offset+glm::ivec3(i*20+(j+1)%20,i*20+j,(1+i)*20+j));
f->emplace_back(offset+glm::ivec3((i+1)*20+j,(i+1)*20+(j+1)%20,i*20+(j+1)%20));
}
}
}
for(int e = 0,offset = 0; e< 8; e++) {
v->emplace_back(0.5f*height*glm::vec3(e&4?-1.0f:1.0f,e&2?-1.0f:1.0f,e&1?-1.0f:1.0f));
for(int j = 0; j < 20; j++) {
offset = 800+80*bool(e&4)+100*(e&3);
f->emplace_back(offset%100 == 80 ? glm::ivec3((j+1)%20+offset,j+offset,1200+e):glm::ivec3(j+offset,(j+1)%20+offset,1200+e));
offset = 400+80*bool(e&2)+100*(((e&4)>>1)+(e&1));
f->emplace_back(offset%100 == 80 ? glm::ivec3((j+1)%20+offset,j+offset,1200+e):glm::ivec3(j+offset,(j+1)%20+offset,1200+e));
offset = 00+80*bool(e&1)+100*((e&6)>>1);
f->emplace_back(offset%100 == 80 ? glm::ivec3((j+1)%20+offset,j+offset,1200+e):glm::ivec3(j+offset,(j+1)%20+offset,1200+e));
}
}
return YbMesh::indicesTriMesh<glm::vec3>(v,f);
}
具体的效果如图:
扩展性部分,对于类似MeshLab的坐标轴的坐标尺度效果(对应本文则是坐标轴网的周期纹理效果),实现上可由顶点着色器与面元着色器简单合作完成。