You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
640 lines
18 KiB
640 lines
18 KiB
/**
|
|
* Wavefront OBJ realted I/O functionality for curves and surfaces.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license that can be found in
|
|
* the LICENSE file.
|
|
*/
|
|
|
|
#ifndef TINYNURBS_OBJ_H
|
|
#define TINYNURBS_OBJ_H
|
|
|
|
#include "../core/curve.h"
|
|
#include "../core/surface.h"
|
|
#include "../util/array2.h"
|
|
#include "../util/util.h"
|
|
#include "glm/glm.hpp"
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
|
|
namespace tinynurbs
|
|
{
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
namespace internal
|
|
{
|
|
|
|
/**
|
|
* Read rational curve data from a Wavefront OBJ stream.
|
|
* @param is The input stream
|
|
* @param[inout] deg Degree of the curve
|
|
* @param[inout] knots Knot vector of the curve
|
|
* @param[inout] ctrlPts Array of control points
|
|
* @param[inout] weights Array of corresponding weights
|
|
* @param[inout] rational Whether rational
|
|
*/
|
|
template <typename T>
|
|
void curveReadOBJ(std::istream &is, unsigned int °, std::vector<T> &knots,
|
|
std::vector<glm::vec<3, T>> &ctrlPts, std::vector<T> &weights, bool &rational)
|
|
{
|
|
T knot_min = 0, knot_max = 1;
|
|
std::vector<glm::vec<3, T>> ctrl_pts_buf;
|
|
std::vector<T> weights_buf;
|
|
std::vector<int> indices;
|
|
std::vector<T> temp_knots;
|
|
|
|
std::string start, token, sline;
|
|
std::istringstream ssline;
|
|
|
|
struct ToParse
|
|
{
|
|
bool deg, cstype, curv, parm;
|
|
};
|
|
|
|
ToParse parsed;
|
|
|
|
while (std::getline(is, sline))
|
|
{
|
|
if (sline.size() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
ssline.str(sline);
|
|
ssline >> start;
|
|
if (start == "v")
|
|
{
|
|
std::vector<double> four_coords;
|
|
four_coords.resize(4, 0.0);
|
|
four_coords[3] = 1.0;
|
|
int index = 0;
|
|
while (ssline && index <= 3)
|
|
{
|
|
ssline >> four_coords[index++];
|
|
}
|
|
ctrl_pts_buf.emplace_back(four_coords[0], four_coords[1], four_coords[2]);
|
|
weights_buf.push_back(four_coords[3]);
|
|
}
|
|
else if (start == "cstype")
|
|
{
|
|
std::string token1;
|
|
ssline >> token1;
|
|
if (token1 == "bspline")
|
|
{
|
|
rational = false;
|
|
parsed.cstype = true;
|
|
}
|
|
else if (token1 == "rat")
|
|
{
|
|
std::string token2;
|
|
ssline >> token2;
|
|
if (token2 == "bspline")
|
|
{
|
|
rational = true;
|
|
parsed.cstype = true;
|
|
}
|
|
}
|
|
}
|
|
else if (start == "deg")
|
|
{
|
|
ssline >> deg;
|
|
parsed.deg = true;
|
|
}
|
|
else if (start == "curv")
|
|
{
|
|
ssline >> knot_min >> knot_max;
|
|
while (ssline >> token)
|
|
{
|
|
if (token == "\\")
|
|
{
|
|
ssline.clear();
|
|
getline(is, sline);
|
|
ssline.str(sline);
|
|
}
|
|
else
|
|
{
|
|
indices.push_back(std::stof(token));
|
|
}
|
|
}
|
|
parsed.curv = true;
|
|
}
|
|
else if (start == "parm")
|
|
{
|
|
ssline >> start;
|
|
if (start == "u")
|
|
{
|
|
while (ssline >> token)
|
|
{
|
|
if (token == "\\")
|
|
{
|
|
ssline.clear();
|
|
std::getline(is, sline);
|
|
ssline.str(sline);
|
|
}
|
|
else
|
|
{
|
|
temp_knots.push_back(std::stof(token));
|
|
}
|
|
}
|
|
}
|
|
parsed.parm = true;
|
|
}
|
|
else if (start == "end")
|
|
{
|
|
break;
|
|
}
|
|
ssline.clear();
|
|
}
|
|
|
|
// Check if necessary data was available in stream
|
|
if (!parsed.cstype)
|
|
{
|
|
throw std::runtime_error("'cstype bspline / cstype rat bspline' line missing in file");
|
|
}
|
|
if (!parsed.deg)
|
|
{
|
|
throw std::runtime_error("'deg' line missing/incomplete in file");
|
|
}
|
|
if (!parsed.curv)
|
|
{
|
|
throw std::runtime_error("'curv' line missing/incomplete in file");
|
|
}
|
|
if (!parsed.parm)
|
|
{
|
|
throw std::runtime_error("'parm' line missing/incomplete in file");
|
|
}
|
|
|
|
int num_knots = temp_knots.size();
|
|
int num_cp = num_knots - deg - 1;
|
|
|
|
ctrlPts.resize(num_cp);
|
|
weights.resize(num_cp);
|
|
size_t num = 0;
|
|
for (int i = 0; i < num_cp; ++i)
|
|
{
|
|
assert(i < ctrlPts.size());
|
|
ctrlPts[i] = ctrl_pts_buf[indices[num] - 1];
|
|
weights[i] = weights_buf[indices[num] - 1];
|
|
++num;
|
|
}
|
|
|
|
knots = temp_knots;
|
|
}
|
|
|
|
/**
|
|
* Read rational surface data from a Wavefront OBJ stream.
|
|
* @param is The input stream
|
|
* @param[inout] deg_u Degree of the surface along u-direction
|
|
* @param[inout] deg_v Degree of the surface along u-direction
|
|
* @param[inout] knots_u Knot vector of the surface along u-direction
|
|
* @param[inout] knots_v Knot vector of the surface along v-direction
|
|
* @param[inout] ctrlPts 2D grid of control points of the surface
|
|
* @param[inout] weights 2D grid of corresponding weights
|
|
* @param[inout] rational Whether rational
|
|
*/
|
|
template <typename T>
|
|
void surfaceReadOBJ(std::istream &is, unsigned int °_u, unsigned int °_v,
|
|
std::vector<T> &knots_u, std::vector<T> &knots_v,
|
|
array2<glm::vec<3, T>> &ctrlPts, array2<T> &weights, bool &rational)
|
|
{
|
|
T uknot_min = 0, uknot_max = 1;
|
|
T vknot_min = 0, vknot_max = 1;
|
|
|
|
std::vector<glm::vec<3, T>> ctrl_pts_buf;
|
|
std::vector<T> weights_buf;
|
|
std::vector<int> indices;
|
|
std::vector<T> temp_uknots;
|
|
std::vector<T> temp_vknots;
|
|
|
|
std::string start, token, sline;
|
|
std::istringstream ssline;
|
|
|
|
struct ToParse
|
|
{
|
|
bool deg, cstype, surf, parm;
|
|
};
|
|
|
|
ToParse parsed;
|
|
|
|
while (std::getline(is, sline))
|
|
{
|
|
if (sline.size() == 0)
|
|
{
|
|
break;
|
|
}
|
|
ssline.str(sline);
|
|
ssline >> start;
|
|
if (start == "v")
|
|
{
|
|
std::vector<double> four_coords;
|
|
four_coords.resize(4);
|
|
four_coords[3] = 1.0;
|
|
int index = 0;
|
|
while (ssline && index <= 3)
|
|
{
|
|
ssline >> four_coords[index++];
|
|
}
|
|
ctrl_pts_buf.emplace_back(four_coords[0], four_coords[1], four_coords[2]);
|
|
weights_buf.push_back(four_coords[3]);
|
|
}
|
|
else if (start == "cstype")
|
|
{
|
|
std::string token1;
|
|
ssline >> token1;
|
|
if (token1 == "bspline")
|
|
{
|
|
rational = false;
|
|
parsed.cstype = true;
|
|
}
|
|
else if (token1 == "rat")
|
|
{
|
|
std::string token2;
|
|
ssline >> token2;
|
|
if (token2 == "bspline")
|
|
{
|
|
rational = true;
|
|
parsed.cstype = true;
|
|
}
|
|
}
|
|
}
|
|
else if (start == "deg")
|
|
{
|
|
ssline >> deg_u >> deg_v;
|
|
parsed.deg = true;
|
|
}
|
|
else if (start == "surf")
|
|
{
|
|
ssline >> uknot_min >> uknot_max >> vknot_min >> vknot_max;
|
|
while (ssline >> token)
|
|
{
|
|
if (token == "\\")
|
|
{
|
|
ssline.clear();
|
|
getline(is, sline);
|
|
ssline.str(sline);
|
|
}
|
|
else
|
|
{
|
|
indices.push_back(std::stof(token));
|
|
}
|
|
}
|
|
parsed.surf = true;
|
|
}
|
|
else if (start == "parm")
|
|
{
|
|
ssline >> start;
|
|
if (start == "u")
|
|
{
|
|
while (ssline >> token)
|
|
{
|
|
if (token == "\\")
|
|
{
|
|
ssline.clear();
|
|
std::getline(is, sline);
|
|
ssline.str(sline);
|
|
}
|
|
else
|
|
{
|
|
temp_uknots.push_back(std::stof(token));
|
|
}
|
|
}
|
|
}
|
|
else if (start == "v")
|
|
{
|
|
while (ssline >> token)
|
|
{
|
|
if (token == "\\")
|
|
{
|
|
ssline.clear();
|
|
std::getline(is, sline);
|
|
ssline.str(sline);
|
|
}
|
|
else
|
|
{
|
|
temp_vknots.push_back(std::stof(token));
|
|
}
|
|
}
|
|
}
|
|
parsed.parm = true;
|
|
}
|
|
else if (start == "end")
|
|
{
|
|
break;
|
|
}
|
|
ssline.clear();
|
|
}
|
|
|
|
// Check if necessary data was available in stream
|
|
if (!parsed.cstype)
|
|
{
|
|
throw std::runtime_error("'cstype bspline / cstype rat bspline' line missing in file");
|
|
}
|
|
if (!parsed.deg)
|
|
{
|
|
throw std::runtime_error("'deg' line missing/incomplete in file");
|
|
}
|
|
if (!parsed.surf)
|
|
{
|
|
throw std::runtime_error("'surf' line missing/incomplete in file");
|
|
}
|
|
if (!parsed.parm)
|
|
{
|
|
throw std::runtime_error("'parm' line missing/incomplete in file");
|
|
}
|
|
|
|
int num_knots_u = temp_uknots.size();
|
|
int num_knots_v = temp_vknots.size();
|
|
int num_cp_u = num_knots_u - deg_u - 1;
|
|
int num_cp_v = num_knots_v - deg_v - 1;
|
|
|
|
ctrlPts.resize(num_cp_u, num_cp_v);
|
|
weights.resize(num_cp_u, num_cp_v);
|
|
size_t num = 0;
|
|
for (int j = 0; j < num_cp_v; ++j)
|
|
{
|
|
for (int i = 0; i < num_cp_u; ++i)
|
|
{
|
|
assert(i < ctrlPts.rows() && j < ctrlPts.cols());
|
|
ctrlPts(i, j) = ctrl_pts_buf[indices[num] - 1];
|
|
weights(i, j) = weights_buf[indices[num] - 1];
|
|
++num;
|
|
}
|
|
}
|
|
|
|
knots_u = temp_uknots;
|
|
knots_v = temp_vknots;
|
|
}
|
|
|
|
/**
|
|
* Save curve data to a Wavefront OBJ stream.
|
|
* @param os The output stream
|
|
* @param deg Degree of the curve
|
|
* @param knots Knot vector of the curve
|
|
* @param ctrlPts Array of control points
|
|
* @param weights Array of corresponding weights
|
|
* @param rational Whether rational
|
|
*/
|
|
template <typename T>
|
|
void curveSaveOBJ(std::ostream &os, unsigned int degree, const std::vector<T> &knots,
|
|
const std::vector<glm::vec<3, T>> &ctrlPts, const std::vector<T> &weights,
|
|
bool rational)
|
|
{
|
|
using std::endl;
|
|
|
|
for (int i = 0; i < ctrlPts.size(); ++i)
|
|
{
|
|
os << "v " << ctrlPts[i].x << " " << ctrlPts[i].y << " " << ctrlPts[i].z << " "
|
|
<< weights[i] << endl;
|
|
}
|
|
|
|
int n_knots = knots.size();
|
|
int n_cp = ctrlPts.size();
|
|
|
|
if (!rational)
|
|
{
|
|
os << "cstype bspline" << endl;
|
|
}
|
|
else
|
|
{
|
|
os << "cstype rat bspline" << endl;
|
|
}
|
|
os << "deg " << degree << endl << "curv ";
|
|
os << knots[degree] << " " << knots[n_knots - degree - 1];
|
|
for (int i = 0; i < n_cp; ++i)
|
|
{
|
|
os << " " << i + 1;
|
|
}
|
|
os << endl << "parm u";
|
|
for (auto knot : knots)
|
|
{
|
|
os << " " << knot;
|
|
}
|
|
os << endl << "end";
|
|
}
|
|
|
|
/**
|
|
* Save surface data to a Wavefront OBJ stream.
|
|
* @param os The output stream
|
|
* @param deg_u Degree of the surface along u-direction
|
|
* @param deg_v Degree of the surface along v-direction
|
|
* @param knots_u Knot vector of the surface along u-direction
|
|
* @param knots_v Knot vector of the surface along v-direction
|
|
* @param ctrlPts 2D grid of control points
|
|
* @param weights 2D grid of corresponding weights
|
|
* @param rational Whether rational
|
|
*/
|
|
template <typename T>
|
|
void surfaceSaveOBJ(std::ostream &os, unsigned int deg_u, unsigned int deg_v,
|
|
const std::vector<T> &knots_u, const std::vector<T> &knots_v,
|
|
const array2<glm::vec<3, T>> &ctrlPts, const array2<T> &weights, bool rational)
|
|
{
|
|
|
|
using std::endl;
|
|
|
|
if (ctrlPts.rows() == 0 || ctrlPts.cols() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int j = 0; j < ctrlPts.cols(); j++)
|
|
{
|
|
for (int i = 0; i < ctrlPts.rows(); i++)
|
|
{
|
|
os << "v " << ctrlPts(i, j).x << " " << ctrlPts(i, j).y << " " << ctrlPts(i, j).z
|
|
<< " " << weights(i, j) << endl;
|
|
}
|
|
}
|
|
|
|
int nknots_u = knots_u.size();
|
|
int nknots_v = knots_v.size();
|
|
|
|
int nCpU = ctrlPts.rows();
|
|
int nCpV = ctrlPts.cols();
|
|
|
|
if (!rational)
|
|
{
|
|
os << "cstype bspline" << endl;
|
|
}
|
|
else
|
|
{
|
|
os << "cstype rat bspline" << endl;
|
|
}
|
|
os << "deg " << deg_u << " " << deg_v << endl << "surf ";
|
|
os << knots_u[deg_u] << " " << knots_u[nknots_u - deg_u - 1] << " " << knots_v[deg_v] << " "
|
|
<< knots_v[nknots_v - deg_v - 1];
|
|
for (int i = 0; i < nCpU * nCpV; i++)
|
|
{
|
|
os << " " << i + 1;
|
|
}
|
|
os << endl << "parm u";
|
|
for (auto knot : knots_u)
|
|
{
|
|
os << " " << knot;
|
|
}
|
|
os << endl << "parm v";
|
|
for (auto knot : knots_v)
|
|
{
|
|
os << " " << knot;
|
|
}
|
|
os << endl << "end";
|
|
}
|
|
|
|
} // namespace internal
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Read curve data from a Wavefront OBJ stream and populate a RationalCurve object
|
|
* @param is The input stream
|
|
* @return RationalCurve object
|
|
*/
|
|
template <typename T> RationalCurve<T> curveReadOBJ(std::istream &is)
|
|
{
|
|
RationalCurve<T> crv;
|
|
std::vector<glm::vec<3, T>> control_points;
|
|
bool rat;
|
|
internal::curveReadOBJ(is, crv.degree, crv.knots, crv.control_points, crv.weights, rat);
|
|
return crv;
|
|
}
|
|
|
|
/**
|
|
* Read surface data from a Wavefront OBJ stream and populate a RationalSurface object
|
|
* @param is The input stream
|
|
* @return RationalSurface object
|
|
*/
|
|
template <typename T> RationalSurface<T> surfaceReadOBJ(std::istream &is)
|
|
{
|
|
RationalSurface<T> srf;
|
|
bool rat;
|
|
internal::surfaceReadOBJ(is, srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v,
|
|
srf.control_points, srf.weights, rat);
|
|
return srf;
|
|
}
|
|
|
|
/**
|
|
* Save curve data from a Wavefront OBJ stream and populate a RationalSurface object
|
|
* @param os The output stream
|
|
* @param Curve object to save
|
|
*/
|
|
template <typename T> void curveSaveOBJ(std::ostream &os, const Curve<T> &crv)
|
|
{
|
|
std::vector<T> w(crv.control_points.size(), T(1));
|
|
internal::curveSaveOBJ(os, crv.degree, crv.knots, crv.control_points, w, false);
|
|
}
|
|
|
|
/**
|
|
* Save rational curve data to a Wavefront OBJ stream.
|
|
* @param os The output stream
|
|
* @param RationalCurve object to save
|
|
*/
|
|
template <typename T> void curveSaveOBJ(std::ostream &os, const RationalCurve<T> &crv)
|
|
{
|
|
internal::curveSaveOBJ(os, crv.degree, crv.knots, crv.control_points, crv.weights, true);
|
|
}
|
|
|
|
/**
|
|
* Save non-rational surface data to a Wavefront OBJ stream.
|
|
* @param os The output stream
|
|
* @param srf Surface object to save
|
|
*/
|
|
template <typename T> void surfaceSaveOBJ(std::ostream &os, const Surface<T> &srf)
|
|
{
|
|
array2<T> w(srf.control_points.rows(), srf.control_points.cols(), T(1));
|
|
internal::surfaceSaveOBJ(os, srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v,
|
|
srf.control_points, w, false);
|
|
}
|
|
|
|
/**
|
|
* Save rational surface data to a Wavefront OBJ stream.
|
|
* @param os The output stream
|
|
* @param srf RationalSurface object to save
|
|
*/
|
|
template <typename T>
|
|
void surfaceSaveOBJ(std::ostream &os, const RationalSurface<T> &srf)
|
|
{
|
|
internal::surfaceSaveOBJ(os, srf.degree_u, srf.degree_v, srf.knots_u, srf.knots_v,
|
|
srf.control_points, srf.weights, true);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Read curve data from a Wavefront OBJ file and populate a RationalCurve object
|
|
* @param filename Name of the file
|
|
* @return RationalCurve object
|
|
*/
|
|
template <typename T> RationalCurve<T> curveReadOBJ(const std::string &filename)
|
|
{
|
|
std::ifstream file(filename);
|
|
if (!file)
|
|
{
|
|
throw std::runtime_error("File not found: " + filename);
|
|
}
|
|
|
|
return curveReadOBJ<T>(file);
|
|
}
|
|
|
|
/**
|
|
* Read surface data from a Wavefront OBJ file and populate a RationalSurface object
|
|
* @param filename Name of the file
|
|
* @return RationalSurface object
|
|
*/
|
|
template <typename T> RationalSurface<T> surfaceReadOBJ(const std::string &filename)
|
|
{
|
|
std::ifstream file(filename);
|
|
if (!file)
|
|
{
|
|
throw std::runtime_error("File not found: " + filename);
|
|
}
|
|
|
|
return surfaceReadOBJ<T>(file);
|
|
}
|
|
|
|
/**
|
|
* Save curve data from a Wavefront OBJ file and populate a RationalSurface object
|
|
* @param filename Name of the file
|
|
* @param Curve object to save
|
|
*/
|
|
template <typename T> void curveSaveOBJ(const std::string &filename, const Curve<T> &crv)
|
|
{
|
|
std::ofstream fout(filename);
|
|
curveSaveOBJ(fout, crv);
|
|
}
|
|
|
|
/**
|
|
* Save rational curve data to a Wavefront OBJ file.
|
|
* @param filename Name of the file
|
|
* @param RationalCurve object to save
|
|
*/
|
|
template <typename T> void curveSaveOBJ(const std::string &filename, const RationalCurve<T> &crv)
|
|
{
|
|
std::ofstream fout(filename);
|
|
curveSaveOBJ(fout, crv);
|
|
}
|
|
|
|
/**
|
|
* Save non-rational surface data to a Wavefront OBJ file.
|
|
* @param filename Name of the file
|
|
* @param srf Surface object to save
|
|
*/
|
|
template <typename T> void surfaceSaveOBJ(const std::string &filename, const Surface<T> &srf)
|
|
{
|
|
std::ofstream fout(filename);
|
|
surfaceSaveOBJ(fout, srf);
|
|
}
|
|
|
|
/**
|
|
* Save rational surface data to a Wavefront OBJ file.
|
|
* @param filename Name of the file
|
|
* @param srf RationalSurface object to save
|
|
*/
|
|
template <typename T>
|
|
void surfaceSaveOBJ(const std::string &filename, const RationalSurface<T> &srf)
|
|
{
|
|
std::ofstream fout(filename);
|
|
surfaceSaveOBJ(fout, srf);
|
|
}
|
|
|
|
} // namespace tinynurbs
|
|
|
|
#endif // TINYNURBS_OBJ_H
|
|
|