continue: uniform and adaptive Loop-color-displacement subdivision
This program is an implementation of matrix representation of mesh tolopogy, hope useful to geometry community.
//////////////////////////////////////////////////////////////////////////////
//MatrixMesh structure
class MatrixMesh
{
protected:
vector<Vector3f> vertices;
vector<int> faces;
vector< vector<int> > vertex_to_faces; // vertex_to_faces[i][j] is the j-th neighboring face of the vertex i
vector< vector<int> > face_to_faces; // face_to_faces[i][j] is the j-th neighboring face of the face i (only 3)
vector< Vector3f> FNormals;
vector< Vector3f> VNormals;
public:
int nbV; // number of vertices
int nbF; // number of faces
MatrixMesh() {}
MatrixMesh(const vector<Vector3f>& _verts, const vector<int>& _faces);
~MatrixMesh();
void InitNeighboringData(); // initialize all neighboring information
void ComputeNormals(); // compute all normals
Vector3f V(int v) const
{
return vertices[v];
}
int F(int v, int j) const
{
return faces[v * 3 + j];
}
Vector3f VN(int v) const
{
return VNormals[v];
}
Vector3f FN(int fi) const
{
return FNormals[fi];
}
void set_vV(int v, const Vector3f & p)
{
vertices[v] = p;
}
int vfNbNeighbors(int v) const {
return vertex_to_faces[v].size();
}
int vfNeighbor(int v, int i) const {
return vertex_to_faces[v][i];
}
};
MatrixMesh::MatrixMesh(const vector<Vector3f>& _verts, const vector<int>& _faces)
{
vertices = _verts;
faces = _faces;
nbV = vertices.size();
nbF = faces.size() / 3;
InitNeighboringData();
ComputeNormals();
}
MatrixMesh::MatrixMesh() {
}
void MatrixMesh::InitNeighboringData()
{
vertex_to_faces.clear();
vertex_to_faces.resize(nbV);
int v0, v1, v2, fj;
for (int fi = 0; fi < nbF; fi++) {
for (int j = 0; j < 3; ++j) {
vertex_to_faces[F(fi, j)].push_back(fi);
}
}
face_to_faces.clear();
face_to_faces.resize(nbF);
for (int fi = 0; fi < nbF; ++fi) {
for (int i = 0; i < 3; ++i) {
v0 = F(fi, i);
v1 = F(fi, (i + 1) % 3);
for (unsigned int j = 0; j < vertex_to_faces[v0].size(); ++j) {
fj = vertex_to_faces[v0][j];
vector<int> l = vertex_to_faces[v1];
if (fj != fi && std::find(l.begin(), l.end(), fj) != l.end()) {
face_to_faces[fi].push_back(fj);
break;
}
}
}
}
}
void MatrixMesh::ComputeNormals()
{
FNormals.clear();
FNormals.resize(nbF);
#pragma omp parallel for
for (int fi = 0; fi < nbF; ++fi) {
const Vector3f & p0 = V(F(fi, 0));
const Vector3f & p1 = V(F(fi, 1));
const Vector3f & p2 = V(F(fi, 2));
FNormals[fi] = cross(p1 - p0, p2 - p0).normalised();
}
VNormals.clear();
VNormals.resize(nbV);
#pragma omp parallel for
for (int vi = 0; vi < nbV; ++vi) {
Vector3f n(0.f, 0.f, 0.f);
for (int j = 0; j < vfNbNeighbors(vi); ++j) {
n += FNormals[vfNeighbor(vi, j)];
}
VNormals[vi] = n.normalised();
}
}
this program computes color gradients, while is different to grey space version. It does in 3-channels, resulting in much visible edges, and following the subpixel read funtion, inspired by OpenCV’s
//color_gradient_domain
void get_color_gradients(const std::string &filename, const std::string &out_filename, cv::Mat & magnitude, cv::Mat & mag_x, cv::Mat & mag_y, int & tex_w, int & tex_h)
{
cv::Mat src = cv::imread(filename.c_str(), CV_LOAD_IMAGE_ANYCOLOR | CV_LOAD_IMAGE_ANYDEPTH);
cv::Size size = src.size();
tex_w = size.width;
tex_h = size.height;
cv::Mat sobel_dismap(size, CV_32F); //derivative magnitude
cv::Mat sobel_3dx; // per-channel horizontal derivative
cv::Mat sobel_3dy; // per-channel vertical derivative
cv::Mat sobel_dx(size, CV_32F); // maximum horizontal derivative
cv::Mat sobel_dy(size, CV_32F); // maximum vertical derivative
cv::Mat smoothed;
// Compute horizontal and vertical image derivatives on all color channels separately
static const int KERNEL_SIZE = 7;
// For some reason cvSmooth/cv::GaussianBlur, cvSobel/cv::Sobel have different defaults for border handling...
GaussianBlur(src, smoothed, cv::Size(KERNEL_SIZE, KERNEL_SIZE), 0, 0, cv::BORDER_REPLICATE);
Sobel(smoothed, sobel_3dx, CV_16S, 1, 0, 3, 1.0, 0.0, cv::BORDER_REPLICATE);
Sobel(smoothed, sobel_3dy, CV_16S, 0, 1, 3, 1.0, 0.0, cv::BORDER_REPLICATE);
cv::Mat smoothed_f;
smoothed.convertTo(smoothed_f, CV_32FC3, 1.0 / 255.0);
short * ptrx = (short *)sobel_3dx.data;
short * ptry = (short *)sobel_3dy.data;
float * ptr0x = (float *)sobel_dx.data;
float * ptr0y = (float *)sobel_dy.data;
float * ptr_dis = (float *)sobel_dismap.data;
const int length_dis = static_cast<const int>(sobel_dismap.step1());
const int length1 = static_cast<const int>(sobel_3dx.step1());
const int length2 = static_cast<const int>(sobel_3dy.step1());
const int length3 = static_cast<const int>(sobel_dx.step1());
const int length4 = static_cast<const int>(sobel_dy.step1());
const int length0 = sobel_3dy.cols * 3;
// B G R 变化梯度最大的通道作为结果
for (int r = 0; r < sobel_3dy.rows; ++r)
{
int ind = 0;
for (int i = 0; i < length0; i += 3)
{
// Use the gradient orientation of the channel whose magnitude is largest
int mag1 = ptrx[i + 0] * ptrx[i + 0] + ptry[i + 0] * ptry[i + 0];
int mag2 = ptrx[i + 1] * ptrx[i + 1] + ptry[i + 1] * ptry[i + 1];
int mag3 = ptrx[i + 2] * ptrx[i + 2] + ptry[i + 2] * ptry[i + 2];
if (mag1 >= mag2 && mag1 >= mag3)
{
ptr0x[ind] = ptrx[i];
ptr0y[ind] = ptry[i];
}
else if (mag2 >= mag1 && mag2 >= mag3)
{
ptr0x[ind] = ptrx[i + 1];
ptr0y[ind] = ptry[i + 1];
}
else
{
ptr0x[ind] = ptrx[i + 2];
ptr0y[ind] = ptry[i + 2];
}
//BGRA (dir vector, magnitude)
float x = ptr0x[ind]; // horizontal
float y = ptr0y[ind]; //vertical
ptr_dis[ind] = sqrtf(x*x + y*y);
++ind;
}
ptrx += length1;
ptr_dis += length_dis;
ptry += length2;
ptr0x += length3;
ptr0y += length4;
}
cv::Mat adjMap;
double min, max;
cv::Mat adjMap_x;
double min_x, max_x;
cv::Mat adjMap_y;
double min_y, max_y;
// watch out, as convertion with it own max, mag_x and mag_y might be larger than magnitude
cv::minMaxIdx(sobel_dismap, &min, &max);
convertScaleAbs(sobel_dismap, adjMap, 255.0 / max);
sobel_dismap *= 1.0f / max;
magnitude = sobel_dismap;
cv::minMaxIdx(sobel_dx, &min_x, &max_x);
//convertScaleAbs(sobel_dx, adjMap_x, 255.0 / max_x);
sobel_dx *= 1.0f / max_x;
mag_x = sobel_dx;
cv::minMaxIdx(sobel_dy, &min_y, &max_y);
//convertScaleAbs(sobel_dy, adjMap_y, 255.0 / max_y);
sobel_dy *= 1.0f / max_y;
mag_y = sobel_dy;
cv::imwrite(out_filename.c_str(), adjMap);
}
double getSubPixel(const cv::Mat& dis_map, float& texcoord_x, float& texcoord_y, int& tex_width, int& tex_height)
{
assert(!dis_map.empty());
float fx = texcoord_x * tex_width;
float fy = (1 - texcoord_y) * tex_height;
//float fy = texcoord_y * tex_height;
cv::Point2f pt(fx, fy);
int x = (int)pt.x;
int y = (int)pt.y;
int x0 = cv::borderInterpolate(x, dis_map.cols, cv::BORDER_REFLECT_101);
int x1 = cv::borderInterpolate(x + 1, dis_map.cols, cv::BORDER_REFLECT_101);
int y0 = cv::borderInterpolate(y, dis_map.rows, cv::BORDER_REFLECT_101);
int y1 = cv::borderInterpolate(y + 1, dis_map.rows, cv::BORDER_REFLECT_101);
float det_x = pt.x - (float)x;
float det_y = pt.y - (float)y;
uchar intensity = (uchar)((dis_map.at<uchar>(y0, x0) * (1.f - det_x) + dis_map.at<uchar>(y0, x1) * det_x) * (1.f - det_y)
+ (dis_map.at<uchar>(y1, x0) * (1.f - det_x) + dis_map.at<uchar>(y1, x1) * det_x) * det_y);
//double intensity = (double)((dis_map.at<float>(y0, x0) * (1.f - det_x) + dis_map.at<float>(y0, x1) * det_x) * (1.f - det_y)
//+ (dis_map.at<float>(y1, x0) * (1.f - det_x) + dis_map.at<float>(y1, x1) * det_x) * det_y);
float value = (float)intensity / 255.0f;
return value;
}