2255 lines
100 KiB
C++
2255 lines
100 KiB
C++
// vmax2bella.cpp - A program to convert VoxelMax (.vmax) files to Bella 3D scene (.bsz) files
|
||
//
|
||
// This program reads VoxelMax files (which store voxel-based 3D models) and
|
||
// converts them to Bella (a 3D rendering engine) scene files.
|
||
|
||
/*
|
||
# Technical Specification: VoxelMax Format
|
||
|
||
## Overview
|
||
- This document specifies a chunked voxel storage format embedded in property list (plist) files. The format provides an efficient representation of 3D voxel data through a combination of Morton-encoded spatial indexing and a sparse representation approach.
|
||
|
||
## File Structure
|
||
- Format: Property List (plist)
|
||
- Structure: Hierarchical key-value structure with nested dictionaries and arrays
|
||
- plist is compressed using the LZFSE, an open source reference c implementation is [here](https://github.com/lzfse/lzfse)
|
||
|
||
```
|
||
root
|
||
└── snapshots (array)
|
||
└── Each snapshot (dictionary)
|
||
├── s (dictionary) - Snapshot data
|
||
│ ├── id (dictionary) - Identifiers
|
||
│ │ ├── c (int64) - Chunk ID
|
||
│ │ ├── s (int64) - Session ID
|
||
│ │ └── t (int64) - Type ID
|
||
│ ├── lc (binary data) - Layer Color Usage
|
||
│ ├── ds (binary data) - Voxel data stream
|
||
│ ├── dlc (binary data) - Deselected Layer Color Usage
|
||
│ └── st (dictionary) - Statistics/metadata
|
||
│ ├── c (int64) - Count of voxels in the chunk
|
||
│ ├── sc (int64) - Selected Count (number of selected voxels)
|
||
│ ├── smin (array) - Selected Minimum coordinates [x,y,z,w]
|
||
│ ├── smax (array) - Selected Maximum coordinates [x,y,z,w]
|
||
│ ├── min (array) - Minimum coordinates of all voxels [x,y,z]
|
||
│ ├── max (array) - Maximum coordinates of all voxels [x,y,z]
|
||
│ └── e (dictionary) - Extent
|
||
│ ├── o (array) - Origin/reference point [x,y,z]
|
||
│ └── s (array) - Size/dimensions [width,height,depth]
|
||
```
|
||
|
||
## Chunking System
|
||
### Volume Organization
|
||
- The total volume is divided into chunks for efficient storage and manipulation
|
||
- Standard chunk size: 32×32×32 voxels
|
||
- Total addressable space: 256×256×256 voxels (8×8×8 chunks)
|
||
|
||
### Morton Encoding for Chunks
|
||
- Chunk IDs are encoded using 24 bits (8 bits per dimension)
|
||
- This allows addressing up to 256 chunks in each dimension , although only 8x8x8 are used in practice
|
||
- The decodeMortonChunkID function extracts x, y, z coordinates from a Morton-encoded chunk ID
|
||
- The resulting chunk coordinates are then multiplied by 32 to get the world position of the chunk
|
||
|
||
### Chunk Addressing
|
||
- Each chunk has 3D coordinates (chunk_x, chunk_y, chunk_z)
|
||
- For a 128×128×128 volume with 32×32×32 chunks, there would be 4×4×4 chunks total (64 chunks)
|
||
- Chunks are only stored if they contain at least one non-empty voxel
|
||
- Each snapshot contains data for a specific chunk, identified by the 'c' value in the 's.id' dictionary
|
||
|
||
## Data Fields
|
||
### Voxel Data Stream (ds)
|
||
- Variable-length binary data
|
||
- Contains pairs of bytes for each voxel: [position_byte, color_byte]
|
||
- Each chunk can contain up to 32,768 voxels (32×32×32)
|
||
- *Position Byte:*
|
||
- The format uses a encoding approach that combines sequential and Morton encoding for optimal storage efficiency:
|
||
- Uses a mix of position=0 bytes (for sequential implicit positions) and Morton-encoded position bytes
|
||
- The decoder maintains an internal position counter that advances through the chunk in a predefined order (x, then y, then z)
|
||
- Color byte 0 indicates "no voxel at this position" (empty space)
|
||
- If a chunk uses the entire 256x256x256 addressable space, then it uses exactly 65,536 bytes (32,768 voxel pairs)
|
||
- This is the dense case and it not memory efficient
|
||
- When we introduce morton encoding we can jump to a specific position in the chunk
|
||
- Data stream can terminate at any point, avoiding the need to store all 32,768 voxel pairs
|
||
### Morton Encoding Process
|
||
- A space-filling curve that interleaves the bits of the x, y, and z coordinates
|
||
- Used to convert 3D coordinates to a 1D index and vice versa
|
||
- Creates a coherent ordering of voxels that preserves spatial locality
|
||
1. Take the binary representation of x, y, and z coordinates
|
||
2. Interleave the bits in the order: z₀, y₀, x₀, z₁, y₁, x₁, z₂, y₂, x₂, ...
|
||
3. The resulting binary number is the Morton code
|
||
|
||
|
||
- *Color Byte:*
|
||
- Stores the color value + 1 (offset of +1 from actual color)
|
||
- Value 0 indicates no voxel at this position
|
||
- A fully populated chunk will have 32,768 voxel pairs (65,536 bytes total in ds)
|
||
|
||
### Snapshot Accumulation
|
||
- Each snapshot contains data for a specific chunk (identified by the chunk ID)
|
||
- Multiple snapshots together build up the complete voxel model
|
||
- Later snapshots for the same chunk ID overwrite earlier ones, allowing for edits over time
|
||
|
||
### Layer Color Usagw (lc)
|
||
- s.lc is a summary table (256 bytes) that tracks which colors are used anywhere in the chunk
|
||
- Each byte position (0-255) corresponds to a color palette ID
|
||
- [TODO] understand why the word layer color is used, what is a layer color
|
||
|
||
### Deselected Layer Color Usage (dlc)
|
||
- Optional 256-byte array
|
||
- Used during editing to track which color layers the user has deselected
|
||
- Primarily for UI state preservation rather than 3D model representation
|
||
|
||
### Statistics Data (st)
|
||
- Dictionary containing metadata about the voxels in a chunk:
|
||
- c (count): Total number of voxels in the chunk
|
||
- sc (selectedCount): Number of currently selected voxels
|
||
- sMin (selectedMin): Array defining minimum coordinates of current selection [x,y,z,w]
|
||
- sMax (selectedMax): Array defining maximum coordinates of current selection [x,y,z,w]
|
||
- min: Array defining minimum coordinates of all voxels [x,y,z]
|
||
- max: Array defining maximum coordinates of all voxels [x,y,z]
|
||
- e (extent): Array defining the bounding box [min_x, min_y, min_z, max_x, max_y, max_z]
|
||
- e.o (extent.origin): Reference point or offset for extent calculations
|
||
|
||
## Coordinate Systems
|
||
### Primary Coordinate System
|
||
- Y-up coordinate system: Y is the vertical axis
|
||
- Origin (0,0,0) is at the bottom-left-front corner
|
||
- Coordinates increase toward right (X+), up (Y+), and backward (Z+)
|
||
|
||
### Addressing Scheme
|
||
1. World Space: Absolute coordinates in the full volume
|
||
2. Chunk Space: Which chunk contains a voxel (chunk_x, chunk_y, chunk_z)
|
||
3. Local Space: Coordinates within a chunk (local_x, local_y, local_z)
|
||
|
||
## Coordinate Conversion
|
||
- *World to Chunk:*
|
||
- chunk_x = floor(world_x / 32)
|
||
- chunk_y = floor(world_y / 32)
|
||
- chunk_z = floor(world_z / 32)
|
||
- *World to Local:*
|
||
- local_x = world_x % 32
|
||
- local_y = world_y % 32
|
||
- local_z = world_z % 32
|
||
- *Chunk+Local to World:*
|
||
- world_x = chunk_x * 32 + local_x
|
||
- world_y = chunk_y * 32 + local_y
|
||
- world_z = chunk_z * 32 + local_z
|
||
|
||
|
||
### Detailed Morton Encoding Example
|
||
To clarify the bit interleaving process, here's a step-by-step example:
|
||
|
||
For position (3,1,2):
|
||
|
||
1. Convert to binary (3 bits each):
|
||
- x = 3 = 011 (bits labeled as x₂x₁x₀)
|
||
- y = 1 = 001 (bits labeled as y₂y₁y₀)
|
||
- z = 2 = 010 (bits labeled as z₂z₁z₀)
|
||
|
||
2. Interleave the bits in the order z₂y₂x₂, z₁y₁x₁, z₀y₀x₀:
|
||
- z₂y₂x₂ = 001 (z₂=0, y₂=0, x₂=1)
|
||
- z₁y₁x₁ = 010 (z₁=1, y₁=0, x₁=0)
|
||
- z₀y₀x₀ = 100 (z₀=0, y₀=0, x₀=0)
|
||
|
||
3. Combine: 001010100 = binary 10000110 = decimal 134
|
||
|
||
Therefore, position (3,1,2) has Morton index 134.
|
||
|
||
```
|
||
Morton Binary Coords Visual (Z-layers)
|
||
Index (zyx bits) (x,y,z)
|
||
|
||
0 000 (000) (0,0,0) Z=0 layer: Z=1 layer:
|
||
1 001 (001) (1,0,0) +---+---+ +---+---+
|
||
2 010 (010) (0,1,0) | 0 | 1 | | 4 | 5 |
|
||
3 011 (011) (1,1,0) +---+---+ +---+---+
|
||
4 100 (100) (0,0,1) | 2 | 3 | | 6 | 7 |
|
||
5 101 (101) (1,0,1) +---+---+ +---+---+
|
||
6 110 (110) (0,1,1)
|
||
7 111 (111) (1,1,1)
|
||
```
|
||
|
||
### Deinterleave the bits:
|
||
Extract bits 0, 3, 6 for the x coordinate
|
||
Extract bits 1, 4, 7 for the y coordinate
|
||
Extract bits 2, 5 for the z coordinate (note: z only needs 2 bits for 0-3 range)
|
||
For example, to convert chunk ID 73 to 3D chunk coordinates:
|
||
73 in binary: 01001001
|
||
|
||
### Deinterleaving:
|
||
```
|
||
x = bits 0,3,6 = 101 = 5
|
||
y = bits 1,4,7 = 001 = 1
|
||
z = bits 2,5 = 00 = 0
|
||
```
|
||
So chunk ID 73 corresponds to chunk position (5,1,0).
|
||
The Morton encoding ensures that spatially close chunks are also close in memory.
|
||
|
||
## Implementation Guidance
|
||
### Reading Algorithm
|
||
1. Parse the plist file to access the snapshot array
|
||
2. For each snapshot:
|
||
a. Extract the chunk ID from s > id > c
|
||
b. Extract the lc and ds data
|
||
c. Process the ds data in pairs of bytes (position, color)
|
||
d. Calculate the world origin by decoding the Morton chunk ID and multiplying by 32
|
||
e. Store the voxels for this chunk ID
|
||
3. Combine all snapshots to build the complete voxel model, using the chunk IDs as keys
|
||
|
||
### Writing Algorithm
|
||
1. Organize voxels by chunk (32×32×32 voxels per chunk)
|
||
2. For each non-empty chunk:
|
||
a. Create a snapshot entry
|
||
b. Set up the id dictionary with the appropriate chunk ID
|
||
c. Set up a 256-byte lc array (all zeros)
|
||
d. Create the ds data by encoding each voxel as a (position, color+1) pair
|
||
e. Set the appropriate byte in lc to 1 if the color is used in ds
|
||
3. Add all snapshots to the array
|
||
4. Write the complete structure to a plist file
|
||
|
||
- [?] Models typically use SessionIDs to group related edits (observed values include 10 and 18)
|
||
|
||
## Snapshot Types
|
||
The 't' field in the snapshot's 's.id' dictionary indicates the type of snapshot:
|
||
- 0: underRestore - Snapshot being restored from a previous state
|
||
- 1: redoRestore - Snapshot being restored during a redo operation
|
||
- 2: undo - Snapshot created for an undo operation
|
||
- 3: redo - Snapshot created for a redo operation
|
||
- 4: checkpoint - Snapshot created as a regular checkpoint during editing (most common)
|
||
- 5: selection - Snapshot representing a selection operation
|
||
|
||
*/
|
||
|
||
// Standard C++ library includes - these provide essential functionality
|
||
#include <iostream> // For input/output operations (cout, cin, etc.)
|
||
#include <fstream> // For file operations (reading/writing files)
|
||
#include <vector> // For dynamic arrays (vectors)
|
||
#include <cstdint> // For fixed-size integer types (uint8_t, uint32_t, etc.)
|
||
#include <map> // For key-value pair data structures (maps)
|
||
#include <filesystem> // For file system operations (directory handling, path manipulation)
|
||
#include <iomanip> // For std::setw and std::setfill
|
||
#include <set> // For set data structure
|
||
#include <algorithm> // For std::sort
|
||
#include <sstream> // For std::ostringstream
|
||
|
||
// Bella SDK includes - external libraries for 3D rendering
|
||
#include "bella_sdk/bella_scene.h" // For creating and manipulating 3D scenes in Bella
|
||
#include "dl_core/dl_main.inl" // Core functionality from the Diffuse Logic engine
|
||
#include "dl_core/dl_fs.h"
|
||
#include "lzfse.h"
|
||
|
||
// PlistCPP includes
|
||
#include "Plist.hpp"
|
||
#include "PlistDate.hpp"
|
||
|
||
// Namespaces allow you to use symbols from a library without prefixing them
|
||
// For example, with these 'using' statements, you can write 'Scene' instead of 'bella_sdk::Scene'
|
||
namespace bsdk = dl::bella_sdk;
|
||
|
||
// Global variable to track if we've found a color palette in the file
|
||
bool has_palette = false;
|
||
|
||
// Forward declarations of functions - tells the compiler that these functions exist
|
||
// and will be defined later in the file
|
||
std::string initializeGlobalLicense();
|
||
std::string initializeGlobalThirdPartyLicences();
|
||
|
||
// Observer class for monitoring scene events
|
||
// This is a custom implementation of the SceneObserver interface from Bella SDK
|
||
// The SceneObserver is called when various events occur in the scene
|
||
struct Observer : public bsdk::SceneObserver
|
||
{
|
||
bool inEventGroup = false; // Flag to track if we're in an event group
|
||
|
||
// Override methods from SceneObserver to provide custom behavior
|
||
|
||
// Called when a node is added to the scene
|
||
void onNodeAdded( bsdk::Node node ) override
|
||
{
|
||
dl::logInfo("%sNode added: %s", inEventGroup ? " " : "", node.name().buf());
|
||
}
|
||
|
||
// Called when a node is removed from the scene
|
||
void onNodeRemoved( bsdk::Node node ) override
|
||
{
|
||
dl::logInfo("%sNode removed: %s", inEventGroup ? " " : "", node.name().buf());
|
||
}
|
||
|
||
// Called when an input value changes
|
||
void onInputChanged( bsdk::Input input ) override
|
||
{
|
||
dl::logInfo("%sInput changed: %s", inEventGroup ? " " : "", input.path().buf());
|
||
}
|
||
|
||
// Called when an input is connected to something
|
||
void onInputConnected( bsdk::Input input ) override
|
||
{
|
||
dl::logInfo("%sInput connected: %s", inEventGroup ? " " : "", input.path().buf());
|
||
}
|
||
|
||
// Called at the start of a group of related events
|
||
void onBeginEventGroup() override
|
||
{
|
||
inEventGroup = true;
|
||
dl::logInfo("Event group begin.");
|
||
}
|
||
|
||
// Called at the end of a group of related events
|
||
void onEndEventGroup() override
|
||
{
|
||
inEventGroup = false;
|
||
dl::logInfo("Event group end.");
|
||
}
|
||
};
|
||
|
||
// Function to decompress LZFSE file to a plist file
|
||
bool decompressLzfseToPlist(const std::string& inputFile, const std::string& outputFile) {
|
||
// Open input file
|
||
std::ifstream inFile(inputFile, std::ios::binary);
|
||
if (!inFile.is_open()) {
|
||
std::cerr << "Error: Could not open input file: " << inputFile << std::endl;
|
||
return false;
|
||
}
|
||
|
||
// Get file size
|
||
inFile.seekg(0, std::ios::end);
|
||
size_t inSize = inFile.tellg();
|
||
inFile.seekg(0, std::ios::beg);
|
||
|
||
// Read compressed data
|
||
std::vector<uint8_t> inBuffer(inSize);
|
||
inFile.read(reinterpret_cast<char*>(inBuffer.data()), inSize);
|
||
inFile.close();
|
||
|
||
// Allocate output buffer (assuming decompressed size is larger)
|
||
// Start with 4x the input size, will resize if needed
|
||
size_t outAllocatedSize = inSize * 4;
|
||
std::vector<uint8_t> outBuffer(outAllocatedSize);
|
||
|
||
// Allocate scratch buffer for lzfse
|
||
size_t scratchSize = lzfse_decode_scratch_size();
|
||
std::vector<uint8_t> scratch(scratchSize);
|
||
|
||
// Decompress data
|
||
size_t decodedSize = 0;
|
||
while (true) {
|
||
decodedSize = lzfse_decode_buffer(
|
||
outBuffer.data(), outAllocatedSize,
|
||
inBuffer.data(), inSize,
|
||
scratch.data());
|
||
|
||
// If output buffer was too small, increase size and retry
|
||
if (decodedSize == 0 || decodedSize == outAllocatedSize) {
|
||
outAllocatedSize *= 2;
|
||
outBuffer.resize(outAllocatedSize);
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
|
||
// Write output file
|
||
std::ofstream outFile(outputFile, std::ios::binary);
|
||
if (!outFile.is_open()) {
|
||
std::cerr << "Error: Could not open output file: " << outputFile << std::endl;
|
||
return false;
|
||
}
|
||
|
||
outFile.write(reinterpret_cast<char*>(outBuffer.data()), decodedSize);
|
||
outFile.close();
|
||
|
||
std::cout << "Successfully decompressed " << inputFile << " to " << outputFile << std::endl;
|
||
std::cout << "Input size: " << inSize << " bytes, Output size: " << decodedSize << " bytes" << std::endl;
|
||
|
||
return true;
|
||
}
|
||
|
||
// Forward declarations
|
||
bool outputBellaFile(const std::string& outputPath, size_t voxelCount);
|
||
bool parsePlistVoxelData(const std::string& filePath, bool verbose);
|
||
std::tuple<int, int, int> decodeMortonChunkID(int64_t mortonID);
|
||
void printSimpleVoxelCoordinates(const std::string& filePath);
|
||
void justPrintVoxelCoordinates(const std::string& filePath);
|
||
|
||
// Function to decode a Morton code to x, y, z coordinates
|
||
void decodeMorton(uint8_t code, uint8_t& x, uint8_t& y, uint8_t& z) {
|
||
// Initialize the coordinates
|
||
x = y = z = 0;
|
||
|
||
// For a 3D Morton code in 8 bits, we have:
|
||
// Bits: 7 6 5 4 3 2 1 0
|
||
// z2 y2 x2 z1 y1 x1 z0 y0 x0
|
||
|
||
// Extract bits for x (positions 0, 3, 6)
|
||
if (code & 0x01) x |= 1; // Bit 0
|
||
if (code & 0x08) x |= 2; // Bit 3
|
||
if (code & 0x40) x |= 4; // Bit 6
|
||
|
||
// Extract bits for y (positions 1, 4, 7)
|
||
if (code & 0x02) y |= 1; // Bit 1
|
||
if (code & 0x10) y |= 2; // Bit 4
|
||
if (code & 0x80) y |= 4; // Bit 7
|
||
|
||
// Extract bits for z (positions 2, 5)
|
||
if (code & 0x04) z |= 1; // Bit 2
|
||
if (code & 0x20) z |= 2; // Bit 5
|
||
|
||
// Note: For a fully 3-bit coordinate system (0-7), we would expect a 9-bit Morton code
|
||
// But since we're using uint8_t (8 bits), we can only represent coordinates up to:
|
||
// x: 0-7 (3 bits)
|
||
// y: 0-7 (3 bits)
|
||
// z: 0-3 (2 bits)
|
||
// This gives a total of 8 bits (3+3+2)
|
||
}
|
||
|
||
// Function to decode a Morton code to x, y, z coordinates for the chunk index
|
||
void decodeChunkMorton(uint8_t code, uint8_t& chunkX, uint8_t& chunkY, uint8_t& chunkZ) {
|
||
// Same decoding as for voxel coordinates
|
||
decodeMorton(code, chunkX, chunkY, chunkZ);
|
||
}
|
||
|
||
// Function to analyze and print detailed information for a specific chunk
|
||
void analyzeChunk(const Plist::data_type& locationTable, const Plist::data_type& voxelData, uint8_t chunkIndex) {
|
||
// First, check if the chunk is occupied
|
||
if (chunkIndex >= locationTable.size() || locationTable[chunkIndex] == 0) {
|
||
std::cout << "Chunk " << (int)chunkIndex << " is not occupied." << std::endl;
|
||
return;
|
||
}
|
||
|
||
// Decode chunk coordinates
|
||
uint8_t chunkX, chunkY, chunkZ;
|
||
decodeChunkMorton(chunkIndex, chunkX, chunkY, chunkZ);
|
||
|
||
std::cout << "Analyzing chunk " << (int)chunkIndex << " at position ("
|
||
<< (int)chunkX << "," << (int)chunkY << "," << (int)chunkZ << ")" << std::endl;
|
||
|
||
// Count voxels in this chunk and their position/color data
|
||
std::map<uint8_t, int> colorCounts; // Map to count occurrences of each color
|
||
std::map<uint8_t, int> positionCounts; // Map to count occurrences of each position
|
||
|
||
// Assuming each voxel is 2 bytes: position, color
|
||
for (size_t i = 0; i < voxelData.size(); i += 2) {
|
||
if (i + 1 < voxelData.size()) {
|
||
uint8_t position = static_cast<uint8_t>(voxelData[i]);
|
||
uint8_t color = static_cast<uint8_t>(voxelData[i + 1]);
|
||
|
||
// Count position and color occurrences
|
||
positionCounts[position]++;
|
||
colorCounts[color]++;
|
||
|
||
// Extract x, y, z from position if it's not 0
|
||
if (position != 0) {
|
||
uint8_t localX, localY, localZ;
|
||
decodeMorton(position, localX, localY, localZ);
|
||
|
||
// Calculate world coordinates
|
||
uint16_t worldX = chunkX * 32 + localX;
|
||
uint16_t worldY = chunkY * 32 + localY;
|
||
uint16_t worldZ = chunkZ * 32 + localZ;
|
||
|
||
std::cout << " Voxel at local (" << (int)localX << "," << (int)localY << "," << (int)localZ
|
||
<< "), world (" << worldX << "," << worldY << "," << worldZ
|
||
<< ") with color " << (int)color << std::endl;
|
||
} else {
|
||
// If position is 0, it's at the origin of the chunk
|
||
uint16_t worldX = chunkX * 32;
|
||
uint16_t worldY = chunkY * 32;
|
||
uint16_t worldZ = chunkZ * 32;
|
||
|
||
// Only print a few of these to avoid flooding output
|
||
if (i < 10) {
|
||
std::cout << " Voxel at local (0,0,0), world (" << worldX << "," << worldY << "," << worldZ
|
||
<< ") with color " << (int)color << std::endl;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Print color statistics
|
||
std::cout << "Color distribution in chunk " << (int)chunkIndex << ":" << std::endl;
|
||
for (const auto& pair : colorCounts) {
|
||
std::cout << " Color " << (int)pair.first << ": " << pair.second << " voxels" << std::endl;
|
||
}
|
||
|
||
// Print position statistics
|
||
std::cout << "Position code distribution in chunk " << (int)chunkIndex << ":" << std::endl;
|
||
for (const auto& pair : positionCounts) {
|
||
if (pair.first == 0) {
|
||
std::cout << " Position 0 (chunk origin): " << pair.second << " voxels" << std::endl;
|
||
} else {
|
||
uint8_t localX, localY, localZ;
|
||
decodeMorton(pair.first, localX, localY, localZ);
|
||
std::cout << " Position " << (int)pair.first << " (" << (int)localX << ","
|
||
<< (int)localY << "," << (int)localZ << "): " << pair.second << " voxels" << std::endl;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Structure to represent a voxel
|
||
struct Voxel {
|
||
uint8_t position; // Local position within chunk (Morton encoded)
|
||
uint8_t color; // Color value
|
||
|
||
Voxel(uint8_t pos, uint8_t col) : position(pos), color(col) {}
|
||
};
|
||
|
||
// Structure to represent a chunk in the final model
|
||
struct Chunk {
|
||
std::vector<Voxel> voxels;
|
||
uint8_t chunkX, chunkY, chunkZ; // Chunk coordinates
|
||
|
||
// Default constructor required for map operations
|
||
Chunk() : chunkX(0), chunkY(0), chunkZ(0) {}
|
||
|
||
// Constructor to initialize chunk coordinates
|
||
Chunk(uint8_t x, uint8_t y, uint8_t z) : chunkX(x), chunkY(y), chunkZ(z) {}
|
||
|
||
// This method provides a more accurate interpretation of the voxel data based on our findings
|
||
// Position byte 0 means the voxel is at origin (0,0,0) of the chunk
|
||
// The pattern is encoded through the sequence of color transitions, not through position expansion
|
||
std::vector<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>> getVoxels() const {
|
||
std::vector<std::tuple<uint8_t, uint8_t, uint8_t, uint8_t>> result;
|
||
|
||
for (const auto& voxel : voxels) {
|
||
uint8_t localX = 0, localY = 0, localZ = 0;
|
||
|
||
if (voxel.position != 0) {
|
||
decodeMorton(voxel.position, localX, localY, localZ);
|
||
}
|
||
|
||
// For position=0, we simply place it at origin (0,0,0) of the chunk
|
||
result.emplace_back(localX, localY, localZ, voxel.color);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// This method returns world coordinates for all voxels in the chunk
|
||
std::vector<std::tuple<uint16_t, uint16_t, uint16_t, uint8_t>> getWorldVoxels() const {
|
||
std::vector<std::tuple<uint16_t, uint16_t, uint16_t, uint8_t>> result;
|
||
|
||
for (const auto& voxel : voxels) {
|
||
uint8_t localX = 0, localY = 0, localZ = 0;
|
||
|
||
if (voxel.position != 0) {
|
||
decodeMorton(voxel.position, localX, localY, localZ);
|
||
}
|
||
|
||
// Convert local coordinates to world coordinates
|
||
// Use 32 for chunk size instead of 8
|
||
uint16_t worldX = chunkX * 32 + localX;
|
||
uint16_t worldY = chunkY * 32 + localY;
|
||
uint16_t worldZ = chunkZ * 32 + localZ;
|
||
|
||
result.emplace_back(worldX, worldY, worldZ, voxel.color);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
};
|
||
|
||
// Function to analyze the Morton codes in a snapshot's data stream
|
||
void analyzeDataStreamMortonCodes(const Plist::data_type& voxelData, size_t snapshotIndex) {
|
||
std::cout << "Morton codes in Snapshot " << snapshotIndex << " data stream:" << std::endl;
|
||
|
||
// Count frequencies of each position value
|
||
std::map<uint8_t, int> positionCounts;
|
||
std::map<uint8_t, int> colorCounts;
|
||
|
||
// Extract all position and color bytes
|
||
std::cout << " First 30 [position, color] pairs (in hex): " << std::endl;
|
||
for (size_t i = 0; i < std::min(voxelData.size(), size_t(60)); i += 2) {
|
||
if (i + 1 < voxelData.size()) {
|
||
uint8_t position = static_cast<uint8_t>(voxelData[i]);
|
||
uint8_t color = static_cast<uint8_t>(voxelData[i + 1]);
|
||
positionCounts[position]++;
|
||
colorCounts[color]++;
|
||
|
||
std::cout << " [" << std::hex << std::setw(2) << std::setfill('0') << (int)position
|
||
<< ", " << std::setw(2) << std::setfill('0') << (int)color << "]";
|
||
|
||
if ((i/2) % 4 == 3) std::cout << std::endl;
|
||
else std::cout << " ";
|
||
}
|
||
}
|
||
std::cout << std::dec << std::endl;
|
||
|
||
// Check if all position counts are the same value
|
||
bool allSamePosition = true;
|
||
uint8_t firstPosition = 0;
|
||
|
||
if (!positionCounts.empty()) {
|
||
firstPosition = positionCounts.begin()->first;
|
||
for (const auto& [position, count] : positionCounts) {
|
||
if (position != firstPosition) {
|
||
allSamePosition = false;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Print frequencies of each position value
|
||
if (allSamePosition) {
|
||
std::cout << " ALL position bytes are " << std::hex << std::setw(2) << std::setfill('0')
|
||
<< (int)firstPosition << std::dec << " (" << voxelData.size() / 2 << " occurrences)" << std::endl;
|
||
} else {
|
||
std::cout << " Position value frequencies:" << std::endl;
|
||
for (const auto& [position, count] : positionCounts) {
|
||
std::cout << " Position " << std::hex << std::setw(2) << std::setfill('0')
|
||
<< (int)position << std::dec << ": " << count << " occurrences" << std::endl;
|
||
}
|
||
}
|
||
|
||
// Print frequencies of each color value
|
||
std::cout << " Color value frequencies:" << std::endl;
|
||
for (const auto& [color, count] : colorCounts) {
|
||
std::cout << " Color " << std::hex << std::setw(2) << std::setfill('0')
|
||
<< (int)color << std::dec << ": " << count << " occurrences" << std::endl;
|
||
}
|
||
|
||
std::cout << " Total voxels in snapshot: " << voxelData.size() / 2 << std::endl;
|
||
|
||
// Check if this might be a fill pattern by analyzing color transitions
|
||
if (voxelData.size() >= 4) {
|
||
std::cout << " Analyzing possible fill pattern:" << std::endl;
|
||
|
||
// Count how many consecutive voxels have the same color
|
||
int runLength = 1;
|
||
uint8_t prevColor = static_cast<uint8_t>(voxelData[1]);
|
||
std::vector<int> runLengths;
|
||
|
||
for (size_t i = 3; i < voxelData.size(); i += 2) {
|
||
uint8_t color = static_cast<uint8_t>(voxelData[i]);
|
||
if (color == prevColor) {
|
||
runLength++;
|
||
} else {
|
||
runLengths.push_back(runLength);
|
||
std::cout << " Run of " << runLength << " voxels with color "
|
||
<< std::hex << std::setw(2) << std::setfill('0') << (int)prevColor << std::dec << std::endl;
|
||
prevColor = color;
|
||
runLength = 1;
|
||
}
|
||
}
|
||
|
||
// Add the last run
|
||
runLengths.push_back(runLength);
|
||
std::cout << " Run of " << runLength << " voxels with color "
|
||
<< std::hex << std::setw(2) << std::setfill('0') << (int)prevColor << std::dec << std::endl;
|
||
|
||
// Calculate average run length
|
||
if (!runLengths.empty()) {
|
||
double avgRunLength = 0;
|
||
for (int len : runLengths) {
|
||
avgRunLength += len;
|
||
}
|
||
avgRunLength /= runLengths.size();
|
||
|
||
std::cout << " Average run length: " << avgRunLength << " voxels" << std::endl;
|
||
std::cout << " Number of color transitions: " << runLengths.size() - 1 << std::endl;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Debug function to print the plist structure recursively
|
||
void printPlistStructure(const boost::any& node, int depth = 0, const std::string& key = "") {
|
||
std::string indent(depth * 2, ' ');
|
||
|
||
if (!key.empty()) {
|
||
std::cout << indent << "Key: '" << key << "' -> ";
|
||
}
|
||
|
||
try {
|
||
// Try as dictionary
|
||
Plist::dictionary_type dict = boost::any_cast<Plist::dictionary_type>(node);
|
||
std::cout << "Dictionary with " << dict.size() << " keys" << std::endl;
|
||
|
||
for (const auto& [dictKey, value] : dict) {
|
||
std::cout << indent << " '" << dictKey << "': ";
|
||
printPlistStructure(value, depth + 1);
|
||
}
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as array
|
||
Plist::array_type array = boost::any_cast<Plist::array_type>(node);
|
||
std::cout << "Array with " << array.size() << " elements" << std::endl;
|
||
|
||
for (size_t i = 0; i < std::min(array.size(), size_t(5)); i++) {
|
||
std::cout << indent << " [" << i << "]: ";
|
||
printPlistStructure(array[i], depth + 1);
|
||
}
|
||
|
||
if (array.size() > 5) {
|
||
std::cout << indent << " ... and " << (array.size() - 5) << " more elements" << std::endl;
|
||
}
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as string
|
||
std::string str = boost::any_cast<std::string>(node);
|
||
std::cout << "String: '" << str << "'" << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as int
|
||
int num = boost::any_cast<int>(node);
|
||
std::cout << "Int: " << num << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as bool
|
||
bool b = boost::any_cast<bool>(node);
|
||
std::cout << "Bool: " << (b ? "true" : "false") << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as data
|
||
Plist::data_type data = boost::any_cast<Plist::data_type>(node);
|
||
std::cout << "Data with " << data.size() << " bytes";
|
||
|
||
// Print first few bytes in hex
|
||
if (!data.empty()) {
|
||
std::cout << " [";
|
||
size_t bytesToShow = std::min(data.size(), size_t(8));
|
||
for (size_t i = 0; i < bytesToShow; i++) {
|
||
if (i > 0) std::cout << " ";
|
||
std::cout << std::hex << std::setw(2) << std::setfill('0')
|
||
<< static_cast<int>(data[i]) << std::dec;
|
||
}
|
||
if (data.size() > 8) {
|
||
std::cout << " ...";
|
||
}
|
||
std::cout << "]";
|
||
}
|
||
std::cout << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as double
|
||
double d = boost::any_cast<double>(node);
|
||
std::cout << "Double: " << d << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as date
|
||
Plist::date_type date = boost::any_cast<Plist::date_type>(node);
|
||
std::cout << "Date object" << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as long int
|
||
long num = boost::any_cast<long>(node);
|
||
std::cout << "Long Int: " << num << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as uint8_t
|
||
uint8_t num = boost::any_cast<uint8_t>(node);
|
||
std::cout << "UInt8: " << (int)num << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
// Try as int8_t
|
||
int8_t num = boost::any_cast<int8_t>(node);
|
||
std::cout << "Int8: " << (int)num << std::endl;
|
||
} catch (const boost::bad_any_cast&) {
|
||
// Print the type info name for debugging
|
||
std::cout << "Unknown type: " << node.type().name() << std::endl;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Function to convert a snapshot type value to its string name
|
||
std::string getSnapshotTypeName(int64_t typeId) {
|
||
switch(typeId) {
|
||
case 0: return "underRestore";
|
||
case 1: return "redoRestore";
|
||
case 2: return "undo";
|
||
case 3: return "redo";
|
||
case 4: return "checkpoint";
|
||
case 5: return "selection";
|
||
default: return "unknown(" + std::to_string(typeId) + ")";
|
||
}
|
||
}
|
||
|
||
// Function to analyze statistics data from a snapshot
|
||
void analyzeSnapshotStatistics(const Plist::dictionary_type& snapshotData, size_t snapshotIndex, size_t actualVoxelCount) {
|
||
std::cout << "SNAPSHOT " << snapshotIndex << " STATISTICS:" << std::endl;
|
||
|
||
if (snapshotData.find("st") == snapshotData.end()) {
|
||
std::cout << " No statistics found" << std::endl;
|
||
return;
|
||
}
|
||
|
||
try {
|
||
Plist::dictionary_type stats = boost::any_cast<Plist::dictionary_type>(snapshotData.at("st"));
|
||
|
||
// Check for voxel count in statistics
|
||
int64_t statsVoxelCount = -1;
|
||
bool foundCount = false;
|
||
|
||
// Check both 'c' and 'count' keys
|
||
if (stats.find("c") != stats.end()) {
|
||
try {
|
||
statsVoxelCount = boost::any_cast<int64_t>(stats.at("c"));
|
||
foundCount = true;
|
||
} catch (const boost::bad_any_cast& e) {
|
||
std::cout << " Error extracting 'c' (count) value: " << e.what() << std::endl;
|
||
}
|
||
} else if (stats.find("count") != stats.end()) {
|
||
try {
|
||
statsVoxelCount = boost::any_cast<int64_t>(stats.at("count"));
|
||
foundCount = true;
|
||
} catch (const boost::bad_any_cast& e) {
|
||
std::cout << " Error extracting 'count' value: " << e.what() << std::endl;
|
||
}
|
||
}
|
||
|
||
if (foundCount) {
|
||
std::cout << " Voxel count (from stats): " << statsVoxelCount << std::endl;
|
||
std::cout << " Voxel count (from data stream): " << actualVoxelCount << std::endl;
|
||
|
||
if (statsVoxelCount > 0) {
|
||
double ratio = static_cast<double>(actualVoxelCount) / statsVoxelCount;
|
||
std::cout << " Ratio (actual/stats): " << ratio << std::endl;
|
||
}
|
||
}
|
||
|
||
// Helper function to print arrays
|
||
auto printArray = [](const std::string& name, const Plist::array_type& array) {
|
||
std::cout << " " << name << " array: [";
|
||
for (size_t i = 0; i < array.size(); i++) {
|
||
if (i > 0) std::cout << ", ";
|
||
try {
|
||
int64_t val = boost::any_cast<int64_t>(array[i]);
|
||
std::cout << val;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
int val = boost::any_cast<int>(array[i]);
|
||
std::cout << val;
|
||
} catch (const boost::bad_any_cast&) {
|
||
try {
|
||
double val = boost::any_cast<double>(array[i]);
|
||
std::cout << val;
|
||
} catch (const boost::bad_any_cast&) {
|
||
std::cout << "?";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
std::cout << "]" << std::endl;
|
||
};
|
||
|
||
// Check for min array
|
||
if (stats.find("min") != stats.end()) {
|
||
try {
|
||
Plist::array_type minArray = boost::any_cast<Plist::array_type>(stats.at("min"));
|
||
printArray("min", minArray);
|
||
} catch (const boost::bad_any_cast& e) {
|
||
std::cout << " Error extracting min array: " << e.what() << std::endl;
|
||
}
|
||
}
|
||
|
||
// Check for max array
|
||
if (stats.find("max") != stats.end()) {
|
||
try {
|
||
Plist::array_type maxArray = boost::any_cast<Plist::array_type>(stats.at("max"));
|
||
printArray("max", maxArray);
|
||
} catch (const boost::bad_any_cast& e) {
|
||
std::cout << " Error extracting max array: " << e.what() << std::endl;
|
||
}
|
||
}
|
||
|
||
// Check for position byte frequencies in the data stream
|
||
if (snapshotData.find("ds") != snapshotData.end()) {
|
||
try {
|
||
Plist::data_type voxelData = boost::any_cast<Plist::data_type>(snapshotData.at("ds"));
|
||
std::map<uint8_t, size_t> positionByteFrequency;
|
||
|
||
// Count the frequency of each position byte (first byte of each pair)
|
||
for (size_t i = 0; i < voxelData.size(); i += 2) {
|
||
if (i + 1 < voxelData.size()) {
|
||
uint8_t positionByte = voxelData[i];
|
||
positionByteFrequency[positionByte]++;
|
||
}
|
||
}
|
||
|
||
// Display frequencies
|
||
for (const auto& [byte, count] : positionByteFrequency) {
|
||
std::cout << " Position byte " << std::setw(2) << std::setfill('0') << std::hex << (int)byte
|
||
<< " occurs " << std::dec << count << " times" << std::endl;
|
||
}
|
||
} catch (const boost::bad_any_cast& e) {
|
||
std::cout << " Error analyzing position bytes: " << e.what() << std::endl;
|
||
}
|
||
}
|
||
|
||
} catch (const boost::bad_any_cast& e) {
|
||
std::cout << " Error extracting statistics: " << e.what() << std::endl;
|
||
}
|
||
}
|
||
|
||
// Function to create a Bella scene file from the extracted voxel data
|
||
bool outputBellaFile(const std::string& outputPath, size_t voxelCount) {
|
||
// Comment out all Bella scene creation code to avoid errors
|
||
std::cout << "Skipping Bella scene creation. Just printing voxel data." << std::endl;
|
||
return true;
|
||
}
|
||
|
||
// Function to analyze a snapshot's location table in detail
|
||
void analyzeLocationTable(const Plist::data_type& locationTable, size_t snapshotIndex, int64_t chunkID) {
|
||
std::cout << "LOCATION TABLE ANALYSIS for Snapshot " << snapshotIndex << " (ChunkID " << chunkID << "):" << std::endl;
|
||
std::cout << " Location table size: " << locationTable.size() << " bytes" << std::endl;
|
||
|
||
// Count non-zero bytes
|
||
int nonZeroCount = 0;
|
||
for (size_t i = 0; i < locationTable.size(); i++) {
|
||
if (locationTable[i] != 0) {
|
||
nonZeroCount++;
|
||
std::cout << " Byte " << i << " = " << static_cast<int>(locationTable[i]) << std::endl;
|
||
}
|
||
}
|
||
|
||
std::cout << " Total non-zero bytes: " << nonZeroCount << " out of " << locationTable.size() << std::endl;
|
||
|
||
// Print first 16 bytes as hex
|
||
std::cout << " First 16 bytes (hex): ";
|
||
for (size_t i = 0; i < std::min(locationTable.size(), size_t(16)); i++) {
|
||
std::cout << std::hex << std::setw(2) << std::setfill('0')
|
||
<< static_cast<int>(locationTable[i]) << " ";
|
||
}
|
||
std::cout << std::dec << std::endl;
|
||
|
||
// Print last 16 bytes as hex
|
||
if (locationTable.size() > 16) {
|
||
std::cout << " Last 16 bytes (hex): ";
|
||
for (size_t i = locationTable.size() - 16; i < locationTable.size(); i++) {
|
||
std::cout << std::hex << std::setw(2) << std::setfill('0')
|
||
<< static_cast<int>(locationTable[i]) << " ";
|
||
}
|
||
std::cout << std::dec << std::endl;
|
||
}
|
||
}
|
||
|
||
// Function to analyze a snapshot's layer color usage data
|
||
void analyzeLayerColorUsage(const Plist::data_type& layerColorTable, size_t snapshotIndex, int64_t chunkID) {
|
||
std::cout << "LAYER COLOR USAGE ANALYSIS for Snapshot " << snapshotIndex << " (ChunkID " << chunkID << "):" << std::endl;
|
||
std::cout << " Layer color table size: " << layerColorTable.size() << " bytes" << std::endl;
|
||
|
||
// Count non-zero bytes (active color layers)
|
||
int activeLayerCount = 0;
|
||
for (size_t i = 0; i < layerColorTable.size(); i++) {
|
||
if (layerColorTable[i] != 0) {
|
||
activeLayerCount++;
|
||
std::cout << " Color Layer " << i << " is active (value=" << static_cast<int>(layerColorTable[i]) << ")" << std::endl;
|
||
}
|
||
}
|
||
|
||
std::cout << " Total active color layers: " << activeLayerCount << " out of " << layerColorTable.size() << " possible" << std::endl;
|
||
|
||
// Print first 16 bytes as hex
|
||
std::cout << " First 16 color layers (hex): ";
|
||
for (size_t i = 0; i < std::min(layerColorTable.size(), size_t(16)); i++) {
|
||
std::cout << std::hex << std::setw(2) << std::setfill('0')
|
||
<< static_cast<int>(layerColorTable[i]) << " ";
|
||
}
|
||
std::cout << std::dec << std::endl;
|
||
|
||
// Print last 16 bytes as hex
|
||
if (layerColorTable.size() > 16) {
|
||
std::cout << " Last 16 color layers (hex): ";
|
||
for (size_t i = layerColorTable.size() - 16; i < layerColorTable.size(); i++) {
|
||
std::cout << std::hex << std::setw(2) << std::setfill('0')
|
||
<< static_cast<int>(layerColorTable[i]) << " ";
|
||
}
|
||
std::cout << std::dec << std::endl;
|
||
}
|
||
}
|
||
|
||
// Function to parse the plist file and extract voxel data
|
||
bool parsePlistVoxelData(const std::string& filePath, bool verbose) {
|
||
// Load and parse the plist file
|
||
boost::any plistData;
|
||
try {
|
||
Plist::readPlist(filePath.c_str(), plistData);
|
||
} catch (std::exception& e) {
|
||
std::cerr << "Error reading plist file: " << e.what() << std::endl;
|
||
return false;
|
||
}
|
||
|
||
// The root should be a dictionary
|
||
Plist::dictionary_type rootDict;
|
||
try {
|
||
rootDict = boost::any_cast<Plist::dictionary_type>(plistData);
|
||
} catch (std::exception& e) {
|
||
std::cerr << "Error casting plist data to dictionary: " << e.what() << std::endl;
|
||
return false;
|
||
}
|
||
|
||
// Print all keys in the root dictionary if verbose
|
||
if (verbose) {
|
||
std::cout << "Root keys: ";
|
||
for (const auto& key : rootDict) {
|
||
std::cout << key.first << " ";
|
||
}
|
||
std::cout << std::endl;
|
||
}
|
||
|
||
// Check if the snapshots key exists in the plist
|
||
if (rootDict.find("snapshots") == rootDict.end()) {
|
||
std::cerr << "Error: No 'snapshots' key found in plist" << std::endl;
|
||
return false;
|
||
}
|
||
|
||
// Get the snapshots array
|
||
Plist::array_type snapshotsArray;
|
||
try {
|
||
snapshotsArray = boost::any_cast<Plist::array_type>(rootDict.at("snapshots"));
|
||
} catch (std::exception& e) {
|
||
std::cerr << "Error casting snapshots to array: " << e.what() << std::endl;
|
||
return false;
|
||
}
|
||
|
||
// Print the structure of the first few snapshots
|
||
if (verbose) {
|
||
std::cout << "\nDetailed structure of the first snapshot:" << std::endl;
|
||
if (!snapshotsArray.empty()) {
|
||
printPlistStructure(snapshotsArray[0], 0, "snapshots[0]");
|
||
}
|
||
}
|
||
|
||
// Statistics tracking
|
||
int totalSnapshots = snapshotsArray.size();
|
||
size_t totalLocationTableBytes = 0;
|
||
size_t totalVoxelDataBytes = 0;
|
||
std::set<std::string> uniqueChunkIds; // Track unique chunk IDs
|
||
|
||
// Additional statistics for snapshot types
|
||
std::map<int64_t, int> snapshotTypeCount; // Count occurrences of each snapshot type
|
||
|
||
std::cout << "Total snapshots in plist: " << totalSnapshots << std::endl;
|
||
|
||
// World model: map of chunk index to chunk data
|
||
std::map<std::string, Chunk> worldModel;
|
||
|
||
// Process each snapshot in the plist sequentially
|
||
for (size_t i = 0; i < snapshotsArray.size(); i++) {
|
||
try {
|
||
Plist::dictionary_type snapshot = boost::any_cast<Plist::dictionary_type>(snapshotsArray[i]);
|
||
|
||
// DEBUG: Print all keys in this snapshot
|
||
if (verbose && i < 5) { // Only print for first 5 snapshots to avoid excessive output
|
||
std::cout << "\nSnapshot " << i << " Keys: ";
|
||
for (const auto& key : snapshot) {
|
||
std::cout << "'" << key.first << "' ";
|
||
}
|
||
std::cout << std::endl;
|
||
|
||
// If snapshot has a 's' key, print all keys in that dictionary too
|
||
if (snapshot.find("s") != snapshot.end()) {
|
||
try {
|
||
Plist::dictionary_type snapshotData = boost::any_cast<Plist::dictionary_type>(snapshot.at("s"));
|
||
std::cout << " Snapshot " << i << " 's' Dictionary Keys: ";
|
||
for (const auto& key : snapshotData) {
|
||
std::cout << "'" << key.first << "' ";
|
||
}
|
||
std::cout << std::endl;
|
||
} catch (std::exception& e) {
|
||
std::cout << " Error getting 's' dictionary keys: " << e.what() << std::endl;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Variables to store snapshot metadata
|
||
std::string chunkId = "unknown";
|
||
bool foundChunkID = false;
|
||
int64_t sessionId = -1;
|
||
int64_t typeId = -1;
|
||
std::string typeName = "unknown";
|
||
size_t voxelsInSnapshot = 0; // Track voxel count
|
||
|
||
// Extract chunk ID (cid) if present
|
||
if (snapshot.find("c") != snapshot.end()) {
|
||
try {
|
||
chunkId = boost::any_cast<std::string>(snapshot.at("c"));
|
||
uniqueChunkIds.insert(chunkId);
|
||
|
||
if (verbose) {
|
||
std::cout << "\nProcessing Snapshot " << i << " with Chunk ID: " << chunkId << std::endl;
|
||
}
|
||
} catch (std::exception& e) {
|
||
// If casting fails, try as an integer
|
||
try {
|
||
int cid = boost::any_cast<int>(snapshot.at("c"));
|
||
chunkId = std::to_string(cid);
|
||
uniqueChunkIds.insert(chunkId);
|
||
|
||
if (verbose) {
|
||
std::cout << "\nProcessing Snapshot " << i << " with Chunk ID: " << chunkId << std::endl;
|
||
}
|
||
} catch (std::exception& e2) {
|
||
if (verbose) {
|
||
std::cout << "\nProcessing Snapshot " << i << " (couldn't extract chunk ID)" << std::endl;
|
||
}
|
||
}
|
||
}
|
||
} else if (verbose) {
|
||
std::cout << "\nProcessing Snapshot " << i << " (no chunk ID found)" << std::endl;
|
||
}
|
||
|
||
// Each snapshot has an 's' key containing voxel data
|
||
if (snapshot.find("s") != snapshot.end()) {
|
||
Plist::dictionary_type snapshotData = boost::any_cast<Plist::dictionary_type>(snapshot.at("s"));
|
||
|
||
// Try to get chunk ID from 's' dictionary if it has an 'id' key
|
||
if (snapshotData.find("id") != snapshotData.end()) {
|
||
try {
|
||
// The 'id' field is a dictionary containing the chunk ID
|
||
Plist::dictionary_type idDict = boost::any_cast<Plist::dictionary_type>(snapshotData.at("id"));
|
||
|
||
// DEBUG: Print all keys in the 'id' dictionary for first few snapshots
|
||
if (verbose && i < 3) {
|
||
std::cout << " 's.id' Dictionary Keys: ";
|
||
for (const auto& key : idDict) {
|
||
std::cout << "'" << key.first << "' ";
|
||
}
|
||
std::cout << std::endl;
|
||
|
||
// Try to extract and print the values in the dictionary with more detailed type info
|
||
for (const auto& [key, value] : idDict) {
|
||
std::cout << " '" << key << "': ";
|
||
std::cout << "Type: '" << value.type().name() << "' ";
|
||
|
||
// Try a variety of data types
|
||
try {
|
||
std::string strValue = boost::any_cast<std::string>(value);
|
||
std::cout << "String '" << strValue << "'";
|
||
} catch (boost::bad_any_cast&) {
|
||
try {
|
||
int intValue = boost::any_cast<int>(value);
|
||
std::cout << "Int " << intValue;
|
||
} catch (boost::bad_any_cast&) {
|
||
try {
|
||
double doubleValue = boost::any_cast<double>(value);
|
||
std::cout << "Double " << doubleValue;
|
||
} catch (boost::bad_any_cast&) {
|
||
try {
|
||
float floatValue = boost::any_cast<float>(value);
|
||
std::cout << "Float " << floatValue;
|
||
} catch (boost::bad_any_cast&) {
|
||
try {
|
||
int64_t int64Value = boost::any_cast<int64_t>(value);
|
||
std::cout << "Int64 " << int64Value;
|
||
} catch (boost::bad_any_cast&) {
|
||
try {
|
||
bool boolValue = boost::any_cast<bool>(value);
|
||
std::cout << "Bool " << (boolValue ? "true" : "false");
|
||
} catch (boost::bad_any_cast&) {
|
||
try {
|
||
Plist::dictionary_type dictValue = boost::any_cast<Plist::dictionary_type>(value);
|
||
std::cout << "Dictionary with " << dictValue.size() << " keys";
|
||
} catch (boost::bad_any_cast&) {
|
||
try {
|
||
Plist::array_type arrayValue = boost::any_cast<Plist::array_type>(value);
|
||
std::cout << "Array with " << arrayValue.size() << " elements";
|
||
} catch (boost::bad_any_cast&) {
|
||
std::cout << "Unknown type - couldn't cast";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
std::cout << std::endl;
|
||
}
|
||
}
|
||
|
||
// Now look for 'c' in the id dictionary
|
||
if (idDict.find("c") != idDict.end()) {
|
||
try {
|
||
// These are type Int64 in plist
|
||
int64_t chunkId_int = boost::any_cast<int64_t>(idDict.at("c"));
|
||
chunkId = std::to_string(chunkId_int);
|
||
|
||
if (verbose) {
|
||
std::cout << " Found chunk ID: " << chunkId << std::endl;
|
||
}
|
||
uniqueChunkIds.insert(chunkId);
|
||
foundChunkID = true;
|
||
} catch (boost::bad_any_cast&) {
|
||
if (verbose) {
|
||
std::cout << " Couldn't cast chunk ID as Int64" << std::endl;
|
||
}
|
||
}
|
||
} else if (verbose) {
|
||
std::cout << " No 'c' key found in 's.id' dictionary" << std::endl;
|
||
}
|
||
|
||
// Also check for session ID ('s')
|
||
if (idDict.find("s") != idDict.end()) {
|
||
try {
|
||
sessionId = boost::any_cast<int64_t>(idDict.at("s"));
|
||
if (verbose) {
|
||
std::cout << " Found session ID: " << sessionId << std::endl;
|
||
}
|
||
} catch (boost::bad_any_cast&) {
|
||
if (verbose) {
|
||
std::cout << " Couldn't cast session ID as Int64" << std::endl;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Check for type ID ('t')
|
||
if (idDict.find("t") != idDict.end()) {
|
||
try {
|
||
typeId = boost::any_cast<int64_t>(idDict.at("t"));
|
||
typeName = getSnapshotTypeName(typeId);
|
||
snapshotTypeCount[typeId]++;
|
||
|
||
if (verbose) {
|
||
std::cout << " Found type ID: " << typeId << " (" << typeName << ")" << std::endl;
|
||
}
|
||
} catch (boost::bad_any_cast&) {
|
||
if (verbose) {
|
||
std::cout << " Couldn't cast type ID as Int64" << std::endl;
|
||
}
|
||
}
|
||
}
|
||
} catch (boost::bad_any_cast&) {
|
||
if (verbose) {
|
||
std::cout << " 's.id' is not a dictionary" << std::endl;
|
||
}
|
||
}
|
||
} else if (verbose) {
|
||
std::cout << " No 'id' key found in 's' dictionary" << std::endl;
|
||
}
|
||
|
||
// Get voxel data stream for counting
|
||
if (snapshotData.find("ds") != snapshotData.end()) {
|
||
Plist::data_type voxelData = boost::any_cast<Plist::data_type>(snapshotData.at("ds"));
|
||
voxelsInSnapshot = voxelData.size() / 2; // Each voxel is 2 bytes
|
||
}
|
||
|
||
// Print snapshot summary even in non-verbose mode
|
||
if (!chunkId.empty()) {
|
||
int64_t chunkID_int = std::stoll(chunkId);
|
||
|
||
// Get world origin coordinates using Morton decoding
|
||
auto [worldOriginX, worldOriginY, worldOriginZ] = decodeMortonChunkID(chunkID_int);
|
||
|
||
// Scale by chunk size (32 instead of 8 for larger chunks)
|
||
worldOriginX *= 32;
|
||
worldOriginY *= 32;
|
||
worldOriginZ *= 32;
|
||
|
||
std::cout << "Snapshot " << i << ": ChunkID=" << chunkId
|
||
<< ", World Origin=(" << worldOriginX << "," << worldOriginY << "," << worldOriginZ << ")"
|
||
<< ", Type=" << typeName
|
||
<< ", SessionID=" << sessionId
|
||
<< ", Data Stream Voxels=" << voxelsInSnapshot << std::endl;
|
||
|
||
// Check if this is a checkpoint type (type ID 4) with voxel data
|
||
if (typeId == 4 && snapshotData.find("ds") != snapshotData.end()) {
|
||
Plist::data_type voxelData = boost::any_cast<Plist::data_type>(snapshotData.at("ds"));
|
||
if (!voxelData.empty()) {
|
||
std::cout << " CHECKPOINT CHUNK WORLD ORIGIN: ("
|
||
<< worldOriginX << "," << worldOriginY << "," << worldOriginZ
|
||
<< ")" << std::endl;
|
||
}
|
||
}
|
||
|
||
// Analyze statistics data for this snapshot
|
||
analyzeSnapshotStatistics(snapshotData, i, voxelsInSnapshot);
|
||
} else {
|
||
std::cout << "Snapshot " << i << ": ChunkID=unknown"
|
||
<< ", World Origin=(unknown,unknown,unknown)"
|
||
<< ", Type=" << typeName
|
||
<< ", SessionID=" << sessionId
|
||
<< ", Data Stream Voxels=" << voxelsInSnapshot << std::endl;
|
||
}
|
||
|
||
// Check for location table (lc) and voxel data stream (ds)
|
||
if (snapshotData.find("lc") != snapshotData.end() && snapshotData.find("ds") != snapshotData.end()) {
|
||
// Get layer color usage table
|
||
Plist::data_type locationTable = boost::any_cast<Plist::data_type>(snapshotData.at("lc"));
|
||
totalLocationTableBytes += locationTable.size();
|
||
|
||
// Add this to analyze the location table in detail
|
||
if (i < 5 || i % 10 == 0) { // Only analyze a subset of snapshots to avoid excessive output
|
||
analyzeLayerColorUsage(locationTable, i, foundChunkID ? std::stoll(chunkId) : -1);
|
||
}
|
||
|
||
// Get voxel data stream
|
||
Plist::data_type voxelData = boost::any_cast<Plist::data_type>(snapshotData.at("ds"));
|
||
totalVoxelDataBytes += voxelData.size();
|
||
|
||
if (verbose) {
|
||
std::cout << " Location table size: " << locationTable.size() << " bytes" << std::endl;
|
||
std::cout << " Voxel data size: " << voxelData.size() << " bytes" << std::endl;
|
||
}
|
||
|
||
// Analyze Morton codes in the data stream
|
||
analyzeDataStreamMortonCodes(voxelData, i);
|
||
|
||
// Find chunks modified in this snapshot
|
||
std::vector<uint8_t> modifiedChunks;
|
||
for (size_t j = 0; j < locationTable.size(); j++) {
|
||
if (locationTable[j] != 0) {
|
||
modifiedChunks.push_back(j);
|
||
|
||
// Decode chunk location from Morton code
|
||
uint8_t chunkX, chunkY, chunkZ;
|
||
decodeChunkMorton(j, chunkX, chunkY, chunkZ);
|
||
|
||
if (verbose) {
|
||
std::cout << " Modified chunk " << (int)j << " at ("
|
||
<< (int)chunkX << "," << (int)chunkY << "," << (int)chunkZ
|
||
<< ")" << std::endl;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Count voxels in the data stream (each voxel is 2 bytes: position and color)
|
||
size_t voxelsInSnapshot = voxelData.size() / 2;
|
||
|
||
// Simple validation check
|
||
if (voxelsInSnapshot == 0 || modifiedChunks.empty()) {
|
||
if (verbose) {
|
||
std::cout << " Warning: Snapshot contains no voxels or no modified chunks" << std::endl;
|
||
}
|
||
continue; // Skip this snapshot
|
||
}
|
||
|
||
if (verbose) {
|
||
std::cout << " Voxels in snapshot: " << voxelsInSnapshot << std::endl;
|
||
std::cout << " Modified chunks: " << modifiedChunks.size() << std::endl;
|
||
}
|
||
|
||
// Extract the chunk ID we found earlier to use as the key in our world model
|
||
std::string modelChunkKey = foundChunkID ? std::to_string(i) + "_" + chunkId : std::to_string(i);
|
||
|
||
// Interpret the voxel data
|
||
for (uint8_t chunkIndex : modifiedChunks) {
|
||
// Decode chunk coordinates
|
||
uint8_t chunkX, chunkY, chunkZ;
|
||
decodeChunkMorton(chunkIndex, chunkX, chunkY, chunkZ);
|
||
|
||
// Create a unique key using the chunk ID if available, or just the chunk index
|
||
std::string chunkKey = modelChunkKey + "_" + std::to_string(chunkIndex);
|
||
|
||
// Get existing chunk or create a new one in our modified world model map
|
||
std::map<std::string, Chunk>& worldModelByChunkId = worldModel;
|
||
Chunk& chunk = worldModelByChunkId[chunkKey];
|
||
|
||
if (chunk.voxels.empty()) {
|
||
// If this is a new chunk, initialize its coordinates
|
||
chunk.chunkX = chunkX;
|
||
chunk.chunkY = chunkY;
|
||
chunk.chunkZ = chunkZ;
|
||
}
|
||
|
||
// Create a vector for this chunk's voxels
|
||
std::vector<Voxel> newVoxels;
|
||
|
||
// Pre-allocate a reasonable amount of memory to avoid frequent reallocations
|
||
newVoxels.reserve(voxelsInSnapshot / modifiedChunks.size());
|
||
|
||
// Loop through voxel data and populate the chunk
|
||
for (size_t j = 0; j < voxelData.size(); j += 2) {
|
||
if (j + 1 < voxelData.size()) {
|
||
uint8_t position = static_cast<uint8_t>(voxelData[j]);
|
||
uint8_t color = static_cast<uint8_t>(voxelData[j + 1]);
|
||
newVoxels.emplace_back(position, color);
|
||
}
|
||
}
|
||
|
||
// For normal snapshots, replace previous voxels in the chunk
|
||
// This assumes that each snapshot contains a complete, updated state for the chunk
|
||
chunk.voxels = newVoxels;
|
||
|
||
if (verbose) {
|
||
std::cout << " Updated chunk " << chunkKey << " with "
|
||
<< newVoxels.size() << " voxels" << std::endl;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (std::exception& e) {
|
||
std::cerr << "Error processing snapshot " << i << ": " << e.what() << std::endl;
|
||
}
|
||
}
|
||
|
||
// Print snapshot type statistics
|
||
std::cout << "\nSNAPSHOT TYPE SUMMARY:" << std::endl;
|
||
for (const auto& [type, count] : snapshotTypeCount) {
|
||
std::cout << " " << getSnapshotTypeName(type) << ": " << count << " snapshots ("
|
||
<< (count * 100.0 / totalSnapshots) << "%)" << std::endl;
|
||
}
|
||
|
||
// Print information about unique chunk IDs
|
||
std::cout << "\nFound " << uniqueChunkIds.size() << " unique chunk IDs across "
|
||
<< totalSnapshots << " snapshots" << std::endl;
|
||
|
||
// Print information about the final world model
|
||
std::cout << "\nFINAL WORLD MODEL:" << std::endl;
|
||
std::cout << "Total chunks occupied: " << worldModel.size() << std::endl;
|
||
|
||
// Count total voxels in the final model
|
||
size_t totalVoxels = 0;
|
||
for (const auto& chunkEntry : worldModel) {
|
||
totalVoxels += chunkEntry.second.voxels.size();
|
||
}
|
||
std::cout << "Total voxels in model: " << totalVoxels << std::endl;
|
||
|
||
// Detailed output for each chunk if verbose
|
||
if (verbose) {
|
||
std::cout << "\nDetailed chunk information:" << std::endl;
|
||
|
||
for (const auto& chunkEntry : worldModel) {
|
||
std::string chunkKey = chunkEntry.first;
|
||
const Chunk& chunk = chunkEntry.second;
|
||
|
||
std::cout << "Chunk " << chunkKey << " at position ("
|
||
<< (int)chunk.chunkX << "," << (int)chunk.chunkY << "," << (int)chunk.chunkZ
|
||
<< ") contains " << chunk.voxels.size() << " voxels" << std::endl;
|
||
|
||
// Print color distribution
|
||
std::map<uint8_t, int> colorCount;
|
||
for (const auto& voxel : chunk.voxels) {
|
||
colorCount[voxel.color]++;
|
||
}
|
||
|
||
std::cout << " Color distribution:" << std::endl;
|
||
for (const auto& [color, count] : colorCount) {
|
||
std::cout << " Color " << (int)color << ": " << count << " voxels" << std::endl;
|
||
}
|
||
|
||
// Count voxels with non-zero positions
|
||
int nonZeroPos = 0;
|
||
for (const auto& voxel : chunk.voxels) {
|
||
if (voxel.position != 0) nonZeroPos++;
|
||
}
|
||
|
||
std::cout << " Non-zero positions: " << nonZeroPos << " out of "
|
||
<< chunk.voxels.size() << std::endl;
|
||
|
||
// Get voxels using correct interpretation
|
||
auto voxels = chunk.getVoxels();
|
||
std::cout << " Total voxels: " << voxels.size() << std::endl;
|
||
|
||
// Show sample voxels
|
||
if (!voxels.empty()) {
|
||
std::cout << " Sample voxels:" << std::endl;
|
||
for (int i = 0; i < std::min(5, (int)voxels.size()); i++) {
|
||
auto [x, y, z, color] = voxels[i];
|
||
std::cout << " Voxel " << i << ": local=(" << (int)x << "," << (int)y << "," << (int)z
|
||
<< ") color=" << (int)color << std::endl;
|
||
}
|
||
}
|
||
|
||
// Print world coordinates for voxels in verbose mode
|
||
if (verbose) {
|
||
auto worldVoxels = chunk.getWorldVoxels();
|
||
std::cout << " World coordinates (showing first 10):" << std::endl;
|
||
|
||
int count = 0;
|
||
std::map<std::tuple<uint16_t, uint16_t, uint16_t>, int> uniquePositions;
|
||
|
||
for (const auto& [worldX, worldY, worldZ, color] : worldVoxels) {
|
||
// Track unique positions
|
||
uniquePositions[std::make_tuple(worldX, worldY, worldZ)]++;
|
||
|
||
// Only show first 10 to avoid overwhelming output
|
||
if (count < 10) {
|
||
std::cout << " Voxel " << count << ": world=(" << worldX << "," << worldY << "," << worldZ
|
||
<< ") color=" << (int)color << std::endl;
|
||
}
|
||
count++;
|
||
}
|
||
|
||
std::cout << " Total voxels: " << worldVoxels.size() << std::endl;
|
||
std::cout << " Unique positions: " << uniquePositions.size() << std::endl;
|
||
|
||
// Show runs with world coordinates if there are interesting patterns
|
||
if (worldVoxels.size() > 1 && uniquePositions.size() < worldVoxels.size() / 2) {
|
||
std::cout << " World runs (first 5):" << std::endl;
|
||
int runCount = 0;
|
||
|
||
for (const auto& [pos, count] : uniquePositions) {
|
||
if (runCount < 5) {
|
||
auto [x, y, z] = pos;
|
||
std::cout << " Position (" << x << "," << y << "," << z
|
||
<< "): " << count << " voxels" << std::endl;
|
||
}
|
||
runCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Only print visualization for a few sample chunks (to keep output manageable)
|
||
static int sampleCounter = 0;
|
||
if (worldModel.size() <= 5 || (sampleCounter++ % (worldModel.size() / 5 + 1) == 0)) {
|
||
// Print raw voxel data representation instead of visualization
|
||
std::cout << " Raw voxel data summary:" << std::endl;
|
||
|
||
// Count position=0 voxels
|
||
int zeroPositionCount = 0;
|
||
for (const auto& voxel : chunk.voxels) {
|
||
if (voxel.position == 0) zeroPositionCount++;
|
||
}
|
||
|
||
std::cout << " Position=0 voxels: " << zeroPositionCount
|
||
<< " (" << (zeroPositionCount * 100.0 / chunk.voxels.size()) << "%)" << std::endl;
|
||
|
||
// Analyze color transitions
|
||
if (chunk.voxels.size() >= 2) {
|
||
int transitions = 0;
|
||
for (size_t i = 1; i < chunk.voxels.size(); i++) {
|
||
if (chunk.voxels[i].color != chunk.voxels[i-1].color) {
|
||
transitions++;
|
||
}
|
||
}
|
||
std::cout << " Color transitions: " << transitions << std::endl;
|
||
|
||
// Count runs of same color
|
||
int currentRunLength = 1;
|
||
int maxRunLength = 1;
|
||
std::vector<int> runLengths;
|
||
|
||
for (size_t i = 1; i < chunk.voxels.size(); i++) {
|
||
if (chunk.voxels[i].color == chunk.voxels[i-1].color) {
|
||
currentRunLength++;
|
||
} else {
|
||
runLengths.push_back(currentRunLength);
|
||
if (currentRunLength > maxRunLength) maxRunLength = currentRunLength;
|
||
currentRunLength = 1;
|
||
}
|
||
}
|
||
runLengths.push_back(currentRunLength);
|
||
if (currentRunLength > maxRunLength) maxRunLength = currentRunLength;
|
||
|
||
// Calculate average run length
|
||
double avgRunLength = 0;
|
||
for (int len : runLengths) {
|
||
avgRunLength += len;
|
||
}
|
||
avgRunLength /= runLengths.size();
|
||
|
||
std::cout << " Average color run length: " << avgRunLength << " voxels" << std::endl;
|
||
std::cout << " Max color run length: " << maxRunLength << " voxels" << std::endl;
|
||
std::cout << " Number of color runs: " << runLengths.size() << std::endl;
|
||
|
||
// Show first few and last few color runs
|
||
int runsToShow = std::min(3, (int)runLengths.size());
|
||
std::cout << " First " << runsToShow << " color runs: ";
|
||
for (int i = 0; i < runsToShow; i++) {
|
||
if (i > 0) std::cout << ", ";
|
||
std::cout << runLengths[i];
|
||
}
|
||
std::cout << std::endl;
|
||
|
||
if (runLengths.size() > 6) {
|
||
std::cout << " Last " << runsToShow << " color runs: ";
|
||
for (int i = runLengths.size() - runsToShow; i < runLengths.size(); i++) {
|
||
if (i > runLengths.size() - runsToShow) std::cout << ", ";
|
||
std::cout << runLengths[i];
|
||
}
|
||
std::cout << std::endl;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Check how full the voxel space is compared to a fully filled 256×256×256 voxel space
|
||
const size_t maxVoxels = 256 * 256 * 256; // Maximum possible voxels in the space
|
||
double percentFull = (double)totalVoxels / maxVoxels * 100.0;
|
||
std::cout << "Voxel space occupancy: " << percentFull << "% of maximum ("
|
||
<< totalVoxels << " out of " << maxVoxels << " voxels)" << std::endl;
|
||
|
||
return true;
|
||
}
|
||
|
||
// Function to print all visible voxels after processing all snapshots
|
||
void printAllVisibleVoxels(const std::string& filePath) {
|
||
boost::any plistData;
|
||
try {
|
||
Plist::readPlist(filePath.c_str(), plistData);
|
||
} catch (std::exception& e) {
|
||
std::cerr << "Error reading plist file: " << e.what() << std::endl;
|
||
return;
|
||
}
|
||
|
||
// Get root dictionary
|
||
Plist::dictionary_type rootDict = boost::any_cast<Plist::dictionary_type>(plistData);
|
||
if (rootDict.find("snapshots") == rootDict.end()) {
|
||
std::cerr << "Error: No 'snapshots' key found in plist" << std::endl;
|
||
return;
|
||
}
|
||
|
||
// Get the snapshots array
|
||
Plist::array_type snapshotsArray = boost::any_cast<Plist::array_type>(rootDict.at("snapshots"));
|
||
std::cout << "Processing " << snapshotsArray.size() << " snapshots..." << std::endl;
|
||
|
||
// Map to store final voxel state: key is chunkID, value is a map of voxel positions to colors
|
||
// A special value of color=0 means "no voxel here" (empty)
|
||
std::map<int64_t, std::map<std::tuple<int, int, int>, uint8_t>> finalVoxelState;
|
||
|
||
// Map to store snapshot indices by chunk ID for debugging
|
||
std::map<int64_t, std::vector<size_t>> snapshotIndicesByChunk;
|
||
|
||
// First pass: Process all snapshots in order to build the final voxel state
|
||
for (size_t snapshotIndex = 0; snapshotIndex < snapshotsArray.size(); snapshotIndex++) {
|
||
try {
|
||
Plist::dictionary_type snapshot = boost::any_cast<Plist::dictionary_type>(snapshotsArray[snapshotIndex]);
|
||
|
||
// Skip if no 's' key
|
||
if (snapshot.find("s") == snapshot.end()) continue;
|
||
|
||
Plist::dictionary_type snapshotData = boost::any_cast<Plist::dictionary_type>(snapshot.at("s"));
|
||
|
||
// Skip if no 'id' dictionary
|
||
if (snapshotData.find("id") == snapshotData.end()) continue;
|
||
|
||
Plist::dictionary_type idDict = boost::any_cast<Plist::dictionary_type>(snapshotData.at("id"));
|
||
|
||
// Skip if no chunk ID
|
||
if (idDict.find("c") == idDict.end()) continue;
|
||
|
||
// Get chunk ID
|
||
int64_t chunkID = boost::any_cast<int64_t>(idDict.at("c"));
|
||
|
||
// Track this snapshot index for this chunk
|
||
snapshotIndicesByChunk[chunkID].push_back(snapshotIndex);
|
||
|
||
// Skip if no voxel data
|
||
if (snapshotData.find("ds") == snapshotData.end()) continue;
|
||
|
||
// Get world origin coordinates for this chunk
|
||
auto [chunkX, chunkY, chunkZ] = decodeMortonChunkID(chunkID);
|
||
|
||
// Scale by chunk size (32)
|
||
int worldOriginX = chunkX * 32;
|
||
int worldOriginY = chunkY * 32;
|
||
int worldOriginZ = chunkZ * 32;
|
||
|
||
// Get voxel data
|
||
Plist::data_type voxelData = boost::any_cast<Plist::data_type>(snapshotData.at("ds"));
|
||
|
||
// Process voxel data using hybrid encoding
|
||
int localX = 0, localY = 0, localZ = 0; // Current position counter within chunk
|
||
|
||
// Process each voxel pair (position, color)
|
||
for (size_t i = 0; i < voxelData.size(); i += 2) {
|
||
if (i + 1 < voxelData.size()) {
|
||
uint8_t position = static_cast<uint8_t>(voxelData[i]);
|
||
uint8_t color = static_cast<uint8_t>(voxelData[i + 1]);
|
||
|
||
if (position != 0) {
|
||
// Non-zero position: Jump to Morton-encoded position
|
||
uint8_t mortonX = 0, mortonY = 0, mortonZ = 0;
|
||
decodeMorton(position, mortonX, mortonY, mortonZ);
|
||
localX = mortonX;
|
||
localY = mortonY;
|
||
localZ = mortonZ;
|
||
}
|
||
|
||
// Calculate world coordinates
|
||
int worldX = worldOriginX + localX;
|
||
int worldY = worldOriginY + localY;
|
||
int worldZ = worldOriginZ + localZ;
|
||
|
||
// Update voxel state for this position
|
||
// Note: color=0 means "no voxel here" (empty space)
|
||
finalVoxelState[chunkID][std::make_tuple(worldX, worldY, worldZ)] = color;
|
||
|
||
// Increment position counter for next voxel (only if current position is 0)
|
||
if (position == 0) {
|
||
// Increment in x-first, then y, then z order (matches typical voxel traversal)
|
||
localX++;
|
||
if (localX >= 32) {
|
||
localX = 0;
|
||
localY++;
|
||
if (localY >= 32) {
|
||
localY = 0;
|
||
localZ++;
|
||
// If we've gone beyond the chunk bounds, wrap around
|
||
if (localZ >= 32) {
|
||
localZ = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (std::exception& e) {
|
||
std::cerr << "Error processing snapshot " << snapshotIndex << ": " << e.what() << std::endl;
|
||
}
|
||
}
|
||
|
||
// Count the total number of visible voxels (color != 0)
|
||
size_t totalVisibleVoxels = 0;
|
||
for (const auto& [chunkID, voxelMap] : finalVoxelState) {
|
||
for (const auto& [pos, color] : voxelMap) {
|
||
if (color != 0) {
|
||
totalVisibleVoxels++;
|
||
}
|
||
}
|
||
}
|
||
|
||
std::cout << "\n===== FINAL VISIBLE VOXEL COUNT: " << totalVisibleVoxels << " =====" << std::endl;
|
||
|
||
// Print snapshot history for each chunk
|
||
std::cout << "\nSNAPSHOT HISTORY BY CHUNK:" << std::endl;
|
||
for (const auto& [chunkID, indices] : snapshotIndicesByChunk) {
|
||
auto [chunkX, chunkY, chunkZ] = decodeMortonChunkID(chunkID);
|
||
std::cout << " ChunkID " << chunkID << " (at chunk position "
|
||
<< chunkX << "," << chunkY << "," << chunkZ
|
||
<< ") appears in " << indices.size() << " snapshots: ";
|
||
|
||
for (size_t i = 0; i < std::min(indices.size(), size_t(5)); i++) {
|
||
if (i > 0) std::cout << ", ";
|
||
std::cout << indices[i];
|
||
}
|
||
|
||
if (indices.size() > 5) {
|
||
std::cout << ", ... (and " << (indices.size() - 5) << " more)";
|
||
}
|
||
std::cout << std::endl;
|
||
}
|
||
|
||
// Print visible voxels (limiting to first 100 to avoid excessive output)
|
||
std::cout << "\n===== VISIBLE VOXELS (first 100): =====" << std::endl;
|
||
size_t voxelsPrinted = 0;
|
||
|
||
std::vector<std::tuple<int, int, int, uint8_t, int, int, int>> allVisibleVoxels;
|
||
|
||
// First collect all visible voxels
|
||
for (const auto& [chunkID, voxelMap] : finalVoxelState) {
|
||
auto [chunkX, chunkY, chunkZ] = decodeMortonChunkID(chunkID);
|
||
int worldOriginX = chunkX * 32;
|
||
int worldOriginY = chunkY * 32;
|
||
int worldOriginZ = chunkZ * 32;
|
||
|
||
for (const auto& [pos, color] : voxelMap) {
|
||
if (color != 0) { // Only include visible voxels
|
||
auto [worldX, worldY, worldZ] = pos;
|
||
int localX = worldX - worldOriginX;
|
||
int localY = worldY - worldOriginY;
|
||
int localZ = worldZ - worldOriginZ;
|
||
|
||
allVisibleVoxels.emplace_back(worldX, worldY, worldZ, color, chunkX, chunkY, chunkZ);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sort voxels by world coordinates to make the output more organized
|
||
std::sort(allVisibleVoxels.begin(), allVisibleVoxels.end(),
|
||
[](const auto& a, const auto& b) {
|
||
const auto& [aX, aY, aZ, aColor, aChunkX, aChunkY, aChunkZ] = a;
|
||
const auto& [bX, bY, bZ, bColor, bChunkX, bChunkY, bChunkZ] = b;
|
||
|
||
if (aX != bX) return aX < bX;
|
||
if (aY != bY) return aY < bY;
|
||
return aZ < bZ;
|
||
});
|
||
|
||
// Print sorted voxels
|
||
for (const auto& voxel : allVisibleVoxels) {
|
||
auto [worldX, worldY, worldZ, color, chunkX, chunkY, chunkZ] = voxel;
|
||
int worldOriginX = chunkX * 32;
|
||
int worldOriginY = chunkY * 32;
|
||
int worldOriginZ = chunkZ * 32;
|
||
int localX = worldX - worldOriginX;
|
||
int localY = worldY - worldOriginY;
|
||
int localZ = worldZ - worldOriginZ;
|
||
|
||
std::cout << " World(" << worldX << "," << worldY << "," << worldZ
|
||
<< "), Chunk(" << chunkX << "," << chunkY << "," << chunkZ
|
||
<< "), Local(" << localX << "," << localY << "," << localZ
|
||
<< "), Color=" << (int)color << std::endl;
|
||
|
||
voxelsPrinted++;
|
||
if (voxelsPrinted >= 100) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (totalVisibleVoxels > 100) {
|
||
std::cout << " ... and " << (totalVisibleVoxels - 100) << " more voxels" << std::endl;
|
||
}
|
||
}
|
||
|
||
// Add the following function before the main function
|
||
void justPrintVoxelCoordinates(const std::string& filePath) {
|
||
// Disable cout temporarily to prevent it from printing tool output
|
||
std::streambuf* oldCoutStreamBuf = std::cout.rdbuf();
|
||
std::ostringstream strCout;
|
||
std::cout.rdbuf(strCout.rdbuf());
|
||
|
||
// Process the file quietly
|
||
boost::any plistData;
|
||
try {
|
||
Plist::readPlist(filePath.c_str(), plistData);
|
||
} catch (std::exception& e) {
|
||
// Restore cout before reporting error
|
||
std::cout.rdbuf(oldCoutStreamBuf);
|
||
std::cerr << "Error: " << e.what() << std::endl;
|
||
return;
|
||
}
|
||
|
||
// Get snapshots array
|
||
Plist::dictionary_type rootDict = boost::any_cast<Plist::dictionary_type>(plistData);
|
||
Plist::array_type snapshotsArray = boost::any_cast<Plist::array_type>(rootDict.at("snapshots"));
|
||
|
||
// Map to store final voxel state
|
||
std::map<int64_t, std::map<std::tuple<int, int, int>, uint8_t>> voxelMap;
|
||
|
||
// Process all snapshots
|
||
for (size_t i = 0; i < snapshotsArray.size(); i++) {
|
||
try {
|
||
Plist::dictionary_type snapshot = boost::any_cast<Plist::dictionary_type>(snapshotsArray[i]);
|
||
|
||
if (snapshot.find("s") == snapshot.end()) continue;
|
||
Plist::dictionary_type snapData = boost::any_cast<Plist::dictionary_type>(snapshot.at("s"));
|
||
|
||
if (snapData.find("id") == snapData.end()) continue;
|
||
Plist::dictionary_type idDict = boost::any_cast<Plist::dictionary_type>(snapData.at("id"));
|
||
|
||
if (idDict.find("c") == idDict.end()) continue;
|
||
int64_t chunkID = boost::any_cast<int64_t>(idDict.at("c"));
|
||
|
||
if (snapData.find("ds") == snapData.end()) continue;
|
||
Plist::data_type voxelData = boost::any_cast<Plist::data_type>(snapData.at("ds"));
|
||
|
||
// Get chunk coordinates and world origin
|
||
auto [chunkX, chunkY, chunkZ] = decodeMortonChunkID(chunkID);
|
||
int worldOriginX = chunkX * 32;
|
||
int worldOriginY = chunkY * 32;
|
||
int worldOriginZ = chunkZ * 32;
|
||
|
||
// Process voxels
|
||
int localX = 0, localY = 0, localZ = 0;
|
||
for (size_t j = 0; j < voxelData.size(); j += 2) {
|
||
if (j + 1 >= voxelData.size()) continue;
|
||
|
||
uint8_t position = static_cast<uint8_t>(voxelData[j]);
|
||
uint8_t color = static_cast<uint8_t>(voxelData[j + 1]);
|
||
|
||
if (position != 0) {
|
||
uint8_t mortonX = 0, mortonY = 0, mortonZ = 0;
|
||
decodeMorton(position, mortonX, mortonY, mortonZ);
|
||
localX = mortonX;
|
||
localY = mortonY;
|
||
localZ = mortonZ;
|
||
}
|
||
|
||
int worldX = worldOriginX + localX;
|
||
int worldY = worldOriginY + localY;
|
||
int worldZ = worldOriginZ + localZ;
|
||
|
||
voxelMap[chunkID][std::make_tuple(worldX, worldY, worldZ)] = color;
|
||
|
||
if (position == 0) {
|
||
localX++;
|
||
if (localX >= 32) {
|
||
localX = 0;
|
||
localY++;
|
||
if (localY >= 32) {
|
||
localY = 0;
|
||
localZ++;
|
||
if (localZ >= 32) {
|
||
localZ = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (std::exception& e) {
|
||
// Just continue to next snapshot
|
||
}
|
||
}
|
||
|
||
// Collect visible voxels
|
||
std::vector<std::tuple<int, int, int, uint8_t>> visible;
|
||
for (const auto& [_, chunk] : voxelMap) {
|
||
for (const auto& [pos, color] : chunk) {
|
||
if (color != 0) {
|
||
auto [x, y, z] = pos;
|
||
visible.emplace_back(x, y, z, color);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sort by coordinates
|
||
std::sort(visible.begin(), visible.end());
|
||
|
||
// Restore cout before printing coordinates
|
||
std::cout.rdbuf(oldCoutStreamBuf);
|
||
|
||
// Print only coordinates
|
||
std::cout << "START VOXEL COORDINATES" << std::endl;
|
||
for (const auto& [x, y, z, color] : visible) {
|
||
std::cout << x << "," << y << "," << z << "," << (int)color << std::endl;
|
||
}
|
||
std::cout << "END VOXEL COORDINATES" << std::endl;
|
||
std::cout << "TOTAL COORDINATES: " << visible.size() << std::endl;
|
||
}
|
||
|
||
|
||
// Main function for the program
|
||
// This is where execution begins
|
||
// The Args object contains command-line arguments
|
||
int DL_main(dl::Args& args)
|
||
{
|
||
// Variable to store the input file path
|
||
std::string filePath;
|
||
std::string lzfseInputPath;
|
||
std::string plistOutputPath;
|
||
bool verbose = false;
|
||
|
||
// Define command-line arguments that the program accepts
|
||
args.add("vi", "voxin", "", "Input .vox file");
|
||
args.add("tp", "thirdparty", "", "prints third party licenses");
|
||
args.add("li", "licenseinfo", "", "prints license info");
|
||
args.add("lz", "lzfsein", "", "Input LZFSE compressed file");
|
||
args.add("po", "plistout", "", "Output plist file path");
|
||
args.add("pl", "plist", "", "Input plist file to parse directly");
|
||
args.add("v", "verbose", "", "Enable verbose output");
|
||
|
||
// Handle special command-line requests
|
||
|
||
// If --version was requested, print version and exit
|
||
if (args.versionReqested())
|
||
{
|
||
printf("%s", dl::bellaSdkVersion().toString().buf());
|
||
return 0;
|
||
}
|
||
|
||
// If --help was requested, print help and exit
|
||
if (args.helpRequested())
|
||
{
|
||
printf("%s", args.help("vmax2bella", dl::fs::exePath(), "Hello\n").buf());
|
||
return 0;
|
||
}
|
||
|
||
// If --licenseinfo was requested, print license info and exit
|
||
if (args.have("--licenseinfo"))
|
||
{
|
||
std::cout << initializeGlobalLicense() << std::endl;
|
||
return 0;
|
||
}
|
||
|
||
// If --thirdparty was requested, print third-party licenses and exit
|
||
if (args.have("--thirdparty"))
|
||
{
|
||
std::cout << initializeGlobalThirdPartyLicences() << std::endl;
|
||
return 0;
|
||
}
|
||
|
||
// Check for verbose flag
|
||
if (args.have("--verbose"))
|
||
{
|
||
verbose = true;
|
||
}
|
||
|
||
// Process LZFSE compressed files
|
||
if (args.have("--lzfsein")) {
|
||
lzfseInputPath = args.value("--lzfsein").buf();
|
||
|
||
if (args.have("--plistout")) {
|
||
plistOutputPath = args.value("--plistout").buf();
|
||
} else {
|
||
// Default output path: change .vmax to .plist
|
||
plistOutputPath = lzfseInputPath + ".plist";
|
||
}
|
||
|
||
std::cout << "Decompressing LZFSE file: " << lzfseInputPath << " to " << plistOutputPath << std::endl;
|
||
if (!decompressLzfseToPlist(lzfseInputPath, plistOutputPath)) {
|
||
std::cerr << "Failed to decompress LZFSE file" << std::endl;
|
||
return 1;
|
||
}
|
||
|
||
// Parse the plist file
|
||
std::cout << "Parsing plist file..." << std::endl;
|
||
if (!parsePlistVoxelData(plistOutputPath, verbose)) {
|
||
std::cerr << "Failed to parse plist file" << std::endl;
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
// Process direct plist files
|
||
if (args.have("--plist")) {
|
||
std::string plistPath = args.value("--plist").buf();
|
||
|
||
// Add a check for the --just-coords flag first
|
||
if (args.have("--just-coords")) {
|
||
justPrintVoxelCoordinates(plistPath);
|
||
return 0;
|
||
}
|
||
|
||
// Only print this message for other options
|
||
printf("Reading directly from plist file: %s\n", plistPath.c_str());
|
||
|
||
// Add a new simple print option for voxel coordinates
|
||
if (args.have("--print-coords")) {
|
||
printSimpleVoxelCoordinates(plistPath);
|
||
return 0;
|
||
}
|
||
|
||
// Add a check for the new --print-voxels flag
|
||
if (args.have("--print-voxels")) {
|
||
printAllVisibleVoxels(plistPath);
|
||
return 0;
|
||
}
|
||
|
||
if (!parsePlistVoxelData(plistPath, verbose)) {
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// If no input file was specified, print error and exit
|
||
std::cout << "No input file specified. Use -pl for plist input or -lz for compressed LZFSE input." << std::endl;
|
||
return 1;
|
||
}
|
||
|
||
// Function that returns the license text for this program
|
||
std::string initializeGlobalLicense()
|
||
{
|
||
// R"(...)" is a C++ raw string literal - allows multi-line strings with preserved formatting
|
||
return R"(
|
||
vmax2bella
|
||
|
||
Copyright (c) 2025 Harvey Fong
|
||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
of this software and associated documentation files (the "Software"), to deal
|
||
in the Software without restriction, including without limitation the rights
|
||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
copies of the Software, and to permit persons to whom the Software is
|
||
furnished to do so, subject to the following conditions:
|
||
|
||
The above copyright notice and this permission notice shall be included in all
|
||
copies or substantial portions of the Software.
|
||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
SOFTWARE.)";
|
||
}
|
||
|
||
// Function that returns third-party license text
|
||
std::string initializeGlobalThirdPartyLicences()
|
||
{
|
||
return R"(
|
||
Bella SDK (Software Development Kit)
|
||
|
||
Copyright Diffuse Logic SCP, all rights reserved.
|
||
|
||
Permission is hereby granted to any person obtaining a copy of this software
|
||
(the "Software"), to use, copy, publish, distribute, sublicense, and/or sell
|
||
copies of the Software.
|
||
|
||
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY. ALL
|
||
IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF MERCHANTABILITY
|
||
ARE HEREBY DISCLAIMED.)
|
||
|
||
===
|
||
|
||
lzfse
|
||
|
||
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
|
||
|
||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||
|
||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||
|
||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
|
||
in the documentation and/or other materials provided with the distribution.
|
||
|
||
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
|
||
from this software without specific prior written permission.
|
||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
)";
|
||
}
|
||
|
||
// Decode a Morton encoded chunk ID into x, y, z coordinates
|
||
std::tuple<int, int, int> decodeMortonChunkID(int64_t mortonID) {
|
||
// Extract the xyz coordinates from the Morton encoded chunk ID
|
||
// Implementation based on VoxelMax's Swift decoding
|
||
int x = 0, y = 0, z = 0;
|
||
|
||
// Extract each dimension by filtering bits at positions 0, 3, 6, etc. for x
|
||
// positions 1, 4, 7, etc. for y, and positions 2, 5, 8, etc. for z
|
||
for (int i = 0; i < 24; i++) { // 24 bits (8 per dimension for chunks)
|
||
int bit = (mortonID >> i) & 1;
|
||
|
||
if (i % 3 == 0) {
|
||
x |= (bit << (i / 3));
|
||
} else if (i % 3 == 1) {
|
||
y |= (bit << (i / 3));
|
||
} else { // i % 3 == 2
|
||
z |= (bit << (i / 3));
|
||
}
|
||
}
|
||
|
||
return {x, y, z};
|
||
}
|
||
|
||
// Function to print just the coordinates of visible voxels
|
||
void printSimpleVoxelCoordinates(const std::string& filePath) {
|
||
boost::any plistData;
|
||
try {
|
||
Plist::readPlist(filePath.c_str(), plistData);
|
||
} catch (std::exception& e) {
|
||
std::cerr << "Error reading plist file: " << e.what() << std::endl;
|
||
return;
|
||
}
|
||
|
||
// Get root dictionary
|
||
Plist::dictionary_type rootDict = boost::any_cast<Plist::dictionary_type>(plistData);
|
||
if (rootDict.find("snapshots") == rootDict.end()) {
|
||
std::cerr << "Error: No 'snapshots' key found in plist" << std::endl;
|
||
return;
|
||
}
|
||
|
||
// Get the snapshots array
|
||
Plist::array_type snapshotsArray = boost::any_cast<Plist::array_type>(rootDict.at("snapshots"));
|
||
|
||
// Map to store final voxel state: key is chunkID, value is a map of voxel positions to colors
|
||
std::map<int64_t, std::map<std::tuple<int, int, int>, uint8_t>> finalVoxelState;
|
||
|
||
std::cout << "Processing " << snapshotsArray.size() << " snapshots..." << std::endl;
|
||
|
||
// Process all snapshots in order to build the final voxel state
|
||
for (size_t snapshotIndex = 0; snapshotIndex < snapshotsArray.size(); snapshotIndex++) {
|
||
try {
|
||
Plist::dictionary_type snapshot = boost::any_cast<Plist::dictionary_type>(snapshotsArray[snapshotIndex]);
|
||
|
||
// Skip if no 's' key
|
||
if (snapshot.find("s") == snapshot.end()) continue;
|
||
|
||
Plist::dictionary_type snapshotData = boost::any_cast<Plist::dictionary_type>(snapshot.at("s"));
|
||
|
||
// Skip if no 'id' dictionary
|
||
if (snapshotData.find("id") == snapshotData.end()) continue;
|
||
|
||
Plist::dictionary_type idDict = boost::any_cast<Plist::dictionary_type>(snapshotData.at("id"));
|
||
|
||
// Skip if no chunk ID
|
||
if (idDict.find("c") == idDict.end()) continue;
|
||
|
||
// Get chunk ID
|
||
int64_t chunkID = boost::any_cast<int64_t>(idDict.at("c"));
|
||
|
||
// Skip if no voxel data
|
||
if (snapshotData.find("ds") == snapshotData.end()) continue;
|
||
|
||
// Get world origin coordinates for this chunk
|
||
auto [chunkX, chunkY, chunkZ] = decodeMortonChunkID(chunkID);
|
||
|
||
// Scale by chunk size (32)
|
||
int worldOriginX = chunkX * 32;
|
||
int worldOriginY = chunkY * 32;
|
||
int worldOriginZ = chunkZ * 32;
|
||
|
||
// Get voxel data
|
||
Plist::data_type voxelData = boost::any_cast<Plist::data_type>(snapshotData.at("ds"));
|
||
|
||
// Process voxel data using hybrid encoding
|
||
int localX = 0, localY = 0, localZ = 0; // Current position counter within chunk
|
||
|
||
// Process each voxel pair (position, color)
|
||
for (size_t i = 0; i < voxelData.size(); i += 2) {
|
||
if (i + 1 < voxelData.size()) {
|
||
uint8_t position = static_cast<uint8_t>(voxelData[i]);
|
||
uint8_t color = static_cast<uint8_t>(voxelData[i + 1]);
|
||
|
||
if (position != 0) {
|
||
// Non-zero position: Jump to Morton-encoded position
|
||
uint8_t mortonX = 0, mortonY = 0, mortonZ = 0;
|
||
decodeMorton(position, mortonX, mortonY, mortonZ);
|
||
localX = mortonX;
|
||
localY = mortonY;
|
||
localZ = mortonZ;
|
||
}
|
||
|
||
// Calculate world coordinates
|
||
int worldX = worldOriginX + localX;
|
||
int worldY = worldOriginY + localY;
|
||
int worldZ = worldOriginZ + localZ;
|
||
|
||
// Update voxel state for this position
|
||
finalVoxelState[chunkID][std::make_tuple(worldX, worldY, worldZ)] = color;
|
||
|
||
// Increment position counter for next voxel (only if current position is 0)
|
||
if (position == 0) {
|
||
// Increment in x-first, then y, then z order (matches typical voxel traversal)
|
||
localX++;
|
||
if (localX >= 32) {
|
||
localX = 0;
|
||
localY++;
|
||
if (localY >= 32) {
|
||
localY = 0;
|
||
localZ++;
|
||
// If we've gone beyond the chunk bounds, wrap around
|
||
if (localZ >= 32) {
|
||
localZ = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (std::exception& e) {
|
||
std::cerr << "Error processing snapshot " << snapshotIndex << ": " << e.what() << std::endl;
|
||
}
|
||
}
|
||
|
||
// Count and print visible voxels
|
||
std::vector<std::tuple<int, int, int, uint8_t>> visibleVoxels;
|
||
|
||
for (const auto& [chunkID, voxelMap] : finalVoxelState) {
|
||
for (const auto& [pos, color] : voxelMap) {
|
||
if (color != 0) { // Only include visible voxels
|
||
auto [worldX, worldY, worldZ] = pos;
|
||
visibleVoxels.emplace_back(worldX, worldY, worldZ, color);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Sort voxels by coordinates
|
||
std::sort(visibleVoxels.begin(), visibleVoxels.end(),
|
||
[](const auto& a, const auto& b) {
|
||
const auto& [aX, aY, aZ, aColor] = a;
|
||
const auto& [bX, bY, bZ, bColor] = b;
|
||
|
||
if (aX != bX) return aX < bX;
|
||
if (aY != bY) return aY < bY;
|
||
return aZ < bZ;
|
||
});
|
||
|
||
// Print the results
|
||
std::cout << "Total visible voxels: " << visibleVoxels.size() << std::endl;
|
||
std::cout << "VOXEL COORDINATES (x,y,z,color):" << std::endl;
|
||
|
||
for (const auto& [x, y, z, color] : visibleVoxels) {
|
||
std::cout << x << "," << y << "," << z << "," << (int)color << std::endl;
|
||
}
|
||
|
||
// Confirm that printing is complete
|
||
std::cout << "End of voxel coordinate listing" << std::endl;
|
||
}
|