voxel-tool-flutter¶
Pure-Dart MagicaVoxel .vox toolkit: read/write (scene graph, materials, frame animation), palettes, VoxelGrid, mesh generation (face culling + greedy meshing), and a Flutter VoxViewer widget.
| 项目信息 | |
|---|---|
| 仓库 | https://github.com/Maicarons/voxel-tool-flutter |
| 主要语言 | Dart |
| Stars | 1 |
| 类型 | 原创项目 |
| 许可证 | MIT |
| 最近更新 | 2026-08-12 |
voxel_tool¶
A pure-Dart toolkit for MagicaVoxel .vox files —
read/write (scene graph, materials, and frame animation), palettes, a sparse
voxel grid, GPU-friendly mesh generation (face culling and greedy meshing),
plus a zero-dependency Flutter preview widget.
It is a Dart/Flutter port of the core of
@voxel-tool/core.
dependencies:
voxel_tool: ^0.1.0
Requires the Flutter SDK (the package ships a
flutter:dependency because of theVoxViewerwidget). The core read/write/mesh logic itself is pure Dart.
Features¶
- Reader —
parseVoxdecodesMAIN / PACK / SIZE / XYZI / RGBA / nTRN / nGRP / nSHP / MATL / FRAM, preserving the scene graph, materials, and frame animation (decoded into per-frame world transforms). - Writer —
toVoxBytes(single model) andtoVoxBytesScene(multi-model + scene graph + materials + animation) emit.voxfiles that round-trip losslessly throughparseVox. VoxelGrid— a sparse, mutable voxel container (set,addSphere,list) for building models programmatically.- Palettes —
defaultPalette(MagicaVoxel's 256 colors),rainbowPalette, andhsvToRgb. - Meshing —
buildVoxelGeometry(face culling + vertex colors) andbuildVoxelGeometryGreedy(greedy meshing), each with a per-material bucket variant. Output usesFloat32List/Uint32Listarrays ready forthree_dart, a custom OpenGL renderer, or any 3D engine. VoxViewer— a software isometric preview widget with drag-to-rotate and Lambert shading, no external 3D engine required.
Quick start¶
Read a .vox file¶
import 'dart:io';
import 'package:voxel_tool/voxel_tool.dart';
final bytes = File('model.vox').readAsBytesSync();
final info = parseVox(bytes);
print('version: ${info.version}');
print('models: ${info.models.length}');
print('scene: ${info.scene.length} instances');
print('frames: ${info.frameCount}');
// The first model's voxels:
final voxels = info.models.first.voxels;
final palette = info.palette ?? defaultPalette();
Write a .vox file¶
import 'dart:io';
import 'package:voxel_tool/voxel_tool.dart';
final grid = VoxelGrid(8, 8, 8);
grid.set(0, 0, 0, 1);
grid.set(7, 7, 7, 2);
final palette = defaultPalette();
final bytes = toVoxBytes(grid, palette);
File('out.vox').writeAsBytesSync(bytes);
Build a model with VoxelGrid¶
final grid = VoxelGrid(32, 32, 32);
grid.addSphere(centerX: 16, centerY: 16, centerZ: 16, radius: 12, colorIndex: 24);
final voxels = grid.list(); // List<Voxel>
print('voxel count: ${grid.length}');
Scene graph + materials + animation¶
final scene = [
const SceneInstance(
modelIndex: 0,
translation: [0, 0, 0],
rotation: 0,
name: 'A',
frames: [
SceneFrame([0, 0, 0], 0),
SceneFrame([2, 0, 0], 0),
SceneFrame([4, 0, 0], 0),
],
),
];
final materials = {
10: const VoxelMaterial(type: '_metal', metalness: 0.8, roughness: 0.3, alpha: 1),
};
final bytes = toVoxBytesScene(
SceneData(models: models, scene: scene, materials: materials, frameCount: 3),
palette,
);
Mesh generation¶
buildVoxelGeometry emits only exposed faces, while
buildVoxelGeometryGreedy merges coplanar, same-color, adjacent faces into the
largest rectangles — far fewer triangles for solid models.
import 'package:voxel_tool/voxel_tool.dart';
final MeshGeometry mesh = buildVoxelGeometryGreedy(voxels, palette);
print('triangles: ${mesh.triangleCount}');
print('bounds: ${mesh.boundingBox()}');
// mesh.positions : Float32List (xyz triples)
// mesh.normals : Float32List (xyz triples)
// mesh.colors : Float32List (rgb, 0..1 sRGB)
// mesh.indices : Uint32List (triangle indices)
Use buildVoxelBuckets / buildVoxelBucketsGreedy when you need one mesh per
material id (for a multi-material glTF / USDZ export).
Flutter preview widget¶
import 'package:flutter/material.dart';
import 'package:voxel_tool/voxel_tool.dart';
import 'package:voxel_tool/widgets.dart';
class MyHomePage extends StatelessWidget {
final List<Voxel> voxels;
final List<Rgba> palette;
const MyHomePage(this.voxels, this.palette, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: VoxViewer(
voxels: voxels,
palette: palette,
width: 320,
height: 320,
),
),
);
}
}
VoxViewer renders with a software isometric projection and painter's-algorithm
depth sort, shades the three visible faces with a simple Lambert term, and
supports drag-to-rotate. It works on every Flutter platform and depends on no
3D engine.
A runnable demo app lives in example/.
API overview¶
| Symbol | Purpose |
|---|---|
parseVox(List<int>) |
Decode a .vox byte stream → ParseResult. |
toVoxBytes(VoxelGrid, [palette]) |
Encode a single model → bytes. |
toVoxBytesScene(SceneData, [palette]) |
Encode a full scene → bytes. |
VoxelGrid |
Sparse mutable voxel container. |
defaultPalette() / rainbowPalette() / hsvToRgb() |
Palette utilities. |
buildVoxelGeometry / buildVoxelGeometryGreedy |
Mesh a List<Voxel>. |
buildVoxelBuckets / buildVoxelBucketsGreedy |
Per-material mesh buckets. |
MeshGeometry |
Typed-array mesh output. |
VoxViewer (via widgets.dart) |
Flutter preview widget. |
Data classes: Rgba, Voxel, VoxelModel, SceneInstance, SceneFrame,
VoxelMaterial, SceneData, ParseResult.
Lossless round-trip contract¶
- Files are written at version 150.
- Legacy files (no scene graph) are read back with one identity instance per model.
MATLmaterials only write the fields that are present.FRAM+ nested_fkeyframes are decoded to per-frame world transforms, soparseVox→toVoxBytesScene→parseVoxis structurally identical.
The test suite (flutter test) verifies all of the above.
Related¶
- JavaScript/TypeScript monorepo:
@voxel-tool/core— viewer, exporter (glTF/GLB/OBJ/STL/PLY/USDZ/FBX), framework components, and CLI.
License¶
MIT — see LICENSE.