Fixed big patches problem.
This commit is contained in:
parent
21ee08ab67
commit
b6d9d125da
|
@ -176,12 +176,10 @@ float Cluster(MyMesh &mesh, unsigned int target_faces) {
|
|||
vector<unsigned int> remap;
|
||||
|
||||
VoronoiPartition part;
|
||||
Box3f box;
|
||||
for(unsigned int i = 0; i < mesh.vert.size(); i++) {
|
||||
const Point3f &p = mesh.vert[i].cP();
|
||||
box.Add(p);
|
||||
if(!mesh.vert[i].IsW()) {
|
||||
part.push_back(Seed(p, 1));
|
||||
part.push_back(p);
|
||||
remap.push_back(i);
|
||||
nseeds--;
|
||||
}
|
||||
|
@ -192,13 +190,12 @@ float Cluster(MyMesh &mesh, unsigned int target_faces) {
|
|||
unsigned int i = rand() % mesh.vert.size();
|
||||
if(mesh.vert[i].IsW() && !mesh.vert[i].IsV()) {
|
||||
const Point3f &p = mesh.vert[i].cP();
|
||||
part.push_back(Seed(p, 1));
|
||||
part.push_back(p);
|
||||
mesh.vert[i].SetV();
|
||||
remap.push_back(i);
|
||||
nseeds--;
|
||||
}
|
||||
}
|
||||
part.SetBox(box);
|
||||
part.Init();
|
||||
|
||||
vector<Point3f> centroid;
|
||||
|
@ -215,13 +212,13 @@ float Cluster(MyMesh &mesh, unsigned int target_faces) {
|
|||
}
|
||||
for(unsigned int i = nborder; i < part.size(); i++) {
|
||||
if(count[i] > 0)
|
||||
part[i].p = centroid[i]/count[i];
|
||||
part[i] = centroid[i]/count[i];
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned int i = nborder; i < part.size(); i++) {
|
||||
assert(mesh.vert[remap[i]].IsV());
|
||||
mesh.vert[remap[i]].P() = part[i].p;
|
||||
mesh.vert[remap[i]].P() = part[i];
|
||||
}
|
||||
|
||||
float error = 0;
|
||||
|
@ -261,5 +258,6 @@ float Cluster(MyMesh &mesh, unsigned int target_faces) {
|
|||
mesh.vn--;
|
||||
}
|
||||
return error;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ void Fragment::Write(outstm *out) {
|
|||
unsigned int ssize = seeds.size();
|
||||
out->write(&ssize, sizeof(unsigned int));
|
||||
|
||||
out->write(&*seeds.begin(), ssize * sizeof(Seed));
|
||||
out->write(&*seeds.begin(), ssize * sizeof(Point3f));
|
||||
out->write(&*seeds_id.begin(), ssize * sizeof(unsigned int));
|
||||
|
||||
unsigned int psize = pieces.size();
|
||||
|
@ -66,7 +66,7 @@ void Fragment::Read(instm *in) {
|
|||
in->read(&ssize, sizeof(unsigned int));
|
||||
seeds.resize(ssize);
|
||||
seeds_id.resize(ssize);
|
||||
in->read(&*seeds.begin(), ssize * sizeof(Seed));
|
||||
in->read(&*seeds.begin(), ssize * sizeof(Point3f));
|
||||
in->read(&*seeds_id.begin(), ssize * sizeof(unsigned int));
|
||||
|
||||
unsigned int psize;
|
||||
|
@ -206,7 +206,7 @@ void nxs::Split(Fragment &out,
|
|||
VoronoiPartition &part) {
|
||||
|
||||
unsigned int nseeds = out.seeds.size();
|
||||
vector<Seed> &seeds = out.seeds;
|
||||
vector<Point3f> &seeds = out.seeds;
|
||||
vector<unsigned int> &seeds_id = out.seeds_id;
|
||||
//preliminary count
|
||||
vector<unsigned int> count;
|
||||
|
@ -222,7 +222,7 @@ void nxs::Split(Fragment &out,
|
|||
|
||||
//pruning small patches
|
||||
float min_size = (newface.size()/3) / 20.0f;
|
||||
vector<Seed> newseeds;
|
||||
vector<Point3f> newseeds;
|
||||
vector<unsigned int> newseeds_id;
|
||||
|
||||
for(unsigned int seed = 0; seed < nseeds; seed++) {
|
||||
|
@ -346,7 +346,7 @@ unsigned int Fragment::Locate(const Point3f &p) {
|
|||
float max_dist = 1e20;
|
||||
unsigned int id = 0xffffffff;
|
||||
for(unsigned int i = 0; i < seeds.size(); i++) {
|
||||
float dist = seeds[i].Dist(p);
|
||||
float dist = Distance(seeds[i], p);
|
||||
if(dist < max_dist) {
|
||||
max_dist = dist;
|
||||
id = i;
|
||||
|
|
|
@ -47,7 +47,7 @@ class Fragment {
|
|||
|
||||
float error;
|
||||
|
||||
std::vector<Seed> seeds;
|
||||
std::vector<vcg::Point3f> seeds;
|
||||
std::vector<unsigned int> seeds_id;
|
||||
|
||||
std::vector<NxsPatch> pieces;
|
||||
|
|
|
@ -146,8 +146,6 @@ bool NexusMt::InitGL(Vbo mode, unsigned int vbosize) {
|
|||
|
||||
void NexusMt::Render() {
|
||||
patches.Flush();
|
||||
Frustumf frustum;
|
||||
frustum.GetView();
|
||||
|
||||
vector<unsigned int> cells;
|
||||
metric->GetView();
|
||||
|
@ -155,8 +153,13 @@ void NexusMt::Render() {
|
|||
tri_total = 0;
|
||||
tri_rendered = 0;
|
||||
|
||||
|
||||
Extract(cells);
|
||||
Draw(cells);
|
||||
}
|
||||
|
||||
void NexusMt::Draw(vector<unsigned int> &cells) {
|
||||
Frustumf frustum;
|
||||
frustum.GetView();
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
if(use_colors)
|
||||
|
|
|
@ -135,7 +135,7 @@ class NexusMt: public Nexus {
|
|||
bool SetComponents(unsigned int mask);
|
||||
|
||||
|
||||
//void ExtractFixed(std::vector<unsigned int> &selected, float error);
|
||||
void Draw(std::vector<unsigned int> &selected);
|
||||
void Extract(std::vector<unsigned int> &selected);
|
||||
|
||||
protected:
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
History
|
||||
|
||||
$Log: not supported by cvs2svn $
|
||||
Revision 1.18 2004/10/21 13:40:16 ponchio
|
||||
Debugging.
|
||||
|
||||
Revision 1.17 2004/10/21 12:22:21 ponchio
|
||||
Small changes.
|
||||
|
||||
|
@ -212,6 +215,7 @@ int main(int argc, char *argv[]) {
|
|||
bool show_colors = true;
|
||||
bool show_normals = true;
|
||||
bool show_statistics = true;
|
||||
bool extract = true;
|
||||
|
||||
NexusMt::MetricKind metric;
|
||||
NexusMt::Mode mode = NexusMt::SMOOTH;
|
||||
|
@ -251,10 +255,11 @@ int main(int argc, char *argv[]) {
|
|||
track.ButtonDown(Trackball::KEY_CTRL); break;
|
||||
case SDLK_q: exit(0); break;
|
||||
case SDLK_b: show_borders = !show_borders; break;
|
||||
case SDLK_e: extract = !extract; break;
|
||||
case SDLK_c: show_colors = !show_colors; break;
|
||||
case SDLK_n: show_normals = !show_normals; break;
|
||||
case SDLK_9: nexus.patches.ram_size *= 0.8; break;
|
||||
case SDLK_0: nexus.patches.ram_size *= 1.2; break;
|
||||
case SDLK_9: nexus.patches.ram_size *= 0.8f; break;
|
||||
case SDLK_0: nexus.patches.ram_size *= 1.2f; break;
|
||||
|
||||
case SDLK_LEFT:
|
||||
ram_size *= 0.7;
|
||||
|
@ -367,9 +372,19 @@ int main(int argc, char *argv[]) {
|
|||
nexus.SetComponent(NexusMt::COLOR, show_colors);
|
||||
nexus.SetComponent(NexusMt::NORMAL, show_normals);
|
||||
|
||||
static vector<unsigned int> cells;
|
||||
watch.Start();
|
||||
if(extract) {
|
||||
nexus.patches.Flush();
|
||||
|
||||
nexus.Render();
|
||||
nexus.metric->GetView();
|
||||
nexus.policy.Init();
|
||||
nexus.tri_total = 0;
|
||||
nexus.tri_rendered = 0;
|
||||
nexus.Extract(cells);
|
||||
nexus.Draw(cells);
|
||||
} else
|
||||
nexus.Draw(cells);
|
||||
|
||||
//cerr Do some reporting:
|
||||
if(show_statistics) {
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
#include "nxsalgo.h"
|
||||
#include "nexus.h"
|
||||
#include "watch.h"
|
||||
|
||||
using namespace nxs;
|
||||
using namespace vcg;
|
||||
|
||||
#ifdef WIN32
|
||||
#include <wrap/system/getopt.h>
|
||||
#else
|
||||
|
@ -17,6 +7,33 @@ using namespace vcg;
|
|||
#include <assert.h>
|
||||
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
#include <vcg/simplex/vertex/with/vc.h>
|
||||
#include <vcg/simplex/face/face.h>
|
||||
#include <vcg/complex/trimesh/base.h>
|
||||
//WARNING WARNING this must be included AFTER mesh includes....
|
||||
#include <wrap/io_trimesh/import_ply.h>
|
||||
#include <vcg/space/index/grid_static_ptr.h>
|
||||
|
||||
#include "nxsalgo.h"
|
||||
#include "nexus.h"
|
||||
#include "watch.h"
|
||||
|
||||
using namespace nxs;
|
||||
using namespace vcg;
|
||||
using namespace std;
|
||||
using namespace tri;
|
||||
|
||||
class CFace;
|
||||
|
||||
class CVertex: public VertexVCf<DUMMYEDGETYPE,CFace ,DUMMYTETRATYPE> {};
|
||||
|
||||
class CFace: public Face<CVertex, DUMMYEDGETYPE , CFace>{};
|
||||
|
||||
class CMesh: public tri::TriMesh<vector<CVertex>, vector<CFace> > {};
|
||||
|
||||
string getSuffix(unsigned int signature) {
|
||||
string suff;
|
||||
if(signature&NXS_COMPRESSED) suff += "Z";
|
||||
|
@ -261,6 +278,24 @@ int main(int argc, char *argv[]) {
|
|||
nexus.Close();
|
||||
return 0;
|
||||
}
|
||||
CMesh mesh;
|
||||
GridStaticPtr<CMesh::FaceContainer> grid;
|
||||
if(add_colors) {
|
||||
if(!plysource.size()) {
|
||||
cerr << "No plysource specified when adding color (-p option)\n";
|
||||
return -1;
|
||||
}
|
||||
if(!tri::io::ImporterPLY<CMesh>::Open(mesh, plysource.c_str())) {
|
||||
cerr << "Could not load ply: " << plysource << endl;
|
||||
return -1;
|
||||
}
|
||||
//calcoliamo il box:
|
||||
Box3f box;
|
||||
for(unsigned int i = 0; i < mesh.vert.size(); i++)
|
||||
box.Add(mesh.vert[i].P());
|
||||
grid.SetBBox(box);
|
||||
grid.Set(mesh.face);
|
||||
}
|
||||
|
||||
if((add & NXS_NORMALS_SHORT) && compress) {
|
||||
cerr << "Its not possible to add normals and compress in the same step\n";
|
||||
|
@ -377,6 +412,7 @@ int main(int argc, char *argv[]) {
|
|||
cerr << "Unsupported color\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(qvertex && add_normals) {
|
||||
report.Init(nexus.index.size());
|
||||
cout << "Quantizing vertices\n";
|
||||
|
@ -391,9 +427,7 @@ int main(int argc, char *argv[]) {
|
|||
report.Finish();
|
||||
}
|
||||
|
||||
//fixing sphere.
|
||||
out.sphere = nexus.sphere;
|
||||
//copying history:
|
||||
out.history = nexus.history;
|
||||
|
||||
out.Close();
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
History
|
||||
|
||||
$Log: not supported by cvs2svn $
|
||||
Revision 1.6 2004/10/15 11:41:03 ponchio
|
||||
Tests and small changes.
|
||||
|
||||
Revision 1.5 2004/09/28 10:26:21 ponchio
|
||||
Rewrote.
|
||||
|
||||
|
@ -48,16 +51,68 @@ Created
|
|||
|
||||
****************************************************************************/
|
||||
|
||||
#pragma warning(disable:4786 4804 4244 4018 4267 4311)
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
#include "pvoronoi.h"
|
||||
#include <ANN/ANN.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace vcg;
|
||||
using namespace nxs;
|
||||
|
||||
bool Seed::Dist(const Point3f &point, float &mindist,
|
||||
void VoronoiPartition::Init() {
|
||||
if(bd) delete bd;
|
||||
buffer.resize(size() * 3);
|
||||
for(unsigned int i = 0; i < size(); i++) {
|
||||
for(int k = 0; k < 3; k++)
|
||||
buffer[i*3+k] = operator[](i)[k];
|
||||
}
|
||||
points.resize(size());
|
||||
for(unsigned int i = 0; i < size(); i++) {
|
||||
points[i] = &buffer[i*3];
|
||||
}
|
||||
bd = new ANNkd_tree(&*points.begin(), size(), 3);
|
||||
|
||||
}
|
||||
void VoronoiPartition::Closest(const vcg::Point3f &p, unsigned int nsize,
|
||||
vector<int> &near,
|
||||
vector<float> &dist) {
|
||||
double point[3];
|
||||
point[0] = p[0];
|
||||
point[1] = p[1];
|
||||
point[2] = p[2];
|
||||
|
||||
near.resize(nsize);
|
||||
dist.resize(nsize);
|
||||
vector<double> dists;
|
||||
dists.resize(nsize);
|
||||
bd->annkSearch(&point[0], nsize, &*near.begin(), &*dists.begin());
|
||||
for(unsigned int i = 0; i < nsize; i++)
|
||||
dist[i] = (float)dists[i];
|
||||
}
|
||||
|
||||
void VoronoiPartition::Closest(const vcg::Point3f &p,
|
||||
int &target, float &dist) {
|
||||
double point[3];
|
||||
point[0] = p[0];
|
||||
point[1] = p[1];
|
||||
point[2] = p[2];
|
||||
double dists;
|
||||
bd->annkSearch(&point[0], 1, &target, &dists, 1);
|
||||
assert(target >= 0);
|
||||
assert(target < size());
|
||||
|
||||
dist = (float)dists;
|
||||
}
|
||||
|
||||
int VoronoiPartition::Locate(const vcg::Point3f &p) {
|
||||
int target = -2;
|
||||
float dist;
|
||||
Closest(p, target, dist);
|
||||
return target;
|
||||
}
|
||||
|
||||
/*bool Seed::Dist(const Point3f &point, float &mindist,
|
||||
Point3f &res) {
|
||||
float newdist = Distance(p, point) * weight;
|
||||
if(newdist < mindist) {
|
||||
|
@ -115,3 +170,4 @@ Point3f VoronoiPartition::FindBorder(vcg::Point3f &p, float radius) {
|
|||
}
|
||||
return m;
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
History
|
||||
|
||||
$Log: not supported by cvs2svn $
|
||||
Revision 1.8 2004/10/15 11:41:03 ponchio
|
||||
Tests and small changes.
|
||||
|
||||
Revision 1.7 2004/09/28 10:26:21 ponchio
|
||||
Rewrote.
|
||||
|
||||
|
@ -63,16 +66,33 @@ Created
|
|||
#include <stdio.h>
|
||||
|
||||
#include <vcg/space/point3.h>
|
||||
#include <vcg/space/box3.h>
|
||||
#include <vcg/space/index/grid_static_ptr.h>
|
||||
|
||||
#include "crude.h"
|
||||
|
||||
//TODO provide a Sort function, to sort spatially the seeds.
|
||||
|
||||
class ANNkd_tree;
|
||||
|
||||
namespace nxs {
|
||||
|
||||
class Seed {
|
||||
|
||||
|
||||
class VoronoiPartition: public std::vector<vcg::Point3f> {
|
||||
public:
|
||||
VoronoiPartition(): bd(NULL) {}
|
||||
void Init();
|
||||
void Closest(const vcg::Point3f &p, unsigned int nsize,
|
||||
std::vector<int> &near,
|
||||
std::vector<float> &dist);
|
||||
void Closest(const vcg::Point3f &p,
|
||||
int &target, float &dist);
|
||||
|
||||
int Locate(const vcg::Point3f &p);
|
||||
|
||||
ANNkd_tree *bd;
|
||||
std::vector<double> buffer;
|
||||
std::vector<double *> points;
|
||||
};
|
||||
/* class Seed {
|
||||
public:
|
||||
vcg::Point3f p;
|
||||
float weight;
|
||||
|
@ -112,7 +132,7 @@ namespace nxs {
|
|||
return target;
|
||||
}
|
||||
vcg::Box3f box;
|
||||
};
|
||||
};*/
|
||||
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
History
|
||||
|
||||
$Log: not supported by cvs2svn $
|
||||
Revision 1.17 2004/10/29 16:33:29 ponchio
|
||||
Trying to fix big patches.
|
||||
|
||||
Revision 1.16 2004/10/22 14:31:56 ponchio
|
||||
Some controls added.
|
||||
|
||||
|
@ -85,35 +88,140 @@ using namespace vcg;
|
|||
using namespace nxs;
|
||||
|
||||
|
||||
void print(Point3f p) {
|
||||
cerr << p[0] << " " << p[1] << " " << p[2] << endl;
|
||||
}
|
||||
|
||||
float getClosest(const Point3f &seed, VoronoiPartition &part) {
|
||||
vector<int> near;
|
||||
vector<float> dist;
|
||||
part.Closest(seed, 2, near, dist);
|
||||
for(int k = 0; k < 2; k++) {
|
||||
int c = near[k];
|
||||
assert(c >= 0);
|
||||
assert(c < part.size());
|
||||
if(part[c] == seed) continue;
|
||||
return Distance(seed, part[c]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int getBest(const Point3f &seed, VoronoiPartition &part,
|
||||
vector<bool> &mark,
|
||||
vector<unsigned int> &counts) {
|
||||
|
||||
vector<int> near;
|
||||
vector<float> dist;
|
||||
int nnear = 7;
|
||||
if(part.size() < 7) nnear = part.size()/2;
|
||||
if(!nnear) return -1;
|
||||
|
||||
part.Closest(seed, nnear, near, dist);
|
||||
int best = -1;
|
||||
int bestcount = -1;
|
||||
int bestdist = -1;
|
||||
for(int k = 0; k < nnear; k++) {
|
||||
int c = near[k];
|
||||
assert(c >= 0);
|
||||
assert(c < part.size()); if(mark[c]) continue;
|
||||
if(part[c] == seed) continue;
|
||||
if(bestcount < 0 ||
|
||||
(counts[c] < bestcount)) {
|
||||
best = c;
|
||||
bestcount = counts[c];
|
||||
}
|
||||
/*if(bestdist < 0 ||
|
||||
Distance(seed, part[c]) < bestdist) {
|
||||
best = c;
|
||||
bestdist = Distance(seed, part[c]);
|
||||
}*/
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
//return false if still not ok
|
||||
bool VoronoiChain::Optimize(int mean, VoronoiPartition &part,
|
||||
vector<Point3f> ¢roids,
|
||||
vector<unsigned int> &counts,
|
||||
bool join) {
|
||||
|
||||
//remove small or really big patches.
|
||||
unsigned int failed = 0;
|
||||
vector<Point3f> seeds;
|
||||
vector<bool> mark;
|
||||
mark.resize(part.size(), false);
|
||||
|
||||
//first pass we check only big ones
|
||||
for(unsigned int i = 0; i < part.size(); i++) {
|
||||
if(counts[i] > max_size || counts[i] > 2 * mean) {
|
||||
failed++;
|
||||
cerr << "Failed> " << counts[i] << endl;
|
||||
float radius= getClosest(part[i], part);
|
||||
radius /= 3;
|
||||
if(radius < 0) continue;
|
||||
seeds.push_back(part[i] + Point3f(1, 0, 0) * radius);
|
||||
seeds.push_back(part[i] + Point3f(0, 1, 0) * radius);
|
||||
seeds.push_back(part[i] + Point3f(0, 0, 1) * radius);
|
||||
|
||||
seeds.push_back(part[i] - Point3f(1, 0, 0) * radius);
|
||||
seeds.push_back(part[i] - Point3f(0, 1, 0) * radius);
|
||||
seeds.push_back(part[i] - Point3f(0, 0, 1) * radius);
|
||||
mark[i];
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < part.size(); i++) {
|
||||
if(mark[i]) continue;
|
||||
if(join && counts[i] < min_size) {
|
||||
failed++;
|
||||
int best = getBest(part[i], part, mark, counts);
|
||||
if(best < 0) continue;
|
||||
assert(mark[best] == false);
|
||||
mark[best] = true;
|
||||
mark[i] = true;
|
||||
seeds.push_back((part[i] + part[best])/2);
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned int i = 0; i < part.size(); i++) {
|
||||
if(mark[i]) continue;
|
||||
if(join) part[i] = centroids[i]/(float)counts[i];
|
||||
seeds.push_back(part[i]);
|
||||
}
|
||||
|
||||
part.clear();
|
||||
for(unsigned int i = 0; i < seeds.size(); i++)
|
||||
part.push_back(seeds[i]);
|
||||
|
||||
if(part.size() == 0) part.push_back(Point3f(0,0,0));
|
||||
part.Init();
|
||||
return failed == 0;
|
||||
}
|
||||
|
||||
void VoronoiChain::Init(Crude &crude, float scaling, int steps) {
|
||||
unsigned int f_cells = crude.Faces() / mean_size;
|
||||
unsigned int c_cells = (unsigned int)(scaling * f_cells);
|
||||
|
||||
//cerr << "mean size: " << mean_size << endl;
|
||||
//cerr << "f cells: " << f_cells << endl;
|
||||
//cerr << "c_cells: " << c_cells << endl;
|
||||
|
||||
levels.push_back(VoronoiPartition());
|
||||
levels.push_back(VoronoiPartition());
|
||||
VoronoiPartition &fine = levels[0];
|
||||
VoronoiPartition &coarse = levels[1];
|
||||
fine.SetBox(crude.GetBox());
|
||||
coarse.SetBox(crude.GetBox());
|
||||
|
||||
srand(0);
|
||||
|
||||
float fine_vmean = mean_size/2.0f;
|
||||
float coarse_vmean = (mean_size/scaling)/2;
|
||||
float coarse_vmean = (mean_size/scaling)/2.0f;
|
||||
|
||||
for(unsigned int i = 0; i < crude.Vertices(); i++) {
|
||||
int f = (int)(fine_vmean * rand()/(RAND_MAX + 1.0));
|
||||
int c = (int)(coarse_vmean * rand()/(RAND_MAX + 1.0));
|
||||
if(f == 1) {
|
||||
if(f == 2) {
|
||||
Point3f &point = crude.GetVertex(i);
|
||||
fine.push_back(Seed(point, 1));
|
||||
fine.push_back(point);
|
||||
}
|
||||
if(c == 1) {
|
||||
if(c == 2) {
|
||||
Point3f &point = crude.GetVertex(i);
|
||||
coarse.push_back(Seed(point, 1));
|
||||
coarse.push_back(point);
|
||||
}
|
||||
}
|
||||
//TODO! Check for duplicates (use the closest :P)
|
||||
|
@ -136,48 +244,17 @@ void VoronoiChain::Init(Crude &crude, float scaling, int steps) {
|
|||
|
||||
report.Init(crude.Vertices());
|
||||
for(unsigned int v = 0; v < crude.Vertices(); v++) {
|
||||
if(v & 0xffff) report.Step(v);
|
||||
unsigned int ftarget;
|
||||
float dist = fine.Closest(crude.vert[v], ftarget);
|
||||
assert(ftarget != -1);
|
||||
centroids[ftarget] += crude.vert[v];
|
||||
counts[ftarget]++;
|
||||
report.Step(v);
|
||||
unsigned int target = fine.Locate(crude.vert[v]);
|
||||
centroids[target] += crude.vert[v];
|
||||
counts[target]++;
|
||||
}
|
||||
|
||||
//remove small or really big patches.
|
||||
unsigned int failed = 0;
|
||||
vector<Seed> seeds;
|
||||
for(unsigned int i = 0; i < fine.size(); i++) {
|
||||
if(counts[i] == 0 || (counts[i] < min_size && step != steps -3)) {
|
||||
failed++;
|
||||
} else if(counts[i] > max_size) {
|
||||
cerr << "Failed: " << i << " tot: " << counts[i] << endl;
|
||||
failed++;
|
||||
Seed s = fine[i];
|
||||
s.p = centroids[i]/(float)counts[i] + Point3f(1, 0, 0);
|
||||
fine[i].weight = (float)pow((counts[i]/2)/(float)fine_vmean, 0.2f);
|
||||
s.weight = fine[i].weight;
|
||||
seeds.push_back(fine[i]);
|
||||
seeds.push_back(s);
|
||||
} else {
|
||||
if(step != steps-1) {
|
||||
fine[i].p = centroids[i]/(float)counts[i];
|
||||
fine[i].weight = (float)pow(counts[i]/(float)fine_vmean, 0.2f);
|
||||
if(step == steps-1) {
|
||||
if(!Optimize(fine_vmean, fine, centroids, counts, false))
|
||||
step--;
|
||||
} else
|
||||
Optimize(fine_vmean, fine, centroids, counts, true);
|
||||
}
|
||||
seeds.push_back(fine[i]);
|
||||
}
|
||||
}
|
||||
fine.clear();
|
||||
for(unsigned int i = 0; i < seeds.size(); i++)
|
||||
fine.push_back(seeds[i]);
|
||||
|
||||
if(fine.size() == 0) fine.push_back(Point3f(0,0,0));
|
||||
fine.Init();
|
||||
if(step == steps-1 && failed) step--;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//here goes some optimization pass.
|
||||
//Coarse optimization.
|
||||
//vector<float> radius;
|
||||
|
@ -193,121 +270,20 @@ void VoronoiChain::Init(Crude &crude, float scaling, int steps) {
|
|||
for(unsigned int v = 0; v < crude.Vertices(); v++) {
|
||||
if(v & 0xffff) report.Step(v);
|
||||
unsigned int ctarget = 0xffffffff;
|
||||
float dist = coarse.Closest(crude.vert[v], ctarget);
|
||||
ctarget = coarse.Locate(crude.vert[v]);
|
||||
// float dist;
|
||||
// coarse.Closest(crude.vert[v], ctarget, dist);
|
||||
assert(ctarget != 0xffffffff);
|
||||
centroids[ctarget] += crude.vert[v];
|
||||
counts[ctarget]++;
|
||||
//if(dist > radius[ctarget]) radius[ctarget] = dist;
|
||||
}
|
||||
|
||||
//remove small or really big patches.
|
||||
unsigned int failed = 0;
|
||||
vector<Seed> seeds;
|
||||
for(unsigned int i = 0; i < coarse.size(); i++) {
|
||||
if(counts[i] == 0 || (counts[i] < min_size && step != steps-3)) {
|
||||
failed++;
|
||||
} else if(counts[i] > max_size) {
|
||||
cerr << "Failed: " << i << " tot: " << counts[i] << endl;
|
||||
failed++;
|
||||
Seed s = coarse[i];
|
||||
s.p = centroids[i]/(float)counts[i] + Point3f(1, 0, 0);
|
||||
coarse[i].weight = (float)pow((counts[i]/2)/(float)coarse_vmean, 0.2f);
|
||||
s.weight = coarse[i].weight;
|
||||
seeds.push_back(coarse[i]);
|
||||
seeds.push_back(s);
|
||||
} else {
|
||||
if(step != steps -1) {
|
||||
coarse[i].p = centroids[i]/(float)counts[i];
|
||||
coarse[i].weight = (float)pow(counts[i]/(float)coarse_vmean, 0.2f);
|
||||
if(step == steps-1) {
|
||||
if(!Optimize(coarse_vmean, coarse, centroids, counts, false))
|
||||
step --;
|
||||
} else
|
||||
Optimize(coarse_vmean, coarse, centroids, counts, true);
|
||||
}
|
||||
seeds.push_back(coarse[i]);
|
||||
}
|
||||
}
|
||||
coarse.clear();
|
||||
for(unsigned int i = 0; i < seeds.size(); i++)
|
||||
coarse.push_back(seeds[i]);
|
||||
|
||||
if(coarse.size() == 0) coarse.push_back(Point3f(0,0,0));
|
||||
coarse.Init();
|
||||
if(step == steps-1 && failed) step--;
|
||||
}
|
||||
|
||||
|
||||
//remove small or zero patches.
|
||||
vector<Seed> seeds;
|
||||
for(unsigned int i = 0; i < coarse.size(); i++) {
|
||||
if(counts[i] > (int)min_size)
|
||||
seeds.push_back(coarse[i]);
|
||||
}
|
||||
coarse.clear();
|
||||
for(unsigned int i = 0; i < seeds.size(); i++)
|
||||
coarse.push_back(seeds[i]);
|
||||
|
||||
if(coarse.size() == 0) coarse.push_back(Point3f(0,0,0));
|
||||
coarse.Init();
|
||||
|
||||
|
||||
//Coarse optimization
|
||||
/* vector< map<unsigned int, Point3f> > centroids;
|
||||
vector< map<unsigned int, unsigned int> > counts;
|
||||
|
||||
for(unsigned int i = 0; i < steps; i++) {
|
||||
cerr << "Optimization step 1: " << i << "/" << steps << endl;
|
||||
centroids.clear();
|
||||
counts.clear();
|
||||
centroids.resize(coarse.size());
|
||||
counts.resize(coarse.size());
|
||||
|
||||
for(unsigned int v = 0; v < crude.Vertices(); v++) {
|
||||
unsigned int ftarget;
|
||||
float dist = fine.Closest(crude.vert[v], ftarget);
|
||||
assert(ftarget != -1);
|
||||
|
||||
unsigned int ctarget;
|
||||
dist = coarse.Closest(crude.vert[v], ctarget);
|
||||
assert(ctarget != -1);
|
||||
|
||||
map<unsigned int, Point3f> ¢roids = centroids[ctarget];
|
||||
map<unsigned int, unsigned int> &count = counts[ctarget];
|
||||
|
||||
if(!centroids.count(ftarget))
|
||||
centroids[ftarget]= Point3f(0, 0, 0);
|
||||
|
||||
if(!count.count(ftarget))
|
||||
count[ftarget] = 0;
|
||||
|
||||
centroids[ftarget] += crude.vert[v];
|
||||
count[ftarget]++;
|
||||
}
|
||||
|
||||
for(unsigned int v = 0; v < coarse.size(); v++) {
|
||||
|
||||
map<unsigned int, Point3f> ¢roids = centroids[v];
|
||||
map<unsigned int, unsigned int> &count = counts[v];
|
||||
|
||||
|
||||
coarse[v].p = Point3f(0, 0, 0);
|
||||
float weight = 0;
|
||||
unsigned int tot_size =0;
|
||||
map<unsigned int, Point3f>::iterator k;
|
||||
for(k = centroids.begin();k != centroids.end(); k++) {
|
||||
unsigned int size = count[(*k).first];
|
||||
tot_size += size;
|
||||
//coarse[v].p += (*k).second / (size * size);
|
||||
//weight += 1/(float)size;
|
||||
coarse[v].p += (*k).second / size;
|
||||
weight += 1;
|
||||
// coarse[v].p += (*k).second;
|
||||
// weight += size;
|
||||
}
|
||||
assert(weight > 0);
|
||||
coarse[v].p /= weight;
|
||||
//TODO find a solution
|
||||
// coarse[v].weight = pow(tot_size/coarse_vmean, 0.25f);
|
||||
|
||||
}
|
||||
coarse.Init();
|
||||
}*/
|
||||
}
|
||||
|
||||
unsigned int VoronoiChain::Locate(unsigned int level,
|
||||
|
@ -418,7 +394,6 @@ void VoronoiChain::BuildLevel(Nexus &nexus, unsigned int offset,
|
|||
levels.push_back(VoronoiPartition());
|
||||
VoronoiPartition &coarse = levels[levels.size()-1];
|
||||
VoronoiPartition &fine = levels[levels.size()-2];
|
||||
coarse.SetBox(fine.box);
|
||||
fine.Init();
|
||||
|
||||
unsigned int tot_coarse = (unsigned int)(fine.size() * scaling);
|
||||
|
@ -452,14 +427,13 @@ void VoronoiChain::BuildLevel(Nexus &nexus, unsigned int offset,
|
|||
//Coarse optimization.
|
||||
vector<Point3f> centroids;
|
||||
vector<unsigned int> counts;
|
||||
//vector<float> radius;
|
||||
|
||||
for(int step = 0; step < steps; step++) {
|
||||
cerr << "Optimization step: " << step+1 << "/" << steps << endl;
|
||||
centroids.clear();
|
||||
counts.clear();
|
||||
centroids.resize(coarse.size(), Point3f(0, 0, 0));
|
||||
counts.resize(coarse.size(), 0);
|
||||
//radius.resize(coarse.size(), 0);
|
||||
|
||||
report.Init(nexus.index.size());
|
||||
for(unsigned int idx = offset; idx < nexus.index.size(); idx++) {
|
||||
|
@ -467,45 +441,18 @@ void VoronoiChain::BuildLevel(Nexus &nexus, unsigned int offset,
|
|||
Patch patch = nexus.GetPatch(idx);
|
||||
for(unsigned int i = 0; i < patch.nv; i++) {
|
||||
|
||||
unsigned int ctarget = 0xffffffff;
|
||||
float dist = coarse.Closest(patch.Vert(i), ctarget);
|
||||
assert(ctarget != 0xffffffff);
|
||||
unsigned int ctarget = coarse.Locate(patch.Vert(i));
|
||||
assert(ctarget < coarse.size());
|
||||
centroids[ctarget] += patch.Vert(i);
|
||||
counts[ctarget]++;
|
||||
//if(dist > radius[ctarget]) radius[ctarget] = dist;
|
||||
}
|
||||
}
|
||||
//remove small or really big patches.
|
||||
unsigned int failed = 0;
|
||||
vector<Seed> seeds;
|
||||
for(unsigned int i = 0; i < coarse.size(); i++) {
|
||||
if(counts[i] == 0 || (counts[i] < min_size && step != steps -3)) {
|
||||
failed++;
|
||||
} else if(counts[i] > max_size) {
|
||||
cerr << "Failed: " << i << " tot: " << counts[i] << endl;
|
||||
failed++;
|
||||
Seed s = coarse[i];
|
||||
s.p = centroids[i]/(float)counts[i] + Point3f(1, 0, 0);
|
||||
coarse[i].weight = (float)pow((counts[i]/2)/(float)coarse_vmean, 0.2f);
|
||||
s.weight = coarse[i].weight;
|
||||
seeds.push_back(coarse[i]);
|
||||
seeds.push_back(s);
|
||||
} else {
|
||||
if(step != steps-1) {
|
||||
coarse[i].p = centroids[i]/(float)counts[i];
|
||||
coarse[i].weight = (float)pow(counts[i]/(float)coarse_vmean, 0.2f);
|
||||
if(step == steps-1) {
|
||||
if(!Optimize(coarse_vmean, coarse, centroids, counts, false))
|
||||
step--;
|
||||
} else
|
||||
Optimize(coarse_vmean, coarse, centroids, counts, true);
|
||||
}
|
||||
seeds.push_back(coarse[i]);
|
||||
}
|
||||
}
|
||||
coarse.clear();
|
||||
for(unsigned int i = 0; i < seeds.size(); i++)
|
||||
coarse.push_back(seeds[i]);
|
||||
|
||||
if(coarse.size() == 0) coarse.push_back(Point3f(0,0,0));
|
||||
coarse.Init();
|
||||
if(step == steps-1 && failed) step--;
|
||||
}
|
||||
newfragments.clear();
|
||||
//TODO add some optimization
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
History
|
||||
|
||||
$Log: not supported by cvs2svn $
|
||||
Revision 1.4 2004/09/30 00:27:42 ponchio
|
||||
Lot of changes. Backup.
|
||||
|
||||
Revision 1.3 2004/09/21 00:53:23 ponchio
|
||||
Lotsa changes.
|
||||
|
||||
|
@ -81,6 +84,10 @@ class VoronoiChain: public PChain {
|
|||
|
||||
float radius;
|
||||
vcg::Box3f box;
|
||||
|
||||
bool Optimize(int mean, VoronoiPartition &part,
|
||||
std::vector<vcg::Point3f> ¢roids,
|
||||
std::vector<unsigned int> &counts, bool join);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
History
|
||||
|
||||
$Log: not supported by cvs2svn $
|
||||
Revision 1.19 2004/10/22 10:37:32 ponchio
|
||||
Split is now in fragment.
|
||||
|
||||
Revision 1.18 2004/10/21 13:40:16 ponchio
|
||||
Debugging.
|
||||
|
||||
|
@ -103,13 +106,6 @@ using namespace std;
|
|||
using namespace vcg;
|
||||
using namespace nxs;
|
||||
|
||||
/*void NexusSplit(Nexus &nexus, VoronoiChain &vchain,
|
||||
unsigned int level,
|
||||
vector<Point3f> &newvert,
|
||||
vector<unsigned int> &newface,
|
||||
vector<Link> &newbord,
|
||||
Nexus::Update &update,
|
||||
float error);*/
|
||||
|
||||
void BuildFragment(Nexus &nexus, VoronoiPartition &part,
|
||||
set<unsigned int> &patches,
|
||||
|
@ -377,241 +373,6 @@ int main(int argc, char *argv[]) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*void NexusSplit(Nexus &nexus, VoronoiChain &vchain,
|
||||
unsigned int level,
|
||||
vector<Point3f> &newvert,
|
||||
vector<unsigned int> &newface,
|
||||
vector<Link> &newbord,
|
||||
Nexus::Update &update,
|
||||
float error) {
|
||||
|
||||
map<unsigned int, Point3f> centroids;
|
||||
map<unsigned int, unsigned int> counts;
|
||||
|
||||
Point3f centroid(0, 0, 0);
|
||||
Box3f box;
|
||||
for(unsigned int f = 0; f < newface.size(); f += 3) {
|
||||
Point3f bari = (newvert[newface[f]] +
|
||||
newvert[newface[f+1]] +
|
||||
newvert[newface[f+2]])/3;
|
||||
centroid += bari;
|
||||
box.Add(bari);
|
||||
unsigned int cell = vchain.Locate(level+1, bari);
|
||||
if(!centroids.count(cell)) centroids[cell] = Point3f(0, 0, 0);
|
||||
if(!counts.count(cell)) counts[cell] = 0;
|
||||
centroids[cell] += bari;
|
||||
counts[cell]++;
|
||||
}
|
||||
centroid /= newface.size()/3;
|
||||
//prune small cells:
|
||||
float min_size = (newface.size()/3) / 20.0f;
|
||||
|
||||
vector<unsigned int> cellremap;
|
||||
VoronoiPartition local;
|
||||
local.SetBox(vchain.levels[level].box);
|
||||
map<unsigned int, Point3f>::iterator r;
|
||||
for(r = centroids.begin(); r != centroids.end(); r++) {
|
||||
unsigned int cell = (*r).first;
|
||||
if(counts[cell] < min_size) continue;
|
||||
|
||||
Point3f seed = (*r).second/counts[cell];
|
||||
Point3f orig = vchain.levels[level+1][cell].p;
|
||||
// seed = (seed + orig*2)/3;
|
||||
seed = orig;
|
||||
local.push_back(seed);
|
||||
cellremap.push_back(cell);
|
||||
}
|
||||
local.Init();
|
||||
|
||||
//if != -1 remap global index to cell index (first arg)
|
||||
map<unsigned int, vector<int> > vert_remap;
|
||||
map<unsigned int, unsigned int> vert_count;
|
||||
|
||||
//simply collects faces
|
||||
map<unsigned int, vector<int> > face_remap;
|
||||
map<unsigned int, unsigned int> face_count;
|
||||
|
||||
for(unsigned int f = 0; f < newface.size(); f += 3) {
|
||||
Point3f bari = (newvert[newface[f]] +
|
||||
newvert[newface[f+1]] +
|
||||
newvert[newface[f+2]])/3;
|
||||
|
||||
// unsigned int cell = vchain.Locate(level+1, bari);
|
||||
unsigned int cell = cellremap[local.Locate(bari)];
|
||||
|
||||
vector<int> &f_remap = face_remap[cell];
|
||||
f_remap.push_back(newface[f]);
|
||||
f_remap.push_back(newface[f+1]);
|
||||
f_remap.push_back(newface[f+2]);
|
||||
face_count[cell]++;
|
||||
|
||||
if(!vert_remap.count(cell)) {
|
||||
vert_remap[cell].resize(newvert.size(), -1);
|
||||
vert_count[cell] = 0;
|
||||
}
|
||||
|
||||
vector<int> &v_remap = vert_remap[cell];
|
||||
|
||||
for(int i = 0; i < 3; i++)
|
||||
if(v_remap[newface[f+i]] == -1)
|
||||
v_remap[newface[f+i]] = vert_count[cell]++;
|
||||
}
|
||||
|
||||
//TODO prune small count cells and assure no big ones.
|
||||
|
||||
//lets count borders
|
||||
map<unsigned int, unsigned int> bord_count;
|
||||
|
||||
map<unsigned int, unsigned int >::iterator c;
|
||||
for(c = vert_count.begin(); c != vert_count.end(); c++) {
|
||||
unsigned int cell = (*c).first;
|
||||
unsigned int &count = bord_count[cell];
|
||||
count = 0;
|
||||
|
||||
vector<int> &v_remap = vert_remap[cell];
|
||||
|
||||
//external borders
|
||||
for(unsigned int i = 0; i < newbord.size(); i++) {
|
||||
Link link = newbord[i];
|
||||
if(v_remap[link.start_vert] == -1) continue;
|
||||
count++;
|
||||
}
|
||||
|
||||
//process internal borders;
|
||||
//TODO higly inefficient!!!
|
||||
map<unsigned int, unsigned int >::iterator t;
|
||||
for(t = vert_count.begin(); t != vert_count.end(); t++) {
|
||||
if(cell == (*t).first) continue;
|
||||
vector<int> &vremapclose = vert_remap[(*t).first];
|
||||
for(unsigned int i = 0; i < newvert.size(); i++) {
|
||||
if(v_remap[i] != -1 && vremapclose[i] != -1) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map<unsigned int, unsigned int> cells2patches;
|
||||
|
||||
//lets allocate space
|
||||
for(c = vert_count.begin(); c != vert_count.end(); c++) {
|
||||
unsigned int cell = (*c).first;
|
||||
//TODO detect best parameter below.
|
||||
unsigned int patch_idx = nexus.AddPatch(vert_count[cell],
|
||||
face_count[cell],
|
||||
6 * bord_count[cell]);
|
||||
|
||||
//why double border space? because at next level
|
||||
//we will need to add those borders...
|
||||
cells2patches[cell] = patch_idx;
|
||||
vchain.newfragments[cell].insert(patch_idx);
|
||||
update.created.push_back(patch_idx);
|
||||
}
|
||||
|
||||
|
||||
//fill it now.
|
||||
for(c = vert_count.begin(); c != vert_count.end(); c++) {
|
||||
unsigned int cell = (*c).first;
|
||||
unsigned int patch_idx = cells2patches[cell];
|
||||
|
||||
//vertices first
|
||||
vector<int> &v_remap = vert_remap[cell];
|
||||
|
||||
vector<Point3f> verts;
|
||||
verts.resize(vert_count[cell]);
|
||||
for(unsigned int i = 0; i < newvert.size(); i++) {
|
||||
if(v_remap[i] != -1)
|
||||
verts[v_remap[i]] = newvert[i];
|
||||
}
|
||||
|
||||
//faces now
|
||||
vector<int> &f_remap = face_remap[cell];
|
||||
|
||||
vector<unsigned short> faces;
|
||||
faces.resize(face_count[cell]*3);
|
||||
|
||||
for(unsigned int i = 0; i < f_remap.size(); i++) {
|
||||
assert(v_remap[f_remap[i]] != -1);
|
||||
faces[i] = v_remap[f_remap[i]];
|
||||
}
|
||||
|
||||
//borders last
|
||||
vector<Link> bords;
|
||||
|
||||
//process external borders
|
||||
//for every esternal link we must update external patches!
|
||||
for(unsigned int i = 0; i < newbord.size(); i++) {
|
||||
Link link = newbord[i];
|
||||
assert(!link.IsNull());
|
||||
if(v_remap[link.start_vert] == -1) continue;
|
||||
link.start_vert = v_remap[link.start_vert];
|
||||
bords.push_back(link);
|
||||
|
||||
|
||||
Border rborder = nexus.GetBorder(link.end_patch);
|
||||
|
||||
unsigned int pos = rborder.Size();
|
||||
if(nexus.borders.ResizeBorder(link.end_patch, pos+1)) {
|
||||
rborder = nexus.GetBorder(link.end_patch);
|
||||
}
|
||||
|
||||
assert(rborder.Size() < rborder.Available());
|
||||
assert(rborder.Available() > pos);
|
||||
|
||||
Link newlink;
|
||||
newlink.start_vert = link.end_vert;
|
||||
newlink.end_vert = link.start_vert;
|
||||
newlink.end_patch = patch_idx;
|
||||
rborder[pos] = newlink;
|
||||
}
|
||||
|
||||
//process internal borders;
|
||||
//TODO higly inefficient!!!
|
||||
map<unsigned int, unsigned int >::iterator t;
|
||||
for(t = vert_count.begin(); t != vert_count.end(); t++) {
|
||||
unsigned int rcell = (*t).first;
|
||||
if(cell == rcell) continue;
|
||||
|
||||
assert(cells2patches.count(rcell));
|
||||
unsigned int rpatch = cells2patches[rcell];
|
||||
assert(rpatch < nexus.index.size());
|
||||
|
||||
vector<int> &vremapclose = vert_remap[rcell];
|
||||
for(unsigned int i = 0; i < newvert.size(); i++) {
|
||||
if(v_remap[i] != -1 && vremapclose[i] != -1) {
|
||||
Link link;
|
||||
link.end_patch = rpatch;
|
||||
link.start_vert = v_remap[i];
|
||||
link.end_vert = vremapclose[i];
|
||||
bords.push_back(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Nexus::PatchInfo &entry = nexus.index[patch_idx];
|
||||
entry.error = error;
|
||||
|
||||
Patch patch = nexus.GetPatch(patch_idx);
|
||||
memcpy(patch.FaceBegin(), &faces[0],
|
||||
faces.size() * sizeof(unsigned short));
|
||||
memcpy(patch.VertBegin(), &verts[0], verts.size() * sizeof(Point3f));
|
||||
|
||||
|
||||
for(unsigned int v = 0; v < verts.size(); v++) {
|
||||
entry.sphere.Add(verts[v]);
|
||||
nexus.sphere.Add(verts[v]);
|
||||
}
|
||||
|
||||
Border border = nexus.GetBorder(patch_idx);
|
||||
assert(border.Available() >= bords.size());
|
||||
if(nexus.borders.ResizeBorder(patch_idx, bords.size())) {
|
||||
border = nexus.GetBorder(patch_idx);
|
||||
}
|
||||
memcpy(&(border[0]), &(bords[0]), bords.size() * sizeof(Link));
|
||||
}
|
||||
}*/
|
||||
|
||||
void BuildFragment(Nexus &nexus, VoronoiPartition &part,
|
||||
set<unsigned int> &patches,
|
||||
Nexus::Update &update,
|
||||
|
|
Loading…
Reference in New Issue