一、三角形选择器 TriangleSelector
选中模型后,点击涂色,可以选择不同耗材和不同工具类型,对模型进行涂色,这种情况会打破原模型的三角面片。
1、光标类型 CursorType
cpp
enum CursorType {
CIRCLE, // 圆形光标(2D平面)
SPHERE, // 球形光标(3D空间)
POINTER, // 指针光标
// BBS 扩展
HEIGHT_RANGE, // 高度范围光标
GAP_FILL, // 间隙填充光标
};
2、ClippingPlane
结构体定义了一个裁剪平面,用于限制三角形选择的范围。这是实现精确三角形选择和分割的重要组件。
cpp
struct ClippingPlane
{
Vec3f normal;
float offset;
ClippingPlane() : normal{0.f, 0.f, 1.f}, offset{FLT_MAX} {};
explicit ClippingPlane(const std::array<float, 4> &clp) : normal{clp[0], clp[1], clp[2]}, offset{clp[3]} {}
bool is_active() const { return offset != FLT_MAX; }
bool is_mesh_point_clipped(const Vec3f &point) const { return normal.dot(point) - offset > 0.f; }
};
裁剪平面的工作原理基于三维空间中的点-平面关系
is_mesh_point_clipped函数用来判断给定点是否被裁剪平面裁剪
-
如果 normal · point > offset :点在平面的正方向(被裁剪)
-
如果 normal · point < offset :点在平面的负方向(未被裁剪)
-
如果 normal · point = offset :点在平面上
点积的几何意义:如果b为单位向量,点积就是a在b上的投影。
3、select_patch
当点击涂色时,左击在模型上进行涂色时,调用此方法,每移动一次都会调用一次。如选择圆进行涂色,会对三角面进行重新分割。最后会调用select_triangle_recursive 使用广度优先算法进行递归。调用split_triangle进行三角形的分割。
调用triangle_midpoint_or_allocate对边进行取中间点进行分割,分割后的顶点数据会写入m_vertices中。
调用push_triangle,分割出来的点重新组合成三角型的顶点索引,写入m_triangles中。
在perform_split中,将新的三角面片索引写到Triangle的children中。
4、triangle_midpoint_or_allocate 获取中间点
cpp
int TriangleSelector::triangle_midpoint_or_allocate(int itriangle, int vertexi, int vertexj)
{
int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj);
if (midpoint == -1) {
Vec3f c = 0.5f * (m_vertices[vertexi].v + m_vertices[vertexj].v);
#ifdef EXPENSIVE_DEBUG_CHECKS
// Verify that the vertex is really a new one.
auto it = std::find_if(m_vertices.begin(), m_vertices.end(), [c](const Vertex &v) {
return v.ref_cnt > 0 && (v.v - c).norm() < EPSILON; });
assert(it == m_vertices.end());
#endif // EXPENSIVE_DEBUG_CHECKS
// Allocate a new vertex, possibly reusing the free list.
if (m_free_vertices_head == -1) {
// Allocate a new vertex.
midpoint = int(m_vertices.size());
m_vertices.emplace_back(c);
} else {
// Reuse a vertex from the free list.
assert(m_free_vertices_head >= -1 && m_free_vertices_head < int(m_vertices.size()));
midpoint = m_free_vertices_head;
memcpy(&m_free_vertices_head, &m_vertices[midpoint].v[0], sizeof(m_free_vertices_head));
assert(m_free_vertices_head >= -1 && m_free_vertices_head < int(m_vertices.size()));
m_vertices[midpoint].v = c;
}
assert(m_vertices[midpoint].ref_cnt == 0);
} else {
#ifndef NDEBUG
Vec3f c1 = 0.5f * (m_vertices[vertexi].v + m_vertices[vertexj].v);
Vec3f c2 = m_vertices[midpoint].v;
float d = (c2 - c1).norm();
assert(std::abs(d) < EPSILON);
#endif // NDEBUG
assert(m_vertices[midpoint].ref_cnt > 0);
}
return midpoint;
}
5、push_triangle 重新组合三角形
cpp
int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state)
{
for (int i : {a, b, c}) {
assert(i >= 0 && i < int(m_vertices.size()));
++m_vertices[i].ref_cnt;
}
int idx;
if (m_free_triangles_head == -1) {
// Allocate a new triangle.
assert(m_invalid_triangles == 0);
idx = int(m_triangles.size());
m_triangles.emplace_back(a, b, c, source_triangle, state);
} else {
// Reuse triangle from the free list.
assert(m_free_triangles_head >= -1 && m_free_triangles_head < int(m_triangles.size()));
assert(! m_triangles[m_free_triangles_head].valid());
assert(m_invalid_triangles > 0);
idx = m_free_triangles_head;
m_free_triangles_head = m_triangles[idx].children[0];
-- m_invalid_triangles;
assert(m_free_triangles_head >= -1 && m_free_triangles_head < int(m_triangles.size()));
assert(m_free_triangles_head == -1 || ! m_triangles[m_free_triangles_head].valid());
assert(m_invalid_triangles >= 0);
assert((m_invalid_triangles == 0) == (m_free_triangles_head == -1));
m_triangles[idx] = {a, b, c, source_triangle, state};
}
assert(m_triangles[idx].valid());
return idx;
}
6、perform_split 完成三角形分割
调用4,5后,将新的三角面片索引写到Triangle的children中
cpp
void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state)
{
// Reserve space for the new triangles upfront, so that the reference to this triangle will not change.
{
size_t num_triangles_new = m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1;
if (m_triangles.capacity() < num_triangles_new)
m_triangles.reserve(next_highest_power_of_2(num_triangles_new));
}
Triangle &tr = m_triangles[facet_idx];
assert(tr.is_split());
// indices of triangle vertices
//#ifdef NDEBUG
// boost::container::small_vector<int, 6> verts_idxs;
//#else // NDEBUG
// For easier debugging.
std::vector<int> verts_idxs;
verts_idxs.reserve(6);
//#endif // NDEBUG
for (int j=0, idx = tr.special_side(); j<3; ++j, idx = next_idx_modulo(idx, 3))
verts_idxs.push_back(tr.verts_idxs[idx]);
auto get_alloc_vertex = [this, &neighbors, &verts_idxs](int edge, int i1, int i2) -> int {
return this->triangle_midpoint_or_allocate(neighbors(edge), verts_idxs[i1], verts_idxs[i2]);
};
int ichild = 0;
switch (tr.number_of_split_sides()) {
case 1:
verts_idxs.insert(verts_idxs.begin()+2, get_alloc_vertex(next_idx_modulo(tr.special_side(), 3), 2, 1));
tr.children[ichild ++] = push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2], tr.source_triangle, old_state);
tr.children[ichild ] = push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0], tr.source_triangle, old_state);
break;
case 2:
verts_idxs.insert(verts_idxs.begin()+1, get_alloc_vertex(tr.special_side(), 1, 0));
verts_idxs.insert(verts_idxs.begin()+4, get_alloc_vertex(prev_idx_modulo(tr.special_side(), 3), 0, 3));
tr.children[ichild ++] = push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4], tr.source_triangle, old_state);
tr.children[ichild ++] = push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4], tr.source_triangle, old_state);
tr.children[ichild ] = push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4], tr.source_triangle, old_state);
break;
case 3:
assert(tr.special_side() == 0);
verts_idxs.insert(verts_idxs.begin()+1, get_alloc_vertex(0, 1, 0));
verts_idxs.insert(verts_idxs.begin()+3, get_alloc_vertex(1, 3, 2));
verts_idxs.insert(verts_idxs.begin()+5, get_alloc_vertex(2, 0, 4));
tr.children[ichild ++] = push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5], tr.source_triangle, old_state);
tr.children[ichild ++] = push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3], tr.source_triangle, old_state);
tr.children[ichild ++] = push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5], tr.source_triangle, old_state);
tr.children[ichild ] = push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5], tr.source_triangle, old_state);
break;
default:
break;
}
#ifndef NDEBUG
assert(this->verify_triangle_neighbors(tr, neighbors));
for (int i = 0; i <= tr.number_of_split_sides(); ++i) {
Vec3i n = this->child_neighbors(tr, neighbors, i);
assert(this->verify_triangle_neighbors(m_triangles[tr.children[i]], n));
}
#endif // NDEBUG
}
7、update_model_object
当鼠标弹起后,会调用此函数将分割的数据写入到ModelVolume的mmu_segmentation_facets中
cpp
void GLGizmoMmuSegmentation::update_model_object()
{
bool updated = false;
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->mmu_segmentation_facets.set(*m_triangle_selectors[idx].get());
}
if (updated) {
const ModelObjectPtrs &mos = wxGetApp().model().objects;
size_t obj_idx = std::find(mos.begin(), mos.end(), mo) - mos.begin();
wxGetApp().obj_list()->update_info_items(obj_idx);
wxGetApp().plater()->get_partplate_list().notify_instance_update(obj_idx, 0);
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
}