<a href="https://mjurczyk.github.io/openvdb/" target="_blank">
  <img width="100%" src="https://github.com/mjurczyk/openvdb/assets/9549760/fb7a04c1-9c69-44c9-addb-f6969b3017c6" title="OpenVDB - Web implementation of volumetric grids" />
</a>

# OpenVDB

This project indirectly ports OpenVDB file format and tools to JavaScript, TypeScript, and Node.
Specific 3D library implementations can be found in sub-directories.

## What is OpenVDB

VDB is a volumetric data format originally implemented in C++ ([AcademySoftwareFoundation/openvdb](https://github.com/AcademySoftwareFoundation/openvdb).) It allows to efficiently store voxel structures of this like smoke, fire, and fluid simulations - as well as normal 3D models converted to voxels.

Keep in mind that, due to memory-access and multithreading limitations of JS, this repo may be more similar to a read-only [NanoVDB](https://github.com/AcademySoftwareFoundation/openvdb/tree/master/nanovdb/nanovdb), instead of the original OpenVDB. Code is nevertheless based on original [OpenVDB](https://github.com/AcademySoftwareFoundation/openvdb/tree/master/openvdb/openvdb).

## Usage

### With `three.js`:

```js
import * as Three from 'three';
import * as OpenVDB from 'openvdb/three';

const scene = new Three.Scene();

// NOTE To load an existing VDB model - use VDBLoader:
new OpenVDB.VDBLoader().load('./assets/bunny.vdb', function (vdb) {
  const fogVolume = new OpenVDB.FogVolume(vdb, {
    resolution: 100,
    progressive: true,
    steps: 20,
    absorbance: 1.0,
    baseColor: 0xff00ff,
  });

  scene.add(fogVolume);
});

// NOTE To create a primitive volumetric shape - use volume converters directly:
const primitiveVolume = new OpenVDB.SphereVolume();
const fogPrimitive = new OpenVDB.FogVolume(primitiveVolume, {
  resolution: 50,
  progressive: false,
  steps: 50,
  absorbance: 0.5,
  baseColor: 0xff00ff,
});

scene.add(fogPrimitive);
```

### With `react-three-fiber`:

```jsx
import * as Three from 'three';
import * as OpenVDB from 'openvdb/three';

export const VDBComponent = () => {
  const [model, setModel] = useState(new Three.Object3D());

  useEffect(() => {
    new OpenVDB.VDBLoader().load('./assets/bunny.vdb', function (vdb) {
      const fogVolume = new OpenVDB.FogVolume(vdb, {
        resolution: 100,
        progressive: true,
        steps: 20,
        absorbance: 1.0,
        baseColor: 0xff00ff,
      });

      setModel(fogVolume);
    });

    return () => {
      fogVolume.dispose();
    };
  }, []);

  return <primitive object={model} />;
};
```

### Without library:

```js
import * as OpenVDB from 'openvdb';

const vdbReader = await OpenVDB.loadVDB('./assets/bunny.vdb');

vdbReader.grids.forEach(function (grid) {
  const voxelValue = grid.getValue({ x: 0.0, y: 0.0, z: 1.0 });
});
```

## Docs

### loadVDB

- `loadVDB( url ): Promise<OpenVDB.OpenVDBReader>`

Loads OpenVDB file and parses its grids. Grids can be accessed via `.grids` property of the value returned from the Promise.

Each grid has a `.getValue( position: Three.Vector3 ): float` method, which returns a voxel value for the given position. See `src/openvdb/three/VolumeToFog.js` for an example of VDB file parsing.

---

### VDBLoader

- `VDBLoader( manager: Three.LoadingManager )`

- `VDBLoader.load( url, onLoad, onProgress, onError ): void`

Loads VDB model. onLoad is called when VDB is loaded and parsed. onError is called when error happens. onProgress is currently not used.

---

### VolumeToBbox

- `VolumeToBbox( vdb: OpenVDB.OpenVDBReader | OpenVDB.GridDescriptor | Array<OpenVDB.GridDescriptor> ): Three.Object3D`

Converts given `OpenVDBReader` to `Three.Object3D`. Resulting object consists of wireframe bounding boxes of all internal nodes within the given VDB. If multiple grids are given - each grid is drawn in a different wireframe color.

---

### VolumeToFog

- `VolumeToFog( vdb: OpenVDB.OpenVDBReader | OpenVDB.GridDescriptor | Array<OpenVDB.GridDescriptor>, { resolution, steps, progressive, absorbance, opacity, radius, baseColor }, onConverted: () => void, onProgress: ({ convertedVoxels, totalVoxels, convertedGrids, totalGrids }) => void ): Three.Object3D`

Converts given `OpenVDBReader` to a volumetric `Three.Object3D`. `onConverted` and `onProgress` are called as the VDB is converted to a fog. Most importantly:

* `resolution: number` - (required) resolution of the resulting 3D texture. Keep in mind this will be risen to the power of 3 - so resolution 200 results in 8 million voxels.
* `steps: number` - (default: 100) detail of the resulting fog volume. High amount of steps combined with large amount of lights in the scene may decrease performance significantly.
* `progressive: boolean` - set to `true` to render the fog on-the-go as it is being parsed, while preserving 60fps.
* `absorbance: number` - (default: `1.0`) `1.0` means fog absorbs all the light going through it, lower values all light to partially traverse through the fog.
* `densityScale: number` - scales density of the entire grid.
* `densityCutoff: number` - defines sharpness of the shape.
* `opacity: number` - alpha opacity of the fog (doesn't affect light calculations.)
* `baseColor: Three.Color | string | number` - (default: `0x000000`) albedo color of the fog volume.
* `emissiveGrid` - VDB emission grid.
* `baseColorGrid` - VDB grid equivalent of `baseColor`.
* `maskGrid` - static VDB grid mask applied to each voxel. Not affected by UV offset.
* `lights` - bitmask of light types affecting the volume (`lights.useDirectionalLights | lights.usePointLights | lights.useSpotLights | lights.useHemisphereLights | lights.useEnvironment`)

---

### VolumeToLevelSet

- `VolumeToLevelSet( vdb: OpenVDB.OpenVDBReader | OpenVDB.GridDescriptor | Array<OpenVDB.GridDescriptor>, onConverted: () => void, onProgress: () => void ): Three.Object3D`

Not yet finished. Converts the given `OpenVDBReader` to a 3D mesh using marching cubes.

---

### CubeVolume

- `CubeVolume(): OpenVDB.GridDescriptor`

Generates a 1x1x1 volumetric cube.

---

### SphereVolume

- `SphereVolume(): OpenVDB.GridDescriptor`

Generates a 1x1x1 volumetric sphere.

---

### CloudVolume


- `CloudVolume( { height: number, density: number } ): OpenVDB.GridDescriptor`

Generates a 1x1x1 volumetric clouds based on noise inputs.

---

### ParametricVolume

- `ParametricVolume( valueFunction(position: Three.Vector3) => number ): OpenVDB.GridDescriptor`

Generates a 1x1x1 volume with values defined using a `valueFunction`. `valueFunction` is called with position of each voxel within the volumetric bounding cube - and expects a value between `0.0` and `1.0` to be returned (`0.0` meaning given voxel is fully transparent, `1.0` meaning given voxel is fully opaque.)

---

## Current Feature Support

Based on files hosted on [https://www.openvdb.org/download/](https://www.openvdb.org/download/).

| Model                | Support    | Notes                                                         |
| -------------------- | ---------- | ------------------------------------------------------------- |
| armadillo.vdb        | ✅ Yes     |                                                               |
| buddha.vdb           | ✅ Yes     |                                                               |
| bunny.vdb            | ✅ Yes     |                                                               |
| bunny_cloud.vdb      | ✅ Yes     |                                                               |
| crawler.vdb          | ❓ Partial | Exceeds memory limits                                         |
| cube.vdb             | ✅ Yes     |                                                               |
| dragon.vdb           | ✅ Yes     |                                                               |
| emu.vdb              | ✅ Yes     |                                                               |
| explosion.vdb        | ✅ Yes     |                                                               |
| fire.vdb             | ✅ Yes     |                                                               |
| icosahedron.vdb      | ✅ Yes     |                                                               |
| iss.vdb              | ✅ Yes     |                                                               |
| smoke1.vdb           | ✅ Yes     |                                                               |
| smoke2.vdb           | ✅ Yes     |                                                               |
| space.vdb            | ❓ Partial | Exceeds memory limits                                         |
| sphere.vdb           | ✅ Yes     |                                                               |
| torus.vdb            | ✅ Yes     |                                                               |
| torus_knot.vdb       | ✅ Yes     |                                                               |
| utahteapot.vdb       | ✅ Yes     |                                                               |
| venusstatue.vdb      | ✅ Yes     |                                                               |
| boat_points.vdb      | ❓ Partial | Loads ok, points rendering not fully implemented              |
| bunny_points.vdb     | ❓ Partial | Loads ok, points rendering not fully implemented              |
| sphere_points.vdb    | ❓ Partial | Loads ok, points rendering not fully implemented              |
| waterfall_points.vdb | ❓ Partial | Loads ok, points rendering not fully implemented              |

## Development

```
git clone https://github.com/mjurczyk/openvdb
cd ./openvdb
npm run i
npm run dev
```

## Credits

To avoid re-coding same things over again, parts of the library may be based / taken directly from the following:

- [gkjohnson/three-mesh-bvh](https://github.com/gkjohnson/three-mesh-bvh): BVH implementation
- [manzt/numcodecs.js](https://github.com/manzt/numcodecs.js/): BLOSC & ZLIB compression
  - [nodeca/pako](https://github.com/issues): ZLIB compression

Be sure to ⭐️ those!
