// All-in-one TinyUSDZ core #include "tinyusdz.hh" // Import to_string() and operator<< features #include #include "pprinter.hh" #include "prim-pprint.hh" #include "value-pprint.hh" // Tydra is a collection of APIs to access/convert USD Prim data // (e.g. Can get Attribute by name) // See /examples/tydra_api for more Tydra API examples. #include "tydra/scene-access.hh" // // create a Scene // void CreateScene(tinyusdz::Stage *stage) { // TinyUSDZ API does not use mutex, smart pointers(e.g. shared_ptr) and C++ // exception. Also threading is opional in TinyUSDZ and currently // multi-threading is not used. `value::token` implementation is simply copy // string for each instances(like C++11 ABI std::string). (on the contrary, // pxrUSD's token implementation uses string-to-id, which requires mutex lock // to make stringId unique. But we think this is inefficient on modern // multi-core CPU, since they locks the mutex every `token` value // instanciation) // // TinyUSDZ's API is not fully multi-thread safe, thus if you want to // manipulate a scene(Stage) in multi-threaded context, The app must take care // of resource locks in the app layer. std::string err; // // Create simple material with UsdPrevieSurface. // tinyusdz::Material mat; mat.name = "mat"; tinyusdz::Shader shader; // Shader container shader.name = "defaultPBR"; { tinyusdz::UsdPreviewSurface surfaceShader; // Concrete Shader node object // // Asssign actual shader object to Shader::value. // Also do not forget set its shader node type name through Shader::info_id // shader.info_id = tinyusdz::kUsdPreviewSurface; // "UsdPreviewSurface" token // // Currently no shader network/connection API. // Manually construct it. // surfaceShader.outputsSurface.set_authored( true); // Author `token outputs:surface` surfaceShader.metallic = 0.3f; // TODO: UsdUVTexture, UsdPrimvarReader***, UsdTransform2d // Connect to UsdPreviewSurface's outputs:surface by setting targetPath. // // token outputs:surface = mat.surface.set(tinyusdz::Path(/* prim path */ "/mat/defaultPBR", /* prop path */ "outputs:surface")); // // Shaer::value is `value::Value` type, so can use '=' to assign Shader // object. // shader.value = std::move(surfaceShader); } tinyusdz::Prim shaderPrim(shader); tinyusdz::Prim matPrim(mat); // matPrim.children().emplace_back(std::move(shaderPrim)); // no uniqueness // check // Use add_child() to ensure child Prim has unique name. if (!matPrim.add_child(std::move(shaderPrim), /* rename Prim name if_required */ true, &err)) { std::cerr << "Failed to constrcut Scene: " << err << "\n"; exit(-1); } // // To construct Prim, first create concrete Prim object(e.g. Xform, GeomMesh), // then add it to tinyusdz::Prim. // // tinyusdz::Xform xform; { xform.name = "root"; // Prim's name(elementPath) { tinyusdz::XformOp op; op.op_type = tinyusdz::XformOp::OpType::Transform; tinyusdz::value::matrix4d a0; tinyusdz::value::matrix4d b0; tinyusdz::Identity(&a0); tinyusdz::Identity(&b0); a0.m[1][1] = 2.1; // column major, so [3][0], [3][1], [3][2] = translate X, Y, Z b0.m[3][0] = 1.0; b0.m[3][1] = 3.1; b0.m[3][2] = 5.1; tinyusdz::value::matrix4d transform = a0 * b0; op.set_value(transform); // `xformOpOrder`(token[]) is represented as std::vector xform.xformOps.push_back(op); } { // `xformOp:***` attribute is represented as XformOp class tinyusdz::XformOp op; op.op_type = tinyusdz::XformOp::OpType::Translate; tinyusdz::value::double3 translate; translate[0] = 1.0; translate[1] = 2.0; translate[2] = 3.0; op.set_value(translate); // `xformOpOrder`(token[]) is represented as std::vector xform.xformOps.push_back(op); } { // .suffix will be prepended to `xformOp:translate` // 'xformOp:translate:move' tinyusdz::XformOp op; op.op_type = tinyusdz::XformOp::OpType::Translate; op.suffix = "move"; tinyusdz::value::double3 translate; // TimeSamples value can be added with `set_timesample` // NOTE: TimeSamples data will be automatically sorted by time when using // it. translate[0] = 0.0; translate[1] = 0.0; translate[2] = 0.0; op.set_timesample(0.0, translate); translate[0] = 1.0; translate[1] = 0.1; translate[2] = 0.3; op.set_timesample(1.0, translate); xform.xformOps.push_back(op); } } tinyusdz::GeomMesh mesh; { mesh.name = "quad"; { std::vector pts; pts.push_back({0.0f, 0.0f, 0.0f}); pts.push_back({1.0f, 0.0f, 0.0f}); pts.push_back({1.0f, 1.0f, 0.0f}); pts.push_back({0.0f, 1.0f, 0.0f}); mesh.points.set_value(pts); } { // quad plane composed of 2 triangles. std::vector indices; std::vector counts; counts.push_back(3); counts.push_back(3); mesh.faceVertexCounts.set_value(counts); indices.push_back(0); indices.push_back(1); indices.push_back(2); indices.push_back(0); indices.push_back(2); indices.push_back(3); mesh.faceVertexIndices.set_value(indices); } // primvar and custom attribute can be added to generic Property container // `props`(map) { // primvar is simply an attribute with prefix `primvars:` // // texCoord2f[] primvars:uv = [ ... ] ( interpolation = "vertex" ) // int[] primvars:uv:indices = [ ... ] // { tinyusdz::Attribute uvAttr; std::vector uvs; uvs.push_back({0.0f, 0.0f}); uvs.push_back({1.0f, 0.0f}); uvs.push_back({1.0f, 1.0f}); uvs.push_back({0.0f, 1.0f}); // Fast path. Set the value directly to Attribute. uvAttr.set_value(uvs); // or we can first build primvar::PrimVar // tinyusdz::primvar::PrimVar uvVar; // uvVar.set_value(uvs); // uvAttr.set_var(std::move(uvVar)); // Currently `interpolation` is described in Attribute metadataum. // You can set builtin(predefined) Attribute Metadatum(e.g. // `interpolation`, `hidden`) through `metas()`. uvAttr.metas().interpolation = tinyusdz::Interpolation::Vertex; tinyusdz::Property uvProp(uvAttr); mesh.props.emplace("primvars:uv", uvProp); // ---------------------- tinyusdz::Attribute uvIndexAttr; std::vector uvIndices; // FIXME: Validate uvIndices.push_back(0); uvIndices.push_back(1); uvIndices.push_back(3); uvIndices.push_back(2); tinyusdz::primvar::PrimVar uvIndexVar; uvIndexVar.set_value(uvIndices); uvIndexAttr.set_var(std::move(uvIndexVar)); // Or you can use this approach(if you want to keep a copy of PrimVar // data) // uvIndexAttr.set_var(uvIndexVar); tinyusdz::Property uvIndexProp(uvIndexAttr); mesh.props.emplace("primvars:uv:indices", uvIndexProp); } // `custom uniform double myvalue = 3.0 ( hidden = 0 )` { tinyusdz::Attribute attrib; double myvalue = 3.0; tinyusdz::primvar::PrimVar var; var.set_value(myvalue); attrib.set_var(std::move(var)); attrib.variability() = tinyusdz::Variability::Uniform; attrib.metas().hidden = false; // NOTE: `custom` keyword would be deprecated in the future USD syntax, // so you can set it false. tinyusdz::Property prop(attrib, /* custom*/ true); mesh.props.emplace("myvalue", prop); } // Add Primvar through GeomPrimvar; { tinyusdz::GeomPrimvar uvPrimvar; uvPrimvar.set_name("my_uv"); std::vector uvs; uvs.push_back({0.0f, 0.0f}); uvs.push_back({1.0f, 0.0f}); uvs.push_back({1.0f, 1.0f}); uvs.push_back({0.0f, 1.0f}); uvPrimvar.set_value(uvs); uvPrimvar.set_interpolation(tinyusdz::Interpolation::Vertex); std::vector uvIndices; uvIndices.push_back(0); uvIndices.push_back(1); uvIndices.push_back(3); uvIndices.push_back(2); uvPrimvar.set_indices(uvIndices); // primvar name is extracted from Primvar::name std::string err; if (!mesh.set_primvar(uvPrimvar, &err)) { std::cerr << "Failed to add Primar: " << err << "\n"; } } } // Material binding(`rel material:binding` is done by manually setup // targetPath. tinyusdz::Relationship materialBinding; materialBinding.set( tinyusdz::Path(/* prim path */ "/mat/defaultPBR", /* prop path */ "")); mesh.materialBinding = materialBinding; // TODO: Explicitly author MaterialBindingAPI apiSchema in Mesh Prim. } tinyusdz::GeomSphere sphere1; { sphere1.name = "sphere"; sphere1.radius = 3.14; } tinyusdz::GeomSphere sphere2; { sphere2.name = "sphere"; // name will be modified to be unique at add_child(). sphere2.radius = 1.05; } // // Create Scene(Stage) hierarchy. // You can add child Prim to parent Prim's `children()`. // // [Xform] // | // +- [Mesh] // +- [Sphere0] // +- [Sphere1] // // [Material] // | // +- [Shader] // // Prim's elementName is read from concrete Prim class(GeomMesh::name, // Xform::name, ...) tinyusdz::Prim meshPrim(mesh); { // `references`, `paylaod` { tinyusdz::Reference ref; ref.asset_path = tinyusdz::value::AssetPath("submesh-000.usd"); std::vector referencesList; referencesList.push_back(ref); meshPrim.metas().references = std::make_pair(tinyusdz::ListEditQual::Append, referencesList); } { tinyusdz::Payload pl; pl.asset_path = tinyusdz::value::AssetPath("submesh-payload-000.usd"); std::vector payloadList; payloadList.push_back(pl); meshPrim.metas().payload = std::make_pair(tinyusdz::ListEditQual::Append, payloadList); } } tinyusdz::Prim spherePrim(sphere1); { // variantSet is maniuplated in Prim. // Currently we don't provide easy API for variantSet. // Need to add variantSet information manually for a while. tinyusdz::VariantSelectionMap vsmap; // List of variantSet in the Prim. std::vector variantSetList; variantSetList.push_back("colorVariant"); // key = variantSet name, value = default Variant selection vsmap.emplace("colorVariant", "red"); spherePrim.metas().variants = vsmap; spherePrim.metas().variantSets = std::make_pair(tinyusdz::ListEditQual::Append, variantSetList); // VariantSet is composed of metas + properties + childPrims tinyusdz::VariantSet variantSet; tinyusdz::Variant redVariant; redVariant.metas().comment = "red color"; tinyusdz::value::color3f redColor({1.0f, 0.0f, 0.0f}); tinyusdz::Attribute redColorAttr; redColorAttr.set_value(redColor); redVariant.properties().emplace("mycolor", redColorAttr); // TODO: Add example to add childPrims under Variant // redVariant.primChildren().emplace(...) tinyusdz::Variant greenVariant; greenVariant.metas().comment = "green color"; tinyusdz::value::color3f greenColor({0.0f, 1.0f, 0.0f}); tinyusdz::Attribute greenColorAttr; greenColorAttr.set_value(greenColor); greenVariant.properties().emplace("mycolor", greenColorAttr); variantSet.name = "red"; variantSet.variantSet.emplace("red", redVariant); variantSet.name = "green"; variantSet.variantSet.emplace("green", greenVariant); spherePrim.variantSets().emplace("colorVariant", variantSet); } tinyusdz::Prim spherePrim2(sphere2); tinyusdz::Prim xformPrim(xform); // xformPrim.children().emplace_back(std::move(meshPrim)); // xformPrim.children().emplace_back(std::move(spherePrim)); // Use add_child() to ensure child Prim has unique name. if (!xformPrim.add_child(std::move(meshPrim), /* rename Prim name if_required */ true, &err)) { std::cerr << "Failed to constrcut Scene: " << err << "\n"; exit(-1); } if (!xformPrim.add_child(std::move(spherePrim), /* rename Prim name if_required */ true, &err)) { std::cerr << "Failed to constrcut Scene: " << err << "\n"; } #if 1 // Must set rename Prim arg `true`, otherwise `add_child` fails since // spherePrim2 does not have valid & unique Prim name. if (!xformPrim.add_child(std::move(spherePrim2), /* rename Prim name if_required */ true, &err)) { std::cerr << "Failed to constrcut Scene: " << err << "\n"; exit(-1); } if (xformPrim.children().size() != 3) { std::cerr << "Internal error. num child Prims must be 3, but got " << xformPrim.children().size() << "\n"; exit(-1); } // If you want to specify the appearance/traversal order of child Prim(e.g. // Showing Prim tree in GUI, Ascii output), set "primChildren"(token[]) // metadata xfromPrim.metas().primChildren.size() must be identical to // xformPrim.children().size() xformPrim.metas().primChildren.push_back( tinyusdz::value::token(xformPrim.children()[1].element_name())); xformPrim.metas().primChildren.push_back( tinyusdz::value::token(xformPrim.children()[0].element_name())); xformPrim.metas().primChildren.push_back( tinyusdz::value::token(xformPrim.children()[2].element_name())); #else // You can replace(or add if corresponding Prim does not exist) existing child // Prim using replace_child() if (!xformPrim.replace_child("sphere", std::move(spherePrim2), &err)) { std::cerr << "Failed to constrcut Scene: " << err << "\n"; exit(-1); } if (xformPrim.children().size() != 2) { std::cerr << "Internal error. num child Prims must be 2, but got " << xformPrim.children().size() << "\n"; exit(-1); } #endif /// /// Add subLayers /// std::vector sublayers; tinyusdz::SubLayer slayer0; slayer0.assetPath = tinyusdz::value::AssetPath("sublayer-000.usd"); sublayers.push_back(slayer0); stage->metas().subLayers = sublayers; stage->metas().defaultPrim = tinyusdz::value::token(xformPrim.element_name()); // token if (!stage->add_root_prim(std::move(xformPrim))) { std::cerr << "Failed to add Prim to Stage root: " << stage->get_error() << "\n"; exit(-1); } if (!stage->add_root_prim(std::move(matPrim))) { std::cerr << "Failed to add Prim to Stage root: " << stage->get_error() << "\n"; exit(-1); } // You can replace(or add if given Prim name does not exist in the Stage) root // Prim using `replace_root_prim`. #if 0 { tinyusdz::Scope scope; tinyusdz::Prim scopePrim(scope); if (!stage->replace_root_prim(/* root Prim name */"root", std::move(scopePrim))) { std::cerr << "Failed to replace Prim to Stage root: " << stage->get_error() << "\n"; exit(-1); } } #endif // You can add Stage metadatum through Stage::metas() stage->metas().comment = "Generated by TinyUSDZ api_tutorial."; { // Dictionary(alias to CustomDataType) is similar to VtDictionary. // it is a map // MetaVariable is similar to Value, but accepts limited variation of // types(double, token, string, float3[], ...) tinyusdz::Dictionary customData; tinyusdz::MetaVariable metavar; double mycustom = 1.3; metavar.set_value("mycustom", mycustom); std::string mystring = "hello"; tinyusdz::MetaVariable metavar2("mystring", mystring); customData.emplace("mycustom", metavar); customData.emplace("mystring", metavar2); customData.emplace("myvalue", 2.45); // Construct MetaVariables implicitly // You can also use SetCustomDataByKey to set custom value with key having // namespaces(':') tinyusdz::MetaVariable intval = int(5); tinyusdz::SetCustomDataByKey("mydict:myval", intval, /* inout */ customData); stage->metas().customLayerData = customData; } // Commit Stage. // Internally, it calls Stage::compute_absolute_prim_path_and_assign_prim_id() // to compute absolute Prim path and assign unique Prim id to each Prims in // the Stage. NOTE: Stage::metas() is not affected by `commit` API, so you can // call `commit` before manipulating StageMetas through `Stage::metas()` if (!stage->commit()) { std::cerr << "Failed to commit Stage. ERR: " << stage->get_error() << "\n"; exit(-1); } } int main(int argc, char **argv) { tinyusdz::Stage stage; // empty scene CreateScene(&stage); if (stage.get_warning().size()) { std::cout << "WARN in Stage: " << stage.get_warning() << "\n"; } // Print USD scene as Ascii. std::cout << to_string(stage) << "\n"; // std::cout << stage.ExportToString() << "\n"; // you can also use pxrUSD // compatible ExportToString(). // Dump Prim tree info. std::cout << stage.dump_prim_tree() << "\n"; { tinyusdz::Path path(/* absolute prim path */ "/root", /* property path */ ""); const tinyusdz::Prim *prim{nullptr}; std::string err; bool ret = stage.find_prim_at_path(path, prim, &err); if (ret) { std::cout << "Found Prim at path: " << tinyusdz::to_string(path) << "\n"; std::cout << "Prim ID: " << prim->prim_id() << "\n"; std::cout << "Prim's absolute_path: " << tinyusdz::to_string(prim->absolute_path()) << "\n"; } else { std::cerr << err << "\n"; } if (!prim) { // This should not be happen though. std::cerr << "Prim is null\n"; return -1; } if (!prim->is()) { std::cerr << "Expected Xform prim." << "\n"; return -1; } // Cast to Xform const tinyusdz::Xform *xform = prim->as(); if (!xform) { std::cerr << "Expected Xform prim." << "\n"; return -1; } } // find Prim by prim_id { uint64_t prim_id = 2; const tinyusdz::Prim *prim{nullptr}; std::string err; bool ret = stage.find_prim_by_prim_id(prim_id, prim, &err); if (ret && prim) { std::cout << "Found Prim by ID: " << prim_id << "\n"; std::cout << "Prim's absolute_path: " << tinyusdz::to_string(prim->absolute_path()) << "\n"; } else { std::cerr << err << "\n"; } } // GetAttribute and GeomPrimvar { tinyusdz::Path path(/* absolute prim path */ "/root/quad", /* property path */ ""); const tinyusdz::Prim *prim{nullptr}; std::string err; bool ret = stage.find_prim_at_path(path, prim, &err); if (ret && prim) { std::cout << "Found Prim at path: " << tinyusdz::to_string(path) << "\n"; std::cout << "Prim ID: " << prim->prim_id() << "\n"; std::cout << "Prim's absolute_path: " << tinyusdz::to_string(prim->absolute_path()) << "\n"; } else { std::cerr << err << "\n"; } if (!prim) { // This should not be happen though. std::cerr << "Prim is null\n"; return -1; } tinyusdz::Attribute attr; if (tinyusdz::tydra::GetAttribute(*prim, "points", &attr, &err)) { std::cout << "point attribute type = " << attr.type_name() << "\n"; // Ensure Attribute has a value(not Attribute connection) if (attr.is_value()) { std::vector pts; if (attr.is_timesamples()) { // TODO: timesamples } else { if (attr.get_value(&pts)) { std::cout << "point attribute value = " << pts << "\n"; } } } } else { std::cerr << err << "\n"; } const tinyusdz::GeomMesh *mesh = prim->as(); if (!mesh) { std::cerr << "Expected GeomMesh.\n"; return -1; } // GeomPrimvar // Access GeomPrimvar { std::cout << "uv is primvar? " << mesh->has_primvar("uv") << "\n"; tinyusdz::GeomPrimvar primvar; std::string err; if (mesh->get_primvar("uv", &primvar, &err)) { std::cout << "uv primvar is Indexed Primvar? " << primvar.has_indices() << "\n"; } else { std::cerr << "get_primvar(\"uv\") failed. err = " << err << "\n"; } // Equivalent to pxr::UsdGeomPrimvar::ComputeFlattened(). // elems[i] = values[indices[i]] tinyusdz::value::Value value; if (primvar.flatten_with_indices(&value, &err)) { // value;:Value can contain any types, but value.array_size() should // work well only for primvar types(e.g. `float[]`, `color3f[]`) It // would report 0 for non-primvar types(e.g.`std::vector`) std::cout << "uv primvars. array size = " << value.array_size() << "\n"; std::cout << "uv primvars. expand_by_indices result = " << tinyusdz::value::pprint_value(value) << "\n"; } else { std::cerr << "expand_by_indices failed. err = " << err << "\n"; } // Typed version std::vector uvs; if (primvar.flatten_with_indices(&uvs, &err)) { // value;:Value can contain any types, but value.array_size() should // work well only for primvar types(e.g. `float[]`, `color3f[]`) It // would report 0 for non-primvar types(e.g.`std::vector`) std::cout << "uv primvars. array size = " << uvs.size() << "\n"; std::cout << "uv primvars. expand_by_indices result = " << tinyusdz::value::pprint_value(uvs) << "\n"; } else { std::cerr << "expand_by_indices failed. err = " << err << "\n"; } std::vector gpvars = mesh->get_primvars(); std::cout << "# of primvars = " << gpvars.size(); for (const auto &item : gpvars) { std::cout << " primvar = " << item.name() << "\n"; } } } return EXIT_SUCCESS; }