@divnotes/dn-voxel-loader
TypeScript icon, indicating that this package has built-in type declarations

0.1.10 • Public • Published

DN Voxel Loader npm version Build Status License: MPL 2.0

A framework-agnostic JavaScript library for loading MagicaVoxel .vox files.

Features

  • Parses .vox file format (version 150).
  • Extracts voxel dimensions, positions, color indices, palette, and materials.
  • Stores voxel data in a Sparse Octree.
  • Zero runtime dependencies.
  • Works in Node.js (v18+) and modern browsers supporting fetch and ArrayBuffer.
  • Provides builds tailored for different environments:
    • ESM (dist/dn-voxel-loader.esm.js): For modern bundlers like Vite, Rollup, Webpack.
    • CJS (dist/dn-voxel-loader.cjs.js): For Node.js require().
    • UMD (dist/dn-voxel-loader.umd.js): For direct use in browsers via <script> tag (exposes dnVoxLoader global).

Installation

npm install @divnotes/dn-voxel-loader
# or
yarn add @divnotes/dn-voxel-loader
# or
pnpm add @divnotes/dn-voxel-loader

Basic Usage

import { load } from '@divnotes/dn-voxel-loader'; // Use this for ESM (bundlers, Node.js with "type": "module")
// For CJS: const { load } = require('@divnotes/dn-voxel-loader');
// For UMD: Use the global dnVoxLoader.load()

// Can be a URL, ArrayBuffer, or Node.js Buffer
const modelSource = 'path/to/your/model.vox';

async function loadModel() {
  try {
    // Load the model data
    const voxelData = await load(modelSource);

    console.log('Model Size:', voxelData.size); // { x: number, y: number, z: number }
    console.log('Octree Root:', voxelData.octree.root); // Access the SparseOctree root node

    // Iterate through all voxels in the octree
    let voxelCount = 0;
    voxelData.octree.iterateVoxels((x, y, z, data) => {
      voxelCount++;
      // Example: Access voxel position (x, y, z) and its data
      // const color = voxelData.palette[data.colorIndex];
      // const material = data.materialId ? voxelData.materials[data.materialId - 1] : null;
      // Use this data for rendering, analysis, etc.
    });
    console.log('Number of Voxels (via iteration):', voxelCount);

    // Alternative: Get all voxels as an array (potentially memory-intensive for large models)
    // const allVoxels = voxelData.octree.getAllVoxels();
    // console.log('Number of Voxels (via array):', allVoxels.length);
    // if (allVoxels.length > 0) {
    //   console.log('First Voxel:', allVoxels[0]); // { x, y, z, colorIndex, materialId? }
    // }

    console.log('Palette Color Count:', voxelData.palette.length); // Should be 257 (index 0 is empty)
    console.log('Color at Index 1:', voxelData.palette[1]); // { r: number, g: number, b: number, a: number }

    const materialCount = voxelData.materials.filter((m) => m).length;
    console.log('Number of Materials:', materialCount);
    if (materialCount > 0) {
      console.log(
        'First Defined Material:',
        voxelData.materials.find((m) => m)
      );
    }

    // Now use the voxelData, particularly by iterating the octree,
    // to render the model in your chosen framework or engine.
    // See the /examples directory for concrete usage.
  } catch (error) {
    console.error('Failed to load voxel model:', error);
  }
}

loadModel();

Returned Data Structure (VoxelData)

The load function returns a Promise that resolves to an object with the following structure:

