mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Merge branch 'anim-mtlx-phase3-fix3' into skinning
This commit is contained in:
@@ -130,7 +130,8 @@ The `MtlxOpenPBRSurface` shader supports all OpenPBR specification parameters:
|
||||
### Subsurface
|
||||
- `subsurface_weight` (float)
|
||||
- `subsurface_color` (color3)
|
||||
- `subsurface_radius` (color3)
|
||||
- `subsurface_radius` (float)
|
||||
- `subsurface_radius_scale` (color3)
|
||||
- `subsurface_scale` (float)
|
||||
- `subsurface_anisotropy` (float)
|
||||
|
||||
|
||||
@@ -89,7 +89,8 @@ Subsurface scattering simulates light penetrating and scattering beneath the sur
|
||||
|-------------------|------------------------|------|---------|------------------|------------------|-------|
|
||||
| `subsurface_weight` | `inputs:subsurface_weight` | float | 0.0 | ❌ | — | **NOT SUPPORTED** |
|
||||
| `subsurface_color` | `inputs:subsurface_color` | color3f | (0.8, 0.8, 0.8) | ❌ | — | **NOT SUPPORTED** |
|
||||
| `subsurface_radius` | `inputs:subsurface_radius` | color3f | (1.0, 1.0, 1.0) | ❌ | — | **NOT SUPPORTED** |
|
||||
| `subsurface_radius` | `inputs:subsurface_radius` | float | 1.0 | ❌ | — | **NOT SUPPORTED** |
|
||||
| `subsurface_radius_scale` | `inputs:subsurface_radius_scale` | color3f | (1.0, 1.0, 1.0) | ❌ | — | **NOT SUPPORTED** |
|
||||
| `subsurface_scale` | `inputs:subsurface_scale` | float | 1.0 | ❌ | — | **NOT SUPPORTED** |
|
||||
| `subsurface_anisotropy` | `inputs:subsurface_anisotropy` | float | 0.0 | ❌ | — | **NOT SUPPORTED** |
|
||||
|
||||
@@ -188,17 +189,17 @@ Geometry properties affect surface normals and tangent space, used for bump mapp
|
||||
| Base Layer | 4 | 3 | 1 | 0 |
|
||||
| Specular | 7 | 2 | 3 | 2 |
|
||||
| Transmission | 6 | 1 | 1 | 4 |
|
||||
| Subsurface | 5 | 0 | 0 | 5 |
|
||||
| Subsurface | 6 | 0 | 0 | 6 |
|
||||
| Sheen | 3 | 3 | 0 | 0 |
|
||||
| Coat | 8 | 2 | 1 | 5 |
|
||||
| Emission | 2 | 2 | 0 | 0 |
|
||||
| Geometry | 3 | 2 | 1 | 0 |
|
||||
| **Total** | **38** | **15 (39%)** | **7 (18%)** | **16 (42%)** |
|
||||
| **Total** | **39** | **15 (38%)** | **7 (18%)** | **17 (44%)** |
|
||||
|
||||
### Critical Limitations for Three.js
|
||||
|
||||
**❌ NOT SUPPORTED (requires custom shaders):**
|
||||
1. **Subsurface Scattering** - All 5 parameters (weight, color, radius, scale, anisotropy)
|
||||
1. **Subsurface Scattering** - All 6 parameters (weight, color, radius, radius_scale, scale, anisotropy)
|
||||
2. **Transmission Effects** - Color, scatter, dispersion (4 parameters)
|
||||
3. **Coat Advanced** - Color, anisotropy, affect properties (5 parameters)
|
||||
4. **Specular Advanced** - IOR level (1 parameter)
|
||||
|
||||
@@ -694,8 +694,16 @@ std::string print_prim_metas(const PrimMeta &meta, const uint32_t indent) {
|
||||
}
|
||||
|
||||
for (const auto &item : meta.unregisteredMetas) {
|
||||
// do not quote
|
||||
ss << pprint::Indent(indent) << item.first << " = " << item.second << "\n";
|
||||
// Quote string values, but keep non-string values as-is
|
||||
std::string value_str = item.second;
|
||||
// Check if the value looks like a string (unquoted) by checking if it needs quoting
|
||||
// String values that are not already quoted should be quoted
|
||||
if (!value_str.empty() && value_str.front() != '"' && value_str.front() != '\'' &&
|
||||
value_str.front() != '[' && !std::isdigit(value_str.front()) &&
|
||||
value_str != "None" && value_str.find("(") == std::string::npos) {
|
||||
value_str = quote(value_str);
|
||||
}
|
||||
ss << pprint::Indent(indent) << item.first << " = " << value_str << "\n";
|
||||
}
|
||||
|
||||
// TODO: deprecate meta.meta and remove it.
|
||||
@@ -726,13 +734,13 @@ std::string print_attr_metas(const AttrMeta &meta, const uint32_t indent) {
|
||||
|
||||
if (meta.bindMaterialAs) {
|
||||
ss << pprint::Indent(indent)
|
||||
<< "bindMaterialAs = " << quote(to_string(meta.bindMaterialAs.value()))
|
||||
<< "bindMaterialAs = " << to_string(meta.bindMaterialAs.value())
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
if (meta.connectability) {
|
||||
ss << pprint::Indent(indent)
|
||||
<< "connectability = " << quote(to_string(meta.connectability.value()))
|
||||
<< "connectability = " << to_string(meta.connectability.value())
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
@@ -748,12 +756,12 @@ std::string print_attr_metas(const AttrMeta &meta, const uint32_t indent) {
|
||||
|
||||
if (meta.outputName) {
|
||||
ss << pprint::Indent(indent)
|
||||
<< "outputName = " << quote(to_string(meta.outputName.value())) << "\n";
|
||||
<< "outputName = " << to_string(meta.outputName.value()) << "\n";
|
||||
}
|
||||
|
||||
if (meta.renderType) {
|
||||
ss << pprint::Indent(indent)
|
||||
<< "renderType = " << quote(to_string(meta.renderType.value())) << "\n";
|
||||
<< "renderType = " << to_string(meta.renderType.value()) << "\n";
|
||||
}
|
||||
|
||||
if (meta.sdrMetadata) {
|
||||
@@ -3220,7 +3228,7 @@ std::string to_string(const GeomMesh &mesh, const uint32_t indent,
|
||||
indent + 1);
|
||||
ss << print_typed_attr(mesh.holeIndices, "holeIndices", indent + 1);
|
||||
|
||||
ss << print_typed_token_attr(mesh.subdivisionScheme, "subdivisonScheme",
|
||||
ss << print_typed_token_attr(mesh.subdivisionScheme, "subdivisionScheme",
|
||||
indent + 1);
|
||||
ss << print_typed_token_attr(mesh.interpolateBoundary, "interpolateBoundary",
|
||||
indent + 1);
|
||||
|
||||
@@ -4496,18 +4496,25 @@ bool ReconstructPrim<GeomMesh>(
|
||||
(names[0] == "subsetFamily") &&
|
||||
(names[2] == "familyType")) {
|
||||
|
||||
DCOUT("subsetFamily" << prop.first);
|
||||
TypedAttributeWithFallback<GeomSubset::FamilyType> familyType{GeomSubset::FamilyType::Unrestricted};
|
||||
if (table.count(prop.first)) {
|
||||
// Already processed
|
||||
} else if ((prop.second.value_type_name() == value::TypeTraits<value::token>::type_name()) &&
|
||||
prop.second.is_attribute() &&
|
||||
!prop.second.is_empty()) {
|
||||
// Parse the token enum value
|
||||
const Attribute &attr = prop.second.get_attribute();
|
||||
TypedAttributeWithFallback<GeomSubset::FamilyType> familyType{GeomSubset::FamilyType::Unrestricted};
|
||||
std::function<nonstd::expected<GeomSubset::FamilyType, std::string>(const std::string &)> fun = FamilyTypeHandler;
|
||||
|
||||
PARSE_UNIFORM_ENUM_PROPERTY(table, prop, prop.first,
|
||||
GeomSubset::FamilyType, FamilyTypeHandler, GeomMesh,
|
||||
familyType, options.strict_allowedToken_check)
|
||||
|
||||
// NOTE: Ignore metadataum of familyType.
|
||||
|
||||
// TODO: Validate familyName
|
||||
mesh->subsetFamilyTypeMap[value::token(names[1])] = familyType.get_value();
|
||||
if (!ParseUniformEnumProperty(prop.first, options.strict_allowedToken_check, fun, attr, &familyType, warn, err)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: Ignore metadata of familyType.
|
||||
// TODO: Validate familyName
|
||||
mesh->subsetFamilyTypeMap[value::token(names[1])] = familyType.get_value();
|
||||
table.insert(prop.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "unicode-xid.hh"
|
||||
#include "common-macros.inc"
|
||||
#include "external/dtoa_milo.h"
|
||||
|
||||
#ifdef __SSE2__
|
||||
#include <emmintrin.h>
|
||||
@@ -1212,4 +1213,20 @@ bool GlobMatchPath(const std::string &pattern, const std::string &path) {
|
||||
return p == pattern.size();
|
||||
}
|
||||
|
||||
char *dtoa(float f, char *buf) {
|
||||
// For float, use simple sprintf for now
|
||||
// dtoa_milo is optimized for double and doesn't work well with float
|
||||
int n = snprintf(buf, 384, "%.9g", static_cast<double>(f));
|
||||
if (n < 0 || n >= 384) {
|
||||
buf[0] = '0';
|
||||
return &buf[1];
|
||||
}
|
||||
return &buf[n];
|
||||
}
|
||||
|
||||
char *dtoa(double d, char *buf) {
|
||||
// Use dtoa_milo for double precision
|
||||
return dtoa_milo(d, buf);
|
||||
}
|
||||
|
||||
} // namespace tinyusdz
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include "str-util.hh"
|
||||
|
||||
#ifdef TINYUSDZ_ENABLE_THREAD
|
||||
#include <thread>
|
||||
@@ -46,7 +47,7 @@ struct ThreadedPrintConfig {
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
};;;;;
|
||||
};
|
||||
|
||||
// Global configuration (can be customized)
|
||||
static ThreadedPrintConfig g_threaded_print_config;
|
||||
@@ -194,6 +195,28 @@ void print_type<char>(OutputAdapter& out, const uint8_t* data) {
|
||||
out.write(static_cast<int>(value));
|
||||
}
|
||||
|
||||
// Specialization for double - print with full precision using dtoa
|
||||
template<>
|
||||
void print_type<double>(OutputAdapter& out, const uint8_t* data) {
|
||||
double value;
|
||||
std::memcpy(&value, data, sizeof(double));
|
||||
char buf[384];
|
||||
char *end = dtoa(value, buf);
|
||||
*end = '\0';
|
||||
out.write(std::string(buf));
|
||||
}
|
||||
|
||||
// Specialization for float - print with full precision using dtoa
|
||||
template<>
|
||||
void print_type<float>(OutputAdapter& out, const uint8_t* data) {
|
||||
float value;
|
||||
std::memcpy(&value, data, sizeof(float));
|
||||
char buf[384];
|
||||
char *end = dtoa(value, buf);
|
||||
*end = '\0';
|
||||
out.write(std::string(buf));
|
||||
}
|
||||
|
||||
// Unified print function for vector types
|
||||
template<typename T, size_t N>
|
||||
void print_vector(OutputAdapter& out, const uint8_t* data) {
|
||||
@@ -1399,8 +1422,16 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if using unified storage (_times non-empty) vs legacy Sample-based storage
|
||||
if (!samples.get_times().empty()) {
|
||||
// Check if using unified storage (_times non-empty AND has actual data in buffers)
|
||||
// vs Sample-based storage (_samples vector)
|
||||
// Note: Some operations like add_value_array_sample() populate _times but store data
|
||||
// in _samples, so we need to check if unified storage buffers actually have data
|
||||
bool has_unified_data = !samples.get_times().empty() &&
|
||||
(!samples.get_values().empty() ||
|
||||
!samples.get_small_values().empty() ||
|
||||
!samples.get_offsets().empty());
|
||||
|
||||
if (has_unified_data) {
|
||||
|
||||
// Phase 3: Access unified storage directly from TimeSamples
|
||||
// Note: TypedArray is no longer supported in Phase 3, so we skip that path
|
||||
@@ -1427,6 +1458,7 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
|
||||
const auto& blocked = samples.get_blocked();
|
||||
const auto& values = samples.get_values();
|
||||
const auto& offsets = samples.get_offsets();
|
||||
const auto& small_values = samples.get_small_values();
|
||||
const auto& array_counts = samples.get_array_counts();
|
||||
|
||||
// Write samples - handle offset table if present
|
||||
@@ -1476,19 +1508,54 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
|
||||
writer.write("\n");
|
||||
}
|
||||
} else {
|
||||
// Legacy: blocked values still counted in offset calculation
|
||||
// Handle case where values is empty but times is not
|
||||
if (values.empty() && !times.empty()) {
|
||||
// No offset table - using direct storage (either _values or _small_values)
|
||||
// Check if using small_values (for types sizeof <= 8) or values buffer (for types sizeof > 8)
|
||||
bool using_small_values = !small_values.empty();
|
||||
|
||||
// Handle case where both storage types are empty but times is not (error case)
|
||||
if (values.empty() && small_values.empty() && !times.empty()) {
|
||||
for (size_t i = 0; i < times.size(); ++i) {
|
||||
writer.write(pprint::Indent(indent + 1));
|
||||
writer.write(times[i]);
|
||||
writer.write(": /* empty value data */");
|
||||
if (i < times.size() - 1) {
|
||||
writer.write(",");
|
||||
}
|
||||
writer.write("\n");
|
||||
}
|
||||
} else if (using_small_values) {
|
||||
// Print small values (stored as uint64_t, need to extract typed value)
|
||||
// NOTE: small_values only contains non-blocked samples, so we need a separate index
|
||||
size_t small_values_index = 0;
|
||||
for (size_t i = 0; i < times.size(); ++i) {
|
||||
writer.write(pprint::Indent(indent + 1));
|
||||
writer.write(times[i]);
|
||||
writer.write(": ");
|
||||
|
||||
// Check blocked array bounds before accessing
|
||||
bool is_blocked = (i < blocked.size()) ? blocked[i] : false;
|
||||
if (is_blocked) {
|
||||
writer.write("None");
|
||||
} else {
|
||||
// Get value from small_values and print it
|
||||
if (small_values_index < small_values.size()) {
|
||||
uint64_t stored_value = small_values[small_values_index];
|
||||
// Cast to typed pointer and print
|
||||
const uint8_t* value_ptr = reinterpret_cast<const uint8_t*>(&stored_value);
|
||||
pprint_pod_value_by_type(writer, value_ptr, type_id);
|
||||
small_values_index++; // Only increment for non-blocked samples
|
||||
} else {
|
||||
writer.write("/* ERROR: small_values index out of bounds */");
|
||||
}
|
||||
}
|
||||
|
||||
if (i < times.size() - 1) {
|
||||
writer.write(",");
|
||||
}
|
||||
writer.write("\n");
|
||||
}
|
||||
} else {
|
||||
// Use values buffer (large types)
|
||||
size_t value_offset = 0;
|
||||
for (size_t i = 0; i < times.size(); ++i) {
|
||||
//TUSDZ_LOG_I("times[" << i << "] = " << times[i]);
|
||||
@@ -1524,8 +1591,8 @@ void pprint_timesamples(StreamWriter& writer, const value::TimeSamples& samples,
|
||||
}
|
||||
writer.write("\n");
|
||||
}
|
||||
} // end else for values.empty() check
|
||||
}
|
||||
} // end else for using_small_values check
|
||||
} // end else for offsets.empty() check
|
||||
} else {
|
||||
// Non-POD path: use regular samples
|
||||
const auto& samples_vec = samples.get_samples();
|
||||
|
||||
@@ -1451,8 +1451,12 @@ struct TimeSamples {
|
||||
_times.push_back(t);
|
||||
_blocked.push_back(1); // Blocked
|
||||
|
||||
// No data needed for blocked sample, just add a dummy offset
|
||||
_offsets.push_back(SIZE_MAX); // Special marker for blocked
|
||||
// For small types (sizeof <= 8), don't use offsets - just rely on _blocked flag
|
||||
// For large types (sizeof > 8), need offset table entry
|
||||
if (sizeof(T) > 8) {
|
||||
_offsets.push_back(SIZE_MAX); // Special marker for blocked
|
||||
}
|
||||
// Note: No entry in _small_values for blocked samples (for small types)
|
||||
|
||||
_dirty = true;
|
||||
return true;
|
||||
@@ -1616,6 +1620,13 @@ struct TimeSamples {
|
||||
return _offsets;
|
||||
}
|
||||
|
||||
const std::vector<uint64_t>& get_small_values() const {
|
||||
if (_dirty) {
|
||||
update();
|
||||
}
|
||||
return _small_values;
|
||||
}
|
||||
|
||||
bool is_array() const {
|
||||
return _is_array;
|
||||
}
|
||||
|
||||
@@ -2998,7 +2998,8 @@ bool ListUVNames(const RenderMaterial &material,
|
||||
// Subsurface
|
||||
fun_float(material.openPBRShader->subsurface_weight);
|
||||
fun_vec3(material.openPBRShader->subsurface_color);
|
||||
fun_vec3(material.openPBRShader->subsurface_radius);
|
||||
fun_float(material.openPBRShader->subsurface_radius);
|
||||
fun_vec3(material.openPBRShader->subsurface_radius_scale);
|
||||
fun_float(material.openPBRShader->subsurface_scale);
|
||||
fun_float(material.openPBRShader->subsurface_anisotropy);
|
||||
|
||||
@@ -7338,6 +7339,12 @@ bool RenderSceneConverter::ConvertOpenPBRSurfaceShader(
|
||||
PushWarn(fmt::format("Failed to convert subsurface_radius parameter for shader: {}", shader_abs_path.prim_part()));
|
||||
return false;
|
||||
}
|
||||
if (!ConvertPreviewSurfaceShaderParam(
|
||||
env, shader_abs_path, shader.subsurface_radius_scale, "subsurface_radius_scale",
|
||||
rshader.subsurface_radius_scale, true)) {
|
||||
PushWarn(fmt::format("Failed to convert subsurface_radius_scale parameter for shader: {}", shader_abs_path.prim_part()));
|
||||
return false;
|
||||
}
|
||||
if (!ConvertPreviewSurfaceShaderParam(
|
||||
env, shader_abs_path, shader.subsurface_scale, "subsurface_scale",
|
||||
rshader.subsurface_scale, true)) {
|
||||
|
||||
@@ -1651,7 +1651,8 @@ class OpenPBRSurfaceShader {
|
||||
// Subsurface scattering
|
||||
ShaderParam<float> subsurface_weight{0.0f};
|
||||
ShaderParam<vec3> subsurface_color{{0.8f, 0.8f, 0.8f}};
|
||||
ShaderParam<vec3> subsurface_radius{{1.0f, 1.0f, 1.0f}};
|
||||
ShaderParam<float> subsurface_radius{1.0f};
|
||||
ShaderParam<vec3> subsurface_radius_scale{{1.0f, 1.0f, 1.0f}};
|
||||
ShaderParam<float> subsurface_scale{1.0f};
|
||||
ShaderParam<float> subsurface_anisotropy{0.0f};
|
||||
|
||||
|
||||
@@ -363,7 +363,8 @@ json ThreeJSMaterialExporter::ConvertOpenPBRToNodeMaterial(const OpenPBRSurfaceS
|
||||
// Subsurface
|
||||
add_param("subsurface_weight", shader.subsurface_weight);
|
||||
setJsonParameter(surface_node["inputs"], "subsurface_color", vec3ToJson(shader.subsurface_color.value), use_grouped);
|
||||
setJsonParameter(surface_node["inputs"], "subsurface_radius", vec3ToJson(shader.subsurface_radius.value), use_grouped);
|
||||
add_param("subsurface_radius", shader.subsurface_radius);
|
||||
setJsonParameter(surface_node["inputs"], "subsurface_radius_scale", vec3ToJson(shader.subsurface_radius_scale.value), use_grouped);
|
||||
add_param("subsurface_scale", shader.subsurface_scale);
|
||||
add_param("subsurface_anisotropy", shader.subsurface_anisotropy);
|
||||
|
||||
@@ -468,7 +469,8 @@ json ThreeJSMaterialExporter::ConvertOpenPBRToPhysicalMaterial(const OpenPBRSurf
|
||||
params["userData"]["subsurface"] = {
|
||||
{"weight", shader.subsurface_weight.value},
|
||||
{"color", vec3ToJson(shader.subsurface_color.value)},
|
||||
{"radius", vec3ToJson(shader.subsurface_radius.value)},
|
||||
{"radius", shader.subsurface_radius.value},
|
||||
{"radius_scale", vec3ToJson(shader.subsurface_radius_scale.value)},
|
||||
{"scale", shader.subsurface_scale.value},
|
||||
{"anisotropy", shader.subsurface_anisotropy.value}
|
||||
};
|
||||
@@ -693,7 +695,8 @@ bool ThreeJSMaterialExporter::ExportMaterialX(const RenderMaterial& material,
|
||||
// Subsurface parameters
|
||||
export_float("subsurface_weight", shader.subsurface_weight);
|
||||
export_color3("subsurface_color", shader.subsurface_color);
|
||||
export_color3("subsurface_radius", shader.subsurface_radius);
|
||||
export_float("subsurface_radius", shader.subsurface_radius);
|
||||
export_color3("subsurface_radius_scale", shader.subsurface_radius_scale);
|
||||
export_float("subsurface_scale", shader.subsurface_scale);
|
||||
export_float("subsurface_anisotropy", shader.subsurface_anisotropy);
|
||||
|
||||
@@ -765,7 +768,8 @@ bool ThreeJSMaterialExporter::ExportMaterialX(const RenderMaterial& material,
|
||||
|
||||
export_texture_node("subsurface_weight", shader.subsurface_weight, "float");
|
||||
export_texture_node_vec3("subsurface_color", shader.subsurface_color, "color3");
|
||||
export_texture_node_vec3("subsurface_radius", shader.subsurface_radius, "color3");
|
||||
export_texture_node("subsurface_radius", shader.subsurface_radius, "float");
|
||||
export_texture_node_vec3("subsurface_radius_scale", shader.subsurface_radius_scale, "color3");
|
||||
export_texture_node("subsurface_scale", shader.subsurface_scale, "float");
|
||||
export_texture_node("subsurface_anisotropy", shader.subsurface_anisotropy, "float");
|
||||
|
||||
|
||||
@@ -653,7 +653,7 @@ static bool WriteMaterialXToString(const MtlxAutodeskStandardSurface &shader,
|
||||
// Subsurface properties
|
||||
EMIT_ATTRIBUTE("subsurface", "float", shader.subsurface)
|
||||
EMIT_ATTRIBUTE("subsurface_color", "color3", shader.subsurface_color)
|
||||
EMIT_ATTRIBUTE("subsurface_radius", "color3", shader.subsurface_radius)
|
||||
EMIT_ATTRIBUTE("subsurface_radius", "float", shader.subsurface_radius)
|
||||
EMIT_ATTRIBUTE("subsurface_scale", "float", shader.subsurface_scale)
|
||||
EMIT_ATTRIBUTE("subsurface_anisotropy", "float", shader.subsurface_anisotropy)
|
||||
|
||||
@@ -795,7 +795,8 @@ static bool WriteMaterialXToString(const MtlxOpenPBRSurface &shader,
|
||||
// Subsurface properties
|
||||
EMIT_ATTRIBUTE("subsurface_weight", "float", shader.subsurface_weight)
|
||||
EMIT_ATTRIBUTE("subsurface_color", "color3", shader.subsurface_color)
|
||||
EMIT_ATTRIBUTE("subsurface_radius", "color3", shader.subsurface_radius)
|
||||
EMIT_ATTRIBUTE("subsurface_radius", "float", shader.subsurface_radius)
|
||||
EMIT_ATTRIBUTE("subsurface_radius_scale", "color3", shader.subsurface_radius_scale)
|
||||
EMIT_ATTRIBUTE("subsurface_scale", "float", shader.subsurface_scale)
|
||||
EMIT_ATTRIBUTE("subsurface_anisotropy", "float", shader.subsurface_anisotropy)
|
||||
|
||||
@@ -1757,7 +1758,7 @@ bool ReadMaterialXFromString(const std::string &str,
|
||||
GET_SHADER_PARAM(name, typeName, "transmission_extra_roughness", "float", float, valueStr, surface.transmission_extra_roughness)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface", "float", float, valueStr, surface.subsurface)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_color", "color3", value::color3f, valueStr, surface.subsurface_color)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_radius", "color3", value::color3f, valueStr, surface.subsurface_radius)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_radius", "float", float, valueStr, surface.subsurface_radius)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_scale", "float", float, valueStr, surface.subsurface_scale)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_anisotropy", "float", float, valueStr, surface.subsurface_anisotropy)
|
||||
GET_SHADER_PARAM(name, typeName, "sheen", "float", float, valueStr, surface.sheen)
|
||||
@@ -2155,7 +2156,8 @@ bool ReadMaterialXFromString(const std::string &str,
|
||||
GET_SHADER_PARAM(name, typeName, "transmission_dispersion", "float", float, valueStr, surface.transmission_dispersion)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_weight", "float", float, valueStr, surface.subsurface_weight)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_color", "color3", value::color3f, valueStr, surface.subsurface_color)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_radius", "color3", value::color3f, valueStr, surface.subsurface_radius)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_radius", "float", float, valueStr, surface.subsurface_radius)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_radius_scale", "color3", value::color3f, valueStr, surface.subsurface_radius_scale)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_scale", "float", float, valueStr, surface.subsurface_scale)
|
||||
GET_SHADER_PARAM(name, typeName, "subsurface_anisotropy", "float", float, valueStr, surface.subsurface_anisotropy)
|
||||
GET_SHADER_PARAM(name, typeName, "sheen_weight", "float", float, valueStr, surface.sheen_weight)
|
||||
|
||||
@@ -228,8 +228,7 @@ struct MtlxAutodeskStandardSurface : ShaderNode {
|
||||
TypedAttributeWithFallback<Animatable<float>> subsurface{0.0f};
|
||||
TypedAttributeWithFallback<Animatable<value::color3f>> subsurface_color{
|
||||
value::color3f{1.0f, 1.0f, 1.0f}};
|
||||
TypedAttributeWithFallback<Animatable<value::color3f>> subsurface_radius{
|
||||
value::color3f{1.0f, 1.0f, 1.0f}};
|
||||
TypedAttributeWithFallback<Animatable<float>> subsurface_radius{1.0f};
|
||||
TypedAttributeWithFallback<Animatable<float>> subsurface_scale{1.0f};
|
||||
TypedAttributeWithFallback<Animatable<float>> subsurface_anisotropy{0.0f};
|
||||
|
||||
|
||||
@@ -370,7 +370,8 @@ struct OpenPBRSurface : ShaderNode {
|
||||
// Subsurface properties
|
||||
TypedAttributeWithFallback<Animatable<float>> subsurface_weight{0.0f}; // "inputs:subsurface_weight"
|
||||
TypedAttributeWithFallback<Animatable<value::color3f>> subsurface_color{value::color3f{0.8f, 0.8f, 0.8f}}; // "inputs:subsurface_color"
|
||||
TypedAttributeWithFallback<Animatable<value::color3f>> subsurface_radius{value::color3f{1.0f, 1.0f, 1.0f}}; // "inputs:subsurface_radius"
|
||||
TypedAttributeWithFallback<Animatable<float>> subsurface_radius{1.0f}; // "inputs:subsurface_radius"
|
||||
TypedAttributeWithFallback<Animatable<value::color3f>> subsurface_radius_scale{value::color3f{1.0f, 1.0f, 1.0f}}; // "inputs:subsurface_radius_scale"
|
||||
TypedAttributeWithFallback<Animatable<float>> subsurface_scale{1.0f}; // "inputs:subsurface_scale"
|
||||
TypedAttributeWithFallback<Animatable<float>> subsurface_anisotropy{0.0f}; // "inputs:subsurface_anisotropy"
|
||||
|
||||
|
||||
@@ -537,13 +537,12 @@ std::ostream &operator<<(std::ostream &ofs,
|
||||
if (tinyusdz::contains(in_s, '@')) {
|
||||
// Escape '@@@'(to '\@@@') if the input path contains '@@@'
|
||||
for (size_t i = 0; i < in_s.length(); i++) {
|
||||
if ((i + 2) < in_s.length()) {
|
||||
if (in_s[i] == '@' && in_s[i + 1] == '@' && in_s[i + 2] == '@') {
|
||||
s += "\\@@@";
|
||||
i += 2;
|
||||
} else {
|
||||
s += in_s[i];
|
||||
}
|
||||
if ((i + 2) < in_s.length() &&
|
||||
in_s[i] == '@' && in_s[i + 1] == '@' && in_s[i + 2] == '@') {
|
||||
s += "\\@@@";
|
||||
i += 2;
|
||||
} else {
|
||||
s += in_s[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2147
tests/compare-usda.js
Executable file
2147
tests/compare-usda.js
Executable file
File diff suppressed because it is too large
Load Diff
297
tests/run-usdcat-compare.sh
Executable file
297
tests/run-usdcat-compare.sh
Executable file
@@ -0,0 +1,297 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to run batch comparisons of tusdcat vs usdcat output with detailed diffs
|
||||
# Tests both USDA and USDC formats
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COMPARE_SCRIPT="$SCRIPT_DIR/compare-usda.js"
|
||||
TUSDCAT_PATH="${TUSDCAT_PATH:-./build/tusdcat}"
|
||||
USDCAT_PATH="${USDCAT_PATH:-~/local/USD/dist/bin/usdcat}"
|
||||
TIMEOUT_MS="${TIMEOUT_MS:-60000}"
|
||||
SHOW_DETAILED_DIFF="${SHOW_DETAILED_DIFF:-true}"
|
||||
SHOW_FAILURE_SUMMARY="${SHOW_FAILURE_SUMMARY:-true}"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print headers
|
||||
print_header() {
|
||||
echo ""
|
||||
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
echo -e "${BLUE}════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to print section
|
||||
print_section() {
|
||||
echo ""
|
||||
echo -e "${YELLOW}→ $1${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to check if file exists
|
||||
check_executable() {
|
||||
local path="$1"
|
||||
local name="$2"
|
||||
|
||||
# Expand ~ if present
|
||||
path="${path/#\~/$HOME}"
|
||||
|
||||
if [ ! -x "$path" ]; then
|
||||
echo -e "${RED}Error: $name not found or not executable at: $path${NC}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to run comparison on a folder
|
||||
run_folder_comparison() {
|
||||
local folder="$1"
|
||||
local folder_name="$2"
|
||||
local file_pattern="$3"
|
||||
|
||||
if [ ! -d "$folder" ]; then
|
||||
echo -e "${RED}Error: Folder not found: $folder${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_section "Testing $folder_name ($file_pattern files)"
|
||||
|
||||
local detailed_flag=""
|
||||
if [ "$SHOW_DETAILED_DIFF" = "true" ]; then
|
||||
detailed_flag="--detailed-diff"
|
||||
fi
|
||||
|
||||
node "$COMPARE_SCRIPT" \
|
||||
--tusdcat "$TUSDCAT_PATH" \
|
||||
--usdcat "$USDCAT_PATH" \
|
||||
--timeout "$TIMEOUT_MS" \
|
||||
--continue-on-error \
|
||||
$detailed_flag \
|
||||
"$folder/$file_pattern"
|
||||
}
|
||||
|
||||
# Function to print failure and warning summary from results file
|
||||
# Note: Uses plain text (no ANSI colors) so output is clean when redirected
|
||||
print_failure_summary() {
|
||||
local results_file="$1"
|
||||
|
||||
if [ ! -f "$results_file" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Strip ANSI codes for processing
|
||||
local clean_results
|
||||
clean_results=$(sed 's/\x1b\[[0-9;]*m//g' "$results_file")
|
||||
|
||||
# Extract failed files (lines with "✗" followed by "difference(s)")
|
||||
local failed_files
|
||||
failed_files=$(echo "$clean_results" | grep -B1 "✗.*difference(s)" | grep "Processing:" | sed 's/.*Processing: //' | sort -u)
|
||||
|
||||
# Extract warning/error files (lines with "⚠" or "Error:")
|
||||
local warning_files
|
||||
warning_files=$(echo "$clean_results" | grep -B1 -E "(⚠|Error:)" | grep "Processing:" | sed 's/.*Processing: //' | sort -u)
|
||||
|
||||
local has_output=false
|
||||
|
||||
if [ -n "$failed_files" ] || [ -n "$warning_files" ]; then
|
||||
echo ""
|
||||
echo "========================================================"
|
||||
echo "Failure and Warning Summary"
|
||||
echo "========================================================"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ -n "$failed_files" ]; then
|
||||
has_output=true
|
||||
local fail_count
|
||||
fail_count=$(echo "$failed_files" | wc -l)
|
||||
echo "[X] Failed Files ($fail_count):"
|
||||
echo "$failed_files" | while read -r file; do
|
||||
echo " - $file"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ -n "$warning_files" ]; then
|
||||
has_output=true
|
||||
local warn_count
|
||||
warn_count=$(echo "$warning_files" | wc -l)
|
||||
echo "[!] Warning/Error Files ($warn_count):"
|
||||
echo "$warning_files" | while read -r file; do
|
||||
echo " - $file"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ "$has_output" = true ]; then
|
||||
echo "--------------------------------------------------------"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
# Expand tilde in paths
|
||||
TUSDCAT_PATH="${TUSDCAT_PATH/#\~/$HOME}"
|
||||
USDCAT_PATH="${USDCAT_PATH/#\~/$HOME}"
|
||||
|
||||
print_header "USD File Format Comparison Suite"
|
||||
echo "Configuration:"
|
||||
echo " Comparison Script: $COMPARE_SCRIPT"
|
||||
echo " tusdcat Path: $TUSDCAT_PATH"
|
||||
echo " usdcat Path: $USDCAT_PATH"
|
||||
echo " Timeout: ${TIMEOUT_MS}ms"
|
||||
echo " Detailed Diff: $SHOW_DETAILED_DIFF"
|
||||
echo " Failure Summary: $SHOW_FAILURE_SUMMARY"
|
||||
echo ""
|
||||
|
||||
# Check prerequisites
|
||||
echo "Checking prerequisites..."
|
||||
|
||||
if [ ! -f "$COMPARE_SCRIPT" ]; then
|
||||
echo -e "${RED}Error: compare-usda.js not found at: $COMPARE_SCRIPT${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! check_executable "$TUSDCAT_PATH" "tusdcat"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! check_executable "$USDCAT_PATH" "usdcat"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ All prerequisites met${NC}"
|
||||
echo ""
|
||||
|
||||
# Create results directory
|
||||
RESULTS_DIR="$SCRIPT_DIR/comparison-results"
|
||||
mkdir -p "$RESULTS_DIR"
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
RESULTS_FILE="$RESULTS_DIR/results_${TIMESTAMP}.log"
|
||||
|
||||
echo "Results will be saved to: $RESULTS_FILE"
|
||||
echo ""
|
||||
|
||||
# Run comparisons and capture output
|
||||
{
|
||||
print_header "USD File Format Comparison Results - $TIMESTAMP"
|
||||
|
||||
# Test USDA files
|
||||
print_section "USDA (ASCII) Format Tests"
|
||||
echo "Testing all .usda files in tests/usda directory..."
|
||||
echo ""
|
||||
run_folder_comparison "$SCRIPT_DIR/usda" "USDA Files" "*.usda" || true
|
||||
|
||||
# Test USDC files
|
||||
print_section "USDC (Binary/Crate) Format Tests"
|
||||
echo "Testing all .usdc files in tests/usdc directory..."
|
||||
echo ""
|
||||
run_folder_comparison "$SCRIPT_DIR/usdc" "USDC Files" "*.usdc" || true
|
||||
|
||||
print_header "Comparison Complete"
|
||||
} | tee "$RESULTS_FILE"
|
||||
|
||||
# Print failure summary if enabled
|
||||
if [ "$SHOW_FAILURE_SUMMARY" = "true" ]; then
|
||||
print_failure_summary "$RESULTS_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Comparison complete!${NC}"
|
||||
echo "Full results saved to: $RESULTS_FILE"
|
||||
echo ""
|
||||
echo "Usage examples for custom runs:"
|
||||
echo " # Test specific USDA file with detailed diff"
|
||||
echo " SHOW_DETAILED_DIFF=true node $COMPARE_SCRIPT --detailed-diff tests/usda/cube.usda"
|
||||
echo ""
|
||||
echo " # Test specific files with tusdcat/usdcat"
|
||||
echo " TUSDCAT_PATH=./build_gcc/tusdcat USDCAT_PATH=~/local/USD/dist/bin/usdcat \\"
|
||||
echo " node $COMPARE_SCRIPT --detailed-diff --tusdcat ./build_gcc/tusdcat --usdcat ~/local/USD/dist/bin/usdcat tests/usda/cube.usda"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Show help
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: $0 [OPTIONS]
|
||||
|
||||
Run comprehensive batch comparisons of tusdcat vs usdcat outputs with detailed diffs.
|
||||
Tests both USDA (ASCII) and USDC (Binary/Crate) file formats.
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
--tusdcat PATH Path to tusdcat executable (default: ./build_gcc/tusdcat)
|
||||
--usdcat PATH Path to usdcat executable (default: ~/local/USD/dist/bin/usdcat)
|
||||
--timeout MS Timeout per file in milliseconds (default: 60000)
|
||||
--no-detailed-diff Disable detailed diff output (shows summary only)
|
||||
--no-failure-summary Disable failure/warning summary at the end
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
TUSDCAT_PATH Override tusdcat path
|
||||
USDCAT_PATH Override usdcat path
|
||||
TIMEOUT_MS Override timeout
|
||||
SHOW_DETAILED_DIFF Set to 'false' to disable detailed diffs (default: true)
|
||||
SHOW_FAILURE_SUMMARY Set to 'false' to disable failure summary (default: true)
|
||||
|
||||
EXAMPLES:
|
||||
# Run with default settings
|
||||
$0
|
||||
|
||||
# Run without detailed diffs (faster, summary only)
|
||||
$0 --no-detailed-diff
|
||||
|
||||
# Run with custom tool paths
|
||||
TUSDCAT_PATH=./build_asan/tusdcat USDCAT_PATH=~/USD/bin/usdcat $0
|
||||
|
||||
# Run with longer timeout for slow systems
|
||||
$0 --timeout 120000
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
--tusdcat)
|
||||
TUSDCAT_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--usdcat)
|
||||
USDCAT_PATH="$2"
|
||||
shift 2
|
||||
;;
|
||||
--timeout)
|
||||
TIMEOUT_MS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-detailed-diff)
|
||||
SHOW_DETAILED_DIFF="false"
|
||||
shift
|
||||
;;
|
||||
--no-failure-summary)
|
||||
SHOW_FAILURE_SUMMARY="false"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Run main function
|
||||
main
|
||||
@@ -49,6 +49,7 @@ TEST_LIST = {
|
||||
{ "strutil_test", strutil_test },
|
||||
{ "tinystring_test", tinystring_test },
|
||||
{ "parse_int_test", parse_int_test },
|
||||
{ "dtoa_test", dtoa_test },
|
||||
{ "timesamples_test", timesamples_test },
|
||||
{ "materialx_config_api_struct_test", materialx_config_api_struct_test },
|
||||
{ "materialx_config_api_parsing_test", materialx_config_api_parsing_test },
|
||||
|
||||
@@ -195,5 +195,135 @@ void parse_int_test(void) {
|
||||
tstring_view sv("+-123");
|
||||
TEST_CHECK(!parse_int(sv, &result));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void dtoa_test(void) {
|
||||
// Test double to ASCII conversion with full precision
|
||||
|
||||
{
|
||||
// Test pi with full double precision
|
||||
double pi = 3.141592653589793;
|
||||
char buf[384];
|
||||
char *end = dtoa(pi, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
// Should preserve full precision (at least 15 significant digits)
|
||||
TEST_CHECK(result.find("3.14159265358979") != std::string::npos);
|
||||
TEST_MSG("pi result: %s", result.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
// Test e with full double precision
|
||||
double e = 2.718281828459045;
|
||||
char buf[384];
|
||||
char *end = dtoa(e, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
// Should preserve full precision
|
||||
TEST_CHECK(result.find("2.71828182845904") != std::string::npos);
|
||||
TEST_MSG("e result: %s", result.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
// Test negative value
|
||||
double neg = -2.718281828459045;
|
||||
char buf[384];
|
||||
char *end = dtoa(neg, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
TEST_CHECK(result[0] == '-');
|
||||
TEST_CHECK(result.find("2.71828182845904") != std::string::npos);
|
||||
TEST_MSG("negative e result: %s", result.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
// Test zero
|
||||
double zero = 0.0;
|
||||
char buf[384];
|
||||
char *end = dtoa(zero, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
TEST_CHECK(result == "0.0");
|
||||
TEST_MSG("zero result: %s", result.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
// Test negative zero
|
||||
double neg_zero = -0.0;
|
||||
char buf[384];
|
||||
char *end = dtoa(neg_zero, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
// dtoa_milo should output "-0.0" for negative zero
|
||||
TEST_CHECK(result == "-0.0");
|
||||
TEST_MSG("negative zero result: %s", result.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
// Test small number
|
||||
double small = 0.000001234567890123456;
|
||||
char buf[384];
|
||||
char *end = dtoa(small, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
// Should use scientific notation for very small numbers
|
||||
TEST_MSG("small number result: %s", result.c_str());
|
||||
TEST_CHECK(result.length() > 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Test large number
|
||||
double large = 1234567890123456.0;
|
||||
char buf[384];
|
||||
char *end = dtoa(large, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
TEST_MSG("large number result: %s", result.c_str());
|
||||
TEST_CHECK(result.length() > 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Test float conversion
|
||||
float f = 3.14159f;
|
||||
char buf[384];
|
||||
char *end = dtoa(f, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
// Should output float with appropriate precision
|
||||
TEST_CHECK(result.find("3.14159") != std::string::npos);
|
||||
TEST_MSG("float result: %s", result.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
// Test one
|
||||
double one = 1.0;
|
||||
char buf[384];
|
||||
char *end = dtoa(one, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
TEST_CHECK(result == "1.0");
|
||||
TEST_MSG("one result: %s", result.c_str());
|
||||
}
|
||||
|
||||
{
|
||||
// Test integer-like double
|
||||
double int_like = 42.0;
|
||||
char buf[384];
|
||||
char *end = dtoa(int_like, buf);
|
||||
*end = '\0';
|
||||
std::string result(buf);
|
||||
|
||||
TEST_CHECK(result == "42.0");
|
||||
TEST_MSG("integer-like result: %s", result.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
void strutil_test(void);
|
||||
void tinystring_test(void);
|
||||
void parse_int_test(void);
|
||||
void dtoa_test(void);
|
||||
|
||||
Reference in New Issue
Block a user