vox-reader
Version:
Takes a Byte Array of .vox file data and returns a JavaScript Object with all the containing informations
162 lines (149 loc) • 4.34 kB
text/typescript
import readString from "../shared/readString/readString";
import readInt from '../shared/readInt/readInt';
import groupArray from '../shared/groupArray/groupArray';
const BLOCK_SIZE = 4;
const flatten = (array : Array<any>) =>
[].concat.apply([], array);
const readDict = (contentData: number[]) => {
const dict: any = {};
let i = 0
const amount = readInt(contentData.splice(0,4))
while(i < amount) {
const keyLength = readInt(contentData.splice(0,4));
const key = readString(flatten(contentData.splice(0,keyLength)));
const valueLength = readInt(contentData.splice(0,4));
const value = readString(contentData.splice(0,valueLength));
i++;
dict[key] = value;
}
return dict
}
const parseVoxChunk = (id : string, contentData : Array<number>) =>
{
const tokens = groupArray(contentData, BLOCK_SIZE);
// base https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox.txt
if(id === 'PACK') return {
numModels: readInt(tokens[0])
};
if(id === 'SIZE'){
if(!tokens[0]){
console.log('SIZE chunk has no data')
}
return {
x: readInt(tokens[0]),
y: readInt(tokens[1]),
z: readInt(tokens[2])
}
}
if(id === 'XYZI') return {
numVoxels: readInt(tokens[0]),
values: tokens
.slice(1)
.map((c : Array<number>) => ({ x: c[0], y: c[1], z: c[2], i: c[3] }))
}
if(id === 'RGBA') return {
values: tokens
.map((c : Array<number>) => ({ r: c[0], g: c[1], b: c[2], a: c[3] }))
}
type frameAttributes = {
_t: string,
}
type nodeAttributes = {
_name: string,
_hidden?: 0 | 1,
}
// extended https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox-extension.txt
if(id === 'nTRN') {
const obj: {
nodeId: number,
nodeAttributes: nodeAttributes,
child: number,
reserved: number,
layer: number,
numFrames: number,
frames: frameAttributes[],
} = {
nodeId: readInt(contentData.splice(0,4)),
nodeAttributes: readDict(contentData),
child: readInt(contentData.splice(0,4)),
reserved: readInt(contentData.splice(0,4)),
layer: readInt(contentData.splice(0,4)),
numFrames: readInt(contentData.splice(0,4)),
frames: [],
}
for(let i = 0; i < obj.numFrames; i++) {
obj.frames.push(readDict(contentData));
}
return obj;
}
if (id === 'nGRP') {
const obj: {
nodeId: number,
nodeAttributes: nodeAttributes,
child: number,
children: number[],
} = {
nodeId: readInt(contentData.splice(0,4)),
nodeAttributes: readDict(contentData),
child: readInt(contentData.splice(0,4)),
children: [],
}
for(let i = 0; i < obj.child; i++) {
obj.children.push(readInt(contentData.splice(0,4)));
}
return obj;
}
if (id === 'nSHP') {
const obj: {
nodeId: number,
nodeAttributes: any,
numModels: number,
models: any[],
} = {
nodeId: readInt(contentData.splice(0,4)),
nodeAttributes: readDict(contentData),
numModels: readInt(contentData.splice(0,4)),
models: [],
}
for(let i = 0; i < obj.numModels; i++) {
obj.models.push([readInt(contentData.splice(0,4)),readDict(contentData)]);
}
return obj;
}
if (id === 'MATL') return {
materialId: readInt(contentData.splice(0,4)),
materialProperties: readDict(contentData),
}
if(id === 'LAYR') return {
layerId: readInt(contentData.splice(0,4)),
layerAttributes: readDict(contentData),
reservedId: readInt(contentData.splice(0,4)),
}
if(id === 'rOBJ') return {
renderAttributes: readDict(contentData),
}
if (id === 'rCAM') return {
cameraId: readInt(contentData.splice(0,4)),
cameraAttributes: readDict(contentData),
}
if (id === 'NOTE') {
const obj: {
numColorNames: number,
colorNames: string[],
} = {
numColorNames: readInt(contentData.splice(0,4)),
colorNames: [],
}
for(let i = 0; i < obj.numColorNames; i++) {
const stringLength = readInt(contentData.splice(0,4));
obj.colorNames.push(readString(flatten(contentData.splice(0,stringLength))));
}
return obj;
}
if (id === 'IMAP') {
return {
indexAssociations: contentData.splice(0,256),
}}
return {};
}
export = parseVoxChunk;