Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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
johnhaddon committed Jun 15, 2026
commit c92057f21134c883f8dde8390af85727566f11c7
5 changes: 5 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
10.7.x.x (relative to 10.7.0.0a10)
========

Features
--------

- PointInstancer : Added class to provide first-class encoding of point instancers.

Improvements
------------

Expand Down
165 changes: 165 additions & 0 deletions include/IECoreScene/PointInstancer.h
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;

Copy link
Copy Markdown
Contributor

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.

@johnhaddon johnhaddon Jun 15, 2026

Copy link
Copy Markdown
Member Author

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.


};

/// 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
2 changes: 1 addition & 1 deletion include/IECoreScene/TypeIds.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ enum TypeId
ShaderTypeId = 108004,
PDCParticleReaderTypeId = 108005,
PDCParticleWriterTypeId = 108006,
RendererTypeId = 108007, // Obsolete, available for reuse
PointInstancerTypeId = 108007,
PrimitiveOpTypeId = 108008,
ParticleReaderTypeId = 108009,
ParticleWriterTypeId = 108010,
Expand Down
Loading