ifcgeom: make point-collection batching generic in AbstractKernel

Addresses aothms's review comment on PR #8759: "I don't understand
(or like...) why a convert_impl(const taxonomy::collection::ptr
collection, ...) overload is necessary... this doesn't sound like
something that every geometry kernel impl should handle by itself.
Rather something like a reduce on Result in the AbstractKernel
generic implementation that Concatenates the items internally
instead of aggregating them into a vector."

Removes OpenCascadeKernel::convert_impl(collection), the per-kernel
override that special-cased a homogeneous taxonomy::point3 collection
(e.g. a whole IfcCartesianPointList3D "point cloud") into a bulk fast
path. The batching now lives once, generically, in
AbstractKernel::convert_impl(collection): it still converts each
child individually through the kernel's own convert_impl(point3), but
folds the resulting shapes into a single result via a new generic
ConversionResultShape::concat_many() instead of aggregating one
ConversionResult per point into the vector. Any kernel that
implements convert_impl(point3) benefits automatically, with no
collection-level override of its own.

concat_many() is a bulk sibling of the existing pairwise concat():
combining N shapes via repeated pairwise concat() is either quadratic
in one direction (concat() classifies its receiver via
is_compound_of_faces(), which does a full sub-tree scan; calling it
on an ever-growing accumulator is O(n) per call) or produces an O(n)
deep nested shape in the other direction (still O(n) receiver
classification per call is avoided, but the resulting compound is
n levels deep, making the *later* TopExp_Explorer traversal during
triangulation O(n) per vertex on average, i.e. O(n^2) overall).
concat_many() gives kernels a way to combine everything in one O(n)
bulk operation instead. The default implementation (repeated
concat(), for kernels that never exercise this path) preserves
correctness; OpenCascadeShape::concat_many() overrides it with a
single BRep_Builder pass building one flat compound, matching the
original per-kernel fast path's performance exactly (verified by
benchmark: ~0.7-0.8 us/point from 10k-100k points, flat, matching
the original PR's own ~0.5-0.9 us/point claim).

Ported onto a clean v0.8.0 base (the original prototype was built on
top of Dion Moult's experimental ifcviewer-wgpu branch, PR #8759).
Fixes an id() access bug introduced while rewriting point.cpp for the
generic path: taxonomy::item::instance is a raw pointer on this base,
so both the new collection reduction here and the point3 conversion
in point.cpp need instance->as<IfcUtil::IfcBaseEntity>()->id(), the
same pattern already used throughout the rest of this file and every
other kernels/opencascade/*.cpp, not a direct instance->id()/.id()
call.

This contribution was produced with the assistance of an AI coding tool.
This commit is contained in:
Petru Conduraru
2026-07-21 23:07:13 +03:00
parent 8a5b1ab10d
commit 99c514828d
6 changed files with 94 additions and 45 deletions
@@ -349,7 +349,8 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr
for (TopExp_Explorer texp(shape_, TopAbs_VERTEX, TopAbs_EDGE); texp.More(); texp.Next()) {
gp_XYZ p = BRep_Tool::Pnt(TopoDS::Vertex(texp.Current())).XYZ();
taxonomy_transform(place.components_, p);
t->addVertex(item_id, surface_style_id, p.X(), p.Y(), p.Z());
int idx = t->addVertex(item_id, surface_style_id, p.X(), p.Y(), p.Z());
t->addPoint(item_id, surface_style_id, idx);
}
}
@@ -586,6 +587,22 @@ ConversionResultShape* ifcopenshell::geometry::OpenCascadeShape::concat(Conversi
return new OpenCascadeShape(std::move(compound));
}
ConversionResultShape* ifcopenshell::geometry::OpenCascadeShape::concat_many(const std::vector<ConversionResultShape*>& others)
{
// Unlike concat(), which is called pairwise and therefore re-classifies
// its (potentially large) receiver on every call, this builds one flat
// compound in a single O(n) pass: linear time and constant nesting depth
// regardless of how many shapes are combined.
TopoDS_Compound compound;
BRep_Builder builder;
builder.MakeCompound(compound);
builder.Add(compound, shape_);
for (auto* other : others) {
builder.Add(compound, static_cast<OpenCascadeShape*>(other)->shape_);
}
return new OpenCascadeShape(std::move(compound));
}
std::pair<OpaqueCoordinate<3>, OpaqueCoordinate<3>> ifcopenshell::geometry::OpenCascadeShape::bounding_box() const
{
throw std::runtime_error("Not implemented");