跳转至

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 the VoxViewer widget). The core read/write/mesh logic itself is pure Dart.


Features

  • Reader — parseVox decodes MAIN / 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) and toVoxBytesScene (multi-model + scene graph + materials + animation) emit .vox files that round-trip losslessly through parseVox.
  • VoxelGrid — a sparse, mutable voxel container (set, addSphere, list) for building models programmatically.
  • Palettes — defaultPalette (MagicaVoxel's 256 colors), rainbowPalette, and hsvToRgb.
  • Meshing — buildVoxelGeometry (face culling + vertex colors) and buildVoxelGeometryGreedy (greedy meshing), each with a per-material bucket variant. Output uses Float32List / Uint32List arrays ready for three_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.
  • MATL materials only write the fields that are present.
  • FRAM + nested _f keyframes are decoded to per-frame world transforms, so parseVox → toVoxBytesScene → parseVox is structurally identical.

The test suite (flutter test) verifies all of the above.


  • JavaScript/TypeScript monorepo: @voxel-tool/core — viewer, exporter (glTF/GLB/OBJ/STL/PLY/USDZ/FBX), framework components, and CLI.

License

MIT — see LICENSE.