render voxel models

This commit is contained in:
Zack Buhman 2026-07-18 14:28:40 -05:00
parent 2dd13ddd5a
commit e5cacffaee
12 changed files with 466 additions and 75 deletions

View File

@ -2,8 +2,18 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGPU</title> <title>WebGPU</title>
<style> <style>
:root {
color-scheme: light dark;
}
div, span {
font: 0.95em sans-serif;
}
select, option {
font: 0.95em sans-serif;
}
canvas { canvas {
position: absolute; position: absolute;
left: 0; left: 0;
@ -13,10 +23,67 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.control-root {
position: fixed;
left: 0;
top: 0;
right: 0;
height: 0;
background: #eee;
}
.control-main {
width: 250px;
float: right;
margin-right: 15px;
}
.label {
width: calc(40% - 0.3rem);
float: left;
clear: left;
overflow: hidden;
margin-left: 0.3rem;
margin-top: 0.25rem;
}
.value {
width: calc(60% - 0.2rem);
float: left;
margin-top: 0.1rem;
margin-right: 0.2rem;
}
.row:not(:last-child) {
border-bottom: 1px solid #999;
}
.row {
background: #444;
height: 1.5rem;
}
</style> </style>
<script src="index.js" type="module"></script> <script src="index.js" type="module"></script>
</head> </head>
<body> <body>
<canvas></canvas> <canvas></canvas>
<div class="control-root">
<div class="control-main">
<div class="row">
<span class="label">model</span>
<select class="value" id="model-select"></select>
</div>
<div class="row">
<span class="label">lighting</span>
<select class="value" id="lighting-select">
<option>unlit</option>
<option>diffuse</option>
</select>
</div>
<!--
<div class="row">
<span class="label">test2</span>
<select class="value">
<option value="test2">test2</option>
</select>
</div>
-->
</div>
</div>
</body> </body>
</html> </html>

235
index.js
View File

@ -35,7 +35,6 @@ renderPass.end();
const commandBuffer = encoder.finish(); const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]); device.queue.submit([commandBuffer]);
const cubeResponse = await fetch("obj/cube.bin"); const cubeResponse = await fetch("obj/cube.bin");
const cubeObj = await cubeResponse.arrayBuffer(); const cubeObj = await cubeResponse.arrayBuffer();
@ -45,8 +44,8 @@ const indexBufferOffset = cubeView.getUint32(4, true);
const indexBufferSize = cubeView.getUint32(8, true); const indexBufferSize = cubeView.getUint32(8, true);
const vertexBufferOffset = cubeView.getUint32(12, true); const vertexBufferOffset = cubeView.getUint32(12, true);
const vertexBufferSize = cubeView.getUint32(16, true); const vertexBufferSize = cubeView.getUint32(16, true);
console.log(`indexBufferOffset ${indexBufferOffset} indexBufferSize ${indexBufferSize}`); //console.log(`indexBufferOffset ${indexBufferOffset} indexBufferSize ${indexBufferSize}`);
console.log(`vertexBufferOffset ${vertexBufferOffset} vertexBufferSize ${vertexBufferSize}`); //console.log(`vertexBufferOffset ${vertexBufferOffset} vertexBufferSize ${vertexBufferSize}`);
const cubeIndexCount = indexBufferSize / 2; const cubeIndexCount = indexBufferSize / 2;
const cubeVerticesStride = 36; const cubeVerticesStride = 36;
@ -59,27 +58,101 @@ const indexVertexBuffer = device.createBuffer({
device.queue.writeBuffer(indexVertexBuffer, 0, cubeObj, indexBufferOffset, indexBufferSize); device.queue.writeBuffer(indexVertexBuffer, 0, cubeObj, indexBufferOffset, indexBufferSize);
device.queue.writeBuffer(indexVertexBuffer, indexBufferSize, cubeObj, vertexBufferOffset, vertexBufferSize); device.queue.writeBuffer(indexVertexBuffer, indexBufferSize, cubeObj, vertexBufferOffset, vertexBufferSize);
const vertexBufferLayout = { //////////////////////////////////////////////////////////////////////
arrayStride: cubeVerticesStride, // instance buffer
attributes: [{ //////////////////////////////////////////////////////////////////////
format: "float32x3",
offset: 0, async function loadVoxModel(path)
shaderLocation: 0, {
}, { const voxResponse = await fetch(path);
format: "float32x2", const vox = await voxResponse.arrayBuffer();
offset: 12, const voxView = new DataView(vox);
shaderLocation: 1, const sizeX = voxView.getUint32(0, true);
}, { const sizeY = voxView.getUint32(4, true);
format: "float16x4", const sizeZ = voxView.getUint32(8, true);
offset: 20, const voxelCount = voxView.getUint32(12, true);
shaderLocation: 2,
}, { const instanceBuffer = device.createBuffer({
format: "float16x4", label: `instanceBuffer %{}`,
offset: 28, size: voxelCount * 4,
shaderLocation: 3, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
}], });
stepMode: "vertex", device.queue.writeBuffer(instanceBuffer, 0, vox, 16, voxelCount * 4);
};
const modelConfigurationBuffer = device.createBuffer({
label: "modelConfigurationBuffer",
size: 4 * 4 + 256 * 4 * 4,
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
});
const paletteOffset = 16 + voxelCount * 4;
const sizeArray = new Float32Array([sizeX, sizeY, sizeZ, 0]);
device.queue.writeBuffer(modelConfigurationBuffer, 0, sizeArray);
device.queue.writeBuffer(modelConfigurationBuffer, 4 * 4, vox, paletteOffset, 256 * 4 * 4);
return {
instanceBuffer: instanceBuffer,
modelConfigurationBuffer: modelConfigurationBuffer,
voxelCount: voxelCount,
path: path,
}
}
const modelPaths = [
"vox/monu1.bin",
"vox/monu7.bin",
"vox/monu9.bin",
"vox/monu16.bin",
"vox/chr_fox.bin",
"vox/ff3.bin",
"vox/dragon.bin",
];
const models = {}
for (const path of modelPaths) {
models[path] = loadVoxModel(path);
}
for (const path of modelPaths) {
models[path] = await models[path];
}
var currentModel = modelPaths[0];
//////////////////////////////////////////////////////////////////////
// vertex layout
//////////////////////////////////////////////////////////////////////
const vertexBufferLayouts = [
{
arrayStride: cubeVerticesStride,
attributes: [{
format: "float32x3",
offset: 0,
shaderLocation: 0,
}, {
format: "float32x2",
offset: 12,
shaderLocation: 1,
}, {
format: "float16x4",
offset: 20,
shaderLocation: 2,
}, {
format: "float16x4",
offset: 28,
shaderLocation: 3,
}],
stepMode: "vertex",
},
{
arrayStride: 4,
attributes: [{
format: "uint8x4",
offset: 0,
shaderLocation: 4,
}],
stepMode: "instance",
},
];
function getPath(path) function getPath(path)
{ {
@ -91,31 +164,44 @@ function getPath(path)
}).then((blob) => { return blob.text() }) }).then((blob) => { return blob.text() })
} }
const indexWgsl = getPath("index.wgsl");
const computeWgsl = getPath("compute.wgsl");
const cellShaderModule = device.createShaderModule({ const cellShaderModule = device.createShaderModule({
label: "cell shader", label: "cell shader",
code: await getPath("index.wgsl") code: await indexWgsl,
}); });
const bindGroupLayout = device.createBindGroupLayout({ const bindGroupLayouts = [
label: "bind group layout", device.createBindGroupLayout({
entries: [{ label: "bind group layout 0",
binding: 0, entries: [{
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE, binding: 0,
buffer: { type: "uniform" } visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
}, { buffer: { type: "uniform" }
binding: 1, }, {
visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE, binding: 1,
buffer: { type: "read-only-storage" } visibility: GPUShaderStage.VERTEX | GPUShaderStage.COMPUTE,
}, { buffer: { type: "read-only-storage" }
binding: 2, }, {
visibility: GPUShaderStage.COMPUTE, binding: 2,
buffer: { type: "storage" } visibility: GPUShaderStage.COMPUTE,
}] buffer: { type: "storage" }
}); }]
}),
device.createBindGroupLayout({
label: "bind group layout 0",
entries: [{
binding: 0,
visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
buffer: { type: "uniform" }
}]
}),
];
const pipelineLayout = device.createPipelineLayout({ const pipelineLayout = device.createPipelineLayout({
label: "pipeline layout", label: "pipeline layout",
bindGroupLayouts: [ bindGroupLayout ], bindGroupLayouts: bindGroupLayouts,
}); });
const cellPipeline = device.createRenderPipeline({ const cellPipeline = device.createRenderPipeline({
@ -124,7 +210,7 @@ const cellPipeline = device.createRenderPipeline({
vertex: { vertex: {
module: cellShaderModule, module: cellShaderModule,
entryPoint: "vertexMain", entryPoint: "vertexMain",
buffers: [vertexBufferLayout], buffers: vertexBufferLayouts,
}, },
fragment: { fragment: {
module: cellShaderModule, module: cellShaderModule,
@ -145,12 +231,17 @@ const cellPipeline = device.createRenderPipeline({
const computeShaderModule = device.createShaderModule({ const computeShaderModule = device.createShaderModule({
label: "compute shader", label: "compute shader",
code: await getPath("compute.wgsl") code: await computeWgsl,
});
const computePipelineLayout = device.createPipelineLayout({
label: "compute pipeline layout",
bindGroupLayouts: [ bindGroupLayouts[0] ],
}); });
const computePipeline = device.createComputePipeline({ const computePipeline = device.createComputePipeline({
label: "compute pipeline", label: "compute pipeline",
layout: pipelineLayout, layout: computePipelineLayout,
compute: { compute: {
module: computeShaderModule, module: computeShaderModule,
entryPoint: "computeMain", entryPoint: "computeMain",
@ -163,11 +254,11 @@ const workGroupSize = 16;
//const uniformArray2 = new Float32Array([gridSize, gridSize, 1, 1]); //const uniformArray2 = new Float32Array([gridSize, gridSize, 1, 1]);
const memory = new WebAssembly.Memory({ initial: 1 }); const memory = new WebAssembly.Memory({ initial: 1 });
const uniformArray = new Float32Array(memory.buffer); const uniformArray = new Float32Array(memory.buffer);
uniformArray[0] = gridSize; uniformArray[0] = 1;
uniformArray[1] = gridSize; uniformArray[1] = 0;
uniformArray[2] = 1; uniformArray[2] = 0;
uniformArray[3] = 1; uniformArray[3] = 0;
const uniformArraySize = 4 * 4 + 4 * 4 * 4; const uniformArraySize = 4 * 4 + (4 * 4 * 4) * 2;
const rotate = await WebAssembly.instantiateStreaming(fetch("rotate.wasm"), { const rotate = await WebAssembly.instantiateStreaming(fetch("rotate.wasm"), {
env: { memory: memory } env: { memory: memory }
}); });
@ -203,7 +294,7 @@ device.queue.writeBuffer(cellStateStorage[1], 0, cellStateArray);
const bindGroups = [ const bindGroups = [
device.createBindGroup({ device.createBindGroup({
label: "cell bind group 0", label: "cell bind group 0",
layout: bindGroupLayout, layout: bindGroupLayouts[0],
entries: [{ entries: [{
binding: 0, binding: 0,
resource: { buffer: uniformBuffer }, resource: { buffer: uniformBuffer },
@ -217,7 +308,7 @@ const bindGroups = [
}), }),
device.createBindGroup({ device.createBindGroup({
label: "cell bind group 1", label: "cell bind group 1",
layout: bindGroupLayout, layout: bindGroupLayouts[0],
entries: [{ entries: [{
binding: 0, binding: 0,
resource: { buffer: uniformBuffer }, resource: { buffer: uniformBuffer },
@ -231,6 +322,19 @@ const bindGroups = [
}) })
]; ];
const modelBindGroups = {};
for (const path of modelPaths) {
const bindGroup = device.createBindGroup({
label: "palette bind group",
layout: bindGroupLayouts[1],
entries: [{
binding: 0,
resource: { buffer: models[path].modelConfigurationBuffer },
}]
});
modelBindGroups[path] = bindGroup;
}
let tick = 0; let tick = 0;
let which = 0; let which = 0;
@ -244,6 +348,18 @@ function createDepthTexture() {
var depthTexture = undefined; var depthTexture = undefined;
const modelSelect = document.getElementById("model-select");
for (const path of modelPaths) {
const option = document.createElement("option");
option.value = path;
option.innerHTML = path;
modelSelect.appendChild(option);
}
modelSelect.value = currentModel;
const lightingSelect = document.getElementById("lighting-select");
lightingSelect.value = "unlit";
function render() function render()
{ {
if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height) { if (canvas.clientWidth !== canvas.width || canvas.clientWidth !== canvas.height) {
@ -273,12 +389,10 @@ function render()
which ^= 1; which ^= 1;
} }
if (canvas.width > canvas.height) { if (lightingSelect.value === "diffuse") {
uniformArray[2] = aspect; uniformArray[0] = 1;
uniformArray[3] = 1;
} else { } else {
uniformArray[2] = 1; uniformArray[0] = 0;
uniformArray[3] = 1 / aspect;
} }
device.queue.writeBuffer(uniformBuffer, 0, uniformArray, 0, uniformArraySize / 4); device.queue.writeBuffer(uniformBuffer, 0, uniformArray, 0, uniformArraySize / 4);
@ -297,14 +411,19 @@ function render()
}, },
}); });
currentModel = modelSelect.value;
renderPass.setPipeline(cellPipeline); renderPass.setPipeline(cellPipeline);
const vertexOffset = indexBufferSize; const vertexOffset = indexBufferSize;
renderPass.setVertexBuffer(0, indexVertexBuffer, vertexOffset, vertexBufferSize); renderPass.setVertexBuffer(0, indexVertexBuffer, vertexOffset, vertexBufferSize);
renderPass.setVertexBuffer(1, models[currentModel].instanceBuffer);
renderPass.setIndexBuffer(indexVertexBuffer, "uint16", 0, indexBufferSize); renderPass.setIndexBuffer(indexVertexBuffer, "uint16", 0, indexBufferSize);
renderPass.setBindGroup(0, bindGroups[which]); renderPass.setBindGroup(0, bindGroups[which]);
renderPass.setBindGroup(1, modelBindGroups[currentModel]);
//renderPass.draw(cubeVerticesCount, gridSize * gridSize); //renderPass.draw(cubeVerticesCount, gridSize * gridSize);
renderPass.drawIndexed(cubeIndexCount, gridSize * gridSize); //renderPass.drawIndexed(cubeIndexCount, gridSize * gridSize);
//renderPass.drawIndexed(cubeIndexCount); //renderPass.drawIndexed(cubeIndexCount);
renderPass.drawIndexed(cubeIndexCount, models[currentModel].voxelCount);
renderPass.end(); renderPass.end();

View File

@ -1,29 +1,41 @@
struct Configuration { struct Configuration {
gridSize: vec2f, lighting: vec4f,
aspect: vec2f, matrix: mat4x4f,
matrix: mat4x4f matrixWorld: mat4x4f,
}; };
@group(0) @binding(0) var<uniform> config: Configuration; @group(0) @binding(0) var<uniform> config: Configuration;
@group(0) @binding(1) var<storage> state: array<u32>; @group(0) @binding(1) var<storage> state: array<u32>;
struct ModelConfiguration {
size: vec4f,
color: array<vec4f, 256>,
};
@group(1) @binding(0) var<uniform> modelConfiguration: ModelConfiguration;
struct VertexInput { struct VertexInput {
@location(0) position: vec3f, @location(0) position: vec3f,
@location(1) texture: vec2f, @location(1) texture: vec2f,
@location(2) normal: vec3f, @location(2) normal: vec3f,
@location(3) tangent: vec4f, @location(3) tangent: vec4f,
@location(4) instancePosition: vec4<u32>,
@builtin(instance_index) instance: u32, @builtin(instance_index) instance: u32,
}; };
struct VertexOutput { struct VertexOutput {
@builtin(position) position: vec4f, @builtin(position) position: vec4f,
@location(0) cell: vec2f, @location(0) positionWorld: vec3f,
@location(1) normal: vec3f, @interpolate(flat) @location(1) colorIndex: i32,
@location(2) normal: vec3f,
@interpolate(flat) @location(3) lighting: i32,
}; };
struct FragmentInput { struct FragmentInput {
@location(0) cell: vec2f, @location(0) positionWorld: vec3f,
@location(1) normal: vec3f, @interpolate(flat) @location(1) colorIndex: i32,
@location(2) normal: vec3f,
@interpolate(flat) @location(3) lighting: i32,
}; };
@vertex @vertex
@ -32,23 +44,75 @@ fn vertexMain(input: VertexInput) -> VertexOutput
let position = input.position * 0.5 + 0.5; let position = input.position * 0.5 + 0.5;
let instance = f32(input.instance); let instance = f32(input.instance);
/*
let state = f32(state[input.instance]); let state = f32(state[input.instance]);
let cell = vec2f(instance % config.gridSize.x, floor(instance / config.gridSize.y)); let cell = vec2f(instance % config.gridSize.x, floor(instance / config.gridSize.y));
let grid = (position * state + vec3f(cell, 0)) / config.gridSize.xxx; let grid = (position * state + vec3f(cell, 0)) / config.gridSize.xxx;
*/
let size = max(max(modelConfiguration.size.x, modelConfiguration.size.y), modelConfiguration.size.z);
let cell = (position * 1 + vec3f(input.instancePosition.xzy) - (modelConfiguration.size.xzy/2)) / size;
var output: VertexOutput; var output: VertexOutput;
//output.position = (config.matrix * vec4f((grid * 2 - 1), 1)) * vec4f(config.aspect, 1, 1); output.position = config.matrix * vec4f(cell, 1);
output.position = config.matrix * vec4f((grid * 2 - 1), 1); output.positionWorld = (config.matrixWorld * vec4f(cell, 1.0f)).xyz;
output.colorIndex = i32(input.instancePosition.w);
//output.position = config.matrix * vec4f(input.position, 1); output.normal = (config.matrixWorld * vec4f(input.normal, 0.0f)).xyz;
output.lighting = i32(config.lighting.x != 0.0);
output.cell = cell / config.gridSize;
output.normal = input.normal;
return output; return output;
} }
fn schlickFresnel(R0: vec3f, normal: vec3f, lightVector: vec3f) -> vec3f
{
let incidentAngle = saturate(dot(normal, lightVector));
let f0 = 1.0 - incidentAngle;
let reflectPercent = R0 + (1.0f - R0) * (f0 * f0 * f0 * f0 * f0);
return reflectPercent;
}
fn blinnPhong(ndotl: f32, lightVector: vec3f, normal: vec3f, toEye: vec3f) -> vec3f
{
let roughness = 0.1;
let m = roughness * 256.0;
let halfVec = normalize(toEye + lightVector);
let roughnessFactor = (m + 8.0) * pow(ndotl, m) / 8.0f;
let fresnelR0 = vec3f(0.95f, 0.95f, 0.95f);
let fresnelFactor = schlickFresnel(fresnelR0, normal, lightVector);
let specular = fresnelFactor * roughnessFactor;
return specular;
}
fn lighting(position: vec3f, normal: vec3f) -> vec3f
{
let d = 0.9;
let eyePosition = vec3f(d, d / 2, d);
let lightPosition = vec3f(1, 1, 1);
let lightVector = normalize(lightPosition - position);
let ndotl = max(dot(lightVector, normal), 0.0);
let intensity = (ndotl + 0.3) / 1.3;
let toEye = normalize(eyePosition - position);
return (1.0 + blinnPhong(ndotl, lightVector, normal, toEye)) * intensity;
}
@fragment @fragment
fn fragmentMain(input: FragmentInput) -> @location(0) vec4f fn fragmentMain(input: FragmentInput) -> @location(0) vec4f
{ {
return vec4f(input.normal * 0.5 + 0.5, 1); let baseColor = modelConfiguration.color[input.colorIndex - 1] / 255.0;
var intensity = vec3f(1.0);
if (input.lighting == 1) {
intensity = lighting(input.positionWorld, normalize(input.normal));
}
return vec4(baseColor.xyz * intensity, baseColor.w);
} }

View File

@ -9,8 +9,8 @@ void rotate(float angle, float aspect, float * buffer)
//XMMATRIX mat2 = XMMatrixTranslation(0, 0, 1); //XMMATRIX mat2 = XMMatrixTranslation(0, 0, 1);
//XMStoreFloat4x4((XMFLOAT4X4*)buffer, mat * mat2); //XMStoreFloat4x4((XMFLOAT4X4*)buffer, mat * mat2);
float d = 0.9;
XMVECTOR eye = XMVectorSet(3, 3, 3, 0); XMVECTOR eye = XMVectorSet(d, d / 2, d, 0);
XMVECTOR at = XMVectorSet(0, 0, 0, 0); XMVECTOR at = XMVectorSet(0, 0, 0, 0);
XMVECTOR up = XMVectorSet(0, 1, 0, 0); XMVECTOR up = XMVectorSet(0, 1, 0, 0);
XMMATRIX view = XMMatrixLookAtRH(eye, at, up); XMMATRIX view = XMMatrixLookAtRH(eye, at, up);
@ -22,4 +22,5 @@ void rotate(float angle, float aspect, float * buffer)
XMMATRIX rot = XMMatrixRotationY(angle); XMMATRIX rot = XMMatrixRotationY(angle);
XMStoreFloat4x4((XMFLOAT4X4*)buffer, rot * view * projection); XMStoreFloat4x4((XMFLOAT4X4*)buffer, rot * view * projection);
XMStoreFloat4x4((XMFLOAT4X4*)(buffer + 4 * 4), rot);
} }

140
tools/vox.py Normal file
View File

@ -0,0 +1,140 @@
import struct
from functools import partial
from dataclasses import dataclass
import sys
with open(sys.argv[1], "rb") as f:
buf = memoryview(f.read())
class Reader:
def __init__(self, buf):
self.index = 0
self.mem = memoryview(buf)
def read(self, n, t):
v = struct.unpack(t, self.mem[self.index:self.index+n])
self.index += n
return v
def read_char4(self):
v = self.read(4, "<cccc")
return b"".join(v)
def read_int(self):
v, = self.read(4, "<i")
return v
def read_xyzi(self):
v = self.read(4, "<BBBB")
return v
def read_memory(self, n):
mem = self.mem[self.index:self.index+n]
self.index += n
return mem
@dataclass
class Chunk:
id: str
chunk: bytes
children: list[Chunk]
@dataclass
class SizeChunk:
x: int
y: int
z: int
@dataclass
class XYZIChunk:
voxels: tuple[int, int, int, int]
@dataclass
class RGBAChunk:
colors: tuple[int, int, int, int]
@dataclass
class PackChunk:
model_count: int
def validate_chunk(chunk):
r = Reader(chunk.chunk)
if chunk.id == b"SIZE":
x = r.read_int()
y = r.read_int()
z = r.read_int()
assert r.index == len(chunk.chunk)
return SizeChunk(x, y, z)
elif chunk.id == b"XYZI":
voxel_count = r.read_int()
voxels = [r.read_xyzi() for _ in range(voxel_count)]
assert r.index == len(chunk.chunk)
return XYZIChunk(voxels)
elif chunk.id == b"RGBA":
colors = [r.read_xyzi() for _ in range(256)]
assert r.index == len(chunk.chunk)
return RGBAChunk(colors)
elif chunk.id == b"PACK":
model_count = r.read_int()
assert r.index == len(chunk.chunk)
return PackChunk(model_count)
else:
assert False, chunk.id
def parse_chunk(r: Reader):
global index
chunk_id = r.read_char4()
chunk_size = r.read_int()
children_size = r.read_int()
chunk = r.read_memory(chunk_size)
children_start = r.index
children = []
while (r.index - children_start) < children_size:
child_chunk = parse_chunk(r)
children.append(child_chunk)
assert (r.index - children_start) == children_size
return Chunk(chunk_id, chunk, children)
reader = Reader(buf)
magic = reader.read_char4()
assert magic == b"VOX ", magic
version = reader.read_int()
assert version == 150, version
main = parse_chunk(reader)
assert reader.index == len(buf)
chunks = []
for chunk in main.children:
chunk = validate_chunk(chunk)
chunks.append(chunk)
with open(sys.argv[2], 'wb') as f:
size_chunk = None
for chunk in chunks:
if type(chunk) is SizeChunk:
print('size')
size_chunk = chunk
f.write(struct.pack("<III", chunk.x, chunk.y, chunk.z))
print(chunk.x, chunk.y, chunk.z)
elif type(chunk) is XYZIChunk:
print('xyzi')
assert size_chunk is not None
#print(size_chunk)
print(len(chunk.voxels))
f.write(struct.pack("<I", len(chunk.voxels)))
for voxel in chunk.voxels:
#print(voxel)
f.write(struct.pack("<BBBB", *voxel))
size_chunk = None
elif type(chunk) is RGBAChunk:
print('rgba')
for color in chunk.colors:
f.write(struct.pack("<ffff", *color))
elif type(chunk) is PackChunk:
print("model count", chunk.model_count)
pass
else:
assert False, type(chunk)

BIN
vox/chr_fox.bin Normal file

Binary file not shown.

BIN
vox/dragon.bin Normal file

Binary file not shown.

BIN
vox/ff3.bin Normal file

Binary file not shown.

BIN
vox/monu1.bin Normal file

Binary file not shown.

BIN
vox/monu16.bin Normal file

Binary file not shown.

BIN
vox/monu7.bin Normal file

Binary file not shown.

BIN
vox/monu9.bin Normal file

Binary file not shown.