first work in progress lzfse decrypt and did a copy paste of vox2bella with adjustments

This commit is contained in:
Harvey Fong 2025-03-16 21:43:03 -06:00
parent 8f1d4d5090
commit 11279d162b
8 changed files with 1245 additions and 1 deletions

View File

@ -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
View File

@ -1,2 +1,349 @@
# vmax2bella
[WORK IN PROGRESS, file format is currently not fully grokked]
Command line convertor from VoxelMax .vmax to DiffuseLogic .bsz
![example](resources/example.jpg)
# 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
View 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 KiB

635
vmax2bella.cpp Normal file
View 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 xxx)
- y = 1 = 001 (bits labeled as yyy)
- z = 2 = 010 (bits labeled as zzz)
2. Interleave the bits in the order zyx, zyx, zyx:
- zyx = 001 (z=0, y=0, x=1)
- zyx = 010 (z=1, y=0, x=0)
- zyx = 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
View 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>