interface VoxelData {
  /** The dimensions of the model volume. */
  size: { x: number; y: number; z: number };
  /**
   * A Sparse Octree containing the voxel data for storage and querying.
   * Use methods like `octree.iterateVoxels((x, y, z, data) => ...)` to access voxels.
   * `data` object contains `{ colorIndex: number, materialId?: number }`.
   */
  octree: SparseOctree;
  /**
   * The color palette. This is an array of 257 RGBA color objects.
   * Index 0 is reserved and represents an empty voxel ({ r: 0, g: 0, b: 0, a: 0 }).
   * Indices 1-256 correspond to the colorIndex property of the voxels.
   * Each color object is { r: number; g: number; b: number; a: number } (0-255).
   * If the .vox file includes an RGBA chunk, that palette is used.
   * Otherwise, the standard MagicaVoxel default palette is loaded.
   */
  palette: { r: number; g: number; b: number; a: number }[];
  /**
   * An array containing materials parsed from MATL chunks (if present).
   * The array index corresponds to `material_id - 1`.
   * Contains `VoxelMaterial` objects or `null` for unused IDs.
   */
  materials: (VoxelMaterial | null)[];
}

// Definition for SparseOctree (conceptual - see src/octree.js for implementation)
interface SparseOctree {
  // Iterate over all non-empty voxels in the octree.
  iterateVoxels(
    callback: (
      x: number,
      y: number,
      z: number,
      data: { colorIndex: number; materialId?: number }
    ) => void
  ): void;

  // Get all voxels as an array (potentially memory-intensive).
  getAllVoxels(): {
    x: number;
    y: number;
    z: number;
    colorIndex: number;
    materialId?: number;
  }[];

  // Get the data for a specific voxel coordinate.
  get(
    x: number,
    y: number,
    z: number
  ): { colorIndex: number; materialId?: number } | null;
}

// Definition for VoxelMaterial (conceptual - see src/types.js)
interface VoxelMaterial {
  id: number;
  type: 'diffuse' | 'metal' | 'glass' | 'emissive' | 'blend' | 'media';
  // Optional properties parsed from the MATL chunk dict.
  // Floats (normalized 0-1 unless specified):
  weight?: number; // (_weight) Blend ratio for 'blend' type
  rough?: number; // (_rough) Roughness
  spec?: number; // (_spec) Specular intensity
  ior?: number; // (_ior) Index of Refraction
  att?: number; // (_att) Attenuation for 'glass'/'media'
  flux?: number; // (_flux) Light intensity for 'emissive' (usually in lumens)
  emit?: number; // (_emit) Emissive power (often scaled by color)
  ldr?: number; // (_ldr) Final emissive color multiplier (linear display range)
  metal?: number; // (_metal) Metallicness for 'metal' type
  power?: number; // (_power) Specular exponent for ggx approx.
  glow?: number; // (_glow) Bloom intensity for 'emissive'
  // Boolean:
  isTotalPower?: boolean; // (_isTotalPower) If true, 'flux' represents total power directly
}

Examples

  • Pure JS: See examples/pure-js/. Open index.html in your browser. This example uses the UMD build and renders the voxels to a 2D Canvas.
  • React Three Fiber: See examples/react-three-fiber/. This demonstrates using the library with React and Three.js.
  • Astro + React + Tailwind: See examples/astro-react-tailwind/. This showcases integration with Astro, React, and Tailwind CSS.

Development

To contribute or work on this library locally:

  1. Clone the repository:

    git clone https://github.com/DivNotes/dn-voxel-loader.git
    cd dn-voxel-loader
  2. Install dependencies:

    npm install
  3. Build the library:

    npm run build

    This generates the dist/ directory containing the ESM, CJS, and UMD builds along with sourcemaps.

  4. Run tests:

    npm run test
    # Or for watch mode:
    npm run test:watch
  5. Check formatting and linting:

    npm run format:check
    npm run lint
    # To automatically fix formatting:
    npm run format
    # To automatically fix linting errors:
    npm run lint:fix
  6. Contribution Guidelines: Please review the Code of Conduct before contributing.

  7. Releases: Releases to npm and GitHub are automated via GitHub Actions when a new version tag (e.g., v1.2.3) is pushed.

License

MPL-2.0

Documentation

Full documentation is available at https://divnotes.github.io/dn-voxel-loader/

Package Sidebar

Install

npm i @divnotes/dn-voxel-loader

Weekly Downloads

9

Version

0.1.10

License

MPL-2.0

Unpacked Size

463 kB

Total Files

13

Last publish

Collaborators

  • yousefdn
  • babakb