update to 25.3.0 sdk, switch from scene to engine sdk

This commit is contained in:
Harvey Fong 2025-07-28 08:06:15 -06:00
parent beeb9a709d
commit 86d621515a
13 changed files with 37 additions and 5648 deletions

View File

@ -36,18 +36,11 @@ Load **bear.bsz** into [bella_gui](https://bellarender.com/builds) for rendering
- [TODO] convert chunk camera for anim
# Build
Download SDK for your OS and move **bella_scene_sdk** into your **workdir**. On Windows rename unzipped folder by removing version ie bella_engine_sdk-24.6.0 -> bella_scene_sdk
https://bellarender.com/builds/
```
workdir/
├── bella_scene_sdk/
├── bella_engine_sdk/
├── libplist/
├── lzfse/
├── opengametools/
@ -55,9 +48,16 @@ workdir/
```
# MacOS
Install Cmake to /Applications
```
curl -LO https://github.com/Kitware/CMake/releases/download/v3.31.6/cmake-3.31.6-macos-universal.dmg
open cmake-3.31.6-macos-universal.dmg
```
```
mkdir workdir
curl -LO https://downloads.bellarender.com/bella_scene_sdk-25.3.0-macos.zip
unzip bella_scene_sdk-25.3.0-macos.zip
git clone https://github.com/lzfse/lzfse
mkdir -p lzfse/build
cd lzfse/build
@ -85,13 +85,17 @@ cd vmax2bella
make all -j4
install_name_tool -change ../lzfse/build/liblzfse.dylib @rpath/liblzfse.dylib bin/Darwin/release/vmax2bella
install_name_tool -change /usr/local/lib/libplist-2.0.4.dylib @rpath/libplist-2.0.4.dylib bin/Darwin/release/vmax2bella
git clone https://github.com/oomer/vmax2bella.git
cd vmax2bella
make all -j4
```
# Linux [NOT READY]
## Linux
```
mkdir workdir
curl -LO https://downloads.bellarender.com/bella_engine_sdk-25.3.0-linux.tar.gz
tar -xvf bella_engine_sdk-25.3.0-linux.tar.gz
git clone https://github.com/lzfse/lzfse
mkdir lzfse/build
cd lzfse/build
@ -107,15 +111,22 @@ git clone https://github.com/jpaver/opengametools.git
git clone https://github.com/oomer/oom.git
git clone https://github.com/oomer/vmax2bella.git
cd vmax2bella
make
make all -j4
```
# Windows
- Install Visual Studio Community 2022
- Add Desktop development with C++ workload
- Launch x64 Native tools Command Prompt for VS2022
## Windows (win10)
- [optioanl] Install https://git-scm.com
- Download Visual Studio Community Edition 2022
- Run VisualStudioSetup.exe
- Workload = [x] Desktop development with C++
- Individual components = [x] Git For Windows
#### x64 Developer console
```
mkdir workdir
curl -LO https://downloads.bellarender.com/bella_engine_sdk-25.3.0-win32.zip
tar -xf bella_engine_sdk-25.3.0-win32.zip
git clone https://github.com/lzfse/lzfse
mkdir -p lzfse/build
cd lzfse/build

586
debug.cpp
View File

@ -1,586 +0,0 @@
#include "debug.h"
/**
* Decodes a voxel's layercolor and color from the ds data stream
*
* @param dsData The raw ds data stream containing layer-color pairs
* @return A vector of Voxel structures with explicit coordinates and colors
*/
std::vector<newVoxel> decodeVoxels2(const std::vector<uint8_t>& dsData, int mortonOffset) {
std::vector<newVoxel> voxels;
for (int i = 0; i < dsData.size() - 1; i += 2) {
dsVoxel _vxVoxel; // VoxelMax data
_vxVoxel.layer = static_cast<int>(dsData[i]);
_vxVoxel.color = static_cast<uint8_t>(dsData[i + 1]);
uint32_t dx, dy, dz;
decodeMorton3DOptimized(i/2 + mortonOffset, dx, dy, dz); // index IS the morton code
if (_vxVoxel.color != 0) {
newVoxel voxel = {dx, dy, dz, _vxVoxel.color};
voxels.push_back(voxel);
}
}
return voxels;
}
/**
* Print a plist node's contents recursively.
* This function takes a plist node and prints its contents in a human-readable format.
* It handles all types of plist nodes (dictionaries, arrays, strings, etc.) by using
* recursion to traverse the entire plist structure.
*
* @param node The plist node to print (plist_t is a pointer to the internal plist structure)
* @param indent The current indentation level (defaults to 0 for the root node)
*/
void printPlistNode(const plist_t& node, int indent ) {
// Early return if node is null (safety check)
if (!node) return;
// Create a string with 'indent * 2' spaces for proper indentation
// This helps visualize the hierarchy of nested structures
std::string indentStr(indent * 2, ' ');
// Get the type of the current node (dictionary, array, string, etc.)
plist_type nodeType = plist_get_node_type(node);
// Handle each type of node differently
switch (nodeType) {
case PLIST_DICT: {
std::cout << indentStr << "Dictionary:" << std::endl;
// Create an iterator for the dictionary
// nullptr is passed as initial value; the iterator will be allocated by plist_dict_new_iter
plist_dict_iter it = nullptr;
plist_dict_new_iter(node, &it);
// Variables to store the current key-value pair
char* key = nullptr; // Will hold the dictionary key (needs to be freed)
plist_t value = nullptr; // Will hold the value node
// Iterate through all items in the dictionary
while (true) {
// Get the next key-value pair
plist_dict_next_item(node, it, &key, &value);
// Break if we've reached the end of the dictionary
if (!key || !value) break;
// Print the key and recursively print its value
std::cout << indentStr << " " << key << ":" << std::endl;
printPlistNode(value, indent + 2); // Increase indent for nested values
// Free the key string (allocated by plist_dict_next_item)
free(key);
key = nullptr; // Set to nullptr to avoid double-free
}
// Free the iterator when done
free(it);
break;
}
case PLIST_ARRAY: {
std::cout << indentStr << "Array:" << std::endl;
uint32_t size = plist_array_get_size(node);
for (uint32_t i = 0; i < size; i++) {
plist_t item = plist_array_get_item(node, i);
std::cout << indentStr << " [" << i << "]:" << std::endl;
printPlistNode(item, indent + 2);
}
break;
}
case PLIST_STRING: {
char* str = nullptr;
plist_get_string_val(node, &str);
std::cout << indentStr << "String: " << (str ? str : "(null)") << std::endl;
free(str);
break;
}
case PLIST_BOOLEAN: {
uint8_t bval;
plist_get_bool_val(node, &bval);
std::cout << indentStr << "Boolean: " << (bval ? "true" : "false") << std::endl;
break;
}
case PLIST_UINT: {
uint64_t val;
plist_get_uint_val(node, &val);
std::cout << indentStr << "Integer: " << val << std::endl;
break;
}
case PLIST_REAL: {
double val;
plist_get_real_val(node, &val);
std::cout << indentStr << "Real: " << val << std::endl;
break;
}
case PLIST_DATE: {
int32_t sec = 0;
int32_t usec = 0;
plist_get_date_val(node, &sec, &usec);
std::cout << indentStr << "Date: " << sec << "." << usec << std::endl;
break;
}
case PLIST_DATA: {
char* data = nullptr;
uint64_t length = 0;
plist_get_data_val(node, &data, &length);
std::cout << indentStr << "Data: <" << length << " bytes>" << std::endl;
free(data);
break;
}
default:
std::cout << indentStr << "Unknown type" << std::endl;
}
}
/**
* New visualization function that definitely uses the correct z-plane
*
* @param voxels The vector of decoded voxels
* @param zPlane The z-coordinate of the plane to visualize
* @param size The size of the grid (default: 32x32)
*/
void visualizeZPlaneFixed(const std::vector<newVoxel>& voxels, int zPlane, int size ) {
// Bounds checking
const int MIN_Z = 0;
const int MAX_Z = 31;
if (zPlane < MIN_Z || zPlane > MAX_Z) {
std::cout << "WARNING: z-plane value " << zPlane << " is out of bounds. Valid range is " << MIN_Z << "-" << MAX_Z << ". Using z=0 instead." << std::endl;
zPlane = 0;
}
std::cout << "Visualizing z-plane: " << zPlane << std::endl;
// Create a 2D grid for visualization
std::vector<std::vector<char>> grid(size, std::vector<char>(size, ' '));
// Count voxels for statistics
int totalVoxels = voxels.size();
int voxelsAtRequestedZ = 0;
int coloredVoxels = 0;
int clearVoxels = 0;
// Loop 1: Debug output for the first few matching voxels
int debugCount = 0;
for (const auto& voxel : voxels) {
if (voxel.z == zPlane) {
voxelsAtRequestedZ++;
// Update the grid and count color types
if (voxel.x >= 0 && voxel.x < size && voxel.y >= 0 && voxel.y < size) {
if (voxel.color == 0x00) {
grid[voxel.y][voxel.x] = '.'; // Clear voxel (0x00)
clearVoxels++;
} else if (voxel.color == 0x25) {
grid[voxel.y][voxel.x] = '#'; // Colored voxel (0x25)
coloredVoxels++;
} else {
grid[voxel.y][voxel.x] = 'X'; // Other color
coloredVoxels++;
}
}
}
}
// Print statistics
std::cout << "\nVisualization Statistics:" << std::endl;
std::cout << "- Total voxels in data: " << totalVoxels << std::endl;
std::cout << "- Voxels at z=" << zPlane << ": " << voxelsAtRequestedZ << std::endl;
std::cout << "- Colored voxels: " << coloredVoxels << " (shown as '#' or 'X')" << std::endl;
std::cout << "- Clear voxels: " << clearVoxels << " (shown as '.')" << std::endl;
// If no matching voxels were found, print a message and return
if (voxelsAtRequestedZ == 0) {
std::cout << "\n*** NO VOXELS FOUND AT Z=" << zPlane << " ***\n" << std::endl;
return;
}
// Print legend
std::cout << "\nLegend:" << std::endl;
std::cout << "- '#': Color 0x25" << std::endl;
std::cout << "- '.': Clear (0x00)" << std::endl;
std::cout << "- 'X': Other colors" << std::endl;
std::cout << "- ' ': No voxel present" << std::endl;
std::cout << "- Each 8x4 section represents one subchunk" << std::endl;
// Print x-axis header
std::cout << "\n ";
for (int x = 0; x < size; x++) {
if (x % 8 == 0) {
std::cout << "|"; // Mark subchunk boundaries
} else {
std::cout << x % 10; // Print digit for readability
}
}
std::cout << std::endl;
// Print divider line
std::cout << " ";
for (int x = 0; x < size; x++) {
if (x % 8 == 0) {
std::cout << "+"; // Mark subchunk corners
} else {
std::cout << "-";
}
}
std::cout << std::endl;
// Print grid with y-axis labels and subchunk markers
for (int y = 0; y < size; y++) {
std::cout << std::setw(2) << y << " ";
// Mark subchunk boundaries on y-axis
if (y % 4 == 0) {
std::cout << "+";
} else {
std::cout << "|";
}
// Print the actual voxel data for this row
for (int x = 0; x < size; x++) {
std::cout << grid[y][x];
}
std::cout << std::endl;
}
std::cout << "\n===============================================\n";
}
/**
* Examines a specific array element at the given index from a plist file.
* This function allows inspection of individual chunks/snapshots in the data.
*
* @param plistFilePath Path to the plist file
* @param index The index of the array element to examine
* @param arrayPath The path to the array in the plist structure
* @return true if successful, false if any errors occurred
*/
bool examinePlistNode(const plist_t& root_node, int snapshotIndex, int zIndex, const std::string& arrayPath) {
std::cout << "Examining Plist array at snapshot " << snapshotIndex << " zIndex " << zIndex << std::endl;
if (!root_node) {
std::cerr << "Failed to process Plist data" << std::endl;
return false;
}
plist_t current_node = root_node;
// if the array path contains slashes, we need to navigate through the structure
std::string path = arrayPath;
size_t pos = 0;
std::string token;
while ((pos = path.find('/')) != std::string::npos) {
token = path.substr(0, pos);
path.erase(0, pos + 1);
// current node must be a dictionary
if (plist_get_node_type(current_node) != PLIST_DICT) {
std::cerr << "error: expected dictionary at path component '" << token << "'" << std::endl;
//plist_free(root_node);
return false;
}
// get the next node in the path
current_node = plist_dict_get_item(current_node, token.c_str());
if (!current_node) {
std::cerr << "error: could not find key '" << token << "' in dictionary" << std::endl;
//plist_free(root_node);
return false;
}
}
// Now path contains the final key name
if (!path.empty() && plist_get_node_type(current_node) == PLIST_DICT) {
current_node = plist_dict_get_item(current_node, path.c_str());
if (!current_node) {
std::cerr << "Error: Could not find key '" << path << "' in dictionary" << std::endl;
return false;
}
}
// Check if we found an array
if (plist_get_node_type(current_node) != PLIST_ARRAY) {
std::cerr << "Error: '" << "arrayPath" << "' is not an array" << std::endl;
return false;
}
// Get Plist node array size
uint32_t arraySize = plist_array_get_size(current_node);
if (snapshotIndex < 0 || snapshotIndex >= static_cast<int>(arraySize)) {
std::cerr << "Error: Index " << snapshotIndex << " is out of range (array size: " << arraySize << ")" << std::endl;
return false;
}
// Get the Plist node at the specified index
plist_t element = plist_array_get_item(current_node, snapshotIndex);
if (!element) {
std::cerr << "Error: Could not get Plist node at snapshot " << snapshotIndex << std::endl;
return false;
}
std::cout << "Array size: " << arraySize << std::endl;
std::cout << "Plist node details at snapshot " << snapshotIndex << " zIndex " << zIndex << ":" << std::endl;
printPlistNode(element);
debugSnapshots(element, snapshotIndex, zIndex);
return true;
}
/**
* Handles 's' dictionary in a Plist node holding 32x32x32 chunks of voxel data.
*
* @param element The Plist node to examine
* @return true if successful, false if any errors occurred
*/
bool debugSnapshots(plist_t element, int snapshotIndex, int zIndex) {
std::cout << "Debugging snapshots" << std::endl;
// Special handling for 's' dictionaries
if (plist_get_node_type(element) == PLIST_DICT) {
plist_t sNode = plist_dict_get_item(element, "s");
if (sNode) {
// Look for specific keys of interest in the 's' dictionary
if (plist_get_node_type(sNode) == PLIST_DICT) {
// Check for 'ds' (data stream) in the 's' dictionary
plist_t dsNode = plist_dict_get_item(sNode, "ds");
if (dsNode && plist_get_node_type(dsNode) == PLIST_DATA) {
char* data = nullptr;
uint64_t length = 0;
plist_get_data_val(dsNode, &data, &length);
std::cout << "\nDetailed analysis of 'ds' data stream (size: " << length << " bytes):" << std::endl;
// Detailed analysis of the data stream
if (length > 0 && data) {
// Display as hex bytes - increased to 384 bytes
std::cout << "First 384 bytes (hex):" << std::endl;
size_t bytesToShow = std::min(static_cast<size_t>(384), static_cast<size_t>(length));
for (size_t i = 0; i < bytesToShow; i++) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(static_cast<uint8_t>(data[i])) << " ";
if ((i + 1) % 16 == 0) std::cout << std::endl;
}
std::cout << std::dec << std::endl;
// If data appears to be position-color pairs (as in voxel data)
if (length % 2 == 0) {
size_t numPairs = length / 2;
std::cout << "Data appears to contain " << numPairs << " position-color pairs" << std::endl;
// Check if all positions are 0 (common for optimized voxel data)
bool allPositionsZero = true;
for (size_t i = 0; i < std::min(numPairs, static_cast<size_t>(100)); i++) {
if (static_cast<uint8_t>(data[i * 2]) != 0) {
allPositionsZero = false;
break;
}
}
if (allPositionsZero) {
// Show only color values for more compact analysis
std::cout << "\nAll position values are 0. Showing only color values:" << std::endl;
std::cout << "First 384 color values (hex):" << std::endl;
size_t colorsToShow = std::min(static_cast<size_t>(384), numPairs);
for (size_t i = 0; i < colorsToShow; i++) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(static_cast<uint8_t>(data[i * 2 + 1])) << " ";
if ((i + 1) % 16 == 0) std::cout << std::endl;
}
std::cout << std::dec << std::endl;
} else {
// Show position-color pairs if positions vary
std::cout << "\nFirst 10 position-color pairs:" << std::endl;
std::cout << "Index | Position | Color" << std::endl;
std::cout << "------|----------|------" << std::endl;
size_t pairsToShow = std::min(static_cast<size_t>(10), numPairs);
for (size_t i = 0; i < pairsToShow; i++) {
uint8_t position = static_cast<uint8_t>(data[i * 2]);
uint8_t color = static_cast<uint8_t>(data[i * 2 + 1]);
std::cout << std::setw(5) << i << " | "
<< std::setw(8) << std::hex << std::setfill('0')
<< static_cast<int>(position) << std::dec << std::setfill(' ') << " | "
<< std::setw(5) << std::hex << std::setfill('0')
<< static_cast<int>(color) << std::dec << std::setfill(' ') << std::endl;
}
}
// Analyze and print color runs
std::cout << "\nAnalyzing color runs:" << std::endl;
if (numPairs > 0) {
uint8_t currentColor = static_cast<uint8_t>(data[1]); // First color
size_t runStart = 0;
size_t runLength = 1;
// Find all runs
std::vector<std::tuple<size_t, size_t, uint8_t>> colorRuns;
for (size_t i = 1; i < numPairs; i++) {
uint8_t color = static_cast<uint8_t>(data[i * 2 + 1]);
if (color == currentColor) {
// Continue the current run
runLength++;
} else {
// End the current run and start a new one
colorRuns.emplace_back(runStart, runStart + runLength - 1, currentColor);
currentColor = color;
runStart = i;
runLength = 1;
}
}
// Add the last run
colorRuns.emplace_back(runStart, runStart + runLength - 1, currentColor);
// Print the runs in a condensed format
std::cout << "Found " << colorRuns.size() << " color runs:" << std::endl;
std::cout << "Color | Voxel Count | Range" << std::endl;
std::cout << "------|-------------|------" << std::endl;
for (const auto& run : colorRuns) {
size_t start = std::get<0>(run);
size_t end = std::get<1>(run);
uint8_t color = std::get<2>(run);
size_t length = end - start + 1;
std::cout << " 0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(color) << " | "
<< std::dec << std::setfill(' ') << std::setw(11) << length << " | "
<< std::setw(5) << start << "-" << std::setw(5) << end
<< std::endl;
}
// Add special notice for full-voxel-space runs
if (colorRuns.size() == 1) {
const auto& singleRun = colorRuns[0];
size_t start = std::get<0>(singleRun);
size_t end = std::get<1>(singleRun);
size_t length = end - start + 1;
uint8_t color = std::get<2>(singleRun);
if (start == 0 && length == 32768) {
std::cout << "\nNOTICE: This chunk contains a single color (0x"
<< std::hex << static_cast<int>(color) << std::dec
<< ") for all 32,768 voxels, which would fill a complete 32x32x32 voxel space." << std::endl;
std::cout << "This could indicate:";
std::cout << "\n - A solid block of one color";
std::cout << "\n - A special encoding for empty/default chunks";
std::cout << "\n - A placeholder or initialization state" << std::endl;
}
}
}
}
// Decode voxels for visualization
// @param TODO: get morton offset from the 'lt' dictionary
std::vector<newVoxel> voxels = decodeVoxels2(std::vector<uint8_t>(data, data + length), 0);
printVoxelTable(voxels, 100);
// Explicitly decode the voxels for visualization
char* data = nullptr;
uint64_t length = 0;
plist_get_data_val(dsNode, &data, &length);
if (length > 0 && data) {
std::vector<newVoxel> voxels = decodeVoxels2(std::vector<uint8_t>(data, data + length), 0);
visualizeZPlaneFixed(voxels, zIndex);
free(data);
}
}
free(data);
}
// Check for 'id' dictionary to get chunk information
plist_t idNode = plist_dict_get_item(sNode, "id");
if (idNode && plist_get_node_type(idNode) == PLIST_DICT) {
plist_t chunkIdNode = plist_dict_get_item(idNode, "c");
if (chunkIdNode && plist_get_node_type(chunkIdNode) == PLIST_UINT) {
uint64_t chunkId;
plist_get_uint_val(chunkIdNode, &chunkId);
std::cout << "\nChunk ID: " << chunkId << std::endl;
}
}
// Check for 'lt' (location table)
plist_t ltNode = plist_dict_get_item(sNode, "lt");
if (ltNode && plist_get_node_type(ltNode) == PLIST_DATA) {
char* data = nullptr;
uint64_t length = 0;
plist_get_data_val(ltNode, &data, &length);
std::cout << "\nLocation table size: " << length << " bytes" << std::endl;
if (length > 0 && data) {
std::cout << "First 16 bytes of location table:" << std::endl;
size_t bytesToShow = std::min(static_cast<size_t>(16), static_cast<size_t>(length));
for (size_t i = 0; i < bytesToShow; i++) {
std::cout << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(static_cast<uint8_t>(data[i])) << " ";
}
std::cout << std::dec << std::endl;
}
free(data);
}
}
}
}
}
/**
* Prints a table of voxel positions and colors
*
* @param voxels The vector of decoded voxels
* @param limit Maximum number of voxels to display (0 for all)
* @param filterZ Optional z-value to filter by
*/
void printVoxelTable(const std::vector<newVoxel>& voxels, size_t limit , int filterZ ) {
int emptyVoxels = 32768 - voxels.size();
std::cout << "Voxels: " << voxels.size() << " Empty: " << emptyVoxels << std::endl;
// Count voxels at the filtered z-level if filtering is active
int filteredCount = 0;
if (filterZ >= 0) {
for (const auto& voxel : voxels) {
if (voxel.z == filterZ) filteredCount++;
}
std::cout << "Voxels at z=" << filterZ << ": " << filteredCount << std::endl;
}
std::cout << "Index | X | Y | Z | Color" << std::endl;
std::cout << "------|----|----|----|---------" << std::endl;
int count = 0;
int shownCount = 0;
for (size_t i = 0; i < voxels.size(); i++) {
const auto& voxel = voxels[i];
// Skip if we're filtering by z and this doesn't match
if (filterZ >= 0 && voxel.z != filterZ) continue;
std::cout << std::setw(6) << i << " | ";
std::cout << std::setw(2) << voxel.x << " | ";
std::cout << std::setw(2) << voxel.y << " | ";
std::cout << std::setw(2) << voxel.z << " | ";
std::cout << "0x" << std::hex << std::setw(2) << std::setfill('0')
<< static_cast<int>(voxel.color) << std::dec << std::setfill(' ') << std::endl;
// Count shown voxels
shownCount++;
// Check if we've reached the limit
if (limit > 0 && shownCount >= limit) {
if (filterZ >= 0) {
int remaining = filteredCount - shownCount;
if (remaining > 0) {
std::cout << "... (output truncated, " << remaining << " more voxels at z=" << filterZ << ")" << std::endl;
}
} else {
std::cout << "... (output truncated, " << (voxels.size() - shownCount) << " more voxels)" << std::endl;
}
break;
}
}
}

24
debug.h
View File

@ -1,24 +0,0 @@
#pragma once
#include <iostream>
#include <vector>
#include <cstdint>
#include <iomanip>
#include "../libplist/include/plist/plist.h" // Library for handling Apple property list files
#include "common.h" // Debugging functions
// Include any necessary structures
// Optimized function to compact bits (From VoxelMax)
uint32_t compactBits(uint32_t n);
// Optimized function to decode Morton code using parallel bit manipulation
void decodeMorton3DOptimized(uint32_t morton, uint32_t& x, uint32_t& y, uint32_t& z);
// Function declarations
std::vector<newVoxel> decodeVoxels(const std::vector<uint8_t>& dsData, int mortonOffset);
void printPlistNode(const plist_t& node, int indent = 0);
bool examinePlistNode(const plist_t& root_node, int snapshotIndex, int zIndex, const std::string& arrayPath);
bool debugSnapshots(plist_t element, int snapshotIndex, int zIndex);
void printVoxelTable(const std::vector<newVoxel>& voxels, size_t limit = 100, int filterZ = -1);
void visualizeZPlaneFixed(const std::vector<newVoxel>& voxels, int zPlane, int size = 32);

View File

@ -1,70 +0,0 @@
#include "extra.h"
// 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.
)";
}

View File

@ -1,11 +1,11 @@
# Project configuration
BELLA_SDK_NAME = bella_scene_sdk
BELLA_SDK_NAME = bella_engine_sdk
EXECUTABLE_NAME = vmax2bella
PLATFORM = $(shell uname)
BUILD_TYPE ?= release# Default to release build if not specified
# Common paths
BELLA_SDK_PATH = ../bella_scene_sdk
BELLA_SDK_PATH = ../bella_engine_sdk
LZFSE_PATH = ../lzfse
LIBPLIST_PATH = ../libplist
OBJ_DIR = obj/$(PLATFORM)/$(BUILD_TYPE)
@ -18,6 +18,7 @@ ifeq ($(PLATFORM), Darwin)
SDK_LIB_EXT = dylib
LZFSE_LIB_NAME = liblzfse.$(SDK_LIB_EXT)
PLIST_LIB_NAME = libplist-2.0.4.$(SDK_LIB_EXT)
USD_LIB_NAME = libdl_usd_ms.$(SDK_LIB_EXT)
MACOS_SDK_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
# Compiler settings
@ -43,6 +44,7 @@ else
SDK_LIB_EXT = so
LZFSE_LIB_NAME = liblzfse.$(SDK_LIB_EXT)
PLIST_LIB_NAME = libplist.$(SDK_LIB_EXT)
USD_LIB_NAME = libdl_usd_ms.$(SDK_LIB_EXT)
# Compiler settings
CC = gcc
@ -96,6 +98,7 @@ $(OUTPUT_FILE): $(OBJECT_FILES)
$(CXX) -o $@ $(OBJECT_FILES) $(LINKER_FLAGS) $(LIB_PATHS) $(LIBRARIES)
@echo "Copying libraries to $(BIN_DIR)..."
@cp $(SDK_LIB_PATH)/$(SDK_LIB_FILE) $(BIN_DIR)/$(SDK_LIB_FILE)
@cp $(SDK_LIB_PATH)/$(USD_LIB_NAME) $(BIN_DIR)/$(USD_LIB_NAME)
@cp $(LZFSE_BUILD_DIR)/$(LZFSE_LIB_NAME) $(BIN_DIR)/$(LZFSE_LIB_NAME)
@cp $(PLIST_LIB_DIR)/$(PLIST_LIB_NAME) $(BIN_DIR)/
@echo "Build complete: $(OUTPUT_FILE)"
@ -108,7 +111,7 @@ clean:
rm -f $(OBJ_DIR)/vmax2bella.o
rm -f $(OUTPUT_FILE)
rm -f $(BIN_DIR)/$(SDK_LIB_FILE)
rm -f $(BIN_DIR)/*.dylib
rm -f $(BIN_DIR)/*.$(SDK_LIB_EXT)
rmdir $(OBJ_DIR) 2>/dev/null || true
rmdir $(BIN_DIR) 2>/dev/null || true
@ -119,8 +122,8 @@ cleanall:
rm -f bin/*/debug/$(EXECUTABLE_NAME)
rm -f bin/*/release/$(SDK_LIB_FILE)
rm -f bin/*/debug/$(SDK_LIB_FILE)
rm -f bin/*/release/*.dylib
rm -f bin/*/debug/*.dylib
rm -f bin/*/release/*.$(SDK_LIB_EXT)
rm -f bin/*/debug/*.$(SDK_LIB_EXT)
rmdir obj/*/release 2>/dev/null || true
rmdir obj/*/debug 2>/dev/null || true
rmdir bin/*/release 2>/dev/null || true

File diff suppressed because it is too large Load Diff

View File

@ -1,236 +0,0 @@
// oomer wrapper code for opengametools voxel conversion
#pragma once
#include "oomer_voxel_vmax.h"
#include <vector>
#include <string>
#include <stdio.h>
#include <stdint.h>
#include <cstdlib>
#include <cstring>
#include "../opengametools/src/ogt_vox.h"
// Only define implementation once to avoid redefinition errors
#ifndef OGT_VOXEL_MESHIFY_IMPLEMENTATION
#define OGT_VOXEL_MESHIFY_IMPLEMENTATION
#endif
#include "../opengametools/src/ogt_voxel_meshify.h"
// Convert a VmaxModel to an ogt_vox_model
// Note: The returned ogt_vox_model must be freed using ogt_vox_free when no longer needed
/*ogt_vox_model* convert_vmax_to_ogt_vox(const VmaxModel& vmaxModel) {
// Use max dimensions from vmaxModel
// Add 1 to get the actual size (since coordinates are 0-based)
uint32_t size_x = vmaxModel.maxx+1;
uint32_t size_y = vmaxModel.maxy+1;
uint32_t size_z = vmaxModel.maxz+1;
// Add some safety checks
if (size_x > 256 || size_y > 256 || size_z > 256) {
std::cout << "Warning: Model dimensions exceed 256 limit. Clamping to 256." << std::endl;
size_x = std::min(size_x, 256u);
size_y = std::min(size_y, 256u);
size_z = std::min(size_z, 256u);
}
if (size_x == 0 || size_y == 0 || size_z == 0) {
std::cout << "Error: Model has zero dimensions. Setting minimum size of 1x1x1." << std::endl;
size_x = std::max(size_x, 1u);
size_y = std::max(size_y, 1u);
size_z = std::max(size_z, 1u);
}
// Create the voxel data array (initialized to 0, which means empty in ogt_vox)
size_t voxel_count = size_x * size_y * size_z;
uint8_t* voxel_data = (uint8_t*)ogt_vox_malloc(voxel_count);
if (!voxel_data) {
std::cout << "Error: Failed to allocate memory for voxel data" << std::endl;
return nullptr;
}
memset(voxel_data, 0, voxel_count); // Initialize all to 0 (empty)
// Fill the voxel data array with color indices
int voxel_count_populated = 0;
for (uint32_t x = 0; x < size_x; x++) {
for (uint32_t y = 0; y < size_y; y++) {
for (uint32_t z = 0; z < size_z; z++) {
// Calculate the index in the 1D array
size_t index = x + (y * size_x) + (z * size_x * size_y);
if (index < voxel_count) {
try {
// Use the new hasVoxelsAt and getVoxelsAt methods instead of directly accessing voxelsSpatial
if (vmaxModel.hasVoxelsAt(x, y, z)) {
const std::vector<VmaxVoxel>& voxels = vmaxModel.getVoxelsAt(x, y, z);
if (!voxels.empty()) {
uint8_t palette_index = voxels[0].palette;
if (palette_index == 0) palette_index = 1; // If palette is 0, use 1 instead to make it visible
voxel_data[index] = palette_index;
voxel_count_populated++;
}
}
// If no voxels at this position, it remains 0 (empty)
}
catch (const std::exception& e) {
std::cout << "ERROR: Exception accessing voxels at [" << x << "][" << y << "][" << z
<< "]: " << e.what() << std::endl;
ogt_vox_free(voxel_data);
return nullptr;
}
}
}
}
}
// Create and initialize the ogt_vox_model
ogt_vox_model* model = (ogt_vox_model*)ogt_vox_malloc(sizeof(ogt_vox_model));
if (!model) {
std::cout << "Error: Failed to allocate memory for ogt_vox_model" << std::endl;
ogt_vox_free(voxel_data);
return nullptr;
}
model->size_x = size_x;
model->size_y = size_y;
model->size_z = size_z;
model->voxel_data = voxel_data;
// Calculate a simple hash for the voxel data
uint32_t hash = 0;
for (size_t i = 0; i < voxel_count; i++) {
hash = hash * 65599 + voxel_data[i];
}
model->voxel_hash = hash;
return model;
}
*/
// Convert a vector of VmaxVoxel to an ogt_vox_model
// Note: The returned ogt_vox_model must be freed using ogt_vox_free when no longer needed
ogt_vox_model* convert_voxelsoftype_to_ogt_vox(const std::vector<VmaxVoxel>& voxelsOfType) {
// Find the maximum dimensions from the voxels
uint32_t size_x = 0;
uint32_t size_y = 0;
uint32_t size_z = 0;
// WARNING must add 1 to each dimension
// because voxel coordinates are 0-based
for (const auto& voxel : voxelsOfType) {
size_x = std::max(size_x, static_cast<uint32_t>(voxel.x)+1); // this seems wasteful
size_y = std::max(size_y, static_cast<uint32_t>(voxel.y)+1);
size_z = std::max(size_z, static_cast<uint32_t>(voxel.z)+1);
}
// Add some safety checks
// This is a dense voxel model, so we need to make sure it's not too large
// todo use a sparse storage like morton
if (size_x > 256 || size_y > 256 || size_z > 256) {
std::cout << "Warning: Model dimensions exceed 256 limit. Clamping to 256." << std::endl;
size_x = std::min(size_x, 256u);
size_y = std::min(size_y, 256u);
size_z = std::min(size_z, 256u);
}
if (size_x == 0 || size_y == 0 || size_z == 0) {
std::cout << "Error: Model has zero dimensions. Setting minimum size of 1x1x1." << std::endl;
size_x = std::max(size_x, 1u);
size_y = std::max(size_y, 1u);
size_z = std::max(size_z, 1u);
}
// Create the voxel data array (initialized to 0, which means empty in ogt_vox)
size_t voxel_count = size_x * size_y * size_z;
uint8_t* voxel_data = (uint8_t*)ogt_vox_malloc(voxel_count);
if (!voxel_data) {
std::cout << "Error: Failed to allocate memory for voxel data" << std::endl;
return nullptr;
}
memset(voxel_data, 0, voxel_count); // Initialize all to 0 (empty)
// Fill the voxel data array with color indices
int voxel_count_populated = 0;
// Loop through the vector of voxels directly
for (const auto& voxel : voxelsOfType) {
// Get the coordinates and palette
uint32_t x = voxel.x;
uint32_t y = voxel.y;
uint32_t z = voxel.z;
// Skip voxels outside our valid range
if (x >= size_x || y >= size_y || z >= size_z)
continue;
// Calculate the index in the 1D array
size_t index = x + (y * size_x) + (z * size_x * size_y);
if (index < voxel_count) {
uint8_t palette_index = 0; // hardcoded for now
if (palette_index == 0) palette_index = 1; // If palette is 0, use 1 instead to make it visible
voxel_data[index] = palette_index;
voxel_count_populated++;
}
}
// Create the model
ogt_vox_model* model = (ogt_vox_model*)ogt_vox_malloc(sizeof(ogt_vox_model));
if (!model) {
std::cout << "Error: Failed to allocate memory for model" << std::endl;
ogt_vox_free(voxel_data);
return nullptr;
}
model->size_x = size_x;
model->size_y = size_y;
model->size_z = size_z;
model->voxel_data = voxel_data;
// Calculate a simple hash for the voxel data
uint32_t hash = 0;
for (size_t i = 0; i < voxel_count; i++) {
hash = hash * 65599 + voxel_data[i];
}
model->voxel_hash = hash;
return model;
}
// Free resources allocated for an ogt_vox_model created by convert_vmax_to_ogt_vox
void free_ogt_vox_model(ogt_vox_model* model) {
if (model) {
if (model->voxel_data) {
ogt_vox_free((void*)model->voxel_data);
}
ogt_vox_free(model);
}
}
// Free resources allocated for an ogt_vox_scene created by create_ogt_vox_scene_from_vmax
/*void free_ogt_vox_scene(ogt_vox_scene* scene) {
if (scene) {
// Free each model
for (uint32_t i = 0; i < scene->num_models; i++) {
free_ogt_vox_model((ogt_vox_model*)scene->models[i]);
}
// Free pointers
if (scene->models) ogt_vox_free((void*)scene->models);
if (scene->instances) ogt_vox_free((void*)scene->instances);
if (scene->layers) ogt_vox_free((void*)scene->layers);
// Free the scene itself
ogt_vox_free(scene);
}
}
*/
// Custom allocator functions for ogt_voxel_meshify
static void* voxel_meshify_malloc(size_t size, void* user_data) {
return malloc(size);
}
static void voxel_meshify_free(void* ptr, void* user_data) {
free(ptr);
}

View File

@ -1,967 +0,0 @@
#pragma once
// This is a set of common oomer utilities for Vmax models
// Will avoid using bella_sdk
// Standard C++ library includes - these provide essential functionality
#include <map> // For key-value pair data structures (maps)
#include <set> // For set data structure
#include <vector> // For dynamic arrays (vectors)
#include <string> // For std::string
#include <cstdint> // For fixed-size integer types (uint8_t, uint32_t, etc.)
#include <fstream> // For file operations (reading/writing files)
#include <iostream> // For input/output operations (cout, cin, etc.)
#include <filesystem> // For file system operations (directory handling, path manipulation)
#include "../lzfse/src/lzfse.h"
#include "../libplist/include/plist/plist.h" // Library for handling Apple property list files
#include "thirdparty/json.hpp"
using json = nlohmann::json;
// Define STB_IMAGE_IMPLEMENTATION before including to create the implementation
#define STB_IMAGE_IMPLEMENTATION
#include "thirdparty/stb_image.h" // STB Image library
// Structure to represent a 4x4 matrix for 3D transformations
// The matrix is stored as a 2D array where m[i][j] represents row i, column j
struct VmaxMatrix4x4 {
double m[4][4];
// Constructor: Creates an identity matrix (1's on diagonal, 0's elsewhere)
VmaxMatrix4x4() {
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 4; j++) {
m[i][j] = (i == j) ? 1.0 : 0.0; // 1.0 on diagonal, 0.0 elsewhere
}
}
}
// Matrix multiplication operator to combine transformations
// Returns: A new matrix that represents the combined transformation
VmaxMatrix4x4 operator*(const VmaxMatrix4x4& other) const {
VmaxMatrix4x4 result;
// Perform matrix multiplication
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 4; j++) {
result.m[i][j] = 0.0;
for(int k = 0; k < 4; k++) {
result.m[i][j] += m[i][k] * other.m[k][j];
}
}
}
return result;
}
// Helper function to create a translation matrix
static VmaxMatrix4x4 createTranslation(double x, double y, double z) {
VmaxMatrix4x4 result;
result.m[3][0] = x; // Translation in X (bottom row)
result.m[3][1] = y; // Translation in Y (bottom row)
result.m[3][2] = z; // Translation in Z (bottom row)
return result;
}
// Helper function to create a scale matrix
static VmaxMatrix4x4 createScale(double x, double y, double z) {
VmaxMatrix4x4 result;
result.m[0][0] = x; // Scale in X
result.m[1][1] = y; // Scale in Y
result.m[2][2] = z; // Scale in Z
return result;
}
};
// Converts axis-angle rotation to a 4x4 rotation matrix
// Parameters:
// ax, ay, az: The axis vector to rotate around (doesn't need to be normalized)
// angle: The angle to rotate by (in radians)
// Returns: A 4x4 rotation matrix that can be used to transform vectors
VmaxMatrix4x4 axisAngleToMatrix4x4(double ax, double ay, double az, double angle) {
// Step 1: Normalize the axis vector to make it a unit vector
// This is required for the rotation formula to work correctly
double length = sqrt(ax*ax + ay*ay + az*az);
if (length != 0) {
ax /= length;
ay /= length;
az /= length;
}
// Step 2: Calculate trigonometric values needed for the rotation
double s = sin(angle); // sine of angle
double c = cos(angle); // cosine of angle
double t = 1.0 - c; // 1 - cos(angle), used in formula
// Step 3: Create rotation matrix using Rodrigues' rotation formula
// This formula converts an axis-angle rotation into a 3x3 matrix
// We'll embed it in the upper-left corner of our 4x4 matrix
VmaxMatrix4x4 result;
// First row of rotation matrix (upper-left 3x3 portion)
result.m[0][0] = t*ax*ax + c; // First column
result.m[0][1] = t*ax*ay + s*az; // Second column (changed sign)
result.m[0][2] = t*ax*az - s*ay; // Third column (changed sign)
// Second row of rotation matrix
result.m[1][0] = t*ax*ay - s*az; // First column (changed sign)
result.m[1][1] = t*ay*ay + c; // Second column
result.m[1][2] = t*ay*az + s*ax; // Third column (changed sign)
// Third row of rotation matrix
result.m[2][0] = t*ax*az + s*ay; // First column (changed sign)
result.m[2][1] = t*ay*az - s*ax; // Second column (changed sign)
result.m[2][2] = t*az*az + c; // Third column
// Fourth row and column remain unchanged (0,0,0,1)
// This is already set by the constructor
return result;
}
// Combine a rotation, translation, and scale into a single 4x4 matrix
// Parameters:
// rotx, roty, rotz: The axis vector to rotate around (doesn't need to be normalized)
// rota: The angle to rotate by (in radians)
// posx, posy, posz: The position to translate to
// scalex, scaley, scalez: The scale to apply to the object
// Returns: A 4x4 matrix that represents the combined transformation
VmaxMatrix4x4 combineVmaxTransforms(double rotx, double roty, double rotz, double rota, double posx, double posy, double posz, double scalex, double scaley, double scalez) {
VmaxMatrix4x4 rotMat4 = axisAngleToMatrix4x4(rotx,
roty,
rotz,
rota);
VmaxMatrix4x4 transMat4 = VmaxMatrix4x4();
transMat4 = transMat4.createTranslation(posx,
posy,
posz);
VmaxMatrix4x4 scaleMat4 = VmaxMatrix4x4();
scaleMat4 = scaleMat4.createScale(scalex,
scaley,
scalez);
VmaxMatrix4x4 resultMat4 = scaleMat4 * rotMat4 * transMat4;
return resultMat4;
}
struct VmaxRGBA {
uint8_t r, g, b, a;
};
// Read a 256x1 PNG file and return a vector of VmaxRGBA colors
std::vector<VmaxRGBA> read256x1PaletteFromPNG(const std::string& filename) {
int width, height, channels;
// Load the image with 4 desired channels (RGBA)
unsigned char* data = stbi_load(filename.c_str(), &width, &height, &channels, 4);
if (!data) {
std::cerr << "Error loading PNG file: " << filename << std::endl;
return {};
}
// Make sure the image is 256x1 as expected
if (width != 256 || height != 1) {
std::cerr << "Warning: Expected a 256x1 image, but got " << width << "x" << height << std::endl;
}
// Create our palette array
std::vector<VmaxRGBA> palette;
// Read each pixel (each pixel is 4 bytes - RGBA)
for (int i = 0; i < width; i++) {
VmaxRGBA color;
color.r = data[i * 4];
color.g = data[i * 4 + 1];
color.b = data[i * 4 + 2];
color.a = data[i * 4 + 3];
palette.push_back(color);
}
stbi_image_free(data); // Free the image data
return palette;
}
// Standard useful voxel structure, maps easily to VoxelMax's voxel structure and probably MagicaVoxel's
// We are using this to unpack a chunked voxel into a simple giant voxel
// using a uint8_t saves memory over a uint32_t and both VM and MV models are 256x256x256
// The world itself can be larger, see scene.json
// Helper struct because in VmaxModel we use array of arrays to store material and color
// Allowing us to group voxels by material and color
struct VmaxVoxel {
uint8_t x, y, z;
uint8_t material; // material value 0-7
uint8_t palette; // Color palette mapping index 0-255, search palette1.png
uint16_t chunkID; // Chunk ID 0-511 8x8x8 is a morton code
uint16_t minMorton; // morton encoded offset from chunk origin 0-32767 32x32x32, decoded value is added to voxel x y z
// Constructor
VmaxVoxel( uint8_t _x,
uint8_t _y,
uint8_t _z,
uint8_t _material,
uint8_t _palette,
uint16_t _chunkID,
uint16_t _minMorton)
: x(_x), y(_y), z(_z), material(_material), palette(_palette), chunkID(_chunkID), minMorton(_minMorton) {
}
};
inline uint32_t compactBits(uint32_t n) {
// For a 32-bit integer in C++
n &= 0x49249249; // Keep only every 3rd bit
n = (n ^ (n >> 2)) & 0xc30c30c3; // Merge groups
n = (n ^ (n >> 4)) & 0x0f00f00f; // Continue merging
n = (n ^ (n >> 8)) & 0x00ff00ff; // Merge larger groups
n = (n ^ (n >> 16)) & 0x0000ffff; // Final merge
return n;
}
// Optimized function to decode Morton code using parallel bit manipulation
inline void decodeMorton3DOptimized(uint32_t morton, uint32_t& x, uint32_t& y, uint32_t& z) {
x = compactBits(morton);
y = compactBits(morton >> 1);
z = compactBits(morton >> 2);
}
struct VmaxMaterial {
std::string materialName;
double transmission;
double roughness;
double metalness;
double emission;
bool enableShadows;
bool dielectric; // future use
bool volumetric; // future use
};
struct VmaxVoxelGrid {
// dimensions of the voxel grid
uint32_t size_x, size_y, size_z;
// voxel data
uint8_t* voxel_data;
};
// Create a structure to represent a model with its voxels with helper functions
// since the xyz coords are at the voxel level, we need an accessor to walk it sequentially
// maybe I create a new structure called VmaxVoxelGrid
struct VmaxModel {
// Model identifier or name
std::string vmaxbFileName; // file name is used like a key
// Voxels organized by material and color
// First dimension: material (0-7)
// Second dimension: color (1-255, index 0 unused since color 0 means no voxel)
std::vector<VmaxVoxel> voxels[8][256];
// EDUCATIONAL NOTES ON DUAL DATA STRUCTURES FOR VOXELS:
// ----------------------------------------------------
// This class uses two different data structures to store the same voxel data,
// each optimized for different access patterns:
//
// 1. voxels[8][256] - Organizes voxels by material and color
// - Efficient for queries like "give me all voxels of material 2, color 37"
// - Poor for spatial queries like "what's at position (x,y,z)?"
//
// 2. voxelsSpatial - Organizes voxels by their spatial position
// - Efficient for spatial queries like "what's at position (x,y,z)?"
// - Uses a map for memory efficiency with sparse data
//
// Tradeoffs:
// - Memory: We use more memory by storing voxels twice
// - Performance: We get optimal performance for both types of queries
// - Complexity: We need to maintain both structures in sync
//
// For novice programmers: This is a common technique in game/graphics programming
// where performance is critical. We're trading some extra memory for faster access.
// Using a map for sparse 3D data instead of a fixed-size 3D array
// Key format: (x << 16) | (y << 8) | z
// This approach is memory-efficient for sparse data (mostly empty space)
// A 3D array would need 256³ = 16.7 million elements even if most are empty!
// todo we need to do morton decoing using chunkid to get x,y,z
std::map<uint32_t, std::vector<VmaxVoxel>> voxelsSpatial;
// Each model has local 0-7 materials
std::array<VmaxMaterial, 8> materials;
// Each model has local colors
std::array<VmaxRGBA, 256> colors;
uint8_t maxx=0, maxy=0, maxz=0;
// Constructor
VmaxModel(const std::string& modelName) : vmaxbFileName(modelName) {
}
// Helper function to create a key for the voxelsSpatial map
static uint32_t makeVoxelKey(uint8_t x, uint8_t y, uint8_t z) {
return (static_cast<uint32_t>(x) << 16) | (static_cast<uint32_t>(y) << 8) | static_cast<uint32_t>(z);
}
// Add a voxel to this model
// EDUCATIONAL NOTE:
// Notice how we maintain BOTH data structures when adding a voxel.
// This is a key practice when using dual data structures - keep them in sync
// by updating both whenever data changes.
void addVoxel(int x, int y, int z, int material, int color, int chunk, int chunkMin) {
//todo add chunk offset to x,y,z
//chunkMin is offset withing each chunk used earlier
uint32_t _tempx, _tempy, _tempz;
decodeMorton3DOptimized(chunk, _tempx, _tempy, _tempz); // index IS the morton code
int worldOffsetX = _tempx * 24; // get world loc within 256x256x256 grid
int worldOffsetY = _tempy * 24; // Don't know why we need to multiply by 24
int worldOffsetZ = _tempz * 24; // use to be 32
x += worldOffsetX;
y += worldOffsetY;
z += worldOffsetZ;
if (material >= 0 && material < 8 && color > 0 && color < 256) {
voxels[material][color].emplace_back(x, y, z, material, color, chunk, chunkMin);
// Add to voxelsSpatial using the map approach
uint32_t key = makeVoxelKey(x, y, z);
//todo add chunk offset to x,y,z
voxelsSpatial[key].emplace_back(x, y, z, material, color, chunk, chunkMin);
if (x > maxx) maxx = x;
if (y > maxy) maxy = y;
if (z > maxz) maxz = z;
}
}
// Get voxels at a specific position
// EDUCATIONAL NOTE:
// This method demonstrates the power of our spatial index.
// Time complexity: O(log n) where n is the number of occupied positions.
// Without voxelsSpatial, we would need to scan through ALL voxels (potentially thousands)
// to find those at a specific position, which would be O(total_voxel_count).
const std::vector<VmaxVoxel>& getVoxelsAt(uint8_t x, uint8_t y, uint8_t z) const {
uint32_t key = makeVoxelKey(x, y, z);
auto it = voxelsSpatial.find(key);
if (it != voxelsSpatial.end()) {
return it->second;
}
static const std::vector<VmaxVoxel> empty;
return empty;
}
// Check if there are voxels at a specific position
// Another spatial query that benefits from our map-based structure
bool hasVoxelsAt(uint8_t x, uint8_t y, uint8_t z) const {
uint32_t key = makeVoxelKey(x, y, z);
auto it = voxelsSpatial.find(key);
return (it != voxelsSpatial.end() && !it->second.empty());
}
// Add materials to this model
void addMaterials(const std::array<VmaxMaterial, 8> newMaterials) {
materials = newMaterials;
}
// Add colors to this model
void addColors(const std::array<VmaxRGBA, 256> newColors) {
colors = newColors;
}
// Get all voxels of a specific material and color
const std::vector<VmaxVoxel>& getVoxels(int material, int color) const {
if (material >= 0 && material < 8 && color > 0 && color < 256) {
return voxels[material][color];
}
static std::vector<VmaxVoxel> empty;
return empty;
}
// Get total voxel count for this model
size_t getTotalVoxelCount() const {
size_t count = 0;
for (int m = 0; m < 8; m++) {
for (int c = 1; c < 256; c++) { // Skip index 0
count += voxels[m][c].size();
}
}
return count;
}
// Get a map of used materials and their associated colors
std::map<int, std::set<int>> getUsedMaterialsAndColors() const {
std::map<int, std::set<int>> result;
// Iterate through fixed size arrays - we know it's 8 materials and 256 colors
for (int material = 0; material < 8; material++) {
for (int color = 1; color < 256; color++) { // Skip index 0 as it means no voxel
if (!voxels[material][color].empty()) {
result[material].insert(color);
}
}
}
return result;
}
};
inline std::array<VmaxMaterial, 8> getVmaxMaterials(plist_t pnodPalettePlist) {
// Directly access the materials array
std::array<VmaxMaterial, 8> vmaxMaterials;
plist_t materialsNode = plist_dict_get_item(pnodPalettePlist, "materials");
if (materialsNode && plist_get_node_type(materialsNode) == PLIST_ARRAY) {
uint32_t materialsCount = plist_array_get_size(materialsNode);
//std::cout << "Found materials array with " << materialsCount << " items" << std::endl;
// Process each material
for (uint32_t i = 0; i < materialsCount; i++) {
plist_t materialNode = plist_array_get_item(materialsNode, i);
if (materialNode && plist_get_node_type(materialNode) == PLIST_DICT) {
plist_t nameNode = plist_dict_get_item(materialNode, "mi");
std::string vmaxMaterialName;
double vmaxTransmission = 0.0; // Declare outside the if block
double vmaxEmission = 0.0; // Declare outside the if block
double vmaxRoughness = 0.0; // Declare outside the if block
double vmaxMetalness = 0.0; // Declare outside the if block
uint8_t vmaxEnableShadows = 1;
if (nameNode) {
char* rawName = nullptr;
plist_get_string_val(nameNode, &rawName);
vmaxMaterialName = rawName ? rawName : "unnamed";
free(rawName);
}
plist_t pnodTc = plist_dict_get_item(materialNode, "tc");
if (pnodTc) { plist_get_real_val(pnodTc, &vmaxTransmission); }
plist_t pnodEmission = plist_dict_get_item(materialNode, "sic");
if (pnodEmission) { plist_get_real_val(pnodEmission, &vmaxEmission); }
plist_t pnodRoughness = plist_dict_get_item(materialNode, "rc");
if (pnodRoughness) { plist_get_real_val(pnodRoughness, &vmaxRoughness); }
plist_t pnodMetalness = plist_dict_get_item(materialNode, "mc");
if (pnodMetalness) { plist_get_real_val(pnodMetalness, &vmaxMetalness); }
plist_t pnodEnableShadow = plist_dict_get_item(materialNode, "sh");
if (pnodEnableShadow) { plist_get_bool_val(pnodEnableShadow, &vmaxEnableShadows); }
vmaxMaterials[i] = {
vmaxMaterialName,
vmaxTransmission,
vmaxRoughness,
vmaxMetalness,
vmaxEmission,
static_cast<bool>(vmaxEnableShadows),
false, // dielectric
false, // volumetric
};
}
}
} else {
std::cout << "No materials array found or invalid type" << std::endl;
}
#ifdef _DEBUG23
for (const auto& material : vmaxMaterials) {
std::cout << "Material: " << material.materialName << std::endl;
std::cout << " Transmission: " << material.transmission << std::endl;
std::cout << " Emission: " << material.emission << std::endl;
std::cout << " Roughness: " << material.roughness << std::endl;
std::cout << " Metalness: " << material.metalness << std::endl;
std::cout << " Enable Shadows: " << material.enableShadows << std::endl;
std::cout << " Dielectric: " << material.dielectric << std::endl;
std::cout << " Volumetric: " << material.volumetric << std::endl;
}
#endif
return vmaxMaterials;
}
/**
* Decodes a voxel's material index and palette index from the ds data stream
*
* @param dsData The raw ds data stream containing material and palette index pairs
* @param mortonOffset offset to apply to the morton code
* @param chunkID chunk ID
* @return vector of VmaxVoxel structures containing the voxels local to a snapshot
*/
inline std::vector<VmaxVoxel> decodeVoxels(const std::vector<uint8_t>& dsData, int mortonOffset, uint16_t chunkID) {
std::vector<VmaxVoxel> voxels;
uint8_t material;
uint8_t color;
for (int i = 0; i < dsData.size() - 1; i += 2) {
material = dsData[i]; // also known as a layer color
color = dsData[i + 1];
uint32_t _tempx, _tempy, _tempz;
decodeMorton3DOptimized(i/2 + mortonOffset,
_tempx,
_tempy,
_tempz); // index IS the morton code
if (color != 0) {
VmaxVoxel voxel = {
static_cast<uint8_t>(_tempx),
static_cast<uint8_t>(_tempy),
static_cast<uint8_t>(_tempz),
material,
color,
chunkID, // todo is wasteful to pass chunkID?
static_cast<uint16_t>(mortonOffset)
};
voxels.push_back(voxel);
}
}
return voxels;
}
//libplist reads in 64 bits
struct VmaxChunkInfo {
int64_t id; // was uint but will use -1 to indicate bad chunk
uint64_t type;
uint64_t mortoncode;
uint32_t voxelOffsetX;
uint32_t voxelOffsetY;
uint32_t voxelOffsetZ;
};
// Helper function to get a nested dictionary item
// @param root: root dictionary
// @param path: path to the item
// @return: item
// Using a vector of strings for dynamic path length
plist_t getNestedPlistNode(plist_t plist_root, const std::vector<std::string>& path) {
plist_t current = plist_root;
for (const auto& key : path) {
if (!current) return nullptr;
current = plist_dict_get_item(current, key.c_str());
}
return current;
}
// Need morton code in snapshot before we can decode voxels
// @param an individual chunk: plist_t of a snapshot dict->item->s
// @return Chunk level info needed to decode voxels
VmaxChunkInfo vmaxChunkInfo(const plist_t& plist_snapshot_dict_item) {
uint64_t id;
uint64_t type;
uint64_t mortoncode;
uint32_t voxelOffsetX, voxelOffsetY, voxelOffsetZ;
try {
plist_t plist_snapshot = getNestedPlistNode(plist_snapshot_dict_item, {"s"});
// vmax file format must guarantee the existence
// s.st.min
// s.id.t
// s.id.c
plist_t plist_min = getNestedPlistNode(plist_snapshot, {"st", "min"});
plist_t plist_min_val = plist_array_get_item(plist_min, 3);
plist_get_uint_val(plist_min_val, &mortoncode);
// convert to 32x32x32 chunk offset
decodeMorton3DOptimized(mortoncode,
voxelOffsetX,
voxelOffsetY,
voxelOffsetZ);
plist_t plist_type = getNestedPlistNode(plist_snapshot, {"id","t"});
plist_get_uint_val(plist_type, &type);
plist_t plist_chunk = getNestedPlistNode(plist_snapshot, {"id","c"});
plist_get_uint_val(plist_chunk, &id);
return VmaxChunkInfo{static_cast<int64_t>(id),
type,
mortoncode,
voxelOffsetX,
voxelOffsetY,
voxelOffsetZ};
} catch (std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
// Just continue to next snapshot
// This bypass might mean we miss useful snapshots
}
return VmaxChunkInfo{-1, 0, 0, 0, 0, 0};
}
// Right after we get VmaxChunkInfo, we can get the voxels because we need morton chunk offset
// @param pnodSnaphot: plist_t of a snapshot
// @return vector of VmaxVoxel
//std::vector<VmaxVoxel> getVmaxSnapshot(plist_t& pnod_each_snapshot) {
std::vector<VmaxVoxel> vmaxVoxelInfo(plist_t& plist_datastream, uint64_t chunkID, uint64_t minMorton) {
std::vector<VmaxVoxel> voxelsArray;
try {
// Extract the binary data
char* data = nullptr;
uint64_t length = 0;
plist_get_data_val(plist_datastream, &data, &length);
auto foo = std::vector<uint8_t>(data, data + length) ;
std::vector<VmaxVoxel> allModelVoxels = decodeVoxels(std::vector<uint8_t>(data, data + length), minMorton, chunkID);
//std::cout << "allModelVoxels: " << allModelVoxels.size() << std::endl;
uint32_t model_8x8x8_x, model_8x8x8_y, model_8x8x8_z;
decodeMorton3DOptimized(chunkID,
model_8x8x8_x,
model_8x8x8_y,
model_8x8x8_z); // index IS the morton code
int model_256x256x256_x = model_8x8x8_x * 8; // convert to model space
int model_256x256x256_y = model_8x8x8_y * 8;
int model_256x256x256_z = model_8x8x8_z * 8;
for (const VmaxVoxel& eachVmaxVoxel : allModelVoxels) {
auto [ chunk_32x32x32_x,
chunk_32x32x32_y,
chunk_32x32x32_z,
materialMap,
colorMap,
chunkID,
minMorton] = eachVmaxVoxel;
int voxel_256x256x256_x = model_256x256x256_x + chunk_32x32x32_x;
int voxel_256x256x256_y = model_256x256x256_y + chunk_32x32x32_y;
int voxel_256x256x256_z = model_256x256x256_z + chunk_32x32x32_z;
auto one_voxel = VmaxVoxel(voxel_256x256x256_x,
voxel_256x256x256_y,
voxel_256x256x256_z,
materialMap,
colorMap,
chunkID,
minMorton);
voxelsArray.push_back(one_voxel);
}
return voxelsArray;
} catch (std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
// Just continue to next snapshot
// This bypass might mean we miss useful snapshots
}
return voxelsArray; // empty return
}
/**
* Read a binary plist file and return a plist node.
* if the file is lzfse compressed, decompress it and parse the decompressed data
*
* Memory Management:
* - Creates temporary buffers for decompression
* - Handles buffer resizing if needed
* - Returns a plist node that must be freed by the caller
*
* @param lzfseFullName Path to the LZFSE file
* @param plistName Name of the plist file to write (optional)
* @return plist_t A pointer to the root node of the parsed plist, or nullptr if failed
*/
// read binary lzfse compressed/uncompressed file
inline plist_t readPlist(const std::string& inStrPlist, std::string outStrPlist, bool decompress) {
// Get file size using std::filesystem
size_t rawFileSize = std::filesystem::file_size(inStrPlist);
std::vector<uint8_t> rawBytes(rawFileSize);
std::vector<uint8_t> outBuffer;
size_t decodedSize = 0;
if (decompress) { // files are either lzfse compressed or uncompressed
std::ifstream rawBytesFile(inStrPlist, std::ios::binary);
if (!rawBytesFile.is_open()) {
std::cerr << "Error: Could not open plist file: " << inStrPlist << std::endl;
throw std::runtime_error("Error message"); // [learned] no need to return nullptr
}
rawBytesFile.read(reinterpret_cast<char*>(rawBytes.data()), rawFileSize);
rawBytesFile.close();
// Start with output buffer 4x input size (compression ratio is usually < 4)
size_t outAllocatedSize = rawFileSize * 8;
// vector<uint8_t> automatically manages memory allocation/deallocation
//std::vector<uint8_t> outBuffer(outAllocatedSize);
outBuffer.resize(outAllocatedSize); // Resize preserves existing content
// LZFSE needs a scratch buffer for its internal operations
// Get the required size and allocate it
size_t scratchSize = lzfse_decode_scratch_size();
std::vector<uint8_t> scratch(scratchSize);
// Decompress the data, growing the output buffer if needed
//size_t decodedSize = 0;
while (true) {
// Try to decompress with current buffer size
decodedSize = lzfse_decode_buffer(
outBuffer.data(), // Where to store decompressed data
outAllocatedSize, // Size of output buffer
rawBytes.data(), // Source of compressed data
rawBytes.size(), // Size of compressed data
scratch.data()); // Scratch space for LZFSE
// Check if we need a larger buffer:
// - decodedSize == 0 indicates failure
// - decodedSize == outAllocatedSize might mean buffer was too small
if (decodedSize == 0 || decodedSize == outAllocatedSize) {
outAllocatedSize *= 2; // Double the buffer size
outBuffer.resize(outAllocatedSize); // Resize preserves existing content
continue; // Try again with larger buffer
}
break; // Successfully decompressed
}
// Check if decompression failed
if (decodedSize == 0) {
std::cerr << "Failed to decompress data" << std::endl;
return nullptr;
}
// If requested, write the decompressed data to a file
if (!outStrPlist.empty()) {
std::ofstream outFile(outStrPlist, std::ios::binary);
if (outFile) {
outFile.write(reinterpret_cast<const char*>(outBuffer.data()), decodedSize);
std::cout << "Wrote decompressed plist to: " << outStrPlist << std::endl;
} else {
std::cerr << "Failed to write plist to file: " << outStrPlist << std::endl;
}
}
} else {
outBuffer.resize(rawFileSize);
// if the file is not compressed, just read the raw bytes
std::ifstream rawBytesFile(inStrPlist, std::ios::binary);
if (!rawBytesFile.is_open()) {
std::cerr << "Error: Could not open plist file: " << inStrPlist << std::endl;
throw std::runtime_error("Error message"); // [learned] no need to return nullptr
}
rawBytesFile.read(reinterpret_cast<char*>(outBuffer.data()), rawFileSize);
rawBytesFile.close();
decodedSize = rawFileSize; // decodedSize is the same as rawFileSize when data is not compressed
} // outBuffer now contains the raw bytes of the plist file
// Parse the decompressed data as a plist
plist_t root_node = nullptr;
plist_format_t format; // Will store the format of the plist (binary, xml, etc.)
// Convert the raw decompressed data into a plist structure
plist_err_t err = plist_from_memory(
reinterpret_cast<const char*>(outBuffer.data()), // Cast uint8_t* to char*
static_cast<uint32_t>(decodedSize), // Cast size_t to uint32_t
&root_node, // Where to store the parsed plist
&format); // Where to store the format
// Check if parsing succeeded
if (err != PLIST_ERR_SUCCESS) {
std::cerr << "Failed to parse plist data" << std::endl;
return nullptr;
}
return root_node; // Caller is responsible for calling plist_free()
}
// Overload for when you only want to specify inStrPlist and decompress
inline plist_t readPlist(const std::string& inStrPlist, bool decompress) {
return readPlist(inStrPlist, "", decompress);
}
// Structure to hold object/model information from VoxelMax's scene.json
struct JsonModelInfo {
std::string id;
std::string parentId;
std::string name;
std::string dataFile; // The .vmaxb file
std::string paletteFile; // The palette PNG
std::string historyFile; // The history file, not sure what this is
// Transform information
std::vector<double> position; // t_p
std::vector<double> rotation; // t_r
std::vector<double> scale; // t_s
// Extent information
std::vector<double> extentCenter; // e_c
std::vector<double> extentMin; // e_mi
std::vector<double> extentMax; // e_ma
};
// Structure to hold group information from VoxelMax's scene.json
struct JsonGroupInfo {
std::string id;
std::string name;
std::vector<double> position;
std::vector<double> rotation;
std::vector<double> scale;
std::vector<double> extentCenter;
std::vector<double> extentMin;
std::vector<double> extentMax;
bool selected = false;
std::string parentId;
};
// Class to parse VoxelMax's scene.json
class JsonVmaxSceneParser {
private:
std::map<std::string, JsonModelInfo> models; // a vmax model can also be called a content
std::map<std::string, JsonGroupInfo> groups;
public:
bool parseScene(const std::string& jsonFilePath) {
try {
// Read the JSON file
std::ifstream file(jsonFilePath);
if (!file.is_open()) {
std::cerr << "Failed to open file: " << jsonFilePath << std::endl;
return false;
}
// Parse the JSON
json sceneData;
file >> sceneData;
file.close();
// Parse groups
if (sceneData.contains("groups") && sceneData["groups"].is_array()) {
for (const auto& group : sceneData["groups"]) {
JsonGroupInfo groupInfo;
// Extract group information
if (group.contains("id")) groupInfo.id = group["id"];
if (group.contains("name")) groupInfo.name = group["name"];
// Extract transform data
if (group.contains("t_p") && group["t_p"].is_array())
groupInfo.position = group["t_p"].get<std::vector<double>>();
if (group.contains("t_r") && group["t_r"].is_array())
groupInfo.rotation = group["t_r"].get<std::vector<double>>();
if (group.contains("t_s") && group["t_s"].is_array())
groupInfo.scale = group["t_s"].get<std::vector<double>>();
// Extract extent data
if (group.contains("e_c") && group["e_c"].is_array())
groupInfo.extentCenter = group["e_c"].get<std::vector<double>>();
if (group.contains("e_mi") && group["e_mi"].is_array())
groupInfo.extentMin = group["e_mi"].get<std::vector<double>>();
if (group.contains("e_ma") && group["e_ma"].is_array())
groupInfo.extentMax = group["e_ma"].get<std::vector<double>>();
// Check if selected
if (group.contains("s")) groupInfo.selected = group["s"].get<bool>();
if (group.contains("pid")) groupInfo.parentId = group["pid"];
// Store the group
groups[groupInfo.id] = groupInfo;
}
}
// Parse objects (models) , objects are instances of models
// Maybe rename this objects, will leave for now
if (sceneData.contains("objects") && sceneData["objects"].is_array()) {
for (const auto& obj : sceneData["objects"]) {
JsonModelInfo modelInfo;
// Extract model information
if (obj.contains("id")) modelInfo.id = obj["id"];
if (obj.contains("pid")) modelInfo.parentId = obj["pid"];
if (obj.contains("n")) modelInfo.name = obj["n"];
// Extract file paths
if (obj.contains("data")) modelInfo.dataFile = obj["data"];// This is the canonical model
if (obj.contains("pal")) modelInfo.paletteFile = obj["pal"];
if (obj.contains("hist")) modelInfo.historyFile = obj["hist"];
// Extract transform data
if (obj.contains("t_p") && obj["t_p"].is_array())
modelInfo.position = obj["t_p"].get<std::vector<double>>();
if (obj.contains("t_r") && obj["t_r"].is_array())
modelInfo.rotation = obj["t_r"].get<std::vector<double>>();
if (obj.contains("t_s") && obj["t_s"].is_array())
modelInfo.scale = obj["t_s"].get<std::vector<double>>();
// Extract extent data
if (obj.contains("e_c") && obj["e_c"].is_array())
modelInfo.extentCenter = obj["e_c"].get<std::vector<double>>();
if (obj.contains("e_mi") && obj["e_mi"].is_array())
modelInfo.extentMin = obj["e_mi"].get<std::vector<double>>();
if (obj.contains("e_ma") && obj["e_ma"].is_array())
modelInfo.extentMax = obj["e_ma"].get<std::vector<double>>();
// Store the model
models[modelInfo.id] = modelInfo;
}
}
return true;
} catch (const std::exception& e) {
std::cerr << "Error parsing JSON: " << e.what() << std::endl;
return false;
}
}
// Get the parsed models
const std::map<std::string, JsonModelInfo>& getModels() const {
return models;
}
// Get the parsed groups
const std::map<std::string, JsonGroupInfo>& getGroups() const {
return groups;
}
/* Groups models by their data file names ie contentsN.vmaxb
* Since we can instance models, we can grab the first one when translating it to Bella
* @return Map where:
* - Key: contentN.vmaxb (string)
* - Value: vector of models using that data file
* Example:
* Input models: {"id1": model1, "id2": model2} where both use "data.file"
* Output: {"data.file": [model1, model2]}
*/
std::map<std::string, std::vector<JsonModelInfo>> getModelContentVMaxbMap() const {
std::map<std::string, std::vector<JsonModelInfo>> fileMap;
for (const auto& [id, model] : models) {
fileMap[model.dataFile].push_back(model);
}
return fileMap;
}
// Print summary of parsed data
void printSummary() const {
std::cout << "=========== Scene Summary ===========" << std::endl;
std::cout << "Groups: " << groups.size() << std::endl;
std::cout << "Models: " << models.size() << std::endl;
// Print unique model files
std::map<std::string, int> modelFiles;
for (const auto& [id, model] : models) {
modelFiles[model.dataFile]++;
}
std::cout << "\nModel Files:" << std::endl;
for (const auto& [file, count] : modelFiles) {
std::cout << " " << file << " (used " << count << " times)" << std::endl;
}
std::cout << "\nGroups:" << std::endl;
for (const auto& [id, group] : groups) {
std::cout << " " << group.name << " (ID: " << id << ")" << std::endl;
if (!group.position.empty()) {
std::cout << " Position: ["
<< group.position[0] << ", "
<< group.position[1] << ", "
<< group.position[2] << "]" << std::endl;
}
}
std::cout << "\nModels:" << std::endl;
for (const auto& [id, model] : models) {
std::cout << " " << model.name << " (ID: " << id << ")" << std::endl;
std::cout << " Data: " << model.dataFile << std::endl;
std::cout << " Palette: " << model.paletteFile << std::endl;
std::cout << " Parent: " << model.parentId << std::endl;
if (!model.position.empty()) {
std::cout << " Position: ["
<< model.position[0] << ", "
<< model.position[1] << ", "
<< model.position[2] << "]" << std::endl;
}
}
}
};

View File

@ -1,47 +0,0 @@
/*
* cnary.c
*
* Created on: Mar 9, 2011
* Author: posixninja
*
* Copyright (c) 2011 Joshua Hill. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdio.h>
#include "node.h"
int main(int argc, char* argv[]) {
puts("Creating root node");
node_t root = node_create(NULL, NULL);
puts("Creating child 1 node");
node_t one = node_create(root, NULL);
puts("Creating child 2 node");
node_t two = node_create(root, NULL);
puts("Creating child 3 node");
node_t three = node_create(one, NULL);
puts("Debugging root node");
node_debug(root);
puts("Destroying root node");
node_destroy(root);
return 0;
}

View File

@ -1,13 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#define PACKAGE_VERSION "2.4.0"
#define PACKAGE "libplist"
#define PACKAGE_BUGREPORT "https://github.com/libimobiledevice/libplist/issues"
#define PACKAGE_NAME "libplist"
#define PACKAGE_STRING "libplist 2.4.0"
#define PACKAGE_TARNAME "libplist"
#define PACKAGE_URL "https://libimobiledevice.org"
#endif /* CONFIG_H */

View File

@ -1,119 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<ProjectGuid>{A34F0D57-E086-45B3-9A7D-8A04CAD3AFEF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>libplist</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<OutDir>$(SolutionDir)bin\$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>WIN32;_WINDOWS;_USRDLL;LIBPLIST_EXPORTS;_CRT_SECURE_NO_WARNINGS;HAVE_CONFIG_H;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;$(ProjectDir)libcnary\include;$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<DisableSpecificWarnings>4244;4267;4146</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\base64.c" />
<ClCompile Include="src\bplist.c" />
<ClCompile Include="src\bytearray.c" />
<ClCompile Include="src\hashtable.c" />
<ClCompile Include="src\jplist.c" />
<ClCompile Include="src\jsmn.c" />
<ClCompile Include="src\oplist.c" />
<ClCompile Include="src\plist.c" />
<ClCompile Include="src\ptrarray.c" />
<ClCompile Include="src\time64.c" />
<ClCompile Include="src\xplist.c" />
<ClCompile Include="libcnary\node.c" />
<ClCompile Include="libcnary\node_list.c" />
<ClCompile Include="libcnary\cnary.c" />
<ClCompile Include="src\out-default.c" />
<ClCompile Include="src\out-limd.c" />
<ClCompile Include="src\out-plutil.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\base64.h" />
<ClInclude Include="src\bytearray.h" />
<ClInclude Include="src\hashtable.h" />
<ClInclude Include="src\jsmn.h" />
<ClInclude Include="src\plist.h" />
<ClInclude Include="src\ptrarray.h" />
<ClInclude Include="src\strbuf.h" />
<ClInclude Include="src\time64.h" />
<ClInclude Include="src\time64_limits.h" />
<ClInclude Include="include\plist\plist.h" />
<ClInclude Include="src\config.h" />
<ClInclude Include="libcnary\include\node.h" />
<ClInclude Include="libcnary\include\node_list.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

File diff suppressed because it is too large Load Diff

View File

@ -173,8 +173,9 @@ The 't' field in the snapshot's 's.id' dictionary indicates the type of snapshot
#include <iostream> // For input/output operations (cout, cin, etc.)
// Bella SDK includes - external libraries for 3D rendering
#include "../bella_scene_sdk/src/bella_sdk/bella_scene.h" // For creating and manipulating 3D scenes in Bella
#include "../bella_scene_sdk/src/dl_core/dl_main.inl" // Core functionality from the Diffuse Logic engine
#include "../bella_engine_sdk/src/bella_sdk/bella_scene.h" // For creating and manipulating 3D scenes in Bella
#include "../bella_engine_sdk/src/bella_sdk/bella_engine.h" // For Engine class and rendering functionality
#include "../bella_engine_sdk/src/dl_core/dl_main.inl" // Core functionality from the Diffuse Logic engine
#ifdef _WIN32
#include <windows.h> // For ShellExecuteW