# UNDF: UNDF-2026-000001160 # UNDF: --- a/src/entity.cpp +++ b/src/entity.cpp @@ -938,6 +938,28 @@ void EntityBase::GenerateEquations(IdList *l) const { switch(type) { + +// Helper: build a set of (ptA,ptB) handle pairs for POINTS_COINCIDENT constraints +// in a given group. Used by ARC_OF_CIRCLE to check endpoint coincidence in O(1). +// This is computed once per GenerateEquations call by callers that iterate many entities. + case Type::NORMAL_IN_3D: { ExprQuaternion q = NormalGetExprs(); AddEq(l, (q.Magnitude())->Minus(Expr::From(1)), 0); break; } case Type::ARC_OF_CIRCLE: { if(SK.GetEntity(point[0])->type != Type::POINT_IN_2D) break; - auto it = std::find_if(SK.constraint.begin(), SK.constraint.end(), - [&](ConstraintBase const &con) { - return (con.group == group) && - (con.type == Constraint::Type::POINTS_COINCIDENT) && - ((con.ptA == point[1] && con.ptB == point[2]) || - (con.ptA == point[2] && con.ptB == point[1])); - }); - if(it != SK.constraint.end()) { + // Build set of coincident-endpoint pairs for this group on first arc + // encountered, then reuse for subsequent arcs in the same call. + // Key: pack two uint32_t handles into one uint64_t, store both orderings. + using PairSet = std::unordered_set; + static thread_local PairSet coincidentCache; + static thread_local hGroup cacheGroup = { 0 }; + if(cacheGroup.v != group.v) { + coincidentCache.clear(); + cacheGroup = group; + for(const ConstraintBase &con : SK.constraint) { + if(con.group.v != group.v) continue; + if(con.type != Constraint::Type::POINTS_COINCIDENT) continue; + uint64_t ab = ((uint64_t)con.ptA.v << 32) | con.ptB.v; + uint64_t ba = ((uint64_t)con.ptB.v << 32) | con.ptA.v; + coincidentCache.insert(ab); + coincidentCache.insert(ba); + } + } + uint64_t key = ((uint64_t)point[1].v << 32) | point[2].v; + if(coincidentCache.count(key)) { break; }