Skip to content

Commit

Permalink
GaftferVDB : Add VolumeScatter
Browse files Browse the repository at this point in the history
  • Loading branch information
dboogert authored and danieldresser-ie committed Jun 30, 2023
1 parent 71008ed commit 5ca4a96
Show file tree
Hide file tree
Showing 10 changed files with 707 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Features
--------

- MeshNormals : Added a new node for adjusting mesh normals.
- VolumeScatter : Added a new node for scattering points throughout a volume.


Improvements
------------
Expand Down
1 change: 1 addition & 0 deletions include/GafferVDB/TypeIds.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ enum TypeId
PointsGridToPointsTypeId = 110956,
SphereLevelSetTypeId = 110957,
PointsToLevelSetTypeId = 110958,
VolumeScatterTypeId = 110959,
LastTypeId = 110974
};

Expand Down
102 changes: 102 additions & 0 deletions include/GafferVDB/VolumeScatter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2020, Don Boogert. All rights reserved.
// Copyright (c) 2023, Image Engine Design. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above
// copyright notice, this list of conditions and the following
// disclaimer.
//
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided with
// the distribution.
//
// * Neither the name of Don Boogert nor the names of
// any other contributors to this software may be used to endorse or
// promote products derived from this software without specific prior
// written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#pragma once

#include "GafferVDB/Export.h"
#include "GafferVDB/TypeIds.h"

#include "GafferScene/BranchCreator.h"

#include "Gaffer/NumericPlug.h"
#include "Gaffer/StringPlug.h"

namespace GafferVDB
{

class GAFFERVDB_API VolumeScatter : public GafferScene::BranchCreator
{

public :

VolumeScatter(const std::string &name = defaultName<VolumeScatter>() );
~VolumeScatter();

IE_CORE_DECLARERUNTIMETYPEDEXTENSION( GafferVDB::VolumeScatter, VolumeScatterTypeId, GafferScene::BranchCreator );

Gaffer::StringPlug *namePlug();
const Gaffer::StringPlug *namePlug() const;

Gaffer::StringPlug *gridPlug();
const Gaffer::StringPlug *gridPlug() const;

Gaffer::FloatPlug *densityPlug();
const Gaffer::FloatPlug *densityPlug() const;

Gaffer::StringPlug *pointTypePlug();
const Gaffer::StringPlug *pointTypePlug() const;

protected :

bool affectsBranchBound( const Gaffer::Plug *input ) const override;
void hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override;
Imath::Box3f computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override;

bool affectsBranchTransform( const Gaffer::Plug *input ) const override;
void hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override;
Imath::M44f computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override;

bool affectsBranchAttributes( const Gaffer::Plug *input ) const override;
void hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override;
IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override;

bool affectsBranchObject( const Gaffer::Plug *input ) const override;
void hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override;
IECore::ConstObjectPtr computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override;

bool affectsBranchChildNames( const Gaffer::Plug *input ) const override;
void hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override;
IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override;

private:

static size_t g_firstPlugIndex;
};

IE_CORE_DECLAREPTR( VolumeScatter )

} // namespace GafferVDB
134 changes: 134 additions & 0 deletions python/GafferVDBTest/VolumeScatterTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
##########################################################################
#
# Copyright (c) 2023, Image Engine Design Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with
# the distribution.
#
# * Neither the name of John Haddon nor the names of
# any other contributors to this software may be used to endorse or
# promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import imath
import unittest
import pathlib

import IECore
import IECoreScene

import Gaffer
import GafferScene
import GafferVDB
import GafferVDBTest

class VolumeScatterTest( GafferVDBTest.VDBTestCase ) :

def test( self ) :

reader = GafferScene.SceneReader()
reader["fileName"].setValue( pathlib.Path( __file__ ).parent / "data" / "smoke.vdb" )

filter = GafferScene.PathFilter()
filter["paths"].setValue( IECore.StringVectorData( [ "/vdb" ] ) )

vs = GafferVDB.VolumeScatter()
vs["in"].setInput( reader["out"] )
vs["filter"].setInput( filter["out"] )

