major refactor, reorg, best plist traversal, oomer_smooth_cube, 7=glass , 8=liquid

This commit is contained in:
Harvey Fong 2025-03-30 07:24:33 -06:00
parent 468cf0e618
commit 7772f4e30b
7 changed files with 27421 additions and 1094 deletions

View File

@ -1,13 +0,0 @@
#pragma once
// Structure to hold voxel information
struct newVoxel {
uint32_t x, y, z; // 3D coordinates
uint8_t color; // Color value
};
struct dsVoxel {
uint8_t layer;
uint8_t color;
};

1584
oomer_misc.h Normal file

File diff suppressed because it is too large Load Diff

744
oomer_voxel_vmax.h Normal file
View File

@ -0,0 +1,744 @@
#pragma once
#include <map> // For std::map
#include <set> // For std::set
#include <vector> // For std::vector (you're already using this)
#include <string> // For std::string (you're already using this)
#include <cstdint> // For uint8_t
#include <fstream> // For std::ifstream and std::ofstream
#include <iostream> // For std::cerr, std::cout
#include <filesystem> // For std::filesystem functions
#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
struct VoxelRGBA {
uint8_t r, g, b, a;
};
// Read a 256x1 PNG file and return a vector of VoxelRGBA colors
std::vector<VoxelRGBA> 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<VoxelRGBA> palette;
// Read each pixel (each pixel is 4 bytes - RGBA)
for (int i = 0; i < width; i++) {
VoxelRGBA 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
};
// Create a structure to represent a model with its voxels with helper functions
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];
// Each model has local 0-7 materials
std::array<VmaxMaterial, 8> materials;
// Each model has local colors
std::array<VoxelRGBA, 256> colors;
// Constructor
VmaxModel(const std::string& modelName) : vmaxbFileName(modelName) {
}
// Add a voxel to this model
void addVoxel(int x, int y, int z, int material, int color, int chunk, int chunkMin) {
if (material >= 0 && material < 8 && color > 0 && color < 256) {
voxels[material][color].emplace_back(x, y, z, material, color, chunk, chunkMin);
}
}
// Add a materials to this model
void addMaterials(const std::array<VmaxMaterial, 8> newMaterials) {
materials = newMaterials;
}
// Add a colors to this model
void addColors(const std::array<VoxelRGBA, 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 _DEBUG2
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) {
//dsVoxel _vxVoxel; // VoxelMax data [todo] - should I use uint8_t for x, y, z?
material = dsData[i]; // also known as a layer color
//_vxVoxel.material = static_cast<int>(dsData[i]); // also known as a layer color
color = dsData[i + 1];
//_vxVoxel.color = static_cast<uint8_t>(dsData[i + 1]);
uint32_t _tempx, _tempy, _tempz;
decodeMorton3DOptimized(i/2 + mortonOffset,
_tempx,
_tempy,
_tempz); // index IS the morton code
//std::cout << "i: " << i << " _tempx: " << _tempx << " _tempy: " << _tempy << " _tempz: " << _tempz << std::endl;
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
// 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;
};
// 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>();
// Store the group
groups[groupInfo.id] = groupInfo;
}
}
// Parse objects (models)
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"];
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;
}
}
}
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 KiB

24765
thirdparty/json.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff