ryein / dendro

volumetric modeling for grasshopper built on top of openvdb
Mozilla Public License 2.0
67 stars 20 forks source link

MeshToVolume custom grid size #12

Closed mariuszhermansdorfer closed 2 months ago

mariuszhermansdorfer commented 1 year ago

@snabela, @ryein, @SamuelAl thanks for putting this great library together!

I'm trying to find a way either in Dendro or OpenVDB to convert a mesh to volume with a custom grid size. Imagine, I want to provide some buffer around the mesh's bounding box in all dimensions. Here is a basic example: image

Do you have any idea on how to approach it?

ryein commented 1 year ago

Grid size in voxel space is determined by the resolution input in Dendro. Make that smaller and you will get a much smaller voxel grid. The buffer input determines how much "space" in voxel units around the object is considered "active".

I am willing to bet this isn't the answer you were looking for, so if you throw me a bit more context I will see if I can help. Happy to help.

mariuszhermansdorfer commented 1 year ago

Thanks for a prompt response @ryein!

What I have are 2 inputs:

I'd like the resulting volume to have the extents of the larger bounding box.

Here is another visualization. The cone represents the mesh, the cubes represent the volume (for the sake of readability I deleted the interior of the box but conceptually it's still there) image image

Ideally, I'd get a boolean grid. A lot of zeros for the empty voxels, and a few ones for the cells containing the mesh. image

ryein commented 1 year ago

don't know if this approach works, but i used the offset volume to create two shapes (cone and cube) and then boolean unioned them.

gh_attempt

mariuszhermansdorfer commented 1 year ago

Wouldn't this create false positives (1) on the edges of the box?

I only want the cone mesh to show 1, while the remaining voxels would have a value of 0.

Also, to be clear - I need to export the results as a TXT file as 0/1 for each voxel. I'm cool with writing this exporter either in C# or wrapped c++ but am trying to wrap my head around how to create the OpenVDB grid of an arbitrary size which is larger than the provided mesh.

ryein commented 1 year ago

This would need to be a custom OpenVDB script. You can't use the scripting within Grasshopper unfortunately unless you wrote the functionality into the C++ API in Dendro. Probably be faster to just build OpenVDB locally and write a C++ program to do what you need. I would assume you would need to load in the geometry as grids and then run a custom iterator to output active/inactive values for each voxel. You would also need to assign the cube's grid structure for the cone.

mariuszhermansdorfer commented 1 year ago

I should have been clear from the beginning :) I’m working on a local Dendro fork and can write both c++ and c# code. What I need help with is understanding how OpenVDB works with grids.

I imagine this basic workflow:

  1. Create grid based on the bounding box
  2. Flood-fill with 0s
  3. Add mesh to the grid
  4. Mark intersecting voxels with 1s
  5. Generate output array of results

Would that work? How do I ‘add’ a mesh to an existing grid? The only function I found was MeshToVolume which creates a new grid.

ryein commented 1 year ago

I think your approach is correct. I am not 100% sure the quickest way to solve this so you'll have to trial and error. My first thought would be to use the Compositing library in OpenVDB in your step 4 above. That would allow you to do operations like openvdb::tools::compMax(*boxGrid, *coneGrid); This would add the cone to the boxGrid.

openvdb composite

also, i would assume you would create a grid for the cone using MeshToVolume.

mariuszhermansdorfer commented 1 year ago

Awesome. That's the piece of the puzzle I've been missing. Any ideas on how to align these grids and make sure the voxels overlap?

Something like: set grid origin -> world origin,
set grid alignment -> world XYZ, set grid increment -> voxel size

I'll test your suggestion and report back.

mariuszhermansdorfer commented 1 year ago

@ryein, thanks to our exchange I was able to write the following code. It creates 2 separate grids, merges them together and uses a bounding box to iterate over the results:

#include <openvdb.h>
#include <tools/MeshToVolume.h>
#include <tools/Composite.h>
#include <math/Transform.h>

#include <iostream>
#include <fstream>

using namespace openvdb;

extern "C" __declspec(dllexport) math::Transform * CreateTransform(const float voxelSize) {
    return new math::Transform(*math::Transform::createLinearTransform(voxelSize));
}

extern "C" __declspec(dllexport) FloatGrid * CreateFloatGrid(math::Transform* transformPtr) {
    FloatGrid::Ptr grid = FloatGrid::create(/*background value=*/std::numeric_limits<float>::max());
    grid->setTransform(math::Transform::Ptr(transformPtr, [](math::Transform*) {}));

    return new FloatGrid(*grid);
}

extern "C" __declspec(dllexport) math::CoordBBox * CreateBoundingBox(const FloatGrid * floatGrid, const float minX, const float minY, const float minZ, const float maxX, const float maxY, const float maxZ) {

    auto voxel_min_coord_d = floatGrid->worldToIndex(math::Vec3s(minX, minY, minZ));
    auto voxel_max_coord_d = floatGrid->worldToIndex(math::Vec3s(maxX, maxY, maxZ));

    math::Coord voxel_min_coord(math::Round(voxel_min_coord_d.x()), math::Round(voxel_min_coord_d.y()), math::Round(voxel_min_coord_d.z()));
    math::Coord voxel_max_coord(math::Round(voxel_max_coord_d.x()), math::Round(voxel_max_coord_d.y()), math::Round(voxel_max_coord_d.z()));

    return new math::CoordBBox(voxel_min_coord, voxel_max_coord);
}

extern "C" __declspec(dllexport) FloatGrid * CreateMeshGrid(math::Transform * transformPtr, float vertices[], int verticesLength, int faces[], int facesLength) {

    std::vector<Vec3s> points;
    for (int i = 0; i < verticesLength; i += 3) {
        points.push_back(Vec3s(vertices[i], vertices[i + 1], vertices[i + 2]));
    }

    std::vector<Vec3I> triangles;
    for (int i = 0; i < facesLength; i += 3)
        triangles.push_back(Vec3I(faces[i], faces[i + 1], faces[i + 2]));

    FloatGrid::Ptr meshGrid = tools::meshToUnsignedDistanceField<FloatGrid>(*transformPtr, points, triangles, std::vector<Vec4I>{}, 1.0);

    return new FloatGrid(*meshGrid);
}

extern "C" __declspec(dllexport) void MergeGridsAndOutput(const FloatGrid * gridPtr, const FloatGrid * meshGridPtr, const math::CoordBBox * bboxPtr)
{
    FloatGrid::Ptr grid = gridPtr->deepCopy();
    FloatGrid::Ptr meshGrid = meshGridPtr->deepCopy();
    math::CoordBBox bbox = *bboxPtr;

    // Perform the CSG union operation
    tools::csgUnion(*grid, *meshGrid);

    // Iterate through the CoordBBox and write the voxel values to the text file
    std::ofstream outFile("voxel_values.txt");

    auto accessor = grid->getAccessor();
    Coord ijk;
    int& i = ijk[0], & j = ijk[1], & k = ijk[2];
    Coord end = bbox.getEnd();

    for (i = bbox.getStart()[0]; i < end[0]; ++i) {
        for (j = bbox.getStart()[1]; j < end[1]; ++j) {
            for (k = bbox.getStart()[2]; k < end[2]; ++k) {
                outFile << accessor.getValue(ijk) << std::endl;
            }
        }
    }

    outFile.close();
}

extern "C" __declspec(dllexport) void DeleteFloatGrid(FloatGrid* grid) {
    delete grid;
}

extern "C" __declspec(dllexport) void DeleteBoundingBox(math::CoordBBox* bbox) {
    delete bbox;
}