self.assertEqual( vs["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "vdb" ] ) )
self.assertEqual( vs["out"].childNames( "/vdb" ), IECore.InternedStringVectorData( [ "scatter" ] ) )
self.assertEqual( vs["out"].childNames( "/vdb/scatter" ), IECore.InternedStringVectorData() )

vs["name"].setValue( "test" )

self.assertEqual( vs["out"].childNames( "/vdb" ), IECore.InternedStringVectorData( [ "test" ] ) )

vs["destination"].setValue( "/" )

self.assertEqual( vs["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "vdb", "test" ] ) )
self.assertEqual( vs["out"].childNames( "/vdb" ), IECore.InternedStringVectorData( ) )


bound = vs['out'].bound( "/test" )
self.assertTrue( bound.min().equalWithAbsError( imath.V3f( -34.31, -12.88, -26.69 ), 0.01 ) )
self.assertTrue( bound.max().equalWithAbsError( imath.V3f( 19.55, 93.83, 27.64 ), 0.01 ) )

points = vs['out'].object( "/test" )

numP = len( points["P"].data )
self.assertEqual( numP, 18449 )

# Characterize the set of points generated in a way that we know approximately matches this smoke vdb.
# These values are derived from the current distribution - if the distribution changes in the future,
# the tolerances will need to loosen, but we should still see approximately these values, which come
# from the shape of the fog ( the tolerances are pretty loose, due to some areas of very low density
# where a change in distribution can have a big impact on the bounding box )
self.assertTrue( points.bound().min().equalWithAbsError( imath.V3f(-31.85, -11.02, -25.19 ), 1.0 ) )
self.assertTrue( points.bound().max().equalWithAbsError( imath.V3f(17.34, 91.58, 25.57 ), 1.0 ) )

# The center should be fairly close to the true centre, relative to the size of the bound
center = sum( points["P"].data ) / numP
self.assertLess( ( ( center - imath.V3f(-4.50, 17.88, -0.593) ) / points.bound().size() ).length(), 0.003 )

diffs = [ ( i - center ) for i in points["P"].data ]
variance = sum( [ ( i - center ) * ( i - center ) for i in points["P"].data ] ) / numP
stdDev = imath.V3f( *[ i ** 0.5 for i in variance ] )
self.assertTrue( stdDev.equalWithRelError( imath.V3f( 7.92, 20.75, 6.94 ), 0.02 ) )

vs["density"].setValue( 2 )

self.assertEqual( len( vs['out'].object( "/test" )["P"].data ), 36788 )

self.assertEqual( vs['out'].object( "/test" )["type"], IECoreScene.PrimitiveVariable( IECoreScene.PrimitiveVariable.Interpolation.Constant, "gl:point" ) )

vs["pointType"].setValue( "sphere" )

self.assertEqual( vs['out'].object( "/test" )["type"], IECoreScene.PrimitiveVariable( IECoreScene.PrimitiveVariable.Interpolation.Constant, "sphere" ) )

def testFail( self ) :

reader = GafferScene.SceneReader()
reader["fileName"].setValue( pathlib.Path( __file__ ).parent / "data" / "sphere.vdb" )

filter = GafferScene.PathFilter()
filter["paths"].setValue( IECore.StringVectorData( [ "/vdb" ] ) )

vs = GafferVDB.VolumeScatter()
vs["in"].setInput( reader["out"] )
vs["filter"].setInput( filter["out"] )

# The grid name doesn't match, so we silently return an empty object
self.assertEqual( vs['out'].object( "/vdb/scatter" ), IECore.NullObject() )

vs["grid"].setValue( "ls_sphere" )

with self.assertRaisesRegex( RuntimeError, "VolumeScatter does not yet support level sets" ) :
vs['out'].object( "/vdb/scatter" )

if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions python/GafferVDBTest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from .ModuleTest import ModuleTest
from .SphereLevelSetTest import SphereLevelSetTest
from .PointsToLevelSetTest import PointsToLevelSetTest
from .VolumeScatterTest import VolumeScatterTest

if __name__ == "__main__":
import unittest
Expand Down
124 changes: 124 additions & 0 deletions python/GafferVDBUI/VolumeScatterUI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
##########################################################################
#
# Copyright (c) 2020, Don Boogert. All rights reserved.
# Copyright (c) 2023, Image Engine Design. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above
# copyright notice, this list of conditions and the following
# disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with
# the distribution.
#
# * Neither the name of Don Boogert nor the names of
# any other contributors to this software may be used to endorse or
# promote products derived from this software without specific prior
# written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import GafferUI
import GafferVDB

GafferUI.Metadata.registerNode(

GafferVDB.VolumeScatter,

"description",
"""
Scatter points according the voxel values of a VDB grid.
""",

plugs = {

"name" : [

"description",
"""
The name given to the PointsPrimitive -
this will be placed under the location specified by
"destination".
""",

],

"grid" : [

"description",
"""
Name of grid in VDBObject in which points will be scattered.
""",

],

"density" : [

"description",
"""
This density is multiplied with the value of the grid to produce a number of points per unit volume.
""",
],

"pointType" : [

"description",
"""
The render type of the points. This defaults to
"gl:point" so that the points are rendered in a
lightweight manner in the viewport.
""",

"preset:GL Point", "gl:point",
"preset:Particle", "particle",
"preset:Sphere", "sphere",
"preset:Disk", "disk",
"preset:Patch", "patch",
"preset:Blobby", "blobby",

"plugValueWidget:type", "GafferUI.PresetsPlugValueWidget",

],

"destination" : [

"description",
"""
The location where the points primitives will be placed in the output scene.
When the destination is evaluated, the `${scene:path}` variable holds
the location of the source mesh, so the default value parents the points
under the mesh.
> Tip : `${scene:path}/..` may be used to place the points alongside the
> source mesh.
""",

],

"parent" : [

"description",
"""
This plug has been deprecated in favour of using a filter to select the volume.
"""
],

}
)
1 change: 1 addition & 0 deletions python/GafferVDBUI/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@
from . import PointsGridToPointsUI
from . import SphereLevelSetUI
from . import PointsToLevelSetUI
from . import VolumeScatterUI

__import__( "IECore" ).loadConfig( "GAFFER_STARTUP_PATHS", subdirectory = "GafferVDBUI" )
Loading

0 comments on commit 5ca4a96

Please sign in to comment.