Mat importObjModel(const std::string &filename, Viz3d &viz3d, bool returnCloud) {
	attrib_t attributes;
	vector<material_t> materials;
	vector<shape_t> shapes;
	ifstream objFile(filename);

	if (!objFile.is_open()) {
		cerr << "Can't open " + string(filename) << endl;
		return Mat();
	}

	string err;
	MaterialFileReader reader(".\\");
	bool ok = LoadObj(&attributes, &shapes, &materials, &err, &objFile, &reader);

	if (!err.empty()) {
		cerr << "TinyObj: got error " << err << endl;
	}

	if (!ok) {
		cerr << "TinyObj: can't parse " << filename << endl;
		return Mat();
	}

	Mat pointsProxy = Mat(attributes.vertices, false).reshape(3, 1);
	centralize(pointsProxy);

	vector<vector<index_t>> faces(materials.size() + 1);
	vector<vector<int>> numFaceVertices(materials.size() + 1);
	for (const auto &shape : shapes) {
		size_t accum = 0;
		for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); ++f) {
			auto fv = shape.mesh.num_face_vertices[f];
			int matIdx = shape.mesh.material_ids[f] > -1 ? shape.mesh.material_ids[f] : materials.size();
			numFaceVertices[matIdx].push_back(fv);
			for (size_t v = 0; v < fv; ++v) {
				faces[matIdx].push_back(shape.mesh.indices[accum + v]);
			}
			accum += fv;
		}
	}

	for (size_t i = 0; i < materials.size() + 1; ++i) {
		if (numFaceVertices[i].empty()) continue;
		vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
		vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
		points->SetDataType(_VTK_FLT);
		vtkSmartPointer<vtkCellArray> polys = vtkSmartPointer<vtkCellArray>::New();
		vtkSmartPointer<vtkDataArray> tcoords = vtkSmartPointer<_vtkArrayType>::New();
		tcoords->SetNumberOfComponents(2);
		vtkSmartPointer<vtkDataArray> normals = vtkSmartPointer<_vtkArrayType>::New();
		normals->SetNumberOfComponents(3);
		size_t accum = 0;
		for (int fv : numFaceVertices[i]) {
			polys->InsertNextCell(fv);
			for (int v = 0; v < fv; ++v) {
				const index_t index = faces[i][accum + v];
				points->InsertNextPoint(attributes.vertices.data() + 3 * index.vertex_index);
				polys->InsertCellPoint(accum + v);
				if (index.texcoord_index > -1) {
					FltType u = attributes.texcoords[2 * index.texcoord_index];
					FltType v = attributes.texcoords[2 * index.texcoord_index + 1];
					if (FLIP_TCOORD_Y) v = 1.0 - v;
					tcoords->InsertNextTuple2(u, v);
				}
				if (index.normal_index > -1) {
					normals->InsertNextTuple(attributes.normals.data() + 3 * index.normal_index);
				}

			}
			accum += fv;
		}
		polyData->SetPoints(points);
		polyData->SetPolys(polys);
		if (points->GetNumberOfPoints() == tcoords->GetNumberOfTuples()) {
			if (i < materials.size() && hasTexture(materials[i]))
				polyData->GetPointData()->SetTCoords(tcoords);
		}
		if (points->GetNumberOfPoints() == normals->GetNumberOfTuples()) {
			polyData->GetPointData()->SetNormals(normals);
		}
		polyData->Squeeze();
		if (i < materials.size()) {
			viz3d.showWidget("mesh_" + to_string(i + 1), WMaterialMesh(polyData, materials[i]));
		} else {
			viz3d.showWidget("mesh_" + to_string(i + 1), WMaterialMesh(polyData));
		}
	}
	return returnCloud ? Mat(attributes.vertices, true).reshape(3, 1) : Mat();
}
Mat importPlyModel(const std::string &filename, Viz3d &viz3d, bool returnCloud) {
	Mesh mesh = loadMeshTypeEnforced(filename, Mesh::LOAD_PLY);
	centralize(mesh.cloud);
	viz3d.showWidget("mesh_1", WMesh(mesh));
	return returnCloud ? mesh.cloud : Mat();
}