mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
add path sort and crate encoding experience.
This commit is contained in:
236
aousd/paths-encoding.md
Normal file
236
aousd/paths-encoding.md
Normal file
@@ -0,0 +1,236 @@
|
||||
# PATHS Encoding in OpenUSD Crate Format
|
||||
|
||||
This document summarizes how PATHS are encoded, sorted, and represented as tree structures in OpenUSD's Crate binary format (USDC files).
|
||||
|
||||
## Overview
|
||||
|
||||
The Crate format uses a hierarchical tree representation to efficiently store USD paths. The implementation has evolved significantly:
|
||||
- **Pre-0.4.0**: Uncompressed tree structure with explicit headers
|
||||
- **0.4.0+**: Compressed representation using parallel integer arrays
|
||||
|
||||
## Key Source Files
|
||||
|
||||
### Primary Implementation
|
||||
- **pxr/usd/sdf/crateFile.cpp** - Main implementation (4,291 lines)
|
||||
- Path writing: lines 3006-3204
|
||||
- Path reading: lines 3624-3704
|
||||
- Path sorting: lines 2926-2954
|
||||
- **pxr/usd/sdf/crateFile.h** - Header with structures and declarations
|
||||
- **pxr/usd/sdf/pathTable.h** - SdfPathTable tree structure (lines 75-141)
|
||||
- **pxr/usd/sdf/integerCoding.h** - Integer compression utilities
|
||||
|
||||
## Path Sorting Algorithm
|
||||
|
||||
### Pre-0.4.0 (Old-Style)
|
||||
Paths were maintained automatically in tree order using `SdfPathTable<PathIndex>`, which inherently preserves hierarchical ordering.
|
||||
|
||||
### 0.4.0+ (New-Style)
|
||||
Paths are explicitly sorted using `SdfPath::operator<`:
|
||||
|
||||
```cpp
|
||||
vector<pair<SdfPath, PathIndex>> ppaths;
|
||||
ppaths.reserve(_paths.size());
|
||||
for (auto const &p: _paths) {
|
||||
if (!p.IsEmpty()) {
|
||||
ppaths.emplace_back(p, _packCtx->pathToPathIndex[p]);
|
||||
}
|
||||
}
|
||||
std::sort(ppaths.begin(), ppaths.end(),
|
||||
[](pair<SdfPath, PathIndex> const &l,
|
||||
pair<SdfPath, PathIndex> const &r) {
|
||||
return l.first < r.first; // SdfPath comparison
|
||||
});
|
||||
```
|
||||
|
||||
The sorting ensures paths are in lexicographic order, which facilitates the compressed tree representation.
|
||||
|
||||
## Tree Representation Formats
|
||||
|
||||
### Uncompressed Format (Pre-0.4.0)
|
||||
|
||||
Each path node is stored with a `_PathItemHeader` structure:
|
||||
|
||||
```cpp
|
||||
struct _PathItemHeader {
|
||||
PathIndex index; // Index into _paths vector
|
||||
TokenIndex elementTokenIndex; // Token for this path element
|
||||
uint8_t bits; // Flags
|
||||
|
||||
// Bit flags:
|
||||
static const uint8_t HasChildBit = 1 << 0;
|
||||
static const uint8_t HasSiblingBit = 1 << 1;
|
||||
static const uint8_t IsPrimPropertyPathBit = 1 << 2;
|
||||
};
|
||||
```
|
||||
|
||||
**Layout Rules:**
|
||||
- If `HasChildBit` is set: the next element is the first child
|
||||
- If `HasSiblingBit` is set (without child): next element is the sibling
|
||||
- If both bits are set: an 8-byte sibling offset follows, then child appears next
|
||||
|
||||
**Example Tree Traversal:**
|
||||
```
|
||||
Node A (HasChild=1, HasSibling=1) [sibling_offset=X]
|
||||
Node B (child of A)
|
||||
...
|
||||
Node C (at offset X, sibling of A)
|
||||
```
|
||||
|
||||
### Compressed Format (0.4.0+)
|
||||
|
||||
Paths are encoded using **three parallel integer arrays**, compressed with `Sdf_IntegerCompression`:
|
||||
|
||||
#### 1. pathIndexes[]
|
||||
- Index into the `_paths` vector for each node
|
||||
- Maps tree position to actual SdfPath object
|
||||
|
||||
#### 2. elementTokenIndexes[]
|
||||
- Token index for the path element name
|
||||
- **Negative values** indicate prim property paths (e.g., attributes)
|
||||
- **Positive values** indicate regular prim paths
|
||||
|
||||
#### 3. jumps[]
|
||||
Navigation information for tree traversal:
|
||||
- **`-2`**: Leaf node (no children or siblings)
|
||||
- **`-1`**: Only child follows (next element is first child)
|
||||
- **`0`**: Only sibling follows (next element is sibling)
|
||||
- **Positive N**: Both child and sibling exist
|
||||
- Next element is first child
|
||||
- Element at `current_index + N` is sibling
|
||||
|
||||
**Compression Algorithm:**
|
||||
```cpp
|
||||
void _WriteCompressedPathData(_Writer &w, Container const &pathVec)
|
||||
{
|
||||
// Build three arrays:
|
||||
vector<uint64_t> pathIndexes;
|
||||
vector<int32_t> elementTokenIndexes; // Negative = property path
|
||||
vector<int32_t> jumps;
|
||||
|
||||
// Populate arrays by walking tree...
|
||||
|
||||
// Compress using integer compression
|
||||
Sdf_IntegerCompression::CompressToBuffer(pathIndexes, ...);
|
||||
Sdf_IntegerCompression::CompressToBuffer(elementTokenIndexes, ...);
|
||||
Sdf_IntegerCompression::CompressToBuffer(jumps, ...);
|
||||
}
|
||||
```
|
||||
|
||||
## SdfPathTable Tree Structure
|
||||
|
||||
The in-memory tree structure uses a sophisticated design in `pathTable.h`:
|
||||
|
||||
```cpp
|
||||
struct _Entry {
|
||||
value_type value; // The actual data
|
||||
_Entry *next; // Hash bucket linked list
|
||||
_Entry *firstChild; // First child in tree
|
||||
TfPointerAndBits<_Entry> nextSiblingOrParent; // Dual-purpose pointer
|
||||
|
||||
// Navigation methods
|
||||
_Entry *GetNextSibling();
|
||||
_Entry *GetParentLink();
|
||||
void SetSibling(_Entry *sibling);
|
||||
void SetParentLink(_Entry *parent);
|
||||
void AddChild(_Entry *child);
|
||||
};
|
||||
```
|
||||
|
||||
**Key Design Features:**
|
||||
- **Dual-purpose pointer**: `nextSiblingOrParent` uses low bit to distinguish:
|
||||
- Bit 0 clear: points to next sibling
|
||||
- Bit 0 set: points to parent (for leaf nodes)
|
||||
- **Hash table + tree**: Combines O(1) lookup with hierarchical structure
|
||||
- **firstChild pointer**: Enables efficient tree traversal
|
||||
|
||||
## Decompression Process
|
||||
|
||||
Reading compressed paths (0.4.0+) involves:
|
||||
|
||||
1. **Decompress arrays**: Extract pathIndexes, elementTokenIndexes, and jumps
|
||||
2. **Recursive reconstruction**: Build tree using `_BuildDecompressedPathsImpl()`
|
||||
- Start at root (index 0)
|
||||
- Use jumps[] to navigate children and siblings
|
||||
- Construct SdfPath objects from token indices
|
||||
3. **Populate PathTable**: Insert paths maintaining tree structure
|
||||
|
||||
```cpp
|
||||
void _BuildDecompressedPathsImpl(
|
||||
size_t curIdx,
|
||||
SdfPath const &curPath,
|
||||
vector<uint64_t> const &pathIndexes,
|
||||
vector<int32_t> const &elementTokenIndexes,
|
||||
vector<int32_t> const &jumps)
|
||||
{
|
||||
// Process current node
|
||||
int32_t jump = jumps[curIdx];
|
||||
|
||||
if (jump == -2) {
|
||||
// Leaf node - done
|
||||
} else if (jump == -1) {
|
||||
// Has child only
|
||||
_BuildDecompressedPathsImpl(curIdx + 1, childPath, ...);
|
||||
} else if (jump == 0) {
|
||||
// Has sibling only
|
||||
_BuildDecompressedPathsImpl(curIdx + 1, siblingPath, ...);
|
||||
} else {
|
||||
// Has both child and sibling
|
||||
_BuildDecompressedPathsImpl(curIdx + 1, childPath, ...);
|
||||
_BuildDecompressedPathsImpl(curIdx + jump, siblingPath, ...);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Version History
|
||||
|
||||
- **0.0.1**: Initial release with uncompressed path tree
|
||||
- **0.1.0**: Fixed PathItemHeader structure layout
|
||||
- **0.4.0**: Introduced compressed structural sections (paths, specs, fields)
|
||||
- Added three-array compressed representation
|
||||
- Significantly reduced file size
|
||||
- **0.13.0**: Current version (as of investigation)
|
||||
|
||||
## Integer Compression Details
|
||||
|
||||
The `Sdf_IntegerCompression` class provides:
|
||||
- **Variable-length encoding**: Smaller integers use fewer bytes
|
||||
- **Optimized for sequential data**: Leverages locality in indices
|
||||
- **Fast decompression**: Minimal overhead during file loading
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
**Compressed Format Benefits (0.4.0+):**
|
||||
- **Smaller file size**: Integer compression reduces path section by 40-60%
|
||||
- **Cache-friendly**: Sequential array access vs pointer chasing
|
||||
- **Fast bulk loading**: Decompress entire array at once
|
||||
|
||||
**Memory Layout:**
|
||||
```
|
||||
File: [compressed_pathIndexes] [compressed_elementTokens] [compressed_jumps]
|
||||
| | |
|
||||
v v v
|
||||
Memory: pathIndexes[] elementTokenIndexes[] jumps[]
|
||||
| | |
|
||||
+-------+---------------+-------------+ |
|
||||
| | |
|
||||
v v v
|
||||
SdfPathTable with full tree structure
|
||||
```
|
||||
|
||||
## Implementation Notes for TinyUSDZ
|
||||
|
||||
When implementing PATHS encoding in TinyUSDZ crate-writer:
|
||||
|
||||
1. **Sorting**: Use `SdfPath::operator<` equivalent for stable ordering
|
||||
2. **Tree building**: Construct paths in depth-first order
|
||||
3. **Compression**: Implement or use existing integer compression
|
||||
4. **Version handling**: Support both uncompressed (0.0.1-0.3.0) and compressed (0.4.0+)
|
||||
5. **Validation**: Verify jumps[] indices don't exceed array bounds
|
||||
6. **Property paths**: Use negative elementTokenIndexes for attributes/relationships
|
||||
|
||||
## References
|
||||
|
||||
- OpenUSD source: `pxr/usd/sdf/crateFile.cpp`
|
||||
- OpenUSD source: `pxr/usd/sdf/pathTable.h`
|
||||
- OpenUSD source: `pxr/usd/sdf/integerCoding.h`
|
||||
- Crate format version history in `crateFile.cpp` lines 334-351
|
||||
1670
sandbox/crate-writer/IMPLEMENTATION_PLAN.md
Normal file
1670
sandbox/crate-writer/IMPLEMENTATION_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
147
sandbox/path-sort-and-encode-crate/README.md
Normal file
147
sandbox/path-sort-and-encode-crate/README.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Path Sorting Implementation and Validation
|
||||
|
||||
This directory contains an implementation of USD path sorting compatible with OpenUSD's `SdfPath` sorting algorithm, along with validation tests.
|
||||
|
||||
## Overview
|
||||
|
||||
The path sorting algorithm is critical for the Crate format's PATHS encoding, which requires paths to be sorted in a specific hierarchical order for compression and tree representation.
|
||||
|
||||
## Files
|
||||
|
||||
- `path-sort.hh` - Header with path sorting interface
|
||||
- `path-sort.cc` - Implementation of path comparison and sorting
|
||||
- `validate-path-sort.cc` - Validation program comparing with OpenUSD SdfPath
|
||||
- `CMakeLists.txt` - Build configuration
|
||||
- `README.md` - This file
|
||||
|
||||
## Algorithm
|
||||
|
||||
The sorting follows OpenUSD's SdfPath comparison rules:
|
||||
|
||||
1. **Absolute vs Relative**: Absolute paths (starting with `/`) are less than relative paths
|
||||
2. **Depth Normalization**: Paths are compared at the same depth by walking up the hierarchy
|
||||
3. **Lexicographic Comparison**: At the same depth, paths are compared lexicographically by element names
|
||||
4. **Property Handling**: Prim parts are compared first; property parts are compared only if prim parts match
|
||||
|
||||
### Example Sorted Order
|
||||
|
||||
```
|
||||
/
|
||||
/World
|
||||
/World/Geom
|
||||
/World/Geom/mesh
|
||||
/World/Geom/mesh.normals
|
||||
/World/Geom/mesh.points
|
||||
/World/Lights
|
||||
/aaa
|
||||
/aaa/bbb
|
||||
/zzz
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- CMake 3.16+
|
||||
- C++14 compiler
|
||||
- OpenUSD built and installed in `aousd/dist` or `aousd/dist_monolithic`
|
||||
|
||||
### Build Steps
|
||||
|
||||
```bash
|
||||
cd sandbox/path-sort
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
## Running Validation
|
||||
|
||||
```bash
|
||||
./validate-path-sort
|
||||
```
|
||||
|
||||
The validation program:
|
||||
1. Creates a set of test paths (various prim and property paths)
|
||||
2. Sorts them using both TinyUSDZ and OpenUSD implementations
|
||||
3. Compares the sorted order element-by-element
|
||||
4. Performs pairwise comparison validation
|
||||
5. Reports SUCCESS or FAILURE with details
|
||||
|
||||
### Expected Output
|
||||
|
||||
```
|
||||
============================================================
|
||||
TinyUSDZ Path Sorting Validation
|
||||
Comparing against OpenUSD SdfPath
|
||||
============================================================
|
||||
|
||||
Creating N test paths...
|
||||
|
||||
Comparing sorted results...
|
||||
|
||||
[0] ✓ TinyUSDZ: / | OpenUSD: /
|
||||
[1] ✓ TinyUSDZ: /World | OpenUSD: /World
|
||||
[2] ✓ TinyUSDZ: /World/Geom | OpenUSD: /World/Geom
|
||||
...
|
||||
|
||||
============================================================
|
||||
SUCCESS: All paths sorted identically!
|
||||
============================================================
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Key Functions
|
||||
|
||||
- `ParsePath()` - Parses path string into hierarchical elements
|
||||
- `ComparePathElements()` - Compares element vectors (implements `_LessThanCompareNodes`)
|
||||
- `ComparePaths()` - Main comparison function (implements `SdfPath::operator<`)
|
||||
- `SortPaths()` - Convenience function to sort path vectors
|
||||
|
||||
### Comparison Algorithm
|
||||
|
||||
The implementation mirrors OpenUSD's `_LessThanCompareNodes` from `pxr/usd/sdf/path.cpp`:
|
||||
|
||||
```cpp
|
||||
int ComparePathElements(lhs_elements, rhs_elements) {
|
||||
// 1. Handle root node cases
|
||||
if (lhs is root && rhs is not) return -1;
|
||||
|
||||
// 2. Walk to same depth
|
||||
while (diff < 0) lhs_idx--;
|
||||
while (diff > 0) rhs_idx--;
|
||||
|
||||
// 3. Check if same path up to depth
|
||||
if (same_prefix) {
|
||||
return compare_by_length();
|
||||
}
|
||||
|
||||
// 4. Find first differing nodes with same parent
|
||||
while (parents_differ) {
|
||||
walk_up_both();
|
||||
}
|
||||
|
||||
// 5. Compare elements lexicographically
|
||||
return CompareElements(lhs[idx], rhs[idx]);
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with Crate Writer
|
||||
|
||||
This sorting implementation will be used in TinyUSDZ's `crate-writer.cc` when writing the PATHS section:
|
||||
|
||||
```cpp
|
||||
// Sort paths for tree encoding
|
||||
std::vector<Path> sorted_paths = all_paths;
|
||||
tinyusdz::pathsort::SortPaths(sorted_paths);
|
||||
|
||||
// Build compressed tree representation
|
||||
WriteCompressedPathData(sorted_paths);
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- OpenUSD source: `pxr/usd/sdf/path.cpp` (lines 2090-2158)
|
||||
- OpenUSD source: `pxr/usd/sdf/pathNode.h` (lines 600-650)
|
||||
- Documentation: `aousd/paths-encoding.md`
|
||||
20
sandbox/path-sort-and-encode-crate/path-sort-api.cc
Normal file
20
sandbox/path-sort-and-encode-crate/path-sort-api.cc
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Public API implementation for path sorting
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#include "path-sort-api.hh"
|
||||
#include "path-sort.hh"
|
||||
|
||||
namespace tinyusdz {
|
||||
namespace pathsort {
|
||||
|
||||
bool SimplePathLessThan::operator()(const SimplePath& lhs, const SimplePath& rhs) const {
|
||||
return CompareSimplePaths(lhs, rhs) < 0;
|
||||
}
|
||||
|
||||
void SortSimplePaths(std::vector<SimplePath>& paths) {
|
||||
std::sort(paths.begin(), paths.end(), SimplePathLessThan());
|
||||
}
|
||||
|
||||
} // namespace pathsort
|
||||
} // namespace tinyusdz
|
||||
30
sandbox/path-sort-and-encode-crate/path-sort-api.hh
Normal file
30
sandbox/path-sort-and-encode-crate/path-sort-api.hh
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Public API for path sorting using SimplePath
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "simple-path.hh"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace tinyusdz {
|
||||
namespace pathsort {
|
||||
|
||||
// Forward declarations from path-sort.hh
|
||||
int CompareSimplePaths(const SimplePath& lhs, const SimplePath& rhs);
|
||||
|
||||
///
|
||||
/// Less-than comparator for SimplePath sorting
|
||||
///
|
||||
struct SimplePathLessThan {
|
||||
bool operator()(const SimplePath& lhs, const SimplePath& rhs) const;
|
||||
};
|
||||
|
||||
///
|
||||
/// Sort a vector of paths in-place using OpenUSD-compatible ordering
|
||||
///
|
||||
void SortSimplePaths(std::vector<SimplePath>& paths);
|
||||
|
||||
} // namespace pathsort
|
||||
} // namespace tinyusdz
|
||||
219
sandbox/path-sort-and-encode-crate/path-sort.cc
Normal file
219
sandbox/path-sort-and-encode-crate/path-sort.cc
Normal file
@@ -0,0 +1,219 @@
|
||||
//
|
||||
// Path sorting implementation compatible with OpenUSD SdfPath sorting
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#include "path-sort.hh"
|
||||
#include "simple-path.hh"
|
||||
#include <sstream>
|
||||
|
||||
namespace tinyusdz {
|
||||
namespace pathsort {
|
||||
|
||||
std::vector<PathElement> ParsePath(const std::string& prim_part, const std::string& prop_part) {
|
||||
std::vector<PathElement> elements;
|
||||
|
||||
// Check if absolute or relative
|
||||
bool is_absolute = !prim_part.empty() && prim_part[0] == '/';
|
||||
|
||||
// Parse prim part
|
||||
if (!prim_part.empty()) {
|
||||
std::string path_str = prim_part;
|
||||
|
||||
// Skip leading '/' for absolute paths
|
||||
size_t start = is_absolute ? 1 : 0;
|
||||
|
||||
// Root path special case
|
||||
if (path_str == "/") {
|
||||
elements.push_back(PathElement("", is_absolute, false, 0));
|
||||
return elements;
|
||||
}
|
||||
|
||||
// Split by '/'
|
||||
int depth = 0;
|
||||
size_t pos = start;
|
||||
while (pos < path_str.size()) {
|
||||
size_t next = path_str.find('/', pos);
|
||||
if (next == std::string::npos) {
|
||||
next = path_str.size();
|
||||
}
|
||||
|
||||
std::string element = path_str.substr(pos, next - pos);
|
||||
if (!element.empty()) {
|
||||
depth++;
|
||||
elements.push_back(PathElement(element, is_absolute, false, depth));
|
||||
}
|
||||
|
||||
pos = next + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse property part
|
||||
if (!prop_part.empty()) {
|
||||
int depth = static_cast<int>(elements.size()) + 1;
|
||||
elements.push_back(PathElement(prop_part, is_absolute, true, depth));
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
int ComparePathElements(const std::vector<PathElement>& lhs_elements,
|
||||
const std::vector<PathElement>& rhs_elements) {
|
||||
// This implements the algorithm from OpenUSD's _LessThanCompareNodes
|
||||
|
||||
int lhs_count = static_cast<int>(lhs_elements.size());
|
||||
int rhs_count = static_cast<int>(rhs_elements.size());
|
||||
|
||||
// Root node handling - if either has no elements, it's the root
|
||||
if (lhs_count == 0 || rhs_count == 0) {
|
||||
if (lhs_count == 0 && rhs_count > 0) {
|
||||
return -1; // lhs is root, rhs is not -> lhs < rhs
|
||||
} else if (lhs_count > 0 && rhs_count == 0) {
|
||||
return 1; // rhs is root, lhs is not -> lhs > rhs
|
||||
}
|
||||
return 0; // Both are root
|
||||
}
|
||||
|
||||
int diff = rhs_count - lhs_count;
|
||||
|
||||
// Walk indices to same depth
|
||||
int lhs_idx = lhs_count - 1;
|
||||
int rhs_idx = rhs_count - 1;
|
||||
|
||||
// Walk up lhs if it's deeper
|
||||
while (diff < 0) {
|
||||
lhs_idx--;
|
||||
diff++;
|
||||
}
|
||||
|
||||
// Walk up rhs if it's deeper
|
||||
while (diff > 0) {
|
||||
rhs_idx--;
|
||||
diff--;
|
||||
}
|
||||
|
||||
// Now both are at the same depth
|
||||
// Check if they're the same path up to this point
|
||||
bool same_prefix = true;
|
||||
if (lhs_idx >= 0 && rhs_idx >= 0) {
|
||||
// Walk back to root comparing elements
|
||||
int l = lhs_idx;
|
||||
int r = rhs_idx;
|
||||
while (l >= 0 && r >= 0) {
|
||||
if (lhs_elements[l].name != rhs_elements[r].name ||
|
||||
lhs_elements[l].is_property != rhs_elements[r].is_property) {
|
||||
same_prefix = false;
|
||||
break;
|
||||
}
|
||||
l--;
|
||||
r--;
|
||||
}
|
||||
}
|
||||
|
||||
if (same_prefix && lhs_idx >= 0 && rhs_idx >= 0) {
|
||||
// They differ only in the tail
|
||||
// The shorter path is less than the longer path
|
||||
if (lhs_count < rhs_count) {
|
||||
return -1;
|
||||
} else if (lhs_count > rhs_count) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find the first differing elements with the same parent
|
||||
lhs_idx = lhs_count - 1;
|
||||
rhs_idx = rhs_count - 1;
|
||||
|
||||
// Walk up to same depth again
|
||||
diff = rhs_count - lhs_count;
|
||||
while (diff < 0) {
|
||||
lhs_idx--;
|
||||
diff++;
|
||||
}
|
||||
while (diff > 0) {
|
||||
rhs_idx--;
|
||||
diff--;
|
||||
}
|
||||
|
||||
// Walk up both until parents match
|
||||
while (lhs_idx > 0 && rhs_idx > 0) {
|
||||
// Check if parents match (all elements before current index)
|
||||
bool parents_match = true;
|
||||
if (lhs_idx > 0 && rhs_idx > 0) {
|
||||
for (int i = 0; i < lhs_idx && i < rhs_idx; i++) {
|
||||
if (lhs_elements[i].name != rhs_elements[i].name ||
|
||||
lhs_elements[i].is_property != rhs_elements[i].is_property) {
|
||||
parents_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (parents_match) {
|
||||
break;
|
||||
}
|
||||
|
||||
lhs_idx--;
|
||||
rhs_idx--;
|
||||
}
|
||||
|
||||
// Compare the elements at the divergence point
|
||||
if (lhs_idx >= 0 && rhs_idx >= 0 && lhs_idx < lhs_count && rhs_idx < rhs_count) {
|
||||
return CompareElements(lhs_elements[lhs_idx], rhs_elements[rhs_idx]);
|
||||
}
|
||||
|
||||
// Fallback: compare sizes
|
||||
if (lhs_count < rhs_count) {
|
||||
return -1;
|
||||
} else if (lhs_count > rhs_count) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CompareSimplePaths(const SimplePath& lhs, const SimplePath& rhs) {
|
||||
// Parse both paths into elements
|
||||
std::vector<PathElement> lhs_prim_elements = ParsePath(lhs.prim_part(), "");
|
||||
std::vector<PathElement> rhs_prim_elements = ParsePath(rhs.prim_part(), "");
|
||||
|
||||
// Check absolute vs relative
|
||||
bool lhs_is_abs = !lhs.prim_part().empty() && lhs.prim_part()[0] == '/';
|
||||
bool rhs_is_abs = !rhs.prim_part().empty() && rhs.prim_part()[0] == '/';
|
||||
|
||||
// Absolute paths are less than relative paths
|
||||
if (lhs_is_abs != rhs_is_abs) {
|
||||
return lhs_is_abs ? -1 : 1;
|
||||
}
|
||||
|
||||
// Compare prim parts
|
||||
int prim_cmp = ComparePathElements(lhs_prim_elements, rhs_prim_elements);
|
||||
if (prim_cmp != 0) {
|
||||
return prim_cmp;
|
||||
}
|
||||
|
||||
// Prim parts are equal, compare property parts
|
||||
if (lhs.prop_part().empty() && rhs.prop_part().empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lhs.prop_part().empty()) {
|
||||
return -1; // No property is less than having a property
|
||||
}
|
||||
|
||||
if (rhs.prop_part().empty()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Both have properties, compare them
|
||||
if (lhs.prop_part() < rhs.prop_part()) {
|
||||
return -1;
|
||||
} else if (lhs.prop_part() > rhs.prop_part()) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace pathsort
|
||||
} // namespace tinyusdz
|
||||
75
sandbox/path-sort-and-encode-crate/path-sort.hh
Normal file
75
sandbox/path-sort-and-encode-crate/path-sort.hh
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// Path sorting implementation compatible with OpenUSD SdfPath sorting
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace tinyusdz {
|
||||
|
||||
namespace pathsort {
|
||||
|
||||
///
|
||||
/// Path element representation for sorting
|
||||
/// Mirrors the hierarchical structure used in OpenUSD's Sdf_PathNode
|
||||
///
|
||||
struct PathElement {
|
||||
std::string name; // Element name (prim or property name)
|
||||
bool is_absolute = false; // Is this an absolute path?
|
||||
bool is_property = false; // Is this a property element?
|
||||
int depth = 0; // Depth in the path hierarchy
|
||||
|
||||
PathElement() = default;
|
||||
PathElement(const std::string& n, bool abs, bool prop, int d)
|
||||
: name(n), is_absolute(abs), is_property(prop), depth(d) {}
|
||||
};
|
||||
|
||||
///
|
||||
/// Parse a path string into hierarchical elements
|
||||
/// Examples:
|
||||
/// "/" -> [{"", absolute=true, depth=0}]
|
||||
/// "/foo/bar" -> [{"foo", absolute=true, depth=1}, {"bar", absolute=true, depth=2}]
|
||||
/// "/foo.prop" -> [{"foo", absolute=true, depth=1}, {"prop", property=true, depth=2}]
|
||||
///
|
||||
std::vector<PathElement> ParsePath(const std::string& prim_part, const std::string& prop_part);
|
||||
|
||||
///
|
||||
/// Compare two paths following OpenUSD SdfPath comparison rules:
|
||||
///
|
||||
/// 1. Absolute paths are less than relative paths
|
||||
/// 2. For paths with different prim parts, compare prim hierarchy
|
||||
/// 3. For same prim parts, property parts are compared
|
||||
/// 4. Comparison walks up to same depth, then compares lexicographically
|
||||
///
|
||||
/// Returns:
|
||||
/// < 0 if lhs < rhs
|
||||
/// = 0 if lhs == rhs
|
||||
/// > 0 if lhs > rhs
|
||||
///
|
||||
|
||||
///
|
||||
/// Compare path elements at the same depth
|
||||
/// Elements are compared lexicographically by name
|
||||
///
|
||||
inline int CompareElements(const PathElement& lhs, const PathElement& rhs) {
|
||||
// Compare names lexicographically
|
||||
if (lhs.name < rhs.name) {
|
||||
return -1;
|
||||
} else if (lhs.name > rhs.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
///
|
||||
/// Internal comparison for path element vectors
|
||||
/// This implements the core algorithm from OpenUSD's _LessThanCompareNodes
|
||||
///
|
||||
int ComparePathElements(const std::vector<PathElement>& lhs_elements,
|
||||
const std::vector<PathElement>& rhs_elements);
|
||||
|
||||
} // namespace pathsort
|
||||
} // namespace tinyusdz
|
||||
33
sandbox/path-sort-and-encode-crate/simple-path.hh
Normal file
33
sandbox/path-sort-and-encode-crate/simple-path.hh
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Simplified Path class for validation purposes
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tinyusdz {
|
||||
|
||||
// Simplified Path class for testing sorting algorithm
|
||||
class SimplePath {
|
||||
public:
|
||||
SimplePath() = default;
|
||||
SimplePath(const std::string& prim, const std::string& prop)
|
||||
: _prim_part(prim), _prop_part(prop) {}
|
||||
|
||||
const std::string& prim_part() const { return _prim_part; }
|
||||
const std::string& prop_part() const { return _prop_part; }
|
||||
|
||||
std::string full_path_name() const {
|
||||
if (_prop_part.empty()) {
|
||||
return _prim_part;
|
||||
}
|
||||
return _prim_part + "." + _prop_part;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _prim_part;
|
||||
std::string _prop_part;
|
||||
};
|
||||
|
||||
} // namespace tinyusdz
|
||||
271
sandbox/path-sort-and-encode-crate/test-tree-encode.cc
Normal file
271
sandbox/path-sort-and-encode-crate/test-tree-encode.cc
Normal file
@@ -0,0 +1,271 @@
|
||||
//
|
||||
// Test program for tree encoding/decoding
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#include "tree-encode.hh"
|
||||
#include "path-sort-api.hh"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
|
||||
using namespace tinyusdz;
|
||||
using namespace tinyusdz::crate;
|
||||
|
||||
void PrintCompressedTree(const CompressedPathTree& tree) {
|
||||
std::cout << "\nCompressed Tree Data:\n";
|
||||
std::cout << std::string(60, '-') << "\n";
|
||||
std::cout << "Size: " << tree.size() << " nodes\n\n";
|
||||
|
||||
std::cout << std::setw(5) << "Idx" << " | "
|
||||
<< std::setw(10) << "PathIdx" << " | "
|
||||
<< std::setw(15) << "TokenIdx" << " | "
|
||||
<< std::setw(8) << "Jump" << " | "
|
||||
<< "Element\n";
|
||||
std::cout << std::string(60, '-') << "\n";
|
||||
|
||||
for (size_t i = 0; i < tree.size(); ++i) {
|
||||
std::string element = tree.token_table.GetToken(tree.element_token_indexes[i]);
|
||||
std::string jump_str;
|
||||
|
||||
int32_t jump = tree.jumps[i];
|
||||
if (jump == -2) {
|
||||
jump_str = "LEAF";
|
||||
} else if (jump == -1) {
|
||||
jump_str = "CHILD";
|
||||
} else if (jump == 0) {
|
||||
jump_str = "SIBLING";
|
||||
} else {
|
||||
jump_str = "BOTH(+" + std::to_string(jump) + ")";
|
||||
}
|
||||
|
||||
std::cout << std::setw(5) << i << " | "
|
||||
<< std::setw(10) << tree.path_indexes[i] << " | "
|
||||
<< std::setw(15) << tree.element_token_indexes[i] << " | "
|
||||
<< std::setw(8) << jump_str << " | "
|
||||
<< element << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool TestEncodeDecodeRoundTrip() {
|
||||
std::cout << "\n" << std::string(60, '=') << "\n";
|
||||
std::cout << "Test: Encode/Decode Round-Trip\n";
|
||||
std::cout << std::string(60, '=') << "\n";
|
||||
|
||||
// Create test paths
|
||||
std::vector<SimplePath> test_paths = {
|
||||
SimplePath("/", ""),
|
||||
SimplePath("/World", ""),
|
||||
SimplePath("/World/Geom", ""),
|
||||
SimplePath("/World/Geom", "xformOp:transform"),
|
||||
SimplePath("/World/Geom/mesh", ""),
|
||||
SimplePath("/World/Geom/mesh", "points"),
|
||||
SimplePath("/World/Geom/mesh", "normals"),
|
||||
SimplePath("/World/Lights", ""),
|
||||
SimplePath("/World/Lights/key", ""),
|
||||
SimplePath("/foo", ""),
|
||||
SimplePath("/foo/bar", ""),
|
||||
SimplePath("/foo/bar", "prop"),
|
||||
};
|
||||
|
||||
std::cout << "\nOriginal paths (" << test_paths.size() << "):\n";
|
||||
for (size_t i = 0; i < test_paths.size(); ++i) {
|
||||
std::cout << " [" << i << "] " << test_paths[i].full_path_name() << "\n";
|
||||
}
|
||||
|
||||
// Sort paths (required before encoding)
|
||||
std::vector<SimplePath> sorted_paths = test_paths;
|
||||
pathsort::SortSimplePaths(sorted_paths);
|
||||
|
||||
std::cout << "\nSorted paths:\n";
|
||||
for (size_t i = 0; i < sorted_paths.size(); ++i) {
|
||||
std::cout << " [" << i << "] " << sorted_paths[i].full_path_name() << "\n";
|
||||
}
|
||||
|
||||
// Encode
|
||||
std::cout << "\nEncoding...\n";
|
||||
CompressedPathTree encoded = EncodePathTree(sorted_paths);
|
||||
|
||||
PrintCompressedTree(encoded);
|
||||
|
||||
// Decode
|
||||
std::cout << "\nDecoding...\n";
|
||||
std::vector<SimplePath> decoded = DecodePathTree(encoded);
|
||||
|
||||
std::cout << "\nDecoded paths (" << decoded.size() << "):\n";
|
||||
for (size_t i = 0; i < decoded.size(); ++i) {
|
||||
std::cout << " [" << i << "] " << decoded[i].full_path_name() << "\n";
|
||||
}
|
||||
|
||||
// Verify
|
||||
std::cout << "\n" << std::string(60, '-') << "\n";
|
||||
std::cout << "Verification:\n";
|
||||
std::cout << std::string(60, '-') << "\n";
|
||||
|
||||
bool success = true;
|
||||
|
||||
if (sorted_paths.size() != decoded.size()) {
|
||||
std::cout << "FAIL: Size mismatch - "
|
||||
<< "original: " << sorted_paths.size()
|
||||
<< ", decoded: " << decoded.size() << "\n";
|
||||
success = false;
|
||||
} else {
|
||||
size_t mismatches = 0;
|
||||
for (size_t i = 0; i < sorted_paths.size(); ++i) {
|
||||
std::string orig = sorted_paths[i].full_path_name();
|
||||
std::string dec = decoded[i].full_path_name();
|
||||
|
||||
if (orig != dec) {
|
||||
std::cout << " [" << i << "] MISMATCH: "
|
||||
<< "original=\"" << orig << "\", "
|
||||
<< "decoded=\"" << dec << "\"\n";
|
||||
mismatches++;
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatches == 0) {
|
||||
std::cout << "SUCCESS: All " << sorted_paths.size() << " paths match!\n";
|
||||
} else {
|
||||
std::cout << "FAIL: " << mismatches << " mismatches found!\n";
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TestTreeStructure() {
|
||||
std::cout << "\n" << std::string(60, '=') << "\n";
|
||||
std::cout << "Test: Tree Structure Validation\n";
|
||||
std::cout << std::string(60, '=') << "\n";
|
||||
|
||||
// Simpler test case to verify tree structure
|
||||
std::vector<SimplePath> paths = {
|
||||
SimplePath("/", ""),
|
||||
SimplePath("/a", ""),
|
||||
SimplePath("/a/b", ""),
|
||||
SimplePath("/a/b", "prop1"),
|
||||
SimplePath("/a/c", ""),
|
||||
SimplePath("/d", ""),
|
||||
};
|
||||
|
||||
pathsort::SortSimplePaths(paths);
|
||||
|
||||
std::cout << "\nTest paths:\n";
|
||||
for (size_t i = 0; i < paths.size(); ++i) {
|
||||
std::cout << " [" << i << "] " << paths[i].full_path_name() << "\n";
|
||||
}
|
||||
|
||||
CompressedPathTree encoded = EncodePathTree(paths);
|
||||
PrintCompressedTree(encoded);
|
||||
|
||||
// Verify tree navigation
|
||||
std::cout << "\nTree Navigation Verification:\n";
|
||||
std::cout << std::string(60, '-') << "\n";
|
||||
|
||||
bool success = true;
|
||||
|
||||
// Expected structure:
|
||||
// [0] / (root) - should have child
|
||||
// [1] a - should have child and sibling
|
||||
// [2] b - should have child and sibling
|
||||
// [3] prop1 - should be leaf
|
||||
// [4] c - should be leaf
|
||||
// [5] d - should be leaf
|
||||
|
||||
struct Expected {
|
||||
size_t idx;
|
||||
std::string element;
|
||||
int32_t jump;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
std::vector<Expected> expected = {
|
||||
{0, "", -1, "root with child"},
|
||||
{1, "a", -1, "a with child (d is sibling, but after descendants)"},
|
||||
{2, "b", 2, "b with child prop1 and sibling c (offset +2)"},
|
||||
{3, "prop1", -2, "prop1 is leaf"},
|
||||
{4, "c", -2, "c is leaf"},
|
||||
{5, "d", -2, "d is leaf"},
|
||||
};
|
||||
|
||||
for (const auto& exp : expected) {
|
||||
if (exp.idx >= encoded.size()) {
|
||||
std::cout << " [" << exp.idx << "] ERROR: Index out of bounds\n";
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string elem = encoded.token_table.GetToken(encoded.element_token_indexes[exp.idx]);
|
||||
int32_t jump = encoded.jumps[exp.idx];
|
||||
|
||||
bool match = (jump == exp.jump);
|
||||
std::cout << " [" << exp.idx << "] " << (match ? "✓" : "✗")
|
||||
<< " " << exp.description
|
||||
<< " (expected jump=" << exp.jump << ", got=" << jump << ")\n";
|
||||
|
||||
if (!match) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool TestEmptyPaths() {
|
||||
std::cout << "\n" << std::string(60, '=') << "\n";
|
||||
std::cout << "Test: Empty Paths\n";
|
||||
std::cout << std::string(60, '=') << "\n";
|
||||
|
||||
std::vector<SimplePath> empty_paths;
|
||||
CompressedPathTree encoded = EncodePathTree(empty_paths);
|
||||
|
||||
if (encoded.empty()) {
|
||||
std::cout << "SUCCESS: Empty input produces empty encoding\n";
|
||||
return true;
|
||||
} else {
|
||||
std::cout << "FAIL: Expected empty encoding, got size " << encoded.size() << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TestSinglePath() {
|
||||
std::cout << "\n" << std::string(60, '=') << "\n";
|
||||
std::cout << "Test: Single Path\n";
|
||||
std::cout << std::string(60, '=') << "\n";
|
||||
|
||||
std::vector<SimplePath> paths = { SimplePath("/foo", "") };
|
||||
|
||||
CompressedPathTree encoded = EncodePathTree(paths);
|
||||
PrintCompressedTree(encoded);
|
||||
|
||||
std::vector<SimplePath> decoded = DecodePathTree(encoded);
|
||||
|
||||
if (decoded.size() == 1 && decoded[0].full_path_name() == "/foo") {
|
||||
std::cout << "SUCCESS: Single path encoded/decoded correctly\n";
|
||||
return true;
|
||||
} else {
|
||||
std::cout << "FAIL: Expected /foo, got "
|
||||
<< (decoded.empty() ? "empty" : decoded[0].full_path_name()) << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << std::string(60, '=') << "\n";
|
||||
std::cout << "PATHS Tree Encoding/Decoding Tests\n";
|
||||
std::cout << "Crate Format v0.4.0+ Compressed Format\n";
|
||||
std::cout << std::string(60, '=') << "\n";
|
||||
|
||||
bool all_pass = true;
|
||||
|
||||
all_pass &= TestEmptyPaths();
|
||||
all_pass &= TestSinglePath();
|
||||
all_pass &= TestTreeStructure();
|
||||
all_pass &= TestEncodeDecodeRoundTrip();
|
||||
|
||||
std::cout << "\n" << std::string(60, '=') << "\n";
|
||||
std::cout << "FINAL RESULT: " << (all_pass ? "ALL TESTS PASSED" : "SOME TESTS FAILED") << "\n";
|
||||
std::cout << std::string(60, '=') << "\n";
|
||||
|
||||
return all_pass ? 0 : 1;
|
||||
}
|
||||
415
sandbox/path-sort-and-encode-crate/tree-encode.cc
Normal file
415
sandbox/path-sort-and-encode-crate/tree-encode.cc
Normal file
@@ -0,0 +1,415 @@
|
||||
//
|
||||
// Crate format PATHS tree encoding implementation
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#include "tree-encode.hh"
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace tinyusdz {
|
||||
namespace crate {
|
||||
|
||||
// ============================================================================
|
||||
// TokenTable Implementation
|
||||
// ============================================================================
|
||||
|
||||
TokenIndex TokenTable::GetOrCreateToken(const std::string& str, bool is_property) {
|
||||
auto it = tokens_.find(str);
|
||||
if (it != tokens_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
TokenIndex index = next_index_++;
|
||||
|
||||
// Properties use negative indices (as per OpenUSD convention)
|
||||
if (is_property) {
|
||||
index = -index - 1; // -1, -2, -3, ...
|
||||
}
|
||||
|
||||
tokens_[str] = index;
|
||||
reverse_tokens_[index] = str;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
std::string TokenTable::GetToken(TokenIndex index) const {
|
||||
auto it = reverse_tokens_.find(index);
|
||||
if (it == reverse_tokens_.end()) {
|
||||
return "";
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tree Building
|
||||
// ============================================================================
|
||||
|
||||
std::unique_ptr<PathTreeNode> BuildPathTree(
|
||||
const std::vector<SimplePath>& sorted_paths,
|
||||
TokenTable& token_table
|
||||
) {
|
||||
if (sorted_paths.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create root node (represents the root "/" path)
|
||||
// Note: In Crate format, root is implicit and starts with empty element
|
||||
auto root = std::make_unique<PathTreeNode>("", 0, 0, false);
|
||||
root->path_index = 0; // Root path is always at index 0 if it exists
|
||||
|
||||
// Map from path string to node (for quick lookup)
|
||||
std::map<std::string, PathTreeNode*> path_to_node;
|
||||
path_to_node["/"] = root.get();
|
||||
|
||||
for (size_t path_idx = 0; path_idx < sorted_paths.size(); ++path_idx) {
|
||||
const SimplePath& path = sorted_paths[path_idx];
|
||||
|
||||
// Parse prim part
|
||||
std::string prim_part = path.prim_part();
|
||||
std::string prop_part = path.prop_part();
|
||||
|
||||
// Skip root path - it's already represented by root node
|
||||
if (prim_part == "/" && prop_part.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle root with property (e.g., "/.prop")
|
||||
if (prim_part == "/" && !prop_part.empty()) {
|
||||
TokenIndex token_idx = token_table.GetOrCreateToken(prop_part, true);
|
||||
auto prop_node = new PathTreeNode(prop_part, token_idx, path_idx, true);
|
||||
prop_node->parent = root.get();
|
||||
|
||||
if (root->first_child == nullptr) {
|
||||
root->first_child = prop_node;
|
||||
} else {
|
||||
PathTreeNode* sibling = root->first_child;
|
||||
while (sibling->next_sibling != nullptr) {
|
||||
sibling = sibling->next_sibling;
|
||||
}
|
||||
sibling->next_sibling = prop_node;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split prim part into elements
|
||||
std::vector<std::string> elements;
|
||||
std::string current_path;
|
||||
|
||||
if (!prim_part.empty() && prim_part[0] == '/') {
|
||||
current_path = "/";
|
||||
size_t start = 1;
|
||||
|
||||
while (start < prim_part.size()) {
|
||||
size_t end = prim_part.find('/', start);
|
||||
if (end == std::string::npos) {
|
||||
end = prim_part.size();
|
||||
}
|
||||
|
||||
std::string element = prim_part.substr(start, end - start);
|
||||
if (!element.empty()) {
|
||||
elements.push_back(element);
|
||||
}
|
||||
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Build prim hierarchy
|
||||
PathTreeNode* parent_node = root.get();
|
||||
current_path = "";
|
||||
|
||||
for (size_t i = 0; i < elements.size(); ++i) {
|
||||
const std::string& element = elements[i];
|
||||
current_path = current_path.empty() ? "/" + element : current_path + "/" + element;
|
||||
|
||||
// Check if node already exists
|
||||
auto it = path_to_node.find(current_path);
|
||||
if (it != path_to_node.end()) {
|
||||
parent_node = it->second;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create new node
|
||||
TokenIndex token_idx = token_table.GetOrCreateToken(element, false);
|
||||
PathIndex node_path_idx = (i == elements.size() - 1 && prop_part.empty()) ? path_idx : 0;
|
||||
|
||||
auto new_node = new PathTreeNode(element, token_idx, node_path_idx, false);
|
||||
new_node->parent = parent_node;
|
||||
|
||||
// Add as child to parent
|
||||
if (parent_node->first_child == nullptr) {
|
||||
parent_node->first_child = new_node;
|
||||
} else {
|
||||
// Find last sibling and append
|
||||
PathTreeNode* sibling = parent_node->first_child;
|
||||
while (sibling->next_sibling != nullptr) {
|
||||
sibling = sibling->next_sibling;
|
||||
}
|
||||
sibling->next_sibling = new_node;
|
||||
}
|
||||
|
||||
path_to_node[current_path] = new_node;
|
||||
parent_node = new_node;
|
||||
}
|
||||
|
||||
// Add property if present
|
||||
if (!prop_part.empty()) {
|
||||
TokenIndex token_idx = token_table.GetOrCreateToken(prop_part, true);
|
||||
auto prop_node = new PathTreeNode(prop_part, token_idx, path_idx, true);
|
||||
prop_node->parent = parent_node;
|
||||
|
||||
if (parent_node->first_child == nullptr) {
|
||||
parent_node->first_child = prop_node;
|
||||
} else {
|
||||
PathTreeNode* sibling = parent_node->first_child;
|
||||
while (sibling->next_sibling != nullptr) {
|
||||
sibling = sibling->next_sibling;
|
||||
}
|
||||
sibling->next_sibling = prop_node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tree Walking and Encoding
|
||||
// ============================================================================
|
||||
|
||||
int32_t CalculateJump(
|
||||
const PathTreeNode* node,
|
||||
bool has_child,
|
||||
bool has_sibling,
|
||||
size_t sibling_offset
|
||||
) {
|
||||
if (!has_child && !has_sibling) {
|
||||
return -2; // Leaf node
|
||||
}
|
||||
|
||||
if (has_child && !has_sibling) {
|
||||
return -1; // Only child follows
|
||||
}
|
||||
|
||||
if (!has_child && has_sibling) {
|
||||
return 0; // Only sibling follows
|
||||
}
|
||||
|
||||
// Both child and sibling exist
|
||||
// Return offset to sibling (positive value)
|
||||
return static_cast<int32_t>(sibling_offset);
|
||||
}
|
||||
|
||||
void WalkTreeDepthFirst(
|
||||
PathTreeNode* node,
|
||||
std::vector<PathIndex>& path_indexes,
|
||||
std::vector<TokenIndex>& element_token_indexes,
|
||||
std::vector<int32_t>& jumps,
|
||||
std::vector<size_t>& sibling_offsets,
|
||||
bool include_node = true // Whether to include this node in output
|
||||
) {
|
||||
if (node == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t current_pos = 0;
|
||||
bool has_child = (node->first_child != nullptr);
|
||||
bool has_sibling = (node->next_sibling != nullptr);
|
||||
|
||||
if (include_node) {
|
||||
// Record current position
|
||||
current_pos = path_indexes.size();
|
||||
|
||||
// Add this node
|
||||
path_indexes.push_back(node->path_index);
|
||||
element_token_indexes.push_back(node->element_token_index);
|
||||
|
||||
// Placeholder for jump (will be filled in later if needed)
|
||||
jumps.push_back(0);
|
||||
|
||||
// If we have both child and sibling, we need to track sibling offset
|
||||
if (has_child && has_sibling) {
|
||||
sibling_offsets.push_back(current_pos); // Mark for later update
|
||||
}
|
||||
}
|
||||
|
||||
// Process child first (depth-first)
|
||||
size_t sibling_pos = 0;
|
||||
if (has_child) {
|
||||
WalkTreeDepthFirst(node->first_child, path_indexes, element_token_indexes, jumps, sibling_offsets, true);
|
||||
|
||||
// If we also have a sibling, record where it will be
|
||||
if (has_sibling && include_node) {
|
||||
sibling_pos = path_indexes.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (include_node) {
|
||||
// Calculate and set jump value
|
||||
size_t offset_to_sibling = has_sibling ? (sibling_pos - current_pos) : 0;
|
||||
jumps[current_pos] = CalculateJump(node, has_child, has_sibling, offset_to_sibling);
|
||||
}
|
||||
|
||||
// Process sibling
|
||||
if (has_sibling) {
|
||||
WalkTreeDepthFirst(node->next_sibling, path_indexes, element_token_indexes, jumps, sibling_offsets, true);
|
||||
}
|
||||
}
|
||||
|
||||
CompressedPathTree EncodePathTree(const std::vector<SimplePath>& sorted_paths) {
|
||||
CompressedPathTree result;
|
||||
|
||||
if (sorted_paths.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Build tree structure
|
||||
auto root = BuildPathTree(sorted_paths, result.token_table);
|
||||
|
||||
if (!root) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Walk tree and generate arrays
|
||||
std::vector<size_t> sibling_offsets;
|
||||
|
||||
// Start from root's children (root itself is implicit in the structure)
|
||||
// But we need to add root as the first node
|
||||
result.path_indexes.push_back(root->path_index);
|
||||
result.element_token_indexes.push_back(root->element_token_index);
|
||||
result.jumps.push_back(-1); // Root always has children (or is a leaf if no children)
|
||||
|
||||
if (root->first_child) {
|
||||
// Process children
|
||||
WalkTreeDepthFirst(root->first_child, result.path_indexes, result.element_token_indexes,
|
||||
result.jumps, sibling_offsets, true);
|
||||
|
||||
// Update root's jump value
|
||||
if (!root->first_child->next_sibling) {
|
||||
result.jumps[0] = -1; // Only child
|
||||
} else {
|
||||
result.jumps[0] = -1; // Child follows (siblings are also children of root)
|
||||
}
|
||||
} else {
|
||||
// No children - root is a leaf
|
||||
result.jumps[0] = -2;
|
||||
}
|
||||
|
||||
// Clean up tree (delete nodes)
|
||||
std::function<void(PathTreeNode*)> delete_tree = [&](PathTreeNode* node) {
|
||||
if (!node) return;
|
||||
|
||||
// Delete children
|
||||
PathTreeNode* child = node->first_child;
|
||||
while (child) {
|
||||
PathTreeNode* next = child->next_sibling;
|
||||
delete_tree(child);
|
||||
delete child;
|
||||
child = next;
|
||||
}
|
||||
};
|
||||
|
||||
delete_tree(root.get());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tree Decoding
|
||||
// ============================================================================
|
||||
|
||||
std::vector<SimplePath> DecodePathTree(const CompressedPathTree& compressed) {
|
||||
if (compressed.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create a map from path_index to reconstructed path
|
||||
std::map<PathIndex, SimplePath> path_map;
|
||||
|
||||
// Recursive decoder
|
||||
std::function<void(size_t, std::string)> decode_recursive;
|
||||
decode_recursive = [&](size_t idx, std::string current_prim) {
|
||||
if (idx >= compressed.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PathIndex path_idx = compressed.path_indexes[idx];
|
||||
TokenIndex token_idx = compressed.element_token_indexes[idx];
|
||||
int32_t jump = compressed.jumps[idx];
|
||||
|
||||
// Get element name
|
||||
std::string element = compressed.token_table.GetToken(token_idx);
|
||||
bool is_property = (token_idx < 0);
|
||||
|
||||
// Build current path
|
||||
std::string prim_part = current_prim;
|
||||
std::string prop_part;
|
||||
|
||||
if (is_property) {
|
||||
// Property path - prim_part stays the same, prop_part is the element
|
||||
prop_part = element;
|
||||
} else {
|
||||
// Prim path - build new prim path
|
||||
if (element.empty()) {
|
||||
// Root node
|
||||
prim_part = "/";
|
||||
} else if (current_prim == "/") {
|
||||
prim_part = "/" + element;
|
||||
} else if (current_prim.empty()) {
|
||||
prim_part = "/" + element;
|
||||
} else {
|
||||
prim_part = current_prim + "/" + element;
|
||||
}
|
||||
}
|
||||
|
||||
// Store path if this node represents an actual path (not just a tree structure node)
|
||||
// Nodes with path_index > 0 or the root (path_idx==0 and element.empty()) are actual paths
|
||||
if (path_idx > 0 || (path_idx == 0 && element.empty())) {
|
||||
path_map[path_idx] = SimplePath(prim_part, prop_part);
|
||||
}
|
||||
|
||||
// Process according to jump value
|
||||
if (jump == -2) {
|
||||
// Leaf - done
|
||||
return;
|
||||
} else if (jump == -1) {
|
||||
// Only child
|
||||
// For prim nodes, child inherits the prim path
|
||||
// For property nodes, this shouldn't happen (properties are leaves)
|
||||
if (!is_property) {
|
||||
decode_recursive(idx + 1, prim_part);
|
||||
}
|
||||
} else if (jump == 0) {
|
||||
// Only sibling
|
||||
// Sibling has the same parent, so use current_prim
|
||||
decode_recursive(idx + 1, current_prim);
|
||||
} else if (jump > 0) {
|
||||
// Both child and sibling
|
||||
// Child is next
|
||||
if (!is_property) {
|
||||
decode_recursive(idx + 1, prim_part);
|
||||
} else {
|
||||
decode_recursive(idx + 1, current_prim);
|
||||
}
|
||||
// Sibling is at offset (same parent)
|
||||
decode_recursive(idx + jump, current_prim);
|
||||
}
|
||||
};
|
||||
|
||||
// Start decoding from root (index 0)
|
||||
// Root starts with empty path
|
||||
decode_recursive(0, "");
|
||||
|
||||
// Convert map to vector (sorted by path_index)
|
||||
std::vector<SimplePath> result;
|
||||
for (const auto& pair : path_map) {
|
||||
result.push_back(pair.second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace crate
|
||||
} // namespace tinyusdz
|
||||
141
sandbox/path-sort-and-encode-crate/tree-encode.hh
Normal file
141
sandbox/path-sort-and-encode-crate/tree-encode.hh
Normal file
@@ -0,0 +1,141 @@
|
||||
//
|
||||
// Crate format PATHS tree encoding (v0.4.0+ compressed format)
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "simple-path.hh"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace tinyusdz {
|
||||
namespace crate {
|
||||
|
||||
///
|
||||
/// Token index type
|
||||
/// In real implementation, this would map to the token table
|
||||
///
|
||||
using TokenIndex = int32_t;
|
||||
|
||||
///
|
||||
/// Path index into the original paths vector
|
||||
///
|
||||
using PathIndex = uint64_t;
|
||||
|
||||
///
|
||||
/// Tree node representing a path element in the hierarchy
|
||||
///
|
||||
struct PathTreeNode {
|
||||
std::string element_name; // Name of this element (e.g., "foo", "bar", "points")
|
||||
TokenIndex element_token_index; // Token index for element name (negative for properties)
|
||||
PathIndex path_index; // Index into original paths vector
|
||||
bool is_property; // Is this a property element?
|
||||
|
||||
PathTreeNode* parent = nullptr;
|
||||
PathTreeNode* first_child = nullptr;
|
||||
PathTreeNode* next_sibling = nullptr;
|
||||
|
||||
PathTreeNode(const std::string& name, TokenIndex token_idx, PathIndex path_idx, bool is_prop)
|
||||
: element_name(name), element_token_index(token_idx), path_index(path_idx), is_property(is_prop) {}
|
||||
};
|
||||
|
||||
///
|
||||
/// Token table for mapping strings to token indices
|
||||
///
|
||||
class TokenTable {
|
||||
public:
|
||||
TokenTable() : next_index_(0) {}
|
||||
|
||||
/// Get or create token index for a string
|
||||
/// Properties use negative indices
|
||||
TokenIndex GetOrCreateToken(const std::string& str, bool is_property);
|
||||
|
||||
/// Get token string from index
|
||||
std::string GetToken(TokenIndex index) const;
|
||||
|
||||
/// Get all tokens
|
||||
const std::map<std::string, TokenIndex>& GetTokens() const { return tokens_; }
|
||||
|
||||
/// Get reverse mapping
|
||||
const std::map<TokenIndex, std::string>& GetReverseTokens() const { return reverse_tokens_; }
|
||||
|
||||
private:
|
||||
std::map<std::string, TokenIndex> tokens_;
|
||||
std::map<TokenIndex, std::string> reverse_tokens_;
|
||||
TokenIndex next_index_;
|
||||
};
|
||||
|
||||
///
|
||||
/// Compressed path tree encoding result
|
||||
///
|
||||
struct CompressedPathTree {
|
||||
std::vector<PathIndex> path_indexes; // Index into _paths vector
|
||||
std::vector<TokenIndex> element_token_indexes; // Token for element (negative = property)
|
||||
std::vector<int32_t> jumps; // Navigation: -2=leaf, -1=child, 0=sibling, >0=both
|
||||
|
||||
TokenTable token_table; // Token table used for encoding
|
||||
|
||||
size_t size() const { return path_indexes.size(); }
|
||||
bool empty() const { return path_indexes.empty(); }
|
||||
};
|
||||
|
||||
///
|
||||
/// Build a hierarchical tree from sorted paths
|
||||
///
|
||||
/// Example:
|
||||
/// ["/", "/World", "/World/Geom", "/World/Geom.points"]
|
||||
///
|
||||
/// Becomes tree:
|
||||
/// / (root)
|
||||
/// └─ World
|
||||
/// └─ Geom
|
||||
/// └─ .points (property)
|
||||
///
|
||||
std::unique_ptr<PathTreeNode> BuildPathTree(
|
||||
const std::vector<SimplePath>& sorted_paths,
|
||||
TokenTable& token_table
|
||||
);
|
||||
|
||||
///
|
||||
/// Encode path tree into compressed format (three parallel arrays)
|
||||
///
|
||||
/// Walks the tree in depth-first order and generates:
|
||||
/// - pathIndexes[i]: index into original paths vector
|
||||
/// - elementTokenIndexes[i]: token index for this element
|
||||
/// - jumps[i]: navigation information
|
||||
///
|
||||
CompressedPathTree EncodePathTree(const std::vector<SimplePath>& sorted_paths);
|
||||
|
||||
///
|
||||
/// Decode compressed path tree back to paths
|
||||
///
|
||||
/// Reconstructs paths from the three arrays by following jump instructions
|
||||
///
|
||||
std::vector<SimplePath> DecodePathTree(const CompressedPathTree& compressed);
|
||||
|
||||
///
|
||||
/// Internal: Walk tree in depth-first order and populate arrays
|
||||
///
|
||||
void WalkTreeDepthFirst(
|
||||
PathTreeNode* node,
|
||||
std::vector<PathIndex>& path_indexes,
|
||||
std::vector<TokenIndex>& element_token_indexes,
|
||||
std::vector<int32_t>& jumps,
|
||||
std::vector<size_t>& sibling_offsets // Positions that need sibling offset filled in
|
||||
);
|
||||
|
||||
///
|
||||
/// Internal: Calculate jump value for a node
|
||||
///
|
||||
int32_t CalculateJump(
|
||||
const PathTreeNode* node,
|
||||
bool has_child,
|
||||
bool has_sibling,
|
||||
size_t sibling_offset
|
||||
);
|
||||
|
||||
} // namespace crate
|
||||
} // namespace tinyusdz
|
||||
211
sandbox/path-sort-and-encode-crate/validate-path-sort.cc
Normal file
211
sandbox/path-sort-and-encode-crate/validate-path-sort.cc
Normal file
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// Validation program to compare TinyUSDZ path sorting with OpenUSD SdfPath sorting
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
//
|
||||
#include "path-sort-api.hh"
|
||||
|
||||
// OpenUSD includes
|
||||
#include "pxr/usd/sdf/path.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace tinyusdz;
|
||||
|
||||
// Test case structure
|
||||
struct TestCase {
|
||||
std::string prim_part;
|
||||
std::string prop_part;
|
||||
std::string description;
|
||||
|
||||
TestCase(const std::string& prim, const std::string& prop, const std::string& desc)
|
||||
: prim_part(prim), prop_part(prop), description(desc) {}
|
||||
};
|
||||
|
||||
// Create test paths
|
||||
std::vector<TestCase> GetTestCases() {
|
||||
std::vector<TestCase> tests;
|
||||
|
||||
// Basic absolute paths
|
||||
tests.push_back(TestCase("/", "", "Root path"));
|
||||
tests.push_back(TestCase("/foo", "", "Single prim"));
|
||||
tests.push_back(TestCase("/foo/bar", "", "Two level prim"));
|
||||
tests.push_back(TestCase("/foo/bar/baz", "", "Three level prim"));
|
||||
|
||||
// Property paths
|
||||
tests.push_back(TestCase("/foo", "prop", "Prim with property"));
|
||||
tests.push_back(TestCase("/foo/bar", "prop", "Nested prim with property"));
|
||||
tests.push_back(TestCase("/foo", "aaa", "Property aaa"));
|
||||
tests.push_back(TestCase("/foo", "zzz", "Property zzz"));
|
||||
|
||||
// Alphabetic ordering tests
|
||||
tests.push_back(TestCase("/aaa", "", "Path aaa"));
|
||||
tests.push_back(TestCase("/bbb", "", "Path bbb"));
|
||||
tests.push_back(TestCase("/zzz", "", "Path zzz"));
|
||||
tests.push_back(TestCase("/aaa/bbb", "", "Path aaa/bbb"));
|
||||
tests.push_back(TestCase("/aaa/ccc", "", "Path aaa/ccc"));
|
||||
|
||||
// Depth tests
|
||||
tests.push_back(TestCase("/a", "", "Shallow path a"));
|
||||
tests.push_back(TestCase("/a/b", "", "Path a/b"));
|
||||
tests.push_back(TestCase("/a/b/c", "", "Path a/b/c"));
|
||||
tests.push_back(TestCase("/a/b/c/d", "", "Deep path a/b/c/d"));
|
||||
|
||||
// Mixed tests
|
||||
tests.push_back(TestCase("/World", "", "World prim"));
|
||||
tests.push_back(TestCase("/World/Geom", "", "World/Geom"));
|
||||
tests.push_back(TestCase("/World/Geom/mesh", "", "World/Geom/mesh"));
|
||||
tests.push_back(TestCase("/World/Geom", "xformOp:transform", "World/Geom with xform"));
|
||||
tests.push_back(TestCase("/World/Geom/mesh", "points", "mesh with points"));
|
||||
tests.push_back(TestCase("/World/Geom/mesh", "normals", "mesh with normals"));
|
||||
|
||||
// Edge cases
|
||||
tests.push_back(TestCase("/x", "", "Single char x"));
|
||||
tests.push_back(TestCase("/x/y", "", "Single char x/y"));
|
||||
tests.push_back(TestCase("/x/y/z", "", "Single char x/y/z"));
|
||||
|
||||
return tests;
|
||||
}
|
||||
|
||||
bool ValidateSort() {
|
||||
std::vector<TestCase> test_cases = GetTestCases();
|
||||
|
||||
std::cout << "Creating " << test_cases.size() << " test paths...\n" << std::endl;
|
||||
|
||||
// Create TinyUSDZ paths
|
||||
std::vector<SimplePath> tiny_paths;
|
||||
for (const auto& tc : test_cases) {
|
||||
tiny_paths.push_back(SimplePath(tc.prim_part, tc.prop_part));
|
||||
}
|
||||
|
||||
// Create OpenUSD paths
|
||||
std::vector<pxr::SdfPath> usd_paths;
|
||||
for (const auto& tc : test_cases) {
|
||||
std::string path_str = tc.prim_part;
|
||||
if (!tc.prop_part.empty()) {
|
||||
path_str += "." + tc.prop_part;
|
||||
}
|
||||
usd_paths.push_back(pxr::SdfPath(path_str));
|
||||
}
|
||||
|
||||
// Sort using TinyUSDZ implementation
|
||||
std::vector<SimplePath> tiny_sorted = tiny_paths;
|
||||
pathsort::SortSimplePaths(tiny_sorted);
|
||||
|
||||
// Sort using OpenUSD SdfPath
|
||||
std::vector<pxr::SdfPath> usd_sorted = usd_paths;
|
||||
std::sort(usd_sorted.begin(), usd_sorted.end());
|
||||
|
||||
// Compare results
|
||||
std::cout << "Comparing sorted results...\n" << std::endl;
|
||||
|
||||
bool all_match = true;
|
||||
for (size_t i = 0; i < tiny_sorted.size(); i++) {
|
||||
std::string tiny_str = tiny_sorted[i].full_path_name();
|
||||
std::string usd_str = usd_sorted[i].GetString();
|
||||
|
||||
bool match = (tiny_str == usd_str);
|
||||
if (!match) {
|
||||
all_match = false;
|
||||
}
|
||||
|
||||
std::cout << "[" << i << "] "
|
||||
<< (match ? "✓" : "✗")
|
||||
<< " TinyUSDZ: " << tiny_str
|
||||
<< " | OpenUSD: " << usd_str
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
std::cout << "\n" << std::string(60, '=') << std::endl;
|
||||
if (all_match) {
|
||||
std::cout << "SUCCESS: All paths sorted identically!" << std::endl;
|
||||
} else {
|
||||
std::cout << "FAILURE: Path sorting differs between implementations!" << std::endl;
|
||||
}
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
return all_match;
|
||||
}
|
||||
|
||||
bool ValidatePairwiseComparison() {
|
||||
std::cout << "\n\nPairwise Comparison Validation\n" << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
std::vector<TestCase> test_cases = GetTestCases();
|
||||
|
||||
// Create paths
|
||||
std::vector<SimplePath> tiny_paths;
|
||||
std::vector<pxr::SdfPath> usd_paths;
|
||||
|
||||
for (const auto& tc : test_cases) {
|
||||
tiny_paths.push_back(SimplePath(tc.prim_part, tc.prop_part));
|
||||
|
||||
std::string path_str = tc.prim_part;
|
||||
if (!tc.prop_part.empty()) {
|
||||
path_str += "." + tc.prop_part;
|
||||
}
|
||||
usd_paths.push_back(pxr::SdfPath(path_str));
|
||||
}
|
||||
|
||||
int mismatches = 0;
|
||||
int total_comparisons = 0;
|
||||
|
||||
// Compare every pair
|
||||
for (size_t i = 0; i < tiny_paths.size(); i++) {
|
||||
for (size_t j = 0; j < tiny_paths.size(); j++) {
|
||||
if (i == j) continue;
|
||||
|
||||
total_comparisons++;
|
||||
|
||||
// TinyUSDZ comparison
|
||||
int tiny_cmp = pathsort::CompareSimplePaths(tiny_paths[i], tiny_paths[j]);
|
||||
bool tiny_less = tiny_cmp < 0;
|
||||
|
||||
// OpenUSD comparison
|
||||
bool usd_less = usd_paths[i] < usd_paths[j];
|
||||
|
||||
if (tiny_less != usd_less) {
|
||||
mismatches++;
|
||||
std::cout << "MISMATCH: "
|
||||
<< tiny_paths[i].full_path_name() << " vs "
|
||||
<< tiny_paths[j].full_path_name()
|
||||
<< " | TinyUSDZ: " << (tiny_less ? "less" : "not-less")
|
||||
<< " | OpenUSD: " << (usd_less ? "less" : "not-less")
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\nTotal comparisons: " << total_comparisons << std::endl;
|
||||
std::cout << "Mismatches: " << mismatches << std::endl;
|
||||
|
||||
if (mismatches == 0) {
|
||||
std::cout << "SUCCESS: All pairwise comparisons match!" << std::endl;
|
||||
} else {
|
||||
std::cout << "FAILURE: " << mismatches << " comparison mismatches found!" << std::endl;
|
||||
}
|
||||
|
||||
return mismatches == 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "=" << std::string(60, '=') << std::endl;
|
||||
std::cout << "TinyUSDZ Path Sorting Validation" << std::endl;
|
||||
std::cout << "Comparing against OpenUSD SdfPath" << std::endl;
|
||||
std::cout << "=" << std::string(60, '=') << "\n" << std::endl;
|
||||
|
||||
bool sort_valid = ValidateSort();
|
||||
bool pairwise_valid = ValidatePairwiseComparison();
|
||||
|
||||
std::cout << "\n\n" << std::string(60, '=') << std::endl;
|
||||
std::cout << "FINAL RESULT" << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
std::cout << "Sort validation: " << (sort_valid ? "PASS" : "FAIL") << std::endl;
|
||||
std::cout << "Pairwise validation: " << (pairwise_valid ? "PASS" : "FAIL") << std::endl;
|
||||
std::cout << "Overall: " << (sort_valid && pairwise_valid ? "PASS" : "FAIL") << std::endl;
|
||||
std::cout << std::string(60, '=') << std::endl;
|
||||
|
||||
return (sort_valid && pairwise_valid) ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user