first work in progress lzfse decrypt and did a copy paste of vox2bella with adjustments
This commit is contained in:
parent
8f1d4d5090
commit
11279d162b
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 oomer
|
||||
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
|
||||
|
||||
347
README.md
347
README.md
@ -1,2 +1,349 @@
|
||||
# vmax2bella
|
||||
[WORK IN PROGRESS, file format is currently not fully grokked]
|
||||
|
||||
Command line convertor from VoxelMax .vmax to DiffuseLogic .bsz
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
# Build
|
||||
|
||||
# MacOS
|
||||
|
||||
```
|
||||
mkdir workdir
|
||||
git clone https://github.com/lzfse/lzfse
|
||||
mkdir lzfse/build
|
||||
cd lzfse/build
|
||||
/Applications/CMake.app/Contents/bin/cmake ..
|
||||
make -j4
|
||||
cd ../..
|
||||
git clone https://github.com/oomer/vmax2bella.git
|
||||
cd vmax2bella
|
||||
make
|
||||
```
|
||||
|
||||
# Linux
|
||||
|
||||
```
|
||||
mkdir workdir
|
||||
git clone https://github.com/lzfse/lzfse
|
||||
mkdir lzfse/build
|
||||
cd lzfse/build
|
||||
cmake ..
|
||||
make -j4
|
||||
cd ../..
|
||||
git clone https://github.com/oomer/vmax2bella.git
|
||||
cd vmax2bella
|
||||
make
|
||||
```
|
||||
|
||||
# Windows
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Technical Specification: VoxelMax Format
|
||||
|
||||
## Overview
|
||||
- This document specifies a chunked voxel storage format embedded in property list (plist) files. The format provides an efficient representation of 3D voxel data through a combination of Morton-encoded spatial indexing and a sparse representation approach.
|
||||
|
||||
## File Structure
|
||||
- Format: Property List (plist)
|
||||
- Structure: Hierarchical key-value structure with nested dictionaries and arrays
|
||||
- plist is compressed using the LZFSE, an open source reference c implementation is [here](https://github.com/lzfse/lzfse)
|
||||
|
||||
```
|
||||
root
|
||||
└── snapshots (array)
|
||||
└── Each snapshot (dictionary)
|
||||
├── s (dictionary) - Snapshot data
|
||||
│ ├── lc (binary data) - Location table
|
||||
│ ├── ds (binary data) - Voxel data stream
|
||||
│ ├── dlc (binary data) - Default layer colors
|
||||
│ └── st (dictionary) - Statistics/metadata
|
||||
└── Other metadata fields:
|
||||
├── cid (chunk id)
|
||||
├── sid (edit session id)
|
||||
└── t (type - VXVolumeSnapshotType)
|
||||
```
|
||||
|
||||
## Chunking System
|
||||
### Volume Organization
|
||||
- The total volume is divided into chunks for efficient storage and manipulation
|
||||
- Standard chunk size: 8×8×8 voxels
|
||||
- Total addressable space: 256×256×256 voxels (32×32×32 chunks)
|
||||
### Chunk Addressing
|
||||
- Each chunk has 3D coordinates (chunk_x, chunk_y, chunk_z)
|
||||
- ie a 16×16×16 volume with 8×8×8 chunks, there would be 2×2×2 chunks total
|
||||
- Chunks are only stored if they contain at least one non-empty voxel
|
||||
|
||||
## Data Fields
|
||||
### Location Table (lc)
|
||||
- Fixed-size binary array (256 bytes)
|
||||
- Each byte represents a position in a 3D volume
|
||||
- Non-zero values (typically 1) indicate occupied positions
|
||||
- The index of each byte is interpreted as a Morton code for the position
|
||||
- Size of 256 bytes limits addressable chunks to 256
|
||||
- ie a 16×16×16 volume would get 16÷8 = 2 chunks, Total chunks = 2×2×2 = 8 chunks total
|
||||
### Voxel Data Stream (ds)
|
||||
- Variable-length binary data
|
||||
- Contains pairs of bytes for each voxel: [position_byte, color_byte]
|
||||
- *Position Byte:*
|
||||
- Usually 0 (meaning position at origin of chunk)
|
||||
- Can encode a local position within a chunk using Morton encoding
|
||||
- *Color Byte:*
|
||||
- Stores the color value + 1 (offset of +1 from actual color)
|
||||
- Value 0 would represent -1 (typically not used)
|
||||
|
||||
### Default Layer Colors (dlc)
|
||||
- Optional 256-byte array
|
||||
- May contain default color information for specific layers
|
||||
|
||||
### Statistics Data (st)
|
||||
- Dictionary containing metadata like:
|
||||
- e (extent): Array defining the bounding box [min_x, min_y, min_z, max_x, max_y, max_z]
|
||||
- Other statistics fields may include count, max, min, etc.
|
||||
|
||||
## 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 / 8)
|
||||
- chunk_y = floor(world_y / 8)
|
||||
- chunk_z = floor(world_z / 8)
|
||||
- *World to Local:*
|
||||
- local_x = world_x % 8
|
||||
- local_y = world_y % 8
|
||||
- local_z = world_z % 8
|
||||
- *Chunk+Local to World:*
|
||||
- world_x = chunk_x * 8 + local_x
|
||||
- world_y = chunk_y * 8 + local_y
|
||||
- world_z = chunk_z * 8 + local_z
|
||||
|
||||
## Morton Encoding
|
||||
### Morton Code (Z-order curve)
|
||||
- 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
|
||||
### Encoding Process
|
||||
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
|
||||
### For Chunk Indexing
|
||||
- Morton code is used to map the 3D chunk coordinates to a 1D index in the lc array
|
||||
Formula: index = interleave_bits(chunk_x, chunk_y, chunk_z)
|
||||
|
||||
### Detailed Example
|
||||
To clarify the bit interleaving process, here's a step-by-step example:
|
||||
|
||||
For position (3,1,2):
|
||||
|
||||
1. Convert to binary (3 bits each):
|
||||
- x = 3 = 011 (bits labeled as x₂x₁x₀)
|
||||
- y = 1 = 001 (bits labeled as y₂y₁y₀)
|
||||
- z = 2 = 010 (bits labeled as z₂z₁z₀)
|
||||
|
||||
2. Interleave the bits in the order z₂y₂x₂, z₁y₁x₁, z₀y₀x₀:
|
||||
- z₂y₂x₂ = 001 (z₂=0, y₂=0, x₂=1)
|
||||
- z₁y₁x₁ = 010 (z₁=1, y₁=0, x₁=0)
|
||||
- z₀y₀x₀ = 100 (z₀=0, y₀=0, x₀=0)
|
||||
|
||||
3. Combine: 001010100 = binary 10000110 = decimal 134
|
||||
|
||||
Therefore, position (3,1,2) has Morton index 134.
|
||||
|
||||
An 8×8×8 chunk contains 512 voxels total, with Morton indices ranging from 0 to 511:
|
||||
Morton index 0 corresponds to position (0,0,0)
|
||||
Morton index 511 corresponds to position (7,7,7)
|
||||
The Morton indices are organized by z-layer:
|
||||
Z=0 layer: indices 0-63
|
||||
Z=1 layer: indices 64-127
|
||||
Z=2 layer: indices 128-191
|
||||
Z=3 layer: indices 192-255
|
||||
Z=4 layer: indices 256-319
|
||||
Z=5 layer: indices 320-383
|
||||
Z=6 layer: indices 384-447
|
||||
Z=7 layer: indices 448-511
|
||||
Each z-layer contains 64 positions (8×8), and with 8 layers, we get the full 512 positions for the chunk.
|
||||
|
||||
### For Local Positions
|
||||
The position byte in the voxel data can contain a Morton-encoded local position
|
||||
- Typically 0 (representing the origin of the chunk)
|
||||
- When non-zero, it encodes a specific position within the chunk
|
||||
|
||||
## Color Representation
|
||||
### Color Values
|
||||
- Stored as a single byte (8-bit)
|
||||
- Range: 0-255
|
||||
- Important: Stored with an offset of +1 from actual color
|
||||
|
||||
### Color Interpretation
|
||||
Format doesn't specify a specific color model (RGB, palette, etc.)
|
||||
- Interpretation depends on the implementation
|
||||
## Snapshots and Edit History
|
||||
###Snapshots
|
||||
- Each snapshot represents a single edit operation
|
||||
- Multiple snapshots build up the complete voxel model over time
|
||||
- Later snapshots override earlier ones for the same positions
|
||||
|
||||
### Snapshot Metadata
|
||||
- cid (Chunk ID): Which chunk was modified
|
||||
- sid (Session ID): Identifies the editing session
|
||||
- t (Type): Type of snapshot (VXVolumeSnapshotType)
|
||||
|
||||
## Special Considerations
|
||||
### Sparse Representation
|
||||
- Only non-empty chunks and voxels are stored
|
||||
- This creates an efficient representation for mostly empty volumes
|
||||
### Position Encoding
|
||||
- When all voxels in a chunk are at the origin (0,0,0), the position byte is always 0
|
||||
- For more complex structures, the position byte encodes local positions within the chunk
|
||||
### Color Offset
|
||||
- The +1 offset for colors must be accounted for when reading and writing
|
||||
- This might be to reserve 0 as a special value (e.g., for "no color")
|
||||
### Field Sizes
|
||||
- The location table (lc) is fixed at 256 bytes
|
||||
- The voxel data stream (ds) varies based on the number of voxels
|
||||
- For a simple 16×16×16 volume with 8×8×8 chunks, only 64 chunks can be addressed (2×2×2 chunks = 8 chunks total)
|
||||
## Implementation Guidance
|
||||
### Reading Algorithm
|
||||
1. Parse the plist file to access the snapshot array
|
||||
2. For each snapshot, extract the lc and ds data
|
||||
3. Scan the lc data for non-zero entries to identify occupied positions
|
||||
4. For each non-zero entry, decode the Morton index to get the chunk coordinates
|
||||
5. Process the ds data in pairs of bytes (position, color)
|
||||
6. For each voxel, calculate its absolute position and store with the corrected color (subtract 1)
|
||||
7. Combine all snapshots to build the complete voxel model
|
||||
|
||||
### Writing Algorithm
|
||||
1. Organize voxels by chunk
|
||||
2. For each non-empty chunk:
|
||||
3. Create a snapshot entry
|
||||
4. Set up a 256-byte lc array (all zeros)
|
||||
5. Set the appropriate byte in lc to 1 based on the chunk's Morton code
|
||||
6. Create the ds data by encoding each voxel as a (position, color+1) pair
|
||||
7. Add metadata as needed
|
||||
8. Add all snapshots to the array
|
||||
9. Write the complete structure to a plist file
|
||||
10. Compress with LZFSE
|
||||
12. [hand wabving] Package up in a MacOS style package directory with some other files
|
||||
## Practical Limits
|
||||
- 256-byte location table can address up to 256 distinct positions
|
||||
- 8-bit color values allow 256 different colors (including the offset)
|
||||
- For a 256×256×256 volume with 8×8×8 chunks, there would be 32×32×32 = 32,768 chunks total
|
||||
- The format could efficiently represent volumes with millions of voxels
|
||||
|
||||
|
||||
-----
|
||||
|
||||
# Scratch notes
|
||||
|
||||
```
|
||||
Morton Binary Coords Visual (Z-layers)
|
||||
Index (zyx bits) (x,y,z)
|
||||
|
||||
0 000 (000) (0,0,0) Z=0 layer: Z=1 layer:
|
||||
1 001 (001) (1,0,0) +---+---+ +---+---+
|
||||
2 010 (010) (0,1,0) | 0 | 1 | | 4 | 5 |
|
||||
3 011 (011) (1,1,0) +---+---+ +---+---+
|
||||
4 100 (100) (0,0,1) | 2 | 3 | | 6 | 7 |
|
||||
5 101 (101) (1,0,1) +---+---+ +---+---+
|
||||
6 110 (110) (0,1,1)
|
||||
7 111 (111) (1,1,1)
|
||||
```
|
||||
|
||||
In memory, this would be stored linearly:
|
||||
|
||||
```
|
||||
Memory: [0][1][2][3][4][5][6][7]
|
||||
| | | | | | | |
|
||||
v v v v v v v v
|
||||
Coords: (0,0,0)(1,0,0)(0,1,0)(1,1,0)(0,0,1)(1,0,1)(0,1,1)(1,1,1)
|
||||
```
|
||||
|
||||
All voxels with z=0 (Morton indices 0-3 in our 2×2×2 example) are stored contiguously
|
||||
Then all voxels with z=1 (Morton indices 4-7) are stored contiguously
|
||||
This pattern scales to larger volumes too:
|
||||
In a 4×4×4 cube, indices 0-15 would be the entire z=0 layer
|
||||
Indices 16-31 would be the z=1 layer
|
||||
And so on
|
||||
|
||||
## Morton encoding
|
||||
|
||||
Morton encoding completes an entire z-layer before moving to the next one. This happens because the highest-order bit in the Morton code (the z bit in the most significant position) changes less frequently than the lower-order bits, creating this layered organization.
|
||||
This property makes operations like rendering slice-by-slice more efficient, as entire z-layers are stored together.
|
||||
|
||||
|
||||
The Morton encoding is applied at the chunk level (8×8×8)
|
||||
It processes the entire front z-layer (z=0) first, traversing in a zig-zag pattern
|
||||
The zig-zag pattern creates a space-filling curve that maintains spatial locality
|
||||
After completing one z-layer, it moves to the next (z=1)
|
||||
This continues through all 8 possible z-layers in the chunk
|
||||
The location table (lc) uses this Morton ordering to efficiently address all possible chunks, and within each chunk, the data follows this same pattern when decoding positions.
|
||||
This approach is particularly efficient for this voxel format because:
|
||||
It groups related voxels together in memory
|
||||
It enables quick access to entire z-layers
|
||||
It maintains spatial locality which improves cache performance
|
||||
|
||||
## Location table chunks
|
||||
|
||||
The 256 bytes in the lc table doesn't represent voxels within a chunk, but rather which chunks exist in the entire volume:
|
||||
Each byte in the 256-byte lc array corresponds to a potential chunk position
|
||||
For a full 256×256×256 voxel world, you'd have 32×32×32 = 32,768 possible chunks
|
||||
The 256-byte limit means each snapshot can only reference up to 256 distinct chunks
|
||||
This is why the format uses multiple snapshots - it allows the format to build complex models by combining multiple snapshots, each referencing up to 256 chunks.
|
||||
So while 256 bytes is small for individual storage, it's actually a limitation on how many chunks can be referenced in a single snapshot. For most practical voxel models, this is adequate since they typically use far fewer than 256 chunks.
|
||||
|
||||
|
||||
In each snapshot:
|
||||
The cid (Chunk ID) field identifies which specific 8×8×8 chunk was modified
|
||||
The s dictionary contains the actual edit data:
|
||||
lc (Location Table): Shows which voxels in the chunk were edited
|
||||
ds (Data Stream): Contains the color values for those edited voxels
|
||||
Each snapshot represents a single edit operation affecting one 8×8×8 chunk. The format builds up complex models by combining multiple snapshots, each modifying different chunks or the same chunks at different times.
|
||||
Later snapshots override earlier ones when they modify the same voxel positions, allowing for an edit history that reflects the model's evolution.
|
||||
|
||||
## chunk ID
|
||||
|
||||
The cid (Chunk ID) has a range of 0-255 to match the addressing capacity of the lc table.
|
||||
This means:
|
||||
- Each snapshot can identify one specific chunk via its cid
|
||||
- Can reference up to 256 unique chunks across all snapshots
|
||||
- This 0-255 range aligns with the Morton encoding scheme for chunk coordinates and provides a clean 8-bit identifier for each chunk.
|
||||
|
||||
|
||||
## Morton encoding on the chunks
|
||||
|
||||
The chunk indices use Morton encoding (Z-order curve), just like the voxel positions within chunks.
|
||||
To convert from a cid in range 0-255 to chunk coordinates in an 8×8×8 chunk volume:
|
||||
Convert the index to binary (8 bits)
|
||||
|
||||
### Deinterleave the bits:
|
||||
Extract bits 0, 3, 6 for the x coordinate
|
||||
Extract bits 1, 4, 7 for the y coordinate
|
||||
Extract bits 2, 5 for the z coordinate (note: z only needs 2 bits for 0-3 range)
|
||||
For example, to convert chunk ID 73 to 3D chunk coordinates:
|
||||
73 in binary: 01001001
|
||||
|
||||
### Deinterleaving:
|
||||
```
|
||||
x = bits 0,3,6 = 101 = 5
|
||||
y = bits 1,4,7 = 001 = 1
|
||||
z = bits 2,5 = 00 = 0
|
||||
```
|
||||
|
||||
So chunk ID 73 corresponds to chunk position (5,1,0).
|
||||
The Morton encoding ensures that spatially close chunks are also close in memory.
|
||||
122
makefile
Normal file
122
makefile
Normal file
@ -0,0 +1,122 @@
|
||||
SDKNAME =bella_scene_sdk
|
||||
OUTNAME =vmax2bella
|
||||
UNAME =$(shell uname)
|
||||
|
||||
ifeq ($(UNAME), Darwin)
|
||||
|
||||
SDKBASE = ../bella_scene_sdk
|
||||
|
||||
SDKFNAME = lib$(SDKNAME).dylib
|
||||
LZFSELIBNAME = liblzfse.dylib
|
||||
INCLUDEDIRS = -I$(SDKBASE)/src
|
||||
INCLUDEDIRS2 = -I../lzfse/src
|
||||
LIBDIR = $(SDKBASE)/lib
|
||||
LIBDIRS = -L$(LIBDIR)
|
||||
LZFSEBUILDDIR = ../lzfse/build
|
||||
LZFSELIBDIR = -L$(LZFSEBUILDDIR)
|
||||
OBJDIR = obj/$(UNAME)
|
||||
BINDIR = bin/$(UNAME)
|
||||
OUTPUT = $(BINDIR)/$(OUTNAME)
|
||||
|
||||
ISYSROOT = /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
|
||||
|
||||
CC = clang
|
||||
CXX = clang++
|
||||
|
||||
CCFLAGS = -arch x86_64\
|
||||
-arch arm64\
|
||||
-mmacosx-version-min=11.0\
|
||||
-isysroot $(ISYSROOT)\
|
||||
-fvisibility=hidden\
|
||||
-O3\
|
||||
$(INCLUDEDIRS)\
|
||||
$(INCLUDEDIRS2)
|
||||
|
||||
CFLAGS = $(CCFLAGS)\
|
||||
-std=c17
|
||||
|
||||
CXXFLAGS = $(CCFLAGS)\
|
||||
-std=c++17
|
||||
|
||||
CPPDEFINES = -DNDEBUG=1\
|
||||
-DDL_USE_SHARED
|
||||
|
||||
LIBS = -l$(SDKNAME)\
|
||||
-lm\
|
||||
-ldl\
|
||||
-llzfse
|
||||
|
||||
LINKFLAGS = -mmacosx-version-min=11.0\
|
||||
-isysroot $(ISYSROOT)\
|
||||
-framework Cocoa\
|
||||
-framework IOKit\
|
||||
-fvisibility=hidden\
|
||||
-O5\
|
||||
-rpath @executable_path
|
||||
else
|
||||
|
||||
SDKBASE = ../bella_scene_sdk
|
||||
|
||||
SDKFNAME = lib$(SDKNAME).so
|
||||
SDKFNAME2 = liblzfse.so
|
||||
INCLUDEDIRS = -I$(SDKBASE)/src
|
||||
INCLUDEDIRS2 = -I../lzfse/src
|
||||
LIBDIR = $(SDKBASE)/lib
|
||||
LIBDIRS = -L$(LIBDIR)
|
||||
OBJDIR = obj/$(UNAME)
|
||||
BINDIR = bin/$(UNAME)
|
||||
OUTPUT = $(BINDIR)/$(OUTNAME)
|
||||
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
|
||||
CCFLAGS = -m64\
|
||||
-Wall\
|
||||
-fvisibility=hidden\
|
||||
-D_FILE_OFFSET_BITS=64\
|
||||
-O3\
|
||||
$(INCLUDEDIRS)\
|
||||
$(INCLUDEDIRS2)
|
||||
|
||||
|
||||
CFLAGS = $(CCFLAGS)\
|
||||
-std=c11
|
||||
|
||||
CXXFLAGS = $(CCFLAGS)\
|
||||
-std=c++11
|
||||
|
||||
CPPDEFINES = -DNDEBUG=1\
|
||||
-DDL_USE_SHARED
|
||||
|
||||
LIBS = -l$(SDKNAME)\
|
||||
-lm\
|
||||
-ldl\
|
||||
-lrt\
|
||||
-lpthread\
|
||||
-llzfse
|
||||
|
||||
LINKFLAGS = -m64\
|
||||
-fvisibility=hidden\
|
||||
-O3\
|
||||
-Wl,-rpath,'$$ORIGIN'\
|
||||
-Wl,-rpath,'$$ORIGIN/lib'
|
||||
endif
|
||||
|
||||
OBJS = vmax2bella.o
|
||||
OBJ = $(patsubst %,$(OBJDIR)/%,$(OBJS))
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp
|
||||
@mkdir -p $(@D)
|
||||
$(CXX) -c -o $@ $< $(CXXFLAGS) $(CPPDEFINES)
|
||||
|
||||
$(OUTPUT): $(OBJ)
|
||||
@mkdir -p $(@D)
|
||||
$(CXX) -o $@ $^ $(LINKFLAGS) $(LIBDIRS) $(LZFSELIBDIR) $(LIBS)
|
||||
@cp $(LIBDIR)/$(SDKFNAME) $(BINDIR)/$(SDKFNAME)
|
||||
@cp $(LZFSEBUILDDIR)/$(LZFSELIBNAME) $(BINDIR)/$(LZFSELIBNAME)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(OBJDIR)/*.o
|
||||
rm -f $(OUTPUT)
|
||||
rm -f $(BINDIR)/$(SDKFNAME)
|
||||
BIN
resources/DayEnvironmentHDRI019_1K-TONEMAPPED.jpg
Normal file
BIN
resources/DayEnvironmentHDRI019_1K-TONEMAPPED.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 419 KiB |
2
resources/DayEnvironmentHDRI019_1K-TONEMAPPED.txt
Normal file
2
resources/DayEnvironmentHDRI019_1K-TONEMAPPED.txt
Normal file
@ -0,0 +1,2 @@
|
||||
DayEnvironmentHDRI019_1K-TONEMAPPED.jpg from ambientCG.com,
|
||||
licensed under the Creative Commons CC0 1.0 Universal License.
|
||||
BIN
resources/example.jpg
Normal file
BIN
resources/example.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 828 KiB |
635
vmax2bella.cpp
Normal file
635
vmax2bella.cpp
Normal file
@ -0,0 +1,635 @@
|
||||
// vmax2bella.cpp - A program to convert VoxelMax (.vmax) files to Bella 3D scene (.bsz) files
|
||||
//
|
||||
// This program reads VoxelMax files (which store voxel-based 3D models) and
|
||||
// converts them to Bella (a 3D rendering engine) scene files.
|
||||
|
||||
/*
|
||||
# Technical Specification: VoxelMax Format
|
||||
|
||||
## Overview
|
||||
- This document specifies a chunked voxel storage format embedded in property list (plist) files. The format provides an efficient representation of 3D voxel data through a combination of Morton-encoded spatial indexing and a sparse representation approach.
|
||||
|
||||
## File Structure
|
||||
- Format: Property List (plist)
|
||||
- Structure: Hierarchical key-value structure with nested dictionaries and arrays
|
||||
- plist is compressed using the LZFSE, an open source reference c implementation is [here](https://github.com/lzfse/lzfse)
|
||||
|
||||
```
|
||||
root
|
||||
└── snapshots (array)
|
||||
└── Each snapshot (dictionary)
|
||||
├── s (dictionary) - Snapshot data
|
||||
│ ├── lc (binary data) - Location table
|
||||
│ ├── ds (binary data) - Voxel data stream
|
||||
│ ├── dlc (binary data) - Default layer colors
|
||||
│ └── st (dictionary) - Statistics/metadata
|
||||
└── Other metadata fields:
|
||||
├── cid (chunk id)
|
||||
├── sid (edit session id)
|
||||
└── t (type - VXVolumeSnapshotType)
|
||||
```
|
||||
|
||||
## Chunking System
|
||||
### Volume Organization
|
||||
- The total volume is divided into chunks for efficient storage and manipulation
|
||||
- Standard chunk size: 8×8×8 voxels
|
||||
- Total addressable space: 256×256×256 voxels (32×32×32 chunks)
|
||||
### Chunk Addressing
|
||||
- Each chunk has 3D coordinates (chunk_x, chunk_y, chunk_z)
|
||||
- ie a 16×16×16 volume with 8×8×8 chunks, there would be 2×2×2 chunks total
|
||||
- Chunks are only stored if they contain at least one non-empty voxel
|
||||
|
||||
## Data Fields
|
||||
### Location Table (lc)
|
||||
- Fixed-size binary array (256 bytes)
|
||||
- Each byte represents a position in a 3D volume
|
||||
- Non-zero values (typically 1) indicate occupied positions
|
||||
- The index of each byte is interpreted as a Morton code for the position
|
||||
- Size of 256 bytes limits addressable chunks to 256
|
||||
- ie a 16×16×16 volume would get 16÷8 = 2 chunks, Total chunks = 2×2×2 = 8 chunks total
|
||||
### Voxel Data Stream (ds)
|
||||
- Variable-length binary data
|
||||
- Contains pairs of bytes for each voxel: [position_byte, color_byte]
|
||||
- *Position Byte:*
|
||||
- Usually 0 (meaning position at origin of chunk)
|
||||
- Can encode a local position within a chunk using Morton encoding
|
||||
- *Color Byte:*
|
||||
- Stores the color value + 1 (offset of +1 from actual color)
|
||||
- Value 0 would represent -1 (typically not used)
|
||||
|
||||
### Default Layer Colors (dlc)
|
||||
- Optional 256-byte array
|
||||
- May contain default color information for specific layers
|
||||
|
||||
### Statistics Data (st)
|
||||
- Dictionary containing metadata like:
|
||||
- e (extent): Array defining the bounding box [min_x, min_y, min_z, max_x, max_y, max_z]
|
||||
- Other statistics fields may include count, max, min, etc.
|
||||
|
||||
## 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 / 8)
|
||||
- chunk_y = floor(world_y / 8)
|
||||
- chunk_z = floor(world_z / 8)
|
||||
- *World to Local:*
|
||||
- local_x = world_x % 8
|
||||
- local_y = world_y % 8
|
||||
- local_z = world_z % 8
|
||||
- *Chunk+Local to World:*
|
||||
- world_x = chunk_x * 8 + local_x
|
||||
- world_y = chunk_y * 8 + local_y
|
||||
- world_z = chunk_z * 8 + local_z
|
||||
|
||||
## Morton Encoding
|
||||
### Morton Code (Z-order curve)
|
||||
- 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
|
||||
### Encoding Process
|
||||
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
|
||||
### For Chunk Indexing
|
||||
- Morton code is used to map the 3D chunk coordinates to a 1D index in the lc array
|
||||
Formula: index = interleave_bits(chunk_x, chunk_y, chunk_z)
|
||||
|
||||
### Detailed Example
|
||||
To clarify the bit interleaving process, here's a step-by-step example:
|
||||
|
||||
For position (3,1,2):
|
||||
|
||||
1. Convert to binary (3 bits each):
|
||||
- x = 3 = 011 (bits labeled as x₂x₁x₀)
|
||||
- y = 1 = 001 (bits labeled as y₂y₁y₀)
|
||||
- z = 2 = 010 (bits labeled as z₂z₁z₀)
|
||||
|
||||
2. Interleave the bits in the order z₂y₂x₂, z₁y₁x₁, z₀y₀x₀:
|
||||
- z₂y₂x₂ = 001 (z₂=0, y₂=0, x₂=1)
|
||||
- z₁y₁x₁ = 010 (z₁=1, y₁=0, x₁=0)
|
||||
- z₀y₀x₀ = 100 (z₀=0, y₀=0, x₀=0)
|
||||
|
||||
3. Combine: 001010100 = binary 10000110 = decimal 134
|
||||
|
||||
Therefore, position (3,1,2) has Morton index 134.
|
||||
|
||||
Yes, an 8×8×8 chunk contains 512 voxels total, with Morton indices ranging from 0 to 511:
|
||||
Morton index 0 corresponds to position (0,0,0)
|
||||
Morton index 511 corresponds to position (7,7,7)
|
||||
The Morton indices are organized by z-layer:
|
||||
Z=0 layer: indices 0-63
|
||||
Z=1 layer: indices 64-127
|
||||
Z=2 layer: indices 128-191
|
||||
Z=3 layer: indices 192-255
|
||||
Z=4 layer: indices 256-319
|
||||
Z=5 layer: indices 320-383
|
||||
Z=6 layer: indices 384-447
|
||||
Z=7 layer: indices 448-511
|
||||
Each z-layer contains 64 positions (8×8), and with 8 layers, we get the full 512 positions for the chunk.
|
||||
|
||||
### For Local Positions
|
||||
The position byte in the voxel data can contain a Morton-encoded local position
|
||||
- Typically 0 (representing the origin of the chunk)
|
||||
- When non-zero, it encodes a specific position within the chunk
|
||||
|
||||
## Color Representation
|
||||
### Color Values
|
||||
- Stored as a single byte (8-bit)
|
||||
- Range: 0-255
|
||||
- Important: Stored with an offset of +1 from actual color
|
||||
|
||||
### Color Interpretation
|
||||
Format doesn't specify a specific color model (RGB, palette, etc.)
|
||||
- Interpretation depends on the implementation
|
||||
## Snapshots and Edit History
|
||||
###Snapshots
|
||||
- Each snapshot represents a single edit operation
|
||||
- Multiple snapshots build up the complete voxel model over time
|
||||
- Later snapshots override earlier ones for the same positions
|
||||
|
||||
### Snapshot Metadata
|
||||
- cid (Chunk ID): Which chunk was modified
|
||||
- sid (Session ID): Identifies the editing session
|
||||
- t (Type): Type of snapshot (VXVolumeSnapshotType)
|
||||
|
||||
## Special Considerations
|
||||
### Sparse Representation
|
||||
- Only non-empty chunks and voxels are stored
|
||||
- This creates an efficient representation for mostly empty volumes
|
||||
### Position Encoding
|
||||
- When all voxels in a chunk are at the origin (0,0,0), the position byte is always 0
|
||||
- For more complex structures, the position byte encodes local positions within the chunk
|
||||
### Color Offset
|
||||
- The +1 offset for colors must be accounted for when reading and writing
|
||||
- This might be to reserve 0 as a special value (e.g., for "no color")
|
||||
### Field Sizes
|
||||
- The location table (lc) is fixed at 256 bytes
|
||||
- The voxel data stream (ds) varies based on the number of voxels
|
||||
- For a simple 16×16×16 volume with 8×8×8 chunks, only 64 chunks can be addressed (2×2×2 chunks = 8 chunks total)
|
||||
## Implementation Guidance
|
||||
### Reading Algorithm
|
||||
1. Parse the plist file to access the snapshot array
|
||||
2. For each snapshot, extract the lc and ds data
|
||||
3. Scan the lc data for non-zero entries to identify occupied positions
|
||||
4. For each non-zero entry, decode the Morton index to get the chunk coordinates
|
||||
5. Process the ds data in pairs of bytes (position, color)
|
||||
6. For each voxel, calculate its absolute position and store with the corrected color (subtract 1)
|
||||
7. Combine all snapshots to build the complete voxel model
|
||||
|
||||
### Writing Algorithm
|
||||
1. Organize voxels by chunk
|
||||
2. For each non-empty chunk:
|
||||
3. Create a snapshot entry
|
||||
4. Set up a 256-byte lc array (all zeros)
|
||||
5. Set the appropriate byte in lc to 1 based on the chunk's Morton code
|
||||
6. Create the ds data by encoding each voxel as a (position, color+1) pair
|
||||
7. Add metadata as needed
|
||||
8. Add all snapshots to the array
|
||||
9. Write the complete structure to a plist file
|
||||
10. Compress with LZFSE
|
||||
12. [hand wabving] Package up in a MacOS style package directory with some other files
|
||||
## Practical Limits
|
||||
- 256-byte location table can address up to 256 distinct positions
|
||||
- 8-bit color values allow 256 different colors (including the offset)
|
||||
- For a 256×256×256 volume with 8×8×8 chunks, there would be 32×32×32 = 32,768 chunks total
|
||||
- The format could efficiently represent volumes with millions of voxels
|
||||
*/
|
||||
|
||||
// Standard C++ library includes - these provide essential functionality
|
||||
#include <iostream> // For input/output operations (cout, cin, etc.)
|
||||
#include <fstream> // For file operations (reading/writing files)
|
||||
#include <vector> // For dynamic arrays (vectors)
|
||||
#include <cstdint> // For fixed-size integer types (uint8_t, uint32_t, etc.)
|
||||
#include <map> // For key-value pair data structures (maps)
|
||||
#include <filesystem> // For file system operations (directory handling, path manipulation)
|
||||
|
||||
// Bella SDK includes - external libraries for 3D rendering
|
||||
#include "bella_sdk/bella_scene.h" // For creating and manipulating 3D scenes in Bella
|
||||
#include "dl_core/dl_main.inl" // Core functionality from the Diffuse Logic engine
|
||||
#include "dl_core/dl_fs.h"
|
||||
#include "lzfse.h"
|
||||
|
||||
// Namespaces allow you to use symbols from a library without prefixing them
|
||||
// For example, with these 'using' statements, you can write 'Scene' instead of 'bella_sdk::Scene'
|
||||
namespace bsdk = dl::bella_sdk;
|
||||
|
||||
|
||||
// Global variable to track if we've found a color palette in the file
|
||||
bool has_palette = false;
|
||||
|
||||
// Forward declarations of functions - tells the compiler that these functions exist
|
||||
// and will be defined later in the file
|
||||
std::string initializeGlobalLicense();
|
||||
std::string initializeGlobalThirdPartyLicences();
|
||||
|
||||
// Observer class for monitoring scene events
|
||||
// This is a custom implementation of the SceneObserver interface from Bella SDK
|
||||
// The SceneObserver is called when various events occur in the scene
|
||||
struct Observer : public bsdk::SceneObserver
|
||||
{
|
||||
bool inEventGroup = false; // Flag to track if we're in an event group
|
||||
|
||||
// Override methods from SceneObserver to provide custom behavior
|
||||
|
||||
// Called when a node is added to the scene
|
||||
void onNodeAdded( bsdk::Node node ) override
|
||||
{
|
||||
dl::logInfo("%sNode added: %s", inEventGroup ? " " : "", node.name().buf());
|
||||
}
|
||||
|
||||
// Called when a node is removed from the scene
|
||||
void onNodeRemoved( bsdk::Node node ) override
|
||||
{
|
||||
dl::logInfo("%sNode removed: %s", inEventGroup ? " " : "", node.name().buf());
|
||||
}
|
||||
|
||||
// Called when an input value changes
|
||||
void onInputChanged( bsdk::Input input ) override
|
||||
{
|
||||
dl::logInfo("%sInput changed: %s", inEventGroup ? " " : "", input.path().buf());
|
||||
}
|
||||
|
||||
// Called when an input is connected to something
|
||||
void onInputConnected( bsdk::Input input ) override
|
||||
{
|
||||
dl::logInfo("%sInput connected: %s", inEventGroup ? " " : "", input.path().buf());
|
||||
}
|
||||
|
||||
// Called at the start of a group of related events
|
||||
void onBeginEventGroup() override
|
||||
{
|
||||
inEventGroup = true;
|
||||
dl::logInfo("Event group begin.");
|
||||
}
|
||||
|
||||
// Called at the end of a group of related events
|
||||
void onEndEventGroup() override
|
||||
{
|
||||
inEventGroup = false;
|
||||
dl::logInfo("Event group end.");
|
||||
}
|
||||
};
|
||||
|
||||
// Function to decompress LZFSE file to a plist file
|
||||
bool decompressLzfseToPlist(const std::string& inputFile, const std::string& outputFile) {
|
||||
// Open input file
|
||||
std::ifstream inFile(inputFile, std::ios::binary);
|
||||
if (!inFile.is_open()) {
|
||||
std::cerr << "Error: Could not open input file: " << inputFile << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get file size
|
||||
inFile.seekg(0, std::ios::end);
|
||||
size_t inSize = inFile.tellg();
|
||||
inFile.seekg(0, std::ios::beg);
|
||||
|
||||
// Read compressed data
|
||||
std::vector<uint8_t> inBuffer(inSize);
|
||||
inFile.read(reinterpret_cast<char*>(inBuffer.data()), inSize);
|
||||
inFile.close();
|
||||
|
||||
// Allocate output buffer (assuming decompressed size is larger)
|
||||
// Start with 4x the input size, will resize if needed
|
||||
size_t outAllocatedSize = inSize * 4;
|
||||
std::vector<uint8_t> outBuffer(outAllocatedSize);
|
||||
|
||||
// Allocate scratch buffer for lzfse
|
||||
size_t scratchSize = lzfse_decode_scratch_size();
|
||||
std::vector<uint8_t> scratch(scratchSize);
|
||||
|
||||
// Decompress data
|
||||
size_t decodedSize = 0;
|
||||
while (true) {
|
||||
decodedSize = lzfse_decode_buffer(
|
||||
outBuffer.data(), outAllocatedSize,
|
||||
inBuffer.data(), inSize,
|
||||
scratch.data());
|
||||
|
||||
// If output buffer was too small, increase size and retry
|
||||
if (decodedSize == 0 || decodedSize == outAllocatedSize) {
|
||||
outAllocatedSize *= 2;
|
||||
outBuffer.resize(outAllocatedSize);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Write output file
|
||||
std::ofstream outFile(outputFile, std::ios::binary);
|
||||
if (!outFile.is_open()) {
|
||||
std::cerr << "Error: Could not open output file: " << outputFile << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
outFile.write(reinterpret_cast<char*>(outBuffer.data()), decodedSize);
|
||||
outFile.close();
|
||||
|
||||
std::cout << "Successfully decompressed " << inputFile << " to " << outputFile << std::endl;
|
||||
std::cout << "Input size: " << inSize << " bytes, Output size: " << decodedSize << " bytes" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Main function for the program
|
||||
// This is where execution begins
|
||||
// The Args object contains command-line arguments
|
||||
int DL_main(dl::Args& args)
|
||||
{
|
||||
// Variable to store the input file path
|
||||
std::string filePath;
|
||||
std::string lzfseInputPath;
|
||||
std::string plistOutputPath;
|
||||
|
||||
// Define command-line arguments that the program accepts
|
||||
args.add("vi", "voxin", "", "Input .vox file");
|
||||
args.add("tp", "thirdparty", "", "prints third party licenses");
|
||||
args.add("li", "licenseinfo", "", "prints license info");
|
||||
args.add("lz", "lzfsein", "", "Input LZFSE compressed file");
|
||||
args.add("po", "plistout", "", "Output plist file path");
|
||||
|
||||
// Handle special command-line requests
|
||||
|
||||
// If --version was requested, print version and exit
|
||||
if (args.versionReqested())
|
||||
{
|
||||
printf("%s", dl::bellaSdkVersion().toString().buf());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If --help was requested, print help and exit
|
||||
if (args.helpRequested())
|
||||
{
|
||||
printf("%s", args.help("vox2bella", dl::fs::exePath(), "Hello\n").buf());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If --licenseinfo was requested, print license info and exit
|
||||
if (args.have("--licenseinfo"))
|
||||
{
|
||||
std::cout << initializeGlobalLicense() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If --thirdparty was requested, print third-party licenses and exit
|
||||
if (args.have("--thirdparty"))
|
||||
{
|
||||
std::cout << initializeGlobalThirdPartyLicences() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
// Get the input file path from command line arguments
|
||||
if (args.have("--voxin"))
|
||||
{
|
||||
filePath = args.value("--voxin").buf();
|
||||
}
|
||||
/*else
|
||||
{
|
||||
// If no input file was specified, print error and exit
|
||||
std::cout << "Mandatory -vi .vox input missing" << std::endl;
|
||||
return 1;
|
||||
}*/
|
||||
|
||||
// Check for LZFSE decompression request
|
||||
if ((args.have("--lzfsein") && (args.have("--plistout") )))
|
||||
{
|
||||
lzfseInputPath = args.value("--lzfsein").buf();
|
||||
std::cout << "lzfseInputPath: " << lzfseInputPath << std::endl;
|
||||
plistOutputPath = args.value("--plistout").buf();
|
||||
std::cout << "plistOutputPath: " << plistOutputPath << std::endl;
|
||||
return decompressLzfseToPlist(lzfseInputPath, plistOutputPath) ? 0 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Mandatory sing" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create a new Bella scene
|
||||
bsdk::Scene sceneWrite;
|
||||
sceneWrite.loadDefs(); // Load scene definitions
|
||||
|
||||
// Create a vector to store voxel color indices
|
||||
std::vector<uint8_t> voxelPalette;
|
||||
|
||||
// Create the basic scene elements in Bella
|
||||
// Each line creates a different type of node in the scene
|
||||
auto beautyPass = sceneWrite.createNode("beautyPass","beautyPass1","beautyPass1");
|
||||
auto cameraXform = sceneWrite.createNode("xform","cameraXform1","cameraXform1");
|
||||
auto camera = sceneWrite.createNode("camera","camera1","camera1");
|
||||
auto sensor = sceneWrite.createNode("sensor","sensor1","sensor1");
|
||||
auto lens = sceneWrite.createNode("thinLens","thinLens1","thinLens1");
|
||||
auto imageDome = sceneWrite.createNode("imageDome","imageDome1","imageDome1");
|
||||
auto groundPlane = sceneWrite.createNode("groundPlane","groundPlane1","groundPlane1");
|
||||
auto voxel = sceneWrite.createNode("box","box1","box1");
|
||||
auto groundMat = sceneWrite.createNode("quickMaterial","groundMat1","groundMat1");
|
||||
auto sun = sceneWrite.createNode("sun","sun1","sun1");
|
||||
|
||||
/* Commented out: Loop to create material nodes
|
||||
for( uint8_t i = 0; ;i++)
|
||||
{
|
||||
String nodeName = String("voxMat") + String(i); // Create the node name
|
||||
auto dielectric = sceneWrite.createNode("dielectric",nodeName,nodeName);
|
||||
{
|
||||
Scene::EventScope es(sceneWrite);
|
||||
dielectric["ior"] = 1.51f;
|
||||
dielectric["roughness"] = 22.0f;
|
||||
dielectric["depth"] = 44.0f;
|
||||
}
|
||||
if(i==255)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Set up the scene with an EventScope
|
||||
// EventScope groups multiple changes together for efficiency
|
||||
{
|
||||
bsdk::Scene::EventScope es(sceneWrite);
|
||||
auto settings = sceneWrite.settings(); // Get scene settings
|
||||
auto world = sceneWrite.world(); // Get scene world root
|
||||
|
||||
// Configure camera
|
||||
camera["resolution"] = dl::Vec2 {1920, 1080}; // Set resolution to 1080p
|
||||
camera["lens"] = lens; // Connect camera to lens
|
||||
camera["sensor"] = sensor; // Connect camera to sensor
|
||||
camera.parentTo(cameraXform); // Parent camera to its transform
|
||||
cameraXform.parentTo(world); // Parent camera transform to world
|
||||
|
||||
// Position the camera with a transformation matrix
|
||||
cameraXform["steps"][0]["xform"] = dl::Mat4 {0.525768608156, -0.850627633385, 0, 0, -0.234464751651, -0.144921468924, -0.961261695938, 0, 0.817675761479, 0.505401223947, -0.275637355817, 0, -88.12259018466, -54.468125200218, 50.706001690932, 1};
|
||||
|
||||
// Configure environment (image-based lighting)
|
||||
imageDome["ext"] = ".jpg";
|
||||
imageDome["dir"] = "./resources";
|
||||
imageDome["multiplier"] = 6.0f;
|
||||
imageDome["file"] = "DayEnvironmentHDRI019_1K-TONEMAPPED";
|
||||
|
||||
// Configure ground plane
|
||||
groundPlane["elevation"] = -.5f;
|
||||
groundPlane["material"] = groundMat;
|
||||
|
||||
/* Commented out: Sun configuration
|
||||
sun["size"] = 20.0f;
|
||||
sun["month"] = "july";
|
||||
sun["rotation"] = 50.0f;*/
|
||||
|
||||
// Configure materials
|
||||
groundMat["type"] = "metal";
|
||||
groundMat["roughness"] = 22.0f;
|
||||
|
||||
// Configure voxel box dimensions
|
||||
voxel["radius"] = 0.33f;
|
||||
voxel["sizeX"] = 0.99f;
|
||||
voxel["sizeY"] = 0.99f;
|
||||
voxel["sizeZ"] = 0.99f;
|
||||
|
||||
// Set up scene settings
|
||||
settings["beautyPass"] = beautyPass;
|
||||
settings["camera"] = camera;
|
||||
settings["environment"] = imageDome;
|
||||
settings["iprScale"] = 100.0f;
|
||||
settings["threads"] = bsdk::Input(0); // Auto-detect thread count
|
||||
settings["groundPlane"] = groundPlane;
|
||||
settings["iprNavigation"] = "maya"; // Use Maya-like navigation in viewer
|
||||
//settings["sun"] = sun;
|
||||
}
|
||||
|
||||
// Process all chunks in the VOX file
|
||||
// Loop until we reach the end of the file
|
||||
/*while (file.peek() != EOF) {
|
||||
readChunk(file, palette, voxelPalette, sceneWrite, voxel);
|
||||
}*/
|
||||
|
||||
// If the file didn't have a palette, create materials using the default palette
|
||||
/*if (!has_palette)
|
||||
{
|
||||
for(int i=0; i<256; i++)
|
||||
{
|
||||
// Extract RGBA components from the palette color
|
||||
// Bit shifting and masking extracts individual byte components
|
||||
uint8_t r = (palette[i] >> 0) & 0xFF; // Red (lowest byte)
|
||||
uint8_t g = (palette[i] >> 8) & 0xFF; // Green (second byte)
|
||||
uint8_t b = (palette[i] >> 16) & 0xFF; // Blue (third byte)
|
||||
uint8_t a = (palette[i] >> 24) & 0xFF; // Alpha (highest byte)
|
||||
|
||||
// Create a unique material name
|
||||
dl::String nodeName = dl::String("voxMat") + dl::String(i);
|
||||
// Create an Oren-Nayar material (diffuse material model)
|
||||
auto voxMat = sceneWrite.createNode("orenNayar", nodeName, nodeName);
|
||||
{
|
||||
bsdk::Scene::EventScope es(sceneWrite);
|
||||
// Commented out: Alternative material settings
|
||||
//dielectric["ior"] = 1.41f;
|
||||
//dielectric["roughness"] = 40.0f;
|
||||
//dielectric["depth"] = 33.0f;
|
||||
|
||||
// Set the material color (convert 0-255 values to 0.0-1.0 range)
|
||||
voxMat["reflectance"] = dl::Rgba{ static_cast<double>(r)/255.0,
|
||||
static_cast<double>(g)/255.0,
|
||||
static_cast<double>(b)/255.0,
|
||||
static_cast<double>(a)/255.0};
|
||||
}
|
||||
|
||||
}
|
||||
}*/
|
||||
|
||||
// Assign materials to voxels based on their color indices
|
||||
/*for (int i = 0; i < voxelPalette.size(); i++)
|
||||
{
|
||||
// Find the transform node for this voxel
|
||||
auto xformNode = sceneWrite.findNode(dl::String("voxXform") + dl::String(i));
|
||||
// Find the material node for this voxel's color
|
||||
auto matNode = sceneWrite.findNode(dl::String("voxMat") + dl::String(voxelPalette[i]));
|
||||
// Assign the material to the voxel
|
||||
xformNode["material"] = matNode;
|
||||
}
|
||||
|
||||
// Close the input file
|
||||
file.close();*/
|
||||
|
||||
// Create the output file path by replacing .vox with .bsz
|
||||
//std::filesystem::path bszPath = voxPath.stem().string() + ".bsz";
|
||||
// Write the Bella cene to the output file
|
||||
sceneWrite.write(dl::String("foo.bsz"));
|
||||
|
||||
// Return success
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 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.
|
||||
)";
|
||||
}
|
||||
138
vmax2bella.vcxproj
Normal file
138
vmax2bella.vcxproj
Normal file
@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="PseudoDebug|x64">
|
||||
<Configuration>PseudoDebug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{84cf8081-b10a-4742-9542-c95ca62b9589}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='PseudoDebug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='PseudoDebug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='PseudoDebug|x64'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>PSEUDODEBUG;_CONSOLE;DL_USE_SHARED;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<InlineFunctionExpansion>Disabled</InlineFunctionExpansion>
|
||||
<AdditionalIncludeDirectories>..\bella_engine_sdk\src;..\lzfse\src\include</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>lib</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>bella_scene_sdk.lib;Shlwapi.lib;lzfse.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;DL_USE_SHARED;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>src</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalLibraryDirectories>lib</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>bella_scene_sdk.lib;Shlwapi.lib;lzfse.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="$(PlatformToolset.Contains('Intel'))">
|
||||
<ClCompile>
|
||||
<WarningLevel>TurnOffAllWarnings</WarningLevel>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
<PreprocessorDefinitions>_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<InterproceduralOptimization>NoIPO</InterproceduralOptimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<InterproceduralOptimization>false</InterproceduralOptimization>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<PostBuildEvent>
|
||||
<Command>copy "$(ProjectDir)lib\*.dll" "$(TargetDir)"</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\bella_scene_sdk\src\bella_sdk\api.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\bella_sdk\bella_nodeapi.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\bella_sdk\bella_scene.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\bella_sdk\bella_sceneapi.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\bella_sdk\bella_types.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\api.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_args.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_array.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_compress.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_defines.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_file.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_fs.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_hash.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_hashmap.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_hw.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_licensing.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_logging.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_main.inl" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_math.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_nullable.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_os.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_path.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_pcgrng.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_platform.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_references.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_refvector.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_string.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_stringio.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_time.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_topomap.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_types.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_vector.h" />
|
||||
<ClInclude Include="..\bella_scene_sdk\src\dl_core\dl_version.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="vmax2bella.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Library Include="..\bella_scene_sdk\lib\bella_scene_sdk.lib" />
|
||||
<Library Include="..\lzfse\build\lib\liblzfse.lib" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
Loading…
x
Reference in New Issue
Block a user