Skip to content

Commit f09ca65

Browse files
committed
Initial investigation into lofting open profile with tags
1 parent 4fc2f62 commit f09ca65

4 files changed

Lines changed: 472 additions & 52 deletions

File tree

src/ifcgeom/infra_sweep_helper.cpp

Lines changed: 236 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,27 @@ namespace {
1414
}
1515
}
1616

17+
namespace {
18+
template <typename T, typename Cmp = std::less<T>>
19+
bool has_intersection(const std::set<T, Cmp>& A,
20+
const std::set<T, Cmp>& B) {
21+
auto itA = A.begin();
22+
auto itB = B.begin();
23+
24+
while (itA != A.end() && itB != B.end()) {
25+
if (Cmp()(*itA, *itB)) {
26+
++itA;
27+
} else if (Cmp()(*itB, *itA)) {
28+
++itB;
29+
} else {
30+
return true;
31+
}
32+
}
33+
return false;
34+
}
35+
36+
}
37+
1738
taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_, const IfcUtil::IfcBaseClass* inst, const taxonomy::function_item::ptr& fn, std::vector<cross_section>& cross_sections)
1839
{
1940
std::sort(cross_sections.begin(), cross_sections.end());
@@ -25,7 +46,7 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
2546
// @todo currently only the case is handled where directrix returns a function_item
2647
// @todo this "if" statement is not really required because the function returns at the start if the Directrix is not a function_item function
2748
if (fn) {
28-
function_item_evaluator evaluator(settings_,fn);
49+
function_item_evaluator evaluator(settings_, fn);
2950
double start = std::max(0., cross_sections.front().dist_along);
3051
double end = std::min(fn->length(), cross_sections.back().dist_along);
3152

@@ -45,21 +66,24 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
4566
// parameter is minimum number of steps
4667
num_steps = (size_t)std::ceil(param);
4768
}
69+
auto delta_step = curve_length / num_steps;
4870
std::vector<double> longitudes;
4971
for (auto& x : cross_sections) {
5072
longitudes.push_back(x.dist_along);
5173
}
5274
longitudes.push_back(std::numeric_limits<double>::infinity());
5375
auto profile_index = longitudes.begin();
5476
for (size_t i = 0; i <= num_steps; ++i) {
55-
auto dist_along = start + curve_length / num_steps * i;
77+
auto dist_along = start + delta_step * i;
5678
while (dist_along > *(profile_index + 1)) {
5779
profile_index++;
5880
if (profile_index == longitudes.end()) {
5981
// @todo handle this?
6082
}
6183
}
6284

85+
const bool is_last_placement_of_this_profile = profile_index + 1 >= longitudes.end() ? false : ((start + delta_step * (i+1)) > *(profile_index + 1));
86+
6387
auto relative_dist_along = (dist_along - *profile_index) / (*(profile_index + 1) - *profile_index);
6488
const auto& profile_a = cross_sections[std::distance(longitudes.begin(), profile_index)].section_geometry;
6589
const auto& offset_a = cross_sections[std::distance(longitudes.begin(), profile_index)].offset;
@@ -143,28 +167,203 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
143167
} else if (rotation_a != rotation_b) {
144168
Logger::Error("Direction vectors on cross section placements only supported when used consistently");
145169
}
170+
146171
taxonomy::loop::ptr w1, w2;
147172
taxonomy::edge::ptr e1, e2;
173+
taxonomy::point3::ptr p1, p2;
174+
148175
for (auto tmp_ : boost::combine(loops_a, loops_b)) {
149176
boost::tie(w1, w2) = tmp_;
150-
if (w1->children.size() != w2->children.size()) {
151-
Logger::Warning("Mismatching number of edges: " +
152-
std::to_string(w1->children.size()) + " vs " +
153-
std::to_string(w2->children.size()),
154-
inst
155-
);
177+
178+
if (w1->closed != w2->closed) {
179+
Logger::Warning("Mismatching closed property on loops", inst);
180+
return nullptr;
181+
}
182+
183+
if (w1->tags.is_initialized() != w2->tags.is_initialized()) {
184+
Logger::Warning("Mismatching availability tags on loops", inst);
156185
return nullptr;
186+
}
187+
188+
if (w1->tags) {
189+
// check uniqueness
190+
std::set<std::string> tags_seen;
191+
for (const auto& t : *w1->tags) {
192+
if (tags_seen.find(t) != tags_seen.end()) {
193+
Logger::Warning("Duplicate tag '" + t + "' on loft profile", inst);
194+
return nullptr;
195+
}
196+
tags_seen.insert(t);
197+
}
157198
}
158-
std::vector<taxonomy::point3::ptr> points;
159-
for (auto tmp__ : boost::combine(w1->children, w2->children)) {
160-
boost::tie(e1, e2) = tmp__;
161-
auto& p1 = boost::get<taxonomy::point3::ptr>(e1->start);
162-
auto& p2 = boost::get<taxonomy::point3::ptr>(e2->start);
163-
164-
auto p3 = (lerp(p1->ccomponents(), p2->ccomponents(), relative_dist_along) + interpolated_offset).eval();
165-
// auto p4 = (interpolated_rotation * p3).eval();
166-
points.push_back(taxonomy::make<taxonomy::point3>(p3));
199+
200+
if (w2->tags) {
201+
// check uniqueness
202+
std::set<std::string> tags_seen;
203+
for (const auto& t : *w2->tags) {
204+
if (tags_seen.find(t) != tags_seen.end()) {
205+
Logger::Warning("Duplicate tag '" + t + "' on loft profile", inst);
206+
return nullptr;
207+
}
208+
tags_seen.insert(t);
209+
}
210+
}
211+
212+
std::map<std::string, taxonomy::point3::ptr> tag_to_point_on_w1, tag_to_point_on_w2;
213+
214+
auto loop_to_points = [](const taxonomy::loop::ptr& loop, const boost::optional<std::vector<std::string>>& input_tags) -> std::pair<std::vector<taxonomy::point3::ptr>, std::vector<std::set<std::string>>> {
215+
std::vector<taxonomy::point3::ptr> points;
216+
std::vector<std::set<std::string>> tags;
217+
std::vector<std::string>::const_iterator tag_it;
218+
219+
if (!loop->closed.get_value_or(false)) {
220+
points = {boost::get<taxonomy::point3::ptr>(loop->children[0]->start)};
221+
if (input_tags) {
222+
tags = {{input_tags->front()}};
223+
tag_it = ++input_tags->begin();
224+
}
225+
}
226+
for (auto& e : loop->children) {
227+
const auto& p1 = boost::get<taxonomy::point3::ptr>(e->start);
228+
const auto& p2 = boost::get<taxonomy::point3::ptr>(e->end);
229+
if (input_tags && p1->ccomponents() == p2->ccomponents()) {
230+
tags.back().insert(*tag_it);
231+
++tag_it;
232+
} else {
233+
points.push_back(p2);
234+
if (input_tags) {
235+
tags.emplace_back();
236+
tags.back().insert(*tag_it);
237+
++tag_it;
238+
}
239+
}
240+
}
241+
if (!input_tags) {
242+
if (loop->closed.get_value_or(false)) {
243+
// close polygon by referencing first point
244+
points.push_back(points.front());
245+
}
246+
}
247+
return {points, tags};
248+
};
249+
250+
auto combine_tags = [](const std::vector<std::set<std::string>>& tag_sets) -> std::set<std::string> {
251+
return std::accumulate(
252+
tag_sets.begin(), tag_sets.end(), std::set<std::string>{},
253+
[](std::set<std::string> acc,
254+
const std::set<std::string>& m) {
255+
acc.insert(m.begin(), m.end());
256+
return acc;
257+
});
258+
};
259+
260+
auto join_tags = [](const std::set<std::string>& tag_set) -> std::string {
261+
std::string result;
262+
for (auto it = tag_set.begin(); it != tag_set.end(); ++it) {
263+
if (it != tag_set.begin()) {
264+
result += ", ";
265+
}
266+
result += *it;
267+
}
268+
return result;
269+
};
270+
271+
auto [w1_points, w1_tags] = loop_to_points(w1, w1->tags);
272+
auto [w2_points, w2_tags] = loop_to_points(w2, w2->tags);
273+
274+
if (w1->tags && w2->tags) {
275+
{
276+
auto it = w1_points.begin();
277+
auto jt = w1_tags.begin();
278+
while (it != w1_points.end() && jt != w1_tags.end()) {
279+
for (auto& t : *jt) {
280+
tag_to_point_on_w1[t] = *it;
281+
}
282+
++it;
283+
++jt;
284+
}
285+
}
286+
287+
{
288+
auto it = w2_points.begin();
289+
auto jt = w2_tags.begin();
290+
while (it != w2_points.end() && jt != w2_tags.end()) {
291+
for (auto& t : *jt) {
292+
tag_to_point_on_w2[t] = *it;
293+
}
294+
++it;
295+
++jt;
296+
}
297+
}
298+
299+
auto w1_tags_combined = combine_tags(w1_tags);
300+
auto w2_tags_combined = combine_tags(w2_tags);
301+
302+
// For every point (which can have multiple tags in case of 0-width edges) there needs to be a corresponding point on the other profile
303+
304+
for (auto& p1_tags : w1_tags) {
305+
if (!has_intersection(p1_tags, w2_tags_combined)) {
306+
Logger::Warning("No matching tags found on loft profiles: " + join_tags(p1_tags) + " not in " + join_tags(w2_tags_combined), inst);
307+
return nullptr;
308+
}
309+
}
310+
311+
for (auto& p2_tags : w2_tags) {
312+
if (!has_intersection(p2_tags, w1_tags_combined)) {
313+
Logger::Warning("No matching tags found on loft profiles: " + join_tags(p2_tags) + " not in " + join_tags(w1_tags_combined), inst);
314+
return nullptr;
315+
}
316+
}
317+
} else {
318+
if (w1->children.size() != w2->children.size()) {
319+
Logger::Warning("Mismatching number of edges: " +
320+
std::to_string(w1->children.size()) + " vs " +
321+
std::to_string(w2->children.size()),
322+
inst);
323+
return nullptr;
324+
}
167325
}
326+
327+
std::vector<taxonomy::point3::ptr> points;
328+
329+
std::vector<std::string> common_tags_vec;
330+
if (w1->tags) {
331+
std::set<std::string> common_tags;
332+
for (const auto& t : *w1->tags) {
333+
if (tag_to_point_on_w2.find(t) == tag_to_point_on_w2.end()) {
334+
continue;
335+
}
336+
337+
const auto& p1 = tag_to_point_on_w1[t];
338+
const auto& p2 = tag_to_point_on_w2[t];
339+
340+
auto p3 = (lerp(p1->ccomponents(), p2->ccomponents(), relative_dist_along) + interpolated_offset).eval();
341+
342+
std::set<std::string> tags_for_this_point_on_subsequent_profile = {t};
343+
344+
if (is_last_placement_of_this_profile) {
345+
for (auto& ts : w2_tags) {
346+
if (ts.find(t) != ts.end()) {
347+
tags_for_this_point_on_subsequent_profile = ts;
348+
}
349+
}
350+
}
351+
352+
for (auto& x : tags_for_this_point_on_subsequent_profile) {
353+
points.push_back(taxonomy::make<taxonomy::point3>(p3));
354+
common_tags_vec.push_back(x);
355+
}
356+
}
357+
} else {
358+
for (auto tmp__ : boost::combine(w1_points, w2_points)) {
359+
boost::tie(p1, p2) = tmp__;
360+
auto p3 = (lerp(p1->ccomponents(), p2->ccomponents(), relative_dist_along) + interpolated_offset).eval();
361+
points.push_back(taxonomy::make<taxonomy::point3>(p3));
362+
}
363+
}
364+
365+
/*
366+
// This is handled in the loop_to_points() function above
168367
if (!points.empty()) {
169368
if (!w1->closed.get_value_or(true) && !w2->closed.get_value_or(true)) {
170369
// open polygon, add last point
@@ -178,12 +377,17 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
178377
points.push_back(points.front());
179378
}
180379
}
380+
*/
181381

182382
auto interpolated_loop = polygon_from_points(points);
183-
interpolated_loop->external = w1->external;
184383
if (interpolated->kind() == taxonomy::FACE) {
185-
std::static_pointer_cast<taxonomy::face>(interpolated)->children.push_back(interpolated_loop);
384+
interpolated_loop->external = w1->external;
385+
std::static_pointer_cast<taxonomy::face>(interpolated)->children.push_back(interpolated_loop);
186386
} else {
387+
if (w1->tags) {
388+
std::static_pointer_cast<taxonomy::loop>(interpolated)->tags = common_tags_vec;
389+
}
390+
std::static_pointer_cast<taxonomy::loop>(interpolated)->closed = w1->closed;
187391
std::static_pointer_cast<taxonomy::loop>(interpolated)->children = interpolated_loop->children;
188392
}
189393
}
@@ -227,6 +431,19 @@ taxonomy::loft::ptr ifcopenshell::geometry::make_loft(const Settings& settings_,
227431
auto m = (m4b * loft->children.back()->matrix->ccomponents()).eval();
228432
loft->children.back()->matrix->components() = m;
229433
}
434+
435+
auto find_closest = [](const std::vector<double>& v, double target) -> std::vector<double>::const_iterator {
436+
auto it = std::lower_bound(v.begin(), v.end(), target);
437+
438+
if (it == v.begin()) {
439+
return it;
440+
}
441+
442+
double after = *it;
443+
double before = *(it - 1);
444+
445+
return (std::abs(after - target) < std::abs(target - before)) ? it : (it - 1);
446+
};
230447
}
231448

232449
return loft;

0 commit comments

Comments
 (0)