-
Notifications
You must be signed in to change notification settings - Fork 129
PointInstancer class #1537
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
johnhaddon
merged 8 commits into
ImageEngine:main
from
johnhaddon:firstClassPointInstancer
Jun 15, 2026
Merged
PointInstancer class #1537
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
99d489d
PrimitiveVariableBinding : Bind IndexedView
johnhaddon b37900d
PrimitiveVariable : Make IndexedView `begin()` and `end()` const
johnhaddon 038eca1
Primitive : Add todo
johnhaddon c92057f
PointInstancer : Add first-class PointInstancer type
johnhaddon 8435128
PointsAlgo : Preserve input properties in `deletePoints()`
johnhaddon 2cf0bae
USD PrimitiveAlgo : Add conversion from `PrimitiveVariable::IndexedView`
johnhaddon ed14a2f
USD PointInstancerAlgo : Drop IECOREUSD_POINTINSTANCER_RELATIVE_PROTO…
johnhaddon 292e7db
USD PointInstancerAlgo : Use IECoreScene::PointInstancer class
johnhaddon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
PointInstancer : Add first-class PointInstancer type
It's worth calling out a few design decisions here... Inheritance from PointsPrimitive -------------------------------- This isn't analogous to UsdGeomPointInstancer, which isn't even a UsdGeomGprim. But it gives us strong backwards compatibility when loading from USD, and lets us use all our existing PrimitiveVariable-based algo and nodes. Query classes ------------- Most queries need to do a name-based lookup to get PrimitiveVariables. The query classes allow us to do the lookup once in the constructor, so that it isn't repeated for every point. Providing a point-based API allows client code to do threading however it wants, and to copy values directly into renderer buffers without generating intermediate arrays. The VisibilityQuery class is separate from TransformQuery because it has greater construction costs, that you might not want to pay if you don't need visibility queries. This also makes it logical for us to have additional query classes in future, which allows us to extend queries without ABI issues. No Inactive IDs, only Invisible IDs ----------------------------------- USD has both, but this choice is due to USD constraints (the former isn't animateable, the latter is). Since we have no way of preventing animation in Cortex or Gaffer, it doesn't make sense to have both in Cortex. We'll merge the two on loading from USD, and then we can write back to `invisibleIds` successfully no matter whether or not it is animated in Cortex/Gaffer. This does sacrifice round-tripping, but makes life simpler in Cortex/Gaffer. The counter-argument here might be that the distinction might be useful in a PromotePointInstances node in Gaffer. Inactive IDs wouldn't be output at all, but invisible ones could be output as scene locations with `scene:visible = false`. I don't know how useful that would be though.
- Loading branch information
commit c92057f21134c883f8dde8390af85727566f11c7
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| ////////////////////////////////////////////////////////////////////////// | ||
| // | ||
| // Copyright (c) 2026, Cinesite VFX Ltd. 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. | ||
| // | ||
| ////////////////////////////////////////////////////////////////////////// | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "IECoreScene/PointsPrimitive.h" | ||
|
|
||
| #include <unordered_set> | ||
|
|
||
| namespace IECoreScene | ||
| { | ||
|
|
||
| class IECORESCENE_API PointInstancer : public IECoreScene::PointsPrimitive | ||
| { | ||
|
|
||
| public : | ||
|
|
||
| PointInstancer( size_t numPoints = 0 ); | ||
| ~PointInstancer() override; | ||
|
|
||
| IE_CORE_DECLAREEXTENSIONOBJECT( PointInstancer, IECoreScene::PointInstancerTypeId, IECoreScene::PointsPrimitive ); | ||
|
|
||
| /// PrimitiveVariable accessors | ||
| /// =========================== | ||
| /// | ||
| /// Accessor methods are provided for all PrimitiveVariables which have | ||
| /// specific meaning to the PointInstancer. | ||
| /// | ||
| /// In USD, these are stored as bare attributes rather than primvars, | ||
| /// and as such they can't have indices. We prefer to use | ||
| /// PrimitiveVariables for uniformity of access in algorithms such as | ||
| /// `PointsAlgo::deletePoints()` and for nodes in Gaffer, meaning that | ||
| /// in Cortex they _can_ have indices. The accessors are designed to | ||
| /// discourage the use of indices, but remain robust should they be | ||
| /// encountered : | ||
| /// | ||
| /// - `set()` functions create a PrimitiveVariable with null `indices`. | ||
| /// Pass a `nullptr` value to remove the PrimitiveVariable. | ||
| /// - `get()` functions return an IndexedView. An invalid view is returned | ||
| /// if the PrimitiveVariable doesn't exist or has the wrong type or | ||
| /// interpolation. | ||
| /// | ||
| /// For full flexibility when needed, access `Primitive::variables` | ||
| /// directly instead. | ||
| /// | ||
| /// \todo We currently use our own names for each of these properties, for | ||
| /// backwards compatibility with Instancer workflows in Gaffer. But in future | ||
| /// we should align the names to USD. | ||
|
|
||
| /// Sets the prototypes to be instanced. Interpretation is left to | ||
| /// the consuming rendering system, but in practice these are locations | ||
| /// in a SceneInterface or Gaffer scene. | ||
| void setPrototypes( IECore::StringVectorDataPtr &prototypes ); | ||
| /// Returns an invalid view if the variable doesn't exist, or if it | ||
| /// exists but has the wrong type or interpolation. | ||
| PrimitiveVariable::IndexedView<std::string> getPrototypes() const; | ||
|
|
||
| void setPrototypeIndex( const IECore::IntVectorDataPtr &prototypeIndex ); | ||
| PrimitiveVariable::IndexedView<int> getPrototypeIndex() const; | ||
|
|
||
| void setPosition( const IECore::V3fVectorDataPtr &position ); | ||
| PrimitiveVariable::IndexedView<Imath::V3f> getPosition() const; | ||
|
|
||
| void setScale( const IECore::V3fVectorDataPtr &scale ); | ||
| PrimitiveVariable::IndexedView<Imath::V3f> getScale() const; | ||
|
|
||
| void setOrientation( const IECore::QuatfVectorDataPtr &orientation ); | ||
| PrimitiveVariable::IndexedView<Imath::Quatf> getOrientation() const; | ||
|
|
||
| void setID( const IECore::Int64VectorDataPtr &ids ); | ||
| PrimitiveVariable::IndexedView<int64_t> getID() const; | ||
|
|
||
| /// Constant primitive variable named "invisibleIds". | ||
| /// | ||
| /// > Note : In addition to this, USD also has "inactiveIds" whose | ||
| /// > only distinguishing feature is not being animatable. Since we | ||
| /// > have no concept of a non-animatable PrimitiveVariable, we have | ||
| /// > only "invisibleIds". | ||
| void setInvisibleIDs( const IECore::Int64VectorDataPtr &invisibleIds ); | ||
| PrimitiveVariable::IndexedView<int64_t> getInvisibleIDs() const; | ||
|
|
||
| /// Queries | ||
| /// ======= | ||
| /// | ||
| /// Higher-level functionality used to evaluate the instancing. | ||
|
|
||
| struct IECORESCENE_API VisibilityQuery | ||
| { | ||
|
|
||
| VisibilityQuery( const PointInstancer &pointInstancer ) | ||
| : m_id( pointInstancer.getID() ) | ||
| { | ||
| if( auto invisibleIds = pointInstancer.getInvisibleIDs() ) | ||
| { | ||
| m_invisibleIds.insert( invisibleIds.begin(), invisibleIds.end() ); | ||
| } | ||
| } | ||
|
|
||
| bool visible( size_t pointIndex ) | ||
| { | ||
| const size_t id = m_id ? m_id[pointIndex] : pointIndex; | ||
| return m_invisibleIds.find( id ) == m_invisibleIds.end(); | ||
| } | ||
|
|
||
| private : | ||
|
|
||
| PrimitiveVariable::IndexedView<int64_t> m_id; | ||
| std::unordered_set<int64_t> m_invisibleIds; | ||
|
|
||
| }; | ||
|
|
||
| /// Utility class to query properties for individual instances. | ||
| struct IECORESCENE_API TransformQuery | ||
| { | ||
|
|
||
| TransformQuery( const PointInstancer &pointInstancer ); | ||
|
|
||
| Imath::M44f transform( size_t pointIndex ) const; | ||
|
|
||
| private : | ||
|
|
||
| PrimitiveVariable::IndexedView<Imath::V3f> m_position; | ||
| PrimitiveVariable::IndexedView<Imath::Quatf> m_orientation; | ||
| PrimitiveVariable::IndexedView<Imath::V3f> m_scale; | ||
|
|
||
| }; | ||
|
|
||
| }; | ||
|
|
||
| IE_CORE_DECLAREPTR( PointInstancer ) | ||
|
|
||
| } // namespace IECoreScene | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've probably already thought this through, and it's probably fine, but just to document my thoughts:
The current instancer gives the option for invisibleIds to be either a list of ids, or a per-vertex vector of booleans, where each boolean corresponds to one id. It probably makes sense that we don't support the latter here, since it isn't part of the USD spec. It often is the more natural thing to author in Gaffer ( for example, using an OSLObject to output a boolean for anything outside a bounding sphere ), but I guess we would just convert to this form if we supported converting an instancer like that to a PointInstancer.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, for the most part I'm just aligning to the USD spec here, even if there is a more natural representation in Cortex/Gaffer. I have a suspicion that even in USD, a per-vertex bool might be the better representation when the new sparse array edits are taken into account, but USD is so pervasive that we have little choice but to align with it. And I'm not keen on having multiple representations if we can avoid it.
Part of the plan in Gaffer is to introduce a new PointInstancer node that converts a PointsPrimitive to a PointInstancer, and as you suggest, this would be a natural place to convert from array-of-bools to list-of-ids.