mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Implement comprehensive serialization system to extract OpenPBR material properties from Tydra RenderMaterial and convert to JSON or XML format for use in JavaScript/Three.js applications. New files: - web/openpbr-serializer.hh: Core serialization implementation - JSON serialization for all OpenPBR parameters - XML (MaterialX 1.38) output for MaterialX tools/renderers - Support for UsdPreviewSurface materials - web/test-openpbr-material.js: Usage examples and Three.js integration - web/BUILD_STATUS.md: Build documentation and API reference Modified files: - web/binding.cc: Updated getMaterial() method - New getMaterialWithFormat(id, format) method for "json"/"xml" output - Legacy getMaterial(id) maintained for backward compatibility - Proper error handling and format validation Features: - Complete OpenPBR support: base, specular, transmission, subsurface, sheen, coat, emission, and geometry modifiers - Handles both value and texture parameters - Dual format output (JSON for JS, XML for MaterialX) - C++14/17/20 compatible implementation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2562 lines
76 KiB
C++
2562 lines
76 KiB
C++
// SPDX-License-Identifier: Apache 2.0
|
|
// Copyright 2024-Present Light Transport Entertainment, Inc.
|
|
//
|
|
#include <emscripten/bind.h>
|
|
#include <emscripten/console.h>
|
|
#include <emscripten/em_js.h>
|
|
#include <emscripten/fetch.h>
|
|
#include <emscripten/emscripten.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <random>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
//#include "external/fast_float/include/fast_float/bigint.h"
|
|
#include "tinyusdz.hh"
|
|
#include "pprinter.hh"
|
|
#include "typed-array.hh"
|
|
#include "value-types.hh"
|
|
#include "tydra/render-data.hh"
|
|
#include "tydra/scene-access.hh"
|
|
#include "openpbr-serializer.hh"
|
|
|
|
#include "tydra/mcp-context.hh"
|
|
#include "tydra/mcp-resources.hh"
|
|
#include "tydra/mcp-tools.hh"
|
|
#include "usd-to-json.hh"
|
|
#include "json-to-usd.hh"
|
|
#include "sha256.hh"
|
|
#include "logger.hh"
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Weverything"
|
|
#endif
|
|
|
|
#include "external/jsonhpp/nlohmann/json.hpp"
|
|
|
|
#ifdef __clang__
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
// Handling Asset
|
|
// Due to the limitatrion of C++(synchronous) initiated async file(fetch) read,
|
|
// We decided to fetch asset in JavaScript layer.
|
|
//
|
|
// 1. First list up assets(textures, USD scenes(for composition)
|
|
// 2. Load(fetch) assets to memory in JavaScript layer
|
|
// 3. Set binary data to EMAssetResolutionResolver.
|
|
// 4. Use EMAssetResolutionResolver to load asset(simply lookup binary data by asset name)
|
|
//
|
|
|
|
|
|
using namespace emscripten;
|
|
|
|
namespace detail {
|
|
|
|
std::array<double, 9> toArray(const tinyusdz::value::matrix3d &m) {
|
|
std::array<double, 9> ret;
|
|
|
|
ret[0] = m.m[0][0];
|
|
ret[1] = m.m[0][1];
|
|
ret[2] = m.m[0][2];
|
|
|
|
ret[3] = m.m[1][0];
|
|
ret[4] = m.m[1][1];
|
|
ret[5] = m.m[1][2];
|
|
|
|
ret[6] = m.m[2][0];
|
|
ret[7] = m.m[2][1];
|
|
ret[8] = m.m[2][2];
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::array<double, 16> toArray(const tinyusdz::value::matrix4d &m) {
|
|
std::array<double, 16> ret;
|
|
|
|
ret[0] = m.m[0][0];
|
|
ret[1] = m.m[0][1];
|
|
ret[2] = m.m[0][2];
|
|
ret[3] = m.m[0][3];
|
|
|
|
ret[4] = m.m[1][0];
|
|
ret[5] = m.m[1][1];
|
|
ret[6] = m.m[1][2];
|
|
ret[7] = m.m[1][3];
|
|
|
|
ret[8] = m.m[2][0];
|
|
ret[9] = m.m[2][1];
|
|
ret[10] = m.m[2][2];
|
|
ret[11] = m.m[2][3];
|
|
|
|
ret[12] = m.m[3][0];
|
|
ret[13] = m.m[3][1];
|
|
ret[14] = m.m[3][2];
|
|
ret[15] = m.m[3][3];
|
|
|
|
return ret;
|
|
}
|
|
|
|
// To RGBA
|
|
bool ToRGBA(const std::vector<uint8_t> &src, int channels,
|
|
std::vector<uint8_t> &dst) {
|
|
uint32_t npixels = src.size() / channels;
|
|
dst.resize(npixels * 4);
|
|
|
|
if (channels == 1) { // grayscale
|
|
for (size_t i = 0; i < npixels; i++) {
|
|
dst[4 * i + 0] = src[i];
|
|
dst[4 * i + 1] = src[i];
|
|
dst[4 * i + 2] = src[i];
|
|
dst[4 * i + 3] = 1.0f;
|
|
}
|
|
} else if (channels == 2) { // assume luminance + alpha
|
|
for (size_t i = 0; i < npixels; i++) {
|
|
dst[4 * i + 0] = src[2 * i + 0];
|
|
dst[4 * i + 1] = src[2 * i + 0];
|
|
dst[4 * i + 2] = src[2 * i + 0];
|
|
dst[4 * i + 3] = src[2 * i + 1];
|
|
}
|
|
} else if (channels == 3) {
|
|
for (size_t i = 0; i < npixels; i++) {
|
|
dst[4 * i + 0] = src[3 * i + 0];
|
|
dst[4 * i + 1] = src[3 * i + 1];
|
|
dst[4 * i + 2] = src[3 * i + 2];
|
|
dst[4 * i + 3] = 1.0f;
|
|
}
|
|
} else if (channels == 4) {
|
|
dst = src;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool uint8arrayToBuffer(const emscripten::val& u8, tinyusdz::TypedArray<uint8_t> &buf) {
|
|
size_t n = u8["byteLength"].as<size_t>();
|
|
buf.resize(n);
|
|
|
|
// Copy JS typed array -> v (one memcpy under the hood)
|
|
emscripten::val view = emscripten::val::global("Uint8Array").new_(u8["buffer"], u8["byteOffset"], n);
|
|
emscripten::val heapView = emscripten::val(emscripten::typed_memory_view(n, buf.data()));
|
|
heapView.call<void>("set", view);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
} // namespace detail
|
|
|
|
// Simple UUID v4 generator
|
|
std::string generateUUID() {
|
|
static std::random_device rd;
|
|
static std::mt19937 gen(rd());
|
|
static std::uniform_int_distribution<> dis(0, 15);
|
|
static std::uniform_int_distribution<> dis2(8, 11);
|
|
|
|
std::stringstream ss;
|
|
ss << std::hex;
|
|
|
|
// Generate 32 hex characters with hyphens at positions 8, 12, 16, 20
|
|
for (int i = 0; i < 36; i++) {
|
|
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
|
ss << "-";
|
|
} else if (i == 14) {
|
|
ss << "4"; // Version 4 UUID
|
|
} else if (i == 19) {
|
|
ss << dis2(gen); // Variant bits
|
|
} else {
|
|
ss << dis(gen);
|
|
}
|
|
}
|
|
|
|
return ss.str();
|
|
}
|
|
|
|
struct AssetCacheEntry {
|
|
std::string sha256_hash;
|
|
std::string binary;
|
|
std::string uuid;
|
|
|
|
AssetCacheEntry() : uuid(generateUUID()) {}
|
|
AssetCacheEntry(const std::string& data)
|
|
: sha256_hash(tinyusdz::sha256(data.c_str(), data.size())),
|
|
binary(data),
|
|
uuid(generateUUID()) {}
|
|
AssetCacheEntry(std::string&& data) noexcept
|
|
: sha256_hash(tinyusdz::sha256(data.c_str(), data.size())),
|
|
binary(std::move(data)),
|
|
uuid(generateUUID()) {}
|
|
};
|
|
|
|
// Progress callback function type for streaming
|
|
using ProgressCallback = std::function<void(const std::string&, size_t, size_t)>;
|
|
|
|
// Streaming asset entry that builds incrementally
|
|
struct StreamingAssetEntry {
|
|
std::string binary;
|
|
size_t expected_size;
|
|
size_t current_size;
|
|
std::string sha256_hash;
|
|
std::string uuid;
|
|
ProgressCallback progress_callback;
|
|
|
|
StreamingAssetEntry() : expected_size(0), current_size(0), uuid(generateUUID()) {}
|
|
|
|
bool appendChunk(const std::string& chunk) {
|
|
binary.append(chunk);
|
|
current_size = binary.size();
|
|
|
|
if (progress_callback && expected_size > 0) {
|
|
progress_callback(sha256_hash, current_size, expected_size);
|
|
}
|
|
|
|
return current_size <= expected_size;
|
|
}
|
|
|
|
bool isComplete() const {
|
|
return expected_size > 0 && current_size >= expected_size;
|
|
}
|
|
|
|
AssetCacheEntry finalize() {
|
|
if (isComplete()) {
|
|
sha256_hash = tinyusdz::sha256(binary.c_str(), binary.size());
|
|
AssetCacheEntry entry;
|
|
entry.sha256_hash = sha256_hash;
|
|
entry.binary = std::move(binary);
|
|
entry.uuid = uuid; // Preserve the UUID from streaming
|
|
return entry;
|
|
}
|
|
return AssetCacheEntry();
|
|
}
|
|
};
|
|
|
|
struct EMAssetResolutionResolver {
|
|
|
|
static int Resolve(const char *asset_name,
|
|
const std::vector<std::string> &search_paths,
|
|
std::string *resolved_asset_name, std::string *err,
|
|
void *userdata) {
|
|
(void)err;
|
|
(void)userdata;
|
|
(void)search_paths;
|
|
|
|
if (!asset_name) {
|
|
return -2; // err
|
|
}
|
|
|
|
if (!resolved_asset_name) {
|
|
return -2; // err
|
|
}
|
|
|
|
// TODO: searchpath
|
|
(*resolved_asset_name) = asset_name;
|
|
return 0; // OK
|
|
}
|
|
|
|
// AssetResoltion handlers
|
|
static int Size(const char *asset_name, uint64_t *nbytes, std::string *err,
|
|
void *userdata) {
|
|
(void)userdata;
|
|
|
|
if (!asset_name) {
|
|
if (err) {
|
|
(*err) += "asset_name arg is nullptr.\n";
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (!nbytes) {
|
|
if (err) {
|
|
(*err) += "nbytes arg is nullptr.\n";
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
EMAssetResolutionResolver *p = reinterpret_cast<EMAssetResolutionResolver *>(userdata);
|
|
const AssetCacheEntry &entry = p->get(asset_name);
|
|
|
|
//std::cout << asset_name << ".size " << entry.binary.size() << "\n";
|
|
|
|
(*nbytes) = uint64_t(entry.binary.size());
|
|
return 0; // OK
|
|
}
|
|
|
|
static int Read(const char *asset_name, uint64_t req_nbytes, uint8_t *out_buf,
|
|
uint64_t *nbytes, std::string *err, void *userdata) {
|
|
if (!asset_name) {
|
|
if (err) {
|
|
(*err) += "asset_name arg is nullptr.\n";
|
|
}
|
|
return -3;
|
|
}
|
|
|
|
if (!nbytes) {
|
|
if (err) {
|
|
(*err) += "nbytes arg is nullptr.\n";
|
|
}
|
|
return -3;
|
|
}
|
|
|
|
if (req_nbytes < 9) { // at least 9 bytes(strlen("#usda 1.0")) or more
|
|
return -2;
|
|
}
|
|
|
|
EMAssetResolutionResolver *p = reinterpret_cast<EMAssetResolutionResolver *>(userdata);
|
|
|
|
if (p->has(asset_name)) {
|
|
const AssetCacheEntry &entry = p->get(asset_name);
|
|
if (entry.binary.size() > req_nbytes) {
|
|
return -2;
|
|
}
|
|
memcpy(out_buf, entry.binary.data(), entry.binary.size());
|
|
(*nbytes) = entry.binary.size();
|
|
return 0; // ok
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// Assume content is loaded in JS layer.
|
|
bool add(const std::string &asset_name, const std::string &binary) {
|
|
bool overwritten = has(asset_name);
|
|
|
|
cache[asset_name] = AssetCacheEntry(binary);
|
|
|
|
return overwritten;
|
|
}
|
|
|
|
bool has(const std::string &asset_name) const {
|
|
return cache.count(asset_name);
|
|
}
|
|
|
|
const AssetCacheEntry &get(const std::string &asset_name) const {
|
|
if (!cache.count(asset_name)) {
|
|
return empty_entry_;
|
|
}
|
|
|
|
return cache.at(asset_name);
|
|
}
|
|
|
|
std::string getHash(const std::string &asset_name) const {
|
|
if (!cache.count(asset_name)) {
|
|
return std::string();
|
|
}
|
|
return cache.at(asset_name).sha256_hash;
|
|
}
|
|
|
|
bool verifyHash(const std::string &asset_name, const std::string &expected_hash) const {
|
|
if (!cache.count(asset_name)) {
|
|
return false;
|
|
}
|
|
return cache.at(asset_name).sha256_hash == expected_hash;
|
|
}
|
|
|
|
std::string getUUID(const std::string &asset_name) const {
|
|
if (!cache.count(asset_name)) {
|
|
return std::string();
|
|
}
|
|
return cache.at(asset_name).uuid;
|
|
}
|
|
|
|
std::string getStreamingUUID(const std::string &asset_name) const {
|
|
if (!streaming_cache.count(asset_name)) {
|
|
return std::string();
|
|
}
|
|
return streaming_cache.at(asset_name).uuid;
|
|
}
|
|
|
|
// Get all asset UUIDs
|
|
emscripten::val getAssetUUIDs() const {
|
|
emscripten::val uuids = emscripten::val::object();
|
|
for (const auto &pair : cache) {
|
|
uuids.set(pair.first, pair.second.uuid);
|
|
}
|
|
return uuids;
|
|
}
|
|
|
|
// Find asset by UUID
|
|
std::string findAssetByUUID(const std::string &uuid) const {
|
|
for (const auto &pair : cache) {
|
|
if (pair.second.uuid == uuid) {
|
|
return pair.first;
|
|
}
|
|
}
|
|
return std::string();
|
|
}
|
|
|
|
// Get asset by UUID
|
|
const AssetCacheEntry &getByUUID(const std::string &uuid) const {
|
|
for (const auto &pair : cache) {
|
|
if (pair.second.uuid == uuid) {
|
|
return pair.second;
|
|
}
|
|
}
|
|
return empty_entry_;
|
|
}
|
|
|
|
// Check if asset exists by UUID
|
|
bool hasByUUID(const std::string &uuid) const {
|
|
for (const auto &pair : cache) {
|
|
if (pair.second.uuid == uuid) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Delete asset by name
|
|
bool deleteAsset(const std::string &asset_name) {
|
|
if (!cache.count(asset_name)) {
|
|
return false;
|
|
}
|
|
cache.erase(asset_name);
|
|
return true;
|
|
}
|
|
|
|
// Delete asset by UUID
|
|
bool deleteAssetByUUID(const std::string &uuid) {
|
|
for (auto it = cache.begin(); it != cache.end(); ++it) {
|
|
if (it->second.uuid == uuid) {
|
|
cache.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Delete streaming asset if exists
|
|
bool deleteStreamingAsset(const std::string &asset_name) {
|
|
if (!streaming_cache.count(asset_name)) {
|
|
return false;
|
|
}
|
|
streaming_cache.erase(asset_name);
|
|
return true;
|
|
}
|
|
|
|
emscripten::val getCacheDataAsMemoryView(const std::string &asset_name) const {
|
|
if (!cache.count(asset_name)) {
|
|
return emscripten::val::undefined();
|
|
}
|
|
const AssetCacheEntry &entry = cache.at(asset_name);
|
|
return emscripten::val(emscripten::typed_memory_view(entry.binary.size(),
|
|
reinterpret_cast<const uint8_t*>(entry.binary.data())));
|
|
}
|
|
|
|
// Zero-copy method using raw pointers for direct Uint8Array access
|
|
bool addFromRawPointer(const std::string &asset_name, uintptr_t dataPtr, size_t size) {
|
|
if (size == 0) {
|
|
return false;
|
|
}
|
|
|
|
// Direct access to the data without copying during read
|
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(dataPtr);
|
|
|
|
// Only copy once into our storage format
|
|
std::string binary;
|
|
binary.reserve(size);
|
|
binary.assign(reinterpret_cast<const char*>(data), size);
|
|
|
|
bool overwritten = has(asset_name);
|
|
cache[asset_name] = AssetCacheEntry(std::move(binary));
|
|
|
|
return overwritten;
|
|
}
|
|
|
|
void clear() {
|
|
cache.clear();
|
|
streaming_cache.clear();
|
|
}
|
|
|
|
// Streaming asset methods
|
|
bool startStreamingAsset(const std::string &asset_name, size_t expected_size) {
|
|
streaming_cache[asset_name] = StreamingAssetEntry();
|
|
streaming_cache[asset_name].expected_size = expected_size;
|
|
streaming_cache[asset_name].current_size = 0;
|
|
return true;
|
|
}
|
|
|
|
bool appendAssetChunk(const std::string &asset_name, const std::string &chunk) {
|
|
if (!streaming_cache.count(asset_name)) {
|
|
return false;
|
|
}
|
|
return streaming_cache[asset_name].appendChunk(chunk);
|
|
}
|
|
|
|
bool finalizeStreamingAsset(const std::string &asset_name) {
|
|
if (!streaming_cache.count(asset_name)) {
|
|
return false;
|
|
}
|
|
|
|
StreamingAssetEntry &entry = streaming_cache[asset_name];
|
|
if (!entry.isComplete()) {
|
|
return false;
|
|
}
|
|
|
|
cache[asset_name] = std::move(entry.finalize());
|
|
streaming_cache.erase(asset_name);
|
|
return true;
|
|
}
|
|
|
|
bool isStreamingAssetComplete(const std::string &asset_name) const {
|
|
if (!streaming_cache.count(asset_name)) {
|
|
return false;
|
|
}
|
|
return streaming_cache.at(asset_name).isComplete();
|
|
}
|
|
|
|
emscripten::val getStreamingProgress(const std::string &asset_name) const {
|
|
emscripten::val progress = emscripten::val::object();
|
|
|
|
if (!streaming_cache.count(asset_name)) {
|
|
progress.set("exists", false);
|
|
return progress;
|
|
}
|
|
|
|
const StreamingAssetEntry &entry = streaming_cache.at(asset_name);
|
|
progress.set("exists", true);
|
|
progress.set("current", double(entry.current_size));
|
|
progress.set("total", double(entry.expected_size));
|
|
progress.set("complete", entry.isComplete());
|
|
progress.set("uuid", entry.uuid);
|
|
if (entry.expected_size > 0) {
|
|
progress.set("percentage", (double(entry.current_size) / double(entry.expected_size)) * 100.0);
|
|
} else {
|
|
progress.set("percentage", 0.0);
|
|
}
|
|
|
|
return progress;
|
|
}
|
|
|
|
// TODO: Use IndexDB?
|
|
//
|
|
// <uri, AssetCacheEntry>
|
|
std::map<std::string, AssetCacheEntry> cache;
|
|
std::map<std::string, StreamingAssetEntry> streaming_cache;
|
|
AssetCacheEntry empty_entry_;
|
|
};
|
|
|
|
bool SetupEMAssetResolution(
|
|
tinyusdz::AssetResolutionResolver &resolver,
|
|
/* must be the persistent pointer address until usd load finishes */
|
|
const EMAssetResolutionResolver *p) {
|
|
if (!p) {
|
|
return false;
|
|
}
|
|
|
|
tinyusdz::AssetResolutionHandler handler;
|
|
handler.resolve_fun = EMAssetResolutionResolver::Resolve;
|
|
handler.size_fun = EMAssetResolutionResolver::Size;
|
|
handler.read_fun = EMAssetResolutionResolver::Read;
|
|
handler.write_fun = nullptr;
|
|
handler.userdata =
|
|
reinterpret_cast<void *>(const_cast<EMAssetResolutionResolver *>(p));
|
|
|
|
resolver.register_wildcard_asset_resolution_handler(handler);
|
|
|
|
return true;
|
|
}
|
|
|
|
///
|
|
/// Simple C++ wrapper class for Emscripten
|
|
///
|
|
class TinyUSDZLoaderNative {
|
|
public:
|
|
struct CompositionFeatures {
|
|
bool subLayers{true};
|
|
bool inherits{true};
|
|
bool variantSets{true};
|
|
bool references{true};
|
|
bool payload{true}; // Not 'payloads'
|
|
bool specializes{true};
|
|
};
|
|
|
|
// Default constructor for async loading
|
|
TinyUSDZLoaderNative() : loaded_(false) {}
|
|
~TinyUSDZLoaderNative() {}
|
|
|
|
#if 0
|
|
///
|
|
/// `binary` is the buffer for TinyUSDZ binary(e.g. buffer read by
|
|
/// fs.readFileSync) std::string can be used as UInt8Array in JS layer.
|
|
///
|
|
TinyUSDZLoaderNative(const std::string &binary) {
|
|
loadFromBinary(binary);
|
|
}
|
|
#endif
|
|
|
|
bool stageToRenderScene(const tinyusdz::Stage &stage, bool is_usdz, const std::string &binary) {
|
|
|
|
tinyusdz::tydra::RenderSceneConverterEnv env(stage);
|
|
|
|
// load texture in C++ image loader? default = false(Use JS to decode texture image)
|
|
env.scene_config.load_texture_assets = loadTextureInNative_;
|
|
|
|
env.material_config.preserve_texel_bitdepth = true;
|
|
|
|
// Free GeomMesh data in stage after using it to save memory.
|
|
env.mesh_config.lowmem = true;
|
|
|
|
// Do not try to build indices(avoid temp memory consumption of vertex similarity search)
|
|
//env.mesh_config.prefer_non_indexed = true;
|
|
|
|
//env.mesh_config.build_index_method = 0; // simple
|
|
|
|
if (is_usdz) {
|
|
// TODO: Support USDZ + Composition
|
|
// Setup AssetResolutionResolver to read a asset(file) from memory.
|
|
bool asset_on_memory =
|
|
false; // duplicate asset data from USDZ(binary) to UDSZAsset struct.
|
|
|
|
if (!tinyusdz::ReadUSDZAssetInfoFromMemory(
|
|
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size(),
|
|
asset_on_memory, &usdz_asset_, &warn_, &error_)) {
|
|
std::cerr << "Failed to read USDZ assetInfo. \n";
|
|
loaded_ = false;
|
|
return false;
|
|
}
|
|
|
|
tinyusdz::AssetResolutionResolver arr;
|
|
|
|
// NOTE: Pointer address of usdz_asset must be valid until the call of
|
|
// RenderSceneConverter::ConvertToRenderScene.
|
|
if (!tinyusdz::SetupUSDZAssetResolution(arr, &usdz_asset_)) {
|
|
std::cerr << "Failed to setup AssetResolution for USDZ asset\n";
|
|
loaded_ = false;
|
|
return false;
|
|
}
|
|
|
|
env.asset_resolver = arr;
|
|
} else {
|
|
tinyusdz::AssetResolutionResolver arr;
|
|
if (!SetupEMAssetResolution(arr, &em_resolver_)) {
|
|
std::cerr << "Failed to setup FetchAssetResolution\n";
|
|
loaded_ = false;
|
|
return false;
|
|
}
|
|
|
|
env.asset_resolver = arr;
|
|
}
|
|
|
|
// RenderScene: Scene graph object which is suited for GL/Vulkan renderer
|
|
tinyusdz::tydra::RenderSceneConverter converter;
|
|
|
|
// env.timecode = timecode; // TODO
|
|
loaded_ = converter.ConvertToRenderScene(env, &render_scene_);
|
|
if (!loaded_) {
|
|
std::cerr << "Failed to convert USD Stage to RenderScene: \n"
|
|
<< converter.GetError() << "\n";
|
|
error_ = converter.GetError();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool loadAsLayerFromBinary(const std::string &binary, const std::string &filename) {
|
|
|
|
|
|
bool is_usdz = tinyusdz::IsUSDZ(
|
|
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size());
|
|
|
|
tinyusdz::USDLoadOptions options;
|
|
options.max_memory_limit_in_mb = max_memory_limit_mb_;
|
|
|
|
loaded_ = tinyusdz::LoadLayerFromMemory(
|
|
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size(),
|
|
filename, &layer_, &warn_, &error_, options);
|
|
|
|
if (!loaded_) {
|
|
return false;
|
|
}
|
|
|
|
loaded_as_layer_ = true;
|
|
filename_ = filename;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool loadFromBinary(const std::string &binary, const std::string &filename) {
|
|
|
|
//if (enableComposition_) {
|
|
// return loadAndCompositeFromBinary(binary, filename);
|
|
//}
|
|
|
|
bool is_usdz = tinyusdz::IsUSDZ(
|
|
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size());
|
|
|
|
tinyusdz::USDLoadOptions options;
|
|
options.max_memory_limit_in_mb = max_memory_limit_mb_;
|
|
|
|
tinyusdz::Stage stage;
|
|
loaded_ = tinyusdz::LoadUSDFromMemory(
|
|
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size(),
|
|
filename, &stage, &warn_, &error_, options);
|
|
|
|
if (!loaded_) {
|
|
return false;
|
|
}
|
|
|
|
loaded_as_layer_ = false;
|
|
filename_ = filename;
|
|
|
|
std::cout << "loaded\n";
|
|
#if 0
|
|
tinyusdz::tydra::RenderSceneConverterEnv env(stage);
|
|
|
|
//
|
|
// false = Load Texture in JS Layer
|
|
//
|
|
|
|
env.scene_config.load_texture_assets = loadTextureInNative_;
|
|
|
|
env.material_config.preserve_texel_bitdepth = true;
|
|
|
|
if (is_usdz) {
|
|
// TODO: Support USDZ + Composition
|
|
// Setup AssetResolutionResolver to read a asset(file) from memory.
|
|
bool asset_on_memory =
|
|
false; // duplicate asset data from USDZ(binary) to UDSZAsset struct.
|
|
|
|
if (!tinyusdz::ReadUSDZAssetInfoFromMemory(
|
|
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size(),
|
|
asset_on_memory, &usdz_asset_, &warn_, &error_)) {
|
|
std::cerr << "Failed to read USDZ assetInfo. \n";
|
|
loaded_ = false;
|
|
return false;
|
|
}
|
|
|
|
tinyusdz::AssetResolutionResolver arr;
|
|
|
|
// NOTE: Pointer address of usdz_asset must be valid until the call of
|
|
// RenderSceneConverter::ConvertToRenderScene.
|
|
if (!tinyusdz::SetupUSDZAssetResolution(arr, &usdz_asset_)) {
|
|
std::cerr << "Failed to setup AssetResolution for USDZ asset\n";
|
|
loaded_ = false;
|
|
return false;
|
|
}
|
|
|
|
env.asset_resolver = arr;
|
|
} else {
|
|
tinyusdz::AssetResolutionResolver arr;
|
|
if (!SetupFetchAssetResolution(arr, &em_resolver_)) {
|
|
std::cerr << "Failed to setup FetchAssetResolution\n";
|
|
loaded_ = false;
|
|
return false;
|
|
}
|
|
|
|
env.asset_resolver = arr;
|
|
}
|
|
|
|
// RenderScene: Scene graph object which is suited for GL/Vulkan renderer
|
|
tinyusdz::tydra::RenderSceneConverter converter;
|
|
|
|
// env.timecode = timecode; // TODO
|
|
loaded_ = converter.ConvertToRenderScene(env, &render_scene_);
|
|
if (!loaded_) {
|
|
std::cerr << "Failed to convert USD Stage to RenderScene: \n"
|
|
<< converter.GetError() << "\n";
|
|
error_ = converter.GetError();
|
|
return false;
|
|
}
|
|
#else
|
|
return stageToRenderScene(stage, is_usdz, binary);
|
|
#endif
|
|
|
|
}
|
|
|
|
// u8 : Uint8Array object.
|
|
bool loadTest(const std::string &filename, const emscripten::val &u8) {
|
|
|
|
tinyusdz::TypedArray<uint8_t> binary;
|
|
detail::uint8arrayToBuffer(u8, binary);
|
|
std::cout << "binary.size = " << binary.size() << "\n";
|
|
|
|
//bool is_usdz = tinyusdz::IsUSDZ(
|
|
// reinterpret_cast<const uint8_t *>(binary.data()), binary.size());
|
|
|
|
tinyusdz::USDLoadOptions options;
|
|
options.max_memory_limit_in_mb = max_memory_limit_mb_;
|
|
|
|
#if 0
|
|
tinyusdz::Stage stage;
|
|
loaded_ = tinyusdz::LoadUSDFromMemory(
|
|
reinterpret_cast<const uint8_t *>(binary.data()), binary.size(),
|
|
filename, &stage, &warn_, &error_, options);
|
|
|
|
if (!loaded_) {
|
|
return false;
|
|
}
|
|
#else
|
|
std::cout << "layer\n";
|
|
tinyusdz::Layer layer;
|
|
loaded_ = tinyusdz::LoadLayerFromMemory(
|
|
reinterpret_cast<const uint8_t *>(binary.data()), binary.size(),
|
|
filename, &layer, &warn_, &error_, options);
|
|
|
|
if (!loaded_) {
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
loaded_as_layer_ = false;
|
|
filename_ = filename;
|
|
|
|
//std::cout << "loaded\n";
|
|
|
|
return true;
|
|
}
|
|
|
|
// Test function for value::Value memory usage estimation
|
|
// arrayLength: optional parameter to specify the size of array tests (default: 10000)
|
|
emscripten::val testValueMemoryUsage(emscripten::val arrayLengthVal) {
|
|
emscripten::val result = emscripten::val::object();
|
|
emscripten::val tests = emscripten::val::array();
|
|
|
|
// Get array length from parameter or use default
|
|
int arrayLength = 10000;
|
|
if (!arrayLengthVal.isUndefined() && !arrayLengthVal.isNull()) {
|
|
arrayLength = arrayLengthVal.as<int>();
|
|
}
|
|
|
|
// Test 1: Empty value
|
|
{
|
|
tinyusdz::value::Value v;
|
|
size_t mem = v.estimate_memory_usage();
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "Empty value");
|
|
test.set("bytes", mem);
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 2: Simple types
|
|
{
|
|
tinyusdz::value::Value v1(42); // int32
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "int32(42)");
|
|
test.set("bytes", v1.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
{
|
|
tinyusdz::value::Value v2(3.14f); // float
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "float(3.14)");
|
|
test.set("bytes", v2.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
{
|
|
tinyusdz::value::Value v3(2.718); // double
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "double(2.718)");
|
|
test.set("bytes", v3.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 3: Vector types
|
|
{
|
|
tinyusdz::value::float3 f3{1.0f, 2.0f, 3.0f};
|
|
tinyusdz::value::Value v(f3);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "float3");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 4: Matrix types
|
|
{
|
|
tinyusdz::value::matrix4d m4d;
|
|
tinyusdz::value::Value v(m4d);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "matrix4d");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 5: String type
|
|
{
|
|
std::string str = "Hello, World! This is a test string.";
|
|
tinyusdz::value::Value v(str);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "string('" + str + "')");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 6: Token type
|
|
{
|
|
tinyusdz::value::token tok("myToken");
|
|
tinyusdz::value::Value v(tok);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "token('myToken')");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 7: Array of floats
|
|
{
|
|
std::vector<float> floats = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
|
|
tinyusdz::value::Value v(floats);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "float array (5 elements)");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 8: Array of float3
|
|
{
|
|
std::vector<tinyusdz::value::float3> vec3s = {
|
|
{1.0f, 0.0f, 0.0f},
|
|
{0.0f, 1.0f, 0.0f},
|
|
{0.0f, 0.0f, 1.0f}
|
|
};
|
|
tinyusdz::value::Value v(vec3s);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "float3 array (3 elements)");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 9: Array of strings
|
|
{
|
|
std::vector<std::string> strings = {"one", "two", "three", "four"};
|
|
tinyusdz::value::Value v(strings);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "string array (4 elements)");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 10: Color types (role types)
|
|
{
|
|
tinyusdz::value::color3f c3f{1.0f, 0.5f, 0.0f};
|
|
tinyusdz::value::Value v(c3f);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "color3f");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 11: Normal types (role types)
|
|
{
|
|
tinyusdz::value::normal3f n3f{0.0f, 1.0f, 0.0f};
|
|
tinyusdz::value::Value v(n3f);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "normal3f");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 12: TimeSamples
|
|
{
|
|
tinyusdz::value::TimeSamples ts;
|
|
ts.add_sample(0.0, tinyusdz::value::Value(1.0f));
|
|
ts.add_sample(1.0, tinyusdz::value::Value(2.0f));
|
|
ts.add_sample(2.0, tinyusdz::value::Value(3.0f));
|
|
size_t mem = ts.estimate_memory_usage();
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "TimeSamples (3 samples)");
|
|
test.set("bytes", mem);
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 13: Large array test (using specified array length)
|
|
{
|
|
std::vector<float> large_array(arrayLength, 1.0f);
|
|
tinyusdz::value::Value v(large_array);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "float array (" + std::to_string(arrayLength) + " elements)");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 13b: Large float3 array test (using specified array length / 3)
|
|
{
|
|
int vec3Count = std::max(1, arrayLength / 3);
|
|
std::vector<tinyusdz::value::float3> large_vec3_array;
|
|
large_vec3_array.reserve(vec3Count);
|
|
for (int i = 0; i < vec3Count; ++i) {
|
|
large_vec3_array.push_back({static_cast<float>(i), static_cast<float>(i+1), static_cast<float>(i+2)});
|
|
}
|
|
tinyusdz::value::Value v(large_vec3_array);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "float3 array (" + std::to_string(vec3Count) + " elements)");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 13c: Large int array test (using specified array length)
|
|
{
|
|
std::vector<int32_t> large_int_array(arrayLength, 42);
|
|
tinyusdz::value::Value v(large_int_array);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "int32 array (" + std::to_string(arrayLength) + " elements)");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 14: Half precision types
|
|
{
|
|
tinyusdz::value::half h(tinyusdz::value::float_to_half_full(1.5f));
|
|
tinyusdz::value::Value v(h);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "half(1.5)");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Test 15: Quaternion types
|
|
{
|
|
tinyusdz::value::quatf q{0.0f, 0.0f, 0.0f, 1.0f};
|
|
tinyusdz::value::Value v(q);
|
|
emscripten::val test = emscripten::val::object();
|
|
test.set("name", "quatf");
|
|
test.set("bytes", v.estimate_memory_usage());
|
|
tests.call<void>("push", test);
|
|
}
|
|
|
|
// Calculate total memory
|
|
size_t totalMemory = 0;
|
|
int numTests = tests["length"].as<int>();
|
|
for (int i = 0; i < numTests; ++i) {
|
|
emscripten::val test = tests[i];
|
|
totalMemory += test["bytes"].as<size_t>();
|
|
}
|
|
|
|
result.set("tests", tests);
|
|
result.set("success", true);
|
|
result.set("totalTests", numTests);
|
|
result.set("totalMemory", totalMemory);
|
|
result.set("arrayLength", arrayLength);
|
|
|
|
return result;
|
|
}
|
|
|
|
emscripten::val testLayer(emscripten::val arrayLengthVal) {
|
|
|
|
// Get array length from parameter or use default
|
|
int arrayLength = 10000;
|
|
if (!arrayLengthVal.isUndefined() && !arrayLengthVal.isNull()) {
|
|
arrayLength = arrayLengthVal.as<int>();
|
|
}
|
|
|
|
std::cout << "arrayLen " << arrayLength << "\n";
|
|
#if 1
|
|
// create Attrib
|
|
std::vector<tinyusdz::value::point3f> points(arrayLength);
|
|
tinyusdz::Attribute attr;
|
|
attr.set_value(std::move(points));
|
|
|
|
std::cout << "Attr.memusage " << attr.estimate_memory_usage() << "\n";
|
|
size_t totalMemory = 0; //attr.estimate_memory_usage();
|
|
#else
|
|
tinyusdz::TypedArray<tinyusdz::value::point3f> points(arrayLength);
|
|
tinyusdz::Attribute attr;
|
|
//std::cout << "attr.set_value\n";
|
|
//attr.set_value(std::move(points));
|
|
|
|
tinyusdz::primvar::PrimVar var;
|
|
std::cout << "pvar";
|
|
var.set_value(std::move(points));
|
|
|
|
//std::vector<tinyusdz::value::point3f> points(arrayLength);
|
|
//tinyusdz::value::Value v(std::move(points));
|
|
size_t totalMemory = points.size() * sizeof(tinyusdz::value::point3f);
|
|
std::cout << "totalMemory " << totalMemory << "\n";
|
|
#endif
|
|
|
|
|
|
emscripten::val result = emscripten::val::object();
|
|
result.set("totalMemory", totalMemory);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
#if 0 // TODO: Remove
|
|
//
|
|
// Current limitation: can't specify usdz for USD to be composited(e.g. subLayer'ed, reference'ed)
|
|
// Toplevel USD can be USDZ.
|
|
//
|
|
bool loadAndCompositeFromBinary(const std::string &binary, const std::string &filename) {
|
|
|
|
std::cout << "loadAndComposite " << std::endl;
|
|
|
|
bool is_usdz = tinyusdz::IsUSDZ(
|
|
reinterpret_cast<const uint8_t *>(binary.c_str()), binary.size());
|
|
|
|
tinyusdz::Layer root_layer;
|
|
bool ret = tinyusdz::LoadLayerFromMemory(reinterpret_cast<const uint8_t*>(binary.data()), binary.size(), filename, &root_layer, &warn_, &error_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
tinyusdz::Stage stage;
|
|
stage.metas() = root_layer.metas();
|
|
|
|
std::string warn;
|
|
|
|
tinyusdz::AssetResolutionResolver resolver;
|
|
if (!SetupEMAssetResolution(resolver, &em_resolver_)) {
|
|
std::cerr << "Failed to setup FetchAssetResolution\n";
|
|
return false;
|
|
}
|
|
const std::string base_dir = "./"; // FIXME
|
|
resolver.set_current_working_path(base_dir);
|
|
resolver.set_search_paths({base_dir});
|
|
|
|
filename_ = filename;
|
|
|
|
// TODO: Control composition feature flag from JS layer.
|
|
CompositionFeatures comp_features;
|
|
constexpr int kMaxIteration = 32; // Reduce iterations for web
|
|
|
|
//
|
|
// LIVRPS strength ordering
|
|
// - [x] Local(subLayers)
|
|
// - [x] Inherits
|
|
// - [x] VariantSets
|
|
// - [x] References
|
|
// - [x] Payload
|
|
// - [ ] Specializes
|
|
//
|
|
|
|
tinyusdz::Layer src_layer = root_layer;
|
|
if (comp_features.subLayers) {
|
|
tinyusdz::Layer composited_layer;
|
|
if (!tinyusdz::CompositeSublayers(resolver, src_layer, &composited_layer, &warn_, &error_)) {
|
|
//std::cerr << "Failed to composite subLayers: " << err << "\n";
|
|
return false;
|
|
}
|
|
|
|
std::cout << "# `subLayers` composited\n";
|
|
//std::cout << composited_layer << "\n";
|
|
|
|
src_layer = std::move(composited_layer);
|
|
}
|
|
|
|
// TODO: Find more better way to Recursively resolve references/payload/variants
|
|
for (int i = 0; i < kMaxIteration; i++) {
|
|
|
|
bool has_unresolved = false;
|
|
|
|
if (comp_features.references) {
|
|
if (!src_layer.check_unresolved_references()) {
|
|
std::cout << "# iter " << i << ": no unresolved references.\n";
|
|
} else {
|
|
has_unresolved = true;
|
|
|
|
tinyusdz::Layer composited_layer;
|
|
if (!tinyusdz::CompositeReferences(resolver, src_layer, &composited_layer, &warn_, &error_)) {
|
|
return false;
|
|
}
|
|
|
|
|
|
src_layer = std::move(composited_layer);
|
|
}
|
|
}
|
|
|
|
|
|
if (comp_features.payload) {
|
|
if (!src_layer.check_unresolved_payload()) {
|
|
std::cout << "# iter " << i << ": no unresolved payload.\n";
|
|
} else {
|
|
has_unresolved = true;
|
|
|
|
tinyusdz::Layer composited_layer;
|
|
if (!tinyusdz::CompositePayload(resolver, src_layer, &composited_layer, &warn_, &error_)) {
|
|
return false;
|
|
}
|
|
|
|
src_layer = std::move(composited_layer);
|
|
}
|
|
}
|
|
|
|
if (comp_features.inherits) {
|
|
if (!src_layer.check_unresolved_inherits()) {
|
|
std::cout << "# iter " << i << ": no unresolved inherits.\n";
|
|
} else {
|
|
has_unresolved = true;
|
|
|
|
tinyusdz::Layer composited_layer;
|
|
if (!tinyusdz::CompositeInherits(src_layer, &composited_layer, &warn_, &error_)) {
|
|
return false;
|
|
}
|
|
|
|
src_layer = std::move(composited_layer);
|
|
}
|
|
}
|
|
|
|
if (comp_features.variantSets) {
|
|
if (!src_layer.check_unresolved_variant()) {
|
|
std::cout << "# iter " << i << ": no unresolved variant.\n";
|
|
} else {
|
|
has_unresolved = true;
|
|
|
|
tinyusdz::Layer composited_layer;
|
|
if (!tinyusdz::CompositeVariant(src_layer, &composited_layer, &warn_, &error_)) {
|
|
return false;
|
|
}
|
|
|
|
src_layer = std::move(composited_layer);
|
|
}
|
|
}
|
|
|
|
|
|
std::cout << "# has_unresolved_references: " << src_layer.check_unresolved_references() << "\n";
|
|
std::cout << "# all resolved? " << !has_unresolved << "\n";
|
|
|
|
if (!has_unresolved) {
|
|
std::cout << "# of composition iteration to resolve fully: " << (i + 1) << "\n";
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
tinyusdz::Stage comp_stage;
|
|
ret = LayerToStage(src_layer, &comp_stage, &warn_, &error_);
|
|
|
|
if (!ret) {
|
|
return false;
|
|
}
|
|
|
|
return stageToRenderScene(stage, is_usdz, binary);
|
|
}
|
|
#endif
|
|
|
|
|
|
int numMeshes() const { return render_scene_.meshes.size(); }
|
|
|
|
// Legacy method for backward compatibility
|
|
emscripten::val getMaterial(int mat_id) const {
|
|
// Default to JSON format for backward compatibility
|
|
return getMaterial(mat_id, "json");
|
|
}
|
|
|
|
// New method that supports format parameter (json or xml)
|
|
emscripten::val getMaterial(int mat_id, const std::string& format) const {
|
|
emscripten::val result = emscripten::val::object();
|
|
|
|
if (!loaded_) {
|
|
result.set("error", "Scene not loaded");
|
|
return result;
|
|
}
|
|
|
|
if (mat_id < 0 || mat_id >= render_scene_.materials.size()) {
|
|
result.set("error", "Invalid material ID");
|
|
return result;
|
|
}
|
|
|
|
const auto &material = render_scene_.materials[mat_id];
|
|
|
|
// Determine serialization format
|
|
tinyusdz::tydra::SerializationFormat serFormat;
|
|
if (format == "xml") {
|
|
serFormat = tinyusdz::tydra::SerializationFormat::XML;
|
|
} else if (format == "json") {
|
|
serFormat = tinyusdz::tydra::SerializationFormat::JSON;
|
|
} else {
|
|
// For backward compatibility, if format is not recognized,
|
|
// return the old format
|
|
if (format.empty() || format == "legacy") {
|
|
// Return legacy format for backward compatibility
|
|
emscripten::val mat = emscripten::val::object();
|
|
|
|
// Check if material has UsdPreviewSurface
|
|
if (!material.hasUsdPreviewSurface()) {
|
|
mat.set("error", "Material does not have UsdPreviewSurface shader");
|
|
return mat;
|
|
}
|
|
|
|
const auto &m = material;
|
|
const auto &shader = *m.surfaceShader;
|
|
|
|
mat.set("diffuseColor", shader.diffuseColor.value);
|
|
if (shader.diffuseColor.is_texture()) {
|
|
mat.set("diffuseColorTextureId", shader.diffuseColor.texture_id);
|
|
}
|
|
|
|
mat.set("emissiveColor", shader.emissiveColor.value);
|
|
if (shader.emissiveColor.is_texture()) {
|
|
mat.set("emissiveColorTextureId", shader.emissiveColor.texture_id);
|
|
}
|
|
|
|
mat.set("useSpecularWorkflow", shader.useSpecularWorkflow);
|
|
if (shader.useSpecularWorkflow) {
|
|
mat.set("specularColor", shader.specularColor.value);
|
|
if (shader.specularColor.is_texture()) {
|
|
mat.set("specularColorTextureId", shader.specularColor.texture_id);
|
|
}
|
|
} else {
|
|
mat.set("metallic", shader.metallic.value);
|
|
if (shader.metallic.is_texture()) {
|
|
mat.set("metallicTextureId", shader.metallic.texture_id);
|
|
}
|
|
}
|
|
|
|
mat.set("roughness", shader.roughness.value);
|
|
if (shader.roughness.is_texture()) {
|
|
mat.set("roughnessTextureId", shader.roughness.texture_id);
|
|
}
|
|
|
|
mat.set("clearcoat", shader.clearcoat.value);
|
|
if (shader.clearcoat.is_texture()) {
|
|
mat.set("clearcoatTextureId", shader.clearcoat.texture_id);
|
|
}
|
|
|
|
mat.set("clearcoatRoughness", shader.clearcoatRoughness.value);
|
|
if (shader.clearcoatRoughness.is_texture()) {
|
|
mat.set("clearcoatRoughnessTextureId", shader.clearcoatRoughness.texture_id);
|
|
}
|
|
|
|
mat.set("opacity", shader.opacity.value);
|
|
if (shader.opacity.is_texture()) {
|
|
mat.set("opacityTextureId", shader.opacity.texture_id);
|
|
}
|
|
|
|
mat.set("opacityThreshold", shader.opacityThreshold.value);
|
|
if (shader.opacityThreshold.is_texture()) {
|
|
mat.set("opacityThresholdTextureId", shader.opacityThreshold.texture_id);
|
|
}
|
|
|
|
mat.set("ior", shader.ior.value);
|
|
if (shader.ior.is_texture()) {
|
|
mat.set("iorTextureId", shader.ior.texture_id);
|
|
}
|
|
|
|
mat.set("normal", shader.normal.value);
|
|
if (shader.normal.is_texture()) {
|
|
mat.set("normalTextureId", shader.normal.texture_id);
|
|
}
|
|
|
|
mat.set("displacement", shader.displacement.value);
|
|
if (shader.displacement.is_texture()) {
|
|
mat.set("displacementTextureId", shader.displacement.texture_id);
|
|
}
|
|
|
|
mat.set("occlusion", shader.occlusion.value);
|
|
if (shader.occlusion.is_texture()) {
|
|
mat.set("occlusionTextureId", shader.occlusion.texture_id);
|
|
}
|
|
|
|
return mat;
|
|
}
|
|
|
|
result.set("error", "Unsupported format. Use 'json' or 'xml'");
|
|
return result;
|
|
}
|
|
|
|
// Use the new serialization function
|
|
auto serialized = tinyusdz::tydra::serializeMaterial(material, serFormat);
|
|
|
|
if (serialized.has_value()) {
|
|
result.set("data", serialized.value());
|
|
result.set("format", format);
|
|
} else {
|
|
result.set("error", serialized.error());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
emscripten::val getTexture(int tex_id) const {
|
|
emscripten::val tex = emscripten::val::object();
|
|
|
|
if (!loaded_) {
|
|
return tex;
|
|
}
|
|
|
|
if (tex_id >= render_scene_.textures.size()) {
|
|
return tex;
|
|
}
|
|
|
|
const auto &t = render_scene_.textures[tex_id];
|
|
|
|
tex.set("textureImageId", int(t.texture_image_id));
|
|
tex.set("wrapS", to_string(t.wrapS));
|
|
tex.set("wrapT", to_string(t.wrapT));
|
|
// TOOD: bias, scale, rot/scale/trans, etc
|
|
|
|
return tex;
|
|
}
|
|
|
|
emscripten::val getImage(int img_id) const {
|
|
emscripten::val img = emscripten::val::object();
|
|
|
|
if (!loaded_) {
|
|
return img;
|
|
}
|
|
|
|
if (img_id >= render_scene_.images.size()) {
|
|
return img;
|
|
}
|
|
|
|
const auto &i = render_scene_.images[img_id];
|
|
|
|
img.set("width", int(i.width));
|
|
img.set("height", int(i.height));
|
|
img.set("channels", int(i.channels));
|
|
img.set("uri", i.asset_identifier);
|
|
img.set("decoded", bool(i.decoded));
|
|
img.set("colorSpace", to_string(i.colorSpace));
|
|
img.set("usdColorSpace", to_string(i.usdColorSpace));
|
|
img.set("bufferId", int(i.buffer_id));
|
|
|
|
if ((i.buffer_id >= 0) && (i.buffer_id < render_scene_.buffers.size())) {
|
|
const auto &b = render_scene_.buffers[i.buffer_id];
|
|
|
|
// TODO: Support HDR
|
|
|
|
img.set("data",
|
|
emscripten::typed_memory_view(b.data.size(), b.data.data()));
|
|
}
|
|
|
|
return img;
|
|
}
|
|
|
|
emscripten::val getMesh(int mesh_id) const {
|
|
emscripten::val mesh = emscripten::val::object();
|
|
|
|
if (!loaded_) {
|
|
return mesh;
|
|
}
|
|
|
|
if (mesh_id >= render_scene_.meshes.size()) {
|
|
return mesh;
|
|
}
|
|
|
|
const tinyusdz::tydra::RenderMesh &rmesh =
|
|
render_scene_.meshes[size_t(mesh_id)];
|
|
|
|
//if (rmesh.has_indices()) {
|
|
const uint32_t *indices_ptr = rmesh.faceVertexIndices().data();
|
|
mesh.set("faceVertexIndices",
|
|
emscripten::typed_memory_view(rmesh.faceVertexIndices().size(),
|
|
indices_ptr));
|
|
const uint32_t *counts_ptr = rmesh.faceVertexCounts().data();
|
|
mesh.set("faceVertexCounts",
|
|
emscripten::typed_memory_view(rmesh.faceVertexCounts().size(),
|
|
counts_ptr));
|
|
//} else {
|
|
// // Assume all triangles and facevarying attributes.
|
|
// if (!rmesh.is_triangulated()) {
|
|
// TUSDZ_LOG_E("Mesh must be triangulated when the mesh doesn't have indices\n");
|
|
// return mesh;
|
|
// }
|
|
//}
|
|
|
|
// TODO: Use three.js scene description format?
|
|
mesh.set("primName", rmesh.prim_name);
|
|
mesh.set("displayName", rmesh.display_name);
|
|
mesh.set("absPath", rmesh.abs_path);
|
|
//mesh.set("hasIndices", rmesh.has_indices());
|
|
|
|
|
|
const float *points_ptr =
|
|
reinterpret_cast<const float *>(rmesh.points.data());
|
|
// vec3
|
|
mesh.set("points", emscripten::typed_memory_view(rmesh.points.size() * 3,
|
|
points_ptr));
|
|
|
|
if (!rmesh.normals.empty()) {
|
|
const float *normals_ptr =
|
|
reinterpret_cast<const float *>(rmesh.normals.data.data());
|
|
|
|
mesh.set("normals", emscripten::typed_memory_view(
|
|
rmesh.normals.vertex_count() * 3, normals_ptr));
|
|
}
|
|
|
|
{
|
|
// slot 0 hardcoded.
|
|
uint32_t uvSlotId = 0;
|
|
if (rmesh.texcoords.count(uvSlotId)) {
|
|
const float *uvs_ptr = reinterpret_cast<const float *>(
|
|
rmesh.texcoords.at(uvSlotId).data.data());
|
|
|
|
// assume vec2
|
|
mesh.set("texcoords",
|
|
emscripten::typed_memory_view(
|
|
rmesh.texcoords.at(uvSlotId).vertex_count() * 2, uvs_ptr));
|
|
}
|
|
}
|
|
|
|
if (!rmesh.tangents.empty()) {
|
|
const float *tangents_ptr =
|
|
reinterpret_cast<const float *>(rmesh.tangents.data.data());
|
|
|
|
mesh.set("tangents", emscripten::typed_memory_view(
|
|
rmesh.tangents.vertex_count() * 3, tangents_ptr));
|
|
}
|
|
|
|
mesh.set("materialId", rmesh.material_id);
|
|
mesh.set("doubleSided", rmesh.doubleSided);
|
|
|
|
return mesh;
|
|
}
|
|
|
|
int getDefaultRootNodeId() { return render_scene_.default_root_node; }
|
|
|
|
emscripten::val getDefaultRootNode() {
|
|
return getRootNode(getDefaultRootNodeId());
|
|
}
|
|
|
|
emscripten::val getRootNode(int idx) {
|
|
emscripten::val val = emscripten::val::object();
|
|
|
|
if ((idx < 0) || (idx >= render_scene_.nodes.size())) {
|
|
return val;
|
|
}
|
|
|
|
val = buildNodeRec(render_scene_.nodes[size_t(idx)]);
|
|
return val;
|
|
}
|
|
|
|
int numRootNodes() { return render_scene_.nodes.size(); }
|
|
|
|
void setEnableComposition(bool enabled) { enableComposition_ = enabled; }
|
|
void setLoadTextureInNative(bool onoff) {
|
|
loadTextureInNative_ = onoff;
|
|
}
|
|
|
|
void setMaxMemoryLimitMB(int32_t limit_mb) {
|
|
max_memory_limit_mb_ = limit_mb;
|
|
}
|
|
|
|
int32_t getMaxMemoryLimitMB() const {
|
|
return max_memory_limit_mb_;
|
|
}
|
|
|
|
emscripten::val getAssetSearchPaths() const {
|
|
emscripten::val arr = emscripten::val::array();
|
|
for (size_t i = 0; i < search_paths_.size(); i++) {
|
|
arr.call<void>("push", search_paths_[i]);
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
void setBaseWorkingPath(const std::string &path) {
|
|
base_dir_ = path;
|
|
}
|
|
|
|
std::string getBaseWorkingPath() const {
|
|
return base_dir_;
|
|
}
|
|
|
|
void clearAssetSearchPaths() {
|
|
search_paths_.clear();
|
|
}
|
|
|
|
void addAssetSearchPath(const std::string &path) {
|
|
search_paths_.push_back(path);
|
|
}
|
|
|
|
// Return filename passed to loadFromBinary/loadAsLayerFromBinary.
|
|
std::string getURI() const {
|
|
return filename_;
|
|
}
|
|
|
|
emscripten::val extractSublayerAssetPaths() {
|
|
emscripten::val arr = emscripten::val::array();
|
|
|
|
|
|
std::vector<std::string> paths = tinyusdz::ExtractSublayerAssetPaths(layer_);
|
|
for (size_t i = 0; i < paths.size(); i++) {
|
|
arr.call<void>("push", paths[i]);
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
emscripten::val extractReferencesAssetPaths() {
|
|
emscripten::val arr = emscripten::val::array();
|
|
|
|
|
|
std::vector<std::string> paths = tinyusdz::ExtractReferencesAssetPaths(layer_);
|
|
for (size_t i = 0; i < paths.size(); i++) {
|
|
arr.call<void>("push", paths[i]);
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
emscripten::val extractPayloadAssetPaths() {
|
|
emscripten::val arr = emscripten::val::array();
|
|
|
|
|
|
std::vector<std::string> paths = tinyusdz::ExtractPayloadAssetPaths(layer_);
|
|
for (size_t i = 0; i < paths.size(); i++) {
|
|
arr.call<void>("push", paths[i]);
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
bool hasSublayers() {
|
|
const tinyusdz::Layer &curr = composited_ ? composed_layer_ : layer_;
|
|
return curr.metas().subLayers.size();
|
|
}
|
|
|
|
|
|
bool composeSublayers() {
|
|
|
|
tinyusdz::AssetResolutionResolver resolver;
|
|
if (!SetupEMAssetResolution(resolver, &em_resolver_)) {
|
|
std::cerr << "Failed to setup EMAssetResolution\n";
|
|
return false;
|
|
}
|
|
const std::string base_dir = "./"; // FIXME
|
|
resolver.set_current_working_path(base_dir);
|
|
resolver.set_search_paths({base_dir});
|
|
|
|
if (composited_) {
|
|
layer_ = std::move(composed_layer_);
|
|
}
|
|
|
|
if (!tinyusdz::CompositeSublayers(resolver, layer_, &composed_layer_, &warn_, &error_)) {
|
|
std::cerr << "Failed to composite subLayers: \n";
|
|
if (composited_) {
|
|
// make 'layer_' and 'composed_layer_' invalid
|
|
loaded_as_layer_ = false;
|
|
composited_ = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
composited_ = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool hasReferences() {
|
|
return tinyusdz::HasReferences(composited_ ? composed_layer_ : layer_, /* force_check */true);
|
|
}
|
|
|
|
bool composeReferences() {
|
|
|
|
tinyusdz::AssetResolutionResolver resolver;
|
|
if (!SetupEMAssetResolution(resolver, &em_resolver_)) {
|
|
std::cerr << "Failed to setup EMAssetResolution\n";
|
|
return false;
|
|
}
|
|
const std::string base_dir = "./"; // FIXME
|
|
resolver.set_current_working_path(base_dir);
|
|
resolver.set_search_paths({base_dir});
|
|
|
|
|
|
if (composited_) {
|
|
layer_ = std::move(composed_layer_);
|
|
}
|
|
|
|
if (!tinyusdz::CompositeReferences(resolver, layer_, &composed_layer_, &warn_, &error_)) {
|
|
std::cerr << "Failed to composite references: \n";
|
|
if (composited_) {
|
|
// make 'layer_' and 'composed_layer_' invalid
|
|
loaded_as_layer_ = false;
|
|
composited_ = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
composited_ = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool hasPayload() {
|
|
return tinyusdz::HasPayload(layer_, /* force_check */true);
|
|
}
|
|
|
|
bool composePayload() {
|
|
|
|
tinyusdz::AssetResolutionResolver resolver;
|
|
if (!SetupEMAssetResolution(resolver, &em_resolver_)) {
|
|
std::cerr << "Failed to setup EMAssetResolution\n";
|
|
return false;
|
|
}
|
|
const std::string base_dir = "./"; // FIXME
|
|
resolver.set_current_working_path(base_dir);
|
|
resolver.set_search_paths({base_dir});
|
|
|
|
if (composited_) {
|
|
layer_ = std::move(composed_layer_);
|
|
}
|
|
|
|
if (!tinyusdz::CompositePayload(resolver, layer_, &composed_layer_, &warn_, &error_)) {
|
|
std::cerr << "Failed to composite payload: \n";
|
|
if (composited_) {
|
|
// make 'layer_' and 'composed_layer_' invalid
|
|
loaded_as_layer_ = false;
|
|
composited_ = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
composited_ = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool hasInherits() {
|
|
return tinyusdz::HasInherits(composited_ ? composed_layer_ : layer_ );
|
|
}
|
|
|
|
bool composeInherits() {
|
|
|
|
if (composited_) {
|
|
layer_ = std::move(composed_layer_);
|
|
}
|
|
|
|
if (!tinyusdz::CompositeInherits( layer_, &composed_layer_, &warn_, &error_)) {
|
|
std::cerr << "Failed to composite inherits: \n";
|
|
if (composited_) {
|
|
// make 'layer_' and 'composed_layer_' invalid
|
|
loaded_as_layer_ = false;
|
|
composited_ = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
composited_ = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool hasVariants() {
|
|
return tinyusdz::HasVariants(composited_ ? composed_layer_ : layer_ );
|
|
}
|
|
|
|
bool composeVariants() {
|
|
|
|
if (composited_) {
|
|
layer_ = std::move(composed_layer_);
|
|
}
|
|
|
|
if (!tinyusdz::CompositeVariant( layer_, &composed_layer_, &warn_, &error_)) {
|
|
std::cerr << "Failed to composite variant: \n";
|
|
if (composited_) {
|
|
// make 'layer_' and 'composed_layer_' invalid
|
|
loaded_as_layer_ = false;
|
|
composited_ = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
composited_ = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool layerToRenderScene() {
|
|
|
|
if (!loaded_as_layer_) {
|
|
std::cerr << "not loaded as layer\n";
|
|
return false;
|
|
}
|
|
|
|
tinyusdz::Stage stage;
|
|
|
|
const tinyusdz::Layer &curr = composited_ ? composed_layer_ : layer_;
|
|
|
|
if (!tinyusdz::LayerToStage(curr, &stage, &warn_, &error_)) {
|
|
std::cerr << "Failed to LayerToStage \n";
|
|
return false;
|
|
}
|
|
|
|
std::string empty;
|
|
return stageToRenderScene(stage, /* TODO: is_usdz*/false, empty);
|
|
|
|
}
|
|
|
|
std::string layerToString() const {
|
|
if (!loaded_) {
|
|
return std::string();
|
|
}
|
|
if (!loaded_as_layer_) {
|
|
return std::string();
|
|
}
|
|
|
|
const tinyusdz::Layer &curr = composited_ ? composed_layer_ : layer_;
|
|
|
|
return tinyusdz::to_string(curr);
|
|
}
|
|
|
|
void clearAssets() {
|
|
em_resolver_.clear();
|
|
}
|
|
|
|
void setAsset(const std::string &name, const std::string &binary) {
|
|
em_resolver_.add(name, binary);
|
|
}
|
|
|
|
// Streaming asset methods
|
|
bool startStreamingAsset(const std::string &name, size_t expected_size) {
|
|
return em_resolver_.startStreamingAsset(name, expected_size);
|
|
}
|
|
|
|
bool appendAssetChunk(const std::string &name, const std::string &chunk) {
|
|
return em_resolver_.appendAssetChunk(name, chunk);
|
|
}
|
|
|
|
bool finalizeStreamingAsset(const std::string &name) {
|
|
return em_resolver_.finalizeStreamingAsset(name);
|
|
}
|
|
|
|
bool isStreamingAssetComplete(const std::string &name) const {
|
|
return em_resolver_.isStreamingAssetComplete(name);
|
|
}
|
|
|
|
emscripten::val getStreamingProgress(const std::string &name) const {
|
|
return em_resolver_.getStreamingProgress(name);
|
|
}
|
|
|
|
bool hasAsset(const std::string &name) const {
|
|
return em_resolver_.has(name);
|
|
}
|
|
|
|
std::string getAssetHash(const std::string &name) const {
|
|
return em_resolver_.getHash(name);
|
|
}
|
|
|
|
bool verifyAssetHash(const std::string &name, const std::string &expected_hash) const {
|
|
return em_resolver_.verifyHash(name, expected_hash);
|
|
}
|
|
|
|
emscripten::val getAsset(const std::string &name) const {
|
|
emscripten::val val = emscripten::val::object();
|
|
if (em_resolver_.has(name)) {
|
|
const AssetCacheEntry &entry = em_resolver_.get(name);
|
|
val.set("name", name);
|
|
val.set("data", emscripten::typed_memory_view(entry.binary.size(), entry.binary.data()));
|
|
val.set("sha256", entry.sha256_hash);
|
|
val.set("uuid", entry.uuid);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
std::string getAssetUUID(const std::string &name) const {
|
|
return em_resolver_.getUUID(name);
|
|
}
|
|
|
|
std::string getStreamingAssetUUID(const std::string &name) const {
|
|
return em_resolver_.getStreamingUUID(name);
|
|
}
|
|
|
|
emscripten::val getAllAssetUUIDs() const {
|
|
return em_resolver_.getAssetUUIDs();
|
|
}
|
|
|
|
std::string findAssetByUUID(const std::string &uuid) const {
|
|
return em_resolver_.findAssetByUUID(uuid);
|
|
}
|
|
|
|
// Get asset by UUID instead of name
|
|
emscripten::val getAssetByUUID(const std::string &uuid) const {
|
|
emscripten::val val = emscripten::val::object();
|
|
|
|
if (!em_resolver_.hasByUUID(uuid)) {
|
|
val.set("error", "Asset not found with UUID: " + uuid);
|
|
return val;
|
|
}
|
|
|
|
const AssetCacheEntry &entry = em_resolver_.getByUUID(uuid);
|
|
const std::string name = em_resolver_.findAssetByUUID(uuid);
|
|
|
|
val.set("name", name);
|
|
val.set("data", emscripten::typed_memory_view(entry.binary.size(),
|
|
reinterpret_cast<const uint8_t*>(entry.binary.data())));
|
|
val.set("sha256", entry.sha256_hash);
|
|
val.set("uuid", entry.uuid);
|
|
|
|
return val;
|
|
}
|
|
|
|
// Delete asset by name or UUID
|
|
bool deleteAsset(const std::string &nameOrUuid) {
|
|
// First try to delete by name
|
|
if (em_resolver_.deleteAsset(nameOrUuid)) {
|
|
return true;
|
|
}
|
|
|
|
// If not found by name, try to delete by UUID
|
|
return em_resolver_.deleteAssetByUUID(nameOrUuid);
|
|
}
|
|
|
|
// Delete asset specifically by UUID
|
|
bool deleteAssetByUUID(const std::string &uuid) {
|
|
return em_resolver_.deleteAssetByUUID(uuid);
|
|
}
|
|
|
|
// Delete asset specifically by name
|
|
bool deleteAssetByName(const std::string &name) {
|
|
return em_resolver_.deleteAsset(name);
|
|
}
|
|
|
|
// Get number of cached assets
|
|
size_t getAssetCount() const {
|
|
return em_resolver_.cache.size();
|
|
}
|
|
|
|
// Check if asset exists (by name or UUID)
|
|
bool assetExists(const std::string &nameOrUuid) const {
|
|
return em_resolver_.has(nameOrUuid) || em_resolver_.hasByUUID(nameOrUuid);
|
|
}
|
|
|
|
emscripten::val getAssetCacheDataAsMemoryView(const std::string &name) const {
|
|
return em_resolver_.getCacheDataAsMemoryView(name);
|
|
}
|
|
|
|
bool setAssetFromRawPointer(const std::string &name, uintptr_t dataPtr, size_t size) {
|
|
return em_resolver_.addFromRawPointer(name, dataPtr, size);
|
|
}
|
|
|
|
emscripten::val extractUnresolvedTexturePaths() const {
|
|
emscripten::val val;
|
|
|
|
// Assume USD is converted to RenderScene before calling this function.
|
|
|
|
for (const tinyusdz::tydra::TextureImage &texImg : render_scene_.images) {
|
|
//
|
|
if (texImg.buffer_id == -1) {
|
|
std::string path = texImg.asset_identifier;
|
|
val.call<void>("push", path);
|
|
}
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
bool mcpCreateContext(const std::string &session_id) {
|
|
|
|
if (mcp_ctx_.count(session_id)) {
|
|
// Context already exists
|
|
return false;
|
|
}
|
|
|
|
mcp_ctx_[session_id] = tinyusdz::tydra::mcp::Context();
|
|
mcp_session_id_ = session_id;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool mcpSelectContext(const std::string &session_id) {
|
|
|
|
if (!mcp_ctx_.count(session_id)) {
|
|
// Context does not exist
|
|
return false;
|
|
}
|
|
|
|
mcp_session_id_ = session_id;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// return JSON string
|
|
std::string mcpToolsList() {
|
|
|
|
if (!mcp_ctx_.count(mcp_session_id_)) {
|
|
// TODO: better error message
|
|
return "{ \"error\": \"invalid session_id\"}";
|
|
}
|
|
|
|
//Context &ctx = mcp_ctx_.at(mcp_session_id_);
|
|
tinyusdz::tydra::mcp::Context &ctx = mcp_global_ctx_;
|
|
|
|
nlohmann::json result;
|
|
if (!tinyusdz::tydra::mcp::GetToolsList(ctx, result)) {
|
|
std::cerr << "[tydra:mcp:GetToolsList] failed." << "\n";
|
|
// TODO: Report error more nice way.
|
|
result = nlohmann::json::object();
|
|
result["isError"] = true;
|
|
result["content"] = nlohmann::json::array();
|
|
}
|
|
|
|
std::string s_result = result.dump();
|
|
|
|
return s_result;
|
|
}
|
|
|
|
// args: JSON string
|
|
// return JSON string
|
|
std::string mcpToolsCall(const std::string &tool_name, const std::string &args) {
|
|
|
|
if (!mcp_ctx_.count(mcp_session_id_)) {
|
|
// TODO: better error message
|
|
return "{ \"error\": \"invalid session_id\"}";
|
|
}
|
|
|
|
nlohmann::json j_args = nlohmann::json::parse(args);
|
|
|
|
//Context &ctx = mcp_ctx_.at(mcp_session_id_);
|
|
auto &ctx = mcp_global_ctx_;
|
|
|
|
nlohmann::json result;
|
|
|
|
std::string err;
|
|
if (!tinyusdz::tydra::mcp::CallTool(ctx, tool_name, j_args, result, err)) {
|
|
// TODO: Report error more nice way.
|
|
std::cerr << "[tydra:mcp:CallTool]" << err << "\n";
|
|
result = nlohmann::json::object();
|
|
result["isError"] = true;
|
|
result["content"] = nlohmann::json::array();
|
|
|
|
nlohmann::json e;
|
|
e["type"] = "text";
|
|
|
|
nlohmann::json msg;
|
|
msg["error"] = err;
|
|
e["text"] = msg.dump();
|
|
|
|
result["content"].push_back(e);
|
|
}
|
|
|
|
std::string s_result = result.dump();
|
|
|
|
return s_result;
|
|
}
|
|
|
|
std::string mcpResourcesList() {
|
|
|
|
if (!mcp_ctx_.count(mcp_session_id_)) {
|
|
// TODO: better error message
|
|
return "{ \"error\": \"invalid session_id\"}";
|
|
}
|
|
|
|
//Context &ctx = mcp_ctx_.at(mcp_session_id_);
|
|
auto &ctx = mcp_global_ctx_;
|
|
|
|
nlohmann::json result;
|
|
|
|
if (!tinyusdz::tydra::mcp::GetResourcesList(ctx, result)) {
|
|
// TODO: Report error more nice way.
|
|
std::cerr << "[tydra:mcp:ListResources] failed\n";
|
|
result = nlohmann::json::object();
|
|
result["isError"] = true;
|
|
//result["content"] = nlohmann::json::array();
|
|
}
|
|
|
|
std::string s_result = result.dump();
|
|
|
|
return s_result;
|
|
}
|
|
|
|
std::string mcpResourcesRead(const std::string &uri) {
|
|
|
|
if (!mcp_ctx_.count(mcp_session_id_)) {
|
|
// TODO: better error message
|
|
return "{ \"error\": \"invalid session_id\"}";
|
|
}
|
|
|
|
//Context &ctx = mcp_ctx_.at(mcp_session_id_);
|
|
auto &ctx = mcp_global_ctx_;
|
|
|
|
nlohmann::json content;
|
|
|
|
if (!tinyusdz::tydra::mcp::ReadResource(ctx, uri, content)) {
|
|
// TODO: Report error more nice way.
|
|
std::cerr << "[tydra:mcp:ReadResources] failed\n";
|
|
content = nlohmann::json::object();
|
|
content["isError"] = true;
|
|
//content["content"] = nlohmann::json::array();
|
|
}
|
|
|
|
std::string s_content = content.dump();
|
|
|
|
return s_content;
|
|
}
|
|
|
|
// JSON <-> USD Layer conversion methods
|
|
std::string layerToJSON() const {
|
|
if (!loaded_as_layer_) {
|
|
return "{\"error\": \"No layer loaded\"}";
|
|
}
|
|
|
|
const tinyusdz::Layer &curr = composited_ ? composed_layer_ : layer_;
|
|
|
|
nlohmann::json json_obj = tinyusdz::ToJSON(curr);
|
|
return json_obj.dump(2); // Pretty print with 2 spaces
|
|
}
|
|
|
|
std::string layerToJSONWithOptions(bool embedBuffers, const std::string& arrayMode) const {
|
|
if (!loaded_as_layer_) {
|
|
return "{\"error\": \"No layer loaded\"}";
|
|
}
|
|
|
|
const tinyusdz::Layer &curr = composited_ ? composed_layer_ : layer_;
|
|
|
|
tinyusdz::USDToJSONOptions options;
|
|
options.embedBuffers = embedBuffers;
|
|
|
|
if (arrayMode == "buffer") {
|
|
options.arrayMode = tinyusdz::ArraySerializationMode::Buffer;
|
|
} else {
|
|
options.arrayMode = tinyusdz::ArraySerializationMode::Base64;
|
|
}
|
|
|
|
std::string json_str, warn, err;
|
|
bool success = tinyusdz::to_json_string(curr, options, &json_str, &warn, &err);
|
|
|
|
if (!success) {
|
|
return "{\"error\": \"Failed to convert layer to JSON: " + err + "\"}";
|
|
}
|
|
|
|
return json_str;
|
|
}
|
|
|
|
bool loadLayerFromJSON(const std::string& json_string) {
|
|
std::string warn, err;
|
|
|
|
bool success = tinyusdz::JSONToLayer(json_string, &layer_, &warn, &err);
|
|
|
|
if (success) {
|
|
loaded_ = true;
|
|
loaded_as_layer_ = true;
|
|
composited_ = false;
|
|
warn_ = warn;
|
|
error_.clear();
|
|
filename_ = "from_json.usd";
|
|
} else {
|
|
loaded_ = false;
|
|
loaded_as_layer_ = false;
|
|
composited_ = false;
|
|
warn_ = warn;
|
|
error_ = err;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// TODO: Deprecate
|
|
bool ok() const { return loaded_; }
|
|
|
|
const std::string &error() const { return error_; }
|
|
const std::string &warn() const { return warn_; }
|
|
|
|
private:
|
|
|
|
|
|
// Simple glTF-like Node
|
|
emscripten::val buildNodeRec(const tinyusdz::tydra::Node &rnode) {
|
|
emscripten::val node = emscripten::val::object();
|
|
|
|
node.set("primName", rnode.prim_name);
|
|
node.set("displayName", rnode.display_name);
|
|
node.set("absPath", rnode.abs_path);
|
|
|
|
std::string nodeTypeStr = to_string(rnode.nodeType);
|
|
node.set("nodeType", nodeTypeStr);
|
|
|
|
node.set("contentId",
|
|
rnode.id); // e.g. index to Mesh if nodeType == 'mesh'
|
|
|
|
std::array<double, 16> localMatrix = detail::toArray(rnode.local_matrix);
|
|
std::array<double, 16> globalMatrix = detail::toArray(rnode.global_matrix);
|
|
|
|
node.set("localMatrix", localMatrix);
|
|
node.set("globalMatrix", globalMatrix);
|
|
node.set("hasResetXform", rnode.has_resetXform);
|
|
|
|
emscripten::val children = emscripten::val::array();
|
|
|
|
for (const tinyusdz::tydra::Node &child : rnode.children) {
|
|
emscripten::val child_val = buildNodeRec(child);
|
|
|
|
children.call<void>("push", child_val);
|
|
}
|
|
|
|
node.set("children", children);
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
bool loaded_{false};
|
|
bool loaded_as_layer_{false};
|
|
bool enableComposition_{false};
|
|
bool loadTextureInNative_{false}; // true: Let JavaScript to decode texture image.
|
|
|
|
// Set appropriate default memory limits based on WASM architecture
|
|
#ifdef TINYUSDZ_WASM_MEMORY64
|
|
int32_t max_memory_limit_mb_{8192}; // 8GB for MEMORY64
|
|
#else
|
|
int32_t max_memory_limit_mb_{2048}; // 2GB for 32-bit WASM
|
|
#endif
|
|
|
|
std::string filename_;
|
|
std::string warn_;
|
|
std::string error_;
|
|
|
|
tinyusdz::Layer layer_;
|
|
tinyusdz::Layer composed_layer_;
|
|
bool composited_{false};
|
|
std::vector<std::string> search_paths_;
|
|
std::string base_dir_{"./"};
|
|
|
|
tinyusdz::tydra::RenderScene render_scene_;
|
|
tinyusdz::USDZAsset usdz_asset_;
|
|
EMAssetResolutionResolver em_resolver_;
|
|
|
|
// key = session_id
|
|
std::unordered_map<std::string, tinyusdz::tydra::mcp::Context> mcp_ctx_;
|
|
std::string mcp_session_id_;
|
|
|
|
tinyusdz::tydra::mcp::Context mcp_global_ctx_;
|
|
};
|
|
|
|
///
|
|
/// USD composition
|
|
///
|
|
class TinyUSDZComposerNative {
|
|
public:
|
|
// Default constructor for async loading
|
|
TinyUSDZComposerNative() : loaded_(false) {}
|
|
|
|
bool loaded() const { return loaded_; }
|
|
const std::string &error() const { return error_; }
|
|
|
|
private:
|
|
bool loaded_{false};
|
|
std::string warn_;
|
|
std::string error_;
|
|
|
|
tinyusdz::Layer root_layer_;
|
|
};
|
|
|
|
#if 0
|
|
// Helper to register std::array
|
|
namespace emscripten {
|
|
namespace internal {
|
|
template<typename T, size_t N>
|
|
struct TypeID<std::array<T, N>> {
|
|
static constexpr TYPEID get() {
|
|
return TypeID<val>::get();
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// Convert std::array<float, 3> to/from JavaScript array
|
|
namespace emscripten {
|
|
namespace internal {
|
|
template<>
|
|
struct BindingType<std::array<float, 3>> {
|
|
typedef std::array<float, 3> WireType;
|
|
static WireType toWireType(const std::array<float, 3>& arr) {
|
|
return arr;
|
|
}
|
|
static std::array<float, 3> fromWireType(const WireType& arr) {
|
|
return arr;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// TODO: quaternion type.
|
|
|
|
// Register STL
|
|
EMSCRIPTEN_BINDINGS(stl_wrappters) {
|
|
register_vector<float>("VectorFloat");
|
|
register_vector<int16_t>("VectorInt16");
|
|
register_vector<uint16_t>("VectorUInt16");
|
|
register_vector<uint32_t>("VectorUInt");
|
|
register_vector<int>("VectorInt");
|
|
register_vector<std::string>("VectorString");
|
|
}
|
|
|
|
// Register the array type
|
|
EMSCRIPTEN_BINDINGS(array_bindings) {
|
|
value_array<std::array<int16_t, 2>>("Short2Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>());
|
|
value_array<std::array<int16_t, 3>>("Short3Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>());
|
|
value_array<std::array<int16_t, 4>>("Short4Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>());
|
|
|
|
value_array<std::array<uint16_t, 2>>("UShort2Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>());
|
|
value_array<std::array<uint16_t, 3>>("UShort3Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>());
|
|
value_array<std::array<uint16_t, 4>>("UShort4Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>());
|
|
|
|
value_array<std::array<int, 2>>("Int2Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>());
|
|
value_array<std::array<int, 3>>("Int3Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>());
|
|
value_array<std::array<int, 4>>("Int4Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>());
|
|
|
|
value_array<std::array<uint32_t, 2>>("UInt2Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>());
|
|
value_array<std::array<uint32_t, 3>>("UInt3Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>());
|
|
value_array<std::array<uint32_t, 4>>("UInt4Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>());
|
|
|
|
value_array<std::array<float, 2>>("Float2Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>());
|
|
value_array<std::array<float, 3>>("Float3Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>());
|
|
value_array<std::array<float, 4>>("Float4Array")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>());
|
|
|
|
// for mat33
|
|
value_array<std::array<float, 9>>("Mat33")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>())
|
|
.element(emscripten::index<4>())
|
|
.element(emscripten::index<5>())
|
|
.element(emscripten::index<6>())
|
|
.element(emscripten::index<7>())
|
|
.element(emscripten::index<8>());
|
|
|
|
value_array<std::array<double, 9>>("DMat33")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>())
|
|
.element(emscripten::index<4>())
|
|
.element(emscripten::index<5>())
|
|
.element(emscripten::index<6>())
|
|
.element(emscripten::index<7>())
|
|
.element(emscripten::index<8>());
|
|
|
|
// for mat44
|
|
value_array<std::array<float, 16>>("Mat44")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>())
|
|
.element(emscripten::index<4>())
|
|
.element(emscripten::index<5>())
|
|
.element(emscripten::index<6>())
|
|
.element(emscripten::index<7>())
|
|
.element(emscripten::index<8>())
|
|
.element(emscripten::index<9>())
|
|
.element(emscripten::index<10>())
|
|
.element(emscripten::index<11>())
|
|
.element(emscripten::index<12>())
|
|
.element(emscripten::index<13>())
|
|
.element(emscripten::index<14>())
|
|
.element(emscripten::index<15>());
|
|
|
|
value_array<std::array<double, 16>>("DMat44")
|
|
.element(emscripten::index<0>())
|
|
.element(emscripten::index<1>())
|
|
.element(emscripten::index<2>())
|
|
.element(emscripten::index<3>())
|
|
.element(emscripten::index<4>())
|
|
.element(emscripten::index<5>())
|
|
.element(emscripten::index<6>())
|
|
.element(emscripten::index<7>())
|
|
.element(emscripten::index<8>())
|
|
.element(emscripten::index<9>())
|
|
.element(emscripten::index<10>())
|
|
.element(emscripten::index<11>())
|
|
.element(emscripten::index<12>())
|
|
.element(emscripten::index<13>())
|
|
.element(emscripten::index<14>())
|
|
.element(emscripten::index<15>());
|
|
}
|
|
|
|
EMSCRIPTEN_BINDINGS(tinyusdz_module) {
|
|
class_<TinyUSDZLoaderNative>("TinyUSDZLoaderNative")
|
|
.constructor<>() // Default constructor for async loading
|
|
//.constructor<const std::string &>() // Keep original for compatibility
|
|
#if defined(TINYUSDZ_WASM_ASYNCIFY)
|
|
.function("loadAsync", &TinyUSDZLoaderNative::loadAsync)
|
|
#endif
|
|
.function("loadAsLayerFromBinary", &TinyUSDZLoaderNative::loadAsLayerFromBinary)
|
|
.function("loadFromBinary", &TinyUSDZLoaderNative::loadFromBinary)
|
|
.function("loadTest", &TinyUSDZLoaderNative::loadTest)
|
|
.function("testValueMemoryUsage", &TinyUSDZLoaderNative::testValueMemoryUsage)
|
|
.function("testLayer", &TinyUSDZLoaderNative::testLayer)
|
|
//.function("loadAndCompositeFromBinary", &TinyUSDZLoaderNative::loadFromBinary)
|
|
|
|
// For Stage
|
|
.function("extractUnresolvedTexturePaths", &TinyUSDZLoaderNative::extractUnresolvedTexturePaths)
|
|
.function("getURI", &TinyUSDZLoaderNative::getURI)
|
|
.function("getMesh", &TinyUSDZLoaderNative::getMesh)
|
|
.function("numMeshes", &TinyUSDZLoaderNative::numMeshes)
|
|
.function("getMaterial", select_overload<emscripten::val(int) const>(&TinyUSDZLoaderNative::getMaterial))
|
|
.function("getMaterialWithFormat", select_overload<emscripten::val(int, const std::string&) const>(&TinyUSDZLoaderNative::getMaterial))
|
|
.function("getTexture", &TinyUSDZLoaderNative::getTexture)
|
|
.function("getImage", &TinyUSDZLoaderNative::getImage)
|
|
.function("getDefaultRootNodeId",
|
|
&TinyUSDZLoaderNative::getDefaultRootNodeId)
|
|
.function("getRootNode", &TinyUSDZLoaderNative::getRootNode)
|
|
.function("getDefaultRootNode", &TinyUSDZLoaderNative::getDefaultRootNode)
|
|
.function("numRootNodes", &TinyUSDZLoaderNative::numRootNodes)
|
|
.function("setLoadTextureInNative",
|
|
&TinyUSDZLoaderNative::setLoadTextureInNative)
|
|
|
|
.function("setMaxMemoryLimitMB",
|
|
&TinyUSDZLoaderNative::setMaxMemoryLimitMB)
|
|
.function("getMaxMemoryLimitMB",
|
|
&TinyUSDZLoaderNative::getMaxMemoryLimitMB)
|
|
|
|
.function("setEnableComposition",
|
|
&TinyUSDZLoaderNative::setEnableComposition)
|
|
.function("extractSublayerAssetPaths",
|
|
&TinyUSDZLoaderNative::extractSublayerAssetPaths)
|
|
.function("extractReferencesAssetPaths",
|
|
&TinyUSDZLoaderNative::extractReferencesAssetPaths)
|
|
.function("extractPayloadAssetPaths",
|
|
&TinyUSDZLoaderNative::extractPayloadAssetPaths)
|
|
|
|
.function("composeSublayers",
|
|
&TinyUSDZLoaderNative::composeSublayers)
|
|
|
|
.function("hasReferences",
|
|
&TinyUSDZLoaderNative::hasReferences)
|
|
|
|
.function("composeReferences",
|
|
&TinyUSDZLoaderNative::composeReferences)
|
|
|
|
.function("hasPayload",
|
|
&TinyUSDZLoaderNative::hasPayload)
|
|
|
|
.function("composePayload",
|
|
&TinyUSDZLoaderNative::composePayload)
|
|
|
|
.function("hasInherits",
|
|
&TinyUSDZLoaderNative::hasInherits)
|
|
|
|
.function("composeInherits",
|
|
&TinyUSDZLoaderNative::composeInherits)
|
|
|
|
// TODO: nested variants
|
|
.function("hasVariants",
|
|
&TinyUSDZLoaderNative::hasInherits)
|
|
|
|
.function("composeVariants",
|
|
&TinyUSDZLoaderNative::composeVariants)
|
|
|
|
.function("layerToRenderScene",
|
|
&TinyUSDZLoaderNative::layerToRenderScene)
|
|
|
|
|
|
.function("setAsset",
|
|
&TinyUSDZLoaderNative::setAsset)
|
|
.function("startStreamingAsset",
|
|
&TinyUSDZLoaderNative::startStreamingAsset)
|
|
.function("appendAssetChunk",
|
|
&TinyUSDZLoaderNative::appendAssetChunk)
|
|
.function("finalizeStreamingAsset",
|
|
&TinyUSDZLoaderNative::finalizeStreamingAsset)
|
|
.function("isStreamingAssetComplete",
|
|
&TinyUSDZLoaderNative::isStreamingAssetComplete)
|
|
.function("getStreamingProgress",
|
|
&TinyUSDZLoaderNative::getStreamingProgress)
|
|
.function("hasAsset",
|
|
&TinyUSDZLoaderNative::hasAsset)
|
|
.function("getAsset",
|
|
&TinyUSDZLoaderNative::getAsset)
|
|
.function("getAssetCacheDataAsMemoryView",
|
|
&TinyUSDZLoaderNative::getAssetCacheDataAsMemoryView)
|
|
.function("setAssetFromRawPointer",
|
|
&TinyUSDZLoaderNative::setAssetFromRawPointer, emscripten::allow_raw_pointers())
|
|
.function("getAssetHash",
|
|
&TinyUSDZLoaderNative::getAssetHash)
|
|
.function("verifyAssetHash",
|
|
&TinyUSDZLoaderNative::verifyAssetHash)
|
|
.function("getAssetUUID",
|
|
&TinyUSDZLoaderNative::getAssetUUID)
|
|
.function("getStreamingAssetUUID",
|
|
&TinyUSDZLoaderNative::getStreamingAssetUUID)
|
|
.function("getAllAssetUUIDs",
|
|
&TinyUSDZLoaderNative::getAllAssetUUIDs)
|
|
.function("findAssetByUUID",
|
|
&TinyUSDZLoaderNative::findAssetByUUID)
|
|
.function("getAssetByUUID",
|
|
&TinyUSDZLoaderNative::getAssetByUUID)
|
|
.function("deleteAsset",
|
|
&TinyUSDZLoaderNative::deleteAsset)
|
|
.function("deleteAssetByUUID",
|
|
&TinyUSDZLoaderNative::deleteAssetByUUID)
|
|
.function("deleteAssetByName",
|
|
&TinyUSDZLoaderNative::deleteAssetByName)
|
|
.function("getAssetCount",
|
|
&TinyUSDZLoaderNative::getAssetCount)
|
|
.function("assetExists",
|
|
&TinyUSDZLoaderNative::assetExists)
|
|
.function("clearAssets",
|
|
&TinyUSDZLoaderNative::clearAssets)
|
|
|
|
.function("layerToString",
|
|
&TinyUSDZLoaderNative::layerToString)
|
|
|
|
// JSON conversion methods
|
|
.function("layerToJSON",
|
|
&TinyUSDZLoaderNative::layerToJSON)
|
|
.function("layerToJSONWithOptions",
|
|
&TinyUSDZLoaderNative::layerToJSONWithOptions)
|
|
.function("loadLayerFromJSON",
|
|
&TinyUSDZLoaderNative::loadLayerFromJSON)
|
|
|
|
.function("setBaseWorkingPath", &TinyUSDZLoaderNative::setBaseWorkingPath)
|
|
.function("getBaseWorkingPath", &TinyUSDZLoaderNative::getBaseWorkingPath)
|
|
.function("clearAssetSearchPaths", &TinyUSDZLoaderNative::clearAssetSearchPaths)
|
|
.function("addAssetSearchPath", &TinyUSDZLoaderNative::addAssetSearchPath)
|
|
.function("getAssetSearchPaths", &TinyUSDZLoaderNative::getAssetSearchPaths)
|
|
|
|
|
|
// MCP
|
|
.function("mcpCreateContext", &TinyUSDZLoaderNative::mcpCreateContext)
|
|
.function("mcpSelectContext", &TinyUSDZLoaderNative::mcpSelectContext)
|
|
.function("mcpResourcesList", &TinyUSDZLoaderNative::mcpResourcesList)
|
|
.function("mcpResourcesRead", &TinyUSDZLoaderNative::mcpResourcesRead)
|
|
.function("mcpToolsList", &TinyUSDZLoaderNative::mcpToolsList)
|
|
.function("mcpToolsCall", &TinyUSDZLoaderNative::mcpToolsCall)
|
|
|
|
.function("ok", &TinyUSDZLoaderNative::ok)
|
|
.function("error", &TinyUSDZLoaderNative::error);
|
|
|
|
class_<TinyUSDZComposerNative>("TinyUSDZComposerNative")
|
|
.constructor<>() // Default constructor for async loading
|
|
.function("ok", &TinyUSDZComposerNative::loaded)
|
|
.function("error", &TinyUSDZComposerNative::error);
|
|
}
|
|
|