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 a5fea5bfd300b77d9995f190e357b0f1c9c3ee23
parent 2606834ffe80150207818df9ee1192113d144859
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date:   Fri, 18 Oct 2024 13:12:18 +0200

Add code to try reaching the expected glass ratio

The goal is to reach the ratio on a wall by wall basis: if a wall cannot
reach the ratio, we do not try to compensate on other walls.

The algorithm has some input constraints (min windows spacing, min and
max windows width) that are just #defined for now; some of them could be
part of user input.

Diffstat:
Msrc/cg_construction_mode_2.c | 353+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
1 file changed, 200 insertions(+), 153 deletions(-)

diff --git a/src/cg_construction_mode_2.c b/src/cg_construction_mode_2.c @@ -784,12 +784,13 @@ build_windows { res_T res = RES_OK; size_t i, j, adjoining_n, removed_count; - size_t removed_windows_sz = 0, removed_windows_adj = 0, removed_windows_self = 0; + size_t removed_windows_adj = 0, removed_windows_self = 0; + size_t removed_wall_sz = 0, missed_windows_ratio = 0; size_t list_n = 0, array_n; const char* prefix; - double N[3]; - double dir[3]; - double scale[3], sc; + double N[3], dir[3], scale[3]; + double effective_ratio, total_wall_surface = 0, total_windows_surface = 0; + int meet_effective_ratio; struct scad_geometry* surface = NULL; struct scad_geometry* win_surface = NULL; struct scad_geometry* surface_ = NULL; @@ -823,7 +824,6 @@ build_windows allocator = city->allocator; adjoining_n = darray_adjoining_data_size_get(adjoining_data); adj = darray_adjoining_data_data_get(adjoining_data); - sc = sqrt(data->glass_ratio); /* The glass fraction of walls' height and width */ darray_geometries_init(allocator, &hole_array); darray_geometries_init(allocator, &glass_array); str_init(allocator, &name); @@ -855,9 +855,8 @@ build_windows ERR(darray_geometries_reserve(&glass_array, list_n)); for(i = 0; i < list_n; i++) { double hsz, wall_surface, wall_width, center[3]; - size_t center_n; - size_t count, windows_count, n; - int removed = 0; + size_t center_n, count, n; + int removed_wall = 0; ERR(scad_geometry_get_count(list[i], &center_n)); ASSERT(center_n == 1); @@ -867,155 +866,191 @@ build_windows if(N[2] != 0) { /* keep only vertical face */ - removed = 1; + removed_wall = 1; } d3_normalize(N, N); + ERR(scad_geometry_get_mass(list[i], &wall_surface)); + wall_width = wall_surface / wall_height; + total_wall_surface += wall_surface; /* TODO: put it in parameters */ -#define CG2_MODE2_WINDOWS_WIDTH_HINT 1.2 -#define CG2_MODE2_WINDOWS_SPACING_HINT 0.4 -#define CG2_MODE2_WINDOWS_HEIGHT_DEFAULT_RATIO 0.6 - - if(!removed) { - ERR(scad_geometry_get_mass(list[i], &wall_surface)); - wall_width = wall_surface / wall_height; - if(wall_width < CG2_MIN_WINDOWS_WIDTH + CG2_MODE2_WINDOWS_SPACING_HINT * 2) { +#define CG2_MODE2_WINDOWS_MIN_WIDTH 1.2 +#define CG2_MODE2_WINDOWS_MAX_WIDTH 3.0 +#define CG2_MODE2_WINDOWS_MIN_SPACING 0.4 +#define CG2_MODE2_WINDOWS_HEIGHT_RATIO 0.6 + + if(!removed_wall) { + double window_height, expected_total_width_from_ratio; + double wc, best_wc, best_wc_score, best_wc_spacing, best_wc_width; + /* The basic result is: no window */ + best_wc = 0; + best_wc_score = 0; + best_wc_spacing = wall_width; + best_wc_width = 0; + /* search for the count, width, spacing that best meet expectations */ + window_height = CG2_MODE2_WINDOWS_HEIGHT_RATIO * wall_height; + expected_total_width_from_ratio = wall_width * data->glass_ratio; + wc = 1; + while(wall_width >= + wc * CG2_MODE2_WINDOWS_MIN_WIDTH + (wc + 1) * CG2_MODE2_WINDOWS_MIN_SPACING) { + double score; + double max_achievable_width = MMIN(CG2_MODE2_WINDOWS_MAX_WIDTH, + (wall_width - (wc + 1) * CG2_MODE2_WINDOWS_MIN_SPACING) / wc); + if(max_achievable_width * wc >= expected_total_width_from_ratio + && CG2_MODE2_WINDOWS_MIN_WIDTH * wc <= expected_total_width_from_ratio) + { + /* Expected ratio is achievable! */ + score = 1; + if(score > best_wc_score) { + best_wc = wc; + best_wc_score = score; + best_wc_width = expected_total_width_from_ratio / wc; + best_wc_spacing = (wall_width - wc * best_wc_width) / (wc + 1); + } + } else { + /* Compute the width that is closest to expectations */ + double best_width_for_wc = + (max_achievable_width * wc < expected_total_width_from_ratio) + ? max_achievable_width : CG2_MODE2_WINDOWS_MIN_WIDTH; + score = 1 / (1 + fabs(wc * best_width_for_wc - expected_total_width_from_ratio)); + ASSERT(score < 1); + if(score > best_wc_score) { + best_wc = wc; + best_wc_score = score; + best_wc_width = best_width_for_wc; + best_wc_spacing = (wall_width - wc * best_wc_width) / (wc + 1); + } + } + /* Try with 1 more window */ + wc++; + } + if(best_wc_score == 0) { /* No room for a single minimal window */ - removed = 1; - removed_windows_sz++; + removed_wall = 1; + removed_wall_sz++; + } + else if(best_wc_score < 1) { + missed_windows_ratio++; } - } - if(!removed) { - double window_width, window_height, window_spacing; - double windows_total_width_from_ratio, max_window_width, total_spacing; - double sw, sh, wc; - /* Compute a number of windows to fit in this wall and meet the ratio - * target */ - window_height = CG2_MODE2_WINDOWS_HEIGHT_DEFAULT_RATIO * wall_height; - wc = (wall_width - CG2_MODE2_WINDOWS_SPACING_HINT) - / (CG2_MODE2_WINDOWS_SPACING_HINT + CG2_MODE2_WINDOWS_WIDTH_HINT); - windows_count = MMAX(1, (size_t)floor(wc)); - wc = (double)windows_count; - /* Compute the effective windows width */ - windows_total_width_from_ratio = - wall_surface * data->glass_ratio / window_height; - max_window_width = - (wall_width - (wc + 1) * CG2_MODE2_WINDOWS_SPACING_HINT) / wc; - window_width = MMIN(windows_total_width_from_ratio / wc, max_window_width); - ASSERT(window_width > CG2_MIN_WINDOWS_WIDTH); - total_spacing = wall_width - wc * window_width; - window_spacing = total_spacing / (wc + 1); - sw = MMIN(1, 1.01 * window_width / wall_width); - sh = MMIN(1, 1.01 * window_height / wall_height); - d3(scale, sw, sw, sh); - ERR(scad_geometry_dilate(surface, center, scale, "scaled_", &scaled_)); - sw = window_width / wall_width; - sh = window_height / wall_height; - d3(scale, sw, sw, sh); - ERR(scad_geometry_dilate(surface, center, scale, "scaled", &scaled)); - - /* Try to create the planed windows */ - for(n = 0; n < windows_count; n++) { - double dxdydz[3], offset; - /* Compute windows positioning */ - offset = - 0.5 * (wall_width - window_width) /* to the left border */ - + window_spacing + (double)n * (window_spacing + window_width); - ASSERT(offset + window_width < wall_width * 0.5); - dxdydz[0] = -N[1] * offset; - dxdydz[1] = N[0] * offset; - /* Windows are in the upper part of the wall (not vertically centered) */ - dxdydz[2] = (wall_height - window_height) * 0.4 * (1 - sc); - ERR(str_printf(&name, "surface+_%lu_w%lu", - (long unsigned)i, (long unsigned)n)); - /* surface_ is used to check for neighbor compatibility with a slitghly - * bigger size than the actual window */ - ERR(scad_geometry_translate(scaled_, dxdydz, str_cget(&name), &surface_)); - - if(adjoining_n) { - /* Use the same distance used in early stages for close neighbor detection */ - hsz = CG2_CLOSE_NEIGHBOR_DISTANCE + data->wall_thickness + - data->internal_insulation_thickness + data->external_insulation_thickness; - d3_muld(dir, N, hsz); - ERR(str_printf(&name, "detect_adj_%lu_w%lu", + if(!removed_wall) { + double sw, sh; + sw = MMIN(1, 1.01 * best_wc_width / wall_width); + sh = MMIN(1, 1.01 * window_height / wall_height); + d3(scale, sw, sw, sh); + ERR(scad_geometry_dilate(surface, center, scale, "scaled_", &scaled_)); + sw = best_wc_width / wall_width; + sh = window_height / wall_height; + d3(scale, sw, sw, sh); + ERR(scad_geometry_dilate(surface, center, scale, "scaled", &scaled)); + + /* Try to create the planed windows */ + for(n = 0; n < (size_t)best_wc; n++) { + int removed_windows = 0; + double dxdydz[3], offset; + /* Compute windows positioning */ + offset = - 0.5 * (wall_width - best_wc_width) /* to the left border */ + + best_wc_spacing + (double)n * (best_wc_spacing + best_wc_width); + dxdydz[0] = -N[1] * offset; + dxdydz[1] = N[0] * offset; + /* Windows are in the upper part of the wall (not vertically centered) */ + dxdydz[2] = (wall_height - window_height) * 0.2; + ERR(str_printf(&name, "surface+_%lu_w%lu", (long unsigned)i, (long unsigned)n)); - ERR(scad_geometry_extrude(surface_, str_cget(&name), dir, &detect)); - - /* Check if detect intersects adjoining envelops */ - /* Push only if don't intersect */ - adj_list = MEM_REALLOC(allocator, adj_list, - adjoining_n * sizeof(struct scad_geometry*)); - for(j = 0; j < adjoining_n; j++) adj_list[j] = adj[j].envelop; - - ERR(str_printf(&name, "adj_intersect_%lu", (long unsigned)i)); - ERR(scad_intersect_geometries(str_cget(&name), &detect, 1, adj_list, - adjoining_n, &hole_adjoining_intersect)); - ERR(scad_geometry_get_count(hole_adjoining_intersect, &count)); - if(count) { - removed = 1; - removed_windows_adj++; + /* surface_ is used to check for neighbor compatibility with a slitghly + * bigger size than the actual window */ + ERR(scad_geometry_translate(scaled_, dxdydz, str_cget(&name), &surface_)); + + if(adjoining_n) { + /* Use the same distance used in early stages for close neighbor detection */ + hsz = CG2_CLOSE_NEIGHBOR_DISTANCE + data->wall_thickness + + data->internal_insulation_thickness + data->external_insulation_thickness; + d3_muld(dir, N, hsz); + ERR(str_printf(&name, "detect_adj_%lu_w%lu", + (long unsigned)i, (long unsigned)n)); + ERR(scad_geometry_extrude(surface_, str_cget(&name), dir, &detect)); + + /* Check if detect intersects adjoining envelops */ + /* Push only if don't intersect */ + adj_list = MEM_REALLOC(allocator, adj_list, + adjoining_n * sizeof(struct scad_geometry*)); + for(j = 0; j < adjoining_n; j++) adj_list[j] = adj[j].envelop; + + ERR(str_printf(&name, "adj_intersect_%lu", (long unsigned)i)); + ERR(scad_intersect_geometries(str_cget(&name), &detect, 1, adj_list, + adjoining_n, &hole_adjoining_intersect)); + ERR(scad_geometry_get_count(hole_adjoining_intersect, &count)); + if(count) { + removed_windows = 1; + removed_windows_adj++; + } + ERR(scad_geometry_ref_put(hole_adjoining_intersect)); + hole_adjoining_intersect = NULL; + ERR(scad_geometry_ref_put(detect)); + detect = NULL; } - ERR(scad_geometry_ref_put(hole_adjoining_intersect)); - hole_adjoining_intersect = NULL; - ERR(scad_geometry_ref_put(detect)); - detect = NULL; - } - /* 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 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 to the offseting algorithm). */ - if(!removed) { - /* Use smaller distance than the one used for neighbor detection */ - hsz = 0.1 + data->wall_thickness + data->internal_insulation_thickness - + data->external_insulation_thickness; - d3_muld(dir, N, hsz); - ERR(str_printf(&name, "detect_self_%lu", (long unsigned)i)); - ERR(scad_geometry_extrude(surface_, str_cget(&name), dir, &detect)); - /* Compute intersection between detect and envelop: the number of - * components is expected to be 1, or the window is better removed */ - ERR(str_printf(&name, "self_intersect_%lu", (long unsigned)i)); - ERR(scad_intersect_geometries(str_cget(&name), &benv, 1, &detect, 1, - &problem)); - ERR(scad_geometry_get_count(problem, &count)); - if(count != 1) { - removed = 1; - removed_windows_self++; + /* 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 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 to the offseting algorithm). */ + if(!removed_windows) { + /* Use smaller distance than the one used for neighbor detection */ + hsz = 0.1 + data->wall_thickness + data->internal_insulation_thickness + + data->external_insulation_thickness; + d3_muld(dir, N, hsz); + ERR(str_printf(&name, "detect_self_%lu", (long unsigned)i)); + ERR(scad_geometry_extrude(surface_, str_cget(&name), dir, &detect)); + /* Compute intersection between detect and envelop: the number of + * components is expected to be 1, or the window is better removed */ + ERR(str_printf(&name, "self_intersect_%lu", (long unsigned)i)); + ERR(scad_intersect_geometries(str_cget(&name), &benv, 1, &detect, 1, + &problem)); + ERR(scad_geometry_get_count(problem, &count)); + if(count != 1) { + removed_windows = 1; + removed_windows_self++; + } + ERR(scad_geometry_ref_put(detect)); + detect = NULL; + ERR(scad_geometry_ref_put(problem)); + problem = NULL; + ERR(scad_geometry_ref_put(surface_)); + surface_ = NULL; } - ERR(scad_geometry_ref_put(detect)); - detect = NULL; - ERR(scad_geometry_ref_put(problem)); - problem = NULL; - ERR(scad_geometry_ref_put(surface_)); - surface_ = NULL; - } - if(!removed) { - ERR(scad_geometry_translate(scaled, dxdydz, "win_surface", &win_surface)); - ERR(str_printf(&name, "hole_%lu_w%lu", (long unsigned)i, (long unsigned)n)); - ERR(scad_geometry_extrude(win_surface, str_cget(&name), dir, &hole)); - ERR(darray_geometries_push_back(&hole_array, &hole)); - d3_muld(dir, N, 0.024); - ERR(str_printf(&name, "glass_%lu_w%lu", (long unsigned)i, (long unsigned)n)); - ERR(scad_geometry_extrude(win_surface, str_cget(&name), dir, &glass)); - ERR(darray_geometries_push_back(&glass_array, &glass)); - ERR(darray_geometries_push_back(current_cad, &glass)); - ERR(scad_geometry_ref_put(hole)); - hole = NULL; - ERR(scad_geometry_ref_put(glass)); - glass = NULL; - ERR(scad_geometry_ref_put(win_surface)); - win_surface = NULL; + if(!removed_windows) { + ERR(scad_geometry_translate(scaled, dxdydz, "win_surface", &win_surface)); + ERR(str_printf(&name, "hole_%lu_w%lu", (long unsigned)i, (long unsigned)n)); + ERR(scad_geometry_extrude(win_surface, str_cget(&name), dir, &hole)); + ERR(darray_geometries_push_back(&hole_array, &hole)); + d3_muld(dir, N, 0.024); + ERR(str_printf(&name, "glass_%lu_w%lu", (long unsigned)i, (long unsigned)n)); + ERR(scad_geometry_extrude(win_surface, str_cget(&name), dir, &glass)); + ERR(darray_geometries_push_back(&glass_array, &glass)); + ERR(darray_geometries_push_back(current_cad, &glass)); + ERR(scad_geometry_ref_put(hole)); + hole = NULL; + ERR(scad_geometry_ref_put(glass)); + glass = NULL; + ERR(scad_geometry_ref_put(win_surface)); + win_surface = NULL; + total_windows_surface += best_wc_width * window_height; + } } } - ERR(scad_geometry_ref_put(scaled)); - scaled = NULL; - ERR(scad_geometry_ref_put(scaled_)); - scaled_ = NULL; + if(scaled) { + ERR(scad_geometry_ref_put(scaled)); + scaled = NULL; + } + if(scaled_) { + ERR(scad_geometry_ref_put(scaled_)); + scaled_ = NULL; + } } if(hole) { ERR(scad_geometry_ref_put(hole)); @@ -1046,31 +1081,43 @@ build_windows surface_ = NULL; } } - removed_count = removed_windows_sz + removed_windows_adj + removed_windows_self; array_n = darray_geometries_size_get(&hole_array); ASSERT(array_n == darray_geometries_size_get(&glass_array)); prefix = str_cget(&data_cad->building->name); + if(removed_wall_sz != 0) { + logger_print(city->logger, LOG_WARNING, + "Building '%s' has %zu walls with no windows due to wall size.\n", + prefix, removed_wall_sz); + } + if(missed_windows_ratio != 0) { + logger_print(city->logger, LOG_WARNING, + "Building '%s' has %zu walls with windows, but missing glass ratio target.\n", + prefix, missed_windows_ratio); + } + removed_count = removed_windows_adj + removed_windows_self; if(removed_count != 0) { logger_print(city->logger, LOG_WARNING, - "Building '%s' has %zu/%zu windows removed:\n", + "Building '%s' has %zu/%zu windows removed on walls with windows:\n", prefix, removed_count, removed_count + array_n); - if(removed_windows_sz != 0) { - logger_print(city->logger, LOG_WARNING, - "- %zu windows removed due to too small size.\n", removed_windows_sz); - } if(removed_windows_adj != 0) { logger_print(city->logger, LOG_WARNING, - "- %zu windows removed due to close neighbors.\n", removed_windows_adj); + "- %zu windows removed due to close neighbors.\n", removed_windows_adj); } if(removed_windows_self != 0) { logger_print(city->logger, LOG_WARNING, - "- %zu windows removed due to self conflicts.\n", removed_windows_self); + "- %zu windows removed due to self conflicts.\n", removed_windows_self); } } else { logger_print(city->logger, LOG_OUTPUT, - "Building '%s' has no window removed (out of %zu).\n", prefix, array_n); - } + "Building '%s' has no window removed (out of %zu).\n", prefix, array_n); + } + effective_ratio = total_windows_surface / total_wall_surface; + meet_effective_ratio = data->glass_ratio == 0 + || fabs(effective_ratio - data->glass_ratio)/ data->glass_ratio < 0.01; + logger_print(city->logger, (meet_effective_ratio ? LOG_OUTPUT : LOG_WARNING), + "Building '%s' overall glass ratio is %.1f %% (expected is %.1f %%).\n", + prefix, 100 * effective_ratio, 100 * data->glass_ratio); if(array_n > 0) { hole_list = darray_geometries_data_get(&hole_array);