mirror of
https://github.com/WerWolv/ImHex-Patterns.git
synced 2026-03-28 07:47:02 -05:00
patterns: Add support for MagicaVoxel .vox files (#390)
* patterns: Added support for MagicaVoxel .vox files * Fixed incorrect IMAP field size and added basic chunk size mismatch detection and recovery. * Fixed pattern for "_r" values and added RotationToMat3. * Added test vox file. --------- Co-authored-by: paxcut <53811119+paxcut@users.noreply.github.com>
This commit is contained in:
270
patterns/vox.hexpat
Normal file
270
patterns/vox.hexpat
Normal file
@@ -0,0 +1,270 @@
|
||||
#pragma author Jake Ryan
|
||||
#pragma description MagicaVoxel scene description format
|
||||
#pragma magic [ 56 4F 58 20 ] @ 0
|
||||
#pragma array_limit 0x100000
|
||||
|
||||
import std.io;
|
||||
import std.core;
|
||||
|
||||
// Input and output variables
|
||||
|
||||
// If true, color data will be formatted as normalized floats.
|
||||
bool formatColorsAsFloat in;
|
||||
|
||||
// If true, chunks will be treated as though they were in a single big array instead of a hierarchy.
|
||||
bool readChunksAsLinear in;
|
||||
|
||||
bool attemptRecoveryOnChunkSizeMismatch in;
|
||||
|
||||
// Pattern definitions
|
||||
// https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox.txt
|
||||
// https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox-extension.txt
|
||||
struct VoxHeader
|
||||
{
|
||||
char magic[4];
|
||||
u32 version;
|
||||
};
|
||||
|
||||
struct Voxel
|
||||
{
|
||||
u8 x;
|
||||
u8 y;
|
||||
u8 z;
|
||||
u8 colorIndex;
|
||||
} [[static, format("FormatVoxel")]];
|
||||
|
||||
fn FormatVoxel(ref Voxel v)
|
||||
{
|
||||
return std::format("{}, {}, {}", v.x, v.y, v.z);
|
||||
};
|
||||
|
||||
struct RGBA8
|
||||
{
|
||||
u8 r [[color("FF0000")]];
|
||||
u8 g [[color("00FF00")]];
|
||||
u8 b [[color("0000FF")]];
|
||||
u8 a;
|
||||
} [[static, color(std::format("{:02X}{:02X}{:02X}", r, g, b)), format("FormatRGBA8")]];
|
||||
|
||||
fn FormatRGBA8(ref RGBA8 c)
|
||||
{
|
||||
if (formatColorsAsFloat)
|
||||
{
|
||||
return std::format("{:4.2f}, {:4.2f}, {:4.2f}, {:4.2f}", c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::format("{:3}, {:3}, {:3}, {:3}", c.r, c.g, c.b, c.a);
|
||||
}
|
||||
};
|
||||
|
||||
struct String
|
||||
{
|
||||
u32 sizeBytes;
|
||||
char data[sizeBytes];
|
||||
} [[format("FormatString")]];
|
||||
|
||||
fn FormatString(ref String s)
|
||||
{
|
||||
return std::format("\"{}\"", s.data);
|
||||
};
|
||||
|
||||
struct Vec3 { float v[3]; };
|
||||
struct Mat3 { Vec3 r0, r1, r2; } [[format("FormatMat3")]];
|
||||
|
||||
fn FormatMat3(ref Mat3 m)
|
||||
{
|
||||
return std::format("({}, {}, {}) ({}, {}, {}) ({}, {}, {})",
|
||||
m.r0.v[0], m.r0.v[1], m.r0.v[2],
|
||||
m.r1.v[0], m.r1.v[1], m.r1.v[2],
|
||||
m.r2.v[0], m.r2.v[1], m.r2.v[2]);
|
||||
};
|
||||
|
||||
bitfield Rotation
|
||||
{
|
||||
firstRowIndex : 2;
|
||||
secondRowIndex : 2;
|
||||
firstRowSign : 1;
|
||||
secondRowSign : 1;
|
||||
thirdRowSign : 1;
|
||||
padding : 1;
|
||||
};
|
||||
|
||||
// Unused, but demonstrates how a 3x3 matrix could be constructed from Rotation.
|
||||
fn RotationToMat3(Rotation r)
|
||||
{
|
||||
Mat3 mat;
|
||||
mat.r0.v[r.firstRowIndex] = -(r.firstRowSign * 2 - 1);
|
||||
mat.r1.v[r.secondRowIndex] = -(r.secondRowSign * 2 - 1);
|
||||
mat.r2.v[0] = -(r.thirdRowSign * 2.0 - 1) * !(r.firstRowIndex == 0 || r.secondRowIndex == 0);
|
||||
mat.r2.v[1] = -(r.thirdRowSign * 2.0 - 1) * !(r.firstRowIndex == 1 || r.secondRowIndex == 1);
|
||||
mat.r2.v[2] = -(r.thirdRowSign * 2.0 - 1) * !(r.firstRowIndex == 2 || r.secondRowIndex == 2);
|
||||
return mat;
|
||||
};
|
||||
|
||||
struct KeyValue
|
||||
{
|
||||
String key;
|
||||
// Depending on the key, the value must be parsed as one or more floats or integers.
|
||||
String value;
|
||||
} [[format("FormatKeyValue")]];
|
||||
|
||||
fn FormatKeyValue(ref KeyValue kv)
|
||||
{
|
||||
return std::format("{}: {}", kv.key, kv.value);
|
||||
};
|
||||
|
||||
struct Dict
|
||||
{
|
||||
u32 count;
|
||||
KeyValue pairs[count] [[inline]];
|
||||
} [[format("FormatDict")]];
|
||||
|
||||
fn FormatDict(ref Dict d)
|
||||
{
|
||||
return std::format("count: {}", d.count);
|
||||
};
|
||||
|
||||
struct Model
|
||||
{
|
||||
s32 modelId;
|
||||
Dict attributes;
|
||||
};
|
||||
|
||||
struct Chunk
|
||||
{
|
||||
char id[4];
|
||||
u32 sizeBytes;
|
||||
u32 childrenBytes;
|
||||
|
||||
// Contents of the chunk
|
||||
s32 cursorPosBeforeContent = $;
|
||||
match (id)
|
||||
{
|
||||
("MAIN"): {}
|
||||
("PACK"):
|
||||
{
|
||||
u32 numModels;
|
||||
}
|
||||
("SIZE"):
|
||||
{
|
||||
u32 sizeX, sizeY, sizeZ;
|
||||
}
|
||||
("XYZI"):
|
||||
{
|
||||
u32 numVoxels;
|
||||
Voxel voxels[numVoxels];
|
||||
}
|
||||
("RGBA"):
|
||||
{
|
||||
RGBA8 colors[256];
|
||||
}
|
||||
("nTRN"):
|
||||
{
|
||||
s32 nodeId;
|
||||
Dict attributes;
|
||||
s32 childNodeId;
|
||||
s32 reservedMustBeNegative1;
|
||||
s32 layerId;
|
||||
u32 numFrames;
|
||||
Dict frames[numFrames];
|
||||
}
|
||||
("nGRP"):
|
||||
{
|
||||
s32 nodeId;
|
||||
Dict attributes;
|
||||
u32 numChildren;
|
||||
s32 childrenIds[numChildren];
|
||||
}
|
||||
("nSHP"):
|
||||
{
|
||||
s32 nodeId;
|
||||
Dict attributes;
|
||||
u32 numModels;
|
||||
Model models[numModels];
|
||||
}
|
||||
("MATL"): // Material properties
|
||||
{
|
||||
s32 materialId;
|
||||
Dict attributes;
|
||||
}
|
||||
("LAYR"):
|
||||
{
|
||||
s32 layerId;
|
||||
Dict attributes;
|
||||
s32 reservedMustBeNegative1;
|
||||
}
|
||||
("rOBJ"): // Rendering attributes
|
||||
{
|
||||
Dict attributes;
|
||||
}
|
||||
("rCAM"): // Camera attributes
|
||||
{
|
||||
s32 cameraId;
|
||||
Dict attributes;
|
||||
}
|
||||
("NOTE"): // Names for colors
|
||||
{
|
||||
u32 numNames;
|
||||
String names[numNames];
|
||||
}
|
||||
("IMAP"): // Index map
|
||||
{
|
||||
// The documentation says this field is i32x256, but it's u8x256 in actual models.
|
||||
u8 paletteIndices[256];
|
||||
}
|
||||
(_):
|
||||
{
|
||||
u8 unknownData[sizeBytes];
|
||||
std::warning(std::format("Unknown chunk ID at 0x{:X}: \"{}\"", $, id));
|
||||
}
|
||||
}
|
||||
|
||||
s32 actualContentSize = ($ - cursorPosBeforeContent);
|
||||
if (actualContentSize != sizeBytes)
|
||||
{
|
||||
str warningTextSecondHalf = std::format("Content size mismatch! Expected: {}. Actual: {}", sizeBytes, actualContentSize);
|
||||
str warningText = std::format("Chunk at 0x{:X} with id {}: {}", addressof(this), id, warningTextSecondHalf);
|
||||
if (attemptRecoveryOnChunkSizeMismatch)
|
||||
{
|
||||
std::warning(warningText);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::error(warningText);
|
||||
}
|
||||
// Limited recovery- reading past EoF is still possible if chunk is nested.
|
||||
// Setting readChunksAsLinear to true can make recovery more robust as the outermost array will be broken out of.
|
||||
break;
|
||||
}
|
||||
|
||||
if (childrenBytes > 0 && !readChunksAsLinear)
|
||||
{
|
||||
s32 cursorPosBeforeChildren = $;
|
||||
Chunk children[while(true)];
|
||||
}
|
||||
|
||||
// The node with id MAIN is also the root node.
|
||||
if (id != "MAIN" && !readChunksAsLinear)
|
||||
{
|
||||
// We are done parsing the children for this parent if the cursor has advanced parent.childrenBytes bytes since then.
|
||||
if ($ - parent.cursorPosBeforeChildren >= parent.childrenBytes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
} [[format("ChunkFormatter")]];
|
||||
|
||||
fn ChunkFormatter(ref Chunk chunk)
|
||||
{
|
||||
return chunk.id;
|
||||
};
|
||||
|
||||
VoxHeader header @ 0 [[inline]];
|
||||
Chunk mainChunk[readChunksAsLinear ? 0 : 1] @ $ [[inline]];
|
||||
Chunk chunks[while(readChunksAsLinear && !std::mem::eof())] @ $;
|
||||
|
||||
if (std::core::member_count(mainChunk) > 0)
|
||||
{
|
||||
std::core::set_display_name(mainChunk[0], "mainChunk");
|
||||
}
|
||||
Reference in New Issue
Block a user