city_generator2

Generated conformal 3D meshes representing a city
git clone git://git.meso-star.fr/city_generator2.git
Log | Files | Refs | README | LICENSE

commit 0b7f78c48125a8dc05d8c09a07f3cb415196f189
parent c7576e1b9fcbd86d10927be07f288ce801b14f1f
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Wed, 18 Oct 2023 10:12:38 +0200

Change invalid file management; reject buildings too close to ground edge

Diffstat:
MREADME.md | 6+++++-
Mcmake/CMakeLists.txt | 2++
Msrc/cg_args.c | 4+---
Msrc/cg_building.c | 21+++++++++++++--------
Msrc/cg_building.h | 3++-
Msrc/cg_city.c | 301+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/cg_city.h | 10+++++++---
Msrc/cg_construction_mode.c | 28+++++++++++++++++++---------
Msrc/cg_construction_mode.h | 4+---
Msrc/cg_construction_mode_0.c | 32++++++++++++++++++++------------
Msrc/cg_construction_mode_1.c | 69++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/cg_default.h.in | 1+
12 files changed, 280 insertions(+), 201 deletions(-)

diff --git a/README.md b/README.md @@ -34,7 +34,11 @@ To set them one can use the -D<VAR>=<VAL> pattern. The parameters are: either -a the change the type to ascii, or -b to change it to binary. - `CG2_CLOSE_NEIGHBOR_DISTANCE`: The distance up to which a close neighbor - building prevents windows to be generated. Default is 2 meters. + building prevents windows to be generated. Default is 2 meters, must be + strictly positive. + +- `CG2_MIN_DISTANCE_TO_MAP_LIMITS': The minimum distance between a building and + the ground limits. Default is 2 meters, must be strictly positive. ## Release notes diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -23,6 +23,8 @@ option(STL_OUTPUT_DEFAULT_IS_BINARY "Default is to use binary format for output STL files" OFF) set(CG2_CLOSE_NEIGHBOR_DISTANCE "2" CACHE STRING "Distance up to which windows are not generated") +set(CG2_MIN_DISTANCE_TO_MAP_LIMITS "2" CACHE STRING + "Minimum distance from a building to the map limit") if(CMAKE_HOST_UNIX) set(CG2_DOC "TROFF" CACHE STRING diff --git a/src/cg_args.c b/src/cg_args.c @@ -44,7 +44,7 @@ short_help(void) { print_version(); printf("\nUsage:\n" - "city_generator2 -m <FILENAME> { -c <FILENAME> }+ -[%c] [-s] [-V verbosity] [-k] [-f <NAME>] [-F][-E]\n" + "city_generator2 -m <FILENAME> { -c <FILENAME> }+ -[%c] [-s] [-V verbosity] [-k] [-f <NAME>] [-F <level>][-E]\n" "city_generator2 [-h]\n" "city_generator2 [-v]\n", CG2_ARGS_CHANGE_BINARY_DEFAULT_OPTION @@ -140,7 +140,6 @@ parse_args case '?': /* Unrecognized option */ { char* ptr = strchr(option_list, optopt); - res = RES_BAD_ARG; if(ptr && ptr[1] == ':') { logger_print(logger, LOG_ERROR, "Missing argument for option -%c\n", @@ -227,7 +226,6 @@ parse_args logger_print(logger, LOG_ERROR, "Invalid argument for option -%c: %s\n", opt, optarg); - res = RES_BAD_ARG; goto error; } break; diff --git a/src/cg_building.c b/src/cg_building.c @@ -138,20 +138,25 @@ build_adjoining htable_building_begin(close_buildings, &it); htable_building_end(close_buildings, &end); prev = darray_adjoining_data_size_get(adjoining); - ERR(darray_adjoining_data_resize(adjoining, prev + count)); + ERR(darray_adjoining_data_reserve(adjoining, prev + count)); while(!htable_building_iterator_eq(&it, &end)) { unsigned char flags = *htable_building_iterator_data_get(&it); struct building* close_building; - struct adjoining_data* adj; + struct adjoining_data adj; ASSERT(flags & CLOSE_PROXIMITY); (void)flags; close_building = *htable_building_iterator_key_get(&it); - adj = darray_adjoining_data_data_get(adjoining) + prev + i; - ERR(build_envelop(close_building, &adj->envelop)); - ERR(darray_geometries_push_back(current_cad, &adj->envelop)); - adj->main_building = building; - adj->adjoining_building = close_building; - ERR(str_append_printf(&msg, " '%s'", str_cget(&close_building->name))); htable_building_iterator_next(&it); + if(close_building->event_flags & BUILDING_REMOVED) + continue; + adjoining_data_init(allocator, &adj); + ERR(build_envelop(close_building, &adj.envelop)); + ERR(darray_geometries_push_back(current_cad, &adj.envelop)); + ERR(scad_geometry_ref_put(adj.envelop)); /* Ownership transfer */ + adj.main_building = building; + adj.adjoining_building = close_building; + ERR(str_append_printf(&msg, (i ? ", '%s'" : " '%s'"), + str_cget(&close_building->name))); + ERR(darray_adjoining_data_push_back(adjoining, &adj)); i++; } diff --git a/src/cg_building.h b/src/cg_building.h @@ -47,7 +47,8 @@ enum building_event { BUILDING_NO_EVENT = 0, BUILDING_WITH_OVERLAPPING = BIT(0), BUILDING_OUT_OF_GROUND_EXTENT = BIT(1), - BUILDING_INSIDE_BUILDING = BIT(2), + BUILDING_TOO_CLOSE_TO_BORDER = BIT(2), + BUILDING_INSIDE_BUILDING = BIT(3), BUILDING_REMOVED = BIT(5), BUILDING_WITH_CLOSE_NEIGHBOR = BIT(6), BUILDING_DUMPED_TO_OBJ = BIT(7), diff --git a/src/cg_city.c b/src/cg_city.c @@ -43,31 +43,8 @@ #include <string.h> -static int -non_overlap_is_inside - (struct building* b1, - struct building* b2) -{ - size_t count, i; - double pt[2]; - int situation; - ASSERT(b1 && b2); - SCPR(polygon_get_components_count(b1->pg, &count)); - ASSERT(count == 1); - SCPR(polygon_get_components_count(b2->pg, &count)); - ASSERT(count == 1); - SCPR(polygon_get_vertices_count(b2->pg, 0, &count)); - for(i = 0; i < count; i++) { - SCPR(polygon_get_position(b2->pg, 0, i, pt)); - SCPR(point_in_component(b1->pg, 0, pt, &situation)); - if(situation != 0) break; - /* If vertex is shared test the next one */ - } - return (situation != -1); -} - /* Unregister building from adjoining information of other buildings */ -static void +void unregister_close_building (struct city* city, struct building* building) @@ -77,6 +54,8 @@ unregister_close_building for(i = 0; i < city->allocated_buildings_count; i++) { struct building *b = city->buildings+i; unsigned char *ptr; + if(b->event_flags & BUILDING_ALREADY_UNREGISTRED) return; + b->event_flags |= BUILDING_ALREADY_UNREGISTRED; ptr = htable_building_find(&b->close_buildings, &building); if(!ptr) continue; /* No registered information */ n = htable_building_erase(&b->close_buildings, &building); @@ -96,7 +75,7 @@ make_b_pair } res_T -dump_obj +dump_footprint_to_obj (struct mem_allocator* allocator, struct building* building, struct scpr_polygon* alternate_polygon) /* Can be NULL */ @@ -113,7 +92,7 @@ dump_obj str_init(allocator, &filename); ERR(str_copy(&filename, &building->name)); - ERR(str_append(&filename, ".obj")); + ERR(str_append(&filename, "_footprint.obj")); stream = fopen(str_cget(&filename), "w"); p = alternate_polygon ? alternate_polygon : building->pg; ERR(scpr_polygon_dump_to_obj(p, stream)); @@ -151,6 +130,8 @@ int overlapping_segments struct building* building = ctx->buildings + i; const struct scpr_polygon* pg = ctx->alternate_polygons ? ctx->alternate_polygons[i] : building->pg; + if(building->event_flags & BUILDING_REMOVED) + continue; if(pg == segment1->polygon) { building1 = building; name1 = str_cget(&building->name); @@ -238,9 +219,10 @@ int simple_intersection } /* Dump error polygons in OBJ files */ if(ctx->dump_footprints_level == 1) { - ERR(dump_obj(allocator, building1, NULL)); + ERR(dump_footprint_to_obj(allocator, building1, NULL)); } - ERR(darray_pbuilding_push_back(&ctx->city->removed_buildings, &building1)); + building1->event_flags |= BUILDING_REMOVED; + unregister_close_building(ctx->city, building1); break; case CLOSE_PROXIMITY: break; @@ -262,16 +244,17 @@ int simple_intersection } /* Dump error polygons in OBJ files */ if(ctx->dump_footprints_level == 1) { - ERR(dump_obj(allocator, building1, NULL)); - ERR(dump_obj(allocator, building2, NULL)); + ERR(dump_footprint_to_obj(allocator, building1, NULL)); + ERR(dump_footprint_to_obj(allocator, building2, NULL)); } - ERR(darray_pbuilding_push_back(&ctx->city->removed_buildings, &building1)); - ERR(darray_pbuilding_push_back(&ctx->city->removed_buildings, &building2)); + building1->event_flags |= BUILDING_REMOVED; + building2->event_flags |= BUILDING_REMOVED; + unregister_close_building(ctx->city, building1); + unregister_close_building(ctx->city, building2); break; case CLOSE_PROXIMITY: logger_print(logger, LOG_OUTPUT, - "Buildings '%s' and '%s' are in close proximity.\n", - name1, name2); + "Buildings '%s' and '%s' are in close proximity.\n", name1, name2); break; default: FATAL("Invalid type."); } @@ -354,7 +337,6 @@ create_city city->dump_footprints_level = args->dump_footprints_level; htable_names_init(allocator, &city->dump_footprint_names); htable_common_init(allocator, &city->common); - darray_pbuilding_init(allocator, &city->removed_buildings); ground_init(allocator, &city->ground); city->array_and_tables_initialized = 1; /* Some specific building footprints will be dumped (command line request) */ @@ -397,14 +379,18 @@ create_city || (tmp_res != RES_OK && city->dump_footprints_level == 1) || city->dump_footprints_level == 2; if(dump) { - ERR(dump_obj(allocator, building, NULL)); + ERR(dump_footprint_to_obj(allocator, building, NULL)); } if(tmp_res != RES_OK) { if(city->keep_running_on_errors) { logger_print(city->logger, LOG_WARNING, "Building '%s' will not be part of the output.\n", str_cget(&building->name)); - ERR(darray_pbuilding_push_back(&city->removed_buildings, &building)); + unregister_close_building(city, building); + building->event_flags |= BUILDING_REMOVED; + /* Insert a NULL polygon to preserve indexes */ + tmp_polygon = NULL; + ERR(darray_polygons_push_back(&offset_polygons, &tmp_polygon)); continue; } res = tmp_res; @@ -438,7 +424,7 @@ create_city /* Check for polygons overlapping */ ctx.city = city; ctx.buildings = city->buildings; - ctx.buildings_count = city->initialized_buildings_count; + ctx.buildings_count = city->allocated_buildings_count; ctx.intersection_found = &error_occured; ctx.search_type = OVERLAPPING_PROXIMITY; ctx.dump_footprints_level = city->dump_footprints_level; @@ -460,12 +446,14 @@ create_city if(b1->event_flags & BUILDING_REMOVED) continue; for(j = 0; j < i; j++) { struct building* b2 = city->buildings + j; - int in = 0; + int in1, in2; if((b2->event_flags & BUILDING_REMOVED)) continue; - /* b1 and b2 and 2 different valid buildings */ - if(non_overlap_is_inside(b1, b2)) { - ASSERT(!non_overlap_is_inside(b2, b1)); - ERR(darray_pbuilding_push_back(&city->removed_buildings, &b2)); + /* b1 and b2 are 2 different valid buildings */ + ERR(scpr_is_component_in_component(b1->pg, 0, b2->pg, 0, &in1)); + ERR(scpr_is_component_in_component(b2->pg, 0, b1->pg, 0, &in2)); + ASSERT(!in1 || !in2); + if(in2) { + unregister_close_building(city, b2); b2->event_flags |= BUILDING_INSIDE_BUILDING | BUILDING_REMOVED; if(city->keep_running_on_errors) { logger_print(logger, LOG_WARNING, @@ -480,8 +468,8 @@ create_city str_cget(&b2->name), str_cget(&b1->name)); } } - else if(non_overlap_is_inside(b2, b1)) { - ERR(darray_pbuilding_push_back(&city->removed_buildings, &b1)); + else if(in1) { + unregister_close_building(city, b1); b1->event_flags |= BUILDING_INSIDE_BUILDING | BUILDING_REMOVED; if(city->keep_running_on_errors) { logger_print(logger, LOG_WARNING, @@ -496,20 +484,12 @@ create_city str_cget(&b1->name), str_cget(&b2->name)); } } - if(in && !city->keep_running_on_errors) { + if((in1 || in2) && !city->keep_running_on_errors) { res = RES_BAD_ARG; goto error; } } } - /* Remove removed buildings from proximity information of other buildings */ - for(i = 0; i < darray_pbuilding_size_get(&city->removed_buildings); i++) { - struct building* b = darray_pbuilding_data_get(&city->removed_buildings)[i]; - if(b->event_flags & BUILDING_ALREADY_UNREGISTRED) - continue; /* Do not unregister twice! */ - b->event_flags |= BUILDING_ALREADY_UNREGISTRED; - unregister_close_building(city, b); - } /* Check for polygons proximity */ ctx.search_type = CLOSE_PROXIMITY; @@ -525,17 +505,24 @@ exit: if(close_intersector) SCPR(intersector_ref_put(close_intersector)); htable_names_release(&names); *out_city = city; - time_sub(&dt, time_current(&dt), &t0); - time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); - logger_print(city->logger, LOG_OUTPUT, "Map implantation analyzed in %s.\n", buf); + if(res == RES_OK && city) { + time_sub(&dt, time_current(&dt), &t0); + time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); + logger_print(city->logger, LOG_OUTPUT, + "Map implantation analyzed in %s.\n", buf); + } return res; error: + time_sub(&dt, time_current(&dt), &t0); + time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); if(building) { logger_print(logger, LOG_ERROR, "Error creating building '%s'.\n", str_cget(&building->name)); } else { logger_print(logger, LOG_ERROR, "Error creating city.\n"); } + logger_print(city->logger, LOG_ERROR, + "Map implantation canceled after %s.\n", buf); CHK(RES_OK == release_city(city)); city = NULL; goto exit; @@ -556,7 +543,6 @@ release_city(struct city* city) if(city->array_and_tables_initialized) { htable_common_release(&city->common); htable_names_release(&city->dump_footprint_names); - darray_pbuilding_release(&city->removed_buildings); } /* iterate on building */ for (i = 0; i < city->allocated_buildings_count; i++) { @@ -574,6 +560,8 @@ error: goto exit; } +#define ERR1(Expr) if((tmp_res = (Expr)) != RES_OK) goto error1; else (void)0 + res_T city_cad_build(struct city* city) { @@ -586,17 +574,19 @@ city_cad_build(struct city* city) struct darray_double common; void* cad = NULL; struct darray_geometries current_cad; + struct mem_allocator* allocator; struct time t0, dt; char buf[128]; ASSERT(city); - darray_geometries_init(city->allocator, &current_cad); - darray_adjoining_data_init(city->allocator, &adjoining_data); - darray_double_init(city->allocator, &common); + allocator = city->allocator; + darray_geometries_init(allocator, &current_cad); + darray_adjoining_data_init(allocator, &adjoining_data); + darray_double_init(allocator, &common); /* Initialize star-cad */ - ERR(scad_initialize(city->logger, city->allocator, city->verbosisty_level)); + ERR(scad_initialize(city->logger, allocator, city->verbosisty_level)); scad_initialized = 1; options.Mesh.MeshSizeFromPoints = 0; #ifdef NDEBUG @@ -606,97 +596,121 @@ city_cad_build(struct city* city) #endif ERR(scad_set_options(&options)); - /* iterate on buildings */ + /* Iterate on buildings + * If a building fails and keep_running is set, try hard to continue with + * remaining buildings. */ for(i = 0; i < city->allocated_buildings_count; i++) { + struct adjoining_data* adj; + res_T tmp_res; building = city->buildings + i; if(city->dump_footprints_level == 2) { - ERR(dump_obj(city->allocator, building, NULL)); + ERR1(dump_footprint_to_obj(allocator, building, NULL)); } if(building->event_flags & BUILDING_REMOVED) { + continue; + } + + /* create building CAD */ + time_current(&t0); + logger_print(city->logger, LOG_OUTPUT, + "Start processing building '%s'.\n", str_cget(&building->name)); + darray_geometries_clear(&current_cad); + tmp_res = building->functors->build_cad(building, city->dump_footprints_level, + city->keep_running_on_errors, &adjoining_data, &current_cad, + (void**)&cad); + if(tmp_res != RES_OK) { if(city->dump_footprints_level == 1) { - ERR(dump_obj(city->allocator, building, NULL)); + ERR1(dump_footprint_to_obj(allocator, building, NULL)); } - } else { - /* create building CAD */ - struct adjoining_data* adj; - time_current(&t0); - logger_print(city->logger, LOG_OUTPUT, - "Start processing building '%s'.\n", str_cget(&building->name)); - darray_geometries_clear(&current_cad); - res = building->functors->build_cad(building, city->dump_footprints_level, - city->keep_running_on_errors, &adjoining_data, &current_cad, - (void**)&cad); - if(res != RES_OK) { - if(city->dump_footprints_level == 1) { - ERR(dump_obj(city->allocator, building, NULL)); - } - time_sub(&dt, time_current(&dt), &t0); - time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); - logger_print(city->logger, LOG_OUTPUT, - "Building '%s': stopped after %s.\n", str_cget(&building->name), buf); - if(city->keep_running_on_errors) { - logger_print(city->logger, LOG_WARNING, - "Building '%s' will not be part of the output.\n", - str_cget(&building->name)); - /* Unregister building from adjoining information of other buildings */ - unregister_close_building(city, building); - darray_adjoining_data_clear(&adjoining_data); - /* FIXME: adjoining buildings may have been built already, taking this - * building into account. There is no simple way to undo this other - * than rebuild them after this building removal. The visible effects - * are on walls' meshes being uselessly refined due to conformity with - * the now-removed building, and possible window removal that should - * now be reversed. */ - res = RES_OK; - continue; - } + time_sub(&dt, time_current(&dt), &t0); + time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); + if(city->keep_running_on_errors) { + logger_print(city->logger, LOG_WARNING, + "Building '%s' will not be part of the output (aborted after %s).\n", + str_cget(&building->name), buf); + /* Unregister building from adjoining information of other buildings */ + unregister_close_building(city, building); + darray_adjoining_data_clear(&adjoining_data); + /* FIXME: adjoining buildings may have been built already, taking this + * building into account. There is no simple way to undo this other + * than rebuild them after this building removal. The visible effects + * are on walls' meshes being uselessly refined due to conformity with + * the now-removed building, and possible window removal that should + * now be reversed. */ + continue; + } else { + logger_print(city->logger, LOG_ERROR, + "Building '%s': stopped after %s.\n", + str_cget(&building->name), buf); + } + } + ERR1(tmp_res); + ERR1(scad_scene_mesh()); + /* Keep the mesh of some geometry if planned */ + adj = darray_adjoining_data_data_get(&adjoining_data); + for(a = 0; a < darray_adjoining_data_size_get(&adjoining_data); a++) { + struct b_pair pair; + size_t c; + ERR1(scad_geometry_get_count(adj[a].common_geometry, &c)); + if(c == 0) { + /* Not really adjoining */ + continue; } - ERR(res); - ERR(scad_scene_mesh()); + if(!adj[a].save || building != adj[a].main_building) { + /* Only the building that created the record can do the job + * (as geom lifetime is limited to a single iteration) */ + continue; + } + darray_double_clear(&common); + ERR1(scad_stl_get_data(adj[a].common_geometry, &common)); + make_b_pair(&pair, building, adj[a].adjoining_building); + ASSERT(darray_double_size_get(&common) > 0 + && darray_double_size_get(&common) % 9 == 0); + ERR1(htable_common_set(&city->common, &pair, &common)); + } + tmp_res = building->functors->export_stl(cad, city->binary_export); + if(tmp_res == RES_OK) { /* Keep ground/building's boundary triangles now as the geometry will be * released before ground is built */ - ERR(building->functors->save_ground_connection_triangles(cad, - &city->ground.ground_trg)); - /* Keep the mesh of some geometry if planned */ - adj = darray_adjoining_data_data_get(&adjoining_data); - for(a = 0; a < darray_adjoining_data_size_get(&adjoining_data); a++) { - struct b_pair pair; - size_t c; - ERR(scad_geometry_get_count(adj[a].common_geometry, &c)); - if(c == 0) { - /* Not really adjoining */ - continue; - } - if(!adj[a].save || building != adj[a].main_building) { - /* Only the building that created the record can do the job - * (as geom lifetime is limited to a single iteration) */ - continue; - } - darray_double_clear(&common); - ERR(scad_stl_get_data(adj[a].common_geometry, &common)); - make_b_pair(&pair, building, adj[a].adjoining_building); - ASSERT(darray_double_size_get(&common) > 0 - && darray_double_size_get(&common) % 9 == 0); - ERR(htable_common_set(&city->common, &pair, &common)); - } - ERR(building->functors->export_stl(cad, city->binary_export)); + ERR1(building->functors->save_ground_connection_triangles(cad, + &city->ground.ground_trg)); building->event_flags |= BUILDING_CAD_EXPORTED_TO_STL; + generated_buildings_count++; logger_print(city->logger, LOG_OUTPUT, "Building '%s': %s STL files created.\n", str_cget(&building->name), (city->binary_export ? "binary" : "ascii")); - /* Cannot keep any geometry from one building to the other */ + } + else if(city->keep_running_on_errors) { + logger_print(city->logger, LOG_ERROR, + "Some STL files could be missing for building '%s'.\n", + str_cget(&building->name)); + logger_print(city->logger, LOG_ERROR, + "You may want to delete any generated STL file for this building " + "after inspecting them.\n"); + } +error1: + /* Cannot keep any geometry from one building to the other */ + if(cad) { ERR(building->functors->release_cad(cad)); cad = NULL; /* Avoid double release */ - darray_adjoining_data_clear(&adjoining_data); - darray_geometries_clear(&current_cad); - ERR(scad_scene_clear()); - generated_buildings_count++; - time_sub(&dt, time_current(&dt), &t0); - time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); + } + darray_adjoining_data_clear(&adjoining_data); + darray_geometries_clear(&current_cad); + ERR(scad_scene_clear()); + time_sub(&dt, time_current(&dt), &t0); + time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); + if(tmp_res == RES_OK) { logger_print(city->logger, LOG_OUTPUT, "Building '%s': done in %s.\n", str_cget(&building->name), buf); } + else if(city->keep_running_on_errors) { + logger_print(city->logger, LOG_ERROR, + "Building '%s' canceled after %s.\n", str_cget(&building->name), buf); + } + if(!city->keep_running_on_errors) { + ERR(tmp_res); + } } building = NULL; city->cad_generated_buildings_count = generated_buildings_count; @@ -710,11 +724,15 @@ exit: return res; error: if(building) { + time_sub(&dt, time_current(&dt), &t0); + time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); logger_print(city->logger, LOG_ERROR, - "Error generating CAD for building '%s'.\n", str_cget(&building->name)); + "Error generating CAD for building '%s'; canceled after %s.\n", + str_cget(&building->name), buf); } goto exit; } +#undef ERR1 res_T city_ground_build(struct city* city) @@ -765,18 +783,25 @@ exit: ground_clear(&city->ground); if(scad_initialized) SCAD(finalize()); time_sub(&dt, time_current(&dt), &t0); - time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); - logger_print(city->logger, LOG_OUTPUT, "Ground done in %s.\n", buf); + if(res == RES_OK) { + time_dump(&dt, TIME_SEC | TIME_MIN, NULL, buf, sizeof(buf)); + logger_print(city->logger, LOG_OUTPUT, "Ground done in %s.\n", buf); + } return res; error: + time_sub(&dt, time_current(&dt), &t0); + time_dump(&dt, TIME_SEC | TIME_MSEC, NULL, buf, sizeof(buf)); if(building) { logger_print(city->logger, LOG_ERROR, "Error generating ground footprint for building '%s'.\n", str_cget(&building->name)); } else if(i == city->allocated_buildings_count) { - logger_print(city->logger, LOG_ERROR, - "Error generating ground.\n"); + logger_print(city->logger, LOG_ERROR, "Error generating ground.\n"); + } else { + logger_print(city->logger, LOG_ERROR, "Unknown error generating ground.\n"); } + logger_print(city->logger, LOG_ERROR, + "Ground processing aborted after %s.\n", buf); goto exit; } diff --git a/src/cg_city.h b/src/cg_city.h @@ -56,7 +56,7 @@ static INLINE res_T ppoly_copy(struct scpr_polygon** dst, struct scpr_polygon* const* src) { ASSERT(dst && src); *dst = *src; - SCPR(polygon_ref_get(*src)); + if(*src) SCPR(polygon_ref_get(*src)); return RES_OK; } static FINLINE res_T @@ -131,7 +131,6 @@ struct city { initialized_buildings_count; struct htable_names dump_footprint_names; struct htable_common common; - struct darray_pbuilding removed_buildings; struct mem_allocator* allocator; struct logger* logger; struct scpr_device* scpr; @@ -142,6 +141,11 @@ struct city { int array_and_tables_initialized; }; +void +unregister_close_building + (struct city* city, + struct building* building); + res_T create_city (struct mem_allocator* allocator, @@ -163,7 +167,7 @@ res_T release_city(struct city* city); res_T -dump_obj +dump_footprint_to_obj (struct mem_allocator* allocator, struct building* building, struct scpr_polygon* alternate_polygon); /* Can be NULL */ diff --git a/src/cg_construction_mode.c b/src/cg_construction_mode.c @@ -18,7 +18,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include "cg.h" -#include "cg_types.h" +#include "cg_default.h" #include "cg_building.h" #include "cg_city.h" #include "cg_city_parsing_schemas.h" @@ -27,6 +27,8 @@ #include <rsys/rsys.h> #include <rsys/str.h> #include <rsys/logger.h> +#include <rsys/double2.h> + #include <star/scad.h> #include <star/scpr.h> @@ -51,15 +53,14 @@ res_T init_building_base (struct building* building, struct city* city, - struct parsed_city_building* parsed_data, - const double lower[2], - const double upper[2]) + struct parsed_city_building* parsed_data) { int inside, cw; size_t count; + double l[2], u[2], t[2]; res_T res = RES_OK; - ASSERT(city && building && parsed_data && lower && upper); + ASSERT(city && building && parsed_data); building->city = city; building->height = parsed_data->height; @@ -76,17 +77,26 @@ init_building_base "Building '%s' had quasi-identical vertices that have been merged.\n", str_cget(&building->name)); } - ERR(scpr_polygon_in_bbox(building->pg, lower, upper, &inside)); + /* First try with a slightly decreased bbox as buildings are not allowed to + * reach the bbox limit */ + d2(t, CG2_MIN_DISTANCE_TO_MAP_LIMITS, CG2_MIN_DISTANCE_TO_MAP_LIMITS); + d2_add(l, city->lower, t); + d2_sub(u, city->upper, t); + ERR(scpr_polygon_in_bbox(building->pg, l, u, &inside)); if(!inside) { + ERR(scpr_polygon_in_bbox(building->pg, city->lower, city->upper, &inside)); logger_print(city->logger, (city->keep_running_on_errors ? LOG_WARNING : LOG_ERROR), - "Building '%s' is out of the ground extent.\n", + (inside + ? "Building '%s' is too close to the map limits.\n" + : "Building '%s' is out of the ground extent.\n"), str_cget(&building->name)); - building->event_flags |= BUILDING_OUT_OF_GROUND_EXTENT | BUILDING_REMOVED; + building->event_flags + |= (inside ? BUILDING_TOO_CLOSE_TO_BORDER : BUILDING_OUT_OF_GROUND_EXTENT); res = RES_BAD_ARG; goto error; } - /* Force orientation so that pg's normal is downward */ + /* Force orientation so that pg's normal is upward */ ERR(scpr_polygon_is_component_cw(building->pg, 0, &cw)); if(!cw) { ERR(scpr_polygon_reverse_component(building->pg, 0)); diff --git a/src/cg_construction_mode.h b/src/cg_construction_mode.h @@ -50,9 +50,7 @@ res_T init_building_base (struct building* building, struct city* city, - struct parsed_city_building* parsed_data, - const double lower[2], - const double upper[2]); + struct parsed_city_building* parsed_data); res_T release_building_base diff --git a/src/cg_construction_mode_0.c b/src/cg_construction_mode_0.c @@ -439,7 +439,7 @@ init_cmode_0 building->construction_mode = mode_0; building->functors = &functors_0; - ERR(init_building_base(building, city, parsed_data, lower, upper)); + ERR(init_building_base(building, city, parsed_data)); str_init(allocator, &dataset_name); name_initialized = 1; @@ -534,7 +534,9 @@ build_cad_cmode_0 ERR(scpr_polygon_get_components_count(pg_int, &c)); if(c != 1) { logger_print(logger, (keep_running_on_errors ? LOG_WARNING : LOG_ERROR), - "Building '%s' is too small with respect to wall thickness.\n", name); + "Building '%s' shape is not compatible with wall thickness.\n", name); + building->event_flags |= BUILDING_REMOVED; + unregister_close_building(building->city, building); error_msg_printed = 1; res = RES_BAD_ARG; goto error; @@ -553,8 +555,9 @@ build_cad_cmode_0 ERR(scpr_intersector_check(overlapping_intersector, &callbacks, &ctx)); if(error_occured) { logger_print(logger, LOG_ERROR, - "Internal error building CAD for building '%s'.\n", name); - ERR(darray_pbuilding_push_back(&building->city->removed_buildings, &building)); + "Internal error generating CAD for building '%s'.\n", name); + building->event_flags |= BUILDING_REMOVED; + unregister_close_building(ctx.city, building); error_msg_printed = 1; res = RES_BAD_ARG; goto error; @@ -576,7 +579,6 @@ build_cad_cmode_0 adjoining_n = htable_building_size_get(&building->close_buildings); if (adjoining_n > 0) { ERR(build_adjoining(building, current_cad, adjoining_data)); - ASSERT(adjoining_n == darray_adjoining_data_size_get(adjoining_data)); } /* build fake ground */ @@ -597,6 +599,7 @@ build_cad_cmode_0 ERR(scad_geometries_swap(cur_cad, partitioned, cad_count, Scad_swap_geometry)); /* After partitioning, manage common parts with other buildings */ + adjoining_n = darray_adjoining_data_size_get(adjoining_data); if(adjoining_n > 0) { size_t a; struct b_pair pair; @@ -654,7 +657,7 @@ exit: error: if(logger && building && !error_msg_printed) { logger_print(logger, LOG_ERROR, - "Unknown error building CAD for building '%s'.\n", + "Unknown error generating CAD for building '%s'.\n", str_cget(&building->name)); } if(data_cad) CHK(RES_OK == release_cad_cmode_0(data_cad)); @@ -722,12 +725,6 @@ export_stl_cmode_0 allocator = data_cad->building->city->allocator; - /* floor export */ - ERR(scad_stl_export(data_cad->floor, NULL, Scad_force_normals_outward, binary)); - - /* roof export */ - ERR(scad_stl_export(data_cad->roof, NULL, Scad_force_normals_outward, binary)); - /* wall export */ if(darray_geometries_size_get(&data_cad->adj_walls) == 0) { ERR(scad_stl_export(data_cad->wall, NULL, Scad_force_normals_outward, binary)); @@ -778,6 +775,12 @@ export_stl_cmode_0 binary)); } + /* floor export */ + ERR(scad_stl_export(data_cad->floor, NULL, Scad_force_normals_outward, binary)); + + /* roof export */ + ERR(scad_stl_export(data_cad->roof, NULL, Scad_force_normals_outward, binary)); + /* cavity export */ ERR(scad_stl_export(data_cad->cavity, NULL, Scad_force_normals_outward, binary)); @@ -804,6 +807,11 @@ exit: } return res; error: + if(data_cad) { + logger_print(data_cad->building->city->logger, LOG_ERROR, + "Internal error '"__FILE__"': creating STL for building '%s'.\n", + str_cget(&data_cad->building->name)); + } goto exit; } diff --git a/src/cg_construction_mode_1.c b/src/cg_construction_mode_1.c @@ -88,8 +88,10 @@ do_offset ASSERT(offset < 0); logger_print(building->city->logger, (building->city->keep_running_on_errors ? LOG_WARNING : LOG_ERROR), - "Building '%s' is too small with respect to wall thickness.\n", + "Building '%s' shape is not compatible with wall thickness.\n", str_cget(&building->name)); + building->event_flags |= BUILDING_REMOVED; + unregister_close_building(building->city, building); *error_msg_printed = 1; res = RES_BAD_ARG; goto error; @@ -905,7 +907,7 @@ build_windows /* Check if the window intersects an unexpected wall of the building: * - the window is too large wrt of external size of the wall (the window's * size is a % of the internal size of the wall that can be larger than - * the external size due to angles). + * the external size due to corners). * - another wall facing the current one at a too close distance (can be the * prev/next with sharp angle, or not), * - a tiny unwanted wall created by noise at the polygon level (mainly due @@ -1455,7 +1457,7 @@ init_cmode_1 building->construction_mode = mode_1; building->functors = &functors_1; - ERR(init_building_base(building, city, parsed_data, lower, upper)); + ERR(init_building_base(building, city, parsed_data)); str_init(allocator, &dataset_name); name_initialized = 1; @@ -1511,7 +1513,7 @@ build_cad_cmode_1 double zero = 0; struct mem_allocator* allocator; struct logger* logger = NULL; - size_t i, cad_count; + size_t i, cad_count = 0; struct scad_geometry** cur_cad = NULL; struct scad_geometry** partitioned = NULL; @@ -1634,9 +1636,10 @@ build_cad_cmode_1 ERR(scpr_intersector_check(overlapping_intersector, &callbacks, &ctx)); if(error_occured) { logger_print(logger, LOG_ERROR, - "Internal error building CAD for building '%s'.\n", + "Internal error generating CAD for building '%s'.\n", name); - ERR(darray_pbuilding_push_back(&building->city->removed_buildings, &building)); + building->event_flags |= BUILDING_REMOVED; + unregister_close_building(ctx.city, building); error_msg_printed = 1; res = RES_BAD_ARG; goto error; @@ -1646,7 +1649,6 @@ build_cad_cmode_1 adjoining_n = htable_building_size_get(&building->close_buildings); if (adjoining_n > 0) { ERR(build_adjoining(building, current_cad, adjoining_data)); - ASSERT(adjoining_n == darray_adjoining_data_size_get(adjoining_data)); } /* windows */ @@ -1671,8 +1673,16 @@ build_cad_cmode_1 /* Swap original geometry and partitioned geometry in data_cad (was * accumulated into current_cad) */ ERR(scad_geometries_swap(cur_cad, partitioned, cad_count, Scad_swap_geometry)); + for(i = 0; i < cad_count; i++) { + if(partitioned[i]) { + ERR(scad_geometry_ref_put(partitioned[i])); + } + } + MEM_RM(allocator, partitioned); + partitioned = NULL; /* After partitioning, manage common parts with other buildings */ + adjoining_n = darray_adjoining_data_size_get(adjoining_data); if(adjoining_n > 0) { size_t a, c; struct b_pair pair; @@ -1731,7 +1741,7 @@ exit: error: if(logger && building && !error_msg_printed) { logger_print(logger, LOG_ERROR, - "Unknown error building CAD for building '%s'.\n", + "Unknown error generating CAD for building '%s'.\n", str_cget(&building->name)); } if(data_cad) CHK(RES_OK == release_cad_cmode_1(data_cad)); @@ -1800,25 +1810,33 @@ export_stl_cmode_1 allocator = data_cad->building->city->allocator; - /* floor export */ - ERR(scad_stl_export(data_cad->floor, NULL, Scad_force_normals_outward, binary)); - - /* roof export */ - ERR(scad_stl_export(data_cad->roof, NULL, Scad_force_normals_outward, binary)); - - /* wall export */ if(darray_geometries_size_get(&data_cad->adj_walls) == 0) { + /* wall export */ ERR(scad_stl_export(data_cad->wall, NULL, Scad_force_normals_outward, binary)); + + /* external insulation export */ + if(data_cad->external_insulation) { + ERR(scad_stl_export(data_cad->external_insulation, NULL, + Scad_force_normals_outward, binary)); + } } else { /* There is some adjoining building(s) to manage */ + struct scad_geometry* extern_most = data_cad->external_insulation + ? data_cad->external_insulation : data_cad->wall; size_t common_count = darray_common_trg_size_get(&data_cad->common_trg); const char* tmp; + if(data_cad->external_insulation) { + /* wall export is not impacted as walls are not the external layer */ + ERR(scad_stl_export(data_cad->wall, NULL, Scad_force_normals_outward, binary)); + } + + /* The external layer must use the common triangles */ darray_double_init(allocator, &trg); str_init(allocator, &name); initialized = 1; /* Get the triangles that are not common with adjoining buildings */ - ERR(scad_stl_get_data_partial(data_cad->wall, + ERR(scad_stl_get_data_partial(extern_most, darray_geometries_data_get(&data_cad->adj_walls), darray_geometries_size_get(&data_cad->adj_walls), &trg)); coord_count = darray_double_size_get(&trg); @@ -1849,13 +1867,19 @@ export_stl_cmode_1 coord_count += sz; ASSERT(coord_count % 9 == 0); } - ERR(scad_geometry_get_name(data_cad->wall, &tmp)); + ERR(scad_geometry_get_name(extern_most, &tmp)); ERR(str_set(&name, tmp)); ERR(str_append(&name, ".stl")); ERR(scad_stl_data_write(&trg, str_cget(&name), Scad_force_normals_outward, binary)); } + /* floor export */ + ERR(scad_stl_export(data_cad->floor, NULL, Scad_force_normals_outward, binary)); + + /* roof export */ + ERR(scad_stl_export(data_cad->roof, NULL, Scad_force_normals_outward, binary)); + /* foundation export */ if (data_cad->foundation) { ERR(scad_stl_export(data_cad->foundation, NULL, Scad_force_normals_outward, @@ -1880,12 +1904,6 @@ export_stl_cmode_1 Scad_force_normals_outward, binary)); } - /* external insulation export*/ - if (data_cad->external_insulation) { - ERR(scad_stl_export(data_cad->external_insulation, NULL, - Scad_force_normals_outward, binary)); - } - /* roof insulation export*/ if (data_cad->roof_insulation) { ERR(scad_stl_export(data_cad->roof_insulation, NULL, @@ -1941,6 +1959,11 @@ exit: } return res; error: + if(data_cad) { + logger_print(data_cad->building->city->logger, LOG_ERROR, + "Internal error creating STL for building '%s'.\n", + str_cget(&data_cad->building->name)); + } goto exit; } diff --git a/src/cg_default.h.in b/src/cg_default.h.in @@ -22,5 +22,6 @@ #define CG2_ARGS_STL_NON_DEFAULT_STR @CG2_ARGS_STL_NON_DEFAULT_STR@ #define CG2_ARGS_CHANGE_BINARY_DEFAULT_OPTION '@CG2_ARGS_CHANGE_BINARY_DEFAULT_OPTION@' #define CG2_CLOSE_NEIGHBOR_DISTANCE @CG2_CLOSE_NEIGHBOR_DISTANCE@ +#define CG2_MIN_DISTANCE_TO_MAP_LIMITS @CG2_MIN_DISTANCE_TO_MAP_LIMITS@ #endif