Initial support of converting Alembic curve to .gltf.

This commit is contained in:
Syoyo Fujita 2016-10-15 20:12:23 +09:00
parent c39d93879a
commit 1db9a358a7
2 changed files with 472 additions and 14 deletions

View File

@ -22,10 +22,40 @@ Edit include and lib paths for Alembic OpenEXR(ilmbase) in `Makefile`, then simp
I am testing with Alembic data using Blender's Alembic exporter(feature available from Blender 2.78)
## Extensions
### Curves
Curves are reprenseted as point primitive(mode = 0) in glTF level for the backward compatibility of existing glTF specification.
The number of vertices per curve information is aded to `NVERRTS` attribute.
"ext_mode" extra property is added and set it to "curves" string to specify the object as curves primitive.
Here is an example JSON description of curves primitive.
"meshes": {
"curves_1": {
"primitives": [
{
"attributes": {
"POSITION": "accessor_vertices",
"NVERTS": "accessor_nverts",
},
"material": "material_1",
"mode": 0,
"extra" {
"ext_mode" : "curves"
}
}
]
}
},
## TODO
* [ ] Support normals and texcoords
* [ ] Support mutiple meshes
* [ ] Support animation(time samples)
* [ ] Support scene graph(node hierarchy)
* [ ] Support Point, Curve and SubD?
* [ ] Support Point and SubD?

View File

@ -40,13 +40,38 @@
#include "../../tiny_gltf_loader.h" // To import some TINYGLTF_*** macros.
// @todo { texcoords, normals }
typedef struct
{
std::vector<float> vertices;
// Either `normals` or `facevarying_normals` is filled.
std::vector<float> normals;
std::vector<float> facevarying_normals;
// Either `texcoords` or `facevarying_texcoords` is filled.
std::vector<float> texcoords;
std::vector<float> facevarying_texcoords;
std::vector<unsigned int> faces;
} Mesh;
// Curves are represented as an array of curve.
// i'th curve has nverts[i] points.
// TODO(syoyo) knots, order to support NURBS curve.
typedef struct
{
std::vector<float> points;
std::vector<int> nverts; // # of vertices per strand(curve).
} Curves;
// Points represent particle data.
// TODO(syoyo)
typedef struct
{
std::vector<float> points;
std::vector<float> radiuss;
} Points;
// ----------------------------------------------------------------
// writer module
// @todo { move writer code to tiny_gltf_writer.h }
@ -264,13 +289,114 @@ BuildFaceSet(
}
static void readPolyNormals(
std::vector<float> *normals,
std::vector<float> *facevarying_normals,
Alembic::AbcGeom::IN3fGeomParam normals_param)
{
normals->clear();
facevarying_normals->clear();
if (!normals_param) {
return;
}
if ((normals_param.getScope() != Alembic::AbcGeom::kVertexScope) &&
(normals_param.getScope() != Alembic::AbcGeom::kVaryingScope) &&
(normals_param.getScope() != Alembic::AbcGeom::kFacevaryingScope))
{
std::cout << "Normal vector has an unsupported scope" << std::endl;
return;
}
if (normals_param.getScope() == Alembic::AbcGeom::kVertexScope) {
std::cout << "Normal: VertexScope" << std::endl;
} else if (normals_param.getScope() == Alembic::AbcGeom::kVaryingScope) {
std::cout << "Normal: VaryingScope" << std::endl;
} else if (normals_param.getScope() == Alembic::AbcGeom::kFacevaryingScope) {
std::cout << "Normal: FacevaryingScope" << std::endl;
}
// @todo { lerp normal among time sample.}
Alembic::AbcGeom::IN3fGeomParam::Sample samp;
Alembic::AbcGeom::ISampleSelector samplesel(
0.0, Alembic::AbcGeom::ISampleSelector::kNearIndex);
normals_param.getExpanded(samp, samplesel);
Alembic::Abc::N3fArraySamplePtr P = samp.getVals();
size_t sample_size = P->size();
if (normals_param.getScope() == Alembic::AbcGeom::kFacevaryingScope) {
for (size_t i = 0; i < sample_size; i++) {
facevarying_normals->push_back((*P)[i].x);
facevarying_normals->push_back((*P)[i].y);
facevarying_normals->push_back((*P)[i].z);
}
} else {
for (size_t i = 0; i < sample_size; i++) {
normals->push_back((*P)[i].x);
normals->push_back((*P)[i].y);
normals->push_back((*P)[i].z);
}
}
}
// @todo { Support multiple UVset. }
static void readPolyUVs(
std::vector<float> *uvs,
std::vector<float> *facevarying_uvs,
Alembic::AbcGeom::IV2fGeomParam uvs_param)
{
uvs->clear();
facevarying_uvs->clear();
if (!uvs_param) {
return;
}
if (uvs_param.getNumSamples() > 0) {
std::string uv_set_name = Alembic::Abc::GetSourceName(uvs_param.getMetaData());
std::cout << "UVset : " << uv_set_name << std::endl;
}
if (uvs_param.isConstant()) {
std::cout << "UV is constant" << std::endl;
}
if (uvs_param.getScope() == Alembic::AbcGeom::kVertexScope) {
std::cout << "UV: VertexScope" << std::endl;
} else if (uvs_param.getScope() == Alembic::AbcGeom::kVaryingScope) {
std::cout << "UV: VaryingScope" << std::endl;
} else if (uvs_param.getScope() == Alembic::AbcGeom::kFacevaryingScope) {
std::cout << "UV: FacevaryingScope" << std::endl;
}
// @todo { lerp normal among time sample.}
Alembic::AbcGeom::IV2fGeomParam::Sample samp;
Alembic::AbcGeom::ISampleSelector samplesel(
0.0, Alembic::AbcGeom::ISampleSelector::kNearIndex);
uvs_param.getIndexed(samp, samplesel);
Alembic::Abc::V2fArraySamplePtr P = samp.getVals();
size_t sample_size = P->size();
if (uvs_param.getScope() == Alembic::AbcGeom::kFacevaryingScope) {
for (size_t i = 0; i < sample_size; i++) {
facevarying_uvs->push_back((*P)[i].x);
facevarying_uvs->push_back((*P)[i].y);
}
} else {
for (size_t i = 0; i < sample_size; i++) {
uvs->push_back((*P)[i].x);
uvs->push_back((*P)[i].y);
}
}
}
// Traverse Alembic object tree and extract mesh object.
// Currently we only extract first found polygon mesh.
static void VisitObjectAndExtractMesh(Mesh* mesh, std::stringstream& ss, bool& foundMesh, const Alembic::AbcGeom::IObject& obj, const std::string& indent) {
if (foundMesh) {
return;
}
// Currently we only extract first found geometry object.
static void VisitObjectAndExtractObject(Mesh* mesh, Curves* curves, std::stringstream& ss, bool& foundMesh, bool &foundCurves, const Alembic::AbcGeom::IObject& obj, const std::string& indent) {
std::string path = obj.getFullName();
@ -283,8 +409,12 @@ static void VisitObjectAndExtractMesh(Mesh* mesh, std::stringstream& ss, bool& f
for (size_t i = 0; i < obj.getNumChildren(); i++) {
const Alembic::AbcGeom::ObjectHeader& header = obj.getChildHeader(i);
ss << " Child: header = " << header.getName() << std::endl;
if (Alembic::AbcGeom::IPolyMesh::matches(header)) {
Alembic::AbcGeom::ICompoundProperty cprops = obj.getChild(i).getProperties();
VisitProperties(ss, props, indent);
if ((!foundMesh) && (Alembic::AbcGeom::IPolyMesh::matches(header))) {
// Polygon
Alembic::AbcGeom::IPolyMesh pmesh(obj, header.getName());
@ -303,10 +433,31 @@ static void VisitObjectAndExtractMesh(Mesh* mesh, std::stringstream& ss, bool& f
<< std::endl;
// normals
Alembic::AbcGeom::IN3fGeomParam normals_param = ps.getNormalsParam();
std::vector<float> normals;
std::vector<float> facevarying_normals;
readPolyNormals(&normals, &facevarying_normals, normals_param);
std::cout << " # of normals = " << (normals.size() / 3) << std::endl;
std::cout << " # of facevarying normals = " << (facevarying_normals.size() / 3) << std::endl;
mesh->normals = normals;
// UV
Alembic::AbcGeom::IV2fGeomParam uvs_param = ps.getUVsParam();
std::vector<float> uvs;
std::vector<float> facevarying_uvs;
readPolyUVs(&uvs, &facevarying_uvs, uvs_param);
std::cout << " # of uvs = " << (uvs.size() / 2) << std::endl;
std::cout << " # of facevarying_uvs = " << (facevarying_uvs.size() / 2) << std::endl;
mesh->texcoords = uvs;
mesh->facevarying_texcoords = facevarying_uvs;
std::vector<unsigned int> faces; // temp
bool ret = BuildFaceSet(faces, P, psample.getFaceIndices(), psample.getFaceCounts());
assert(ret);
(void)(ret);
if (!ret) {
std::cout << " No faces in polymesh" << std::endl;
}
mesh->vertices.resize(3 * P->size());
memcpy(mesh->vertices.data(), P->get(), sizeof(float) * 3 * P->size());
@ -318,13 +469,75 @@ static void VisitObjectAndExtractMesh(Mesh* mesh, std::stringstream& ss, bool& f
} else {
std::cout << "Warning: # of samples = 0" << std::endl;
}
} else if ((!foundCurves) && Alembic::AbcGeom::ICurves::matches(header)) {
// Curves
Alembic::AbcGeom::ICurves curve(obj, header.getName());
Alembic::AbcGeom::ISampleSelector samplesel(
0.0, Alembic::AbcGeom::ISampleSelector::kNearIndex);
Alembic::AbcGeom::ICurvesSchema::Sample psample;
Alembic::AbcGeom::ICurvesSchema& ps = curve.getSchema();
std::cout << " # of samples = " << ps.getNumSamples() << std::endl;
if (ps.getNumSamples() > 0) {
ps.get(psample, samplesel);
const size_t num_curves = psample.getNumCurves();
std::cout << " # of curves = " << num_curves << std::endl;
Alembic::Abc::P3fArraySamplePtr P = psample.getPositions();
Alembic::Abc::FloatArraySamplePtr knots = psample.getKnots();
Alembic::Abc::UcharArraySamplePtr orders = psample.getOrders();
Alembic::Abc::Int32ArraySamplePtr num_vertices = psample.getCurvesNumVertices();
if (knots) std::cout << " # of knots= " << knots->size() << std::endl;
if (orders) std::cout << " # of orders= " << orders->size() << std::endl;
std::cout << " # of nvs= " << num_vertices->size() << std::endl;
curves->points.resize(3 * P->size());
memcpy(curves->points.data(), P->get(), sizeof(float) * 3 * P->size());
//for (size_t k = 0; k < P->size(); k++) {
// std::cout << "P[" << k << "] " << (*P)[k].x << ", " << (*P)[k].y << ", " << (*P)[k].z << std::endl;
//}
//if (knots) {
//for (size_t k = 0; k < knots->size(); k++) {
// std::cout << "knots[" << k << "] " << (*knots)[k] << std::endl;
//}
//}
//if (orders) {
//for (size_t k = 0; k < orders->size(); k++) {
// std::cout << "orders[" << k << "] " << (*orders)[k] << std::endl;
//}
//}
//for (size_t k = 0; k < num_vertices->size(); k++) {
// std::cout << "nv[" << k << "] " << (*num_vertices)[k] << std::endl;
//}
if (num_vertices) {
curves->nverts.resize(num_vertices->size());
memcpy(curves->nverts.data(), num_vertices->get(), sizeof(int) * num_vertices->size());
}
foundCurves = true;
return;
} else {
std::cout << "Warning: # of samples = 0" << std::endl;
}
}
VisitObjectAndExtractMesh(mesh, ss, foundMesh, Alembic::AbcGeom::IObject(obj, obj.getChildHeader(i).getName()), indent);
VisitObjectAndExtractObject(mesh, curves, ss, foundMesh, foundCurves, Alembic::AbcGeom::IObject(obj, obj.getChildHeader(i).getName()), indent);
}
}
static bool SaveGLTF(const std::string& output_filename,
static bool SaveMeshToGLTF(const std::string& output_filename,
const Mesh& mesh) {
picojson::object root;
@ -398,6 +611,7 @@ static bool SaveGLTF(const std::string& output_filename,
picojson::object attributes;
attributes["POSITION"] = picojson::value(std::string("accessor_vertices"));
picojson::object primitive;
primitive["attributes"] = picojson::value(attributes);
@ -506,6 +720,209 @@ static bool SaveGLTF(const std::string& output_filename,
return true;
}
static bool SaveCurvesToGLTF(const std::string& output_filename,
const Curves& curves) {
picojson::object root;
{
picojson::object asset;
asset["generator"] = picojson::value("abc2gltf");
asset["premultipliedAlpha"] = picojson::value(true);
asset["version"] = picojson::value(static_cast<double>(1));
picojson::object profile;
profile["api"] = picojson::value("WebGL");
profile["version"] = picojson::value("1.0.2");
asset["profile"] = picojson::value(profile);
root["assets"] = picojson::value(asset);
}
{
picojson::object buffers;
{
{
std::string b64data = base64_encode(reinterpret_cast<unsigned char const*>(curves.points.data()), curves.points.size() * sizeof(float));
picojson::object buf;
buf["type"] = picojson::value("arraybuffer");
buf["uri"] = picojson::value(
std::string("data:application/octet-stream;base64,") + b64data);
buf["byteLength"] =
picojson::value(static_cast<double>(curves.points.size() * sizeof(float)));
buffers["points"] = picojson::value(buf);
}
// Out extension
{
std::string b64data = base64_encode(reinterpret_cast<unsigned char const*>(curves.nverts.data()), curves.nverts.size() * sizeof(int));
picojson::object buf;
buf["type"] = picojson::value("arraybuffer");
buf["uri"] = picojson::value(
std::string("data:application/octet-stream;base64,") + b64data);
buf["byteLength"] =
picojson::value(static_cast<double>(curves.nverts.size() * sizeof(int)));
buffers["nverts"] = picojson::value(buf);
}
}
root["buffers"] = picojson::value(buffers);
}
{
picojson::object buffer_views;
{
{
picojson::object buffer_view_points;
buffer_view_points["buffer"] = picojson::value(std::string("points"));
buffer_view_points["byteLength"] = picojson::value(static_cast<double>(curves.points.size() * sizeof(float)));
buffer_view_points["byteOffset"] = picojson::value(static_cast<double>(0));
buffer_view_points["target"] = picojson::value(static_cast<int64_t>(TINYGLTF_TARGET_ARRAY_BUFFER));
buffer_views["bufferView_points"] = picojson::value(buffer_view_points);
}
{
picojson::object buffer_view_nverts;
buffer_view_nverts["buffer"] = picojson::value(std::string("nverts"));
buffer_view_nverts["byteLength"] = picojson::value(static_cast<double>(curves.nverts.size() * sizeof(int)));
buffer_view_nverts["byteOffset"] = picojson::value(static_cast<double>(0));
buffer_view_nverts["target"] = picojson::value(static_cast<int64_t>(TINYGLTF_TARGET_ARRAY_BUFFER));
buffer_views["bufferView_nverts"] = picojson::value(buffer_view_nverts);
}
}
root["bufferViews"] = picojson::value(buffer_views);
}
{
picojson::object attributes;
attributes["POSITION"] = picojson::value(std::string("accessor_points"));
attributes["NVERTS"] = picojson::value(std::string("accessor_nverts"));
// Extra information for curves primtive.
picojson::object extra;
extra["ext_mode"] = picojson::value("curves");
picojson::object primitive;
primitive["attributes"] = picojson::value(attributes);
//primitive["indices"] = picojson::value("accessor_indices");
primitive["material"] = picojson::value("material_1");
primitive["mode"] = picojson::value(static_cast<int64_t>(TINYGLTF_MODE_POINTS)); // Use GL_POINTS for backward compatibility
primitive["extra"] = picojson::value(extra);
picojson::array primitive_array;
primitive_array.push_back(picojson::value(primitive));
picojson::object m;
m["primitives"] = picojson::value(primitive_array);
picojson::object meshes;
meshes["mesh_1"] = picojson::value(m);
root["meshes"] = picojson::value(meshes);
}
{
picojson::object accessors;
{
picojson::object accessor_points;
accessor_points["bufferView"] = picojson::value(std::string("bufferView_points"));
accessor_points["byteOffset"] = picojson::value(static_cast<int64_t>(0));
accessor_points["byteStride"] = picojson::value(static_cast<double>(3 * sizeof(float)));
accessor_points["componentType"] = picojson::value(static_cast<int64_t>(TINYGLTF_COMPONENT_TYPE_FLOAT));
accessor_points["count"] = picojson::value(static_cast<int64_t>(curves.points.size()));
accessor_points["type"] = picojson::value(std::string("VEC3"));
accessors["accessor_points"] = picojson::value(accessor_points);
}
{
picojson::object accessor_nverts;
accessor_nverts["bufferView"] = picojson::value(std::string("bufferView_nverts"));
accessor_nverts["byteOffset"] = picojson::value(static_cast<int64_t>(0));
accessor_nverts["byteStride"] = picojson::value(static_cast<double>(sizeof(int)));
accessor_nverts["componentType"] = picojson::value(static_cast<int64_t>(TINYGLTF_COMPONENT_TYPE_INT));
accessor_nverts["count"] = picojson::value(static_cast<int64_t>(curves.nverts.size()));
accessor_nverts["type"] = picojson::value(std::string("SCALAR"));
accessors["accessor_nverts"] = picojson::value(accessor_nverts);
}
picojson::object accessor_indices;
root["accessors"] = picojson::value(accessors);
}
{
// Use Default Material(Do not supply `material.technique`)
picojson::object default_material;
picojson::object materials;
materials["material_1"] = picojson::value(default_material);
root["materials"] = picojson::value(materials);
}
{
picojson::object nodes;
picojson::object node;
picojson::array meshes;
meshes.push_back(picojson::value(std::string("mesh_1")));
node["meshes"] = picojson::value(meshes);
nodes["node_1"] = picojson::value(node);
root["nodes"] = picojson::value(nodes);
}
{
picojson::object defaultScene;
picojson::array nodes;
nodes.push_back(picojson::value(std::string("node_1")));
defaultScene["nodes"] = picojson::value(nodes);
root["scene"] = picojson::value("defaultScene");
picojson::object scenes;
scenes["defaultScene"] = picojson::value(defaultScene);
root["scenes"] = picojson::value(scenes);
}
// @todo {}
picojson::object shaders;
picojson::object programs;
picojson::object techniques;
picojson::object materials;
picojson::object skins;
root["shaders"] = picojson::value(shaders);
root["programs"] = picojson::value(programs);
root["techniques"] = picojson::value(techniques);
root["materials"] = picojson::value(materials);
root["skins"] = picojson::value(skins);
std::ofstream ifs(output_filename.c_str());
if (ifs.bad()) {
std::cerr << "Failed to open " << output_filename << std::endl;
return false;
}
picojson::value v = picojson::value(root);
std::string s = v.serialize(/* pretty */true);
ifs.write(s.data(), static_cast<ssize_t>(s.size()));
ifs.close();
return true;
}
int main(int argc, char** argv) {
std::string abc_filename;
@ -528,13 +945,15 @@ int main(int argc, char** argv) {
std::stringstream ss;
Mesh mesh;
Curves curves;
bool foundMesh = false;
VisitObjectAndExtractMesh(&mesh, ss, foundMesh, root, /* indent */" ");
bool foundCurves = false;
VisitObjectAndExtractObject(&mesh, &curves, ss, foundMesh, foundCurves, root, /* indent */" ");
std::cout << ss.str() << std::endl;
if (foundMesh) {
bool ret = SaveGLTF(gltf_filename, mesh);
bool ret = SaveMeshToGLTF(gltf_filename, mesh);
if (ret) {
std::cout << "Wrote " << gltf_filename << std::endl;
} else {
@ -544,5 +963,14 @@ int main(int argc, char** argv) {
std::cout << "No polygon mesh found in Alembic file" << std::endl;
}
if (foundCurves) {
bool ret = SaveCurvesToGLTF(gltf_filename, curves);
if (ret) {
std::cout << "Wrote " << gltf_filename << std::endl;
} else {
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}