Skip to content
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

Box3: Use index in Box3#setFromObject #28327

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from

Conversation

donmccurdy
Copy link
Collaborator

@donmccurdy donmccurdy commented May 10, 2024

Related:

Ideally, three.js would not be constrained to >=1 buffer per geometry. Buffer attributes or interleaved buffers could be shared between multiple geometries. See #27926 and #17089.

One relevant limitation today is that Box3#setFromObject and other methods iterate over buffer attributes without using the index. Doing so is slower and will overestimate the bounds, when the mesh uses only a small subset of the vertices in the buffer.

If this approach looks OK, I'd like to make similar PRs for BufferGeometry#computeBoundingBox etc.

/cc @takahirox @gkjohnson

Copy link

📦 Bundle size

Full ESM build, minified and gzipped.

Filesize dev Filesize PR Diff
678.5 kB (168.1 kB) 678.5 kB (168 kB) +56 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Filesize dev Filesize PR Diff
457.6 kB (110.4 kB) 457.6 kB (110.4 kB) +56 B

@gkjohnson
Copy link
Collaborator

I agree with this change. For the sake of memory efficiency I think it would be good to more freely share attribute date. Not to make this more complex but there's a question of whether or not bounding boxes should account for groups and drawingRange, as well.

Buffer attributes or interleaved buffers could be shared between multiple geometries.
...
If this approach looks OK, I'd like to make similar PRs for BufferGeometry#computeBoundingBox etc.

I'd be interested in what a full list of functions that needs to change looks like. At least BufferGeometry.computeBoundingBox and computeBoundingSphere would need to change. But computeVertexNormals and computeTangents also blindly iterate over every position attribute rather than just those referenced in the index and affecting other unrelated geometry. Maybe that's okay but there are likely other places that are impacted by this kind philosophy change.

I wonder if it's worth making some utility functions to make this easier, though I'm not immediately sure what those would look like. I often find myself iterating over just the attributes that are referenced by the indices.

@Mugen87
Copy link
Collaborator

Mugen87 commented May 10, 2024

Not to make this more complex but there's a question of whether or not bounding boxes should account for groups and drawingRange, as well.

Related #8690.

Honor indices, groups or draw ranges all share the same goal, to make the bounding volumes more tight in certain use cases. They all affect the draw start and count properties and I can see why adding support makes sense. However, all three attributes serve different purposes. Indices usually do not change similar to other vertex data. Groups enable multi material support and should not affect the overall draw count or start of the entire geometry. And draw range is (at least for me) a dynamic property that might change temporarily or over time.

Honoring each property to optimize bounding volumes comes with the cost of additional code complexity.

I'm willing to support this PR (or in general the idea to honor the index attribute in bounding volumes) but I would welcome to officially exclude support for groups and draw ranges.

@gkjohnson
Copy link
Collaborator

gkjohnson commented May 11, 2024

Honoring each property to optimize bounding volumes comes with the cost of additional code complexity.

I agree which is why a utility function may be helpful to reduce code redundancy here. Though this likely comes at a non zero performance cost:

geometry.forEachUsedVertexIndex( index => {

  // iterate over only used indices in groups, draw range, index buffer

} )

// or

for ( const index of geometry.getUsedVertexIndices() ) {

  // generator approach

}

Either way these changes result in likely processing the same vertex index multiple times with indexed geometry, as well, which will impact perf.

And draw range is (at least for me) a dynamic property that might change temporarily or over time.

Can you explain your use cases for draw range? I'm wondering if the value would have changed with more recent additions to three like instance and batched geometry. I've only used drawRange my CSG project to avoid recreating geometry to resize buffers and improve performance (which I think is a nice use case) but it does mean that bounds should update based on draw range and it's an issue right now that they don't.

Don, apologies if this is outside the scope of the feedback you were interested in 😅

@Mugen87
Copy link
Collaborator

Mugen87 commented May 11, 2024

Can you explain your use cases for draw range?

Animated lines was typical use case for me. Updating the bounding volumes per animation step is too costly so at least in my use cases it was more performant to accept larger bounding volumes.

@donmccurdy
Copy link
Collaborator Author

At least BufferGeometry.computeBoundingBox and computeBoundingSphere would need to change.

That's mainly what I had in mind – there might be some other places e.g. in raycasting.

But computeVertexNormals and computeTangents also blindly iterate over every position attribute...

I think these are OK as-is. We're generating a new attribute and it needs to have the length of the existing attributes. If the user wants to mutate the geometry without the overhead, something like the proposal of BufferGeometryUtils.toCompact would be a better fit than trying to support it "in place".

Can you explain your use cases for draw range?

Animated lines was typical use case for me.

Yes, this and other procedural geometry.

We can support both indexed an non-indexed iteration, and the code remains pretty simple. The index cannot grow, so if a geometry starts out using only 10% of the vertices, it will never use more than that, only less with draw range... so I think there's a strong argument that the indices should be used here.

I think ideally draw range would be respected by these methods, but it's beyond my intentions right now, and I'm a bit worried about the overhead of putting abstractions and callbacks around vertex iteration, I'm not sure on this.

I don't feel that geometry.groups should be embedded in these APIs. I don't see the cost/benefit as favorable and the complexity can become high. The use cases for groups are broad, you can do things like drawing the whole mesh twice with front-facing vs. back-facing materials, and we don't really want to iterate the vertices twice to compute a bbox, or try to track which ranges of vertices have already been iterated. I think that's too much.

@gkjohnson
Copy link
Collaborator

Animated lines was typical use case for me.

Yes, this and other procedural geometry.

Either way this is an improvement but aren't these cases where you specifically don't want to rely on automatic bounds generation though and instead set it based on the known bounds of the animation?

The use cases for groups are broad, you can do things like drawing the whole mesh twice with front-facing vs. back-facing materials

I know users won't always respect the docs but groups explicitly are disallowed from overlapping. Right now raycasting will return two intersections if groups overlap. From the docs:

Every vertex and index must belong to exactly one group — groups must not share vertices or indices, and must not leave vertices or indices unused.

The "must not leave vertices or indices unused" is new to me and doesn't seem necessary but this is a digression 😅.

we don't really want to iterate the vertices twice to compute a bbox

Just to be clear we will be iterating over some vertices twice (or a dozen times) when using indexed geometry due to them being referenced multiple times. Though understood you may mean iterating over the index range multiple times.

@donmccurdy
Copy link
Collaborator Author

donmccurdy commented May 14, 2024

Either way this is an improvement but aren't these cases where you specifically don't want to rely on automatic bounds generation though and instead set it based on the known bounds of the animation?

By procedural I don't meant to imply animation – I remember a TiltBrush-like app using drawRange to allocate geometry for brushstrokes in advance, for example. You'd want the bounding box to be computed based on the draw range, and it's easy enough to recompute the bounding box when necessary (e.g. after each brush stroke).

But somewhere we do have to draw the line ... if it's nice-to-have for drawRange but simply too much complexity/overhead and should be managed by the user I am OK with that too.

Just to be clear we will be iterating over some vertices twice (or a dozen times) when using indexed geometry due to them being referenced multiple times. Though understood you may mean iterating over the index range multiple times.

True, and true without indices as well... but maybe more importantly, I do not think geometry.groups necessarily pulls its weight in terms of usefulness for the added complexity, and would prefer not to add to the complexity further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants