Public
Edited
Jan 7
Importers
8 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
parseChunks = (buffer) => {
const iterator = buffer[Symbol.iterator]();
const BYTE = () => iterator.next().value;
const VOXEL = () => ({ x: BYTE(), y: BYTE(), z: BYTE(), color: BYTE() });
const RGBA = () => ({ r: BYTE(), g: BYTE(), b: BYTE(), a: BYTE() });
const LONG = () => BYTE() + (BYTE() << 8) + (BYTE() << 16) + (BYTE() << 24);
const array = (n, fn) => [...Array(n)].map(fn);
const STR = (n) => array(n, () => String.fromCharCode(BYTE())).join("");
const DICT = () => new Map(array(LONG(), (_) => [STR(LONG()), STR(LONG())]));
const BASE = () => ({ id: LONG(), attrs: DICT() });
const FRAME = () => array(LONG(), DICT)[0];
const TRN = () => ({ child: LONG(), layer: LONG(LONG()), transfo: FRAME() });
const MODEL = () => array(LONG(), BASE)[0];
const chunkParsers = {
MAIN: () => {}, // only children chunks
PACK: () => ({ nbModels: LONG() }),
SIZE: () => ({ x: LONG(), y: LONG(), z: LONG() }),
XYZI: () => ({ voxels: array(LONG(), VOXEL) }),
RGBA: () => ({ palette: [{}, ...array(256, RGBA)] }), // color 0 reserved ?
// extensions
nTRN: () => ({ ...BASE(), ...TRN() }),
nGRP: () => ({ ...BASE(), ids: array(LONG(), LONG) }),
nSHP: () => ({ ...BASE(), model: MODEL() }),
MATL: () => ({ ...BASE() }),
LAYR: () => ({ ...BASE(), reserved: LONG() }),
rOBJ: () => ({ attrs: DICT() }) // not documented: background, grid, fog, etc.
};
const CHUNK = () => {
const [type, dataLength, childrenLength] = [STR(4), LONG(), LONG()];
const chunkLength = 4 + 4 + 4 + dataLength + childrenLength;
const parser = chunkParsers[type];
// if chunk type is unknown, the corresponding bytes are skipped
const data = parser ? parser() : { data: STR(dataLength) };
const chunks = function* (rest) {
while (rest > 0) {
const { chunk: child, length } = CHUNK();
yield child;
rest -= length;
}
};
const chunk = { type, ...data };
if (childrenLength) chunk.children = [...chunks(childrenLength)];
return { chunk, length: chunkLength };
};

STR(4); // "VOX "
LONG(); // version number: 150
return CHUNK().chunk;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
vox2scene = (buffer) => {
const { children } = parseChunks(buffer);
const chunks = children.reduce((chunks, chunk) => {
const { type } = chunk;
(chunks[type] = chunks[type] || []).push(chunk);
return chunks;
}, {});

// build deduped color palette
const { palette } = chunks.RGBA[0]; // array of 257x{r, g, b, a}
const globalColorMap = new Map();
const globalColors = new Map();
const idMap = new Map();
let colorId = 0;
const rgb2hex = (r, g, b) =>
((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1);
palette.slice(1).forEach(({ r, g, b, a }, index) => {
const hexColor = rgb2hex(r, g, b);
if (!globalColors.has(hexColor)) {
globalColors.set(hexColor, [r, g, b]);
globalColorMap.set(++colorId, hexColor);
}
idMap.set(index, colorId);
});

/*let materials = [];
const matlMap = new Map();
if (chunks.MATL) {
// array of 256x{ ... }
materials = chunks.MATL.map(({ attrs }) => ({
// https://docs.rs/vox-format/0.1.0/vox_format/types/struct.Material.html
//_rough: float
//_type: "_diffuse" | "_metal" | "_glass" | "_emit"
//_weight: float [0, 1]
//_rough: float
//_spec: float
//_ior: float
//_att: float
//_flux: float
//_plastic
rough: 0,
metal: attrs._type == "_metal" ? 1 - attrs.weight : 0,
emit: attrs._type == "_emit" ? attrs.weight : 0,
transparent: attrs._type == "_glass" ? 1 - attrs.weight : 0,
refract: 0
}));
}*/

// load voxel models
const colors = new Map(); // colors effectively used in models
const model = (voxVoxels) => {
const voxels = [];
const colorMap = new Map();
voxVoxels.forEach(({ x, y, z, color }) => {
const colorId = idMap.get(color);
const colorHex = globalColorMap.get(colorId);
colorMap.set(colorId, colorHex);
colors.set(colorHex, globalColors.get(colorHex));
voxels.push([x, y, z, colorId]);
});
return { voxels, colorMap };
};
const models = new Map(
chunks.XYZI.map(({ voxels }, i) => [i, model(voxels)])
);

// build scene tree
const scene = (root) => ({ models, colors, root });
if (!chunks.nTRN) return scene({ translation: [0, 0, 0], model: 0 });

const chunkMap = (chunks) =>
new Map(chunks.map((chunk) => [chunk.id, chunk]));
const transfos = chunkMap(chunks.nTRN);
const groups = chunkMap(chunks.nGRP);
const shapes = chunkMap(chunks.nSHP);
const splitV3 = (str) => str.split(" ").map((value) => +value);
const recTree = ({ child: childId, transfo }) => {
const child = groups.get(childId) || shapes.get(childId);
const node = { translation: splitV3(transfo.get("_t") || "0 0 0") };
if (child.model) node.model = child.model.id;
else node.children = child.ids.map((id) => recTree(transfos.get(id)));
return node;
};
return scene(recTree(transfos.get(0)));
}
Insert cell
Insert cell
scene2vixel = (scene) => {
const { models, colors, materials, root } = scene;
const data = {
xyz: [],
rgb: [],
rough: [],
metal: [],
emit: [],
transparent: [],
refract: []
};
let [xmax, ymax, zmax] = [0, 0, 0];
const addV3 = ([x1, y1, z1], [x2, y2, z2]) => [x1 + x2, y1 + y2, z1 + z2];
const recTree = (node, translation) => {
const [tx, ty, tz] = addV3(translation, node.translation);
if (node.children) {
node.children.forEach((child) => recTree(child, [tx, ty, tz]));
} else {
const { voxels, colorMap, matlMap } = models.get(node.model);
for (const [x, y, z, colorId, mtl] of voxels) {
const [X, Y, Z] = addV3([x, y, z], [tx, ty, tz]);
data.xyz.push(Y, Z, X); // Vixel uses y axis as height
xmax = Math.max(xmax, X);
ymax = Math.max(ymax, Y);
zmax = Math.max(zmax, Z);
const [r, g, b] = colors.get(colorMap.get(colorId));
data.rgb.push(r, g, b);
if (mtl) {
const material = materials.get(matlMap.get(mtl));
const { rough, metal, emit, transparent, refract } = material;
data.rough.push(rough);
data.metal.push(metal);
data.emit.push(emit);
data.transparent.push(transparent);
data.refract.push(refract);
}
}
}
};
recTree(root, [0, 0, 0]);

return { width: ++ymax, height: ++zmax, depth: ++xmax, ...data };
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more