From e1c2743bf8a384d1dd1f56738965cb19beeed873 Mon Sep 17 00:00:00 2001 From: Harvey Fong Date: Mon, 31 Mar 2025 21:49:48 -0600 Subject: [PATCH] first working groups and instances --- makefile | 2 +- oomer_misc.h | 11 + oomer_voxel_vmax.h | 213 ++++++++++-- vmax2bella.cpp | 788 ++++++++++++++++++++++++++++++--------------- 4 files changed, 742 insertions(+), 272 deletions(-) diff --git a/makefile b/makefile index 045fa33..dce9e7b 100644 --- a/makefile +++ b/makefile @@ -77,7 +77,7 @@ CXX_FLAGS = $(COMMON_FLAGS) -std=c++17 -Wno-deprecated-declarations CPP_DEFINES = -DNDEBUG=1 -DDL_USE_SHARED # Objects -OBJECTS = vmax2bella.o extra.o debug.o +OBJECTS = vmax2bella.o OBJECT_FILES = $(patsubst %,$(OBJ_DIR)/%,$(OBJECTS)) # Build rules diff --git a/oomer_misc.h b/oomer_misc.h index 0ab12cd..264e62d 100644 --- a/oomer_misc.h +++ b/oomer_misc.h @@ -153,6 +153,17 @@ COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, (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. + +=== + +DayEnvironmentHDRI019_1K-TONEMAPPED.jpg from ambientCG.com, +licensed under the Creative Commons CC0 1.0 Universal License. + +=== +https://github.com/libimobiledevice/libplist + +This software uses libraries from the libplist project under the LGPL version 2.1. + )"; } diff --git a/oomer_voxel_vmax.h b/oomer_voxel_vmax.h index aeb5dd6..078d93f 100644 --- a/oomer_voxel_vmax.h +++ b/oomer_voxel_vmax.h @@ -1,13 +1,14 @@ #pragma once -#include // For std::map -#include // For std::set -#include // For std::vector (you're already using this) -#include // For std::string (you're already using this) -#include // For uint8_t -#include // For std::ifstream and std::ofstream -#include // For std::cerr, std::cout -#include // For std::filesystem functions +// Standard C++ library includes - these provide essential functionality +#include // For key-value pair data structures (maps) +#include // For set data structure +#include // For dynamic arrays (vectors) +#include // For std::string +#include // For fixed-size integer types (uint8_t, uint32_t, etc.) +#include // For file operations (reading/writing files) +#include // For input/output operations (cout, cin, etc.) +#include // 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 @@ -19,12 +20,172 @@ using json = nlohmann::json; #define STB_IMAGE_IMPLEMENTATION #include "thirdparty/stb_image.h" // STB Image library -struct VoxelRGBA { + + +struct VmaxVector3 { + double x, y, z; +}; + +// Converts axis-angle rotation to Euler angles (in XYZ order) +// 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: Vector3 containing Euler angles (x=pitch, y=yaw, z=roll) in radians +VmaxVector3 axisAngleToEulerXYZ(double ax, double ay, double az, double angle) { + // First normalize the axis + double length = sqrt(ax*ax + ay*ay + az*az); + if (length != 0) { + ax /= length; + ay /= length; + az /= length; + } + + // Compute sin and cos of angle + double s = sin(angle); + double c = cos(angle); + double t = 1.0 - c; + + // Build rotation matrix from axis-angle + // This is the Rodrigues' rotation formula + double m11 = t*ax*ax + c; + double m12 = t*ax*ay - s*az; + double m13 = t*ax*az + s*ay; + + double m21 = t*ax*ay + s*az; + double m22 = t*ay*ay + c; + double m23 = t*ay*az - s*ax; + + double m31 = t*ax*az - s*ay; + double m32 = t*ay*az + s*ax; + double m33 = t*az*az + c; + + // Convert rotation matrix to euler angles + VmaxVector3 euler; + + // Handle gimbal lock cases + if (m13 > 0.998) { // singularity at north pole + euler.y = atan2(m12, m22); + euler.x = M_PI/2; + euler.z = 0; + } + else if (m13 < -0.998) { // singularity at south pole + euler.y = atan2(m12, m22); + euler.x = -M_PI/2; + euler.z = 0; + } + else { + euler.x = asin(-m13); + euler.y = atan2(m23, m33); + euler.z = atan2(m12, m11); + } + + return euler; +} + +// 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) + // This is the default starting point for any transformation + 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; +} + +struct VmaxRGBA { uint8_t r, g, b, a; }; -// Read a 256x1 PNG file and return a vector of VoxelRGBA colors -std::vector read256x1PaletteFromPNG(const std::string& filename) { +// Read a 256x1 PNG file and return a vector of VmaxRGBA colors +std::vector 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); @@ -38,10 +199,10 @@ std::vector read256x1PaletteFromPNG(const std::string& filename) { std::cerr << "Warning: Expected a 256x1 image, but got " << width << "x" << height << std::endl; } // Create our palette array - std::vector palette; + std::vector palette; // Read each pixel (each pixel is 4 bytes - RGBA) for (int i = 0; i < width; i++) { - VoxelRGBA color; + VmaxRGBA color; color.r = data[i * 4]; color.g = data[i * 4 + 1]; color.b = data[i * 4 + 2]; @@ -117,7 +278,7 @@ struct VmaxModel { // Each model has local 0-7 materials std::array materials; // Each model has local colors - std::array colors; + std::array colors; // Constructor VmaxModel(const std::string& modelName) : vmaxbFileName(modelName) { @@ -136,7 +297,7 @@ struct VmaxModel { } // Add a colors to this model - void addColors(const std::array newColors) { + void addColors(const std::array newColors) { colors = newColors; } @@ -183,7 +344,7 @@ inline std::array getVmaxMaterials(plist_t pnodPalettePlist) { 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; + //std::cout << "Found materials array with " << materialsCount << " items" << std::endl; // Process each material for (uint32_t i = 0; i < materialsCount; i++) { @@ -229,7 +390,7 @@ inline std::array getVmaxMaterials(plist_t pnodPalettePlist) { } else { std::cout << "No materials array found or invalid type" << std::endl; } - #ifdef _DEBUG2 + #ifdef _DEBUG23 for (const auto& material : vmaxMaterials) { std::cout << "Material: " << material.materialName << std::endl; std::cout << " Transmission: " << material.transmission << std::endl; @@ -561,6 +722,7 @@ struct JsonGroupInfo { std::vector extentMin; std::vector extentMax; bool selected = false; + std::string parentId; }; // Class to parse VoxelMax's scene.json @@ -616,12 +778,14 @@ public: // Check if selected if (group.contains("s")) groupInfo.selected = group["s"].get(); + if (group.contains("pid")) groupInfo.parentId = group["pid"]; // Store the group groups[groupInfo.id] = groupInfo; } } - // Parse objects (models) + // 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; @@ -632,7 +796,7 @@ public: if (obj.contains("n")) modelInfo.name = obj["n"]; // Extract file paths - if (obj.contains("data")) modelInfo.dataFile = obj["data"]; + 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"]; @@ -714,7 +878,7 @@ public: 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; @@ -739,6 +903,15 @@ public: << model.position[1] << ", " << model.position[2] << "]" << std::endl; } + + if (!model.rotation.empty()) { + VmaxVector3 VmaxEuler = axisAngleToEulerXYZ(model.rotation[0], model.rotation[1], model.rotation[2], model.rotation[3]); + std::cout << " Rotation: [" + << VmaxEuler.x << ", " + << VmaxEuler.y << ", " + << VmaxEuler.z << "]" << std::endl; + } } + */ } }; diff --git a/vmax2bella.cpp b/vmax2bella.cpp index 8dcb9eb..0862ce8 100644 --- a/vmax2bella.cpp +++ b/vmax2bella.cpp @@ -1,26 +1,274 @@ -#include -#include "../bella_scene_sdk/src/bella_sdk/bella_scene.h" -#include "../bella_scene_sdk/src/dl_core/dl_main.inl" +// vmax2bella.cpp - A program to convert VoxelMax (.vmax) files to Bella 3D scene (.bsz) files +// +// This program reads VoxelMax files (which store voxel-based 3D models) and +// converts them to Bella (a 3D rendering engine) scene files. -#include "oomer_voxel_vmax.h" // common voxel code and structures -#include "oomer_misc.h" +/* +# Technical Specification: VoxelMax Format -int addModelToScene(dl::bella_sdk::Scene& belScene, const VmaxModel& vmaxModel, const std::vector& vmaxPalette, const std::array& vmaxMaterial); +## Overview +- This document specifies a chunked voxel storage format embedded in property list (plist) files. The format provides an efficient representation of 3D voxel data through a combination of Morton-encoded spatial indexing and a sparse representation approach. + +## File Structure +- Format: Property List (plist) +- Structure: Hierarchical key-value structure with nested dictionaries and arrays +- plist is compressed using the LZFSE, an open source reference c implementation is [here](https://github.com/lzfse/lzfse) + +``` +root +└── snapshots (array) + └── Each snapshot (dictionary) + ├── s (dictionary) - Snapshot data + │ ├── id (dictionary) - Identifiers + │ │ ├── c (int64) - Chunk ID + │ │ ├── s (int64) - Session ID + │ │ └── t (int64) - Type ID + │ ├── lc (binary data) - Layer Color Usage + │ ├── ds (binary data) - Voxel data stream + │ ├── dlc (binary data) - Deselected Layer Color Usage + │ └── st (dictionary) - Statistics/metadata + │ ├── c (int64) - Count of voxels in the chunk + │ ├── sc (int64) - Selected Count (number of selected voxels) + │ ├── smin (array) - Selected Minimum coordinates [x,y,z,w] + │ ├── smax (array) - Selected Maximum coordinates [x,y,z,w] + │ ├── min (array) - Minimum coordinates of all voxels [x,y,z] + │ ├── max (array) - Maximum coordinates of all voxels [x,y,z] + │ └── e (dictionary) - Extent + │ ├── o (array) - Origin/reference point [x,y,z] + │ └── s (array) - Size/dimensions [width,height,depth] +``` + +## Chunking System +### Volume Organization +- The total volume is divided into chunks for efficient storage and manipulation +- Standard chunk size: 32×32×32 voxels +- Total addressable space: 256×256×256 voxels (8×8×8 chunks) + +### Morton Encoding for Chunks +- Chunk IDs are encoded using 24 bits (8 bits per dimension) +- This provides full addressability for the 8×8×8 chunks without requiring sequential traversal +- The decodeMortonChunkID function extracts x, y, z coordinates from a Morton-encoded chunk ID stored in s.id.c +- The resulting chunk coordinates are then multiplied by 32 to get the world position of the chunk + +### Voxel-Level Hybrid Encoding +- Within each 32×32×32 chunk, voxels use a hybrid addressing system +- The format uses a hybrid encoding approach that combines sequential traversal and Morton encoding: +- st.min store and offset from origin of 32x32x32 chunk +- iterate through all voxels in chunk x=0 to 31, y=0 to 31, z=0 to 31 it that order +- start at origin (0,0,0) with a counter= 0 +- do counter + st.min and decode this morton value to get x,y,z + +### Chunk Addressing +- Chunks are only stored if they contain at least one non-empty voxel +- Each snapshot contains data for a specific chunk, identified by the 'c' value in the 's.id' dictionary + +## Data Fields +### Voxel Data Stream (ds) +- Variable-length binary data +- Contains pairs of bytes for each voxel: [layer_byte, color_byte] +- Each chunk can contain up to 32,768 voxels (32×32×32) +- *Position Byte:* + - The format uses a hybrid encoding approach that combines sequential traversal and Morton encoding: + - Data stream can terminate at any point, avoiding the need to store all 32,768 voxel pairs + +### Morton Encoding Process +- A space-filling curve that interleaves the bits of the x, y, and z coordinates +- Used to convert 3D coordinates to a 1D index and vice versa +- Creates a coherent ordering of voxels that preserves spatial locality +1. Take the binary representation of x, y, and z coordinates +2. Interleave the bits in the order: z₀, y₀, x₀, z₁, y₁, x₁, z₂, y₂, x₂, ... +3. The resulting binary number is the Morton code + +- *Color Byte:* + - Stores the color value + 1 (offset of +1 from actual color) + - Value 0 indicates no voxel at this position +- A fully populated chunk will have 32,768 voxel pairs (65,536 bytes total in ds) + +### Snapshot Accumulation +- Each snapshot contains data for a specific chunk (identified by the chunk ID) +- Multiple snapshots together build up the complete voxel model +- Later snapshots for the same chunk ID overwrite earlier ones, allowing for edits over time + +### Layer Color Usage (lc) +- s.lc is a summary table (256 bytes) that tracks which colors are used anywhere in the chunk +- Each byte position (0-255) corresponds to a color palette ID +- [TODO] understand why the word layer color is used, what is a layer color + +### Deselected Layer Color Usage (dlc) +- Optional 256-byte array +- Used during editing to track which color layers the user has deselected +- Primarily for UI state preservation rather than 3D model representation + +### Statistics Data (st) +- Dictionary containing metadata about the voxels in a chunk: + - c (count): Total number of voxels in the chunk + - sc (selectedCount): Number of currently selected voxels + - sMin (selectedMin): Array defining minimum coordinates of current selection [x,y,z,w] + - sMax (selectedMax): Array defining maximum coordinates of current selection [x,y,z,w] + - min: Array defining minimum coordinates of all voxels [x,y,z] + - max: Array defining maximum coordinates of all voxels [x,y,z] + - e (extent): Array defining the bounding box [min_x, min_y, min_z, max_x, max_y, max_z] + - e.o (extent.origin): Reference point or offset for extent calculations + +## Coordinate Systems +### Primary Coordinate System +- Y-up coordinate system: Y is the vertical axis +- Origin (0,0,0) is at the bottom-left-front corner +- Coordinates increase toward right (X+), up (Y+), and backward (Z+) + +### Addressing Scheme +1. World Space: Absolute coordinates in the full volume +2. Chunk Space: Which chunk contains a voxel (chunk_x, chunk_y, chunk_z) +3. Local Space: Coordinates within a chunk (local_x, local_y, local_z) + +## Coordinate Conversion +- *World to Chunk:* + - chunk_x = floor(world_x / 32) + - chunk_y = floor(world_y / 32) + - chunk_z = floor(world_z / 32) +- *World to Local:* + - local_x = world_x % 32 + - local_y = world_y % 32 + - local_z = world_z % 32 +- *Chunk+Local to World:* + - world_x = chunk_x * 32 + local_x + - world_y = chunk_y * 32 + local_y + - world_z = chunk_z * 32 + local_z + +## Implementation Guidance +### Reading Algorithm +1. Parse the plist file to access the snapshot array +2. For each snapshot: + a. Extract the chunk ID from s > id > c + b. Extract the lc and ds data + c. Process the ds data in pairs of bytes (position, color) + d. Calculate the world origin by decoding the Morton chunk ID and multiplying by 32 + e. Store the voxels for this chunk ID +3. Combine all snapshots to build the complete voxel model, using the chunk IDs as keys + +### Writing Algorithm +1. Organize voxels by chunk (32×32×32 voxels per chunk) +2. For each non-empty chunk: + a. Create a snapshot entry + b. Set up the id dictionary with the appropriate chunk ID + c. Set up a 256-byte lc array (all zeros) + d. Create the ds data by encoding each voxel as a (position, color+1) pair + e. Set the appropriate byte in lc to 1 if the color is used in ds +3. Add all snapshots to the array +4. Write the complete structure to a plist file + +- [?] Models typically use SessionIDs to group related edits (observed values include 10 and 18) + +## Snapshot Types +The 't' field in the snapshot's 's.id' dictionary indicates the type of snapshot: + - 0: underRestore - Snapshot being restored from a previous state + - 1: redoRestore - Snapshot being restored during a redo operation + - 2: undo - Snapshot created for an undo operation + - 3: redo - Snapshot created for a redo operation + - 4: checkpoint - Snapshot created as a regular checkpoint during editing (most common) + - 5: selection - Snapshot representing a selection operation + +*/ + +#include // 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 "oomer_voxel_vmax.h" // common vmax voxel code and structures +#include "oomer_misc.h" // common misc code + +dl::bella_sdk::Node essentialsToScene(dl::bella_sdk::Scene& belScene); +dl::bella_sdk::Node addModelToScene(dl::bella_sdk::Scene& belScene, dl::bella_sdk::Node& belWorld, const VmaxModel& vmaxModel, const std::vector& vmaxPalette, const std::array& vmaxMaterial); int DL_main(dl::Args& args) { - args.add("he", "hello", "world", "Print a hello message with custom text"); - args.add("vi", "voxin", "voxin", "voxin"); + args.add("i", "input", "", "vmax directory or vmax.zip file"); + args.add("o", "output", "", "set output bella file name"); + args.add("tp", "thirdparty", "", "prints third party licenses"); + args.add("li", "licenseinfo", "", "prints license info"); - dl::String bszName; - dl::String vmaxDirName; - if (args.have("--voxin")) + // If --help was requested, print help and exit + if (args.helpRequested()) { + std::cout << args.help("vmax2bella © 2025 Harvey Fong","vmax2bella", "1.0") << std::endl; + return 0; + } + + // If --licenseinfo was requested, print license info and exit + if (args.have("--licenseinfo")) { - vmaxDirName = args.value("--voxin"); - bszName = vmaxDirName.replace("vmax", "bsa"); + std::cout << initializeGlobalLicense() << std::endl; + return 0; + } + + // If --thirdparty was requested, print third-party licenses and exit + if (args.have("--thirdparty")) + { + std::cout << initializeGlobalThirdPartyLicences() << std::endl; + return 0; + } + if (args.have("--input")) + { + dl::String bszName; + dl::String vmaxDirName; + vmaxDirName = args.value("--input"); + bszName = vmaxDirName.replace("vmax", "bsz"); + + // Create a new scene + dl::bella_sdk::Scene belScene; + belScene.loadDefs(); + auto belWorld = belScene.world(true); + + // Parse the scene.json file to get the models JsonVmaxSceneParser vmaxSceneParser; vmaxSceneParser.parseScene((vmaxDirName+"/scene.json").buf()); - auto models = vmaxSceneParser.getModels(); + //auto models = vmaxSceneParser.getModels(); + vmaxSceneParser.printSummary(); + std::map jsonGroups = vmaxSceneParser.getGroups(); + std::map belGroupNodes; // Map of UUID to bella node + std::map belCanonicalNodes; // Map of UUID to bella node + + // First pass to create all the Bella nodes for the groups + for (const auto& [groupName, groupInfo] : jsonGroups) { + dl::String belGroupUUID = dl::String(groupName.c_str()); + belGroupUUID = belGroupUUID.replace("-", "_"); // Make sure the group name is valid for a Bella node name + belGroupUUID = "_" + belGroupUUID; // Make sure the group name is valid for a Bella node name + belGroupNodes[belGroupUUID] = belScene.createNode("xform", belGroupUUID, belGroupUUID); // Create a Bella node for the group + VmaxMatrix4x4 objectMat4 = axisAngleToMatrix4x4( groupInfo.rotation[0], + groupInfo.rotation[1], + groupInfo.rotation[2], + groupInfo.rotation[3]); + VmaxMatrix4x4 objectTransMat4 = VmaxMatrix4x4(); + //std::vector translation = extentCenter-position; + objectTransMat4 = objectTransMat4.createTranslation(groupInfo.position[0], + groupInfo.position[1], + groupInfo.position[2]); + objectMat4 = objectMat4 * objectTransMat4; + + belGroupNodes[belGroupUUID]["steps"][0]["xform"] = dl::Mat4({ + objectMat4.m[0][0], objectMat4.m[0][1], objectMat4.m[0][2], objectMat4.m[0][3], + objectMat4.m[1][0], objectMat4.m[1][1], objectMat4.m[1][2], objectMat4.m[1][3], + objectMat4.m[2][0], objectMat4.m[2][1], objectMat4.m[2][2], objectMat4.m[2][3], + objectMat4.m[3][0], objectMat4.m[3][1], objectMat4.m[3][2], objectMat4.m[3][3] + }); + } + + // json file is allowed the parent to be defined after the child, requiring us to create all the bella nodes before we can parent them + for (const auto& [groupName, groupInfo] : jsonGroups) { + dl::String belGroupUUID = dl::String(groupName.c_str()); + belGroupUUID = belGroupUUID.replace("-", "_"); + belGroupUUID = "_" + belGroupUUID; + if (groupInfo.parentId == "") { + belGroupNodes[belGroupUUID].parentTo(belWorld); // Group without a parent is a child of the world + } else { + dl::String belPPPGroupUUID = dl::String(groupInfo.parentId.c_str()); + belPPPGroupUUID = belPPPGroupUUID.replace("-", "_"); + belPPPGroupUUID = "_" + belPPPGroupUUID; + dl::bella_sdk::Node myParentGroup = belGroupNodes[belPPPGroupUUID]; // Get bella obj + belGroupNodes[belGroupUUID].parentTo(myParentGroup); // Group underneath a group + } + } // Efficiently process unique models by examining only the first instance of each model type. // Example: If we have 100 instances of 3 different models: @@ -28,67 +276,51 @@ int DL_main(dl::Args& args) { // "model2.vmaxb": [instance1, ..., instance30], // "model3.vmaxb": [instance1, ..., instance20] // This loop runs only 3 times (once per unique model), not 100 times (once per instance) - //std::map> modelVmaxbMap = vmaxSceneParser.getModelContentVMaxbMap(); + auto modelVmaxbMap = vmaxSceneParser.getModelContentVMaxbMap(); - for (auto& model : modelVmaxbMap) { - for (const auto& [vmaxContentName, vmaxModelList] : modelVmaxbMap) { - std::cout << "model: " << vmaxContentName << std::endl; - const auto& jsonModelInfo = vmaxModelList.front(); // get the first model, others are instances at the scene level - std::cout << "name: " << jsonModelInfo.name << std::endl; - std::cout << "content: " << jsonModelInfo.dataFile << std::endl; - std::cout << "palette: " << jsonModelInfo.paletteFile << std::endl; - dl::String materialName = vmaxDirName + "/" + jsonModelInfo.paletteFile.c_str(); - materialName = materialName.replace(".png", ".settings.vmaxpsb"); - std::cout << "material: " << materialName << std::endl; - plist_t pnod_material = readPlist(materialName.buf(),false); - std::cout << "pnod_material: " << pnod_material << std::endl; - std::array vmaxMaterials = getVmaxMaterials(pnod_material); - } - } - } + std::vector allModels; + std::vector> vmaxPalettes; // one palette per model + std::vector> vmaxMaterials; // one material per model + //std::vector> allMaterials; // one material per model + //std::vector> allPalettes; - if (args.helpRequested()) { - std::cout << args.help("Hello App", "hello", "1.0") << std::endl; - return 0; - } - - if (args.have("--hello")) { - dl::String helloText = args.value("--hello", "world"); - } - - // Create a new scene - dl::bella_sdk::Scene belScene; - belScene.loadDefs(); - - // Root node - auto belWorld = belScene.world(true); - - JsonVmaxSceneParser vmaxSceneParser; - vmaxSceneParser.parseScene((vmaxDirName+"/scene.json").buf()); - auto models = vmaxSceneParser.getModels(); - - // Efficiently process unique models by examining only the first instance of each model type. - // Example: If we have 100 instances of 3 different models: - // "model1.vmaxb": [instance1, instance2, ..., instance50], - // "model2.vmaxb": [instance1, ..., instance30], - // "model3.vmaxb": [instance1, ..., instance20] - // This loop runs only 3 times (once per unique model), not 100 times (once per instance) - //std::map> modelVmaxbMap = vmaxSceneParser.getModelContentVMaxbMap(); - - auto modelVmaxbMap = vmaxSceneParser.getModelContentVMaxbMap(); - - std::vector> vmaxPalettes; - std::vector> vmaxMaterials; - std::vector allModels; - std::vector> allMaterials; // dynamic vector of fixed arrays - std::vector> allPalettes; - for (auto& model : modelVmaxbMap) { + essentialsToScene(belScene); // create the basic scene elements in Bella + + // Loop over each model defined in scene.json and process the first instance + // This will be out canonical models, not instances + // todo rename model to objects as per vmax for (const auto& [vmaxContentName, vmaxModelList] : modelVmaxbMap) { - std::cout << "model: " << vmaxContentName << std::endl; + std::cout << "vmaxContentName: " << vmaxContentName << std::endl; + VmaxModel currentVmaxModel(vmaxContentName); const auto& jsonModelInfo = vmaxModelList.front(); // get the first model, others are instances at the scene level - std::cout << "name: " << jsonModelInfo.name << std::endl; - std::cout << "content: " << jsonModelInfo.dataFile << std::endl; - std::cout << "palette: " << jsonModelInfo.paletteFile << std::endl; + std::vector position = jsonModelInfo.position; + std::vector rotation = jsonModelInfo.rotation; + std::vector scale = jsonModelInfo.scale; + std::vector extentCenter = jsonModelInfo.extentCenter; + + //what is this for? + /* + auto jsonParentId = jsonModelInfo.parentId; + auto belParentId = dl::String(jsonParentId.c_str()); + dl::String belParentGroupUUID = belParentId.replace("-", "_"); + if (jsonParentId != "") { + belGroupNodes[belParentGroupUUID].parentTo(belWorld); + } + */ + + VmaxMatrix4x4 modelMatrix = axisAngleToMatrix4x4(rotation[0], rotation[1], rotation[2], rotation[3]); + VmaxMatrix4x4 transMatrix = VmaxMatrix4x4(); + //std::vector translation = extentCenter-position; + //transMatrix = transMatrix.createTranslation(extentCenter[0]-position[0], extentCenter[1]-position[1], extentCenter[2]-position[2]); + transMatrix = transMatrix.createTranslation(position[0], + position[1], + position[2]); + modelMatrix = transMatrix*modelMatrix; + //std::cout << modelMatrix.m[0][0] << "," << modelMatrix.m[0][1] << "," << modelMatrix.m[0][2] << "," << modelMatrix.m[0][3] << std::endl; + //std::cout << modelMatrix.m[1][0] << "," << modelMatrix.m[1][1] << "," << modelMatrix.m[1][2] << "," << modelMatrix.m[1][3] << std::endl; + //std::cout << modelMatrix.m[2][0] << "," << modelMatrix.m[2][1] << "," << modelMatrix.m[2][2] << "," << modelMatrix.m[2][3] << std::endl; + //std::cout << modelMatrix.m[3][0] << "," << modelMatrix.m[3][1] << "," << modelMatrix.m[3][2] << "," << modelMatrix.m[3][3] << std::endl; + // Get file names dl::String materialName = vmaxDirName + "/" + jsonModelInfo.paletteFile.c_str(); materialName = materialName.replace(".png", ".settings.vmaxpsb"); @@ -96,35 +328,19 @@ int DL_main(dl::Args& args) { // Get this models colors from the paletteN.png dl::String pngName = vmaxDirName + "/" + jsonModelInfo.paletteFile.c_str(); vmaxPalettes.push_back(read256x1PaletteFromPNG(pngName.buf())); // gather all models palettes - allPalettes.push_back(read256x1PaletteFromPNG(pngName.buf())); // gather all models palettes + //allPalettes.push_back(read256x1PaletteFromPNG(pngName.buf())); // gather all models palettes if (vmaxPalettes.empty()) { throw std::runtime_error("Failed to read palette from: png " ); } - // lstSnapshots contains ALL snapshots - // if a chunkID is present more than once it holds the past history of a snapshot's state - // The playback of each of these snapshots is the entire creation history of a chunk - // Therefore a chunkID's last snapshot is the latest user edit - // Therefore we need to process each chunkdID's snapshot in reverse order and ignore - // all previous snapshots for that chunkID unless we need animation - // [todo] implement animation - // Read contentsN.vmaxb plist file, lzfse compressed - dl::String modelName2 = vmaxDirName + "/" + jsonModelInfo.dataFile.c_str(); - plist_t plist_model_root = readPlist(modelName2.buf(), true); // decompress=true + dl::String modelFileName = vmaxDirName + "/" + jsonModelInfo.dataFile.c_str(); + plist_t plist_model_root = readPlist(modelFileName.buf(), true); // decompress=true plist_t plist_snapshots_array = plist_dict_get_item(plist_model_root, "snapshots"); uint32_t snapshots_array_size = plist_array_get_size(plist_snapshots_array); - std::cout << "snapshots_array_size: " << snapshots_array_size << std::endl; + //std::cout << "snapshots_array_size: " << snapshots_array_size << std::endl; // Create a VmaxModel object - VmaxModel currentVmaxModel(vmaxContentName); - - // 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); - // } - //} - + //VmaxModel currentVmaxModel(vmaxContentName); for (uint32_t i = 0; i < snapshots_array_size; i++) { plist_t plist_snapshot = plist_array_get_item(plist_snapshots_array, i); plist_t plist_chunk = getNestedPlistNode(plist_snapshot, {"s", "id", "c"}); @@ -132,13 +348,13 @@ int DL_main(dl::Args& args) { uint64_t chunkID; plist_get_uint_val(plist_chunk, &chunkID); VmaxChunkInfo chunkInfo = vmaxChunkInfo(plist_snapshot); - std::cout << "\nChunkID: " << chunkInfo.id << std::endl; - std::cout << "TypeID: " << chunkInfo.type << std::endl; - std::cout << "MortonCode: " << chunkInfo.mortoncode << "\n" < xvoxels = vmaxVoxelInfo(plist_datastream, chunkInfo.id, chunkInfo.mortoncode); - std::cout << "xxxvoxels: " << xvoxels.size() << std::endl; + //std::cout << "xxxvoxels: " << xvoxels.size() << std::endl; for (const auto& voxel : xvoxels) { currentVmaxModel.addVoxel(voxel.x, voxel.y, voxel.z, voxel.material, voxel.palette ,chunkInfo.id, chunkInfo.mortoncode); @@ -149,75 +365,114 @@ int DL_main(dl::Args& args) { plist_t plist_material = readPlist(materialName.buf(),false); // decompress=false std::array currentMaterials = getVmaxMaterials(plist_material); vmaxMaterials.push_back(currentMaterials); - allMaterials.push_back(currentMaterials); - //allPalettes.push_back(vmaxPalettes); - int modelIndex=0; - // Need to access voxles by material and color groupings - for (const auto& eachModel : allModels) { - if (modelIndex == 0) { - std::cout << "Model: " << eachModel.vmaxbFileName << std::endl; - std::cout << "Voxel Count Model: " << eachModel.getTotalVoxelCount() << std::endl; - - addModelToScene(belScene, eachModel, allPalettes[modelIndex], allMaterials[modelIndex]); - std::map> materialColorMap = eachModel.getUsedMaterialsAndColors(); - //auto materialColorMap = eachModel.getUsedMaterialsAndColors(); - for (const auto& [eachMaterial, eachColorPalette] : materialColorMap) { - // Create Bella material for this material type - // ... your Bella material creation code here ... - std::cout << eachMaterial << std::endl; - // Iterate through each color used by this material - for (int eachColor : eachColorPalette) { - // Get all voxels for this material/color combination - const std::vector& voxelsOfType = eachModel.getVoxels(eachMaterial, eachColor); - std::cout << "voxelsOfType: " << voxelsOfType.size() << std::endl; - // Now voxelsOfType contains all voxels sharing this material/color - // Create a single Bella object for all these voxels - // ... your Bella object creation code here ... - } - } - } - modelIndex++; - } } + //} + int modelIndex=0; + // Need to access voxles by material and color groupings + // Models are canonical models, not instances + // Vmax objects are instances of models + + // First create canonical models and they are NOT attached to belWorld + for (const auto& eachModel : allModels) { + //if (modelIndex == 0) { // only process the first model + std::cout << modelIndex << "Model: " << eachModel.vmaxbFileName << std::endl; + std::cout << "Voxel Count Model: " << eachModel.getTotalVoxelCount() << std::endl; + + dl::bella_sdk::Node belModel = addModelToScene(belScene, belWorld, eachModel, vmaxPalettes[modelIndex], vmaxMaterials[modelIndex]); + // TODO add to a map of canonical models + dl::String lllmodelName = dl::String(eachModel.vmaxbFileName.c_str()); + dl::String lllcanonicalName = lllmodelName.replace(".vmaxb", ""); + std::cout << "========lllcanonicalName: " << lllcanonicalName.buf() << std::endl; + belCanonicalNodes[lllcanonicalName.buf()] = belModel; + modelIndex++; + } + + // Second Loop through each vmax object and create an instance of the canonical model + // This is the instances of the models, we did a pass to create the canonical models earlier + for (const auto& [vmaxContentName, vmaxModelList] : modelVmaxbMap) { + //std::cout << "model: " << vmaxContentName << std::endl; + VmaxModel currentVmaxModel(vmaxContentName); + for(const auto& jsonModelInfo : vmaxModelList) { + std::vector position = jsonModelInfo.position; + std::vector rotation = jsonModelInfo.rotation; + std::vector scale = jsonModelInfo.scale; + std::vector extentCenter = jsonModelInfo.extentCenter; + auto jsonParentId = jsonModelInfo.parentId; + auto belParentId = dl::String(jsonParentId.c_str()); + dl::String belParentGroupUUID = belParentId.replace("-", "_"); + belParentGroupUUID = "_" + belParentGroupUUID; + + auto belObjectId = dl::String(jsonModelInfo.id.c_str()); + belObjectId = belObjectId.replace("-", "_"); + belObjectId = "_" + belObjectId; + + dl::String getCanonicalName = dl::String(jsonModelInfo.dataFile.c_str()); + dl::String canonicalName = getCanonicalName.replace(".vmaxb", ""); + //get bel node from canonical name + auto belCanonicalNode = belCanonicalNodes[canonicalName.buf()]; + // create a new xform + auto foofoo = belScene.findNode(canonicalName); + + VmaxMatrix4x4 objectMat4 = axisAngleToMatrix4x4( rotation[0], + rotation[1], + rotation[2], + rotation[3]); + VmaxMatrix4x4 objectTransMat4 = VmaxMatrix4x4(); + //std::vector translation = extentCenter-position; + /*objectTransMat4 = objectTransMat4.createTranslation(extentCenter[0]-position[0], + extentCenter[1]-position[1], + extentCenter[2]-position[2]);*/ + objectTransMat4 = objectTransMat4.createTranslation(position[0], + position[1], + position[2]); + objectMat4 = objectMat4 * objectTransMat4; + + auto belNodeObjectInstance = belScene.createNode("xform", belObjectId, belObjectId); + belNodeObjectInstance["steps"][0]["xform"] = dl::Mat4({ + objectMat4.m[0][0], objectMat4.m[0][1], objectMat4.m[0][2], objectMat4.m[0][3], + objectMat4.m[1][0], objectMat4.m[1][1], objectMat4.m[1][2], objectMat4.m[1][3], + objectMat4.m[2][0], objectMat4.m[2][1], objectMat4.m[2][2], objectMat4.m[2][3], + objectMat4.m[3][0], objectMat4.m[3][1], objectMat4.m[3][2], objectMat4.m[3][3] + }); + + if (jsonParentId == "") { + belNodeObjectInstance.parentTo(belScene.world()); + } else { + belNodeObjectInstance.parentTo(belGroupNodes[belParentGroupUUID]); + } + foofoo.parentTo(belNodeObjectInstance); + } + } + + // Write Bella File .bsz=compressed .bsa=ascii .bsx=binary + belScene.write(bszName.buf()); } - - - // Write Bella File .bsz=compressed .bsa=ascii .bsx=binary - std::cout << "writing to: " << bszName.buf() << std::endl; - belScene.write(bszName.buf()); - //writeBszScene(bszName.buf(), AllModels[0], vmaxPalettes); return 0; } -int addModelToScene(dl::bella_sdk::Scene& belScene, const VmaxModel& vmaxModel, const std::vector& vmaxPalette, const std::array& vmaxMaterial) { - // Create a new Bella scene +// @param belScene - the scene to create the essentials in +// @return - the world node +dl::bella_sdk::Node essentialsToScene(dl::bella_sdk::Scene& belScene) { // Create the basic scene elements in Bella - // Each line creates a different type of node in the scene - auto belBeautyPass = belScene.createNode("beautyPass","beautyPass1","beautyPass1"); - auto belCamForm = belScene.createNode("xform","cameraXform1","cameraXform1"); - auto belCam = belScene.createNode("camera","camera1","camera1"); - auto belSensor = belScene.createNode("sensor","sensor1","sensor1"); - auto belLens = belScene.createNode("thinLens","thinLens1","thinLens1"); - auto belImageDome = belScene.createNode("imageDome","imageDome1","imageDome1"); - auto belGroundPlane = belScene.createNode("groundPlane","groundPlane1","groundPlane1"); - auto belVoxel = belScene.createNode("box","box1","box1"); - auto belVoxelForm = belScene.createNode("xform","voxelXform1","voxelXform1"); - auto belVoxelMat = belScene.createNode("orenNayar","voxelMat1","voxelMat1"); - auto belGroundMat = belScene.createNode("quickMaterial","groundMat1","groundMat1"); - auto belSun = belScene.createNode("sun","sun1","sun1"); - auto belColorDome = belScene.createNode("colorDome","colorDome1","colorDome1"); - auto oomerSmoothCube = belScene.createNode("mesh", "oomerSmoothCube"); - - // Set up the scene with an EventScope - // EventScope groups multiple changes together for efficiency + // Each line creates a different type of node in the scene auto belBeautyPass = belScene.createNode("beautyPass","oomerBeautyPass","oomerBeautyPass"); + auto belWorld = belScene.world(); // Get scene world root { - #include "smoothcube.h" dl::bella_sdk::Scene::EventScope es(belScene); - auto belSettings = belScene.settings(); // Get scene settings - auto belWorld = belScene.world(); // Get scene world root + auto belCamForm = belScene.createNode("xform","oomerCameraXform","oomerCameraXform"); + auto belCam = belScene.createNode("camera","oomerCamera","oomerCamera"); + auto belSensor = belScene.createNode("sensor","oomerSensor","oomerSensor"); + auto belLens = belScene.createNode("thinLens","oomerThinLens","oomerThinLens"); + auto belImageDome = belScene.createNode("imageDome","oomerImageDome","oomerImageDome"); + auto belGroundPlane = belScene.createNode("groundPlane","oomerGroundPlane","oomerGroundPlane"); + + auto belBeautyPass = belScene.createNode("beautyPass","oomerBeautyPass","oomerBeautyPass"); + auto belGroundMat = belScene.createNode("quickMaterial","oomerGroundMat","oomerGroundMat"); + auto belSun = belScene.createNode("sun","oomerSun","oomerSun"); + auto belColorDome = belScene.createNode("colorDome","oomerColorDome","oomerColorDome"); + auto belSettings = belScene.settings(); // Get scene settings // Configure camera belCam["resolution"] = dl::Vec2 {1920, 1080}; // Set resolution to 1080p belCam["lens"] = belLens; // Connect camera to lens @@ -237,7 +492,6 @@ int addModelToScene(dl::bella_sdk::Scene& belScene, const VmaxModel& vmaxModel, belColorDome["zenith"] = dl::Rgba{1.0f, 1.0f, 1.0f, 1.0f}; belColorDome["horizon"] = dl::Rgba{.85f, 0.76f, 0.294f, 1.0f}; belColorDome["altitude"] = 14.0f; - // Configure ground plane belGroundPlane["elevation"] = -.5f; belGroundPlane["material"] = belGroundMat; @@ -252,16 +506,6 @@ int addModelToScene(dl::bella_sdk::Scene& belScene, const VmaxModel& vmaxModel, belGroundMat["roughness"] = 22.0f; belGroundMat["color"] = dl::Rgba{0.138431623578, 0.5, 0.3, 1.0}; - // Configure voxel box dimensions - belVoxel["radius"] = 0.33f; - belVoxel["sizeX"] = 0.99f; - belVoxel["sizeY"] = 0.99f; - belVoxel["sizeZ"] = 0.99f; - belVoxel.parentTo(belVoxelForm); - belVoxelForm["steps"][0]["xform"] = dl::Mat4 {0.999,0,0,0,0,0.999,0,0,0,0,0.999,0,0,0,0,1}; - belVoxelMat["reflectance"] = dl::Rgba{0.0, 0.0, 0.0, 1.0}; - belVoxelForm["material"] = belVoxelMat; - // Set up scene settings belSettings["beautyPass"] = belBeautyPass; belSettings["camera"] = belCam; @@ -271,102 +515,144 @@ int addModelToScene(dl::bella_sdk::Scene& belScene, const VmaxModel& vmaxModel, belSettings["groundPlane"] = belGroundPlane; belSettings["iprNavigation"] = "maya"; // Use Maya-like navigation in viewer //settings["sun"] = sun; + + auto belVoxel = belScene.createNode("box","oomerVoxel","oomerVoxel"); + auto belLiqVoxel = belScene.createNode("box","oomerLiqVoxel","oomerLiqVoxel"); + auto belVoxelForm = belScene.createNode("xform","oomerVoxelXform","oomerVoxelXform"); + auto belLiqVoxelForm = belScene.createNode("xform","oomerLiqVoxelXform","oomerLiqVoxelXform"); + auto belVoxelMat = belScene.createNode("orenNayar","oomerVoxelMat","oomerVoxelMat"); + auto belMeshVoxel = belScene.createNode("mesh", "oomerMeshVoxel"); + #include "resources/smoothcube.h" + // Configure voxel box dimensions + belVoxel["radius"] = 0.33f; + belVoxel["sizeX"] = 0.99f; + belVoxel["sizeY"] = 0.99f; + belVoxel["sizeZ"] = 0.99f; + + // Less gap to make liquid look better, allows more light to pass through + belLiqVoxel["sizeX"] = 0.99945f; + belLiqVoxel["sizeY"] = 0.99945f; + belLiqVoxel["sizeZ"] = 0.99945f; + + belVoxel.parentTo(belVoxelForm); + belVoxelForm["steps"][0]["xform"] = dl::Mat4 {0.999,0,0,0,0,0.999,0,0,0,0,0.999,0,0,0,0,1}; + belVoxelMat["reflectance"] = dl::Rgba{0.0, 0.0, 0.0, 1.0}; + belVoxelForm["material"] = belVoxelMat; + + } + return belWorld; +} + + +// Only add the canonical model to the scene +// We'll use xforms to instance the model +// Each model is stores in contentsN.vmaxb as a lzfe compressed plist +// Each model has a paletteN.png that maps 0-255 to colors +// The model stored in contentsN.vmaxb can have mulitple snapshots +// Each snapshot contains a chunkID, and a datastream +// The datastream contains the voxels for the snapshot +// The voxels are stored in chunks, each chunk is 8x8x8 voxels +// The chunks are stored in a morton order +dl::bella_sdk::Node addModelToScene(dl::bella_sdk::Scene& belScene, dl::bella_sdk::Node& belWorld, const VmaxModel& vmaxModel, const std::vector& vmaxPalette, const std::array& vmaxMaterial) { // Create Bella scene nodes for each voxel int i = 0; dl::String modelName = dl::String(vmaxModel.vmaxbFileName.c_str()); - auto modelXform = belScene.createNode("xform", modelName, modelName); - modelXform.parentTo(belScene.world()); + dl::String canonicalName = modelName.replace(".vmaxb", ""); + dl::bella_sdk::Node belCanonicalNode; + { + dl::bella_sdk::Scene::EventScope es(belScene); - for (const auto& [material, colorID] : vmaxModel.getUsedMaterialsAndColors()) { - for (int color : colorID) { - auto belInstancer = belScene.createNode("instancer", - dl::String("instMat") + dl::String(material) + dl::String("Color") + dl::String(color)); - auto xformsArray = dl::ds::Vector(); - belInstancer.parentTo(modelXform); + auto belVoxel = belScene.findNode("oomerVoxel"); + auto belLiqVoxel = belScene.findNode("oomerLiqVoxel"); + auto belMeshVoxel = belScene.findNode("oomerMeshVoxel"); + auto belVoxelForm = belScene.findNode("oomerVoxelXform"); + auto belLiqVoxelForm = belScene.findNode("oomerLiqVoxelXform"); + + auto modelXform = belScene.createNode("xform", canonicalName, canonicalName); + modelXform["steps"][0]["xform"] = dl::Mat4 {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1}; + for (const auto& [material, colorID] : vmaxModel.getUsedMaterialsAndColors()) { + for (int color : colorID) { + auto belInstancer = belScene.createNode("instancer", + canonicalName + dl::String("Material") + dl::String(material) + dl::String("Color") + dl::String(color)); + auto xformsArray = dl::ds::Vector(); + belInstancer["steps"][0]["xform"] = dl::Mat4 {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1}; + belInstancer.parentTo(modelXform); + + auto belMaterial = belScene.createNode("quickMaterial", + canonicalName + dl::String("vmaxMat") + dl::String(material) + dl::String("Color") + dl::String(color)); - auto belMaterial = belScene.createNode("quickMaterial", - dl::String("vmaxMat") + dl::String(material) + dl::String("Color") + dl::String(color)); + if(material==7) { + belMaterial["type"] = "liquid"; + //belMaterial["roughness"] = vmaxMaterial[material].roughness * 100.0f; + belMaterial["liquidDepth"] = 100.0f; + belMaterial["ior"] = 1.11f; + } else if(material==6 || vmaxPalette[color-1].a < 255) { + belMaterial["type"] = "glass"; + belMaterial["roughness"] = vmaxMaterial[material].roughness * 100.0f; + belMaterial["glassDepth"] = 200.0f; + } else if(vmaxMaterial[material].metalness > 0.1f) { + belMaterial["type"] = "metal"; + belMaterial["roughness"] = vmaxMaterial[material].roughness * 100.0f; + } else if(vmaxMaterial[material].transmission > 0.0f) { + belMaterial["type"] = "dielectric"; + belMaterial["transmission"] = vmaxMaterial[material].transmission; + } else if(vmaxMaterial[material].emission > 0.0f) { + belMaterial["type"] = "emitter"; + belMaterial["emitterUnit"] = "radiance"; + belMaterial["energy"] = vmaxMaterial[material].emission*1.0f; + } else { + belMaterial["type"] = "plastic"; + belMaterial["roughness"] = vmaxMaterial[material].roughness * 100.0f; + } + belInstancer["material"] = belMaterial; + // Convert 0-255 to 0-1 , remember to -1 color index becuase voxelmax needs 0 to indicate no voxel + double bellaR = static_cast(vmaxPalette[color-1].r)/255.0; + double bellaG = static_cast(vmaxPalette[color-1].g)/255.0; + double bellaB = static_cast(vmaxPalette[color-1].b)/255.0; + double bellaA = static_cast(vmaxPalette[color-1].a)/255.0; + belMaterial["color"] = dl::Rgba{ // convert sRGB to linear + srgbToLinear(bellaR), + srgbToLinear(bellaG), + srgbToLinear(bellaB), + bellaA // alpha is already linear + }; // colors ready to use in Bella - std::cout << "vmaxMaterial: " << vmaxMaterial[material].materialName << std::endl; - std::cout << "metalness: " << vmaxMaterial[material].metalness << std::endl; - std::cout << "roughness: " << vmaxMaterial[material].roughness << std::endl; - std::cout << "transmission: " << vmaxMaterial[material].transmission << std::endl; - std::cout << "emission: " << vmaxMaterial[material].emission << std::endl; + // Get all voxels for this material/color combination + const std::vector& voxelsOfType = vmaxModel.getVoxels(material, color); + int showchunk =0; - if(material==7) { - belMaterial["type"] = "liquid"; - belMaterial["roughness"] = vmaxMaterial[material].roughness * 100.0f; - } else if(material==6) { - belMaterial["type"] = "glass"; - belMaterial["roughness"] = vmaxMaterial[material].roughness * 100.0f; - belMaterial["glassDepth"] = 200.0f; - } else if(vmaxMaterial[material].metalness > 0.1f) { - belMaterial["type"] = "metal"; - belMaterial["roughness"] = vmaxMaterial[material].roughness * 100.0f; - } else if(vmaxMaterial[material].transmission > 0.0f) { - belMaterial["type"] = "dielectric"; - belMaterial["transmission"] = vmaxMaterial[material].transmission; - } else if(vmaxMaterial[material].emission > 0.0f) { - belMaterial["type"] = "emitter"; - belMaterial["emitterUnit"] = "radiance"; - belMaterial["energy"] = vmaxMaterial[material].emission*1.0f; - } else { - belMaterial["type"] = "plastic"; - belMaterial["roughness"] = vmaxMaterial[material].roughness * 100.0f; - } - belInstancer["material"] = belMaterial; - std::cout << "material: " << material << std::endl; - // Convert 0-255 to 0-1 - double bellaR = static_cast(vmaxPalette[color-1].r)/255.0; - double bellaG = static_cast(vmaxPalette[color-1].g)/255.0; - double bellaB = static_cast(vmaxPalette[color-1].b)/255.0; - double bellaA = static_cast(vmaxPalette[color-1].a)/255.0; - /* - belMaterial["color"] = dl::Rgba{ // convert sRGB to linear - bellaR, - bellaG, - bellaB, - bellaA // alpha is already linear - }; // colors ready to use in Bella - */ - belMaterial["color"] = dl::Rgba{ // convert sRGB to linear - srgbToLinear(bellaR), - srgbToLinear(bellaG), - srgbToLinear(bellaB), - bellaA // alpha is already linear - }; // colors ready to use in Bella - - // Get all voxels for this material/color combination - const std::vector& voxelsOfType = vmaxModel.getVoxels(material, color); - int showchunk =0; - - // Right now we group voxels by MatCol ie Mat0Col2 - // But voxels are stored in chunks with many colors - // Since we aren't grouping voxels in chunks, we need to traverse the voxels - // and offset each voxel by the morton decode of chunk index - for (const auto& eachvoxel : voxelsOfType) { - // Get chunk coordinates and world origin - uint32_t _tempx, _tempy, _tempz; - decodeMorton3DOptimized(eachvoxel.chunkID, _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 - xformsArray.push_back( dl::Mat4f{ 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - static_cast(eachvoxel.x + worldOffsetX), - static_cast(eachvoxel.y + worldOffsetY), - static_cast(eachvoxel.z + worldOffsetZ), 1 }); - } - belInstancer["steps"][0]["instances"] = xformsArray; - oomerSmoothCube.parentTo(belInstancer); - if(vmaxMaterial[material].emission > 0.0f) { - belVoxelForm.parentTo(belInstancer); + // Right now we group voxels by MatCol ie Mat0Col2 + // But voxels are stored in chunks with many colors + // Since we aren't grouping voxels in chunks, we need to traverse the voxels + // and offset each voxel by the morton decode of chunk index + for (const auto& eachvoxel : voxelsOfType) { + // Get chunk coordinates and world origin + uint32_t _tempx, _tempy, _tempz; + decodeMorton3DOptimized(eachvoxel.chunkID, _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 + xformsArray.push_back( dl::Mat4f{ 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + static_cast(eachvoxel.x + worldOffsetX), + static_cast(eachvoxel.y + worldOffsetY), + static_cast(eachvoxel.z + worldOffsetZ), 1 }); + } + belInstancer["steps"][0]["instances"] = xformsArray; + if(material==7) { + belLiqVoxel.parentTo(belInstancer); + } else { + belMeshVoxel.parentTo(belInstancer); + } + if(vmaxMaterial[material].emission > 0.0f) { + belVoxelForm.parentTo(belInstancer); + } } } - i++; + return modelXform; } - return 0; + return dl::bella_sdk::Node(); }