#include #include #include #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++11-long-long" #pragma clang diagnostic ignored "-Wold-style-cast" #pragma clang diagnostic ignored "-Wpadded" #pragma clang diagnostic ignored "-Wsign-conversion" #pragma clang diagnostic ignored "-Wc++11-extensions" #pragma clang diagnostic ignored "-Wconversion" #pragma clang diagnostic ignored "-Wreserved-id-macro" #pragma clang diagnostic ignored "-Wfloat-equal" #pragma clang diagnostic ignored "-Wdeprecated" #pragma clang diagnostic ignored "-Wweak-vtables" #pragma clang diagnostic ignored "-Wextra-semi" #pragma clang diagnostic ignored "-Wswitch-enum" #pragma clang diagnostic ignored "-Wglobal-constructors" #pragma clang diagnostic ignored "-Wunused-parameter" #pragma clang diagnostic ignored "-Wexit-time-destructors" #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" #pragma clang diagnostic ignored "-Wdouble-promotion" #pragma clang diagnostic ignored "-Wcovered-switch-default" #endif #include #include #include #define PICOJSON_USE_INT64 #include "../../picojson.h" #ifdef __clang__ #pragma clang diagnostic pop #endif #include "../../tiny_gltf_loader.h" // To import some TINYGLTF_*** macros. template class real3 { public: real3() {} real3(T xx, T yy, T zz) { v[0] = xx; v[1] = yy; v[2] = zz; } explicit real3(const T *p) { v[0] = p[0]; v[1] = p[1]; v[2] = p[2]; } inline T x() const { return v[0]; } inline T y() const { return v[1]; } inline T z() const { return v[2]; } real3 operator*(T f) const { return real3(x() * f, y() * f, z() * f); } real3 operator-(const real3 &f2) const { return real3(x() - f2.x(), y() - f2.y(), z() - f2.z()); } real3 operator*(const real3 &f2) const { return real3(x() * f2.x(), y() * f2.y(), z() * f2.z()); } real3 operator+(const real3 &f2) const { return real3(x() + f2.x(), y() + f2.y(), z() + f2.z()); } real3 &operator+=(const real3 &f2) { v[0] += f2.x(); v[1] += f2.y(); v[2] += f2.z(); return (*this); } real3 operator/(const real3 &f2) const { return real3(x() / f2.x(), y() / f2.y(), z() / f2.z()); } T operator[](int i) const { return v[i]; } T &operator[](int i) { return v[i]; } T v[3]; // T pad; // for alignment(when T = float) }; template inline real3 operator*(T f, const real3 &v) { return real3(v.x() * f, v.y() * f, v.z() * f); } template inline real3 vneg(const real3 &rhs) { return real3(-rhs.x(), -rhs.y(), -rhs.z()); } template inline T vlength(const real3 &rhs) { return std::sqrt(rhs.x() * rhs.x() + rhs.y() * rhs.y() + rhs.z() * rhs.z()); } template inline real3 vnormalize(const real3 &rhs) { real3 v = rhs; T len = vlength(rhs); if (std::fabs(len) > 1.0e-6f) { float inv_len = 1.0f / len; v.v[0] *= inv_len; v.v[1] *= inv_len; v.v[2] *= inv_len; } return v; } template inline real3 vcross(real3 a, real3 b) { real3 c; c[0] = a[1] * b[2] - a[2] * b[1]; c[1] = a[2] * b[0] - a[0] * b[2]; c[2] = a[0] * b[1] - a[1] * b[0]; return c; } template inline T vdot(real3 a, real3 b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } typedef real3 float3; class Node { public: Node() {} virtual ~Node(); Node(const Node &rhs) { name = rhs.name; children = rhs.children; } Node &operator=(const Node &rhs) { name = rhs.name; children = rhs.children; return (*this); } std::string name; std::vector children; }; static void CalcNormal(float3 &N, float3 v0, float3 v1, float3 v2) { float3 v10 = v1 - v0; float3 v20 = v2 - v0; N = vcross(v20, v10); N = vnormalize(N); } static void ComputeGeometricNormals(std::vector *normals_out, const unsigned char *data, const size_t vertex_stride, const unsigned int *indices, const size_t num_indices) { for (size_t i = 0; i < num_indices / 3; i++) { const unsigned int i0 = indices[3 * i + 0]; const unsigned int i1 = indices[3 * i + 1]; const unsigned int i2 = indices[3 * i + 2]; const float *p0 = reinterpret_cast(&data[vertex_stride * i0]); const float *p1 = reinterpret_cast(&data[vertex_stride * i1]); const float *p2 = reinterpret_cast(&data[vertex_stride * i2]); float3 v0(p0[0], p0[1], p0[2]); float3 v1(p1[0], p1[1], p1[2]); float3 v2(p2[0], p2[1], p2[2]); float3 n; CalcNormal(n, v0, v1, v2); // printf("n = %f, %f, %f\n", n[0], n[1], n[2]); (*normals_out)[3 * i0 + 0] += n[0]; (*normals_out)[3 * i0 + 1] += n[1]; (*normals_out)[3 * i0 + 2] += n[2]; (*normals_out)[3 * i1 + 0] += n[0]; (*normals_out)[3 * i1 + 1] += n[1]; (*normals_out)[3 * i1 + 2] += n[2]; (*normals_out)[3 * i2 + 0] += n[0]; (*normals_out)[3 * i2 + 1] += n[1]; (*normals_out)[3 * i2 + 2] += n[2]; } // normalize for (size_t i = 0; i < normals_out->size() / 3; i++) { float3 n((*normals_out)[3 * i + 0], (*normals_out)[3 * i + 1], (*normals_out)[3 * i + 2]); n = vnormalize(n); (*normals_out)[3 * i + 0] = n[0]; (*normals_out)[3 * i + 1] = n[1]; (*normals_out)[3 * i + 2] = n[2]; } } Node::~Node() {} class Xform : public Node { public: Xform() : Node() { xform[0] = 1.0; xform[1] = 0.0; xform[2] = 0.0; xform[3] = 0.0; xform[4] = 0.0; xform[5] = 1.0; xform[6] = 0.0; xform[7] = 0.0; xform[8] = 0.0; xform[9] = 0.0; xform[10] = 1.0; xform[11] = 0.0; xform[12] = 0.0; xform[13] = 0.0; xform[14] = 0.0; xform[15] = 1.0; } virtual ~Xform(); Xform &operator=(const Xform &rhs) { if (this != &rhs) { Node::operator=(rhs); for (size_t i = 0; i < 16; i++) { xform[i] = rhs.xform[i]; } } return (*this); } double xform[16]; }; Xform::~Xform() {} class Mesh : public Node { public: Mesh() : Node() {} virtual ~Mesh(); Mesh &operator=(const Mesh &rhs) { if (this != &rhs) { Node::operator=(rhs); vertices = rhs.vertices; normals = rhs.normals; facevarying_normals = rhs.facevarying_normals; texcoords = rhs.texcoords; facevarying_texcoords = rhs.facevarying_texcoords; faces = rhs.faces; } return (*this); } std::vector vertices; // Either `normals` or `facevarying_normals` is filled. std::vector normals; std::vector facevarying_normals; // Either `texcoords` or `facevarying_texcoords` is filled. std::vector texcoords; std::vector facevarying_texcoords; std::vector faces; }; Mesh::~Mesh() {} // Curves are represented as an array of curve. // i'th curve has nverts[i] points. // TODO(syoyo) knots, order to support NURBS curve. class Curves : public Node { public: Curves() : Node() {} virtual ~Curves(); Curves &operator=(const Curves &rhs) { if (this != &rhs) { Node::operator=(rhs); points = rhs.points; nverts = rhs.nverts; } return (*this); } std::vector points; std::vector nverts; // # of vertices per strand(curve). }; Curves::~Curves() {} // Points represent particle data. // TODO(syoyo) class Points : public Node { public: Points() : Node() {} ~Points(); Points &operator=(const Points &rhs) { if (this != &rhs) { Node::operator=(rhs); points = rhs.points; radiuss = rhs.radiuss; } return (*this); } std::vector points; std::vector radiuss; }; Points::~Points() {} // TODO(Nurbs) class Scene { public: Scene() : root_node(NULL) {} ~Scene() { Destroy(); } void Destroy() { delete root_node; root_node = NULL; { std::map::const_iterator it( xform_map.begin()); for (; it != xform_map.end(); it++) { delete it->second; } } { std::map::const_iterator it(mesh_map.begin()); for (; it != mesh_map.end(); it++) { delete it->second; } } { std::map::const_iterator it( curves_map.begin()); for (; it != curves_map.end(); it++) { delete it->second; } } } std::map xform_map; std::map mesh_map; std::map curves_map; // std::map points_map; std::map id_map; const Node *root_node; // std::map node_map; }; // ---------------------------------------------------------------- // writer module // @todo { move writer code to tiny_gltf_writer.h } // http://www.adp-gmbh.ch/cpp/common/base64.html static const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; static std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len) { std::string ret; int i = 0; int j = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; while (in_len--) { char_array_3[i++] = *(bytes_to_encode++); if (i == 3) { char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = static_cast( ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4)); char_array_4[2] = static_cast( ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6)); char_array_4[3] = char_array_3[2] & 0x3f; for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; i = 0; } } if (i) { for (j = i; j < 3; j++) char_array_3[j] = '\0'; char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = static_cast( ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4)); char_array_4[2] = static_cast( ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6)); char_array_4[3] = char_array_3[2] & 0x3f; for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; while ((i++ < 3)) ret += '='; } return ret; } // static bool EncodeFloatArray(picojson::array* arr, const std::vector& // values) { // for (size_t i = 0; i < values.size(); i++) { // arr->push_back(picojson::value(values[i])); // } // // return true; //} // // --------------------------------------------------------------------------- static const char *g_sep = ":"; static void VisitProperties(std::stringstream &ss, Alembic::AbcGeom::ICompoundProperty parent, const std::string &indent); template static void VisitSimpleProperty(std::stringstream &ss, PROP i_prop, const std::string &indent) { std::string ptype = "ScalarProperty "; if (i_prop.isArray()) { ptype = "ArrayProperty "; } std::string mdstring = "interpretation="; mdstring += i_prop.getMetaData().get("interpretation"); std::stringstream dtype; dtype << "datatype="; dtype << i_prop.getDataType(); mdstring += g_sep; mdstring += dtype.str(); ss << indent << " " << ptype << "name=" << i_prop.getName() << g_sep << mdstring << g_sep << "numsamps=" << i_prop.getNumSamples() << std::endl; } static void VisitCompoundProperty(std::stringstream &ss, Alembic::Abc::ICompoundProperty i_prop, const std::string &indent) { std::string io_indent = indent + " "; std::string interp = "schema="; interp += i_prop.getMetaData().get("schema"); ss << io_indent << "CompoundProperty " << "name=" << i_prop.getName() << g_sep << interp << std::endl; VisitProperties(ss, i_prop, io_indent); } void VisitProperties(std::stringstream &ss, Alembic::AbcGeom::ICompoundProperty parent, const std::string &indent) { for (size_t i = 0; i < parent.getNumProperties(); i++) { const Alembic::Abc::PropertyHeader &header = parent.getPropertyHeader(i); if (header.isCompound()) { VisitCompoundProperty( ss, Alembic::Abc::ICompoundProperty(parent, header.getName()), indent); } else if (header.isScalar()) { VisitSimpleProperty( ss, Alembic::Abc::IScalarProperty(parent, header.getName()), indent); } else { assert(header.isArray()); VisitSimpleProperty( ss, Alembic::Abc::IArrayProperty(parent, header.getName()), indent); } } } static bool BuildFaceSet(std::vector &faces, Alembic::Abc::P3fArraySamplePtr iP, Alembic::Abc::Int32ArraySamplePtr iIndices, Alembic::Abc::Int32ArraySamplePtr iCounts) { faces.clear(); // Get the number of each thing. size_t numFaces = iCounts->size(); size_t numIndices = iIndices->size(); size_t numPoints = iP->size(); if (numFaces < 1 || numIndices < 1 || numPoints < 1) { // Invalid. std::cerr << "Mesh update quitting because bad arrays" << ", numFaces = " << numFaces << ", numIndices = " << numIndices << ", numPoints = " << numPoints << std::endl; return false; } // Make triangles. size_t faceIndexBegin = 0; size_t faceIndexEnd = 0; for (size_t face = 0; face < numFaces; ++face) { faceIndexBegin = faceIndexEnd; size_t count = static_cast((*iCounts)[face]); faceIndexEnd = faceIndexBegin + count; // Check this face is valid if (faceIndexEnd > numIndices || faceIndexEnd < faceIndexBegin) { std::cerr << "Mesh update quitting on face: " << face << " because of wonky numbers" << ", faceIndexBegin = " << faceIndexBegin << ", faceIndexEnd = " << faceIndexEnd << ", numIndices = " << numIndices << ", count = " << count << std::endl; // Just get out, make no more triangles. break; } // Checking indices are valid. bool goodFace = true; for (size_t fidx = faceIndexBegin; fidx < faceIndexEnd; ++fidx) { if (static_cast(((*iIndices)[fidx])) >= numPoints) { std::cout << "Mesh update quitting on face: " << face << " because of bad indices" << ", indexIndex = " << fidx << ", vertexIndex = " << (*iIndices)[fidx] << ", numPoints = " << numPoints << std::endl; goodFace = false; break; } } // Make triangles to fill this face. if (goodFace && count > 2) { faces.push_back( static_cast((*iIndices)[faceIndexBegin + 0])); faces.push_back( static_cast((*iIndices)[faceIndexBegin + 1])); faces.push_back( static_cast((*iIndices)[faceIndexBegin + 2])); for (size_t c = 3; c < count; ++c) { faces.push_back( static_cast((*iIndices)[faceIndexBegin + 0])); faces.push_back( static_cast((*iIndices)[faceIndexBegin + c - 1])); faces.push_back( static_cast((*iIndices)[faceIndexBegin + c])); } } } return true; } static void readPolyNormals(std::vector *normals, std::vector *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 *uvs, std::vector *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 data. static void VisitObjectAndExtractNode(Node *node_out, std::stringstream &ss, const Alembic::AbcGeom::IObject &obj, const std::string &indent) { std::string path = obj.getFullName(); node_out->name = path; if (path.compare("/") != 0) { ss << "Object: path = " << path << std::endl; } Alembic::AbcGeom::ICompoundProperty props = obj.getProperties(); VisitProperties(ss, props, indent); ss << "# of children = " << obj.getNumChildren() << std::endl; for (size_t i = 0; i < obj.getNumChildren(); i++) { const Alembic::AbcGeom::ObjectHeader &header = obj.getChildHeader(i); ss << " Child: header = " << header.getName() << std::endl; Alembic::AbcGeom::ICompoundProperty cprops = obj.getChild(i).getProperties(); VisitProperties(ss, props, indent); Node *node = NULL; if (Alembic::AbcGeom::IXform::matches(header)) { ss << " IXform" << std::endl; Alembic::AbcGeom::IXform xform(obj, header.getName()); if (xform.getSchema().isConstant()) { Alembic::AbcGeom::M44d static_matrix = xform.getSchema().getValue().getMatrix(); ss << "IXform static: " << header.getName() << ", " << static_matrix << std::endl; Xform *xform_node = new Xform(); xform_node->name = header.getName(); xform_node->xform[0] = static_matrix[0][0]; xform_node->xform[1] = static_matrix[0][1]; xform_node->xform[2] = static_matrix[0][2]; xform_node->xform[3] = static_matrix[0][3]; xform_node->xform[4] = static_matrix[1][0]; xform_node->xform[5] = static_matrix[1][1]; xform_node->xform[6] = static_matrix[1][2]; xform_node->xform[7] = static_matrix[1][3]; xform_node->xform[8] = static_matrix[2][0]; xform_node->xform[9] = static_matrix[2][1]; xform_node->xform[10] = static_matrix[2][2]; xform_node->xform[11] = static_matrix[2][3]; xform_node->xform[12] = static_matrix[3][0]; xform_node->xform[13] = static_matrix[3][1]; xform_node->xform[14] = static_matrix[3][2]; xform_node->xform[15] = static_matrix[3][3]; node = xform_node; } else { Alembic::AbcGeom::ISampleSelector samplesel( 0.0, Alembic::AbcGeom::ISampleSelector::kNearIndex); Alembic::AbcGeom::IXformSchema &ps = xform.getSchema(); Alembic::AbcGeom::M44d matrix = ps.getValue(samplesel).getMatrix(); ss << matrix << std::endl; } ss << " Child: xform" << std::endl; } else if (Alembic::AbcGeom::IPolyMesh::matches(header)) { ss << " IPolyMesh" << std::endl; // Polygon Alembic::AbcGeom::IPolyMesh pmesh(obj, header.getName()); Alembic::AbcGeom::ISampleSelector samplesel( 0.0, Alembic::AbcGeom::ISampleSelector::kNearIndex); Alembic::AbcGeom::IPolyMeshSchema::Sample psample; Alembic::AbcGeom::IPolyMeshSchema &ps = pmesh.getSchema(); std::cout << " # of samples = " << ps.getNumSamples() << std::endl; if (ps.getNumSamples() > 0) { Mesh *mesh = new Mesh(); ps.get(psample, samplesel); Alembic::Abc::P3fArraySamplePtr P = psample.getPositions(); std::cout << " # of positions = " << P->size() << std::endl; std::cout << " # of face counts = " << psample.getFaceCounts()->size() << std::endl; std::vector faces; // temp bool ret = BuildFaceSet(faces, P, psample.getFaceIndices(), psample.getFaceCounts()); 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()); mesh->faces = faces; // normals Alembic::AbcGeom::IN3fGeomParam normals_param = ps.getNormalsParam(); std::vector normals; std::vector 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; // TODO(syoyo): Do not generate normals when `facevarying_normals' // exists. if (normals.empty() && (!mesh->vertices.empty()) && (!mesh->faces.empty())) { // Compute geometric normal. normals.resize(3 * P->size(), 0.0f); std::cout << "Compute normals" << std::endl; ComputeGeometricNormals( &normals, reinterpret_cast(P->get()), /* stride */ sizeof(float) * 3, &mesh->faces.at(0), mesh->faces.size()); } mesh->normals = normals; // UV Alembic::AbcGeom::IV2fGeomParam uvs_param = ps.getUVsParam(); std::vector uvs; std::vector 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; node = mesh; } else { std::cout << "Warning: # of samples = 0" << std::endl; } } else if (Alembic::AbcGeom::ICurves::matches(header)) { ss << " ICurves" << std::endl; // 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 << "Curve: # 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; if (num_curves > 0) { Curves *curves = new Curves(); 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()); #if 0 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; } #endif if (num_vertices) { curves->nverts.resize(num_vertices->size()); memcpy(curves->nverts.data(), num_vertices->get(), sizeof(int) * num_vertices->size()); } node = curves; } } else { std::cout << "Warning: # of samples = 0" << std::endl; } } else if (Alembic::AbcGeom::IFaceSet::matches(header)) { ss << " IFaceSet" << std::endl; } else { ss << " TODO" << std::endl; } if (node) { // Visit child. VisitObjectAndExtractNode( node, ss, Alembic::AbcGeom::IObject(obj, obj.getChildHeader(i).getName()), indent); } node_out->children.push_back(node); } } static bool ConvertNodeToGLTF(picojson::object *root_out, const Scene &scene, const Node *node) { assert(node); assert(!node->name.empty()); // FIXME(syoyo): Add serialization method for each class. if (dynamic_cast(node)) { const Xform *xform = dynamic_cast(node); std::cout << "root xform" << node->name << std::endl; picojson::object json_node; picojson::array json_xform; picojson::array json_meshes; for (size_t i = 0; i < 16; i++) { json_xform.push_back(picojson::value(xform->xform[i])); } json_node["matrix"] = picojson::value(json_xform); json_node["name"] = picojson::value(xform->name); if (xform->children.size() > 0) { for (size_t i = 0; i < xform->children.size(); i++) { if (dynamic_cast(xform->children[i])) { std::map::const_iterator it = scene.id_map.find(xform->children[i]->name); assert(it != scene.id_map.end()); std::stringstream ss; ss << "_" << it->second; const std::string prefix = ss.str(); json_meshes.push_back(picojson::value(std::string("mesh") + prefix)); } else if (dynamic_cast(xform->children[i])) { std::map::const_iterator it = scene.id_map.find(xform->children[i]->name); assert(it != scene.id_map.end()); std::stringstream ss; ss << "_" << it->second; const std::string prefix = ss.str(); json_meshes.push_back(picojson::value(std::string("mesh") + prefix)); } } if (json_meshes.size() > 0) { json_node["meshes"] = picojson::value(json_meshes); } } picojson::array json_children; if (xform->children.size() > 0) { for (size_t i = 0; i < xform->children.size(); i++) { if (xform->children[i]) { picojson::object json_child_node; bool ret = ConvertNodeToGLTF(root_out, scene, xform->children[i]); if (ret) { assert(!(xform->children[i]->name.empty())); json_children.push_back(picojson::value(xform->children[i]->name)); } } } if (!json_children.empty()) { json_node["children"] = picojson::value(json_children); } } (*root_out)[node->name] = picojson::value(json_node); } else { return false; } return true; } static bool ConvertSceneToGLTF(picojson::object *out, const Scene &scene) { assert(scene.root_node); // Nodes picojson::object nodes; picojson::array node_names; { // picojson::object node; ConvertNodeToGLTF(&nodes, scene, scene.root_node); // nodes[scene.root_node->name] = picojson::value(node); node_names.push_back(picojson::value(scene.root_node->name)); } (*out)["nodes"] = picojson::value(nodes); picojson::object scenes; picojson::object defaultScene; defaultScene["nodes"] = picojson::value(node_names); scenes["defaultScene"] = picojson::value(defaultScene); (*out)["scene"] = picojson::value("defaultScene"); (*out)["scenes"] = picojson::value(scenes); // @todo {} picojson::object shaders; picojson::object programs; picojson::object techniques; picojson::object materials; picojson::object skins; (*out)["shaders"] = picojson::value(shaders); (*out)["programs"] = picojson::value(programs); (*out)["techniques"] = picojson::value(techniques); (*out)["materials"] = picojson::value(materials); (*out)["skins"] = picojson::value(skins); return true; } static bool ConvertMeshToGLTF(picojson::object *buffers_out, picojson::object *buffer_views_out, picojson::object *meshes_out, picojson::object *accessors_out, const Mesh &mesh, const int id) { std::stringstream prefix_ss; prefix_ss << "_" << id; const std::string prefix = prefix_ss.str(); { // Position { std::string vertices_b64data = base64_encode( reinterpret_cast(mesh.vertices.data()), mesh.vertices.size() * sizeof(float)); picojson::object buf; buf["type"] = picojson::value("arraybuffer"); buf["uri"] = picojson::value(std::string("data:application/octet-stream;base64,") + vertices_b64data); buf["byteLength"] = picojson::value( static_cast(mesh.vertices.size() * sizeof(float))); (*buffers_out)["vertices" + prefix] = picojson::value(buf); } // Normal if (!mesh.normals.empty()) { std::string normals_b64data = base64_encode( reinterpret_cast(mesh.normals.data()), mesh.normals.size() * sizeof(float)); picojson::object buf; buf["type"] = picojson::value("arraybuffer"); buf["uri"] = picojson::value(std::string("data:application/octet-stream;base64,") + normals_b64data); buf["byteLength"] = picojson::value( static_cast(mesh.normals.size() * sizeof(float))); (*buffers_out)["normals" + prefix] = picojson::value(buf); } { std::string faces_b64data = base64_encode( reinterpret_cast(mesh.faces.data()), mesh.faces.size() * sizeof(unsigned int)); picojson::object buf; buf["type"] = picojson::value("arraybuffer"); buf["uri"] = picojson::value( std::string("data:application/octet-stream;base64,") + faces_b64data); buf["byteLength"] = picojson::value( static_cast(mesh.faces.size() * sizeof(unsigned int))); (*buffers_out)["indices" + prefix] = picojson::value(buf); } } { { picojson::object buffer_view_vertices; buffer_view_vertices["buffer"] = picojson::value(std::string("vertices") + prefix); buffer_view_vertices["byteLength"] = picojson::value( static_cast(mesh.vertices.size() * sizeof(float))); buffer_view_vertices["byteOffset"] = picojson::value(static_cast(0)); buffer_view_vertices["target"] = picojson::value(static_cast(TINYGLTF_TARGET_ARRAY_BUFFER)); (*buffer_views_out)["bufferView_vertices" + prefix] = picojson::value(buffer_view_vertices); } if (!mesh.normals.empty()) { picojson::object buffer_view_normals; buffer_view_normals["buffer"] = picojson::value(std::string("normals") + prefix); buffer_view_normals["byteLength"] = picojson::value( static_cast(mesh.normals.size() * sizeof(float))); buffer_view_normals["byteOffset"] = picojson::value(static_cast(0)); buffer_view_normals["target"] = picojson::value(static_cast(TINYGLTF_TARGET_ARRAY_BUFFER)); (*buffer_views_out)["bufferView_normals" + prefix] = picojson::value(buffer_view_normals); } { picojson::object buffer_view_indices; buffer_view_indices["buffer"] = picojson::value(std::string("indices") + prefix); buffer_view_indices["byteLength"] = picojson::value( static_cast(mesh.faces.size() * sizeof(unsigned int))); buffer_view_indices["byteOffset"] = picojson::value(static_cast(0)); buffer_view_indices["target"] = picojson::value( static_cast(TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)); (*buffer_views_out)["bufferView_indices" + prefix] = picojson::value(buffer_view_indices); } } { picojson::object attributes; attributes["POSITION"] = picojson::value(std::string("accessor_vertices") + prefix); if (!mesh.normals.empty()) { attributes["NORMAL"] = picojson::value(std::string("accessor_normals") + prefix); } picojson::object primitive; primitive["attributes"] = picojson::value(attributes); primitive["indices"] = picojson::value("accessor_indices" + prefix); primitive["material"] = picojson::value("material_1"); // FIXME(syoyo): Assign material. primitive["mode"] = picojson::value(static_cast(TINYGLTF_MODE_TRIANGLES)); picojson::array primitive_array; primitive_array.push_back(picojson::value(primitive)); picojson::object m; m["name"] = picojson::value(mesh.name); m["primitives"] = picojson::value(primitive_array); picojson::object meshes; (*meshes_out)["mesh" + prefix] = picojson::value(m); } { picojson::object accessors; { picojson::object accessor_vertices; accessor_vertices["bufferView"] = picojson::value(std::string("bufferView_vertices" + prefix)); accessor_vertices["byteOffset"] = picojson::value(static_cast(0)); accessor_vertices["byteStride"] = picojson::value(static_cast(3 * sizeof(float))); accessor_vertices["componentType"] = picojson::value(static_cast(TINYGLTF_COMPONENT_TYPE_FLOAT)); accessor_vertices["count"] = picojson::value(static_cast(mesh.vertices.size())); accessor_vertices["type"] = picojson::value(std::string("VEC3")); (*accessors_out)["accessor_vertices" + prefix] = picojson::value(accessor_vertices); } if (!mesh.normals.empty()) { picojson::object accessor_normals; accessor_normals["bufferView"] = picojson::value(std::string("bufferView_normals" + prefix)); accessor_normals["byteOffset"] = picojson::value(static_cast(0)); accessor_normals["byteStride"] = picojson::value(static_cast(3 * sizeof(float))); accessor_normals["componentType"] = picojson::value(static_cast(TINYGLTF_COMPONENT_TYPE_FLOAT)); accessor_normals["count"] = picojson::value(static_cast(mesh.vertices.size())); accessor_normals["type"] = picojson::value(std::string("VEC3")); (*accessors_out)["accessor_normals" + prefix] = picojson::value(accessor_normals); } { picojson::object accessor_indices; accessor_indices["bufferView"] = picojson::value(std::string("bufferView_indices" + prefix)); accessor_indices["byteOffset"] = picojson::value(static_cast(0)); accessor_indices["componentType"] = picojson::value( static_cast(TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)); accessor_indices["count"] = picojson::value(static_cast(mesh.faces.size())); accessor_indices["type"] = picojson::value(std::string("SCALAR")); (*accessors_out)["accessor_indices" + prefix] = picojson::value(accessor_indices); } } return true; } static bool ConvertCurvesToGLTF(picojson::object *buffers_out, picojson::object *buffer_views_out, picojson::object *meshes_out, picojson::object *accessors_out, const Curves &curves, const int id) { std::stringstream prefix_ss; prefix_ss << "_" << id; const std::string prefix = prefix_ss.str(); {{{std::string b64data = base64_encode( reinterpret_cast(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(curves.points.size() * sizeof(float))); (*buffers_out)["points" + prefix] = picojson::value(buf); } // Out extension { std::string b64data = base64_encode( reinterpret_cast(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(curves.nverts.size() * sizeof(int))); (*buffers_out)["nverts" + prefix] = picojson::value(buf); } } } {{{picojson::object buffer_view_points; buffer_view_points["buffer"] = picojson::value(std::string("points") + prefix); buffer_view_points["byteLength"] = picojson::value(static_cast(curves.points.size() * sizeof(float))); buffer_view_points["byteOffset"] = picojson::value(static_cast(0)); buffer_view_points["target"] = picojson::value(static_cast(TINYGLTF_TARGET_ARRAY_BUFFER)); (*buffer_views_out)["bufferView_points" + prefix] = picojson::value(buffer_view_points); } { picojson::object buffer_view_nverts; buffer_view_nverts["buffer"] = picojson::value(std::string("nverts") + prefix); buffer_view_nverts["byteLength"] = picojson::value(static_cast(curves.nverts.size() * sizeof(int))); buffer_view_nverts["byteOffset"] = picojson::value(static_cast(0)); buffer_view_nverts["target"] = picojson::value(static_cast(TINYGLTF_TARGET_ARRAY_BUFFER)); (*buffer_views_out)["bufferView_nverts" + prefix] = picojson::value(buffer_view_nverts); } } } { picojson::object attributes; attributes["POSITION"] = picojson::value(std::string("accessor_points") + prefix); attributes["NVERTS"] = picojson::value(std::string("accessor_nverts") + prefix); // 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( TINYGLTF_MODE_POINTS)); // Use GL_POINTS for backward compatibility primitive["extras"] = picojson::value(extra); picojson::array primitive_array; primitive_array.push_back(picojson::value(primitive)); picojson::object m; m["name"] = picojson::value(curves.name); m["primitives"] = picojson::value(primitive_array); picojson::object meshes; (*meshes_out)["mesh" + prefix] = picojson::value(m); } { { picojson::object accessor_points; accessor_points["bufferView"] = picojson::value(std::string("bufferView_points") + prefix); accessor_points["byteOffset"] = picojson::value(static_cast(0)); accessor_points["byteStride"] = picojson::value(static_cast(3 * sizeof(float))); accessor_points["componentType"] = picojson::value(static_cast(TINYGLTF_COMPONENT_TYPE_FLOAT)); accessor_points["count"] = picojson::value(static_cast(curves.points.size())); accessor_points["type"] = picojson::value(std::string("VEC3")); (*accessors_out)["accessor_points" + prefix] = picojson::value(accessor_points); } { picojson::object accessor_nverts; accessor_nverts["bufferView"] = picojson::value(std::string("bufferView_nverts") + prefix); accessor_nverts["byteOffset"] = picojson::value(static_cast(0)); accessor_nverts["byteStride"] = picojson::value(static_cast(sizeof(int))); accessor_nverts["componentType"] = picojson::value(static_cast(TINYGLTF_COMPONENT_TYPE_INT)); accessor_nverts["count"] = picojson::value(static_cast(curves.nverts.size())); accessor_nverts["type"] = picojson::value(std::string("SCALAR")); (*accessors_out)["accessor_nverts" + prefix] = picojson::value(accessor_nverts); } } return true; } static bool SaveSceneToGLTF(const std::string &output_filename, const Scene &scene) { picojson::object root; ConvertSceneToGLTF(&root, scene); // Write header. { picojson::object asset; asset["generator"] = picojson::value("abc2gltf"); asset["premultipliedAlpha"] = picojson::value(true); asset["version"] = picojson::value(static_cast(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; picojson::object buffer_views; picojson::object meshes; picojson::object accessors; // PolyMesh std::map::const_iterator mesh_it( scene.mesh_map.begin()); for (; mesh_it != scene.mesh_map.end(); mesh_it++) { const Mesh &mesh = *(mesh_it->second); assert(scene.id_map.find(mesh.name) != scene.id_map.end()); const int id = (scene.id_map.find(mesh.name))->second; bool ret = ConvertMeshToGLTF(&buffers, &buffer_views, &meshes, &accessors, mesh, id); assert(ret); (void)ret; } // Curves std::map::const_iterator curves_it( scene.curves_map.begin()); for (; curves_it != scene.curves_map.end(); curves_it++) { const Curves &curves = *(curves_it->second); assert(scene.id_map.find(curves_it->first) != scene.id_map.end()); const int id = (scene.id_map.find(curves.name))->second; std::cout << "Curves: " << curves.name << std::endl; bool ret = ConvertCurvesToGLTF(&buffers, &buffer_views, &meshes, &accessors, curves, id); assert(ret); (void)ret; } root["buffers"] = picojson::value(buffers); root["bufferViews"] = picojson::value(buffer_views); root["meshes"] = picojson::value(meshes); 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); } 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(s.size())); ifs.close(); return true; } // Flatten node tree. // Also assign ID for each graphics primitive. static void ConvertNodeToScene(Scene *scene, int *id, const Node *node) { if (!node) return; std::cout << "node " << node->name << std::endl; if (dynamic_cast(node)) { // Allow only one root node in the scene. if (scene->root_node == NULL) { scene->root_node = node; } else { // Assume child xform node assert(scene->xform_map.find(node->name) == scene->xform_map.end()); scene->xform_map[node->name] = dynamic_cast(node); // scene->id_map[node->name] = (*id)++; } } else if (dynamic_cast(node)) { assert(scene->mesh_map.find(node->name) == scene->mesh_map.end()); scene->mesh_map[node->name] = dynamic_cast(node); scene->id_map[node->name] = (*id)++; } else if (dynamic_cast(node)) { assert(scene->curves_map.find(node->name) == scene->curves_map.end()); scene->curves_map[node->name] = dynamic_cast(node); scene->id_map[node->name] = (*id)++; } for (size_t i = 0; i < node->children.size(); i++) { ConvertNodeToScene(scene, id, node->children[i]); } } int main(int argc, char **argv) { std::string abc_filename; std::string gltf_filename; if (argc < 3) { std::cerr << "Usage: gltf2abc input.abc output.gltf" << std::endl; return EXIT_FAILURE; } abc_filename = std::string(argv[1]); gltf_filename = std::string(argv[2]); Alembic::AbcCoreFactory::IFactory factory; Alembic::AbcGeom::IArchive archive = factory.getArchive(abc_filename); Alembic::AbcGeom::IObject root = archive.getTop(); std::cout << "# of children " << root.getNumChildren() << std::endl; Scene scene; std::stringstream ss; Node *node = new Node(); VisitObjectAndExtractNode(node, ss, root, /* indent */ " "); // std::cout << ss.str() << std::endl; int id = 1; // ID starts from 1. ConvertNodeToScene(&scene, &id, node); delete node; SaveSceneToGLTF(gltf_filename, scene); return EXIT_SUCCESS; }